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 <zeertzjq@outlook.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/errors.h b/src/errors.h
index 9467528..d8b1ff6 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -3172,7 +3172,8 @@
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 41444f4..69b6a4f 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -388,6 +388,20 @@
}
/*
+ * 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.
*/
static int
@@ -1195,6 +1209,7 @@
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 @@
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 @@
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 c1628ee..06f4ad4 100644
--- a/src/getchar.c
+++ b/src/getchar.c
@@ -2384,14 +2384,33 @@
* "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 @@
++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 @@
}
--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 @@
}
}
}
+ else if (!allow_number)
+ rettv->v_type = VAR_STRING;
+ else
+ rettv->vval.v_number = n;
}
/*
@@ -2500,7 +2528,7 @@
void
f_getchar(typval_T *argvars, typval_T *rettv)
{
- getchar_common(argvars, rettv);
+ getchar_common(argvars, rettv, TRUE);
}
/*
@@ -2509,25 +2537,7 @@
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 b706183..90dcc54 100644
--- a/src/proto/typval.pro
+++ b/src/proto/typval.pro
@@ -18,7 +18,9 @@
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 e31e2ed..4672fc0 100644
--- a/src/testdir/test_functions.vim
+++ b/src/testdir/test_functions.vim
@@ -2562,6 +2562,75 @@
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 cfaf0ac..80ed2b2 100644
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -1838,8 +1838,10 @@
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 @@
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 @@
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 e57d898..cd39a0d 100644
--- a/src/typval.c
+++ b/src/typval.c
@@ -527,6 +527,20 @@
}
/*
+ * 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.
*/
@@ -539,6 +553,18 @@
}
/*
+ * 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.
*/
int
diff --git a/src/version.c b/src/version.c
index 21e2787..7137c67 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1068,
+/**/
1067,
/**/
1066,