aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/autoload/health/provider.vim7
-rw-r--r--runtime/autoload/man.vim100
-rw-r--r--runtime/doc/eval.txt8
-rw-r--r--runtime/doc/lsp.txt46
-rw-r--r--runtime/ftplugin/man.vim4
-rw-r--r--runtime/lua/vim/lsp.lua1
-rw-r--r--runtime/lua/vim/lsp/buf.lua60
-rw-r--r--runtime/lua/vim/lsp/callbacks.lua27
-rw-r--r--runtime/lua/vim/lsp/protocol.lua4
-rw-r--r--src/nvim/CMakeLists.txt6
-rw-r--r--src/nvim/eval.c132
-rw-r--r--src/nvim/eval.lua2
-rw-r--r--src/nvim/eval/funcs.c2
-rw-r--r--src/nvim/eval/typval.c8
-rw-r--r--src/nvim/lua/executor.c4
-rw-r--r--src/nvim/main.c39
-rw-r--r--src/nvim/misc1.c9
-rw-r--r--src/nvim/ops.c10
-rw-r--r--src/nvim/shada.c30
-rw-r--r--src/nvim/spellfile.c83
-rw-r--r--src/nvim/testdir/shared.vim2
-rw-r--r--src/nvim/testdir/test_assert.vim13
-rw-r--r--src/nvim/testdir/test_diffmode.vim22
-rw-r--r--src/nvim/testdir/test_registers.vim18
-rw-r--r--src/nvim/tui/tui.c4
-rw-r--r--test/functional/core/startup_spec.lua25
-rw-r--r--test/functional/eval/null_spec.lua6
-rw-r--r--test/functional/ex_cmds/echo_spec.lua24
-rw-r--r--test/functional/legacy/assert_spec.lua8
-rw-r--r--test/functional/lua/luaeval_spec.lua12
-rw-r--r--test/functional/plugin/lsp_spec.lua143
-rw-r--r--test/functional/shada/errors_spec.lua21
-rw-r--r--test/functional/shada/variables_spec.lua35
-rw-r--r--test/unit/eval/typval_spec.lua24
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(&params);
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))