mirror of
https://github.com/neovim/neovim
synced 2025-07-22 14:21:58 +00:00
Compare commits
58 Commits
Author | SHA1 | Date | |
---|---|---|---|
0159e4daae | |||
c9e4f86b75 | |||
14cdaca6a8 | |||
7b0ae589f0 | |||
8ec5bc9126 | |||
959cf5e53c | |||
dc15b3a92c | |||
18375c6df6 | |||
7b1315fe61 | |||
27bac13be6 | |||
d26d489e2e | |||
a6eab6e25e | |||
cd8f6c5fb7 | |||
f8e0011534 | |||
7d67bd5865 | |||
2132c063af | |||
132053c1d2 | |||
51d6b26729 | |||
f7002337c0 | |||
6bda2f56eb | |||
be58ba250e | |||
d0e9a11e39 | |||
5c42376c15 | |||
41f761130e | |||
a265201307 | |||
33000bd9cf | |||
9f73b7c214 | |||
eaa1c47377 | |||
942b16adf7 | |||
cf62554e5a | |||
6436100b6e | |||
04cde576ed | |||
cd864748d3 | |||
09ff3146f3 | |||
f809664f89 | |||
d83df7f7b5 | |||
64dc7a1b55 | |||
5a813160ae | |||
917f306666 | |||
a9cca1b050 | |||
989ccb8222 | |||
2ae4c96d91 | |||
ae89330ec0 | |||
2229e99ef9 | |||
88336851ee | |||
3a0543bd61 | |||
008b83f5a2 | |||
915dda3f96 | |||
b6b12ea7c3 | |||
502a56867d | |||
78482138ae | |||
fe815244f0 | |||
56d86970b0 | |||
6c4f66f381 | |||
6f965f41df | |||
f027c5e1e4 | |||
f589c2619b | |||
8336488ce1 |
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@ -28,10 +28,14 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y autoconf automake build-essential cmake gcc-11 gettext gperf libtool-bin locales ninja-build pkg-config unzip
|
||||
- if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly')
|
||||
run: printf 'NVIM_BUILD_TYPE=Release\n' >> $GITHUB_ENV
|
||||
- if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly')
|
||||
run: printf 'NVIM_BUILD_TYPE=RelWithDebInfo\n' >> $GITHUB_ENV
|
||||
- name: Build release
|
||||
id: build
|
||||
run: |
|
||||
CC=gcc-11 make CMAKE_BUILD_TYPE=RelWithDebInfo CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX:PATH="
|
||||
CC=gcc-11 make CMAKE_BUILD_TYPE=${NVIM_BUILD_TYPE} CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX:PATH="
|
||||
printf '::set-output name=version::%s\n' "$(./build/bin/nvim --version | head -n 3 | sed -z 's/\n/%0A/g')"
|
||||
printf '::set-output name=release::%s\n' "$(./build/bin/nvim --version | head -n 1)"
|
||||
make DESTDIR="$GITHUB_WORKSPACE/build/release/nvim-linux64" install
|
||||
@ -80,9 +84,13 @@ jobs:
|
||||
brew update >/dev/null
|
||||
brew upgrade
|
||||
brew install automake ninja
|
||||
- if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly')
|
||||
run: printf 'NVIM_BUILD_TYPE=Release\n' >> $GITHUB_ENV
|
||||
- if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly')
|
||||
run: printf 'NVIM_BUILD_TYPE=RelWithDebInfo\n' >> $GITHUB_ENV
|
||||
- name: Build release
|
||||
run: |
|
||||
make CMAKE_BUILD_TYPE=Release CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11"
|
||||
make CMAKE_BUILD_TYPE=${NVIM_BUILD_TYPE} CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11"
|
||||
make DESTDIR="$GITHUB_WORKSPACE/build/release/nvim-osx64" install
|
||||
- name: Create package
|
||||
run: |
|
||||
|
@ -1,4 +1,4 @@
|
||||
.DONE:
|
||||
@echo "Please use GNU Make (gmake) to build neovim"
|
||||
@echo "Use GNU Make (gmake) to build neovim"
|
||||
.DEFAULT:
|
||||
@echo "Please use GNU Make (gmake) to build neovim"
|
||||
@echo "Use GNU Make (gmake) to build neovim"
|
||||
|
@ -137,7 +137,7 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY
|
||||
# version string, else they are combined with the result of `git describe`.
|
||||
set(NVIM_VERSION_MAJOR 0)
|
||||
set(NVIM_VERSION_MINOR 5)
|
||||
set(NVIM_VERSION_PATCH 0)
|
||||
set(NVIM_VERSION_PATCH 1)
|
||||
set(NVIM_VERSION_PRERELEASE "") # for package maintainers
|
||||
|
||||
# API level
|
||||
|
@ -28,10 +28,10 @@ Reporting problems
|
||||
Developer guidelines
|
||||
--------------------
|
||||
|
||||
- Nvim contributors should read `:help dev`.
|
||||
- External UI developers should read `:help dev-ui`.
|
||||
- API client developers should read `:help dev-api-client`.
|
||||
- Nvim developers are _strongly encouraged_ to install `ninja` for faster builds.
|
||||
- Read `:help dev` if you are working on Nvim core.
|
||||
- Read `:help dev-ui` if you are developing a UI.
|
||||
- Read `:help dev-api-client` if you are developing an API client.
|
||||
- Install `ninja` for faster builds of Nvim.
|
||||
```
|
||||
sudo apt-get install ninja-build
|
||||
make distclean
|
||||
|
@ -49,6 +49,10 @@ endif()
|
||||
set(ENV{TMPDIR} "${BUILD_DIR}/Xtest_tmpdir/${TEST_PATH}")
|
||||
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.
|
||||
execute_process(
|
||||
COMMAND ${BUSTED_PRG} -v -o test.busted.outputHandlers.${BUSTED_OUTPUT_TYPE}
|
||||
@ -58,6 +62,7 @@ execute_process(
|
||||
--lpath=?.lua
|
||||
${BUSTED_ARGS}
|
||||
${TEST_PATH}
|
||||
TIMEOUT $ENV{TEST_TIMEOUT}
|
||||
WORKING_DIRECTORY ${WORKING_DIR}
|
||||
ERROR_VARIABLE err
|
||||
RESULT_VARIABLE res
|
||||
|
@ -65,6 +65,7 @@ function! man#open_page(count, mods, ...) abort
|
||||
let b:man_sect = sect
|
||||
endfunction
|
||||
|
||||
" Called when a man:// buffer is opened.
|
||||
function! man#read_page(ref) abort
|
||||
try
|
||||
let [sect, name] = s:extract_sect_and_name_ref(a:ref)
|
||||
@ -121,6 +122,15 @@ function! s:system(cmd, ...) abort
|
||||
return opts.stdout
|
||||
endfunction
|
||||
|
||||
function! s:set_options(pager) abort
|
||||
setlocal filetype=man
|
||||
setlocal noswapfile buftype=nofile bufhidden=hide
|
||||
setlocal nomodified readonly nomodifiable
|
||||
if a:pager
|
||||
nnoremap <silent> <buffer> <nowait> q :lclose<CR>:q<CR>
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:get_page(path) abort
|
||||
" Disable hard-wrap by using a big $MANWIDTH (max 1000 on some systems #9065).
|
||||
" Soft-wrap: ftplugin/man.vim sets wrap/breakindent/….
|
||||
@ -134,9 +144,7 @@ function! s:get_page(path) abort
|
||||
endfunction
|
||||
|
||||
function! s:put_page(page) abort
|
||||
setlocal modifiable
|
||||
setlocal noreadonly
|
||||
setlocal noswapfile
|
||||
setlocal modifiable noreadonly noswapfile
|
||||
silent keepjumps %delete _
|
||||
silent put =a:page
|
||||
while getline(1) =~# '^\s*$'
|
||||
@ -148,7 +156,7 @@ function! s:put_page(page) abort
|
||||
silent! keeppatterns keepjumps %s/\s\{199,}/\=repeat(' ', 10)/g
|
||||
1
|
||||
lua require("man").highlight_man_page()
|
||||
setlocal filetype=man
|
||||
call s:set_options(v:false)
|
||||
endfunction
|
||||
|
||||
function! man#show_toc() abort
|
||||
@ -397,6 +405,7 @@ function! s:format_candidate(path, psect) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Called when Nvim is invoked as $MANPAGER.
|
||||
function! man#init_pager() abort
|
||||
" https://github.com/neovim/neovim/issues/6828
|
||||
let og_modifiable = &modifiable
|
||||
@ -420,6 +429,7 @@ function! man#init_pager() abort
|
||||
execute 'silent file man://'.tolower(fnameescape(ref))
|
||||
endif
|
||||
|
||||
call s:set_options(v:true)
|
||||
let &l:modifiable = og_modifiable
|
||||
endfunction
|
||||
|
||||
|
@ -158,7 +158,9 @@ function! s:clipboard.get(reg) abort
|
||||
end
|
||||
|
||||
let clipboard_data = s:try_cmd(s:paste[a:reg])
|
||||
if match(&clipboard, '\v(unnamed|unnamedplus)') >= 0 && get(s:selections[a:reg].data, 0, []) == clipboard_data
|
||||
if match(&clipboard, '\v(unnamed|unnamedplus)') >= 0
|
||||
\ && type(clipboard_data) == v:t_list
|
||||
\ && get(s:selections[a:reg].data, 0, []) ==# clipboard_data
|
||||
" When system clipboard return is same as our cache return the cache
|
||||
" as it contains regtype information
|
||||
return s:selections[a:reg].data
|
||||
|
@ -54,6 +54,8 @@ Functions ~
|
||||
without stopping the job. Use chanclose(id) to close
|
||||
any socket.
|
||||
|
||||
Lua ~
|
||||
*vim.register_keystroke_callback()* Use |vim.on_key()| instead.
|
||||
|
||||
Modifiers ~
|
||||
*cpo-<*
|
||||
|
@ -127,14 +127,20 @@ Sometimes a GUI or other application may want to force a provider to
|
||||
|
||||
DOCUMENTATION *dev-doc*
|
||||
|
||||
- Do not prefix help tags with "nvim-". Use |vim_diff.txt| to document
|
||||
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|.
|
||||
- Move deprecated features to |deprecated.txt|.
|
||||
- "Just say it". Avoid mushy, colloquial phrasing in all documentation
|
||||
(docstrings, user manual, website materials, newsletters, …). Don't mince
|
||||
words. Personality and flavor, used sparingly, are welcome--but in general,
|
||||
optimize for the reader's time and energy: be "precise yet concise".
|
||||
- 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.
|
||||
- "terminal" in a help tag always means "the embedded terminal emulator", not
|
||||
"the user host terminal".
|
||||
- "terminal" in a help tag always means "the embedded terminal emulator",
|
||||
not "the user host terminal".
|
||||
- Use "tui-" to prefix help tags related to the host terminal, and "TUI"
|
||||
in prose if possible.
|
||||
- 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
|
||||
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
|
||||
objects: tables are usually better.
|
||||
want to learn a big, fancy inheritance hierarchy. Thus avoid specialized
|
||||
objects; tables or values are usually better.
|
||||
|
||||
|
||||
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}
|
||||
|
||||
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".
|
||||
|
||||
|
||||
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:
|
||||
|
@ -5641,6 +5641,9 @@ jobstart({cmd}[, {opts}]) *jobstart()*
|
||||
before invoking `on_stderr`. |channel-buffered|
|
||||
stdout_buffered: (boolean) Collect data until EOF (stream
|
||||
closed) before invoking `on_stdout`. |channel-buffered|
|
||||
stdin: (string) Either "pipe" (default) to connect the
|
||||
job's stdin to a channel or "null" to disconnect
|
||||
stdin.
|
||||
width: (number) Width of the `pty` terminal.
|
||||
|
||||
{opts} is passed as |self| dictionary to the callback; the
|
||||
|
@ -202,23 +202,28 @@ responses and notifications from LSP servers.
|
||||
|
||||
For |lsp-request|, each |lsp-handler| has this signature: >
|
||||
|
||||
function(err, method, result, client_id, bufnr, config)
|
||||
function(err, result, ctx, config)
|
||||
<
|
||||
Parameters: ~
|
||||
{err} (table|nil)
|
||||
When the language server is unable to complete a
|
||||
request, a table with information about the error
|
||||
is sent. Otherwise, it is `nil`. See |lsp-response|.
|
||||
{method} (string)
|
||||
The |lsp-method| name.
|
||||
{result} (Result | Params | nil)
|
||||
When the language server is able to succesfully
|
||||
complete a request, this contains the `result` key
|
||||
of the response. See |lsp-response|.
|
||||
{client_id} (number)
|
||||
The ID of the |vim.lsp.client|.
|
||||
{bufnr} (Buffer)
|
||||
Buffer handle, or 0 for current.
|
||||
{ctx} (table)
|
||||
Context describes additional calling state
|
||||
associated with the handler. It consists of the
|
||||
following key, value pairs:
|
||||
|
||||
{method} (string)
|
||||
The |lsp-method| name.
|
||||
{client_id} (number)
|
||||
The ID of the |vim.lsp.client|.
|
||||
{bufnr} (Buffer)
|
||||
Buffer handle, or 0 for current.
|
||||
{config} (table)
|
||||
Configuration for the handler.
|
||||
|
||||
@ -238,21 +243,24 @@ For |lsp-request|, each |lsp-handler| has this signature: >
|
||||
|
||||
For |lsp-notification|, each |lsp-handler| has this signature: >
|
||||
|
||||
function(err, method, params, client_id, bufnr, config)
|
||||
function(err, result, ctx, config)
|
||||
<
|
||||
Parameters: ~
|
||||
{err} (nil)
|
||||
This is always `nil`.
|
||||
See |lsp-notification|
|
||||
{method} (string)
|
||||
The |lsp-method| name.
|
||||
{params} (Params)
|
||||
{result} (Result)
|
||||
This contains the `params` key of the notification.
|
||||
See |lsp-notification|
|
||||
{client_id} (number)
|
||||
The ID of the |vim.lsp.client|
|
||||
{bufnr} (nil)
|
||||
`nil`, as the server doesn't have an associated buffer.
|
||||
{ctx} (table)
|
||||
Context describes additional calling state
|
||||
associated with the handler. It consists of the
|
||||
following key, value pairs:
|
||||
|
||||
{method} (string)
|
||||
The |lsp-method| name.
|
||||
{client_id} (number)
|
||||
The ID of the |vim.lsp.client|.
|
||||
{config} (table)
|
||||
Configuration for the handler.
|
||||
|
||||
@ -1355,7 +1363,7 @@ goto_prev({opts}) *vim.lsp.diagnostic.goto_prev()*
|
||||
{opts} table See |vim.lsp.diagnostic.goto_next()|
|
||||
|
||||
*vim.lsp.diagnostic.on_publish_diagnostics()*
|
||||
on_publish_diagnostics({_}, {_}, {params}, {client_id}, {_}, {config})
|
||||
on_publish_diagnostics({_}, {result}, {ctx}, {config})
|
||||
|lsp-handler| for the method "textDocument/publishDiagnostics"
|
||||
|
||||
Note:
|
||||
@ -1581,7 +1589,7 @@ get({bufnr}) *vim.lsp.codelens.get()*
|
||||
table ( `CodeLens[]` )
|
||||
|
||||
*vim.lsp.codelens.on_codelens()*
|
||||
on_codelens({err}, {_}, {result}, {client_id}, {bufnr})
|
||||
on_codelens({err}, {result}, {ctx}, {_})
|
||||
|lsp-handler| for the method `textDocument/codeLens`
|
||||
|
||||
refresh() *vim.lsp.codelens.refresh()*
|
||||
@ -1614,17 +1622,25 @@ progress_handler({_}, {_}, {params}, {client_id})
|
||||
See also: ~
|
||||
https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
|
||||
|
||||
*vim.lsp.handlers.signature_help()*
|
||||
signature_help({_}, {method}, {result}, {_}, {bufnr}, {config})
|
||||
Parameters: ~
|
||||
{config} table Configuration table.
|
||||
• border: (default=nil)
|
||||
• Add borders to the floating window
|
||||
• See |vim.api.nvim_open_win()|
|
||||
hover({_}, {result}, {ctx}, {config}) *vim.lsp.handlers.hover()*
|
||||
|lsp-handler| for the method "textDocument/hover" >
|
||||
|
||||
vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(
|
||||
vim.lsp.handlers.hover, {
|
||||
-- Use a sharp border with `FloatBorder` highlights
|
||||
border = "single"
|
||||
}
|
||||
)
|
||||
<
|
||||
See also: ~
|
||||
https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration@seehttps://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition@seehttps://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition@seehttps://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation|lsp-handler| for the method "textDocument/signatureHelp">
|
||||
|
||||
*vim.lsp.handlers.signature_help()*
|
||||
signature_help({_}, {result}, {ctx}, {config})
|
||||
|lsp-handler| for the method "textDocument/signatureHelp". The
|
||||
active parameter is highlighted with
|
||||
|hl-LspSignatureActiveParameter|. >
|
||||
|
||||
vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(
|
||||
vim.lsp.handlers.signature_help, {
|
||||
-- Use a sharp border with `FloatBorder` highlights
|
||||
|
@ -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()*
|
||||
Highlights the yanked text. The fields of the optional dict {opts}
|
||||
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`)
|
||||
- {on_macro} highlight when executing macro (default `false`)
|
||||
- {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()*
|
||||
@ -1167,37 +1167,6 @@ region({bufnr}, {pos1}, {pos2}, {regtype}, {inclusive}) *vim.region()*
|
||||
Return: ~
|
||||
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()*
|
||||
Defers callback `cb` until the Nvim API is safe to call.
|
||||
|
||||
@ -1505,14 +1474,12 @@ validate({opt}) *vim.validate()*
|
||||
|
||||
vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}}
|
||||
=> NOP (success)
|
||||
<
|
||||
>
|
||||
vim.validate{arg1={1, 'table'}}
|
||||
=> error('arg1: expected table, got number')
|
||||
<
|
||||
>
|
||||
vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}}
|
||||
=> error('arg1: expected even number, got 3')
|
||||
|
||||
vim.validate{arg1={1, 'table'}}
|
||||
=> error('arg1: expected table, got number')
|
||||
|
||||
vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}}
|
||||
=> error('arg1: expected even number, got 3')
|
||||
<
|
||||
|
||||
Parameters: ~
|
||||
|
@ -329,12 +329,12 @@ argument.
|
||||
-w{number} Set the 'window' option to {number}.
|
||||
|
||||
*-w*
|
||||
-w {scriptout} All the characters that you type are recorded in the file
|
||||
"scriptout", until you exit Vim. This is useful if you want
|
||||
to create a script file to be used with "vim -s" or
|
||||
":source!". When the "scriptout" file already exists, new
|
||||
characters are appended. See also |complex-repeat|.
|
||||
{scriptout} cannot start with a digit.
|
||||
-w {scriptout} All keys that you type are recorded in the file "scriptout",
|
||||
until you exit Vim. Useful to create a script file to be used
|
||||
with "vim -s" or ":source!". Appends to the "scriptout" file
|
||||
if it already exists. {scriptout} cannot start with a digit.
|
||||
See also |vim.on_key()|.
|
||||
See also |complex-repeat|.
|
||||
|
||||
*-W*
|
||||
-W {scriptout} Like -w, but do not append, overwrite an existing file.
|
||||
|
@ -474,11 +474,9 @@ Query:iter_matches({self}, {node}, {source}, {start}, {stop})
|
||||
for id, node in pairs(match) do
|
||||
local name = query.captures[id]
|
||||
-- `node` was captured by the `name` capture in the match
|
||||
<
|
||||
>
|
||||
local node_data = metadata[id] -- Node level metadata
|
||||
<
|
||||
>
|
||||
|
||||
local node_data = metadata[id] -- Node level metadata
|
||||
|
||||
... use the info here ...
|
||||
end
|
||||
end
|
||||
|
@ -6,16 +6,16 @@
|
||||
|
||||
Differences between Nvim and Vim *vim-differences*
|
||||
|
||||
Nvim differs from Vim in many ways, although editor and VimL features are
|
||||
mostly identical. This document is a complete and centralized reference of
|
||||
the differences.
|
||||
Nvim differs from Vim in many ways, although editor and Vimscript (not
|
||||
Vim9script) features are mostly identical. This document is a complete and
|
||||
centralized reference of the differences.
|
||||
|
||||
Type |gO| to see the table of contents.
|
||||
|
||||
==============================================================================
|
||||
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_DATA_HOME/nvim/shada/main.shada` instead of `.viminfo` for persistent
|
||||
session information. |shada|
|
||||
@ -78,17 +78,19 @@ the differences.
|
||||
MAJOR COMPONENTS ~
|
||||
|
||||
API |API|
|
||||
Lua scripting |lua|
|
||||
Job control |job-control|
|
||||
Remote plugins |remote-plugin|
|
||||
LSP framework |lsp|
|
||||
Lua scripting |lua|
|
||||
Parsing engine |treesitter|
|
||||
Providers
|
||||
Clipboard |provider-clipboard|
|
||||
Node.js plugins |provider-nodejs|
|
||||
Python plugins |provider-python|
|
||||
Ruby plugins |provider-ruby|
|
||||
Remote plugins |remote-plugin|
|
||||
Shared data |shada|
|
||||
Embedded terminal |terminal|
|
||||
VimL parser |nvim_parse_expression()|
|
||||
Terminal emulator |terminal|
|
||||
Vimscript parser |nvim_parse_expression()|
|
||||
XDG base directories |xdg|
|
||||
|
||||
USER EXPERIENCE ~
|
||||
@ -137,7 +139,7 @@ FEATURES ~
|
||||
|
||||
Command-line highlighting:
|
||||
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*
|
||||
|input()|, |inputdialog()| support custom highlighting. |input()-highlight|
|
||||
*g:Nvim_color_cmdline*
|
||||
@ -380,7 +382,7 @@ TUI:
|
||||
UI/Display:
|
||||
|Visual| selection highlights the character at cursor. |visual-use|
|
||||
|
||||
VimL (Vim script) compatibility:
|
||||
Vimscript compatibility:
|
||||
`count` does not alias to |v:count|
|
||||
`errmsg` does not alias to |v:errmsg|
|
||||
`shell_error` does not alias to |v:shell_error|
|
||||
|
@ -6,14 +6,6 @@ if exists('b:did_ftplugin') || &filetype !=# 'man'
|
||||
endif
|
||||
let b:did_ftplugin = 1
|
||||
|
||||
let s:pager = !exists('b:man_sect')
|
||||
|
||||
if s:pager
|
||||
call man#init_pager()
|
||||
endif
|
||||
|
||||
setlocal noswapfile buftype=nofile bufhidden=hide
|
||||
setlocal nomodified readonly nomodifiable
|
||||
setlocal noexpandtab tabstop=8 softtabstop=8 shiftwidth=8
|
||||
setlocal wrap breakindent linebreak
|
||||
|
||||
@ -32,11 +24,7 @@ if !exists('g:no_plugin_maps') && !exists('g:no_man_maps')
|
||||
nnoremap <silent> <buffer> k gk
|
||||
nnoremap <silent> <buffer> gO :call man#show_toc()<CR>
|
||||
nnoremap <silent> <buffer> <2-LeftMouse> :Man<CR>
|
||||
if s:pager
|
||||
nnoremap <silent> <buffer> <nowait> q :lclose<CR>:q<CR>
|
||||
else
|
||||
nnoremap <silent> <buffer> <nowait> q :lclose<CR><C-W>c
|
||||
endif
|
||||
nnoremap <silent> <buffer> <nowait> q :lclose<CR><C-W>c
|
||||
endif
|
||||
|
||||
if get(g:, 'ft_man_folding_enable', 0)
|
||||
|
@ -494,7 +494,6 @@ local convert_value_to_lua = (function()
|
||||
for _, key_value_str in ipairs(comma_split) do
|
||||
local key, value = unpack(vim.split(key_value_str, ":"))
|
||||
key = vim.trim(key)
|
||||
value = vim.trim(value)
|
||||
|
||||
result[key] = value
|
||||
end
|
||||
|
@ -85,7 +85,11 @@ function highlight.on_yank(opts)
|
||||
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,
|
||||
function()
|
||||
if api.nvim_buf_is_valid(bufnr) then
|
||||
api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1)
|
||||
end
|
||||
end,
|
||||
timeout
|
||||
)
|
||||
end
|
||||
|
@ -218,7 +218,7 @@ local function validate_client_config(config)
|
||||
config = { config, 't' };
|
||||
}
|
||||
validate {
|
||||
root_dir = { config.root_dir, is_dir, "directory" };
|
||||
root_dir = { config.root_dir, optional_validator(is_dir), "directory" };
|
||||
handlers = { config.handlers, "t", true };
|
||||
capabilities = { config.capabilities, "t", true };
|
||||
cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" };
|
||||
@ -380,7 +380,7 @@ do
|
||||
end
|
||||
state.pending_change = function()
|
||||
state.pending_change = nil
|
||||
if client.is_stopped() then
|
||||
if client.is_stopped() or not vim.api.nvim_buf_is_valid(bufnr) then
|
||||
return
|
||||
end
|
||||
local contentChanges
|
||||
@ -455,12 +455,15 @@ local function text_document_did_open_handler(bufnr, client)
|
||||
vim.schedule(function()
|
||||
vim.lsp.handlers["textDocument/publishDiagnostics"](
|
||||
nil,
|
||||
"textDocument/publishDiagnostics",
|
||||
{
|
||||
diagnostics = vim.lsp.diagnostic.get(bufnr, client.id),
|
||||
uri = vim.uri_from_bufnr(bufnr),
|
||||
},
|
||||
client.id
|
||||
{
|
||||
method="textDocument/publishDiagnostics",
|
||||
client_id=client.id,
|
||||
bufnr=bufnr,
|
||||
}
|
||||
)
|
||||
end)
|
||||
end
|
||||
@ -555,7 +558,7 @@ end
|
||||
---
|
||||
--- The following parameters describe fields in the {config} table.
|
||||
---
|
||||
--@param root_dir: (required, string) Directory where the LSP server will base
|
||||
--@param root_dir: (string) Directory where the LSP server will base
|
||||
--- its rootUri on initialization.
|
||||
---
|
||||
--@param cmd: (required, string or list treated like |jobstart()|) Base command
|
||||
@ -590,6 +593,10 @@ end
|
||||
--- as `initializationOptions`. See `initialize` in the LSP spec.
|
||||
---
|
||||
--@param name (string, default=client-id) Name in log messages.
|
||||
--
|
||||
--@param workspace_folders (table) List of workspace folders passed to the
|
||||
--- language server. Defaults to root_dir if not set. See `workspaceFolders` in
|
||||
--- the LSP spec
|
||||
---
|
||||
--@param get_language_id function(bufnr, filetype) -> language ID as string.
|
||||
--- Defaults to the filetype.
|
||||
@ -677,11 +684,11 @@ function lsp.start_client(config)
|
||||
--@param method (string) LSP method name
|
||||
--@param params (table) The parameters for that method.
|
||||
function dispatch.notification(method, params)
|
||||
local _ = log.debug() and log.debug('notification', method, params)
|
||||
local _ = log.trace() and log.trace('notification', method, params)
|
||||
local handler = resolve_handler(method)
|
||||
if handler then
|
||||
-- Method name is provided here for convenience.
|
||||
handler(nil, method, params, client_id)
|
||||
handler(nil, params, {method=method, client_id=client_id})
|
||||
end
|
||||
end
|
||||
|
||||
@ -691,13 +698,13 @@ function lsp.start_client(config)
|
||||
--@param method (string) LSP method name
|
||||
--@param params (table) The parameters for that method
|
||||
function dispatch.server_request(method, params)
|
||||
local _ = log.debug() and log.debug('server_request', method, params)
|
||||
local _ = log.trace() and log.trace('server_request', method, params)
|
||||
local handler = resolve_handler(method)
|
||||
if handler then
|
||||
local _ = log.debug() and log.debug("server_request: found handler for", method)
|
||||
return handler(nil, method, params, client_id)
|
||||
local _ = log.trace() and log.trace("server_request: found handler for", method)
|
||||
return handler(nil, params, {method=method, client_id=client_id})
|
||||
end
|
||||
local _ = log.debug() and log.debug("server_request: no handler found for", method)
|
||||
local _ = log.warn() and log.warn("server_request: no handler found for", method)
|
||||
return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
|
||||
end
|
||||
|
||||
@ -775,6 +782,14 @@ function lsp.start_client(config)
|
||||
off = 'off'; messages = 'messages'; verbose = 'verbose';
|
||||
}
|
||||
local version = vim.version()
|
||||
|
||||
if config.root_dir and not config.workspace_folders then
|
||||
config.workspace_folders = {{
|
||||
uri = vim.uri_from_fname(config.root_dir);
|
||||
name = string.format("%s", config.root_dir);
|
||||
}};
|
||||
end
|
||||
|
||||
local initialize_params = {
|
||||
-- The process Id of the parent process that started the server. Is null if
|
||||
-- the process has not been started by another process. If the parent
|
||||
@ -793,7 +808,7 @@ function lsp.start_client(config)
|
||||
rootPath = config.root_dir;
|
||||
-- The rootUri of the workspace. Is null if no folder is open. If both
|
||||
-- `rootPath` and `rootUri` are set `rootUri` wins.
|
||||
rootUri = vim.uri_from_fname(config.root_dir);
|
||||
rootUri = config.root_dir and vim.uri_from_fname(config.root_dir);
|
||||
-- User provided initialization options.
|
||||
initializationOptions = config.init_options;
|
||||
-- The capabilities provided by the client (editor or tool)
|
||||
@ -815,16 +830,13 @@ function lsp.start_client(config)
|
||||
-- -- workspace folder in the user interface.
|
||||
-- name
|
||||
-- }
|
||||
workspaceFolders = {{
|
||||
uri = vim.uri_from_fname(config.root_dir);
|
||||
name = string.format("%s", config.root_dir);
|
||||
}};
|
||||
workspaceFolders = config.workspace_folders,
|
||||
}
|
||||
if config.before_init then
|
||||
-- TODO(ashkan) handle errors here.
|
||||
pcall(config.before_init, initialize_params, config)
|
||||
end
|
||||
local _ = log.debug() and log.debug(log_prefix, "initialize_params", initialize_params)
|
||||
local _ = log.trace() and log.trace(log_prefix, "initialize_params", initialize_params)
|
||||
rpc.request('initialize', initialize_params, function(init_err, result)
|
||||
assert(not init_err, tostring(init_err))
|
||||
assert(result, "server sent empty result")
|
||||
@ -894,7 +906,7 @@ function lsp.start_client(config)
|
||||
|
||||
local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr)
|
||||
return rpc.request(method, params, function(err, result)
|
||||
handler(err, method, result, client_id, bufnr)
|
||||
handler(err, result, {method=method, client_id=client_id, bufnr=bufnr})
|
||||
end)
|
||||
end
|
||||
|
||||
@ -915,7 +927,7 @@ function lsp.start_client(config)
|
||||
--@see |vim.lsp.buf_request_sync()|
|
||||
function client.request_sync(method, params, timeout_ms, bufnr)
|
||||
local request_result = nil
|
||||
local function _sync_handler(err, _, result)
|
||||
local function _sync_handler(err, result)
|
||||
request_result = { err = err, result = result }
|
||||
end
|
||||
|
||||
@ -1150,7 +1162,7 @@ end
|
||||
---@param bufnr (number) Buffer handle, or 0 for current
|
||||
---@param client_id (number) the client id
|
||||
function lsp.buf_is_attached(bufnr, client_id)
|
||||
return (all_buffer_active_clients[bufnr] or {})[client_id] == true
|
||||
return (all_buffer_active_clients[resolve_bufnr(bufnr)] or {})[client_id] == true
|
||||
end
|
||||
|
||||
--- Gets a client by id, or nil if the id is invalid.
|
||||
@ -1274,7 +1286,7 @@ function lsp.buf_request(bufnr, method, params, handler)
|
||||
local unsupported_err = lsp._unsupported_method(method)
|
||||
handler = handler or lsp.handlers[method]
|
||||
if handler then
|
||||
handler(unsupported_err, method, bufnr)
|
||||
handler(unsupported_err, nil, {method=method, bufnr=bufnr})
|
||||
end
|
||||
return
|
||||
end
|
||||
@ -1314,8 +1326,8 @@ function lsp.buf_request_all(bufnr, method, params, callback)
|
||||
end
|
||||
end)
|
||||
|
||||
local function _sync_handler(err, _, result, client_id)
|
||||
request_results[client_id] = { error = err, result = result }
|
||||
local function _sync_handler(err, result, ctx)
|
||||
request_results[ctx.client_id] = { error = err, result = result }
|
||||
result_count = result_count + 1
|
||||
set_expected_result_count()
|
||||
|
||||
@ -1421,7 +1433,7 @@ function lsp.omnifunc(findstart, base)
|
||||
local params = util.make_position_params()
|
||||
|
||||
local items = {}
|
||||
lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, _, result)
|
||||
lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, result)
|
||||
if err or not result or vim.fn.mode() ~= "i" then return end
|
||||
local matches = util.text_document_completion_list_to_complete_items(result, prefix)
|
||||
-- TODO(ashkan): is this the best way to do this?
|
||||
@ -1496,8 +1508,8 @@ end
|
||||
--@param handler (function) See |lsp-handler|
|
||||
--@param override_config (table) Table containing the keys to override behavior of the {handler}
|
||||
function lsp.with(handler, override_config)
|
||||
return function(err, method, params, client_id, bufnr, config)
|
||||
return handler(err, method, params, client_id, bufnr, vim.tbl_deep_extend("force", config or {}, override_config))
|
||||
return function(err, result, ctx, config)
|
||||
return handler(err, result, ctx, vim.tbl_deep_extend("force", config or {}, override_config))
|
||||
end
|
||||
end
|
||||
|
||||
|
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
|
@ -126,7 +126,7 @@ local function select_client(method)
|
||||
|
||||
if #clients > 1 then
|
||||
local choices = {}
|
||||
for k,v in ipairs(clients) do
|
||||
for k,v in pairs(clients) do
|
||||
table.insert(choices, string.format("%d %s", k, v.name))
|
||||
end
|
||||
local user_choice = vim.fn.confirm(
|
||||
@ -204,9 +204,9 @@ function M.formatting_seq_sync(options, timeout_ms, order)
|
||||
local clients = vim.tbl_values(vim.lsp.buf_get_clients());
|
||||
|
||||
-- sort the clients according to `order`
|
||||
for _, client_name in ipairs(order or {}) do
|
||||
for _, client_name in pairs(order or {}) do
|
||||
-- if the client exists, move to the end of the list
|
||||
for i, client in ipairs(clients) do
|
||||
for i, client in pairs(clients) do
|
||||
if client.name == client_name then
|
||||
table.insert(clients, table.remove(clients, i))
|
||||
break
|
||||
@ -215,7 +215,7 @@ function M.formatting_seq_sync(options, timeout_ms, order)
|
||||
end
|
||||
|
||||
-- loop through the clients and make synchronous formatting requests
|
||||
for _, client in ipairs(clients) do
|
||||
for _, client in pairs(clients) do
|
||||
if client.resolved_capabilities.document_formatting then
|
||||
local params = util.make_formatting_params(options)
|
||||
local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, vim.api.nvim_get_current_buf())
|
||||
@ -286,7 +286,7 @@ local function pick_call_hierarchy_item(call_hierarchy_items)
|
||||
return call_hierarchy_items[1]
|
||||
end
|
||||
local items = {}
|
||||
for i, item in ipairs(call_hierarchy_items) do
|
||||
for i, item in pairs(call_hierarchy_items) do
|
||||
local entry = item.detail or item.name
|
||||
table.insert(items, string.format("%d. %s", i, entry))
|
||||
end
|
||||
@ -300,13 +300,21 @@ end
|
||||
--@private
|
||||
local function call_hierarchy(method)
|
||||
local params = util.make_position_params()
|
||||
request('textDocument/prepareCallHierarchy', params, function(err, _, result)
|
||||
request('textDocument/prepareCallHierarchy', params, function(err, result, ctx)
|
||||
if err then
|
||||
vim.notify(err.message, vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
local call_hierarchy_item = pick_call_hierarchy_item(result)
|
||||
vim.lsp.buf_request(0, method, { item = call_hierarchy_item })
|
||||
local client = vim.lsp.get_client_by_id(ctx.client_id)
|
||||
if client then
|
||||
client.request(method, { item = call_hierarchy_item }, nil, ctx.bufnr)
|
||||
else
|
||||
vim.notify(string.format(
|
||||
'Client with id=%d disappeared during call hierarchy request', ctx.client_id),
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@ -328,8 +336,8 @@ end
|
||||
---
|
||||
function M.list_workspace_folders()
|
||||
local workspace_folders = {}
|
||||
for _, client in ipairs(vim.lsp.buf_get_clients()) do
|
||||
for _, folder in ipairs(client.workspaceFolders) do
|
||||
for _, client in pairs(vim.lsp.buf_get_clients()) do
|
||||
for _, folder in pairs(client.workspaceFolders) do
|
||||
table.insert(workspace_folders, folder.name)
|
||||
end
|
||||
end
|
||||
@ -347,9 +355,9 @@ function M.add_workspace_folder(workspace_folder)
|
||||
return
|
||||
end
|
||||
local params = util.make_workspace_params({{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}, {{}})
|
||||
for _, client in ipairs(vim.lsp.buf_get_clients()) do
|
||||
for _, client in pairs(vim.lsp.buf_get_clients()) do
|
||||
local found = false
|
||||
for _, folder in ipairs(client.workspaceFolders) do
|
||||
for _, folder in pairs(client.workspaceFolders) do
|
||||
if folder.name == workspace_folder then
|
||||
found = true
|
||||
print(workspace_folder, "is already part of this workspace")
|
||||
@ -371,8 +379,8 @@ function M.remove_workspace_folder(workspace_folder)
|
||||
vim.api.nvim_command("redraw")
|
||||
if not (workspace_folder and #workspace_folder > 0) then return end
|
||||
local params = util.make_workspace_params({{}}, {{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}})
|
||||
for _, client in ipairs(vim.lsp.buf_get_clients()) do
|
||||
for idx, folder in ipairs(client.workspaceFolders) do
|
||||
for _, client in pairs(vim.lsp.buf_get_clients()) do
|
||||
for idx, folder in pairs(client.workspaceFolders) do
|
||||
if folder.name == workspace_folder then
|
||||
vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params)
|
||||
client.workspaceFolders[idx] = nil
|
||||
@ -422,6 +430,21 @@ function M.clear_references()
|
||||
util.buf_clear_references()
|
||||
end
|
||||
|
||||
--- Requests code actions from all clients and calls the handler exactly once
|
||||
--- with all aggregated results
|
||||
---@private
|
||||
local function code_action_request(params)
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local method = 'textDocument/codeAction'
|
||||
vim.lsp.buf_request_all(bufnr, method, params, function(results)
|
||||
local actions = {}
|
||||
for _, r in pairs(results) do
|
||||
vim.list_extend(actions, r.result or {})
|
||||
end
|
||||
vim.lsp.handlers[method](nil, actions, {bufnr=bufnr, method=method})
|
||||
end)
|
||||
end
|
||||
|
||||
--- Selects a code action from the input list that is available at the current
|
||||
--- cursor position.
|
||||
--
|
||||
|
@ -169,7 +169,7 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
|
||||
if lens.command then
|
||||
countdown()
|
||||
else
|
||||
client.request('codeLens/resolve', lens, function(_, _, result)
|
||||
client.request('codeLens/resolve', lens, function(_, result)
|
||||
if result and result.command then
|
||||
lens.command = result.command
|
||||
-- Eager display to have some sort of incremental feedback
|
||||
@ -192,17 +192,17 @@ end
|
||||
|
||||
--- |lsp-handler| for the method `textDocument/codeLens`
|
||||
---
|
||||
function M.on_codelens(err, _, result, client_id, bufnr)
|
||||
function M.on_codelens(err, result, ctx, _)
|
||||
assert(not err, vim.inspect(err))
|
||||
|
||||
M.save(result, bufnr, client_id)
|
||||
M.save(result, ctx.bufnr, ctx.client_id)
|
||||
|
||||
-- Eager display for any resolved (and unresolved) lenses and refresh them
|
||||
-- once resolved.
|
||||
M.display(result, bufnr, client_id)
|
||||
resolve_lenses(result, bufnr, client_id, function()
|
||||
M.display(result, bufnr, client_id)
|
||||
active_refreshes[bufnr] = nil
|
||||
M.display(result, ctx.bufnr, ctx.client_id)
|
||||
resolve_lenses(result, ctx.bufnr, ctx.client_id, function()
|
||||
M.display(result, ctx.bufnr, ctx.client_id)
|
||||
active_refreshes[ctx.bufnr] = nil
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -203,8 +203,10 @@ local bufnr_and_client_cacher_mt = {
|
||||
-- Diagnostic Saving & Caching {{{
|
||||
local _diagnostic_cleanup = setmetatable({}, bufnr_and_client_cacher_mt)
|
||||
local diagnostic_cache = setmetatable({}, bufnr_and_client_cacher_mt)
|
||||
local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_client_cacher_mt)
|
||||
local diagnostic_cache_lines = setmetatable({}, bufnr_and_client_cacher_mt)
|
||||
local diagnostic_cache_counts = setmetatable({}, bufnr_and_client_cacher_mt)
|
||||
local diagnostic_attached_buffers = {}
|
||||
|
||||
local _bufs_waiting_to_update = setmetatable({}, bufnr_and_client_cacher_mt)
|
||||
|
||||
@ -826,6 +828,7 @@ function M.clear(bufnr, client_id, diagnostic_ns, sign_ns)
|
||||
|
||||
diagnostic_ns = diagnostic_ns or M._get_diagnostic_namespace(client_id)
|
||||
sign_ns = sign_ns or M._get_sign_namespace(client_id)
|
||||
diagnostic_cache_extmarks[bufnr][client_id] = {}
|
||||
|
||||
assert(bufnr, "bufnr is required")
|
||||
assert(diagnostic_ns, "Need diagnostic_ns, got nil")
|
||||
@ -1005,15 +1008,16 @@ end
|
||||
--- - Update diagnostics in InsertMode or wait until InsertLeave
|
||||
--- - severity_sort: (default=false)
|
||||
--- - Sort diagnostics (and thus signs and virtual text)
|
||||
function M.on_publish_diagnostics(_, _, params, client_id, _, config)
|
||||
local uri = params.uri
|
||||
function M.on_publish_diagnostics(_, result, ctx, config)
|
||||
local client_id = ctx.client_id
|
||||
local uri = result.uri
|
||||
local bufnr = vim.uri_to_bufnr(uri)
|
||||
|
||||
if not bufnr then
|
||||
return
|
||||
end
|
||||
|
||||
local diagnostics = params.diagnostics
|
||||
local diagnostics = result.diagnostics
|
||||
|
||||
if config and if_nil(config.severity_sort, false) then
|
||||
table.sort(diagnostics, function(a, b) return a.severity > b.severity end)
|
||||
@ -1038,6 +1042,54 @@ function M.on_publish_diagnostics(_, _, params, client_id, _, config)
|
||||
M.display(diagnostics, bufnr, client_id, config)
|
||||
end
|
||||
|
||||
-- restores the extmarks set by M.display
|
||||
--- @param last number last line that was changed
|
||||
-- @private
|
||||
local function restore_extmarks(bufnr, last)
|
||||
for client_id, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do
|
||||
local ns = M._get_diagnostic_namespace(client_id)
|
||||
local extmarks_current = api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {details = true})
|
||||
local found = {}
|
||||
for _, extmark in ipairs(extmarks_current) do
|
||||
-- nvim_buf_set_lines will move any extmark to the line after the last
|
||||
-- nvim_buf_set_text will move any extmark to the last line
|
||||
if extmark[2] ~= last + 1 then
|
||||
found[extmark[1]] = true
|
||||
end
|
||||
end
|
||||
for _, extmark in ipairs(extmarks) do
|
||||
if not found[extmark[1]] then
|
||||
local opts = extmark[4]
|
||||
opts.id = extmark[1]
|
||||
-- HACK: end_row should be end_line
|
||||
if opts.end_row then
|
||||
opts.end_line = opts.end_row
|
||||
opts.end_row = nil
|
||||
end
|
||||
pcall(api.nvim_buf_set_extmark, bufnr, ns, extmark[2], extmark[3], opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- caches the extmarks set by M.display
|
||||
-- @private
|
||||
local function save_extmarks(bufnr, client_id)
|
||||
bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr
|
||||
if not diagnostic_attached_buffers[bufnr] then
|
||||
api.nvim_buf_attach(bufnr, false, {
|
||||
on_lines = function(_, _, _, _, _, last)
|
||||
restore_extmarks(bufnr, last - 1)
|
||||
end,
|
||||
on_detach = function()
|
||||
diagnostic_cache_extmarks[bufnr] = nil
|
||||
end})
|
||||
diagnostic_attached_buffers[bufnr] = true
|
||||
end
|
||||
local ns = M._get_diagnostic_namespace(client_id)
|
||||
diagnostic_cache_extmarks[bufnr][client_id] = api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {details = true})
|
||||
end
|
||||
|
||||
--@private
|
||||
--- Display diagnostics for the buffer, given a configuration.
|
||||
function M.display(diagnostics, bufnr, client_id, config)
|
||||
@ -1108,9 +1160,46 @@ function M.display(diagnostics, bufnr, client_id, config)
|
||||
if signs_opts then
|
||||
M.set_signs(diagnostics, bufnr, client_id, nil, signs_opts)
|
||||
end
|
||||
|
||||
-- cache extmarks
|
||||
save_extmarks(bufnr, client_id)
|
||||
end
|
||||
-- }}}
|
||||
-- Diagnostic User Functions {{{
|
||||
|
||||
--- Redraw diagnostics for the given buffer and client
|
||||
---
|
||||
--- This calls the "textDocument/publishDiagnostics" handler manually using
|
||||
--- the cached diagnostics already received from the server. This can be useful
|
||||
--- for redrawing diagnostics after making changes in diagnostics
|
||||
--- configuration. |lsp-handler-configuration|
|
||||
---
|
||||
---@param bufnr (optional, number): Buffer handle, defaults to current
|
||||
---@param client_id (optional, number): Redraw diagnostics for the given
|
||||
--- client. The default is to redraw diagnostics for all attached
|
||||
--- clients.
|
||||
function M.redraw(bufnr, client_id)
|
||||
bufnr = get_bufnr(bufnr)
|
||||
if not client_id then
|
||||
return vim.lsp.for_each_buffer_client(bufnr, function(client)
|
||||
M.redraw(bufnr, client.id)
|
||||
end)
|
||||
end
|
||||
|
||||
-- We need to invoke the publishDiagnostics handler directly instead of just
|
||||
-- calling M.display so that we can preserve any custom configuration options
|
||||
-- the user may have set with vim.lsp.with.
|
||||
vim.lsp.handlers["textDocument/publishDiagnostics"](
|
||||
nil,
|
||||
{
|
||||
uri = vim.uri_from_bufnr(bufnr),
|
||||
diagnostics = M.get(bufnr, client_id),
|
||||
},
|
||||
{
|
||||
method = "textDocument/publishDiagnostics",
|
||||
client_id = client_id,
|
||||
bufnr = bufnr,
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
--- Open a floating window with the diagnostics from {line_nr}
|
||||
---
|
||||
|
@ -17,23 +17,21 @@ local function err_message(...)
|
||||
api.nvim_command("redraw")
|
||||
end
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
|
||||
M['workspace/executeCommand'] = function(err, _)
|
||||
if err then
|
||||
error("Could not execute code action: "..err.message)
|
||||
end
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
|
||||
M['workspace/executeCommand'] = function(_, _, _, _)
|
||||
-- Error handling is done implicitly by wrapping all handlers; see end of this file
|
||||
end
|
||||
|
||||
-- @msg of type ProgressParams
|
||||
-- Basically a token of type number/string
|
||||
local function progress_handler(_, _, params, client_id)
|
||||
---@private
|
||||
local function progress_handler(_, result, ctx, _)
|
||||
local client_id = ctx.client_id
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
local client_name = client and client.name or string.format("id=%d", client_id)
|
||||
if not client then
|
||||
err_message("LSP[", client_name, "] client has shut down after sending the message")
|
||||
end
|
||||
local val = params.value -- unspecified yet
|
||||
local token = params.token -- string or number
|
||||
local val = result.value -- unspecified yet
|
||||
local token = result.token -- string or number
|
||||
|
||||
|
||||
if val.kind then
|
||||
@ -64,10 +62,11 @@ end
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
|
||||
M['$/progress'] = progress_handler
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
|
||||
M['window/workDoneProgress/create'] = function(_, _, params, client_id)
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
|
||||
M['window/workDoneProgress/create'] = function(_, result, ctx)
|
||||
local client_id = ctx.client_id
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
local token = params.token -- string or number
|
||||
local token = result.token -- string or number
|
||||
local client_name = client and client.name or string.format("id=%d", client_id)
|
||||
if not client then
|
||||
err_message("LSP[", client_name, "] client has shut down after sending the message")
|
||||
@ -76,12 +75,12 @@ M['window/workDoneProgress/create'] = function(_, _, params, client_id)
|
||||
return vim.NIL
|
||||
end
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
|
||||
M['window/showMessageRequest'] = function(_, _, params)
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
|
||||
M['window/showMessageRequest'] = function(_, result)
|
||||
|
||||
local actions = params.actions
|
||||
print(params.message)
|
||||
local option_strings = {params.message, "\nRequest Actions:"}
|
||||
local actions = result.actions
|
||||
print(result.message)
|
||||
local option_strings = {result.message, "\nRequest Actions:"}
|
||||
for i, action in ipairs(actions) do
|
||||
local title = action.title:gsub('\r\n', '\\r\\n')
|
||||
title = title:gsub('\n', '\\n')
|
||||
@ -97,8 +96,9 @@ M['window/showMessageRequest'] = function(_, _, params)
|
||||
end
|
||||
end
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
|
||||
M['client/registerCapability'] = function(_, _, _, client_id)
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
|
||||
M['client/registerCapability'] = function(_, _, ctx)
|
||||
local client_id = ctx.client_id
|
||||
local warning_tpl = "The language server %s triggers a registerCapability "..
|
||||
"handler despite dynamicRegistration set to false. "..
|
||||
"Report upstream, this warning is harmless"
|
||||
@ -109,25 +109,25 @@ M['client/registerCapability'] = function(_, _, _, client_id)
|
||||
return vim.NIL
|
||||
end
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
|
||||
M['textDocument/codeAction'] = function(_, _, actions)
|
||||
if actions == nil or vim.tbl_isempty(actions) then
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
|
||||
M['textDocument/codeAction'] = function(_, result)
|
||||
if result == nil or vim.tbl_isempty(result) then
|
||||
print("No code actions available")
|
||||
return
|
||||
end
|
||||
|
||||
local option_strings = {"Code Actions:"}
|
||||
for i, action in ipairs(actions) do
|
||||
local option_strings = {"Code actions:"}
|
||||
for i, action in ipairs(result) do
|
||||
local title = action.title:gsub('\r\n', '\\r\\n')
|
||||
title = title:gsub('\n', '\\n')
|
||||
table.insert(option_strings, string.format("%d. %s", i, title))
|
||||
end
|
||||
|
||||
local choice = vim.fn.inputlist(option_strings)
|
||||
if choice < 1 or choice > #actions then
|
||||
if choice < 1 or choice > #result then
|
||||
return
|
||||
end
|
||||
local action_chosen = actions[choice]
|
||||
local action_chosen = result[choice]
|
||||
-- textDocument/codeAction can return either Command[] or CodeAction[].
|
||||
-- If it is a CodeAction, it can have either an edit, a command or both.
|
||||
-- Edits should be executed first
|
||||
@ -143,8 +143,8 @@ M['textDocument/codeAction'] = function(_, _, actions)
|
||||
end
|
||||
end
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
|
||||
M['workspace/applyEdit'] = function(_, _, workspace_edit)
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
|
||||
M['workspace/applyEdit'] = function(_, workspace_edit)
|
||||
if not workspace_edit then return end
|
||||
-- TODO(ashkan) Do something more with label?
|
||||
if workspace_edit.label then
|
||||
@ -157,30 +157,30 @@ M['workspace/applyEdit'] = function(_, _, workspace_edit)
|
||||
}
|
||||
end
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_configuration
|
||||
M['workspace/configuration'] = function(err, _, params, client_id)
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_configuration
|
||||
M['workspace/configuration'] = function(_, result, ctx)
|
||||
local client_id = ctx.client_id
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
if not client then
|
||||
err_message("LSP[id=", client_id, "] client has shut down after sending the message")
|
||||
return
|
||||
end
|
||||
if err then error(vim.inspect(err)) end
|
||||
if not params.items then
|
||||
if not result.items then
|
||||
return {}
|
||||
end
|
||||
|
||||
local result = {}
|
||||
for _, item in ipairs(params.items) do
|
||||
local response = {}
|
||||
for _, item in ipairs(result.items) do
|
||||
if item.section then
|
||||
local value = util.lookup_section(client.config.settings, item.section) or vim.NIL
|
||||
-- For empty sections with no explicit '' key, return settings as is
|
||||
if value == vim.NIL and item.section == '' then
|
||||
value = client.config.settings or vim.NIL
|
||||
end
|
||||
table.insert(result, value)
|
||||
table.insert(response, value)
|
||||
end
|
||||
end
|
||||
return result
|
||||
return response
|
||||
end
|
||||
|
||||
M['textDocument/publishDiagnostics'] = function(...)
|
||||
@ -191,51 +191,65 @@ M['textDocument/codeLens'] = function(...)
|
||||
return require('vim.lsp.codelens').on_codelens(...)
|
||||
end
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
|
||||
M['textDocument/references'] = function(_, _, result)
|
||||
if not result then return end
|
||||
util.set_qflist(util.locations_to_items(result))
|
||||
api.nvim_command("copen")
|
||||
|
||||
|
||||
---@private
|
||||
--- Return a function that converts LSP responses to list items and opens the list
|
||||
---
|
||||
--- The returned function has an optional {config} parameter that accepts a table
|
||||
--- with the following keys:
|
||||
---
|
||||
--- loclist: (boolean) use the location list (default is to use the quickfix list)
|
||||
---
|
||||
---@param map_result function `((resp, bufnr) -> list)` to convert the response
|
||||
---@param entity name of the resource used in a `not found` error message
|
||||
local function response_to_list(map_result, entity)
|
||||
return function(_,result, ctx, config)
|
||||
if not result or vim.tbl_isempty(result) then
|
||||
vim.notify('No ' .. entity .. ' found')
|
||||
else
|
||||
config = config or {}
|
||||
if config.loclist then
|
||||
util.set_loclist(map_result(result, ctx.bufnr))
|
||||
api.nvim_command("lopen")
|
||||
else
|
||||
util.set_qflist(map_result(result, ctx.bufnr))
|
||||
api.nvim_command("copen")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--@private
|
||||
--- Prints given list of symbols to the quickfix list.
|
||||
--@param _ (not used)
|
||||
--@param _ (not used)
|
||||
--@param result (list of Symbols) LSP method name
|
||||
--@param result (table) result of LSP method; a location or a list of locations.
|
||||
---(`textDocument/definition` can return `Location` or `Location[]`
|
||||
local symbol_handler = function(_, _, result, _, bufnr)
|
||||
if not result or vim.tbl_isempty(result) then return end
|
||||
|
||||
util.set_qflist(util.symbols_to_items(result, bufnr))
|
||||
api.nvim_command("copen")
|
||||
end
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
|
||||
M['textDocument/documentSymbol'] = symbol_handler
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
|
||||
M['workspace/symbol'] = symbol_handler
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
|
||||
M['textDocument/references'] = response_to_list(util.locations_to_items, 'references')
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
|
||||
M['textDocument/rename'] = function(_, _, result)
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
|
||||
M['textDocument/documentSymbol'] = response_to_list(util.symbols_to_items, 'document symbols')
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
|
||||
M['workspace/symbol'] = response_to_list(util.symbols_to_items, 'symbols')
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
|
||||
M['textDocument/rename'] = function(_, result, _)
|
||||
if not result then return end
|
||||
util.apply_workspace_edit(result)
|
||||
end
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
|
||||
M['textDocument/rangeFormatting'] = function(_, _, result)
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
|
||||
M['textDocument/rangeFormatting'] = function(_, result, ctx, _)
|
||||
if not result then return end
|
||||
util.apply_text_edits(result)
|
||||
util.apply_text_edits(result, ctx.bufnr)
|
||||
end
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
|
||||
M['textDocument/formatting'] = function(_, _, result)
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
|
||||
M['textDocument/formatting'] = function(_, result, ctx, _)
|
||||
if not result then return end
|
||||
util.apply_text_edits(result)
|
||||
util.apply_text_edits(result, ctx.bufnr)
|
||||
end
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
|
||||
M['textDocument/completion'] = function(_, _, result)
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
|
||||
M['textDocument/completion'] = function(_, result, _, _)
|
||||
if vim.tbl_isempty(result or {}) then return end
|
||||
local row, col = unpack(api.nvim_win_get_cursor(0))
|
||||
local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1])
|
||||
@ -260,9 +274,9 @@ end
|
||||
--- - border: (default=nil)
|
||||
--- - Add borders to the floating window
|
||||
--- - See |vim.api.nvim_open_win()|
|
||||
function M.hover(_, method, result, _, _, config)
|
||||
function M.hover(_, result, ctx, config)
|
||||
config = config or {}
|
||||
config.focus_id = method
|
||||
config.focus_id = ctx.method
|
||||
if not (result and result.contents) then
|
||||
-- return { 'No information available' }
|
||||
return
|
||||
@ -281,13 +295,13 @@ M['textDocument/hover'] = M.hover
|
||||
|
||||
--@private
|
||||
--- Jumps to a location. Used as a handler for multiple LSP methods.
|
||||
--@param _ (not used)
|
||||
--@param method (string) LSP method name
|
||||
--@param result (table) result of LSP method; a location or a list of locations.
|
||||
---@param _ (not used)
|
||||
---@param result (table) result of LSP method; a location or a list of locations.
|
||||
---@param ctx (table) table containing the context of the request, including the method
|
||||
---(`textDocument/definition` can return `Location` or `Location[]`
|
||||
local function location_handler(_, method, result)
|
||||
local function location_handler(_, result, ctx, _)
|
||||
if result == nil or vim.tbl_isempty(result) then
|
||||
local _ = log.info() and log.info(method, 'No location found')
|
||||
local _ = log.info() and log.info(ctx.method, 'No location found')
|
||||
return nil
|
||||
end
|
||||
|
||||
@ -328,17 +342,19 @@ M['textDocument/implementation'] = location_handler
|
||||
--- - border: (default=nil)
|
||||
--- - Add borders to the floating window
|
||||
--- - See |vim.api.nvim_open_win()|
|
||||
function M.signature_help(_, method, result, _, bufnr, config)
|
||||
function M.signature_help(_, result, ctx, config)
|
||||
config = config or {}
|
||||
config.focus_id = method
|
||||
config.focus_id = ctx.method
|
||||
-- When use `autocmd CompleteDone <silent><buffer> lua vim.lsp.buf.signature_help()` to call signatureHelp handler
|
||||
-- If the completion item doesn't have signatures It will make noise. Change to use `print` that can use `<silent>` to ignore
|
||||
if not (result and result.signatures and result.signatures[1]) then
|
||||
print('No signature help available')
|
||||
return
|
||||
end
|
||||
local ft = api.nvim_buf_get_option(bufnr, 'filetype')
|
||||
local lines = util.convert_signature_help_to_markdown_lines(result, ft)
|
||||
local client = vim.lsp.get_client_by_id(ctx.client_id)
|
||||
local triggers = client.resolved_capabilities.signature_help_trigger_characters
|
||||
local ft = api.nvim_buf_get_option(ctx.bufnr, 'filetype')
|
||||
local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers)
|
||||
lines = util.trim_empty_lines(lines)
|
||||
if vim.tbl_isempty(lines) then
|
||||
print('No signature help available')
|
||||
@ -350,10 +366,10 @@ end
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
|
||||
M['textDocument/signatureHelp'] = M.signature_help
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
|
||||
M['textDocument/documentHighlight'] = function(_, _, result, _, bufnr, _)
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
|
||||
M['textDocument/documentHighlight'] = function(_, result, ctx, _)
|
||||
if not result then return end
|
||||
util.buf_highlight_references(bufnr, result)
|
||||
util.buf_highlight_references(ctx.bufnr, result)
|
||||
end
|
||||
|
||||
--@private
|
||||
@ -364,7 +380,7 @@ end
|
||||
--@returns `CallHierarchyIncomingCall[]` if {direction} is `"from"`,
|
||||
--@returns `CallHierarchyOutgoingCall[]` if {direction} is `"to"`,
|
||||
local make_call_hierarchy_handler = function(direction)
|
||||
return function(_, _, result)
|
||||
return function(_, result)
|
||||
if not result then return end
|
||||
local items = {}
|
||||
for _, call_hierarchy_call in pairs(result) do
|
||||
@ -389,10 +405,11 @@ M['callHierarchy/incomingCalls'] = make_call_hierarchy_handler('from')
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy/outgoingCalls
|
||||
M['callHierarchy/outgoingCalls'] = make_call_hierarchy_handler('to')
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window/logMessage
|
||||
M['window/logMessage'] = function(_, _, result, client_id)
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage
|
||||
M['window/logMessage'] = function(_, result, ctx, _)
|
||||
local message_type = result.type
|
||||
local message = result.message
|
||||
local client_id = ctx.client_id
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
local client_name = client and client.name or string.format("id=%d", client_id)
|
||||
if not client then
|
||||
@ -402,7 +419,7 @@ M['window/logMessage'] = function(_, _, result, client_id)
|
||||
log.error(message)
|
||||
elseif message_type == protocol.MessageType.Warning then
|
||||
log.warn(message)
|
||||
elseif message_type == protocol.MessageType.Info then
|
||||
elseif message_type == protocol.MessageType.Info or message_type == protocol.MessageType.Log then
|
||||
log.info(message)
|
||||
else
|
||||
log.debug(message)
|
||||
@ -410,10 +427,11 @@ M['window/logMessage'] = function(_, _, result, client_id)
|
||||
return result
|
||||
end
|
||||
|
||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window/showMessage
|
||||
M['window/showMessage'] = function(_, _, result, client_id)
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessage
|
||||
M['window/showMessage'] = function(_, result, ctx, _)
|
||||
local message_type = result.type
|
||||
local message = result.message
|
||||
local client_id = ctx.client_id
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
local client_name = client and client.name or string.format("id=%d", client_id)
|
||||
if not client then
|
||||
@ -430,16 +448,23 @@ 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, bufnr, config)
|
||||
local _ = log.debug() and log.debug('default_handler', method, {
|
||||
params = params, client_id = client_id, err = err, bufnr = bufnr, config = config
|
||||
M[k] = function(err, result, ctx, config)
|
||||
local _ = log.trace() and log.trace('default_handler', ctx.method, {
|
||||
err = err, result = result, ctx=vim.inspect(ctx), config = config
|
||||
})
|
||||
|
||||
if err then
|
||||
return err_message(tostring(err))
|
||||
local client = vim.lsp.get_client_by_id(ctx.client_id)
|
||||
local client_name = client and client.name or string.format("client_id=%d", ctx.client_id)
|
||||
-- LSP spec:
|
||||
-- interface ResponseError:
|
||||
-- code: integer;
|
||||
-- message: string;
|
||||
-- data?: string | number | boolean | array | object | null;
|
||||
return err_message(tostring(err.code) .. ': ' .. err.message)
|
||||
end
|
||||
|
||||
return fn(err, method, params, client_id, bufnr, config)
|
||||
return fn(err, result, ctx, config)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -14,10 +14,11 @@ log.levels = vim.deepcopy(vim.log.levels)
|
||||
|
||||
-- Default log level is warn.
|
||||
local current_log_level = log.levels.WARN
|
||||
local log_date_format = "%FT%H:%M:%S%z"
|
||||
local log_date_format = "%F %H:%M:%S"
|
||||
local format_func = function(arg) return vim.inspect(arg, {newline=''}) end
|
||||
|
||||
do
|
||||
local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/"
|
||||
local path_sep = vim.loop.os_uname().version:match("Windows") and "\\" or "/"
|
||||
--@private
|
||||
local function path_join(...)
|
||||
return table.concat(vim.tbl_flatten{...}, path_sep)
|
||||
@ -33,7 +34,7 @@ do
|
||||
vim.fn.mkdir(vim.fn.stdpath('cache'), "p")
|
||||
local logfile = assert(io.open(logfilename, "a+"))
|
||||
-- Start message for logging
|
||||
logfile:write(string.format("[ START ] %s ] LSP logging initiated\n", os.date(log_date_format)))
|
||||
logfile:write(string.format("[START][%s] LSP logging initiated\n", os.date(log_date_format)))
|
||||
for level, levelnr in pairs(log.levels) do
|
||||
-- Also export the log level on the root object.
|
||||
log[level] = levelnr
|
||||
@ -56,14 +57,14 @@ do
|
||||
if levelnr < current_log_level then return false end
|
||||
if argc == 0 then return true end
|
||||
local info = debug.getinfo(2, "Sl")
|
||||
local fileinfo = string.format("%s:%s", info.short_src, info.currentline)
|
||||
local parts = { table.concat({"[", level, "]", os.date(log_date_format), "]", fileinfo, "]"}, " ") }
|
||||
local header = string.format("[%s][%s] ...%s:%s", level, os.date(log_date_format), string.sub(info.short_src, #info.short_src - 15), info.currentline)
|
||||
local parts = { header }
|
||||
for i = 1, argc do
|
||||
local arg = select(i, ...)
|
||||
if arg == nil then
|
||||
table.insert(parts, "nil")
|
||||
else
|
||||
table.insert(parts, vim.inspect(arg, {newline=''}))
|
||||
table.insert(parts, format_func(arg))
|
||||
end
|
||||
end
|
||||
logfile:write(table.concat(parts, '\t'), "\n")
|
||||
@ -88,6 +89,18 @@ function log.set_level(level)
|
||||
end
|
||||
end
|
||||
|
||||
--- Gets the current log level.
|
||||
function log.get_level()
|
||||
return current_log_level
|
||||
end
|
||||
|
||||
--- Sets formatting function used to format logs
|
||||
---@param handle function function to apply to logging arguments, pass vim.inspect for multi-line formatting
|
||||
function log.set_format_func(handle)
|
||||
assert(handle == vim.inspect or type(handle) == 'function', "handle must be a function")
|
||||
format_func = handle
|
||||
end
|
||||
|
||||
--- Checks whether the level is sufficient for logging.
|
||||
--@param level number log level
|
||||
--@returns (bool) true if would log, false if not
|
||||
|
@ -392,7 +392,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
--@param payload (table) Converted into a JSON string, see |json_encode()|
|
||||
--@returns true if the payload could be scheduled, false if the main event-loop is in the process of closing.
|
||||
local function encode_and_send(payload)
|
||||
local _ = log.debug() and log.debug("rpc.send.payload", payload)
|
||||
local _ = log.debug() and log.debug("rpc.send", payload)
|
||||
if handle == nil or handle:is_closing() then return false end
|
||||
-- TODO(ashkan) remove this once we have a Lua json_encode
|
||||
schedule(function()
|
||||
@ -493,7 +493,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
-- on_error(client_errors.INVALID_SERVER_JSON, err)
|
||||
return
|
||||
end
|
||||
local _ = log.debug() and log.debug("decoded", decoded)
|
||||
local _ = log.debug() and log.debug("rpc.receive", decoded)
|
||||
|
||||
if type(decoded.method) == 'string' and decoded.id then
|
||||
-- Server Request
|
||||
|
@ -1,4 +1,5 @@
|
||||
local protocol = require 'vim.lsp.protocol'
|
||||
local snippet = require 'vim.lsp._snippet'
|
||||
local vim = vim
|
||||
local validate = vim.validate
|
||||
local api = vim.api
|
||||
@ -147,10 +148,6 @@ local function sort_by_key(fn)
|
||||
return false
|
||||
end
|
||||
end
|
||||
--@private
|
||||
local edit_sort_key = sort_by_key(function(e)
|
||||
return {e.A[1], e.A[2], e.i}
|
||||
end)
|
||||
|
||||
--@private
|
||||
--- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position
|
||||
@ -175,6 +172,7 @@ local function get_line_byte_from_position(bufnr, position)
|
||||
if ok then
|
||||
return result
|
||||
end
|
||||
return math.min(#lines[1], col)
|
||||
end
|
||||
end
|
||||
return col
|
||||
@ -238,53 +236,119 @@ function M.get_progress_messages()
|
||||
end
|
||||
|
||||
--- Applies a list of text edits to a buffer.
|
||||
--@param text_edits (table) list of `TextEdit` objects
|
||||
--@param buf_nr (number) Buffer id
|
||||
---@param text_edits table list of `TextEdit` objects
|
||||
---@param bufnr number Buffer id
|
||||
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit
|
||||
function M.apply_text_edits(text_edits, bufnr)
|
||||
if not next(text_edits) then return end
|
||||
if not api.nvim_buf_is_loaded(bufnr) then
|
||||
vim.fn.bufload(bufnr)
|
||||
end
|
||||
api.nvim_buf_set_option(bufnr, 'buflisted', true)
|
||||
local start_line, finish_line = math.huge, -1
|
||||
local cleaned = {}
|
||||
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_position(bufnr, e.range.start)
|
||||
local end_row = e.range["end"].line
|
||||
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.
|
||||
table.insert(cleaned, {
|
||||
i = i;
|
||||
A = {start_row; start_col};
|
||||
B = {end_row; end_col};
|
||||
lines = vim.split(e.newText, '\n', true);
|
||||
|
||||
-- Fix reversed range and indexing each text_edits
|
||||
local index = 0
|
||||
text_edits = vim.tbl_map(function(text_edit)
|
||||
index = index + 1
|
||||
text_edit._index = index
|
||||
|
||||
if text_edit.range.start.line > text_edit.range['end'].line or text_edit.range.start.line == text_edit.range['end'].line and text_edit.range.start.character > text_edit.range['end'].character then
|
||||
local start = text_edit.range.start
|
||||
text_edit.range.start = text_edit.range['end']
|
||||
text_edit.range['end'] = start
|
||||
end
|
||||
return text_edit
|
||||
end, text_edits)
|
||||
|
||||
-- Sort text_edits
|
||||
table.sort(text_edits, function(a, b)
|
||||
if a.range.start.line ~= b.range.start.line then
|
||||
return a.range.start.line > b.range.start.line
|
||||
end
|
||||
if a.range.start.character ~= b.range.start.character then
|
||||
return a.range.start.character > b.range.start.character
|
||||
end
|
||||
if a._index ~= b._index then
|
||||
return a._index > b._index
|
||||
end
|
||||
end)
|
||||
|
||||
-- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't accept it so we should fix it here.
|
||||
local has_eol_text_edit = false
|
||||
local max = vim.api.nvim_buf_line_count(bufnr)
|
||||
local len = vim.str_utfindex(vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '')
|
||||
text_edits = vim.tbl_map(function(text_edit)
|
||||
if max <= text_edit.range.start.line then
|
||||
text_edit.range.start.line = max - 1
|
||||
text_edit.range.start.character = len
|
||||
text_edit.newText = '\n' .. text_edit.newText
|
||||
has_eol_text_edit = true
|
||||
end
|
||||
if max <= text_edit.range['end'].line then
|
||||
text_edit.range['end'].line = max - 1
|
||||
text_edit.range['end'].character = len
|
||||
has_eol_text_edit = true
|
||||
end
|
||||
return text_edit
|
||||
end, text_edits)
|
||||
|
||||
-- Some LSP servers are depending on the VSCode behavior.
|
||||
-- The VSCode will re-locate the cursor position after applying TextEdit so we also do it.
|
||||
local is_current_buf = vim.api.nvim_get_current_buf() == bufnr
|
||||
local cursor = (function()
|
||||
if not is_current_buf then
|
||||
return {
|
||||
row = -1,
|
||||
col = -1,
|
||||
}
|
||||
end
|
||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||
return {
|
||||
row = cursor[1] - 1,
|
||||
col = cursor[2],
|
||||
}
|
||||
end)()
|
||||
|
||||
-- Apply text edits.
|
||||
local is_cursor_fixed = false
|
||||
for _, text_edit in ipairs(text_edits) do
|
||||
local e = {
|
||||
start_row = text_edit.range.start.line,
|
||||
start_col = get_line_byte_from_position(bufnr, text_edit.range.start),
|
||||
end_row = text_edit.range['end'].line,
|
||||
end_col = get_line_byte_from_position(bufnr, text_edit.range['end']),
|
||||
text = vim.split(text_edit.newText, '\n', true),
|
||||
}
|
||||
vim.api.nvim_buf_set_text(bufnr, e.start_row, e.start_col, e.end_row, e.end_col, e.text)
|
||||
|
||||
local row_count = (e.end_row - e.start_row) + 1
|
||||
if e.end_row < cursor.row then
|
||||
cursor.row = cursor.row + (#e.text - row_count)
|
||||
is_cursor_fixed = true
|
||||
elseif e.end_row == cursor.row and e.end_col <= cursor.col then
|
||||
cursor.row = cursor.row + (#e.text - row_count)
|
||||
cursor.col = #e.text[#e.text] + (cursor.col - e.end_col)
|
||||
if #e.text == 1 then
|
||||
cursor.col = cursor.col + e.start_col
|
||||
end
|
||||
is_cursor_fixed = true
|
||||
end
|
||||
end
|
||||
|
||||
if is_cursor_fixed then
|
||||
vim.api.nvim_win_set_cursor(0, {
|
||||
cursor.row + 1,
|
||||
math.min(cursor.col, #(vim.api.nvim_buf_get_lines(bufnr, cursor.row, cursor.row + 1, false)[1] or ''))
|
||||
})
|
||||
end
|
||||
|
||||
-- Reverse sort the orders so we can apply them without interfering with
|
||||
-- eachother. Also add i as a sort key to mimic a stable sort.
|
||||
table.sort(cleaned, edit_sort_key)
|
||||
local lines = api.nvim_buf_get_lines(bufnr, start_line, finish_line + 1, false)
|
||||
local fix_eol = api.nvim_buf_get_option(bufnr, 'fixeol')
|
||||
local set_eol = fix_eol and api.nvim_buf_line_count(bufnr) <= finish_line + 1
|
||||
if set_eol and (#lines == 0 or #lines[#lines] ~= 0) then
|
||||
table.insert(lines, '')
|
||||
-- Remove final line if needed
|
||||
local fix_eol = has_eol_text_edit
|
||||
fix_eol = fix_eol and api.nvim_buf_get_option(bufnr, 'fixeol')
|
||||
fix_eol = fix_eol and (vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '') == ''
|
||||
if fix_eol then
|
||||
vim.api.nvim_buf_set_lines(bufnr, -2, -1, false, {})
|
||||
end
|
||||
|
||||
for i = #cleaned, 1, -1 do
|
||||
local e = cleaned[i]
|
||||
local A = {e.A[1] - start_line, e.A[2]}
|
||||
local B = {e.B[1] - start_line, e.B[2]}
|
||||
lines = M.set_lines(lines, A, B, e.lines)
|
||||
end
|
||||
if set_eol and #lines[#lines] == 0 then
|
||||
table.remove(lines)
|
||||
end
|
||||
api.nvim_buf_set_lines(bufnr, start_line, finish_line + 1, false, lines)
|
||||
end
|
||||
|
||||
-- local valid_windows_path_characters = "[^<>:\"/\\|?*]"
|
||||
@ -579,9 +643,13 @@ end
|
||||
--@param input (string) unparsed snippet
|
||||
--@returns (string) parsed snippet
|
||||
function M.parse_snippet(input)
|
||||
local res, _ = parse_snippet_rec(input, false)
|
||||
|
||||
return res
|
||||
local ok, parsed = pcall(function()
|
||||
return tostring(snippet.parse(input))
|
||||
end)
|
||||
if not ok then
|
||||
return input
|
||||
end
|
||||
return parsed
|
||||
end
|
||||
|
||||
--@private
|
||||
@ -810,16 +878,16 @@ function M.convert_input_to_markdown_lines(input, contents)
|
||||
-- If it's plaintext, then wrap it in a <text></text> block
|
||||
|
||||
-- Some servers send input.value as empty, so let's ignore this :(
|
||||
input.value = input.value or ''
|
||||
local value = input.value or ''
|
||||
|
||||
if input.kind == "plaintext" then
|
||||
-- wrap this in a <text></text> block so that stylize_markdown
|
||||
-- can properly process it as plaintext
|
||||
input.value = string.format("<text>\n%s\n</text>", input.value or "")
|
||||
value = string.format("<text>\n%s\n</text>", value)
|
||||
end
|
||||
|
||||
-- assert(type(input.value) == 'string')
|
||||
list_extend(contents, split_lines(input.value))
|
||||
-- assert(type(value) == 'string')
|
||||
list_extend(contents, split_lines(value))
|
||||
-- MarkupString variation 2
|
||||
elseif input.language then
|
||||
-- Some servers send input.value as empty, so let's ignore this :(
|
||||
|
@ -26,6 +26,8 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release date="2021-09-26" version="0.5.1"/>
|
||||
<release date="2021-07-02" version="0.5.0"/>
|
||||
<release date="2020-08-04" version="0.4.4"/>
|
||||
<release date="2019-11-06" version="0.4.3"/>
|
||||
<release date="2019-09-15" version="0.4.2"/>
|
||||
|
@ -6,7 +6,7 @@ endif
|
||||
let g:loaded_man = 1
|
||||
|
||||
command! -bang -bar -range=-1 -complete=customlist,man#complete -nargs=* Man
|
||||
\ if <bang>0 | set ft=man |
|
||||
\ if <bang>0 | call man#init_pager() |
|
||||
\ else | call man#open_page(<count>, <q-mods>, <f-args>) | endif
|
||||
|
||||
augroup man
|
||||
|
@ -27,11 +27,7 @@ if &filetype != 'man'
|
||||
finish
|
||||
endif
|
||||
|
||||
if !exists('b:man_sect')
|
||||
call man#init_pager()
|
||||
endif
|
||||
|
||||
if b:man_sect =~# '^[023]'
|
||||
if get(b:, 'man_sect', '') =~# '^[023]'
|
||||
syntax case match
|
||||
syntax include @c $VIMRUNTIME/syntax/c.vim
|
||||
syntax match manCFuncDefinition display '\<\h\w*\>\ze\(\s\|\n\)*(' contained
|
||||
|
@ -12,6 +12,7 @@
|
||||
# - CMakeLists.txt: Unset NVIM_VERSION_PRERELEASE
|
||||
# - CMakeLists.txt: Unset NVIM_API_PRERELEASE
|
||||
# - Create test/functional/fixtures/api_level_N.mpack
|
||||
# - Add date and version to runtime/nvim.appdata.xml
|
||||
# - Tag the commit.
|
||||
# Create the "version bump" commit:
|
||||
# - CMakeLists.txt: Set NVIM_VERSION_PRERELEASE to "-dev"
|
||||
@ -62,6 +63,10 @@ _do_release_commit() {
|
||||
git add test/functional/fixtures/api_level_$__API_LEVEL.mpack
|
||||
fi
|
||||
|
||||
$__sed -i.bk 's,(<releases>),\1\
|
||||
<release date="'"${__DATE}"'" version="'"${__VERSION}"'"/>,' runtime/nvim.appdata.xml
|
||||
git add runtime/nvim.appdata.xml
|
||||
|
||||
if ! test "$ARG1" = '--use-current-commit' ; then
|
||||
echo "Building changelog since ${__LAST_TAG}..."
|
||||
|
||||
@ -75,14 +80,12 @@ _do_release_commit() {
|
||||
_do_bump_commit() {
|
||||
$__sed -i.bk 's/(NVIM_VERSION_PRERELEASE) ""/\1 "-dev"/' CMakeLists.txt
|
||||
$__sed -i.bk 's/set\((NVIM_VERSION_PATCH) [[:digit:]]/set(\1 ?/' CMakeLists.txt
|
||||
$__sed -i.bk 's,(<releases>),\1\
|
||||
<release date="'"${__DATE}"'" version="xxx"/>,' runtime/nvim.appdata.xml
|
||||
rm CMakeLists.txt.bk
|
||||
rm runtime/nvim.appdata.xml.bk
|
||||
rm -f CMakeLists.txt.bk
|
||||
rm -f runtime/nvim.appdata.xml.bk
|
||||
nvim +'/NVIM_VERSION' +1new +'exe "norm! iUpdate version numbers!!!"' \
|
||||
-O CMakeLists.txt runtime/nvim.appdata.xml
|
||||
-O CMakeLists.txt
|
||||
|
||||
git add CMakeLists.txt runtime/nvim.appdata.xml
|
||||
git add CMakeLists.txt
|
||||
git commit -m "$__BUMP_MSG"
|
||||
}
|
||||
|
||||
@ -92,11 +95,7 @@ fi
|
||||
_do_bump_commit
|
||||
echo "
|
||||
Next steps:
|
||||
- Update runtime/nvim.appdata.xml on _master_
|
||||
- Run tests/CI (version_spec.lua)!
|
||||
- Push the tag:
|
||||
git push --follow-tags
|
||||
- Update the 'stable' tag:
|
||||
git push --force upstream HEAD^:refs/tags/stable
|
||||
git fetch --tags
|
||||
- Update website: index.html"
|
||||
|
@ -1724,7 +1724,7 @@ BYPASS_AU:
|
||||
void block_autocmds(void)
|
||||
{
|
||||
// Remember the value of v:termresponse.
|
||||
if (is_autocmd_blocked()) {
|
||||
if (!is_autocmd_blocked()) {
|
||||
old_termresponse = get_vim_var_str(VV_TERMRESPONSE);
|
||||
}
|
||||
autocmd_blocked++;
|
||||
@ -1737,7 +1737,7 @@ void unblock_autocmds(void)
|
||||
// When v:termresponse was set while autocommands were blocked, trigger
|
||||
// the autocommands now. Esp. useful when executing a shell command
|
||||
// during startup (nvim -d).
|
||||
if (is_autocmd_blocked()
|
||||
if (!is_autocmd_blocked()
|
||||
&& get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) {
|
||||
apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, false, curbuf);
|
||||
}
|
||||
|
@ -289,6 +289,9 @@ static void close_cb(Stream *stream, void *data)
|
||||
/// `on_stdout` is ignored
|
||||
/// @param[in] detach True if the job should not be killed when nvim exits,
|
||||
/// ignored if `pty` is true
|
||||
/// @param[in] stdin_mode Stdin mode. Either kChannelStdinPipe to open a
|
||||
/// channel for stdin or kChannelStdinNull to leave
|
||||
/// stdin disconnected.
|
||||
/// @param[in] cwd Initial working directory for the job. Nvim's working
|
||||
/// directory if `cwd` is NULL
|
||||
/// @param[in] pty_width Width of the pty, ignored if `pty` is false
|
||||
@ -302,7 +305,7 @@ static void close_cb(Stream *stream, void *data)
|
||||
Channel *channel_job_start(char **argv, CallbackReader on_stdout,
|
||||
CallbackReader on_stderr, Callback on_exit,
|
||||
bool pty, bool rpc, bool overlapped, bool detach,
|
||||
const char *cwd,
|
||||
ChannelStdinMode stdin_mode, const char *cwd,
|
||||
uint16_t pty_width, uint16_t pty_height,
|
||||
dict_T *env, varnumber_T *status_out)
|
||||
{
|
||||
@ -345,7 +348,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
|
||||
proc->overlapped = overlapped;
|
||||
|
||||
char *cmd = xstrdup(proc->argv[0]);
|
||||
bool has_out, has_err;
|
||||
bool has_in, has_out, has_err;
|
||||
if (proc->type == kProcessTypePty) {
|
||||
has_out = true;
|
||||
has_err = false;
|
||||
@ -353,7 +356,17 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
|
||||
has_out = rpc || callback_reader_set(chan->on_data);
|
||||
has_err = callback_reader_set(chan->on_stderr);
|
||||
}
|
||||
int status = process_spawn(proc, true, has_out, has_err);
|
||||
|
||||
switch (stdin_mode) {
|
||||
case kChannelStdinPipe:
|
||||
has_in = true;
|
||||
break;
|
||||
case kChannelStdinNull:
|
||||
has_in = false;
|
||||
break;
|
||||
}
|
||||
|
||||
int status = process_spawn(proc, has_in, has_out, has_err);
|
||||
if (status) {
|
||||
EMSG3(_(e_jobspawn), os_strerror(status), cmd);
|
||||
xfree(cmd);
|
||||
@ -369,7 +382,9 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
|
||||
tv_dict_free(proc->env);
|
||||
}
|
||||
|
||||
wstream_init(&proc->in, 0);
|
||||
if (has_in) {
|
||||
wstream_init(&proc->in, 0);
|
||||
}
|
||||
if (has_out) {
|
||||
rstream_init(&proc->out, 0);
|
||||
}
|
||||
|
@ -28,6 +28,10 @@ typedef enum {
|
||||
kChannelPartAll
|
||||
} ChannelPart;
|
||||
|
||||
typedef enum {
|
||||
kChannelStdinPipe,
|
||||
kChannelStdinNull,
|
||||
} ChannelStdinMode;
|
||||
|
||||
typedef struct {
|
||||
Stream in;
|
||||
|
@ -1446,15 +1446,29 @@ void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left,
|
||||
|
||||
/// skipwhite: skip over ' ' and '\t'.
|
||||
///
|
||||
/// @param[in] q String to skip in.
|
||||
/// @param[in] p String to skip in.
|
||||
///
|
||||
/// @return Pointer to character after the skipped whitespace.
|
||||
char_u *skipwhite(const char_u *q)
|
||||
char_u *skipwhite(const char_u *const p)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
||||
FUNC_ATTR_NONNULL_RET
|
||||
{
|
||||
const char_u *p = q;
|
||||
while (ascii_iswhite(*p)) {
|
||||
return skipwhite_len(p, STRLEN(p));
|
||||
}
|
||||
|
||||
/// Like `skipwhite`, but skip up to `len` characters.
|
||||
/// @see skipwhite
|
||||
///
|
||||
/// @param[in] p String to skip in.
|
||||
/// @param[in] len Max length to skip.
|
||||
///
|
||||
/// @return Pointer to character after the skipped whitespace, or the `len`-th
|
||||
/// character in the string.
|
||||
char_u *skipwhite_len(const char_u *p, size_t len)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
||||
FUNC_ATTR_NONNULL_RET
|
||||
{
|
||||
for (; len > 0 && ascii_iswhite(*p); len--) {
|
||||
p++;
|
||||
}
|
||||
return (char_u *)p;
|
||||
@ -1600,6 +1614,18 @@ char_u* skiptowhite_esc(char_u *p) {
|
||||
return p;
|
||||
}
|
||||
|
||||
/// Skip over text until '\n' or NUL.
|
||||
///
|
||||
/// @param[in] p Text to skip over.
|
||||
///
|
||||
/// @return Pointer to the next '\n' or NUL character.
|
||||
char_u *skip_to_newline(const char_u *const p)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
||||
FUNC_ATTR_NONNULL_RET
|
||||
{
|
||||
return (char_u *)xstrchrnul((const char *)p, NL);
|
||||
}
|
||||
|
||||
/// Gets a number from a string and skips over it, signalling overflow.
|
||||
///
|
||||
/// @param[out] pp A pointer to a pointer to char_u.
|
||||
|
@ -5181,6 +5181,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
bool pty = false;
|
||||
bool clear_env = false;
|
||||
bool overlapped = false;
|
||||
ChannelStdinMode stdin_mode = kChannelStdinPipe;
|
||||
CallbackReader on_stdout = CALLBACK_READER_INIT,
|
||||
on_stderr = CALLBACK_READER_INIT;
|
||||
Callback on_exit = CALLBACK_NONE;
|
||||
@ -5195,6 +5196,17 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
|
||||
overlapped = tv_dict_get_number(job_opts, "overlapped") != 0;
|
||||
|
||||
char *s = tv_dict_get_string(job_opts, "stdin", false);
|
||||
if (s) {
|
||||
if (!strncmp(s, "null", NUMBUFLEN)) {
|
||||
stdin_mode = kChannelStdinNull;
|
||||
} else if (!strncmp(s, "pipe", NUMBUFLEN)) {
|
||||
// Nothing to do, default value
|
||||
} else {
|
||||
EMSG3(_(e_invargNval), "stdin", s);
|
||||
}
|
||||
}
|
||||
|
||||
if (pty && rpc) {
|
||||
EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set");
|
||||
shell_free_argv(argv);
|
||||
@ -5251,8 +5263,8 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
env = create_environment(job_env, clear_env, pty, term_name);
|
||||
|
||||
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
|
||||
rpc, overlapped, detach, cwd, width, height,
|
||||
env, &rettv->vval.v_number);
|
||||
rpc, overlapped, detach, stdin_mode, cwd,
|
||||
width, height, env, &rettv->vval.v_number);
|
||||
if (chan) {
|
||||
channel_create_event(chan, NULL);
|
||||
}
|
||||
@ -7732,8 +7744,9 @@ 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, false, NULL, 0, 0,
|
||||
NULL, &rettv->vval.v_number);
|
||||
false, true, false, false,
|
||||
kChannelStdinPipe, NULL, 0, 0, NULL,
|
||||
&rettv->vval.v_number);
|
||||
if (chan) {
|
||||
channel_create_event(chan, NULL);
|
||||
}
|
||||
@ -10849,10 +10862,11 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||
const bool rpc = false;
|
||||
const bool overlapped = false;
|
||||
const bool detach = false;
|
||||
ChannelStdinMode stdin_mode = kChannelStdinPipe;
|
||||
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,
|
||||
pty, rpc, overlapped, detach, cwd,
|
||||
term_width, curwin->w_height_inner,
|
||||
pty, rpc, overlapped, detach, stdin_mode,
|
||||
cwd, term_width, curwin->w_height_inner,
|
||||
env, &rettv->vval.v_number);
|
||||
if (rettv->vval.v_number <= 0) {
|
||||
return;
|
||||
|
@ -2635,44 +2635,42 @@ static void cmd_source(char_u *fname, exarg_T *eap)
|
||||
}
|
||||
}
|
||||
|
||||
/// Concatenate VimL line if it starts with a line continuation into a growarray
|
||||
/// (excluding the continuation chars and leading whitespace)
|
||||
///
|
||||
/// @note Growsize of the growarray may be changed to speed up concatenations!
|
||||
///
|
||||
/// @param ga the growarray to append to
|
||||
/// @param init_growsize the starting growsize value of the growarray
|
||||
/// @param p pointer to the beginning of the line to consider
|
||||
/// @param len the length of this line
|
||||
///
|
||||
/// @return true if this line did begin with a continuation (the next line
|
||||
/// should also be considered, if it exists); false otherwise
|
||||
static bool concat_continued_line(garray_T *const ga, const int init_growsize,
|
||||
const char_u *const p, size_t len)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
const char_u *const line = skipwhite_len(p, len);
|
||||
len -= (size_t)(line - p);
|
||||
// Skip lines starting with '\" ', concat lines starting with '\'
|
||||
if (len >= 3 && STRNCMP(line, "\"\\ ", 3) == 0) {
|
||||
return true;
|
||||
} else if (len == 0 || line[0] != '\\') {
|
||||
return false;
|
||||
}
|
||||
if (ga->ga_len > init_growsize) {
|
||||
ga_set_growsize(ga, MAX(ga->ga_len, 8000));
|
||||
}
|
||||
ga_concat_len(ga, (const char *)line + 1, len - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
linenr_T curr_lnum;
|
||||
const linenr_T final_lnum;
|
||||
} GetBufferLineCookie;
|
||||
|
||||
/// Get one line from the current selection in the buffer.
|
||||
/// Called by do_cmdline() when it's called from cmd_source_buffer().
|
||||
///
|
||||
/// @return pointer to allocated line, or NULL for end-of-file or
|
||||
/// some error.
|
||||
static char_u *get_buffer_line(int c, void *cookie, int indent, bool do_concat)
|
||||
{
|
||||
GetBufferLineCookie *p = cookie;
|
||||
if (p->curr_lnum > p->final_lnum) {
|
||||
return NULL;
|
||||
}
|
||||
char_u *curr_line = ml_get(p->curr_lnum);
|
||||
p->curr_lnum++;
|
||||
return (char_u *)xstrdup((const char *)curr_line);
|
||||
}
|
||||
|
||||
static void cmd_source_buffer(const exarg_T *eap)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
GetBufferLineCookie cookie = {
|
||||
.curr_lnum = eap->line1,
|
||||
.final_lnum = eap->line2,
|
||||
};
|
||||
if (curbuf != NULL && curbuf->b_fname
|
||||
&& path_with_extension((const char *)curbuf->b_fname, "lua")) {
|
||||
nlua_source_using_linegetter(get_buffer_line, (void *)&cookie,
|
||||
":source (no file)");
|
||||
} else {
|
||||
source_using_linegetter((void *)&cookie, get_buffer_line,
|
||||
":source (no file)");
|
||||
}
|
||||
}
|
||||
|
||||
/// ":source" and associated commands.
|
||||
///
|
||||
/// @return address holding the next breakpoint line for a source cookie
|
||||
@ -2725,17 +2723,27 @@ typedef struct {
|
||||
static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat)
|
||||
{
|
||||
GetStrLineCookie *p = cookie;
|
||||
size_t i = p->offset;
|
||||
if (strlen((char *)p->buf) <= p->offset) {
|
||||
if (STRLEN(p->buf) <= p->offset) {
|
||||
return NULL;
|
||||
}
|
||||
while (!(p->buf[i] == '\n' || p->buf[i] == '\0')) {
|
||||
i++;
|
||||
const char_u *line = p->buf + p->offset;
|
||||
const char_u *eol = skip_to_newline(line);
|
||||
garray_T ga;
|
||||
ga_init(&ga, sizeof(char_u), 400);
|
||||
ga_concat_len(&ga, (const char *)line, (size_t)(eol - line));
|
||||
if (do_concat && vim_strchr(p_cpo, CPO_CONCAT) == NULL) {
|
||||
while (eol[0] != NUL) {
|
||||
line = eol + 1;
|
||||
const char_u *const next_eol = skip_to_newline(line);
|
||||
if (!concat_continued_line(&ga, 400, line, (size_t)(next_eol - line))) {
|
||||
break;
|
||||
}
|
||||
eol = next_eol;
|
||||
}
|
||||
}
|
||||
size_t line_length = i - p->offset;
|
||||
char_u *buf = xmemdupz(p->buf + p->offset, line_length);
|
||||
p->offset = i + 1;
|
||||
return buf;
|
||||
ga_append(&ga, NUL);
|
||||
p->offset = (size_t)(eol - p->buf) + 1;
|
||||
return ga.ga_data;
|
||||
}
|
||||
|
||||
static int source_using_linegetter(void *cookie,
|
||||
@ -2770,6 +2778,40 @@ static int source_using_linegetter(void *cookie,
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void cmd_source_buffer(const exarg_T *const eap)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
if (curbuf == NULL) {
|
||||
return;
|
||||
}
|
||||
garray_T ga;
|
||||
ga_init(&ga, sizeof(char_u), 400);
|
||||
const linenr_T final_lnum = eap->line2;
|
||||
// Copy the contents to be executed.
|
||||
for (linenr_T curr_lnum = eap->line1; curr_lnum <= final_lnum; curr_lnum++) {
|
||||
// Adjust growsize to current length to speed up concatenating many lines.
|
||||
if (ga.ga_len > 400) {
|
||||
ga_set_growsize(&ga, MAX(ga.ga_len, 8000));
|
||||
}
|
||||
ga_concat(&ga, ml_get(curr_lnum));
|
||||
ga_append(&ga, NL);
|
||||
}
|
||||
((char_u *)ga.ga_data)[ga.ga_len - 1] = NUL;
|
||||
const GetStrLineCookie cookie = {
|
||||
.buf = ga.ga_data,
|
||||
.offset = 0,
|
||||
};
|
||||
if (curbuf->b_fname
|
||||
&& path_with_extension((const char *)curbuf->b_fname, "lua")) {
|
||||
nlua_source_using_linegetter(get_str_line, (void *)&cookie,
|
||||
":source (no file)");
|
||||
} else {
|
||||
source_using_linegetter((void *)&cookie, get_str_line,
|
||||
":source (no file)");
|
||||
}
|
||||
ga_clear(&ga);
|
||||
}
|
||||
|
||||
/// Executes lines in `src` as Ex commands.
|
||||
///
|
||||
/// @see do_source()
|
||||
@ -3227,26 +3269,11 @@ char_u *getsourceline(int c, void *cookie, int indent, bool do_concat)
|
||||
|
||||
ga_init(&ga, (int)sizeof(char_u), 400);
|
||||
ga_concat(&ga, line);
|
||||
if (*p == '\\') {
|
||||
ga_concat(&ga, p + 1);
|
||||
}
|
||||
for (;; ) {
|
||||
while (sp->nextline != NULL
|
||||
&& concat_continued_line(&ga, 400, sp->nextline,
|
||||
STRLEN(sp->nextline))) {
|
||||
xfree(sp->nextline);
|
||||
sp->nextline = get_one_sourceline(sp);
|
||||
if (sp->nextline == NULL) {
|
||||
break;
|
||||
}
|
||||
p = skipwhite(sp->nextline);
|
||||
if (*p == '\\') {
|
||||
// Adjust the growsize to the current length to speed up
|
||||
// concatenating many lines.
|
||||
if (ga.ga_len > 400) {
|
||||
ga_set_growsize(&ga, (ga.ga_len > 8000) ? 8000 : ga.ga_len);
|
||||
}
|
||||
ga_concat(&ga, p + 1);
|
||||
} else if (p[0] != '"' || p[1] != '\\' || p[2] != ' ') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ga_append(&ga, NUL);
|
||||
xfree(line);
|
||||
|
@ -303,6 +303,7 @@ int do_cmdline_cmd(const char *cmd)
|
||||
/// DOCMD_KEYTYPED - Don't reset KeyTyped.
|
||||
/// DOCMD_EXCRESET - Reset the exception environment (used for debugging).
|
||||
/// DOCMD_KEEPLINE - Store first typed line (for repeating with ".").
|
||||
/// DOCMD_PREVIEW - During 'inccommand' preview.
|
||||
///
|
||||
/// @return FAIL if cmdline could not be executed, OK otherwise
|
||||
int do_cmdline(char_u *cmdline, LineGetter fgetline,
|
||||
@ -601,7 +602,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
|
||||
recursive--;
|
||||
|
||||
// Ignore trailing '|'-separated commands in preview-mode ('inccommand').
|
||||
if (State & CMDPREVIEW) {
|
||||
if ((State & CMDPREVIEW) && (flags & DOCMD_PREVIEW)) {
|
||||
next_cmdline = NULL;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#define DOCMD_KEYTYPED 0x08 // don't reset KeyTyped
|
||||
#define DOCMD_EXCRESET 0x10 // reset exception environment (for debugging
|
||||
#define DOCMD_KEEPLINE 0x20 // keep typed line for repeating with "."
|
||||
#define DOCMD_PREVIEW 0x40 // during 'inccommand' preview
|
||||
|
||||
/* defines for eval_vars() */
|
||||
#define VALID_PATH 1
|
||||
|
@ -2251,7 +2251,7 @@ static int command_line_changed(CommandLineState *s)
|
||||
State |= CMDPREVIEW;
|
||||
emsg_silent++; // Block error reporting as the command may be incomplete
|
||||
msg_silent++; // Block messages, namely ones that prompt
|
||||
do_cmdline(ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_NOWAIT);
|
||||
do_cmdline(ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_NOWAIT|DOCMD_PREVIEW);
|
||||
msg_silent--; // Unblock messages
|
||||
emsg_silent--; // Unblock error reporting
|
||||
|
||||
|
@ -268,7 +268,7 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
|
||||
}
|
||||
ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns;
|
||||
map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id);
|
||||
map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id);
|
||||
map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, start_id);
|
||||
marktree_del_itr(buf->b_marktree, itr, false);
|
||||
} else {
|
||||
marktree_itr_next(buf->b_marktree, itr);
|
||||
|
@ -1555,8 +1555,8 @@ int vgetc(void)
|
||||
*/
|
||||
may_garbage_collect = false;
|
||||
|
||||
// Exec lua callbacks for on_keystroke
|
||||
nlua_execute_log_keystroke(c);
|
||||
// Execute Lua on_key callbacks.
|
||||
nlua_execute_on_key(c);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
@ -1490,7 +1490,7 @@ int nlua_expand_pat(expand_T *xp,
|
||||
lua_getfield(lstate, -1, "_expand_pat");
|
||||
luaL_checktype(lstate, -1, LUA_TFUNCTION);
|
||||
|
||||
// [ vim, vim._log_keystroke, buf ]
|
||||
// [ vim, vim._on_key, buf ]
|
||||
lua_pushlstring(lstate, (const char *)pat, STRLEN(pat));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void nlua_execute_log_keystroke(int c)
|
||||
void nlua_execute_on_key(int c)
|
||||
{
|
||||
char_u buf[NUMBUFLEN];
|
||||
size_t buf_len = special_to_buf(c, mod_mask, false, buf);
|
||||
@ -1778,17 +1778,17 @@ void nlua_execute_log_keystroke(int c)
|
||||
// [ vim ]
|
||||
lua_getglobal(lstate, "vim");
|
||||
|
||||
// [ vim, vim._log_keystroke ]
|
||||
lua_getfield(lstate, -1, "_log_keystroke");
|
||||
// [ vim, vim._on_key]
|
||||
lua_getfield(lstate, -1, "_on_key");
|
||||
luaL_checktype(lstate, -1, LUA_TFUNCTION);
|
||||
|
||||
// [ vim, vim._log_keystroke, buf ]
|
||||
// [ vim, vim._on_key, buf ]
|
||||
lua_pushlstring(lstate, (const char *)buf, buf_len);
|
||||
|
||||
if (lua_pcall(lstate, 1, 0, 0)) {
|
||||
nlua_error(
|
||||
lstate,
|
||||
_("Error executing vim.log_keystroke lua callback: %.*s"));
|
||||
_("Error executing vim.on_key Lua callback: %.*s"));
|
||||
}
|
||||
|
||||
// [ vim ]
|
||||
|
@ -4,6 +4,7 @@
|
||||
-- 1. runtime/lua/vim/ (the runtime): For "nice to have" features, e.g. the
|
||||
-- `inspect` and `lpeg` modules.
|
||||
-- 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.
|
||||
--
|
||||
-- Guideline: "If in doubt, put it in the runtime".
|
||||
@ -425,26 +426,35 @@ function vim.notify(msg, log_level, _opts)
|
||||
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 string will contain the literal keys typed.
|
||||
--- See |i_CTRL-V|
|
||||
--- The Nvim command-line option |-w| is related but does not support callbacks
|
||||
--- and cannot be toggled dynamically.
|
||||
---
|
||||
---@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}
|
||||
--@param ns_id number? Namespace ID. If not passed or 0, will generate and return a new
|
||||
--- namespace ID from |nvim_create_namesapce()|
|
||||
---@param ns_id number? Namespace ID. If nil or 0, generates and returns a new
|
||||
--- |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.
|
||||
--- This is to prevent the annoying situation of every keystroke erroring
|
||||
--- while trying to remove a broken callback.
|
||||
--@note {fn} will not be cleared from |nvim_buf_clear_namespace()|
|
||||
--@note {fn} will receive the keystrokes after mappings have been evaluated
|
||||
function vim.register_keystroke_callback(fn, ns_id)
|
||||
---@note {fn} will be removed if an error occurs while calling.
|
||||
---@note {fn} will not be cleared by |nvim_buf_clear_namespace()|
|
||||
---@note {fn} will receive the keys after mappings have been evaluated
|
||||
function vim.on_key(fn, ns_id)
|
||||
if fn == nil and ns_id == nil then
|
||||
return #on_key_cbs
|
||||
end
|
||||
|
||||
vim.validate {
|
||||
fn = { fn, 'c', 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('')
|
||||
end
|
||||
|
||||
on_keystroke_callbacks[ns_id] = fn
|
||||
on_key_cbs[ns_id] = fn
|
||||
return ns_id
|
||||
end
|
||||
|
||||
--- Function that executes the keystroke callbacks.
|
||||
--@private
|
||||
function vim._log_keystroke(char)
|
||||
--- Executes the on_key callbacks.
|
||||
---@private
|
||||
function vim._on_key(char)
|
||||
local failed_ns_ids = {}
|
||||
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)
|
||||
if not ok then
|
||||
vim.register_keystroke_callback(nil, k)
|
||||
|
||||
vim.on_key(nil, k)
|
||||
table.insert(failed_ns_ids, k)
|
||||
table.insert(failed_messages, err_msg)
|
||||
end
|
||||
@ -475,7 +484,7 @@ function vim._log_keystroke(char)
|
||||
|
||||
if failed_ns_ids[1] then
|
||||
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_messages, "\n")))
|
||||
end
|
||||
|
@ -338,7 +338,7 @@ int main(int argc, char **argv)
|
||||
// prepare screen now, so external UIs can display messages
|
||||
starting = NO_BUFFERS;
|
||||
screenclear();
|
||||
TIME_MSG("initialized screen early for UI");
|
||||
TIME_MSG("init screen for UI");
|
||||
}
|
||||
|
||||
|
||||
|
@ -676,7 +676,7 @@ void win_set_minimal_style(win_T *wp)
|
||||
}
|
||||
|
||||
// signcolumn: use 'auto'
|
||||
if (wp->w_p_scl[0] != 'a') {
|
||||
if (wp->w_p_scl[0] != 'a' || STRLEN(wp->w_p_scl) >= 8) {
|
||||
xfree(wp->w_p_scl);
|
||||
wp->w_p_scl = (char_u *)xstrdup("auto");
|
||||
}
|
||||
@ -737,6 +737,37 @@ void win_config_float(win_T *wp, FloatConfig fconfig)
|
||||
redraw_later(wp, NOT_VALID);
|
||||
}
|
||||
|
||||
// compute initial position
|
||||
if (wp->w_float_config.relative == kFloatRelativeWindow) {
|
||||
int row = wp->w_float_config.row;
|
||||
int col = wp->w_float_config.col;
|
||||
Error dummy = ERROR_INIT;
|
||||
win_T *parent = find_window_by_handle(wp->w_float_config.window, &dummy);
|
||||
if (parent) {
|
||||
row += parent->w_winrow;
|
||||
col += parent->w_wincol;
|
||||
ScreenGrid *grid = &parent->w_grid;
|
||||
int row_off = 0, col_off = 0;
|
||||
screen_adjust_grid(&grid, &row_off, &col_off);
|
||||
row += row_off;
|
||||
col += col_off;
|
||||
}
|
||||
api_clear_error(&dummy);
|
||||
if (wp->w_float_config.bufpos.lnum >= 0) {
|
||||
pos_T pos = { wp->w_float_config.bufpos.lnum + 1,
|
||||
wp->w_float_config.bufpos.col, 0 };
|
||||
int trow, tcol, tcolc, tcole;
|
||||
textpos2screenpos(wp, &pos, &trow, &tcol, &tcolc, &tcole, true);
|
||||
row += trow - 1;
|
||||
col += tcol - 1;
|
||||
}
|
||||
wp->w_winrow = row;
|
||||
wp->w_wincol = col;
|
||||
} else {
|
||||
wp->w_winrow = fconfig.row;
|
||||
wp->w_wincol = fconfig.col;
|
||||
}
|
||||
|
||||
// changing border style while keeping border only requires redrawing border
|
||||
if (fconfig.border) {
|
||||
wp->w_redr_border = true;
|
||||
@ -770,7 +801,6 @@ int win_fdccol_count(win_T *wp)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ui_ext_win_position(win_T *wp)
|
||||
{
|
||||
if (!wp->w_floating) {
|
||||
@ -817,6 +847,8 @@ void ui_ext_win_position(win_T *wp)
|
||||
|
||||
int comp_row = (int)row - (south ? wp->w_height : 0);
|
||||
int comp_col = (int)col - (east ? wp->w_width : 0);
|
||||
comp_row += grid->comp_row;
|
||||
comp_col += grid->comp_col;
|
||||
comp_row = MAX(MIN(comp_row, Rows-wp->w_height_outer-1), 0);
|
||||
comp_col = MAX(MIN(comp_col, Columns-wp->w_width_outer), 0);
|
||||
wp->w_winrow = comp_row;
|
||||
@ -2702,7 +2734,11 @@ static win_T *win_free_mem(
|
||||
// When deleting the current window of another tab page select a new
|
||||
// current window.
|
||||
if (tp != NULL && win == tp->tp_curwin) {
|
||||
tp->tp_curwin = wp;
|
||||
if (win_valid(tp->tp_prevwin) && tp->tp_prevwin != win) {
|
||||
tp->tp_curwin = tp->tp_prevwin;
|
||||
} else {
|
||||
tp->tp_curwin = tp->tp_firstwin;
|
||||
}
|
||||
}
|
||||
|
||||
return wp;
|
||||
|
@ -258,6 +258,9 @@ Number; !must be defined to function properly):
|
||||
|
||||
- `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
|
||||
to `build/bin/nvim`).
|
||||
|
||||
|
@ -5,6 +5,7 @@ local eq, clear, eval, command, nvim, next_msg =
|
||||
local meths = helpers.meths
|
||||
local exec_lua = helpers.exec_lua
|
||||
local retry = helpers.retry
|
||||
local isCI = helpers.isCI
|
||||
|
||||
describe('notify', function()
|
||||
local channel
|
||||
@ -76,6 +77,10 @@ describe('notify', function()
|
||||
end)
|
||||
|
||||
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
|
||||
local catchan = eval("jobstart(['cat'], {'rpc': v:true})")
|
||||
eq({id=catchan, stream='job', mode='rpc', client = {}}, exec_lua ([[
|
||||
|
@ -115,6 +115,19 @@ describe('API', function()
|
||||
nvim('exec','autocmd BufAdd * :let x1 = "Hello"', false)
|
||||
nvim('command', 'new foo')
|
||||
eq('Hello', request('nvim_eval', 'g:x1'))
|
||||
|
||||
-- Line continuations
|
||||
nvim('exec', [[
|
||||
let abc = #{
|
||||
\ a: 1,
|
||||
"\ b: 2,
|
||||
\ c: 3
|
||||
\ }]], false)
|
||||
eq({a = 1, c = 3}, request('nvim_eval', 'g:abc'))
|
||||
|
||||
-- try no spaces before continuations to catch off-by-one error
|
||||
nvim('exec', 'let ab = #{\n\\a: 98,\n"\\ b: 2\n\\}', false)
|
||||
eq({a = 98}, request('nvim_eval', 'g:ab'))
|
||||
end)
|
||||
|
||||
it('non-ASCII input', function()
|
||||
|
@ -346,6 +346,21 @@ describe('API/win', function()
|
||||
eq(2, #meths.list_wins())
|
||||
eq('', funcs.getcmdwintype())
|
||||
end)
|
||||
|
||||
it('closing current (float) window of another tabpage #15313', function()
|
||||
command('tabedit')
|
||||
eq(2, eval('tabpagenr()'))
|
||||
local win = meths.open_win(0, true, {
|
||||
relative='editor', row=10, col=10, width=50, height=10
|
||||
})
|
||||
local tabpage = eval('tabpagenr()')
|
||||
command('tabprevious')
|
||||
eq(1, eval('tabpagenr()'))
|
||||
meths.win_close(win, false)
|
||||
|
||||
eq(1001, meths.tabpage_get_win(tabpage).id)
|
||||
helpers.assert_alive()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('hide', function()
|
||||
|
@ -348,6 +348,12 @@ describe('jobs', function()
|
||||
eq(false, pcall(function()
|
||||
nvim('command', 'call jobsend(j, ["some data"])')
|
||||
end))
|
||||
|
||||
command("let g:job_opts.stdin = 'null'")
|
||||
nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)")
|
||||
eq(false, pcall(function()
|
||||
nvim('command', 'call jobsend(j, ["some data"])')
|
||||
end))
|
||||
end)
|
||||
|
||||
it('disallows jobsend on a non-existent job', function()
|
||||
|
@ -8,6 +8,8 @@ local feed = helpers.feed
|
||||
local feed_command = helpers.feed_command
|
||||
local write_file = helpers.write_file
|
||||
local exec = helpers.exec
|
||||
local exc_exec = helpers.exc_exec
|
||||
local exec_lua = helpers.exec_lua
|
||||
local eval = helpers.eval
|
||||
local exec_capture = helpers.exec_capture
|
||||
local neq = helpers.neq
|
||||
@ -18,16 +20,30 @@ describe(':source', function()
|
||||
end)
|
||||
|
||||
it('current buffer', function()
|
||||
insert('let a = 2')
|
||||
insert([[
|
||||
let a = 2
|
||||
let b = #{
|
||||
\ k: "v"
|
||||
"\ (o_o)
|
||||
\ }]])
|
||||
|
||||
command('source')
|
||||
eq('2', meths.exec('echo a', true))
|
||||
eq("{'k': 'v'}", meths.exec('echo b', true))
|
||||
|
||||
exec('set cpoptions+=C')
|
||||
eq('Vim(let):E15: Invalid expression: #{', exc_exec('source'))
|
||||
end)
|
||||
|
||||
it('selection in current buffer', function()
|
||||
insert(
|
||||
'let a = 2\n'..
|
||||
'let a = 3\n'..
|
||||
'let a = 4\n')
|
||||
insert([[
|
||||
let a = 2
|
||||
let a = 3
|
||||
let a = 4
|
||||
let b = #{
|
||||
"\ (>_<)
|
||||
\ K: "V"
|
||||
\ }]])
|
||||
|
||||
-- Source the 2nd line only
|
||||
feed('ggjV')
|
||||
@ -38,13 +54,26 @@ describe(':source', function()
|
||||
feed('ggjVG')
|
||||
feed_command(':source')
|
||||
eq('4', meths.exec('echo a', true))
|
||||
eq("{'K': 'V'}", meths.exec('echo b', true))
|
||||
|
||||
exec('set cpoptions+=C')
|
||||
eq('Vim(let):E15: Invalid expression: #{', exc_exec("'<,'>source"))
|
||||
end)
|
||||
|
||||
it('does not break if current buffer is modified while sourced', function()
|
||||
insert [[
|
||||
bwipeout!
|
||||
let a = 123
|
||||
]]
|
||||
command('source')
|
||||
eq('123', meths.exec('echo a', true))
|
||||
end)
|
||||
|
||||
it('multiline heredoc command', function()
|
||||
insert(
|
||||
'lua << EOF\n'..
|
||||
'y = 4\n'..
|
||||
'EOF\n')
|
||||
insert([[
|
||||
lua << EOF
|
||||
y = 4
|
||||
EOF]])
|
||||
|
||||
command('source')
|
||||
eq('4', meths.exec('echo luaeval("y")', true))
|
||||
@ -67,13 +96,21 @@ describe(':source', function()
|
||||
vim.g.b = 5
|
||||
vim.g.b = 6
|
||||
vim.g.b = 7
|
||||
a = [=[
|
||||
"\ a
|
||||
\ b]=]
|
||||
]])
|
||||
|
||||
command('edit '..test_file)
|
||||
|
||||
feed('ggjV')
|
||||
feed_command(':source')
|
||||
|
||||
eq(6, eval('g:b'))
|
||||
|
||||
feed('GVkk')
|
||||
feed_command(':source')
|
||||
eq(' "\\ a\n \\ b', exec_lua('return _G.a'))
|
||||
|
||||
os.remove(test_file)
|
||||
end)
|
||||
|
||||
@ -84,12 +121,16 @@ describe(':source', function()
|
||||
vim.g.c = 10
|
||||
vim.g.c = 11
|
||||
vim.g.c = 12
|
||||
a = [=[
|
||||
\ 1
|
||||
"\ 2]=]
|
||||
]])
|
||||
|
||||
command('edit '..test_file)
|
||||
feed_command(':source')
|
||||
|
||||
eq(12, eval('g:c'))
|
||||
eq(' \\ 1\n "\\ 2', exec_lua('return _G.a'))
|
||||
os.remove(test_file)
|
||||
end)
|
||||
|
||||
|
@ -168,6 +168,9 @@ describe('memory usage', function()
|
||||
end)
|
||||
|
||||
it('releases memory when closing windows when folds exist', function()
|
||||
if helpers.is_os('mac') then
|
||||
pending('macOS memory compression causes flakiness')
|
||||
end
|
||||
local pid = eval('getpid()')
|
||||
source([[
|
||||
new
|
||||
|
25
test/functional/lua/highlight_spec.lua
Normal file
25
test/functional/lua/highlight_spec.lua
Normal file
@ -0,0 +1,25 @@
|
||||
local helpers = require('test.functional.helpers')(after_each)
|
||||
local exec_lua = helpers.exec_lua
|
||||
local eq = helpers.eq
|
||||
local eval = helpers.eval
|
||||
local command = helpers.command
|
||||
local clear = helpers.clear
|
||||
|
||||
describe('vim.highlight.on_yank', function()
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
end)
|
||||
|
||||
it('does not show errors even if buffer is wiped before timeout', function()
|
||||
command('new')
|
||||
exec_lua[[
|
||||
vim.highlight.on_yank({timeout = 10, on_macro = true, event = {operator = "y", regtype = "v"}})
|
||||
vim.cmd('bwipeout!')
|
||||
]]
|
||||
helpers.sleep(10)
|
||||
helpers.feed('<cr>') -- avoid hang if error message exists
|
||||
eq('', eval('v:errmsg'))
|
||||
end)
|
||||
|
||||
end)
|
@ -6,6 +6,7 @@ local funcs = helpers.funcs
|
||||
local meths = helpers.meths
|
||||
local dedent = helpers.dedent
|
||||
local command = helpers.command
|
||||
local insert = helpers.insert
|
||||
local clear = helpers.clear
|
||||
local eq = helpers.eq
|
||||
local ok = helpers.ok
|
||||
@ -1332,12 +1333,12 @@ describe('lua stdlib', function()
|
||||
|
||||
it('should work for key-value pair options', function()
|
||||
local listchars = exec_lua [[
|
||||
vim.opt.listchars = "tab:>~,space:_"
|
||||
vim.opt.listchars = "tab:> ,space:_"
|
||||
return vim.opt.listchars:get()
|
||||
]]
|
||||
|
||||
eq({
|
||||
tab = ">~",
|
||||
tab = "> ",
|
||||
space = "_",
|
||||
}, listchars)
|
||||
end)
|
||||
@ -1855,7 +1856,7 @@ describe('lua stdlib', function()
|
||||
end)
|
||||
|
||||
it('vim.region', function()
|
||||
helpers.insert(helpers.dedent( [[
|
||||
insert(helpers.dedent( [[
|
||||
text tααt tααt text
|
||||
text tαxt txtα tex
|
||||
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] ]])
|
||||
end)
|
||||
|
||||
describe('vim.execute_on_keystroke', function()
|
||||
it('should keep track of keystrokes', function()
|
||||
helpers.insert([[hello world ]])
|
||||
describe('vim.on_key', function()
|
||||
it('tracks keystrokes', function()
|
||||
insert([[hello world ]])
|
||||
|
||||
exec_lua [[
|
||||
KeysPressed = {}
|
||||
keys = {}
|
||||
|
||||
vim.register_keystroke_callback(function(buf)
|
||||
vim.on_key(function(buf)
|
||||
if buf:byte() == 27 then
|
||||
buf = "<ESC>"
|
||||
end
|
||||
|
||||
table.insert(KeysPressed, buf)
|
||||
table.insert(keys, buf)
|
||||
end)
|
||||
]]
|
||||
|
||||
helpers.insert([[next 🤦 lines å ]])
|
||||
insert([[next 🤦 lines å ]])
|
||||
|
||||
-- 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)
|
||||
|
||||
it('should allow removing trackers.', function()
|
||||
helpers.insert([[hello world]])
|
||||
it('allows removing on_key listeners', function()
|
||||
insert([[hello world]])
|
||||
|
||||
exec_lua [[
|
||||
KeysPressed = {}
|
||||
keys = {}
|
||||
|
||||
return vim.register_keystroke_callback(function(buf)
|
||||
return vim.on_key(function(buf)
|
||||
if buf:byte() == 27 then
|
||||
buf = "<ESC>"
|
||||
end
|
||||
|
||||
table.insert(KeysPressed, buf)
|
||||
table.insert(keys, buf)
|
||||
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
|
||||
eq('inext lines<ESC>', exec_lua [[return table.concat(KeysPressed, '')]])
|
||||
eq('inext lines<ESC>', exec_lua [[return table.concat(keys, '')]])
|
||||
end)
|
||||
|
||||
it('should not call functions that error again.', function()
|
||||
helpers.insert([[hello world]])
|
||||
it('skips any function that caused an error', function()
|
||||
insert([[hello world]])
|
||||
|
||||
exec_lua [[
|
||||
KeysPressed = {}
|
||||
keys = {}
|
||||
|
||||
return vim.register_keystroke_callback(function(buf)
|
||||
return vim.on_key(function(buf)
|
||||
if buf:byte() == 27 then
|
||||
buf = "<ESC>"
|
||||
end
|
||||
|
||||
table.insert(KeysPressed, buf)
|
||||
table.insert(keys, buf)
|
||||
|
||||
if buf == 'l' then
|
||||
error("Dumb Error")
|
||||
@ -1929,35 +1932,30 @@ describe('lua stdlib', function()
|
||||
end)
|
||||
]]
|
||||
|
||||
helpers.insert([[next lines]])
|
||||
helpers.insert([[more lines]])
|
||||
insert([[next lines]])
|
||||
insert([[more lines]])
|
||||
|
||||
-- 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)
|
||||
|
||||
it('should process mapped keys, not unmapped keys', function()
|
||||
it('processes mapped keys, not unmapped keys', function()
|
||||
exec_lua [[
|
||||
KeysPressed = {}
|
||||
keys = {}
|
||||
|
||||
vim.cmd("inoremap hello world")
|
||||
|
||||
vim.register_keystroke_callback(function(buf)
|
||||
vim.on_key(function(buf)
|
||||
if buf:byte() == 27 then
|
||||
buf = "<ESC>"
|
||||
end
|
||||
|
||||
table.insert(KeysPressed, buf)
|
||||
table.insert(keys, buf)
|
||||
end)
|
||||
]]
|
||||
insert("hello")
|
||||
|
||||
helpers.insert("hello")
|
||||
|
||||
local next_status = exec_lua [[
|
||||
return table.concat(KeysPressed, '')
|
||||
]]
|
||||
|
||||
eq("iworld<ESC>", next_status)
|
||||
eq('iworld<ESC>', exec_lua[[return table.concat(keys, '')]])
|
||||
end)
|
||||
end)
|
||||
|
||||
|
@ -32,7 +32,7 @@ describe('vim.lsp.codelens', function()
|
||||
command = { title = 'Lens1', command = 'Dummy' }
|
||||
},
|
||||
}
|
||||
vim.lsp.codelens.on_codelens(nil, 'textDocument/codeLens', lenses, 1, bufnr)
|
||||
vim.lsp.codelens.on_codelens(nil, lenses, {method='textDocument/codeLens', client_id=1, bufnr=bufnr})
|
||||
]], bufnr)
|
||||
|
||||
local stored_lenses = exec_lua('return vim.lsp.codelens.get(...)', bufnr)
|
||||
|
@ -205,8 +205,8 @@ describe('vim.lsp.diagnostic', function()
|
||||
make_warning("Warning 1", 2, 1, 2, 5),
|
||||
}
|
||||
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { uri = fake_uri, diagnostics = server_1_diags }, 1)
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { uri = fake_uri, diagnostics = server_2_diags }, 2)
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, { uri = fake_uri, diagnostics = server_1_diags }, {client_id=1})
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, { uri = fake_uri, diagnostics = server_2_diags }, {client_id=2})
|
||||
return {
|
||||
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1),
|
||||
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", 2),
|
||||
@ -258,8 +258,8 @@ describe('vim.lsp.diagnostic', function()
|
||||
make_warning("Warning 1", 2, 1, 2, 5),
|
||||
}
|
||||
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { uri = fake_uri, diagnostics = server_1_diags }, 1)
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { uri = fake_uri, diagnostics = server_2_diags }, 2)
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, { uri = fake_uri, diagnostics = server_1_diags }, {client_id=1})
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, { uri = fake_uri, diagnostics = server_2_diags }, {client_id=2})
|
||||
return {
|
||||
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1),
|
||||
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", 2),
|
||||
@ -435,14 +435,14 @@ describe('vim.lsp.diagnostic', function()
|
||||
|
||||
it('should return all diagnostics when no severity is supplied', function()
|
||||
eq(2, exec_lua [[
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, {
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_error("Error 1", 1, 1, 1, 5),
|
||||
make_warning("Warning on Server 1", 1, 1, 2, 5),
|
||||
make_error("Error On Other Line", 2, 1, 1, 5),
|
||||
}
|
||||
}, 1)
|
||||
}, {client_id=1})
|
||||
|
||||
return #vim.lsp.diagnostic.get_line_diagnostics(diagnostic_bufnr, 1)
|
||||
]])
|
||||
@ -450,7 +450,7 @@ describe('vim.lsp.diagnostic', function()
|
||||
|
||||
it('should return only requested diagnostics when severity_limit is supplied', function()
|
||||
eq(2, exec_lua [[
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, {
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_error("Error 1", 1, 1, 1, 5),
|
||||
@ -458,7 +458,7 @@ describe('vim.lsp.diagnostic', function()
|
||||
make_information("Ignored information", 1, 1, 2, 5),
|
||||
make_error("Error On Other Line", 2, 1, 1, 5),
|
||||
}
|
||||
}, 1)
|
||||
}, {client_id=1})
|
||||
|
||||
return #vim.lsp.diagnostic.get_line_diagnostics(diagnostic_bufnr, 1, { severity_limit = "Warning" })
|
||||
]])
|
||||
@ -470,12 +470,12 @@ describe('vim.lsp.diagnostic', function()
|
||||
exec_lua [[
|
||||
vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||
virtual_text = function() return true end,
|
||||
})(nil, nil, {
|
||||
})(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||
}
|
||||
}, 1
|
||||
}, {client_id=1}
|
||||
)
|
||||
]]
|
||||
|
||||
@ -487,12 +487,12 @@ describe('vim.lsp.diagnostic', function()
|
||||
exec_lua [[
|
||||
vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||
virtual_text = function() return false end,
|
||||
})(nil, nil, {
|
||||
})(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||
}
|
||||
}, 1
|
||||
}, {client_id=1}
|
||||
)
|
||||
]]
|
||||
|
||||
@ -509,12 +509,12 @@ describe('vim.lsp.diagnostic', function()
|
||||
exec_lua [[
|
||||
vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||
update_in_insert = false,
|
||||
})(nil, nil, {
|
||||
})(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||
}
|
||||
}, 1
|
||||
}, {client_id=1}
|
||||
)
|
||||
]]
|
||||
|
||||
@ -551,12 +551,12 @@ describe('vim.lsp.diagnostic', function()
|
||||
return SetVirtualTextOriginal(...)
|
||||
end
|
||||
|
||||
PublishDiagnostics(nil, nil, {
|
||||
PublishDiagnostics(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||
}
|
||||
}, 1
|
||||
}, {client_id=1}
|
||||
)
|
||||
]]
|
||||
|
||||
@ -605,12 +605,12 @@ describe('vim.lsp.diagnostic', function()
|
||||
return SetVirtualTextOriginal(...)
|
||||
end
|
||||
|
||||
PublishDiagnostics(nil, nil, {
|
||||
PublishDiagnostics(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||
}
|
||||
}, 1
|
||||
}, {client_id=1}
|
||||
)
|
||||
]]
|
||||
|
||||
@ -647,12 +647,12 @@ describe('vim.lsp.diagnostic', function()
|
||||
exec_lua [[
|
||||
vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||
update_in_insert = true,
|
||||
})(nil, nil, {
|
||||
})(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||
}
|
||||
}, 1
|
||||
}, {client_id=1}
|
||||
)
|
||||
]]
|
||||
|
||||
@ -677,12 +677,12 @@ describe('vim.lsp.diagnostic', function()
|
||||
},
|
||||
})
|
||||
|
||||
PublishDiagnostics(nil, nil, {
|
||||
PublishDiagnostics(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||
}
|
||||
}, 1
|
||||
}, {client_id=1}
|
||||
)
|
||||
|
||||
return vim.api.nvim_buf_get_extmarks(
|
||||
@ -714,12 +714,12 @@ describe('vim.lsp.diagnostic', function()
|
||||
end,
|
||||
})
|
||||
|
||||
PublishDiagnostics(nil, nil, {
|
||||
PublishDiagnostics(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||
}
|
||||
}, 1
|
||||
}, {client_id=1}
|
||||
)
|
||||
|
||||
return vim.api.nvim_buf_get_extmarks(
|
||||
@ -747,12 +747,12 @@ describe('vim.lsp.diagnostic', function()
|
||||
},
|
||||
})
|
||||
|
||||
PublishDiagnostics(nil, nil, {
|
||||
PublishDiagnostics(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_warning('Delayed Diagnostic', 4, 4, 4, 4),
|
||||
}
|
||||
}, 1
|
||||
}, {client_id=1}
|
||||
)
|
||||
|
||||
return count_of_extmarks_for_client(diagnostic_bufnr, 1)
|
||||
@ -838,10 +838,10 @@ describe('vim.lsp.diagnostic', function()
|
||||
}
|
||||
|
||||
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, {
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = diagnostics
|
||||
}, 1
|
||||
}, {client_id=1}
|
||||
)
|
||||
|
||||
vim.lsp.diagnostic.set_signs(diagnostics, diagnostic_bufnr, 1)
|
||||
@ -863,13 +863,13 @@ describe('vim.lsp.diagnostic', function()
|
||||
local loc_list = exec_lua [[
|
||||
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, {
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_error('Farther Diagnostic', 4, 4, 4, 4),
|
||||
make_error('Lower Diagnostic', 1, 1, 1, 1),
|
||||
}
|
||||
}, 1
|
||||
}, {client_id=1}
|
||||
)
|
||||
|
||||
vim.lsp.diagnostic.set_loclist()
|
||||
@ -884,20 +884,20 @@ describe('vim.lsp.diagnostic', function()
|
||||
local loc_list = exec_lua [[
|
||||
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, {
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_error('Lower Diagnostic', 1, 1, 1, 1),
|
||||
}
|
||||
}, 1
|
||||
}, {client_id=1}
|
||||
)
|
||||
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, {
|
||||
vim.lsp.diagnostic.on_publish_diagnostics(nil, {
|
||||
uri = fake_uri,
|
||||
diagnostics = {
|
||||
make_warning('Farther Diagnostic', 4, 4, 4, 4),
|
||||
}
|
||||
}, 2
|
||||
}, {client_id=2}
|
||||
)
|
||||
|
||||
vim.lsp.diagnostic.set_loclist()
|
||||
|
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)
|
||||
|
@ -130,9 +130,12 @@ local function test_rpc_server(config)
|
||||
end
|
||||
|
||||
describe('LSP', function()
|
||||
before_each(function()
|
||||
clear_notrace()
|
||||
end)
|
||||
|
||||
describe('server_name specified', function()
|
||||
before_each(function()
|
||||
clear_notrace()
|
||||
-- Run an instance of nvim on the file which contains our "scripts".
|
||||
-- Pass TEST_NAME to pick the script.
|
||||
local test_name = "basic_init"
|
||||
@ -216,7 +219,7 @@ describe('LSP', function()
|
||||
|
||||
it('should run correctly', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "test", {}, 1};
|
||||
{NIL, {}, {method="test", client_id=1}};
|
||||
}
|
||||
test_rpc_server {
|
||||
test_name = "basic_init";
|
||||
@ -241,7 +244,7 @@ describe('LSP', function()
|
||||
|
||||
it('should fail', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "test", {}, 1};
|
||||
{NIL, {}, {method="test", client_id=1}};
|
||||
}
|
||||
test_rpc_server {
|
||||
test_name = "basic_init";
|
||||
@ -269,8 +272,8 @@ describe('LSP', function()
|
||||
return
|
||||
end
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1, NIL};
|
||||
{NIL, "test", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, {}, {method="test", client_id=1}};
|
||||
}
|
||||
test_rpc_server {
|
||||
test_name = "basic_init";
|
||||
@ -292,12 +295,12 @@ describe('LSP', function()
|
||||
|
||||
it('client should return settings via workspace/configuration handler', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, "workspace/configuration", { items = {
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, { items = {
|
||||
{ section = "testSetting1" };
|
||||
{ section = "testSetting2" };
|
||||
}}, 1};
|
||||
{NIL, "start", {}, 1};
|
||||
}}, { method="workspace/configuration", client_id=1}};
|
||||
{NIL, {}, {method="start", client_id=1}};
|
||||
}
|
||||
local client
|
||||
test_rpc_server {
|
||||
@ -309,9 +312,9 @@ describe('LSP', function()
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||||
end;
|
||||
on_handler = function(err, method, params, client_id)
|
||||
eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
|
||||
if method == 'start' then
|
||||
on_handler = function(err, result, ctx)
|
||||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||||
if ctx.method == 'start' then
|
||||
exec_lua([=[
|
||||
local client = vim.lsp.get_client_by_id(TEST_RPC_CLIENT_ID)
|
||||
client.config.settings = {
|
||||
@ -319,35 +322,34 @@ describe('LSP', function()
|
||||
testSetting2 = false;
|
||||
}]=])
|
||||
end
|
||||
if method == 'workspace/configuration' then
|
||||
local result = exec_lua([=[
|
||||
if ctx.method == 'workspace/configuration' then
|
||||
local server_result = exec_lua([=[
|
||||
local method, params = ...
|
||||
return require'vim.lsp.handlers'['workspace/configuration'](err, method, params, TEST_RPC_CLIENT_ID)]=], method, params)
|
||||
client.notify('workspace/configuration', result)
|
||||
return require'vim.lsp.handlers'['workspace/configuration'](err, params, {method=method, client_id=TEST_RPC_CLIENT_ID})]=], ctx.method, result)
|
||||
client.notify('workspace/configuration', server_result)
|
||||
end
|
||||
if method == 'shutdown' then
|
||||
if ctx.method == 'shutdown' then
|
||||
client.stop()
|
||||
end
|
||||
end;
|
||||
}
|
||||
end)
|
||||
it('workspace/configuration returns NIL per section if client was started without config.settings', function()
|
||||
clear_notrace()
|
||||
fake_lsp_server_setup('workspace/configuration no settings')
|
||||
eq({ NIL, NIL, }, exec_lua [[
|
||||
local params = {
|
||||
local result = {
|
||||
items = {
|
||||
{section = 'foo'},
|
||||
{section = 'bar'},
|
||||
}
|
||||
}
|
||||
return vim.lsp.handlers['workspace/configuration'](nil, nil, params, TEST_RPC_CLIENT_ID)
|
||||
return vim.lsp.handlers['workspace/configuration'](nil, result, {client_id=TEST_RPC_CLIENT_ID})
|
||||
]])
|
||||
end)
|
||||
|
||||
it('should verify capabilities sent', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
}
|
||||
test_rpc_server {
|
||||
test_name = "basic_check_capabilities";
|
||||
@ -371,7 +373,7 @@ describe('LSP', function()
|
||||
|
||||
it('client.supports_methods() should validate capabilities', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
}
|
||||
test_rpc_server {
|
||||
test_name = "capabilities_for_client_supports_method";
|
||||
@ -405,7 +407,7 @@ describe('LSP', function()
|
||||
|
||||
it('should call unsupported_method when trying to call an unsupported method', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
}
|
||||
test_rpc_server {
|
||||
test_name = "capabilities_for_client_supports_method";
|
||||
@ -413,7 +415,8 @@ describe('LSP', function()
|
||||
exec_lua([=[
|
||||
BUFFER = vim.api.nvim_get_current_buf()
|
||||
lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)
|
||||
vim.lsp.handlers['textDocument/typeDefinition'] = function(err, method)
|
||||
vim.lsp.handlers['textDocument/typeDefinition'] = function(err, result, ctx)
|
||||
local method = ctx.method
|
||||
vim.lsp._last_lsp_handler = { err = err; method = method }
|
||||
end
|
||||
vim.lsp._unsupported_method = function(method)
|
||||
@ -446,7 +449,7 @@ describe('LSP', function()
|
||||
|
||||
it('shouldn\'t call unsupported_method when no client and trying to call an unsupported method', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
}
|
||||
test_rpc_server {
|
||||
test_name = "capabilities_for_client_supports_method";
|
||||
@ -479,8 +482,8 @@ describe('LSP', function()
|
||||
|
||||
it('should not send didOpen if the buffer closes before init', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, "finish", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, {}, {method="finish", client_id=1}};
|
||||
}
|
||||
local client
|
||||
test_rpc_server {
|
||||
@ -511,9 +514,9 @@ describe('LSP', function()
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||||
end;
|
||||
on_handler = function(err, method, params, client_id)
|
||||
eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
|
||||
if method == 'finish' then
|
||||
on_handler = function(err, result, ctx)
|
||||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||||
if ctx.method == 'finish' then
|
||||
client.stop()
|
||||
end
|
||||
end;
|
||||
@ -522,9 +525,9 @@ describe('LSP', function()
|
||||
|
||||
it('should check the body sent attaching before init', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, "finish", {}, 1};
|
||||
{NIL, "start", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, {}, {method="finish", client_id=1}};
|
||||
{NIL, {}, {method="start", client_id=1}};
|
||||
}
|
||||
local client
|
||||
test_rpc_server {
|
||||
@ -554,12 +557,12 @@ describe('LSP', function()
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||||
end;
|
||||
on_handler = function(err, method, params, client_id)
|
||||
if method == 'start' then
|
||||
on_handler = function(err, result, ctx)
|
||||
if ctx.method == 'start' then
|
||||
client.notify('finish')
|
||||
end
|
||||
eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
|
||||
if method == 'finish' then
|
||||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||||
if ctx.method == 'finish' then
|
||||
client.stop()
|
||||
end
|
||||
end;
|
||||
@ -568,9 +571,9 @@ describe('LSP', function()
|
||||
|
||||
it('should check the body sent attaching after init', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, "finish", {}, 1};
|
||||
{NIL, "start", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, {}, {method="finish", client_id=1}};
|
||||
{NIL, {}, {method="start", client_id=1}};
|
||||
}
|
||||
local client
|
||||
test_rpc_server {
|
||||
@ -597,12 +600,12 @@ describe('LSP', function()
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||||
end;
|
||||
on_handler = function(err, method, params, client_id)
|
||||
if method == 'start' then
|
||||
on_handler = function(err, result, ctx)
|
||||
if ctx.method == 'start' then
|
||||
client.notify('finish')
|
||||
end
|
||||
eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
|
||||
if method == 'finish' then
|
||||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||||
if ctx.method == 'finish' then
|
||||
client.stop()
|
||||
end
|
||||
end;
|
||||
@ -611,9 +614,9 @@ describe('LSP', function()
|
||||
|
||||
it('should check the body and didChange full', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, "finish", {}, 1};
|
||||
{NIL, "start", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, {}, {method="finish", client_id=1}};
|
||||
{NIL, {}, {method="start", client_id=1}};
|
||||
}
|
||||
local client
|
||||
test_rpc_server {
|
||||
@ -640,8 +643,8 @@ describe('LSP', function()
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||||
end;
|
||||
on_handler = function(err, method, params, client_id)
|
||||
if method == 'start' then
|
||||
on_handler = function(err, result, ctx)
|
||||
if ctx.method == 'start' then
|
||||
exec_lua [[
|
||||
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
|
||||
"boop";
|
||||
@ -649,8 +652,8 @@ describe('LSP', function()
|
||||
]]
|
||||
client.notify('finish')
|
||||
end
|
||||
eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
|
||||
if method == 'finish' then
|
||||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||||
if ctx.method == 'finish' then
|
||||
client.stop()
|
||||
end
|
||||
end;
|
||||
@ -659,9 +662,9 @@ describe('LSP', function()
|
||||
|
||||
it('should check the body and didChange full with noeol', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, "finish", {}, 1};
|
||||
{NIL, "start", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, {}, {method="finish", client_id=1}};
|
||||
{NIL, {}, {method="start", client_id=1}};
|
||||
}
|
||||
local client
|
||||
test_rpc_server {
|
||||
@ -689,8 +692,8 @@ describe('LSP', function()
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||||
end;
|
||||
on_handler = function(err, method, params, client_id)
|
||||
if method == 'start' then
|
||||
on_handler = function(err, result, ctx)
|
||||
if ctx.method == 'start' then
|
||||
exec_lua [[
|
||||
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
|
||||
"boop";
|
||||
@ -698,8 +701,8 @@ describe('LSP', function()
|
||||
]]
|
||||
client.notify('finish')
|
||||
end
|
||||
eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
|
||||
if method == 'finish' then
|
||||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||||
if ctx.method == 'finish' then
|
||||
client.stop()
|
||||
end
|
||||
end;
|
||||
@ -708,9 +711,9 @@ describe('LSP', function()
|
||||
|
||||
it('should check the body and didChange incremental', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, "finish", {}, 1};
|
||||
{NIL, "start", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, {}, {method="finish", client_id=1}};
|
||||
{NIL, {}, {method="start", client_id=1}};
|
||||
}
|
||||
local client
|
||||
test_rpc_server {
|
||||
@ -738,8 +741,8 @@ describe('LSP', function()
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||||
end;
|
||||
on_handler = function(err, method, params, client_id)
|
||||
if method == 'start' then
|
||||
on_handler = function(err, result, ctx)
|
||||
if ctx.method == 'start' then
|
||||
exec_lua [[
|
||||
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
|
||||
"123boop";
|
||||
@ -747,8 +750,8 @@ describe('LSP', function()
|
||||
]]
|
||||
client.notify('finish')
|
||||
end
|
||||
eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
|
||||
if method == 'finish' then
|
||||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||||
if ctx.method == 'finish' then
|
||||
client.stop()
|
||||
end
|
||||
end;
|
||||
@ -758,9 +761,9 @@ describe('LSP', function()
|
||||
-- TODO(askhan) we don't support full for now, so we can disable these tests.
|
||||
pending('should check the body and didChange incremental normal mode editing', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, "finish", {}, 1};
|
||||
{NIL, "start", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, {}, {method="finish", client_id=1}};
|
||||
{NIL, {}, {method="start", client_id=1}};
|
||||
}
|
||||
local client
|
||||
test_rpc_server {
|
||||
@ -787,13 +790,13 @@ describe('LSP', function()
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||||
end;
|
||||
on_handler = function(err, method, params, client_id)
|
||||
if method == 'start' then
|
||||
on_handler = function(err, result, ctx)
|
||||
if ctx.method == 'start' then
|
||||
helpers.command("normal! 1Go")
|
||||
client.notify('finish')
|
||||
end
|
||||
eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
|
||||
if method == 'finish' then
|
||||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||||
if ctx.method == 'finish' then
|
||||
client.stop()
|
||||
end
|
||||
end;
|
||||
@ -802,9 +805,9 @@ describe('LSP', function()
|
||||
|
||||
it('should check the body and didChange full with 2 changes', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, "finish", {}, 1};
|
||||
{NIL, "start", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, {}, {method="finish", client_id=1}};
|
||||
{NIL, {}, {method="start", client_id=1}};
|
||||
}
|
||||
local client
|
||||
test_rpc_server {
|
||||
@ -831,8 +834,8 @@ describe('LSP', function()
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||||
end;
|
||||
on_handler = function(err, method, params, client_id)
|
||||
if method == 'start' then
|
||||
on_handler = function(err, result, ctx)
|
||||
if ctx.method == 'start' then
|
||||
exec_lua [[
|
||||
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
|
||||
"321";
|
||||
@ -843,8 +846,8 @@ describe('LSP', function()
|
||||
]]
|
||||
client.notify('finish')
|
||||
end
|
||||
eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
|
||||
if method == 'finish' then
|
||||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||||
if ctx.method == 'finish' then
|
||||
client.stop()
|
||||
end
|
||||
end;
|
||||
@ -853,9 +856,9 @@ describe('LSP', function()
|
||||
|
||||
it('should check the body and didChange full lifecycle', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, "finish", {}, 1};
|
||||
{NIL, "start", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, {}, {method="finish", client_id=1}};
|
||||
{NIL, {}, {method="start", client_id=1}};
|
||||
}
|
||||
local client
|
||||
test_rpc_server {
|
||||
@ -882,8 +885,8 @@ describe('LSP', function()
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||||
end;
|
||||
on_handler = function(err, method, params, client_id)
|
||||
if method == 'start' then
|
||||
on_handler = function(err, result,ctx)
|
||||
if ctx.method == 'start' then
|
||||
exec_lua [[
|
||||
vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, {
|
||||
"321";
|
||||
@ -895,8 +898,8 @@ describe('LSP', function()
|
||||
]]
|
||||
client.notify('finish')
|
||||
end
|
||||
eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
|
||||
if method == 'finish' then
|
||||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||||
if ctx.method == 'finish' then
|
||||
client.stop()
|
||||
end
|
||||
end;
|
||||
@ -907,9 +910,9 @@ describe('LSP', function()
|
||||
describe("parsing tests", function()
|
||||
it('should handle invalid content-length correctly', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, "finish", {}, 1};
|
||||
{NIL, "start", {}, 1};
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, {}, {method="finish", client_id=1}};
|
||||
{NIL, {}, {method="start", client_id=1}};
|
||||
}
|
||||
local client
|
||||
test_rpc_server {
|
||||
@ -924,22 +927,22 @@ describe('LSP', function()
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||||
end;
|
||||
on_handler = function(err, method, params, client_id)
|
||||
eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
|
||||
on_handler = function(err, result, ctx)
|
||||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||||
end;
|
||||
}
|
||||
end)
|
||||
|
||||
it('should not trim vim.NIL from the end of a list', function()
|
||||
local expected_handlers = {
|
||||
{NIL, "shutdown", {}, 1};
|
||||
{NIL, "finish", {}, 1};
|
||||
{NIL, "workspace/executeCommand", {
|
||||
{NIL, {}, {method="shutdown", client_id=1}};
|
||||
{NIL, {}, {method="finish", client_id=1}};
|
||||
{NIL,{
|
||||
arguments = { "EXTRACT_METHOD", {metadata = {}}, 3, 0, 6123, NIL },
|
||||
command = "refactor.perform",
|
||||
title = "EXTRACT_METHOD"
|
||||
}, 1};
|
||||
{NIL, "start", {}, 1};
|
||||
}, {method="workspace/executeCommand", client_id=1}};
|
||||
{NIL, {}, {method="start", client_id=1}};
|
||||
}
|
||||
local client
|
||||
test_rpc_server {
|
||||
@ -963,9 +966,9 @@ describe('LSP', function()
|
||||
eq(0, code, "exit code", fake_lsp_logfile)
|
||||
eq(0, signal, "exit signal", fake_lsp_logfile)
|
||||
end;
|
||||
on_handler = function(err, method, params, client_id)
|
||||
eq(table.remove(expected_handlers), {err, method, params, client_id}, "expected handler")
|
||||
if method == 'finish' then
|
||||
on_handler = function(err, result, ctx)
|
||||
eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
|
||||
if ctx.method == 'finish' then
|
||||
client.stop()
|
||||
end
|
||||
end;
|
||||
@ -1088,6 +1091,30 @@ describe('LSP', function()
|
||||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||||
}, buf_lines(1))
|
||||
end)
|
||||
it('applies complex edits (reversed range)', function()
|
||||
local edits = {
|
||||
make_edit(0, 0, 0, 0, {"", "12"});
|
||||
make_edit(0, 0, 0, 0, {"3", "foo"});
|
||||
make_edit(0, 1, 0, 1, {"bar", "123"});
|
||||
make_edit(0, #"First line of text", 0, #"First ", {"guy"});
|
||||
make_edit(1, #'Second', 1, 0, {"baz"});
|
||||
make_edit(2, #"Third", 2, #'Th', {"e next"});
|
||||
make_edit(3, #"Fourth", 3, #'', {"another line of text", "before this"});
|
||||
make_edit(3, #"Fourth line of text", 3, #'Fourth', {"!"});
|
||||
}
|
||||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||||
eq({
|
||||
'';
|
||||
'123';
|
||||
'fooFbar';
|
||||
'123irst guy';
|
||||
'baz line of text';
|
||||
'The next line of text';
|
||||
'another line of text';
|
||||
'before this!';
|
||||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||||
}, buf_lines(1))
|
||||
end)
|
||||
it('applies non-ASCII characters edits', function()
|
||||
local edits = {
|
||||
make_edit(4, 3, 4, 4, {"ä"});
|
||||
@ -1116,6 +1143,86 @@ describe('LSP', function()
|
||||
}, buf_lines(1))
|
||||
end)
|
||||
|
||||
describe('cursor position', function()
|
||||
it('don\'t fix the cursor if the range contains the cursor', function()
|
||||
funcs.nvim_win_set_cursor(0, { 2, 6 })
|
||||
local edits = {
|
||||
make_edit(1, 0, 1, 19, 'Second line of text')
|
||||
}
|
||||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||||
eq({
|
||||
'First line of text';
|
||||
'Second line of text';
|
||||
'Third line of text';
|
||||
'Fourth line of text';
|
||||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||||
}, buf_lines(1))
|
||||
eq({ 2, 6 }, funcs.nvim_win_get_cursor(0))
|
||||
end)
|
||||
|
||||
it('fix the cursor to the valid column if the content was removed', function()
|
||||
funcs.nvim_win_set_cursor(0, { 2, 6 })
|
||||
local edits = {
|
||||
make_edit(1, 0, 1, 19, '')
|
||||
}
|
||||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||||
eq({
|
||||
'First line of text';
|
||||
'';
|
||||
'Third line of text';
|
||||
'Fourth line of text';
|
||||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||||
}, buf_lines(1))
|
||||
eq({ 2, 0 }, funcs.nvim_win_get_cursor(0))
|
||||
end)
|
||||
|
||||
it('fix the cursor row', function()
|
||||
funcs.nvim_win_set_cursor(0, { 3, 0 })
|
||||
local edits = {
|
||||
make_edit(1, 0, 2, 0, '')
|
||||
}
|
||||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||||
eq({
|
||||
'First line of text';
|
||||
'Third line of text';
|
||||
'Fourth line of text';
|
||||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||||
}, buf_lines(1))
|
||||
eq({ 2, 0 }, funcs.nvim_win_get_cursor(0))
|
||||
end)
|
||||
|
||||
it('fix the cursor col', function()
|
||||
funcs.nvim_win_set_cursor(0, { 2, 11 })
|
||||
local edits = {
|
||||
make_edit(1, 7, 1, 11, '')
|
||||
}
|
||||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||||
eq({
|
||||
'First line of text';
|
||||
'Second of text';
|
||||
'Third line of text';
|
||||
'Fourth line of text';
|
||||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||||
}, buf_lines(1))
|
||||
eq({ 2, 7 }, funcs.nvim_win_get_cursor(0))
|
||||
end)
|
||||
|
||||
it('fix the cursor row and col', function()
|
||||
funcs.nvim_win_set_cursor(0, { 2, 12 })
|
||||
local edits = {
|
||||
make_edit(0, 11, 1, 12, '')
|
||||
}
|
||||
exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
|
||||
eq({
|
||||
'First line of text';
|
||||
'Third line of text';
|
||||
'Fourth line of text';
|
||||
'å å ɧ 汉语 ↥ 🤦 🦄';
|
||||
}, buf_lines(1))
|
||||
eq({ 1, 11 }, funcs.nvim_win_get_cursor(0))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('with LSP end line after what Vim considers to be the end line', function()
|
||||
it('applies edits when the last linebreak is considered a new line', function()
|
||||
local edits = {
|
||||
@ -1223,7 +1330,7 @@ describe('LSP', function()
|
||||
label = nil;
|
||||
edit = {};
|
||||
}
|
||||
return vim.lsp.handlers['workspace/applyEdit'](nil, nil, apply_edit)
|
||||
return vim.lsp.handlers['workspace/applyEdit'](nil, apply_edit)
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
@ -1441,8 +1548,10 @@ describe('LSP', function()
|
||||
{ label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} },
|
||||
-- nested snippet tokens
|
||||
{ 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
|
||||
{ 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 expected = {
|
||||
@ -1454,8 +1563,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(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 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(${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(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()', 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))
|
||||
@ -1973,7 +2083,7 @@ describe('LSP', function()
|
||||
describe('vim.lsp.buf.outgoing_calls', function()
|
||||
it('does nothing for an empty response', function()
|
||||
local qflist_count = exec_lua([=[
|
||||
require'vim.lsp.handlers'['callHierarchy/outgoingCalls']()
|
||||
require'vim.lsp.handlers'['callHierarchy/outgoingCalls'](nil, nil, {}, nil)
|
||||
return #vim.fn.getqflist()
|
||||
]=])
|
||||
eq(0, qflist_count)
|
||||
@ -2020,7 +2130,7 @@ describe('LSP', function()
|
||||
}
|
||||
} }
|
||||
local handler = require'vim.lsp.handlers'['callHierarchy/outgoingCalls']
|
||||
handler(nil, nil, rust_analyzer_response)
|
||||
handler(nil, rust_analyzer_response, {})
|
||||
return vim.fn.getqflist()
|
||||
]=])
|
||||
|
||||
@ -2044,7 +2154,7 @@ describe('LSP', function()
|
||||
describe('vim.lsp.buf.incoming_calls', function()
|
||||
it('does nothing for an empty response', function()
|
||||
local qflist_count = exec_lua([=[
|
||||
require'vim.lsp.handlers'['callHierarchy/incomingCalls']()
|
||||
require'vim.lsp.handlers'['callHierarchy/incomingCalls'](nil, nil, {})
|
||||
return #vim.fn.getqflist()
|
||||
]=])
|
||||
eq(0, qflist_count)
|
||||
@ -2092,7 +2202,7 @@ describe('LSP', function()
|
||||
} }
|
||||
|
||||
local handler = require'vim.lsp.handlers'['callHierarchy/incomingCalls']
|
||||
handler(nil, nil, rust_analyzer_response)
|
||||
handler(nil, rust_analyzer_response, {})
|
||||
return vim.fn.getqflist()
|
||||
]=])
|
||||
|
||||
|
@ -10,9 +10,11 @@ local pending_c_parser = helpers.pending_c_parser
|
||||
before_each(clear)
|
||||
|
||||
describe('treesitter parser API', function()
|
||||
clear()
|
||||
if pending_c_parser(pending) then return end
|
||||
|
||||
it('parses buffer', function()
|
||||
if helpers.pending_win32(pending) or pending_c_parser(pending) then return end
|
||||
if helpers.pending_win32(pending) then return end
|
||||
|
||||
insert([[
|
||||
int main() {
|
||||
@ -103,8 +105,6 @@ void ui_refresh(void)
|
||||
}]]
|
||||
|
||||
it('allows to iterate over nodes children', function()
|
||||
if pending_c_parser(pending) then return end
|
||||
|
||||
insert(test_text);
|
||||
|
||||
local res = exec_lua([[
|
||||
@ -127,8 +127,6 @@ void ui_refresh(void)
|
||||
end)
|
||||
|
||||
it('allows to get a child by field', function()
|
||||
if pending_c_parser(pending) then return end
|
||||
|
||||
insert(test_text);
|
||||
|
||||
local res = exec_lua([[
|
||||
@ -162,8 +160,6 @@ void ui_refresh(void)
|
||||
]]
|
||||
|
||||
it("supports runtime queries", function()
|
||||
if pending_c_parser(pending) then return end
|
||||
|
||||
local ret = exec_lua [[
|
||||
return require"vim.treesitter.query".get_query("c", "highlights").captures[1]
|
||||
]]
|
||||
@ -172,8 +168,6 @@ void ui_refresh(void)
|
||||
end)
|
||||
|
||||
it('support query and iter by capture', function()
|
||||
if pending_c_parser(pending) then return end
|
||||
|
||||
insert(test_text)
|
||||
|
||||
local res = exec_lua([[
|
||||
@ -203,8 +197,6 @@ void ui_refresh(void)
|
||||
end)
|
||||
|
||||
it('support query and iter by match', function()
|
||||
if pending_c_parser(pending) then return end
|
||||
|
||||
insert(test_text)
|
||||
|
||||
local res = exec_lua([[
|
||||
@ -236,8 +228,6 @@ void ui_refresh(void)
|
||||
end)
|
||||
|
||||
it('can match special regex characters like \\ * + ( with `vim-match?`', function()
|
||||
if pending_c_parser(pending) then return end
|
||||
|
||||
insert('char* astring = "\\n"; (1 + 1) * 2 != 2;')
|
||||
|
||||
local res = exec_lua([[
|
||||
@ -271,8 +261,6 @@ void ui_refresh(void)
|
||||
end)
|
||||
|
||||
it('supports builtin query predicate any-of?', function()
|
||||
if pending_c_parser(pending) then return end
|
||||
|
||||
insert([[
|
||||
#include <stdio.h>
|
||||
|
||||
@ -330,8 +318,6 @@ void ui_refresh(void)
|
||||
end)
|
||||
|
||||
it('allow loading query with escaped quotes and capture them with `lua-match?` and `vim-match?`', function()
|
||||
if pending_c_parser(pending) then return end
|
||||
|
||||
insert('char* astring = "Hello World!";')
|
||||
|
||||
local res = exec_lua([[
|
||||
@ -407,8 +393,6 @@ void ui_refresh(void)
|
||||
|
||||
|
||||
it('allows to set simple ranges', function()
|
||||
if pending_c_parser(pending) then return end
|
||||
|
||||
insert(test_text)
|
||||
|
||||
local res = exec_lua [[
|
||||
@ -450,8 +434,6 @@ void ui_refresh(void)
|
||||
eq(range_tbl, { { { 0, 0, 0, 17, 1, 508 } } })
|
||||
end)
|
||||
it("allows to set complex ranges", function()
|
||||
if pending_c_parser() then return end
|
||||
|
||||
insert(test_text)
|
||||
|
||||
local res = exec_lua [[
|
||||
|
@ -697,4 +697,50 @@ end]]
|
||||
|
|
||||
]]}
|
||||
end)
|
||||
it('does not crash when deleting a cleared buffer #15212', function()
|
||||
exec_lua [[
|
||||
ns = vim.api.nvim_create_namespace("myplugin")
|
||||
vim.api.nvim_buf_set_extmark(0, ns, 0, 0, {virt_text = {{"a"}}, end_col = 0})
|
||||
]]
|
||||
screen:expect{grid=[[
|
||||
^ a |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
|
|
||||
]]}
|
||||
|
||||
exec_lua [[
|
||||
vim.api.nvim_buf_clear_namespace(0, ns, 0, -1)
|
||||
vim.cmd("bdelete")
|
||||
]]
|
||||
screen:expect{grid=[[
|
||||
^ |
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
{1:~ }|
|
||||
|
|
||||
]]}
|
||||
helpers.assert_alive()
|
||||
end)
|
||||
end)
|
||||
|
@ -152,6 +152,132 @@ describe('float window', function()
|
||||
eq(10, width)
|
||||
end)
|
||||
|
||||
it('opened with correct position', function()
|
||||
local pos = exec_lua([[
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
|
||||
local opts = {
|
||||
width = 10,
|
||||
height = 10,
|
||||
col = 7,
|
||||
row = 9,
|
||||
relative = 'editor',
|
||||
style = 'minimal'
|
||||
}
|
||||
|
||||
local win_id = vim.api.nvim_open_win(bufnr, false, opts)
|
||||
|
||||
return vim.api.nvim_win_get_position(win_id)
|
||||
]])
|
||||
|
||||
eq(9, pos[1])
|
||||
eq(7, pos[2])
|
||||
end)
|
||||
|
||||
it('opened with correct position relative to the cursor', function()
|
||||
local pos = exec_lua([[
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
|
||||
local opts = {
|
||||
width = 10,
|
||||
height = 10,
|
||||
col = 7,
|
||||
row = 9,
|
||||
relative = 'cursor',
|
||||
style = 'minimal'
|
||||
}
|
||||
|
||||
local win_id = vim.api.nvim_open_win(bufnr, false, opts)
|
||||
|
||||
return vim.api.nvim_win_get_position(win_id)
|
||||
]])
|
||||
|
||||
eq(9, pos[1])
|
||||
eq(7, pos[2])
|
||||
end)
|
||||
|
||||
it('opened with correct position relative to another window', function()
|
||||
local pos = exec_lua([[
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
|
||||
local par_opts = {
|
||||
width = 50,
|
||||
height = 50,
|
||||
col = 7,
|
||||
row = 9,
|
||||
relative = 'editor',
|
||||
style = 'minimal'
|
||||
}
|
||||
|
||||
local par_win_id = vim.api.nvim_open_win(bufnr, false, par_opts)
|
||||
|
||||
local opts = {
|
||||
width = 10,
|
||||
height = 10,
|
||||
col = 7,
|
||||
row = 9,
|
||||
relative = 'win',
|
||||
style = 'minimal',
|
||||
win = par_win_id
|
||||
}
|
||||
|
||||
local win_id = vim.api.nvim_open_win(bufnr, false, opts)
|
||||
|
||||
return vim.api.nvim_win_get_position(win_id)
|
||||
]])
|
||||
|
||||
eq(18, pos[1])
|
||||
eq(14, pos[2])
|
||||
end)
|
||||
|
||||
|
||||
it('opened with correct position relative to another relative window', function()
|
||||
local pos = exec_lua([[
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
|
||||
local root_opts = {
|
||||
width = 50,
|
||||
height = 50,
|
||||
col = 7,
|
||||
row = 9,
|
||||
relative = 'editor',
|
||||
style = 'minimal'
|
||||
}
|
||||
|
||||
local root_win_id = vim.api.nvim_open_win(bufnr, false, root_opts)
|
||||
|
||||
local par_opts = {
|
||||
width = 20,
|
||||
height = 20,
|
||||
col = 2,
|
||||
row = 3,
|
||||
relative = 'win',
|
||||
win = root_win_id,
|
||||
style = 'minimal'
|
||||
}
|
||||
|
||||
local par_win_id = vim.api.nvim_open_win(bufnr, false, par_opts)
|
||||
|
||||
local opts = {
|
||||
width = 10,
|
||||
height = 10,
|
||||
col = 3,
|
||||
row = 2,
|
||||
relative = 'win',
|
||||
win = par_win_id,
|
||||
style = 'minimal'
|
||||
}
|
||||
|
||||
local win_id = vim.api.nvim_open_win(bufnr, false, opts)
|
||||
|
||||
return vim.api.nvim_win_get_position(win_id)
|
||||
]])
|
||||
|
||||
eq(14, pos[1])
|
||||
eq(12, pos[2])
|
||||
end)
|
||||
|
||||
|
||||
local function with_ext_multigrid(multigrid)
|
||||
local screen
|
||||
before_each(function()
|
||||
@ -620,6 +746,134 @@ describe('float window', function()
|
||||
end
|
||||
end)
|
||||
|
||||
it("would not break 'minimal' style with signcolumn=auto:[min]-[max]", function()
|
||||
command('set number')
|
||||
command('set signcolumn=auto:1-3')
|
||||
command('set colorcolumn=1')
|
||||
command('set cursorline')
|
||||
command('set foldcolumn=1')
|
||||
command('hi NormalFloat guibg=#333333')
|
||||
feed('ix<cr>y<cr><esc>gg')
|
||||
local win = meths.open_win(0, false, {relative='editor', width=20, height=4, row=4, col=10, style='minimal'})
|
||||
if multigrid then
|
||||
screen:expect{grid=[[
|
||||
## grid 1
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[3:----------------------------------------]|
|
||||
## grid 2
|
||||
{19: }{20: 1 }{22:^x}{21: }|
|
||||
{19: }{14: 2 }{22:y} |
|
||||
{19: }{14: 3 }{22: } |
|
||||
{0:~ }|
|
||||
{0:~ }|
|
||||
{0:~ }|
|
||||
## grid 3
|
||||
|
|
||||
## grid 4
|
||||
{15:x }|
|
||||
{15:y }|
|
||||
{15: }|
|
||||
{15: }|
|
||||
]], float_pos={[4] = {{id = 1001}, "NW", 1, 4, 10, true}}}
|
||||
else
|
||||
screen:expect{grid=[[
|
||||
{19: }{20: 1 }{22:^x}{21: }|
|
||||
{19: }{14: 2 }{22:y} |
|
||||
{19: }{14: 3 }{22: } {15:x } |
|
||||
{0:~ }{15:y }{0: }|
|
||||
{0:~ }{15: }{0: }|
|
||||
{0:~ }{15: }{0: }|
|
||||
|
|
||||
]]}
|
||||
end
|
||||
|
||||
command('sign define piet1 text=𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄ texthl=Search')
|
||||
command('sign place 1 line=1 name=piet1 buffer=1')
|
||||
-- signcolumn=auto:1-3 still works if there actually are signs
|
||||
if multigrid then
|
||||
screen:expect{grid=[[
|
||||
## grid 1
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[3:----------------------------------------]|
|
||||
## grid 2
|
||||
{19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }|
|
||||
{19: }{14: 2 }{22:y} |
|
||||
{19: }{14: 3 }{22: } |
|
||||
{0:~ }|
|
||||
{0:~ }|
|
||||
{0:~ }|
|
||||
## grid 3
|
||||
|
|
||||
## grid 4
|
||||
{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x }|
|
||||
{19: }{15:y }|
|
||||
{19: }{15: }|
|
||||
{15: }|
|
||||
]], float_pos={[4] = {{id = 1001}, "NW", 1, 4, 10, true}}}
|
||||
|
||||
else
|
||||
screen:expect([[
|
||||
{19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }|
|
||||
{19: }{14: 2 }{22:y} |
|
||||
{19: }{14: 3 }{22: } {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } |
|
||||
{0:~ }{19: }{15:y }{0: }|
|
||||
{0:~ }{19: }{15: }{0: }|
|
||||
{0:~ }{15: }{0: }|
|
||||
|
|
||||
]])
|
||||
end
|
||||
command('sign unplace 1 buffer=1')
|
||||
|
||||
local buf = meths.create_buf(false, true)
|
||||
meths.win_set_buf(win, buf)
|
||||
if multigrid then
|
||||
screen:expect{grid=[[
|
||||
## grid 1
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[2:----------------------------------------]|
|
||||
[3:----------------------------------------]|
|
||||
## grid 2
|
||||
{19: }{20: 1 }{22:^x}{21: }|
|
||||
{19: }{14: 2 }{22:y} |
|
||||
{19: }{14: 3 }{22: } |
|
||||
{0:~ }|
|
||||
{0:~ }|
|
||||
{0:~ }|
|
||||
## grid 3
|
||||
|
|
||||
## grid 4
|
||||
{15: }|
|
||||
{15: }|
|
||||
{15: }|
|
||||
{15: }|
|
||||
]], float_pos={[4] = {{id = 1001}, "NW", 1, 4, 10, true}}}
|
||||
else
|
||||
screen:expect([[
|
||||
{19: }{20: 1 }{22:^x}{21: }|
|
||||
{19: }{14: 2 }{22:y} |
|
||||
{19: }{14: 3 }{22: } {15: } |
|
||||
{0:~ }{15: }{0: }|
|
||||
{0:~ }{15: }{0: }|
|
||||
{0:~ }{15: }{0: }|
|
||||
|
|
||||
]])
|
||||
end
|
||||
end)
|
||||
|
||||
it('can have border', function()
|
||||
local buf = meths.create_buf(false, false)
|
||||
meths.buf_set_lines(buf, 0, -1, true, {' halloj! ',
|
||||
|
@ -1487,6 +1487,29 @@ describe("inccommand=nosplit", function()
|
||||
]])
|
||||
eq(eval('v:null'), eval('v:exiting'))
|
||||
end)
|
||||
|
||||
it("does not break bar-separated command #8796", function()
|
||||
source([[
|
||||
function! F()
|
||||
if v:false | return | endif
|
||||
endfun
|
||||
]])
|
||||
command('call timer_start(10, {-> F()}, {"repeat":-1})')
|
||||
feed(':%s/')
|
||||
sleep(20) -- Allow some timer activity.
|
||||
screen:expect([[
|
||||
Inc substitution on |
|
||||
two lines |
|
||||
Inc substitution on |
|
||||
two lines |
|
||||
|
|
||||
{15:~ }|
|
||||
{15:~ }|
|
||||
{15:~ }|
|
||||
{15:~ }|
|
||||
:%s/^ |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe(":substitute, 'inccommand' with a failing expression", function()
|
||||
|
Reference in New Issue
Block a user