From cdee8ea8ec52bb107002579c54caf76d88333e5a Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Fri, 3 Jan 2025 03:33:24 +0900 Subject: [PATCH] [builtin/completion_osh] Support SparseArray COMPREPLY and COMP_ARGV (#2216) * [builtin/completion_osh] Support SparseArray COMPREPLY with `compgen -F` * [builtin/completion_osh] Support SparseArray COMP_ARGV with `compadjust` * SparseArray_GetValues is used to obtain comp_argv from the shell variable COMP_ARGV. This ignores all the unset elements and only picks up the set elements. Then the word index would be changed. This might not be desired. Maybe it is better to first convert it to BashArray and use the member strs? * Conversely, when there are unset elements in COMP_ARGV with BashArray, doesn't that cause problems in the later AdjustArg? --- builtin/completion_osh.py | 9 +++++-- core/completion.py | 16 +++++++------ spec/ble-idioms.test.sh | 49 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/builtin/completion_osh.py b/builtin/completion_osh.py index 8dc8a8e7e..65125e904 100644 --- a/builtin/completion_osh.py +++ b/builtin/completion_osh.py @@ -5,6 +5,7 @@ from _devbuild.gen.syntax_asdl import loc from _devbuild.gen.value_asdl import (value, value_e) +from core import bash_impl from core import completion from core import error from core import state @@ -454,9 +455,13 @@ def Run(self, cmd_val): # TODO: How does the user test a completion function programmatically? Set # COMP_ARGV? val = self.mem.GetValue('COMP_ARGV') - if val.tag() != value_e.BashArray: + if val.tag() == value_e.BashArray: + comp_argv = cast(value.BashArray, val).strs + elif val.tag() == value_e.SparseArray: + comp_argv = bash_impl.SparseArray_GetValues( + cast(value.SparseArray, val)) + else: raise error.Usage("COMP_ARGV should be an array", loc.Missing) - comp_argv = cast(value.BashArray, val).strs # These are the ones from COMP_WORDBREAKS that we care about. The rest occur # "outside" of words. diff --git a/core/completion.py b/core/completion.py index 4601712a6..2789c99cf 100755 --- a/core/completion.py +++ b/core/completion.py @@ -627,21 +627,23 @@ def Matches(self, comp): # unset it. Not changing it means there were no completions. # TODO: This writes over the command line; it would be better # to use an error object. - print_stderr('osh error: Ran function %r but COMPREPLY was unset' % - self.func.name) + print_stderr( + 'osh error: Ran function %r but COMPREPLY was unset' % + self.func.name) return - elif case(value_e.Str): val = cast(value.Str, UP_val) strs = [val.s] - elif case(value_e.BashArray): val = cast(value.BashArray, UP_val) strs = bash_impl.BashArray_GetValues(val) - + elif case(value_e.SparseArray): + val = cast(value.SparseArray, UP_val) + strs = bash_impl.SparseArray_GetValues(val) else: - print_stderr('osh error: COMPREPLY should be an array or a string, got %s' % - ui.ValType(val)) + print_stderr( + 'osh error: COMPREPLY should be an array or a string, got %s' + % ui.ValType(val)) return if 0: diff --git a/spec/ble-idioms.test.sh b/spec/ble-idioms.test.sh index 95bae8767..fc14f0eac 100644 --- a/spec/ble-idioms.test.sh +++ b/spec/ble-idioms.test.sh @@ -1064,6 +1064,55 @@ echo "[${a[@]: -4}][${a[*]: -4}]" ## END +#### SparseArray: compgen -F _set_COMPREPLY +case $SH in zsh|mksh|ash) exit ;; esac + +a=({0..9}) +unset -v 'a[2]' 'a[4]' 'a[6]' + +case ${SH##*/} in +osh) + eval '_set_COMPREPLY() { setglobal COMPREPLY = _a2sp(a); }' + ;; +*) + _set_COMPREPLY() { COMPREPLY=("${a[@]}"); } + ;; +esac + +compgen -F _set_COMPREPLY + +## STDOUT: +0 +1 +3 +5 +7 +8 +9 +## END + +## N-I zsh/mksh/ash STDOUT: +## END + + +#### SparseArray: compgen -F _set_COMPREPLY +case $SH in bash|zsh|mksh|ash) exit ;; esac + +a=(echo 'Hello,' 'Bash' 'world!') +var COMP_ARGV = _a2sp(a) +compadjust cur prev words cword +argv.py "$cur" "$prev" "$cword" +argv.py "${words[@]}" + +## STDOUT: +['world!', 'Bash', '3'] +['echo', 'Hello,', 'Bash', 'world!'] +## END + +## N-I bash/zsh/mksh/ash STDOUT: +## END + + #### SparseArray (YSH): $[a1 === a2] case $SH in bash|zsh|mksh|ash) exit ;; esac