From 55e0ee71aaf5956f708f35b3a05b318f58c16427 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Mon, 23 Dec 2024 20:38:48 +0900 Subject: [PATCH] vi_nmap: support bash-5.3 rlfunc "bash-vi-complete" * vi_nmap: also support "vi-complete" * complete: change the behavior of the glob completions * complete: fix variable leak of "ret" in "ble/complete/source:glob" --- docs/ChangeLog.md | 1 + lib/core-complete.sh | 47 ++++++++++++-- lib/core-decode.emacs-rlfunc.txt | 1 + lib/core-decode.vi_imap-rlfunc.txt | 1 + lib/core-decode.vi_nmap-rlfunc.txt | 3 +- lib/keymap.vi.sh | 84 ++++++++++++++++++++----- note.txt | 98 ++++++++++++++++++++++++++++++ 7 files changed, 215 insertions(+), 20 deletions(-) diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index abe85606..299dba0b 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -19,6 +19,7 @@ - progcomp: use Bash 5.3 `compgen -V` for completions with newlines (motivated by RBT22) `#D2253` 0e8c388a - main: fix attach failure with `--attach=prompt` in Bash 5.3 POSIX mode `#D2267` 49845707 - syntax: fix a problem that `$_` is not preserved `#D2269` e053690d + - keymap/vi: support bash-5.3 readline bindable function `bash-vi-complete` in `vi_nmap` `#D2305` xxxxxxxx - bgproc: support opts `kill9-timeout=TIMEOUT` `#D2034` 3ab41652 - progcomp(cd): change display name and support mandb desc (requested by EmilySeville7cfg) `#D2039` 74402098 - cmdspec: add completion options for builtins (motivated by EmilySeville7cfg) `#D2040` 9bd24691 diff --git a/lib/core-complete.sh b/lib/core-complete.sh index bf0910d4..b857416f 100644 --- a/lib/core-complete.sh +++ b/lib/core-complete.sh @@ -6370,6 +6370,26 @@ function ble/complete/context:hostname/generate-sources { ble/complete/context/overwrite-sources hostname } +# Note: The behavior of the glob completion in Bash seems inconsistent or +# really complicated, so we simplify the behavior so that the user can predict +# the consequence. In Bash, * is appended to the pattern when one of the +# following condition is met: +# +# * An argument to the readline bindable function is specified. +# * "glob-complete-word" is called inside the emacs editing mode. +# * "bash-vi-command" is attempted on a word that does not contain any glob +# characters +# +# Note: Even though "glob-complete-word" appends '*', the bindable functions +# "glob-expand-word" and "glob-list-word" do not append '*' unles an argument +# is supplied. This does not satisfy the command duration. +# +# In this implementation, we first attempt the pathname expansion without +# appending '*', and if it doesn't produce any words or only produces the +# original word, we attempt another pathname expansion by appending '*'. When +# an argument is specified, we attempt the pathname expansion with suffix '*' +# from the beginning. +# function ble/complete/context:glob/generate-sources { comp_type=$comp_type:raw ble/complete/context:syntax/generate-sources || return "$?" @@ -6379,9 +6399,25 @@ function ble/complete/source:glob { [[ $comps_flags == *v* ]] || return 1 [[ :$comp_type: == *:[maA]:* ]] && return 1 - local pattern=$COMPV - ble/complete/source/eval-simple-word "$pattern"; (($?==148)) && return 148 - if ((!${#ret[@]})) && [[ $pattern != *'*' ]]; then + local ret pattern=$COMPV + + # We first attempt pathname expansion without appending '*'. + local prefix_expansion= + if [[ ${comp_edit_arg-} ]]; then + # Note: When an edit arg is specified, we attempt the pathname expansion + # with suffix '*' from the beginning. This mimics Bash's behavior. + prefix_expansion=1 + else + ble/complete/source/eval-simple-word "$pattern"; (($?==148)) && return 148 + if ((!${#ret[@]})) && [[ $pattern != *'*' ]]; then + prefix_expansion=1 + elif ((${#ret[@]}==1)) && [[ $ret == "$pattern" ]]; then + prefix_expansion=1 + fi + fi + + # We then attempt pathname expansion with suffix '*' if necessary. + if [[ $prefix_expansion ]]; then ble/complete/source/eval-simple-word "$pattern*"; (($?==148)) && return 148 fi @@ -8257,8 +8293,9 @@ _ble_complete_state= ## 一部の補完源で complete_limit に達した時に補完全体を中止します。 ## function ble/widget/complete { - local opts=$1 - ble-edit/content/clear-arg + local opts=$1 arg= + ble-edit/content/get-arg + local comp_edit_arg=$arg # Note: referenced by source:glob local state=$_ble_complete_state _ble_complete_state=start diff --git a/lib/core-decode.emacs-rlfunc.txt b/lib/core-decode.emacs-rlfunc.txt index 5e1293c1..79b5ed22 100644 --- a/lib/core-decode.emacs-rlfunc.txt +++ b/lib/core-decode.emacs-rlfunc.txt @@ -8,6 +8,7 @@ backward-delete-char delete-region-or delete-backward-char backward-kill-line kill-backward-line backward-kill-word kill-backward-cword backward-word backward-cword +bash-vi-complete - beginning-of-history history-beginning beginning-of-line beginning-of-line bracketed-paste-begin bracketed-paste diff --git a/lib/core-decode.vi_imap-rlfunc.txt b/lib/core-decode.vi_imap-rlfunc.txt index cdc91960..4399964f 100644 --- a/lib/core-decode.vi_imap-rlfunc.txt +++ b/lib/core-decode.vi_imap-rlfunc.txt @@ -8,6 +8,7 @@ backward-delete-char vi_imap/delete-region-or vi_imap/delete-backwa backward-kill-line kill-backward-line backward-kill-word kill-backward-cword backward-word backward-sword +bash-vi-complete - beginning-of-history history-beginning beginning-of-line beginning-of-line bracketed-paste-begin vi_imap/bracketed-paste diff --git a/lib/core-decode.vi_nmap-rlfunc.txt b/lib/core-decode.vi_nmap-rlfunc.txt index b370c996..48689458 100644 --- a/lib/core-decode.vi_nmap-rlfunc.txt +++ b/lib/core-decode.vi_nmap-rlfunc.txt @@ -8,6 +8,7 @@ backward-delete-char vi-rlfunc/backward-delete-char backward-kill-line vi-rlfunc/backward-kill-line backward-kill-word vi-rlfunc/backward-kill-word backward-word vi-command/backward-vword +bash-vi-complete vi-rlfunc/bash-vi-complete beginning-of-history vi-command/history-beginning beginning-of-line vi-command/beginning-of-line bracketed-paste-begin vi-command/bracketed-paste @@ -138,7 +139,7 @@ vi-change-char vi_nmap/replace-char vi-change-to vi-rlfunc/change-to vi-char-search vi-rlfunc/char-search vi-column vi-command/nth-column -vi-complete vi_nmap/complete +vi-complete vi-rlfunc/vi-complete vi-delete vi_nmap/kill-forward-char vi-delete-to vi-rlfunc/delete-to vi-editing-mode vi_nmap/insert-mode diff --git a/lib/keymap.vi.sh b/lib/keymap.vi.sh index 92688052..88f2d407 100644 --- a/lib/keymap.vi.sh +++ b/lib/keymap.vi.sh @@ -272,23 +272,38 @@ function ble/widget/vi_imap/menu-complete { } function ble/widget/vi_nmap/complete { - local ARG FLAG REG; ble/keymap:vi/get-arg 1 - ble-edit/content/eolp || ((_ble_edit_ind++)) + # We reset the edit argument for "ble/widget/complete". + local ARG FLAG REG; ble/keymap:vi/get-arg '' + _ble_edit_arg=$ARG + + # Note: We adjust the cursor position unless we are at the end of a line. To + # be consistent with Bash's "bash-vi-complete" and "vi-complete", we do not + # adjust the cursor position also when the current position is tab or + # space. This behavior seems also natural for the user's perspective, + # though it introduces a non-trivial behavioral variation. + ble-edit/content/eolp || + [[ ${_ble_edit_str:_ble_edit_ind:1} == ["$_ble_term_IFS"] ]] || + ((_ble_edit_ind++)) + local keymap=$_ble_decode_keymap ble/widget/complete "$@"; local ext=$? if [[ $_ble_decode_keymap == "$keymap" ]]; then - # Note: We record the editing area `[`] through - # "ble/keymap:vi/complete/insert.hook", so we do not need to manually set - # up the edit area by calling "ble/keymap:vi/mark/{start,end}-edit-area". - # "ble-edit/undo/add" is also called through - # "ble/keymap:vi/mark/set-previous-edit-area" called from - # "ble/keymap:vi/complete/insert.hook". - # Note: if "ble/widget/complete" enters another mode such as the - # menu-complete mode, we do not try to adjust the state here. Instead, - # we adjust the state in "ble/complete/menu_complete/exit" after the - # corresponding "ble/decode/keymap/pop". - ble-edit/content/bolp || ((_ble_edit_ind--)) - ble/keymap:vi/adjust-command-mode + if [[ :$1: == *:vi_nmap/insert-mode:* ]]; then + ble/widget/vi_nmap/.insert-mode + else + # Note: We record the editing area `[`] through + # "ble/keymap:vi/complete/insert.hook", so we do not need to manually set + # up the edit area by calling "ble/keymap:vi/mark/{start,end}-edit-area". + # "ble-edit/undo/add" is also called through + # "ble/keymap:vi/mark/set-previous-edit-area" called from + # "ble/keymap:vi/complete/insert.hook". + # Note: if "ble/widget/complete" enters another mode such as the + # menu-complete mode, we do not try to adjust the state here. Instead, + # we adjust the state in "ble/complete/menu_complete/exit" after the + # corresponding "ble/decode/keymap/pop". + ble-edit/content/bolp || ((_ble_edit_ind--)) + ble/keymap:vi/adjust-command-mode + fi fi return "$ext" } @@ -352,6 +367,47 @@ function ble-decode/keymap:vi_imap/bind-complete { ble-bind -f 'C-x g' 'vi_imap/complete show_menu:context=glob' } +function ble/widget/vi-rlfunc/vi-complete { + local key=0 + ((${#KEYS[@]})) && key=${KEYS[${#KEYS[@]}-1]:-0} + + if ((key==0x2a)); then # '*' + ble/widget/vi_nmap/complete "insert-all:vi_nmap/insert-mode:$@" + elif ((key==0x3d)); then # '=' + ble/widget/vi_nmap/complete "show-menu:$@" + elif ((key==0x5c)); then # '\' + ble/complete/menu/clear + ble/widget/vi_nmap/complete "vi_nmap/insert-mode:$@" + else + # Note: Bash does not enter the insert mode in the other cases, but it + # seems inconsistent. We here enter the insert mode also with the other + # types of completions. In our implementation, '=' is the only exception, + # which makes sense because "show-menu" only shows the status in the info + # panel and doesn't change the command line. The users who want the Bash + # behavior that doesn't enter the insert mode should use widget + # "vi_nmap/complete". + ble/widget/vi_nmap/complete "vi_nmap/insert-mode:$@" + fi +} + +function ble/widget/vi-rlfunc/bash-vi-complete { + local key=0 + ((${#KEYS[@]})) && key=${KEYS[${#KEYS[@]}-1]:-0} + + if ((key==0x2a)); then # '*' + ble/widget/vi_nmap/complete "context=glob:insert-all:vi_nmap/insert-mode:$@" + elif ((key==0x3d)); then # '=' + ble/widget/vi_nmap/complete "context=glob:show-menu:$@" + elif ((key==0x5c)); then # '\' + ble/complete/menu/clear + ble/widget/vi_nmap/complete "context=glob:vi_nmap/insert-mode:$@" + else + # Note: This implementation enters the insert mode in this case. See the + # code comment in ble/widget/vi-rlfunc/vi-complete. + ble/widget/vi_nmap/complete "vi_nmap/insert-mode:$@" + fi +} + #------------------------------------------------------------------------------ # modes diff --git a/note.txt b/note.txt index a01e10c5..46dc1981 100644 --- a/note.txt +++ b/note.txt @@ -7703,6 +7703,104 @@ bash_tips 2024-12-23 + * edit: bash-5.3 "bash-vi-complete" を実装 [#D2305] + + bash-vi-complete は vi-complete に似た readline bindable function の様だが + そもそも vi-complete が実装されていない。 + + ? vi-complete と complete の違いは何か? これは Bash の実装を確認する必要が + ある → 実はこれは結構振る舞いが異なるのでは。key によって振る舞いを切り + 替える様になっている。更に bash-vi-complete という物もあって、その違いは + よく分からない。 + + * bash-vi-complete は何なのか説明を探そうとしたが見つからない。そもそも + readline に備わっている昨日なので man readline には見つからない。一方で、 + devel の man bash の方にも載っていない。ソースコードを見て振る舞いを調べ + るしかないのか? 一応 bashline.c (bash_vi_complete) の最初にコメントで何か + 書かれているが結局よく分からない。rl_vi_complete との違いは bash の glob + を使うという事? 或いは、途中の文字列も glob pattern として展開してしまう + という事? + + 取り合えず Bash における両実装を確認する。vi-complete と bash-vi-complete + は両者とも最初にカーソル位置を修正している。前方が空白でない場合にカーソル + 位置を一つ進めている。 + + ? done: うーん。これについては vi_nmap/complete も同様に実装するべき? + + と思ったが、まあ、敢えて vi-complete ではなく complete を選んだ場合には + やはり vi として consistent な振る舞いということで現在の振る舞いを保持 + する? 勝手にユーザーにとって non-trivial な振る舞いをするのも考え物であ + る… + + が空白の位置にカーソルが置かれている場合には文字を一つ進めて補完を試み + るのも変な感じがするのでやはり既定で文字を進めない様にする? + + + 構造としては類似だが vi-complete と bash-vi-complete では実際に実行する操作 + が全く異なる? よく分からないので先ずは vi-complete について確認する事にする。 + vi-complete では次に key に応じて振る舞いを分けている。`*` -> `*`, `=` -> + `?`, `\\` -> TAB に変換して rl_internal_complete に渡している。具体的な振る + 舞いについては rl_internal_complete のコメントに書かれている。 + + > `*' means insert all of the possible completions. + > `?' means list the possible completions. + > TAB means do standard completion. + + うーん。つまり… `\\` を指定した場合には既存の補完を削除して改めて最初から + 補完を実行するという事? ble/complete/menu/clear を呼び出してから実行すると + いう事だろうか。 + + 更に、'*' と '\\' の場合には挿入モードに移行している。どういう事だろうか? + + ? これが POSIX の要求という事なのだろうか? 然し XCU を見ても set -o vi で + vi editor になるという事が書かれているだけで TAB がどう振る舞うべきか等に + ついては何も記述されていない気がする? そもそも XCU では補完の話はしていな + い。completion で色々記述があるが何れも "コマンド実行完了" や "文法として + のコマンド終わり" を意味している様だ。一方で XCU vi の方を見ても何処にも + 補完の話は書かれていない。 の振る舞いの説明についても書かれていない + (insert mode で生の tab を入力すること以外は)。-I についての記述 + もない。うーん。謎だ。 + + 具体的に振る舞いを確認してみても、'*' と '\\' の時にだけ insert-mode に移行 + して、'?' の時などには insert-mode に移行しないようだ。これは変だ。或いは、 + これは話が逆で '?' の時だけ insert-mode に移行しないという事で、その他の場 + 合には予期しない呼び出しだから適当に処理しているという事だろうか。 + + さて、次に bash-vi-complete の実装を確認する。うーん。これは結局既存の + widget たちを呼び出している様な気がする。という事は、ble.sh 側の実装でも単 + に opts に context=glob を追加して呼び出せば良い様な気がする。 + + bash-vi-complete では他に bigWord に対する処理という物が加わっている。うー + ん。どうやら現在の単語を抽出して、現在の単語に glob 文字が含まれていなけれ + ば glob pattern の末尾に * を追加している様だ。うーん。面倒だ。 + + ? そもそも現在の実装の context=glob はどの様に振る舞っていたか。先ず展開を + 試みている。それで何も生成されなければ * を追加して再展開を実行している。 + + これは bash-vi-complete の動作と同じになるだろうか? うーんならない気がす + る。例えば glob 文字を含まない場合を考える。この場合は、bash-vi-complete + だとそれから始まるファイルが全て生成されるが、ble.sh の context=glob では + 完全一致するファイルが存在する時ただ一個の候補が生成されてそれに確定する。 + glob 文字を含む場合には bash-vi-complete だと一致するファイルが存在しない + 時に何もしない事になるが、ble.sh の context=glob では末尾に * をつけて再 + 展開を行うのでファイルが生成される場合がある。 + + そもそも bash の M-g はどの様に振る舞うのかと調べてみたが glob 文字が含ま + れていても問答無用で末尾に * をつけて生成している気がする。従って候補が生 + 成されなかった時にだけ * を末尾につけて再展開するというのは ble.sh による + 拡張である。 + + ? bash の M-g の振る舞いは何処で実装されているのか。もしこれが編集関数に + よって実装されているのだとしたら bash-vi-complete で * を末尾に付加して + いるのは果たして必要な物なのだろうか? + + うーん。調べてみると bash は emacs mode でかつ M-g の時だけ * を末尾に + 付加する様である。M-* や C-x* など他の場合には * を末尾には付加しない。 + 不思議な仕様だし余り consistent とは言えない。 + + うーん。bash の実装が何か変な気がするので、この * が付加される条件は勝手 + に単純化する事にする。 + * edit: more widgets in vi_nmap (requested by excited-bore) [#D2304] https://github.com/akinomyoga/ble.sh/issues/534