diff options
59 files changed, 1612 insertions, 585 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 93e153be53..4aa6a2de32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -256,6 +256,10 @@ option( if(TRAVIS_CI_BUILD) message(STATUS "Travis CI build enabled.") add_definitions(-Werror) + if(DEFINED ENV{BUILD_32BIT}) + # Get some test coverage for unsigned char + add_definitions(-funsigned-char) + endif() endif() if(CMAKE_BUILD_TYPE MATCHES Debug) @@ -13,10 +13,6 @@ CLINT_ERRORS_FILE_PATH := /reports/clint/errors.json BUILD_TYPE ?= $(shell (type ninja > /dev/null 2>&1 && echo "Ninja") || \ echo "Unix Makefiles") -ifneq (,$(PREFIX)) - CMAKE_FLAGS += -DCMAKE_INSTALL_PREFIX:PATH="$(PREFIX)" -endif - ifeq (,$(BUILD_TOOL)) ifeq (Ninja,$(BUILD_TYPE)) ifneq ($(shell cmake --help 2>/dev/null | grep Ninja),) diff --git a/contrib/local.mk.example b/contrib/local.mk.example index 6959107ea2..a0b2d034e1 100644 --- a/contrib/local.mk.example +++ b/contrib/local.mk.example @@ -2,7 +2,7 @@ # Individual entries must be uncommented to take effect. # By default, the installation prefix is '/usr/local'. -# PREFIX := /usr/local/nvim-latest +# CMAKE_EXTRA_FLAGS += -DCMAKE_INSTALL_PREFIX=/usr/local/nvim-latest # These CFLAGS can be used in addition to those specified in CMakeLists.txt: # CMAKE_EXTRA_FLAGS="-DCMAKE_C_FLAGS=-ftrapv -Wlogical-op" diff --git a/runtime/autoload/health.vim b/runtime/autoload/health.vim index 783c30cbf6..336adc65e5 100644 --- a/runtime/autoload/health.vim +++ b/runtime/autoload/health.vim @@ -13,6 +13,9 @@ function! s:enhance_syntax() abort syntax keyword healthSuggestion SUGGESTIONS highlight link healthSuggestion String + + " We do not care about markdown syntax errors in :CheckHealth output. + highlight! link markdownError Normal endfunction " Runs the specified healthchecks. diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim index d769525373..60e56034e1 100644 --- a/runtime/autoload/health/nvim.vim +++ b/runtime/autoload/health/nvim.vim @@ -57,6 +57,57 @@ function! s:check_manifest() abort endif endfunction +function! s:check_tmux() abort + if empty($TMUX) || !executable('tmux') + return + endif + call health#report_start('tmux configuration') + let suggestions = ["Set escape-time in ~/.tmux.conf: set-option -sg escape-time 10", + \ 'See https://github.com/neovim/neovim/wiki/FAQ'] + let cmd = 'tmux show-option -qvgs escape-time' + let out = system(cmd) + let tmux_esc_time = substitute(out, '\v(\s|\r|\n)', '', 'g') + + if v:shell_error + call health#report_error('command failed: '.cmd."\n".out) + elseif empty(tmux_esc_time) + call health#report_error('escape-time is not set', suggestions) + elseif tmux_esc_time > 500 + call health#report_error( + \ 'escape-time ('.tmux_esc_time.') is higher than 300ms', suggestions) + else + call health#report_ok('escape-time = '.tmux_esc_time.'ms') + endif +endfunction + +function! s:check_terminfo() abort + if !executable('infocmp') + return + endif + call health#report_start('terminfo') + let suggestions = [ + \ "Set key_backspace to \\177 (ASCII BACKSPACE). Run these commands:\n" + \ .'infocmp $TERM | sed ''s/kbs=^[hH]/kbs=\\177/'' > $TERM.ti' + \ ."\n" + \ .'tic $TERM.ti', + \ 'See https://github.com/neovim/neovim/wiki/FAQ'] + let cmd = 'infocmp -L' + let out = system(cmd) + let kbs_entry = matchstr(out, 'key_backspace=\S*') + + if v:shell_error + call health#report_error('command failed: '.cmd."\n".out) + elseif !empty(matchstr(out, '\Vkey_backspace=^H')) + call health#report_error('key_backspace (kbs) entry is ^H (ASCII DELETE): ' + \ .kbs_entry, suggestions) + else + call health#report_info('key_backspace terminfo entry: ' + \ .(empty(kbs_entry) ? '? (not found)' : kbs_entry)) + endif +endfunction + function! health#nvim#check() abort call s:check_manifest() + call s:check_tmux() + call s:check_terminfo() endfunction diff --git a/runtime/autoload/remote/host.vim b/runtime/autoload/remote/host.vim index 1f30b91ab8..51f7e5886f 100644 --- a/runtime/autoload/remote/host.vim +++ b/runtime/autoload/remote/host.vim @@ -190,7 +190,7 @@ function! s:RegistrationCommands(host) abort call remote#host#RegisterClone(host_id, a:host) let pattern = s:plugin_patterns[a:host] let paths = globpath(&rtp, 'rplugin/'.a:host.'/'.pattern, 0, 1) - let paths = map(paths, 'tr(v:val,"\\","/")') " Normalize slashes #4795 + let paths = map(paths, 'tr(resolve(v:val),"\\","/")') " Normalize slashes #4795 let paths = uniq(sort(paths)) if empty(paths) return [] diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index 2b69929cfe..6997d331e4 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -6,6 +6,10 @@ Nvim *deprecated* +The items listed below are "deprecated". This means they will be removed in +the future. They should not be used in new scripts, and old scripts should be +updated. + ============================================================================== Normal commands ~ diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 1eb5ec45c0..cfd62bacfe 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -6840,23 +6840,30 @@ system({cmd} [, {input}]) *system()* *E677* Get the output of the shell command {cmd} as a |string|. {cmd} will be run the same as in |jobstart()|. See |systemlist()| to get the output as a |List|. - - When {input} is given and is a string this string is written - to a file and passed as stdin to the command. The string is - written as-is, you need to take care of using the correct line - separators yourself. - If {input} is given and is a |List| it is written to the file - in a way |writefile()| does with {binary} set to "b" (i.e. - with a newline between each list item with newlines inside - list items converted to NULs). - Pipes are not used. + Not to be used for interactive commands. + + If {input} is a string it is written to a pipe and passed as + stdin to the command. The string is written as-is, line + separators are not changed. + If {input} is a |List| it is written to the pipe as + |writefile()| does with {binary} set to "b" (i.e. with + a newline between each list item, and newlines inside list + items converted to NULs). + *E5677* + Note: system() cannot write to or read from backgrounded ("&") + shell commands, e.g.: > + :echo system("cat - &", "foo")) +< which is equivalent to: > + $ echo foo | bash -c 'cat - &' +< The pipes are disconnected (unless overridden by shell + redirection syntax) before input can reach it. Use + |jobstart()| instead. Note: Use |shellescape()| or |::S| with |expand()| or |fnamemodify()| to escape special characters in a command argument. Newlines in {cmd} may cause the command to fail. The characters in 'shellquote' and 'shellxquote' may also cause trouble. - This is not to be used for interactive commands. The result is a String. Example: > :let files = system("ls " . shellescape(expand('%:h'))) @@ -6871,9 +6878,6 @@ system({cmd} [, {input}]) *system()* *E677* The command executed is constructed using several options when {cmd} is a string: 'shell' 'shellcmdflag' {cmd} - The command will be executed in "cooked" mode, so that a - CTRL-C will interrupt the command (on Unix at least). - The resulting error code can be found in |v:shell_error|. This function will fail in |restricted-mode|. diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 818d6cf64c..a82e17c857 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -1436,7 +1436,7 @@ original HTML files completion of tags (and only tags) isn't context aware. RUBY *ft-ruby-omni* {Nvim} -NOTE: Completion for ruby code is not currently provided by Nvim. +NOTE: |compl-omni| for Ruby code requires |provider-ruby| to be installed. Ruby completion will parse your buffer on demand in order to provide a list of completions. These completions will be drawn from modules loaded by 'require' diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 249208911b..bb1f993ab6 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -76,6 +76,15 @@ Python plugins |provider-python| Clipboard integration |provider-clipboard| +USER EXPERIENCE ~ + +A major goal of Nvim is to work intuitively and consistently. For example, +Nvim does not have `-X`, a platform-specific option available in some Vim +builds (with potential surprises: http://stackoverflow.com/q/14635295). Nvim +avoids features that cannot be provided on all platforms--instead that is +delegated to external plugins/extensions. + + OTHER FEATURES ~ |bracketed-paste-mode| is built-in and enabled by default. @@ -99,6 +108,7 @@ Options: Commands: |:CheckHealth| + |:drop| is available on all platforms |:Man| is available by default, with many improvements such as completion Functions: @@ -131,10 +141,10 @@ are always available and may be used simultaneously in separate plugins. The `neovim` pip package must be installed to use Python plugins in Nvim (see |provider-python|). -|:!| and |system()| do not support "interactive" commands; use |:terminal| for -that instead. Terminal Vim supports interactive |:!| and |system()|, but gui -Vim does not. See ":help gui-pty" in Vim: - http://vimdoc.sourceforge.net/htmldoc/gui_x11.html#gui-pty +|:!| does not support "interactive" commands. Use |:terminal| instead. +(GUI Vim has a similar limitation, see ":help gui-pty" in Vim.) + +|system()| does not support writing/reading "backgrounded" commands. |E5677| |mkdir()| behaviour changed: 1. Assuming /tmp/foo does not exist and /tmp can be written to diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim index 02d2b4e557..f6fefd0155 100644 --- a/runtime/ftplugin/man.vim +++ b/runtime/ftplugin/man.vim @@ -21,7 +21,7 @@ if has('vim_starting') " all caps it is impossible to tell what the original capitilization was. let ref = tolower(matchstr(getline(1), '^\S\+')) let b:man_sect = man#extract_sect_and_name_ref(ref)[0] - execute 'file man://'.ref + execute 'silent file man://'.ref endif setlocal buftype=nofile diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 5c570eb52a..32e871ea79 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -5,6 +5,14 @@ " Version: 7.4-45 " Automatically generated keyword lists: {{{1 +" ############################################################################# +" ############################################################################# +" Note: Be careful when merging the upstream version of this file. +" Much of this is generated by scripts/genvimvim.lua (result is installed +" to: $VIMRUNTIME/syntax/vim/generated.vim) +" ############################################################################# +" ############################################################################# + " Quit when a syntax file was already loaded {{{2 if exists("b:current_syntax") finish diff --git a/scripts/gendispatch.lua b/scripts/gendispatch.lua index 00ed84212b..94789e1ef0 100644 --- a/scripts/gendispatch.lua +++ b/scripts/gendispatch.lua @@ -52,16 +52,18 @@ package.path = nvimsrcdir .. '/?.lua;' .. package.path -- names of all headers relative to the source root (for inclusion in the -- generated file) headers = {} --- output c file(dispatch function + metadata serialized with msgpack) -outputf = arg[#arg-1] --- output mpack file (metadata) +-- output h file with generated dispatch functions +dispatch_outputf = arg[#arg-2] +-- output h file with packed metadata +funcs_metadata_outputf = arg[#arg-1] +-- output metadata mpack file, for use by other build scripts mpack_outputf = arg[#arg] -- set of function names, used to detect duplicates function_names = {} -- read each input file, parse and append to the api metadata -for i = 2, #arg - 2 do +for i = 2, #arg - 3 do local full_path = arg[i] local parts = {} for part in string.gmatch(full_path, '[^/]+') do @@ -165,66 +167,27 @@ for _,f in ipairs(functions) do end --- start building the output -output = io.open(outputf, 'wb') - -output:write([[ -#include <inttypes.h> -#include <stdbool.h> -#include <stdint.h> -#include <assert.h> -#include <msgpack.h> - -#include "nvim/map.h" -#include "nvim/log.h" -#include "nvim/vim.h" -#include "nvim/msgpack_rpc/helpers.h" -#include "nvim/api/private/dispatch.h" -#include "nvim/api/private/helpers.h" -#include "nvim/api/private/defs.h" +funcs_metadata_output = io.open(funcs_metadata_outputf, 'wb') +funcs_metadata_output:write([[ +static const uint8_t funcs_metadata[] = { ]]) -for i = 1, #headers do - if headers[i]:sub(-12) ~= '.generated.h' then - output:write('\n#include "nvim/'..headers[i]..'"') - end -end - -output:write([[ - - -static const uint8_t msgpack_metadata[] = { - -]]) -- serialize the API metadata using msgpack and embed into the resulting -- binary for easy querying by clients packed_exported_functions = mpack.pack(exported_functions) for i = 1, #packed_exported_functions do - output:write(string.byte(packed_exported_functions, i)..', ') + funcs_metadata_output:write(string.byte(packed_exported_functions, i)..', ') if i % 10 == 0 then - output:write('\n ') + funcs_metadata_output:write('\n ') end end -output:write([[ +funcs_metadata_output:write([[ }; - -void msgpack_rpc_init_function_metadata(Dictionary *metadata) -{ - msgpack_unpacked unpacked; - msgpack_unpacked_init(&unpacked); - if (msgpack_unpack_next(&unpacked, - (const char *)msgpack_metadata, - sizeof(msgpack_metadata), - NULL) != MSGPACK_UNPACK_SUCCESS) { - abort(); - } - Object functions; - msgpack_rpc_to_object(&unpacked.data, &functions); - msgpack_unpacked_destroy(&unpacked); - PUT(*metadata, "functions", functions); -} - ]]) +funcs_metadata_output:close() + +-- start building the dispatch wrapper output +output = io.open(dispatch_outputf, 'wb') local function real_type(type) local rv = type @@ -246,7 +209,7 @@ for i = 1, #functions do if fn.impl_name == nil then local args = {} - output:write('Object handle_'..fn.name..'(uint64_t channel_id, uint64_t request_id, Array args, Error *error)') + output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)') output:write('\n{') output:write('\n Object ret = NIL;') -- Declare/initialize variables that will hold converted arguments @@ -336,22 +299,12 @@ end -- Generate a function that initializes method names with handler functions output:write([[ -static Map(String, MsgpackRpcRequestHandler) *methods = NULL; - -void msgpack_rpc_add_method_handler(String method, MsgpackRpcRequestHandler handler) -{ - map_put(String, MsgpackRpcRequestHandler)(methods, method, handler); -} - void msgpack_rpc_init_method_table(void) { methods = map_new(String, MsgpackRpcRequestHandler)(); ]]) --- Keep track of the maximum method name length in order to avoid walking --- strings longer than that when searching for a method handler -local max_fname_len = 0 for i = 1, #functions do local fn = functions[i] output:write(' msgpack_rpc_add_method_handler('.. @@ -360,32 +313,9 @@ for i = 1, #functions do '(MsgpackRpcRequestHandler) {.fn = handle_'.. (fn.impl_name or fn.name).. ', .async = '..tostring(fn.async)..'});\n') - if #fn.name > max_fname_len then - max_fname_len = #fn.name - end end output:write('\n}\n\n') - -output:write([[ -MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, - size_t name_len) -{ - String m = { - .data=(char *)name, - .size=MIN(name_len, ]]..max_fname_len..[[) - }; - MsgpackRpcRequestHandler rv = - map_get(String, MsgpackRpcRequestHandler)(methods, m); - - if (!rv.fn) { - rv.fn = msgpack_rpc_handle_missing_method; - } - - return rv; -} -]]) - output:close() mpack_output = io.open(mpack_outputf, 'wb') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index cbea6a05c9..49edfda838 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -18,7 +18,8 @@ set(API_METADATA ${PROJECT_BINARY_DIR}/api_metadata.mpack) set(FUNCS_DATA ${PROJECT_BINARY_DIR}/funcs_data.mpack) set(HEADER_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gendeclarations.lua) set(GENERATED_INCLUDES_DIR ${PROJECT_BINARY_DIR}/include) -set(GENERATED_API_DISPATCH ${GENERATED_DIR}/api/private/dispatch.c) +set(GENERATED_API_DISPATCH ${GENERATED_DIR}/api/private/dispatch_wrappers.generated.h) +set(GENERATED_FUNCS_METADATA ${GENERATED_DIR}/api/private/funcs_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) set(GENERATED_FUNCS_HASH_INPUT ${GENERATED_DIR}/funcs.generated.h.gperf) @@ -197,8 +198,11 @@ add_custom_command(OUTPUT ${GENERATED_UNICODE_TABLES} ${UNICODE_FILES} ) -add_custom_command(OUTPUT ${GENERATED_API_DISPATCH} ${API_METADATA} - COMMAND ${LUA_PRG} ${DISPATCH_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} ${API_HEADERS} ${GENERATED_API_DISPATCH} ${API_METADATA} +add_custom_command(OUTPUT ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA} + ${API_METADATA} + COMMAND ${LUA_PRG} ${DISPATCH_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} + ${API_HEADERS} ${GENERATED_API_DISPATCH} + ${GENERATED_FUNCS_METADATA} ${API_METADATA} DEPENDS ${API_HEADERS} ${MSGPACK_RPC_HEADERS} diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index c4415ddf94..eaaae943d2 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -28,9 +28,9 @@ /// Gets the buffer line count /// -/// @param buffer The buffer handle -/// @param[out] err Details of an error that may have occurred -/// @return The line count +/// @param buffer Buffer handle +/// @param[out] err Error details, if any +/// @return Line count Integer nvim_buf_line_count(Buffer buffer, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -50,10 +50,10 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// for negative indices use /// "nvim_buf_get_lines(buffer, index-1, index, true)" /// -/// @param buffer The buffer handle -/// @param index The line index -/// @param[out] err Details of an error that may have occurred -/// @return The line string +/// @param buffer Buffer handle +/// @param index Line index +/// @param[out] err Error details, if any +/// @return Line string String buffer_get_line(Buffer buffer, Integer index, Error *err) { String rv = { .size = 0 }; @@ -78,10 +78,10 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) /// for negative indices use /// "nvim_buf_set_lines(buffer, index-1, index, true, [line])" /// -/// @param buffer The buffer handle -/// @param index The line index -/// @param line The new line. -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param index Line index +/// @param line Contents of the new line +/// @param[out] err Error details, if any void buffer_set_line(Buffer buffer, Integer index, String line, Error *err) { Object l = STRING_OBJ(line); @@ -97,9 +97,9 @@ void buffer_set_line(Buffer buffer, Integer index, String line, Error *err) /// "nvim_buf_set_lines(buffer, index, index+1, true, [])" /// for negative indices use /// "nvim_buf_set_lines(buffer, index-1, index, true, [])" -/// @param buffer The buffer handle -/// @param index The line index -/// @param[out] err Details of an error that may have occurred +/// @param buffer buffer handle +/// @param index line index +/// @param[out] err Error details, if any void buffer_del_line(Buffer buffer, Integer index, Error *err) { Array array = ARRAY_DICT_INIT; @@ -113,13 +113,13 @@ void buffer_del_line(Buffer buffer, Integer index, Error *err) /// where newstart = start + int(not include_start) - int(start < 0) /// newend = end + int(include_end) - int(end < 0) /// int(bool) = 1 if bool is true else 0 -/// @param buffer The buffer handle -/// @param start The first line index -/// @param end The last line index -/// @param include_start True if the slice includes the `start` parameter -/// @param include_end True if the slice includes the `end` parameter -/// @param[out] err Details of an error that may have occurred -/// @return An array of lines +/// @param buffer Buffer handle +/// @param start First line index +/// @param end Last line index +/// @param include_start True if the slice includes the `start` parameter +/// @param include_end True if the slice includes the `end` parameter +/// @param[out] err Error details, if any +/// @return Array of lines ArrayOf(String) buffer_get_line_slice(Buffer buffer, Integer start, Integer end, @@ -142,12 +142,12 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer, /// Out-of-bounds indices are clamped to the nearest valid value, unless /// `strict_indexing` is set. /// -/// @param buffer The buffer handle -/// @param start The first line index -/// @param end The last line index (exclusive) -/// @param strict_indexing whether out-of-bounds should be an error. -/// @param[out] err Details of an error that may have occurred -/// @return An array of lines +/// @param buffer Buffer handle +/// @param start First line index +/// @param end Last line index (exclusive) +/// @param strict_indexing Whether out-of-bounds should be an error. +/// @param[out] err Error details, if any +/// @return Array of lines ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, Buffer buffer, Integer start, @@ -191,7 +191,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, Object str = STRING_OBJ(cstr_to_string(bufstr)); // Vim represents NULs as NLs, but this may confuse clients. - if (channel_id != INVALID_CHANNEL) { + if (channel_id != INTERNAL_CALL) { strchrsub(str.data.string.data, '\n', '\0'); } @@ -219,14 +219,14 @@ end: /// newend = end + int(include_end) + int(end < 0) /// int(bool) = 1 if bool is true else 0 /// -/// @param buffer The buffer handle -/// @param start The first line index -/// @param end The last line index -/// @param include_start True if the slice includes the `start` parameter -/// @param include_end True if the slice includes the `end` parameter -/// @param replacement An array of lines to use as replacement(A 0-length -// array will simply delete the line range) -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param start First line index +/// @param end Last line index +/// @param include_start True if the slice includes the `start` parameter +/// @param include_end True if the slice includes the `end` parameter +/// @param replacement Array of lines to use as replacement (0-length +// array will delete the line range) +/// @param[out] err Error details, if any void buffer_set_line_slice(Buffer buffer, Integer start, Integer end, @@ -253,12 +253,12 @@ void buffer_set_line_slice(Buffer buffer, /// Out-of-bounds indices are clamped to the nearest valid value, unless /// `strict_indexing` is set. /// -/// @param buffer The buffer handle -/// @param start The first line index -/// @param end The last line index (exclusive) -/// @param strict_indexing whether out-of-bounds should be an error. -/// @param replacement An array of lines to use as replacement -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param start First line index +/// @param end Last line index (exclusive) +/// @param strict_indexing Whether out-of-bounds should be an error. +/// @param replacement Array of lines to use as replacement +/// @param[out] err Error details, if any void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, @@ -312,7 +312,7 @@ void nvim_buf_set_lines(uint64_t channel_id, // line and convert NULs to newlines to avoid truncation. lines[i] = xmallocz(l.size); for (size_t j = 0; j < l.size; j++) { - if (l.data[j] == '\n' && channel_id != INVALID_CHANNEL) { + if (l.data[j] == '\n' && channel_id != INTERNAL_CALL) { api_set_error(err, Exception, _("string cannot contain newlines")); new_len = i + 1; goto end; @@ -411,10 +411,10 @@ end: /// Gets a buffer-scoped (b:) variable. /// -/// @param buffer The buffer handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value +/// @param buffer Buffer handle +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Variable value Object nvim_buf_get_var(Buffer buffer, String name, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -428,10 +428,10 @@ Object nvim_buf_get_var(Buffer buffer, String name, Error *err) /// Sets a buffer-scoped (b:) variable /// -/// @param buffer The buffer handle -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any void nvim_buf_set_var(Buffer buffer, String name, Object value, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -445,9 +445,9 @@ void nvim_buf_set_var(Buffer buffer, String name, Object value, Error *err) /// Removes a buffer-scoped (b:) variable /// -/// @param buffer The buffer handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param name Variable name +/// @param[out] err Error details, if any void nvim_buf_del_var(Buffer buffer, String name, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -463,11 +463,11 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err) /// /// @deprecated /// -/// @param buffer The buffer handle -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @param buffer Buffer handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +/// @return Old value or nil if there was no previous value. /// /// @warning It may return nil if there was no previous value /// or if previous value was `v:null`. @@ -486,10 +486,10 @@ Object buffer_set_var(Buffer buffer, String name, Object value, Error *err) /// /// @deprecated /// -/// @param buffer The buffer handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The old value +/// @param buffer Buffer handle +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Old value Object buffer_del_var(Buffer buffer, String name, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -504,10 +504,10 @@ Object buffer_del_var(Buffer buffer, String name, Error *err) /// Gets a buffer option value /// -/// @param buffer The buffer handle -/// @param name The option name -/// @param[out] err Details of an error that may have occurred -/// @return The option value +/// @param buffer Buffer handle +/// @param name Option name +/// @param[out] err Error details, if any +/// @return Option value Object nvim_buf_get_option(Buffer buffer, String name, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -519,13 +519,13 @@ Object nvim_buf_get_option(Buffer buffer, String name, Error *err) return get_option_from(buf, SREQ_BUF, name, err); } -/// Sets a buffer option value. Passing 'nil' as value deletes the option(only +/// Sets a buffer option value. Passing 'nil' as value deletes the option (only /// works if there's a global fallback) /// -/// @param buffer The buffer handle -/// @param name The option name -/// @param value The option value -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param name Option name +/// @param value Option value +/// @param[out] err Error details, if any void nvim_buf_set_option(Buffer buffer, String name, Object value, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -539,9 +539,9 @@ void nvim_buf_set_option(Buffer buffer, String name, Object value, Error *err) /// Gets the buffer number /// -/// @param buffer The buffer handle -/// @param[out] err Details of an error that may have occurred -/// @return The buffer number +/// @param buffer Buffer handle +/// @param[out] err Error details, if any +/// @return Buffer number Integer nvim_buf_get_number(Buffer buffer, Error *err) { Integer rv = 0; @@ -556,9 +556,9 @@ Integer nvim_buf_get_number(Buffer buffer, Error *err) /// Gets the full file name for the buffer /// -/// @param buffer The buffer handle -/// @param[out] err Details of an error that may have occurred -/// @return The buffer name +/// @param buffer Buffer handle +/// @param[out] err Error details, if any +/// @return Buffer name String nvim_buf_get_name(Buffer buffer, Error *err) { String rv = STRING_INIT; @@ -573,9 +573,9 @@ String nvim_buf_get_name(Buffer buffer, Error *err) /// Sets the full file name for a buffer /// -/// @param buffer The buffer handle -/// @param name The buffer name -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param name Buffer name +/// @param[out] err Error details, if any void nvim_buf_set_name(Buffer buffer, String name, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -603,7 +603,7 @@ void nvim_buf_set_name(Buffer buffer, String name, Error *err) /// Checks if a buffer is valid /// -/// @param buffer The buffer handle +/// @param buffer Buffer handle /// @return true if the buffer is valid, false otherwise Boolean nvim_buf_is_valid(Buffer buffer) { @@ -615,11 +615,11 @@ Boolean nvim_buf_is_valid(Buffer buffer) /// /// @deprecated use nvim_buf_set_lines(buffer, lnum, lnum, true, lines) /// -/// @param buffer The buffer handle -/// @param lnum Insert the lines after `lnum`. If negative, it will append -/// to the end of the buffer. -/// @param lines An array of lines -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param lnum Insert the lines after `lnum`. If negative, appends to +/// the end of the buffer. +/// @param lines Array of lines +/// @param[out] err Error details, if any void buffer_insert(Buffer buffer, Integer lnum, ArrayOf(String) lines, @@ -632,10 +632,10 @@ void buffer_insert(Buffer buffer, /// Return a tuple (row,col) representing the position of the named mark /// -/// @param buffer The buffer handle -/// @param name The mark's name -/// @param[out] err Details of an error that may have occurred -/// @return The (row, col) tuple +/// @param buffer Buffer handle +/// @param name Mark name +/// @param[out] err Error details, if any +/// @return (row, col) tuple ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) { Array rv = ARRAY_DICT_INIT; @@ -694,15 +694,15 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) /// request an unique src_id at initialization, and later asynchronously add and /// clear highlights in response to buffer changes. /// -/// @param buffer The buffer handle -/// @param src_id Source group to use or 0 to use a new group, -/// or -1 for ungrouped highlight -/// @param hl_group Name of the highlight group to use -/// @param line The line to highlight -/// @param col_start Start of range of columns to highlight -/// @param col_end End of range of columns to highlight, -/// or -1 to highlight to end of line -/// @param[out] err Details of an error that may have occurred +/// @param buffer Buffer handle +/// @param src_id Source group to use or 0 to use a new group, +/// or -1 for ungrouped highlight +/// @param hl_group Name of the highlight group to use +/// @param line Line to highlight +/// @param col_start Start of range of columns to highlight +/// @param col_end End of range of columns to highlight, +/// or -1 to highlight to end of line +/// @param[out] err Error details, if any /// @return The src_id that was used Integer nvim_buf_add_highlight(Buffer buffer, Integer src_id, @@ -740,12 +740,12 @@ Integer nvim_buf_add_highlight(Buffer buffer, /// To clear a source group in the entire buffer, pass in 1 and -1 to /// line_start and line_end respectively. /// -/// @param buffer The buffer handle -/// @param src_id Highlight source group to clear, or -1 to clear all groups. +/// @param buffer Buffer handle +/// @param src_id Highlight source group to clear, or -1 to clear all. /// @param line_start Start of range of lines to clear -/// @param line_end End of range of lines to clear (exclusive) -/// or -1 to clear to end of file. -/// @param[out] err Details of an error that may have occurred +/// @param line_end End of range of lines to clear (exclusive) or -1 to clear +/// to end of file. +/// @param[out] err Error details, if any void nvim_buf_clear_highlight(Buffer buffer, Integer src_id, Integer line_start, diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index a6710193ff..1d5ecd3071 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -33,8 +33,8 @@ typedef enum { /// Used as the message ID of notifications. #define NO_RESPONSE UINT64_MAX -/// Used as channel_id when the call is local -#define INVALID_CHANNEL UINT64_MAX +/// Used as channel_id when the call is local. +#define INTERNAL_CALL UINT64_MAX typedef struct { ErrorType type; diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c new file mode 100644 index 0000000000..9b3bcc380a --- /dev/null +++ b/src/nvim/api/private/dispatch.c @@ -0,0 +1,45 @@ +#include <inttypes.h> +#include <stdbool.h> +#include <stdint.h> +#include <assert.h> +#include <msgpack.h> + +#include "nvim/map.h" +#include "nvim/log.h" +#include "nvim/vim.h" +#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/api/private/dispatch.h" +#include "nvim/api/private/helpers.h" +#include "nvim/api/private/defs.h" + +#include "nvim/api/buffer.h" +#include "nvim/api/tabpage.h" +#include "nvim/api/ui.h" +#include "nvim/api/vim.h" +#include "nvim/api/window.h" + +static Map(String, MsgpackRpcRequestHandler) *methods = NULL; + +static void msgpack_rpc_add_method_handler(String method, + MsgpackRpcRequestHandler handler) +{ + map_put(String, MsgpackRpcRequestHandler)(methods, method, handler); +} + +MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, + size_t name_len) +{ + String m = { .data = (char *)name, .size = name_len }; + MsgpackRpcRequestHandler rv = + map_get(String, MsgpackRpcRequestHandler)(methods, m); + + if (!rv.fn) { + rv.fn = msgpack_rpc_handle_missing_method; + } + + return rv; +} + +#ifdef INCLUDE_GENERATED_DECLARATIONS +#include "api/private/dispatch_wrappers.generated.h" +#endif diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h index d91456c306..39aabd708a 100644 --- a/src/nvim/api/private/dispatch.h +++ b/src/nvim/api/private/dispatch.h @@ -4,7 +4,6 @@ #include "nvim/api/private/defs.h" typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, - uint64_t request_id, Array args, Error *error); @@ -18,6 +17,7 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/dispatch.h.generated.h" +# include "api/private/dispatch_wrappers.h.generated.h" #endif #endif // NVIM_API_PRIVATE_DISPATCH_H diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index d80ee7dc67..c0ee735d1a 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -7,6 +7,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/handle.h" +#include "nvim/msgpack_rpc/helpers.h" #include "nvim/ascii.h" #include "nvim/vim.h" #include "nvim/buffer.h" @@ -27,6 +28,7 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.c.generated.h" +# include "api/private/funcs_metadata.generated.h" #endif /// Start block that may cause vimscript exceptions @@ -761,7 +763,7 @@ Dictionary api_metadata(void) static Dictionary metadata = ARRAY_DICT_INIT; if (!metadata.size) { - msgpack_rpc_init_function_metadata(&metadata); + init_function_metadata(&metadata); init_error_type_metadata(&metadata); init_type_metadata(&metadata); } @@ -769,6 +771,22 @@ Dictionary api_metadata(void) return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary; } +static void init_function_metadata(Dictionary *metadata) +{ + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + if (msgpack_unpack_next(&unpacked, + (const char *)funcs_metadata, + sizeof(funcs_metadata), + NULL) != MSGPACK_UNPACK_SUCCESS) { + abort(); + } + Object functions; + msgpack_rpc_to_object(&unpacked.data, &functions); + msgpack_unpacked_destroy(&unpacked); + PUT(*metadata, "functions", functions); +} + static void init_error_type_metadata(Dictionary *metadata) { Dictionary types = ARRAY_DICT_INIT; @@ -784,6 +802,7 @@ static void init_error_type_metadata(Dictionary *metadata) PUT(*metadata, "error_types", DICTIONARY_OBJ(types)); } + static void init_type_metadata(Dictionary *metadata) { Dictionary types = ARRAY_DICT_INIT; diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 8b1fb041e2..9e61ec1871 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -11,9 +11,9 @@ /// Gets the windows in a tabpage /// -/// @param tabpage The tabpage -/// @param[out] err Details of an error that may have occurred -/// @return The windows in `tabpage` +/// @param tabpage Tabpage +/// @param[out] err Error details, if any +/// @return List of windows in `tabpage` ArrayOf(Window) nvim_tabpage_list_wins(Tabpage tabpage, Error *err) { Array rv = ARRAY_DICT_INIT; @@ -39,10 +39,10 @@ ArrayOf(Window) nvim_tabpage_list_wins(Tabpage tabpage, Error *err) /// Gets a tab-scoped (t:) variable /// -/// @param tabpage The tab page handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Variable value Object nvim_tabpage_get_var(Tabpage tabpage, String name, Error *err) { tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -56,10 +56,10 @@ Object nvim_tabpage_get_var(Tabpage tabpage, String name, Error *err) /// Sets a tab-scoped (t:) variable /// -/// @param tabpage handle -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any void nvim_tabpage_set_var(Tabpage tabpage, String name, Object value, @@ -76,9 +76,9 @@ void nvim_tabpage_set_var(Tabpage tabpage, /// Removes a tab-scoped (t:) variable /// -/// @param tabpage handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @param[out] err Error details, if any void nvim_tabpage_del_var(Tabpage tabpage, String name, Error *err) { tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -94,11 +94,11 @@ void nvim_tabpage_del_var(Tabpage tabpage, String name, Error *err) /// /// @deprecated /// -/// @param tabpage handle -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +/// @return Old value or nil if there was no previous value. /// /// @warning It may return nil if there was no previous value /// or if previous value was `v:null`. @@ -117,10 +117,10 @@ Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err) /// /// @deprecated /// -/// @param tabpage handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The old value +/// @param tabpage Tabpage handle +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Old value Object tabpage_del_var(Tabpage tabpage, String name, Error *err) { tabpage_T *tab = find_tab_by_handle(tabpage, err); @@ -132,11 +132,11 @@ Object tabpage_del_var(Tabpage tabpage, String name, Error *err) return dict_set_value(tab->tp_vars, name, NIL, true, true, err); } -/// Gets the current window in a tab page +/// Gets the current window in a tabpage /// -/// @param tabpage The tab page handle -/// @param[out] err Details of an error that may have occurred -/// @return The Window handle +/// @param tabpage Tabpage handle +/// @param[out] err Error details, if any +/// @return Window handle Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) { Window rv = 0; @@ -159,10 +159,27 @@ Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) } } -/// Checks if a tab page is valid +/// Gets the tabpage number /// -/// @param tabpage The tab page handle -/// @return true if the tab page is valid, false otherwise +/// @param tabpage Tabpage handle +/// @param[out] err Error details, if any +/// @return Tabpage number +Integer nvim_tabpage_get_number(Tabpage tabpage, Error *err) +{ + Integer rv = 0; + tabpage_T *tab = find_tab_by_handle(tabpage, err); + + if (!tab) { + return rv; + } + + return tabpage_index(tab); +} + +/// Checks if a tabpage is valid +/// +/// @param tabpage Tabpage handle +/// @return true if the tabpage is valid, false otherwise Boolean nvim_tabpage_is_valid(Tabpage tabpage) { Error stub = ERROR_INIT; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d8cdad961b..491375bd73 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -33,24 +33,26 @@ # include "api/vim.c.generated.h" #endif -/// Executes an ex-mode command str +/// Executes an ex-command. +/// On VimL error: Returns the VimL error; v:errmsg is not updated. /// -/// @param str The command str -/// @param[out] err Details of an error that may have occurred -void nvim_command(String str, Error *err) +/// @param command Ex-command string +/// @param[out] err Error details (including actual VimL error), if any +void nvim_command(String command, Error *err) { // Run the command try_start(); - do_cmdline_cmd(str.data); + do_cmdline_cmd(command.data); update_screen(VALID); try_end(err); } -/// Passes input keys to Neovim +/// Passes input keys to Nvim. +/// On VimL error: Does not fail, but updates v:errmsg. /// -/// @param keys to be typed -/// @param mode specifies the mapping options -/// @param escape_csi the string needs escaping for K_SPECIAL/CSI bytes +/// @param keys to be typed +/// @param mode mapping options +/// @param escape_csi If true, escape K_SPECIAL/CSI bytes in `keys` /// @see feedkeys() /// @see vim_strsave_escape_csi void nvim_feedkeys(String keys, String mode, Boolean escape_csi) @@ -102,13 +104,15 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) } } -/// Passes input keys to Neovim. Unlike `nvim_feedkeys`, this will use a -/// lower-level input buffer and the call is not deferred. -/// This is the most reliable way to emulate real user input. +/// Passes keys to Nvim as raw user-input. +/// On VimL error: Does not fail, but updates v:errmsg. +/// +/// Unlike `nvim_feedkeys`, this uses a lower-level input buffer and the call +/// is not deferred. This is the most reliable way to emulate real user input. /// /// @param keys to be typed -/// @return The number of bytes actually written, which can be lower than -/// requested if the buffer becomes full. +/// @return Number of bytes actually written (can be fewer than +/// requested if the buffer becomes full). Integer nvim_input(String keys) FUNC_API_ASYNC { @@ -152,19 +156,19 @@ String nvim_command_output(String str, Error *err) return cstr_to_string((char *)get_vim_var_str(VV_COMMAND_OUTPUT)); } -/// Evaluates the expression str using the Vim internal expression -/// evaluator (see |expression|). -/// Dictionaries and lists are recursively expanded. +/// Evaluates a VimL expression (:help expression). +/// Dictionaries and Lists are recursively expanded. +/// On VimL error: Returns a generic error; v:errmsg is not updated. /// -/// @param str The expression str -/// @param[out] err Details of an error that may have occurred -/// @return The expanded object -Object nvim_eval(String str, Error *err) +/// @param expr VimL expression string +/// @param[out] err Error details, if any +/// @return Evaluation result or expanded object +Object nvim_eval(String expr, Error *err) { Object rv = OBJECT_INIT; // Evaluate the expression try_start(); - typval_T *expr_result = eval_expr((char_u *) str.data, NULL); + typval_T *expr_result = eval_expr((char_u *)expr.data, NULL); if (!expr_result) { api_set_error(err, Exception, _("Failed to evaluate expression")); @@ -180,11 +184,12 @@ Object nvim_eval(String str, Error *err) return rv; } -/// Call the given function with the given arguments stored in an array. +/// Calls a VimL function with the given arguments. +/// On VimL error: Returns a generic error; v:errmsg is not updated. /// -/// @param fname Function to call -/// @param args Functions arguments packed in an Array -/// @param[out] err Details of an error that may have occurred +/// @param fname Function to call +/// @param args Function arguments packed in an Array +/// @param[out] err Error details, if any /// @return Result of the function call Object nvim_call_function(String fname, Array args, Error *err) { @@ -229,12 +234,12 @@ free_vim_args: return rv; } -/// Calculates the number of display cells `str` occupies, tab is counted as -/// one cell. +/// Calculates the number of display cells occupied by `text`. +/// <Tab> counts as one cell. /// -/// @param str Some text -/// @param[out] err Details of an error that may have occurred -/// @return The number of cells +/// @param text Some text +/// @param[out] err Error details, if any +/// @return Number of cells Integer nvim_strwidth(String str, Error *err) { if (str.size > INT_MAX) { @@ -245,9 +250,9 @@ Integer nvim_strwidth(String str, Error *err) return (Integer) mb_string2cells((char_u *) str.data); } -/// Gets a list of paths contained in 'runtimepath' +/// Gets the paths contained in 'runtimepath'. /// -/// @return The list of paths +/// @return List of paths ArrayOf(String) nvim_list_runtime_paths(void) { Array rv = ARRAY_DICT_INIT; @@ -285,10 +290,10 @@ ArrayOf(String) nvim_list_runtime_paths(void) return rv; } -/// Changes Vim working directory +/// Changes the global working directory. /// -/// @param dir The new working directory -/// @param[out] err Details of an error that may have occurred +/// @param dir Directory path +/// @param[out] err Error details, if any void nvim_set_current_dir(String dir, Error *err) { if (dir.size >= MAXPATHL) { @@ -315,8 +320,8 @@ void nvim_set_current_dir(String dir, Error *err) /// Gets the current line /// -/// @param[out] err Details of an error that may have occurred -/// @return The current line string +/// @param[out] err Error details, if any +/// @return Current line string String nvim_get_current_line(Error *err) { return buffer_get_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); @@ -324,8 +329,8 @@ String nvim_get_current_line(Error *err) /// Sets the current line /// -/// @param line The line contents -/// @param[out] err Details of an error that may have occurred +/// @param line Line contents +/// @param[out] err Error details, if any void nvim_set_current_line(String line, Error *err) { buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err); @@ -333,36 +338,36 @@ void nvim_set_current_line(String line, Error *err) /// Deletes the current line /// -/// @param[out] err Details of an error that may have occurred +/// @param[out] err Error details, if any void nvim_del_current_line(Error *err) { buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); } -/// Gets a global variable +/// Gets a global (g:) variable /// -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Variable value Object nvim_get_var(String name, Error *err) { return dict_get_value(&globvardict, name, err); } -/// Sets a global variable +/// Sets a global (g:) variable /// -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any void nvim_set_var(String name, Object value, Error *err) { dict_set_value(&globvardict, name, value, false, false, err); } -/// Removes a global variable +/// Removes a global (g:) variable /// -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred +/// @param name Variable name +/// @param[out] err Error details, if any void nvim_del_var(String name, Error *err) { dict_set_value(&globvardict, name, NIL, true, false, err); @@ -372,10 +377,10 @@ void nvim_del_var(String name, Error *err) /// /// @deprecated /// -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +/// @return Old value or nil if there was no previous value. /// /// @warning It may return nil if there was no previous value /// or if previous value was `v:null`. @@ -388,19 +393,19 @@ Object vim_set_var(String name, Object value, Error *err) /// /// @deprecated /// -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The old value +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Old value Object vim_del_var(String name, Error *err) { return dict_set_value(&globvardict, name, NIL, true, true, err); } -/// Gets a vim variable +/// Gets a v: variable /// -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Variable value Object nvim_get_vvar(String name, Error *err) { return dict_get_value(&vimvardict, name, err); @@ -408,9 +413,9 @@ Object nvim_get_vvar(String name, Error *err) /// Gets an option value string /// -/// @param name The option name -/// @param[out] err Details of an error that may have occurred -/// @return The option value +/// @param name Option name +/// @param[out] err Error details, if any +/// @return Option value Object nvim_get_option(String name, Error *err) { return get_option_from(NULL, SREQ_GLOBAL, name, err); @@ -418,9 +423,9 @@ Object nvim_get_option(String name, Error *err) /// Sets an option value /// -/// @param name The option name -/// @param value The new option value -/// @param[out] err Details of an error that may have occurred +/// @param name Option name +/// @param value New option value +/// @param[out] err Error details, if any void nvim_set_option(String name, Object value, Error *err) { set_option_to(NULL, SREQ_GLOBAL, name, value, err); @@ -428,7 +433,7 @@ void nvim_set_option(String name, Object value, Error *err) /// Writes a message to vim output buffer /// -/// @param str The message +/// @param str Message void nvim_out_write(String str) { write_msg(str, false); @@ -436,16 +441,17 @@ void nvim_out_write(String str) /// Writes a message to vim error buffer /// -/// @param str The message +/// @param str Message void nvim_err_write(String str) { write_msg(str, true); } -/// Higher level error reporting function that ensures all str contents -/// are written by sending a trailing linefeed to `nvim_err_write` +/// Writes a message to vim error buffer. Appends a linefeed to ensure all +/// contents are written. /// -/// @param str The message +/// @param str Message +/// @see nvim_err_write() void nvim_err_writeln(String str) { nvim_err_write(str); @@ -454,7 +460,7 @@ void nvim_err_writeln(String str) /// Gets the current list of buffer handles /// -/// @return The number of buffers +/// @return List of buffer handles ArrayOf(Buffer) nvim_list_bufs(void) { Array rv = ARRAY_DICT_INIT; @@ -475,7 +481,7 @@ ArrayOf(Buffer) nvim_list_bufs(void) /// Gets the current buffer /// -/// @reqturn The buffer handle +/// @return Buffer handle Buffer nvim_get_current_buf(void) { return curbuf->handle; @@ -483,8 +489,8 @@ Buffer nvim_get_current_buf(void) /// Sets the current buffer /// -/// @param id The buffer handle -/// @param[out] err Details of an error that may have occurred +/// @param id Buffer handle +/// @param[out] err Error details, if any void nvim_set_current_buf(Buffer buffer, Error *err) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -505,7 +511,7 @@ void nvim_set_current_buf(Buffer buffer, Error *err) /// Gets the current list of window handles /// -/// @return The number of windows +/// @return List of window handles ArrayOf(Window) nvim_list_wins(void) { Array rv = ARRAY_DICT_INIT; @@ -526,7 +532,7 @@ ArrayOf(Window) nvim_list_wins(void) /// Gets the current window /// -/// @return The window handle +/// @return Window handle Window nvim_get_current_win(void) { return curwin->handle; @@ -534,7 +540,7 @@ Window nvim_get_current_win(void) /// Sets the current window /// -/// @param handle The window handle +/// @param handle Window handle void nvim_set_current_win(Window window, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -555,7 +561,7 @@ void nvim_set_current_win(Window window, Error *err) /// Gets the current list of tabpage handles /// -/// @return The number of tab pages +/// @return List of tabpage handles ArrayOf(Tabpage) nvim_list_tabpages(void) { Array rv = ARRAY_DICT_INIT; @@ -574,18 +580,18 @@ ArrayOf(Tabpage) nvim_list_tabpages(void) return rv; } -/// Gets the current tab page +/// Gets the current tabpage /// -/// @return The tab page handle +/// @return Tabpage handle Tabpage nvim_get_current_tabpage(void) { return curtab->handle; } -/// Sets the current tab page +/// Sets the current tabpage /// -/// @param handle The tab page handle -/// @param[out] err Details of an error that may have occurred +/// @param handle Tabpage handle +/// @param[out] err Error details, if any void nvim_set_current_tabpage(Tabpage tabpage, Error *err) { tabpage_T *tp = find_tab_by_handle(tabpage, err); @@ -606,8 +612,8 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err) /// Subscribes to event broadcasts /// -/// @param channel_id The channel id (passed automatically by the dispatcher) -/// @param event The event type string +/// @param channel_id Channel id (passed automatically by the dispatcher) +/// @param event Event type string void nvim_subscribe(uint64_t channel_id, String event) FUNC_API_NOEVAL { @@ -620,8 +626,8 @@ void nvim_subscribe(uint64_t channel_id, String event) /// Unsubscribes to event broadcasts /// -/// @param channel_id The channel id (passed automatically by the dispatcher) -/// @param event The event type string +/// @param channel_id Channel id (passed automatically by the dispatcher) +/// @param event Event type string void nvim_unsubscribe(uint64_t channel_id, String event) FUNC_API_NOEVAL { @@ -663,13 +669,101 @@ Array nvim_get_api_info(uint64_t channel_id) return rv; } +/// Call many api methods atomically +/// +/// This has two main usages: Firstly, to perform several requests from an +/// async context atomically, i.e. without processing requests from other rpc +/// clients or redrawing or allowing user interaction in between. Note that api +/// methods that could fire autocommands or do event processing still might do +/// so. For instance invoking the :sleep command might call timer callbacks. +/// Secondly, it can be used to reduce rpc overhead (roundtrips) when doing +/// many requests in sequence. +/// +/// @param calls an array of calls, where each call is described by an array +/// with two elements: the request name, and an array of arguments. +/// @param[out] err Details of a validation error of the nvim_multi_request call +/// itself, i e malformatted `calls` parameter. Errors from called methods will +/// be indicated in the return value, see below. +/// +/// @return an array with two elements. The first is an array of return +/// values. The second is NIL if all calls succeeded. If a call resulted in +/// an error, it is a three-element array with the zero-based index of the call +/// which resulted in an error, the error type and the error message. If an +/// error ocurred, the values from all preceding calls will still be returned. +Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) + FUNC_API_NOEVAL +{ + Array rv = ARRAY_DICT_INIT; + Array results = ARRAY_DICT_INIT; + Error nested_error = ERROR_INIT; + + size_t i; // also used for freeing the variables + for (i = 0; i < calls.size; i++) { + if (calls.items[i].type != kObjectTypeArray) { + api_set_error(err, + Validation, + _("All items in calls array must be arrays")); + goto validation_error; + } + Array call = calls.items[i].data.array; + if (call.size != 2) { + api_set_error(err, + Validation, + _("All items in calls array must be arrays of size 2")); + goto validation_error; + } + + if (call.items[0].type != kObjectTypeString) { + api_set_error(err, + Validation, + _("name must be String")); + goto validation_error; + } + String name = call.items[0].data.string; + + if (call.items[1].type != kObjectTypeArray) { + api_set_error(err, + Validation, + _("args must be Array")); + goto validation_error; + } + Array args = call.items[1].data.array; + + MsgpackRpcRequestHandler handler = msgpack_rpc_get_handler_for(name.data, + name.size); + Object result = handler.fn(channel_id, args, &nested_error); + if (nested_error.set) { + // error handled after loop + break; + } + + ADD(results, result); + } + + ADD(rv, ARRAY_OBJ(results)); + if (nested_error.set) { + Array errval = ARRAY_DICT_INIT; + ADD(errval, INTEGER_OBJ((Integer)i)); + ADD(errval, INTEGER_OBJ(nested_error.type)); + ADD(errval, STRING_OBJ(cstr_to_string(nested_error.msg))); + ADD(rv, ARRAY_OBJ(errval)); + } else { + ADD(rv, NIL); + } + return rv; + +validation_error: + api_free_array(results); + return rv; +} + + /// Writes a message to vim output or error buffer. The string is split /// and flushed after each newline. Incomplete lines are kept for writing /// later. /// -/// @param message The message to write -/// @param to_err true if it should be treated as an error message (use -/// `emsg` instead of `msg` to print each line) +/// @param message Message to write +/// @param to_err true: message is an error (uses `emsg` instead of `msg`) static void write_msg(String message, bool to_err) { static size_t out_pos = 0, err_pos = 0; diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 166e43f698..ef881fa0eb 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -15,9 +15,9 @@ /// Gets the current buffer in a window /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return The buffer handle +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return Buffer handle Buffer nvim_win_get_buf(Window window, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -31,9 +31,9 @@ Buffer nvim_win_get_buf(Window window, Error *err) /// Gets the cursor position in the window /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return the (row, col) tuple +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return (row, col) tuple ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Error *err) { Array rv = ARRAY_DICT_INIT; @@ -49,9 +49,9 @@ ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Error *err) /// Sets the cursor position in the window /// -/// @param window The window handle -/// @param pos the (row, col) tuple representing the new position -/// @param[out] err Details of an error that may have occurred +/// @param window Window handle +/// @param pos (row, col) tuple representing the new position +/// @param[out] err Error details, if any void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -95,9 +95,9 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) /// Gets the window height /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return the height in rows +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return Height as a count of rows Integer nvim_win_get_height(Window window, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -112,9 +112,9 @@ Integer nvim_win_get_height(Window window, Error *err) /// Sets the window height. This will only succeed if the screen is split /// horizontally. /// -/// @param window The window handle -/// @param height the new height in rows -/// @param[out] err Details of an error that may have occurred +/// @param window Window handle +/// @param height Height as a count of rows +/// @param[out] err Error details, if any void nvim_win_set_height(Window window, Integer height, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -138,9 +138,9 @@ void nvim_win_set_height(Window window, Integer height, Error *err) /// Gets the window width /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return the width in columns +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return Width as a count of columns Integer nvim_win_get_width(Window window, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -155,9 +155,9 @@ Integer nvim_win_get_width(Window window, Error *err) /// Sets the window width. This will only succeed if the screen is split /// vertically. /// -/// @param window The window handle -/// @param width the new width in columns -/// @param[out] err Details of an error that may have occurred +/// @param window Window handle +/// @param width Width as a count of columns +/// @param[out] err Error details, if any void nvim_win_set_width(Window window, Integer width, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -181,10 +181,10 @@ void nvim_win_set_width(Window window, Integer width, Error *err) /// Gets a window-scoped (w:) variable /// -/// @param window The window handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The variable value +/// @param window Window handle +/// @param name Variable name +/// @param[out] err Error details, if any +/// @return Variable value Object nvim_win_get_var(Window window, String name, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -198,10 +198,10 @@ Object nvim_win_get_var(Window window, String name, Error *err) /// Sets a window-scoped (w:) variable /// -/// @param window The window handle -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred +/// @param window Window handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any void nvim_win_set_var(Window window, String name, Object value, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -215,9 +215,9 @@ void nvim_win_set_var(Window window, String name, Object value, Error *err) /// Removes a window-scoped (w:) variable /// -/// @param window The window handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred +/// @param window Window handle +/// @param name Variable name +/// @param[out] err Error details, if any void nvim_win_del_var(Window window, String name, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -233,11 +233,11 @@ void nvim_win_del_var(Window window, String name, Error *err) /// /// @deprecated /// -/// @param window The window handle -/// @param name The variable name -/// @param value The variable value -/// @param[out] err Details of an error that may have occurred -/// @return The old value or nil if there was no previous value. +/// @param window Window handle +/// @param name Variable name +/// @param value Variable value +/// @param[out] err Error details, if any +/// @return Old value or nil if there was no previous value. /// /// @warning It may return nil if there was no previous value /// or if previous value was `v:null`. @@ -256,10 +256,10 @@ Object window_set_var(Window window, String name, Object value, Error *err) /// /// @deprecated /// -/// @param window The window handle -/// @param name The variable name -/// @param[out] err Details of an error that may have occurred -/// @return The old value +/// @param window Window handle +/// @param name variable name +/// @param[out] err Error details, if any +/// @return Old value Object window_del_var(Window window, String name, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -273,10 +273,10 @@ Object window_del_var(Window window, String name, Error *err) /// Gets a window option value /// -/// @param window The window handle -/// @param name The option name -/// @param[out] err Details of an error that may have occurred -/// @return The option value +/// @param window Window handle +/// @param name Option name +/// @param[out] err Error details, if any +/// @return Option value Object nvim_win_get_option(Window window, String name, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -291,10 +291,10 @@ Object nvim_win_get_option(Window window, String name, Error *err) /// Sets a window option value. Passing 'nil' as value deletes the option(only /// works if there's a global fallback) /// -/// @param window The window handle -/// @param name The option name -/// @param value The option value -/// @param[out] err Details of an error that may have occurred +/// @param window Window handle +/// @param name Option name +/// @param value Option value +/// @param[out] err Error details, if any void nvim_win_set_option(Window window, String name, Object value, Error *err) { win_T *win = find_window_by_handle(window, err); @@ -308,9 +308,9 @@ void nvim_win_set_option(Window window, String name, Object value, Error *err) /// Gets the window position in display cells. First position is zero. /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return The (row, col) tuple with the window position +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return (row, col) tuple with the window position ArrayOf(Integer, 2) nvim_win_get_position(Window window, Error *err) { Array rv = ARRAY_DICT_INIT; @@ -324,11 +324,11 @@ ArrayOf(Integer, 2) nvim_win_get_position(Window window, Error *err) return rv; } -/// Gets the window tab page +/// Gets the window tabpage /// -/// @param window The window handle -/// @param[out] err Details of an error that may have occurred -/// @return The tab page that contains the window +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return Tabpage that contains the window Tabpage nvim_win_get_tabpage(Window window, Error *err) { Tabpage rv = 0; @@ -341,9 +341,29 @@ Tabpage nvim_win_get_tabpage(Window window, Error *err) return rv; } +/// Gets the window number +/// +/// @param window Window handle +/// @param[out] err Error details, if any +/// @return Window number +Integer nvim_win_get_number(Window window, Error *err) +{ + Integer rv = 0; + win_T *win = find_window_by_handle(window, err); + + if (!win) { + return rv; + } + + int tabnr; + win_get_tabwin(window, &tabnr, (int *)&rv); + + return rv; +} + /// Checks if a window is valid /// -/// @param window The window handle +/// @param window Window handle /// @return true if the window is valid, false otherwise Boolean nvim_win_is_valid(Window window) { diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 51c9fb1556..24744f1437 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -462,8 +462,7 @@ static void insert_enter(InsertState *s) o_lnum = curwin->w_cursor.lnum; } - foldUpdateAll(curwin); - foldOpenCursor(); + foldUpdateAfterInsert(); if (s->cmdchar != 'r' && s->cmdchar != 'v') { apply_autocmds(EVENT_INSERTLEAVE, NULL, NULL, false, curbuf); } @@ -665,9 +664,10 @@ static int insert_execute(VimState *state, int key) // Pressing CTRL-Y selects the current match. When // compl_enter_selects is set the Enter key does the same. - if (s->c == Ctrl_Y - || (compl_enter_selects - && (s->c == CAR || s->c == K_KENTER || s->c == NL))) { + if ((s->c == Ctrl_Y + || (compl_enter_selects + && (s->c == CAR || s->c == K_KENTER || s->c == NL))) + && stop_arrow() == OK) { ins_compl_delete(); ins_compl_insert(); } @@ -2351,9 +2351,6 @@ void set_completion(colnr_T startcol, list_T *list) } ins_compl_clear(); - if (stop_arrow() == FAIL) - return; - compl_direction = FORWARD; if (startcol > curwin->w_cursor.col) startcol = curwin->w_cursor.col; @@ -3264,14 +3261,19 @@ static bool ins_compl_prep(int c) } else { int prev_col = curwin->w_cursor.col; - /* put the cursor on the last char, for 'tw' formatting */ - if (prev_col > 0) + // put the cursor on the last char, for 'tw' formatting + if (prev_col > 0) { dec_cursor(); - if (stop_arrow() == OK) + } + + if (!arrow_used && !ins_need_undo) { insertchar(NUL, 0, -1); + } + if (prev_col > 0 - && get_cursor_line_ptr()[curwin->w_cursor.col] != NUL) + && get_cursor_line_ptr()[curwin->w_cursor.col] != NUL) { inc_cursor(); + } } // If the popup menu is displayed pressing CTRL-Y means accepting @@ -3948,16 +3950,20 @@ static int ins_compl_get_exp(pos_T *ini) /* Delete the old text being completed. */ static void ins_compl_delete(void) { - int i; + int col; - /* - * In insert mode: Delete the typed part. - * In replace mode: Put the old characters back, if any. - */ - i = compl_col + (compl_cont_status & CONT_ADDING ? compl_length : 0); - backspace_until_column(i); - // TODO: is this sufficient for redrawing? Redrawing everything causes - // flicker, thus we can't do that. + // In insert mode: Delete the typed part. + // In replace mode: Put the old characters back, if any. + col = compl_col + (compl_cont_status & CONT_ADDING ? compl_length : 0); + if ((int)curwin->w_cursor.col > col) { + if (stop_arrow() == FAIL) { + return; + } + backspace_until_column(col); + } + + // TODO(vim): is this sufficient for redrawing? Redrawing everything + // causes flicker, thus we can't do that. changed_cline_bef_curs(); // clear v:completed_item set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc()); @@ -4319,8 +4325,11 @@ static int ins_complete(int c, bool enable_pum) colnr_T curs_col; /* cursor column */ int n; int save_w_wrow; + int insert_match; compl_direction = ins_compl_key2dir(c); + insert_match = ins_compl_use_match(c); + if (!compl_started) { /* First time we hit ^N or ^P (in a row, I mean) */ @@ -4653,6 +4662,8 @@ static int ins_complete(int c, bool enable_pum) showmode(); edit_submode_extra = NULL; ui_flush(); + } else if (insert_match && stop_arrow() == FAIL) { + return FAIL; } compl_shown_match = compl_curr_match; @@ -4662,7 +4673,7 @@ static int ins_complete(int c, bool enable_pum) * Find next match (and following matches). */ save_w_wrow = curwin->w_wrow; - n = ins_compl_next(TRUE, ins_compl_key2count(c), ins_compl_use_match(c)); + n = ins_compl_next(true, ins_compl_key2count(c), insert_match); /* may undisplay the popup menu */ ins_compl_upd_pum(); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index cae032f437..76f33e7d8c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7133,7 +7133,7 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) } Error err = ERROR_INIT; - Object result = fn(INVALID_CHANNEL, NO_RESPONSE, args, &err); + Object result = fn(INTERNAL_CALL, args, &err); if (err.set) { nvim_err_writeln(cstr_as_string(err.msg)); @@ -9527,24 +9527,35 @@ static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) varnumber_T n; int error = FALSE; - /* Position the cursor. Needed after a message that ends in a space. */ - ui_cursor_goto(msg_row, msg_col); - ++no_mapping; ++allow_keys; for (;; ) { - if (argvars[0].v_type == VAR_UNKNOWN) - /* getchar(): blocking wait. */ + // Position the cursor. Needed after a message that ends in a space, + // or if event processing caused a redraw. + ui_cursor_goto(msg_row, msg_col); + + if (argvars[0].v_type == VAR_UNKNOWN) { + // getchar(): blocking wait. + if (!(char_avail() || using_script() || input_available())) { + input_enable_events(); + (void)os_inchar(NULL, 0, -1, 0); + input_disable_events(); + if (!multiqueue_empty(main_loop.events)) { + multiqueue_process_events(main_loop.events); + continue; + } + } n = safe_vgetc(); - else if (get_tv_number_chk(&argvars[0], &error) == 1) - /* getchar(1): only check if char avail */ + } else if (get_tv_number_chk(&argvars[0], &error) == 1) { + // getchar(1): only check if char avail n = vpeekc_any(); - else if (error || vpeekc_any() == NUL) - /* illegal argument or getchar(0) and no char avail: return zero */ + } else if (error || vpeekc_any() == NUL) { + // illegal argument or getchar(0) and no char avail: return zero n = 0; - else - /* getchar(0) and char avail: return char */ + } else { + // getchar(0) and char avail: return char n = safe_vgetc(); + } if (n == K_IGNORE) continue; diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c index 5126dfd84e..92efc9fa2e 100644 --- a/src/nvim/event/rstream.c +++ b/src/nvim/event/rstream.c @@ -112,8 +112,8 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf) // to `alloc_cb` will return the same unused pointer(`rbuffer_produced` // won't be called) && cnt != 0) { - DLOG("Closing Stream(%p) because of %s(%zd)", stream, - uv_strerror((int)cnt), cnt); + DLOG("Closing Stream (%p): %s (%s)", stream, + uv_err_name((int)cnt), os_strerror((int)cnt)); // Read error or EOF, either way stop the stream and invoke the callback // with eof == true uv_read_stop(uvstream); diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h index 30871c7711..f61e01d25b 100644 --- a/src/nvim/ex_eval.h +++ b/src/nvim/ex_eval.h @@ -23,19 +23,19 @@ struct eslist_elem { #define CSTACK_LEN 50 struct condstack { - short cs_flags[CSTACK_LEN]; /* CSF_ flags */ - char cs_pending[CSTACK_LEN]; /* CSTP_: what's pending in ":finally"*/ + int cs_flags[CSTACK_LEN]; // CSF_ flags + char cs_pending[CSTACK_LEN]; // CSTP_: what's pending in ":finally" union { - void *csp_rv[CSTACK_LEN]; /* return typeval for pending return */ - void *csp_ex[CSTACK_LEN]; /* exception for pending throw */ + void *csp_rv[CSTACK_LEN]; // return typeval for pending return + void *csp_ex[CSTACK_LEN]; // exception for pending throw } cs_pend; - void *cs_forinfo[CSTACK_LEN]; /* info used by ":for" */ - int cs_line[CSTACK_LEN]; /* line nr of ":while"/":for" line */ - int cs_idx; /* current entry, or -1 if none */ - int cs_looplevel; /* nr of nested ":while"s and ":for"s */ - int cs_trylevel; /* nr of nested ":try"s */ - eslist_T *cs_emsg_silent_list; /* saved values of "emsg_silent" */ - char cs_lflags; /* loop flags: CSL_ flags */ + void *cs_forinfo[CSTACK_LEN]; // info used by ":for" + int cs_line[CSTACK_LEN]; // line nr of ":while"/":for" line + int cs_idx; // current entry, or -1 if none + int cs_looplevel; // nr of nested ":while"s and ":for"s + int cs_trylevel; // nr of nested ":try"s + eslist_T *cs_emsg_silent_list; // saved values of "emsg_silent" + int cs_lflags; // loop flags: CSL_ flags }; # define cs_rettv cs_pend.csp_rv # define cs_exception cs_pend.csp_ex diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 4254697241..7444eb8a38 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -4396,6 +4396,7 @@ static HistoryType hist_char2type(const int c) case '>': { return HIST_DEBUG; } + case NUL: case '/': case '?': { return HIST_SEARCH; diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 70030b8525..c84d738cb4 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -788,6 +788,19 @@ void foldUpdate(win_T *wp, linenr_T top, linenr_T bot) } } +/// Updates folds when leaving insert-mode. +void foldUpdateAfterInsert(void) +{ + if (foldmethodIsManual(curwin) // foldmethod=manual: No need to update. + // These foldmethods are too slow, do not auto-update on insert-leave. + || foldmethodIsSyntax(curwin) || foldmethodIsExpr(curwin)) { + return; + } + + foldUpdateAll(curwin); + foldOpenCursor(); +} + /* foldUpdateAll() {{{2 */ /* * Update all lines in a window for folding. diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 3ed85677fc..0b20647771 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1761,8 +1761,8 @@ static void cs_print_tags_priv(char **matches, char **cntxts, */ static int cs_read_prompt(size_t i) { - char ch; - char *buf = NULL; /* buffer for possible error message from cscope */ + int ch; + char *buf = NULL; // buffer for possible error message from cscope size_t bufpos = 0; char *cs_emsg = _("E609: Cscope error: %s"); size_t cs_emsg_len = strlen(cs_emsg); @@ -1774,35 +1774,34 @@ static int cs_read_prompt(size_t i) size_t maxlen = IOSIZE - cs_emsg_len; for (;; ) { - while ((ch = (char)getc(csinfo[i].fr_fp)) != EOF && ch != CSCOPE_PROMPT[0]) - /* if there is room and char is printable */ + while ((ch = getc(csinfo[i].fr_fp)) != EOF && ch != CSCOPE_PROMPT[0]) { + // if there is room and char is printable if (bufpos < maxlen - 1 && vim_isprintc(ch)) { // lazy buffer allocation if (buf == NULL) { buf = xmalloc(maxlen); } - { - /* append character to the message */ - buf[bufpos++] = ch; - buf[bufpos] = NUL; - if (bufpos >= epromptlen - && strcmp(&buf[bufpos - epromptlen], eprompt) == 0) { - /* remove eprompt from buf */ - buf[bufpos - epromptlen] = NUL; - - /* print message to user */ - (void)EMSG2(cs_emsg, buf); + // append character to the message + buf[bufpos++] = (char)ch; + buf[bufpos] = NUL; + if (bufpos >= epromptlen + && strcmp(&buf[bufpos - epromptlen], eprompt) == 0) { + // remove eprompt from buf + buf[bufpos - epromptlen] = NUL; + + // print message to user + (void)EMSG2(cs_emsg, buf); - /* send RETURN to cscope */ - (void)putc('\n', csinfo[i].to_fp); - (void)fflush(csinfo[i].to_fp); + // send RETURN to cscope + (void)putc('\n', csinfo[i].to_fp); + (void)fflush(csinfo[i].to_fp); - /* clear buf */ - bufpos = 0; - buf[bufpos] = NUL; - } + // clear buf + bufpos = 0; + buf[bufpos] = NUL; } } + } for (size_t n = 0; n < strlen(CSCOPE_PROMPT); ++n) { if (n > 0) diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index fef1d08db7..98636263b9 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -452,7 +452,7 @@ static void on_request_event(void **argv) Array args = e->args; uint64_t request_id = e->request_id; Error error = ERROR_INIT; - Object result = handler.fn(channel->id, request_id, args, &error); + Object result = handler.fn(channel->id, args, &error); if (request_id != NO_RESPONSE) { // send the response msgpack_packer response; diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index 33e9bb1c07..14e1c2d978 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -455,7 +455,6 @@ void msgpack_rpc_from_dictionary(Dictionary result, msgpack_packer *res) /// Handler executed when an invalid method name is passed Object msgpack_rpc_handle_missing_method(uint64_t channel_id, - uint64_t request_id, Array args, Error *error) { @@ -466,7 +465,6 @@ Object msgpack_rpc_handle_missing_method(uint64_t channel_id, /// Handler executed when malformated arguments are passed Object msgpack_rpc_handle_invalid_arguments(uint64_t channel_id, - uint64_t request_id, Array args, Error *error) { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 6dcbf50750..76e3829bee 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -5925,8 +5925,7 @@ static void nv_replace(cmdarg_T *cap) set_last_insert(cap->nchar); } - foldUpdateAll(curwin); - foldOpenCursor(); + foldUpdateAfterInsert(); } /* diff --git a/src/nvim/option.c b/src/nvim/option.c index e2a5d38bee..81919c00d2 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1032,6 +1032,15 @@ void set_init_3(void) xfree(p); } + if (bufempty()) { + int idx_ffs = findoption((char_u *)"ffs"); + + // Apply the first entry of 'fileformats' to the initial buffer. + if (idx_ffs >= 0 && (options[idx_ffs].flags & P_WAS_SET)) { + set_fileformat(default_fileformat(), OPT_LOCAL); + } + } + set_title_defaults(); } diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 008952fa97..3c821936e9 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -163,8 +163,12 @@ int os_nodetype(const char *name) // saves us the hassle. int nodetype = NODE_WRITABLE; - int fd = os_open(name, O_RDONLY, 0); - switch(uv_guess_handle(fd)) { + int fd = os_open(name, O_RDONLY +#ifdef O_NONBLOCK + | O_NONBLOCK +#endif + , 0); + switch (uv_guess_handle(fd)) { case UV_TTY: // FILE_TYPE_CHAR nodetype = NODE_WRITABLE; break; diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index e9a3dcbff8..18ee008d66 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -545,6 +545,16 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer, static void shell_write_cb(Stream *stream, void *data, int status) { + if (status) { + // Can happen if system() tries to send input to a shell command that was + // backgrounded (:call system("cat - &", "foo")). #3529 #5241 + EMSG2(_("E5677: Error writing input to shell-command: %s"), + uv_err_name(status)); + } + if (stream->closed) { // Process may have exited before this write. + ELOG("stream was already closed"); + return; + } stream_close(stream, NULL, NULL); } diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 4d21887240..67e7c97905 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -34,6 +34,7 @@ NEW_TESTS = \ test_cscope.res \ test_hardcopy.res \ test_help_tagjump.res \ + test_history.res \ test_langmap.res \ test_syntax.res \ test_usercommands.res \ diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index 7169b6076f..036a4c0470 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -10,5 +10,6 @@ source test_menu.vim source test_popup.vim source test_regexp_utf8.vim source test_syn_attr.vim +source test_tabpage.vim source test_unlet.vim source test_matchadd_conceal_utf8.vim diff --git a/src/nvim/testdir/test_history.vim b/src/nvim/testdir/test_history.vim new file mode 100644 index 0000000000..ee6acfffc3 --- /dev/null +++ b/src/nvim/testdir/test_history.vim @@ -0,0 +1,65 @@ +" Tests for the history functions + +if !has('cmdline_hist') + finish +endif + +set history=7 + +function History_Tests(hist) + " First clear the history + call histadd(a:hist, 'dummy') + call assert_true(histdel(a:hist)) + call assert_equal(-1, histnr(a:hist)) + call assert_equal('', histget(a:hist)) + + call assert_true(histadd(a:hist, 'ls')) + call assert_true(histadd(a:hist, 'buffers')) + call assert_equal('buffers', histget(a:hist)) + call assert_equal('ls', histget(a:hist, -2)) + call assert_equal('ls', histget(a:hist, 1)) + call assert_equal('', histget(a:hist, 5)) + call assert_equal('', histget(a:hist, -5)) + call assert_equal(2, histnr(a:hist)) + call assert_true(histdel(a:hist, 2)) + call assert_false(histdel(a:hist, 7)) + call assert_equal(1, histnr(a:hist)) + call assert_equal('ls', histget(a:hist, -1)) + + call assert_true(histadd(a:hist, 'buffers')) + call assert_true(histadd(a:hist, 'ls')) + call assert_equal('ls', histget(a:hist, -1)) + call assert_equal(4, histnr(a:hist)) + + " Test for removing entries matching a pattern + for i in range(1, 3) + call histadd(a:hist, 'text_' . i) + endfor + call assert_true(histdel(a:hist, 'text_\d\+')) + call assert_equal('ls', histget(a:hist, -1)) + + " Test for freeing the entire history list + for i in range(1, 7) + call histadd(a:hist, 'text_' . i) + endfor + call histdel(a:hist) + for i in range(1, 7) + call assert_equal('', histget(a:hist, i)) + call assert_equal('', histget(a:hist, i - 7 - 1)) + endfor +endfunction + +function Test_History() + for h in ['cmd', ':', '', 'search', '/', '?', 'expr', '=', 'input', '@', 'debug', '>'] + call History_Tests(h) + endfor + + " Negative tests + call assert_false(histdel('abc')) + call assert_equal('', histget('abc')) + call assert_fails('call histdel([])', 'E730:') + call assert_equal('', histget(10)) + call assert_fails('call histget([])', 'E730:') + call assert_equal(-1, histnr('abc')) + call assert_fails('call histnr([])', 'E730:') +endfunction diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim new file mode 100644 index 0000000000..e6b85d6e14 --- /dev/null +++ b/src/nvim/testdir/test_tabpage.vim @@ -0,0 +1,189 @@ +" Tests for tabpage + +function Test_tabpage() + bw! + " Simple test for opening and closing a tab page + tabnew + call assert_equal(2, tabpagenr()) + quit + + " Open three tab pages and use ":tabdo" + 0tabnew + 1tabnew + $tabnew + tabdo call append(line('$'), tabpagenr()) + tabclose! 2 + tabrewind + let line1 = getline('$') + undo + q + tablast + let line2 = getline('$') + q! + call append(line('$'), line1) + call append(line('$'), line2) + unlet line1 line2 + call assert_equal(['', '3', '1', '4'], getline(1, '$')) + " + " Test for settabvar() and gettabvar() functions. Open a new tab page and + " set 3 variables to a number, string and a list. Verify that the variables + " are correctly set. + tabnew + tabfirst + call settabvar(2, 'val_num', 100) + call settabvar(2, 'val_str', 'SetTabVar test') + call settabvar(2, 'val_list', ['red', 'blue', 'green']) + " + call assert_true(gettabvar(2, 'val_num') == 100 && gettabvar(2, 'val_str') == 'SetTabVar test' && gettabvar(2, 'val_list') == ['red', 'blue', 'green']) + + tabnext 2 + call assert_true(t:val_num == 100 && t:val_str == 'SetTabVar test' && t:val_list == ['red', 'blue', 'green']) + tabclose + + if has('gui') || has('clientserver') + " Test for ":tab drop exist-file" to keep current window. + sp test1 + tab drop test1 + call assert_true(tabpagenr('$') == 1 && winnr('$') == 2 && winnr() == 1) + close + " + " + " Test for ":tab drop new-file" to keep current window of tabpage 1. + split + tab drop newfile + call assert_true(tabpagenr('$') == 2 && tabpagewinnr(1, '$') == 2 && tabpagewinnr(1) == 1) + tabclose + q + " + " + " Test for ":tab drop multi-opend-file" to keep current tabpage and window. + new test1 + tabnew + new test1 + tab drop test1 + call assert_true(tabpagenr() == 2 && tabpagewinnr(2, '$') == 2 && tabpagewinnr(2) == 1) + tabclose + q + endif + " + " + for i in range(9) | tabnew | endfor + normal! 1gt + call assert_equal(1, tabpagenr()) + tabmove 5 + call assert_equal(5, tabpagenr()) + .tabmove + call assert_equal(5, tabpagenr()) + tabmove - + call assert_equal(4, tabpagenr()) + tabmove + + call assert_equal(5, tabpagenr()) + tabmove -2 + call assert_equal(3, tabpagenr()) + tabmove +4 + call assert_equal(7, tabpagenr()) + tabmove + call assert_equal(10, tabpagenr()) + tabmove -20 + call assert_equal(1, tabpagenr()) + tabmove +20 + call assert_equal(10, tabpagenr()) + 0tabmove + call assert_equal(1, tabpagenr()) + $tabmove + call assert_equal(10, tabpagenr()) + tabmove 0 + call assert_equal(1, tabpagenr()) + tabmove $ + call assert_equal(10, tabpagenr()) + 3tabmove + call assert_equal(4, tabpagenr()) + 7tabmove 5 + call assert_equal(5, tabpagenr()) + call assert_fails("tabmove foo", 'E474:') +endfunc + +" Test autocommands +function Test_tabpage_with_autocmd() + if !has('autocmd') + return + endif + tabonly! + command -nargs=1 -bar C :call add(s:li, '=== ' . <q-args> . ' ===')|<args> + augroup TestTabpageGroup + au! + autocmd TabEnter * call add(s:li, 'TabEnter') + autocmd WinEnter * call add(s:li, 'WinEnter') + autocmd BufEnter * call add(s:li, 'BufEnter') + autocmd TabLeave * call add(s:li, 'TabLeave') + autocmd WinLeave * call add(s:li, 'WinLeave') + autocmd BufLeave * call add(s:li, 'BufLeave') + augroup END + + let s:li = [] + let t:a='a' + C tab split + call assert_equal(['=== tab split ===', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter'], s:li) + let s:li = [] + let t:a='b' + C tabnew + call assert_equal(['=== tabnew ===', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', 'BufLeave', 'BufEnter'], s:li) + let t:a='c' + let s:li = split(join(map(range(1, tabpagenr('$')), 'gettabvar(v:val, "a")')) , '\s\+') + call assert_equal(['a', 'b', 'c'], s:li) + + let s:li = [] + C call map(range(1, tabpagenr('$')), 'settabvar(v:val, ''a'', v:val*2)') + call assert_equal(["=== call map(range(1, tabpagenr('$')), 'settabvar(v:val, ''a'', v:val*2)') ==="], s:li) + let s:li = split(join(map(range(1, tabpagenr('$')), 'gettabvar(v:val, "a")')) , '\s\+') + call assert_equal(['2', '4', '6'], s:li) + + let s:li = [] + let w:a='a' + C vsplit + call assert_equal(['=== vsplit ===', 'WinLeave', 'WinEnter'], s:li) + let s:li = [] + let w:a='a' + let tabn=tabpagenr() + let winr=range(1, winnr('$')) + C tabnext 1 + call assert_equal(['=== tabnext 1 ===', 'BufLeave', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', 'BufEnter'], s:li) + let s:li = split(join(map(copy(winr), 'gettabwinvar('.tabn.', v:val, "a")')), '\s\+') + call assert_equal(['a', 'a'], s:li) + let s:li = [] + C call map(copy(winr), 'settabwinvar('.tabn.', v:val, ''a'', v:val*2)') + let s:li = split(join(map(copy(winr), 'gettabwinvar('.tabn.', v:val, "a")')), '\s\+') + call assert_equal(['2', '4'], s:li) + + augroup TabDestructive + autocmd TabEnter * :C tabnext 2 | C tabclose 3 + augroup END + let s:li = [] + C tabnext 3 + call assert_equal(['=== tabnext 3 ===', 'BufLeave', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', '=== tabnext 2 ===', '=== tabclose 3 ==='], s:li) + call assert_equal(['2/2'], [tabpagenr().'/'.tabpagenr('$')]) + + autocmd! TabDestructive TabEnter + let s:li = [] + C tabnew + call assert_equal(['=== tabnew ===', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', 'BufLeave', 'BufEnter'], s:li) + let s:li = [] + C tabnext 1 + call assert_equal(['=== tabnext 1 ===', 'BufLeave', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', 'BufEnter'], s:li) + + autocmd TabDestructive TabEnter * nested :C tabnext 2 | C tabclose 3 + let s:li = [] + C tabnext 3 + call assert_equal(['=== tabnext 3 ===', 'BufLeave', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', '=== tabnext 2 ===', 'BufLeave', 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', '=== tabnext 2 ===', '=== tabclose 3 ===', 'BufEnter', '=== tabclose 3 ==='], s:li) + call assert_equal(['2/2'], [tabpagenr().'/'.tabpagenr('$')]) + + delcommand C + autocmd! TabDestructive + augroup! TabDestructive + autocmd! TestTabpageGroup + augroup! TestTabpageGroup + tabonly! + bw! +endfunction + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/version.c b/src/nvim/version.c index e6ef600f8c..2dfe5d3248 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -214,7 +214,7 @@ static int included_patches[] = { // 2230, // 2229, // 2228, - // 2227, + 2227, // 2226, // 2225, // 2224, @@ -608,7 +608,7 @@ static int included_patches[] = { // 1836, // 1835, // 1834, - // 1833, + 1833, 1832, 1831, // 1830 NA @@ -739,7 +739,7 @@ static int included_patches[] = { // 1707, // 1706 NA // 1705 NA - // 1704, + 1704, 1703, // 1702, // 1701, @@ -824,7 +824,7 @@ static int included_patches[] = { // 1622 NA // 1621 NA 1620, - // 1619, + 1619, // 1618 NA // 1617 NA // 1616 NA @@ -855,7 +855,7 @@ static int included_patches[] = { // 1591, // 1590, // 1589, - // 1588, + 1588, // 1587 NA // 1586, // 1585, @@ -878,7 +878,7 @@ static int included_patches[] = { 1568, 1567, // 1566 NA - // 1565, + 1565, // 1564, // 1563, // 1562 NA @@ -949,7 +949,7 @@ static int included_patches[] = { // 1497 NA // 1496 NA // 1495 NA - // 1494, + 1494, // 1493 NA 1492, 1491, @@ -2652,14 +2652,13 @@ void intro_message(int colon) N_(NVIM_VERSION_LONG), "", N_("by Bram Moolenaar et al."), - N_("Vim is open source and freely distributable"), - "", - N_("Type \":Tutor\" or \":help nvim\" to get started!"), - "", - N_("Still have questions? https://neovim.io/community"), + N_("Nvim is open source and freely distributable"), + N_("https://neovim.io/community"), "", + N_("type :help nvim<Enter> if you are new! "), + N_("type :CheckHealth<Enter> to optimize Nvim"), N_("type :q<Enter> to exit "), - N_("type :help<Enter> or <F1> for on-line help"), + N_("type :help<Enter> for help "), "", N_("Help poor children in Uganda!"), N_("type :help iccf<Enter> for information "), diff --git a/src/nvim/window.c b/src/nvim/window.c index a259654d52..03a2e9a842 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -278,10 +278,11 @@ newwindow: /* cursor to last accessed (previous) window */ case 'p': case Ctrl_P: - if (prevwin == NULL) + if (!win_valid(prevwin)) { beep_flush(); - else + } else { win_goto(prevwin); + } break; /* exchange current and next window */ @@ -3768,8 +3769,15 @@ win_free ( hash_init(&wp->w_vars->dv_hashtab); unref_var_dict(wp->w_vars); - if (prevwin == wp) + if (prevwin == wp) { prevwin = NULL; + } + FOR_ALL_TABS(ttp) { + if (ttp->tp_prevwin == wp) { + ttp->tp_prevwin = NULL; + } + } + win_free_lsize(wp); for (i = 0; i < wp->w_tagstacklen; ++i) @@ -5716,45 +5724,46 @@ int win_getid(typval_T *argvars) int win_gotoid(typval_T *argvars) { - win_T *wp; - tabpage_T *tp; int id = get_tv_number(&argvars[0]); - for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) { - for (wp = tp == curtab ? firstwin : tp->tp_firstwin; - wp != NULL; wp = wp->w_next) { - if (wp->handle == id) { - goto_tabpage_win(tp, wp); - return 1; - } + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->handle == id) { + goto_tabpage_win(tp, wp); + return 1; } } return 0; } -void win_id2tabwin(typval_T *argvars, list_T *list) +void win_get_tabwin(handle_T id, int *tabnr, int *winnr) { - win_T *wp; - tabpage_T *tp; - int winnr = 1; - int tabnr = 1; - int id = get_tv_number(&argvars[0]); + *tabnr = 0; + *winnr = 0; - for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) { - for (wp = tp == curtab ? firstwin : tp->tp_firstwin; - wp != NULL; wp = wp->w_next) { + int tnum = 1, wnum = 1; + FOR_ALL_TABS(tp) { + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { if (wp->handle == id) { - list_append_number(list, tabnr); - list_append_number(list, winnr); + *winnr = wnum; + *tabnr = tnum; return; } - winnr++; + wnum++; } - tabnr++; - winnr = 1; + tnum++; + wnum = 1; } - list_append_number(list, 0); - list_append_number(list, 0); +} + +void win_id2tabwin(typval_T *argvars, list_T *list) +{ + int winnr = 1; + int tabnr = 1; + int id = get_tv_number(&argvars[0]); + + win_get_tabwin(id, &tabnr, &winnr); + list_append_number(list, tabnr); + list_append_number(list, winnr); } int win_id2win(typval_T *argvars) diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index 71ec213b97..3d3a2bb046 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -1,4 +1,3 @@ --- Sanity checks for buffer_* API calls via msgpack-rpc local helpers = require('test.functional.helpers')(after_each) local clear, nvim, buffer = helpers.clear, helpers.nvim, helpers.buffer local curbuf, curwin, eq = helpers.curbuf, helpers.curwin, helpers.eq @@ -6,7 +5,7 @@ local curbufmeths, ok = helpers.curbufmeths, helpers.ok local funcs, request = helpers.funcs, helpers.request local NIL = helpers.NIL -describe('buffer_* functions', function() +describe('api/buf', function() before_each(clear) -- access deprecated functions diff --git a/test/functional/api/server_notifications_spec.lua b/test/functional/api/server_notifications_spec.lua index 88e8c60560..78639d7ed7 100644 --- a/test/functional/api/server_notifications_spec.lua +++ b/test/functional/api/server_notifications_spec.lua @@ -1,4 +1,3 @@ --- Tests for nvim notifications local helpers = require('test.functional.helpers')(after_each) local eq, clear, eval, execute, nvim, next_message = helpers.eq, helpers.clear, helpers.eval, helpers.execute, helpers.nvim, diff --git a/test/functional/api/tabpage_spec.lua b/test/functional/api/tabpage_spec.lua index 47dede8b44..e10f30085f 100644 --- a/test/functional/api/tabpage_spec.lua +++ b/test/functional/api/tabpage_spec.lua @@ -1,4 +1,3 @@ --- Sanity checks for tabpage_* API calls via msgpack-rpc local helpers = require('test.functional.helpers')(after_each) local clear, nvim, tabpage, curtab, eq, ok = helpers.clear, helpers.nvim, helpers.tabpage, helpers.curtab, helpers.eq, @@ -8,7 +7,7 @@ local funcs = helpers.funcs local request = helpers.request local NIL = helpers.NIL -describe('tabpage_* functions', function() +describe('api/tabpage', function() before_each(clear) describe('list_wins and get_win', function() @@ -51,6 +50,22 @@ describe('tabpage_* functions', function() end) end) + describe('get_number', function() + it('works', function() + local tabs = nvim('list_tabpages') + eq(1, tabpage('get_number', tabs[1])) + + nvim('command', 'tabnew') + local tab1, tab2 = unpack(nvim('list_tabpages')) + eq(1, tabpage('get_number', tab1)) + eq(2, tabpage('get_number', tab2)) + + nvim('command', '-tabmove') + eq(2, tabpage('get_number', tab1)) + eq(1, tabpage('get_number', tab2)) + end) + end) + describe('is_valid', function() it('works', function() nvim('command', 'tabnew') diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 6b30c0e81c..ce6c52e334 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1,4 +1,3 @@ --- Sanity checks for vim_* API calls via msgpack-rpc local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local NIL = helpers.NIL @@ -9,10 +8,10 @@ local meths = helpers.meths local funcs = helpers.funcs local request = helpers.request -describe('vim_* functions', function() +describe('api', function() before_each(clear) - describe('command', function() + describe('nvim_command', function() it('works', function() local fname = helpers.tmpname() nvim('command', 'new') @@ -29,9 +28,18 @@ describe('vim_* functions', function() f:close() os.remove(fname) end) + + it("VimL error: fails (VimL error), does NOT update v:errmsg", function() + -- Most API methods return generic errors (or no error) if a VimL + -- expression fails; nvim_command returns the VimL error details. + local status, rv = pcall(nvim, "command", "bogus_command") + eq(false, status) -- nvim_command() failed. + eq("E492:", string.match(rv, "E%d*:")) -- VimL error was returned. + eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + end) end) - describe('eval', function() + describe('nvim_eval', function() it('works', function() nvim('command', 'let g:v1 = "a"') nvim('command', 'let g:v2 = [1, 2, {"v3": 3}]') @@ -46,18 +54,41 @@ describe('vim_* functions', function() it('works under deprecated name', function() eq(2, request("vim_eval", "1+1")) end) + + it("VimL error: fails (generic error), does NOT update v:errmsg", function() + local status, rv = pcall(nvim, "eval", "bogus expression") + eq(false, status) -- nvim_eval() failed. + ok(nil ~= string.find(rv, "Failed to evaluate expression")) + eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + end) end) - describe('call_function', function() + describe('nvim_call_function', function() it('works', function() nvim('call_function', 'setqflist', {{{ filename = 'something', lnum = 17}}, 'r'}) eq(17, nvim('call_function', 'getqflist', {})[1].lnum) eq(17, nvim('call_function', 'eval', {17})) eq('foo', nvim('call_function', 'simplify', {'this/./is//redundant/../../../foo'})) end) + + it("VimL error: fails (generic error), does NOT update v:errmsg", function() + local status, rv = pcall(nvim, "call_function", "bogus function", {"arg1"}) + eq(false, status) -- nvim_call_function() failed. + ok(nil ~= string.find(rv, "Error calling function")) + eq("", nvim("eval", "v:errmsg")) -- v:errmsg was not updated. + end) end) - describe('strwidth', function() + describe('nvim_input', function() + it("VimL error: does NOT fail, updates v:errmsg", function() + local status, _ = pcall(nvim, "input", ":call bogus_fn()<CR>") + local v_errnum = string.match(nvim("eval", "v:errmsg"), "E%d*:") + eq(true, status) -- nvim_input() did not fail. + eq("E117:", v_errnum) -- v:errmsg was updated. + end) + end) + + describe('nvim_strwidth', function() it('works', function() eq(3, nvim('strwidth', 'abc')) -- 6 + (neovim) @@ -70,7 +101,7 @@ describe('vim_* functions', function() end) end) - describe('{get,set}_current_line', function() + describe('nvim_get_current_line, nvim_set_current_line', function() it('works', function() eq('', nvim('get_current_line')) nvim('set_current_line', 'abc') @@ -78,7 +109,7 @@ describe('vim_* functions', function() end) end) - describe('{get,set,del}_var', function() + describe('nvim_get_var, nvim_set_var, nvim_del_var', function() it('works', function() nvim('set_var', 'lua', {1, 2, {['3'] = 1}}) eq({1, 2, {['3'] = 1}}, nvim('get_var', 'lua')) @@ -109,7 +140,7 @@ describe('vim_* functions', function() end) end) - describe('{get,set}_option', function() + describe('nvim_get_option, nvim_set_option', function() it('works', function() ok(nvim('get_option', 'equalalways')) nvim('set_option', 'equalalways', false) @@ -117,7 +148,7 @@ describe('vim_* functions', function() end) end) - describe('{get,set}_current_buf and list_bufs', function() + describe('nvim_{get,set}_current_buf, nvim_list_bufs', function() it('works', function() eq(1, #nvim('list_bufs')) eq(nvim('list_bufs')[1], nvim('get_current_buf')) @@ -129,7 +160,7 @@ describe('vim_* functions', function() end) end) - describe('{get,set}_current_win and list_wins', function() + describe('nvim_{get,set}_current_win, nvim_list_wins', function() it('works', function() eq(1, #nvim('list_wins')) eq(nvim('list_wins')[1], nvim('get_current_win')) @@ -142,7 +173,7 @@ describe('vim_* functions', function() end) end) - describe('{get,set}_current_tabpage and list_tabpages', function() + describe('nvim_{get,set}_current_tabpage, nvim_list_tabpages', function() it('works', function() eq(1, #nvim('list_tabpages')) eq(nvim('list_tabpages')[1], nvim('get_current_tabpage')) @@ -161,7 +192,7 @@ describe('vim_* functions', function() end) end) - describe('replace_termcodes', function() + describe('nvim_replace_termcodes', function() it('escapes K_SPECIAL as K_SPECIAL KS_SPECIAL KE_FILLER', function() eq('\128\254X', helpers.nvim('replace_termcodes', '\128', true, true, true)) end) @@ -183,7 +214,7 @@ describe('vim_* functions', function() end) end) - describe('feedkeys', function() + describe('nvim_feedkeys', function() it('CSI escaping', function() local function on_setup() -- notice the special char(…) \xe2\80\xa6 @@ -210,7 +241,7 @@ describe('vim_* functions', function() end) end) - describe('err_write', function() + describe('nvim_err_write', function() local screen before_each(function() @@ -298,6 +329,94 @@ describe('vim_* functions', function() end) end) + describe('nvim_call_atomic', function() + it('works', function() + meths.buf_set_lines(0, 0, -1, true, {'first'}) + local req = { + {'nvim_get_current_line', {}}, + {'nvim_set_current_line', {'second'}}, + } + eq({{'first', NIL}, NIL}, meths.call_atomic(req)) + eq({'second'}, meths.buf_get_lines(0, 0, -1, true)) + end) + + it('allows multiple return values', function() + local req = { + {'nvim_set_var', {'avar', true}}, + {'nvim_set_var', {'bvar', 'string'}}, + {'nvim_get_var', {'avar'}}, + {'nvim_get_var', {'bvar'}}, + } + eq({{NIL, NIL, true, 'string'}, NIL}, meths.call_atomic(req)) + end) + + it('is aborted by errors in call', function() + local error_types = meths.get_api_info()[2].error_types + local req = { + {'nvim_set_var', {'one', 1}}, + {'nvim_buf_set_lines', {}}, + {'nvim_set_var', {'two', 2}}, + } + eq({{NIL}, {1, error_types.Exception.id, + 'Wrong number of arguments: expecting 5 but got 0'}}, + meths.call_atomic(req)) + eq(1, meths.get_var('one')) + eq(false, pcall(meths.get_var, 'two')) + + -- still returns all previous successful calls + req = { + {'nvim_set_var', {'avar', 5}}, + {'nvim_set_var', {'bvar', 'string'}}, + {'nvim_get_var', {'avar'}}, + {'nvim_buf_get_lines', {0, 10, 20, true}}, + {'nvim_get_var', {'bvar'}}, + } + eq({{NIL, NIL, 5}, {3, error_types.Validation.id, 'Index out of bounds'}}, + meths.call_atomic(req)) + + req = { + {'i_am_not_a_method', {'xx'}}, + {'nvim_set_var', {'avar', 10}}, + } + eq({{}, {0, error_types.Exception.id, 'Invalid method name'}}, + meths.call_atomic(req)) + eq(5, meths.get_var('avar')) + end) + + it('throws error on malformated arguments', function() + local req = { + {'nvim_set_var', {'avar', 1}}, + {'nvim_set_var'}, + {'nvim_set_var', {'avar', 2}}, + } + local status, err = pcall(meths.call_atomic, req) + eq(false, status) + ok(err:match(' All items in calls array must be arrays of size 2') ~= nil) + -- call before was done, but not after + eq(1, meths.get_var('avar')) + + req = { + {'nvim_set_var', {'bvar', {2,3}}}, + 12, + } + status, err = pcall(meths.call_atomic, req) + eq(false, status) + ok(err:match('All items in calls array must be arrays') ~= nil) + eq({2,3}, meths.get_var('bvar')) + + req = { + {'nvim_set_current_line', 'little line'}, + {'nvim_set_var', {'avar', 3}}, + } + status, err = pcall(meths.call_atomic, req) + eq(false, status) + ok(err:match('args must be Array') ~= nil) + -- call before was done, but not after + eq(1, meths.get_var('avar')) + eq({''}, meths.buf_get_lines(0, 0, -1, true)) + end) + end) + it('can throw exceptions', function() local status, err = pcall(nvim, 'get_option', 'invalid-option') eq(false, status) diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 2c43e4db2c..465bda6bc9 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1,4 +1,3 @@ --- Sanity checks for window_* API calls via msgpack-rpc local helpers = require('test.functional.helpers')(after_each) local clear, nvim, curbuf, curbuf_contents, window, curwin, eq, neq, ok, feed, insert, eval = helpers.clear, helpers.nvim, helpers.curbuf, @@ -29,7 +28,7 @@ local function is_visible(str) return false end -describe('window_* functions', function() +describe('api/win', function() before_each(clear) describe('get_buf', function() @@ -200,6 +199,30 @@ describe('window_* functions', function() end) end) + describe('get_number', function() + it('works', function() + local wins = nvim('list_wins') + eq(1, window('get_number', wins[1])) + + nvim('command', 'split') + local win1, win2 = unpack(nvim('list_wins')) + eq(1, window('get_number', win1)) + eq(2, window('get_number', win2)) + + nvim('command', 'wincmd J') + eq(2, window('get_number', win1)) + eq(1, window('get_number', win2)) + + nvim('command', 'tabnew') + local win3 = nvim('list_wins')[3] + -- First tab page + eq(2, window('get_number', win1)) + eq(1, window('get_number', win2)) + -- Second tab page + eq(1, window('get_number', win3)) + end) + end) + describe('is_valid', function() it('works', function() nvim('command', 'split') diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index adbe17d4b5..5872ebe8ee 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -121,14 +121,14 @@ describe('jobs', function() eq({'notification', 'exit', {0, 0}}, next_msg()) end) - it('can preserve newlines', function() + it('preserves newlines', function() nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") nvim('command', 'call jobsend(j, "a\\n\\nc\\n\\n\\n\\nb\\n\\n")') eq({'notification', 'stdout', {0, {'a', '', 'c', '', '', '', 'b', '', ''}}}, next_msg()) end) - it('can preserve NULs', function() + it('preserves NULs', function() nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") nvim('command', 'call jobsend(j, ["\n123\n", "abc\\nxyz\n", ""])') eq({'notification', 'stdout', {0, {'\n123\n', 'abc\nxyz\n', ''}}}, @@ -137,7 +137,7 @@ describe('jobs', function() eq({'notification', 'exit', {0, 0}}, next_msg()) end) - it('can avoid sending final newline', function() + it('avoids sending final newline', function() nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") nvim('command', 'call jobsend(j, ["some data", "without\nfinal nl"])') eq({'notification', 'stdout', {0, {'some data', 'without\nfinal nl'}}}, @@ -146,7 +146,7 @@ describe('jobs', function() eq({'notification', 'exit', {0, 0}}, next_msg()) end) - it('can close the job streams with jobclose', function() + it('closes the job streams with jobclose', function() nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") nvim('command', 'call jobclose(j, "stdin")') eq({'notification', 'exit', {0, 0}}, next_msg()) diff --git a/test/functional/eval/execute_spec.lua b/test/functional/eval/execute_spec.lua index 10a6de6eb4..b5b481435a 100644 --- a/test/functional/eval/execute_spec.lua +++ b/test/functional/eval/execute_spec.lua @@ -70,16 +70,16 @@ describe('execute()', function() end) it('silences command run inside', function() - local screen = Screen.new(20, 5) + local screen = Screen.new(40, 5) screen:attach() screen:set_default_attr_ids( {[0] = {bold=true, foreground=255}} ) feed(':let g:mes = execute("echon 42")<CR>') screen:expect([[ - ^ | - {0:~ }| - {0:~ }| - {0:~ }| - | + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + :let g:mes = execute("echon 42") | ]]) eq('42', eval('g:mes')) end) diff --git a/test/functional/shell/viml_system_spec.lua b/test/functional/eval/system_spec.lua index b8de7cc86f..6393477260 100644 --- a/test/functional/shell/viml_system_spec.lua +++ b/test/functional/eval/system_spec.lua @@ -1,10 +1,7 @@ --- Specs for --- - `system()` --- - `systemlist()` - local helpers = require('test.functional.helpers')(after_each) -local eq, clear, eval, feed, nvim = - helpers.eq, helpers.clear, helpers.eval, helpers.feed, helpers.nvim +local eq, clear, eval, execute, feed, nvim = + helpers.eq, helpers.clear, helpers.eval, helpers.execute, helpers.feed, + helpers.nvim local Screen = require('test.functional.ui.screen') @@ -120,12 +117,30 @@ describe('system()', function() it('returns the program output', function() eq("echoed", eval('system("echo -n echoed")')) end) + it('to backgrounded command does not crash', function() + -- This is indeterminate, just exercise the codepath. May get E5677. + execute('call system("echo -n echoed &")') + local v_errnum = string.match(eval("v:errmsg"), "^E%d*:") + if v_errnum then + eq("E5677:", v_errnum) + end + eq(2, eval("1+1")) -- Still alive? + end) end) describe('passing input', function() it('returns the program output', function() eq("input", eval('system("cat -", "input")')) end) + it('to backgrounded command does not crash', function() + -- This is indeterminate, just exercise the codepath. May get E5677. + execute('call system("cat - &")') + local v_errnum = string.match(eval("v:errmsg"), "^E%d*:") + if v_errnum then + eq("E5677:", v_errnum) + end + eq(2, eval("1+1")) -- Still alive? + end) end) describe('passing a lot of input', function() diff --git a/test/functional/eval/timer_spec.lua b/test/functional/eval/timer_spec.lua index 295c763d74..fba9466b78 100644 --- a/test/functional/eval/timer_spec.lua +++ b/test/functional/eval/timer_spec.lua @@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen') local ok, feed, eq, eval = helpers.ok, helpers.feed, helpers.eq, helpers.eval local source, nvim_async, run = helpers.source, helpers.nvim_async, helpers.run local clear, execute, funcs = helpers.clear, helpers.execute, helpers.funcs +local curbufmeths = helpers.curbufmeths describe('timers', function() before_each(function() @@ -62,19 +63,76 @@ describe('timers', function() end) it('are paused when event processing is disabled', function() - -- this is not the intended behavior, but at least there will - -- not be a burst of queued up callbacks - execute("call timer_start(50, 'MyHandler', {'repeat': 2})") + execute("call timer_start(50, 'MyHandler', {'repeat': -1})") run(nil, nil, nil, 100) local count = eval("g:val") + -- shows two line error message and thus invokes the return prompt. + -- if we start to allow event processing here, we need to change this test. + execute("throw 'fatal error'") + run(nil, nil, nil, 300) + feed("<cr>") + local diff = eval("g:val") - count + ok(0 <= diff and diff <= 4) + end) + + it('are triggered in blocking getchar() call', function() + execute("call timer_start(50, 'MyHandler', {'repeat': -1})") nvim_async("command", "let g:c = getchar()") run(nil, nil, nil, 300) feed("c") - local diff = eval("g:val") - count - ok(0 <= diff and diff <= 2) + local count = eval("g:val") + ok(count >= 5) eq(99, eval("g:c")) end) + it('can invoke redraw in blocking getchar() call', function() + local screen = Screen.new(40, 6) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold=true, foreground=Screen.colors.Blue}, + }) + + curbufmeths.set_lines(0, -1, true, {"ITEM 1", "ITEM 2"}) + source([[ + func! AddItem(timer) + call nvim_buf_set_lines(0, 2, 2, v:true, ['ITEM 3']) + redraw + endfunc + call timer_start(200, 'AddItem') + ]]) + nvim_async("command", "let g:c2 = getchar()") + + screen:expect([[ + ITEM 1 | + ITEM 2 | + {1:~ }| + {1:~ }| + {1:~ }| + ^ | + ]]) + + screen:sleep(200) + screen:expect([[ + ITEM 1 | + ITEM 2 | + ITEM 3 | + {1:~ }| + {1:~ }| + ^ | + ]]) + + feed("3") + eq(51, eval("g:c2")) + screen:expect([[ + ^ITEM 1 | + ITEM 2 | + ITEM 3 | + {1:~ }| + {1:~ }| + | + ]]) + end) + it('can be stopped', function() local t = eval("timer_start(50, 'MyHandler', {'repeat': -1})") eq(0,eval("g:val")) diff --git a/test/functional/shell/bang_filter_spec.lua b/test/functional/ex_cmds/bang_filter_spec.lua index a320e6d018..a320e6d018 100644 --- a/test/functional/shell/bang_filter_spec.lua +++ b/test/functional/ex_cmds/bang_filter_spec.lua diff --git a/test/functional/dict_notifications_spec.lua b/test/functional/ex_cmds/dict_notifications_spec.lua index dc87312911..dc87312911 100644 --- a/test/functional/dict_notifications_spec.lua +++ b/test/functional/ex_cmds/dict_notifications_spec.lua diff --git a/test/functional/ex_cmds/write_spec.lua b/test/functional/ex_cmds/write_spec.lua index c1bc5d3140..4ac9f312ef 100644 --- a/test/functional/ex_cmds/write_spec.lua +++ b/test/functional/ex_cmds/write_spec.lua @@ -1,20 +1,26 @@ --- Specs for :write - local helpers = require('test.functional.helpers')(after_each) -local eq, eval, clear, write_file, execute, source = - helpers.eq, helpers.eval, helpers.clear, helpers.write_file, - helpers.execute, helpers.source +local eq, eval, clear, write_file, execute, source, insert = + helpers.eq, helpers.eval, helpers.clear, helpers.write_file, + helpers.execute, helpers.source, helpers.insert if helpers.pending_win32(pending) then return end describe(':write', function() - after_each(function() + local function cleanup() os.remove('test_bkc_file.txt') os.remove('test_bkc_link.txt') + os.remove('test_fifo') + end + before_each(function() + clear() + cleanup() + end) + after_each(function() + cleanup() end) it('&backupcopy=auto preserves symlinks', function() - clear('--cmd', 'set backupcopy=auto') + execute('set backupcopy=auto') write_file('test_bkc_file.txt', 'content0') execute("silent !ln -s test_bkc_file.txt test_bkc_link.txt") source([[ @@ -27,7 +33,7 @@ describe(':write', function() end) it('&backupcopy=no replaces symlink with new file', function() - clear('--cmd', 'set backupcopy=no') + execute('set backupcopy=no') write_file('test_bkc_file.txt', 'content0') execute("silent !ln -s test_bkc_file.txt test_bkc_link.txt") source([[ @@ -38,4 +44,23 @@ describe(':write', function() eq(eval("['content0']"), eval("readfile('test_bkc_file.txt')")) eq(eval("['content1']"), eval("readfile('test_bkc_link.txt')")) end) + + it("appends FIFO file", function() + if eval("executable('mkfifo')") == 0 then + pending('missing "mkfifo" command', function()end) + return + end + + local text = "some fifo text from write_spec" + assert(os.execute("mkfifo test_fifo")) + insert(text) + + -- Blocks until a consumer reads the FIFO. + execute("write >> test_fifo") + + -- Read the FIFO, this will unblock the :write above. + local fifo = assert(io.open("test_fifo")) + eq(text.."\n", fifo:read("*all")) + fifo:close() + end) end) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 325f41e506..ff62b4de86 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -1,11 +1,13 @@ require('coxpcall') local lfs = require('lfs') -local ChildProcessStream = require('nvim.child_process_stream') -local SocketStream = require('nvim.socket_stream') -local TcpStream = require('nvim.tcp_stream') -local Session = require('nvim.session') local global_helpers = require('test.helpers') +-- nvim client: Found in .deps/usr/share/lua/<version>/nvim/ if "bundled". +local Session = require('nvim.session') +local TcpStream = require('nvim.tcp_stream') +local SocketStream = require('nvim.socket_stream') +local ChildProcessStream = require('nvim.child_process_stream') + local check_logs = global_helpers.check_logs local neq = global_helpers.neq local eq = global_helpers.eq @@ -134,10 +136,14 @@ local function stop() session:stop() end +-- Executes an ex-command. VimL errors manifest as client (lua) errors, but +-- v:errmsg will not be updated. local function nvim_command(cmd) request('nvim_command', cmd) end +-- Evaluates a VimL expression. +-- Fails on VimL error, but does not update v:errmsg. local function nvim_eval(expr) return request('nvim_eval', expr) end @@ -158,10 +164,14 @@ local os_name = (function() end) end)() +-- Executes a VimL function. +-- Fails on VimL error, but does not update v:errmsg. local function nvim_call(name, ...) return request('nvim_call_function', name, {...}) end +-- Sends user input to Nvim. +-- Does not fail on VimL error, but v:errmsg will be updated. local function nvim_feed(input) while #input > 0 do local written = request('nvim_input', input) @@ -279,6 +289,8 @@ local function insert(...) nvim_feed('<ESC>') end +-- Executes an ex-command by user input. Because nvim_input() is used, VimL +-- errors will not manifest as client (lua) errors. Use command() for that. local function execute(...) for _, v in ipairs({...}) do if v:sub(1, 1) ~= '/' then diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index 42dd25023a..8a042be7f7 100644 --- a/test/functional/legacy/assert_spec.lua +++ b/test/functional/legacy/assert_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local nvim, call = helpers.meths, helpers.call local clear, eq = helpers.clear, helpers.eq local source, execute = helpers.source, helpers.execute +local exc_exec = helpers.exc_exec local function expected_errors(errors) eq(errors, nvim.get_vvar('errors')) @@ -62,6 +63,20 @@ describe('assert function:', function() call('assert_equal', 'true', 'false') expected_errors({"Expected 'true' but got 'false'"}) end) + + it('should change v:errors when expected is not equal to actual', function() + source([[ + function CheckAssert() + let s:v = {} + let s:x = {"a": s:v} + let s:v["b"] = s:x + let s:w = {"c": s:x, "d": ''} + call assert_equal(s:w, '') + endfunction + ]]) + eq('Vim(call):E724: unable to correctly dump variable with self-referencing container', + exc_exec('call CheckAssert()')) + end) end) -- assert_notequal({expected}, {actual}[, {msg}]) diff --git a/test/functional/legacy/quickfix_spec.lua b/test/functional/legacy/quickfix_spec.lua index df8f2625db..7657b8641b 100644 --- a/test/functional/legacy/quickfix_spec.lua +++ b/test/functional/legacy/quickfix_spec.lua @@ -277,6 +277,118 @@ describe('helpgrep', function() augroup! testgroup endfunction + + " This will test for problems in quickfix: + " A. incorrectly copying location lists which caused the location list to show + " a different name than the file that was actually being displayed. + " B. not reusing the window for which the location list window is opened but + " instead creating new windows. + " C. make sure that the location list window is not reused instead of the + " window it belongs to. + " + " Set up the test environment: + function! ReadTestProtocol(name) + let base = substitute(a:name, '\v^test://(.*)%(\.[^.]+)?', '\1', '') + let word = substitute(base, '\v(.*)\..*', '\1', '') + + setl modifiable + setl noreadonly + setl noswapfile + setl bufhidden=delete + %del _ + " For problem 2: + " 'buftype' has to be set to reproduce the constant opening of new windows + setl buftype=nofile + + call setline(1, word) + + setl nomodified + setl nomodifiable + setl readonly + exe 'doautocmd BufRead ' . substitute(a:name, '\v^test://(.*)', '\1', '') + endfunction + + function Test_locationlist() + enew + + augroup testgroup + au! + autocmd BufReadCmd test://* call ReadTestProtocol(expand("<amatch>")) + augroup END + + let words = [ "foo", "bar", "baz", "quux", "shmoo", "spam", "eggs" ] + + let qflist = [] + for word in words + call add(qflist, {'filename': 'test://' . word . '.txt', 'text': 'file ' . word . '.txt', }) + " NOTE: problem 1: + " intentionally not setting 'lnum' so that the quickfix entries are not + " valid + call setloclist(0, qflist, ' ') + endfor + + " Test A + lrewind + enew + lopen + lnext + lnext + lnext + lnext + vert split + wincmd L + lopen + wincmd p + lnext + let fileName = expand("%") + wincmd p + let locationListFileName = substitute(getline(line('.')), '\([^|]*\)|.*', '\1', '') + let fileName = substitute(fileName, '\\', '/', 'g') + let locationListFileName = substitute(locationListFileName, '\\', '/', 'g') + call assert_equal("test://bar.txt", fileName) + call assert_equal("test://bar.txt", locationListFileName) + + wincmd n | only + + " Test B: + lrewind + lopen + 2 + exe "normal \<CR>" + wincmd p + 3 + exe "normal \<CR>" + wincmd p + 4 + exe "normal \<CR>" + call assert_equal(2, winnr('$')) + wincmd n | only + + " Test C: + lrewind + lopen + " Let's move the location list window to the top to check whether it (the + " first window found) will be reused when we try to open new windows: + wincmd K + 2 + exe "normal \<CR>" + wincmd p + 3 + exe "normal \<CR>" + wincmd p + 4 + exe "normal \<CR>" + 1wincmd w + call assert_equal('quickfix', &buftype) + 2wincmd w + let bufferName = expand("%") + let bufferName = substitute(bufferName, '\\', '/', 'g') + call assert_equal('test://quux.txt', bufferName) + + wincmd n | only + + augroup! testgroup + endfunction ]]) end) @@ -339,4 +451,9 @@ describe('helpgrep', function() call('Test_locationlist_curwin_was_closed') expected_empty() end) + + it('checks locationlist protocol read', function() + call('Test_locationlist') + expected_empty() + end) end) diff --git a/test/functional/terminal/api_spec.lua b/test/functional/terminal/api_spec.lua index 58d6c75940..045bdb0749 100644 --- a/test/functional/terminal/api_spec.lua +++ b/test/functional/terminal/api_spec.lua @@ -22,7 +22,7 @@ describe('api', function() -- Start the socket from the child nvim. child_session.feed_data(":echo serverstart('"..socket_name.."')\n") - -- Wait for socket creation by abusing expect(). + -- Wait for socket creation. screen:expect([[ {1: } | {4:~ }| @@ -37,6 +37,16 @@ describe('api', function() local socket_session2 = helpers.connect(socket_name) child_session.feed_data("i[tui] insert-mode") + -- Wait for stdin to be processed. + screen:expect([[ + [tui] insert-mode{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) ok(socket_session1:request("nvim_ui_attach", 42, 6, {rgb=true})) ok(socket_session2:request("nvim_ui_attach", 25, 30, {rgb=true})) diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 09b4eaa8d5..4247be0417 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -13,6 +13,42 @@ describe(':terminal', function() clear() screen = Screen.new(50, 4) screen:attach({rgb=false}) + end) + + it("does not interrupt Press-ENTER prompt #2748", function() + -- Ensure that :messages shows Press-ENTER. + source([[ + echomsg "msg1" + echomsg "msg2" + ]]) + -- Invoke a command that emits frequent terminal activity. + execute([[terminal while true; do echo X; done]]) + helpers.feed([[<C-\><C-N>]]) + screen:expect([[ + X | + X | + ^X | + | + ]]) + helpers.sleep(10) -- Let some terminal activity happen. + execute("messages") + screen:expect([[ + X | + msg1 | + msg2 | + Press ENTER or type command to continue^ | + ]]) + end) + +end) + +describe(':terminal (with fake shell)', function() + local screen + + before_each(function() + clear() + screen = Screen.new(50, 4) + screen:attach({rgb=false}) -- shell-test.c is a fake shell that prints its arguments and exits. nvim('set_option', 'shell', nvim_dir..'/shell-test') nvim('set_option', 'shellcmdflag', 'EXE') @@ -20,12 +56,12 @@ describe(':terminal', function() -- Invokes `:terminal {cmd}` using a fake shell (shell-test.c) which prints -- the {cmd} and exits immediately . - local function terminal_run_fake_shell_cmd(cmd) + local function terminal_with_fake_shell(cmd) execute("terminal "..(cmd and cmd or "")) end it('with no argument, acts like termopen()', function() - terminal_run_fake_shell_cmd() + terminal_with_fake_shell() wait() screen:expect([[ ready $ | @@ -36,7 +72,7 @@ describe(':terminal', function() end) it('executes a given command through the shell', function() - terminal_run_fake_shell_cmd('echo hi') + terminal_with_fake_shell('echo hi') wait() screen:expect([[ ready $ echo hi | @@ -47,7 +83,7 @@ describe(':terminal', function() end) it('allows quotes and slashes', function() - terminal_run_fake_shell_cmd([[echo 'hello' \ "world"]]) + terminal_with_fake_shell([[echo 'hello' \ "world"]]) wait() screen:expect([[ ready $ echo 'hello' \ "world" | @@ -66,14 +102,14 @@ describe(':terminal', function() end) it('ignores writes if the backing stream closes', function() - terminal_run_fake_shell_cmd() + terminal_with_fake_shell() helpers.feed('iiXXXXXXX') wait() -- Race: Though the shell exited (and streams were closed by SIGCHLD -- handler), :terminal cleanup is pending on the main-loop. -- This write should be ignored (not crash, #5445). helpers.feed('iiYYYYYYY') - wait() + eq(2, eval("1+1")) -- Still alive? end) end) diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua index 0897c2d836..da3400fb27 100644 --- a/test/functional/viml/completion_spec.lua +++ b/test/functional/viml/completion_spec.lua @@ -317,6 +317,62 @@ describe('completion', function() end) end) + describe('completeopt+=noinsert does not add blank undo items', function() + before_each(function() + source([[ + function! TestComplete() abort + call complete(1, ['foo', 'bar']) + return '' + endfunction + ]]) + execute('set completeopt+=noselect,noinsert') + execute('inoremap <right> <c-r>=TestComplete()<cr>') + end) + + local tests = { + ['<up>, <down>, <cr>'] = {'<down><cr>', '<up><cr>'}, + ['<c-n>, <c-p>, <c-y>'] = {'<c-n><c-y>', '<c-p><c-y>'}, + } + + for name, seq in pairs(tests) do + it('using ' .. name, function() + feed('iaaa<esc>') + feed('A<right>' .. seq[1] .. '<esc>') + feed('A<right><esc>A<right><esc>') + feed('A<cr>bbb<esc>') + feed('A<right>' .. seq[2] .. '<esc>') + feed('A<right><esc>A<right><esc>') + feed('A<cr>ccc<esc>') + feed('A<right>' .. seq[1] .. '<esc>') + feed('A<right><esc>A<right><esc>') + + local expected = { + {'foo', 'bar', 'foo'}, + {'foo', 'bar', 'ccc'}, + {'foo', 'bar'}, + {'foo', 'bbb'}, + {'foo'}, + {'aaa'}, + {''}, + } + + for i = 1, #expected do + if i > 1 then + feed('u') + end + eq(expected[i], eval('getline(1, "$")')) + end + + for i = #expected, 1, -1 do + if i < #expected then + feed('<c-r>') + end + eq(expected[i], eval('getline(1, "$")')) + end + end) + end + end) + describe("refresh:always", function() before_each(function() source([[ |