Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix npm install <tab> completion #16

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,41 @@ Then source it in your `.zshrc`
source ~/.zsh-better-npm-completion/zsh-better-npm-completion.plugin.zsh
```

## Module Install Cache

When running `npm install rea<tab>`, it will perform an `npm search` for matching packages. In addition
it will look in your local npm cache directory for package suggestions. However building this list
can be pretty slow. This completion makes use of the zsh completion caching mechanism to cache the
module list if you have caching enabled.

We try to enable it by default, however if you have something
like below in your zshrc forces cache for all completions to be turned off.

```zsh
zstyle ':completion:*' use-cache off
```

To specifically turn npm cache back on, you may add the following:

```zsh
zstyle ':completion::complete:npm::' use-cache on
```

By default the cache will be valid for 1 hour. But can be modified by setting a cache policy like this:

```zsh
_npm_install_cache_policy() {
# rebuild if cache is more than 24 hours old
local -a oldp
# See http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers
# Nmh+24 ... N == NULL_GLOB, m == modified time, h == hour, +24 == +24 units (i.e. [M]onth, [w]weeks, [h]ours, [m]inutes, [s]econds)
oldp=( "$1"(Nmh+24) )
(( $#oldp ))
}
zstyle ':completion::complete:npm::' cache-policy _npm_install_cache_policy
```


## Related

- [`zsh-nvm`](https://github.com/lukechilds/zsh-nvm) - Zsh plugin for installing, updating and loading `nvm`
Expand Down
118 changes: 107 additions & 11 deletions zsh-better-npm-completion.plugin.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,84 @@ _zbnc_no_of_npm_args() {
}

_zbnc_list_cached_modules() {
ls ~/.npm 2>/dev/null
local term="${words[$CURRENT]}"

case $term in
'.'* | '/'* | '~'* | '-'* | '_'*)
return
esac

# enable cache if the user hasn't explicitly set it
local use_cache
zstyle -s ":completion:${curcontext}:" use-cache use_cache
if [[ -z "$use_cache" ]]; then
zstyle ":completion:${curcontext}:" use-cache on
fi

# set default cache policy if the user hasn't set it
local update_policy
zstyle -s ":completion:${curcontext}:" cache-policy update_policy
if [[ -z "$update_policy" ]]; then
zstyle ":completion:${curcontext}:" cache-policy _zbnc_list_cached_modules_policy
fi

local hash=$(echo "$term" | md5)
cache_name="zbnc_cached_modules_$hash"

if _cache_invalid $cache_name || ! _retrieve_cache $cache_name; then
if [[ -z "$term" ]]; then
_modules=$(_zbnc_list_cached_modules_no_cache)
else
_modules=$(_zbnc_list_search_modules)
fi

if [ $? -eq 0 ]; then
_store_cache $cache_name _modules
else
# some error occurred, the user is probably not logged in
# set _modules to an empty string so that no completion is attempted
_modules=""
fi
else
_retrieve_cache $cache_name
fi
echo $_modules
}

_zbnc_list_cached_modules_policy() {
# rebuild if cache is more than an hour old
local -a oldp
# See http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers
oldp=( "$1"(Nmh+1) )
(( $#oldp ))
}

_zbnc_list_search_modules() {
local term="${words[$CURRENT]}"
[[ ! -z "$term" ]] && NPM_CONFIG_SEARCHLIMIT=1000 npm search --no-description --parseable "$term" 2>/dev/null | awk '{print $1}'
_zbnc_list_cached_modules_no_cache
}

_zbnc_list_cached_modules_no_cache() {
local cache_dir="$(npm config get cache)/_cacache"
export NODE_PATH="${NODE_PATH}:$(npm prefix -g)/lib/node_modules:$(npm prefix -g)/lib/node_modules/npm/node_modules"
node --eval="require('cacache');" &>/dev/null || npm install -g cacache &>/dev/null
if [ -d "${cache_dir}" ]; then
node <<CACHE_LS 2>/dev/null
const cacache = require('cacache');
cacache.ls('${cache_dir}').then(cache => {
const packages = Object.values(cache).forEach(entry => {
const id = ((entry || {}).metadata || {}).id;
if (id) {
console.log(id.substr(0, id.lastIndexOf('@')));
}
});
});
CACHE_LS
else
# Fallback to older cache location ... i think node < 10
ls --color=never ~/.npm 2>/dev/null
fi
}

_zbnc_recursively_look_for() {
Expand Down Expand Up @@ -56,13 +133,15 @@ _zbnc_parse_package_json_for_deps() {
_zbnc_npm_install_completion() {

# Only run on `npm install ?`
[[ ! "$(_zbnc_no_of_npm_args)" = "3" ]] && return
[[ $(_zbnc_no_of_npm_args) -lt 3 ]] && return

# Return if we don't have any cached modules
[[ "$(_zbnc_list_cached_modules)" = "" ]] && return
local modules=($(_zbnc_list_cached_modules))

# Add modules if we found some
[[ ${#modules[@]} -gt 0 ]] && _values $modules

# If we do, recommend them
_values $(_zbnc_list_cached_modules)
# Include local files
_files

# Make sure we don't run default completion
custom_completion=true
Expand Down Expand Up @@ -119,17 +198,34 @@ _zbnc_default_npm_completion() {
}

_zbnc_zsh_better_npm_completion() {

# Store custom completion status
local custom_completion=false

# Load custom completion commands
case "$(_zbnc_npm_command)" in
i|install)
_zbnc_npm_install_completion
i|install|add)
_arguments -n -C \
'(-g --global)'{-g,--global}'[Package will be installed as a global package.]' \
'(-P --save-prod -D --save-dev -O --save-optional --no-save)'{-P,--save-prod}'[Package will appear in your dependencies. This is the default unless -D or -O are present.]' \
'(-D --save-dev -P --save-prod -O --save-optional --no-save)'{-D,--save-dev}'[Package will appear in your devDependencies.]' \
'(-O --save-optional -P --save-prod -D --save-dev --no-save)'{-O,--save-optional}'[Package will appear in your optionalDependencies.]' \
'(--no-save -P --save-prod -D --save-dev -O --save-optional -E --save-exact -B --save-bundle)--no-save[Prevents saving to dependencies.]' \
'(-E --save-exact --no-save)'{-E,--save-exact}'[Saved dependencies will be configured with an exact version rather than using npm’s default semver range operator.]' \
'(-B --save-bundle --no-save)'{-B,--save-bundle}'[Saved dependencies will also be added to your bundleDependencies list.]' \
'(- *)--help[show help message.]' \
"(- *)--version[show program's version number and exit.]" \
'*:args:_zbnc_npm_install_completion' && return 0
;;
r|uninstall)
_zbnc_npm_uninstall_completion
r|uninstall|remove|rm|un|unlink)
_arguments -n -C \
'(-g --global)'{-g,--global}'[Package will be removed from global packages.]' \
'(-P --save-prod -D --save-dev -O --save-optional --no-save)'{-P,--save-prod}'[Package will be removed from your dependencies.]' \
'(-D --save-dev -P --save-prod -O --save-optional --no-save)'{-D,--save-dev}'[Package will be removed from your devDependencies.]' \
'(-O --save-optional -P --save-prod -D --save-dev --no-save)'{-O,--save-optional}'[Package will be removed from your optionalDependencies.]' \
'(--no-save -P --save-prod -D --save-dev -O --save-optional)--no-save[Package will not be removed from your package.json file.]' \
'(- *)--help[show help message.]' \
"(- *)--version[show program's version number and exit.]" \
'*:args:_zbnc_npm_uninstall_completion' && return 0
;;
run)
_zbnc_npm_run_completion
Expand Down