Skip to content

Commit

Permalink
vi_nmap: support bash-5.3 rlfunc "bash-vi-complete"
Browse files Browse the repository at this point in the history
* 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"
  • Loading branch information
akinomyoga committed Dec 24, 2024
1 parent d7ec488 commit 55e0ee7
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 20 deletions.
1 change: 1 addition & 0 deletions docs/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 42 additions & 5 deletions lib/core-complete.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 "$?"
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/core-decode.emacs-rlfunc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/core-decode.vi_imap-rlfunc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion lib/core-decode.vi_nmap-rlfunc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
84 changes: 70 additions & 14 deletions lib/keymap.vi.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down Expand Up @@ -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

Expand Down
98 changes: 98 additions & 0 deletions note.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 の方を見ても何処にも
補完の話は書かれていない。<tab> の振る舞いの説明についても書かれていない
(insert mode で生の tab を入力すること以外は)。<control>-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

Expand Down

0 comments on commit 55e0ee7

Please sign in to comment.