Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Post History
I figured out a solution myself. I'm not a zsh expert so this may be improved, but it works. 1) make a custom completion file for the diff command The completion file that zsh uses for the diff ...
#4: Post edited
- I figured out a solution myself.
- I'm not a zsh expert so this may be improved, but it works.
- ### 1) make a custom completion file for the diff command
- The completion file that zsh uses for the `diff` command is called `_diff`.
- On my computer, this file is located at `/usr/share/zsh/functions/Completion/Unix/_diff` and is shown below:
- ```sh
- #compdef diff gdiff
- _diff_options "$words[1]" ':original file:_files' ':new file:_files'
- ```
- We will create a new `_diff` file to override the default file above in order to perform the custom completion.
- We save this file as `/home/trevor/zsh_completions/_diff` whose contents are shown below:
- ```sh
- #compdef diff
- function _files_common() {
- last_buffer_item=$words[-1]
- matches=$(sed -n 's/^\(.*\){\(.*\),\(.*\)}\(.*\)$/\1 \2 \3 \4/p' <<< $last_buffer_item)
- if [[ -n $matches ]]; then
- # parse sed output
- before_braces_dir=$(cut -d ' ' -f 1 <<< $matches)
- folder_1=$(cut -d ' ' -f 2 <<< $matches)
- folder_2=$(cut -d ' ' -f 3 <<< $matches)
- after_braces=$(cut -d ' ' -f 4 <<< $matches)
- # get any directories after the braces
- after_braces_dir=$(sed -n 's/^\/\(.*\/\).*$/\1/p' <<< $after_braces)
- # full path of each of the two directories
- path_1=$before_braces_dir$folder_1/$after_braces_dir
- path_2=$before_braces_dir$folder_2/$after_braces_dir
- # arrays for compadd
- local -a _descriptions_files _values_files _descriptions_dirs _values_dirs
- # iterate through names in path 1, and see if they're in path 2
- # note: ls -F adds a slash at the end of folder names
- names=( $( ls -F ${path_1//\~/$HOME} ) )
- for name in "${names[@]}"; do
- test_path=$path_2$name
- commandline_arg=$before_braces_dir{$folder_1,$folder_2}/$after_braces_dir$name
- # if path is a file
- if [[ -f ${test_path//\~/$HOME} ]]; then
- _values_files+=( $commandline_arg )
- _descriptions_files+=( $name )
- # if path is a directory
- elif [[ -d ${test_path//\~/$HOME} ]]; then
- _values_dirs+=( $commandline_arg )
- _descriptions_dirs+=( $name )
- fi
- done
- # add completions
- # for directory completions, use an empty string as the suffix to prevent a
- # space being added at the end of the completion
- compadd -Q -d _descriptions_files -a _values_files
- compadd -S '' -Q -d _descriptions_dirs -a _values_dirs
- fi
- }
- # if the argument contains left and right braces, use the _files_common function
- # otherwise, use the _files function
- # note: the format of these commands is based on
- # /usr/share/zsh/functions/Completion/Unix/_diff
- last_buffer_item=$words[-1]
- if [[ $last_buffer_item == *{*,*}* ]]; then
- _diff_options "$words[1]" ':both files:_files_common'
- else
- _diff_options "$words[1]" ':original file:_files' ':new file:_files'
- fi
- ```
- ### 2) add the custom completion file to zsh's path
- To make zsh use the custom completion file from the previous step, we add the directory of the custom completion file to the `fpath` variable by adding this to your `.zshrc`:
- ```sh
- fpath=(/home/trevor/zsh_completions/ $fpath)
- ```
- Note that we must prepend to `fpath` rather than append in order to give the custom file precedence.
- Also note that this line should be added before the `compinit` command.
- ### 3) configure zshrc to run complete-word instead of expand-or-complete for the diff command
- Next, find the function that is mapped to <Tab> key in zsh.
- To do so, run `$ bindkey` and then find the `"^I"` entry, which corresponds to <Tab>.
- Doing this on my system shows that <Tab> is set to `expand-or-complete`.
If we were to use `expand-or-complete` with diff and my custom completion function, then zsh will expand the commandline argument with braces into two separate arguments.- Instead, we need to use `complete-word` instead of `expand-or-complete` when the `diff` function is being used.
- To do so, add this to your `.zshrc`:
- ```sh
- custom_tab () {
- words=(${(z)BUFFER})
- if [[ ${words[1]} == diff ]]; then
- zle complete-word
- else
- zle expand-or-complete
- fi
- }
- zle -N custom_tab
- bindkey '^I' custom_tab
- ```
- ---
- ### notes
- Here is a reiteration of a couple of key concepts that have been used to make this work:
- 1) The `-Q` flag must be used with the `compadd` function to not quote metacharacters.
- Without this flag, zsh will use `\{` in place of `{`, and `\}` in place of `}` in the commandline argument.
- 2) The `-S ''` flag is used with the `compadd` function to prevent a space from being added after completion of directories.
- 3) If the `~` character is present in the commandline argument, it will not be expanded and the solution will not work.
- So, we replace the `~` character with `$HOME` in the appropriate places, which is done via `${path_1//\~/$HOME}` and `${test_path//\~/$HOME}`.
- ---
- There are some edge cases in which this solution will not work, but can be fixed with some modifications:
- 1) The completion options will not include hidden files or folders, but they can be added by adding the `-a` option to the `ls` command.
- ---
For further modification of this code, the following notes may be helpful.- 1) It may be helpful to replace any `\{` characters with `{`, and any `\}` characters with `}` in the `PREFIX` parameter (i.e., `PREFIX=${PREFIX//\\\{/\{}` and `PREFIX=${PREFIX//\\\}/\}}`).
- 2) It may also be helpful to use the `ignore_braces` option.
- I figured out a solution myself.
- I'm not a zsh expert so this may be improved, but it works.
- ### 1) make a custom completion file for the diff command
- The completion file that zsh uses for the `diff` command is called `_diff`.
- On my computer, this file is located at `/usr/share/zsh/functions/Completion/Unix/_diff` and is shown below:
- ```sh
- #compdef diff gdiff
- _diff_options "$words[1]" ':original file:_files' ':new file:_files'
- ```
- We will create a new `_diff` file to override the default file above in order to perform the custom completion.
- We save this file as `/home/trevor/zsh_completions/_diff` whose contents are shown below:
- ```sh
- #compdef diff
- function _files_common() {
- last_buffer_item=$words[-1]
- matches=$(sed -n 's/^\(.*\){\(.*\),\(.*\)}\(.*\)$/\1 \2 \3 \4/p' <<< $last_buffer_item)
- if [[ -n $matches ]]; then
- # parse sed output
- before_braces_dir=$(cut -d ' ' -f 1 <<< $matches)
- folder_1=$(cut -d ' ' -f 2 <<< $matches)
- folder_2=$(cut -d ' ' -f 3 <<< $matches)
- after_braces=$(cut -d ' ' -f 4 <<< $matches)
- # get any directories after the braces
- after_braces_dir=$(sed -n 's/^\/\(.*\/\).*$/\1/p' <<< $after_braces)
- # full path of each of the two directories
- path_1=$before_braces_dir$folder_1/$after_braces_dir
- path_2=$before_braces_dir$folder_2/$after_braces_dir
- # arrays for compadd
- local -a _descriptions_files _values_files _descriptions_dirs _values_dirs
- # iterate through names in path 1, and see if they're in path 2
- # note: ls -F adds a slash at the end of folder names
- names=( $( ls -F ${path_1//\~/$HOME} ) )
- for name in "${names[@]}"; do
- test_path=$path_2$name
- commandline_arg=$before_braces_dir{$folder_1,$folder_2}/$after_braces_dir$name
- # if path is a file
- if [[ -f ${test_path//\~/$HOME} ]]; then
- _values_files+=( $commandline_arg )
- _descriptions_files+=( $name )
- # if path is a directory
- elif [[ -d ${test_path//\~/$HOME} ]]; then
- _values_dirs+=( $commandline_arg )
- _descriptions_dirs+=( $name )
- fi
- done
- # add completions
- # for directory completions, use an empty string as the suffix to prevent a
- # space being added at the end of the completion
- compadd -Q -d _descriptions_files -a _values_files
- compadd -S '' -Q -d _descriptions_dirs -a _values_dirs
- fi
- }
- # if the argument contains left and right braces, use the _files_common function
- # otherwise, use the _files function
- # note: the format of these commands is based on
- # /usr/share/zsh/functions/Completion/Unix/_diff
- last_buffer_item=$words[-1]
- if [[ $last_buffer_item == *{*,*}* ]]; then
- _diff_options "$words[1]" ':both files:_files_common'
- else
- _diff_options "$words[1]" ':original file:_files' ':new file:_files'
- fi
- ```
- ### 2) add the custom completion file to zsh's path
- To make zsh use the custom completion file from the previous step, we add the directory of the custom completion file to the `fpath` variable by adding this to your `.zshrc`:
- ```sh
- fpath=(/home/trevor/zsh_completions/ $fpath)
- ```
- Note that we must prepend to `fpath` rather than append in order to give the custom file precedence.
- Also note that this line should be added before the `compinit` command.
- ### 3) configure zshrc to run complete-word instead of expand-or-complete for the diff command
- Next, find the function that is mapped to <Tab> key in zsh.
- To do so, run `$ bindkey` and then find the `"^I"` entry, which corresponds to <Tab>.
- Doing this on my system shows that <Tab> is set to `expand-or-complete`.
- If we were to use `expand-or-complete` with the `diff` command and my custom completion function, then zsh will expand the commandline argument with braces into two separate arguments.
- Instead, we need to use `complete-word` instead of `expand-or-complete` when the `diff` function is being used.
- To do so, add this to your `.zshrc`:
- ```sh
- custom_tab () {
- words=(${(z)BUFFER})
- if [[ ${words[1]} == diff ]]; then
- zle complete-word
- else
- zle expand-or-complete
- fi
- }
- zle -N custom_tab
- bindkey '^I' custom_tab
- ```
- ---
- ### notes
- Here is a reiteration of a couple of key concepts that have been used to make this work:
- 1) The `-Q` flag must be used with the `compadd` function to not quote metacharacters.
- Without this flag, zsh will use `\{` in place of `{`, and `\}` in place of `}` in the commandline argument.
- 2) The `-S ''` flag is used with the `compadd` function to prevent a space from being added after completion of directories.
- 3) If the `~` character is present in the commandline argument, it will not be expanded and the solution will not work.
- So, we replace the `~` character with `$HOME` in the appropriate places, which is done via `${path_1//\~/$HOME}` and `${test_path//\~/$HOME}`.
- ---
- There are some edge cases in which this solution will not work, but can be fixed with some modifications:
- 1) The completion options will not include hidden files or folders, but they can be added by adding the `-a` option to the `ls` command.
- ---
- For further modification of this code, the following notes may be helpful:
- 1) It may be helpful to replace any `\{` characters with `{`, and any `\}` characters with `}` in the `PREFIX` parameter (i.e., `PREFIX=${PREFIX//\\\{/\{}` and `PREFIX=${PREFIX//\\\}/\}}`).
- 2) It may also be helpful to use the `ignore_braces` option.
#3: Post edited
- I figured out a solution myself.
- I'm not a zsh expert so this may be improved, but it works.
- ### 1) make a custom completion file for the diff command
- The completion file that zsh uses for the `diff` command is called `_diff`.
- On my computer, this file is located at `/usr/share/zsh/functions/Completion/Unix/_diff` and is shown below:
- ```sh
- #compdef diff gdiff
- _diff_options "$words[1]" ':original file:_files' ':new file:_files'
- ```
- We will create a new `_diff` file to override the default file above in order to perform the custom completion.
- We save this file as `/home/trevor/zsh_completions/_diff` whose contents are shown below:
- ```sh
- #compdef diff
- function _files_common() {
- last_buffer_item=$words[-1]
- matches=$(sed -n 's/^\(.*\){\(.*\),\(.*\)}\(.*\)$/\1 \2 \3 \4/p' <<< $last_buffer_item)
- if [[ -n $matches ]]; then
- # parse sed output
- before_braces_dir=$(cut -d ' ' -f 1 <<< $matches)
- folder_1=$(cut -d ' ' -f 2 <<< $matches)
- folder_2=$(cut -d ' ' -f 3 <<< $matches)
- after_braces=$(cut -d ' ' -f 4 <<< $matches)
- # get any directories after the braces
- after_braces_dir=$(sed -n 's/^\/\(.*\/\).*$/\1/p' <<< $after_braces)
- # full path of each of the two directories
- path_1=$before_braces_dir$folder_1/$after_braces_dir
- path_2=$before_braces_dir$folder_2/$after_braces_dir
- # arrays for compadd
- local -a _descriptions_files _values_files _descriptions_dirs _values_dirs
- # iterate through names in path 1, and see if they're in path 2
- # note: ls -F adds a slash at the end of folder names
- names=( $( ls -F ${path_1//\~/$HOME} ) )
- for name in "${names[@]}"; do
- test_path=$path_2$name
- commandline_arg=$before_braces_dir{$folder_1,$folder_2}/$after_braces_dir$name
- # if path is a file
- if [[ -f ${test_path//\~/$HOME} ]]; then
- _values_files+=( $commandline_arg )
- _descriptions_files+=( $name )
- # if path is a directory
- elif [[ -d ${test_path//\~/$HOME} ]]; then
- _values_dirs+=( $commandline_arg )
- _descriptions_dirs+=( $name )
- fi
- done
- # add completions
- # for directory completions, use an empty string as the suffix to prevent a
- # space being added at the end of the completion
- compadd -Q -d _descriptions_files -a _values_files
- compadd -S '' -Q -d _descriptions_dirs -a _values_dirs
- fi
- }
- # if the argument contains left and right braces, use the _files_common function
- # otherwise, use the _files function
- # note: the format of these commands is based on
- # /usr/share/zsh/functions/Completion/Unix/_diff
- last_buffer_item=$words[-1]
- if [[ $last_buffer_item == *{*,*}* ]]; then
- _diff_options "$words[1]" ':both files:_files_common'
- else
- _diff_options "$words[1]" ':original file:_files' ':new file:_files'
- fi
- ```
- ### 2) add the custom completion file to zsh's path
- To make zsh use the custom completion file from the previous step, we add the directory of the custom completion file to the `fpath` variable by adding this to your `.zshrc`:
- ```sh
- fpath=(/home/trevor/zsh_completions/ $fpath)
- ```
- Note that we must prepend to `fpath` rather than append in order to give the custom file precedence.
- Also note that this line should be added before the `compinit` command.
- ### 3) configure zshrc to run complete-word instead of expand-or-complete for the diff command
- Next, find the function that is mapped to <Tab> key in zsh.
- To do so, run `$ bindkey` and then find the `"^I"` entry, which corresponds to <Tab>.
- Doing this on my system shows that <Tab> is set to `expand-or-complete`.
- If we were to use `expand-or-complete` with diff and my custom completion function, then zsh will expand the commandline argument with braces into two separate arguments.
- Instead, we need to use `complete-word` instead of `expand-or-complete` when the `diff` function is being used.
- To do so, add this to your `.zshrc`:
- ```sh
- custom_tab () {
- words=(${(z)BUFFER})
- if [[ ${words[1]} == diff ]]; then
- zle complete-word
- else
- zle expand-or-complete
- fi
- }
- zle -N custom_tab
- bindkey '^I' custom_tab
- ```
- ### notes
- Here is a reiteration of a couple of key concepts that have been used to make this work:
- 1) The `-Q` flag must be used with the `compadd` function to not quote metacharacters.
- Without this flag, zsh will use `\{` in place of `{`, and `\}` in place of `}` in the commandline argument.
- 2) The `-S ''` flag is used with the `compadd` function to prevent a space from being added after completion of directories.
- 3) If the `~` character is present in the commandline argument, it will not be expanded and the solution will not work.
- So, we replace the `~` character with `$HOME` in the appropriate places, which is done via `${path_1//\~/$HOME}` and `${test_path//\~/$HOME}`.
- ---
- There are some edge cases in which this solution will not work, but can be fixed with some modifications:
- 1) The completion options will not include hidden files or folders, but they can be added by adding the `-a` option to the `ls` command.
- ---
- For further modification of this code, the following notes may be helpful.
- 1) It may be helpful to replace any `\{` characters with `{`, and any `\}` characters with `}` in the `PREFIX` parameter (i.e., `PREFIX=${PREFIX//\\\{/\{}` and `PREFIX=${PREFIX//\\\}/\}}`).
- 2) It may also be helpful to use the `ignore_braces` option.
- I figured out a solution myself.
- I'm not a zsh expert so this may be improved, but it works.
- ### 1) make a custom completion file for the diff command
- The completion file that zsh uses for the `diff` command is called `_diff`.
- On my computer, this file is located at `/usr/share/zsh/functions/Completion/Unix/_diff` and is shown below:
- ```sh
- #compdef diff gdiff
- _diff_options "$words[1]" ':original file:_files' ':new file:_files'
- ```
- We will create a new `_diff` file to override the default file above in order to perform the custom completion.
- We save this file as `/home/trevor/zsh_completions/_diff` whose contents are shown below:
- ```sh
- #compdef diff
- function _files_common() {
- last_buffer_item=$words[-1]
- matches=$(sed -n 's/^\(.*\){\(.*\),\(.*\)}\(.*\)$/\1 \2 \3 \4/p' <<< $last_buffer_item)
- if [[ -n $matches ]]; then
- # parse sed output
- before_braces_dir=$(cut -d ' ' -f 1 <<< $matches)
- folder_1=$(cut -d ' ' -f 2 <<< $matches)
- folder_2=$(cut -d ' ' -f 3 <<< $matches)
- after_braces=$(cut -d ' ' -f 4 <<< $matches)
- # get any directories after the braces
- after_braces_dir=$(sed -n 's/^\/\(.*\/\).*$/\1/p' <<< $after_braces)
- # full path of each of the two directories
- path_1=$before_braces_dir$folder_1/$after_braces_dir
- path_2=$before_braces_dir$folder_2/$after_braces_dir
- # arrays for compadd
- local -a _descriptions_files _values_files _descriptions_dirs _values_dirs
- # iterate through names in path 1, and see if they're in path 2
- # note: ls -F adds a slash at the end of folder names
- names=( $( ls -F ${path_1//\~/$HOME} ) )
- for name in "${names[@]}"; do
- test_path=$path_2$name
- commandline_arg=$before_braces_dir{$folder_1,$folder_2}/$after_braces_dir$name
- # if path is a file
- if [[ -f ${test_path//\~/$HOME} ]]; then
- _values_files+=( $commandline_arg )
- _descriptions_files+=( $name )
- # if path is a directory
- elif [[ -d ${test_path//\~/$HOME} ]]; then
- _values_dirs+=( $commandline_arg )
- _descriptions_dirs+=( $name )
- fi
- done
- # add completions
- # for directory completions, use an empty string as the suffix to prevent a
- # space being added at the end of the completion
- compadd -Q -d _descriptions_files -a _values_files
- compadd -S '' -Q -d _descriptions_dirs -a _values_dirs
- fi
- }
- # if the argument contains left and right braces, use the _files_common function
- # otherwise, use the _files function
- # note: the format of these commands is based on
- # /usr/share/zsh/functions/Completion/Unix/_diff
- last_buffer_item=$words[-1]
- if [[ $last_buffer_item == *{*,*}* ]]; then
- _diff_options "$words[1]" ':both files:_files_common'
- else
- _diff_options "$words[1]" ':original file:_files' ':new file:_files'
- fi
- ```
- ### 2) add the custom completion file to zsh's path
- To make zsh use the custom completion file from the previous step, we add the directory of the custom completion file to the `fpath` variable by adding this to your `.zshrc`:
- ```sh
- fpath=(/home/trevor/zsh_completions/ $fpath)
- ```
- Note that we must prepend to `fpath` rather than append in order to give the custom file precedence.
- Also note that this line should be added before the `compinit` command.
- ### 3) configure zshrc to run complete-word instead of expand-or-complete for the diff command
- Next, find the function that is mapped to <Tab> key in zsh.
- To do so, run `$ bindkey` and then find the `"^I"` entry, which corresponds to <Tab>.
- Doing this on my system shows that <Tab> is set to `expand-or-complete`.
- If we were to use `expand-or-complete` with diff and my custom completion function, then zsh will expand the commandline argument with braces into two separate arguments.
- Instead, we need to use `complete-word` instead of `expand-or-complete` when the `diff` function is being used.
- To do so, add this to your `.zshrc`:
- ```sh
- custom_tab () {
- words=(${(z)BUFFER})
- if [[ ${words[1]} == diff ]]; then
- zle complete-word
- else
- zle expand-or-complete
- fi
- }
- zle -N custom_tab
- bindkey '^I' custom_tab
- ```
- ---
- ### notes
- Here is a reiteration of a couple of key concepts that have been used to make this work:
- 1) The `-Q` flag must be used with the `compadd` function to not quote metacharacters.
- Without this flag, zsh will use `\{` in place of `{`, and `\}` in place of `}` in the commandline argument.
- 2) The `-S ''` flag is used with the `compadd` function to prevent a space from being added after completion of directories.
- 3) If the `~` character is present in the commandline argument, it will not be expanded and the solution will not work.
- So, we replace the `~` character with `$HOME` in the appropriate places, which is done via `${path_1//\~/$HOME}` and `${test_path//\~/$HOME}`.
- ---
- There are some edge cases in which this solution will not work, but can be fixed with some modifications:
- 1) The completion options will not include hidden files or folders, but they can be added by adding the `-a` option to the `ls` command.
- ---
- For further modification of this code, the following notes may be helpful.
- 1) It may be helpful to replace any `\{` characters with `{`, and any `\}` characters with `}` in the `PREFIX` parameter (i.e., `PREFIX=${PREFIX//\\\{/\{}` and `PREFIX=${PREFIX//\\\}/\}}`).
- 2) It may also be helpful to use the `ignore_braces` option.
#2: Post edited
- I figured out a solution myself.
- I'm not a zsh expert so this may be improved, but it works.
- ### 1) make a custom completion file for the diff command
- The completion file that zsh uses for the `diff` command is called `_diff`.
- On my computer, this file is located at `/usr/share/zsh/functions/Completion/Unix/_diff` and is shown below:
- ```sh
- #compdef diff gdiff
- _diff_options "$words[1]" ':original file:_files' ':new file:_files'
- ```
- We will create a new `_diff` file to override the default file above in order to perform the custom completion.
- We save this file as `/home/trevor/zsh_completions/_diff` whose contents are shown below:
- ```sh
- #compdef diff
- function _files_common() {
- last_buffer_item=$words[-1]
- matches=$(sed -n 's/^\(.*\){\(.*\),\(.*\)}\(.*\)$/\1 \2 \3 \4/p' <<< $last_buffer_item)
- if [[ -n $matches ]]; then
- # parse sed output
- before_braces_dir=$(cut -d ' ' -f 1 <<< $matches)
- folder_1=$(cut -d ' ' -f 2 <<< $matches)
- folder_2=$(cut -d ' ' -f 3 <<< $matches)
- after_braces=$(cut -d ' ' -f 4 <<< $matches)
- # get any directories after the braces
- after_braces_dir=$(sed -n 's/^\/\(.*\/\).*$/\1/p' <<< $after_braces)
- # full path of each of the two directories
- path_1=$before_braces_dir$folder_1/$after_braces_dir
- path_2=$before_braces_dir$folder_2/$after_braces_dir
- # arrays for compadd
- local -a _descriptions_files _values_files _descriptions_dirs _values_dirs
- # iterate through names in path 1, and see if they're in path 2
- # note: ls -F adds a slash at the end of folder names
- names=( $( ls -F ${path_1//\~/$HOME} ) )
- for name in "${names[@]}"; do
- test_path=$path_2$name
- commandline_arg=$before_braces_dir{$folder_1,$folder_2}/$after_braces_dir$name
- # if path is a file
- if [[ -f ${test_path//\~/$HOME} ]]; then
- _values_files+=( $commandline_arg )
- _descriptions_files+=( $name )
- # if path is a directory
- elif [[ -d ${test_path//\~/$HOME} ]]; then
- _values_dirs+=( $commandline_arg )
- _descriptions_dirs+=( $name )
- fi
- done
- # add completions
- # for directory completions, use an empty string as the suffix to prevent a
- # space being added at the end of the completion
- compadd -Q -d _descriptions_files -a _values_files
- compadd -S '' -Q -d _descriptions_dirs -a _values_dirs
- fi
- }
- # if the argument contains left and right braces, use the _files_common function
- # otherwise, use the _files function
- # note: the format of these commands is based on
- # /usr/share/zsh/functions/Completion/Unix/_diff
- last_buffer_item=$words[-1]
- if [[ $last_buffer_item == *{*,*}* ]]; then
- _diff_options "$words[1]" ':both files:_files_common'
- else
- _diff_options "$words[1]" ':original file:_files' ':new file:_files'
- fi
- ```
- ### 2) add the custom completion file to zsh's path
- To make zsh use the custom completion file from the previous step, we add the directory of the custom completion file to the `fpath` variable by adding this to your `.zshrc`:
- ```sh
- fpath=(/home/trevor/zsh_completions/ $fpath)
- ```
- Note that we must prepend to `fpath` rather than append in order to give the custom file precedence.
- Also note that this line should be added before the `compinit` command.
- ### 3) configure zshrc to run complete-word instead of expand-or-complete for the diff command
- Next, find the function that is mapped to <Tab> key in zsh.
- To do so, run `$ bindkey` and then find the `"^I"` entry, which corresponds to <Tab>.
- Doing this on my system shows that <Tab> is set to `expand-or-complete`.
- If we were to use `expand-or-complete` with diff and my custom completion function, then zsh will expand the commandline argument with braces into two separate arguments.
- Instead, we need to use `complete-word` instead of `expand-or-complete` when the `diff` function is being used.
- To do so, add this to your `.zshrc`:
- ```sh
- custom_tab () {
- words=(${(z)BUFFER})
- if [[ ${words[1]} == diff ]]; then
- zle complete-word
- else
- zle expand-or-complete
- fi
- }
- zle -N custom_tab
- bindkey '^I' custom_tab
- ```
## notes- Here is a reiteration of a couple of key concepts that have been used to make this work:
- 1) The `-Q` flag must be used with the `compadd` function to not quote metacharacters.
- Without this flag, zsh will use `\{` in place of `{`, and `\}` in place of `}` in the commandline argument.
- 2) The `-S ''` flag is used with the `compadd` function to prevent a space from being added after completion of directories.
- 3) If the `~` character is present in the commandline argument, it will not be expanded and the solution will not work.
- So, we replace the `~` character with `$HOME` in the appropriate places, which is done via `${path_1//\~/$HOME}` and `${test_path//\~/$HOME}`.
- ---
- There are some edge cases in which this solution will not work, but can be fixed with some modifications:
- 1) The completion options will not include hidden files or folders, but they can be added by adding the `-a` option to the `ls` command.
- ---
- For further modification of this code, the following notes may be helpful.
- 1) It may be helpful to replace any `\{` characters with `{`, and any `\}` characters with `}` in the `PREFIX` parameter (i.e., `PREFIX=${PREFIX//\\\{/\{}` and `PREFIX=${PREFIX//\\\}/\}}`).
- 2) It may also be helpful to use the `ignore_braces` option.
- I figured out a solution myself.
- I'm not a zsh expert so this may be improved, but it works.
- ### 1) make a custom completion file for the diff command
- The completion file that zsh uses for the `diff` command is called `_diff`.
- On my computer, this file is located at `/usr/share/zsh/functions/Completion/Unix/_diff` and is shown below:
- ```sh
- #compdef diff gdiff
- _diff_options "$words[1]" ':original file:_files' ':new file:_files'
- ```
- We will create a new `_diff` file to override the default file above in order to perform the custom completion.
- We save this file as `/home/trevor/zsh_completions/_diff` whose contents are shown below:
- ```sh
- #compdef diff
- function _files_common() {
- last_buffer_item=$words[-1]
- matches=$(sed -n 's/^\(.*\){\(.*\),\(.*\)}\(.*\)$/\1 \2 \3 \4/p' <<< $last_buffer_item)
- if [[ -n $matches ]]; then
- # parse sed output
- before_braces_dir=$(cut -d ' ' -f 1 <<< $matches)
- folder_1=$(cut -d ' ' -f 2 <<< $matches)
- folder_2=$(cut -d ' ' -f 3 <<< $matches)
- after_braces=$(cut -d ' ' -f 4 <<< $matches)
- # get any directories after the braces
- after_braces_dir=$(sed -n 's/^\/\(.*\/\).*$/\1/p' <<< $after_braces)
- # full path of each of the two directories
- path_1=$before_braces_dir$folder_1/$after_braces_dir
- path_2=$before_braces_dir$folder_2/$after_braces_dir
- # arrays for compadd
- local -a _descriptions_files _values_files _descriptions_dirs _values_dirs
- # iterate through names in path 1, and see if they're in path 2
- # note: ls -F adds a slash at the end of folder names
- names=( $( ls -F ${path_1//\~/$HOME} ) )
- for name in "${names[@]}"; do
- test_path=$path_2$name
- commandline_arg=$before_braces_dir{$folder_1,$folder_2}/$after_braces_dir$name
- # if path is a file
- if [[ -f ${test_path//\~/$HOME} ]]; then
- _values_files+=( $commandline_arg )
- _descriptions_files+=( $name )
- # if path is a directory
- elif [[ -d ${test_path//\~/$HOME} ]]; then
- _values_dirs+=( $commandline_arg )
- _descriptions_dirs+=( $name )
- fi
- done
- # add completions
- # for directory completions, use an empty string as the suffix to prevent a
- # space being added at the end of the completion
- compadd -Q -d _descriptions_files -a _values_files
- compadd -S '' -Q -d _descriptions_dirs -a _values_dirs
- fi
- }
- # if the argument contains left and right braces, use the _files_common function
- # otherwise, use the _files function
- # note: the format of these commands is based on
- # /usr/share/zsh/functions/Completion/Unix/_diff
- last_buffer_item=$words[-1]
- if [[ $last_buffer_item == *{*,*}* ]]; then
- _diff_options "$words[1]" ':both files:_files_common'
- else
- _diff_options "$words[1]" ':original file:_files' ':new file:_files'
- fi
- ```
- ### 2) add the custom completion file to zsh's path
- To make zsh use the custom completion file from the previous step, we add the directory of the custom completion file to the `fpath` variable by adding this to your `.zshrc`:
- ```sh
- fpath=(/home/trevor/zsh_completions/ $fpath)
- ```
- Note that we must prepend to `fpath` rather than append in order to give the custom file precedence.
- Also note that this line should be added before the `compinit` command.
- ### 3) configure zshrc to run complete-word instead of expand-or-complete for the diff command
- Next, find the function that is mapped to <Tab> key in zsh.
- To do so, run `$ bindkey` and then find the `"^I"` entry, which corresponds to <Tab>.
- Doing this on my system shows that <Tab> is set to `expand-or-complete`.
- If we were to use `expand-or-complete` with diff and my custom completion function, then zsh will expand the commandline argument with braces into two separate arguments.
- Instead, we need to use `complete-word` instead of `expand-or-complete` when the `diff` function is being used.
- To do so, add this to your `.zshrc`:
- ```sh
- custom_tab () {
- words=(${(z)BUFFER})
- if [[ ${words[1]} == diff ]]; then
- zle complete-word
- else
- zle expand-or-complete
- fi
- }
- zle -N custom_tab
- bindkey '^I' custom_tab
- ```
- ### notes
- Here is a reiteration of a couple of key concepts that have been used to make this work:
- 1) The `-Q` flag must be used with the `compadd` function to not quote metacharacters.
- Without this flag, zsh will use `\{` in place of `{`, and `\}` in place of `}` in the commandline argument.
- 2) The `-S ''` flag is used with the `compadd` function to prevent a space from being added after completion of directories.
- 3) If the `~` character is present in the commandline argument, it will not be expanded and the solution will not work.
- So, we replace the `~` character with `$HOME` in the appropriate places, which is done via `${path_1//\~/$HOME}` and `${test_path//\~/$HOME}`.
- ---
- There are some edge cases in which this solution will not work, but can be fixed with some modifications:
- 1) The completion options will not include hidden files or folders, but they can be added by adding the `-a` option to the `ls` command.
- ---
- For further modification of this code, the following notes may be helpful.
- 1) It may be helpful to replace any `\{` characters with `{`, and any `\}` characters with `}` in the `PREFIX` parameter (i.e., `PREFIX=${PREFIX//\\\{/\{}` and `PREFIX=${PREFIX//\\\}/\}}`).
- 2) It may also be helpful to use the `ignore_braces` option.
#1: Initial revision
I figured out a solution myself. I'm not a zsh expert so this may be improved, but it works. ### 1) make a custom completion file for the diff command The completion file that zsh uses for the `diff` command is called `_diff`. On my computer, this file is located at `/usr/share/zsh/functions/Completion/Unix/_diff` and is shown below: ```sh #compdef diff gdiff _diff_options "$words[1]" ':original file:_files' ':new file:_files' ``` We will create a new `_diff` file to override the default file above in order to perform the custom completion. We save this file as `/home/trevor/zsh_completions/_diff` whose contents are shown below: ```sh #compdef diff function _files_common() { last_buffer_item=$words[-1] matches=$(sed -n 's/^\(.*\){\(.*\),\(.*\)}\(.*\)$/\1 \2 \3 \4/p' <<< $last_buffer_item) if [[ -n $matches ]]; then # parse sed output before_braces_dir=$(cut -d ' ' -f 1 <<< $matches) folder_1=$(cut -d ' ' -f 2 <<< $matches) folder_2=$(cut -d ' ' -f 3 <<< $matches) after_braces=$(cut -d ' ' -f 4 <<< $matches) # get any directories after the braces after_braces_dir=$(sed -n 's/^\/\(.*\/\).*$/\1/p' <<< $after_braces) # full path of each of the two directories path_1=$before_braces_dir$folder_1/$after_braces_dir path_2=$before_braces_dir$folder_2/$after_braces_dir # arrays for compadd local -a _descriptions_files _values_files _descriptions_dirs _values_dirs # iterate through names in path 1, and see if they're in path 2 # note: ls -F adds a slash at the end of folder names names=( $( ls -F ${path_1//\~/$HOME} ) ) for name in "${names[@]}"; do test_path=$path_2$name commandline_arg=$before_braces_dir{$folder_1,$folder_2}/$after_braces_dir$name # if path is a file if [[ -f ${test_path//\~/$HOME} ]]; then _values_files+=( $commandline_arg ) _descriptions_files+=( $name ) # if path is a directory elif [[ -d ${test_path//\~/$HOME} ]]; then _values_dirs+=( $commandline_arg ) _descriptions_dirs+=( $name ) fi done # add completions # for directory completions, use an empty string as the suffix to prevent a # space being added at the end of the completion compadd -Q -d _descriptions_files -a _values_files compadd -S '' -Q -d _descriptions_dirs -a _values_dirs fi } # if the argument contains left and right braces, use the _files_common function # otherwise, use the _files function # note: the format of these commands is based on # /usr/share/zsh/functions/Completion/Unix/_diff last_buffer_item=$words[-1] if [[ $last_buffer_item == *{*,*}* ]]; then _diff_options "$words[1]" ':both files:_files_common' else _diff_options "$words[1]" ':original file:_files' ':new file:_files' fi ``` ### 2) add the custom completion file to zsh's path To make zsh use the custom completion file from the previous step, we add the directory of the custom completion file to the `fpath` variable by adding this to your `.zshrc`: ```sh fpath=(/home/trevor/zsh_completions/ $fpath) ``` Note that we must prepend to `fpath` rather than append in order to give the custom file precedence. Also note that this line should be added before the `compinit` command. ### 3) configure zshrc to run complete-word instead of expand-or-complete for the diff command Next, find the function that is mapped to <Tab> key in zsh. To do so, run `$ bindkey` and then find the `"^I"` entry, which corresponds to <Tab>. Doing this on my system shows that <Tab> is set to `expand-or-complete`. If we were to use `expand-or-complete` with diff and my custom completion function, then zsh will expand the commandline argument with braces into two separate arguments. Instead, we need to use `complete-word` instead of `expand-or-complete` when the `diff` function is being used. To do so, add this to your `.zshrc`: ```sh custom_tab () { words=(${(z)BUFFER}) if [[ ${words[1]} == diff ]]; then zle complete-word else zle expand-or-complete fi } zle -N custom_tab bindkey '^I' custom_tab ``` ## notes Here is a reiteration of a couple of key concepts that have been used to make this work: 1) The `-Q` flag must be used with the `compadd` function to not quote metacharacters. Without this flag, zsh will use `\{` in place of `{`, and `\}` in place of `}` in the commandline argument. 2) The `-S ''` flag is used with the `compadd` function to prevent a space from being added after completion of directories. 3) If the `~` character is present in the commandline argument, it will not be expanded and the solution will not work. So, we replace the `~` character with `$HOME` in the appropriate places, which is done via `${path_1//\~/$HOME}` and `${test_path//\~/$HOME}`. --- There are some edge cases in which this solution will not work, but can be fixed with some modifications: 1) The completion options will not include hidden files or folders, but they can be added by adding the `-a` option to the `ls` command. --- For further modification of this code, the following notes may be helpful. 1) It may be helpful to replace any `\{` characters with `{`, and any `\}` characters with `}` in the `PREFIX` parameter (i.e., `PREFIX=${PREFIX//\\\{/\{}` and `PREFIX=${PREFIX//\\\}/\}}`). 2) It may also be helpful to use the `ignore_braces` option.