aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/autoload/health/provider.vim2
-rw-r--r--runtime/autoload/provider/pythonx.vim2
-rw-r--r--runtime/doc/autocmd.txt1
-rw-r--r--runtime/doc/editing.txt4
-rw-r--r--runtime/doc/eval.txt7
-rw-r--r--runtime/doc/lsp.txt9
-rw-r--r--runtime/doc/lua.txt99
-rw-r--r--runtime/doc/vim_diff.txt4
-rw-r--r--runtime/filetype.vim3
-rw-r--r--runtime/lua/vim/highlight.lua60
-rw-r--r--runtime/lua/vim/lsp.lua63
-rw-r--r--runtime/lua/vim/lsp/callbacks.lua6
-rw-r--r--runtime/lua/vim/lsp/log.lua2
-rw-r--r--runtime/lua/vim/lsp/protocol.lua3
-rw-r--r--runtime/lua/vim/lsp/util.lua121
-rw-r--r--runtime/lua/vim/shared.lua2
-rw-r--r--runtime/lua/vim/uri.lua10
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim21
-rwxr-xr-xscripts/vim-patch.sh2
-rw-r--r--src/nvim/api/vim.c9
-rw-r--r--src/nvim/eval.c56
-rw-r--r--src/nvim/eval.h1
-rw-r--r--src/nvim/eval/funcs.c8
-rw-r--r--src/nvim/eval/typval_encode.c.h2
-rw-r--r--src/nvim/eval/userfunc.c157
-rw-r--r--src/nvim/eval/userfunc.h6
-rw-r--r--src/nvim/event/stream.c8
-rw-r--r--src/nvim/ex_cmds2.c6
-rw-r--r--src/nvim/fileio.c11
-rw-r--r--src/nvim/lua/executor.c102
-rw-r--r--src/nvim/lua/vim.lua63
-rw-r--r--src/nvim/main.c2
-rw-r--r--src/nvim/message.c7
-rw-r--r--src/nvim/move.c2
-rw-r--r--src/nvim/ops.c4
-rw-r--r--src/nvim/os/os_win_console.c32
-rw-r--r--src/nvim/os/os_win_console.h4
-rw-r--r--src/nvim/os/tty.c4
-rw-r--r--src/nvim/screen.c6
-rw-r--r--src/nvim/testdir/runtest.vim16
-rw-r--r--src/nvim/testdir/test_autocmd.vim10
-rw-r--r--src/nvim/testdir/test_breakindent.vim49
-rw-r--r--src/nvim/testdir/test_cursor_func.vim15
-rw-r--r--src/nvim/testdir/test_filetype.vim1
-rw-r--r--src/nvim/testdir/test_match.vim31
-rw-r--r--src/nvim/testdir/test_options.vim7
-rw-r--r--src/nvim/testdir/test_startup.vim9
-rw-r--r--src/nvim/tui/tui.c31
-rw-r--r--test/functional/api/vim_spec.lua8
-rw-r--r--test/functional/autocmd/textyankpost_spec.lua48
-rw-r--r--test/functional/core/startup_spec.lua26
-rw-r--r--test/functional/legacy/memory_usage_spec.lua161
-rw-r--r--test/functional/lua/uri_spec.lua23
-rw-r--r--test/functional/lua/vim_spec.lua193
-rw-r--r--test/functional/plugin/lsp_spec.lua31
-rw-r--r--third-party/CMakeLists.txt9
56 files changed, 1366 insertions, 213 deletions
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim
index 6d481e9f49..4975dc66b8 100644
--- a/runtime/autoload/health/provider.vim
+++ b/runtime/autoload/health/provider.vim
@@ -581,7 +581,7 @@ function! s:check_ruby() abort
endif
call health#report_info('Host: '. host)
- let latest_gem_cmd = has('win32') ? 'cmd /c gem list -ra ^^neovim$' : 'gem list -ra ^neovim$'
+ let latest_gem_cmd = has('win32') ? 'cmd /c gem list -ra "^^neovim$"' : 'gem list -ra ^neovim$'
let latest_gem = s:system(split(latest_gem_cmd))
if s:shell_error || empty(latest_gem)
call health#report_error('Failed to run: '. latest_gem_cmd,
diff --git a/runtime/autoload/provider/pythonx.vim b/runtime/autoload/provider/pythonx.vim
index 23e7ff8f64..ffb9bf3021 100644
--- a/runtime/autoload/provider/pythonx.vim
+++ b/runtime/autoload/provider/pythonx.vim
@@ -29,7 +29,7 @@ endfunction
function! s:get_python_candidates(major_version) abort
return {
\ 2: ['python2', 'python2.7', 'python2.6', 'python'],
- \ 3: ['python3', 'python3.8', 'python3.7', 'python3.6', 'python3.5',
+ \ 3: ['python3', 'python3.9', 'python3.8', 'python3.7', 'python3.6', 'python3.5',
\ 'python3.4', 'python3.3', 'python']
\ }[a:major_version]
endfunction
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index 64ca7b6a45..f1753b75cc 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -844,6 +844,7 @@ TextYankPost Just after a |yank| or |deleting| command, but not
regcontents
regname
regtype
+ visual
The `inclusive` flag combined with the |'[|
and |']| marks can be used to calculate the
precise region of the operation.
diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt
index 23a65f16e4..ac398ec494 100644
--- a/runtime/doc/editing.txt
+++ b/runtime/doc/editing.txt
@@ -1265,7 +1265,7 @@ exist, the next-higher scope in the hierarchy applies.
other tabs and windows is not changed.
*:tcd-*
-:tcd[!] - Change to the previous current directory (before the
+:tc[d][!] - Change to the previous current directory (before the
previous ":tcd {path}" command).
*:tch* *:tchdir*
@@ -1280,7 +1280,7 @@ exist, the next-higher scope in the hierarchy applies.
:lch[dir][!] Same as |:lcd|.
*:lcd-*
-:lcd[!] - Change to the previous current directory (before the
+:lc[d][!] - Change to the previous current directory (before the
previous ":lcd {path}" command).
*:pw* *:pwd* *E187*
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 1992c34102..7f50769023 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1423,6 +1423,10 @@ PREDEFINED VIM VARIABLES *vim-variable* *v:var* *v:*
*E963*
Some variables can be set by the user, but the type cannot be changed.
+ *v:argv* *argv-variable*
+v:argv The command line arguments Vim was invoked with. This is a
+ list of strings. The first item is the Vim command.
+
*v:beval_col* *beval_col-variable*
v:beval_col The number of the column, over which the mouse pointer is.
This is the byte index in the |v:beval_lnum| line.
@@ -1587,6 +1591,8 @@ v:event Dictionary of event data for the current |autocommand|. Valid
operation.
regtype Type of register as returned by
|getregtype()|.
+ visual Selection is visual (as opposed to,
+ e.g., via motion).
completed_item Current selected complete item on
|CompleteChanged|, Is `{}` when no complete
item selected.
@@ -2600,6 +2606,7 @@ argv([{nr} [, {winid}])
the whole |arglist| is returned.
The {winid} argument specifies the window ID, see |argc()|.
+ For the Vim command line arguments see |v:argv|.
assert_beeps({cmd}) *assert_beeps()*
Run {cmd} and add an error message to |v:errors| if it does
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 9460e600e3..9deaf26983 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -1042,15 +1042,10 @@ get_current_line_to_cursor()
get_severity_highlight_name({severity})
TODO: Documentation
- *vim.lsp.util.highlight_range()*
-highlight_range({bufnr}, {ns}, {hiname}, {start}, {finish})
- TODO: Documentation
-
- *vim.lsp.util.highlight_region()*
-highlight_region({ft}, {start}, {finish})
+jump_to_location({location}) *vim.lsp.util.jump_to_location()*
TODO: Documentation
-jump_to_location({location}) *vim.lsp.util.jump_to_location()*
+preview_location({location}) *vim.lsp.util.preview_location()*
TODO: Documentation
locations_to_items({locations}) *vim.lsp.util.locations_to_items()*
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 09034353a3..5a49d36503 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -693,6 +693,41 @@ identical identifiers, highlighting both as |hl-WarningMsg|: >
(eq? @WarningMsg.left @WarningMsg.right))
------------------------------------------------------------------------------
+VIM.HIGHLIGHT *lua-highlight*
+
+Nvim includes a function for highlighting a selection on yank (see for example
+https://github.com/machakann/vim-highlightedyank). To enable it, add
+>
+ au TextYankPost * silent! lua require'vim.highlight'.on_yank()
+<
+to your `init.vim`. You can customize the highlight group and the duration of
+the highlight via
+>
+ au TextYankPost * silent! lua require'vim.highlight'.on_yank("IncSearch", 500)
+<
+If you want to exclude visual selections from highlighting on yank, use
+>
+au TextYankPost * silent! lua return (not vim.v.event.visual) and require'vim.highlight'.on_yank()
+<
+
+vim.highlight.on_yank([{higroup}, {timeout}, {event}])
+ *vim.highlight.on_yank()*
+ Highlights the yanked text. Optional arguments are the highlight group
+ to use ({higroup}, default `"IncSearch"`), the duration of highlighting
+ in milliseconds ({timeout}, default `500`), and the event structure
+ that is fired ({event}, default `vim.v.event`).
+
+
+vim.highlight.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {rtype}, {inclusive})
+ *vim.highlight.range()*
+ Highlights the range between {start} and {finish} (tuples of {line,col})
+ in buffer {bufnr} with the highlight group {higroup} using the namespace
+ {ns}. Optional arguments are the type of range (characterwise, linewise,
+ or blockwise, see |setreg|; default to characterwise) and whether the
+ range is inclusive (default false).
+
+
+------------------------------------------------------------------------------
VIM.REGEX *lua-regex*
Vim regexes can be used directly from lua. Currently they only allow
@@ -758,6 +793,14 @@ vim.empty_dict() *vim.empty_dict()*
Note: if numeric keys are added to the table, the metatable will be
ignored and the dict converted to a list/array anyway.
+vim.region({bufnr}, {pos1}, {pos2}, {type}, {inclusive}) *vim.region()*
+ Converts a selection specified by the buffer ({bufnr}), starting
+ position ({pos1}, a zero-indexed pair `{line1,column1}`), ending
+ position ({pos2}, same format as {pos1}), the type of the register
+ for the selection ({type}, see |regtype|), and a boolean indicating
+ whether the selection is inclusive or not, into a zero-indexed table
+ of linewise selections of the form `{linenr = {startcol, endcol}}` .
+
vim.rpcnotify({channel}, {method}[, {args}...]) *vim.rpcnotify()*
Sends {event} to {channel} via |RPC| and returns immediately.
If {channel} is 0, the event is broadcast to all channels.
@@ -798,6 +841,62 @@ vim.schedule({callback}) *vim.schedule()*
Schedules {callback} to be invoked soon by the main event-loop. Useful
to avoid |textlock| or other temporary restrictions.
+
+vim.defer_fn({fn}, {timeout}) *vim.defer_fn*
+ Defers calling {fn} until {timeout} ms passes. Use to do a one-shot timer
+ that calls {fn}.
+
+ Parameters: ~
+ {fn} Callback to call once {timeout} expires
+ {timeout} Time in ms to wait before calling {fn}
+
+ Returns: ~
+ |vim.loop|.new_timer() object
+
+vim.wait({time}, {callback} [, {interval}]) *vim.wait()*
+ Wait for {time} in milliseconds until {callback} returns `true`.
+
+ Executes {callback} immediately and at approximately {interval}
+ milliseconds (default 200). Nvim still processes other events during
+ this time.
+
+
+ Returns: ~
+ If {callback} returns `true` during the {time}:
+ `true, nil`
+
+ If {callback} never returns `true` during the {time}:
+ `false, -1`
+
+ If {callback} is interrupted during the {time}:
+ `false, -2`
+
+ If {callback} errors, the error is raised.
+
+ Examples: >
+
+ ---
+ -- Wait for 100 ms, allowing other events to process
+ vim.wait(100, function() end)
+
+ ---
+ -- Wait for 100 ms or until global variable set.
+ vim.wait(100, function() return vim.g.waiting_for_var end)
+
+ ---
+ -- Wait for 1 second or until global variable set, checking every ~500 ms
+ vim.wait(1000, function() return vim.g.waiting_for_var end, 500)
+
+ ---
+ -- Schedule a function to set a value in 100ms
+ vim.defer_fn(function() vim.g.timer_result = true end, 100)
+
+ -- Would wait ten seconds if results blocked. Actually only waits 100 ms
+ if vim.wait(10000, function() return vim.g.timer_result end) then
+ print('Only waiting a little bit of time!')
+ end
+<
+
vim.fn.{func}({...}) *vim.fn*
Invokes |vim-function| or |user-function| {func} with arguments {...}.
To call autoload functions, use the syntax: >
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index 376375e4ef..24b562543e 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -116,6 +116,10 @@ backwards-compatibility cost. Some examples:
- Directories for 'directory' and 'undodir' are auto-created.
- Terminal features such as 'guicursor' are enabled where possible.
+Some features are built in that otherwise required external plugins:
+
+- Highlighting the yanked region, see |lua-highlight|.
+
ARCHITECTURE ~
External plugins run in separate processes. |remote-plugin| This improves
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index 0b5003dc44..b29168984c 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -543,6 +543,9 @@ au BufNewFile,BufRead */etc/elinks.conf,*/.elinks/elinks.conf setf elinks
" ERicsson LANGuage; Yaws is erlang too
au BufNewFile,BufRead *.erl,*.hrl,*.yaws setf erlang
+" Elm
+au BufNewFile,BufRead *.elm setf elm
+
" Elm Filter Rules file
au BufNewFile,BufRead filter-rules setf elmfilt
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
new file mode 100644
index 0000000000..69c3c8a4dc
--- /dev/null
+++ b/runtime/lua/vim/highlight.lua
@@ -0,0 +1,60 @@
+local api = vim.api
+
+local highlight = {}
+
+--- Highlight range between two positions
+---
+--@param bufnr number of buffer to apply highlighting to
+--@param ns namespace to add highlight to
+--@param higroup highlight group to use for highlighting
+--@param rtype type of range (:help setreg, default charwise)
+--@param inclusive boolean indicating whether the range is end-inclusive (default false)
+function highlight.range(bufnr, ns, higroup, start, finish, rtype, inclusive)
+ rtype = rtype or 'v'
+ inclusive = inclusive or false
+
+ -- sanity check
+ if start[2] < 0 or finish[2] < start[2] then return end
+
+ local region = vim.region(bufnr, start, finish, rtype, inclusive)
+ for linenr, cols in pairs(region) do
+ api.nvim_buf_add_highlight(bufnr, ns, higroup, linenr, cols[1], cols[2])
+ end
+
+end
+
+--- Highlight the yanked region
+---
+--- use from init.vim via
+--- au TextYankPost * lua require'vim.highlight'.on_yank()
+--- customize highlight group and timeout via
+--- au TextYankPost * lua require'vim.highlight'.on_yank("IncSearch", 500)
+---
+-- @param higroup highlight group for yanked region
+-- @param timeout time in ms before highlight is cleared
+-- @param event event structure
+function highlight.on_yank(higroup, timeout, event)
+ event = event or vim.v.event
+ if event.operator ~= 'y' or event.regtype == '' then return end
+ higroup = higroup or "IncSearch"
+ timeout = timeout or 500
+
+ local bufnr = api.nvim_get_current_buf()
+ local yank_ns = api.nvim_create_namespace('')
+ api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1)
+
+ local pos1 = vim.fn.getpos("'[")
+ local pos2 = vim.fn.getpos("']")
+
+ pos1 = {pos1[2] - 1, pos1[3] - 1 + pos1[4]}
+ pos2 = {pos2[2] - 1, pos2[3] - 1 + pos2[4]}
+
+ highlight.range(bufnr, yank_ns, higroup, pos1, pos2, event.regtype, event.inclusive)
+
+ vim.defer_fn(
+ function() api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1) end,
+ timeout
+ )
+end
+
+return highlight
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 61da2130c8..84812b8c64 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -46,31 +46,6 @@ local function is_dir(filename)
return stat and stat.type == 'directory' or false
end
--- TODO Use vim.wait when that is available, but provide an alternative for now.
-local wait = vim.wait or function(timeout_ms, condition, interval)
- validate {
- timeout_ms = { timeout_ms, 'n' };
- condition = { condition, 'f' };
- interval = { interval, 'n', true };
- }
- assert(timeout_ms > 0, "timeout_ms must be > 0")
- local _ = log.debug() and log.debug("wait.fallback", timeout_ms)
- interval = interval or 200
- local interval_cmd = "sleep "..interval.."m"
- local timeout = timeout_ms + uv.now()
- -- TODO is there a better way to sync this?
- while true do
- uv.update_time()
- if condition() then
- return 0
- end
- if uv.now() >= timeout then
- return -1
- end
- nvim_command(interval_cmd)
- -- vim.loop.sleep(10)
- end
-end
local wait_result_reason = { [-1] = "timeout"; [-2] = "interrupted"; [-3] = "error" }
local valid_encodings = {
@@ -122,19 +97,19 @@ local function validate_encoding(encoding)
end
function lsp._cmd_parts(input)
- local cmd, cmd_args
- if vim.tbl_islist(input) then
- cmd = input[1]
- cmd_args = {}
- -- Don't mutate our input.
- for i, v in ipairs(input) do
- assert(type(v) == 'string', "input arguments must be strings")
- if i > 1 then
- table.insert(cmd_args, v)
- end
+ vim.validate{cmd={
+ input,
+ function() return vim.tbl_islist(input) end,
+ "list"}}
+
+ local cmd = input[1]
+ local cmd_args = {}
+ -- Don't mutate our input.
+ for i, v in ipairs(input) do
+ vim.validate{["cmd argument"]={v, "s"}}
+ if i > 1 then
+ table.insert(cmd_args, v)
end
- else
- error("cmd type must be list.")
end
return cmd, cmd_args
end
@@ -524,7 +499,7 @@ function lsp.start_client(config)
function client.request(method, params, callback, bufnr)
if not callback then
callback = resolve_callback(method)
- or error("not found: request callback for client "..client.name)
+ or error(string.format("not found: %q request callback for client %q.", method, client.name))
end
local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback, bufnr)
-- TODO keep these checks or just let it go anyway?
@@ -810,8 +785,8 @@ function lsp._vim_exit_handler()
for _, client in pairs(active_clients) do
client.stop()
end
- local wait_result = wait(500, function() return tbl_isempty(active_clients) end, 50)
- if wait_result ~= 0 then
+
+ if not vim.wait(500, function() return tbl_isempty(active_clients) end, 50) then
for _, client in pairs(active_clients) do
client.stop(true)
end
@@ -889,12 +864,14 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
for _ in pairs(client_request_ids) do
expected_result_count = expected_result_count + 1
end
- local wait_result = wait(timeout_ms or 100, function()
+
+ local wait_result, reason = vim.wait(timeout_ms or 100, function()
return result_count >= expected_result_count
end, 10)
- if wait_result ~= 0 then
+
+ if not wait_result then
cancel()
- return nil, wait_result_reason[wait_result]
+ return nil, wait_result_reason[reason]
end
return request_results
end
diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua
index 37e9f1e5c1..7c51fc2cc2 100644
--- a/runtime/lua/vim/lsp/callbacks.lua
+++ b/runtime/lua/vim/lsp/callbacks.lua
@@ -242,12 +242,12 @@ end
-- Add boilerplate error validation and logging for all of these.
for k, fn in pairs(M) do
- M[k] = function(err, method, params, client_id)
- local _ = log.debug() and log.debug('default_callback', method, { params = params, client_id = client_id, err = err })
+ M[k] = function(err, method, params, client_id, bufnr)
+ log.debug('default_callback', method, { params = params, client_id = client_id, err = err, bufnr = bufnr })
if err then
error(tostring(err))
end
- return fn(err, method, params, client_id)
+ return fn(err, method, params, client_id, bufnr)
end
end
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index 78aabf08ce..696ce43a59 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -24,7 +24,7 @@ do
local function path_join(...)
return table.concat(vim.tbl_flatten{...}, path_sep)
end
- local logfilename = path_join(vim.fn.stdpath('data'), 'vim-lsp.log')
+ local logfilename = path_join(vim.fn.stdpath('data'), 'lsp.log')
--- Return the log filename.
function log.get_filename()
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 877d11411b..7d5f8f5ef1 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -633,8 +633,7 @@ function protocol.make_client_capabilities()
dynamicRegistration = false;
completionItem = {
- -- TODO(tjdevries): Is it possible to implement this in plain lua?
- snippetSupport = false;
+ snippetSupport = true;
commitCharactersSupport = false;
preselectSupport = false;
deprecatedSupport = false;
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 1b099fb3f4..02d233fb7b 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -3,6 +3,7 @@ local vim = vim
local validate = vim.validate
local api = vim.api
local list_extend = vim.list_extend
+local highlight = require 'vim.highlight'
local M = {}
@@ -199,6 +200,66 @@ function M.get_current_line_to_cursor()
return line:sub(pos[2]+1)
end
+local function parse_snippet_rec(input, inner)
+ local res = ""
+
+ local close, closeend = nil, nil
+ if inner then
+ close, closeend = input:find("}", 1, true)
+ while close ~= nil and input:sub(close-1,close-1) == "\\" do
+ close, closeend = input:find("}", closeend+1, true)
+ end
+ end
+
+ local didx = input:find('$', 1, true)
+ if didx == nil and close == nil then
+ return input, ""
+ elseif close ~=nil and (didx == nil or close < didx) then
+ -- No inner placeholders
+ return input:sub(0, close-1), input:sub(closeend+1)
+ end
+
+ res = res .. input:sub(0, didx-1)
+ input = input:sub(didx+1)
+
+ local tabstop, tabstopend = input:find('^%d+')
+ local placeholder, placeholderend = input:find('^{%d+:')
+ local choice, choiceend = input:find('^{%d+|')
+
+ if tabstop then
+ input = input:sub(tabstopend+1)
+ elseif choice then
+ input = input:sub(choiceend+1)
+ close, closeend = input:find("|}", 1, true)
+
+ res = res .. input:sub(0, close-1)
+ input = input:sub(closeend+1)
+ elseif placeholder then
+ -- TODO: add support for variables
+ input = input:sub(placeholderend+1)
+
+ -- placeholders and variables are recursive
+ while input ~= "" do
+ local r, tail = parse_snippet_rec(input, true)
+ r = r:gsub("\\}", "}")
+
+ res = res .. r
+ input = tail
+ end
+ else
+ res = res .. "$"
+ end
+
+ return res, input
+end
+
+-- Parse completion entries, consuming snippet tokens
+function M.parse_snippet(input)
+ local res, _ = parse_snippet_rec(input, false)
+
+ return res
+end
+
-- Sort by CompletionItem.sortText
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
local function sort_completion_items(items)
@@ -213,9 +274,17 @@ end
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
local function get_completion_word(item)
if item.textEdit ~= nil and item.textEdit.newText ~= nil then
- return item.textEdit.newText
+ if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then
+ return item.textEdit.newText
+ else
+ return M.parse_snippet(item.textEdit.newText)
+ end
elseif item.insertText ~= nil then
- return item.insertText
+ if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then
+ return item.insertText
+ else
+ return M.parse_snippet(item.insertText)
+ end
end
return item.label
end
@@ -479,6 +548,28 @@ function M.jump_to_location(location)
return true
end
+--- Preview a location in a floating windows
+---
+--- behavior depends on type of location:
+--- - for Location, range is shown (e.g., function definition)
+--- - for LocationLink, targetRange is shown (e.g., body of function definition)
+---
+--@param location a single Location or LocationLink
+--@return bufnr,winnr buffer and window number of floating window or nil
+function M.preview_location(location)
+ -- location may be LocationLink or Location (more useful for the former)
+ local uri = location.targetUri or location.uri
+ if uri == nil then return end
+ local bufnr = vim.uri_to_bufnr(uri)
+ if not api.nvim_buf_is_loaded(bufnr) then
+ vim.fn.bufload(bufnr)
+ end
+ local range = location.targetRange or location.range
+ local contents = api.nvim_buf_get_lines(bufnr, range.start.line, range["end"].line+1, false)
+ local filetype = api.nvim_buf_get_option(bufnr, 'filetype')
+ return M.open_floating_preview(contents, filetype)
+end
+
local function find_window_by_var(name, value)
for _, win in ipairs(api.nvim_list_wins()) do
if npcall(api.nvim_win_get_var, win, name) == value then
@@ -599,7 +690,7 @@ function M.fancy_floating_markdown(contents, opts)
vim.cmd("ownsyntax markdown")
local idx = 1
- local function highlight_region(ft, start, finish)
+ local function apply_syntax_to_region(ft, start, finish)
if ft == '' then return end
local name = ft..idx
idx = idx + 1
@@ -615,8 +706,8 @@ function M.fancy_floating_markdown(contents, opts)
-- make sure that regions between code blocks are definitely markdown.
-- local ph = {start = 0; finish = 1;}
for _, h in ipairs(highlights) do
- -- highlight_region('markdown', ph.finish, h.start)
- highlight_region(h.ft, h.start, h.finish)
+ -- apply_syntax_to_region('markdown', ph.finish, h.start)
+ apply_syntax_to_region(h.ft, h.start, h.finish)
-- ph = h
end
@@ -670,19 +761,6 @@ function M.open_floating_preview(contents, filetype, opts)
return floating_bufnr, floating_winnr
end
-local function highlight_range(bufnr, ns, hiname, start, finish)
- if start[1] == finish[1] then
- -- TODO care about encoding here since this is in byte index?
- api.nvim_buf_add_highlight(bufnr, ns, hiname, start[1], start[2], finish[2])
- else
- api.nvim_buf_add_highlight(bufnr, ns, hiname, start[1], start[2], -1)
- for line = start[1] + 1, finish[1] - 1 do
- api.nvim_buf_add_highlight(bufnr, ns, hiname, line, 0, -1)
- end
- api.nvim_buf_add_highlight(bufnr, ns, hiname, finish[1], 0, finish[2])
- end
-end
-
do
local diagnostic_ns = api.nvim_create_namespace("vim_lsp_diagnostics")
local reference_ns = api.nvim_create_namespace("vim_lsp_references")
@@ -755,7 +833,7 @@ do
local lines = {"Diagnostics:"}
local highlights = {{0, "Bold"}}
local line_diagnostics = M.get_line_diagnostics()
- if not line_diagnostics then return end
+ if vim.tbl_isempty(line_diagnostics) then return end
for i, diagnostic in ipairs(line_diagnostics) do
-- for i, mark in ipairs(marks) do
@@ -816,8 +894,7 @@ do
[protocol.DiagnosticSeverity.Hint]='Hint',
}
- -- TODO care about encoding here since this is in byte index?
- highlight_range(bufnr, diagnostic_ns,
+ highlight.range(bufnr, diagnostic_ns,
underline_highlight_name..hlmap[diagnostic.severity],
{start.line, start.character},
{finish.line, finish.character}
@@ -841,7 +918,7 @@ do
[protocol.DocumentHighlightKind.Write] = "LspReferenceWrite";
}
local kind = reference["kind"] or protocol.DocumentHighlightKind.Text
- highlight_range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos)
+ highlight.range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos)
end
end
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 2135bfc837..5dc8d6dc10 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -79,7 +79,7 @@ function vim.gsplit(s, sep, plain)
end
return function()
- if done then
+ if done or s == '' then
return
end
if sep == '' then
diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua
index e28cc9e20f..9c3535c676 100644
--- a/runtime/lua/vim/uri.lua
+++ b/runtime/lua/vim/uri.lua
@@ -65,9 +65,11 @@ local function uri_from_fname(path)
return table.concat(uri_parts)
end
+local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9+-.]*)://.*'
+
local function uri_from_bufnr(bufnr)
local fname = vim.api.nvim_buf_get_name(bufnr)
- local scheme = fname:match("^([a-z]+)://.*")
+ local scheme = fname:match(URI_SCHEME_PATTERN)
if scheme then
return fname
else
@@ -76,6 +78,10 @@ local function uri_from_bufnr(bufnr)
end
local function uri_to_fname(uri)
+ local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri)
+ if scheme ~= 'file' then
+ return uri
+ end
uri = uri_decode(uri)
-- TODO improve this.
if is_windows_file_uri(uri) then
@@ -89,7 +95,7 @@ end
-- Return or create a buffer for a uri.
local function uri_to_bufnr(uri)
- local scheme = assert(uri:match("^([a-z]+)://.*"), 'Uri must contain a scheme: ' .. uri)
+ local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri)
if scheme == 'file' then
return vim.fn.bufadd(uri_to_fname(uri))
else
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index aa2b69ad97..28dc3256c7 100644
--- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
+++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
@@ -645,7 +645,7 @@ func s:InstallCommands()
command Gdb call win_gotoid(s:gdbwin)
command Program call win_gotoid(s:ptywin)
command Source call s:GotoSourcewinOrCreateIt()
- " command Winbar call s:InstallWinbar()
+ command Winbar call s:InstallWinbar()
" TODO: can the K mapping be restored?
nnoremap K :Evaluate<CR>
@@ -655,6 +655,19 @@ endfunc
" let s:winbar_winids = []
+" Install the window toolbar in the current window.
+func s:InstallWinbar()
+ " if has('menu') && &mouse != ''
+ " nnoremenu WinBar.Step :Step<CR>
+ " nnoremenu WinBar.Next :Over<CR>
+ " nnoremenu WinBar.Finish :Finish<CR>
+ " nnoremenu WinBar.Cont :Continue<CR>
+ " nnoremenu WinBar.Stop :Stop<CR>
+ " nnoremenu WinBar.Eval :Evaluate<CR>
+ " call add(s:winbar_winids, win_getid(winnr()))
+ " endif
+endfunc
+
" Delete installed debugger commands in the current window.
func s:DeleteCommands()
delcommand Break
@@ -670,7 +683,7 @@ func s:DeleteCommands()
delcommand Gdb
delcommand Program
delcommand Source
- " delcommand Winbar
+ delcommand Winbar
nunmap K
@@ -940,7 +953,7 @@ func s:GotoSourcewinOrCreateIt()
if !win_gotoid(s:sourcewin)
new
let s:sourcewin = win_getid(winnr())
- " call s:InstallWinbar()
+ call s:InstallWinbar()
endif
endfunc
@@ -971,7 +984,7 @@ func s:HandleCursor(msg)
" TODO: find existing window
exe 'split ' . fnameescape(fname)
let s:sourcewin = win_getid(winnr())
- " call s:InstallWinbar()
+ call s:InstallWinbar()
else
exe 'edit ' . fnameescape(fname)
endif
diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh
index a8b622d5c4..9c4349abca 100755
--- a/scripts/vim-patch.sh
+++ b/scripts/vim-patch.sh
@@ -7,7 +7,7 @@ set -p
# Ensure that the user has a bash that supports -A
if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then
- echo "This script requires bash version 3 or later (you have ${BASH_VERSION})." >&2
+ >&2 echo "error: script requires bash 4+ (you have ${BASH_VERSION})."
exit 1
fi
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 087ab37296..40cef87cf0 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1037,7 +1037,7 @@ void nvim_set_current_win(Window window, Error *err)
///
/// @param listed Sets 'buflisted'
/// @param scratch Creates a "throwaway" |scratch-buffer| for temporary work
-/// (always 'nomodified')
+/// (always 'nomodified'). Also sets 'nomodeline' on the buffer.
/// @param[out] err Error details, if any
/// @return Buffer handle, or 0 on error
///
@@ -1067,9 +1067,10 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
if (scratch) {
aco_save_T aco;
aucmd_prepbuf(&aco, buf);
- set_option_value("bh", 0L, "hide", OPT_LOCAL);
- set_option_value("bt", 0L, "nofile", OPT_LOCAL);
- set_option_value("swf", 0L, NULL, OPT_LOCAL);
+ set_option_value("bufhidden", 0L, "hide", OPT_LOCAL);
+ set_option_value("buftype", 0L, "nofile", OPT_LOCAL);
+ set_option_value("swapfile", 0L, NULL, OPT_LOCAL);
+ set_option_value("modeline", 0L, NULL, OPT_LOCAL); // 'nomodeline'
aucmd_restbuf(&aco);
}
return buf->b_fnum;
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 4acee7b453..017d46e802 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -230,6 +230,7 @@ static struct vimvar {
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO),
VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO),
+ VV(VV_ARGV, "argv", VAR_LIST, VV_RO),
};
#undef VV
@@ -443,7 +444,7 @@ void eval_clear(void)
// unreferenced lists and dicts
(void)garbage_collect(false);
- // functions
+ // functions not garbage collected
free_all_functions();
}
@@ -872,17 +873,19 @@ char_u *eval_to_string(char_u *arg, char_u **nextcmd, int convert)
char_u *eval_to_string_safe(char_u *arg, char_u **nextcmd, int use_sandbox)
{
char_u *retval;
- void *save_funccalp;
+ funccal_entry_T funccal_entry;
- save_funccalp = save_funccal();
- if (use_sandbox)
- ++sandbox;
- ++textlock;
- retval = eval_to_string(arg, nextcmd, FALSE);
- if (use_sandbox)
- --sandbox;
- --textlock;
- restore_funccal(save_funccalp);
+ save_funccal(&funccal_entry);
+ if (use_sandbox) {
+ sandbox++;
+ }
+ textlock++;
+ retval = eval_to_string(arg, nextcmd, false);
+ if (use_sandbox) {
+ sandbox--;
+ }
+ textlock--;
+ restore_funccal();
return retval;
}
@@ -5025,7 +5028,7 @@ bool garbage_collect(bool testing)
// 3. Check if any funccal can be freed now.
// This may call us back recursively.
- did_free = did_free || free_unref_funccal(copyID, testing);
+ did_free = free_unref_funccal(copyID, testing) || did_free;
} else if (p_verbose > 0) {
verb_msg(_(
"Not enough memory to set references, garbage collection aborted!"));
@@ -8108,6 +8111,23 @@ void set_vim_var_dict(const VimVarIndex idx, dict_T *const val)
}
}
+/// Set the v:argv list.
+void set_argv_var(char **argv, int argc)
+{
+ list_T *l = tv_list_alloc(argc);
+ int i;
+
+ if (l == NULL) {
+ getout(1);
+ }
+ tv_list_set_lock(l, VAR_FIXED);
+ for (i = 0; i < argc; i++) {
+ tv_list_append_string(l, (const char *const)argv[i], -1);
+ TV_LIST_ITEM_TV(tv_list_last(l))->v_lock = VAR_FIXED;
+ }
+ set_vim_var_list(VV_ARGV, l);
+}
+
/*
* Set v:register if needed.
*/
@@ -9748,9 +9768,11 @@ const void *var_shada_iter(const void *const iter, const char **const name,
void var_set_global(const char *const name, typval_T vartv)
{
- funccall_T *const saved_funccal = (funccall_T *)save_funccal();
+ funccal_entry_T funccall_entry;
+
+ save_funccal(&funccall_entry);
set_var(name, strlen(name), &vartv, false);
- restore_funccal(saved_funccal);
+ restore_funccal();
}
int store_session_globals(FILE *fd)
@@ -10306,8 +10328,10 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
.autocmd_fname = autocmd_fname,
.autocmd_match = autocmd_match,
.autocmd_bufnr = autocmd_bufnr,
- .funccalp = save_funccal()
+ .funccalp = (void *)get_current_funccal()
};
+ funccal_entry_T funccal_entry;
+ save_funccal(&funccal_entry);
provider_call_nesting++;
typval_T argvars[3] = {
@@ -10334,7 +10358,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
tv_list_unref(arguments);
// Restore caller scope information
- restore_funccal(provider_caller_scope.funccalp);
+ restore_funccal();
provider_caller_scope = saved_provider_caller_scope;
provider_call_nesting--;
assert(provider_call_nesting >= 0);
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index ebc0eb0b1a..0b4cbb3b4d 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -159,6 +159,7 @@ typedef enum {
VV_ECHOSPACE,
VV_EXITING,
VV_LUA,
+ VV_ARGV,
} VimVarIndex;
/// All recognized msgpack types
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 79a52d9779..25beb4be50 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -7243,7 +7243,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match;
linenr_T save_sourcing_lnum;
int save_autocmd_bufnr;
- void *save_funccalp;
+ funccal_entry_T funccal_entry;
if (l_provider_call_nesting) {
// If this is called from a provider function, restore the scope
@@ -7254,7 +7254,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
save_autocmd_fname = autocmd_fname;
save_autocmd_match = autocmd_match;
save_autocmd_bufnr = autocmd_bufnr;
- save_funccalp = save_funccal();
+ save_funccal(&funccal_entry);
current_sctx = provider_caller_scope.script_ctx;
sourcing_name = provider_caller_scope.sourcing_name;
@@ -7262,7 +7262,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
autocmd_fname = provider_caller_scope.autocmd_fname;
autocmd_match = provider_caller_scope.autocmd_match;
autocmd_bufnr = provider_caller_scope.autocmd_bufnr;
- restore_funccal(provider_caller_scope.funccalp);
+ set_current_funccal((funccall_T *)(provider_caller_scope.funccalp));
}
@@ -7280,7 +7280,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
autocmd_fname = save_autocmd_fname;
autocmd_match = save_autocmd_match;
autocmd_bufnr = save_autocmd_bufnr;
- restore_funccal(save_funccalp);
+ restore_funccal();
}
if (ERROR_SET(&err)) {
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
index af21a6fbe3..94986a64b5 100644
--- a/src/nvim/eval/typval_encode.c.h
+++ b/src/nvim/eval/typval_encode.c.h
@@ -607,7 +607,7 @@ _convert_one_value_regular_dict: {}
kMPConvDict);
TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict,
tv->vval.v_dict->dv_hashtab.ht_used);
- assert(saved_copyID != copyID && saved_copyID != copyID - 1);
+ assert(saved_copyID != copyID);
_mp_push(*mpstack, ((MPConvStackVal) {
.tv = tv,
.type = kMPConvDict,
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index ae8557a8bc..c054433255 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -43,11 +43,11 @@ hashtab_T func_hashtab;
static garray_T funcargs = GA_EMPTY_INIT_VALUE;
// pointer to funccal for currently active function
-funccall_T *current_funccal = NULL;
+static funccall_T *current_funccal = NULL;
// Pointer to list of previously used funccal, still around because some
// item in it is still being used.
-funccall_T *previous_funccal = NULL;
+static funccall_T *previous_funccal = NULL;
static char *e_funcexts = N_(
"E122: Function %s already exists, add ! to replace it");
@@ -541,14 +541,8 @@ static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr)
v->di_tv.vval.v_number = nr;
}
-/*
- * Free "fc" and what it contains.
- */
-static void
-free_funccal(
- funccall_T *fc,
- int free_val // a: vars were allocated
-)
+// Free "fc"
+static void free_funccal(funccall_T *fc)
{
for (int i = 0; i < fc->fc_funcs.ga_len; i++) {
ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
@@ -563,56 +557,89 @@ free_funccal(
}
ga_clear(&fc->fc_funcs);
- // The a: variables typevals may not have been allocated, only free the
- // allocated variables.
- vars_clear_ext(&fc->l_avars.dv_hashtab, free_val);
+ func_ptr_unref(fc->func);
+ xfree(fc);
+}
+// Free "fc" and what it contains.
+// Can be called only when "fc" is kept beyond the period of it called,
+// i.e. after cleanup_function_call(fc).
+static void free_funccal_contents(funccall_T *fc)
+{
// Free all l: variables.
vars_clear(&fc->l_vars.dv_hashtab);
- // Free the a:000 variables if they were allocated.
- if (free_val) {
- TV_LIST_ITER(&fc->l_varlist, li, {
- tv_clear(TV_LIST_ITEM_TV(li));
- });
- }
+ // Free all a: variables.
+ vars_clear(&fc->l_avars.dv_hashtab);
- func_ptr_unref(fc->func);
- xfree(fc);
+ // Free the a:000 variables.
+ TV_LIST_ITER(&fc->l_varlist, li, {
+ tv_clear(TV_LIST_ITEM_TV(li));
+ });
+
+ free_funccal(fc);
}
/// Handle the last part of returning from a function: free the local hashtable.
/// Unless it is still in use by a closure.
static void cleanup_function_call(funccall_T *fc)
{
+ bool may_free_fc = fc->fc_refcount <= 0;
+ bool free_fc = true;
+
current_funccal = fc->caller;
- // If the a:000 list and the l: and a: dicts are not referenced and there
- // is no closure using it, we can free the funccall_T and what's in it.
- if (!fc_referenced(fc)) {
- free_funccal(fc, false);
+ // Free all l: variables if not referred.
+ if (may_free_fc && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT) {
+ vars_clear(&fc->l_vars.dv_hashtab);
} else {
- static int made_copy = 0;
+ free_fc = false;
+ }
- // "fc" is still in use. This can happen when returning "a:000",
- // assigning "l:" to a global variable or defining a closure.
- // Link "fc" in the list for garbage collection later.
- fc->caller = previous_funccal;
- previous_funccal = fc;
+ // If the a:000 list and the l: and a: dicts are not referenced and
+ // there is no closure using it, we can free the funccall_T and what's
+ // in it.
+ if (may_free_fc && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) {
+ vars_clear_ext(&fc->l_avars.dv_hashtab, false);
+ } else {
+ free_fc = false;
// Make a copy of the a: variables, since we didn't do that above.
TV_DICT_ITER(&fc->l_avars, di, {
tv_copy(&di->di_tv, &di->di_tv);
});
+ }
+
+ if (may_free_fc && fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated)
+ == DO_NOT_FREE_CNT) {
+ fc->l_varlist.lv_first = NULL; // NOLINT(runtime/deprecated)
+
+ } else {
+ free_fc = false;
// Make a copy of the a:000 items, since we didn't do that above.
TV_LIST_ITER(&fc->l_varlist, li, {
tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li));
});
+ }
- if (++made_copy == 10000) {
- // We have made a lot of copies. This can happen when
- // repetitively calling a function that creates a reference to
+ if (free_fc) {
+ free_funccal(fc);
+ } else {
+ static int made_copy = 0;
+
+ // "fc" is still in use. This can happen when returning "a:000",
+ // assigning "l:" to a global variable or defining a closure.
+ // Link "fc" in the list for garbage collection later.
+ fc->caller = previous_funccal;
+ previous_funccal = fc;
+
+ if (want_garbage_collect) {
+ // If garbage collector is ready, clear count.
+ made_copy = 0;
+ } else if (++made_copy >= (int)((4096 * 1024) / sizeof(*fc))) {
+ // We have made a lot of copies, worth 4 Mbyte. This can happen
+ // when repetitively calling a function that creates a reference to
// itself somehow. Call the garbage collector soon to avoid using
// too much memory.
made_copy = 0;
@@ -639,7 +666,7 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force)
for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) {
if (fc == *pfc) {
*pfc = fc->caller;
- free_funccal(fc, true);
+ free_funccal_contents(fc);
return;
}
}
@@ -766,7 +793,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
// check for CTRL-C hit
line_breakcheck();
// prepare the funccall_T structure
- fc = xmalloc(sizeof(funccall_T));
+ fc = xcalloc(1, sizeof(funccall_T));
fc->caller = current_funccal;
current_funccal = fc;
fc->func = fp;
@@ -881,9 +908,11 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
}
if (ai >= 0 && ai < MAX_FUNC_ARGS) {
- tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]);
- *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i];
- TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED;
+ listitem_T *li = &fc->l_listitems[ai];
+
+ *TV_LIST_ITEM_TV(li) = argvars[i];
+ TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED;
+ tv_list_append(&fc->l_varlist, li);
}
}
@@ -1106,21 +1135,26 @@ static bool func_name_refcount(char_u *name)
return isdigit(*name) || *name == '<';
}
-/*
- * Save the current function call pointer, and set it to NULL.
- * Used when executing autocommands and for ":source".
- */
-void *save_funccal(void)
-{
- funccall_T *fc = current_funccal;
+static funccal_entry_T *funccal_stack = NULL;
+// Save the current function call pointer, and set it to NULL.
+// Used when executing autocommands and for ":source".
+void save_funccal(funccal_entry_T *entry)
+{
+ entry->top_funccal = current_funccal;
+ entry->next = funccal_stack;
+ funccal_stack = entry;
current_funccal = NULL;
- return (void *)fc;
}
-void restore_funccal(void *vfc)
+void restore_funccal(void)
{
- current_funccal = (funccall_T *)vfc;
+ if (funccal_stack == NULL) {
+ IEMSG("INTERNAL: restore_funccal()");
+ } else {
+ current_funccal = funccal_stack->top_funccal;
+ funccal_stack = funccal_stack->next;
+ }
}
funccall_T *get_current_funccal(void)
@@ -1128,6 +1162,11 @@ funccall_T *get_current_funccal(void)
return current_funccal;
}
+void set_current_funccal(funccall_T *fc)
+{
+ current_funccal = fc;
+}
+
#if defined(EXITFREE)
void free_all_functions(void)
{
@@ -1137,10 +1176,13 @@ void free_all_functions(void)
uint64_t todo = 1;
uint64_t used;
- // Clean up the call stack.
+ // Clean up the current_funccal chain and the funccal stack.
while (current_funccal != NULL) {
tv_clear(current_funccal->rettv);
cleanup_function_call(current_funccal);
+ if (current_funccal == NULL && funccal_stack != NULL) {
+ restore_funccal();
+ }
}
// First clear what the functions contain. Since this may lower the
@@ -3121,7 +3163,7 @@ bool free_unref_funccal(int copyID, int testing)
if (can_free_funccal(*pfc, copyID)) {
funccall_T *fc = *pfc;
*pfc = fc->caller;
- free_funccal(fc, true);
+ free_funccal_contents(fc);
did_free = true;
did_free_funccal = true;
} else {
@@ -3314,9 +3356,18 @@ bool set_ref_in_call_stack(int copyID)
bool abort = false;
for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
- abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
- abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
+ abort = abort || set_ref_in_funccal(fc, copyID);
}
+
+ // Also go through the funccal_stack.
+ for (funccal_entry_T *entry = funccal_stack; entry != NULL;
+ entry = entry->next) {
+ for (funccall_T *fc = entry->top_funccal; !abort && fc != NULL;
+ fc = fc->caller) {
+ abort = abort || set_ref_in_funccal(fc, copyID);
+ }
+ }
+
return abort;
}
diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h
index ad8e071548..e8ad0bf1da 100644
--- a/src/nvim/eval/userfunc.h
+++ b/src/nvim/eval/userfunc.h
@@ -11,6 +11,12 @@ typedef struct {
dictitem_T *fd_di; ///< Dictionary item used.
} funcdict_T;
+typedef struct funccal_entry funccal_entry_T;
+struct funccal_entry {
+ void *top_funccal;
+ funccal_entry_T *next;
+};
+
/// errors for when calling a function
typedef enum {
ERROR_UNKNOWN = 0,
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index 0e87f7c6c1..1e9e530a42 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -11,6 +11,9 @@
#include "nvim/rbuffer.h"
#include "nvim/macros.h"
#include "nvim/event/stream.h"
+#ifdef WIN32
+# include "nvim/os/os_win_console.h"
+#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/stream.c.generated.h"
@@ -62,6 +65,11 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream)
if (type == UV_TTY) {
uv_tty_init(&loop->uv, &stream->uv.tty, fd, 0);
uv_tty_set_mode(&stream->uv.tty, UV_TTY_MODE_RAW);
+ DWORD dwMode;
+ if (GetConsoleMode(stream->uv.tty.handle, &dwMode)) {
+ dwMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
+ SetConsoleMode(stream->uv.tty.handle, dwMode);
+ }
stream->uvstream = STRUCT_CAST(uv_stream_t, &stream->uv.tty);
} else {
#endif
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 9f4055af8d..7f4b01e306 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -3107,7 +3107,6 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
int retval = FAIL;
static scid_T last_current_SID = 0;
static int last_current_SID_seq = 0;
- void *save_funccalp;
int save_debug_break_level = debug_break_level;
scriptitem_T *si = NULL;
proftime_T wait_start;
@@ -3228,7 +3227,8 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
// Don't use local function variables, if called from a function.
// Also starts profiling timer for nested script.
- save_funccalp = save_funccal();
+ funccal_entry_T funccalp_entry;
+ save_funccal(&funccalp_entry);
// Check if this script was sourced before to finds its SID.
// If it's new, generate a new SID.
@@ -3353,7 +3353,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
}
current_sctx = save_current_sctx;
- restore_funccal(save_funccalp);
+ restore_funccal();
if (l_do_profiling == PROF_YES) {
prof_child_exit(&wait_start); // leaving a child now
}
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 2335aba6dd..f29304867a 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -6736,7 +6736,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
static int nesting = 0;
AutoPatCmd patcmd;
AutoPat *ap;
- void *save_funccalp;
char_u *save_cmdarg;
long save_cmdbang;
static int filechangeshell_busy = FALSE;
@@ -6926,8 +6925,9 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
if (do_profiling == PROF_YES)
prof_child_enter(&wait_time); /* doesn't count for the caller itself */
- /* Don't use local function variables, if called from a function */
- save_funccalp = save_funccal();
+ // Don't use local function variables, if called from a function.
+ funccal_entry_T funccal_entry;
+ save_funccal(&funccal_entry);
/*
* When starting to execute autocommands, save the search patterns.
@@ -7016,9 +7016,10 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
autocmd_bufnr = save_autocmd_bufnr;
autocmd_match = save_autocmd_match;
current_sctx = save_current_sctx;
- restore_funccal(save_funccalp);
- if (do_profiling == PROF_YES)
+ restore_funccal();
+ if (do_profiling == PROF_YES) {
prof_child_exit(&wait_time);
+ }
KeyTyped = save_KeyTyped;
xfree(fname);
xfree(sfname);
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 144646fca2..327ed6d6b7 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -28,6 +28,8 @@
#include "nvim/ascii.h"
#include "nvim/change.h"
#include "nvim/eval/userfunc.h"
+#include "nvim/event/time.h"
+#include "nvim/event/loop.h"
#ifdef WIN32
#include "nvim/os/os.h"
@@ -255,6 +257,101 @@ static struct luaL_Reg regex_meta[] = {
{ NULL, NULL }
};
+// Dummy timer callback. Used by f_wait().
+static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
+{
+}
+
+// Dummy timer close callback. Used by f_wait().
+static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
+{
+ xfree(tw);
+}
+
+static bool nlua_wait_condition(lua_State *lstate, int *status,
+ bool *callback_result)
+{
+ lua_pushvalue(lstate, 2);
+ *status = lua_pcall(lstate, 0, 1, 0);
+ if (*status) {
+ return true; // break on error, but keep error on stack
+ }
+ *callback_result = lua_toboolean(lstate, -1);
+ lua_pop(lstate, 1);
+ return *callback_result; // break if true
+}
+
+/// "vim.wait(timeout, condition[, interval])" function
+static int nlua_wait(lua_State *lstate)
+ FUNC_ATTR_NONNULL_ALL
+{
+ intptr_t timeout = luaL_checkinteger(lstate, 1);
+ if (timeout < 0) {
+ return luaL_error(lstate, "timeout must be > 0");
+ }
+
+ // Check if condition can be called.
+ bool is_function = (lua_type(lstate, 2) == LUA_TFUNCTION);
+
+ // Check if condition is callable table
+ if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) {
+ is_function = (lua_type(lstate, -1) == LUA_TFUNCTION);
+ lua_pop(lstate, 1);
+ }
+
+ if (!is_function) {
+ lua_pushliteral(lstate, "vim.wait: condition must be a function");
+ return lua_error(lstate);
+ }
+
+ intptr_t interval = 200;
+ if (lua_gettop(lstate) >= 3) {
+ interval = luaL_checkinteger(lstate, 3);
+ if (interval < 0) {
+ return luaL_error(lstate, "interval must be > 0");
+ }
+ }
+
+ TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
+
+ // Start dummy timer.
+ time_watcher_init(&main_loop, tw, NULL);
+ tw->events = main_loop.events;
+ tw->blockable = true;
+ time_watcher_start(tw, dummy_timer_due_cb,
+ (uint64_t)interval, (uint64_t)interval);
+
+ int pcall_status = 0;
+ bool callback_result = false;
+
+ LOOP_PROCESS_EVENTS_UNTIL(
+ &main_loop,
+ main_loop.events,
+ (int)timeout,
+ nlua_wait_condition(lstate, &pcall_status, &callback_result) || got_int);
+
+ // Stop dummy timer
+ time_watcher_stop(tw);
+ time_watcher_close(tw, dummy_timer_close_cb);
+
+ if (pcall_status) {
+ return lua_error(lstate);
+ } else if (callback_result) {
+ lua_pushboolean(lstate, 1);
+ lua_pushnil(lstate);
+ } else if (got_int) {
+ got_int = false;
+ vgetc();
+ lua_pushboolean(lstate, 0);
+ lua_pushinteger(lstate, -2);
+ } else {
+ lua_pushboolean(lstate, 0);
+ lua_pushinteger(lstate, -1);
+ }
+
+ return 2;
+}
+
/// Initialize lua interpreter state
///
/// Called by lua interpreter itself to initialize state.
@@ -305,7 +402,6 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
// regex
lua_pushcfunction(lstate, &nlua_regex);
lua_setfield(lstate, -2, "regex");
-
luaL_newmetatable(lstate, "nvim_regex");
luaL_register(lstate, NULL, regex_meta);
lua_pushvalue(lstate, -1); // [meta, meta]
@@ -320,6 +416,10 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, &nlua_rpcnotify);
lua_setfield(lstate, -2, "rpcnotify");
+ // wait
+ lua_pushcfunction(lstate, &nlua_wait);
+ lua_setfield(lstate, -2, "wait");
+
// vim.loop
luv_set_loop(lstate, &main_loop.uv);
luv_set_callback(lstate, nlua_luv_cfpcall);
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 43a0a76b94..523d23ec12 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -415,4 +415,67 @@ do
vim.wo = new_win_opt_accessor(nil)
end
+--- Get a table of lines with start, end columns for a region marked by two points
+---
+--@param bufnr number of buffer
+--@param pos1 (line, column) tuple marking beginning of region
+--@param pos2 (line, column) tuple marking end of region
+--@param regtype type of selection (:help setreg)
+--@param inclusive boolean indicating whether the selection is end-inclusive
+--@return region lua table of the form {linenr = {startcol,endcol}}
+function vim.region(bufnr, pos1, pos2, regtype, inclusive)
+ if not vim.api.nvim_buf_is_loaded(bufnr) then
+ vim.fn.bufload(bufnr)
+ end
+
+ -- in case of block selection, columns need to be adjusted for non-ASCII characters
+ -- TODO: handle double-width characters
+ local bufline
+ if regtype:byte() == 22 then
+ bufline = vim.api.nvim_buf_get_lines(bufnr, pos1[1], pos1[1] + 1, true)[1]
+ pos1[2] = vim.str_utfindex(bufline, pos1[2])
+ end
+
+ local region = {}
+ for l = pos1[1], pos2[1] do
+ local c1, c2
+ if regtype:byte() == 22 then -- block selection: take width from regtype
+ c1 = pos1[2]
+ c2 = c1 + regtype:sub(2)
+ -- and adjust for non-ASCII characters
+ bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1]
+ if c1 < #bufline then
+ c1 = vim.str_byteindex(bufline, c1)
+ end
+ if c2 < #bufline then
+ c2 = vim.str_byteindex(bufline, c2)
+ end
+ else
+ c1 = (l == pos1[1]) and (pos1[2]) or 0
+ c2 = (l == pos2[1]) and (pos2[2] + (inclusive and 1 or 0)) or -1
+ end
+ table.insert(region, l, {c1, c2})
+ end
+ return region
+end
+
+--- Defers calling `fn` until `timeout` ms passes.
+---
+--- Use to do a one-shot timer that calls `fn`
+--@param fn Callback to call once `timeout` expires
+--@param timeout Number of milliseconds to wait before calling `fn`
+--@return timer luv timer object
+function vim.defer_fn(fn, timeout)
+ vim.validate { fn = { fn, 'c', true}; }
+ local timer = vim.loop.new_timer()
+ timer:start(timeout, 0, vim.schedule_wrap(function()
+ timer:stop()
+ timer:close()
+
+ fn()
+ end))
+
+ return timer
+end
+
return module
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 4a9f2371a2..6ac9cdfbae 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -269,6 +269,8 @@ int main(int argc, char **argv)
early_init();
+ set_argv_var(argv, argc); // set v:argv
+
// Check if we have an interactive window.
check_and_set_isatty(&params);
diff --git a/src/nvim/message.c b/src/nvim/message.c
index a12e665099..9aa588e035 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -2574,10 +2574,15 @@ static int do_more_prompt(int typed_char)
msgchunk_T *mp;
int i;
+ // If headless mode is enabled and no input is required, this variable
+ // will be true. However If server mode is enabled, the message "--more--"
+ // should be displayed.
+ bool no_need_more = headless_mode && !embedded_mode;
+
// We get called recursively when a timer callback outputs a message. In
// that case don't show another prompt. Also when at the hit-Enter prompt
// and nothing was typed.
- if (entered || (State == HITRETURN && typed_char == 0)) {
+ if (no_need_more || entered || (State == HITRETURN && typed_char == 0)) {
return false;
}
entered = true;
diff --git a/src/nvim/move.c b/src/nvim/move.c
index d4f82bc601..8a8a639a52 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -996,7 +996,7 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp,
col -= wp->w_leftcol;
- if (col >= 0 && col < width) {
+ if (col >= 0 && col < wp->w_width) {
coloff = col - scol + (local ? 0 : wp->w_wincol) + 1;
} else {
scol = ccol = ecol = 0;
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index a70224f98b..755c1519fd 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -2748,6 +2748,10 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
buf[1] = NUL;
tv_dict_add_str(dict, S_LEN("operator"), buf);
+ // Selection type: visual or not.
+ tv_dict_add_special(dict, S_LEN("visual"),
+ oap->is_VIsual ? kSpecialVarTrue : kSpecialVarFalse);
+
tv_dict_set_keys_readonly(dict);
textlock++;
apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, false, curbuf);
diff --git a/src/nvim/os/os_win_console.c b/src/nvim/os/os_win_console.c
index 8a0aa2f5ae..18dcfeafa0 100644
--- a/src/nvim/os/os_win_console.c
+++ b/src/nvim/os/os_win_console.c
@@ -2,8 +2,14 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include "nvim/vim.h"
+#include "nvim/os/input.h"
#include "nvim/os/os_win_console.h"
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/os_win_console.c.generated.h"
+#endif
+
+
int os_get_conin_fd(void)
{
const HANDLE conin_handle = CreateFile("CONIN$",
@@ -40,3 +46,29 @@ void os_replace_stdout_and_stderr_to_conout(void)
const int conerr_fd = _open_osfhandle((intptr_t)conout_handle, 0);
assert(conerr_fd == STDERR_FILENO);
}
+
+void os_set_vtp(bool enable)
+{
+ static TriState is_legacy = kNone;
+ if (is_legacy == kNone) {
+ uv_tty_vtermstate_t state;
+ uv_tty_get_vterm_state(&state);
+ is_legacy = (state == UV_TTY_UNSUPPORTED) ? kTrue : kFalse;
+ }
+ if (!is_legacy && !os_has_vti()) {
+ uv_tty_set_vterm_state(enable ? UV_TTY_SUPPORTED : UV_TTY_UNSUPPORTED);
+ }
+}
+
+static bool os_has_vti(void)
+{
+ static TriState has_vti = kNone;
+ if (has_vti == kNone) {
+ HANDLE handle = (HANDLE)_get_osfhandle(input_global_fd());
+ DWORD dwMode;
+ if (handle != INVALID_HANDLE_VALUE && GetConsoleMode(handle, &dwMode)) {
+ has_vti = !!(dwMode & ENABLE_VIRTUAL_TERMINAL_INPUT) ? kTrue : kFalse;
+ }
+ }
+ return has_vti == kTrue;
+}
diff --git a/src/nvim/os/os_win_console.h b/src/nvim/os/os_win_console.h
index 154ec83d8a..7b5800afa8 100644
--- a/src/nvim/os/os_win_console.h
+++ b/src/nvim/os/os_win_console.h
@@ -5,4 +5,8 @@
# include "os/os_win_console.h.generated.h"
#endif
+#ifndef ENABLE_VIRTUAL_TERMINAL_INPUT
+# define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
+#endif
+
#endif // NVIM_OS_OS_WIN_CONSOLE_H
diff --git a/src/nvim/os/tty.c b/src/nvim/os/tty.c
index 4f525bed9a..c80ef99084 100644
--- a/src/nvim/os/tty.c
+++ b/src/nvim/os/tty.c
@@ -28,7 +28,7 @@ void os_tty_guess_term(const char **term, int out_fd)
if (winpty) {
// Force TERM=win32con when running in winpty.
*term = "win32con";
- uv_set_vterm_state(UV_UNSUPPORTED);
+ uv_tty_set_vterm_state(UV_TTY_UNSUPPORTED);
return;
}
@@ -55,7 +55,7 @@ void os_tty_guess_term(const char **term, int out_fd)
}
if (conemu_ansi) {
- uv_set_vterm_state(UV_SUPPORTED);
+ uv_tty_set_vterm_state(UV_TTY_SUPPORTED);
}
}
#endif
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 9e958663aa..ba52f5b489 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -2994,6 +2994,12 @@ win_line (
c_final = NUL;
n_extra =
get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, false));
+ if (row == startrow) {
+ n_extra -= win_col_off2(wp);
+ if (n_extra < 0) {
+ n_extra = 0;
+ }
+ }
if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr) {
need_showbreak = false;
}
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index adf7463936..e249d499c4 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -44,6 +44,10 @@ if &lines < 24 || &columns < 80
qa!
endif
+if has('reltime')
+ let s:start_time = reltime()
+endif
+
" Common with all tests on all systems.
source setup.vim
@@ -100,6 +104,9 @@ endfunc
func RunTheTest(test)
echo 'Executing ' . a:test
+ if has('reltime')
+ let func_start = reltime()
+ endif
" Avoid stopping at the "hit enter" prompt
set nomore
@@ -124,7 +131,11 @@ func RunTheTest(test)
endtry
endif
- call add(s:messages, 'Executing ' . a:test)
+ let message = 'Executed ' . a:test
+ if has('reltime')
+ let message ..= ' in ' .. reltimestr(reltime(func_start)) .. ' seconds'
+ endif
+ call add(s:messages, message)
let s:done += 1
if a:test =~ 'Test_nocatch_'
@@ -230,6 +241,9 @@ func FinishTesting()
else
let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
endif
+ if has('reltime')
+ let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds'
+ endif
echo message
call add(s:messages, message)
if s:fail > 0
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 954e5d875f..5217aa7339 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -1246,23 +1246,23 @@ func Test_TextYankPost()
norm "ayiw
call assert_equal(
- \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'a', 'operator': 'y', 'regtype': 'v'},
+ \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'a', 'operator': 'y', 'visual': v:false, 'regtype': 'v'},
\g:event)
norm y_
call assert_equal(
- \{'regcontents': ['foo'], 'inclusive': v:false, 'regname': '', 'operator': 'y', 'regtype': 'V'},
+ \{'regcontents': ['foo'], 'inclusive': v:false, 'regname': '', 'operator': 'y', 'visual': v:false, 'regtype': 'V'},
\g:event)
call feedkeys("\<C-V>y", 'x')
call assert_equal(
- \{'regcontents': ['f'], 'inclusive': v:true, 'regname': '', 'operator': 'y', 'regtype': "\x161"},
+ \{'regcontents': ['f'], 'inclusive': v:true, 'regname': '', 'operator': 'y', 'visual': v:true, 'regtype': "\x161"},
\g:event)
norm "xciwbar
call assert_equal(
- \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'x', 'operator': 'c', 'regtype': 'v'},
+ \{'regcontents': ['foo'], 'inclusive': v:true, 'regname': 'x', 'operator': 'c', 'visual': v:false, 'regtype': 'v'},
\g:event)
norm "bdiw
call assert_equal(
- \{'regcontents': ['bar'], 'inclusive': v:true, 'regname': 'b', 'operator': 'd', 'regtype': 'v'},
+ \{'regcontents': ['bar'], 'inclusive': v:true, 'regname': 'b', 'operator': 'd', 'visual': v:false, 'regtype': 'v'},
\g:event)
call assert_equal({}, v:event)
diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim
index 5675bf74dd..a4c1f62a43 100644
--- a/src/nvim/testdir/test_breakindent.vim
+++ b/src/nvim/testdir/test_breakindent.vim
@@ -373,3 +373,52 @@ func Test_breakindent19_sbr_nextpage()
call s:compare_lines(expect, lines)
call s:close_windows('set breakindent& briopt& sbr&')
endfunc
+
+func Test_breakindent20_cpo_n_nextpage()
+ let s:input = ""
+ call s:test_windows('setl breakindent briopt=min:14 cpo+=n number')
+ call setline(1, repeat('a', 200))
+ norm! 1gg
+ redraw!
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ " 1 aaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " Scroll down one screen line
+ setl scrolloff=5
+ norm! 5gj
+ redraw!
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ "--1 aaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+
+ setl briopt+=shift:2
+ norm! 1gg
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ " 1 aaaaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+ " Scroll down one screen line
+ norm! 5gj
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ "--1 aaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaa",
+ \ " aaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
+
+ call s:close_windows('set breakindent& briopt& cpo& number&')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim
index e8e561dfd8..2e190911b2 100644
--- a/src/nvim/testdir/test_cursor_func.vim
+++ b/src/nvim/testdir/test_cursor_func.vim
@@ -93,3 +93,18 @@ func Test_screenpos()
close
bwipe!
endfunc
+
+func Test_screenpos_number()
+ rightbelow new
+ rightbelow 73vsplit
+ call setline (1, repeat('x', 66))
+ setlocal number
+ redraw
+ let winid = win_getid()
+ let [winrow, wincol] = win_screenpos(winid)
+ let pos = screenpos(winid, 1, 66)
+ call assert_equal(winrow, pos.row)
+ call assert_equal(wincol + 66 + 3, pos.col)
+ close
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index ace56a375f..832f1726fb 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -151,6 +151,7 @@ let s:filename_checks = {
\ 'ecd': ['file.ecd'],
\ 'edif': ['file.edf', 'file.edif', 'file.edo'],
\ 'elinks': ['/etc/elinks.conf', '/.elinks/elinks.conf'],
+ \ 'elm': ['file.elm'],
\ 'elmfilt': ['filter-rules'],
\ 'erlang': ['file.erl', 'file.hrl', 'file.yaws'],
\ 'eruby': ['file.erb', 'file.rhtml'],
diff --git a/src/nvim/testdir/test_match.vim b/src/nvim/testdir/test_match.vim
index 7a1894bc16..09448ca71b 100644
--- a/src/nvim/testdir/test_match.vim
+++ b/src/nvim/testdir/test_match.vim
@@ -149,6 +149,21 @@ function Test_match()
highlight MyGroup3 NONE
endfunc
+func Test_match_error()
+ call assert_fails('match Error', 'E475:')
+ call assert_fails('match Error /', 'E475:')
+ call assert_fails('4match Error /x/', 'E476:')
+ call assert_fails('match Error /x/ x', 'E488:')
+endfunc
+
+func Test_matchadd_error()
+ call assert_fails("call matchadd('GroupDoesNotExist', 'X')", 'E28:')
+ call assert_fails("call matchadd('Search', '\\(')", 'E475:')
+ call assert_fails("call matchadd('Search', 'XXX', 1, 123, 1)", 'E715:')
+ call assert_fails("call matchadd('Error', 'XXX', 1, 3)", 'E798:')
+ call assert_fails("call matchadd('Error', 'XXX', 1, 0)", 'E799:')
+endfunc
+
func Test_matchaddpos()
syntax on
set hlsearch
@@ -263,6 +278,17 @@ func Test_matchaddpos_using_negative_priority()
set hlsearch&
endfunc
+func Test_matchaddpos_error()
+ call assert_fails("call matchaddpos('Error', 1)", 'E686:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 1)", 'E798:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 2)", 'E798:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 0)", 'E799:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 123, 1)", 'E715:')
+ call assert_fails("call matchaddpos('Error', [1], 1, 5, {'window':12345})", 'E957:')
+ " Why doesn't the following error have an error code E...?
+ call assert_fails("call matchaddpos('Error', [{}])", 'E5031:')
+endfunc
+
func OtherWindowCommon()
let lines =<< trim END
call setline(1, 'Hello Vim world')
@@ -288,6 +314,11 @@ func Test_matchdelete_other_window()
call delete('XscriptMatchCommon')
endfunc
+func Test_matchdelete_error()
+ call assert_fails("call matchdelete(0)", 'E802:')
+ call assert_fails("call matchdelete(1, -1)", 'E957:')
+endfunc
+
func Test_matchclear_other_window()
if !CanRunVimInTerminal()
throw 'Skipped: cannot make screendumps'
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index 41f1710faf..400af33c58 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -42,6 +42,13 @@ function Test_wildchar()
set wildchar&
endfunction
+func Test_wildoptions()
+ set wildoptions=
+ set wildoptions+=tagfile
+ set wildoptions+=tagfile
+ call assert_equal('tagfile', &wildoptions)
+endfunc
+
function! Test_options()
let caught = 'ok'
try
diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim
index f03c493275..9abaca5957 100644
--- a/src/nvim/testdir/test_startup.vim
+++ b/src/nvim/testdir/test_startup.vim
@@ -584,3 +584,12 @@ func Test_start_with_tabs()
" clean up
call StopVimInTerminal(buf)
endfunc
+
+func Test_v_argv()
+ let out = system(GetVimCommand() . ' -es -V1 -X arg1 --cmd "echo v:argv" --cmd q')
+ let list = split(out, "', '")
+ call assert_match('vim', list[0])
+ let idx = index(list, 'arg1')
+ call assert_true(idx > 2)
+ call assert_equal(['arg1', '--cmd', 'echo v:argv', '--cmd', 'q'']'], list[idx:])
+endfunc
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 2c4d02812b..b4d91a01fc 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -33,6 +33,9 @@
#include "nvim/os/os.h"
#include "nvim/os/signal.h"
#include "nvim/os/tty.h"
+#ifdef WIN32
+# include "nvim/os/os_win_console.h"
+#endif
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui_bridge.h"
@@ -1015,8 +1018,22 @@ static void tui_mouse_on(UI *ui)
{
TUIData *data = ui->data;
if (!data->mouse_enabled) {
+#ifdef WIN32
+ // Windows versions with vtp(ENABLE_VIRTUAL_TERMINAL_PROCESSING) and
+ // no vti(ENABLE_VIRTUAL_TERMINAL_INPUT) will need to use mouse traking of
+ // libuv. For this reason, vtp (vterm) state of libuv is temporarily
+ // disabled because the control sequence needs to be processed by libuv
+ // instead of Windows vtp.
+ // ref. https://docs.microsoft.com/en-us/windows/console/setconsolemode
+ flush_buf(ui);
+ os_set_vtp(false);
+#endif
unibi_out_ext(ui, data->unibi_ext.enable_mouse);
data->mouse_enabled = true;
+#ifdef WIN32
+ flush_buf(ui);
+ os_set_vtp(true);
+#endif
}
}
@@ -1024,8 +1041,22 @@ static void tui_mouse_off(UI *ui)
{
TUIData *data = ui->data;
if (data->mouse_enabled) {
+#ifdef WIN32
+ // Windows versions with vtp(ENABLE_VIRTUAL_TERMINAL_PROCESSING) and
+ // no vti(ENABLE_VIRTUAL_TERMINAL_INPUT) will need to use mouse traking of
+ // libuv. For this reason, vtp (vterm) state of libuv is temporarily
+ // disabled because the control sequence needs to be processed by libuv
+ // instead of Windows vtp.
+ // ref. https://docs.microsoft.com/en-us/windows/console/setconsolemode
+ flush_buf(ui);
+ os_set_vtp(false);
+#endif
unibi_out_ext(ui, data->unibi_ext.disable_mouse);
data->mouse_enabled = false;
+#ifdef WIN32
+ flush_buf(ui);
+ os_set_vtp(true);
+#endif
}
}
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 2e9d0f57ac..debdfb9e9a 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -1808,7 +1808,7 @@ describe('API', function()
eq({id=1}, meths.get_current_buf())
end)
- it("doesn't cause BufEnter or BufWinEnter autocmds", function()
+ it("does not trigger BufEnter, BufWinEnter", function()
command("let g:fired = v:false")
command("au BufEnter,BufWinEnter * let g:fired = v:true")
@@ -1818,7 +1818,7 @@ describe('API', function()
eq(false, eval('g:fired'))
end)
- it('|scratch-buffer|', function()
+ it('scratch-buffer', function()
eq({id=2}, meths.create_buf(false, true))
eq({id=3}, meths.create_buf(true, true))
eq({id=4}, meths.create_buf(true, true))
@@ -1845,6 +1845,7 @@ describe('API', function()
eq('nofile', meths.buf_get_option(b, 'buftype'))
eq('hide', meths.buf_get_option(b, 'bufhidden'))
eq(false, meths.buf_get_option(b, 'swapfile'))
+ eq(false, meths.buf_get_option(b, 'modeline'))
end
--
@@ -1860,8 +1861,9 @@ describe('API', function()
eq('nofile', meths.buf_get_option(edited_buf, 'buftype'))
eq('hide', meths.buf_get_option(edited_buf, 'bufhidden'))
eq(false, meths.buf_get_option(edited_buf, 'swapfile'))
+ eq(false, meths.buf_get_option(edited_buf, 'modeline'))
- -- scratch buffer can be wiped without error
+ -- Scratch buffer can be wiped without error.
command('bwipe')
screen:expect([[
^ |
diff --git a/test/functional/autocmd/textyankpost_spec.lua b/test/functional/autocmd/textyankpost_spec.lua
index 8c23b72cff..3898d59e58 100644
--- a/test/functional/autocmd/textyankpost_spec.lua
+++ b/test/functional/autocmd/textyankpost_spec.lua
@@ -27,7 +27,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(1, eval('g:count'))
@@ -40,7 +41,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'baz ' },
regname = '',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
eq(2, eval('g:count'))
@@ -50,7 +52,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo', 'baz' },
regname = '',
- regtype = "\0223" -- ^V + block width
+ regtype = "\0223", -- ^V + block width
+ visual = true
}, eval('g:event'))
eq(3, eval('g:count'))
end)
@@ -62,7 +65,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
command('set debug=msg')
@@ -92,7 +96,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(1, eval('g:count'))
eq({ 'foo\nbar' }, funcs.getreg('+',1,1))
@@ -105,7 +110,8 @@ describe('TextYankPost', function()
operator = 'd',
regcontents = { 'foo' },
regname = '',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
eq(1, eval('g:count'))
@@ -115,7 +121,8 @@ describe('TextYankPost', function()
operator = 'd',
regcontents = { '\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(2, eval('g:count'))
@@ -125,7 +132,8 @@ describe('TextYankPost', function()
operator = 'c',
regcontents = { 'baz' },
regname = '',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
eq(3, eval('g:count'))
end)
@@ -153,7 +161,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'bar' },
regname = 'b',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
feed('"*yy')
@@ -162,7 +171,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '*',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
command("set clipboard=unnamed")
@@ -174,7 +184,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
feed('"*yy')
@@ -183,7 +194,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'foo\nbar' },
regname = '*',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
end)
@@ -194,7 +206,8 @@ describe('TextYankPost', function()
operator = 'd',
regcontents = { 'foo\nbar' },
regname = '+',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(1, eval('g:count'))
@@ -204,7 +217,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'baz text' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(2, eval('g:count'))
@@ -214,7 +228,8 @@ describe('TextYankPost', function()
operator = 'y',
regcontents = { 'baz ' },
regname = '',
- regtype = 'v'
+ regtype = 'v',
+ visual = false
}, eval('g:event'))
eq(3, eval('g:count'))
@@ -224,7 +239,8 @@ describe('TextYankPost', function()
operator = 'd',
regcontents = { 'baz text' },
regname = '',
- regtype = 'V'
+ regtype = 'V',
+ visual = false
}, eval('g:event'))
eq(4, eval('g:count'))
end)
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
index cc10d36a10..3269fbc68d 100644
--- a/test/functional/core/startup_spec.lua
+++ b/test/functional/core/startup_spec.lua
@@ -277,6 +277,32 @@ describe('startup', function()
[4] = {bold = true, foreground = Screen.colors.Blue1},
}})
end)
+
+ it('fixed hang issue with --headless (#11386)', function()
+ local expected = ''
+ local period = 100
+ for i = 1, period - 1 do
+ expected = expected .. i .. '\r\n'
+ end
+ expected = expected .. period
+ eq(
+ expected,
+ -- FIXME(codehex): We should really set a timeout for the system function.
+ -- If this test fails, there will be a waiting input state.
+ funcs.system({nvim_prog, '-u', 'NONE', '-c',
+ 'for i in range(1, 100) | echo i | endfor | quit',
+ '--headless'
+ })
+ )
+ end)
+
+ it("get command line arguments from v:argv", function()
+ local out = funcs.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless',
+ '--cmd', nvim_set,
+ '-c', [[echo v:argv[-1:] len(v:argv) > 1]],
+ '+q' })
+ eq('[\'+q\'] 1', out)
+ end)
end)
describe('sysinit', function()
diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua
new file mode 100644
index 0000000000..28ca749749
--- /dev/null
+++ b/test/functional/legacy/memory_usage_spec.lua
@@ -0,0 +1,161 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local eval = helpers.eval
+local eq = helpers.eq
+local feed_command = helpers.feed_command
+local iswin = helpers.iswin
+local retry = helpers.retry
+local ok = helpers.ok
+local source = helpers.source
+
+local monitor_memory_usage = {
+ memory_usage = function(self)
+ local handle
+ if iswin() then
+ handle = io.popen('wmic process where processid=' ..self.pid..' get WorkingSetSize')
+ else
+ handle = io.popen('ps -o rss= -p '..self.pid)
+ end
+ return tonumber(handle:read('*a'):match('%d+'))
+ end,
+ op = function(self)
+ retry(nil, 10000, function()
+ local val = self.memory_usage(self)
+ if self.max < val then
+ self.max = val
+ end
+ table.insert(self.hist, val)
+ ok(#self.hist > 20)
+ local result = {}
+ for key,value in ipairs(self.hist) do
+ if value ~= self.hist[key + 1] then
+ table.insert(result, value)
+ end
+ end
+ table.remove(self.hist, 1)
+ self.last = self.hist[#self.hist]
+ eq(#result, 1)
+ end)
+ end,
+ dump = function(self)
+ return 'max: '..self.max ..', last: '..self.last
+ end,
+ monitor_memory_usage = function(self, pid)
+ local obj = {
+ pid = pid,
+ max = 0,
+ last = 0,
+ hist = {},
+ }
+ setmetatable(obj, { __index = self })
+ obj:op()
+ return obj
+ end
+}
+setmetatable(monitor_memory_usage,
+{__call = function(self, pid)
+ return monitor_memory_usage.monitor_memory_usage(self, pid)
+end})
+
+describe('memory usage', function()
+ local function check_result(tbl, status, result)
+ if not status then
+ print('')
+ for key, val in pairs(tbl) do
+ print(key, val:dump())
+ end
+ error(result)
+ end
+ end
+
+ local function isasan()
+ local version = eval('execute("version")')
+ return version:match('-fsanitize=[a-z,]*address')
+ end
+
+ before_each(clear)
+
+ --[[
+ Case: if a local variable captures a:000, funccall object will be free
+ just after it finishes.
+ ]]--
+ it('function capture vargs', function()
+ if isasan() then
+ pending('ASAN build is difficult to estimate memory usage')
+ end
+ if iswin() and eval("executable('wmic')") == 0 then
+ pending('missing "wmic" command')
+ elseif eval("executable('ps')") == 0 then
+ pending('missing "ps" command')
+ end
+
+ local pid = eval('getpid()')
+ local before = monitor_memory_usage(pid)
+ source([[
+ func s:f(...)
+ let x = a:000
+ endfunc
+ for _ in range(10000)
+ call s:f(0)
+ endfor
+ ]])
+ local after = monitor_memory_usage(pid)
+ -- Estimate the limit of max usage as 2x initial usage.
+ -- The lower limit can fluctuate a bit, use 97%.
+ check_result({before=before, after=after},
+ pcall(ok, before.last * 97 / 100 < after.max))
+ check_result({before=before, after=after},
+ pcall(ok, before.last * 2 > after.max))
+ -- In this case, garbage collecting is not needed.
+ -- The value might fluctuate a bit, allow for 3% tolerance below and 5% above.
+ -- Based on various test runs.
+ local lower = after.last * 97 / 100
+ local upper = after.last * 105 / 100
+ check_result({before=before, after=after}, pcall(ok, lower < after.max))
+ check_result({before=before, after=after}, pcall(ok, after.max < upper))
+ end)
+
+ --[[
+ Case: if a local variable captures l: dict, funccall object will not be
+ free until garbage collector runs, but after that memory usage doesn't
+ increase so much even when rerun Xtest.vim since system memory caches.
+ ]]--
+ it('function capture lvars', function()
+ if isasan() then
+ pending('ASAN build is difficult to estimate memory usage')
+ end
+ if iswin() and eval("executable('wmic')") == 0 then
+ pending('missing "wmic" command')
+ elseif eval("executable('ps')") == 0 then
+ pending('missing "ps" command')
+ end
+
+ local pid = eval('getpid()')
+ local before = monitor_memory_usage(pid)
+ local fname = source([[
+ if !exists('s:defined_func')
+ func s:f()
+ let x = l:
+ endfunc
+ endif
+ let s:defined_func = 1
+ for _ in range(10000)
+ call s:f()
+ endfor
+ ]])
+ local after = monitor_memory_usage(pid)
+ for _ = 1, 3 do
+ feed_command('so '..fname)
+ end
+ local last = monitor_memory_usage(pid)
+ -- The usage may be a bit less than the last value, use 80%.
+ -- Allow for 20% tolerance at the upper limit. That's very permissive, but
+ -- otherwise the test fails sometimes.
+ local lower = before.last * 8 / 10
+ local upper = (after.max + (after.last - before.last)) * 12 / 10
+ check_result({before=before, after=after, last=last},
+ pcall(ok, lower < last.last))
+ check_result({before=before, after=after, last=last},
+ pcall(ok, last.last < upper))
+ end)
+end)
diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua
index a3b8e685e1..f782769935 100644
--- a/test/functional/lua/uri_spec.lua
+++ b/test/functional/lua/uri_spec.lua
@@ -112,6 +112,29 @@ describe('URI methods', function()
eq('C:\\xy\\åäö\\ɧ\\汉语\\↥\\🤦\\🦄\\å\\بِيَّ.txt', exec_lua(test_case))
end)
end)
+
+ describe('decode non-file URI', function()
+ it('uri_to_fname returns non-file URI unchanged', function()
+ eq('jdt1.23+x-z://content/%5C/', exec_lua [[
+ return vim.uri_to_fname('jdt1.23+x-z://content/%5C/')
+ ]])
+ end)
+
+ it('uri_to_fname returns non-file upper-case scheme URI unchanged', function()
+ eq('JDT://content/%5C/', exec_lua [[
+ return vim.uri_to_fname('JDT://content/%5C/')
+ ]])
+ end)
+ end)
+
+ describe('decode URI without scheme', function()
+ it('fails because URI must have a scheme', function()
+ eq(false, exec_lua [[
+ return pcall(vim.uri_to_fname, 'not_an_uri.txt')
+ ]])
+ end)
+ end)
+
end)
describe('uri to bufnr', function()
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
index 46ae56955b..61bc3e1e3c 100644
--- a/test/functional/lua/vim_spec.lua
+++ b/test/functional/lua/vim_spec.lua
@@ -243,6 +243,7 @@ describe('lua stdlib', function()
{ "here be dragons", " ", false, { "here", "be", "dragons"} },
{ "axaby", "ab?", false, { '', 'x', 'y' } },
{ "f v2v v3v w2w ", "([vw])2%1", false, { 'f ', ' v3v ', ' ' } },
+ { "", "", false, {} },
{ "x*yz*oo*l", "*", true, { 'x', 'yz', 'oo', 'l' } },
}
@@ -1046,4 +1047,196 @@ describe('lua stdlib', function()
eq({}, exec_lua[[return {re1:match_line(0, 1, 1, 7)}]])
eq({0,3}, exec_lua[[return {re1:match_line(0, 1, 0, 7)}]])
end)
+
+ it('vim.defer_fn', function()
+ eq(false, exec_lua [[
+ vim.g.test = false
+ vim.defer_fn(function() vim.g.test = true end, 150)
+ return vim.g.test
+ ]])
+ exec_lua [[vim.wait(1000, function() return vim.g.test end)]]
+ eq(true, exec_lua[[return vim.g.test]])
+ end)
+
+ it('vim.region', function()
+ helpers.insert(helpers.dedent( [[
+ text tααt tααt text
+ text tαxt txtα tex
+ text tαxt tαxt
+ ]]))
+ eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]])
+ end)
+
+ describe('vim.wait', function()
+ before_each(function()
+ exec_lua[[
+ -- high precision timer
+ get_time = function()
+ return vim.fn.reltimefloat(vim.fn.reltime())
+ end
+ ]]
+ end)
+
+ it('should run from lua', function()
+ exec_lua[[vim.wait(100, function() return true end)]]
+ end)
+
+ it('should wait the expected time if false', function()
+ eq({time = true, wait_result = {false, -1}}, exec_lua[[
+ start_time = get_time()
+ wait_succeed, wait_fail_val = vim.wait(200, function() return false end)
+
+ return {
+ -- 150ms waiting or more results in true. Flaky tests are bad.
+ time = (start_time + 0.15) < get_time(),
+ wait_result = {wait_succeed, wait_fail_val}
+ }
+ ]])
+ end)
+
+
+ it('should not block other events', function()
+ eq({time = true, wait_result = true}, exec_lua[[
+ start_time = get_time()
+
+ vim.g.timer_result = false
+ timer = vim.loop.new_timer()
+ timer:start(100, 0, vim.schedule_wrap(function()
+ vim.g.timer_result = true
+ end))
+
+ -- Would wait ten seconds if results blocked.
+ wait_result = vim.wait(10000, function() return vim.g.timer_result end)
+
+ return {
+ time = (start_time + 5) > get_time(),
+ wait_result = wait_result,
+ }
+ ]])
+ end)
+
+ it('should work with vim.defer_fn', function()
+ eq({time = true, wait_result = true}, exec_lua[[
+ start_time = get_time()
+
+ vim.defer_fn(function() vim.g.timer_result = true end, 100)
+ wait_result = vim.wait(10000, function() return vim.g.timer_result end)
+
+ return {
+ time = (start_time + 5) > get_time(),
+ wait_result = wait_result,
+ }
+ ]])
+ end)
+
+ it('should require functions to be passed', function()
+ local pcall_result = exec_lua [[
+ return {pcall(function() vim.wait(1000, 13) end)}
+ ]]
+
+ eq(pcall_result[1], false)
+ matches('condition must be a function', pcall_result[2])
+ end)
+
+ it('should not crash when callback errors', function()
+ local pcall_result = exec_lua [[
+ return {pcall(function() vim.wait(1000, function() error("As Expected") end) end)}
+ ]]
+
+ eq(pcall_result[1], false)
+ matches('As Expected', pcall_result[2])
+ end)
+
+ it('should call callbacks exactly once if they return true immediately', function()
+ eq(true, exec_lua [[
+ vim.g.wait_count = 0
+ vim.wait(1000, function()
+ vim.g.wait_count = vim.g.wait_count + 1
+ return true
+ end, 20)
+ return vim.g.wait_count == 1
+ ]])
+ end)
+
+ it('should call callbacks few times with large `interval`', function()
+ eq(true, exec_lua [[
+ vim.g.wait_count = 0
+ vim.wait(50, function() vim.g.wait_count = vim.g.wait_count + 1 end, 200)
+ return vim.g.wait_count < 5
+ ]])
+ end)
+
+ it('should call callbacks more times with small `interval`', function()
+ eq(true, exec_lua [[
+ vim.g.wait_count = 0
+ vim.wait(50, function() vim.g.wait_count = vim.g.wait_count + 1 end, 5)
+ return vim.g.wait_count > 5
+ ]])
+ end)
+
+ it('should play nice with `not` when fails', function()
+ eq(true, exec_lua [[
+ if not vim.wait(50, function() end) then
+ return true
+ end
+
+ return false
+ ]])
+ end)
+
+ it('should play nice with `if` when success', function()
+ eq(true, exec_lua [[
+ if vim.wait(50, function() return true end) then
+ return true
+ end
+
+ return false
+ ]])
+ end)
+
+ it('should return immediately with false if timeout is 0', function()
+ eq({false, -1}, exec_lua [[
+ return {
+ vim.wait(0, function() return false end)
+ }
+ ]])
+ end)
+
+ it('should work with tables with __call', function()
+ eq(true, exec_lua [[
+ local t = setmetatable({}, {__call = function(...) return true end})
+ return vim.wait(100, t, 10)
+ ]])
+ end)
+
+ it('should work with tables with __call that change', function()
+ eq(true, exec_lua [[
+ local t = {count = 0}
+ setmetatable(t, {
+ __call = function()
+ t.count = t.count + 1
+ return t.count > 3
+ end
+ })
+
+ return vim.wait(1000, t, 10)
+ ]])
+ end)
+
+ it('should not work with negative intervals', function()
+ local pcall_result = exec_lua [[
+ return pcall(function() vim.wait(1000, function() return false end, -1) end)
+ ]]
+
+ eq(false, pcall_result)
+ end)
+
+ it('should not work with weird intervals', function()
+ local pcall_result = exec_lua [[
+ return pcall(function() vim.wait(1000, function() return false end, 'a string value') end)
+ ]]
+
+ eq(false, pcall_result)
+ end)
+ end)
end)
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index 5fe44d1bf2..ea0f52c85b 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -6,6 +6,7 @@ local buf_lines = helpers.buf_lines
local dedent = helpers.dedent
local exec_lua = helpers.exec_lua
local eq = helpers.eq
+local pcall_err = helpers.pcall_err
local pesc = helpers.pesc
local insert = helpers.insert
local retry = helpers.retry
@@ -705,7 +706,6 @@ describe('LSP', function()
end;
}
end)
-
end)
describe("parsing tests", function()
@@ -733,7 +733,23 @@ describe('LSP', function()
end;
}
end)
+ end)
+ describe('lsp._cmd_parts test', function()
+ local function _cmd_parts(input)
+ return exec_lua([[
+ lsp = require('vim.lsp')
+ return lsp._cmd_parts(...)
+ ]], input)
+ end
+ it('should valid cmd argument', function()
+ eq(true, pcall(_cmd_parts, {"nvim"}))
+ eq(true, pcall(_cmd_parts, {"nvim", "--head"}))
+ end)
+ it('should invalid cmd argument', function()
+ eq('Error executing lua: .../shared.lua: cmd: expected list, got nvim', pcall_err(_cmd_parts, "nvim"))
+ eq('Error executing lua: .../shared.lua: cmd argument: expected string, got number', pcall_err(_cmd_parts, {"nvim", 1}))
+ end)
end)
end)
@@ -957,7 +973,14 @@ describe('LSP', function()
{ label='foocar', insertText='foobar', textEdit={} },
-- resolves into textEdit.newText
{ label='foocar', insertText='foodar', textEdit={newText='foobar'} },
- { label='foocar', textEdit={newText='foobar'} }
+ { label='foocar', textEdit={newText='foobar'} },
+ -- real-world snippet text
+ { label='foocar', insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} },
+ { label='foocar', insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} },
+ -- nested snippet tokens
+ { label='foocar', insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} },
+ -- plain text
+ { label='foocar', insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} },
}
local completion_list_items = {items=completion_list}
local expected = {
@@ -967,6 +990,10 @@ describe('LSP', function()
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foobar', textEdit={} } } } } },
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar', textEdit={newText='foobar'} } } } } },
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', textEdit={newText='foobar'} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2,typ3 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } },
}
eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list, prefix))
diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
index 153028bb2b..5d22332fae 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -135,12 +135,11 @@ include(ExternalProject)
if(WIN32)
# "nvim" branch of https://github.com/neovim/libuv
- set(LIBUV_URL https://github.com/neovim/libuv/archive/d5ff3004d26b9bb863b76247399a9c72a0ff184c.tar.gz)
- set(LIBUV_SHA256 0f5dfd92269713ed275273966ed73578fc68db669c509b01210cd58c1cf6361d)
+ set(LIBUV_URL https://github.com/neovim/libuv/archive/b899d12b0d56d217f31222da83f8c398355b69ef.tar.gz)
+ set(LIBUV_SHA256 eb7e37b824887e1b31a4e31d1d9bad4c03d8b98532d9cce5f67a3b70495a4b2a)
else()
- # blueyed/nvim-fixes (for *BSD build fixes).
- set(LIBUV_URL https://github.com/blueyed/libuv/archive/2af4cf2.tar.gz)
- set(LIBUV_SHA256 SKIP)
+ set(LIBUV_URL https://github.com/libuv/libuv/archive/v1.34.2.tar.gz)
+ set(LIBUV_SHA256 0d9d38558b45c006c1ea4e8529bae64caf8becda570295ea74e3696362aeb7f2)
endif()
set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/cpp-3.0.0/msgpack-3.0.0.tar.gz)