custom directory completion appends whitespace - bash

I have the following directory structure:
/home/tichy/xxx/yyy/aaa
/home/tichy/xxx/yyy/aab
/home/tichy/xxx/yyy/aac
I would like to enter cdw y<TAB> and get cdw yyy/<CURSOR> as a result, so I could add cdw yyy/a<TAB> and get cdw yyy/aa<CURSOR>
The solution I came up with gives me the following:
cdw y<TAB> => cdw yyy<SPACE><CURSOR>
Following code I have so far:
_cdw () {
local cur prev dirs
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
COMPREPLY=($(compgen -d -- /home/tichy/xxx/${cur}|perl -pe 's{^/home/tichy/xxx/}{}'))
# no difference, a bit more logical:
dirs=$(compgen -o nospace -d /home/tichy/xxx/${cur}|perl -pe 's/{^/home/tichy/xxx/}{}')
COMPREPLY=($(compgen -d -W ${dir} ${cur}|perl -pe 's{^/home/tichy/xxx/}{}'))
return 0
}
complete -F _cdw cdw
cdw () {
cd /home/tichy/xxx/$#
}
Any ideas what's wrong? It seems to me that the completion process seems to be finished and isn't expecting any more input.

The simplest solution I've found so far is to generate completions that look like this:
COMPREPLY=( $( compgen -W "file1 file2 file3 dir1/ dir2/ dir3/" ) )
and add this line just before returning
[[ $COMPREPLY == */ ]] && compopt -o nospace
This sets the nospace option whenever the completion may fill in until the slash so that the user ends up with:
cmd dir1/<cursorhere>
instead of:
cmd dir1/ <cursorhere>
and it doesn't set the nospace option whenever the completion may fill in until a full filename so that the user ends up with:
cmd file1 <cursorhere>
instead of:
cmd file1<cursorhere>

If I understand correctly, you want to bash-autocomplete a directory name, and not have the extra space? (That's what I was looking for when I got to this page).
If so, when you register the completion function, use "-o nospace".
complete -o nospace -F _cdw cdw
I don't know if nospace works on compgen.

How about something like this:
COMPREPLY=( $(cdw; compgen -W "$(for d in ${cur}* ${cur}*/*; do [[ -d "$d" ]] && echo $d/; done)" -- ${cur}) )
(I'm not sure if you can call your shell function from here or not, otherwise you may have to duplicate it a bit.)
This also gets rid of your perl hack :-)

completion provides a solution for this without any workaround: funciton _filedir defined in /etc/bash_completion:
626 # This function performs file and directory completion. It's better than
627 # simply using 'compgen -f', because it honours spaces in filenames.
628 # #param $1 If `-d', complete only on directories. Otherwise filter/pick only
629 # completions with `.$1' and the uppercase version of it as file
630 # extension.
631 #
632 _filedir()
Then, specifying the following is enough:
_cdw () {
local cur prev dirs
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
_filedir # add -d at the end to complete only dirs, no files
return 0
}

Related

Bash complete function - Separating completion parts with character other than space

I've written a bash completion script to essentially do file/directory completion, but using . as the separator instead of /. However, it's not behaving as I expect it to.
Before I dive further, does anyone know of any options for this, or something that's already been written that can do this? The motivation for this is to enable completion when calling python with the -m flag. It seems crazy that this doesn't exist yet, but I was unable to find anything relevant.
My issue is that bash doesn't recognize . as a separator for completion options, and won't show the next options until I add an additional space to the end of the current command.
Here's a few concrete examples, given this directory structure.
/module
/script1.py
/script2.py
For instance, when I use the ls command, it works like this
$ ls mo<TAB>
$ ls module/<TAB><TAB>
script1.py script2.py
However, with my function, it's working like this:
$ python -m mod<TAB>
$ python -m module.<TAB><TAB>
module.
So instead of showing the next entries, it just shows the finished string again. However, if I add a space, it then works, but I don't want it to include the space:
$ python -m mod<TAB>
$ python -m module. <TAB><TAB> # (note the space here after the dot)
script1 script2 # (Note, I'm intentionally removing the file extension here).
I'd like the completion to act just like the bottom example, except not be forced to include the space to go to the next set of options
I've got about 50 tabs open and I've tried a bunch of recommendations, but nothing seems to be able to solve this how I'd like. There are a few other caveats here that would take a lot of time to go through, so I'm happy to expand on any other points if I've skipped something important. I've attached my code below, any help would be greatly appreciated. Thanks!
#!/bin/bash
_python_target() {
local cur opts cur_path
# Retrieving the current typed argument
cur="${COMP_WORDS[COMP_CWORD]}"
# Preparing an array to store available list for completions
# COMREPLY will be checked to suggest the list
COMPREPLY=()
# Here, we'll only handle the case of "-m"
# Hence, the classic autocompletion is disabled
# (ie COMREPLY stays an empty array)
if [[ "${COMP_WORDS[1]}" != "-m" ]]
then
return 0
fi
# add each path component to the current path to check for additional files
cur_path=""
for word in ${COMP_WORDS[#]:2:COMP_CWORD-2}; do
path_component=$(echo ${word} | sed 's/\./\//g')
cur_path="${cur_path}${path_component}"
done
cur_path="./${cur_path}"
if [[ ! -f "$cur_path" && ! -d "$cur_path" ]]; then
return 0
fi
# this is not very pretty, but it works. Open to comments on this too
file_opts="$(find ${cur_path} -name "*.py" -type f -maxdepth 1 -print0 | xargs -0 basename -a | sed 's/\.[^.]*$//')"
dir_opts="$(find ${cur_path} ! -path ${cur_path} -type d -maxdepth 1 -print0 | xargs -0 basename -a | xargs -I {} echo {}.)"
opts="${file_opts} ${dir_opts}"
# We store the whole list by invoking "compgen" and filling
# COMREPLY with its output content.
COMPREPLY=($(compgen -W "$opts" -- "$cur"))
[[ $COMPREPLY == *\. ]] && compopt -o nospace
}
complete -F _python_target python
Here's a draft example:
_python_target()
{
local cmd=$1 cur=$2 pre=$3
if [[ $pre != -m ]]; then
return
fi
local cur_slash=${cur//./\/}
local i arr arr2
arr=( $( compgen -f "$cur_slash" ) )
arr2=()
for i in "${arr[#]}"; do
if [[ -d $i ]]; then
arr2+=( "$i/" )
elif [[ $i == *.py ]]; then
arr2+=( "${i%.py}" )
fi
done
arr2=( "${arr2[#]//\//.}" )
COMPREPLY=( $( compgen -W "${arr2[*]}" -- "$cur" ) )
}
complete -o nospace -F _python_target python
Try with the python-2.7.18 source code directory:

How can you convert this bash completion function to a zsh completion function?

How can you convert this bash completion function to a zsh completion function?
This bash function allows you to tab-complete the names of directories above your current working directory so you can more quickly and accurately navigate up the file hierarchy.
Example:
$ pwd
/Users/me/Animals/Mammals/Ungulates/Goats/Ibex
$ upto Animals
$ pwd
/Users/me/Animals
Code:
function upto {
if [[ -z $1 ]]; then
return
fi
local upto=$1
cd "${PWD/\/$upto\/*//$upto}"
}
_upto()
{
local cur=${COMP_WORDS[COMP_CWORD]}
local d=${PWD//\//\ }
COMPREPLY=( $( compgen -W "$d" -- "$cur" ) )
}
complete -F _upto upto
I'd like to have a similar one in zsh, but I don't fully understand the mechanics of how this one works, much less how to modify it for zsh.
Try this completion function
_upto() {
local parents;
parents=(${(s:/:)PWD});
compadd -V 'Parent Dirs' -- "${(Oa)parents[#]}";
}
Enable it with compdef _upto upto.
Test it by creating a deply nested folder: f=/tmp/foo/bar/one/two/three;mkdir -p $f;cd $f. Typing upto <Tab> should give
three two one bar foo tmp
To reverse the directories (i.e. start proposals at root rather at ..), remove the (Oa) flag from the compadd line.

Complete command options with quoted values

I am trying to write a Bash completion function for completing long command line options like --param1=value1. This works fine if value1 is not quoted. But in many cases value1 needs to be quoted, for example, --param1='Hello world'. In this case, Bash completion stops working. For example:
_myprog()
{
local cur="${COMP_WORDS[$COMP_CWORD]}"
local words=(--param1 --param2)
COMPREPLY=( $(compgen -W "${words[*]}" -- "$cur") )
}
complete -F _myprog myprog
If I source this source script.sh and then type myprog --param1='hello' <tab><tab> nothing happens. It works fine if I start the quote before the double dashes, like myprog '--param1=hello' <tab><tab>..
Any suggestions?
This can be done using COMP_LINE instead of COMP_WORDS:
_myprog()
{
local comp_line=( $COMP_LINE )
local cur
if [[ ${COMP_LINE: -1} == ' ' ]] ; then
cur=""
else
cur="${comp_line[#]: -1}"
fi
local words=(--param1 --param2)
COMPREPLY=( $(compgen -W "${words[*]}" -- "$cur") )
}

How to make bash completion ignore certain directories?

I am trying to write a completion function that ignores file names ending with *.bak and also directories named backup. Apart from this it should work like the default Readline completion (for example the one used for the ls command). Here is my first attempt:
$ cat setup.sh
_compTest() {
local cur
FIGNORE='bak:backup'
cur="${COMP_WORDS[$COMP_CWORD]}"
compopt -o filenames
COMPREPLY=( $(compgen -f "$cur") )
}
complete -F _compTest aaa
After sourcing:
$ . setup.sh
I can type
$ aaa <tab><tab>
and I get a completion list excluding *.bak files, but unfortunately not backup directories.
How can I extend this function to exclude also backup directories from the completion list?
This can be done by post processing the array returned by compgen:
_compTest() {
local cur words
cur="${COMP_WORDS[$COMP_CWORD]}"
compopt -o filenames
words=( $(compgen -f "$cur") )
COMPREPLY=()
for val in "${words[#]}" ; do
name=$(basename "$val")
if [[ $name == *.bak || $name == "backup" ]] ; then
continue
fi
COMPREPLY+=( "$val" )
done
}

How do I enable bash tab complete from a different path?

I want to enable bash tab-complete to look for directories, but not in the current directory.
So for instance, if I do:
$ ls $P
dirs/ are/ here/
$ cd /not/the/P/path
$ ls
other/ stuff/
$ myProg <tab>
dirs/ are/ here
This changes the usual behavior, where I would normally see files in the current directory.
Due diligence: The best I could come up with is:
_myProg ()
{
local cur
COMPREPLY=()
cur=${COMP_WORDS[COMP_CWORD]}
if [ "${P}x" = "x" ]; then
return 1
fi
case "$cur" in
*)
pth=${P}/$( echo $cur | egrep -o "^.*/[^/]*$" )
COMPREPLY=( $( compgen -W "$( cd $pth && ls -1d "$cur"* 2>/dev/null -- "$cur" )" ) )
;;
esac
return 0
}
complete -o nospace -F _myProg myProg
which initially shows directories, but doesnt let me drill down into the directories how I want (like ls works).
Is $CDPATH helpful for you? See Advanced Bash Scripting Guide.
_myProg()
{
COMPREPLY=($(cd $P; compgen -f $2))
}
complete -onospace -F_myProg myProg

Resources