mirror of
https://github.com/neovim/neovim.git
synced 2025-12-16 11:25:33 +00:00
Merge #15667 release-0.5: backports
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
.DONE:
|
.DONE:
|
||||||
@echo "Please use GNU Make (gmake) to build neovim"
|
@echo "Use GNU Make (gmake) to build neovim"
|
||||||
.DEFAULT:
|
.DEFAULT:
|
||||||
@echo "Please use GNU Make (gmake) to build neovim"
|
@echo "Use GNU Make (gmake) to build neovim"
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ Reporting problems
|
|||||||
Developer guidelines
|
Developer guidelines
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
- Nvim contributors should read `:help dev`.
|
- Read `:help dev` if you are working on Nvim core.
|
||||||
- External UI developers should read `:help dev-ui`.
|
- Read `:help dev-ui` if you are developing a UI.
|
||||||
- API client developers should read `:help dev-api-client`.
|
- Read `:help dev-api-client` if you are developing an API client.
|
||||||
- Nvim developers are _strongly encouraged_ to install `ninja` for faster builds.
|
- Install `ninja` for faster builds of Nvim.
|
||||||
```
|
```
|
||||||
sudo apt-get install ninja-build
|
sudo apt-get install ninja-build
|
||||||
make distclean
|
make distclean
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ endif()
|
|||||||
set(ENV{TMPDIR} "${BUILD_DIR}/Xtest_tmpdir/${TEST_PATH}")
|
set(ENV{TMPDIR} "${BUILD_DIR}/Xtest_tmpdir/${TEST_PATH}")
|
||||||
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory $ENV{TMPDIR})
|
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory $ENV{TMPDIR})
|
||||||
|
|
||||||
|
if(NOT DEFINED ENV{TEST_TIMEOUT} OR "$ENV{TEST_TIMEOUT}" STREQUAL "")
|
||||||
|
set(ENV{TEST_TIMEOUT} 1200)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(ENV{SYSTEM_NAME} ${CMAKE_HOST_SYSTEM_NAME}) # used by test/helpers.lua.
|
set(ENV{SYSTEM_NAME} ${CMAKE_HOST_SYSTEM_NAME}) # used by test/helpers.lua.
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND ${BUSTED_PRG} -v -o test.busted.outputHandlers.${BUSTED_OUTPUT_TYPE}
|
COMMAND ${BUSTED_PRG} -v -o test.busted.outputHandlers.${BUSTED_OUTPUT_TYPE}
|
||||||
@@ -58,6 +62,7 @@ execute_process(
|
|||||||
--lpath=?.lua
|
--lpath=?.lua
|
||||||
${BUSTED_ARGS}
|
${BUSTED_ARGS}
|
||||||
${TEST_PATH}
|
${TEST_PATH}
|
||||||
|
TIMEOUT $ENV{TEST_TIMEOUT}
|
||||||
WORKING_DIRECTORY ${WORKING_DIR}
|
WORKING_DIRECTORY ${WORKING_DIR}
|
||||||
ERROR_VARIABLE err
|
ERROR_VARIABLE err
|
||||||
RESULT_VARIABLE res
|
RESULT_VARIABLE res
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ Functions ~
|
|||||||
without stopping the job. Use chanclose(id) to close
|
without stopping the job. Use chanclose(id) to close
|
||||||
any socket.
|
any socket.
|
||||||
|
|
||||||
|
Lua ~
|
||||||
|
*vim.register_keystroke_callback()* Use |vim.on_key()| instead.
|
||||||
|
|
||||||
Modifiers ~
|
Modifiers ~
|
||||||
*cpo-<*
|
*cpo-<*
|
||||||
|
|||||||
@@ -127,14 +127,20 @@ Sometimes a GUI or other application may want to force a provider to
|
|||||||
|
|
||||||
DOCUMENTATION *dev-doc*
|
DOCUMENTATION *dev-doc*
|
||||||
|
|
||||||
- Do not prefix help tags with "nvim-". Use |vim_diff.txt| to document
|
- "Just say it". Avoid mushy, colloquial phrasing in all documentation
|
||||||
differences from Vim; no other distinction is necessary.
|
(docstrings, user manual, website materials, newsletters, …). Don't mince
|
||||||
- If a Vim feature is removed, delete its help section and move its tag to
|
words. Personality and flavor, used sparingly, are welcome--but in general,
|
||||||
|vim_diff.txt|.
|
optimize for the reader's time and energy: be "precise yet concise".
|
||||||
- Move deprecated features to |deprecated.txt|.
|
- Prefer the active voice: "Foo does X", not "X is done by Foo".
|
||||||
|
- Vim differences:
|
||||||
|
- Do not prefix help tags with "nvim-". Use |vim_diff.txt| to catalog
|
||||||
|
differences from Vim; no other distinction is necessary.
|
||||||
|
- If a Vim feature is removed, delete its help section and move its tag to
|
||||||
|
|vim_diff.txt|.
|
||||||
|
- Mention deprecated features in |deprecated.txt| and delete their old doc.
|
||||||
- Use consistent language.
|
- Use consistent language.
|
||||||
- "terminal" in a help tag always means "the embedded terminal emulator", not
|
- "terminal" in a help tag always means "the embedded terminal emulator",
|
||||||
"the user host terminal".
|
not "the user host terminal".
|
||||||
- Use "tui-" to prefix help tags related to the host terminal, and "TUI"
|
- Use "tui-" to prefix help tags related to the host terminal, and "TUI"
|
||||||
in prose if possible.
|
in prose if possible.
|
||||||
- Docstrings: do not start parameter descriptions with "The" or "A" unless it
|
- Docstrings: do not start parameter descriptions with "The" or "A" unless it
|
||||||
@@ -222,13 +228,13 @@ LUA *dev-lua*
|
|||||||
|
|
||||||
- Keep the core Lua modules |lua-stdlib| simple. Avoid elaborate OOP or
|
- Keep the core Lua modules |lua-stdlib| simple. Avoid elaborate OOP or
|
||||||
pseudo-OOP designs. Plugin authors just want functions to call, they don't
|
pseudo-OOP designs. Plugin authors just want functions to call, they don't
|
||||||
want to learn a big, fancy inheritance hierarchy. So we should avoid complex
|
want to learn a big, fancy inheritance hierarchy. Thus avoid specialized
|
||||||
objects: tables are usually better.
|
objects; tables or values are usually better.
|
||||||
|
|
||||||
|
|
||||||
API *dev-api*
|
API *dev-api*
|
||||||
|
|
||||||
Use this template to name new API functions:
|
Use this template to name new RPC |API| functions:
|
||||||
nvim_{thing}_{action}_{arbitrary-qualifiers}
|
nvim_{thing}_{action}_{arbitrary-qualifiers}
|
||||||
|
|
||||||
If the function acts on an object then {thing} is the name of that object
|
If the function acts on an object then {thing} is the name of that object
|
||||||
@@ -356,4 +362,19 @@ External UIs are expected to implement these common features:
|
|||||||
published in this event. See also "mouse_on", "mouse_off".
|
published in this event. See also "mouse_on", "mouse_off".
|
||||||
|
|
||||||
|
|
||||||
|
NAMING *dev-naming*
|
||||||
|
|
||||||
|
Naming is important. Consistent naming in the API and UI helps both users and
|
||||||
|
developers discover and intuitively understand related concepts ("families"),
|
||||||
|
and reduces cognitive burden. Discoverability encourages code re-use and
|
||||||
|
likewise avoids redundant, overlapping mechanisms, which reduces code
|
||||||
|
surface-area, and thereby minimizes bugs...
|
||||||
|
|
||||||
|
Naming conventions ~
|
||||||
|
|
||||||
|
Use the "on_" prefix to name event handlers and also the interface for
|
||||||
|
"registering" such handlers (on_key). The dual nature is acceptable to avoid
|
||||||
|
a confused collection of naming conventions for these related concepts.
|
||||||
|
|
||||||
|
|
||||||
vim:tw=78:ts=8:noet:ft=help:norl:
|
vim:tw=78:ts=8:noet:ft=help:norl:
|
||||||
|
|||||||
@@ -572,11 +572,11 @@ If you want to exclude visual selections from highlighting on yank, use
|
|||||||
vim.highlight.on_yank({opts}) *vim.highlight.on_yank()*
|
vim.highlight.on_yank({opts}) *vim.highlight.on_yank()*
|
||||||
Highlights the yanked text. The fields of the optional dict {opts}
|
Highlights the yanked text. The fields of the optional dict {opts}
|
||||||
control the highlight:
|
control the highlight:
|
||||||
- {higroup} highlight group for yanked region (default `"IncSearch"`)
|
- {higroup} highlight group for yanked region (default |hl-IncSearch|)
|
||||||
- {timeout} time in ms before highlight is cleared (default `150`)
|
- {timeout} time in ms before highlight is cleared (default `150`)
|
||||||
- {on_macro} highlight when executing macro (default `false`)
|
- {on_macro} highlight when executing macro (default `false`)
|
||||||
- {on_visual} highlight when yanking visual selection (default `true`)
|
- {on_visual} highlight when yanking visual selection (default `true`)
|
||||||
- {event} event structure (default `vim.v.event`)
|
- {event} event structure (default |v:event|)
|
||||||
|
|
||||||
vim.highlight.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {rtype}, {inclusive})
|
vim.highlight.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {rtype}, {inclusive})
|
||||||
*vim.highlight.range()*
|
*vim.highlight.range()*
|
||||||
@@ -1167,37 +1167,6 @@ region({bufnr}, {pos1}, {pos2}, {regtype}, {inclusive}) *vim.region()*
|
|||||||
Return: ~
|
Return: ~
|
||||||
region lua table of the form {linenr = {startcol,endcol}}
|
region lua table of the form {linenr = {startcol,endcol}}
|
||||||
|
|
||||||
*vim.register_keystroke_callback()*
|
|
||||||
register_keystroke_callback({fn}, {ns_id})
|
|
||||||
Register a lua {fn} with an {id} to be run after every
|
|
||||||
keystroke.
|
|
||||||
|
|
||||||
If {fn} is nil, it removes the callback for the associated
|
|
||||||
{ns_id}
|
|
||||||
Note:
|
|
||||||
{fn} will not be cleared from |nvim_buf_clear_namespace()|
|
|
||||||
|
|
||||||
Note:
|
|
||||||
{fn} will receive the keystrokes after mappings have been
|
|
||||||
evaluated
|
|
||||||
|
|
||||||
Parameters: ~
|
|
||||||
{fn} function: Function to call. It should take one
|
|
||||||
argument, which is a string. The string will contain
|
|
||||||
the literal keys typed. See |i_CTRL-V|
|
|
||||||
{ns_id} number? Namespace ID. If not passed or 0, will
|
|
||||||
generate and return a new namespace ID from
|
|
||||||
|nvim_create_namesapce()|
|
|
||||||
|
|
||||||
Return: ~
|
|
||||||
number Namespace ID associated with {fn}
|
|
||||||
|
|
||||||
Note:
|
|
||||||
{fn} will be automatically removed if an error occurs
|
|
||||||
while calling. This is to prevent the annoying situation
|
|
||||||
of every keystroke erroring while trying to remove a
|
|
||||||
broken callback.
|
|
||||||
|
|
||||||
schedule_wrap({cb}) *vim.schedule_wrap()*
|
schedule_wrap({cb}) *vim.schedule_wrap()*
|
||||||
Defers callback `cb` until the Nvim API is safe to call.
|
Defers callback `cb` until the Nvim API is safe to call.
|
||||||
|
|
||||||
|
|||||||
@@ -329,12 +329,12 @@ argument.
|
|||||||
-w{number} Set the 'window' option to {number}.
|
-w{number} Set the 'window' option to {number}.
|
||||||
|
|
||||||
*-w*
|
*-w*
|
||||||
-w {scriptout} All the characters that you type are recorded in the file
|
-w {scriptout} All keys that you type are recorded in the file "scriptout",
|
||||||
"scriptout", until you exit Vim. This is useful if you want
|
until you exit Vim. Useful to create a script file to be used
|
||||||
to create a script file to be used with "vim -s" or
|
with "vim -s" or ":source!". Appends to the "scriptout" file
|
||||||
":source!". When the "scriptout" file already exists, new
|
if it already exists. {scriptout} cannot start with a digit.
|
||||||
characters are appended. See also |complex-repeat|.
|
See also |vim.on_key()|.
|
||||||
{scriptout} cannot start with a digit.
|
See also |complex-repeat|.
|
||||||
|
|
||||||
*-W*
|
*-W*
|
||||||
-W {scriptout} Like -w, but do not append, overwrite an existing file.
|
-W {scriptout} Like -w, but do not append, overwrite an existing file.
|
||||||
|
|||||||
@@ -6,16 +6,16 @@
|
|||||||
|
|
||||||
Differences between Nvim and Vim *vim-differences*
|
Differences between Nvim and Vim *vim-differences*
|
||||||
|
|
||||||
Nvim differs from Vim in many ways, although editor and VimL features are
|
Nvim differs from Vim in many ways, although editor and Vimscript (not
|
||||||
mostly identical. This document is a complete and centralized reference of
|
Vim9script) features are mostly identical. This document is a complete and
|
||||||
the differences.
|
centralized reference of the differences.
|
||||||
|
|
||||||
Type |gO| to see the table of contents.
|
Type |gO| to see the table of contents.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
1. Configuration *nvim-config*
|
1. Configuration *nvim-config*
|
||||||
|
|
||||||
- Use `$XDG_CONFIG_HOME/nvim/init.vim` instead of `.vimrc` for configuration.
|
- Use `$XDG_CONFIG_HOME/nvim/init.vim` instead of `.vimrc` for your |config|.
|
||||||
- Use `$XDG_CONFIG_HOME/nvim` instead of `.vim` to store configuration files.
|
- Use `$XDG_CONFIG_HOME/nvim` instead of `.vim` to store configuration files.
|
||||||
- Use `$XDG_DATA_HOME/nvim/shada/main.shada` instead of `.viminfo` for persistent
|
- Use `$XDG_DATA_HOME/nvim/shada/main.shada` instead of `.viminfo` for persistent
|
||||||
session information. |shada|
|
session information. |shada|
|
||||||
@@ -78,17 +78,19 @@ the differences.
|
|||||||
MAJOR COMPONENTS ~
|
MAJOR COMPONENTS ~
|
||||||
|
|
||||||
API |API|
|
API |API|
|
||||||
Lua scripting |lua|
|
|
||||||
Job control |job-control|
|
Job control |job-control|
|
||||||
Remote plugins |remote-plugin|
|
LSP framework |lsp|
|
||||||
|
Lua scripting |lua|
|
||||||
|
Parsing engine |treesitter|
|
||||||
Providers
|
Providers
|
||||||
Clipboard |provider-clipboard|
|
Clipboard |provider-clipboard|
|
||||||
Node.js plugins |provider-nodejs|
|
Node.js plugins |provider-nodejs|
|
||||||
Python plugins |provider-python|
|
Python plugins |provider-python|
|
||||||
Ruby plugins |provider-ruby|
|
Ruby plugins |provider-ruby|
|
||||||
|
Remote plugins |remote-plugin|
|
||||||
Shared data |shada|
|
Shared data |shada|
|
||||||
Embedded terminal |terminal|
|
Terminal emulator |terminal|
|
||||||
VimL parser |nvim_parse_expression()|
|
Vimscript parser |nvim_parse_expression()|
|
||||||
XDG base directories |xdg|
|
XDG base directories |xdg|
|
||||||
|
|
||||||
USER EXPERIENCE ~
|
USER EXPERIENCE ~
|
||||||
@@ -137,7 +139,7 @@ FEATURES ~
|
|||||||
|
|
||||||
Command-line highlighting:
|
Command-line highlighting:
|
||||||
The expression prompt (|@=|, |c_CTRL-R_=|, |i_CTRL-R_=|) is highlighted
|
The expression prompt (|@=|, |c_CTRL-R_=|, |i_CTRL-R_=|) is highlighted
|
||||||
using a built-in VimL expression parser. |expr-highlight|
|
using a built-in Vimscript expression parser. |expr-highlight|
|
||||||
*E5408* *E5409*
|
*E5408* *E5409*
|
||||||
|input()|, |inputdialog()| support custom highlighting. |input()-highlight|
|
|input()|, |inputdialog()| support custom highlighting. |input()-highlight|
|
||||||
*g:Nvim_color_cmdline*
|
*g:Nvim_color_cmdline*
|
||||||
@@ -380,7 +382,7 @@ TUI:
|
|||||||
UI/Display:
|
UI/Display:
|
||||||
|Visual| selection highlights the character at cursor. |visual-use|
|
|Visual| selection highlights the character at cursor. |visual-use|
|
||||||
|
|
||||||
VimL (Vim script) compatibility:
|
Vimscript compatibility:
|
||||||
`count` does not alias to |v:count|
|
`count` does not alias to |v:count|
|
||||||
`errmsg` does not alias to |v:errmsg|
|
`errmsg` does not alias to |v:errmsg|
|
||||||
`shell_error` does not alias to |v:shell_error|
|
`shell_error` does not alias to |v:shell_error|
|
||||||
|
|||||||
399
runtime/lua/vim/lsp/_snippet.lua
Normal file
399
runtime/lua/vim/lsp/_snippet.lua
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
local P = {}
|
||||||
|
|
||||||
|
---Take characters until the target characters (The escape sequence is '\' + char)
|
||||||
|
---@param targets string[] The character list for stop consuming text.
|
||||||
|
---@param specials string[] If the character isn't contained in targets/specials, '\' will be left.
|
||||||
|
P.take_until = function(targets, specials)
|
||||||
|
targets = targets or {}
|
||||||
|
specials = specials or {}
|
||||||
|
|
||||||
|
return function(input, pos)
|
||||||
|
local new_pos = pos
|
||||||
|
local raw = {}
|
||||||
|
local esc = {}
|
||||||
|
while new_pos <= #input do
|
||||||
|
local c = string.sub(input, new_pos, new_pos)
|
||||||
|
if c == '\\' then
|
||||||
|
table.insert(raw, '\\')
|
||||||
|
new_pos = new_pos + 1
|
||||||
|
c = string.sub(input, new_pos, new_pos)
|
||||||
|
if not vim.tbl_contains(targets, c) and not vim.tbl_contains(specials, c) then
|
||||||
|
table.insert(esc, '\\')
|
||||||
|
end
|
||||||
|
table.insert(raw, c)
|
||||||
|
table.insert(esc, c)
|
||||||
|
new_pos = new_pos + 1
|
||||||
|
else
|
||||||
|
if vim.tbl_contains(targets, c) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
table.insert(raw, c)
|
||||||
|
table.insert(esc, c)
|
||||||
|
new_pos = new_pos + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if new_pos == pos then
|
||||||
|
return P.unmatch(pos)
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
parsed = true,
|
||||||
|
value = {
|
||||||
|
raw = table.concat(raw, ''),
|
||||||
|
esc = table.concat(esc, '')
|
||||||
|
},
|
||||||
|
pos = new_pos,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
P.unmatch = function(pos)
|
||||||
|
return {
|
||||||
|
parsed = false,
|
||||||
|
value = nil,
|
||||||
|
pos = pos,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
P.map = function(parser, map)
|
||||||
|
return function(input, pos)
|
||||||
|
local result = parser(input, pos)
|
||||||
|
if result.parsed then
|
||||||
|
return {
|
||||||
|
parsed = true,
|
||||||
|
value = map(result.value),
|
||||||
|
pos = result.pos,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return P.unmatch(pos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
P.lazy = function(factory)
|
||||||
|
return function(input, pos)
|
||||||
|
return factory()(input, pos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
P.token = function(token)
|
||||||
|
return function(input, pos)
|
||||||
|
local maybe_token = string.sub(input, pos, pos + #token - 1)
|
||||||
|
if token == maybe_token then
|
||||||
|
return {
|
||||||
|
parsed = true,
|
||||||
|
value = maybe_token,
|
||||||
|
pos = pos + #token,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return P.unmatch(pos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
P.pattern = function(p)
|
||||||
|
return function(input, pos)
|
||||||
|
local maybe_match = string.match(string.sub(input, pos), '^' .. p)
|
||||||
|
if maybe_match then
|
||||||
|
return {
|
||||||
|
parsed = true,
|
||||||
|
value = maybe_match,
|
||||||
|
pos = pos + #maybe_match,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return P.unmatch(pos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
P.many = function(parser)
|
||||||
|
return function(input, pos)
|
||||||
|
local values = {}
|
||||||
|
local new_pos = pos
|
||||||
|
while new_pos <= #input do
|
||||||
|
local result = parser(input, new_pos)
|
||||||
|
if not result.parsed then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
table.insert(values, result.value)
|
||||||
|
new_pos = result.pos
|
||||||
|
end
|
||||||
|
if #values > 0 then
|
||||||
|
return {
|
||||||
|
parsed = true,
|
||||||
|
value = values,
|
||||||
|
pos = new_pos,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
return P.unmatch(pos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
P.any = function(...)
|
||||||
|
local parsers = { ... }
|
||||||
|
return function(input, pos)
|
||||||
|
for _, parser in ipairs(parsers) do
|
||||||
|
local result = parser(input, pos)
|
||||||
|
if result.parsed then
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return P.unmatch(pos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
P.opt = function(parser)
|
||||||
|
return function(input, pos)
|
||||||
|
local result = parser(input, pos)
|
||||||
|
return {
|
||||||
|
parsed = true,
|
||||||
|
value = result.value,
|
||||||
|
pos = result.pos,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
P.seq = function(...)
|
||||||
|
local parsers = { ... }
|
||||||
|
return function(input, pos)
|
||||||
|
local values = {}
|
||||||
|
local new_pos = pos
|
||||||
|
for _, parser in ipairs(parsers) do
|
||||||
|
local result = parser(input, new_pos)
|
||||||
|
if result.parsed then
|
||||||
|
table.insert(values, result.value)
|
||||||
|
new_pos = result.pos
|
||||||
|
else
|
||||||
|
return P.unmatch(pos)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return {
|
||||||
|
parsed = true,
|
||||||
|
value = values,
|
||||||
|
pos = new_pos,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local Node = {}
|
||||||
|
|
||||||
|
Node.Type = {
|
||||||
|
SNIPPET = 0,
|
||||||
|
TABSTOP = 1,
|
||||||
|
PLACEHOLDER = 2,
|
||||||
|
VARIABLE = 3,
|
||||||
|
CHOICE = 4,
|
||||||
|
TRANSFORM = 5,
|
||||||
|
FORMAT = 6,
|
||||||
|
TEXT = 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
function Node:__tostring()
|
||||||
|
local insert_text = {}
|
||||||
|
if self.type == Node.Type.SNIPPET then
|
||||||
|
for _, c in ipairs(self.children) do
|
||||||
|
table.insert(insert_text, tostring(c))
|
||||||
|
end
|
||||||
|
elseif self.type == Node.Type.CHOICE then
|
||||||
|
table.insert(insert_text, self.items[1])
|
||||||
|
elseif self.type == Node.Type.PLACEHOLDER then
|
||||||
|
for _, c in ipairs(self.children or {}) do
|
||||||
|
table.insert(insert_text, tostring(c))
|
||||||
|
end
|
||||||
|
elseif self.type == Node.Type.TEXT then
|
||||||
|
table.insert(insert_text, self.esc)
|
||||||
|
end
|
||||||
|
return table.concat(insert_text, '')
|
||||||
|
end
|
||||||
|
|
||||||
|
--@see https://code.visualstudio.com/docs/editor/userdefinedsnippets#_grammar
|
||||||
|
|
||||||
|
local S = {}
|
||||||
|
S.dollar = P.token('$')
|
||||||
|
S.open = P.token('{')
|
||||||
|
S.close = P.token('}')
|
||||||
|
S.colon = P.token(':')
|
||||||
|
S.slash = P.token('/')
|
||||||
|
S.comma = P.token(',')
|
||||||
|
S.pipe = P.token('|')
|
||||||
|
S.plus = P.token('+')
|
||||||
|
S.minus = P.token('-')
|
||||||
|
S.question = P.token('?')
|
||||||
|
S.int = P.map(P.pattern('[0-9]+'), function(value)
|
||||||
|
return tonumber(value, 10)
|
||||||
|
end)
|
||||||
|
S.var = P.pattern('[%a_][%w_]+')
|
||||||
|
S.text = function(targets, specials)
|
||||||
|
return P.map(P.take_until(targets, specials), function(value)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.TEXT,
|
||||||
|
raw = value.raw,
|
||||||
|
esc = value.esc,
|
||||||
|
}, Node)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
S.toplevel = P.lazy(function()
|
||||||
|
return P.any(S.placeholder, S.tabstop, S.variable, S.choice)
|
||||||
|
end)
|
||||||
|
|
||||||
|
S.format = P.any(
|
||||||
|
P.map(P.seq(S.dollar, S.int), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.FORMAT,
|
||||||
|
capture_index = values[2],
|
||||||
|
}, Node)
|
||||||
|
end),
|
||||||
|
P.map(P.seq(S.dollar, S.open, S.int, S.close), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.FORMAT,
|
||||||
|
capture_index = values[3],
|
||||||
|
}, Node)
|
||||||
|
end),
|
||||||
|
P.map(P.seq(S.dollar, S.open, S.int, S.colon, S.slash, P.any(
|
||||||
|
P.token('upcase'),
|
||||||
|
P.token('downcase'),
|
||||||
|
P.token('capitalize'),
|
||||||
|
P.token('camelcase'),
|
||||||
|
P.token('pascalcase')
|
||||||
|
), S.close), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.FORMAT,
|
||||||
|
capture_index = values[3],
|
||||||
|
modifier = values[6],
|
||||||
|
}, Node)
|
||||||
|
end),
|
||||||
|
P.map(P.seq(S.dollar, S.open, S.int, S.colon, P.any(
|
||||||
|
P.seq(S.question, P.take_until({ ':' }, { '\\' }), S.colon, P.take_until({ '}' }, { '\\' })),
|
||||||
|
P.seq(S.plus, P.take_until({ '}' }, { '\\' })),
|
||||||
|
P.seq(S.minus, P.take_until({ '}' }, { '\\' }))
|
||||||
|
), S.close), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.FORMAT,
|
||||||
|
capture_index = values[3],
|
||||||
|
if_text = values[5][2].esc,
|
||||||
|
else_text = (values[5][4] or {}).esc,
|
||||||
|
}, Node)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
|
||||||
|
S.transform = P.map(P.seq(
|
||||||
|
S.slash,
|
||||||
|
P.take_until({ '/' }, { '\\' }),
|
||||||
|
S.slash,
|
||||||
|
P.many(P.any(S.format, S.text({ '$', '/' }, { '\\' }))),
|
||||||
|
S.slash,
|
||||||
|
P.opt(P.pattern('[ig]+'))
|
||||||
|
), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.TRANSFORM,
|
||||||
|
pattern = values[2].raw,
|
||||||
|
format = values[4],
|
||||||
|
option = values[6],
|
||||||
|
}, Node)
|
||||||
|
end)
|
||||||
|
|
||||||
|
S.tabstop = P.any(
|
||||||
|
P.map(P.seq(S.dollar, S.int), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.TABSTOP,
|
||||||
|
tabstop = values[2],
|
||||||
|
}, Node)
|
||||||
|
end),
|
||||||
|
P.map(P.seq(S.dollar, S.open, S.int, S.close), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.TABSTOP,
|
||||||
|
tabstop = values[3],
|
||||||
|
}, Node)
|
||||||
|
end),
|
||||||
|
P.map(P.seq(S.dollar, S.open, S.int, S.transform, S.close), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.TABSTOP,
|
||||||
|
tabstop = values[3],
|
||||||
|
transform = values[4],
|
||||||
|
}, Node)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
|
||||||
|
S.placeholder = P.any(
|
||||||
|
P.map(P.seq(S.dollar, S.open, S.int, S.colon, P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), S.close), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.PLACEHOLDER,
|
||||||
|
tabstop = values[3],
|
||||||
|
children = values[5],
|
||||||
|
}, Node)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
|
||||||
|
S.choice = P.map(P.seq(
|
||||||
|
S.dollar,
|
||||||
|
S.open,
|
||||||
|
S.int,
|
||||||
|
S.pipe,
|
||||||
|
P.many(
|
||||||
|
P.map(P.seq(S.text({ ',', '|' }), P.opt(S.comma)), function(values)
|
||||||
|
return values[1].esc
|
||||||
|
end)
|
||||||
|
),
|
||||||
|
S.pipe,
|
||||||
|
S.close
|
||||||
|
), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.CHOICE,
|
||||||
|
tabstop = values[3],
|
||||||
|
items = values[5],
|
||||||
|
}, Node)
|
||||||
|
end)
|
||||||
|
|
||||||
|
S.variable = P.any(
|
||||||
|
P.map(P.seq(S.dollar, S.var), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.VARIABLE,
|
||||||
|
name = values[2],
|
||||||
|
}, Node)
|
||||||
|
end),
|
||||||
|
P.map(P.seq(S.dollar, S.open, S.var, S.close), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.VARIABLE,
|
||||||
|
name = values[3],
|
||||||
|
}, Node)
|
||||||
|
end),
|
||||||
|
P.map(P.seq(S.dollar, S.open, S.var, S.transform, S.close), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.VARIABLE,
|
||||||
|
name = values[3],
|
||||||
|
transform = values[4],
|
||||||
|
}, Node)
|
||||||
|
end),
|
||||||
|
P.map(P.seq(S.dollar, S.open, S.var, S.colon, P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), S.close), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.VARIABLE,
|
||||||
|
name = values[3],
|
||||||
|
children = values[5],
|
||||||
|
}, Node)
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
|
||||||
|
S.snippet = P.map(P.many(P.any(S.toplevel, S.text({ '$' }, { '}', '\\' }))), function(values)
|
||||||
|
return setmetatable({
|
||||||
|
type = Node.Type.SNIPPET,
|
||||||
|
children = values,
|
||||||
|
}, Node)
|
||||||
|
end)
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---The snippet node type enum
|
||||||
|
---@types table<string, number>
|
||||||
|
M.NodeType = Node.Type
|
||||||
|
|
||||||
|
---Parse snippet string and returns the AST
|
||||||
|
---@param input string
|
||||||
|
---@return table
|
||||||
|
function M.parse(input)
|
||||||
|
local result = S.snippet(input, 1)
|
||||||
|
if not result.parsed then
|
||||||
|
error('snippet parsing failed.')
|
||||||
|
end
|
||||||
|
return result.value
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
local protocol = require 'vim.lsp.protocol'
|
local protocol = require 'vim.lsp.protocol'
|
||||||
|
local snippet = require 'vim.lsp._snippet'
|
||||||
local vim = vim
|
local vim = vim
|
||||||
local validate = vim.validate
|
local validate = vim.validate
|
||||||
local api = vim.api
|
local api = vim.api
|
||||||
@@ -579,9 +580,13 @@ end
|
|||||||
--@param input (string) unparsed snippet
|
--@param input (string) unparsed snippet
|
||||||
--@returns (string) parsed snippet
|
--@returns (string) parsed snippet
|
||||||
function M.parse_snippet(input)
|
function M.parse_snippet(input)
|
||||||
local res, _ = parse_snippet_rec(input, false)
|
local ok, parsed = pcall(function()
|
||||||
|
return tostring(snippet.parse(input))
|
||||||
return res
|
end)
|
||||||
|
if not ok then
|
||||||
|
return input
|
||||||
|
end
|
||||||
|
return parsed
|
||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
|
|||||||
@@ -1555,8 +1555,8 @@ int vgetc(void)
|
|||||||
*/
|
*/
|
||||||
may_garbage_collect = false;
|
may_garbage_collect = false;
|
||||||
|
|
||||||
// Exec lua callbacks for on_keystroke
|
// Execute Lua on_key callbacks.
|
||||||
nlua_execute_log_keystroke(c);
|
nlua_execute_on_key(c);
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1490,7 +1490,7 @@ int nlua_expand_pat(expand_T *xp,
|
|||||||
lua_getfield(lstate, -1, "_expand_pat");
|
lua_getfield(lstate, -1, "_expand_pat");
|
||||||
luaL_checktype(lstate, -1, LUA_TFUNCTION);
|
luaL_checktype(lstate, -1, LUA_TFUNCTION);
|
||||||
|
|
||||||
// [ vim, vim._log_keystroke, buf ]
|
// [ vim, vim._on_key, buf ]
|
||||||
lua_pushlstring(lstate, (const char *)pat, STRLEN(pat));
|
lua_pushlstring(lstate, (const char *)pat, STRLEN(pat));
|
||||||
|
|
||||||
if (lua_pcall(lstate, 1, 2, 0) != 0) {
|
if (lua_pcall(lstate, 1, 2, 0) != 0) {
|
||||||
@@ -1764,7 +1764,7 @@ char_u *nlua_register_table_as_callable(typval_T *const arg)
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void nlua_execute_log_keystroke(int c)
|
void nlua_execute_on_key(int c)
|
||||||
{
|
{
|
||||||
char_u buf[NUMBUFLEN];
|
char_u buf[NUMBUFLEN];
|
||||||
size_t buf_len = special_to_buf(c, mod_mask, false, buf);
|
size_t buf_len = special_to_buf(c, mod_mask, false, buf);
|
||||||
@@ -1778,17 +1778,17 @@ void nlua_execute_log_keystroke(int c)
|
|||||||
// [ vim ]
|
// [ vim ]
|
||||||
lua_getglobal(lstate, "vim");
|
lua_getglobal(lstate, "vim");
|
||||||
|
|
||||||
// [ vim, vim._log_keystroke ]
|
// [ vim, vim._on_key]
|
||||||
lua_getfield(lstate, -1, "_log_keystroke");
|
lua_getfield(lstate, -1, "_on_key");
|
||||||
luaL_checktype(lstate, -1, LUA_TFUNCTION);
|
luaL_checktype(lstate, -1, LUA_TFUNCTION);
|
||||||
|
|
||||||
// [ vim, vim._log_keystroke, buf ]
|
// [ vim, vim._on_key, buf ]
|
||||||
lua_pushlstring(lstate, (const char *)buf, buf_len);
|
lua_pushlstring(lstate, (const char *)buf, buf_len);
|
||||||
|
|
||||||
if (lua_pcall(lstate, 1, 0, 0)) {
|
if (lua_pcall(lstate, 1, 0, 0)) {
|
||||||
nlua_error(
|
nlua_error(
|
||||||
lstate,
|
lstate,
|
||||||
_("Error executing vim.log_keystroke lua callback: %.*s"));
|
_("Error executing vim.on_key Lua callback: %.*s"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// [ vim ]
|
// [ vim ]
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
-- 1. runtime/lua/vim/ (the runtime): For "nice to have" features, e.g. the
|
-- 1. runtime/lua/vim/ (the runtime): For "nice to have" features, e.g. the
|
||||||
-- `inspect` and `lpeg` modules.
|
-- `inspect` and `lpeg` modules.
|
||||||
-- 2. runtime/lua/vim/shared.lua: Code shared between Nvim and tests.
|
-- 2. runtime/lua/vim/shared.lua: Code shared between Nvim and tests.
|
||||||
|
-- (This will go away if we migrate to nvim as the test-runner.)
|
||||||
-- 3. src/nvim/lua/: Compiled-into Nvim itself.
|
-- 3. src/nvim/lua/: Compiled-into Nvim itself.
|
||||||
--
|
--
|
||||||
-- Guideline: "If in doubt, put it in the runtime".
|
-- Guideline: "If in doubt, put it in the runtime".
|
||||||
@@ -425,26 +426,35 @@ function vim.notify(msg, log_level, _opts)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
local on_keystroke_callbacks = {}
|
function vim.register_keystroke_callback()
|
||||||
|
error('vim.register_keystroke_callback is deprecated, instead use: vim.on_key')
|
||||||
|
end
|
||||||
|
|
||||||
--- Register a lua {fn} with an {id} to be run after every keystroke.
|
local on_key_cbs = {}
|
||||||
|
|
||||||
|
--- Adds Lua function {fn} with namespace id {ns_id} as a listener to every,
|
||||||
|
--- yes every, input key.
|
||||||
---
|
---
|
||||||
--@param fn function: Function to call. It should take one argument, which is a string.
|
--- The Nvim command-line option |-w| is related but does not support callbacks
|
||||||
--- The string will contain the literal keys typed.
|
--- and cannot be toggled dynamically.
|
||||||
--- See |i_CTRL-V|
|
|
||||||
---
|
---
|
||||||
|
---@param fn function: Callback function. It should take one string argument.
|
||||||
|
--- On each key press, Nvim passes the key char to fn(). |i_CTRL-V|
|
||||||
--- If {fn} is nil, it removes the callback for the associated {ns_id}
|
--- If {fn} is nil, it removes the callback for the associated {ns_id}
|
||||||
--@param ns_id number? Namespace ID. If not passed or 0, will generate and return a new
|
---@param ns_id number? Namespace ID. If nil or 0, generates and returns a new
|
||||||
--- namespace ID from |nvim_create_namesapce()|
|
--- |nvim_create_namesapce()| id.
|
||||||
---
|
---
|
||||||
--@return number Namespace ID associated with {fn}
|
---@return number Namespace id associated with {fn}. Or count of all callbacks
|
||||||
|
---if on_key() is called without arguments.
|
||||||
---
|
---
|
||||||
--@note {fn} will be automatically removed if an error occurs while calling.
|
---@note {fn} will be removed if an error occurs while calling.
|
||||||
--- This is to prevent the annoying situation of every keystroke erroring
|
---@note {fn} will not be cleared by |nvim_buf_clear_namespace()|
|
||||||
--- while trying to remove a broken callback.
|
---@note {fn} will receive the keys after mappings have been evaluated
|
||||||
--@note {fn} will not be cleared from |nvim_buf_clear_namespace()|
|
function vim.on_key(fn, ns_id)
|
||||||
--@note {fn} will receive the keystrokes after mappings have been evaluated
|
if fn == nil and ns_id == nil then
|
||||||
function vim.register_keystroke_callback(fn, ns_id)
|
return #on_key_cbs
|
||||||
|
end
|
||||||
|
|
||||||
vim.validate {
|
vim.validate {
|
||||||
fn = { fn, 'c', true},
|
fn = { fn, 'c', true},
|
||||||
ns_id = { ns_id, 'n', true }
|
ns_id = { ns_id, 'n', true }
|
||||||
@@ -454,20 +464,19 @@ function vim.register_keystroke_callback(fn, ns_id)
|
|||||||
ns_id = vim.api.nvim_create_namespace('')
|
ns_id = vim.api.nvim_create_namespace('')
|
||||||
end
|
end
|
||||||
|
|
||||||
on_keystroke_callbacks[ns_id] = fn
|
on_key_cbs[ns_id] = fn
|
||||||
return ns_id
|
return ns_id
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Function that executes the keystroke callbacks.
|
--- Executes the on_key callbacks.
|
||||||
--@private
|
---@private
|
||||||
function vim._log_keystroke(char)
|
function vim._on_key(char)
|
||||||
local failed_ns_ids = {}
|
local failed_ns_ids = {}
|
||||||
local failed_messages = {}
|
local failed_messages = {}
|
||||||
for k, v in pairs(on_keystroke_callbacks) do
|
for k, v in pairs(on_key_cbs) do
|
||||||
local ok, err_msg = pcall(v, char)
|
local ok, err_msg = pcall(v, char)
|
||||||
if not ok then
|
if not ok then
|
||||||
vim.register_keystroke_callback(nil, k)
|
vim.on_key(nil, k)
|
||||||
|
|
||||||
table.insert(failed_ns_ids, k)
|
table.insert(failed_ns_ids, k)
|
||||||
table.insert(failed_messages, err_msg)
|
table.insert(failed_messages, err_msg)
|
||||||
end
|
end
|
||||||
@@ -475,7 +484,7 @@ function vim._log_keystroke(char)
|
|||||||
|
|
||||||
if failed_ns_ids[1] then
|
if failed_ns_ids[1] then
|
||||||
error(string.format(
|
error(string.format(
|
||||||
"Error executing 'on_keystroke' with ns_ids of '%s'\n With messages: %s",
|
"Error executing 'on_key' with ns_ids '%s'\n Messages: %s",
|
||||||
table.concat(failed_ns_ids, ", "),
|
table.concat(failed_ns_ids, ", "),
|
||||||
table.concat(failed_messages, "\n")))
|
table.concat(failed_messages, "\n")))
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ int main(int argc, char **argv)
|
|||||||
// prepare screen now, so external UIs can display messages
|
// prepare screen now, so external UIs can display messages
|
||||||
starting = NO_BUFFERS;
|
starting = NO_BUFFERS;
|
||||||
screenclear();
|
screenclear();
|
||||||
TIME_MSG("initialized screen early for UI");
|
TIME_MSG("init screen for UI");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -258,6 +258,9 @@ Number; !must be defined to function properly):
|
|||||||
|
|
||||||
- `TEST_SKIP_FRAGILE` (F) (D): makes test suite skip some fragile tests.
|
- `TEST_SKIP_FRAGILE` (F) (D): makes test suite skip some fragile tests.
|
||||||
|
|
||||||
|
- `TEST_TIMEOUT` (FU) (I): specifies maximum time, in seconds, before the test
|
||||||
|
suite run is killed
|
||||||
|
|
||||||
- `NVIM_PROG`, `NVIM_PRG` (F) (S): override path to Neovim executable (default
|
- `NVIM_PROG`, `NVIM_PRG` (F) (S): override path to Neovim executable (default
|
||||||
to `build/bin/nvim`).
|
to `build/bin/nvim`).
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ local eq, clear, eval, command, nvim, next_msg =
|
|||||||
local meths = helpers.meths
|
local meths = helpers.meths
|
||||||
local exec_lua = helpers.exec_lua
|
local exec_lua = helpers.exec_lua
|
||||||
local retry = helpers.retry
|
local retry = helpers.retry
|
||||||
|
local isCI = helpers.isCI
|
||||||
|
|
||||||
describe('notify', function()
|
describe('notify', function()
|
||||||
local channel
|
local channel
|
||||||
@@ -76,6 +77,10 @@ describe('notify', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
it('cancels stale events on channel close', function()
|
it('cancels stale events on channel close', function()
|
||||||
|
if isCI() then
|
||||||
|
pending('hangs on CI #14083 #15251')
|
||||||
|
return
|
||||||
|
end
|
||||||
if helpers.pending_win32(pending) then return end
|
if helpers.pending_win32(pending) then return end
|
||||||
local catchan = eval("jobstart(['cat'], {'rpc': v:true})")
|
local catchan = eval("jobstart(['cat'], {'rpc': v:true})")
|
||||||
eq({id=catchan, stream='job', mode='rpc', client = {}}, exec_lua ([[
|
eq({id=catchan, stream='job', mode='rpc', client = {}}, exec_lua ([[
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ local funcs = helpers.funcs
|
|||||||
local meths = helpers.meths
|
local meths = helpers.meths
|
||||||
local dedent = helpers.dedent
|
local dedent = helpers.dedent
|
||||||
local command = helpers.command
|
local command = helpers.command
|
||||||
|
local insert = helpers.insert
|
||||||
local clear = helpers.clear
|
local clear = helpers.clear
|
||||||
local eq = helpers.eq
|
local eq = helpers.eq
|
||||||
local ok = helpers.ok
|
local ok = helpers.ok
|
||||||
@@ -1855,7 +1856,7 @@ describe('lua stdlib', function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
it('vim.region', function()
|
it('vim.region', function()
|
||||||
helpers.insert(helpers.dedent( [[
|
insert(helpers.dedent( [[
|
||||||
text tααt tααt text
|
text tααt tααt text
|
||||||
text tαxt txtα tex
|
text tαxt txtα tex
|
||||||
text tαxt tαxt
|
text tαxt tαxt
|
||||||
@@ -1863,65 +1864,67 @@ describe('lua stdlib', function()
|
|||||||
eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]])
|
eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('vim.execute_on_keystroke', function()
|
describe('vim.on_key', function()
|
||||||
it('should keep track of keystrokes', function()
|
it('tracks keystrokes', function()
|
||||||
helpers.insert([[hello world ]])
|
insert([[hello world ]])
|
||||||
|
|
||||||
exec_lua [[
|
exec_lua [[
|
||||||
KeysPressed = {}
|
keys = {}
|
||||||
|
|
||||||
vim.register_keystroke_callback(function(buf)
|
vim.on_key(function(buf)
|
||||||
if buf:byte() == 27 then
|
if buf:byte() == 27 then
|
||||||
buf = "<ESC>"
|
buf = "<ESC>"
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(KeysPressed, buf)
|
table.insert(keys, buf)
|
||||||
end)
|
end)
|
||||||
]]
|
]]
|
||||||
|
|
||||||
helpers.insert([[next 🤦 lines å ]])
|
insert([[next 🤦 lines å ]])
|
||||||
|
|
||||||
-- It has escape in the keys pressed
|
-- It has escape in the keys pressed
|
||||||
eq('inext 🤦 lines å <ESC>', exec_lua [[return table.concat(KeysPressed, '')]])
|
eq('inext 🤦 lines å <ESC>', exec_lua [[return table.concat(keys, '')]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('should allow removing trackers.', function()
|
it('allows removing on_key listeners', function()
|
||||||
helpers.insert([[hello world]])
|
insert([[hello world]])
|
||||||
|
|
||||||
exec_lua [[
|
exec_lua [[
|
||||||
KeysPressed = {}
|
keys = {}
|
||||||
|
|
||||||
return vim.register_keystroke_callback(function(buf)
|
return vim.on_key(function(buf)
|
||||||
if buf:byte() == 27 then
|
if buf:byte() == 27 then
|
||||||
buf = "<ESC>"
|
buf = "<ESC>"
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(KeysPressed, buf)
|
table.insert(keys, buf)
|
||||||
end, vim.api.nvim_create_namespace("logger"))
|
end, vim.api.nvim_create_namespace("logger"))
|
||||||
]]
|
]]
|
||||||
|
|
||||||
helpers.insert([[next lines]])
|
insert([[next lines]])
|
||||||
|
|
||||||
exec_lua("vim.register_keystroke_callback(nil, vim.api.nvim_create_namespace('logger'))")
|
eq(1, exec_lua('return vim.on_key()'))
|
||||||
|
exec_lua("vim.on_key(nil, vim.api.nvim_create_namespace('logger'))")
|
||||||
|
eq(0, exec_lua('return vim.on_key()'))
|
||||||
|
|
||||||
helpers.insert([[more lines]])
|
insert([[more lines]])
|
||||||
|
|
||||||
-- It has escape in the keys pressed
|
-- It has escape in the keys pressed
|
||||||
eq('inext lines<ESC>', exec_lua [[return table.concat(KeysPressed, '')]])
|
eq('inext lines<ESC>', exec_lua [[return table.concat(keys, '')]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('should not call functions that error again.', function()
|
it('skips any function that caused an error', function()
|
||||||
helpers.insert([[hello world]])
|
insert([[hello world]])
|
||||||
|
|
||||||
exec_lua [[
|
exec_lua [[
|
||||||
KeysPressed = {}
|
keys = {}
|
||||||
|
|
||||||
return vim.register_keystroke_callback(function(buf)
|
return vim.on_key(function(buf)
|
||||||
if buf:byte() == 27 then
|
if buf:byte() == 27 then
|
||||||
buf = "<ESC>"
|
buf = "<ESC>"
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(KeysPressed, buf)
|
table.insert(keys, buf)
|
||||||
|
|
||||||
if buf == 'l' then
|
if buf == 'l' then
|
||||||
error("Dumb Error")
|
error("Dumb Error")
|
||||||
@@ -1929,35 +1932,30 @@ describe('lua stdlib', function()
|
|||||||
end)
|
end)
|
||||||
]]
|
]]
|
||||||
|
|
||||||
helpers.insert([[next lines]])
|
insert([[next lines]])
|
||||||
helpers.insert([[more lines]])
|
insert([[more lines]])
|
||||||
|
|
||||||
-- Only the first letter gets added. After that we remove the callback
|
-- Only the first letter gets added. After that we remove the callback
|
||||||
eq('inext l', exec_lua [[ return table.concat(KeysPressed, '') ]])
|
eq('inext l', exec_lua [[ return table.concat(keys, '') ]])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('should process mapped keys, not unmapped keys', function()
|
it('processes mapped keys, not unmapped keys', function()
|
||||||
exec_lua [[
|
exec_lua [[
|
||||||
KeysPressed = {}
|
keys = {}
|
||||||
|
|
||||||
vim.cmd("inoremap hello world")
|
vim.cmd("inoremap hello world")
|
||||||
|
|
||||||
vim.register_keystroke_callback(function(buf)
|
vim.on_key(function(buf)
|
||||||
if buf:byte() == 27 then
|
if buf:byte() == 27 then
|
||||||
buf = "<ESC>"
|
buf = "<ESC>"
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(KeysPressed, buf)
|
table.insert(keys, buf)
|
||||||
end)
|
end)
|
||||||
]]
|
]]
|
||||||
|
insert("hello")
|
||||||
|
|
||||||
helpers.insert("hello")
|
eq('iworld<ESC>', exec_lua[[return table.concat(keys, '')]])
|
||||||
|
|
||||||
local next_status = exec_lua [[
|
|
||||||
return table.concat(KeysPressed, '')
|
|
||||||
]]
|
|
||||||
|
|
||||||
eq("iworld<ESC>", next_status)
|
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|||||||
152
test/functional/plugin/lsp/snippet_spec.lua
Normal file
152
test/functional/plugin/lsp/snippet_spec.lua
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
|
local snippet = require('vim.lsp._snippet')
|
||||||
|
|
||||||
|
local eq = helpers.eq
|
||||||
|
local exec_lua = helpers.exec_lua
|
||||||
|
|
||||||
|
describe('vim.lsp._snippet', function()
|
||||||
|
before_each(helpers.clear)
|
||||||
|
after_each(helpers.clear)
|
||||||
|
|
||||||
|
local parse = function(...)
|
||||||
|
return exec_lua('return require("vim.lsp._snippet").parse(...)', ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
it('should parse only text', function()
|
||||||
|
eq({
|
||||||
|
type = snippet.NodeType.SNIPPET,
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.TEXT,
|
||||||
|
raw = 'TE\\$\\}XT',
|
||||||
|
esc = 'TE$}XT'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, parse('TE\\$\\}XT'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should parse tabstop', function()
|
||||||
|
eq({
|
||||||
|
type = snippet.NodeType.SNIPPET,
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.TABSTOP,
|
||||||
|
tabstop = 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.TABSTOP,
|
||||||
|
tabstop = 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, parse('$1${2}'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should parse placeholders', function()
|
||||||
|
eq({
|
||||||
|
type = snippet.NodeType.SNIPPET,
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.PLACEHOLDER,
|
||||||
|
tabstop = 1,
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.PLACEHOLDER,
|
||||||
|
tabstop = 2,
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.TEXT,
|
||||||
|
raw = 'TE\\$\\}XT',
|
||||||
|
esc = 'TE$}XT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.TABSTOP,
|
||||||
|
tabstop = 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.TABSTOP,
|
||||||
|
tabstop = 1,
|
||||||
|
transform = {
|
||||||
|
type = snippet.NodeType.TRANSFORM,
|
||||||
|
pattern = 'regex',
|
||||||
|
option = 'i',
|
||||||
|
format = {
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.FORMAT,
|
||||||
|
capture_index = 1,
|
||||||
|
modifier = 'upcase'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.TEXT,
|
||||||
|
raw = 'TE\\$\\}XT',
|
||||||
|
esc = 'TE$}XT'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}, parse('${1:${2:TE\\$\\}XT$3${1/regex/${1:/upcase}/i}TE\\$\\}XT}}'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should parse variables', function()
|
||||||
|
eq({
|
||||||
|
type = snippet.NodeType.SNIPPET,
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.VARIABLE,
|
||||||
|
name = 'VAR',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.VARIABLE,
|
||||||
|
name = 'VAR',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.VARIABLE,
|
||||||
|
name = 'VAR',
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.TABSTOP,
|
||||||
|
tabstop = 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.VARIABLE,
|
||||||
|
name = 'VAR',
|
||||||
|
transform = {
|
||||||
|
type = snippet.NodeType.TRANSFORM,
|
||||||
|
pattern = 'regex',
|
||||||
|
format = {
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.FORMAT,
|
||||||
|
capture_index = 1,
|
||||||
|
modifier = 'upcase',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}, parse('$VAR${VAR}${VAR:$1}${VAR/regex/${1:/upcase}/}'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should parse choice', function()
|
||||||
|
eq({
|
||||||
|
type = snippet.NodeType.SNIPPET,
|
||||||
|
children = {
|
||||||
|
{
|
||||||
|
type = snippet.NodeType.CHOICE,
|
||||||
|
tabstop = 1,
|
||||||
|
items = {
|
||||||
|
',',
|
||||||
|
'|'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, parse('${1|\\,,\\||}'))
|
||||||
|
end)
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
@@ -1441,8 +1441,10 @@ describe('LSP', function()
|
|||||||
{ label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} },
|
{ label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} },
|
||||||
-- nested snippet tokens
|
-- nested snippet tokens
|
||||||
{ label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} },
|
{ label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} },
|
||||||
|
-- braced tabstop
|
||||||
|
{ label='foocar', sortText="j", insertText='foodar()${0}', insertTextFormat=2, textEdit={} },
|
||||||
-- plain text
|
-- plain text
|
||||||
{ label='foocar', sortText="j", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} },
|
{ label='foocar', sortText="k", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} },
|
||||||
}
|
}
|
||||||
local completion_list_items = {items=completion_list}
|
local completion_list_items = {items=completion_list}
|
||||||
local expected = {
|
local expected = {
|
||||||
@@ -1454,8 +1456,9 @@ 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', sortText="f", 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', sortText="f", 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', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } },
|
{ 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', sortText="g", insertText='foodar', insertTextFormat=2, 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', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} } } } } },
|
{ 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', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, 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', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} } } } } },
|
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, 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', sortText="j", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } },
|
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar()', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="j", insertText='foodar()${0}', insertTextFormat=2, 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', sortText="k", 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))
|
eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list, prefix))
|
||||||
|
|||||||
Reference in New Issue
Block a user