mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
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
edf0f7db28
This commit is contained in:
parent
87e806186c
commit
af069c5c05
8
runtime/doc/builtin.txt
generated
8
runtime/doc/builtin.txt
generated
@ -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
|
||||
|
@ -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*
|
||||
|
||||
|
8
runtime/lua/vim/_meta/vimfn.lua
generated
8
runtime/lua/vim/_meta/vimfn.lua
generated
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
92
test/functional/vimscript/getchar_spec.lua
Normal file
92
test/functional/vimscript/getchar_spec.lua
Normal file
@ -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)
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user