diff --git a/lib/core-complete.sh b/lib/core-complete.sh index e14d879d..638a3939 100644 --- a/lib/core-complete.sh +++ b/lib/core-complete.sh @@ -2498,6 +2498,9 @@ function ble/complete/progcomp/.compgen { if [[ $is_special_completion ]]; then # -I, -D, etc. ble/util/assign compdef 'builtin complete -p "$compcmd" 2>/dev/null' + elif ble/syntax:bash/simple-word/is-simple "$compcmd"; then + # 既に呼び出し元で quote されている想定 + ble/util/assign compdef "builtin complete -p -- $compcmd 2>/dev/null" else ble/util/assign compdef 'builtin complete -p -- "$compcmd" 2>/dev/null' fi @@ -2636,12 +2639,72 @@ function ble/complete/progcomp/.compline-rewrite-command { [[ $1 != "$ocmd" ]] || (($#>=2)) || return 1 local IFS=$_ble_term_IFS local ins="$*" - comp_line=$ins${comp_line:${#ocmd}} - ((comp_point-=${#ocmd},comp_point<0&&(comp_point=0),comp_point+=${#ins})) + if (($#==0)); then + # コマンド除去 (aliasで空に展開された時) + local ret; ble/string#ltrim "${comp_line:${#ocmd}}" + ((comp_point-=${#comp_line}-${#ret})) + comp_line=$ret + else + comp_line=$ins${comp_line:${#ocmd}} + ((comp_point-=${#ocmd})) + fi + ((comp_point<0&&(comp_point=0),comp_point+=${#ins})) comp_words=("$@" "${comp_words[@]:1}") ((comp_cword&&(comp_cword+=$#-1))) } +function ble/complete/progcomp/.split-alias-words { + local tail=$1 + local rex_redir='^'$_ble_syntax_bash_RexRedirect + local rex_word='^'$_ble_syntax_bash_simple_rex_element'+' + local rex_delim=$'^[\n;|&]' + local rex_spaces=$'^[ \t]+' + local rex_misc='^[<>()]+' + + local -a words=() + while [[ $tail ]]; do + if [[ $tail =~ $rex_redir && $tail != ['<>']'('* ]]; then + ble/array#push words "$BASH_REMATCH" + tail=${tail:${#BASH_REMATCH}} + elif [[ $tail =~ $rex_word ]]; then + local w=$BASH_REMATCH + tail=${tail:${#w}} + if [[ $tail && $tail != ["$_ble_term_IFS;|&<>()"]* ]]; then + local s=${tail%%["$_ble_term_IFS"]*} + tail=${tail:${#s}} + w=$w$s + fi + ble/array#push words "$w" + elif [[ $tail =~ $rex_delim ]]; then + words=() + tail=${tail:${#BASH_REMATCH}} + elif [[ $tail =~ $rex_spaces ]]; then + tail=${tail:${#BASH_REMATCH}} + elif [[ $tail =~ $rex_misc ]]; then + ble/array#push words "$BASH_REMATCH" + tail=${tail:${#BASH_REMATCH}} + else + local w=${tail%%["$_ble_term_IFS"]*} + ble/array#push words "$w" + tail=${tail:${#w}} + fi + done + + # skip assignments/redirections + local i=0 rex_assign='^[a-zA-Z_0-9]+(\['$_ble_syntax_bash_simple_rex_element'*\])?\+?=' + while ((i<${#words[@]})); do + if [[ ${words[i]} =~ $rex_assign ]]; then + ((i++)) + elif [[ ${words[i]} =~ $rex_redir && ${words[i]} != ['<>']'('* ]]; then + ((i+=2)) + else + break + fi + done + + ret=("${words[@]:i}") +} + ## @fn ble/complete/progcomp cmd opts ## 補完指定を検索して対応する補完関数を呼び出します。 ## @var[in] comp_line comp_words comp_point comp_cword @@ -2654,30 +2717,52 @@ function ble/complete/progcomp { comp_words=("${tmp[@]}") local -a alias_args=() - local alias_checked=' ' + [[ :$opts: == *:__recursive__:* ]] || + local alias_checked=' ' while :; do - if ble/is-function "ble/cmdinfo/complete:$cmd"; then - ble/complete/progcomp/.compline-rewrite-command "$cmd" "${alias_args[@]}" - "ble/cmdinfo/complete:$cmd" "$opts" + + # @var cmd ... 元のコマンド名 + # @var ucmd ... simple-word/eval したコマンド名 + # @var qcmds ... simple-word/eval x quote-word したコマンド + local ret ucmd qcmds + ucmd=$cmd qcmds=("$cmd") + if ble/syntax:bash/simple-word/is-simple "$cmd" && + ble/syntax:bash/simple-word/eval "$cmd" noglob && + [[ $ret != "$cmd" || ${#ret[0]} -ne 1 ]]; then + + ucmd=${ret[0]} qcmds=() + local word + for word in "${ret[@]}"; do + ble/string#quote-word "$word" quote-empty + ble/array#push qcmds "$ret" + done + fi + + if ble/is-function "ble/cmdinfo/complete:$ucmd"; then + ble/complete/progcomp/.compline-rewrite-command "${qcmds[@]}" "${alias_args[@]}" + "ble/cmdinfo/complete:$ucmd" "$opts" return "$?" - elif [[ $cmd == */?* ]] && ble/is-function "ble/cmdinfo/complete:${cmd##*/}"; then - ble/complete/progcomp/.compline-rewrite-command "${cmd##*/}" "${alias_args[@]}" - "ble/cmdinfo/complete:${cmd##*/}" "$opts" + elif [[ $ucmd == */?* ]] && ble/is-function "ble/cmdinfo/complete:${ucmd##*/}"; then + ble/string#quote-word "${ucmd##*/}"; qcmds[0]=$ret + ble/complete/progcomp/.compline-rewrite-command "${qcmds[@]}" "${alias_args[@]}" + "ble/cmdinfo/complete:${ucmd##*/}" "$opts" return "$?" - elif builtin complete -p "$cmd" &>/dev/null; then - ble/complete/progcomp/.compline-rewrite-command "$cmd" "${alias_args[@]}" + elif builtin complete -p "$ucmd" &>/dev/null; then + ble/complete/progcomp/.compline-rewrite-command "${qcmds[@]}" "${alias_args[@]}" ble/complete/progcomp/.compgen "$opts" return "$?" - elif [[ $cmd == */?* ]] && builtin complete -p "${cmd##*/}" &>/dev/null; then - ble/complete/progcomp/.compline-rewrite-command "${cmd##*/}" "${alias_args[@]}" + elif [[ $ucmd == */?* ]] && builtin complete -p "${ucmd##*/}" &>/dev/null; then + ble/string#quote-word "${ucmd##*/}"; qcmds[0]=$ret + ble/complete/progcomp/.compline-rewrite-command "${qcmds[@]}" "${alias_args[@]}" ble/complete/progcomp/.compgen "$opts" return "$?" elif # bash-completion の loader を呼び出して遅延補完設定をチェックする。 - ble/function#try __load_completion "${cmd##*/}" &>/dev/null && - builtin complete -p "${cmd##*/}" &>/dev/null + ble/function#try __load_completion "${ucmd##*/}" &>/dev/null && + builtin complete -p "${ucmd##*/}" &>/dev/null then - ble/complete/progcomp/.compline-rewrite-command "${cmd##*/}" "${alias_args[@]}" + ble/string#quote-word "${ucmd##*/}"; qcmds[0]=$ret + ble/complete/progcomp/.compline-rewrite-command "${qcmds[@]}" "${alias_args[@]}" ble/complete/progcomp/.compgen "$opts" return "$?" fi @@ -2688,7 +2773,21 @@ function ble/complete/progcomp { local ret ble/util/expand-alias "$cmd" - ble/string#split-words ret "$ret" + [[ $ret == "$cmd" ]] && break + ble/complete/progcomp/.split-alias-words "$ret" + if ((${#ret[@]}==0)); then + # alias 展開により内容が消滅した時は次の単語をコマンドとして再度展開を繰り返す + ble/complete/progcomp/.compline-rewrite-command "${alias_args[@]}" + if ((${#comp_words[@]})); then + if ((comp_cword==0)); then + ble/complete/source:command + else + ble/complete/progcomp "${comp_words[0]}" "__recursive__:$opts" + fi + fi + return $? + fi + [[ $alias_checked != *" $ret "* ]] || break cmd=$ret ((${#ret[@]}>=2)) && diff --git a/lib/core-syntax.sh b/lib/core-syntax.sh index 4d2fd3a6..092412d0 100644 --- a/lib/core-syntax.sh +++ b/lib/core-syntax.sh @@ -1484,7 +1484,7 @@ function ble/syntax:bash/simple-word/eval/.cache-load { ## cached ## 展開結果をキャッシュします。 ## -## 以下はパス名展開の起こる可能性にある単語に大して有効です。 +## 以下はパス名展開の起こる可能性にある単語に対して有効です。 ## ## stopcheck ## ユーザー入力があった場合に中断します。 diff --git a/memo/ChangeLog.md b/memo/ChangeLog.md index 71173cac..395a4d53 100644 --- a/memo/ChangeLog.md +++ b/memo/ChangeLog.md @@ -46,6 +46,7 @@ - util (`bleopt`): support option `-r` and `-u` and wildcards in option names - util (`blehook`): hide internal hooks by default and support option `-a` - util, color: fix argument analysis of `bleopt`, `blehook`, and `ble-face` (fixup c94d292) `#D1571` bb53271 +- progcomp: support quoted commands and better `progcomp_alias` `#D1581` `#D1583` 0000000 ## Changes @@ -74,7 +75,7 @@ - main: preserve user-space overridden builtins `#D1519` 0860be0 - util (`ble/util/type`): fix a bug that aliases are not properly highlighted (reported by 3ximus) `#D1526` 45b30a7 - main: preserve user's `expand_aliases` and allow aliases in internal space (fixup 0860be0) `#D1574` afc4112 - - main: main: fix expand_aliases unset on ble-reload (fixup afc4112) `#D1577` 0000000 + - main: main: fix expand_aliases unset on ble-reload (fixup afc4112) `#D1577` 3417388 - syntax (`ble/syntax:bash/simple-word/eval`): optimize large array passing (motivated by timjrd) `#D1522` c89aa23 - main: accept non-regular files as `blerc` and add option `--norc` `#D1530` 7244e2f - prompt: let `stderr` pass through to tty in evaluating `PS0` (reported by tycho-kirchner) `#D1541` 24a88ce @@ -124,7 +125,7 @@ - sabbrev (`ble-sabbrev`): fix delayed output before the initialization `#D1573` 5d85238 - main: fix the workaround for `set -u` `#D1575` 76073a9 - history: fix the workaround for bash-3.0 bug of reducing histories `#D1576` 15c9133 -- syntax: fix a bug that argument completion is attempted in nested commands (reported by huresche) `#D1579` 0000000 +- syntax: fix a bug that argument completion is attempted in nested commands (reported by huresche) `#D1579` 301d40f ## Compatibility @@ -157,7 +158,7 @@ - tui, edit: add a new render mode for full-screen applications 817889d - main: prefer `nawk` over `mawk` and `gawk` `#D1523` `#D1524` c89aa23 - main (`ble/bin/.freeze-utility-path`): fix unupdated temporary implementations `#D1528` c70a3b4 - - util (`ble/util/assign`): work around subshell conflits `#D1578` 0000000 + - util (`ble/util/assign`): work around subshell conflicts `#D1578` 6e4bb12 - test (`test-canvas`): fix dependency on `ext/contra` `#D1525` c89aa23 - util: inherit special file descriptors `#D1552` 98835b5 - global: use `_ble_term_IFS` `#D1557` d23ad3c diff --git a/note.txt b/note.txt index d94d5f75..5cdbfdc3 100644 --- a/note.txt +++ b/note.txt @@ -1442,20 +1442,6 @@ bash_tips 2021-05-29 - * complete: そもそも -F に指定する事のできる文字列には制限がある筈である。こ - れを満たさない場合には強制的に其処で終端させる等の対策が必要なのではないか。 - - 然し、;&| 等が含まれていた場合にどの様に取り扱うべきかは不明である。明らか - に何かが間違っているけれども、だからと言ってその直前までを関数名と捉えるの - にも無理がある様な気がする。然し、それ以外に解釈のしようがないとも言える。 - - * complete: quote していると補完関数が認識されない。例えば git co とすると OK - だが 'git' co とすると反応しない。然し実の所、これは普通の bash でも同様で - ある。普通の bash でも 'git' としている時には git の補完関数は呼び出されな - い。 - - * complete: alias の展開結果で変数代入を除去するべきではないか - * syntax: \q を着色したい 2021-05-28 @@ -4706,6 +4692,86 @@ bash_tips 2021-05-29 + * complete: quote されているコマンド名に対しても補完関数を適切に探索 [#D1583] + + quote していると補完関数が認識されない。例えば git co とすると OK だが + 'git' co とすると反応しない。然し実の所、これは普通の bash でも同様である。 + 普通の bash でも 'git' としている時には git の補完関数は呼び出されない。 + + unquote した上で補完を探索するべきである気がする。但し、alias 展開などに関 + しては quote された物で実行するという事。comp_line, comp_words に入っている + のは展開前の quote された物であるので余計な事は考えなくて良い。 + + * [reject] complete: そもそも -F に指定する事のできる文字列には制限がある筈である [#D1582] + + これを満たさない場合には強制的に其処で終端させる等の対策が必要なのではない + か。 + + 然し、;&| 等が含まれていた場合にどの様に取り扱うべきかは不明である。明らか + に何かが間違っているけれども、だからと言ってその直前までを関数名と捉えるの + にも無理がある様な気がする。然し、それ以外に解釈のしようがないとも言える。 + + 或いは途中に空白がある場合には ble.sh の拡張として関数に余分の引数を渡す事 + にする? 然し、これは最新の bash-5.1 では使えない事なので古い Bash だけで使 + える機能として提供しても仕方がない。結局これは失敗するべきなのではないか。 + そして失敗するのだとしたら現状の動作のままで良いという事の気がする。 + + * complete: alias の展開結果で変数代入を除去するべきではないか [#D1581] + + というより実の所 simple-word でできるだけ解析していくべきの気がする。と思っ + たが、 ; や変数代入があった時の振る舞い、文法エラーがある場合の振る舞いなど + は用途によってまちまちなのでこんk内は core-complete.sh の側で実装する事に + した。 + + simple-word の終端しない版というのはあっただろうか。なかったが + simple_rex_element をそのまま使えば良い。 + + * __load_completion を呼び出すと -D に相当する処理が勝手に入ってしまう。 + complete が駄目な気がする。自前で補完定義を探してロードする関数を作ってみ + たが、その後で気づいたのは __load_completion は別に見つからなかった時に勝 + 手に既定の定義を使う訳ではないのだという事。 + + 以下の自前の定義は結局使われる事はないのだった。 + + | _ble_complete_progcomp_bashcomp_initialized= + | _ble_complete_progcomp_bashcomp_dirs=() + | function ble/complete/progcomp/.bashcomp-initialize { + | if [[ ! $_ble_complete_progcomp_bashcomp_initialized ]]; then + | _ble_complete_progcomp_bashcomp_initialized=1 + | + | local user repo bin + | user=${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions + | [[ $BASH_COMPLETION_USER_DIR ]] && user=$BASH_COMPLETION_USER_DIR/completions + | ble/string#split repo : "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" + | bin=./completions + | [[ $BASH_SOURCE == */* ]] && user=${BASH_SOURCE%/*}/completions + | + | local path + | for path in "$user" "${repo[@]}" "$bin"; do + | [[ -d $path ]] && ble/array#push _ble_complete_progcomp_bashcomp_dirs "$path" + | done + | fi + | } + | function ble/complete/progcomp/.load-bash-completion { + | local cmd=$1 + | ble/is-function __load_completion || return 1 + | ble/complete/progcomp/.bashcomp-initialize + | + | local dir file + | for dir in "${dirs[@]}"; do + | for file in "$dir"/{"$cmd","$cmd.bash","_$cmd"}; do + | [[ -s $file ]] || continue + | source "$file" + | return 0 + | done + | done + | + | ((_ble_base>=40000)) || return 1 + | ble/is-function _filedir_xspec && + | [[ ${_xspecs[$cmd]+set} ]] && + | complete -F _filedir_xspec "$cmd" + | } + * complete: "\a" 等のコマンド名の補完で問題が起こる (reported by huresche) [#D1579] https://github.com/akinomyoga/ble.sh/issues/116