I am trying to create a completion where one of my completions will be dynamically generated depending on the values of the other flags. For example
local state
_arguments \
'-f[fabric]:fabric:->fabrics' \
'-c[containers/application where log resides]:container:->containers' \
'-l[name of log file]:log:->logs'
case "$state" in
(fabrics)
_values 'fabrics' \
'fab1' \
'fab2'
;;
(containers)
_values 'containers' \
'container1' \
'container2'
;;
(logs)
# show A B C if "-c container1" was entered
# show D E F if "-c container2" was entered
# show G if "-c" was not provided yet
esac
I am having trouble getting the "-l" flag to be dynamically generated.
We could inspect the $words:
Completion Special Parameters
...
Inside completion widgets, and any functions called from them, some parameters have special meaning;
...
words
This array contains the words present on the command line currently being edited.
-- zshcompwid(1): Completion Special Parameters, Completion Widgets
We could do the stuff like this:
(logs)
local -i index=${words[(I)-c]}
local -i ret=0
if ((index == 0)); then
_values 'logs' F
ret=$?
elif [[ "$words[index+1]" == container1 ]]; then
_values 'logs' A B C
ret=$?
elif [[ "$words[index+1]" == container2 ]]; then
_values 'logs' D E F
ret=$?
fi
return ret
To inspect arrays, it is usefull to use the array Subscript Flags:
Subscript Flags
If the opening bracket, or the comma in a range, in any subscript expression is directly followed by an opening parenthesis, the string up to the matching closing one is considered to be a list of flags, as in name[(flags)exp].
-- zshparam(1), Subscript Flags, Array Subscripts, Array Parameters
So,$words[(I)-c] means I "flag" + -c as "exp" for $words which is "Last matching element's index for "-c" in the array $word". For example:
$ tmp=(my-test-command -f flag -c container1 -l)
$ echo $tmp[(I)-c]
4
$ echo $tmp[(I)container1]
5
$ tmp=(my-test-command -f flag -c container1 -c container2 -l)
$ echo $tmp[(I)-c]
6
Related
I am using Zsh in Vi-mode.
When $KEYMAP == vicmd (i.e. command-mode), I want hitting backspace to move the cursor to the left by one character, without deleting anything. [working]
When $KEYMAP == viins && $ZLE_STATE == *insert* (i.e. insert-mode), I want hitting backspace to move the cursor to the left by one character, deleting the immediately preceding character on the line. [working]
When $KEYMAP == viins && $ZLE_STATE == *overwrite* (i.e. overwrite-mode / replace-mode), I want hitting backspace to move the cursor to the left by one character, restoring the immediately preceding character on the line with the one that had originally been there prior to entering into overwrite-mode. [NOT working]
Here is an example:
# [COMMAND MODE] We start with the following string on the command line:
$ Hello, world!
^
cursor position
# [REPLACE MODE] Now, I hit "R" to enter replace-mode and I type "stuff".
$ Helstufforld!
^
cursor position
# [REPLACE MODE] Finally, I hit backspace 3 times.
$ Helst, world!
^
cursor position
The above example shows what I want to happen when I hit backspace while in overwrite-mode; however, what really happens is the following:
# [COMMAND MODE] We start with the following string on the command line:
$ Hello, world!
^
cursor position
# [REPLACE MODE] Now, I hit "R" to enter replace-mode and I type "stuff".
$ Helstufforld!
^
cursor position
# [REPLACE MODE] Finally, I hit backspace 3 times.
$ Helstworld!
^
cursor position
Notice how, when hitting backspace in the second example, rather than restoring the original 3 characters that were just overwritten (i.e. ", w"), instead the last 3 characters that replaced these characters (i.e. "uff") were deleted, and the characters to the right of the cursor were shifted to the left.
How do I get the behavior that I want?
Okay, so I ended up hacking together a solution to the problem I was having, which I will post here in case anyone else encounters the same issue.
Solution
Put this in your .zshrc:
readonly ZLE_VI_MODE_CMD=0
readonly ZLE_VI_MODE_INS=1
readonly ZLE_VI_MODE_REP=2
readonly ZLE_VI_MODE_OTH=3
function zle-vi-mode {
if [[ $KEYMAP == vicmd ]]; then
echo -n $ZLE_VI_MODE_CMD
elif [[ $KEYMAP == (viins|main) ]] && [[ $ZLE_STATE == *insert* ]]; then
echo -n $ZLE_VI_MODE_INS
elif [[ $KEYMAP == (viins|main) ]] && [[ $ZLE_STATE == *overwrite* ]]; then
echo -n $ZLE_VI_MODE_REP
else
echo -n $ZLE_VI_MODE_OTH
fi
}
function zle-backward-delete-char-fix {
case "$(zle-vi-mode)" in
$ZLE_VI_MODE_REP)
if [[ $CURSOR -le $MARK ]]; then
CURSOR=$(( $(($CURSOR-1)) > 0 ? $(($CURSOR-1)) : 0 ))
MARK=$CURSOR
else
zle undo
fi
;;
*)
zle backward-delete-char
;;
esac
}
zle -N zle-backward-delete-char-fix
## Change cursor shape according to the current Vi-mode.
function zle-line-init zle-keymap-select {
case "$(zle-vi-mode)" in
$ZLE_VI_MODE_CMD) echo -ne '\e[2 q' ;; # cursor -> block
$ZLE_VI_MODE_INS) echo -ne '\e[6 q' ;; # cursor -> vertical bar
$ZLE_VI_MODE_REP)
echo -ne '\e[4 q' # cursor -> underline
MARK=$CURSOR
;;
*)
;;
esac
}
zle -N zle-line-init
zle -N zle-keymap-select
bindkey -v
bindkey '^?' zle-backward-delete-char-fix
bindkey '^h' zle-backward-delete-char-fix
The above code will, additionally, cause your cursor shape to change depending on what vi-mode you are currently in (i.e. since this is a copy/paste from my .zshrc, and that's what I like). If you don't want this, and just want the plain fix, replace the zle-init-line / zle-keymap-select function with the following:
function zle-line-init zle-keymap-select {
case "$(zle-vi-mode)" in
$ZLE_VI_MODE_REP)
MARK=$CURSOR
;;
*)
;;
esac
}
I've build a command line tool and I need to complete arguments with zsh. I never wrote a zsh completion function so I looked in the scripts provided with zsh but I missed something so that it could work properly.
So, mytool can take a variable number of values and two options.
Here are some call examples:
mytool ONE
mytool ONE TWO
mytool AAA BBB CCC DDD EEE --info
In order to complete the values, I hava another executable that outputs all possible lines to stdout, like this simplified script named getdata:
#!/usr/local/bin/zsh
echo ONE
echo TWO ONE
echo TWO TWO
# ... a lot of lines
echo OTHER ONE
echo ONE ANOTHER LINE
echo AAA BBB CCC DDD EEE
Each completion must match to a whole line, so in my getdata example, it will not be possible to just complete with the value TWO because this whole line does not exist, it must be TWO ONE or TWO TWO.
As this script is quite time consuming, I would like to use zsh caching feature. So, here is my zsh complete script:
compdef _complete_mytool mytool
__mytool_caching_policy() {
oldp=( "$1"(Nmh+1) ) # 1 hour
(( $#oldp ))
}
__mytool_deployments() {
local cache_policy
zstyle -s ":completion:${curcontext}:" cache-policy cache_policy
if [[ -z "$cache_policy" ]]; then
zstyle ":completion:${curcontext}:" cache-policy __mytool_caching_policy
fi
if ( [[ ${+_mytool_values} -eq 0 ]] || _cache_invalid mytool_deployments ) \
&& ! _retrieve_cache mytool_deployments;
then
local -a lines
_mytool_values=(${(f)"$(_call_program values getdata)"})
_store_cache mytool_deployments _mytool_values
fi
_describe "mytool values" _mytool_values
}
_complete_mytool() {
integer ret=1
local -a context expl line state state_descr args
typeset -A opt_args
args+=(
'*:values:->values'
'--help[show this help message and exit]'
'(-i --info)'{-i,--info}'[display info about values and exit]'
'(-v --version)'{-v,--version}'[display version about values and exit]'
)
_call_function res __mytool_deployments
return ret
}
But when I try to complete, spaces are escaped with backslash, and I don't want this behaviour.
mytool OTHER\ ONE
The options seem not to be completed too... So, any help will be greatly appreciated.
Thanks to okdana on the freenode zsh channel who helped me a lot.
So, the solution is:
compdef _complete_mytool mytool
__mytool_caching_policy() {
oldp=( "$1"(Nmh+1) ) # 1 hour
(( $#oldp ))
}
__mytool_deployments() {
local cache_policy
zstyle -s ":completion:${curcontext}:" cache-policy cache_policy
if [[ -z "$cache_policy" ]]; then
zstyle ":completion:${curcontext}:" cache-policy __mytool_caching_policy
fi
if ( [[ ${+_mytool_values} -eq 0 ]] || _cache_invalid mytool_deployments ) \
&& ! _retrieve_cache mytool_deployments;
then
local -a lines
_mytool_values=(${(f)"$(_call_program values getdata)"})
_store_cache mytool_deployments _mytool_values
fi
_describe "mytool values" _mytool_values -Q
}
_complete_mytool() {
_arguments : \
': :__mytool_deployments' \
'--help[show this help message and exit]' \
'(-i --info)'{-i,--info}'[display info about values and exit]' \
'(-v --version)'{-v,--version}'[display version about values and exit]'
}
I have a list of header files created thus:
expand=$(1)/$(1).h
HDRS=$(foreach x, $(DIRS), $(call expand,$(x)))
Which yields a list like a/a.h b/b.h ...
but when I use this in a for loop:
for i in $(HDRS) ; do \
echo $$i \
cp $$i $(some_dir) \
done
$$i is empty. And the cp fails, having only one argument.
The usual variants of $$i ( $i, $$i, $(i), ${i} ), don't change anything, nor do the usual variants of $(HDRS) ("$(HDRS)", etc.).
gmake echos the for-loop as
for i in a.h b.h ; \
do \
echo $i \
cp $i somedir \
done
Which looks correct.
But the implicit bash shell emits an error "/bin/sh -c: line 5: syntax error: unexpected end of file"
gmake then exits due to the failed command.
Due to the \, make emits the recipe as a single line. This confuses the shell. Try this instead, using ; in place of the line terminator:
for i in a.h b.h ; \
do \
echo $i ; \
cp $i somedir ; \
done
I'm trying to add a completion to a custom vs function which basically open the first filename matching the argument.
OPTIONAL (If you want more information about this function you can find my medium post here => https://medium.com/#victor.boissiere/how-to-quickly-open-files-with-your-editor-1a51b3fe21bf)
In my current folder I have the following:
./example.sh
./custom/directory.sh
./custom/example.sh
Behavior
vs direct<TAB> => completes to custom/directory.sh SUCCESS
vs example<TAB> => vs
Why does it removes the argument and does not let me choose between example.sh and custom/example.sh ?
Code
_vs() {
local curcontext="$curcontext" state line expl
_arguments -C \
'*:: :->open_files'
case "$state" in
open_files)
local file=${words[CURRENT]}
compadd -U - `find . -type f -ipath "*$file*" | sed "s|^\./||"`
;;
esac
return 0
}
compdef _vs vs
I just found the solution thanks to this post => https://superuser.com/questions/1264778/changing-to-a-directory-found-somewhere-in-the-tree-hierarchy/1278919#1278919
I just needed to add compstate[insert]=menu # no expand
Solution
_vs() {
local curcontext="$curcontext" state line expl
_arguments -C \
'*:: :->open_files'
case "$state" in
open_files)
local file=${words[CURRENT]}
compadd -U - `find . -type f -ipath "*$file*" | sed "s|^\./||"`
compstate[insert]=menu # add this
;;
esac
return 0
}
compdef _vs vs
I have a script that takes file like arguments (multi part arguments), I am fetching the possible values and putting them in an array called raw and then using
_multi_parts / "(${raw[#]})"
to autocomplete. The problem is that this is case sensitive, how can I make it so that it I type mycommand get fo and press Tab it will autocomplete to mycommand get Foo/ if Foo is one of the things in raw.
The full completion is here for reference:
_mycommand() {
local curcontext="$curcontext" state line
_arguments "1: :->command" "*: :->label"
case $state in
command)
_arguments "1: :(add get ls rm)"
;;
*)
case $words[2] in
add|get|ls|rm)
if [ -f ~/.pts ]; then
IFS=$'\n' read -d '' -r raw <<< "$(mycommand ls)"
_multi_parts / "(${raw[#]})"
fi
;;
*)
_files
;;
esac
esac
}
_mycommand "$#"
mycommand ls outputs path like names like the following:
Foo/Bar/x
Foo/Bar/y
Derp/z
Placeholder/z
Okay I figured it out:
Change lines
IFS=$'\n' read -d '' -r raw <<< "$(mycommand ls)"
_multi_parts / "(${raw[#]})"
To
IFS=$'\n' raw=($(mycommand ls))
_multi_parts -M "m:{[:lower:][:upper:]}={[:upper:][:lower:]}" / raw