aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/builtin.txt8
-rw-r--r--runtime/doc/news.txt5
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua8
-rw-r--r--src/nvim/eval.lua8
-rw-r--r--src/nvim/getchar.c33
-rw-r--r--test/functional/vimscript/getchar_spec.lua92
-rw-r--r--test/old/testdir/test_functions.vim63
7 files changed, 209 insertions, 8 deletions
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 3d9010aa2c..96574e2899 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -3114,6 +3114,14 @@ getchar([{expr} [, {opts}]]) *getchar()*
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: automagically decide
+ between "keep" and "msg")
+
number If |TRUE|, return a Number when getting
a single character.
If |FALSE|, the return value is always
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 2208428f75..eda8d945cb 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -410,6 +410,11 @@ UI
• |:checkhealth| can display in a floating window, controlled by the
|g:health| variable.
+VIMSCRIPT
+
+• |getchar()| and |getcharstr()| have optional {opts} |Dict| argument to control:
+ cursor behavior, return type, and whether to simplify the returned key.
+
==============================================================================
CHANGED FEATURES *news-changed*
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index c0be6b089c..098c0e907a 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -2781,6 +2781,14 @@ function vim.fn.getchangelist(buf) end
--- 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: automagically decide
+--- between "keep" and "msg")
+---
--- number If |TRUE|, return a Number when getting
--- a single character.
--- If |FALSE|, the return value is always
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 82e3992287..9d787c68ea 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -3507,6 +3507,14 @@ M.funcs = {
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: automagically decide
+ between "keep" and "msg")
+
number If |TRUE|, return a Number when getting
a single character.
If |FALSE|, the return value is always
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index c9d2165fb0..5bf89ee5a8 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1872,9 +1872,11 @@ static int no_reduce_keys = 0; ///< Do not apply modifiers to the key.
static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number)
FUNC_ATTR_NONNULL_ALL
{
- varnumber_T n;
+ varnumber_T n = 0;
+ const int called_emsg_start = called_emsg;
bool error = false;
bool simplify = true;
+ char cursor_flag = NUL;
if (argvars[0].v_type != VAR_UNKNOWN
&& tv_check_for_opt_dict_arg(argvars, 1) == FAIL) {
@@ -1888,10 +1890,28 @@ static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number
allow_number = tv_dict_get_bool(d, "number", true);
} else if (tv_dict_has_key(d, "number")) {
semsg(_(e_invarg2), "number");
- error = true;
}
simplify = tv_dict_get_bool(d, "simplify", true);
+
+ const char *cursor_str = tv_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_invargNval), "cursor", cursor_str);
+ } else {
+ cursor_flag = cursor_str[0];
+ }
+ }
+ }
+
+ if (called_emsg != called_emsg_start) {
+ return;
+ }
+
+ if (cursor_flag == 'h') {
+ ui_busy_start();
}
no_mapping++;
@@ -1899,9 +1919,8 @@ static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number
if (!simplify) {
no_reduce_keys++;
}
- while (!error) {
- if (msg_col > 0) {
- // Position the cursor. Needed after a message that ends in a space.
+ while (true) {
+ if (cursor_flag == 'm' || (cursor_flag == NUL && msg_col > 0)) {
ui_cursor_goto(msg_row, msg_col);
}
@@ -1945,6 +1964,10 @@ static void getchar_common(typval_T *argvars, typval_T *rettv, bool allow_number
no_reduce_keys--;
}
+ if (cursor_flag == 'h') {
+ ui_busy_stop();
+ }
+
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/test/functional/vimscript/getchar_spec.lua b/test/functional/vimscript/getchar_spec.lua
new file mode 100644
index 0000000000..1327d741cf
--- /dev/null
+++ b/test/functional/vimscript/getchar_spec.lua
@@ -0,0 +1,92 @@
+local n = require('test.functional.testnvim')()
+local Screen = require('test.functional.ui.screen')
+
+local clear = n.clear
+local exec = n.exec
+local feed = n.feed
+local async_command = n.async_meths.nvim_command
+
+describe('getchar()', function()
+ before_each(clear)
+
+ -- oldtest: Test_getchar_cursor_position()
+ it('cursor positioning', function()
+ local screen = Screen.new(40, 6)
+ exec([[
+ call setline(1, ['foobar', 'foobar', 'foobar'])
+ call cursor(3, 6)
+ ]])
+ screen:expect([[
+ foobar |*2
+ fooba^r |
+ {1:~ }|*2
+ |
+ ]])
+
+ -- Default: behaves like "msg" when immediately after printing a message,
+ -- even if :sleep moved cursor elsewhere.
+ for _, cmd in ipairs({
+ 'echo 1234 | call getchar()',
+ 'echo 1234 | call getchar(-1, {})',
+ "echo 1234 | call getchar(-1, #{cursor: 'msg'})",
+ 'echo 1234 | sleep 1m | call getchar()',
+ 'echo 1234 | sleep 1m | call getchar(-1, {})',
+ "echo 1234 | sleep 1m | call getchar(-1, #{cursor: 'msg'})",
+ }) do
+ async_command(cmd)
+ screen:expect([[
+ foobar |*3
+ {1:~ }|*2
+ 1234^ |
+ ]])
+ feed('a')
+ screen:expect([[
+ foobar |*2
+ fooba^r |
+ {1:~ }|*2
+ 1234 |
+ ]])
+ end
+
+ -- Default: behaves like "keep" when not immediately after printing a message.
+ for _, cmd in ipairs({
+ 'call getchar()',
+ 'call getchar(-1, {})',
+ "call getchar(-1, #{cursor: 'keep'})",
+ "echo 1234 | sleep 1m | call getchar(-1, #{cursor: 'keep'})",
+ }) do
+ async_command(cmd)
+ screen:expect_unchanged()
+ feed('a')
+ screen:expect_unchanged()
+ end
+
+ async_command("call getchar(-1, #{cursor: 'msg'})")
+ screen:expect([[
+ foobar |*3
+ {1:~ }|*2
+ ^1234 |
+ ]])
+ feed('a')
+ screen:expect([[
+ foobar |*2
+ fooba^r |
+ {1:~ }|*2
+ 1234 |
+ ]])
+
+ async_command("call getchar(-1, #{cursor: 'hide'})")
+ screen:expect([[
+ foobar |*3
+ {1:~ }|*2
+ 1234 |
+ ]])
+ feed('a')
+ screen:expect([[
+ foobar |*2
+ fooba^r |
+ {1:~ }|*2
+ 1234 |
+ ]])
+ end)
+end)
diff --git a/test/old/testdir/test_functions.vim b/test/old/testdir/test_functions.vim
index f57743900a..738a417b86 100644
--- a/test/old/testdir/test_functions.vim
+++ b/test/old/testdir/test_functions.vim
@@ -2458,6 +2458,14 @@ func Test_getchar()
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:')
@@ -2476,10 +2484,59 @@ func Test_getchar()
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()
- if !has('libcall')
- return
- endif
+ CheckFeature libcall
if has('win32')
let libc = 'msvcrt.dll'