From 0d76f60668181dd59a3edb3aa4302fcc0e665c46 Mon Sep 17 00:00:00 2001 From: pvincent Date: Mon, 6 Apr 2026 16:34:15 +0400 Subject: [PATCH] miaou-ls --- lib/RECURSIVE_COMPLETION.md | 127 ------------------------------------ lib/miaou.completion | 28 ++++++-- tools/miaou-ls | 32 ++++++++- 3 files changed, 55 insertions(+), 132 deletions(-) delete mode 100644 lib/RECURSIVE_COMPLETION.md diff --git a/lib/RECURSIVE_COMPLETION.md b/lib/RECURSIVE_COMPLETION.md deleted file mode 100644 index ff6a1b4..0000000 --- a/lib/RECURSIVE_COMPLETION.md +++ /dev/null @@ -1,127 +0,0 @@ -## 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 - ↑ - 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. \ No newline at end of file diff --git a/lib/miaou.completion b/lib/miaou.completion index 668b6d5..4a193dc 100644 --- a/lib/miaou.completion +++ b/lib/miaou.completion @@ -1,5 +1,9 @@ function _incus_container { - incus list --format csv --columns n + incus list --format csv --columns n type=CONTAINER +} + +function _incus_running_container { + incus list --format csv --columns n type=CONTAINER state=RUNNING } function _active_users { @@ -15,7 +19,7 @@ function _miaou_login { case $COMP_CWORD in 1) # first argument — containers - COMPREPLY=($(compgen -W "$(_incus_container)" -- "$cur")) + COMPREPLY=($(compgen -W "$(_incus_running_container)" -- "$cur")) ;; 2) # second argument - options @@ -42,10 +46,10 @@ function _miaou_exec() { [[ ${COMP_WORDS[$i]} == "--" ]] && dashdash_pos=$i done - # complete container names + # complete running container names if ((COMP_CWORD == 1)); then # first argument — containers - COMPREPLY=($(compgen -W "$(_incus_container)" -- "$cur")) + COMPREPLY=($(compgen -W "$(_incus_running_container)" -- "$cur")) return fi @@ -119,5 +123,21 @@ EOF } +function _miaou_ls { + local cur="${COMP_WORDS[COMP_CWORD]}" + + if [[ $cur =~ ^- ]]; then + # options + COMPREPLY=(--vm) + else + if ((COMP_CWORD == 1)); then + # containers + COMPREPLY=(--vm $(compgen -W "$(_incus_container)" -- "$cur")) + fi + fi + +} + complete -F _miaou_login "miaou-login" complete -F _miaou_exec "miaou-exec" +complete -F _miaou_ls "miaou-ls" diff --git a/tools/miaou-ls b/tools/miaou-ls index baee62d..9fe3f8b 100755 --- a/tools/miaou-ls +++ b/tools/miaou-ls @@ -2,11 +2,41 @@ # CONSTANTS +CONTAINER_PREFIX= +TYPE=container + # FUNCTIONS +function usage { + echo "$(basename "$0") [CONTAINER_PREFIX] < --vm >" +} + +function parse_options { + while [[ $# -gt 0 ]]; do + case "$1" in + --help | -h) + usage && exit 0 + ;; + --vm) + TYPE='VIRTUAL-MACHINE' + ;; + *) + if [[ -z $CONTAINER_PREFIX ]]; then + CONTAINER_PREFIX=$1 + else + echo >&2 "Unknown option: $1" && usage && exit 2 + fi + ;; + esac + + shift 1 # Move to the next argument + done +} + function ls { - incus list -c ns4 -f compact,noheader + incus list -c ns4 -f compact,noheader "$CONTAINER_PREFIX" type=$TYPE } # MAIN +parse_options "$@" ls