aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/autoload/health/provider.vim24
-rw-r--r--runtime/autoload/provider/pythonx.vim4
-rw-r--r--runtime/filetype.vim2
-rw-r--r--runtime/lua/vim/_editor.lua104
-rw-r--r--runtime/lua/vim/filetype.lua3
-rwxr-xr-x[-rw-r--r--]src/nvim/CMakeLists.txt4
-rw-r--r--src/nvim/api/ui_events.in.h4
-rw-r--r--src/nvim/eval/funcs.c18
-rw-r--r--src/nvim/ex_eval.c315
-rw-r--r--src/nvim/generators/c_grammar.lua1
-rwxr-xr-x[-rw-r--r--]src/nvim/generators/gen_api_ui_events.lua74
-rw-r--r--src/nvim/main.c12
-rw-r--r--src/nvim/map.c1
-rw-r--r--src/nvim/map.h2
-rw-r--r--src/nvim/mouse.c16
-rw-r--r--src/nvim/msgpack_rpc/channel.c8
-rw-r--r--src/nvim/testdir/test_filetype.vim2
-rw-r--r--src/nvim/testdir/test_functions.vim69
-rw-r--r--src/nvim/ui_client.c162
-rw-r--r--src/nvim/ui_client.h4
-rw-r--r--test/functional/api/vim_spec.lua427
-rw-r--r--test/functional/terminal/tui_spec.lua14
22 files changed, 963 insertions, 307 deletions
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim
index 2f35179338..6022e05c22 100644
--- a/runtime/autoload/health/provider.vim
+++ b/runtime/autoload/health/provider.vim
@@ -301,7 +301,7 @@ function! s:check_python() abort
call health#report_info(printf('Using: g:%s = "%s"', host_prog_var, get(g:, host_prog_var)))
endif
- let [pyname, pythonx_errors] = provider#pythonx#Detect(3)
+ let [pyname, pythonx_warnings] = provider#pythonx#Detect(3)
if empty(pyname)
call health#report_warn('No Python executable found that can `import neovim`. '
@@ -311,8 +311,9 @@ function! s:check_python() abort
endif
" No Python executable could `import neovim`, or host_prog_var was used.
- if !empty(pythonx_errors)
- call health#report_error('Python provider error:', pythonx_errors)
+ if !empty(pythonx_warnings)
+ call health#report_warn(pythonx_warnings, ['See :help provider-python for more information.',
+ \ 'You may disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim'])
elseif !empty(pyname) && empty(python_exe)
if !exists('g:'.host_prog_var)
@@ -573,7 +574,8 @@ function! s:check_ruby() abort
\ ['Run `gem install neovim` to ensure the neovim RubyGem is installed.',
\ 'Run `gem environment` to ensure the gem bin directory is in $PATH.',
\ 'If you are using rvm/rbenv/chruby, try "rehashing".',
- \ 'See :help g:ruby_host_prog for non-standard gem installations.'])
+ \ 'See :help g:ruby_host_prog for non-standard gem installations.',
+ \ 'You may disable this provider (and warning) by adding `let g:loaded_ruby_provider = 0` to your init.vim'])
return
endif
call health#report_info('Host: '. host)
@@ -634,7 +636,8 @@ function! s:check_node() abort
if empty(host)
call health#report_warn('Missing "neovim" npm (or yarn) package.',
\ ['Run in shell: npm install -g neovim',
- \ 'Run in shell (if you use yarn): yarn global add neovim'])
+ \ 'Run in shell (if you use yarn): yarn global add neovim',
+ \ 'You may disable this provider (and warning) by adding `let g:loaded_node_provider = 0` to your init.vim'])
return
endif
call health#report_info('Nvim node.js host: '. host)
@@ -683,14 +686,15 @@ function! s:check_perl() abort
return
endif
- let [perl_exec, perl_errors] = provider#perl#Detect()
+ let [perl_exec, perl_warnings] = provider#perl#Detect()
if empty(perl_exec)
- if !empty(perl_errors)
- call health#report_error('perl provider error:', perl_errors)
- else
+ if !empty(perl_warnings)
+ call health#report_warn(perl_warnings, ['See :help provider-perl for more information.',
+ \ 'You may disable this provider (and warning) by adding `let g:loaded_node_provider = 0` to your init.vim'])
+ else
call health#report_warn('No usable perl executable found')
endif
- return
+ return
endif
call health#report_info('perl executable: '. perl_exec)
diff --git a/runtime/autoload/provider/pythonx.vim b/runtime/autoload/provider/pythonx.vim
index 5b299b322c..048f898e62 100644
--- a/runtime/autoload/provider/pythonx.vim
+++ b/runtime/autoload/provider/pythonx.vim
@@ -56,7 +56,7 @@ function! provider#pythonx#DetectByModule(module, major_version) abort
endfor
" No suitable Python executable found.
- return ['', 'provider/pythonx: Could not load Python '.a:major_version.":\n".join(errors, "\n")]
+ return ['', 'Could not load Python '.a:major_version.":\n".join(errors, "\n")]
endfunction
" Returns array: [prog_exitcode, prog_version]
@@ -99,7 +99,7 @@ function! provider#pythonx#CheckForModule(prog, module, major_version) abort
endif
if prog_exitcode == 2
- return [0, prog_path.' does not have the "' . a:module . '" module. :help provider-python']
+ return [0, prog_path.' does not have the "' . a:module . '" module.']
elseif prog_exitcode == 127
" This can happen with pyenv's shims.
return [0, prog_path . ' does not exist: ' . prog_version]
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index 8114ad4092..9df89674e3 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -664,7 +664,7 @@ au BufNewFile,BufRead *.fs call dist#ft#FTfs()
au BufNewFile,BufRead *.fsi,*.fsx setf fsharp
" GDB command files
-au BufNewFile,BufRead .gdbinit,gdbinit setf gdb
+au BufNewFile,BufRead .gdbinit,gdbinit,.gdbearlyinit,gdbearlyinit,*.gdb setf gdb
" GDMO
au BufNewFile,BufRead *.mo,*.gdmo setf gdmo
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index a0c60a7dcf..d4db4850bd 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -128,7 +128,7 @@ local function inspect(object, options) -- luacheck: no unused
end
do
- local tdots, tick, got_line1 = 0, 0, false
+ local tdots, tick, got_line1, undo_started, trailing_nl = 0, 0, false, false, false
--- Paste handler, invoked by |nvim_paste()| when a conforming UI
--- (such as the |TUI|) pastes text into the editor.
@@ -156,44 +156,80 @@ do
--- - 3: ends the paste (exactly once)
---@returns false if client should cancel the paste.
function vim.paste(lines, phase)
- local call = vim.api.nvim_call_function
local now = vim.loop.now()
- local mode = call('mode', {}):sub(1,1)
- if phase < 2 then -- Reset flags.
- tdots, tick, got_line1 = now, 0, false
- elseif mode ~= 'c' then
+ local is_first_chunk = phase < 2
+ local is_last_chunk = phase == -1 or phase == 3
+ if is_first_chunk then -- Reset flags.
+ tdots, tick, got_line1, undo_started, trailing_nl = now, 0, false, false, false
+ end
+ if #lines == 0 then
+ lines = {''}
+ end
+ if #lines == 1 and lines[1] == '' and not is_last_chunk then
+ -- An empty chunk can cause some edge cases in streamed pasting,
+ -- so don't do anything unless it is the last chunk.
+ return true
+ end
+ -- Note: mode doesn't always start with "c" in cmdline mode, so use getcmdtype() instead.
+ if vim.fn.getcmdtype() ~= '' then -- cmdline-mode: paste only 1 line.
+ if not got_line1 then
+ got_line1 = (#lines > 1)
+ vim.api.nvim_set_option('paste', true) -- For nvim_input().
+ -- Escape "<" and control characters
+ local line1 = lines[1]:gsub('<', '<lt>'):gsub('(%c)', '\022%1')
+ vim.api.nvim_input(line1)
+ vim.api.nvim_set_option('paste', false)
+ end
+ return true
+ end
+ local mode = vim.api.nvim_get_mode().mode
+ if undo_started then
vim.api.nvim_command('undojoin')
end
- if mode == 'c' and not got_line1 then -- cmdline-mode: paste only 1 line.
- got_line1 = (#lines > 1)
- vim.api.nvim_set_option('paste', true) -- For nvim_input().
- local line1 = lines[1]:gsub('<', '<lt>'):gsub('[\r\n\012\027]', ' ') -- Scrub.
- vim.api.nvim_input(line1)
- vim.api.nvim_set_option('paste', false)
- elseif mode ~= 'c' then
- if phase < 2 and mode:find('^[vV\22sS\19]') then
- vim.api.nvim_command([[exe "normal! \<Del>"]])
- vim.api.nvim_put(lines, 'c', false, true)
- elseif phase < 2 and not mode:find('^[iRt]') then
- vim.api.nvim_put(lines, 'c', true, true)
- -- XXX: Normal-mode: workaround bad cursor-placement after first chunk.
- vim.api.nvim_command('normal! a')
- elseif phase < 2 and mode == 'R' then
- local nchars = 0
- for _, line in ipairs(lines) do
- nchars = nchars + line:len()
+ if mode:find('^i') or mode:find('^n?t') then -- Insert mode or Terminal buffer
+ vim.api.nvim_put(lines, 'c', false, true)
+ elseif phase < 2 and mode:find('^R') and not mode:find('^Rv') then -- Replace mode
+ -- TODO: implement Replace mode streamed pasting
+ -- TODO: support Virtual Replace mode
+ local nchars = 0
+ for _, line in ipairs(lines) do
+ nchars = nchars + line:len()
+ end
+ local row, col = unpack(vim.api.nvim_win_get_cursor(0))
+ local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1]
+ local firstline = lines[1]
+ firstline = bufline:sub(1, col)..firstline
+ lines[1] = firstline
+ lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len())
+ vim.api.nvim_buf_set_lines(0, row-1, row, false, lines)
+ elseif mode:find('^[nvV\22sS\19]') then -- Normal or Visual or Select mode
+ if mode:find('^n') then -- Normal mode
+ -- When there was a trailing new line in the previous chunk,
+ -- the cursor is on the first character of the next line,
+ -- so paste before the cursor instead of after it.
+ vim.api.nvim_put(lines, 'c', not trailing_nl, false)
+ else -- Visual or Select mode
+ vim.api.nvim_command([[exe "silent normal! \<Del>"]])
+ local del_start = vim.fn.getpos("'[")
+ local cursor_pos = vim.fn.getpos('.')
+ if mode:find('^[VS]') then -- linewise
+ if cursor_pos[2] < del_start[2] then -- replacing lines at eof
+ -- create a new line
+ vim.api.nvim_put({''}, 'l', true, true)
+ end
+ vim.api.nvim_put(lines, 'c', false, false)
+ else
+ -- paste after cursor when replacing text at eol, otherwise paste before cursor
+ vim.api.nvim_put(lines, 'c', cursor_pos[3] < del_start[3], false)
end
- local row, col = unpack(vim.api.nvim_win_get_cursor(0))
- local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1]
- local firstline = lines[1]
- firstline = bufline:sub(1, col)..firstline
- lines[1] = firstline
- lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len())
- vim.api.nvim_buf_set_lines(0, row-1, row, false, lines)
- else
- vim.api.nvim_put(lines, 'c', false, true)
end
+ -- put cursor at the end of the text instead of one character after it
+ vim.fn.setpos('.', vim.fn.getpos("']"))
+ trailing_nl = lines[#lines] == ''
+ else -- Don't know what to do in other modes
+ return false
end
+ undo_started = true
if phase ~= -1 and (now - tdots >= 100) then
local dots = ('.'):rep(tick % 4)
tdots = now
@@ -202,7 +238,7 @@ do
-- message when there are zero dots.
vim.api.nvim_command(('echo "%s"'):format(dots))
end
- if phase == -1 or phase == 3 then
+ if is_last_chunk then
vim.api.nvim_command('redraw'..(tick > 1 and '|echo ""' or ''))
end
return true -- Paste will not continue if not returning `true`.
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index f5e4dabfb6..b356f5e3dc 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -225,6 +225,7 @@ local extension = {
fsi = "fsharp",
fsx = "fsharp",
fusion = "fusion",
+ gdb = "gdb",
gdmo = "gdmo",
mo = "gdmo",
tres = "gdresource",
@@ -919,6 +920,8 @@ local filename = {
mtab = "fstab",
[".gdbinit"] = "gdb",
gdbinit = "gdb",
+ [".gdbearlyinit"] = "gdb",
+ gdbearlyinit = "gdb",
["lltxxxxx.txt"] = "gedcom",
["TAG_EDITMSG"] = "gitcommit",
["MERGE_MSG"] = "gitcommit",
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 96e5d1e77c..8e17f94abc 100644..100755
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -39,6 +39,7 @@ set(GENERATED_UI_EVENTS ${GENERATED_DIR}/ui_events.generated.h)
set(GENERATED_UI_EVENTS_CALL ${GENERATED_DIR}/ui_events_call.generated.h)
set(GENERATED_UI_EVENTS_REMOTE ${GENERATED_DIR}/ui_events_remote.generated.h)
set(GENERATED_UI_EVENTS_BRIDGE ${GENERATED_DIR}/ui_events_bridge.generated.h)
+set(GENERATED_UI_EVENTS_CLIENT ${GENERATED_DIR}/ui_events_client.generated.h)
set(GENERATED_UI_EVENTS_METADATA ${GENERATED_DIR}/api/private/ui_events_metadata.generated.h)
set(GENERATED_EX_CMDS_ENUM ${GENERATED_INCLUDES_DIR}/ex_cmds_enum.generated.h)
set(GENERATED_EX_CMDS_DEFS ${GENERATED_DIR}/ex_cmds_defs.generated.h)
@@ -271,6 +272,7 @@ foreach(sfile ${NVIM_SOURCES}
"${GENERATED_UI_EVENTS_REMOTE}"
"${GENERATED_UI_EVENTS_BRIDGE}"
"${GENERATED_KEYSETS}"
+ "${GENERATED_UI_EVENTS_CLIENT}"
)
get_filename_component(full_d ${sfile} PATH)
file(RELATIVE_PATH d "${CMAKE_CURRENT_LIST_DIR}" "${full_d}")
@@ -368,6 +370,7 @@ add_custom_command(
${GENERATED_UI_EVENTS_REMOTE}
${GENERATED_UI_EVENTS_BRIDGE}
${GENERATED_UI_EVENTS_METADATA}
+ ${GENERATED_UI_EVENTS_CLIENT}
COMMAND ${LUA_PRG} ${API_UI_EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h
${GENERATED_UI_EVENTS}
@@ -375,6 +378,7 @@ add_custom_command(
${GENERATED_UI_EVENTS_REMOTE}
${GENERATED_UI_EVENTS_BRIDGE}
${GENERATED_UI_EVENTS_METADATA}
+ ${GENERATED_UI_EVENTS_CLIENT}
DEPENDS
${API_UI_EVENTS_GENERATOR}
${GENERATOR_C_GRAMMAR}
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index 03fe5c5058..db348359eb 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -78,13 +78,13 @@ void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs,
void hl_group_set(String name, Integer id)
FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL;
void grid_resize(Integer grid, Integer width, Integer height)
- FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL;
+ FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IMPL;
void grid_clear(Integer grid)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
void grid_cursor_goto(Integer grid, Integer row, Integer col)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL;
void grid_line(Integer grid, Integer row, Integer col_start, Array data)
- FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY;
+ FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL;
void grid_scroll(Integer grid, Integer top, Integer bot,
Integer left, Integer right, Integer rows, Integer cols)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL;
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index b74f9759ac..738ed7f85e 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -3734,7 +3734,7 @@ static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
varnumber_T winid = 0;
varnumber_T winrow = 0;
varnumber_T wincol = 0;
- linenr_T line = 0;
+ linenr_T lnum = 0;
varnumber_T column = 0;
tv_dict_alloc_ret(rettv);
@@ -3753,18 +3753,8 @@ static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
winrow = row + 1 + wp->w_border_adj[0]; // Adjust by 1 for top border
wincol = col + 1 + wp->w_border_adj[3]; // Adjust by 1 for left border
if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width) {
- char_u *p;
- int count;
-
- mouse_comp_pos(wp, &row, &col, &line);
-
- // limit to text length plus one
- p = ml_get_buf(wp->w_buffer, line, false);
- count = (int)STRLEN(p);
- if (col > count) {
- col = count;
- }
-
+ (void)mouse_comp_pos(wp, &row, &col, &lnum);
+ col = vcol2col(wp, lnum, col);
column = col + 1;
}
}
@@ -3772,7 +3762,7 @@ static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_dict_add_nr(d, S_LEN("winid"), winid);
tv_dict_add_nr(d, S_LEN("winrow"), winrow);
tv_dict_add_nr(d, S_LEN("wincol"), wincol);
- tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)line);
+ tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)lnum);
tv_dict_add_nr(d, S_LEN("column"), column);
}
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index 25b6aa7d8a..3c7c635d98 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -88,27 +88,26 @@
*/
static int cause_abort = FALSE;
-// Return true when immediately aborting on error, or when an interrupt
-// occurred or an exception was thrown but not caught. Use for ":{range}call"
-// to check whether an aborted function that does not handle a range itself
-// should be called again for the next line in the range. Also used for
-// cancelling expression evaluation after a function call caused an immediate
-// abort. Note that the first emsg() call temporarily resets "force_abort"
-// until the throw point for error messages has been reached. That is, during
-// cancellation of an expression evaluation after an aborting function call or
-// due to a parsing error, aborting() always returns the same value.
-// "got_int" is also set by calling interrupt().
+/// @return true when immediately aborting on error, or when an interrupt
+/// occurred or an exception was thrown but not caught.
+///
+/// Use for ":{range}call" to check whether an aborted function that does not
+/// handle a range itself should be called again for the next line in the range.
+/// Also used for cancelling expression evaluation after a function call caused
+/// an immediate abort. Note that the first emsg() call temporarily resets
+/// "force_abort" until the throw point for error messages has been reached.
+/// That is, during cancellation of an expression evaluation after an aborting
+/// function call or due to a parsing error, aborting() always returns the same
+/// value. "got_int" is also set by calling interrupt().
int aborting(void)
{
return (did_emsg && force_abort) || got_int || current_exception;
}
-/*
- * The value of "force_abort" is temporarily reset by the first emsg() call
- * during an expression evaluation, and "cause_abort" is used instead. It might
- * be necessary to restore "force_abort" even before the throw point for the
- * error message has been reached. update_force_abort() should be called then.
- */
+/// The value of "force_abort" is temporarily reset by the first emsg() call
+/// during an expression evaluation, and "cause_abort" is used instead. It might
+/// be necessary to restore "force_abort" even before the throw point for the
+/// error message has been reached. update_force_abort() should be called then.
void update_force_abort(void)
{
if (cause_abort) {
@@ -116,23 +115,19 @@ void update_force_abort(void)
}
}
-/*
- * Return TRUE if a command with a subcommand resulting in "retcode" should
- * abort the script processing. Can be used to suppress an autocommand after
- * execution of a failing subcommand as long as the error message has not been
- * displayed and actually caused the abortion.
- */
+/// @return TRUE if a command with a subcommand resulting in "retcode" should
+/// abort the script processing. Can be used to suppress an autocommand after
+/// execution of a failing subcommand as long as the error message has not been
+/// displayed and actually caused the abortion.
int should_abort(int retcode)
{
return (retcode == FAIL && trylevel != 0 && !emsg_silent) || aborting();
}
-/*
- * Return TRUE if a function with the "abort" flag should not be considered
- * ended on an error. This means that parsing commands is continued in order
- * to find finally clauses to be executed, and that some errors in skipped
- * commands are still reported.
- */
+/// @return TRUE if a function with the "abort" flag should not be considered
+/// ended on an error. This means that parsing commands is continued in order
+/// to find finally clauses to be executed, and that some errors in skipped
+/// commands are still reported.
int aborted_in_try(void)
{
// This function is only called after an error. In this case, "force_abort"
@@ -140,13 +135,15 @@ int aborted_in_try(void)
return force_abort;
}
-// cause_errthrow(): Cause a throw of an error exception if appropriate.
-// Return true if the error message should not be displayed by emsg().
-// Sets "ignore", if the emsg() call should be ignored completely.
-//
-// When several messages appear in the same command, the first is usually the
-// most specific one and used as the exception value. The "severe" flag can be
-// set to true, if a later but severer message should be used instead.
+/// cause_errthrow(): Cause a throw of an error exception if appropriate.
+///
+/// @return true if the error message should not be displayed by emsg().
+///
+/// Sets "ignore", if the emsg() call should be ignored completely.
+///
+/// When several messages appear in the same command, the first is usually the
+/// most specific one and used as the exception value. The "severe" flag can be
+/// set to true, if a later but severer message should be used instead.
bool cause_errthrow(const char_u *mesg, bool severe, bool *ignore)
FUNC_ATTR_NONNULL_ALL
{
@@ -279,9 +276,7 @@ bool cause_errthrow(const char_u *mesg, bool severe, bool *ignore)
}
}
-/*
- * Free a "msg_list" and the messages it contains.
- */
+/// Free a "msg_list" and the messages it contains.
static void free_msglist(struct msglist *l)
{
struct msglist *messages, *next;
@@ -295,21 +290,17 @@ static void free_msglist(struct msglist *l)
}
}
-/*
- * Free global "*msg_list" and the messages it contains, then set "*msg_list"
- * to NULL.
- */
+/// Free global "*msg_list" and the messages it contains, then set "*msg_list"
+/// to NULL.
void free_global_msglist(void)
{
free_msglist(*msg_list);
*msg_list = NULL;
}
-/*
- * Throw the message specified in the call to cause_errthrow() above as an
- * error exception. If cstack is NULL, postpone the throw until do_cmdline()
- * has returned (see do_one_cmd()).
- */
+/// Throw the message specified in the call to cause_errthrow() above as an
+/// error exception. If cstack is NULL, postpone the throw until do_cmdline()
+/// has returned (see do_one_cmd()).
void do_errthrow(cstack_T *cstack, char_u *cmdname)
{
/*
@@ -339,11 +330,11 @@ void do_errthrow(cstack_T *cstack, char_u *cmdname)
*msg_list = NULL;
}
-/*
- * do_intthrow(): Replace the current exception by an interrupt or interrupt
- * exception if appropriate. Return TRUE if the current exception is discarded,
- * FALSE otherwise.
- */
+/// do_intthrow(): Replace the current exception by an interrupt or interrupt
+/// exception if appropriate.
+///
+/// @return TRUE if the current exception is discarded or,
+/// FALSE otherwise.
int do_intthrow(cstack_T *cstack)
{
// If no interrupt occurred or no try conditional is active and no exception
@@ -386,7 +377,7 @@ int do_intthrow(cstack_T *cstack)
return true;
}
-// Get an exception message that is to be stored in current_exception->value.
+/// Get an exception message that is to be stored in current_exception->value.
char *get_exception_string(void *value, except_type_T type, char_u *cmdname, int *should_free)
{
char *ret, *mesg;
@@ -445,10 +436,12 @@ char *get_exception_string(void *value, except_type_T type, char_u *cmdname, int
}
-// Throw a new exception. Return FAIL when out of memory or it was tried to
-// throw an illegal user exception. "value" is the exception string for a
-// user or interrupt exception, or points to a message list in case of an
-// error exception.
+/// Throw a new exception. "value" is the exception string for a
+/// user or interrupt exception, or points to a message list in case of an
+/// error exception.
+///
+/// @return FAIL when out of memory or it was tried to throw an illegal user
+/// exception.
static int throw_exception(void *value, except_type_T type, char_u *cmdname)
{
except_T *excp;
@@ -524,10 +517,8 @@ fail:
return FAIL;
}
-/*
- * Discard an exception. "was_finished" is set when the exception has been
- * caught and the catch clause has been ended normally.
- */
+/// Discard an exception. "was_finished" is set when the exception has been
+/// caught and the catch clause has been ended normally.
static void discard_exception(except_T *excp, bool was_finished)
{
char_u *saved_IObuff;
@@ -579,9 +570,7 @@ static void discard_exception(except_T *excp, bool was_finished)
xfree(excp);
}
-/*
- * Discard the exception currently being thrown.
- */
+/// Discard the exception currently being thrown.
void discard_current_exception(void)
{
if (current_exception != NULL) {
@@ -592,9 +581,7 @@ void discard_current_exception(void)
need_rethrow = false;
}
-/*
- * Put an exception on the caught stack.
- */
+/// Put an exception on the caught stack.
static void catch_exception(except_T *excp)
{
excp->caught = caught_stack;
@@ -640,9 +627,7 @@ static void catch_exception(except_T *excp)
}
}
-/*
- * Remove an exception from the caught stack.
- */
+/// Remove an exception from the caught stack.
static void finish_exception(except_T *excp)
{
if (excp != caught_stack) {
@@ -682,13 +667,11 @@ static void finish_exception(except_T *excp)
#define RP_RESUME 1
#define RP_DISCARD 2
-/*
- * Report information about something pending in a finally clause if required by
- * the 'verbose' option or when debugging. "action" tells whether something is
- * made pending or something pending is resumed or discarded. "pending" tells
- * what is pending. "value" specifies the return value for a pending ":return"
- * or the exception value for a pending exception.
- */
+/// Report information about something pending in a finally clause if required by
+/// the 'verbose' option or when debugging. "action" tells whether something is
+/// made pending or something pending is resumed or discarded. "pending" tells
+/// what is pending. "value" specifies the return value for a pending ":return"
+/// or the exception value for a pending exception.
static void report_pending(int action, int pending, void *value)
{
char *mesg;
@@ -764,10 +747,8 @@ static void report_pending(int action, int pending, void *value)
}
}
-/*
- * If something is made pending in a finally clause, report it if required by
- * the 'verbose' option or when debugging.
- */
+/// If something is made pending in a finally clause, report it if required by
+/// the 'verbose' option or when debugging.
void report_make_pending(int pending, void *value)
{
if (p_verbose >= 14 || debug_break_level > 0) {
@@ -781,10 +762,8 @@ void report_make_pending(int pending, void *value)
}
}
-/*
- * If something pending in a finally clause is resumed at the ":endtry", report
- * it if required by the 'verbose' option or when debugging.
- */
+/// If something pending in a finally clause is resumed at the ":endtry", report
+/// it if required by the 'verbose' option or when debugging.
void report_resume_pending(int pending, void *value)
{
if (p_verbose >= 14 || debug_break_level > 0) {
@@ -798,10 +777,8 @@ void report_resume_pending(int pending, void *value)
}
}
-/*
- * If something pending in a finally clause is discarded, report it if required
- * by the 'verbose' option or when debugging.
- */
+/// If something pending in a finally clause is discarded, report it if required
+/// by the 'verbose' option or when debugging.
void report_discard_pending(int pending, void *value)
{
if (p_verbose >= 14 || debug_break_level > 0) {
@@ -815,7 +792,7 @@ void report_discard_pending(int pending, void *value)
}
}
-// ":eval".
+/// Handle ":eval".
void ex_eval(exarg_T *eap)
{
typval_T tv;
@@ -825,9 +802,7 @@ void ex_eval(exarg_T *eap)
}
}
-/*
- * ":if".
- */
+/// Handle ":if".
void ex_if(exarg_T *eap)
{
int skip;
@@ -856,9 +831,7 @@ void ex_if(exarg_T *eap)
}
}
-/*
- * ":endif".
- */
+/// Handle ":endif".
void ex_endif(exarg_T *eap)
{
did_endif = true;
@@ -883,9 +856,7 @@ void ex_endif(exarg_T *eap)
}
}
-/*
- * ":else" and ":elseif".
- */
+/// Handle ":else" and ":elseif".
void ex_else(exarg_T *eap)
{
int result;
@@ -958,9 +929,7 @@ void ex_else(exarg_T *eap)
}
}
-/*
- * Handle ":while" and ":for".
- */
+/// Handle ":while" and ":for".
void ex_while(exarg_T *eap)
{
bool error;
@@ -1041,9 +1010,7 @@ void ex_while(exarg_T *eap)
}
}
-/*
- * ":continue"
- */
+/// Handle ":continue"
void ex_continue(exarg_T *eap)
{
int idx;
@@ -1075,9 +1042,7 @@ void ex_continue(exarg_T *eap)
}
}
-/*
- * ":break"
- */
+/// Handle ":break"
void ex_break(exarg_T *eap)
{
int idx;
@@ -1098,9 +1063,7 @@ void ex_break(exarg_T *eap)
}
}
-/*
- * ":endwhile" and ":endfor"
- */
+/// Handle ":endwhile" and ":endfor"
void ex_endwhile(exarg_T *eap)
{
cstack_T *const cstack = eap->cstack;
@@ -1175,9 +1138,7 @@ void ex_endwhile(exarg_T *eap)
}
-/*
- * ":throw expr"
- */
+/// Handle ":throw expr"
void ex_throw(exarg_T *eap)
{
const char *arg = (const char *)eap->arg;
@@ -1202,11 +1163,9 @@ void ex_throw(exarg_T *eap)
}
}
-/*
- * Throw the current exception through the specified cstack. Common routine
- * for ":throw" (user exception) and error and interrupt exceptions. Also
- * used for rethrowing an uncaught exception.
- */
+/// Throw the current exception through the specified cstack. Common routine
+/// for ":throw" (user exception) and error and interrupt exceptions. Also
+/// used for rethrowing an uncaught exception.
void do_throw(cstack_T *cstack)
{
int idx;
@@ -1263,9 +1222,7 @@ void do_throw(cstack_T *cstack)
}
}
-/*
- * ":try"
- */
+/// Handle ":try"
void ex_try(exarg_T *eap)
{
int skip;
@@ -1315,9 +1272,7 @@ void ex_try(exarg_T *eap)
}
}
-/*
- * ":catch /{pattern}/" and ":catch"
- */
+/// Handle ":catch /{pattern}/" and ":catch"
void ex_catch(exarg_T *eap)
{
int idx = 0;
@@ -1471,9 +1426,7 @@ void ex_catch(exarg_T *eap)
}
}
-/*
- * ":finally"
- */
+/// Handle ":finally"
void ex_finally(exarg_T *eap)
{
int idx;
@@ -1595,9 +1548,7 @@ void ex_finally(exarg_T *eap)
}
}
-/*
- * ":endtry"
- */
+/// Handle ":endtry"
void ex_endtry(exarg_T *eap)
{
int idx;
@@ -1784,14 +1735,12 @@ void ex_endtry(exarg_T *eap)
* error/interrupt/exception state.
*/
-/*
- * This function works a bit like ex_finally() except that there was not
- * actually an extra try block around the part that failed and an error or
- * interrupt has not (yet) been converted to an exception. This function
- * saves the error/interrupt/ exception state and prepares for the call to
- * do_cmdline() that is going to be made for the cleanup autocommand
- * execution.
- */
+/// This function works a bit like ex_finally() except that there was not
+/// actually an extra try block around the part that failed and an error or
+/// interrupt has not (yet) been converted to an exception. This function
+/// saves the error/interrupt/ exception state and prepares for the call to
+/// do_cmdline() that is going to be made for the cleanup autocommand
+/// execution.
void enter_cleanup(cleanup_T *csp)
{
int pending = CSTP_NONE;
@@ -1834,21 +1783,19 @@ void enter_cleanup(cleanup_T *csp)
}
}
-/*
- * See comment above enter_cleanup() for how this function is used.
- *
- * This function is a bit like ex_endtry() except that there was not actually
- * an extra try block around the part that failed and an error or interrupt
- * had not (yet) been converted to an exception when the cleanup autocommand
- * sequence was invoked.
- *
- * This function has to be called with the address of the cleanup_T structure
- * filled by enter_cleanup() as an argument; it restores the error/interrupt/
- * exception state saved by that function - except there was an aborting
- * error, an interrupt or an uncaught exception during execution of the
- * cleanup autocommands. In the latter case, the saved error/interrupt/
- * exception state is discarded.
- */
+/// This function is a bit like ex_endtry() except that there was not actually
+/// an extra try block around the part that failed and an error or interrupt
+/// had not (yet) been converted to an exception when the cleanup autocommand
+/// sequence was invoked.
+///
+/// See comment above enter_cleanup() for how this function is used.
+///
+/// This function has to be called with the address of the cleanup_T structure
+/// filled by enter_cleanup() as an argument; it restores the error/interrupt/
+/// exception state saved by that function - except there was an aborting
+/// error, an interrupt or an uncaught exception during execution of the
+/// cleanup autocommands. In the latter case, the saved error/interrupt/
+/// exception state is discarded.
void leave_cleanup(cleanup_T *csp)
{
int pending = csp->pending;
@@ -1913,22 +1860,25 @@ void leave_cleanup(cleanup_T *csp)
}
-/*
- * Make conditionals inactive and discard what's pending in finally clauses
- * until the conditional type searched for or a try conditional not in its
- * finally clause is reached. If this is in an active catch clause, finish
- * the caught exception.
- * Return the cstack index where the search stopped.
- * Values used for "searched_cond" are (CSF_WHILE | CSF_FOR) or CSF_TRY or 0,
- * the latter meaning the innermost try conditional not in its finally clause.
- * "inclusive" tells whether the conditional searched for should be made
- * inactive itself (a try conditional not in its finally clause possibly find
- * before is always made inactive). If "inclusive" is TRUE and
- * "searched_cond" is CSF_TRY|CSF_SILENT, the saved former value of
- * "emsg_silent", if reset when the try conditional finally reached was
- * entered, is restored (used by ex_endtry()). This is normally done only
- * when such a try conditional is left.
- */
+/// Make conditionals inactive and discard what's pending in finally clauses
+/// until the conditional type searched for or a try conditional not in its
+/// finally clause is reached. If this is in an active catch clause, finish
+/// the caught exception.
+///
+///
+/// @param searched_cond Possible values are (CSF_WHILE | CSF_FOR) or CSF_TRY or 0,
+/// the latter meaning the innermost try conditional not
+/// in its finally clause.
+/// @param inclusive tells whether the conditional searched for should be made
+/// inactive itself (a try conditional not in its finally
+/// clause possibly find before is always made inactive).
+///
+/// If "inclusive" is TRUE and "searched_cond" is CSF_TRY|CSF_SILENT, the saved
+/// former value of "emsg_silent", if reset when the try conditional finally
+/// reached was entered, is restored (used by ex_endtry()). This is normally
+/// done only when such a try conditional is left.
+///
+/// @return the cstack index where the search stopped.
int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive)
{
int idx;
@@ -2037,9 +1987,7 @@ int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive)
return idx;
}
-/*
- * Return an appropriate error message for a missing endwhile/endfor/endif.
- */
+/// @return an appropriate error message for a missing endwhile/endfor/endif.
static char *get_end_emsg(cstack_T *cstack)
{
if (cstack->cs_flags[cstack->cs_idx] & CSF_WHILE) {
@@ -2052,13 +2000,11 @@ static char *get_end_emsg(cstack_T *cstack)
}
-/*
- * Rewind conditionals until index "idx" is reached. "cond_type" and
- * "cond_level" specify a conditional type and the address of a level variable
- * which is to be decremented with each skipped conditional of the specified
- * type.
- * Also free "for info" structures where needed.
- */
+/// Rewind conditionals until index "idx" is reached. "cond_type" and
+/// "cond_level" specify a conditional type and the address of a level variable
+/// which is to be decremented with each skipped conditional of the specified
+/// type.
+/// Also free "for info" structures where needed.
void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_level)
{
while (cstack->cs_idx > idx) {
@@ -2072,17 +2018,13 @@ void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_lev
}
}
-/*
- * ":endfunction" when not after a ":function"
- */
+/// Handle ":endfunction" when not after a ":function"
void ex_endfunction(exarg_T *eap)
{
emsg(_("E193: :endfunction not inside a function"));
}
-/*
- * Return TRUE if the string "p" looks like a ":while" or ":for" command.
- */
+/// @return TRUE if the string "p" looks like a ":while" or ":for" command.
int has_loop_cmd(char_u *p)
{
int len;
@@ -2104,4 +2046,3 @@ int has_loop_cmd(char_u *p)
}
return FALSE;
}
-
diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua
index f35817c466..70a7be86b5 100644
--- a/src/nvim/generators/c_grammar.lua
+++ b/src/nvim/generators/c_grammar.lua
@@ -49,6 +49,7 @@ local c_proto = Ct(
(fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) *
(fill * Cg((P('FUNC_API_BRIDGE_IMPL') * Cc(true)), 'bridge_impl') ^ -1) *
(fill * Cg((P('FUNC_API_COMPOSITOR_IMPL') * Cc(true)), 'compositor_impl') ^ -1) *
+ (fill * Cg((P('FUNC_API_CLIENT_IMPL') * Cc(true)), 'client_impl') ^ -1) *
fill * P(';')
)
diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua
index 3cb117d8b5..5e70442dce 100644..100755
--- a/src/nvim/generators/gen_api_ui_events.lua
+++ b/src/nvim/generators/gen_api_ui_events.lua
@@ -3,13 +3,14 @@ local mpack = require('mpack')
local nvimdir = arg[1]
package.path = nvimdir .. '/?.lua;' .. package.path
-assert(#arg == 7)
+assert(#arg == 8)
local input = io.open(arg[2], 'rb')
local proto_output = io.open(arg[3], 'wb')
local call_output = io.open(arg[4], 'wb')
local remote_output = io.open(arg[5], 'wb')
local bridge_output = io.open(arg[6], 'wb')
local metadata_output = io.open(arg[7], 'wb')
+local client_output = io.open(arg[8], 'wb')
local c_grammar = require('generators.c_grammar')
local events = c_grammar.grammar:match(input:read('*all'))
@@ -50,6 +51,52 @@ local function write_arglist(output, ev, need_copy)
end
end
+local function call_ui_event_method(output, ev)
+ output:write('void ui_client_event_'..ev.name..'(Array args)\n{\n')
+
+ local hlattrs_args_count = 0
+ if #ev.parameters > 0 then
+ output:write(' if (args.size < '..(#ev.parameters))
+ for j = 1, #ev.parameters do
+ local kind = ev.parameters[j][1]
+ if kind ~= "Object" then
+ if kind == 'HlAttrs' then kind = 'Dictionary' end
+ output:write('\n || args.items['..(j-1)..'].type != kObjectType'..kind..'')
+ end
+ end
+ output:write(') {\n')
+ output:write(' ELOG("Error handling ui event \''..ev.name..'\'");\n')
+ output:write(' return;\n')
+ output:write(' }\n')
+ end
+
+ for j = 1, #ev.parameters do
+ local param = ev.parameters[j]
+ local kind = param[1]
+ output:write(' '..kind..' arg_'..j..' = ')
+ if kind == 'HlAttrs' then
+ -- The first HlAttrs argument is rgb_attrs and second is cterm_attrs
+ output:write('ui_client_dict2hlattrs(args.items['..(j-1)..'].data.dictionary, '..(hlattrs_args_count == 0 and 'true' or 'false')..');\n')
+ hlattrs_args_count = hlattrs_args_count + 1
+ elseif kind == 'Object' then
+ output:write('args.items['..(j-1)..'];\n')
+ else
+ output:write('args.items['..(j-1)..'].data.'..string.lower(kind)..';\n')
+ end
+ end
+
+ output:write(' ui_call_'..ev.name..'(')
+ for j = 1, #ev.parameters do
+ output:write('arg_'..j)
+ if j ~= #ev.parameters then
+ output:write(', ')
+ end
+ end
+ output:write(');\n')
+
+ output:write('}\n\n')
+end
+
for i = 1, #events do
local ev = events[i]
assert(ev.return_type == 'void')
@@ -160,12 +207,35 @@ for i = 1, #events do
call_output:write(";\n")
call_output:write("}\n\n")
end
+
+ if (not ev.remote_only) and (not ev.noexport) and (not ev.client_impl) then
+ call_ui_event_method(client_output, ev)
+ end
end
+-- Generate the map_init method for client handlers
+client_output:write([[
+void ui_client_methods_table_init(void)
+{
+
+]])
+
+for i = 1, #events do
+ local fn = events[i]
+ if (not fn.noexport) and ((not fn.remote_only) or fn.client_impl) then
+ client_output:write(' add_ui_client_event_handler('..
+ '(String) {.data = "'..fn.name..'", '..
+ '.size = sizeof("'..fn.name..'") - 1}, '..
+ '(UIClientHandler) ui_client_event_'..fn.name..');\n')
+ end
+end
+
+client_output:write('\n}\n\n')
+
proto_output:close()
call_output:close()
remote_output:close()
-bridge_output:close()
+client_output:close()
-- don't expose internal attributes like "impl_name" in public metadata
local exported_attributes = {'name', 'parameters',
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 95ef306745..d67b47e82c 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -112,7 +112,6 @@ static const char *err_too_many_args = N_("Too many edit arguments");
static const char *err_extra_cmd =
N_("Too many \"+command\", \"-c command\" or \"--cmd command\" arguments");
-
void event_init(void)
{
loop_init(&main_loop, NULL);
@@ -344,6 +343,12 @@ int main(int argc, char **argv)
TIME_MSG("init screen for UI");
}
+ if (ui_client_channel_id) {
+ ui_client_init(ui_client_channel_id);
+ ui_client_execute(ui_client_channel_id);
+ abort(); // unreachable
+ }
+
init_default_mappings(); // Default mappings.
TIME_MSG("init default mappings");
@@ -840,9 +845,8 @@ static void remote_request(mparm_T *params, int remote_args,
exit(1);
}
- ui_client_init(chan);
- ui_client_execute(chan);
- abort(); // unreachable
+ ui_client_channel_id = chan;
+ return;
}
Array args = ARRAY_DICT_INIT;
diff --git a/src/nvim/map.c b/src/nvim/map.c
index 4e39eb8c07..b3f48ad5d6 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -179,6 +179,7 @@ MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
MAP_IMPL(String, handle_T, 0)
MAP_IMPL(String, int, DEFAULT_INITIALIZER)
MAP_IMPL(int, String, DEFAULT_INITIALIZER)
+MAP_IMPL(String, UIClientHandler, NULL)
MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER)
diff --git a/src/nvim/map.h b/src/nvim/map.h
index 00f72386a7..693ef50127 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -8,6 +8,7 @@
#include "nvim/extmark_defs.h"
#include "nvim/highlight_defs.h"
#include "nvim/map_defs.h"
+#include "nvim/ui_client.h"
#if defined(__NetBSD__)
# undef uint64_t
@@ -48,6 +49,7 @@ MAP_DECLS(HlEntry, int)
MAP_DECLS(String, handle_T)
MAP_DECLS(String, int)
MAP_DECLS(int, String)
+MAP_DECLS(String, UIClientHandler)
MAP_DECLS(ColorKey, ColorItem)
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index 5d007fb173..6b27ce9d7b 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -535,6 +535,22 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
return NULL;
}
+/// Convert a virtual (screen) column to a character column.
+/// The first column is one.
+colnr_T vcol2col(win_T *const wp, const linenr_T lnum, const colnr_T vcol)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // try to advance to the specified column
+ char_u *ptr = ml_get_buf(wp->w_buffer, lnum, false);
+ char_u *const line = ptr;
+ colnr_T count = 0;
+ while (count < vcol && *ptr != NUL) {
+ count += win_lbr_chartabsize(wp, line, ptr, count, NULL);
+ MB_PTR_ADV(ptr);
+ }
+ return (colnr_T)(ptr - line);
+}
+
/// Set UI mouse depending on current mode and 'mouse'.
///
/// Emits mouse_on/mouse_off UI event (unless 'mouse' is empty).
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index f4e836fa81..48ecd5d0ea 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -547,12 +547,8 @@ void rpc_close(Channel *channel)
channel->rpc.closed = true;
channel_decref(channel);
- if (channel->id == ui_client_channel_id) {
- // TODO(bfredl): handle this in ui_client, where os_exit() is safe
- exit(0);
- }
-
- if (channel->streamtype == kChannelStreamStdio) {
+ if (channel->streamtype == kChannelStreamStdio
+ || channel->id == ui_client_channel_id) {
multiqueue_put(main_loop.fast_events, exit_event, 0);
}
}
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 5f4a7dac6e..839d160e39 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -196,7 +196,7 @@ let s:filename_checks = {
\ 'fstab': ['fstab', 'mtab'],
\ 'fusion': ['file.fusion'],
\ 'fvwm': ['/.fvwm/file', 'any/.fvwm/file'],
- \ 'gdb': ['.gdbinit', 'gdbinit'],
+ \ 'gdb': ['.gdbinit', 'gdbinit', 'file.gdb', '.config/gdbearlyinit', '.gdbearlyinit'],
\ 'gdresource': ['file.tscn', 'file.tres'],
\ 'gdscript': ['file.gd'],
\ 'gdmo': ['file.mo', 'file.gdmo'],
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 994d74601a..c2b5653a29 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -1762,6 +1762,75 @@ func Test_getcurpos_setpos()
call assert_equal([0, 0, 0, 0, 0], getcurpos(1999))
endfunc
+func Test_getmousepos()
+ enew!
+ call setline(1, "\t\t\t1234")
+ " call test_setmouse(1, 1)
+ call nvim_input_mouse('left', 'press', '', 0, 0, 0)
+ call getchar() " wait for and consume the mouse press
+ call assert_equal(#{
+ \ screenrow: 1,
+ \ screencol: 1,
+ \ winid: win_getid(),
+ \ winrow: 1,
+ \ wincol: 1,
+ \ line: 1,
+ \ column: 1,
+ \ }, getmousepos())
+ " call test_setmouse(1, 25)
+ call nvim_input_mouse('left', 'press', '', 0, 0, 24)
+ call getchar() " wait for and consume the mouse press
+ call assert_equal(#{
+ \ screenrow: 1,
+ \ screencol: 25,
+ \ winid: win_getid(),
+ \ winrow: 1,
+ \ wincol: 25,
+ \ line: 1,
+ \ column: 4,
+ \ }, getmousepos())
+ " call test_setmouse(1, 50)
+ call nvim_input_mouse('left', 'press', '', 0, 0, 49)
+ call getchar() " wait for and consume the mouse press
+ call assert_equal(#{
+ \ screenrow: 1,
+ \ screencol: 50,
+ \ winid: win_getid(),
+ \ winrow: 1,
+ \ wincol: 50,
+ \ line: 1,
+ \ column: 8,
+ \ }, getmousepos())
+
+ " If the mouse is positioned past the last buffer line, "line" and "column"
+ " should act like it's positioned on the last buffer line.
+ " call test_setmouse(2, 25)
+ call nvim_input_mouse('left', 'press', '', 0, 1, 24)
+ call getchar() " wait for and consume the mouse press
+ call assert_equal(#{
+ \ screenrow: 2,
+ \ screencol: 25,
+ \ winid: win_getid(),
+ \ winrow: 2,
+ \ wincol: 25,
+ \ line: 1,
+ \ column: 4,
+ \ }, getmousepos())
+ " call test_setmouse(2, 50)
+ call nvim_input_mouse('left', 'press', '', 0, 1, 49)
+ call getchar() " wait for and consume the mouse press
+ call assert_equal(#{
+ \ screenrow: 2,
+ \ screencol: 50,
+ \ winid: win_getid(),
+ \ winrow: 2,
+ \ wincol: 50,
+ \ line: 1,
+ \ column: 8,
+ \ }, getmousepos())
+ bwipe!
+endfunc
+
func HasDefault(msg = 'msg')
return a:msg
endfunc
diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c
index 4a435aac4d..4fad3e0709 100644
--- a/src/nvim/ui_client.c
+++ b/src/nvim/ui_client.c
@@ -6,40 +6,55 @@
#include <assert.h>
#include "nvim/vim.h"
+#include "nvim/log.h"
+#include "nvim/map.h"
#include "nvim/ui_client.h"
#include "nvim/api/private/helpers.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/ui.h"
+#include "nvim/highlight.h"
+#include "nvim/screen.h"
+
+static Map(String, UIClientHandler) ui_client_handlers = MAP_INIT;
+
+// Temporary buffer for converting a single grid_line event
+static size_t buf_size = 0;
+static schar_T *buf_char = NULL;
+static sattr_T *buf_attr = NULL;
+
+static void add_ui_client_event_handler(String method, UIClientHandler handler)
+{
+ map_put(String, UIClientHandler)(&ui_client_handlers, method, handler);
+}
void ui_client_init(uint64_t chan)
{
Array args = ARRAY_DICT_INIT;
- int width = 80;
- int height = 25;
+ int width = Columns;
+ int height = Rows;
Dictionary opts = ARRAY_DICT_INIT;
PUT(opts, "rgb", BOOLEAN_OBJ(true));
PUT(opts, "ext_linegrid", BOOLEAN_OBJ(true));
PUT(opts, "ext_termcolors", BOOLEAN_OBJ(true));
- // TODO(bfredl): use the size of the client UI
ADD(args, INTEGER_OBJ((int)width));
ADD(args, INTEGER_OBJ((int)height));
ADD(args, DICTIONARY_OBJ(opts));
rpc_send_event(chan, "nvim_ui_attach", args);
msgpack_rpc_add_redraw(); // GAME!
+ // TODO(bfredl): use a keyset instead
+ ui_client_methods_table_init();
ui_client_channel_id = chan;
}
/// Handler for "redraw" events sent by the NVIM server
///
-/// This is just a stub. The mentioned functionality will be implemented.
-///
-/// This function will be called by handle_request (in msgpack_rpc/channle.c)
+/// This function will be called by handle_request (in msgpack_rpc/channel.c)
/// The individual ui_events sent by the server are individually handled
-/// by their respective handlers defined in ui_events_redraw.generated.h
+/// by their respective handlers defined in ui_events_client.generated.h
///
/// @note The "flush" event is called only once and only after handling all
/// the other events
@@ -50,10 +65,21 @@ Object ui_client_handle_redraw(uint64_t channel_id, Array args, Error *error)
{
for (size_t i = 0; i < args.size; i++) {
Array call = args.items[i].data.array;
- char *method_name = call.items[0].data.string.data;
+ String name = call.items[0].data.string;
+
+ UIClientHandler handler = map_get(String, UIClientHandler)(&ui_client_handlers, name);
+ if (!handler) {
+ ELOG("No ui client handler for %s", name.size ? name.data : "<empty>");
+ continue;
+ }
- fprintf(stderr, "%s: %zu\n", method_name, call.size-1);
+ // fprintf(stderr, "%s: %zu\n", name.data, call.size-1);
+ DLOG("Invoke ui client handler for %s", name.data);
+ for (size_t j = 1; j < call.size; j++) {
+ handler(call.items[j].data.array);
+ }
}
+
return NIL;
}
@@ -68,3 +94,121 @@ void ui_client_execute(uint64_t chan)
getout(0);
}
+
+static HlAttrs ui_client_dict2hlattrs(Dictionary d, bool rgb)
+{
+ Error err = ERROR_INIT;
+ Dict(highlight) dict = { 0 };
+ if (!api_dict_to_keydict(&dict, KeyDict_highlight_get_field, d, &err)) {
+ // TODO(bfredl): log "err"
+ return HLATTRS_INIT;
+ }
+ return dict2hlattrs(&dict, true, NULL, &err);
+}
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+#include "ui_events_client.generated.h"
+#endif
+
+void ui_client_event_grid_resize(Array args)
+{
+ if (args.size < 3
+ || args.items[0].type != kObjectTypeInteger
+ || args.items[1].type != kObjectTypeInteger
+ || args.items[2].type != kObjectTypeInteger) {
+ ELOG("Error handling ui event 'grid_resize'");
+ return;
+ }
+
+ Integer grid = args.items[0].data.integer;
+ Integer width = args.items[1].data.integer;
+ Integer height = args.items[2].data.integer;
+ ui_call_grid_resize(grid, width, height);
+
+ if (buf_size < (size_t)width) {
+ xfree(buf_char);
+ xfree(buf_attr);
+ buf_size = (size_t)width;
+ buf_char = xmalloc(buf_size * sizeof(schar_T));
+ buf_attr = xmalloc(buf_size * sizeof(sattr_T));
+ }
+}
+
+void ui_client_event_grid_line(Array args)
+{
+ if (args.size < 4
+ || args.items[0].type != kObjectTypeInteger
+ || args.items[1].type != kObjectTypeInteger
+ || args.items[2].type != kObjectTypeInteger
+ || args.items[3].type != kObjectTypeArray) {
+ goto error;
+ }
+
+ Integer grid = args.items[0].data.integer;
+ Integer row = args.items[1].data.integer;
+ Integer startcol = args.items[2].data.integer;
+ Array cells = args.items[3].data.array;
+
+ Integer endcol, clearcol;
+ // TODO(hlpr98): Accomodate other LineFlags when included in grid_line
+ LineFlags lineflags = 0;
+ endcol = startcol;
+
+ size_t j = 0;
+ int cur_attr = 0;
+ int clear_attr = 0;
+ int clear_width = 0;
+ for (size_t i = 0; i < cells.size; i++) {
+ if (cells.items[i].type != kObjectTypeArray) {
+ goto error;
+ }
+ Array cell = cells.items[i].data.array;
+
+ if (cell.size < 1 || cell.items[0].type != kObjectTypeString) {
+ goto error;
+ }
+ String sstring = cell.items[0].data.string;
+
+ char *schar = sstring.data;
+ int repeat = 1;
+ if (cell.size >= 2) {
+ if (cell.items[1].type != kObjectTypeInteger
+ || cell.items[1].data.integer < 0) {
+ goto error;
+ }
+ cur_attr = (int)cell.items[1].data.integer;
+ }
+
+ if (cell.size >= 3) {
+ if (cell.items[2].type != kObjectTypeInteger
+ || cell.items[2].data.integer < 0) {
+ goto error;
+ }
+ repeat = (int)cell.items[2].data.integer;
+ }
+
+ if (i == cells.size - 1 && sstring.size == 1 && sstring.data[0] == ' ' && repeat > 1) {
+ clear_width = repeat;
+ break;
+ }
+
+ for (int r = 0; r < repeat; r++) {
+ if (j >= buf_size) {
+ goto error; // _YIKES_
+ }
+ STRLCPY(buf_char[j], schar, sizeof(schar_T));
+ buf_attr[j++] = cur_attr;
+ }
+ }
+
+ endcol = startcol + (int)j;
+ clearcol = endcol + clear_width;
+ clear_attr = cur_attr;
+
+ ui_call_raw_line(grid, row, startcol, endcol, clearcol, clear_attr, lineflags,
+ (const schar_T *)buf_char, (const sattr_T *)buf_attr);
+ return;
+
+error:
+ ELOG("Error handling ui event 'grid_line'");
+}
diff --git a/src/nvim/ui_client.h b/src/nvim/ui_client.h
index 067f78d5c5..253deecc52 100644
--- a/src/nvim/ui_client.h
+++ b/src/nvim/ui_client.h
@@ -3,7 +3,11 @@
#include "nvim/api/private/defs.h"
+typedef void (*UIClientHandler)(Array args);
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
#include "ui_client.h.generated.h"
+#include "ui_events_client.h.generated.h"
#endif
+
#endif // NVIM_UI_CLIENT_H
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index e945a6c706..af6872760a 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -636,34 +636,374 @@ describe('API', function()
eq('Invalid phase: 4',
pcall_err(request, 'nvim_paste', 'foo', true, 4))
end)
- it('stream: multiple chunks form one undo-block', function()
- nvim('paste', '1/chunk 1 (start)\n', true, 1)
- nvim('paste', '1/chunk 2 (end)\n', true, 3)
- local expected1 = [[
- 1/chunk 1 (start)
- 1/chunk 2 (end)
- ]]
- expect(expected1)
- nvim('paste', '2/chunk 1 (start)\n', true, 1)
- nvim('paste', '2/chunk 2\n', true, 2)
- expect([[
- 1/chunk 1 (start)
- 1/chunk 2 (end)
- 2/chunk 1 (start)
- 2/chunk 2
- ]])
- nvim('paste', '2/chunk 3\n', true, 2)
- nvim('paste', '2/chunk 4 (end)\n', true, 3)
- expect([[
- 1/chunk 1 (start)
- 1/chunk 2 (end)
- 2/chunk 1 (start)
- 2/chunk 2
- 2/chunk 3
- 2/chunk 4 (end)
- ]])
- feed('u') -- Undo.
- expect(expected1)
+ local function run_streamed_paste_tests()
+ it('stream: multiple chunks form one undo-block', function()
+ nvim('paste', '1/chunk 1 (start)\n', true, 1)
+ nvim('paste', '1/chunk 2 (end)\n', true, 3)
+ local expected1 = [[
+ 1/chunk 1 (start)
+ 1/chunk 2 (end)
+ ]]
+ expect(expected1)
+ nvim('paste', '2/chunk 1 (start)\n', true, 1)
+ nvim('paste', '2/chunk 2\n', true, 2)
+ expect([[
+ 1/chunk 1 (start)
+ 1/chunk 2 (end)
+ 2/chunk 1 (start)
+ 2/chunk 2
+ ]])
+ nvim('paste', '2/chunk 3\n', true, 2)
+ nvim('paste', '2/chunk 4 (end)\n', true, 3)
+ expect([[
+ 1/chunk 1 (start)
+ 1/chunk 2 (end)
+ 2/chunk 1 (start)
+ 2/chunk 2
+ 2/chunk 3
+ 2/chunk 4 (end)
+ ]])
+ feed('u') -- Undo.
+ expect(expected1)
+ end)
+ it('stream: Insert mode', function()
+ -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
+ feed('afoo<Esc>u')
+ feed('i')
+ nvim('paste', 'aaaaaa', false, 1)
+ nvim('paste', 'bbbbbb', false, 2)
+ nvim('paste', 'cccccc', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect('aaaaaabbbbbbccccccdddddd')
+ feed('<Esc>u')
+ expect('')
+ end)
+ describe('stream: Normal mode', function()
+ describe('on empty line', function()
+ before_each(function()
+ -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
+ feed('afoo<Esc>u')
+ end)
+ after_each(function()
+ feed('u')
+ expect('')
+ end)
+ it('pasting one line', function()
+ nvim('paste', 'aaaaaa', false, 1)
+ nvim('paste', 'bbbbbb', false, 2)
+ nvim('paste', 'cccccc', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect('aaaaaabbbbbbccccccdddddd')
+ end)
+ it('pasting multiple lines', function()
+ nvim('paste', 'aaaaaa\n', false, 1)
+ nvim('paste', 'bbbbbb\n', false, 2)
+ nvim('paste', 'cccccc\n', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect([[
+ aaaaaa
+ bbbbbb
+ cccccc
+ dddddd]])
+ end)
+ end)
+ describe('not at the end of a line', function()
+ before_each(function()
+ feed('i||<Esc>')
+ -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
+ feed('afoo<Esc>u')
+ feed('0')
+ end)
+ after_each(function()
+ feed('u')
+ expect('||')
+ end)
+ it('pasting one line', function()
+ nvim('paste', 'aaaaaa', false, 1)
+ nvim('paste', 'bbbbbb', false, 2)
+ nvim('paste', 'cccccc', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect('|aaaaaabbbbbbccccccdddddd|')
+ end)
+ it('pasting multiple lines', function()
+ nvim('paste', 'aaaaaa\n', false, 1)
+ nvim('paste', 'bbbbbb\n', false, 2)
+ nvim('paste', 'cccccc\n', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect([[
+ |aaaaaa
+ bbbbbb
+ cccccc
+ dddddd|]])
+ end)
+ end)
+ describe('at the end of a line', function()
+ before_each(function()
+ feed('i||<Esc>')
+ -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
+ feed('afoo<Esc>u')
+ feed('2|')
+ end)
+ after_each(function()
+ feed('u')
+ expect('||')
+ end)
+ it('pasting one line', function()
+ nvim('paste', 'aaaaaa', false, 1)
+ nvim('paste', 'bbbbbb', false, 2)
+ nvim('paste', 'cccccc', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect('||aaaaaabbbbbbccccccdddddd')
+ end)
+ it('pasting multiple lines', function()
+ nvim('paste', 'aaaaaa\n', false, 1)
+ nvim('paste', 'bbbbbb\n', false, 2)
+ nvim('paste', 'cccccc\n', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect([[
+ ||aaaaaa
+ bbbbbb
+ cccccc
+ dddddd]])
+ end)
+ end)
+ end)
+ describe('stream: Visual mode', function()
+ describe('neither end at the end of a line', function()
+ before_each(function()
+ feed('i|xxx<CR>xxx|<Esc>')
+ -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
+ feed('afoo<Esc>u')
+ feed('3|vhk')
+ end)
+ after_each(function()
+ feed('u')
+ expect([[
+ |xxx
+ xxx|]])
+ end)
+ it('with non-empty chunks', function()
+ nvim('paste', 'aaaaaa', false, 1)
+ nvim('paste', 'bbbbbb', false, 2)
+ nvim('paste', 'cccccc', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect('|aaaaaabbbbbbccccccdddddd|')
+ end)
+ it('with empty first chunk', function()
+ nvim('paste', '', false, 1)
+ nvim('paste', 'bbbbbb', false, 2)
+ nvim('paste', 'cccccc', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect('|bbbbbbccccccdddddd|')
+ end)
+ it('with all chunks empty', function()
+ nvim('paste', '', false, 1)
+ nvim('paste', '', false, 2)
+ nvim('paste', '', false, 2)
+ nvim('paste', '', false, 3)
+ expect('||')
+ end)
+ end)
+ describe('cursor at the end of a line', function()
+ before_each(function()
+ feed('i||xxx<CR>xxx<Esc>')
+ -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
+ feed('afoo<Esc>u')
+ feed('3|vko')
+ end)
+ after_each(function()
+ feed('u')
+ expect([[
+ ||xxx
+ xxx]])
+ end)
+ it('with non-empty chunks', function()
+ nvim('paste', 'aaaaaa', false, 1)
+ nvim('paste', 'bbbbbb', false, 2)
+ nvim('paste', 'cccccc', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect('||aaaaaabbbbbbccccccdddddd')
+ end)
+ it('with empty first chunk', function()
+ nvim('paste', '', false, 1)
+ nvim('paste', 'bbbbbb', false, 2)
+ nvim('paste', 'cccccc', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect('||bbbbbbccccccdddddd')
+ end)
+ end)
+ describe('other end at the end of a line', function()
+ before_each(function()
+ feed('i||xxx<CR>xxx<Esc>')
+ -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
+ feed('afoo<Esc>u')
+ feed('3|vk')
+ end)
+ after_each(function()
+ feed('u')
+ expect([[
+ ||xxx
+ xxx]])
+ end)
+ it('with non-empty chunks', function()
+ nvim('paste', 'aaaaaa', false, 1)
+ nvim('paste', 'bbbbbb', false, 2)
+ nvim('paste', 'cccccc', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect('||aaaaaabbbbbbccccccdddddd')
+ end)
+ it('with empty first chunk', function()
+ nvim('paste', '', false, 1)
+ nvim('paste', 'bbbbbb', false, 2)
+ nvim('paste', 'cccccc', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect('||bbbbbbccccccdddddd')
+ end)
+ end)
+ end)
+ describe('stream: linewise Visual mode', function()
+ before_each(function()
+ feed('i123456789<CR>987654321<CR>123456789<Esc>')
+ -- If nvim_paste() calls :undojoin without making any changes, this makes it an error.
+ feed('afoo<Esc>u')
+ end)
+ after_each(function()
+ feed('u')
+ expect([[
+ 123456789
+ 987654321
+ 123456789]])
+ end)
+ describe('selecting the start of a file', function()
+ before_each(function()
+ feed('ggV')
+ end)
+ it('pasting text without final new line', function()
+ nvim('paste', 'aaaaaa\n', false, 1)
+ nvim('paste', 'bbbbbb\n', false, 2)
+ nvim('paste', 'cccccc\n', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect([[
+ aaaaaa
+ bbbbbb
+ cccccc
+ dddddd987654321
+ 123456789]])
+ end)
+ it('pasting text with final new line', function()
+ nvim('paste', 'aaaaaa\n', false, 1)
+ nvim('paste', 'bbbbbb\n', false, 2)
+ nvim('paste', 'cccccc\n', false, 2)
+ nvim('paste', 'dddddd\n', false, 3)
+ expect([[
+ aaaaaa
+ bbbbbb
+ cccccc
+ dddddd
+ 987654321
+ 123456789]])
+ end)
+ end)
+ describe('selecting the middle of a file', function()
+ before_each(function()
+ feed('2ggV')
+ end)
+ it('pasting text without final new line', function()
+ nvim('paste', 'aaaaaa\n', false, 1)
+ nvim('paste', 'bbbbbb\n', false, 2)
+ nvim('paste', 'cccccc\n', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect([[
+ 123456789
+ aaaaaa
+ bbbbbb
+ cccccc
+ dddddd123456789]])
+ end)
+ it('pasting text with final new line', function()
+ nvim('paste', 'aaaaaa\n', false, 1)
+ nvim('paste', 'bbbbbb\n', false, 2)
+ nvim('paste', 'cccccc\n', false, 2)
+ nvim('paste', 'dddddd\n', false, 3)
+ expect([[
+ 123456789
+ aaaaaa
+ bbbbbb
+ cccccc
+ dddddd
+ 123456789]])
+ end)
+ end)
+ describe('selecting the end of a file', function()
+ before_each(function()
+ feed('3ggV')
+ end)
+ it('pasting text without final new line', function()
+ nvim('paste', 'aaaaaa\n', false, 1)
+ nvim('paste', 'bbbbbb\n', false, 2)
+ nvim('paste', 'cccccc\n', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect([[
+ 123456789
+ 987654321
+ aaaaaa
+ bbbbbb
+ cccccc
+ dddddd]])
+ end)
+ it('pasting text with final new line', function()
+ nvim('paste', 'aaaaaa\n', false, 1)
+ nvim('paste', 'bbbbbb\n', false, 2)
+ nvim('paste', 'cccccc\n', false, 2)
+ nvim('paste', 'dddddd\n', false, 3)
+ expect([[
+ 123456789
+ 987654321
+ aaaaaa
+ bbbbbb
+ cccccc
+ dddddd
+ ]])
+ end)
+ end)
+ describe('selecting the whole file', function()
+ before_each(function()
+ feed('ggVG')
+ end)
+ it('pasting text without final new line', function()
+ nvim('paste', 'aaaaaa\n', false, 1)
+ nvim('paste', 'bbbbbb\n', false, 2)
+ nvim('paste', 'cccccc\n', false, 2)
+ nvim('paste', 'dddddd', false, 3)
+ expect([[
+ aaaaaa
+ bbbbbb
+ cccccc
+ dddddd]])
+ end)
+ it('pasting text with final new line', function()
+ nvim('paste', 'aaaaaa\n', false, 1)
+ nvim('paste', 'bbbbbb\n', false, 2)
+ nvim('paste', 'cccccc\n', false, 2)
+ nvim('paste', 'dddddd\n', false, 3)
+ expect([[
+ aaaaaa
+ bbbbbb
+ cccccc
+ dddddd
+ ]])
+ end)
+ end)
+ end)
+ end
+ describe('without virtualedit,', function()
+ run_streamed_paste_tests()
+ end)
+ describe('with virtualedit=onemore,', function()
+ before_each(function()
+ command('set virtualedit=onemore')
+ end)
+ run_streamed_paste_tests()
end)
it('non-streaming', function()
-- With final "\n".
@@ -738,6 +1078,37 @@ describe('API', function()
eeffgghh
iijjkkll]])
end)
+ it('when searching in Visual mode', function()
+ feed('v/')
+ nvim('paste', 'aabbccdd', true, -1)
+ eq('aabbccdd', funcs.getcmdline())
+ expect('')
+ end)
+ it('pasting with empty last chunk in Cmdline mode', function()
+ local screen = Screen.new(20, 4)
+ screen:attach()
+ feed(':')
+ nvim('paste', 'Foo', true, 1)
+ nvim('paste', '', true, 3)
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ :Foo^ |
+ ]])
+ end)
+ it('pasting text with control characters in Cmdline mode', function()
+ local screen = Screen.new(20, 4)
+ screen:attach()
+ feed(':')
+ nvim('paste', 'normal! \023\022\006\027', true, -1)
+ screen:expect([[
+ |
+ ~ |
+ ~ |
+ :normal! ^W^V^F^[^ |
+ ]])
+ end)
it('crlf=false does not break lines at CR, CRLF', function()
nvim('paste', 'line 1\r\n\r\rline 2\nline 3\rline 4\r', false, -1)
expect('line 1\r\n\r\rline 2\nline 3\rline 4\r')
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index faf44fa01d..c37cde06ab 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -323,7 +323,7 @@ describe('TUI', function()
feed_data('just paste it™')
feed_data('\027[201~')
screen:expect{grid=[[
- thisjust paste it™{1:3} is here |
+ thisjust paste it{1:™}3 is here |
|
{4:~ }|
{4:~ }|
@@ -379,7 +379,7 @@ describe('TUI', function()
end)
it('paste: normal-mode (+CRLF #10872)', function()
- feed_data(':set ruler')
+ feed_data(':set ruler | echo')
wait_for_mode('c')
feed_data('\n')
wait_for_mode('n')
@@ -423,13 +423,13 @@ describe('TUI', function()
expect_child_buf_lines(expected_crlf)
feed_data('u')
expect_child_buf_lines({''})
+ feed_data(':echo')
+ wait_for_mode('c')
+ feed_data('\n')
+ wait_for_mode('n')
-- CRLF input
feed_data('\027[200~'..table.concat(expected_lf,'\r\n')..'\027[201~')
- screen:expect{
- grid=expected_grid1:gsub(
- ':set ruler *',
- '3 fewer lines; before #1 0 seconds ago '),
- attr_ids=expected_attr}
+ screen:expect{grid=expected_grid1, attr_ids=expected_attr}
expect_child_buf_lines(expected_crlf)
end)