diff options
author | zeertzjq <zeertzjq@outlook.com> | 2025-02-03 08:09:03 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-03 00:09:03 +0000 |
commit | af069c5c05ad99623345071007ad23da51973601 (patch) | |
tree | a18c6dd9be94e6de35ea09057c54084657551b0d | |
parent | 87e806186c721f12c338af86677b6d1e6e2fa44a (diff) | |
download | rneovim-af069c5c05ad99623345071007ad23da51973601.tar.gz rneovim-af069c5c05ad99623345071007ad23da51973601.tar.bz2 rneovim-af069c5c05ad99623345071007ad23da51973601.zip |
vim-patch:9.1.1070: Cannot control cursor positioning of getchar() (#32303)
Problem: Cannot control cursor positioning of getchar().
Solution: Add "cursor" flag to {opts}, with possible values "hide",
"keep" and "msg".
related: vim/vim#10603
closes: vim/vim#16569
https://github.com/vim/vim/commit/edf0f7db28f87611368e158210e58ed30f673098
-rw-r--r-- | runtime/doc/builtin.txt | 8 | ||||
-rw-r--r-- | runtime/doc/news.txt | 5 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 8 | ||||
-rw-r--r-- | src/nvim/eval.lua | 8 | ||||
-rw-r--r-- | src/nvim/getchar.c | 33 | ||||
-rw-r--r-- | test/functional/vimscript/getchar_spec.lua | 92 | ||||
-rw-r--r-- | test/old/testdir/test_functions.vim | 63 |
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' |