aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.builds/openbsd.yml10
-rw-r--r--.github/ISSUE_TEMPLATE/lsp_bug_report.md46
-rw-r--r--config/CMakeLists.txt2
-rw-r--r--runtime/autoload/health/provider.vim2
-rw-r--r--runtime/autoload/provider.vim5
-rw-r--r--runtime/autoload/provider/pythonx.vim4
-rw-r--r--runtime/autoload/remote/define.vim2
-rw-r--r--runtime/doc/autocmd.txt1
-rw-r--r--runtime/doc/editing.txt4
-rw-r--r--runtime/doc/eval.txt13
-rw-r--r--runtime/doc/lsp.txt25
-rw-r--r--runtime/doc/lua.txt70
-rw-r--r--runtime/filetype.vim10
-rw-r--r--runtime/lua/vim/highlight.lua29
-rw-r--r--runtime/lua/vim/lsp.lua69
-rw-r--r--runtime/lua/vim/lsp/callbacks.lua17
-rw-r--r--runtime/lua/vim/lsp/log.lua2
-rw-r--r--runtime/lua/vim/lsp/protocol.lua28
-rw-r--r--runtime/lua/vim/lsp/util.lua311
-rw-r--r--runtime/lua/vim/shared.lua2
-rw-r--r--runtime/lua/vim/treesitter.lua5
-rw-r--r--runtime/lua/vim/uri.lua10
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim21
-rwxr-xr-xscripts/update-ts-runtime.sh39
-rwxr-xr-xscripts/vim-patch.sh2
-rw-r--r--src/nvim/api/private/helpers.c4
-rw-r--r--src/nvim/api/vim.c9
-rw-r--r--src/nvim/buffer.c11
-rw-r--r--src/nvim/channel.c4
-rw-r--r--src/nvim/eval.c101
-rw-r--r--src/nvim/eval.h1
-rw-r--r--src/nvim/eval/decode.c12
-rw-r--r--src/nvim/eval/encode.c7
-rw-r--r--src/nvim/eval/encode.h1
-rw-r--r--src/nvim/eval/executor.c4
-rw-r--r--src/nvim/eval/funcs.c63
-rw-r--r--src/nvim/eval/typval.c44
-rw-r--r--src/nvim/eval/typval.h10
-rw-r--r--src/nvim/eval/typval_encode.c.h17
-rw-r--r--src/nvim/eval/userfunc.c160
-rw-r--r--src/nvim/eval/userfunc.h6
-rw-r--r--src/nvim/event/libuv_process.c7
-rw-r--r--src/nvim/event/process.h2
-rw-r--r--src/nvim/event/stream.c8
-rw-r--r--src/nvim/ex_cmds2.c6
-rw-r--r--src/nvim/ex_getln.c10
-rw-r--r--src/nvim/fileio.c11
-rw-r--r--src/nvim/globals.h4
-rw-r--r--src/nvim/keymap.c4
-rw-r--r--src/nvim/lua/converter.c8
-rw-r--r--src/nvim/lua/executor.c102
-rw-r--r--src/nvim/main.c9
-rw-r--r--src/nvim/message.c12
-rw-r--r--src/nvim/move.c2
-rw-r--r--src/nvim/normal.c15
-rw-r--r--src/nvim/ops.c8
-rw-r--r--src/nvim/os/input.c9
-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/signal.c1
-rw-r--r--src/nvim/os/tty.c4
-rw-r--r--src/nvim/popupmnu.c4
-rw-r--r--src/nvim/regexp_nfa.c6
-rw-r--r--src/nvim/screen.c6
-rw-r--r--src/nvim/syntax.c4
-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_cmdline.vim16
-rw-r--r--src/nvim/testdir/test_cursor_func.vim15
-rw-r--r--src/nvim/testdir/test_filetype.vim3
-rw-r--r--src/nvim/testdir/test_ga.vim1
-rw-r--r--src/nvim/testdir/test_maparg.vim8
-rw-r--r--src/nvim/testdir/test_match.vim31
-rw-r--r--src/nvim/testdir/test_options.vim22
-rw-r--r--src/nvim/testdir/test_startup.vim9
-rw-r--r--src/nvim/tui/tui.c31
-rw-r--r--src/nvim/vim.h1
-rw-r--r--src/tree_sitter/README.md16
-rw-r--r--src/tree_sitter/alloc.h1
-rw-r--r--src/tree_sitter/language.c4
-rw-r--r--src/tree_sitter/language.h2
-rw-r--r--src/tree_sitter/parser.c32
-rw-r--r--src/tree_sitter/parser.h50
-rw-r--r--src/tree_sitter/query.c983
-rw-r--r--src/tree_sitter/stack.c2
-rw-r--r--src/tree_sitter/subtree.c12
-rw-r--r--src/tree_sitter/treesitter_commit_hash.txt1
-rw-r--r--test/functional/api/keymap_spec.lua4
-rw-r--r--test/functional/api/vim_spec.lua13
-rw-r--r--test/functional/autocmd/textyankpost_spec.lua48
-rw-r--r--test/functional/core/startup_spec.lua26
-rw-r--r--test/functional/eval/buf_functions_spec.lua6
-rw-r--r--test/functional/eval/map_functions_spec.lua2
-rw-r--r--test/functional/eval/sort_spec.lua3
-rw-r--r--test/functional/eval/uniq_spec.lua2
-rw-r--r--test/functional/legacy/075_maparg_spec.lua6
-rw-r--r--test/functional/legacy/memory_usage_spec.lua161
-rw-r--r--test/functional/lua/treesitter_spec.lua8
-rw-r--r--test/functional/lua/uri_spec.lua23
-rw-r--r--test/functional/lua/vim_spec.lua180
-rw-r--r--test/functional/plugin/lsp_spec.lua148
-rw-r--r--test/functional/provider/define_spec.lua15
-rw-r--r--test/unit/eval/helpers.lua12
-rw-r--r--test/unit/eval/typval_spec.lua48
-rw-r--r--third-party/CMakeLists.txt9
106 files changed, 2652 insertions, 838 deletions
diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml
index ed2962998c..5fa6556066 100644
--- a/.builds/openbsd.yml
+++ b/.builds/openbsd.yml
@@ -1,17 +1,17 @@
# sourcehut CI: https://builds.sr.ht/~jmk/neovim
-image: openbsd/6.5
+image: openbsd/6.7
packages:
- autoconf-2.69p2
- automake-1.15.1
- cmake
-- gettext-0.19.8.1p3
-- gettext-tools-0.19.8.1
+- gettext-runtime-0.20.1p1
+- gettext-tools-0.20.1p3
- gmake
- libtool
-- ninja-1.8.2p0
-- unzip-6.0p11
+- ninja-1.10.0
+- unzip-6.0p13
sources:
- https://github.com/neovim/neovim
diff --git a/.github/ISSUE_TEMPLATE/lsp_bug_report.md b/.github/ISSUE_TEMPLATE/lsp_bug_report.md
new file mode 100644
index 0000000000..0e5155c7ac
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/lsp_bug_report.md
@@ -0,0 +1,46 @@
+---
+name: Language server client bug report
+about: Report a built-in lsp problem in Nvim
+title: ''
+labels: bug, lsp
+
+---
+
+<!-- Before reporting: search existing issues and check the FAQ. -->
+
+- `nvim --version`:
+- language server name/version:
+- Operating system/version:
+
+<details>
+<summary>nvim -c ":checkhealth nvim nvim_lsp"</summary>
+
+<!-- Paste the results from `nvim -c ":checkhealth nvim nvim_lsp"` here. -->
+
+</details>
+
+<details>
+<summary>lsp.log</summary>
+
+<!--
+Please paste the lsp log before and after the problem.
+
+You can set log level like this.
+`:lua vim.lsp.set_log_level("debug")`
+
+You can find the location of the log with the following command.
+`:lua print(vim.lsp.get_log_path())`
+-->
+
+</details>
+
+### Steps to reproduce using `nvim -u NORC`
+
+```
+nvim -u NORC
+```
+
+### Actual behaviour
+
+### Expected behaviour
+
diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt
index 0ca41d5dfd..6c9e06d59d 100644
--- a/config/CMakeLists.txt
+++ b/config/CMakeLists.txt
@@ -12,7 +12,7 @@ check_type_size("size_t" SIZEOF_SIZE_T)
check_type_size("long long" SIZEOF_LONG_LONG)
check_type_size("void *" SIZEOF_VOID_PTR)
-if (CMAKE_HOST_SYSTEM_VERSION MATCHES ".*-Microsoft")
+if (CMAKE_HOST_SYSTEM_VERSION MATCHES ".*-(Microsoft|microsoft-standard)")
# Windows Subsystem for Linux
set(HAVE_WSL 1)
endif()
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.vim b/runtime/autoload/provider.vim
index dc24e801d0..803c1a0b1c 100644
--- a/runtime/autoload/provider.vim
+++ b/runtime/autoload/provider.vim
@@ -3,8 +3,11 @@
" Start the provider and perform a 'poll' request
"
" Returns a valid channel on success
-function! provider#Poll(argv, orig_name, log_env) abort
+function! provider#Poll(argv, orig_name, log_env, ...) abort
let job = {'rpc': v:true, 'stderr_buffered': v:true}
+ if a:0
+ let job = extend(job, a:1)
+ endif
try
let channel_id = jobstart(a:argv, job)
if channel_id > 0 && rpcrequest(channel_id, 'poll') ==# 'ok'
diff --git a/runtime/autoload/provider/pythonx.vim b/runtime/autoload/provider/pythonx.vim
index 23e7ff8f64..e89d519790 100644
--- a/runtime/autoload/provider/pythonx.vim
+++ b/runtime/autoload/provider/pythonx.vim
@@ -19,7 +19,7 @@ function! provider#pythonx#Require(host) abort
call add(args, plugin.path)
endfor
- return provider#Poll(args, a:host.orig_name, '$NVIM_PYTHON_LOG_FILE')
+ return provider#Poll(args, a:host.orig_name, '$NVIM_PYTHON_LOG_FILE', {'overlapped': v:true})
endfunction
function! s:get_python_executable_from_host_var(major_version) abort
@@ -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/autoload/remote/define.vim b/runtime/autoload/remote/define.vim
index 2688a62a82..2aec96e365 100644
--- a/runtime/autoload/remote/define.vim
+++ b/runtime/autoload/remote/define.vim
@@ -24,7 +24,7 @@ function! remote#define#CommandOnHost(host, method, sync, name, opts)
endif
if has_key(a:opts, 'nargs')
- call add(forward_args, ' <args>')
+ call add(forward_args, ' " . <q-args> . "')
endif
exe s:GetCommandPrefix(a:name, a:opts)
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..92f703a8a3 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
@@ -5499,6 +5506,11 @@ jobstart({cmd}[, {opts}]) *jobstart()*
stdout data.
|on_stderr|: (function) Callback invoked when the job emits
stderr data.
+ overlapped: (boolean) Set FILE_FLAG_OVERLAPPED for the
+ standard input/output passed to the child process.
+ Normally you do not need to set this.
+ (Only available on MS-Windows, On other
+ platforms, this option is silently ignored.)
pty: (boolean) Connect the job to a new pseudo
terminal, and its streams to the master file
descriptor. Then `on_stderr` is ignored,
@@ -5828,6 +5840,7 @@ maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()*
"rhs" The {rhs} of the mapping as typed.
"silent" 1 for a |:map-silent| mapping, else 0.
"noremap" 1 if the {rhs} of the mapping is not remappable.
+ "script" 1 if mapping was defined with <script>.
"expr" 1 for an expression mapping (|:map-<expr>|).
"buffer" 1 for a buffer local mapping (|:map-local|).
"mode" Modes for which the mapping is defined. In
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 9460e600e3..3a93390210 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -164,21 +164,21 @@ name: >
LSP HIGHLIGHT *lsp-highlight*
*hl-LspDiagnosticsError*
-LspDiagnosticsError used for "Error" diagnostic virtual text
+LspDiagnosticsError used for "Error" diagnostic virtual text
*hl-LspDiagnosticsErrorSign*
-LspDiagnosticsErrorSign used for "Error" diagnostic signs in sign column
+LspDiagnosticsErrorSign used for "Error" diagnostic signs in sign column
*hl-LspDiagnosticsWarning*
-LspDiagnosticsWarning used for "Warning" diagnostic virtual text
+LspDiagnosticsWarning used for "Warning" diagnostic virtual text
*hl-LspDiagnosticsWarningSign*
-LspDiagnosticsWarningSign used for "Warning" diagnostic signs in sign column
+LspDiagnosticsWarningSign used for "Warning" diagnostic signs in sign column
*hl-LspDiagnosticsInformation*
-LspDiagnosticInformation used for "Information" diagnostic virtual text
+LspDiagnosticsInformation used for "Information" diagnostic virtual text
*hl-LspDiagnosticsInformationSign*
-LspDiagnosticInformationSign used for "Information" signs in sign column
+LspDiagnosticsInformationSign used for "Information" signs in sign column
*hl-LspDiagnosticsHint*
-LspDiagnosticHint used for "Hint" diagnostic virtual text
+LspDiagnosticsHint used for "Hint" diagnostic virtual text
*hl-LspDiagnosticsHintSign*
-LspDiagnosticHintSign used for "Hint" diagnostic signs in sign column
+LspDiagnosticsHintSign used for "Hint" diagnostic signs in sign column
*hl-LspReferenceText*
LspReferenceText used for highlighting "text" references
*hl-LspReferenceRead*
@@ -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 2b83c35c90..5a49d36503 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -705,6 +705,10 @@ 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()*
@@ -713,6 +717,16 @@ vim.highlight.on_yank([{higroup}, {timeout}, {event}])
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*
@@ -827,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/filetype.vim b/runtime/filetype.vim
index 0b5003dc44..383a45b9d3 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
@@ -880,11 +883,12 @@ au BufNewFile,BufRead *.ll setf lifelines
" Lilo: Linux loader
au BufNewFile,BufRead lilo.conf setf lilo
-" Lisp (*.el = ELisp, *.cl = Common Lisp, *.jl = librep Lisp)
+" Lisp (*.el = ELisp, *.cl = Common Lisp)
+" *.jl was removed, it's also used for Julia, better skip than guess wrong.
if has("fname_case")
- au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,*.jl,*.L,.emacs,.sawfishrc setf lisp
+ au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,*.L,.emacs,.sawfishrc setf lisp
else
- au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,*.jl,.emacs,.sawfishrc setf lisp
+ au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,.emacs,.sawfishrc setf lisp
endif
" SBCL implementation of Common Lisp
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index 5c98c626a4..69c3c8a4dc 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -2,12 +2,34 @@ 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
@@ -27,10 +49,7 @@ function highlight.on_yank(higroup, timeout, event)
pos1 = {pos1[2] - 1, pos1[3] - 1 + pos1[4]}
pos2 = {pos2[2] - 1, pos2[3] - 1 + pos2[4]}
- local region = vim.region(bufnr, pos1, pos2, event.regtype, event.inclusive)
- for linenr, cols in pairs(region) do
- api.nvim_buf_add_highlight(bufnr, yank_ns, higroup, linenr, cols[1], cols[2])
- end
+ 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,
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 61da2130c8..e759511347 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?
@@ -532,6 +507,8 @@ function lsp.start_client(config)
or (not client.resolved_capabilities.signature_help and method == 'textDocument/signatureHelp')
or (not client.resolved_capabilities.goto_definition and method == 'textDocument/definition')
or (not client.resolved_capabilities.implementation and method == 'textDocument/implementation')
+ or (not client.resolved_capabilities.declaration and method == 'textDocument/declaration')
+ 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')
then
@@ -609,9 +586,7 @@ do
old_utf16_size)
local _ = log.debug() and log.debug("on_lines", bufnr, changedtick, firstline,
lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size, nvim_buf_get_lines(bufnr, firstline, new_lastline, true))
- if old_byte_size == 0 then
- return
- end
+
-- Don't do anything if there are no clients attached.
if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
return
@@ -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..4b14f0132d 100644
--- a/runtime/lua/vim/lsp/callbacks.lua
+++ b/runtime/lua/vim/lsp/callbacks.lua
@@ -72,6 +72,17 @@ M['textDocument/publishDiagnostics'] = function(_, _, result)
err_message("LSP.publishDiagnostics: Couldn't find buffer for ", uri)
return
end
+
+ -- Unloaded buffers should not handle diagnostics.
+ -- When the buffer is loaded, we'll call on_attach, which sends textDocument/didOpen.
+ -- This should trigger another publish of the diagnostics.
+ --
+ -- In particular, this stops a ton of spam when first starting a server for current
+ -- unloaded buffers.
+ if not api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+
util.buf_clear_diagnostics(bufnr)
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
@@ -242,12 +253,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..4fded1961d 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;
@@ -898,7 +897,8 @@ function protocol.resolve_capabilities(server_capabilities)
text_document_will_save = ifnil(textDocumentSync.willSave, false);
text_document_will_save_wait_until = ifnil(textDocumentSync.willSaveWaitUntil, false);
text_document_save = ifnil(textDocumentSync.save, false);
- text_document_save_include_text = ifnil(textDocumentSync.save and textDocumentSync.save.includeText, false);
+ text_document_save_include_text = ifnil(type(textDocumentSync.save) == 'table'
+ and textDocumentSync.save.includeText, false);
}
else
return nil, string.format("Invalid type for textDocumentSync: %q", type(textDocumentSync))
@@ -924,6 +924,28 @@ function protocol.resolve_capabilities(server_capabilities)
error("The server sent invalid codeActionProvider")
end
+ if server_capabilities.declarationProvider == nil then
+ general_properties.declaration = false
+ elseif type(server_capabilities.declarationProvider) == 'boolean' then
+ general_properties.declaration = server_capabilities.declarationProvider
+ elseif type(server_capabilities.declarationProvider) == 'table' then
+ -- TODO: support more detailed declarationProvider options.
+ general_properties.declaration = false
+ else
+ error("The server sent invalid declarationProvider")
+ end
+
+ if server_capabilities.typeDefinitionProvider == nil then
+ general_properties.type_definition = false
+ elseif type(server_capabilities.typeDefinitionProvider) == 'boolean' then
+ general_properties.type_definition = server_capabilities.typeDefinitionProvider
+ elseif type(server_capabilities.typeDefinitionProvider) == 'table' then
+ -- TODO: support more detailed typeDefinitionProvider options.
+ general_properties.type_definition = false
+ else
+ error("The server sent invalid typeDefinitionProvider")
+ end
+
if server_capabilities.implementationProvider == nil then
general_properties.implementation = false
elseif type(server_capabilities.implementationProvider) == 'boolean' then
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 5c6d183ac1..4c3c4fa6cb 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 = {}
@@ -91,20 +92,26 @@ local function sort_by_key(fn)
end
end
local edit_sort_key = sort_by_key(function(e)
- return {e.A[1], e.A[2], e.i}
+ return {e.A[1], e.A[2], -e.i}
end)
-local function get_line_byte_from_line_character(bufnr, lnum, cnum)
- -- Skip check when the byte and character position is the same
- if cnum > 0 then
- local lines = api.nvim_buf_get_lines(bufnr, lnum, lnum+1, false)
-
+--- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position
+-- Returns a zero-indexed column, since set_lines() does the conversion to
+-- 1-indexed
+local function get_line_byte_from_position(bufnr, position)
+ -- LSP's line and characters are 0-indexed
+ -- Vim's line and columns are 1-indexed
+ local col = position.character
+ -- When on the first character, we can ignore the difference between byte and
+ -- character
+ if col > 0 then
+ local line = position.line
+ local lines = api.nvim_buf_get_lines(bufnr, line, line + 1, false)
if #lines > 0 then
- return vim.str_byteindex(lines[1], cnum)
+ return vim.str_byteindex(lines[1], col)
end
end
-
- return cnum
+ return col
end
function M.apply_text_edits(text_edits, bufnr)
@@ -117,15 +124,9 @@ function M.apply_text_edits(text_edits, bufnr)
for i, e in ipairs(text_edits) do
-- adjust start and end column for UTF-16 encoding of non-ASCII characters
local start_row = e.range.start.line
- local start_col = get_line_byte_from_line_character(
- bufnr,
- start_row,
- e.range.start.character)
+ local start_col = get_line_byte_from_position(bufnr, e.range.start)
local end_row = e.range["end"].line
- local end_col = get_line_byte_from_line_character(
- bufnr,
- end_row,
- e.range["end"].character)
+ local end_col = get_line_byte_from_position(bufnr, e.range['end'])
start_line = math.min(e.range.start.line, start_line)
finish_line = math.max(e.range["end"].line, finish_line)
-- TODO(ashkan) sanity check ranges for overlap.
@@ -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,14 +274,22 @@ 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
--- Some lanuguage servers return complementary candidates whose prefixes do not match are also returned.
+-- Some language servers return complementary candidates whose prefixes do not match are also returned.
-- So we exclude completion candidates whose prefix does not match.
local function remove_unmatch_completion_items(items, prefix)
return vim.tbl_filter(function(item)
@@ -474,13 +543,33 @@ function M.jump_to_location(location)
api.nvim_buf_set_option(0, 'buflisted', true)
local range = location.range or location.targetSelectionRange
local row = range.start.line
- local col = range.start.character
- local line = api.nvim_buf_get_lines(0, row, row+1, true)[1]
- col = vim.str_byteindex(line, col)
+ local col = get_line_byte_from_position(0, range.start)
api.nvim_win_set_cursor(0, {row + 1, col})
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
@@ -525,13 +614,53 @@ function M.focusable_preview(unique_name, fn)
end)
end
--- Convert markdown into syntax highlighted regions by stripping the code
--- blocks and converting them into highlighted code.
--- This will by default insert a blank line separator after those code block
--- regions to improve readability.
+--- Trim empty lines from input and pad left and right with spaces
+---
+--@param contents table of lines to trim and pad
+--@param opts dictionary with optional fields
+-- - pad_left amount of columns to pad contents at left (default 1)
+-- - pad_right amount of columns to pad contents at right (default 1)
+--@return contents table of trimmed and padded lines
+function M._trim_and_pad(contents, opts)
+ validate {
+ contents = { contents, 't' };
+ opts = { opts, 't', true };
+ }
+ opts = opts or {}
+ local left_padding = (" "):rep(opts.pad_left or 1)
+ local right_padding = (" "):rep(opts.pad_right or 1)
+ contents = M.trim_empty_lines(contents)
+ for i, line in ipairs(contents) do
+ contents[i] = string.format('%s%s%s', left_padding, line:gsub("\r", ""), right_padding)
+ end
+ return contents
+end
+
+
+
+--- Convert markdown into syntax highlighted regions by stripping the code
+--- blocks and converting them into highlighted code.
+--- This will by default insert a blank line separator after those code block
+--- regions to improve readability.
+--- The result is shown in a floating preview
+--- TODO: refactor to separate stripping/converting and make use of open_floating_preview
+---
+--@param contents table of lines to show in window
+--@param opts dictionary with optional fields
+-- - height of floating window
+-- - width of floating window
+-- - wrap_at character to wrap at for computing height
+-- - pad_left amount of columns to pad contents at left
+-- - pad_right amount of columns to pad contents at right
+-- - separator insert separator after code block
+--@return width,height size of float
function M.fancy_floating_markdown(contents, opts)
- local pad_left = opts and opts.pad_left
- local pad_right = opts and opts.pad_right
+ validate {
+ contents = { contents, 't' };
+ opts = { opts, 't', true };
+ }
+ opts = opts or {}
+
local stripped = {}
local highlights = {}
do
@@ -565,31 +694,27 @@ function M.fancy_floating_markdown(contents, opts)
end
end
end
- local width = 0
- for i, v in ipairs(stripped) do
- v = v:gsub("\r", "")
- if pad_left then v = (" "):rep(pad_left)..v end
- if pad_right then v = v..(" "):rep(pad_right) end
- stripped[i] = v
- width = math.max(width, #v)
- end
- if opts and opts.max_width then
- width = math.min(opts.max_width, width)
- end
- -- TODO(ashkan): decide how to make this customizable.
- local insert_separator = true
+ -- Clean up and add padding
+ stripped = M._trim_and_pad(stripped, opts)
+
+ -- Compute size of float needed to show (wrapped) lines
+ opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0))
+ local width, height = M._make_floating_popup_size(stripped, opts)
+
+ -- Insert blank line separator after code block
+ local insert_separator = opts.separator or true
if insert_separator then
for i, h in ipairs(highlights) do
h.start = h.start + i - 1
h.finish = h.finish + i - 1
if h.finish + 1 <= #stripped then
table.insert(stripped, h.finish + 1, string.rep("─", width))
+ height = height + 1
end
end
end
-- Make the floating window.
- local height = #stripped
local bufnr = api.nvim_create_buf(false, true)
local winnr = api.nvim_open_win(bufnr, false, M.make_floating_popup_options(width, height, opts))
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped)
@@ -601,7 +726,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
@@ -617,8 +742,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
@@ -630,33 +755,81 @@ function M.close_preview_autocmd(events, winnr)
api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)")
end
-function M.open_floating_preview(contents, filetype, opts)
+--- Compute size of float needed to show contents (with optional wrapping)
+---
+--@param contents table of lines to show in window
+--@param opts dictionary with optional fields
+-- - height of floating window
+-- - width of floating window
+-- - wrap_at character to wrap at for computing height
+--@return width,height size of float
+function M._make_floating_popup_size(contents, opts)
validate {
contents = { contents, 't' };
- filetype = { filetype, 's', true };
opts = { opts, 't', true };
}
opts = opts or {}
- -- Trim empty lines from the end.
- contents = M.trim_empty_lines(contents)
-
local width = opts.width
- local height = opts.height or #contents
+ local height = opts.height
+ local line_widths = {}
+
if not width then
width = 0
for i, line in ipairs(contents) do
- -- Clean up the input and add left pad.
- line = " "..line:gsub("\r", "")
-- TODO(ashkan) use nvim_strdisplaywidth if/when that is introduced.
- local line_width = vim.fn.strdisplaywidth(line)
- width = math.max(line_width, width)
- contents[i] = line
+ line_widths[i] = vim.fn.strdisplaywidth(line)
+ width = math.max(line_widths[i], width)
end
- -- Add right padding of 1 each.
- width = width + 1
end
+ if not height then
+ height = #contents
+ local wrap_at = opts.wrap_at
+ if wrap_at and width > wrap_at then
+ height = 0
+ if vim.tbl_isempty(line_widths) then
+ for _, line in ipairs(contents) do
+ local line_width = vim.fn.strdisplaywidth(line)
+ height = height + math.ceil(line_width/wrap_at)
+ end
+ else
+ for i = 1, #contents do
+ height = height + math.ceil(line_widths[i]/wrap_at)
+ end
+ end
+ end
+ end
+
+ return width, height
+end
+
+--- Show contents in a floating window
+---
+--@param contents table of lines to show in window
+--@param filetype string of filetype to set for opened buffer
+--@param opts dictionary with optional fields
+-- - height of floating window
+-- - width of floating window
+-- - wrap_at character to wrap at for computing height
+-- - pad_left amount of columns to pad contents at left
+-- - pad_right amount of columns to pad contents at right
+--@return bufnr,winnr buffer and window number of floating window or nil
+function M.open_floating_preview(contents, filetype, opts)
+ validate {
+ contents = { contents, 't' };
+ filetype = { filetype, 's', true };
+ opts = { opts, 't', true };
+ }
+ opts = opts or {}
+
+ -- Clean up input: trim empty lines from the end, pad
+ contents = M._trim_and_pad(contents, opts)
+
+ -- Compute size of float needed to show (wrapped) lines
+ opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0))
+ local width, height = M._make_floating_popup_size(contents, opts)
+
local floating_bufnr = api.nvim_create_buf(false, true)
if filetype then
api.nvim_buf_set_option(floating_bufnr, 'filetype', filetype)
@@ -668,23 +841,10 @@ function M.open_floating_preview(contents, filetype, opts)
end
api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents)
api.nvim_buf_set_option(floating_bufnr, 'modifiable', false)
- M.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden"}, floating_winnr)
+ M.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden", "BufLeave"}, floating_winnr)
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")
@@ -818,8 +978,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}
@@ -843,7 +1002,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..384d22cb89 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 == '' and sep == '') then
return
end
if sep == '' then
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index d3b78a7f73..c502e45bd0 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -162,16 +162,17 @@ function Query:match_preds(match, pattern, bufnr)
-- Here we only want to return if a predicate DOES NOT match, and
-- continue on the other case. This way unknown predicates will not be considered,
-- which allows some testing and easier user extensibility (#12173).
+ -- Also, tree-sitter strips the leading # from predicates for us.
if pred[1] == "eq?" then
local node = match[pred[2]]
local node_text = get_node_text(node, bufnr)
local str
if type(pred[3]) == "string" then
- -- (eq? @aa "foo")
+ -- (#eq? @aa "foo")
str = pred[3]
else
- -- (eq? @aa @bb)
+ -- (#eq? @aa @bb)
str = get_node_text(match[pred[3]], bufnr)
end
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/update-ts-runtime.sh b/scripts/update-ts-runtime.sh
new file mode 100755
index 0000000000..1a947e0ac9
--- /dev/null
+++ b/scripts/update-ts-runtime.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+#
+# This script will update the treesitter runtime to the provided commit.
+# Usage :
+# $0 <tree-sitter commit sha>
+set -e
+
+ts_source_dir="/tmp/tree-sitter"
+ts_url="https://github.com/tree-sitter/tree-sitter.git"
+
+base_dir="$(cd "$(dirname $(dirname $0))" && pwd)"
+ts_dest_dir="$base_dir/src/tree_sitter/"
+ts_current_commit="$ts_dest_dir/treesitter_commit_hash.txt"
+
+echo "Updating treesitter runtime from $(cat "$ts_current_commit") to $1..."
+
+if [ ! -d "$ts_source_dir" ]; then
+ echo "Cloning treesitter..."
+ git clone "$ts_url" "$ts_source_dir"
+else
+ echo "Found a non-empty $ts_source_dir directory..."
+ git -C "$ts_source_dir" fetch
+fi
+
+echo "Checking out $1..."
+git -C "$ts_source_dir" checkout $1
+
+echo "Removing old files..."
+find "$ts_dest_dir" -not -name "LICENSE" -not -name "README.md" -not -type d -delete
+
+echo "Copying files..."
+cp -t "$ts_dest_dir" -r "$ts_source_dir/lib/src"/*
+cp -t "$ts_dest_dir" "$ts_source_dir/lib/include/tree_sitter"/*
+
+echo "$1" > "$ts_current_commit"
+
+make
+TEST_FILE="$base_dir/test/functional/lua/treesitter_spec.lua" make test
+
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/private/helpers.c b/src/nvim/api/private/helpers.c
index a458762cc6..f194b6b474 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1075,8 +1075,8 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
break;
case kObjectTypeBoolean:
- tv->v_type = VAR_SPECIAL;
- tv->vval.v_special = obj.data.boolean? kSpecialVarTrue: kSpecialVarFalse;
+ tv->v_type = VAR_BOOL;
+ tv->vval.v_bool = obj.data.boolean? kBoolVarTrue: kBoolVarFalse;
break;
case kObjectTypeBuffer:
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/buffer.c b/src/nvim/buffer.c
index 7c8f93163a..3ce39feda5 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -186,14 +186,17 @@ int open_buffer(
}
}
- /*
- * if there is no memfile at all, exit
- * This is OK, since there are no changes to lose.
- */
+ // If there is no memfile at all, exit.
+ // This is OK, since there are no changes to lose.
if (curbuf == NULL) {
EMSG(_("E82: Cannot allocate any buffer, exiting..."));
+
+ // Don't try to do any saving, with "curbuf" NULL almost nothing
+ // will work.
+ v_dying = 2;
getout(2);
}
+
EMSG(_("E83: Cannot allocate buffer, using other one..."));
enter_buffer(curbuf);
if (old_tw != curbuf->b_p_tw) {
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 5eb29a7290..37cbfb968b 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -301,7 +301,8 @@ static void close_cb(Stream *stream, void *data)
/// @returns [allocated] channel
Channel *channel_job_start(char **argv, CallbackReader on_stdout,
CallbackReader on_stderr, Callback on_exit,
- bool pty, bool rpc, bool detach, const char *cwd,
+ bool pty, bool rpc, bool overlapped, bool detach,
+ const char *cwd,
uint16_t pty_width, uint16_t pty_height,
char *term_name, char **env, varnumber_T *status_out)
{
@@ -342,6 +343,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
proc->detach = detach;
proc->cwd = cwd;
proc->env = env;
+ proc->overlapped = overlapped;
char *cmd = xstrdup(proc->argv[0]);
bool has_out, has_err;
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 4acee7b453..66cd0e09c6 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -213,8 +213,8 @@ static struct vimvar {
VV(VV_ERRORS, "errors", VAR_LIST, 0),
VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO),
VV(VV_EVENT, "event", VAR_DICT, VV_RO),
- VV(VV_FALSE, "false", VAR_SPECIAL, VV_RO),
- VV(VV_TRUE, "true", VAR_SPECIAL, VV_RO),
+ VV(VV_FALSE, "false", VAR_BOOL, VV_RO),
+ VV(VV_TRUE, "true", VAR_BOOL, VV_RO),
VV(VV_NULL, "null", VAR_SPECIAL, VV_RO),
VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO),
VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO),
@@ -230,12 +230,14 @@ 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
// shorthand
#define vv_type vv_di.di_tv.v_type
#define vv_nr vv_di.di_tv.vval.v_number
+#define vv_bool vv_di.di_tv.vval.v_bool
#define vv_special vv_di.di_tv.vval.v_special
#define vv_float vv_di.di_tv.vval.v_float
#define vv_str vv_di.di_tv.vval.v_string
@@ -387,8 +389,8 @@ void eval_init(void)
set_vim_var_nr(VV_TYPE_FLOAT, VAR_TYPE_FLOAT);
set_vim_var_nr(VV_TYPE_BOOL, VAR_TYPE_BOOL);
- set_vim_var_special(VV_FALSE, kSpecialVarFalse);
- set_vim_var_special(VV_TRUE, kSpecialVarTrue);
+ set_vim_var_bool(VV_FALSE, kBoolVarFalse);
+ set_vim_var_bool(VV_TRUE, kBoolVarTrue);
set_vim_var_special(VV_NULL, kSpecialVarNull);
set_vim_var_special(VV_EXITING, kSpecialVarNull);
@@ -443,7 +445,7 @@ void eval_clear(void)
// unreferenced lists and dicts
(void)garbage_collect(false);
- // functions
+ // functions not garbage collected
free_all_functions();
}
@@ -872,17 +874,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;
}
@@ -1834,12 +1838,15 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
int opt_type;
long numval;
char *stringval = NULL;
+ const char *s = NULL;
const char c1 = *p;
*p = NUL;
varnumber_T n = tv_get_number(tv);
- const char *s = tv_get_string_chk(tv); // != NULL if number or string.
+ if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) {
+ s = tv_get_string_chk(tv); // != NULL if number or string.
+ }
if (s != NULL && op != NULL && *op != '=') {
opt_type = get_option_value(arg, &numval, (char_u **)&stringval,
opt_flags);
@@ -1865,7 +1872,8 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
}
}
}
- if (s != NULL) {
+ if (s != NULL || tv->v_type == VAR_BOOL
+ || tv->v_type == VAR_SPECIAL) {
set_option_value((const char *)arg, n, s, opt_flags);
arg_end = (char_u *)p;
}
@@ -4196,6 +4204,7 @@ eval_index(
}
return FAIL;
}
+ case VAR_BOOL:
case VAR_SPECIAL: {
if (verbose) {
EMSG(_("E909: Cannot index a special variable"));
@@ -4417,6 +4426,7 @@ eval_index(
*rettv = var1;
break;
}
+ case VAR_BOOL:
case VAR_SPECIAL:
case VAR_FUNC:
case VAR_FLOAT:
@@ -5025,7 +5035,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!"));
@@ -5270,6 +5280,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
abort = set_ref_in_func(tv->vval.v_string, NULL, copyID);
break;
case VAR_UNKNOWN:
+ case VAR_BOOL:
case VAR_SPECIAL:
case VAR_FLOAT:
case VAR_NUMBER:
@@ -5740,11 +5751,11 @@ int assert_bool(typval_T *argvars, bool is_true)
if ((argvars[0].v_type != VAR_NUMBER
|| (tv_get_number_chk(&argvars[0], &error) == 0) == is_true
|| error)
- && (argvars[0].v_type != VAR_SPECIAL
- || (argvars[0].vval.v_special
- != (SpecialVarValue) (is_true
- ? kSpecialVarTrue
- : kSpecialVarFalse)))) {
+ && (argvars[0].v_type != VAR_BOOL
+ || (argvars[0].vval.v_bool
+ != (BoolVarValue)(is_true
+ ? kBoolVarTrue
+ : kBoolVarFalse)))) {
prepare_assert_error(&ga);
fill_assert_error(&ga, &argvars[1],
(char_u *)(is_true ? "True" : "False"),
@@ -6117,7 +6128,7 @@ void common_function(typval_T *argvars, typval_T *rettv,
if (tv_list_len(list) == 0) {
arg_idx = 0;
} else if (tv_list_len(list) > MAX_FUNC_ARGS) {
- emsg_funcname((char *)e_toomanyarg, name);
+ emsg_funcname((char *)e_toomanyarg, s);
xfree(name);
goto theend;
}
@@ -6751,6 +6762,7 @@ void mapblock_fill_dict(dict_T *const dict,
}
tv_dict_add_allocated_str(dict, S_LEN("lhs"), lhs);
tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value);
+ tv_dict_add_nr(dict, S_LEN("script"), mp->m_noremap == REMAP_SCRIPT ? 1 : 0);
tv_dict_add_nr(dict, S_LEN("expr"), mp->m_expr ? 1 : 0);
tv_dict_add_nr(dict, S_LEN("silent"), mp->m_silent ? 1 : 0);
tv_dict_add_nr(dict, S_LEN("sid"), (varnumber_T)mp->m_script_ctx.sc_sid);
@@ -8045,6 +8057,17 @@ void set_vim_var_nr(const VimVarIndex idx, const varnumber_T val)
vimvars[idx].vv_nr = val;
}
+/// Set boolean v: {true, false} to the given value
+///
+/// @param[in] idx Index of variable to set.
+/// @param[in] val Value to set to.
+void set_vim_var_bool(const VimVarIndex idx, const BoolVarValue val)
+{
+ tv_clear(&vimvars[idx].vv_tv);
+ vimvars[idx].vv_type = VAR_BOOL;
+ vimvars[idx].vv_bool = val;
+}
+
/// Set special v: variable to the given value
///
/// @param[in] idx Index of variable to set.
@@ -8108,6 +8131,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.
*/
@@ -9122,6 +9162,7 @@ int var_item_copy(const vimconv_T *const conv,
case VAR_FLOAT:
case VAR_FUNC:
case VAR_PARTIAL:
+ case VAR_BOOL:
case VAR_SPECIAL:
tv_copy(from, to);
break;
@@ -9748,9 +9789,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 +10349,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 +10379,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/decode.c b/src/nvim/eval/decode.c
index 42999ddd62..daba304f00 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -795,9 +795,9 @@ json_decode_string_cycle_start:
}
p += 3;
POP(((typval_T) {
- .v_type = VAR_SPECIAL,
+ .v_type = VAR_BOOL,
.v_lock = VAR_UNLOCKED,
- .vval = { .v_special = kSpecialVarTrue },
+ .vval = { .v_bool = kBoolVarTrue },
}), false);
break;
}
@@ -808,9 +808,9 @@ json_decode_string_cycle_start:
}
p += 4;
POP(((typval_T) {
- .v_type = VAR_SPECIAL,
+ .v_type = VAR_BOOL,
.v_lock = VAR_UNLOCKED,
- .vval = { .v_special = kSpecialVarFalse },
+ .vval = { .v_bool = kBoolVarFalse },
}), false);
break;
}
@@ -954,10 +954,10 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
}
case MSGPACK_OBJECT_BOOLEAN: {
*rettv = (typval_T) {
- .v_type = VAR_SPECIAL,
+ .v_type = VAR_BOOL,
.v_lock = VAR_UNLOCKED,
.vval = {
- .v_special = mobj.via.boolean ? kSpecialVarTrue : kSpecialVarFalse
+ .v_bool = mobj.via.boolean ? kBoolVarTrue : kBoolVarFalse
},
};
break;
diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c
index 138f638eb2..137f099df6 100644
--- a/src/nvim/eval/encode.c
+++ b/src/nvim/eval/encode.c
@@ -34,10 +34,13 @@
#define utf_ptr2len(b) ((size_t)utf_ptr2len((char_u *)b))
#define utf_char2len(b) ((size_t)utf_char2len(b))
+const char *const encode_bool_var_names[] = {
+ [kBoolVarTrue] = "true",
+ [kBoolVarFalse] = "false",
+};
+
const char *const encode_special_var_names[] = {
[kSpecialVarNull] = "null",
- [kSpecialVarTrue] = "true",
- [kSpecialVarFalse] = "false",
};
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h
index ccea245ab3..596bb49ae0 100644
--- a/src/nvim/eval/encode.h
+++ b/src/nvim/eval/encode.h
@@ -55,6 +55,7 @@ static inline ListReaderState encode_init_lrstate(const list_T *const list)
}
/// Array mapping values from SpecialVarValue enum to names
+extern const char *const encode_bool_var_names[];
extern const char *const encode_special_var_names[];
/// First codepoint in high surrogates block
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c
index 8cd21f8d62..da05ecda43 100644
--- a/src/nvim/eval/executor.c
+++ b/src/nvim/eval/executor.c
@@ -28,11 +28,13 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2,
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NO_SANITIZE_UNDEFINED
{
// Can't do anything with a Funcref, a Dict or special value on the right.
- if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT) {
+ if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT
+ && tv2->v_type != VAR_BOOL && tv2->v_type != VAR_SPECIAL) {
switch (tv1->v_type) {
case VAR_DICT:
case VAR_FUNC:
case VAR_PARTIAL:
+ case VAR_BOOL:
case VAR_SPECIAL: {
break;
}
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 79a52d9779..1071e75c06 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -175,8 +175,8 @@ static int non_zero_arg(typval_T *argvars)
{
return ((argvars[0].v_type == VAR_NUMBER
&& argvars[0].vval.v_number != 0)
- || (argvars[0].v_type == VAR_SPECIAL
- && argvars[0].vval.v_special == kSpecialVarTrue)
+ || (argvars[0].v_type == VAR_BOOL
+ && argvars[0].vval.v_bool == kBoolVarTrue)
|| (argvars[0].v_type == VAR_STRING
&& argvars[0].vval.v_string != NULL
&& *argvars[0].vval.v_string != NUL));
@@ -1758,21 +1758,23 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr)
n = (tv_dict_len(argvars[0].vval.v_dict) == 0);
break;
}
- case VAR_SPECIAL: {
- // Using switch to get warning if SpecialVarValue receives more values.
- switch (argvars[0].vval.v_special) {
- case kSpecialVarTrue: {
+ case VAR_BOOL: {
+ switch (argvars[0].vval.v_bool) {
+ case kBoolVarTrue: {
n = false;
break;
}
- case kSpecialVarFalse:
- case kSpecialVarNull: {
+ case kBoolVarFalse: {
n = true;
break;
}
}
break;
}
+ case VAR_SPECIAL: {
+ n = argvars[0].vval.v_special == kSpecialVarNull;
+ break;
+ }
case VAR_UNKNOWN: {
internal_error("f_empty(UNKNOWN)");
break;
@@ -4860,6 +4862,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool rpc = false;
bool pty = false;
bool clear_env = false;
+ bool overlapped = false;
CallbackReader on_stdout = CALLBACK_READER_INIT,
on_stderr = CALLBACK_READER_INIT;
Callback on_exit = CALLBACK_NONE;
@@ -4871,12 +4874,23 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rpc = tv_dict_get_number(job_opts, "rpc") != 0;
pty = tv_dict_get_number(job_opts, "pty") != 0;
clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
+ overlapped = tv_dict_get_number(job_opts, "overlapped") != 0;
+
if (pty && rpc) {
EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set");
shell_free_argv(argv);
return;
}
+#ifdef WIN32
+ if (pty && overlapped) {
+ EMSG2(_(e_invarg2),
+ "job cannot have both 'pty' and 'overlapped' options set");
+ shell_free_argv(argv);
+ return;
+ }
+#endif
+
char *new_cwd = tv_dict_get_string(job_opts, "cwd", false);
if (new_cwd && strlen(new_cwd) > 0) {
cwd = new_cwd;
@@ -4943,7 +4957,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
- rpc, detach, cwd, width, height,
+ rpc, overlapped, detach, cwd, width, height,
term_name, env, &rettv->vval.v_number);
if (chan) {
channel_create_event(chan, NULL);
@@ -5189,6 +5203,7 @@ static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr)
break;
}
case VAR_UNKNOWN:
+ case VAR_BOOL:
case VAR_SPECIAL:
case VAR_FLOAT:
case VAR_PARTIAL:
@@ -7243,7 +7258,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 +7269,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 +7277,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 +7295,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)) {
@@ -7369,8 +7384,8 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT,
CALLBACK_READER_INIT, CALLBACK_NONE,
- false, true, false, NULL, 0, 0, NULL, NULL,
- &rettv->vval.v_number);
+ false, true, false, false, NULL, 0, 0,
+ NULL, NULL, &rettv->vval.v_number);
if (chan) {
channel_create_event(chan, NULL);
}
@@ -10458,7 +10473,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin));
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit,
- true, false, false, cwd,
+ true, false, false, false, cwd,
term_width, curwin->w_height_inner,
xstrdup("xterm-256color"), NULL,
&rettv->vval.v_number);
@@ -10808,20 +10823,8 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr)
case VAR_LIST: n = VAR_TYPE_LIST; break;
case VAR_DICT: n = VAR_TYPE_DICT; break;
case VAR_FLOAT: n = VAR_TYPE_FLOAT; break;
- case VAR_SPECIAL: {
- switch (argvars[0].vval.v_special) {
- case kSpecialVarTrue:
- case kSpecialVarFalse: {
- n = VAR_TYPE_BOOL;
- break;
- }
- case kSpecialVarNull: {
- n = 7;
- break;
- }
- }
- break;
- }
+ case VAR_BOOL: n = VAR_TYPE_BOOL; break;
+ case VAR_SPECIAL:n = VAR_TYPE_SPECIAL; break;
case VAR_UNKNOWN: {
internal_error("f_type(UNKNOWN)");
break;
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 35130f6f40..0daaf6c878 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -1693,21 +1693,21 @@ int tv_dict_add_float(dict_T *const d, const char *const key,
return OK;
}
-/// Add a special entry to dictionary
+/// Add a boolean entry to dictionary
///
/// @param[out] d Dictionary to add entry to.
/// @param[in] key Key to add.
/// @param[in] key_len Key length.
-/// @param[in] val SpecialVarValue to add.
+/// @param[in] val BoolVarValue to add.
///
/// @return OK in case of success, FAIL when key already exists.
-int tv_dict_add_special(dict_T *const d, const char *const key,
- const size_t key_len, SpecialVarValue val)
+int tv_dict_add_bool(dict_T *const d, const char *const key,
+ const size_t key_len, BoolVarValue val)
{
dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
- item->di_tv.v_type = VAR_SPECIAL;
- item->di_tv.vval.v_special = val;
+ item->di_tv.v_type = VAR_BOOL;
+ item->di_tv.vval.v_bool = val;
if (tv_dict_add(d, item) == FAIL) {
tv_dict_item_free(item);
return FAIL;
@@ -2013,12 +2013,15 @@ void tv_dict_alloc_ret(typval_T *const ret_tv)
#define TYPVAL_ENCODE_CONV_NIL(tv) \
do { \
- tv->vval.v_special = kSpecialVarFalse; \
+ tv->vval.v_special = kSpecialVarNull; \
tv->v_lock = VAR_UNLOCKED; \
} while (0)
#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
- TYPVAL_ENCODE_CONV_NIL(tv)
+ do { \
+ tv->vval.v_bool = kBoolVarFalse; \
+ tv->v_lock = VAR_UNLOCKED; \
+ } while (0)
#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
do { \
@@ -2293,6 +2296,7 @@ void tv_free(typval_T *tv)
tv_dict_unref(tv->vval.v_dict);
break;
}
+ case VAR_BOOL:
case VAR_SPECIAL:
case VAR_NUMBER:
case VAR_FLOAT:
@@ -2324,6 +2328,7 @@ void tv_copy(const typval_T *const from, typval_T *const to)
switch (from->v_type) {
case VAR_NUMBER:
case VAR_FLOAT:
+ case VAR_BOOL:
case VAR_SPECIAL: {
break;
}
@@ -2425,6 +2430,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock)
case VAR_STRING:
case VAR_FUNC:
case VAR_PARTIAL:
+ case VAR_BOOL:
case VAR_SPECIAL: {
break;
}
@@ -2588,6 +2594,9 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic,
const char *s2 = tv_get_string_buf(tv2, buf2);
return mb_strcmp_ic((bool)ic, s1, s2) == 0;
}
+ case VAR_BOOL: {
+ return tv1->vval.v_bool == tv2->vval.v_bool;
+ }
case VAR_SPECIAL: {
return tv1->vval.v_special == tv2->vval.v_special;
}
@@ -2638,6 +2647,10 @@ bool tv_check_str_or_nr(const typval_T *const tv)
EMSG(_("E728: Expected a Number or a String, Dictionary found"));
return false;
}
+ case VAR_BOOL: {
+ EMSG(_("E5299: Expected a Number or a String, Boolean found"));
+ return false;
+ }
case VAR_SPECIAL: {
EMSG(_("E5300: Expected a Number or a String"));
return false;
@@ -2677,6 +2690,7 @@ bool tv_check_num(const typval_T *const tv)
{
switch (tv->v_type) {
case VAR_NUMBER:
+ case VAR_BOOL:
case VAR_SPECIAL:
case VAR_STRING: {
return true;
@@ -2721,6 +2735,7 @@ bool tv_check_str(const typval_T *const tv)
{
switch (tv->v_type) {
case VAR_NUMBER:
+ case VAR_BOOL:
case VAR_SPECIAL:
case VAR_STRING: {
return true;
@@ -2791,8 +2806,11 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error)
}
return n;
}
+ case VAR_BOOL: {
+ return tv->vval.v_bool == kBoolVarTrue ? 1 : 0;
+ }
case VAR_SPECIAL: {
- return tv->vval.v_special == kSpecialVarTrue ? 1 : 0;
+ return 0;
}
case VAR_UNKNOWN: {
emsgf(_(e_intern2), "tv_get_number(UNKNOWN)");
@@ -2860,6 +2878,10 @@ float_T tv_get_float(const typval_T *const tv)
EMSG(_("E894: Using a Dictionary as a Float"));
break;
}
+ case VAR_BOOL: {
+ EMSG(_("E362: Using a boolean value as a Float"));
+ break;
+ }
case VAR_SPECIAL: {
EMSG(_("E907: Using a special value as a Float"));
break;
@@ -2897,6 +2919,10 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf)
}
return "";
}
+ case VAR_BOOL: {
+ STRCPY(buf, encode_bool_var_names[tv->vval.v_bool]);
+ return buf;
+ }
case VAR_SPECIAL: {
STRCPY(buf, encode_special_var_names[tv->vval.v_special]);
return buf;
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index 4390db1b71..343dd205ff 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -91,10 +91,14 @@ typedef struct dict_watcher {
bool busy; // prevent recursion if the dict is changed in the callback
} DictWatcher;
+/// Bool variable values
+typedef enum {
+ kBoolVarFalse, ///< v:false
+ kBoolVarTrue, ///< v:true
+} BoolVarValue;
+
/// Special variable values
typedef enum {
- kSpecialVarFalse, ///< v:false
- kSpecialVarTrue, ///< v:true
kSpecialVarNull, ///< v:null
} SpecialVarValue;
@@ -114,6 +118,7 @@ typedef enum {
VAR_LIST, ///< List, .v_list is used.
VAR_DICT, ///< Dictionary, .v_dict is used.
VAR_FLOAT, ///< Floating-point value, .v_float is used.
+ VAR_BOOL, ///< true, false
VAR_SPECIAL, ///< Special value (true, false, null), .v_special
///< is used.
VAR_PARTIAL, ///< Partial, .v_partial is used.
@@ -125,6 +130,7 @@ typedef struct {
VarLockStatus v_lock; ///< Variable lock status.
union typval_vval_union {
varnumber_T v_number; ///< Number, for VAR_NUMBER.
+ BoolVarValue v_bool; ///< Bool value, for VAR_BOOL
SpecialVarValue v_special; ///< Special value, for VAR_SPECIAL.
float_T v_float; ///< Floating-point number, for VAR_FLOAT.
char_u *v_string; ///< String, for VAR_STRING and VAR_FUNC, can be NULL.
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
index af21a6fbe3..0aa64b1d5f 100644
--- a/src/nvim/eval/typval_encode.c.h
+++ b/src/nvim/eval/typval_encode.c.h
@@ -379,17 +379,22 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, _mp_last(*mpstack));
break;
}
+ case VAR_BOOL: {
+ switch (tv->vval.v_bool) {
+ case kBoolVarTrue:
+ case kBoolVarFalse: {
+ TYPVAL_ENCODE_CONV_BOOL(tv, tv->vval.v_bool == kBoolVarTrue);
+ break;
+ }
+ }
+ break;
+ }
case VAR_SPECIAL: {
switch (tv->vval.v_special) {
case kSpecialVarNull: {
TYPVAL_ENCODE_CONV_NIL(tv); // -V1037
break;
}
- case kSpecialVarTrue:
- case kSpecialVarFalse: {
- TYPVAL_ENCODE_CONV_BOOL(tv, tv->vval.v_special == kSpecialVarTrue);
- break;
- }
}
break;
}
@@ -607,7 +612,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..4d658498c1 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");
@@ -170,6 +170,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
garray_T newargs = GA_EMPTY_INIT_VALUE;
garray_T *pnewargs;
ufunc_T *fp = NULL;
+ partial_T *pt = NULL;
int varargs;
int ret;
char_u *start = skipwhite(*arg + 1);
@@ -219,7 +220,6 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
int len, flags = 0;
char_u *p;
char_u name[20];
- partial_T *pt;
garray_T newlines;
lambda_no++;
@@ -274,6 +274,7 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
errret:
ga_clear_strings(&newargs);
xfree(fp);
+ xfree(pt);
eval_lavars_used = old_eval_lavars;
return FAIL;
}
@@ -541,14 +542,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 +558,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 +667,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 +794,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 +909,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 +1136,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 +1163,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 +1177,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 +3164,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 +3357,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/libuv_process.c b/src/nvim/event/libuv_process.c
index 37b9f73ba4..13517d3df1 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -52,7 +52,7 @@ int libuv_process_spawn(LibuvProcess *uvproc)
if (!proc->in.closed) {
uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
#ifdef WIN32
- uvproc->uvstdio[0].flags |= UV_OVERLAPPED_PIPE;
+ uvproc->uvstdio[0].flags |= proc->overlapped ? UV_OVERLAPPED_PIPE : 0;
#endif
uvproc->uvstdio[0].data.stream = STRUCT_CAST(uv_stream_t,
&proc->in.uv.pipe);
@@ -61,8 +61,9 @@ int libuv_process_spawn(LibuvProcess *uvproc)
if (!proc->out.closed) {
uvproc->uvstdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
#ifdef WIN32
- // pipe must be readable for IOCP to work.
- uvproc->uvstdio[1].flags |= UV_READABLE_PIPE | UV_OVERLAPPED_PIPE;
+ // pipe must be readable for IOCP to work on Windows.
+ uvproc->uvstdio[1].flags |= proc->overlapped ?
+ (UV_READABLE_PIPE | UV_OVERLAPPED_PIPE) : 0;
#endif
uvproc->uvstdio[1].data.stream = STRUCT_CAST(uv_stream_t,
&proc->out.uv.pipe);
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index b677b80bfe..84e81238e9 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -27,7 +27,7 @@ struct process {
Stream in, out, err;
process_exit_cb cb;
internal_process_cb internal_exit_cb, internal_close_cb;
- bool closed, detach;
+ bool closed, detach, overlapped;
MultiQueue *events;
};
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/ex_getln.c b/src/nvim/ex_getln.c
index b799e86ff7..1a836b7a98 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -432,8 +432,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level);
tv_dict_set_keys_readonly(dict);
// not readonly:
- tv_dict_add_special(dict, S_LEN("abort"),
- s->gotesc ? kSpecialVarTrue : kSpecialVarFalse);
+ tv_dict_add_bool(dict, S_LEN("abort"),
+ s->gotesc ? kBoolVarTrue : kBoolVarFalse);
try_enter(&tstate);
apply_autocmds(EVENT_CMDLINELEAVE, (char_u *)firstcbuf, (char_u *)firstcbuf,
false, curbuf);
@@ -5361,12 +5361,12 @@ void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options)
// Concatenate new results to previous ones.
ga_grow(ga, num_p);
+ // take over the pointers and put them in "ga"
for (int i = 0; i < num_p; i++) {
- ((char_u **)ga->ga_data)[ga->ga_len] = vim_strsave(p[i]);
+ ((char_u **)ga->ga_data)[ga->ga_len] = p[i];
ga->ga_len++;
}
-
- FreeWild(num_p, p);
+ xfree(p);
}
}
}
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/globals.h b/src/nvim/globals.h
index f102c3ddd8..d6d00d6e83 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -203,7 +203,7 @@ EXTERN int msg_nowait INIT(= false); // don't wait for this msg
EXTERN int emsg_off INIT(= 0); // don't display errors for now,
// unless 'debug' is set.
EXTERN int info_message INIT(= false); // printing informative message
-EXTERN int msg_hist_off INIT(= false); // don't add messages to history
+EXTERN bool msg_hist_off INIT(= false); // don't add messages to history
EXTERN int need_clr_eos INIT(= false); // need to clear text before
// displaying a message.
EXTERN int emsg_skip INIT(= 0); // don't display errors for
@@ -478,6 +478,8 @@ EXTERN int sc_col; // column for shown command
EXTERN int starting INIT(= NO_SCREEN);
// true when planning to exit. Might keep running if there is a changed buffer.
EXTERN bool exiting INIT(= false);
+// internal value of v:dying
+EXTERN int v_dying INIT(= 0);
// is stdin a terminal?
EXTERN int stdin_isatty INIT(= true);
// is stdout a terminal?
diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c
index 4b8b9992f5..a553110552 100644
--- a/src/nvim/keymap.c
+++ b/src/nvim/keymap.c
@@ -517,8 +517,8 @@ char_u *get_special_key_name(int c, int modifiers)
/// @param[in,out] srcp Source from which <> are translated. Is advanced to
/// after the <> name if there is a match.
/// @param[in] src_len Length of the srcp.
-/// @param[out] dst Location where translation result will be kept. Must have
-/// at least six bytes.
+/// @param[out] dst Location where translation result will be kept. It must
+// be at least 19 bytes per "<x>" form.
/// @param[in] keycode Prefer key code, e.g. K_DEL in place of DEL.
/// @param[in] in_string Inside a double quoted string
///
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index fca74b5901..69114c967d 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -286,10 +286,10 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
break;
}
case LUA_TBOOLEAN: {
- cur.tv->v_type = VAR_SPECIAL;
- cur.tv->vval.v_special = (lua_toboolean(lstate, -1)
- ? kSpecialVarTrue
- : kSpecialVarFalse);
+ cur.tv->v_type = VAR_BOOL;
+ cur.tv->vval.v_bool = (lua_toboolean(lstate, -1)
+ ? kBoolVarTrue
+ : kBoolVarFalse);
break;
}
case LUA_TSTRING: {
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/main.c b/src/nvim/main.c
index 4a9f2371a2..b3a903cbe3 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);
@@ -619,7 +621,7 @@ void getout(int exitval)
/* Optionally print hashtable efficiency. */
hash_debug_results();
- if (get_vim_var_nr(VV_DYING) <= 1) {
+ if (v_dying <= 1) {
const tabpage_T *next_tp;
// Trigger BufWinLeave for all windows, but only once per buffer.
@@ -668,8 +670,9 @@ void getout(int exitval)
shada_write_file(NULL, false);
}
- if (get_vim_var_nr(VV_DYING) <= 1)
- apply_autocmds(EVENT_VIMLEAVE, NULL, NULL, FALSE, curbuf);
+ if (v_dying <= 1) {
+ apply_autocmds(EVENT_VIMLEAVE, NULL, NULL, false, curbuf);
+ }
profile_dump();
diff --git a/src/nvim/message.c b/src/nvim/message.c
index a12e665099..8999365d32 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -306,11 +306,6 @@ bool msg_attr_keep(char_u *s, int attr, bool keep, bool multiline)
add_msg_hist((const char *)s, -1, attr, multiline);
}
- /* When displaying keep_msg, don't let msg_start() free it, caller must do
- * that. */
- if (s == keep_msg)
- keep_msg = NULL;
-
/* Truncate the message if needed. */
msg_start();
buf = msg_strtrunc(s, FALSE);
@@ -2574,10 +2569,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/normal.c b/src/nvim/normal.c
index 87d687198d..968cfde388 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -621,6 +621,8 @@ static void normal_redraw_mode_message(NormalState *s)
update_screen(0);
// now reset it, otherwise it's put in the history again
keep_msg = kmsg;
+
+ kmsg = vim_strsave(keep_msg);
msg_attr((const char *)kmsg, keep_msg_attr);
xfree(kmsg);
}
@@ -1265,10 +1267,15 @@ static void normal_redraw(NormalState *s)
// Display message after redraw. If an external message is still visible,
// it contains the kept message already.
if (keep_msg != NULL && !msg_ext_is_visible()) {
- // msg_attr_keep() will set keep_msg to NULL, must free the string here.
- // Don't reset keep_msg, msg_attr_keep() uses it to check for duplicates.
- char *p = (char *)keep_msg;
- msg_attr(p, keep_msg_attr);
+ char_u *const p = vim_strsave(keep_msg);
+
+ // msg_start() will set keep_msg to NULL, make a copy
+ // first. Don't reset keep_msg, msg_attr_keep() uses it to
+ // check for duplicates. Never put this message in
+ // history.
+ msg_hist_off = true;
+ msg_attr((const char *)p, keep_msg_attr);
+ msg_hist_off = false;
xfree(p);
}
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index a70224f98b..eb32a1dd9b 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -2740,14 +2740,18 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
tv_dict_add_str(dict, S_LEN("regname"), buf);
// Motion type: inclusive or exclusive.
- tv_dict_add_special(dict, S_LEN("inclusive"),
- oap->inclusive ? kSpecialVarTrue : kSpecialVarFalse);
+ tv_dict_add_bool(dict, S_LEN("inclusive"),
+ oap->inclusive ? kBoolVarTrue : kBoolVarFalse);
// Kind of operation: yank, delete, change).
buf[0] = (char)get_op_char(oap->op_type);
buf[1] = NUL;
tv_dict_add_str(dict, S_LEN("operator"), buf);
+ // Selection type: visual or not.
+ tv_dict_add_bool(dict, S_LEN("visual"),
+ oap->is_VIsual ? kBoolVarTrue : kBoolVarFalse);
+
tv_dict_set_keys_readonly(dict);
textlock++;
apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, false, curbuf);
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index c1580c5fc3..b7878d9da8 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -188,8 +188,13 @@ size_t input_enqueue(String keys)
char *ptr = keys.data;
char *end = ptr + keys.size;
- while (rbuffer_space(input_buffer) >= 6 && ptr < end) {
- uint8_t buf[6] = { 0 };
+ while (rbuffer_space(input_buffer) >= 19 && ptr < end) {
+ // A "<x>" form occupies at least 1 characters, and produces up
+ // to 19 characters (1 + 5 * 3 for the char and 3 for a modifier).
+ // In the case of K_SPECIAL(0x80) or CSI(0x9B), 3 bytes are escaped and
+ // needed, but since the keys are UTF-8, so the first byte cannot be
+ // K_SPECIAL(0x80) or CSI(0x9B).
+ uint8_t buf[19] = { 0 };
unsigned int new_size
= trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true,
false);
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/signal.c b/src/nvim/os/signal.c
index 112de9fed8..bfe230b521 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -157,6 +157,7 @@ static void deadly_signal(int signum)
{
// Set the v:dying variable.
set_vim_var_nr(VV_DYING, 1);
+ v_dying = 1;
WLOG("got signal %d (%s)", signum, signal_name(signum));
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/popupmnu.c b/src/nvim/popupmnu.c
index e06433892d..c712762bdf 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -923,6 +923,6 @@ void pum_set_event_info(dict_T *dict)
tv_dict_add_float(dict, S_LEN("row"), r);
tv_dict_add_float(dict, S_LEN("col"), c);
tv_dict_add_nr(dict, S_LEN("size"), pum_size);
- tv_dict_add_special(dict, S_LEN("scrollbar"),
- pum_scrollbar ? kSpecialVarTrue : kSpecialVarFalse);
+ tv_dict_add_bool(dict, S_LEN("scrollbar"),
+ pum_scrollbar ? kBoolVarTrue : kBoolVarFalse);
}
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index f33c61d39f..2ca5f42e51 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -249,6 +249,7 @@ static char_u e_nul_found[] = N_(
static char_u e_misplaced[] = N_("E866: (NFA regexp) Misplaced %c");
static char_u e_ill_char_class[] = N_(
"E877: (NFA regexp) Invalid character class: %" PRId64);
+static char_u e_value_too_large[] = N_("E951: \\% value too large");
/* Since the out pointers in the list are always
* uninitialized, we use the pointers themselves
@@ -1499,7 +1500,8 @@ static int nfa_regatom(void)
c = getchr();
while (ascii_isdigit(c)) {
if (n > (INT32_MAX - (c - '0')) / 10) {
- EMSG(_("E951: \\% value too large"));
+ // overflow.
+ EMSG(_(e_value_too_large));
return FAIL;
}
n = n * 10 + (c - '0');
@@ -1526,7 +1528,7 @@ static int nfa_regatom(void)
limit = INT32_MAX / MB_MAXBYTES;
}
if (n >= limit) {
- EMSG(_("E951: \\% value too large"));
+ EMSG(_(e_value_too_large));
return FAIL;
}
EMIT((int)n);
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/syntax.c b/src/nvim/syntax.c
index ef4dfb3caa..f3b05c3961 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -7444,6 +7444,8 @@ static int syn_add_group(char_u *name)
return 0;
}
+ char_u *const name_up = vim_strsave_up(name);
+
// Append another syntax_highlight entry.
struct hl_group* hlgp = GA_APPEND_VIA_PTR(struct hl_group, &highlight_ga);
memset(hlgp, 0, sizeof(*hlgp));
@@ -7452,7 +7454,7 @@ static int syn_add_group(char_u *name)
hlgp->sg_rgb_fg = -1;
hlgp->sg_rgb_sp = -1;
hlgp->sg_blend = -1;
- hlgp->sg_name_u = vim_strsave_up(name);
+ hlgp->sg_name_u = name_up;
return highlight_ga.ga_len; /* ID is index plus one */
}
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_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 2c7d64f078..f8d84f1a49 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -692,6 +692,22 @@ func Test_getcmdwin_autocmd()
augroup END
endfunc
+" Test error: "E135: *Filter* Autocommands must not change current buffer"
+func Test_cmd_bang_E135()
+ new
+ call setline(1, ['a', 'b', 'c', 'd'])
+ augroup test_cmd_filter_E135
+ au!
+ autocmd FilterReadPost * help
+ augroup END
+ call assert_fails('2,3!echo "x"', 'E135:')
+
+ augroup test_cmd_filter_E135
+ au!
+ augroup END
+ %bwipe!
+endfunc
+
func Test_verbosefile()
set verbosefile=Xlog
echomsg 'foo'
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..ffd2cee80f 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'],
@@ -251,7 +252,7 @@ let s:filename_checks = {
\ 'lilo': ['lilo.conf'],
\ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf'],
\ 'liquid': ['file.liquid'],
- \ 'lisp': ['sbclrc', '.sbclrc'],
+ \ 'lisp': ['file.lsp', 'file.lisp', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'],
\ 'lite': ['file.lite', 'file.lt'],
\ 'litestep': ['/LiteStep/any/file.rc'],
\ 'loginaccess': ['/etc/login.access'],
diff --git a/src/nvim/testdir/test_ga.vim b/src/nvim/testdir/test_ga.vim
index ea3d211aeb..87f1382342 100644
--- a/src/nvim/testdir/test_ga.vim
+++ b/src/nvim/testdir/test_ga.vim
@@ -24,6 +24,7 @@ func Test_ga_command()
" Test a few multi-bytes characters.
call assert_equal("\n<é> 233, Hex 00e9, Oct 351, Digr e'", Do_ga('é'))
call assert_equal("\n<ẻ> 7867, Hex 1ebb, Oct 17273, Digr e2", Do_ga('ẻ'))
+ call assert_equal("\n<\U00012345> 74565, Hex 00012345, Octal 221505", Do_ga("\U00012345"))
" Test with combining characters.
call assert_equal("\n<e> 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401", Do_ga("e\u0301"))
diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim
index 5f73bd80ad..238d2f900d 100644
--- a/src/nvim/testdir/test_maparg.vim
+++ b/src/nvim/testdir/test_maparg.vim
@@ -15,23 +15,23 @@ function Test_maparg()
map foo<C-V> is<F4>foo
vnoremap <script> <buffer> <expr> <silent> bar isbar
call assert_equal("is<F4>foo", maparg('foo<C-V>'))
- call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'foo<C-V>',
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo<C-V>',
\ 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': sid, 'lnum': lnum + 1,
\ 'rhs': 'is<F4>foo', 'buffer': 0},
\ maparg('foo<C-V>', '', 0, 1))
- call assert_equal({'silent': 1, 'noremap': 1, 'lhs': 'bar', 'mode': 'v',
+ call assert_equal({'silent': 1, 'noremap': 1, 'script': 1, 'lhs': 'bar', 'mode': 'v',
\ 'nowait': 0, 'expr': 1, 'sid': sid, 'lnum': lnum + 2,
\ 'rhs': 'isbar', 'buffer': 1},
\ maparg('bar', '', 0, 1))
let lnum = expand('<sflnum>')
map <buffer> <nowait> foo bar
- call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'foo', 'mode': ' ',
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo', 'mode': ' ',
\ 'nowait': 1, 'expr': 0, 'sid': sid, 'lnum': lnum + 1, 'rhs': 'bar',
\ 'buffer': 1},
\ maparg('foo', '', 0, 1))
let lnum = expand('<sflnum>')
tmap baz foo
- call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'baz', 'mode': 't',
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'baz', 'mode': 't',
\ 'nowait': 0, 'expr': 0, 'sid': sid, 'lnum': lnum + 1, 'rhs': 'foo',
\ 'buffer': 0},
\ maparg('baz', 't', 0, 1))
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..04a5c62f66 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
@@ -554,3 +561,18 @@ func Test_visualbell()
set novisualbell
set belloff=all
endfunc
+
+" Test for setting option values using v:false and v:true
+func Test_opt_boolean()
+ set number&
+ set number
+ call assert_equal(1, &nu)
+ set nonu
+ call assert_equal(0, &nu)
+ let &nu = v:true
+ call assert_equal(1, &nu)
+ let &nu = v:false
+ call assert_equal(0, &nu)
+ set number&
+endfunc
+
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/src/nvim/vim.h b/src/nvim/vim.h
index c1eea1ab90..832703e83d 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -101,6 +101,7 @@ typedef enum {
#define VAR_TYPE_DICT 4
#define VAR_TYPE_FLOAT 5
#define VAR_TYPE_BOOL 6
+#define VAR_TYPE_SPECIAL 7
// values for xp_context when doing command line completion
diff --git a/src/tree_sitter/README.md b/src/tree_sitter/README.md
new file mode 100644
index 0000000000..20cb35e7c3
--- /dev/null
+++ b/src/tree_sitter/README.md
@@ -0,0 +1,16 @@
+Tree-sitter vendor runtime
+==========================
+
+This is the vendor runtime code for treesitter.
+
+The original code can be found [here](https://github.com/tree-sitter/tree-sitter).
+
+As this code is not ours, if you find any bugs, feel free to open an issue, so that we can
+investigate and determine if this should go upstream.
+
+# Updating
+
+To update the treesitter runtime, use the `update-ts-runtime.sh` script in the `scripts` directory:
+```sh
+./scripts/update-ts-runtime.sh <commit you want to update to>
+```
diff --git a/src/tree_sitter/alloc.h b/src/tree_sitter/alloc.h
index 2229995bd1..d3c6b5eca8 100644
--- a/src/tree_sitter/alloc.h
+++ b/src/tree_sitter/alloc.h
@@ -51,6 +51,7 @@ static inline void ts_free(void *buffer) {
#include <stdlib.h>
static inline bool ts_toggle_allocation_recording(bool value) {
+ (void)value;
return false;
}
diff --git a/src/tree_sitter/language.c b/src/tree_sitter/language.c
index a396b4b0b6..c00c49e3c0 100644
--- a/src/tree_sitter/language.c
+++ b/src/tree_sitter/language.c
@@ -33,8 +33,8 @@ void ts_language_table_entry(
assert(symbol < self->token_count);
uint32_t action_index = ts_language_lookup(self, state, symbol);
const TSParseActionEntry *entry = &self->parse_actions[action_index];
- result->action_count = entry->count;
- result->is_reusable = entry->reusable;
+ result->action_count = entry->entry.count;
+ result->is_reusable = entry->entry.reusable;
result->actions = (const TSParseAction *)(entry + 1);
}
}
diff --git a/src/tree_sitter/language.h b/src/tree_sitter/language.h
index f908b4593a..341f0f85af 100644
--- a/src/tree_sitter/language.h
+++ b/src/tree_sitter/language.h
@@ -93,7 +93,7 @@ static inline TSStateId ts_language_next_state(const TSLanguage *self,
if (count > 0) {
TSParseAction action = actions[count - 1];
if (action.type == TSParseActionTypeShift) {
- return action.params.extra ? state : action.params.state;
+ return action.params.shift.extra ? state : action.params.shift.state;
}
}
return 0;
diff --git a/src/tree_sitter/parser.c b/src/tree_sitter/parser.c
index d4b227308b..dd222cd3c4 100644
--- a/src/tree_sitter/parser.c
+++ b/src/tree_sitter/parser.c
@@ -101,9 +101,10 @@ typedef struct {
static const char *ts_string_input_read(
void *_self,
uint32_t byte,
- TSPoint _,
+ TSPoint pt,
uint32_t *length
) {
+ (void)pt;
TSStringInput *self = (TSStringInput *)_self;
if (byte >= self->length) {
*length = 0;
@@ -210,6 +211,7 @@ static ErrorComparison ts_parser__compare_versions(
ErrorStatus a,
ErrorStatus b
) {
+ (void)self;
if (!a.is_in_error && b.is_in_error) {
if (a.cost < b.cost) {
return ErrorComparisonTakeLeft;
@@ -951,15 +953,15 @@ static bool ts_parser__do_all_potential_reductions(
switch (action.type) {
case TSParseActionTypeShift:
case TSParseActionTypeRecover:
- if (!action.params.extra && !action.params.repetition) has_shift_action = true;
+ if (!action.params.shift.extra && !action.params.shift.repetition) has_shift_action = true;
break;
case TSParseActionTypeReduce:
- if (action.params.child_count > 0)
+ if (action.params.reduce.child_count > 0)
ts_reduce_action_set_add(&self->reduce_actions, (ReduceAction){
- .symbol = action.params.symbol,
- .count = action.params.child_count,
- .dynamic_precedence = action.params.dynamic_precedence,
- .production_id = action.params.production_id,
+ .symbol = action.params.reduce.symbol,
+ .count = action.params.reduce.child_count,
+ .dynamic_precedence = action.params.reduce.dynamic_precedence,
+ .production_id = action.params.reduce.production_id,
});
default:
break;
@@ -1250,7 +1252,7 @@ static void ts_parser__recover(
// be counted in error cost calculations.
unsigned n;
const TSParseAction *actions = ts_language_actions(self->language, 1, ts_subtree_symbol(lookahead), &n);
- if (n > 0 && actions[n - 1].type == TSParseActionTypeShift && actions[n - 1].params.extra) {
+ if (n > 0 && actions[n - 1].type == TSParseActionTypeShift && actions[n - 1].params.shift.extra) {
MutableSubtree mutable_lookahead = ts_subtree_make_mut(&self->tree_pool, lookahead);
ts_subtree_set_extra(&mutable_lookahead);
lookahead = ts_subtree_from_mut(mutable_lookahead);
@@ -1379,9 +1381,9 @@ static bool ts_parser__advance(
switch (action.type) {
case TSParseActionTypeShift: {
- if (action.params.repetition) break;
+ if (action.params.shift.repetition) break;
TSStateId next_state;
- if (action.params.extra) {
+ if (action.params.shift.extra) {
// TODO: remove when TREE_SITTER_LANGUAGE_VERSION 9 is out.
if (state == ERROR_STATE) continue;
@@ -1389,7 +1391,7 @@ static bool ts_parser__advance(
next_state = state;
LOG("shift_extra");
} else {
- next_state = action.params.state;
+ next_state = action.params.shift.state;
LOG("shift state:%u", next_state);
}
@@ -1398,7 +1400,7 @@ static bool ts_parser__advance(
next_state = ts_language_next_state(self->language, state, ts_subtree_symbol(lookahead));
}
- ts_parser__shift(self, version, next_state, lookahead, action.params.extra);
+ ts_parser__shift(self, version, next_state, lookahead, action.params.shift.extra);
if (did_reuse) reusable_node_advance(&self->reusable_node);
return true;
}
@@ -1406,10 +1408,10 @@ static bool ts_parser__advance(
case TSParseActionTypeReduce: {
bool is_fragile = table_entry.action_count > 1;
bool is_extra = lookahead.ptr == NULL;
- LOG("reduce sym:%s, child_count:%u", SYM_NAME(action.params.symbol), action.params.child_count);
+ LOG("reduce sym:%s, child_count:%u", SYM_NAME(action.params.reduce.symbol), action.params.reduce.child_count);
StackVersion reduction_version = ts_parser__reduce(
- self, version, action.params.symbol, action.params.child_count,
- action.params.dynamic_precedence, action.params.production_id,
+ self, version, action.params.reduce.symbol, action.params.reduce.child_count,
+ action.params.reduce.dynamic_precedence, action.params.reduce.production_id,
is_fragile, is_extra
);
if (reduction_version != STACK_VERSION_NONE) {
diff --git a/src/tree_sitter/parser.h b/src/tree_sitter/parser.h
index 9df91f8c3c..11bf4fc42a 100644
--- a/src/tree_sitter/parser.h
+++ b/src/tree_sitter/parser.h
@@ -62,13 +62,13 @@ typedef struct {
TSStateId state;
bool extra : 1;
bool repetition : 1;
- };
+ } shift;
struct {
TSSymbol symbol;
int16_t dynamic_precedence;
uint8_t child_count;
uint8_t production_id;
- };
+ } reduce;
} params;
TSParseActionType type : 4;
} TSParseAction;
@@ -83,7 +83,7 @@ typedef union {
struct {
uint8_t count;
bool reusable : 1;
- };
+ } entry;
} TSParseActionEntry;
struct TSLanguage {
@@ -167,22 +167,28 @@ struct TSLanguage {
#define ACTIONS(id) id
-#define SHIFT(state_value) \
- { \
- { \
- .type = TSParseActionTypeShift, \
- .params = {.state = state_value}, \
- } \
+#define SHIFT(state_value) \
+ { \
+ { \
+ .params = { \
+ .shift = { \
+ .state = state_value \
+ } \
+ }, \
+ .type = TSParseActionTypeShift \
+ } \
}
#define SHIFT_REPEAT(state_value) \
{ \
{ \
- .type = TSParseActionTypeShift, \
.params = { \
- .state = state_value, \
- .repetition = true \
+ .shift = { \
+ .state = state_value, \
+ .repetition = true \
+ } \
}, \
+ .type = TSParseActionTypeShift \
} \
}
@@ -194,20 +200,26 @@ struct TSLanguage {
#define SHIFT_EXTRA() \
{ \
{ \
- .type = TSParseActionTypeShift, \
- .params = {.extra = true} \
+ .params = { \
+ .shift = { \
+ .extra = true \
+ } \
+ }, \
+ .type = TSParseActionTypeShift \
} \
}
#define REDUCE(symbol_val, child_count_val, ...) \
{ \
{ \
- .type = TSParseActionTypeReduce, \
.params = { \
- .symbol = symbol_val, \
- .child_count = child_count_val, \
- __VA_ARGS__ \
- } \
+ .reduce = { \
+ .symbol = symbol_val, \
+ .child_count = child_count_val, \
+ __VA_ARGS__ \
+ }, \
+ }, \
+ .type = TSParseActionTypeReduce \
} \
}
diff --git a/src/tree_sitter/query.c b/src/tree_sitter/query.c
index 92a8006179..59902dee3b 100644
--- a/src/tree_sitter/query.c
+++ b/src/tree_sitter/query.c
@@ -8,6 +8,13 @@
#include "./unicode.h"
#include <wctype.h>
+// #define LOG(...) fprintf(stderr, __VA_ARGS__)
+#define LOG(...)
+
+#define MAX_STATE_COUNT 256
+#define MAX_CAPTURE_LIST_COUNT 32
+#define MAX_STEP_CAPTURE_COUNT 3
+
/*
* Stream - A sequence of unicode characters derived from a UTF8 string.
* This struct is used in parsing queries from S-expressions.
@@ -19,37 +26,35 @@ typedef struct {
uint8_t next_size;
} Stream;
-#define MAX_STEP_CAPTURE_COUNT 4
-
/*
* QueryStep - A step in the process of matching a query. Each node within
* a query S-expression maps to one of these steps. An entire pattern is
* represented as a sequence of these steps. Fields:
*
* - `symbol` - The grammar symbol to match. A zero value represents the
- * wildcard symbol, '*'.
+ * wildcard symbol, '_'.
* - `field` - The field name to match. A zero value means that a field name
* was not specified.
- * - `capture_id` - An integer representing the name of the capture associated
- * with this node in the pattern. A `NONE` value means this node is not
- * captured in this pattern.
+ * - `capture_ids` - An array of integers representing the names of captures
+ * associated with this node in the pattern, terminated by a `NONE` value.
* - `depth` - The depth where this node occurs in the pattern. The root node
* of the pattern has depth zero.
- * - `repeat_step_index` - If this step is part of a repetition, the index of
- * the beginning of the repetition. A `NONE` value means this step is not
- * part of a repetition.
+ * - `alternative_index` - The index of a different query step that serves as
+ * an alternative to this step.
*/
typedef struct {
TSSymbol symbol;
TSFieldId field;
uint16_t capture_ids[MAX_STEP_CAPTURE_COUNT];
- uint16_t repeat_step_index;
- uint16_t depth: 11;
+ uint16_t alternative_index;
+ uint16_t depth;
bool contains_captures: 1;
bool is_pattern_start: 1;
bool is_immediate: 1;
- bool is_last: 1;
- bool is_repeated: 1;
+ bool is_last_child: 1;
+ bool is_pass_through: 1;
+ bool is_dead_end: 1;
+ bool alternative_is_immediate: 1;
} QueryStep;
/*
@@ -88,18 +93,35 @@ typedef struct {
* QueryState - The state of an in-progress match of a particular pattern
* in a query. While executing, a `TSQueryCursor` must keep track of a number
* of possible in-progress matches. Each of those possible matches is
- * represented as one of these states.
+ * represented as one of these states. Fields:
+ * - `id` - A numeric id that is exposed to the public API. This allows the
+ * caller to remove a given match, preventing any more of its captures
+ * from being returned.
+ * - `start_depth` - The depth in the tree where the first step of the state's
+ * pattern was matched.
+ * - `pattern_index` - The pattern that the state is matching.
+ * - `consumed_capture_count` - The number of captures from this match that
+ * have already been returned.
+ * - `capture_list_id` - A numeric id that can be used to retrieve the state's
+ * list of captures from the `CaptureListPool`.
+ * - `seeking_immediate_match` - A flag that indicates that the state's next
+ * step must be matched by the very next sibling. This is used when
+ * processing repetitions.
+ * - `has_in_progress_alternatives` - A flag that indicates that there is are
+ * other states that have the same captures as this state, but are at
+ * different steps in their pattern. This means that in order to obey the
+ * 'longest-match' rule, this state should not be returned as a match until
+ * it is clear that there can be no longer match.
*/
typedef struct {
uint32_t id;
uint16_t start_depth;
- uint16_t pattern_index;
uint16_t step_index;
- uint16_t consumed_capture_count;
- uint16_t repeat_match_count;
- uint16_t step_index_on_failure;
- uint8_t capture_list_id;
- bool seeking_non_match;
+ uint16_t pattern_index;
+ uint16_t capture_list_id;
+ uint16_t consumed_capture_count: 14;
+ bool seeking_immediate_match: 1;
+ bool has_in_progress_alternatives: 1;
} QueryState;
typedef Array(TSQueryCapture) CaptureList;
@@ -111,7 +133,8 @@ typedef Array(TSQueryCapture) CaptureList;
* are currently in use.
*/
typedef struct {
- CaptureList list[32];
+ CaptureList list[MAX_CAPTURE_LIST_COUNT];
+ CaptureList empty_list;
uint32_t usage_map;
} CaptureListPool;
@@ -152,14 +175,10 @@ struct TSQueryCursor {
};
static const TSQueryError PARENT_DONE = -1;
-static const uint8_t PATTERN_DONE_MARKER = UINT8_MAX;
+static const uint16_t PATTERN_DONE_MARKER = UINT16_MAX;
static const uint16_t NONE = UINT16_MAX;
static const TSSymbol WILDCARD_SYMBOL = 0;
static const TSSymbol NAMED_WILDCARD_SYMBOL = UINT16_MAX - 1;
-static const uint16_t MAX_STATE_COUNT = 32;
-
-// #define LOG(...) fprintf(stderr, __VA_ARGS__)
-#define LOG(...)
/**********
* Stream
@@ -242,24 +261,31 @@ static void stream_scan_identifier(Stream *stream) {
static CaptureListPool capture_list_pool_new(void) {
return (CaptureListPool) {
+ .empty_list = array_new(),
.usage_map = UINT32_MAX,
};
}
static void capture_list_pool_reset(CaptureListPool *self) {
self->usage_map = UINT32_MAX;
- for (unsigned i = 0; i < 32; i++) {
+ for (unsigned i = 0; i < MAX_CAPTURE_LIST_COUNT; i++) {
array_clear(&self->list[i]);
}
}
static void capture_list_pool_delete(CaptureListPool *self) {
- for (unsigned i = 0; i < 32; i++) {
+ for (unsigned i = 0; i < MAX_CAPTURE_LIST_COUNT; i++) {
array_delete(&self->list[i]);
}
}
-static CaptureList *capture_list_pool_get(CaptureListPool *self, uint16_t id) {
+static const CaptureList *capture_list_pool_get(const CaptureListPool *self, uint16_t id) {
+ if (id >= MAX_CAPTURE_LIST_COUNT) return &self->empty_list;
+ return &self->list[id];
+}
+
+static CaptureList *capture_list_pool_get_mut(CaptureListPool *self, uint16_t id) {
+ assert(id < MAX_CAPTURE_LIST_COUNT);
return &self->list[id];
}
@@ -273,12 +299,14 @@ static uint16_t capture_list_pool_acquire(CaptureListPool *self) {
// the leading zeros in the usage map. An id of zero corresponds to the
// highest-order bit in the bitmask.
uint16_t id = count_leading_zeros(self->usage_map);
- if (id == 32) return NONE;
+ if (id >= MAX_CAPTURE_LIST_COUNT) return NONE;
self->usage_map &= ~bitmask_for_index(id);
+ array_clear(&self->list[id]);
return id;
}
static void capture_list_pool_release(CaptureListPool *self, uint16_t id) {
+ if (id >= MAX_CAPTURE_LIST_COUNT) return;
array_clear(&self->list[id]);
self->usage_map |= bitmask_for_index(id);
}
@@ -416,13 +444,15 @@ static QueryStep query_step__new(
.symbol = symbol,
.depth = depth,
.field = 0,
- .capture_ids = {NONE, NONE, NONE, NONE},
+ .capture_ids = {NONE, NONE, NONE},
+ .alternative_index = NONE,
.contains_captures = false,
- .is_repeated = false,
- .is_last = false,
+ .is_last_child = false,
.is_pattern_start = false,
+ .is_pass_through = false,
+ .is_dead_end = false,
.is_immediate = is_immediate,
- .repeat_step_index = NONE,
+ .alternative_is_immediate = false,
};
}
@@ -511,13 +541,14 @@ static inline bool ts_query__pattern_map_search(
static inline void ts_query__pattern_map_insert(
TSQuery *self,
TSSymbol symbol,
- uint32_t start_step_index
+ uint32_t start_step_index,
+ uint32_t pattern_index
) {
uint32_t index;
ts_query__pattern_map_search(self, symbol, &index);
array_insert(&self->pattern_map, index, ((PatternEntry) {
.step_index = start_step_index,
- .pattern_index = self->pattern_map.size,
+ .pattern_index = pattern_index,
}));
}
@@ -548,12 +579,22 @@ static TSQueryError ts_query__parse_predicate(
TSQuery *self,
Stream *stream
) {
- if (stream->next == ')') return PARENT_DONE;
- if (stream->next != '(') return TSQueryErrorSyntax;
- stream_advance(stream);
+ if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax;
+ const char *predicate_name = stream->input;
+ stream_scan_identifier(stream);
+ uint32_t length = stream->input - predicate_name;
+ uint16_t id = symbol_table_insert_name(
+ &self->predicate_values,
+ predicate_name,
+ length
+ );
+ array_back(&self->predicates_by_pattern)->length++;
+ array_push(&self->predicate_steps, ((TSQueryPredicateStep) {
+ .type = TSQueryPredicateStepTypeString,
+ .value_id = id,
+ }));
stream_skip_whitespace(stream);
- unsigned step_count = 0;
for (;;) {
if (stream->next == ')') {
stream_advance(stream);
@@ -658,7 +699,6 @@ static TSQueryError ts_query__parse_predicate(
return TSQueryErrorSyntax;
}
- step_count++;
stream_skip_whitespace(stream);
}
@@ -675,102 +715,195 @@ static TSQueryError ts_query__parse_pattern(
uint32_t *capture_count,
bool is_immediate
) {
- uint16_t starting_step_index = self->steps.size;
+ uint32_t starting_step_index = self->steps.size;
if (stream->next == 0) return TSQueryErrorSyntax;
- // Finish the parent S-expression
- if (stream->next == ')') {
+ // Finish the parent S-expression.
+ if (stream->next == ')' || stream->next == ']') {
return PARENT_DONE;
}
- // Parse a parenthesized node expression
- else if (stream->next == '(') {
+ // An open bracket is the start of an alternation.
+ else if (stream->next == '[') {
stream_advance(stream);
stream_skip_whitespace(stream);
- // Parse a nested list, which represents a pattern followed by
- // zero-or-more predicates.
- if (stream->next == '(' && depth == 0) {
- TSQueryError e = ts_query__parse_pattern(self, stream, 0, capture_count, is_immediate);
- if (e) return e;
+ // Parse each branch, and add a placeholder step in between the branches.
+ Array(uint32_t) branch_step_indices = array_new();
+ for (;;) {
+ uint32_t start_index = self->steps.size;
+ TSQueryError e = ts_query__parse_pattern(
+ self,
+ stream,
+ depth,
+ capture_count,
+ is_immediate
+ );
- // Parse the predicates.
- stream_skip_whitespace(stream);
+ if (e == PARENT_DONE && stream->next == ']' && branch_step_indices.size > 0) {
+ stream_advance(stream);
+ break;
+ } else if (e) {
+ array_delete(&branch_step_indices);
+ return e;
+ }
+
+ array_push(&branch_step_indices, start_index);
+ array_push(&self->steps, query_step__new(0, depth, false));
+ }
+ (void)array_pop(&self->steps);
+
+ // For all of the branches except for the last one, add the subsequent branch as an
+ // alternative, and link the end of the branch to the current end of the steps.
+ for (unsigned i = 0; i < branch_step_indices.size - 1; i++) {
+ uint32_t step_index = branch_step_indices.contents[i];
+ uint32_t next_step_index = branch_step_indices.contents[i + 1];
+ QueryStep *start_step = &self->steps.contents[step_index];
+ QueryStep *end_step = &self->steps.contents[next_step_index - 1];
+ start_step->alternative_index = next_step_index;
+ end_step->alternative_index = self->steps.size;
+ end_step->is_dead_end = true;
+ }
+
+ array_delete(&branch_step_indices);
+ }
+
+ // An open parenthesis can be the start of three possible constructs:
+ // * A grouped sequence
+ // * A predicate
+ // * A named node
+ else if (stream->next == '(') {
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+
+ // If this parenthesis is followed by a node, then it represents a grouped sequence.
+ if (stream->next == '(' || stream->next == '"' || stream->next == '[') {
+ bool child_is_immediate = false;
for (;;) {
- TSQueryError e = ts_query__parse_predicate(self, stream);
- if (e == PARENT_DONE) {
+ if (stream->next == '.') {
+ child_is_immediate = true;
stream_advance(stream);
stream_skip_whitespace(stream);
- return 0;
+ }
+ TSQueryError e = ts_query__parse_pattern(
+ self,
+ stream,
+ depth,
+ capture_count,
+ child_is_immediate
+ );
+ if (e == PARENT_DONE && stream->next == ')') {
+ stream_advance(stream);
+ break;
} else if (e) {
return e;
}
+
+ child_is_immediate = false;
}
}
- TSSymbol symbol;
-
- // Parse the wildcard symbol
- if (stream->next == '*') {
- symbol = depth > 0 ? NAMED_WILDCARD_SYMBOL : WILDCARD_SYMBOL;
+ // A pound character indicates the start of a predicate.
+ else if (stream->next == '#') {
stream_advance(stream);
+ return ts_query__parse_predicate(self, stream);
}
- // Parse a normal node name
- else if (stream_is_ident_start(stream)) {
- const char *node_name = stream->input;
- stream_scan_identifier(stream);
- uint32_t length = stream->input - node_name;
- symbol = ts_language_symbol_for_name(
- self->language,
- node_name,
- length,
- true
- );
- if (!symbol) {
- stream_reset(stream, node_name);
- return TSQueryErrorNodeType;
- }
- } else {
- return TSQueryErrorSyntax;
- }
+ // Otherwise, this parenthesis is the start of a named node.
+ else {
+ TSSymbol symbol;
- // Add a step for the node.
- array_push(&self->steps, query_step__new(symbol, depth, is_immediate));
+ // Parse the wildcard symbol
+ if (
+ stream->next == '_' ||
- // Parse the child patterns
- stream_skip_whitespace(stream);
- bool child_is_immediate = false;
- uint16_t child_start_step_index = self->steps.size;
- for (;;) {
- if (stream->next == '.') {
- child_is_immediate = true;
+ // TODO - remove.
+ // For temporary backward compatibility, handle '*' as a wildcard.
+ stream->next == '*'
+ ) {
+ symbol = depth > 0 ? NAMED_WILDCARD_SYMBOL : WILDCARD_SYMBOL;
stream_advance(stream);
- stream_skip_whitespace(stream);
}
- TSQueryError e = ts_query__parse_pattern(
- self,
- stream,
- depth + 1,
- capture_count,
- child_is_immediate
- );
- if (e == PARENT_DONE) {
- if (child_is_immediate) {
- self->steps.contents[child_start_step_index].is_last = true;
+ // Parse a normal node name
+ else if (stream_is_ident_start(stream)) {
+ const char *node_name = stream->input;
+ stream_scan_identifier(stream);
+ uint32_t length = stream->input - node_name;
+
+ // TODO - remove.
+ // For temporary backward compatibility, handle predicates without the leading '#' sign.
+ if (length > 0 && (node_name[length - 1] == '!' || node_name[length - 1] == '?')) {
+ stream_reset(stream, node_name);
+ return ts_query__parse_predicate(self, stream);
}
- stream_advance(stream);
- break;
- } else if (e) {
- return e;
+
+ symbol = ts_language_symbol_for_name(
+ self->language,
+ node_name,
+ length,
+ true
+ );
+ if (!symbol) {
+ stream_reset(stream, node_name);
+ return TSQueryErrorNodeType;
+ }
+ } else {
+ return TSQueryErrorSyntax;
}
- child_is_immediate = false;
+ // Add a step for the node.
+ array_push(&self->steps, query_step__new(symbol, depth, is_immediate));
+
+ // Parse the child patterns
+ stream_skip_whitespace(stream);
+ bool child_is_immediate = false;
+ uint16_t child_start_step_index = self->steps.size;
+ for (;;) {
+ if (stream->next == '.') {
+ child_is_immediate = true;
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+ }
+
+ TSQueryError e = ts_query__parse_pattern(
+ self,
+ stream,
+ depth + 1,
+ capture_count,
+ child_is_immediate
+ );
+ if (e == PARENT_DONE && stream->next == ')') {
+ if (child_is_immediate) {
+ self->steps.contents[child_start_step_index].is_last_child = true;
+ }
+ stream_advance(stream);
+ break;
+ } else if (e) {
+ return e;
+ }
+
+ child_is_immediate = false;
+ }
}
}
+ // Parse a wildcard pattern
+ else if (
+ stream->next == '_' ||
+
+ // TODO remove.
+ // For temporary backward compatibility, handle '*' as a wildcard.
+ stream->next == '*'
+ ) {
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+
+ // Add a step that matches any kind of node
+ array_push(&self->steps, query_step__new(WILDCARD_SYMBOL, depth, is_immediate));
+ }
+
// Parse a double-quoted anonymous leaf node expression
else if (stream->next == '"') {
stream_advance(stream);
@@ -842,15 +975,6 @@ static TSQueryError ts_query__parse_pattern(
self->steps.contents[step_index].field = field_id;
}
- // Parse a wildcard pattern
- else if (stream->next == '*') {
- stream_advance(stream);
- stream_skip_whitespace(stream);
-
- // Add a step that matches any kind of node
- array_push(&self->steps, query_step__new(WILDCARD_SYMBOL, depth, is_immediate));
- }
-
else {
return TSQueryErrorSyntax;
}
@@ -861,22 +985,54 @@ static TSQueryError ts_query__parse_pattern(
for (;;) {
QueryStep *step = &self->steps.contents[starting_step_index];
+ // Parse the one-or-more operator.
if (stream->next == '+') {
stream_advance(stream);
- step->is_repeated = true;
- array_back(&self->steps)->repeat_step_index = starting_step_index;
stream_skip_whitespace(stream);
+
+ QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false);
+ repeat_step.alternative_index = starting_step_index;
+ repeat_step.is_pass_through = true;
+ repeat_step.alternative_is_immediate = true;
+ array_push(&self->steps, repeat_step);
+ }
+
+ // Parse the zero-or-more repetition operator.
+ else if (stream->next == '*') {
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+
+ QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false);
+ repeat_step.alternative_index = starting_step_index;
+ repeat_step.is_pass_through = true;
+ repeat_step.alternative_is_immediate = true;
+ array_push(&self->steps, repeat_step);
+
+ while (step->alternative_index != NONE) {
+ step = &self->steps.contents[step->alternative_index];
+ }
+ step->alternative_index = self->steps.size;
+ }
+
+ // Parse the optional operator.
+ else if (stream->next == '?') {
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+
+ while (step->alternative_index != NONE) {
+ step = &self->steps.contents[step->alternative_index];
+ }
+ step->alternative_index = self->steps.size;
}
// Parse an '@'-prefixed capture pattern
else if (stream->next == '@') {
stream_advance(stream);
-
- // Parse the capture name
if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax;
const char *capture_name = stream->input;
stream_scan_identifier(stream);
uint32_t length = stream->input - capture_name;
+ stream_skip_whitespace(stream);
// Add the capture id to the first step of the pattern
uint16_t capture_id = symbol_table_insert_name(
@@ -884,10 +1040,22 @@ static TSQueryError ts_query__parse_pattern(
capture_name,
length
);
- query_step__add_capture(step, capture_id);
- (*capture_count)++;
- stream_skip_whitespace(stream);
+ for (;;) {
+ query_step__add_capture(step, capture_id);
+ if (
+ step->alternative_index != NONE &&
+ step->alternative_index > starting_step_index &&
+ step->alternative_index < self->steps.size
+ ) {
+ starting_step_index = step->alternative_index;
+ step = &self->steps.contents[starting_step_index];
+ } else {
+ break;
+ }
+ }
+
+ (*capture_count)++;
}
// No more suffix modifiers
@@ -950,6 +1118,7 @@ TSQuery *ts_query_new(
Stream stream = stream_new(source, source_len);
stream_skip_whitespace(&stream);
while (stream.input < stream.end) {
+ uint32_t pattern_index = self->predicates_by_pattern.size;
uint32_t start_step_index = self->steps.size;
uint32_t capture_count = 0;
array_push(&self->start_bytes_by_pattern, stream.input - source);
@@ -963,6 +1132,7 @@ TSQuery *ts_query_new(
// If any pattern could not be parsed, then report the error information
// and terminate.
if (*error_type) {
+ if (*error_type == PARENT_DONE) *error_type = TSQueryErrorSyntax;
*error_offset = stream.input - source;
ts_query_delete(self);
return NULL;
@@ -980,14 +1150,21 @@ TSQuery *ts_query_new(
}
// Maintain a map that can look up patterns for a given root symbol.
- self->steps.contents[start_step_index].is_pattern_start = true;
- ts_query__pattern_map_insert(
- self,
- self->steps.contents[start_step_index].symbol,
- start_step_index
- );
- if (self->steps.contents[start_step_index].symbol == WILDCARD_SYMBOL) {
- self->wildcard_root_pattern_count++;
+ for (;;) {
+ QueryStep *step = &self->steps.contents[start_step_index];
+ step->is_pattern_start = true;
+ ts_query__pattern_map_insert(self, step->symbol, start_step_index, pattern_index);
+ if (step->symbol == WILDCARD_SYMBOL) {
+ self->wildcard_root_pattern_count++;
+ }
+
+ // If there are alternatives or options at the root of the pattern,
+ // then add multiple entries to the pattern map.
+ if (step->alternative_index != NONE) {
+ start_step_index = step->alternative_index;
+ } else {
+ break;
+ }
}
}
@@ -1090,7 +1267,7 @@ void ts_query_disable_pattern(
* QueryCursor
***************/
-TSQueryCursor *ts_query_cursor_new() {
+TSQueryCursor *ts_query_cursor_new(void) {
TSQueryCursor *self = ts_malloc(sizeof(TSQueryCursor));
*self = (TSQueryCursor) {
.ascending = false,
@@ -1103,7 +1280,7 @@ TSQueryCursor *ts_query_cursor_new() {
.end_point = POINT_MAX,
};
array_reserve(&self->states, MAX_STATE_COUNT);
- array_reserve(&self->finished_states, MAX_STATE_COUNT);
+ array_reserve(&self->finished_states, MAX_CAPTURE_LIST_COUNT);
return self;
}
@@ -1165,6 +1342,9 @@ static bool ts_query_cursor__first_in_progress_capture(
uint32_t *pattern_index
) {
bool result = false;
+ *state_index = UINT32_MAX;
+ *byte_offset = UINT32_MAX;
+ *pattern_index = UINT32_MAX;
for (unsigned i = 0; i < self->states.size; i++) {
const QueryState *state = &self->states.contents[i];
const CaptureList *captures = capture_list_pool_get(
@@ -1176,10 +1356,7 @@ static bool ts_query_cursor__first_in_progress_capture(
if (
!result ||
capture_byte < *byte_offset ||
- (
- capture_byte == *byte_offset &&
- state->pattern_index < *pattern_index
- )
+ (capture_byte == *byte_offset && state->pattern_index < *pattern_index)
) {
result = true;
*state_index = i;
@@ -1191,85 +1368,138 @@ static bool ts_query_cursor__first_in_progress_capture(
return result;
}
-static bool ts_query__cursor_add_state(
- TSQueryCursor *self,
- const PatternEntry *pattern
-) {
- QueryStep *step = &self->query->steps.contents[pattern->step_index];
-
- // If this pattern begins with a repetition, then avoid creating
- // new states after already matching the repetition one or more times.
- // The query should only one match for the repetition - the one that
- // started the earliest.
- if (step->is_repeated) {
- for (unsigned i = 0; i < self->states.size; i++) {
- QueryState *state = &self->states.contents[i];
- if (state->step_index == pattern->step_index) return true;
- }
+// Determine which node is first in a depth-first traversal
+int ts_query_cursor__compare_nodes(TSNode left, TSNode right) {
+ if (left.id != right.id) {
+ uint32_t left_start = ts_node_start_byte(left);
+ uint32_t right_start = ts_node_start_byte(right);
+ if (left_start < right_start) return -1;
+ if (left_start > right_start) return 1;
+ uint32_t left_node_count = ts_node_end_byte(left);
+ uint32_t right_node_count = ts_node_end_byte(right);
+ if (left_node_count > right_node_count) return -1;
+ if (left_node_count < right_node_count) return 1;
}
+ return 0;
+}
- uint32_t list_id = capture_list_pool_acquire(&self->capture_list_pool);
-
- // If there are no capture lists left in the pool, then terminate whichever
- // state has captured the earliest node in the document, and steal its
- // capture list.
- if (list_id == NONE) {
- uint32_t state_index, byte_offset, pattern_index;
- if (ts_query_cursor__first_in_progress_capture(
- self,
- &state_index,
- &byte_offset,
- &pattern_index
- )) {
- LOG(
- " abandon state. index:%u, pattern:%u, offset:%u.\n",
- state_index, pattern_index, byte_offset
- );
- list_id = self->states.contents[state_index].capture_list_id;
- array_erase(&self->states, state_index);
+// Determine if either state contains a superset of the other state's captures.
+void ts_query_cursor__compare_captures(
+ TSQueryCursor *self,
+ QueryState *left_state,
+ QueryState *right_state,
+ bool *left_contains_right,
+ bool *right_contains_left
+) {
+ const CaptureList *left_captures = capture_list_pool_get(
+ &self->capture_list_pool,
+ left_state->capture_list_id
+ );
+ const CaptureList *right_captures = capture_list_pool_get(
+ &self->capture_list_pool,
+ right_state->capture_list_id
+ );
+ *left_contains_right = true;
+ *right_contains_left = true;
+ unsigned i = 0, j = 0;
+ for (;;) {
+ if (i < left_captures->size) {
+ if (j < right_captures->size) {
+ TSQueryCapture *left = &left_captures->contents[i];
+ TSQueryCapture *right = &right_captures->contents[j];
+ if (left->node.id == right->node.id && left->index == right->index) {
+ i++;
+ j++;
+ } else {
+ switch (ts_query_cursor__compare_nodes(left->node, right->node)) {
+ case -1:
+ *right_contains_left = false;
+ i++;
+ break;
+ case 1:
+ *left_contains_right = false;
+ j++;
+ break;
+ default:
+ *right_contains_left = false;
+ *left_contains_right = false;
+ i++;
+ j++;
+ break;
+ }
+ }
+ } else {
+ *right_contains_left = false;
+ break;
+ }
} else {
- LOG(" too many finished states.\n");
- return false;
+ if (j < right_captures->size) {
+ *left_contains_right = false;
+ }
+ break;
}
}
+}
+static bool ts_query_cursor__add_state(
+ TSQueryCursor *self,
+ const PatternEntry *pattern
+) {
+ if (self->states.size >= MAX_STATE_COUNT) {
+ LOG(" too many states");
+ return false;
+ }
LOG(
" start state. pattern:%u, step:%u\n",
pattern->pattern_index,
pattern->step_index
);
+ QueryStep *step = &self->query->steps.contents[pattern->step_index];
array_push(&self->states, ((QueryState) {
- .capture_list_id = list_id,
+ .capture_list_id = NONE,
.step_index = pattern->step_index,
.pattern_index = pattern->pattern_index,
.start_depth = self->depth - step->depth,
.consumed_capture_count = 0,
- .repeat_match_count = 0,
- .step_index_on_failure = NONE,
- .seeking_non_match = false,
+ .seeking_immediate_match = false,
}));
return true;
}
+// Duplicate the given state and insert the newly-created state immediately after
+// the given state in the `states` array.
static QueryState *ts_query__cursor_copy_state(
TSQueryCursor *self,
const QueryState *state
) {
- uint32_t new_list_id = capture_list_pool_acquire(&self->capture_list_pool);
- if (new_list_id == NONE) return NULL;
- array_push(&self->states, *state);
- QueryState *new_state = array_back(&self->states);
- new_state->capture_list_id = new_list_id;
- CaptureList *old_captures = capture_list_pool_get(
- &self->capture_list_pool,
- state->capture_list_id
- );
- CaptureList *new_captures = capture_list_pool_get(
- &self->capture_list_pool,
- new_list_id
- );
- array_push_all(new_captures, old_captures);
- return new_state;
+ if (self->states.size >= MAX_STATE_COUNT) {
+ LOG(" too many states");
+ return NULL;
+ }
+
+ // If the state has captures, copy its capture list.
+ QueryState copy = *state;
+ copy.capture_list_id = state->capture_list_id;
+ if (state->capture_list_id != NONE) {
+ copy.capture_list_id = capture_list_pool_acquire(&self->capture_list_pool);
+ if (copy.capture_list_id == NONE) {
+ LOG(" too many capture lists");
+ return NULL;
+ }
+ const CaptureList *old_captures = capture_list_pool_get(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ CaptureList *new_captures = capture_list_pool_get_mut(
+ &self->capture_list_pool,
+ copy.capture_list_id
+ );
+ array_push_all(new_captures, old_captures);
+ }
+
+ uint32_t index = (state - self->states.contents) + 1;
+ array_insert(&self->states, index, copy);
+ return &self->states.contents[index];
}
// Walk the tree, processing patterns until at least one pattern finishes,
@@ -1281,56 +1511,62 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
if (self->ascending) {
LOG("leave node. type:%s\n", ts_node_type(ts_tree_cursor_current_node(&self->cursor)));
- // When leaving a node, remove any unfinished states whose next step
- // needed to match something within that node.
+ // Leave this node by stepping to its next sibling or to its parent.
+ bool did_move = true;
+ if (ts_tree_cursor_goto_next_sibling(&self->cursor)) {
+ self->ascending = false;
+ } else if (ts_tree_cursor_goto_parent(&self->cursor)) {
+ self->depth--;
+ } else {
+ did_move = false;
+ }
+
+ // After leaving a node, remove any states that cannot make further progress.
uint32_t deleted_count = 0;
for (unsigned i = 0, n = self->states.size; i < n; i++) {
QueryState *state = &self->states.contents[i];
QueryStep *step = &self->query->steps.contents[state->step_index];
- if ((uint32_t)state->start_depth + (uint32_t)step->depth > self->depth) {
+ // If a state completed its pattern inside of this node, but was deferred from finishing
+ // in order to search for longer matches, mark it as finished.
+ if (step->depth == PATTERN_DONE_MARKER) {
+ if (state->start_depth > self->depth || !did_move) {
+ LOG(" finish pattern %u\n", state->pattern_index);
+ state->id = self->next_state_id++;
+ array_push(&self->finished_states, *state);
+ deleted_count++;
+ continue;
+ }
+ }
+
+ // If a state needed to match something within this node, then remove that state
+ // as it has failed to match.
+ else if ((uint32_t)state->start_depth + (uint32_t)step->depth > self->depth) {
LOG(
" failed to match. pattern:%u, step:%u\n",
state->pattern_index,
state->step_index
);
-
capture_list_pool_release(
&self->capture_list_pool,
state->capture_list_id
);
deleted_count++;
- } else if (deleted_count > 0) {
+ continue;
+ }
+
+ if (deleted_count > 0) {
self->states.contents[i - deleted_count] = *state;
}
}
-
self->states.size -= deleted_count;
- if (ts_tree_cursor_goto_next_sibling(&self->cursor)) {
- self->ascending = false;
- } else if (ts_tree_cursor_goto_parent(&self->cursor)) {
- self->depth--;
- } else {
+ if (!did_move) {
return self->finished_states.size > 0;
}
} else {
- bool has_later_siblings;
- bool can_have_later_siblings_with_this_field;
- TSFieldId field_id = ts_tree_cursor_current_status(
- &self->cursor,
- &has_later_siblings,
- &can_have_later_siblings_with_this_field
- );
+ // If this node is before the selected range, then avoid descending into it.
TSNode node = ts_tree_cursor_current_node(&self->cursor);
- TSSymbol symbol = ts_node_symbol(node);
- bool is_named = ts_node_is_named(node);
- if (symbol != ts_builtin_sym_error && self->query->symbol_map) {
- symbol = self->query->symbol_map[symbol];
- }
-
- // If this node is before the selected range, then avoid descending
- // into it.
if (
ts_node_end_byte(node) <= self->start_byte ||
point_lte(ts_node_end_point(node), self->start_point)
@@ -1347,18 +1583,26 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
point_lte(self->end_point, ts_node_start_point(node))
) return false;
+ // Get the properties of the current node.
+ TSSymbol symbol = ts_node_symbol(node);
+ bool is_named = ts_node_is_named(node);
+ if (symbol != ts_builtin_sym_error && self->query->symbol_map) {
+ symbol = self->query->symbol_map[symbol];
+ }
+ bool can_have_later_siblings;
+ bool can_have_later_siblings_with_this_field;
+ TSFieldId field_id = ts_tree_cursor_current_status(
+ &self->cursor,
+ &can_have_later_siblings,
+ &can_have_later_siblings_with_this_field
+ );
LOG(
- "enter node. "
- "type:%s, field:%s, row:%u state_count:%u, "
- "finished_state_count:%u, has_later_siblings:%d, "
- "can_have_later_siblings_with_this_field:%d\n",
+ "enter node. type:%s, field:%s, row:%u state_count:%u, finished_state_count:%u\n",
ts_node_type(node),
ts_language_field_name_for_id(self->query->language, field_id),
ts_node_start_point(node).row,
self->states.size,
- self->finished_states.size,
- has_later_siblings,
- can_have_later_siblings_with_this_field
+ self->finished_states.size
);
// Add new states for any patterns whose root node is a wildcard.
@@ -1369,7 +1613,7 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
// If this node matches the first step of the pattern, then add a new
// state at the start of this pattern.
if (step->field && field_id != step->field) continue;
- if (!ts_query__cursor_add_state(self, pattern)) break;
+ if (!ts_query_cursor__add_state(self, pattern)) break;
}
// Add new states for any patterns whose root node matches this node.
@@ -1381,7 +1625,7 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
// If this node matches the first step of the pattern, then add a new
// state at the start of this pattern.
if (step->field && field_id != step->field) continue;
- if (!ts_query__cursor_add_state(self, pattern)) break;
+ if (!ts_query_cursor__add_state(self, pattern)) break;
// Advance to the next pattern whose root node matches this node.
i++;
@@ -1392,9 +1636,11 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
}
// Update all of the in-progress states with current node.
- for (unsigned i = 0, n = self->states.size; i < n; i++) {
+ for (unsigned i = 0, copy_count = 0; i < self->states.size; i += 1 + copy_count) {
QueryState *state = &self->states.contents[i];
QueryStep *step = &self->query->steps.contents[state->step_index];
+ state->has_in_progress_alternatives = false;
+ copy_count = 0;
// Check that the node matches all of the criteria for the next
// step of the pattern.
@@ -1407,11 +1653,11 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
step->symbol == symbol ||
step->symbol == WILDCARD_SYMBOL ||
(step->symbol == NAMED_WILDCARD_SYMBOL && is_named);
- bool later_sibling_can_match = has_later_siblings;
- if (step->is_immediate && is_named) {
+ bool later_sibling_can_match = can_have_later_siblings;
+ if ((step->is_immediate && is_named) || state->seeking_immediate_match) {
later_sibling_can_match = false;
}
- if (step->is_last && has_later_siblings) {
+ if (step->is_last_child && can_have_later_siblings) {
node_does_match = false;
}
if (step->field) {
@@ -1424,25 +1670,8 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
}
}
+ // Remove states immediately if it is ever clear that they cannot match.
if (!node_does_match) {
- // If this QueryState has processed a repeating sequence, and that repeating
- // sequence has ended, move on to the *next* step of this state's pattern.
- if (
- state->step_index_on_failure != NONE &&
- (!later_sibling_can_match || step->is_repeated)
- ) {
- LOG(
- " finish repetition state. pattern:%u, step:%u\n",
- state->pattern_index,
- state->step_index
- );
- state->step_index = state->step_index_on_failure;
- state->step_index_on_failure = NONE;
- state->repeat_match_count = 0;
- i--;
- continue;
- }
-
if (!later_sibling_can_match) {
LOG(
" discard state. pattern:%u, step:%u\n",
@@ -1455,115 +1684,201 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
);
array_erase(&self->states, i);
i--;
- n--;
}
-
- state->seeking_non_match = false;
continue;
}
- // The `seeking_non_match` flag indicates that a previous QueryState
- // has already begun processing this repeating sequence, so that *this*
- // QueryState should not begin matching until a separate repeating sequence
- // is found.
- if (state->seeking_non_match) continue;
-
- // Some patterns can match their root node in multiple ways,
- // capturing different children. If this pattern step could match
- // later children within the same parent, then this query state
- // cannot simply be updated in place. It must be split into two
- // states: one that matches this node, and one which skips over
- // this node, to preserve the possibility of matching later
- // siblings.
- QueryState *next_state = state;
+ // Some patterns can match their root node in multiple ways, capturing different
+ // children. If this pattern step could match later children within the same
+ // parent, then this query state cannot simply be updated in place. It must be
+ // split into two states: one that matches this node, and one which skips over
+ // this node, to preserve the possibility of matching later siblings.
if (
- !step->is_pattern_start &&
- step->contains_captures &&
later_sibling_can_match &&
- state->repeat_match_count == 0
+ !step->is_pattern_start &&
+ step->contains_captures
) {
- QueryState *copy = ts_query__cursor_copy_state(self, state);
+ if (ts_query__cursor_copy_state(self, state)) {
+ LOG(
+ " split state for capture. pattern:%u, step:%u\n",
+ state->pattern_index,
+ state->step_index
+ );
+ copy_count++;
+ }
+ }
- // The QueryState that matched this node has begun matching a repeating
- // sequence. The QueryState that *skipped* this node should not start
- // matching later elements of the same repeating sequence.
- if (step->is_repeated) {
- state->seeking_non_match = true;
+ // If the current node is captured in this pattern, add it to the capture list.
+ // For the first capture in a pattern, lazily acquire a capture list.
+ if (step->capture_ids[0] != NONE) {
+ if (state->capture_list_id == NONE) {
+ state->capture_list_id = capture_list_pool_acquire(&self->capture_list_pool);
+
+ // If there are no capture lists left in the pool, then terminate whichever
+ // state has captured the earliest node in the document, and steal its
+ // capture list.
+ if (state->capture_list_id == NONE) {
+ uint32_t state_index, byte_offset, pattern_index;
+ if (ts_query_cursor__first_in_progress_capture(
+ self,
+ &state_index,
+ &byte_offset,
+ &pattern_index
+ )) {
+ LOG(
+ " abandon state. index:%u, pattern:%u, offset:%u.\n",
+ state_index, pattern_index, byte_offset
+ );
+ state->capture_list_id = self->states.contents[state_index].capture_list_id;
+ array_erase(&self->states, state_index);
+ if (state_index < i) {
+ i--;
+ state--;
+ }
+ } else {
+ LOG(" too many finished states.\n");
+ array_erase(&self->states, i);
+ i--;
+ continue;
+ }
+ }
}
- if (copy) {
+ CaptureList *capture_list = capture_list_pool_get_mut(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ for (unsigned j = 0; j < MAX_STEP_CAPTURE_COUNT; j++) {
+ uint16_t capture_id = step->capture_ids[j];
+ if (step->capture_ids[j] == NONE) break;
+ array_push(capture_list, ((TSQueryCapture) { node, capture_id }));
LOG(
- " split state. pattern:%u, step:%u\n",
- copy->pattern_index,
- copy->step_index
+ " capture node. pattern:%u, capture_id:%u, capture_count:%u\n",
+ state->pattern_index,
+ capture_id,
+ capture_list->size
);
- next_state = copy;
- } else {
- LOG(" cannot split state.\n");
}
}
- // If the current node is captured in this pattern, add it to the
- // capture list.
- for (unsigned j = 0; j < MAX_STEP_CAPTURE_COUNT; j++) {
- uint16_t capture_id = step->capture_ids[j];
- if (step->capture_ids[j] == NONE) break;
- CaptureList *capture_list = capture_list_pool_get(
- &self->capture_list_pool,
- next_state->capture_list_id
- );
- array_push(capture_list, ((TSQueryCapture) {
- node,
- capture_id
- }));
- LOG(
- " capture node. pattern:%u, capture_id:%u, capture_count:%u\n",
- next_state->pattern_index,
- capture_id,
- capture_list->size
- );
+ // Advance this state to the next step of its pattern.
+ state->step_index++;
+ state->seeking_immediate_match = false;
+ LOG(
+ " advance state. pattern:%u, step:%u\n",
+ state->pattern_index,
+ state->step_index
+ );
+
+ // If this state's next step has an 'alternative' step (the step is either optional,
+ // or is the end of a repetition), then copy the state in order to pursue both
+ // alternatives. The alternative step itself may have an alternative, so this is
+ // an interative process.
+ unsigned end_index = i + 1;
+ for (unsigned j = i; j < end_index; j++) {
+ QueryState *state = &self->states.contents[j];
+ QueryStep *next_step = &self->query->steps.contents[state->step_index];
+ if (next_step->alternative_index != NONE) {
+ if (next_step->is_dead_end) {
+ state->step_index = next_step->alternative_index;
+ j--;
+ continue;
+ }
+
+ QueryState *copy = ts_query__cursor_copy_state(self, state);
+ if (next_step->is_pass_through) {
+ state->step_index++;
+ j--;
+ }
+ if (copy) {
+ copy_count++;
+ end_index++;
+ copy->step_index = next_step->alternative_index;
+ if (next_step->alternative_is_immediate) {
+ copy->seeking_immediate_match = true;
+ }
+ LOG(
+ " split state for branch. pattern:%u, step:%u, step:%u, immediate:%d\n",
+ copy->pattern_index,
+ state->step_index,
+ copy->step_index,
+ copy->seeking_immediate_match
+ );
+ }
+ }
}
+ }
- // If this is the end of a repetition, then jump back to the beginning
- // of that repetition.
- if (step->repeat_step_index != NONE) {
- next_state->step_index_on_failure = next_state->step_index + 1;
- next_state->step_index = step->repeat_step_index;
- next_state->repeat_match_count++;
- LOG(
- " continue repeat. pattern:%u, match_count:%u\n",
- next_state->pattern_index,
- next_state->repeat_match_count
- );
- } else {
- next_state->step_index++;
- LOG(
- " advance state. pattern:%u, step:%u\n",
- next_state->pattern_index,
- next_state->step_index
- );
+ for (unsigned i = 0; i < self->states.size; i++) {
+ QueryState *state = &self->states.contents[i];
+ bool did_remove = false;
- QueryStep *next_step = step + 1;
+ // Enfore the longest-match criteria. When a query pattern contains optional or
+ // repeated nodes, this is necesssary to avoid multiple redundant states, where
+ // one state has a strict subset of another state's captures.
+ for (unsigned j = i + 1; j < self->states.size; j++) {
+ QueryState *other_state = &self->states.contents[j];
+ if (
+ state->pattern_index == other_state->pattern_index &&
+ state->start_depth == other_state->start_depth
+ ) {
+ bool left_contains_right, right_contains_left;
+ ts_query_cursor__compare_captures(
+ self,
+ state,
+ other_state,
+ &left_contains_right,
+ &right_contains_left
+ );
+ if (left_contains_right) {
+ if (state->step_index == other_state->step_index) {
+ LOG(
+ " drop shorter state. pattern: %u, step_index: %u\n",
+ state->pattern_index,
+ state->step_index
+ );
+ capture_list_pool_release(&self->capture_list_pool, other_state->capture_list_id);
+ array_erase(&self->states, j);
+ j--;
+ continue;
+ }
+ other_state->has_in_progress_alternatives = true;
+ }
+ if (right_contains_left) {
+ if (state->step_index == other_state->step_index) {
+ LOG(
+ " drop shorter state. pattern: %u, step_index: %u\n",
+ state->pattern_index,
+ state->step_index
+ );
+ capture_list_pool_release(&self->capture_list_pool, state->capture_list_id);
+ array_erase(&self->states, i);
+ did_remove = true;
+ break;
+ }
+ state->has_in_progress_alternatives = true;
+ }
+ }
+ }
- // If the pattern is now done, then remove it from the list of
- // in-progress states, and add it to the list of finished states.
+ // If there the state is at the end of its pattern, remove it from the list
+ // of in-progress states and add it to the list of finished states.
+ if (!did_remove) {
+ QueryStep *next_step = &self->query->steps.contents[state->step_index];
if (next_step->depth == PATTERN_DONE_MARKER) {
- LOG(" finish pattern %u\n", next_state->pattern_index);
-
- next_state->id = self->next_state_id++;
- array_push(&self->finished_states, *next_state);
- if (next_state == state) {
- array_erase(&self->states, i);
- i--;
- n--;
+ if (state->has_in_progress_alternatives) {
+ LOG(" defer finishing pattern %u\n", state->pattern_index);
} else {
- self->states.size--;
+ LOG(" finish pattern %u\n", state->pattern_index);
+ state->id = self->next_state_id++;
+ array_push(&self->finished_states, *state);
+ array_erase(&self->states, state - self->states.contents);
+ i--;
}
}
}
}
-
// Continue descending if possible.
if (ts_tree_cursor_goto_first_child(&self->cursor)) {
self->depth++;
@@ -1589,7 +1904,7 @@ bool ts_query_cursor_next_match(
QueryState *state = &self->finished_states.contents[0];
match->id = state->id;
match->pattern_index = state->pattern_index;
- CaptureList *captures = capture_list_pool_get(
+ const CaptureList *captures = capture_list_pool_get(
&self->capture_list_pool,
state->capture_list_id
);
@@ -1631,8 +1946,8 @@ bool ts_query_cursor_next_capture(
// First, identify the position of the earliest capture in an unfinished
// match. For a finished capture to be returned, it must be *before*
// this position.
- uint32_t first_unfinished_capture_byte = UINT32_MAX;
- uint32_t first_unfinished_pattern_index = UINT32_MAX;
+ uint32_t first_unfinished_capture_byte;
+ uint32_t first_unfinished_pattern_index;
uint32_t first_unfinished_state_index;
ts_query_cursor__first_in_progress_capture(
self,
@@ -1647,7 +1962,7 @@ bool ts_query_cursor_next_capture(
uint32_t first_finished_pattern_index = first_unfinished_pattern_index;
for (unsigned i = 0; i < self->finished_states.size; i++) {
const QueryState *state = &self->finished_states.contents[i];
- CaptureList *captures = capture_list_pool_get(
+ const CaptureList *captures = capture_list_pool_get(
&self->capture_list_pool,
state->capture_list_id
);
@@ -1685,7 +2000,7 @@ bool ts_query_cursor_next_capture(
];
match->id = state->id;
match->pattern_index = state->pattern_index;
- CaptureList *captures = capture_list_pool_get(
+ const CaptureList *captures = capture_list_pool_get(
&self->capture_list_pool,
state->capture_list_id
);
diff --git a/src/tree_sitter/stack.c b/src/tree_sitter/stack.c
index ade1577566..6ceee2577f 100644
--- a/src/tree_sitter/stack.c
+++ b/src/tree_sitter/stack.c
@@ -480,6 +480,7 @@ StackSliceArray ts_stack_pop_count(Stack *self, StackVersion version, uint32_t c
}
inline StackAction pop_pending_callback(void *payload, const StackIterator *iterator) {
+ (void)payload;
if (iterator->subtree_count >= 1) {
if (iterator->is_pending) {
return StackActionPop | StackActionStop;
@@ -532,6 +533,7 @@ SubtreeArray ts_stack_pop_error(Stack *self, StackVersion version) {
}
inline StackAction pop_all_callback(void *payload, const StackIterator *iterator) {
+ (void)payload;
return iterator->node->link_count == 0 ? StackActionPop : StackActionNone;
}
diff --git a/src/tree_sitter/subtree.c b/src/tree_sitter/subtree.c
index b98f172339..ef92a32fe4 100644
--- a/src/tree_sitter/subtree.c
+++ b/src/tree_sitter/subtree.c
@@ -21,7 +21,7 @@ typedef struct {
#define TS_MAX_INLINE_TREE_LENGTH UINT8_MAX
#define TS_MAX_TREE_POOL_SIZE 32
-static const ExternalScannerState empty_state = {.length = 0, .short_data = {0}};
+static const ExternalScannerState empty_state = {{.short_data = {0}}, .length = 0};
// ExternalScannerState
@@ -208,7 +208,7 @@ Subtree ts_subtree_new_leaf(
.has_external_tokens = has_external_tokens,
.is_missing = false,
.is_keyword = is_keyword,
- .first_leaf = {.symbol = 0, .parse_state = 0},
+ {{.first_leaf = {.symbol = 0, .parse_state = 0}}}
};
return (Subtree) {.ptr = data};
}
@@ -464,15 +464,17 @@ MutableSubtree ts_subtree_new_node(SubtreePool *pool, TSSymbol symbol,
*data = (SubtreeHeapData) {
.ref_count = 1,
.symbol = symbol,
- .production_id = production_id,
.visible = metadata.visible,
.named = metadata.named,
.has_changes = false,
.fragile_left = fragile,
.fragile_right = fragile,
.is_keyword = false,
- .node_count = 0,
- .first_leaf = {.symbol = 0, .parse_state = 0},
+ {{
+ .node_count = 0,
+ .production_id = production_id,
+ .first_leaf = {.symbol = 0, .parse_state = 0},
+ }}
};
MutableSubtree result = {.ptr = data};
ts_subtree_set_children(result, children->contents, children->size, language);
diff --git a/src/tree_sitter/treesitter_commit_hash.txt b/src/tree_sitter/treesitter_commit_hash.txt
new file mode 100644
index 0000000000..bd7fcfbe76
--- /dev/null
+++ b/src/tree_sitter/treesitter_commit_hash.txt
@@ -0,0 +1 @@
+81d533d2d1b580fdb507accabc91ceddffb5b6f0
diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua
index 210394c83f..5da2c6b531 100644
--- a/test/functional/api/keymap_spec.lua
+++ b/test/functional/api/keymap_spec.lua
@@ -21,6 +21,7 @@ describe('nvim_get_keymap', function()
local foo_bar_string = 'nnoremap foo bar'
local foo_bar_map_table = {
lhs='foo',
+ script=0,
silent=0,
rhs='bar',
expr=0,
@@ -245,6 +246,7 @@ describe('nvim_get_keymap', function()
it('works correctly despite various &cpo settings', function()
local cpo_table = {
+ script=0,
silent=0,
expr=0,
sid=0,
@@ -302,6 +304,7 @@ describe('nvim_get_keymap', function()
lhs='| |',
rhs='| |',
mode='n',
+ script=0,
silent=0,
expr=0,
sid=0,
@@ -343,6 +346,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
to_return.noremap = not opts.noremap and 0 or 1
to_return.lhs = lhs
to_return.rhs = rhs
+ to_return.script = 0
to_return.silent = not opts.silent and 0 or 1
to_return.nowait = not opts.nowait and 0 or 1
to_return.expr = not opts.expr and 0 or 1
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 2e9d0f57ac..72e810e3e4 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -482,6 +482,11 @@ describe('API', function()
eq(true, status) -- nvim_input() did not fail.
eq("E117:", v_errnum) -- v:errmsg was updated.
end)
+
+ it('does not crash even if trans_special result is largest #11788, #12287', function()
+ command("call nvim_input('<M-'.nr2char(0x40000000).'>')")
+ eq(1, eval('1'))
+ end)
end)
describe('nvim_paste', function()
@@ -1808,7 +1813,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 +1823,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 +1850,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 +1866,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/eval/buf_functions_spec.lua b/test/functional/eval/buf_functions_spec.lua
index 37f4c89bfd..06841a4521 100644
--- a/test/functional/eval/buf_functions_spec.lua
+++ b/test/functional/eval/buf_functions_spec.lua
@@ -31,10 +31,12 @@ for _, func in ipairs({'bufname(%s)', 'bufnr(%s)', 'bufwinnr(%s)',
it('errors out when receives v:true/v:false/v:null', function()
-- Not compatible with Vim: in Vim it always results in buffer not found
-- without any error messages.
- for _, var in ipairs({'v:true', 'v:false', 'v:null'}) do
- eq('Vim(call):E5300: Expected a Number or a String',
+ for _, var in ipairs({'v:true', 'v:false'}) do
+ eq('Vim(call):E5299: Expected a Number or a String, Boolean found',
exc_exec('call ' .. func:format(var)))
end
+ eq('Vim(call):E5300: Expected a Number or a String',
+ exc_exec('call ' .. func:format('v:null')))
end)
it('errors out when receives invalid argument', function()
eq('Vim(call):E745: Expected a Number or a String, List found',
diff --git a/test/functional/eval/map_functions_spec.lua b/test/functional/eval/map_functions_spec.lua
index 2747a94570..275c72d212 100644
--- a/test/functional/eval/map_functions_spec.lua
+++ b/test/functional/eval/map_functions_spec.lua
@@ -13,6 +13,7 @@ describe('maparg()', function()
local foo_bar_map_table = {
lhs='foo',
+ script=0,
silent=0,
rhs='bar',
expr=0,
@@ -147,6 +148,7 @@ describe('maparg()', function()
mode = 'n',
noremap = 1,
nowait = 0,
+ script=0,
sid = 0,
silent = 0,
lnum = 0,
diff --git a/test/functional/eval/sort_spec.lua b/test/functional/eval/sort_spec.lua
index 82557575ce..e1cc2c2924 100644
--- a/test/functional/eval/sort_spec.lua
+++ b/test/functional/eval/sort_spec.lua
@@ -14,7 +14,7 @@ before_each(clear)
describe('sort()', function()
it('errors out when sorting special values', function()
- eq('Vim(call):E907: Using a special value as a Float',
+ eq('Vim(call):E362: Using a boolean value as a Float',
exc_exec('call sort([v:true, v:false], "f")'))
end)
@@ -30,6 +30,7 @@ describe('sort()', function()
errors[err] = true
end
eq({
+ ['E362: Using a boolean value as a Float']=true,
['E891: Using a Funcref as a Float']=true,
['E892: Using a String as a Float']=true,
['E893: Using a List as a Float']=true,
diff --git a/test/functional/eval/uniq_spec.lua b/test/functional/eval/uniq_spec.lua
index 0e7a013e32..5cdba0a0f6 100644
--- a/test/functional/eval/uniq_spec.lua
+++ b/test/functional/eval/uniq_spec.lua
@@ -11,7 +11,7 @@ before_each(clear)
describe('uniq()', function()
it('errors out when processing special values', function()
- eq('Vim(call):E907: Using a special value as a Float',
+ eq('Vim(call):E362: Using a boolean value as a Float',
exc_exec('call uniq([v:true, v:false], "f")'))
end)
diff --git a/test/functional/legacy/075_maparg_spec.lua b/test/functional/legacy/075_maparg_spec.lua
index 0164f5077a..ee2b041b51 100644
--- a/test/functional/legacy/075_maparg_spec.lua
+++ b/test/functional/legacy/075_maparg_spec.lua
@@ -49,9 +49,9 @@ describe('maparg()', function()
-- Assert buffer contents.
expect([[
is<F4>foo
- {'lnum': 0, 'silent': 0, 'noremap': 0, 'lhs': 'foo<C-V>', 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': 0, 'rhs': 'is<F4>foo', 'buffer': 0}
- {'lnum': 0, 'silent': 1, 'noremap': 1, 'lhs': 'bar', 'mode': 'v', 'nowait': 0, 'expr': 1, 'sid': 0, 'rhs': 'isbar', 'buffer': 1}
- {'lnum': 0, 'silent': 0, 'noremap': 0, 'lhs': 'foo', 'mode': ' ', 'nowait': 1, 'expr': 0, 'sid': 0, 'rhs': 'bar', 'buffer': 1}
+ {'lnum': 0, 'script': 0, 'silent': 0, 'noremap': 0, 'lhs': 'foo<C-V>', 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': 0, 'rhs': 'is<F4>foo', 'buffer': 0}
+ {'lnum': 0, 'script': 1, 'silent': 1, 'noremap': 1, 'lhs': 'bar', 'mode': 'v', 'nowait': 0, 'expr': 1, 'sid': 0, 'rhs': 'isbar', 'buffer': 1}
+ {'lnum': 0, 'script': 0, 'silent': 0, 'noremap': 0, 'lhs': 'foo', 'mode': ' ', 'nowait': 1, 'expr': 0, 'sid': 0, 'rhs': 'bar', 'buffer': 1}
xrx
yRy
abcd]])
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/treesitter_spec.lua b/test/functional/lua/treesitter_spec.lua
index f93185d1f6..ecee471386 100644
--- a/test/functional/lua/treesitter_spec.lua
+++ b/test/functional/lua/treesitter_spec.lua
@@ -240,17 +240,17 @@ static int nlua_schedule(lua_State *const lstate)
; TODO(bfredl): overlapping matches are unreliable,
; we need a proper priority mechanism
;(type_identifier) @type
-((type_identifier) @Special (eq? @Special "LuaRef"))
+((type_identifier) @Special (#eq? @Special "LuaRef"))
(primitive_type) @type
(sized_type_specifier) @type
; defaults to very magic syntax, for best compatibility
-((identifier) @Identifier (match? @Identifier "^l(u)a_"))
+((identifier) @Identifier (#match? @Identifier "^l(u)a_"))
; still support \M etc prefixes
-((identifier) @Constant (match? @Constant "\M^\[A-Z_]\+$"))
+((identifier) @Constant (#match? @Constant "\M^\[A-Z_]\+$"))
-((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (eq? @WarningMsg.left @WarningMsg.right))
+((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right) (#eq? @WarningMsg.left @WarningMsg.right))
(comment) @comment
]]
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 c68c05dffa..9b2697b3c2 100644
--- a/test/functional/lua/vim_spec.lua
+++ b/test/functional/lua/vim_spec.lua
@@ -243,6 +243,8 @@ 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, {} },
+ { "", "a", false, { '' } },
{ "x*yz*oo*l", "*", true, { 'x', 'yz', 'oo', 'l' } },
}
@@ -1048,13 +1050,13 @@ describe('lua stdlib', function()
end)
it('vim.defer_fn', function()
- exec_lua [[
- vim.g.test = 0
- vim.defer_fn(function() vim.g.test = 1 end, 50)
- ]]
- eq(0, exec_lua[[return vim.g.test]])
- exec_lua [[vim.cmd("sleep 1000m")]]
- eq(1, exec_lua[[return vim.g.test]])
+ 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()
@@ -1066,4 +1068,168 @@ describe('lua stdlib', function()
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 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 f41a5323a8..1ab81a0ef8 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)
@@ -784,20 +800,44 @@ describe('LSP', function()
make_edit(0, 0, 0, 0, {"123"});
make_edit(1, 0, 1, 1, {"2"});
make_edit(2, 0, 2, 2, {"3"});
+ make_edit(3, 2, 3, 4, {""});
}
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
eq({
'123First line of text';
'2econd line of text';
'3ird line of text';
- 'Fourth line of text';
+ 'Foth line of text';
+ 'å å ɧ 汉语 ↥ 🤦 🦄';
+ }, buf_lines(1))
+ end)
+ it('handles edits with the same start position, applying changes in the order in the array', function()
+ local edits = {
+ make_edit(0, 6, 0, 10, {""});
+ make_edit(0, 6, 0, 6, {"REPLACE"});
+ make_edit(1, 0, 1, 3, {""});
+ make_edit(1, 0, 1, 0, {"123"});
+ make_edit(2, 16, 2, 18, {""});
+ make_edit(2, 16, 2, 16, {"XYZ"});
+ make_edit(3, 7, 3, 11, {"this"});
+ make_edit(3, 7, 3, 11, {"will"});
+ make_edit(3, 7, 3, 11, {"not "});
+ make_edit(3, 7, 3, 11, {"show"});
+ make_edit(3, 7, 3, 11, {"(but this will)"});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({
+ 'First REPLACE of text';
+ '123ond line of text';
+ 'Third line of teXYZ';
+ 'Fourth (but this will) of text';
'å å ɧ 汉语 ↥ 🤦 🦄';
}, buf_lines(1))
end)
it('applies complex edits', function()
local edits = {
- make_edit(0, 0, 0, 0, {"", "12"});
make_edit(0, 0, 0, 0, {"3", "foo"});
+ make_edit(0, 0, 0, 0, {"", "12"});
make_edit(0, 1, 0, 1, {"bar", "123"});
make_edit(0, #"First ", 0, #"First line of text", {"guy"});
make_edit(1, 0, 1, #'Second', {"baz"});
@@ -957,7 +997,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 +1014,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))
@@ -1204,7 +1255,7 @@ describe('LSP', function()
]])
end)
end)
- describe('convert SymbolInformation[] to items', function()
+ it('convert SymbolInformation[] to items', function()
local expected = {
{
col = 1,
@@ -1268,11 +1319,11 @@ describe('LSP', function()
end)
describe('lsp.util._get_completion_item_kind_name', function()
- describe('returns the name specified by protocol', function()
+ it('returns the name specified by protocol', function()
eq("Text", exec_lua("return vim.lsp.util._get_completion_item_kind_name(1)"))
eq("TypeParameter", exec_lua("return vim.lsp.util._get_completion_item_kind_name(25)"))
end)
- describe('returns the name not specified by protocol', function()
+ it('returns the name not specified by protocol', function()
eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(nil)"))
eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(vim.NIL)"))
eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(1000)"))
@@ -1280,14 +1331,93 @@ describe('LSP', function()
end)
describe('lsp.util._get_symbol_kind_name', function()
- describe('returns the name specified by protocol', function()
+ it('returns the name specified by protocol', function()
eq("File", exec_lua("return vim.lsp.util._get_symbol_kind_name(1)"))
eq("TypeParameter", exec_lua("return vim.lsp.util._get_symbol_kind_name(26)"))
end)
- describe('returns the name not specified by protocol', function()
+ it('returns the name not specified by protocol', function()
eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(nil)"))
eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(vim.NIL)"))
eq("Unknown", exec_lua("return vim.lsp.util._get_symbol_kind_name(1000)"))
end)
end)
+
+ describe('lsp.util.jump_to_location', function()
+ local target_bufnr
+
+ before_each(function()
+ target_bufnr = exec_lua [[
+ local bufnr = vim.uri_to_bufnr("file://fake/uri")
+ local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"}
+ vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
+ return bufnr
+ ]]
+ end)
+
+ local location = function(start_line, start_char, end_line, end_char)
+ return {
+ uri = "file://fake/uri",
+ range = {
+ start = { line = start_line, character = start_char },
+ ["end"] = { line = end_line, character = end_char },
+ },
+ }
+ end
+
+ local jump = function(msg)
+ eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg))
+ eq(target_bufnr, exec_lua[[return vim.fn.bufnr('%')]])
+ return {
+ line = exec_lua[[return vim.fn.line('.')]],
+ col = exec_lua[[return vim.fn.col('.')]],
+ }
+ end
+
+ it('jumps to a Location', function()
+ local pos = jump(location(0, 9, 0, 9))
+ eq(1, pos.line)
+ eq(10, pos.col)
+ end)
+
+ it('jumps to a LocationLink', function()
+ local pos = jump({
+ targetUri = "file://fake/uri",
+ targetSelectionRange = {
+ start = { line = 0, character = 4 },
+ ["end"] = { line = 0, character = 4 },
+ },
+ targetRange = {
+ start = { line = 1, character = 5 },
+ ["end"] = { line = 1, character = 5 },
+ },
+ })
+ eq(1, pos.line)
+ eq(5, pos.col)
+ end)
+
+ it('jumps to the correct multibyte column', function()
+ local pos = jump(location(1, 2, 1, 2))
+ eq(2, pos.line)
+ eq(4, pos.col)
+ eq('å', exec_lua[[return vim.fn.expand('<cword>')]])
+ end)
+ end)
+
+ describe('lsp.util._make_floating_popup_size', function()
+ before_each(function()
+ exec_lua [[ contents =
+ {"text tαxt txtα tex",
+ "text tααt tααt text",
+ "text tαxt tαxt"}
+ ]]
+ end)
+
+ it('calculates size correctly', function()
+ eq({19,3}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents)} ]])
+ end)
+
+ it('calculates size correctly with wrapping', function()
+ eq({15,5}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents,{width = 15, wrap_at = 14})} ]])
+ end)
+ end)
end)
diff --git a/test/functional/provider/define_spec.lua b/test/functional/provider/define_spec.lua
index 51a8831274..1d50ce0a56 100644
--- a/test/functional/provider/define_spec.lua
+++ b/test/functional/provider/define_spec.lua
@@ -89,6 +89,21 @@ local function command_specs_for(fn, sync, first_arg_factory, init)
runx(sync, handler, on_setup)
end)
+ it('with nargs/double-quote', function()
+ call(fn, args..', {"nargs": "*"}')
+ local function on_setup()
+ command('RpcCommand "arg1" "arg2" "arg3"')
+ end
+
+ local function handler(method, arguments)
+ eq('test-handler', method)
+ eq({'"arg1"', '"arg2"', '"arg3"'}, arguments[1])
+ return ''
+ end
+
+ runx(sync, handler, on_setup)
+ end)
+
it('with range', function()
call(fn,args..', {"range": ""}')
local function on_setup()
diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua
index bcd7c750c5..b600f01ab2 100644
--- a/test/unit/eval/helpers.lua
+++ b/test/unit/eval/helpers.lua
@@ -136,11 +136,15 @@ local function typvalt2lua_tab_init()
return
end
typvalt2lua_tab = {
+ [tonumber(eval.VAR_BOOL)] = function(t)
+ return ({
+ [tonumber(eval.kBoolVarFalse)] = false,
+ [tonumber(eval.kBoolVarTrue)] = true,
+ })[tonumber(t.vval.v_bool)]
+ end,
[tonumber(eval.VAR_SPECIAL)] = function(t)
return ({
- [tonumber(eval.kSpecialVarFalse)] = false,
[tonumber(eval.kSpecialVarNull)] = nil_value,
- [tonumber(eval.kSpecialVarTrue)] = true,
})[tonumber(t.vval.v_special)]
end,
[tonumber(eval.VAR_NUMBER)] = function(t)
@@ -349,8 +353,8 @@ lua2typvalt = function(l, processed)
[null_list] = {'VAR_LIST', {v_list=ffi.cast('list_T*', nil)}},
[null_dict] = {'VAR_DICT', {v_dict=ffi.cast('dict_T*', nil)}},
[nil_value] = {'VAR_SPECIAL', {v_special=eval.kSpecialVarNull}},
- [true] = {'VAR_SPECIAL', {v_special=eval.kSpecialVarTrue}},
- [false] = {'VAR_SPECIAL', {v_special=eval.kSpecialVarFalse}},
+ [true] = {'VAR_BOOL', {v_bool=eval.kBoolVarTrue}},
+ [false] = {'VAR_BOOL', {v_bool=eval.kBoolVarFalse}},
}
for k, v in pairs(special_vals) do
diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua
index ea86ccbf1c..06465071c5 100644
--- a/test/unit/eval/typval_spec.lua
+++ b/test/unit/eval/typval_spec.lua
@@ -48,8 +48,7 @@ local lib = cimport('./src/nvim/eval/typval.h', './src/nvim/memory.h',
local function vimconv_alloc()
return ffi.gc(
- ffi.cast('vimconv_T*', lib.xcalloc(1, ffi.sizeof('vimconv_T'))),
- function(vc)
+ ffi.cast('vimconv_T*', lib.xcalloc(1, ffi.sizeof('vimconv_T'))), function(vc)
lib.convert_setup(vc, nil, nil)
lib.xfree(vc)
end)
@@ -2838,6 +2837,7 @@ describe('typval.c', function()
{lib.VAR_FUNC, 'E729: using Funcref as a String'},
{lib.VAR_LIST, 'E730: using List as a String'},
{lib.VAR_DICT, 'E731: using Dictionary as a String'},
+ {lib.VAR_BOOL, nil},
{lib.VAR_SPECIAL, nil},
{lib.VAR_UNKNOWN, 'E908: using an invalid value as a String'},
}) do
@@ -2868,8 +2868,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E745: Using a List as a Number', 0},
{lib.VAR_DICT, {v_dict=NULL}, 'E728: Using a Dictionary as a Number', 0},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 1},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 1},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', 0},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -2897,8 +2897,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E745: Using a List as a Number', 0},
{lib.VAR_DICT, {v_dict=NULL}, 'E728: Using a Dictionary as a Number', 0},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 1},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 1},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', 0},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -2931,8 +2931,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E745: Using a List as a Number', -1},
{lib.VAR_DICT, {v_dict=NULL}, 'E728: Using a Dictionary as a Number', -1},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 1},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 1},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_number(UNKNOWN)', -1},
}) do
lib.curwin.w_cursor.lnum = 46
@@ -2961,8 +2961,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E893: Using a List as a Float', 0},
{lib.VAR_DICT, {v_dict=NULL}, 'E894: Using a Dictionary as a Float', 0},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, 'E907: Using a special value as a Float', 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, 'E907: Using a special value as a Float', 0},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, 'E907: Using a special value as a Float', 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, 'E362: Using a boolean value as a Float', 0},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, 'E362: Using a boolean value as a Float', 0},
{lib.VAR_UNKNOWN, nil, 'E685: Internal error: tv_get_float(UNKNOWN)', 0},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -2993,8 +2993,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', ''},
{lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', ''},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''},
}) do
-- Using to_cstr in place of Neovim allocated string, cannot
@@ -3005,7 +3005,8 @@ describe('typval.c', function()
local ret = v[4]
eq(ret, check_emsg(function()
local res = lib.tv_get_string(tv)
- if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL then
+ if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL
+ or tv.v_type == lib.VAR_BOOL then
eq(buf, res)
else
neq(buf, res)
@@ -3036,8 +3037,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', nil},
{lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', nil},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -3047,7 +3048,8 @@ describe('typval.c', function()
local ret = v[4]
eq(ret, check_emsg(function()
local res = lib.tv_get_string_chk(tv)
- if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL then
+ if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL
+ or tv.v_type == lib.VAR_BOOL then
eq(buf, res)
else
neq(buf, res)
@@ -3077,8 +3079,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', ''},
{lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', ''},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -3089,7 +3091,8 @@ describe('typval.c', function()
eq(ret, check_emsg(function()
local buf = ffi.new('char[?]', lib.NUMBUFLEN, {0})
local res = lib.tv_get_string_buf(tv, buf)
- if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL then
+ if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL
+ or tv.v_type == lib.VAR_BOOL then
eq(buf, res)
else
neq(buf, res)
@@ -3119,8 +3122,8 @@ describe('typval.c', function()
{lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', nil},
{lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', nil},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'null'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarTrue}, nil, 'true'},
- {lib.VAR_SPECIAL, {v_special=lib.kSpecialVarFalse}, nil, 'false'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'true'},
+ {lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'false'},
{lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil},
}) do
-- Using to_cstr, cannot free with tv_clear
@@ -3131,7 +3134,8 @@ describe('typval.c', function()
eq(ret, check_emsg(function()
local buf = ffi.new('char[?]', lib.NUMBUFLEN, {0})
local res = lib.tv_get_string_buf_chk(tv, buf)
- if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL then
+ if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL
+ or tv.v_type == lib.VAR_BOOL then
eq(buf, res)
else
neq(buf, res)
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)