Browse Source

half-done, apt-get in does not complete yet

main
pvincent 22 hours ago
parent
commit
ac9edb4e32
  1. 127
      lib/RECURSIVE_COMPLETION.md
  2. 122
      lib/miaou.completion

127
lib/RECURSIVE_COMPLETION.md

@ -0,0 +1,127 @@
## Recursive completion — call container's own bash completion
The idea: instead of reimplementing completion, **delegate to bash inside the container** and get back its own completion results.
---
## The approach
```bash
_incus_exec_delegate() {
local cur="${COMP_WORDS[COMP_CWORD]}"
local container="${COMP_WORDS[2]}"
# find '--' position
local dashdash_pos=0
local i
for (( i=0; i < ${#COMP_WORDS[@]}; i++ )); do
[[ "${COMP_WORDS[$i]}" == "--" ]] && dashdash_pos=$i
done
# complete container names
if (( COMP_CWORD == 2 )); then
COMPREPLY=($(compgen -W \
"$(incus list --format csv --columns n 2>/dev/null)" \
-- "$cur"))
return
fi
# before '--'
if (( dashdash_pos == 0 )); then
COMPREPLY=($(compgen -W "-- --env --user --cwd" -- "$cur"))
return
fi
# after '--' — delegate full completion to container's bash
local inner_words=("${COMP_WORDS[@]:$((dashdash_pos + 1))}")
local inner_cword=$(( COMP_CWORD - dashdash_pos - 1 ))
local inner_cmd="${inner_words[0]}"
local inner_line="${inner_words[*]}"
# ask container's bash to run completion
COMPREPLY=($(incus exec "$container" -- bash -c "
# load bash completion
source /usr/share/bash-completion/bash_completion 2>/dev/null
source /etc/bash_completion 2>/dev/null
# rebuild COMP_ variables inside container
COMP_WORDS=(${inner_words[*]@Q})
COMP_CWORD=$inner_cword
COMP_LINE=${inner_line@Q}
COMP_POINT=\${#COMP_LINE}
# find and call the completion function for the command
local compfunc
compfunc=\$(complete -p ${inner_cmd@Q} 2>/dev/null \
| grep -oP '(?<=-F )\S+')
if [[ -n \"\$compfunc\" ]]; then
\$compfunc ${inner_cmd@Q} ${cur@Q} ${inner_words[$((inner_cword - 1))]@Q}
printf '%s\n' \"\${COMPREPLY[@]}\"
else
# fallback to file completion
compgen -f -- ${cur@Q}
fi
" 2>/dev/null))
}
complete -F _incus_exec_delegate incus
```
---
## How it works
```
host$ incus exec mycontainer -- git che<TAB>
inner_cmd="git"
incus exec mycontainer -- bash -c '
source /usr/share/bash-completion/bash_completion
COMP_WORDS=(git che)
COMP_CWORD=1
compfunc=$(complete -p git | grep -oP "(?<=-F )\S+")
$compfunc ... # calls __git_wrap__git_main
printf "%s\n" "${COMPREPLY[@]}"
'
COMPREPLY=(checkout cherry cherry-pick)
```
---
## Caveats
```bash
# completion inside container requires bash-completion installed
incus exec mycontainer -- apt install -y bash-completion
# verify completion available for a command
incus exec mycontainer -- bash -c "complete -p git"
```
---
## Fallback chain
```
container has completion function for cmd?
↓ yes → delegate to it
↓ no → compgen -c (commands)
compgen -f (files)
```
---
## Quick reference
| Step | What happens |
|------|-------------|
| rebuild `COMP_WORDS` | strip `incus exec CTR --` prefix |
| `complete -p CMD` | find completion function in container |
| `$compfunc` | call it with correct args |
| fallback | `compgen -f` for file completion |
The key insight is **rebuilding `COMP_WORDS` and `COMP_CWORD`** with the `incus exec CTR --` prefix stripped — the container's bash then sees the command line exactly as if it were running locally.

122
lib/miaou.completion

@ -30,4 +30,126 @@ function _miaou_login {
esac
}
function _miaou_exec2() {
local cur="${COMP_WORDS[COMP_CWORD]}"
local prev="${COMP_WORDS[COMP_CWORD - 1]}"
local container="${COMP_WORDS[1]}"
# find '--' position
local dashdash_pos=0
local i
for ((i = 0; i < ${#COMP_WORDS[@]}; i++)); do
[[ ${COMP_WORDS[$i]} == "--" ]] && dashdash_pos=$i
done
# complete container names
if ((COMP_CWORD == 1)); then
# first argument — containers
COMPREPLY=($(compgen -W "$(_incus_container)" -- "$cur"))
return
fi
if ((COMP_CWORD == 2)); then
# second argument - options
COMPREPLY=($(compgen -W "--" -- "$cur"))
return
fi
if ((COMP_CWORD == 3)); then
# command or file from inside container
COMPREPLY=($(
incus exec "$container" -- bash << EOF
source /etc/bash_completion
compgen -c -- "$cur"
compgen -f -- "$cur"
EOF
))
fi
if ((COMP_CWORD > 3)); then
command=${COMP_WORDS[dashdash_pos + 1]}
completion_command=$(
incus exec "$container" -- bash << EOF
source /etc/bash_completion
for i in /etc/bash_completion.d/*; do source \$i; done
[[ -f /usr/share/bash-completion/completions/$command ]] && source /usr/share/bash-completion/completions/$command
complete -p "$command" | grep -oP '(?<=-F )\S+'
EOF
)
if [[ -n $completion_command ]]; then
# Remove first $dashdash_pos items
COMP_WORDS=("${COMP_WORDS[@]:dashdash_pos+1}")
COMP_CWORD=$((COMP_CWORD - dashdash_pos - 1))
COMP_LINE="${COMP_WORDS[@]}"
COMP_POINT=${#COMP_LINE}
echo -e "\ncompletion_command $completion_command \nprev=$prev \ncur=$cur \nCOMP_WORDS=(${COMP_WORDS[@]}) \nCOMP_CWORD=$COMP_CWORD \nCOMP_LINE=$COMP_LINE \nCOMP_POINT=$COMP_POINT" >&2
COMPREPLY=($(
incus exec "$container" -- bash << EOF
source /etc/bash_completion
for i in /etc/bash_completion.d/*; do source \$i; done
source /usr/share/bash-completion/bash_completion
[[ -f /usr/share/bash-completion/completions/$command ]] && source /usr/share/bash-completion/completions/$command
COMP_WORDS=(${COMP_WORDS[@]})
COMP_CWORD=$COMP_CWORD
COMP_LINE="$COMP_LINE"
COMP_POINT=$COMP_CWORD
COMP_TYPE=9
COMP_KEY=9
export COMP_WORDS COMP_CWORD COMP_LINE COMP_POINT COMP_TYPE COMP_KEY
$completion_command
echo \${COMPREPLY[@]}
EOF
))
else
COMPREPLY=($(
incus exec "$container" -- bash << EOF
source /etc/bash_completion
compgen -f -- "$cur"
EOF
))
fi
fi
}
function _miaou_exec {
local cur="${COMP_WORDS[COMP_CWORD]}"
local prev="${COMP_WORDS[COMP_CWORD - 1]}"
local container="${COMP_WORDS[1]}"
case $COMP_CWORD in
1)
# first argument — containers
COMPREPLY=($(compgen -W "$(_incus_container)" -- "$cur"))
;;
2)
# second argument - options
COMPREPLY=($(compgen -W "--" -- "$cur"))
;;
*)
# echo "coucou" >&2
# COMPREPLY=($(compgen -W \
# "$(incus exec "$container" -- bash -c 'compgen -c' 2> /dev/null)" \
# -- "$cur"))
COMPREPLY=($(compgen -W \
"$(incus exec "$container" -- \
bash -c "compgen -c -- $(printf '%q' "$cur")" 2> /dev/null)" \
-- "$cur"))
;;
esac
}
complete -F _miaou_login "miaou-login"
complete -F _miaou_exec2 "miaou-exec"
function _toto {
for i in ${!COMP_WORDS[@]}; do echo "COMP_WORDS[$i]=${COMP_WORDS[$i]}"; done
echo COMP_CWORD=$COMP_CWORD
COMPREPLY=(one two three)
}
complete -F _toto toto
Loading…
Cancel
Save