patch 9.1.1070: Cannot control cursor positioning of getchar()
Problem: Cannot control cursor positioning of getchar().
Solution: Add "cursor" flag to {opts}, with possible values "hide",
"keep" and "msg".
related: #10603
closes: #16569
Signed-off-by: zeertzjq <zeertzjq@outlook.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 46c3ba8..39ff14e 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -3953,6 +3953,13 @@
The optional argument {opts} is a Dict and supports the
following items:
+ cursor A String specifying cursor behavior
+ when waiting for a character.
+ "hide": hide the cursor.
+ "keep": keep current cursor unchanged.
+ "msg": move cursor to message area.
+ (default: "msg")
+
number If |TRUE|, return a Number when getting
a single character.
If |FALSE|, the return value is always
diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt
index 2d48cc9..5338b8b 100644
--- a/runtime/doc/todo.txt
+++ b/runtime/doc/todo.txt
@@ -1,4 +1,4 @@
-*todo.txt* For Vim version 9.1. Last change: 2025 Jan 16
+*todo.txt* For Vim version 9.1. Last change: 2025 Feb 02
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -467,9 +467,6 @@
When converting screen column to text position use this.
The line number can be obtained from win->w_lines[].
-Version of getchar() that does not move the cursor - #10603 Use a separate
-argument for the new flag.
-
test_arglist func Test_all_not_allowed_from_cmdwin() hangs on MS-Windows.
Can we add highlighting to ":echowindow"?
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index 010a0cb..68287d3 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -41636,6 +41636,8 @@
- add |dist#vim9#Launch()| and |dist#vim9#Open()| to the |vim-script-library|
and decouple it from |netrw|
- new digraph "APPROACHES THE LIMIT" using ".="
+- Add the optional {opts} |Dict| argument to |getchar()| to control: cursor
+ behaviour, return type and whether or not to simplify the returned key
*added-9.2*
Added ~
diff --git a/src/getchar.c b/src/getchar.c
index 06f4ad4..83a9861 100644
--- a/src/getchar.c
+++ b/src/getchar.c
@@ -2386,9 +2386,11 @@
static void
getchar_common(typval_T *argvars, typval_T *rettv, int allow_number)
{
- varnumber_T n;
+ varnumber_T n = 0;
+ int called_emsg_start = called_emsg;
int error = FALSE;
int simplify = TRUE;
+ char_u cursor_flag = 'm';
if ((in_vim9script()
&& check_for_opt_bool_or_number_arg(argvars, 0) == FAIL)
@@ -2399,18 +2401,31 @@
if (argvars[0].v_type != VAR_UNKNOWN && argvars[1].v_type == VAR_DICT)
{
dict_T *d = argvars[1].vval.v_dict;
+ char_u *cursor_str;
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);
+
+ cursor_str = dict_get_string(d, "cursor", FALSE);
+ if (cursor_str != NULL)
+ {
+ if (STRCMP(cursor_str, "hide") != 0
+ && STRCMP(cursor_str, "keep") != 0
+ && STRCMP(cursor_str, "msg") != 0)
+ semsg(_(e_invalid_value_for_argument_str_str), "cursor",
+ cursor_str);
+ else
+ cursor_flag = cursor_str[0];
+ }
}
+ if (called_emsg != called_emsg_start)
+ return;
+
#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
@@ -2418,14 +2433,16 @@
parse_queued_messages();
#endif
- // Position the cursor. Needed after a message that ends in a space.
- windgoto(msg_row, msg_col);
+ if (cursor_flag == 'h')
+ cursor_sleep();
+ else if (cursor_flag == 'm')
+ windgoto(msg_row, msg_col);
++no_mapping;
++allow_keys;
if (!simplify)
++no_reduce_keys;
- while (!error)
+ for (;;)
{
if (argvars[0].v_type == VAR_UNKNOWN
|| (argvars[0].v_type == VAR_NUMBER
@@ -2453,6 +2470,9 @@
if (!simplify)
--no_reduce_keys;
+ if (cursor_flag == 'h')
+ cursor_unsleep();
+
set_vim_var_nr(VV_MOUSE_WIN, 0);
set_vim_var_nr(VV_MOUSE_WINID, 0);
set_vim_var_nr(VV_MOUSE_LNUM, 0);
diff --git a/src/testdir/test_functions.vim b/src/testdir/test_functions.vim
index 4672fc0..5783c7a 100644
--- a/src/testdir/test_functions.vim
+++ b/src/testdir/test_functions.vim
@@ -2628,6 +2628,14 @@
call assert_fails('call getchar(1, 1)', 'E1206:')
call assert_fails('call getcharstr(1, 1)', 'E1206:')
+ call assert_fails('call getchar(1, #{cursor: "foo"})', 'E475:')
+ call assert_fails('call getcharstr(1, #{cursor: "foo"})', 'E475:')
+ call assert_fails('call getchar(1, #{cursor: 0z})', 'E976:')
+ call assert_fails('call getcharstr(1, #{cursor: 0z})', 'E976:')
+ call assert_fails('call getchar(1, #{simplify: 0z})', 'E974:')
+ call assert_fails('call getcharstr(1, #{simplify: 0z})', 'E974:')
+ call assert_fails('call getchar(1, #{number: []})', 'E745:')
+ call assert_fails('call getchar(1, #{number: {}})', 'E728:')
call assert_fails('call getcharstr(1, #{number: v:true})', 'E475:')
call assert_fails('call getcharstr(1, #{number: v:false})', 'E475:')
@@ -2646,6 +2654,57 @@
enew!
endfunc
+func Test_getchar_cursor_position()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['foobar', 'foobar', 'foobar'])
+ call cursor(3, 6)
+ nnoremap <F1> <Cmd>echo 1234<Bar>call getchar()<CR>
+ nnoremap <F2> <Cmd>call getchar()<CR>
+ nnoremap <F3> <Cmd>call getchar(-1, {})<CR>
+ nnoremap <F4> <Cmd>call getchar(-1, #{cursor: 'msg'})<CR>
+ nnoremap <F5> <Cmd>call getchar(-1, #{cursor: 'keep'})<CR>
+ nnoremap <F6> <Cmd>call getchar(-1, #{cursor: 'hide'})<CR>
+ END
+ call writefile(lines, 'XgetcharCursorPos', 'D')
+ let buf = RunVimInTerminal('-S XgetcharCursorPos', {'rows': 6})
+ call WaitForAssert({-> assert_equal([3, 6], term_getcursor(buf)[0:1])})
+
+ call term_sendkeys(buf, "\<F1>")
+ call WaitForAssert({-> assert_equal([6, 5], term_getcursor(buf)[0:1])})
+ call assert_true(term_getcursor(buf)[2].visible)
+ call term_sendkeys(buf, 'a')
+ call WaitForAssert({-> assert_equal([3, 6], term_getcursor(buf)[0:1])})
+ call assert_true(term_getcursor(buf)[2].visible)
+
+ for key in ["\<F2>", "\<F3>", "\<F4>"]
+ call term_sendkeys(buf, key)
+ call WaitForAssert({-> assert_equal([6, 1], term_getcursor(buf)[0:1])})
+ call assert_true(term_getcursor(buf)[2].visible)
+ call term_sendkeys(buf, 'a')
+ call WaitForAssert({-> assert_equal([3, 6], term_getcursor(buf)[0:1])})
+ call assert_true(term_getcursor(buf)[2].visible)
+ endfor
+
+ call term_sendkeys(buf, "\<F5>")
+ call TermWait(buf, 50)
+ call assert_equal([3, 6], term_getcursor(buf)[0:1])
+ call assert_true(term_getcursor(buf)[2].visible)
+ call term_sendkeys(buf, 'a')
+ call TermWait(buf, 50)
+ call assert_equal([3, 6], term_getcursor(buf)[0:1])
+ call assert_true(term_getcursor(buf)[2].visible)
+
+ call term_sendkeys(buf, "\<F6>")
+ call WaitForAssert({-> assert_false(term_getcursor(buf)[2].visible)})
+ call term_sendkeys(buf, 'a')
+ call WaitForAssert({-> assert_true(term_getcursor(buf)[2].visible)})
+ call assert_equal([3, 6], term_getcursor(buf)[0:1])
+
+ call StopVimInTerminal(buf)
+endfunc
+
func Test_libcall_libcallnr()
CheckFeature libcall
diff --git a/src/version.c b/src/version.c
index f9d99a5..5ed5bcf 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1070,
+/**/
1069,
/**/
1068,