patch 9.1.1068: getchar() can't distinguish between C-I and Tab Commit: https://github.com/vim/vim/commit/e0a2ab397fd13a71efec85b017d5d4d62baf7f63 Author: zeertzjq <zeert...@outlook.com> Date: Sun Feb 2 09:14:35 2025 +0100
patch 9.1.1068: getchar() can't distinguish between C-I and Tab Problem: getchar() can't distinguish between C-I and Tab. Solution: Add {opts} to pass extra flags to getchar() and getcharstr(), with "number" and "simplify" keys. related: #10603 closes: #16554 Signed-off-by: zeertzjq <zeert...@outlook.com> Signed-off-by: Christian Brabandt <c...@256bit.org> diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index e222d7c5a..46c3ba855 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -1,4 +1,4 @@ -*builtin.txt* For Vim version 9.1. Last change: 2025 Feb 01 +*builtin.txt* For Vim version 9.1. Last change: 2025 Feb 02 VIM REFERENCE MANUAL by Bram Moolenaar @@ -228,12 +228,12 @@ getbufvar({buf}, {varname} [, {def}]) getcellpixels() List get character cell pixel size getcellwidths() List get character cell width overrides getchangelist([{buf}]) List list of change list items -getchar([{expr}]) Number or String +getchar([{expr} [, {opts}]]) Number or String get one character from the user getcharmod() Number modifiers for the last typed character getcharpos({expr}) List position of cursor, mark, etc. getcharsearch() Dict last character search -getcharstr([{expr}]) String get one character from the user +getcharstr([{expr} [, {opts}]]) String get one character from the user getcmdcomplpat() String return the completion pattern of the current command-line completion getcmdcompltype() String return the type of the current @@ -3918,14 +3918,16 @@ getchangelist([{buf}]) *getchangelist()* Return type: list<any> -getchar([{expr}]) *getchar()* +getchar([{expr} [, {opts}]]) *getchar()* Get a single character from the user or input stream. - If {expr} is omitted, wait until a character is available. + If {expr} is omitted or is -1, wait until a character is + available. If {expr} is 0, only get a character when one is available. Return zero otherwise. If {expr} is 1, only check if a character is available, it is not consumed. Return zero if no character available. - If you prefer always getting a string use |getcharstr()|. + If you prefer always getting a string use |getcharstr()|, or + specify |FALSE| as "number" in {opts}. Without {expr} and when {expr} is 0 a whole character or special key is returned. If it is a single character, the @@ -3935,7 +3937,8 @@ getchar([{expr}]) *getchar()* starting with 0x80 (decimal: 128). This is the same value as the String "\<Key>", e.g., "\<Left>". The returned value is also a String when a modifier (shift, control, alt) was used - that is not included in the character. + that is not included in the character. |keytrans()| can also + be used to convert a returned String into a readable form. When {expr} is 0 and Esc is typed, there will be a short delay while Vim waits to see if this is the start of an escape @@ -3947,6 +3950,24 @@ getchar([{expr}]) *getchar()* Use getcharmod() to obtain any additional modifiers. + The optional argument {opts} is a Dict and supports the + following items: + + number If |TRUE|, return a Number when getting + a single character. + If |FALSE|, the return value is always + converted to a String, and an empty + String (instead of 0) is returned when + no character is available. + (default: |TRUE|) + + simplify If |TRUE|, include modifiers in the + character if possible. E.g., return + the same value for CTRL-I and <Tab>. + If |FALSE|, don't include modifiers in + the character. + (default: |TRUE|) + When the user clicks a mouse button, the mouse event will be returned. The position can then be found in |v:mouse_col|, |v:mouse_lnum|, |v:mouse_winid| and |v:mouse_win|. @@ -4062,17 +4083,9 @@ getcharsearch() *getcharsearch()* Return type: dict<any> -getcharstr([{expr}]) *getcharstr()* - Get a single character from the user or input stream as a - string. - If {expr} is omitted, wait until a character is available. - If {expr} is 0 or false, only get a character when one is - available. Return an empty string otherwise. - If {expr} is 1 or true, only check if a character is - available, it is not consumed. Return an empty string - if no character is available. - Otherwise this works like |getchar()|, except that a number - result is converted to a string. +getcharstr([{expr} [, {opts}]]) *getcharstr()* + The same as |getchar()|, except that this always returns a + String, and "number" isn't allowed in {opts}. Return type: |String| diff --git a/src/errors.h b/src/errors.h index 94675289c..d8b1ff68f 100644 --- a/src/errors.h +++ b/src/errors.h @@ -3172,7 +3172,8 @@ EXTERN char e_exists_compiled_can_only_be_used_in_def_function[] EXTERN char e_legacy_must_be_followed_by_command[] INIT(= N_("E1234: legacy must be followed by a command")); #ifdef FEAT_EVAL -// E1235 unused +EXTERN char e_bool_or_number_required_for_argument_nr[] + INIT(= N_("E1235: Bool or Number required for argument %d")); EXTERN char e_cannot_use_str_itself_it_is_imported[] INIT(= N_("E1236: Cannot use %s itself, it is imported")); #endif diff --git a/src/evalfunc.c b/src/evalfunc.c index 41444f497..69b6a4fa8 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -387,6 +387,20 @@ arg_bool(type_T *type, type_T *decl_type UNUSED, argcontext_T *context) return check_arg_type(&t_bool, type, context); } +/* + * Check "type" is a bool or a number. + */ + static int +arg_bool_or_nr(type_T *type, type_T *decl_type UNUSED, argcontext_T *context) +{ + if (type->tt_type == VAR_BOOL + || type->tt_type == VAR_NUMBER + || type_any_or_unknown(type)) + return OK; + arg_type_mismatch(&t_number, type, context->arg_idx + 1); + return FAIL; +} + /* * Check "type" is a list of 'any' or a blob. */ @@ -1195,6 +1209,7 @@ static argcheck_T arg24_count[] = {arg_string_or_list_or_dict, arg_any, arg_bool static argcheck_T arg13_cursor[] = {arg_cursor1, arg_number, arg_number}; static argcheck_T arg12_deepcopy[] = {arg_any, arg_bool}; static argcheck_T arg12_execute[] = {arg_string_or_list_string, arg_string}; +static argcheck_T arg12_getchar[] = {arg_bool_or_nr, arg_dict_any}; static argcheck_T arg23_extend[] = {arg_list_or_dict_mod, arg_same_as_prev, arg_extend3}; static argcheck_T arg23_extendnew[] = {arg_list_or_dict, arg_same_struct_as_prev, arg_extend3}; static argcheck_T arg23_get[] = {arg_get1, arg_string_or_nr, arg_any}; @@ -2095,7 +2110,7 @@ static funcentry_T global_functions[] = ret_list_any, f_getcellwidths}, {"getchangelist", 0, 1, FEARG_1, arg1_buffer, ret_list_any, f_getchangelist}, - {"getchar", 0, 1, 0, arg1_bool, + {"getchar", 0, 2, 0, arg12_getchar, ret_any, f_getchar}, {"getcharmod", 0, 0, 0, NULL, ret_number, f_getcharmod}, @@ -2103,7 +2118,7 @@ static funcentry_T global_functions[] = ret_list_number, f_getcharpos}, {"getcharsearch", 0, 0, 0, NULL, ret_dict_any, f_getcharsearch}, - {"getcharstr", 0, 1, 0, arg1_bool, + {"getcharstr", 0, 2, 0, arg12_getchar, ret_string, f_getcharstr}, {"getcmdcomplpat", 0, 0, 0, NULL, ret_string, f_getcmdcomplpat}, diff --git a/src/getchar.c b/src/getchar.c index c1628ee5a..06f4ad435 100644 --- a/src/getchar.c +++ b/src/getchar.c @@ -2384,14 +2384,33 @@ char_avail(void) * "getchar()" and "getcharstr()" functions */ static void -getchar_common(typval_T *argvars, typval_T *rettv) +getchar_common(typval_T *argvars, typval_T *rettv, int allow_number) { varnumber_T n; int error = FALSE; + int simplify = TRUE; - if (in_vim9script() && check_for_opt_bool_arg(argvars, 0) == FAIL) + if ((in_vim9script() + && check_for_opt_bool_or_number_arg(argvars, 0) == FAIL) + || (argvars[0].v_type != VAR_UNKNOWN + && check_for_opt_dict_arg(argvars, 1) == FAIL)) return; + if (argvars[0].v_type != VAR_UNKNOWN && argvars[1].v_type == VAR_DICT) + { + dict_T *d = argvars[1].vval.v_dict; + + if (allow_number) + allow_number = dict_get_bool(d, "number", TRUE); + else if (dict_has_key(d, "number")) + { + semsg(_(e_invalid_argument_str), "number"); + error = TRUE; + } + + simplify = dict_get_bool(d, "simplify", TRUE); + } + #ifdef MESSAGE_QUEUE // vpeekc() used to check for messages, but that caused problems, invoking // a callback where it was not expected. Some plugins use getchar(1) in a @@ -2404,9 +2423,13 @@ getchar_common(typval_T *argvars, typval_T *rettv) ++no_mapping; ++allow_keys; - for (;;) + if (!simplify) + ++no_reduce_keys; + while (!error) { - if (argvars[0].v_type == VAR_UNKNOWN) + if (argvars[0].v_type == VAR_UNKNOWN + || (argvars[0].v_type == VAR_NUMBER + && argvars[0].vval.v_number == -1)) // getchar(): blocking wait. n = plain_vgetc_nopaste(); else if (tv_get_bool_chk(&argvars[0], &error)) @@ -2427,14 +2450,15 @@ getchar_common(typval_T *argvars, typval_T *rettv) } --no_mapping; --allow_keys; + if (!simplify) + --no_reduce_keys; set_vim_var_nr(VV_MOUSE_WIN, 0); set_vim_var_nr(VV_MOUSE_WINID, 0); set_vim_var_nr(VV_MOUSE_LNUM, 0); set_vim_var_nr(VV_MOUSE_COL, 0); - rettv->vval.v_number = n; - if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) + if (n != 0 && (!allow_number || IS_SPECIAL(n) || mod_mask != 0)) { char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1 int i = 0; @@ -2492,6 +2516,10 @@ getchar_common(typval_T *argvars, typval_T *rettv) } } } + else if (!allow_number) + rettv->v_type = VAR_STRING; + else + rettv->vval.v_number = n; } /* @@ -2500,7 +2528,7 @@ getchar_common(typval_T *argvars, typval_T *rettv) void f_getchar(typval_T *argvars, typval_T *rettv) { - getchar_common(argvars, rettv); + getchar_common(argvars, rettv, TRUE); } /* @@ -2509,25 +2537,7 @@ f_getchar(typval_T *argvars, typval_T *rettv) void f_getcharstr(typval_T *argvars, typval_T *rettv) { - getchar_common(argvars, rettv); - - if (rettv->v_type != VAR_NUMBER) - return; - - char_u temp[7]; // mbyte-char: 6, NUL: 1 - varnumber_T n = rettv->vval.v_number; - int i = 0; - - if (n != 0) - { - if (has_mbyte) - i += (*mb_char2bytes)(n, temp + i); - else - temp[i++] = n; - } - temp[i] = NUL; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strnsave(temp, i); + getchar_common(argvars, rettv, FALSE); } /* diff --git a/src/proto/typval.pro b/src/proto/typval.pro index b70618342..90dcc5434 100644 --- a/src/proto/typval.pro +++ b/src/proto/typval.pro @@ -18,7 +18,9 @@ int check_for_number_arg(typval_T *args, int idx); int check_for_opt_number_arg(typval_T *args, int idx); int check_for_float_or_nr_arg(typval_T *args, int idx); int check_for_bool_arg(typval_T *args, int idx); +int check_for_bool_or_number_arg(typval_T *args, int idx); int check_for_opt_bool_arg(typval_T *args, int idx); +int check_for_opt_bool_or_number_arg(typval_T *args, int idx); int check_for_blob_arg(typval_T *args, int idx); int check_for_list_arg(typval_T *args, int idx); int check_for_nonnull_list_arg(typval_T *args, int idx); diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim index e31e2ed73..4672fc0c4 100644 --- a/src/testdir/test_functions.vim +++ b/src/testdir/test_functions.vim @@ -2562,6 +2562,75 @@ func Test_getchar() call assert_equal("\<M-F2>", getchar(0)) call assert_equal(0, getchar(0)) + call feedkeys("\<Tab>", '') + call assert_equal(char2nr("\<Tab>"), getchar()) + call feedkeys("\<Tab>", '') + call assert_equal(char2nr("\<Tab>"), getchar(-1)) + call feedkeys("\<Tab>", '') + call assert_equal(char2nr("\<Tab>"), getchar(-1, {})) + call feedkeys("\<Tab>", '') + call assert_equal(char2nr("\<Tab>"), getchar(-1, #{number: v:true})) + call assert_equal(0, getchar(0)) + call assert_equal(0, getchar(1)) + call assert_equal(0, getchar(0, #{number: v:true})) + call assert_equal(0, getchar(1, #{number: v:true})) + + call feedkeys("\<Tab>", '') + call assert_equal("\<Tab>", getcharstr()) + call feedkeys("\<Tab>", '') + call assert_equal("\<Tab>", getcharstr(-1)) + call feedkeys("\<Tab>", '') + call assert_equal("\<Tab>", getcharstr(-1, {})) + call feedkeys("\<Tab>", '') + call assert_equal("\<Tab>", getchar(-1, #{number: v:false})) + call assert_equal('', getcharstr(0)) + call assert_equal('', getcharstr(1)) + call assert_equal('', getchar(0, #{number: v:false})) + call assert_equal('', getchar(1, #{number: v:false})) + + for key in ["C-I", "C-X", "M-x"] + let lines =<< eval trim END + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar()) + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar(-1)) + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar(-1, {{}})) + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar(-1, {{'number': 1}})) + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar(-1, {{'simplify': 1}})) + call feedkeys("\<*{key}>", '') + call assert_equal("\<*{key}>", getchar(-1, {{'simplify': v:false}})) + call assert_equal(0, getchar(0)) + call assert_equal(0, getchar(1)) + END + call v9.CheckLegacyAndVim9Success(lines) + + let lines =<< eval trim END + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getcharstr()) + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getcharstr(-1)) + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getcharstr(-1, {{}})) + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getchar(-1, {{'number': 0}})) + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getcharstr(-1, {{'simplify': 1}})) + call feedkeys("\<*{key}>", '') + call assert_equal("\<*{key}>", getcharstr(-1, {{'simplify': v:false}})) + call assert_equal('', getcharstr(0)) + call assert_equal('', getcharstr(1)) + END + call v9.CheckLegacyAndVim9Success(lines) + endfor + + call assert_fails('call getchar(1, 1)', 'E1206:') + call assert_fails('call getcharstr(1, 1)', 'E1206:') + call assert_fails('call getcharstr(1, #{number: v:true})', 'E475:') + call assert_fails('call getcharstr(1, #{number: v:false})', 'E475:') + call setline(1, 'xxxx') call test_setmouse(1, 3) let v:mouse_win = 9 diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim index cfaf0ace2..80ed2b230 100644 --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -1838,8 +1838,10 @@ def Test_getchar() endwhile getchar(true)->assert_equal(0) getchar(1)->assert_equal(0) - v9.CheckSourceDefAndScriptFailure(['getchar(2)'], ['E1013: Argument 1: type mismatch, expected bool but got number', 'E1212: Bool required for argument 1']) - v9.CheckSourceDefAndScriptFailure(['getchar("1")'], ['E1013: Argument 1: type mismatch, expected bool but got string', 'E1212: Bool required for argument 1']) + v9.CheckSourceDefExecAndScriptFailure(['getchar(2)'], 'E1023: Using a Number as a Bool: 2') + v9.CheckSourceDefExecAndScriptFailure(['getchar(-2)'], 'E1023: Using a Number as a Bool: -2') + v9.CheckSourceDefAndScriptFailure(['getchar("1")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1235: Bool or Number required for argument 1']) + v9.CheckSourceDefAndScriptFailure(['getchar(1, 1)'], ['E1013: Argument 2: type mismatch, expected dict<any> but got number', 'E1206: Dictionary required for argument 2']) enddef def Test_getcharpos() @@ -1851,8 +1853,14 @@ def Test_getcharpos() enddef def Test_getcharstr() - v9.CheckSourceDefAndScriptFailure(['getcharstr(2)'], ['E1013: Argument 1: type mismatch, expected bool but got number', 'E1212: Bool required for argument 1']) - v9.CheckSourceDefAndScriptFailure(['getcharstr("1")'], ['E1013: Argument 1: type mismatch, expected bool but got string', 'E1212: Bool required for argument 1']) + while len(getcharstr(0)) > 0 + endwhile + getcharstr(true)->assert_equal('') + getcharstr(1)->assert_equal('') + v9.CheckSourceDefExecAndScriptFailure(['getcharstr(2)'], 'E1023: Using a Number as a Bool: 2') + v9.CheckSourceDefExecAndScriptFailure(['getcharstr(-2)'], 'E1023: Using a Number as a Bool: -2') + v9.CheckSourceDefAndScriptFailure(['getcharstr("1")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1235: Bool or Number required for argument 1']) + v9.CheckSourceDefAndScriptFailure(['getcharstr(1, 1)'], ['E1013: Argument 2: type mismatch, expected dict<any> but got number', 'E1206: Dictionary required for argument 2']) enddef def Test_getcompletion() @@ -4989,7 +4997,7 @@ enddef def Test_win_findbuf() v9.CheckSourceDefAndScriptFailure(['win_findbuf("a")'], ['E1013: Argument 1: type mismatch, expected number but got string', 'E1210: Number required for argument 1']) - assert_equal([], win_findbuf(1000)) + assert_equal([], win_findbuf(9999)) assert_equal([win_getid()], win_findbuf(bufnr(''))) enddef diff --git a/src/typval.c b/src/typval.c index e57d89815..cd39a0d97 100644 --- a/src/typval.c +++ b/src/typval.c @@ -526,6 +526,20 @@ check_for_bool_arg(typval_T *args, int idx) return OK; } +/* + * Give an error and return FAIL unless "args[idx]" is a bool or a number. + */ + int +check_for_bool_or_number_arg(typval_T *args, int idx) +{ + if (args[idx].v_type != VAR_BOOL && args[idx].v_type != VAR_NUMBER) + { + semsg(_(e_bool_or_number_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + /* * Check for an optional bool argument at 'idx'. * Return FAIL if the type is wrong. @@ -538,6 +552,18 @@ check_for_opt_bool_arg(typval_T *args, int idx) return check_for_bool_arg(args, idx); } +/* + * Check for an optional bool or number argument at 'idx'. + * Return FAIL if the type is wrong. + */ + int +check_for_opt_bool_or_number_arg(typval_T *args, int idx) +{ + if (args[idx].v_type == VAR_UNKNOWN) + return OK; + return check_for_bool_or_number_arg(args, idx); +} + /* * Give an error and return FAIL unless "args[idx]" is a blob. */ diff --git a/src/version.c b/src/version.c index 21e2787d1..7137c675b 100644 --- a/src/version.c +++ b/src/version.c @@ -704,6 +704,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1068, /**/ 1067, /**/ -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to vim_dev+unsubscr...@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1teVMl-000RKF-6U%40256bit.org.