diff options
34 files changed, 727 insertions, 212 deletions
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 4975dc66b8..0482cb7f3c 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -537,8 +537,8 @@ function! s:check_virtualenv() abort call add(errors, 'no Python executables found in the virtualenv '.bin_dir.' directory.') endif + let msg = '$VIRTUAL_ENV is set to: '.$VIRTUAL_ENV if len(errors) - let msg = '$VIRTUAL_ENV is set to: '.$VIRTUAL_ENV if len(venv_bins) let msg .= "\nAnd its ".bin_dir.' directory contains: ' \.join(map(venv_bins, "fnamemodify(v:val, ':t')"), ', ') @@ -551,7 +551,10 @@ function! s:check_virtualenv() abort let msg .= "\nSo invoking Python may lead to unexpected results." call health#report_warn(msg, keys(hints)) else - call health#report_ok('$VIRTUAL_ENV provides :python, :python3, et al.') + call health#report_info(msg) + call health#report_info('Python version: ' + \.system('python -c "import platform, sys; sys.stdout.write(platform.python_version())"')) + call health#report_ok('$VIRTUAL_ENV provides :!python.') endif endfunction diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 122ae357bc..930db3ceb7 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -52,13 +52,14 @@ function! man#open_page(count, count1, mods, ...) abort let ref = a:2.'('.a:1.')' endif try - let [sect, name] = man#extract_sect_and_name_ref(ref) + let [sect, name] = s:extract_sect_and_name_ref(ref) if a:count ==# a:count1 " v:count defaults to 0 which is a valid section, and v:count1 defaults to " 1, also a valid section. If they are equal, count explicitly set. let sect = string(a:count) endif - let [sect, name, path] = s:verify_exists(sect, name) + let path = s:verify_exists(sect, name) + let [sect, name] = s:extract_sect_and_name_path(path) catch call s:error(v:exception) return @@ -82,8 +83,9 @@ endfunction function! man#read_page(ref) abort try - let [sect, name] = man#extract_sect_and_name_ref(a:ref) - let [sect, name, path] = s:verify_exists(sect, name) + let [sect, name] = s:extract_sect_and_name_ref(a:ref) + let path = s:verify_exists(sect, name) + let [sect, name] = s:extract_sect_and_name_path(path) let page = s:get_page(path) catch call s:error(v:exception) @@ -194,7 +196,7 @@ endfunction " attempt to extract the name and sect out of 'name(sect)' " otherwise just return the largest string of valid characters in ref -function! man#extract_sect_and_name_ref(ref) abort +function! s:extract_sect_and_name_ref(ref) abort if a:ref[0] ==# '-' " try ':Man -pandoc' with this disabled. throw 'manpage name cannot start with ''-''' endif @@ -204,7 +206,7 @@ function! man#extract_sect_and_name_ref(ref) abort if empty(name) throw 'manpage reference cannot contain only parentheses' endif - return [get(b:, 'man_default_sects', ''), name] + return ['', name] endif let left = split(ref, '(') " see ':Man 3X curses' on why tolower. @@ -227,24 +229,62 @@ function! s:get_path(sect, name) abort return substitute(get(split(s:system(['man', s:find_arg, s:section_arg, a:sect, a:name])), 0, ''), '\n\+$', '', '') endfunction +" s:verify_exists attempts to find the path to a manpage +" based on the passed section and name. +" +" 1. If the passed section is empty, b:man_default_sects is used. +" 2. If manpage could not be found with the given sect and name, +" then another attempt is made with b:man_default_sects. +" 3. If it still could not be found, then we try again without a section. +" 4. If still not found but $MANSECT is set, then we try again with $MANSECT +" unset. +" +" This function is careful to avoid duplicating a search if a previous +" step has already done it. i.e if we use b:man_default_sects in step 1, +" then we don't do it again in step 2. function! s:verify_exists(sect, name) abort + let sect = a:sect + if empty(sect) + let sect = get(b:, 'man_default_sects', '') + endif + try - let path = s:get_path(a:sect, a:name) + return s:get_path(sect, a:name) catch /^command error (/ + endtry + + if !empty(get(b:, 'man_default_sects', '')) && sect !=# b:man_default_sects try - let path = s:get_path(get(b:, 'man_default_sects', ''), a:name) + return s:get_path(b:man_default_sects, a:name) catch /^command error (/ - let path = s:get_path('', a:name) endtry - endtry - " Extract the section from the path, because sometimes the actual section is - " more specific than what we provided to `man` (try `:Man 3 App::CLI`). - " Also on linux, name seems to be case-insensitive. So for `:Man PRIntf`, we - " still want the name of the buffer to be 'printf'. - return s:extract_sect_and_name_path(path) + [path] + endif + + if !empty(sect) + try + return s:get_path('', a:name) + catch /^command error (/ + endtry + endif + + if !empty($MANSECT) + try + let MANSECT = $MANSECT + unset $MANSECT + return s:get_path('', a:name) + catch /^command error (/ + finally + let $MANSECT = MANSECT + endtry + endif + + throw 'no manual entry for ' . a:name endfunction -" extracts the name and sect out of 'path/name.sect' +" Extracts the name/section from the 'path/name.sect', because sometimes the actual section is +" more specific than what we provided to `man` (try `:Man 3 App::CLI`). +" Also on linux, name seems to be case-insensitive. So for `:Man PRIntf`, we +" still want the name of the buffer to be 'printf'. function! s:extract_sect_and_name_path(path) abort let tail = fnamemodify(a:path, ':t') if a:path =~# '\.\%([glx]z\|bz2\|lzma\|Z\)$' " valid extensions @@ -275,7 +315,7 @@ function! s:error(msg) abort echohl None endfunction -" see man#extract_sect_and_name_ref on why tolower(sect) +" see s:extract_sect_and_name_ref on why tolower(sect) function! man#complete(arg_lead, cmd_line, cursor_pos) abort let args = split(a:cmd_line) let cmd_offset = index(args, 'Man') @@ -332,15 +372,26 @@ function! s:get_paths(sect, name, do_fallback) abort " callers must try-catch this, as some `man` implementations don't support `s:find_arg` try let mandirs = join(split(s:system(['man', s:find_arg]), ':\|\n'), ',') - return globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1) + let paths = globpath(mandirs, 'man?/'.a:name.'*.'.a:sect.'*', 0, 1) + try + " Prioritize the result from verify_exists as it obeys b:man_default_sects. + let first = s:verify_exists(a:sect, a:name) + let paths = filter(paths, 'v:val !=# first') + let paths = [first] + paths + catch + endtry + return paths catch if !a:do_fallback throw v:exception endif - " fallback to a single path, with the page we're trying to find - let [l:sect, l:name, l:path] = s:verify_exists(a:sect, a:name) - return [l:path] + " Fallback to a single path, with the page we're trying to find. + try + return [s:verify_exists(a:sect, a:name)] + catch + return [] + endtry endtry endfunction @@ -379,7 +430,7 @@ function! man#init_pager() abort " know the correct casing, cf. `man glDrawArraysInstanced`). let ref = substitute(matchstr(getline(1), '^[^)]\+)'), ' ', '_', 'g') try - let b:man_sect = man#extract_sect_and_name_ref(ref)[0] + let b:man_sect = s:extract_sect_and_name_ref(ref)[0] catch let b:man_sect = '' endtry @@ -391,7 +442,7 @@ function! man#init_pager() abort endfunction function! man#goto_tag(pattern, flags, info) abort - let [l:sect, l:name] = man#extract_sect_and_name_ref(a:pattern) + let [l:sect, l:name] = s:extract_sect_and_name_ref(a:pattern) let l:paths = s:get_paths(l:sect, l:name, v:true) let l:structured = [] @@ -401,9 +452,6 @@ function! man#goto_tag(pattern, flags, info) abort let l:structured += [{ 'name': l:n, 'path': l:path }] endfor - " sort by relevance - exact matches first, then the previous order - call sort(l:structured, { a, b -> a.name ==? l:name ? -1 : b.name ==? l:name ? 1 : 0 }) - if &cscopetag " return only a single entry so we work well with :cstag (#11675) let l:structured = l:structured[:0] diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index a346262b0c..efb6272e58 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2019,11 +2019,12 @@ argidx() Number current index in the argument list arglistid([{winnr} [, {tabnr}]]) Number argument list id argv({nr} [, {winid}]) String {nr} entry of the argument list argv([-1, {winid}]) List the argument list +asin({expr}) Float arc sine of {expr} assert_beeps({cmd}) Number assert {cmd} causes a beep assert_equal({exp}, {act} [, {msg}]) Number assert {exp} is equal to {act} -assert_equalfile({fname-one}, {fname-two}) - Number assert file contents is equal +assert_equalfile({fname-one}, {fname-two} [, {msg}]) + Number assert file contents are equal assert_exception({error} [, {msg}]) Number assert {error} is in v:exception assert_fails({cmd} [, {error}]) Number assert {cmd} fails @@ -2039,7 +2040,6 @@ assert_notmatch({pat}, {text} [, {msg}]) Number assert {pat} not matches {text} assert_report({msg}) Number report a test failure assert_true({actual} [, {msg}]) Number assert {actual} is true -asin({expr}) Float arc sine of {expr} atan({expr}) Float arc tangent of {expr} atan2({expr}, {expr}) Float arc tangent of {expr1} / {expr2} browse({save}, {title}, {initdir}, {default}) @@ -2631,7 +2631,7 @@ assert_equal({expected}, {actual}, [, {msg}]) test.vim line 12: Expected 'foo' but got 'bar' ~ *assert_equalfile()* -assert_equalfile({fname-one}, {fname-two}) +assert_equalfile({fname-one}, {fname-two} [, {msg}]) When the files {fname-one} and {fname-two} do not contain exactly the same text an error message is added to |v:errors|. Also see |assert-return|. diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 15587955de..b934d2dfa0 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -758,13 +758,14 @@ code_action({context}) *vim.lsp.buf.code_action()* TODO: Documentation completion({context}) *vim.lsp.buf.completion()* - TODO: Documentation + Retrieves the completion items at the current cursor position. + Can only be called in Insert mode. declaration() *vim.lsp.buf.declaration()* - TODO: Documentation + Jumps to the declaration of the symbol under the cursor. definition() *vim.lsp.buf.definition()* - TODO: Documentation + Jumps to the definition of the symbol under the cursor. document_highlight() *vim.lsp.buf.document_highlight()* Send request to server to resolve document highlights for the @@ -777,13 +778,18 @@ document_highlight() *vim.lsp.buf.document_highlight()* < document_symbol() *vim.lsp.buf.document_symbol()* - TODO: Documentation + Lists all symbols in the current buffer in the quickfix + window. execute_command({command}) *vim.lsp.buf.execute_command()* TODO: Documentation formatting({options}) *vim.lsp.buf.formatting()* - TODO: Documentation + Formats the current buffer. + + The optional {options} table can be used to specify + FormattingOptions, a list of which is available at https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting . Some unspecified options will be automatically derived from + the current Neovim options. *vim.lsp.buf.formatting_sync()* formatting_sync({options}, {timeout_ms}) @@ -794,10 +800,13 @@ formatting_sync({options}, {timeout_ms}) |vim.lsp.buf_request_sync()|. hover() *vim.lsp.buf.hover()* - TODO: Documentation + Displays hover information about the symbol under the cursor + in a floating window. Calling the function twice will jump + into the floating window. implementation() *vim.lsp.buf.implementation()* - TODO: Documentation + Lists all the implementations for the symbol under the cursor + in the quickfix window. npcall({fn}, {...}) *vim.lsp.buf.npcall()* TODO: Documentation @@ -810,10 +819,13 @@ range_formatting({options}, {start_pos}, {end_pos}) TODO: Documentation references({context}) *vim.lsp.buf.references()* - TODO: Documentation + Lists all the references to the symbol under the cursor in the + quickfix window. rename({new_name}) *vim.lsp.buf.rename()* - TODO: Documentation + Renames all references to the symbol under the cursor. If + {new_name} is not provided, the user will be prompted for a + new name using |input()|. request({method}, {params}, {callback}) *vim.lsp.buf.request()* TODO: Documentation @@ -823,10 +835,12 @@ server_ready() *vim.lsp.buf.server_ready()* `true` if server responds. signature_help() *vim.lsp.buf.signature_help()* - TODO: Documentation + Displays signature information about the symbol under the + cursor in a floating window. type_definition() *vim.lsp.buf.type_definition()* - TODO: Documentation + Jumps to the definition of the type of the symbol under the + cursor. workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()* Lists all symbols in the current workspace in the quickfix @@ -837,6 +851,16 @@ workspace_symbol({query}) *vim.lsp.buf.workspace_symbol()* enter a string on the command line. An empty string means no filtering is done. +incoming_calls() *vim.lsp.buf.incoming_calls()* + Lists all the call sites of the symbol under the cursor in the + |quickfix| window. If the symbol can resolve to multiple + items, the user can pick one in the |inputlist|. + +outgoing_calls() *vim.lsp.buf.outgoing_calls()* + Lists all the items that are called by the symbol under the + cursor in the |quickfix| window. If the symbol can resolve to + multiple items, the user can pick one in the |inputlist|. + ============================================================================== Lua module: vim.lsp.callbacks *lsp-callbacks* diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim index 081181cfe9..0416e41368 100644 --- a/runtime/ftplugin/man.vim +++ b/runtime/ftplugin/man.vim @@ -6,7 +6,7 @@ if exists('b:did_ftplugin') || &filetype !=# 'man' endif let b:did_ftplugin = 1 -let s:pager = get(s:, 'pager', 0) || !exists('b:man_sect') +let s:pager = !exists('b:man_sect') if s:pager call man#init_pager() @@ -26,7 +26,7 @@ if !exists('g:no_plugin_maps') && !exists('g:no_man_maps') nnoremap <silent> <buffer> j gj nnoremap <silent> <buffer> k gk nnoremap <silent> <buffer> gO :call man#show_toc()<CR> - if 1 == bufnr('%') || s:pager + if s:pager nnoremap <silent> <buffer> <nowait> q :lclose<CR>:q<CR> else nnoremap <silent> <buffer> <nowait> q :lclose<CR><C-W>c diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 7442f0c0b5..6fe1d15b7e 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -511,6 +511,7 @@ function lsp.start_client(config) or (not client.resolved_capabilities.type_definition and method == 'textDocument/typeDefinition') or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol') or (not client.resolved_capabilities.workspace_symbol and method == 'textDocument/workspaceSymbol') + or (not client.resolved_capabilities.call_hierarchy and method == 'textDocument/prepareCallHierarchy') then callback(unsupported_method(method), method, nil, client_id, bufnr) return diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 839e00c67d..2e27617997 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -30,43 +30,63 @@ function M.server_ready() return not not vim.lsp.buf_notify(0, "window/progress", {}) end +--- Displays hover information about the symbol under the cursor in a floating +--- window. Calling the function twice will jump into the floating window. function M.hover() local params = util.make_position_params() request('textDocument/hover', params) end +--- Jumps to the declaration of the symbol under the cursor. +--- function M.declaration() local params = util.make_position_params() request('textDocument/declaration', params) end +--- Jumps to the definition of the symbol under the cursor. +--- function M.definition() local params = util.make_position_params() request('textDocument/definition', params) end +--- Jumps to the definition of the type of the symbol under the cursor. +--- function M.type_definition() local params = util.make_position_params() request('textDocument/typeDefinition', params) end +--- Lists all the implementations for the symbol under the cursor in the +--- quickfix window. function M.implementation() local params = util.make_position_params() request('textDocument/implementation', params) end +--- Displays signature information about the symbol under the cursor in a +--- floating window. function M.signature_help() local params = util.make_position_params() request('textDocument/signatureHelp', params) end --- TODO(ashkan) ? +--- Retrieves the completion items at the current cursor position. Can only be +--- called in Insert mode. function M.completion(context) local params = util.make_position_params() params.context = context return request('textDocument/completion', params) end +--- Formats the current buffer. +--- +--- The optional {options} table can be used to specify FormattingOptions, a +--- list of which is available at +--- https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting. +--- Some unspecified options will be automatically derived from the current +--- Neovim options. function M.formatting(options) local params = util.make_formatting_params(options) return request('textDocument/formatting', params) @@ -118,6 +138,8 @@ function M.range_formatting(options, start_pos, end_pos) return request('textDocument/rangeFormatting', params) end +--- Renames all references to the symbol under the cursor. If {new_name} is not +--- provided, the user will be prompted for a new name using |input()|. function M.rename(new_name) -- TODO(ashkan) use prepareRename -- * result: [`Range`](#range) \| `{ range: Range, placeholder: string }` \| `null` describing the range of the string to rename and optionally a placeholder text of the string content to be renamed. If `null` is returned then it is deemed that a 'textDocument/rename' request is not valid at the given position. @@ -128,6 +150,8 @@ function M.rename(new_name) request('textDocument/rename', params) end +--- Lists all the references to the symbol under the cursor in the quickfix window. +--- function M.references(context) validate { context = { context, 't', true } } local params = util.make_position_params() @@ -138,11 +162,45 @@ function M.references(context) request('textDocument/references', params) end +--- Lists all symbols in the current buffer in the quickfix window. +--- function M.document_symbol() local params = { textDocument = util.make_text_document_params() } request('textDocument/documentSymbol', params) end +local function pick_call_hierarchy_item(call_hierarchy_items) + if not call_hierarchy_items then return end + if #call_hierarchy_items == 1 then + return call_hierarchy_items[1] + end + local items = {} + for i, item in ipairs(call_hierarchy_items) do + local entry = item.detail or item.name + table.insert(items, string.format("%d. %s", i, entry)) + end + local choice = vim.fn.inputlist(items) + if choice < 1 or choice > #items then + return + end + return choice +end + +function M.incoming_calls() + local params = util.make_position_params() + request('textDocument/prepareCallHierarchy', params, function(_, _, result) + local call_hierarchy_item = pick_call_hierarchy_item(result) + vim.lsp.buf_request(0, 'callHierarchy/incomingCalls', { item = call_hierarchy_item }) + end) +end + +function M.outgoing_calls() + local params = util.make_position_params() + request('textDocument/prepareCallHierarchy', params, function(_, _, result) + local call_hierarchy_item = pick_call_hierarchy_item(result) + vim.lsp.buf_request(0, 'callHierarchy/outgoingCalls', { item = call_hierarchy_item }) + end) +end --- Lists all symbols in the current workspace in the quickfix window. --- diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua index 4b14f0132d..1ed58995d0 100644 --- a/runtime/lua/vim/lsp/callbacks.lua +++ b/runtime/lua/vim/lsp/callbacks.lua @@ -214,6 +214,33 @@ M['textDocument/documentHighlight'] = function(_, _, result, _) util.buf_highlight_references(bufnr, result) end +-- direction is "from" for incoming calls and "to" for outgoing calls +local make_call_hierarchy_callback = function(direction) + -- result is a CallHierarchy{Incoming,Outgoing}Call[] + return function(_, _, result) + if not result then return end + local items = {} + for _, call_hierarchy_call in pairs(result) do + local call_hierarchy_item = call_hierarchy_call[direction] + for _, range in pairs(call_hierarchy_call.fromRanges) do + table.insert(items, { + filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)), + text = call_hierarchy_item.name, + lnum = range.start.line + 1, + col = range.start.character + 1, + }) + end + end + util.set_qflist(items) + api.nvim_command("copen") + api.nvim_command("wincmd p") + end +end + +M['callHierarchy/incomingCalls'] = make_call_hierarchy_callback('from') + +M['callHierarchy/outgoingCalls'] = make_call_hierarchy_callback('to') + M['window/logMessage'] = function(_, _, result, client_id) local message_type = result.type local message = result.message diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 4fded1961d..ef5e08680e 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -713,6 +713,9 @@ function protocol.make_client_capabilities() }; applyEdit = true; }; + callHierarchy = { + dynamicRegistration = false; + }; experimental = nil; } end @@ -912,6 +915,7 @@ function protocol.resolve_capabilities(server_capabilities) general_properties.workspace_symbol = server_capabilities.workspaceSymbolProvider or false general_properties.document_formatting = server_capabilities.documentFormattingProvider or false general_properties.document_range_formatting = server_capabilities.documentRangeFormattingProvider or false + general_properties.call_hierarchy = server_capabilities.callHierarchyProvider or false if server_capabilities.codeActionProvider == nil then general_properties.code_action = false diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 982fab173f..c7258dde12 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -570,10 +570,16 @@ add_library( ) set_property(TARGET libnvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) +if(MSVC) + set(LIBNVIM_NAME libnvim) +else() + set(LIBNVIM_NAME nvim) +endif() set_target_properties( libnvim PROPERTIES POSITION_INDEPENDENT_CODE ON + OUTPUT_NAME ${LIBNVIM_NAME} ) set_property( TARGET libnvim diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e1f9fe0253..0cad5fd6c1 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5552,19 +5552,18 @@ void prepare_assert_error(garray_T *gap) } } -// Append "str" to "gap", escaping unprintable characters. +// Append "p[clen]" to "gap", escaping unprintable characters. // Changes NL to \n, CR to \r, etc. -static void ga_concat_esc(garray_T *gap, char_u *str) +static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) + FUNC_ATTR_NONNULL_ALL { - char_u *p; char_u buf[NUMBUFLEN]; - if (str == NULL) { - ga_concat(gap, (char_u *)"NULL"); - return; - } - - for (p = str; *p != NUL; p++) { + if (clen > 1) { + memmove(buf, p, clen); + buf[clen] = NUL; + ga_concat(gap, buf); + } else { switch (*p) { case BS: ga_concat(gap, (char_u *)"\\b"); break; case ESC: ga_concat(gap, (char_u *)"\\e"); break; @@ -5585,6 +5584,41 @@ static void ga_concat_esc(garray_T *gap, char_u *str) } } +// Append "str" to "gap", escaping unprintable characters. +// Changes NL to \n, CR to \r, etc. +static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) + FUNC_ATTR_NONNULL_ARG(1) +{ + char_u buf[NUMBUFLEN]; + + if (str == NULL) { + ga_concat(gap, (char_u *)"NULL"); + return; + } + + for (const char_u *p = str; *p != NUL; p++) { + int same_len = 1; + const char_u *s = p; + const int c = mb_ptr2char_adv(&s); + const int clen = s - p; + while (*s != NUL && c == utf_ptr2char(s)) { + same_len++; + s += clen; + } + if (same_len > 20) { + ga_concat(gap, (char_u *)"\\["); + ga_concat_esc(gap, p, clen); + ga_concat(gap, (char_u *)" occurs "); + vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len); + ga_concat(gap, buf); + ga_concat(gap, (char_u *)" times]"); + p = s - 1; + } else { + ga_concat_esc(gap, p, clen); + } + } +} + // Fill "gap" with information about an assert error. void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, typval_T *exp_tv, @@ -5609,10 +5643,10 @@ void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, if (exp_str == NULL) { tofree = (char_u *)encode_tv2string(exp_tv, NULL); - ga_concat_esc(gap, tofree); + ga_concat_shorten_esc(gap, tofree); xfree(tofree); } else { - ga_concat_esc(gap, exp_str); + ga_concat_shorten_esc(gap, exp_str); } if (atype != ASSERT_NOTEQUAL) { @@ -5624,7 +5658,7 @@ void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, ga_concat(gap, (char_u *)" but got "); } tofree = (char_u *)encode_tv2string(got_tv, NULL); - ga_concat_esc(gap, tofree); + ga_concat_shorten_esc(gap, tofree); xfree(tofree); } } @@ -5674,6 +5708,9 @@ int assert_equalfile(typval_T *argvars) IObuff[0] = NUL; FILE *const fd1 = os_fopen(fname1, READBIN); + char line1[200]; + char line2[200]; + ptrdiff_t lineidx = 0; if (fd1 == NULL) { snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1); } else { @@ -5682,6 +5719,7 @@ int assert_equalfile(typval_T *argvars) fclose(fd1); snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2); } else { + int64_t linecount = 1; for (int64_t count = 0; ; count++) { const int c1 = fgetc(fd1); const int c2 = fgetc(fd2); @@ -5693,10 +5731,24 @@ int assert_equalfile(typval_T *argvars) } else if (c2 == EOF) { STRCPY(IObuff, "second file is shorter"); break; - } else if (c1 != c2) { - snprintf((char *)IObuff, IOSIZE, - "difference at byte %" PRId64, count); - break; + } else { + line1[lineidx] = c1; + line2[lineidx] = c2; + lineidx++; + if (c1 != c2) { + snprintf((char *)IObuff, IOSIZE, + "difference at byte %" PRId64 ", line %" PRId64, + count, linecount); + break; + } + } + if (c1 == NL) { + linecount++; + lineidx = 0; + } else if (lineidx + 2 == (ptrdiff_t)sizeof(line1)) { + memmove(line1, line1 + 100, lineidx - 100); + memmove(line2, line2 + 100, lineidx - 100); + lineidx -= 100; } } fclose(fd1); @@ -5705,7 +5757,24 @@ int assert_equalfile(typval_T *argvars) } if (IObuff[0] != NUL) { prepare_assert_error(&ga); + if (argvars[2].v_type != VAR_UNKNOWN) { + char *const tofree = encode_tv2echo(&argvars[2], NULL); + ga_concat(&ga, (char_u *)tofree); + xfree(tofree); + ga_concat(&ga, (char_u *)": "); + } ga_concat(&ga, IObuff); + if (lineidx > 0) { + line1[lineidx] = NUL; + line2[lineidx] = NUL; + ga_concat(&ga, (char_u *)" after \""); + ga_concat(&ga, (char_u *)line1); + if (STRCMP(line1, line2) != 0) { + ga_concat(&ga, (char_u *)"\" vs \""); + ga_concat(&ga, (char_u *)line2); + } + ga_concat(&ga, (char_u *)"\""); + } assert_error(&ga); ga_clear(&ga); return 1; @@ -8470,27 +8539,6 @@ void set_selfdict(typval_T *const rettv, dict_T *const selfdict) make_partial(selfdict, rettv); } -// Turn a typeval into a string. Similar to tv_get_string_buf() but uses -// string() on Dict, List, etc. -static const char *tv_stringify(typval_T *varp, char *buf) - FUNC_ATTR_NONNULL_ALL -{ - if (varp->v_type == VAR_LIST - || varp->v_type == VAR_DICT - || varp->v_type == VAR_FUNC - || varp->v_type == VAR_PARTIAL - || varp->v_type == VAR_FLOAT) { - typval_T tmp; - - f_string(varp, &tmp, NULL); - const char *const res = tv_get_string_buf(&tmp, buf); - tv_clear(varp); - *varp = tmp; - return res; - } - return tv_get_string_buf(varp, buf); -} - // Find variable "name" in the list of variables. // Return a pointer to it if found, NULL if not found. // Careful: "a:0" variables don't have a name. @@ -9337,16 +9385,20 @@ void ex_execute(exarg_T *eap) } if (!eap->skip) { - char buf[NUMBUFLEN]; const char *const argstr = eap->cmdidx == CMD_execute - ? tv_get_string_buf(&rettv, buf) - : tv_stringify(&rettv, buf); + ? tv_get_string(&rettv) + : rettv.v_type == VAR_STRING + ? encode_tv2echo(&rettv, NULL) + : encode_tv2string(&rettv, NULL); const size_t len = strlen(argstr); ga_grow(&ga, len + 2); if (!GA_EMPTY(&ga)) { ((char_u *)(ga.ga_data))[ga.ga_len++] = ' '; } memcpy((char_u *)(ga.ga_data) + ga.ga_len, argstr, len + 1); + if (eap->cmdidx != CMD_execute) { + xfree((void *)argstr); + } ga.ga_len += len; } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 410cce05b0..023c60f118 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -28,7 +28,7 @@ return { asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc assert_beeps={args={1, 2}}, assert_equal={args={2, 3}}, - assert_equalfile={args=2}, + assert_equalfile={args={2, 3}}, assert_exception={args={1, 2}}, assert_fails={args={1, 3}}, assert_false={args={1, 2}}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 95686b97bf..e350d09935 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -415,7 +415,7 @@ static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); } -// "assert_equalfile(fname-one, fname-two)" function +// "assert_equalfile(fname-one, fname-two[, msg])" function static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = assert_equalfile(argvars); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index dcfd456ce3..ef8e66a992 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -850,10 +850,14 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, if (l1 == l2) { return true; } - if (l1 == NULL || l2 == NULL) { + if (tv_list_len(l1) != tv_list_len(l2)) { return false; } - if (tv_list_len(l1) != tv_list_len(l2)) { + if (tv_list_len(l1) == 0) { + // empty and NULL list are considered equal + return true; + } + if (l1 == NULL || l2 == NULL) { return false; } diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 9f30609d66..0d5622f1e7 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -987,7 +987,7 @@ int typval_exec_lua_callable( PUSH_ALL_TYPVALS(lstate, argvars, argcount, false); if (lua_pcall(lstate, argcount + offset, 1, 0)) { - luaL_error(lstate, "nlua_CFunction_func_call failed."); + nlua_print(lstate); return ERROR_OTHER; } @@ -1405,7 +1405,9 @@ char_u *nlua_register_table_as_callable(typval_T *const arg) lua_State *const lstate = nlua_enter(); +#ifndef NDEBUG int top = lua_gettop(lstate); +#endif nlua_pushref(lstate, table_ref); if (!lua_getmetatable(lstate, -1)) { diff --git a/src/nvim/main.c b/src/nvim/main.c index ae64046d07..f79fb57eae 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -313,6 +313,26 @@ int main(int argc, char **argv) input_start(STDIN_FILENO); } + // Wait for UIs to set up Nvim or show early messages + // and prompts (--cmd, swapfile dialog, …). + bool use_remote_ui = (embedded_mode && !headless_mode); + bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode); + if (use_remote_ui || use_builtin_ui) { + TIME_MSG("waiting for UI"); + if (use_remote_ui) { + remote_ui_wait_for_attach(); + } else { + ui_builtin_start(); + } + TIME_MSG("done waiting for UI"); + + // prepare screen now, so external UIs can display messages + starting = NO_BUFFERS; + screenclear(); + TIME_MSG("initialized screen early for UI"); + } + + // open terminals when opening files that start with term:// #define PROTO "term://" do_cmdline_cmd("augroup nvim_terminal"); @@ -335,25 +355,6 @@ int main(int argc, char **argv) p_lpl = false; } - // Wait for UIs to set up Nvim or show early messages - // and prompts (--cmd, swapfile dialog, …). - bool use_remote_ui = (embedded_mode && !headless_mode); - bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode); - if (use_remote_ui || use_builtin_ui) { - TIME_MSG("waiting for UI"); - if (use_remote_ui) { - remote_ui_wait_for_attach(); - } else { - ui_builtin_start(); - } - TIME_MSG("done waiting for UI"); - - // prepare screen now, so external UIs can display messages - starting = NO_BUFFERS; - screenclear(); - TIME_MSG("initialized screen early for UI"); - } - // Execute --cmd arguments. exe_pre_commands(¶ms); diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index e10770b6bd..6dafbafb3e 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -1029,6 +1029,15 @@ void fast_breakcheck(void) } } +// Like line_breakcheck() but check 100 times less often. +void veryfast_breakcheck(void) +{ + if (++breakcheck_count >= BREAKCHECK_SKIP * 100) { + breakcheck_count = 0; + os_breakcheck(); + } +} + /// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error. /// Invalidates cached tags. /// diff --git a/src/nvim/ops.c b/src/nvim/ops.c index e905029dae..595a699563 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -3080,10 +3080,12 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (ve_flags == VE_ALL && (curwin->w_cursor.coladd > 0 || endcol2 == curwin->w_cursor.col)) { - if (dir == FORWARD && c == NUL) - ++col; - if (dir != FORWARD && c != NUL) - ++curwin->w_cursor.col; + if (dir == FORWARD && c == NUL) { + col++; + } + if (dir != FORWARD && c != NUL && curwin->w_cursor.coladd > 0) { + curwin->w_cursor.col++; + } if (c == TAB) { if (dir == BACKWARD && curwin->w_cursor.col) curwin->w_cursor.col--; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 19a14f340b..95257fe945 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2676,6 +2676,36 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, if (name == NULL) { break; } + switch (vartv.v_type) { + case VAR_FUNC: + case VAR_PARTIAL: + tv_clear(&vartv); + continue; + case VAR_DICT: + { + dict_T *di = vartv.vval.v_dict; + int copyID = get_copyID(); + if (!set_ref_in_ht(&di->dv_hashtab, copyID, NULL) + && copyID == di->dv_copyID) { + tv_clear(&vartv); + continue; + } + break; + } + case VAR_LIST: + { + list_T *l = vartv.vval.v_list; + int copyID = get_copyID(); + if (!set_ref_in_list(l, copyID, NULL) + && copyID == l->lv_copyID) { + tv_clear(&vartv); + continue; + } + break; + } + default: + break; + } typval_T tgttv; tv_copy(&vartv, &tgttv); ShaDaWriteResult spe_ret; diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 41669789db..6b9348e55d 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -1134,7 +1134,6 @@ static int read_sal_section(FILE *fd, slang_T *slang) salitem_T *smp; int ccnt; char_u *p; - int c = NUL; slang->sl_sofo = false; @@ -1158,7 +1157,9 @@ static int read_sal_section(FILE *fd, slang_T *slang) ga_grow(gap, cnt + 1); // <sal> : <salfromlen> <salfrom> <saltolen> <salto> - for (; gap->ga_len < cnt; ++gap->ga_len) { + for (; gap->ga_len < cnt; gap->ga_len++) { + int c = NUL; + smp = &((salitem_T *)gap->ga_data)[gap->ga_len]; ccnt = getc(fd); // <salfromlen> if (ccnt < 0) @@ -1810,7 +1811,8 @@ spell_reload_one ( #define CONDIT_SUF 4 // add a suffix for matching flags #define CONDIT_AFF 8 // word already has an affix -// Tunable parameters for when the tree is compressed. See 'mkspellmem'. +// Tunable parameters for when the tree is compressed. Filled from the +// 'mkspellmem' option. static long compress_start = 30000; // memory / SBLOCKSIZE static long compress_inc = 100; // memory / SBLOCKSIZE static long compress_added = 500000; // word count @@ -3015,6 +3017,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) char_u message[MAXLINELEN + MAXWLEN]; int flags; int duplicate = 0; + Timestamp last_msg_time = 0; // Open the file. fd = os_fopen((char *)fname, "r"); @@ -3090,18 +3093,22 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) continue; } - // This takes time, print a message every 10000 words. + // This takes time, print a message every 10000 words, but not more + // often than once per second. if (spin->si_verbose && spin->si_msg_count > 10000) { spin->si_msg_count = 0; - vim_snprintf((char *)message, sizeof(message), - _("line %6d, word %6ld - %s"), - lnum, spin->si_foldwcount + spin->si_keepwcount, w); - msg_start(); - msg_puts_long_attr(message, 0); - msg_clr_eos(); - msg_didout = FALSE; - msg_col = 0; - ui_flush(); + if (os_time() > last_msg_time) { + last_msg_time = os_time(); + vim_snprintf((char *)message, sizeof(message), + _("line %6d, word %6ld - %s"), + lnum, spin->si_foldwcount + spin->si_keepwcount, w); + msg_start(); + msg_puts_long_attr(message, 0); + msg_clr_eos(); + msg_didout = false; + msg_col = 0; + ui_flush(); + } } // Store the word in the hashtable to be able to find duplicates. @@ -3914,9 +3921,10 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int ++spin->si_msg_count; if (spin->si_compress_cnt > 1) { - if (--spin->si_compress_cnt == 1) + if (--spin->si_compress_cnt == 1) { // Did enough words to lower the block count limit. spin->si_blocks_cnt += compress_inc; + } } // When we have allocated lots of memory we need to compress the word tree @@ -3955,9 +3963,10 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int // compression useful, or one of them is small, which means // compression goes fast. But when filling the soundfold word tree // there is no keep-case tree. - wordtree_compress(spin, spin->si_foldroot); - if (affixID >= 0) - wordtree_compress(spin, spin->si_keeproot); + wordtree_compress(spin, spin->si_foldroot, "case-folded"); + if (affixID >= 0) { + wordtree_compress(spin, spin->si_keeproot, "keep-case"); + } } return OK; @@ -3990,6 +3999,7 @@ static wordnode_T *get_wordnode(spellinfo_T *spin) // siblings. // Returns the number of nodes actually freed. static int deref_wordnode(spellinfo_T *spin, wordnode_T *node) + FUNC_ATTR_NONNULL_ALL { wordnode_T *np; int cnt = 0; @@ -4009,6 +4019,7 @@ static int deref_wordnode(spellinfo_T *spin, wordnode_T *node) // Free a wordnode_T for re-use later. // Only the "wn_child" field becomes invalid. static void free_wordnode(spellinfo_T *spin, wordnode_T *n) + FUNC_ATTR_NONNULL_ALL { n->wn_child = spin->si_first_free; spin->si_first_free = n; @@ -4016,18 +4027,19 @@ static void free_wordnode(spellinfo_T *spin, wordnode_T *n) } // Compress a tree: find tails that are identical and can be shared. -static void wordtree_compress(spellinfo_T *spin, wordnode_T *root) +static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, + const char *name) + FUNC_ATTR_NONNULL_ALL { hashtab_T ht; - int n; - int tot = 0; - int perc; + long tot = 0; + long perc; // Skip the root itself, it's not actually used. The first sibling is the // start of the tree. if (root->wn_sibling != NULL) { hash_init(&ht); - n = node_compress(spin, root->wn_sibling, &ht, &tot); + const long n = node_compress(spin, root->wn_sibling, &ht, &tot); #ifndef SPELL_PRINTTREE if (spin->si_verbose || p_verbose > 2) @@ -4040,8 +4052,8 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root) else perc = (tot - n) * 100 / tot; vim_snprintf((char *)IObuff, IOSIZE, - _("Compressed %d of %d nodes; %d (%d%%) remaining"), - n, tot, tot - n, perc); + _("Compressed %s of %ld nodes; %ld (%ld%%) remaining"), + name, tot, tot - n, perc); spell_message(spin, IObuff); } #ifdef SPELL_PRINTTREE @@ -4053,23 +4065,23 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root) // Compress a node, its siblings and its children, depth first. // Returns the number of compressed nodes. -static int -node_compress ( +static long node_compress( spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, - int *tot // total count of nodes before compressing, + long *tot // total count of nodes before compressing, // incremented while going through the tree ) + FUNC_ATTR_NONNULL_ALL { wordnode_T *np; wordnode_T *tp; wordnode_T *child; hash_T hash; hashitem_T *hi; - int len = 0; + long len = 0; unsigned nr, n; - int compressed = 0; + long compressed = 0; // Go through the list of siblings. Compress each child and then try // finding an identical child to replace it. @@ -4142,7 +4154,7 @@ node_compress ( node->wn_u1.hashkey[5] = NUL; // Check for CTRL-C pressed now and then. - fast_breakcheck(); + veryfast_breakcheck(); return compressed; } @@ -4749,7 +4761,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) // Compress the soundfold trie. spell_message(spin, (char_u *)_(msg_compressing)); - wordtree_compress(spin, spin->si_foldroot); + wordtree_compress(spin, spin->si_foldroot, "case-folded"); // Write the .sug file. // Make the file name by changing ".spl" to ".sug". @@ -5219,9 +5231,9 @@ mkspell ( if (!error && !got_int) { // Combine tails in the tree. spell_message(&spin, (char_u *)_(msg_compressing)); - wordtree_compress(&spin, spin.si_foldroot); - wordtree_compress(&spin, spin.si_keeproot); - wordtree_compress(&spin, spin.si_prefroot); + wordtree_compress(&spin, spin.si_foldroot, "case-folded"); + wordtree_compress(&spin, spin.si_keeproot, "keep-case"); + wordtree_compress(&spin, spin.si_prefroot, "prefixes"); } if (!error && !got_int) { @@ -5273,7 +5285,8 @@ theend: // Display a message for spell file processing when 'verbose' is set or using // ":mkspell". "str" can be IObuff. -static void spell_message(spellinfo_T *spin, char_u *str) +static void spell_message(const spellinfo_T *spin, char_u *str) + FUNC_ATTR_NONNULL_ALL { if (spin->si_verbose || p_verbose > 2) { if (!spin->si_verbose) diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index b041fdedb1..41ff9b2bd6 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -275,7 +275,7 @@ func GetVimCommand(...) " If using valgrind, make sure every run uses a different log file. if cmd =~ 'valgrind.*--log-file=' - let cmd = substitute(cmd, '--log-file=\(^\s*\)', '--log-file=\1.' . g:valgrind_cnt, '') + let cmd = substitute(cmd, '--log-file=\(\S*\)', '--log-file=\1.' . g:valgrind_cnt, '') let g:valgrind_cnt += 1 endif diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index 4cc90eca7a..b4f7478807 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -28,7 +28,18 @@ func Test_assert_equalfile() call writefile(['1234X89'], 'Xone') call writefile(['1234Y89'], 'Xtwo') call assert_equal(1, assert_equalfile('Xone', 'Xtwo')) - call assert_match("difference at byte 4", v:errors[0]) + call assert_match('difference at byte 4, line 1 after "1234X" vs "1234Y"', v:errors[0]) + call remove(v:errors, 0) + + call writefile([repeat('x', 234) .. 'X'], 'Xone') + call writefile([repeat('x', 234) .. 'Y'], 'Xtwo') + call assert_equal(1, assert_equalfile('Xone', 'Xtwo')) + let xes = repeat('x', 134) + call assert_match('difference at byte 234, line 1 after "' .. xes .. 'X" vs "' .. xes .. 'Y"', v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_equalfile('Xone', 'Xtwo', 'a message')) + call assert_match("a message: difference at byte 234, line 1 after", v:errors[0]) call remove(v:errors, 0) call delete('Xone') diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 42e18ed027..49bbe84869 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -800,3 +800,25 @@ func Test_diff_closeoff() diffoff! enew! endfunc + +func Test_diff_and_scroll() + " this was causing an ml_get error + set ls=2 + for i in range(winheight(0) * 2) + call setline(i, i < winheight(0) - 10 ? i : i + 10) + endfor + vnew + for i in range(winheight(0)*2 + 10) + call setline(i, i < winheight(0) - 10 ? 0 : i) + endfor + diffthis + wincmd p + diffthis + execute 'normal ' . winheight(0) . "\<C-d>" + + bwipe! + bwipe! + set ls& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index d4f58af10a..d20f8d1eef 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -167,4 +167,22 @@ func Test_set_register() enew! endfunc +func Test_ve_blockpaste() + new + set ve=all + 0put =['QWERTZ','ASDFGH'] + call cursor(1,1) + exe ":norm! \<C-V>3ljdP" + call assert_equal(1, col('.')) + call assert_equal(getline(1, 2), ['QWERTZ', 'ASDFGH']) + call cursor(1,1) + exe ":norm! \<C-V>3ljd" + call cursor(1,1) + norm! $3lP + call assert_equal(5, col('.')) + call assert_equal(getline(1, 2), ['TZ QWER', 'GH ASDF']) + set ve&vim + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index b4d91a01fc..bfd9435c49 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -274,7 +274,7 @@ static void terminfo_start(UI *ui) : (konsole ? 1 : 0); patch_terminfo_bugs(data, term, colorterm, vtev, konsolev, iterm_env, nsterm); - augment_terminfo(data, term, colorterm, vtev, konsolev, iterm_env, nsterm); + augment_terminfo(data, term, vtev, konsolev, iterm_env, nsterm); data->can_change_scroll_region = !!unibi_get_str(data->ut, unibi_change_scroll_region); data->can_set_lr_margin = @@ -1907,7 +1907,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, /// This adds stuff that is not in standard terminfo as extended unibilium /// capabilities. static void augment_terminfo(TUIData *data, const char *term, - const char *colorterm, long vte_version, + long vte_version, long konsolev, bool iterm_env, bool nsterm) { unibi_term *ut = data->ut; diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 394eb73187..9b0668f9e6 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -358,6 +358,31 @@ describe('sysinit', function() eq('loaded 1 xdg 0 vim 1', eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))')) end) + + it('fixed hang issue with -D (#12647)', function() + local screen + screen = Screen.new(60, 6) + screen:attach() + command([[let g:id = termopen('"]]..nvim_prog.. + [[" -u NONE -i NONE --cmd "set noruler" -D')]]) + screen:expect([[ + ^ | + Entering Debug mode. Type "cont" to continue. | + cmd: augroup nvim_terminal | + > | + <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All| + | + ]]) + command([[call chansend(g:id, "cont\n")]]) + screen:expect([[ + ^ | + ~ | + [No Name] | + | + <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All| + | + ]]) + end) end) describe('clean', function() diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index afe999e1fa..db0a706319 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -47,10 +47,8 @@ describe('NULL', function() -- Subjectable behaviour - -- FIXME Should return 1 - null_expr_test('is equal to empty list', 'L == []', 0, 0) - -- FIXME Should return 1 - null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 0) + null_expr_test('is equal to empty list', 'L == []', 0, 1) + null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 1) -- Correct behaviour null_expr_test('can be indexed with error message for empty list', 'L[0]', diff --git a/test/functional/ex_cmds/echo_spec.lua b/test/functional/ex_cmds/echo_spec.lua index 408ce52b8c..404dc39ad2 100644 --- a/test/functional/ex_cmds/echo_spec.lua +++ b/test/functional/ex_cmds/echo_spec.lua @@ -71,18 +71,18 @@ describe(':echo :echon :echomsg :echoerr', function() eq('v:true', funcs.String(true)) eq('v:false', funcs.String(false)) eq('v:null', funcs.String(NIL)) - eq('true', eval('StringMsg(v:true)')) - eq('false', eval('StringMsg(v:false)')) - eq('null', eval('StringMsg(v:null)')) - eq('true', funcs.StringMsg(true)) - eq('false', funcs.StringMsg(false)) - eq('null', funcs.StringMsg(NIL)) - eq('true', eval('StringErr(v:true)')) - eq('false', eval('StringErr(v:false)')) - eq('null', eval('StringErr(v:null)')) - eq('true', funcs.StringErr(true)) - eq('false', funcs.StringErr(false)) - eq('null', funcs.StringErr(NIL)) + eq('v:true', eval('StringMsg(v:true)')) + eq('v:false', eval('StringMsg(v:false)')) + eq('v:null', eval('StringMsg(v:null)')) + eq('v:true', funcs.StringMsg(true)) + eq('v:false', funcs.StringMsg(false)) + eq('v:null', funcs.StringMsg(NIL)) + eq('v:true', eval('StringErr(v:true)')) + eq('v:false', eval('StringErr(v:false)')) + eq('v:null', eval('StringErr(v:null)')) + eq('v:true', funcs.StringErr(true)) + eq('v:false', funcs.StringErr(false)) + eq('v:null', funcs.StringErr(NIL)) end) it('dumps values with at most six digits after the decimal point', diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index 3cb5d97869..d48b8882af 100644 --- a/test/functional/legacy/assert_spec.lua +++ b/test/functional/legacy/assert_spec.lua @@ -38,6 +38,9 @@ describe('assert function:', function() call assert_equal(4, n) let l = [1, 2, 3] call assert_equal([1, 2, 3], l) + call assert_equal(v:_null_list, v:_null_list) + call assert_equal(v:_null_list, []) + call assert_equal([], v:_null_list) fu Func() endfu let F1 = function('Func') @@ -92,6 +95,11 @@ describe('assert function:', function() call('assert_equal', 'foo', 'bar', 'testing') expected_errors({"testing: Expected 'foo' but got 'bar'"}) end) + + it('should shorten a long message', function() + call ('assert_equal', 'XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX') + expected_errors({"Expected 'X\\[x occurs 21 times]X' but got 'X\\[y occurs 25 times]X'"}) + end) end) -- assert_notequal({expected}, {actual}[, {msg}]) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 964ea4561e..75966393b1 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -255,6 +255,18 @@ describe('luaeval()', function() ]]) end) + it('can handle functions with errors', function() + eq(true, exec_lua [[ + vim.fn.timer_start(10, function() + error("dead function") + end) + + vim.wait(1000, function() return false end) + + return true + ]]) + end) + it('should handle passing functions around', function() command [[ function VimCanCallLuaCallbacks(Concat, Cb) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 1b022f50df..aaa28390ea 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -1497,4 +1497,147 @@ describe('LSP', function() it('with softtabstop = 0', function() test_tabstop(2, 0) end) it('with softtabstop = -1', function() test_tabstop(3, -1) end) end) + + describe('vim.lsp.buf.outgoing_calls', function() + it('does nothing for an empty response', function() + local qflist_count = exec_lua([=[ + require'vim.lsp.callbacks'['callHierarchy/outgoingCalls']() + return #vim.fn.getqflist() + ]=]) + eq(0, qflist_count) + end) + + it('opens the quickfix list with the right caller', function() + local qflist = exec_lua([=[ + local rust_analyzer_response = { { + fromRanges = { { + ['end'] = { + character = 7, + line = 3 + }, + start = { + character = 4, + line = 3 + } + } }, + to = { + detail = "fn foo()", + kind = 12, + name = "foo", + range = { + ['end'] = { + character = 11, + line = 0 + }, + start = { + character = 0, + line = 0 + } + }, + selectionRange = { + ['end'] = { + character = 6, + line = 0 + }, + start = { + character = 3, + line = 0 + } + }, + uri = "file:///src/main.rs" + } + } } + local callback = require'vim.lsp.callbacks'['callHierarchy/outgoingCalls'] + callback(nil, nil, rust_analyzer_response) + return vim.fn.getqflist() + ]=]) + + local expected = { { + bufnr = 2, + col = 5, + lnum = 4, + module = "", + nr = 0, + pattern = "", + text = "foo", + type = "", + valid = 1, + vcol = 0 + } } + + eq(expected, qflist) + end) + end) + + describe('vim.lsp.buf.incoming_calls', function() + it('does nothing for an empty response', function() + local qflist_count = exec_lua([=[ + require'vim.lsp.callbacks'['callHierarchy/incomingCalls']() + return #vim.fn.getqflist() + ]=]) + eq(0, qflist_count) + end) + + it('opens the quickfix list with the right callee', function() + local qflist = exec_lua([=[ + local rust_analyzer_response = { { + from = { + detail = "fn main()", + kind = 12, + name = "main", + range = { + ['end'] = { + character = 1, + line = 4 + }, + start = { + character = 0, + line = 2 + } + }, + selectionRange = { + ['end'] = { + character = 7, + line = 2 + }, + start = { + character = 3, + line = 2 + } + }, + uri = "file:///src/main.rs" + }, + fromRanges = { { + ['end'] = { + character = 7, + line = 3 + }, + start = { + character = 4, + line = 3 + } + } } + } } + + local callback = require'vim.lsp.callbacks'['callHierarchy/incomingCalls'] + callback(nil, nil, rust_analyzer_response) + return vim.fn.getqflist() + ]=]) + + local expected = { { + bufnr = 2, + col = 5, + lnum = 4, + module = "", + nr = 0, + pattern = "", + text = "main", + type = "", + valid = 1, + vcol = 0 + } } + + eq(expected, qflist) + end) + end) end) diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua index 66c8c4ad2f..77a41caec7 100644 --- a/test/functional/shada/errors_spec.lua +++ b/test/functional/shada/errors_spec.lua @@ -1,7 +1,7 @@ -- ShaDa errors handling support local helpers = require('test.functional.helpers')(after_each) -local nvim_command, eq, exc_exec, redir_exec = - helpers.command, helpers.eq, helpers.exc_exec, helpers.redir_exec +local nvim_command, eq, exc_exec = + helpers.command, helpers.eq, helpers.exc_exec local shada_helpers = require('test.functional.shada.helpers') local reset, clear, get_shada_rw = @@ -494,23 +494,6 @@ $ eq(0, exc_exec('wshada! ' .. shada_fname)) end) - it('errors when a funcref is stored in a variable', function() - nvim_command('let F = function("tr")') - nvim_command('set shada+=!') - eq('\nE5004: Error while dumping variable g:F, itself: attempt to dump function reference' - .. '\nE574: Failed to write variable F', - redir_exec('wshada')) - end) - - it('errors when a self-referencing list is stored in a variable', function() - nvim_command('let L = []') - nvim_command('call add(L, L)') - nvim_command('set shada+=!') - eq('\nE5005: Unable to dump variable g:L: container references itself in index 0' - .. '\nE574: Failed to write variable L', - redir_exec('wshada')) - end) - it('errors with too large items', function() wshada({ 1, 206, 70, 90, 31, 179, 86, 133, 169, 103, 101, 110, 101, 114, 97, diff --git a/test/functional/shada/variables_spec.lua b/test/functional/shada/variables_spec.lua index 74bbceddcc..cc0e7fa537 100644 --- a/test/functional/shada/variables_spec.lua +++ b/test/functional/shada/variables_spec.lua @@ -1,7 +1,7 @@ -- ShaDa variables saving/reading support local helpers = require('test.functional.helpers')(after_each) -local meths, funcs, nvim_command, eq, exc_exec = - helpers.meths, helpers.funcs, helpers.command, helpers.eq, helpers.exc_exec +local meths, funcs, nvim_command, eq = + helpers.meths, helpers.funcs, helpers.command, helpers.eq local shada_helpers = require('test.functional.shada.helpers') local reset, clear = shada_helpers.reset, shada_helpers.clear @@ -121,28 +121,39 @@ describe('ShaDa support code', function() meths.get_var('NESTEDVAR')) end) - it('errors and writes when a funcref is stored in a variable', + it('ignore when a funcref is stored in a variable', function() nvim_command('let F = function("tr")') meths.set_var('U', '10') nvim_command('set shada+=!') - eq('Vim(wshada):E5004: Error while dumping variable g:F, itself: attempt to dump function reference', - exc_exec('wshada')) - meths.set_option('shada', '') - reset('set shada+=!') + nvim_command('wshada') + reset() + nvim_command('set shada+=!') + nvim_command('rshada') eq('10', meths.get_var('U')) end) - it('errors and writes when a self-referencing list is stored in a variable', + it('ignore when a partial is stored in a variable', + function() + nvim_command('let P = { -> 1 }') + meths.set_var('U', '10') + nvim_command('set shada+=!') + nvim_command('wshada') + reset() + nvim_command('set shada+=!') + nvim_command('rshada') + eq('10', meths.get_var('U')) + end) + + it('ignore when a self-referencing list is stored in a variable', function() meths.set_var('L', {}) nvim_command('call add(L, L)') meths.set_var('U', '10') nvim_command('set shada+=!') - eq('Vim(wshada):E5005: Unable to dump variable g:L: container references itself in index 0', - exc_exec('wshada')) - meths.set_option('shada', '') - reset('set shada+=!') + nvim_command('wshada') + reset() + nvim_command('rshada') eq('10', meths.get_var('U')) end) end) diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 06465071c5..7c03005529 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -1234,13 +1234,13 @@ describe('typval.c', function() local l = list() local l2 = list() - -- NULL lists are not equal to empty lists - eq(false, lib.tv_list_equal(l, nil, true, false)) - eq(false, lib.tv_list_equal(nil, l, false, false)) - eq(false, lib.tv_list_equal(nil, l, false, true)) - eq(false, lib.tv_list_equal(l, nil, true, true)) + -- NULL lists are equal to empty lists + eq(true, lib.tv_list_equal(l, nil, true, false)) + eq(true, lib.tv_list_equal(nil, l, false, false)) + eq(true, lib.tv_list_equal(nil, l, false, true)) + eq(true, lib.tv_list_equal(l, nil, true, true)) - -- Yet NULL lists are equal themselves + -- NULL lists are equal themselves eq(true, lib.tv_list_equal(nil, nil, true, false)) eq(true, lib.tv_list_equal(nil, nil, false, false)) eq(true, lib.tv_list_equal(nil, nil, false, true)) @@ -2648,13 +2648,13 @@ describe('typval.c', function() local l2 = lua2typvalt(empty_list) local nl = lua2typvalt(null_list) - -- NULL lists are not equal to empty lists - eq(false, lib.tv_equal(l, nl, true, false)) - eq(false, lib.tv_equal(nl, l, false, false)) - eq(false, lib.tv_equal(nl, l, false, true)) - eq(false, lib.tv_equal(l, nl, true, true)) + -- NULL lists are equal to empty lists + eq(true, lib.tv_equal(l, nl, true, false)) + eq(true, lib.tv_equal(nl, l, false, false)) + eq(true, lib.tv_equal(nl, l, false, true)) + eq(true, lib.tv_equal(l, nl, true, true)) - -- Yet NULL lists are equal themselves + -- NULL lists are equal themselves eq(true, lib.tv_equal(nl, nl, true, false)) eq(true, lib.tv_equal(nl, nl, false, false)) eq(true, lib.tv_equal(nl, nl, false, true)) |