2 changed files with 249 additions and 0 deletions
@ -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. |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue