aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.luacheckrc2
-rw-r--r--runtime/autoload/lsp.vim45
-rw-r--r--runtime/autoload/man.vim10
-rw-r--r--runtime/doc/api.txt119
-rw-r--r--runtime/doc/change.txt11
-rw-r--r--runtime/doc/develop.txt4
-rw-r--r--runtime/doc/eval.txt8
-rw-r--r--runtime/doc/filetype.txt11
-rw-r--r--runtime/doc/index.txt6
-rw-r--r--runtime/doc/intro.txt2
-rw-r--r--runtime/doc/lsp.txt295
-rw-r--r--runtime/doc/lua.txt15
-rw-r--r--runtime/doc/map.txt2
-rw-r--r--runtime/doc/message.txt4
-rw-r--r--runtime/doc/motion.txt52
-rw-r--r--runtime/doc/starting.txt2
-rw-r--r--runtime/doc/ui.txt14
-rw-r--r--runtime/doc/visual.txt8
-rw-r--r--runtime/ftplugin/man.vim2
-rw-r--r--runtime/lua/vim/lsp.lua200
-rw-r--r--runtime/lua/vim/lsp/buf.lua136
-rw-r--r--runtime/lua/vim/lsp/builtin_callbacks.lua296
-rw-r--r--runtime/lua/vim/lsp/callbacks.lua223
-rw-r--r--runtime/lua/vim/lsp/protocol.lua14
-rw-r--r--runtime/lua/vim/lsp/rpc.lua1
-rw-r--r--runtime/lua/vim/lsp/util.lua389
-rw-r--r--runtime/lua/vim/shared.lua28
-rw-r--r--runtime/lua/vim/uri.lua7
-rw-r--r--runtime/nvim.appdata.xml1
-rw-r--r--runtime/plugin/man.vim2
-rw-r--r--runtime/syntax/man.vim3
-rwxr-xr-xscripts/release.sh1
-rw-r--r--src/nvim/CMakeLists.txt12
-rw-r--r--src/nvim/api/buffer.c141
-rw-r--r--src/nvim/api/private/helpers.c26
-rw-r--r--src/nvim/api/vim.c2
-rw-r--r--src/nvim/buffer.c8
-rw-r--r--src/nvim/ex_cmds.c2
-rw-r--r--src/nvim/ex_docmd.c24
-rw-r--r--src/nvim/ex_getln.c17
-rw-r--r--src/nvim/fileio.c3
-rw-r--r--src/nvim/generators/gen_char_blob.lua70
-rw-r--r--src/nvim/lua/executor.c25
-rw-r--r--src/nvim/lua/vim.lua229
-rw-r--r--src/nvim/main.c2
-rw-r--r--src/nvim/memline.c5
-rw-r--r--src/nvim/mouse.c29
-rw-r--r--src/nvim/normal.c26
-rw-r--r--src/nvim/ops.c3
-rw-r--r--src/nvim/option.c7
-rw-r--r--src/nvim/po/af.po2
-rw-r--r--src/nvim/po/fi.po2
-rw-r--r--src/nvim/po/uk.po4
-rw-r--r--src/nvim/quickfix.c467
-rw-r--r--src/nvim/state.c2
-rw-r--r--src/nvim/testdir/test_cmdline.vim28
-rw-r--r--src/nvim/testdir/test_ins_complete.vim18
-rw-r--r--test/functional/api/mark_extended_spec.lua83
-rw-r--r--test/functional/api/proc_spec.lua2
-rw-r--r--test/functional/cmdline/ctrl_r_spec.lua2
-rw-r--r--test/functional/lua/vim_spec.lua71
-rw-r--r--test/functional/normal/put_spec.lua6
-rw-r--r--test/functional/plugin/lsp/util_spec.lua76
-rw-r--r--test/functional/ui/options_spec.lua29
-rw-r--r--test/helpers.lua3
65 files changed, 1860 insertions, 1479 deletions
diff --git a/.luacheckrc b/.luacheckrc
index b945835bba..9c8bddb88e 100644
--- a/.luacheckrc
+++ b/.luacheckrc
@@ -2,6 +2,8 @@
-- Ignore W211 (unused variable) with preload files.
files["**/preload.lua"] = {ignore = { "211" }}
+-- Allow vim module to modify itself, but only here.
+files["src/nvim/lua/vim.lua"] = {ignore = { "122/vim" }}
-- Don't report unused self arguments of methods.
self = false
diff --git a/runtime/autoload/lsp.vim b/runtime/autoload/lsp.vim
deleted file mode 100644
index 4c8f8b396a..0000000000
--- a/runtime/autoload/lsp.vim
+++ /dev/null
@@ -1,45 +0,0 @@
-function! lsp#add_filetype_config(config) abort
- call luaeval('vim.lsp.add_filetype_config(_A)', a:config)
-endfunction
-
-function! lsp#set_log_level(level) abort
- call luaeval('vim.lsp.set_log_level(_A)', a:level)
-endfunction
-
-function! lsp#get_log_path() abort
- return luaeval('vim.lsp.get_log_path()')
-endfunction
-
-function! lsp#omnifunc(findstart, base) abort
- return luaeval("vim.lsp.omnifunc(_A[1], _A[2])", [a:findstart, a:base])
-endfunction
-
-function! lsp#text_document_hover() abort
- lua vim.lsp.buf_request(nil, 'textDocument/hover', vim.lsp.protocol.make_text_document_position_params())
- return ''
-endfunction
-
-function! lsp#text_document_declaration() abort
- lua vim.lsp.buf_request(nil, 'textDocument/declaration', vim.lsp.protocol.make_text_document_position_params())
- return ''
-endfunction
-
-function! lsp#text_document_definition() abort
- lua vim.lsp.buf_request(nil, 'textDocument/definition', vim.lsp.protocol.make_text_document_position_params())
- return ''
-endfunction
-
-function! lsp#text_document_signature_help() abort
- lua vim.lsp.buf_request(nil, 'textDocument/signatureHelp', vim.lsp.protocol.make_text_document_position_params())
- return ''
-endfunction
-
-function! lsp#text_document_type_definition() abort
- lua vim.lsp.buf_request(nil, 'textDocument/typeDefinition', vim.lsp.protocol.make_text_document_position_params())
- return ''
-endfunction
-
-function! lsp#text_document_implementation() abort
- lua vim.lsp.buf_request(nil, 'textDocument/implementation', vim.lsp.protocol.make_text_document_position_params())
- return ''
-endfunction
diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim
index 36f42c0003..809e4a19d8 100644
--- a/runtime/autoload/man.vim
+++ b/runtime/autoload/man.vim
@@ -1,4 +1,4 @@
-" Maintainer: Anmol Sethi <anmol@aubble.com>
+" Maintainer: Anmol Sethi <hi@nhooyr.io>
if exists('s:loaded_man')
finish
@@ -139,7 +139,7 @@ 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/….
" Hard-wrap: driven by `man`.
- let manwidth = !get(g:,'man_hardwrap') ? 999 : (empty($MANWIDTH) ? winwidth(0) : $MANWIDTH)
+ let manwidth = !get(g:,'man_hardwrap', 1) ? 999 : (empty($MANWIDTH) ? winwidth(0) : $MANWIDTH)
" Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db).
" http://comments.gmane.org/gmane.editors.vim.devel/29085
" Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces.
@@ -357,6 +357,10 @@ function! s:format_candidate(path, psect) abort
endfunction
function! man#init_pager() abort
+ " https://github.com/neovim/neovim/issues/6828
+ let og_modifiable = &modifiable
+ setlocal modifiable
+
if getline(1) =~# '^\s*$'
silent keepjumps 1delete _
else
@@ -374,6 +378,8 @@ function! man#init_pager() abort
if -1 == match(bufname('%'), 'man:\/\/') " Avoid duplicate buffers, E95.
execute 'silent file man://'.tolower(fnameescape(ref))
endif
+
+ let &l:modifiable = og_modifiable
endfunction
function! man#goto_tag(pattern, flags, info) abort
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index f97795b0ee..0d040c154b 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -19,6 +19,7 @@ API Usage *api-rpc* *RPC* *rpc*
*msgpack-rpc*
RPC is the typical way to control Nvim programmatically. Nvim implements the
MessagePack-RPC protocol:
+ https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md
https://github.com/msgpack/msgpack/blob/0b8f5ac/spec.md
Many clients use the API: user interfaces (GUIs), remote plugins, scripts like
@@ -935,8 +936,8 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
'list' options. 'signcolumn' is changed to
`auto` and 'colorcolumn' is cleared. The
end-of-buffer region is hidden by setting
- `eob` flag of 'fillchars' to a space char, and
- clearing the |EndOfBuffer| region in
+ `eob` flag of 'fillchars' to a space char,
+ and clearing the |EndOfBuffer| region in
'winhighlight'.
Return: ~
@@ -1022,7 +1023,7 @@ nvim_put({lines}, {type}, {after}, {follow}) *nvim_put()*
{type} Edit behavior: any |getregtype()| result, or:
• "b" |blockwise-visual| mode (may include
width, e.g. "b3")
- • "c" |characterwise| mode
+ • "c" |charwise| mode
• "l" |linewise| mode
• "" guess by contents, see |setreg()|
{after} Insert after cursor (like |p|), or before (like
@@ -1797,81 +1798,93 @@ nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id})
Returns position for a given extmark id
Parameters: ~
- {buffer} The buffer handle
- {namespace} a identifier returned previously with
- nvim_create_namespace
- {id} the extmark id
+ {buffer} Buffer handle, or 0 for current buffer
+ {ns_id} Namespace id from |nvim_create_namespace()|
+ {id} Extmark id
Return: ~
(row, col) tuple or empty list () if extmark id was absent
*nvim_buf_get_extmarks()*
nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts})
- List extmarks in a range (inclusive)
-
- range ends can be specified as (row, col) tuples, as well as
- extmark ids in the same namespace. In addition, 0 and -1 works
- as shorthands for (0,0) and (-1,-1) respectively, so that all
- marks in the buffer can be queried as:
+ Gets extmarks in "traversal order" from a |charwise| region
+ defined by buffer positions (inclusive, 0-indexed
+ |api-indexing|).
+
+ Region can be given as (row,col) tuples, or valid extmark ids
+ (whose positions define the bounds). 0 and -1 are understood
+ as (0,0) and (-1,-1) respectively, thus the following are
+ equivalent:
+>
+ nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+ nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {})
+<
- all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+ If `end` is less than `start` , traversal works backwards.
+ (Useful with `limit` , to get the first marks prior to a given
+ position.)
- If end is a lower position than start, then the range will be
- traversed backwards. This is mostly useful with limited
- amount, to be able to get the first marks prior to a given
- position.
+ Example:
+>
+ local a = vim.api
+ local pos = a.nvim_win_get_cursor(0)
+ local ns = a.nvim_create_namespace('my-plugin')
+ -- Create new extmark at line 1, column 1.
+ local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {})
+ -- Create new extmark at line 3, column 1.
+ local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {})
+ -- Get extmarks only from line 3.
+ local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
+ -- Get all marks in this buffer + namespace.
+ local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {})
+ print(vim.inspect(ms))
+<
Parameters: ~
- {buffer} The buffer handle
- {ns_id} An id returned previously from
- nvim_create_namespace
- {start} One of: extmark id, (row, col) or 0, -1 for
- buffer ends
- {end} One of: extmark id, (row, col) or 0, -1 for
- buffer ends
- {opts} additional options. Supports the keys:
- • amount: Maximum number of marks to return
+ {buffer} Buffer handle, or 0 for current buffer
+ {ns_id} Namespace id from |nvim_create_namespace()|
+ {start} Start of range, given as (row, col) or valid
+ extmark id (whose position defines the bound)
+ {end} End of range, given as (row, col) or valid
+ extmark id (whose position defines the bound)
+ {opts} Optional parameters. Keys:
+ • limit: Maximum number of marks to return
Return: ~
- [[extmark_id, row, col], ...]
+ List of [extmark_id, row, col] tuples in "traversal
+ order".
*nvim_buf_set_extmark()*
nvim_buf_set_extmark({buffer}, {ns_id}, {id}, {line}, {col}, {opts})
- Create or update an extmark at a position
-
- If an invalid namespace is given, an error will be raised.
-
- To create a new extmark, pass in id=0. The new extmark id will
- be returned. To move an existing mark, pass in its id.
+ Creates or updates an extmark.
- It is also allowed to create a new mark by passing in a
- previously unused id, but the caller must then keep track of
- existing and unused ids itself. This is mainly useful over
- RPC, to avoid needing to wait for the return value.
+ To create a new extmark, pass id=0. The extmark id will be
+ returned. It is also allowed to create a new mark by passing
+ in a previously unused id, but the caller must then keep track
+ of existing and unused ids itself. (Useful over RPC, to avoid
+ waiting for the return value.)
Parameters: ~
- {buffer} The buffer handle
- {ns_id} a identifier returned previously with
- nvim_create_namespace
- {id} The extmark's id or 0 to create a new mark.
- {line} The row to set the extmark to.
- {col} The column to set the extmark to.
+ {buffer} Buffer handle, or 0 for current buffer
+ {ns_id} Namespace id from |nvim_create_namespace()|
+ {id} Extmark id, or 0 to create new
+ {line} Line number where to place the mark
+ {col} Column where to place the mark
{opts} Optional parameters. Currently not used.
Return: ~
- the id of the extmark.
+ Id of the created/updated extmark
nvim_buf_del_extmark({buffer}, {ns_id}, {id}) *nvim_buf_del_extmark()*
- Remove an extmark
+ Removes an extmark.
Parameters: ~
- {buffer} The buffer handle
- {ns_id} a identifier returned previously with
- nvim_create_namespace
- {id} The extmarks's id
+ {buffer} Buffer handle, or 0 for current buffer
+ {ns_id} Namespace id from |nvim_create_namespace()|
+ {id} Extmark id
Return: ~
- true on success, false if the extmark was not found.
+ true if the extmark was found, else false
*nvim_buf_add_highlight()*
nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line},
@@ -1916,8 +1929,8 @@ nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line},
*nvim_buf_clear_namespace()*
nvim_buf_clear_namespace({buffer}, {ns_id}, {line_start}, {line_end})
- Clears namespaced objects, highlights and virtual text, from a
- line range
+ Clears namespaced objects (highlights, extmarks, virtual text)
+ from a region.
Lines are 0-indexed. |api-indexing| To clear the namespace in
the entire buffer, specify line_start=0 and line_end=-1.
diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt
index bd3f22a371..dcebbc524c 100644
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -90,7 +90,7 @@ start and end of the motion are not in the same line, and there are only
blanks before the start and there are no non-blanks after the end of the
motion, the delete becomes linewise. This means that the delete also removes
the line of blanks that you might expect to remain. Use the |o_v| operator to
-force the motion to be characterwise.
+force the motion to be charwise.
Trying to delete an empty region of text (e.g., "d0" in the first column)
is an error when 'cpoptions' includes the 'E' flag.
@@ -1074,7 +1074,7 @@ also use these commands to move text from one file to another, because Vim
preserves all registers when changing buffers (the CTRL-^ command is a quick
way to toggle between two files).
- *linewise-register* *characterwise-register*
+ *linewise-register* *charwise-register*
You can repeat the put commands with "." (except for :put) and undo them. If
the command that was used to get the text into the register was |linewise|,
Vim inserts the text below ("p") or above ("P") the line where the cursor is.
@@ -1116,10 +1116,9 @@ this happen. However, if the width of the block is not a multiple of a <Tab>
width and the text after the inserted block contains <Tab>s, that text may be
misaligned.
-Note that after a characterwise yank command, Vim leaves the cursor on the
-first yanked character that is closest to the start of the buffer. This means
-that "yl" doesn't move the cursor, but "yh" moves the cursor one character
-left.
+Note that after a charwise yank command, Vim leaves the cursor on the first
+yanked character that is closest to the start of the buffer. This means that
+"yl" doesn't move the cursor, but "yh" moves the cursor one character left.
Rationale: In Vi the "y" command followed by a backwards motion would
sometimes not move the cursor to the first yanked character,
because redisplaying was skipped. In Vim it always moves to
diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt
index ba887a83c8..4c1430ab1f 100644
--- a/runtime/doc/develop.txt
+++ b/runtime/doc/develop.txt
@@ -349,8 +349,8 @@ External UIs are expected to implement these common features:
chords (<C-,> <C-Enter> <C-S-x> <D-x>) and patterns ("shift shift") that do
not potentially conflict with Nvim defaults, plugins, etc.
- Consider the "option_set" |ui-global| event as a hint for other GUI
- behaviors. UI-related options ('guifont', 'ambiwidth', …) are published in
- this event.
+ behaviors. Various UI-related options ('guifont', 'ambiwidth', …) are
+ published in this event. See also "mouse_on", "mouse_off".
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 84a893a205..f39c40492b 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -4727,7 +4727,7 @@ getreg([{regname} [, 1 [, {list}]]]) *getreg()*
getregtype([{regname}]) *getregtype()*
The result is a String, which is type of register {regname}.
The value will be one of:
- "v" for |characterwise| text
+ "v" for |charwise| text
"V" for |linewise| text
"<CTRL-V>{width}" for |blockwise-visual| text
"" for an empty or unknown register
@@ -6131,7 +6131,7 @@ mode([expr]) Return a string that indicates the current mode.
n Normal
no Operator-pending
- nov Operator-pending (forced characterwise |o_v|)
+ nov Operator-pending (forced charwise |o_v|)
noV Operator-pending (forced linewise |o_V|)
noCTRL-V Operator-pending (forced blockwise |o_CTRL-V|)
niI Normal using |i_CTRL-O| in |Insert-mode|
@@ -7441,7 +7441,7 @@ setreg({regname}, {value} [, {options}])
If {options} contains "a" or {regname} is upper case,
then the value is appended.
{options} can also contain a register type specification:
- "c" or "v" |characterwise| mode
+ "c" or "v" |charwise| mode
"l" or "V" |linewise| mode
"b" or "<CTRL-V>" |blockwise-visual| mode
If a number immediately follows "b" or "<CTRL-V>" then this is
@@ -9708,7 +9708,7 @@ This does NOT work: >
register, "@/" for the search pattern.
If the result of {expr1} ends in a <CR> or <NL>, the
register will be linewise, otherwise it will be set to
- characterwise.
+ charwise.
This can be used to clear the last search pattern: >
:let @/ = ""
< This is different from searching for an empty string,
diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt
index c579c390c6..c649688d99 100644
--- a/runtime/doc/filetype.txt
+++ b/runtime/doc/filetype.txt
@@ -549,7 +549,9 @@ Variables:
*b:man_default_sects* Comma-separated, ordered list of preferred sections.
For example in C one usually wants section 3 or 2: >
:let b:man_default_sections = '3,2'
-*g:man_hardwrap* Hard-wrap to $MANWIDTH. May improve layout.
+*g:man_hardwrap* Hard-wrap to $MANWIDTH or window width if $MANWIDTH is
+ empty. Enabled by default. Set |FALSE| to enable soft
+ wrapping.
To use Nvim as a manpager: >
export MANPAGER='nvim +Man!'
@@ -558,10 +560,13 @@ Note that when running `man` from the shell and with that `MANPAGER` in your
environment, `man` will pre-format the manpage using `groff`. Thus, Neovim
will inevitably display the manual page as it was passed to it from stdin. One
of the caveats of this is that the width will _always_ be hard-wrapped and not
-soft wrapped as with `:Man`. You can set in your environment: >
+soft wrapped as with `g:man_hardwrap=0`. You can set in your environment: >
export MANWIDTH=999
-So `groff`'s pre-formatting output will be the same as with `:Man` i.e soft-wrapped.
+So `groff`'s pre-formatting output will be the same as with `g:man_hardwrap=0` i.e soft-wrapped.
+
+To disable bold highlighting: >
+ :highlight link manBold Normal
PDF *ft-pdf-plugin*
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index 211b7be2cc..ed9853a8da 100644
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -404,7 +404,7 @@ tag char note action in Normal mode ~
|t| t{char} 1 cursor till before Nth occurrence of {char}
to the right
|u| u 2 undo changes
-|v| v start characterwise Visual mode
+|v| v start charwise Visual mode
|w| w 1 cursor N words forward
|x| ["x]x 2 delete N characters under and after the
cursor [into register x]
@@ -866,7 +866,7 @@ These can be used after an operator, but before a {motion} has been entered.
tag char action in Operator-pending mode ~
-----------------------------------------------------------------------
-|o_v| v force operator to work characterwise
+|o_v| v force operator to work charwise
|o_V| V force operator to work linewise
|o_CTRL-V| CTRL-V force operator to work blockwise
@@ -978,7 +978,7 @@ tag command note action in Visual mode ~
|v_r| r 2 replace highlighted area with a character
|v_s| s 2 delete highlighted area and start insert
|v_u| u 2 make highlighted area lowercase
-|v_v| v make Visual mode characterwise or stop
+|v_v| v make Visual mode charwise or stop
Visual mode
|v_x| x 2 delete the highlighted area
|v_y| y yank the highlighted area
diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt
index 3292489eda..c59ed43a47 100644
--- a/runtime/doc/intro.txt
+++ b/runtime/doc/intro.txt
@@ -271,7 +271,7 @@ and <> are part of what you type, the context should make this clear.
operator is pending.
- Ex commands can be used to move the cursor. This can be
used to call a function that does some complicated motion.
- The motion is always characterwise exclusive, no matter
+ The motion is always charwise exclusive, no matter
what ":" command is used. This means it's impossible to
include the last character of a line without the line break
(unless 'virtualedit' is set).
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 26850b3683..c4c164ab6c 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -1,18 +1,18 @@
-*lsp.txt* The Language Server Protocol
+*lsp.txt* Nvim LSP API
- NVIM REFERENCE MANUAL
+ NVIM REFERENCE MANUAL
-Neovim Language Server Protocol (LSP) API
+Nvim Language Server Protocol (LSP) API *lsp*
-Neovim exposes a powerful API that conforms to Microsoft's published Language
-Server Protocol specification. The documentation can be found here:
+Nvim is a client to the Language Server Protocol:
https://microsoft.github.io/language-server-protocol/
+ Type |gO| to see the table of contents.
================================================================================
- *lsp-api*
+LSP API *lsp-api*
Neovim exposes a API for the language server protocol. To get the real benefits
of this API, a language server must be installed.
@@ -24,106 +24,15 @@ After installing a language server to your machine, you must let Neovim know
how to start and interact with that language server.
To do so, you can either:
-- Use the |vim.lsp.add_filetype_config()|, which solves the common use-case of
- a single server for one or more filetypes. This can also be used from vim
- via |lsp#add_filetype_config()|.
+- Use https://github.com/neovim/nvim-lsp and one of the existing servers there
+ or set up a new one using the `nvim_lsp/skeleton` interface (and contribute
+ it if you find it useful). This uses |vim.lsp.start_client()| under the
+ hood.
- Or |vim.lsp.start_client()| and |vim.lsp.buf_attach_client()|. These are the
backbone of the LSP API. These are easy to use enough for basic or more
complex configurations such as in |lsp-advanced-js-example|.
================================================================================
- *lsp-filetype-config*
-
-These are utilities specific to filetype based configurations.
-
- *lsp#add_filetype_config()*
- *vim.lsp.add_filetype_config()*
-lsp#add_filetype_config({config}) for Vim.
-vim.lsp.add_filetype_config({config}) for Lua
-
- These are functions which can be used to create a simple configuration which
- will start a language server for a list of filetypes based on the |FileType|
- event.
- It will lazily start start the server, meaning that it will only start once
- a matching filetype is encountered.
-
- The {config} options are the same as |vim.lsp.start_client()|, but
- with a few additions and distinctions:
-
- Additional parameters:~
- `filetype`
- {string} or {list} of filetypes to attach to.
- `name`
- A unique identifying string among all other servers configured with
- |vim.lsp.add_filetype_config|.
-
- Differences:~
- `root_dir`
- Will default to |getcwd()| instead of being required.
-
- NOTE: the function options in {config} like {config.on_init} are for Lua
- callbacks, not Vim callbacks.
->
- " Go example
- call lsp#add_filetype_config({
- \ 'filetype': 'go',
- \ 'name': 'gopls',
- \ 'cmd': 'gopls'
- \ })
- " Python example
- call lsp#add_filetype_config({
- \ 'filetype': 'python',
- \ 'name': 'pyls',
- \ 'cmd': 'pyls'
- \ })
- " Rust example
- call lsp#add_filetype_config({
- \ 'filetype': 'rust',
- \ 'name': 'rls',
- \ 'cmd': 'rls',
- \ 'capabilities': {
- \ 'clippy_preference': 'on',
- \ 'all_targets': v:false,
- \ 'build_on_save': v:true,
- \ 'wait_to_build': 0
- \ }})
-<
->
- -- From Lua
- vim.lsp.add_filetype_config {
- name = "clangd";
- filetype = {"c", "cpp"};
- cmd = "clangd -background-index";
- capabilities = {
- offsetEncoding = {"utf-8", "utf-16"};
- };
- on_init = vim.schedule_wrap(function(client, result)
- if result.offsetEncoding then
- client.offset_encoding = result.offsetEncoding
- end
- end)
- }
-<
- *vim.lsp.copy_filetype_config()*
-vim.lsp.copy_filetype_config({existing_name}, [{override_config}])
-
- You can use this to copy an existing filetype configuration and change it by
- specifying {override_config} which will override any properties in the
- existing configuration. If you don't specify a new unique name with
- {override_config.name} then it will try to create one and return it.
-
- Returns:~
- `name` the new configuration name.
-
- *vim.lsp.get_filetype_client_by_name()*
-vim.lsp.get_filetype_client_by_name({name})
-
- Use this to look up a client by its name created from
- |vim.lsp.add_filetype_config()|.
-
- Returns nil if the client is not active or the name is not valid.
-
-================================================================================
*lsp-core-api*
These are the core api functions for working with clients. You will mainly be
using |vim.lsp.start_client()| and |vim.lsp.buf_attach_client()| for operations
@@ -178,8 +87,8 @@ vim.lsp.start_client({config})
`callbacks`
A {table} of whose keys are language server method names and the values
are `function(err, method, params, client_id)` See |lsp-callbacks| for
- more. This will be combined with |lsp-builtin-callbacks| to provide
- defaults.
+ more. This will be combined with |lsp-default-callbacks| to resolve
+ the callbacks for a client as a fallback.
`init_options`
A {table} of values to pass in the initialization request as
@@ -203,6 +112,12 @@ vim.lsp.start_client({config})
`vim.lsp.client_errors[code]` can be used to retrieve a human
understandable string.
+ `before_init(initialize_params, config)`
+ A function which is called *before* the request `initialize` is completed.
+ `initialize_params` contains the parameters we are sending to the server
+ and `config` is the config that was passed to `start_client()` for
+ convenience. You can use this to modify parameters before they are sent.
+
`on_init(client, initialize_result)`
A function which is called after the request `initialize` is completed.
`initialize_result` contains `capabilities` and anything else the server
@@ -346,75 +261,81 @@ vim.lsp.rpc_response_error({code}, [{message}], [{data}])
the server.
================================================================================
- *vim.lsp.builtin_callbacks*
-
-The |vim.lsp.builtin_callbacks| table contains the default |lsp-callbacks|
-that are used when creating a new client. The keys are the LSP method names.
-
-The following requests and notifications have built-in callbacks defined to
-handle the response in an idiomatic way.
-
- textDocument/completion
- textDocument/declaration
- textDocument/definition
- textDocument/hover
- textDocument/implementation
- textDocument/rename
- textDocument/signatureHelp
- textDocument/typeDefinition
+LSP CALLBACKS *lsp-callbacks*
+
+DEFAULT CALLBACKS ~
+ *vim.lsp.default_callbacks*
+The `vim.lsp.default_callbacks` table defines default callbacks used when
+creating a new client. Keys are LSP method names: >
+
+ :lua print(vim.inspect(vim.tbl_keys(vim.lsp.default_callbacks)))
+
+These LSP requests/notifications are defined by default:
+
+ textDocument/publishDiagnostics
window/logMessage
window/showMessage
-You can check these via `vim.tbl_keys(vim.lsp.builtin_callbacks)`.
+You can check these via `vim.tbl_keys(vim.lsp.default_callbacks)`.
-These will be automatically used and can be overridden by users (either by
-modifying the |vim.lsp.builtin_callbacks| object or on a per-client basis
-by passing in a table via the {callbacks} parameter on |vim.lsp.start_client|
-or |vim.lsp.add_filetype_config|.
+These will be used preferrentially in `vim.lsp.buf` methods when handling
+requests. They will also be used when responding to server requests and
+notifications.
-More information about callbacks can be found in |lsp-callbacks|.
+Use cases:
+- Users can modify this to customize to their preferences.
+- UI plugins can modify this by assigning to
+ `vim.lsp.default_callbacks[method]` so as to provide more specialized
+ handling, allowing you to leverage the UI capabilities available. UIs should
+ try to be conscientious of any existing changes the user may have set
+ already by checking for existing values.
-================================================================================
- *lsp-callbacks*
+Any callbacks passed directly to `request` methods on a server client will
+have the highest precedence, followed by the `default_callbacks`.
+
+You can override the default handlers,
+- globally: by modifying the `vim.lsp.default_callbacks` table
+- per-client: by passing the {callbacks} table parameter to
+ |vim.lsp.start_client|
+
+Each handler has this signature: >
+
+ function(err, method, params, client_id)
Callbacks are functions which are called in a variety of situations by the
client. Their signature is `function(err, method, params, client_id)` They can
-be set by the {callbacks} parameter for |vim.lsp.start_client| and
-|vim.lsp.add_filetype_config| or via the |vim.lsp.builtin_callbacks|.
-
-This will be called for:
-- notifications from the server, where `err` will always be `nil`
-- requests initiated by the server. The parameter `err` will be `nil` here as
- well.
- For these, you can respond by returning two values: `result, err` The
- err must be in the format of an RPC error, which is
- `{ code, message, data? }`
- You can use |vim.lsp.rpc_response_error()| to help with creating this object.
-- as a callback for requests initiated by the client if the request doesn't
- explicitly specify a callback (such as in |vim.lsp.buf_request|).
+be set by the {callbacks} parameter for |vim.lsp.start_client| or via the
+|vim.lsp.default_callbacks|.
+
+Handlers are called for:
+- Notifications from the server (`err` is always `nil`).
+- Requests initiated by the server (`err` is always `nil`).
+ The handler can respond by returning two values: `result, err`
+ where `err` must be shaped like an RPC error:
+ `{ code, message, data? }`
+ You can use |vim.lsp.rpc_response_error()| to create this object.
+- Handling requests initiated by the client if the request doesn't explicitly
+ specify a callback (such as in |vim.lsp.buf_request|).
================================================================================
- *vim.lsp.protocol*
-vim.lsp.protocol
+VIM.LSP.PROTOCOL *vim.lsp.protocol*
- Contains constants as described in the Language Server Protocol
- specification and helper functions for creating protocol related objects.
+The `vim.lsp.protocol` module provides constants defined in the LSP
+specification, and helper functions for creating protocol-related objects.
https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specifications/specification-3-14.md
- Useful examples are `vim.lsp.protocol.ErrorCodes`. These objects allow
- reverse lookup by either the number or string name.
+Useful examples are `vim.lsp.protocol.ErrorCodes`. These objects allow reverse
+lookup by either the number or string name.
e.g. vim.lsp.protocol.TextDocumentSyncKind.Full == 1
vim.lsp.protocol.TextDocumentSyncKind[1] == "Full"
Utility functions used internally are:
- `vim.lsp.make_client_capabilities()`
+ `vim.lsp.protocol.make_client_capabilities()`
Make a ClientCapabilities object. These are the builtin
capabilities.
- `vim.lsp.make_text_document_position_params()`
- Make a TextDocumentPositionParams object.
- `vim.lsp.resolve_capabilities(server_capabilites)`
+ `vim.lsp.protocol.resolve_capabilities(server_capabilites)`
Creates a normalized object describing capabilities from the server
capabilities.
@@ -482,18 +403,16 @@ vim.lsp.buf_notify({bufnr}, {method}, {params})
================================================================================
*lsp-logging*
- *lsp#set_log_level()*
-lsp#set_log_level({level})
+ *vim.lsp.set_log_level()*
+vim.lsp.set_log_level({level})
You can set the log level for language server client logging.
Possible values: "trace", "debug", "info", "warn", "error"
Default: "warn"
- Example: `call lsp#set_log_level("debug")`
+ Example: `lua vim.lsp.set_log_level("debug")`
- *lsp#get_log_path()*
*vim.lsp.get_log_path()*
-lsp#get_log_path()
vim.lsp.get_log_path()
Returns the path that LSP logs are written.
@@ -508,46 +427,46 @@ vim.lsp.log_levels
================================================================================
*lsp-omnifunc*
*vim.lsp.omnifunc()*
- *lsp#omnifunc*
-lsp#omnifunc({findstart}, {base})
vim.lsp.omnifunc({findstart}, {base})
To configure omnifunc, add the following in your init.vim:
>
- set omnifunc=lsp#omnifunc
+ " Configure for python
+ autocmd Filetype python setl omnifunc=v:lua.vim.lsp.omnifunc
+
+ " Or with on_attach
+ start_client {
+ ...
+ on_attach = function(client, bufnr)
+ vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')
+ end;
+ }
- " This is optional, but you may find it useful
- autocmd CompleteDone * pclose
+ " This is optional, but you may find it useful
+ autocmd CompleteDone * pclose
<
================================================================================
- *lsp-vim-functions*
+LSP FUNCTIONS *lsp-vim-functions*
+
+To use the functions from vim, it is recommended to use |v:lua| to interface
+with the Lua functions. No direct vim functions are provided, but the
+interface is still easy to use from mappings.
These methods can be used in mappings and are the equivalent of using the
request from lua as follows:
>
- lua vim.lsp.buf_request(0, "textDocument/hover", vim.lsp.protocol.make_text_document_position_params())
-<
-
- lsp#text_document_declaration()
- lsp#text_document_definition()
- lsp#text_document_hover()
- lsp#text_document_implementation()
- lsp#text_document_signature_help()
- lsp#text_document_type_definition()
-
->
" Example config
- autocmd Filetype rust,python,go,c,cpp setl omnifunc=lsp#omnifunc
- nnoremap <silent> ;dc :call lsp#text_document_declaration()<CR>
- nnoremap <silent> ;df :call lsp#text_document_definition()<CR>
- nnoremap <silent> ;h :call lsp#text_document_hover()<CR>
- nnoremap <silent> ;i :call lsp#text_document_implementation()<CR>
- nnoremap <silent> ;s :call lsp#text_document_signature_help()<CR>
- nnoremap <silent> ;td :call lsp#text_document_type_definition()<CR>
+ autocmd Filetype rust,python,go,c,cpp setl omnifunc=v:lua.vim.lsp.omnifunc
+ nnoremap <silent> ;dc <cmd>lua vim.lsp.buf.declaration()<CR>
+ nnoremap <silent> ;df <cmd>lua vim.lsp.buf.definition()<CR>
+ nnoremap <silent> ;h <cmd>lua vim.lsp.buf.hover()<CR>
+ nnoremap <silent> ;i <cmd>lua vim.lsp.buf.implementation()<CR>
+ nnoremap <silent> ;s <cmd>lua vim.lsp.buf.signature_help()<CR>
+ nnoremap <silent> ;td <cmd>lua vim.lsp.buf.type_definition()<CR>
<
================================================================================
- *lsp-advanced-js-example*
+LSP EXAMPLE *lsp-advanced-js-example*
For more advanced configurations where just filtering by filetype isn't
sufficient, you can use the `vim.lsp.start_client()` and
@@ -569,7 +488,7 @@ The example will:
local stat = vim.loop.fs_stat(filename)
return stat and stat.type == 'directory' or false
end
-
+
local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/"
-- Asumes filepath is a file.
local function dirname(filepath)
@@ -580,11 +499,11 @@ The example will:
end)
return result, is_changed
end
-
+
local function path_join(...)
return table.concat(vim.tbl_flatten {...}, path_sep)
end
-
+
-- Ascend the buffer's path until we find the rootdir.
-- is_root_path is a function which returns bool
local function buffer_find_root_dir(bufnr, is_root_path)
@@ -606,7 +525,7 @@ The example will:
end
end
end
-
+
-- A table to store our root_dir to client_id lookup. We want one LSP per
-- root directory, and this is how we assert that.
local javascript_lsps = {}
@@ -617,14 +536,14 @@ The example will:
["typescript"] = true;
["typescript.jsx"] = true;
}
-
+
-- Create a template configuration for a server to start, minus the root_dir
-- which we will specify later.
local javascript_lsp_config = {
name = "javascript";
cmd = { path_join(os.getenv("JAVASCRIPT_LANGUAGE_SERVER_DIRECTORY"), "lib", "language-server-stdio.js") };
}
-
+
-- This needs to be global so that we can call it from the autocmd.
function check_start_javascript_lsp()
local bufnr = vim.api.nvim_get_current_buf()
@@ -641,7 +560,7 @@ The example will:
end)
-- We couldn't find a root directory, so ignore this file.
if not root_dir then return end
-
+
-- Check if we have a client alredy or start and store it.
local client_id = javascript_lsps[root_dir]
if not client_id then
@@ -655,7 +574,7 @@ The example will:
-- are already attached.
vim.lsp.buf_attach_client(bufnr, client_id)
end
-
+
vim.api.nvim_command [[autocmd BufReadPost * lua check_start_javascript_lsp()]]
<
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index edcf246295..c0da06ffe3 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -876,16 +876,23 @@ tbl_add_reverse_lookup({o}) *vim.tbl_add_reverse_lookup()*
Parameters: ~
{o} table The table to add the reverse to.
-list_extend({dst}, {src}) *vim.list_extend()*
+list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()*
Extends a list-like table with the values of another list-like
table.
+ NOTE: This mutates dst!
+
Parameters: ~
- {dst} The list which will be modified and appended to.
- {src} The list from which values will be inserted.
+ {dst} list which will be modified and appended to.
+ {src} list from which values will be inserted.
+ {start} Start index on src. defaults to 1
+ {finish} Final index on src. defaults to #src
+
+ Return: ~
+ dst
See also: ~
- |extend()|
+ |vim.tbl_extend()|
tbl_flatten({t}) *vim.tbl_flatten()*
Creates a copy of a list-like table such that any nested
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index abe86749c4..58c0d832e6 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -786,7 +786,7 @@ g@{motion} Call the function set by the 'operatorfunc' option.
character of the text.
The function is called with one String argument:
"line" {motion} was |linewise|
- "char" {motion} was |characterwise|
+ "char" {motion} was |charwise|
"block" {motion} was |blockwise-visual|
Although "block" would rarely appear, since it can
only result from Visual mode where "g@" is not useful.
diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt
index 965b062728..bcfd985e71 100644
--- a/runtime/doc/message.txt
+++ b/runtime/doc/message.txt
@@ -679,8 +679,8 @@ no argument has been specified.
Invalid argument: {arg}
Duplicate argument: {arg}
-An Ex command or function has been executed, but an invalid argument has been
-specified.
+Ex command or function has been executed, but an invalid argument was
+specified. Or a non-executable command was given to |system()|.
*E488* >
Trailing characters
diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt
index e93c833c76..07ff4cf030 100644
--- a/runtime/doc/motion.txt
+++ b/runtime/doc/motion.txt
@@ -60,11 +60,11 @@ After applying the operator the cursor is mostly left at the start of the text
that was operated upon. For example, "yfe" doesn't move the cursor, but "yFe"
moves the cursor leftwards to the "e" where the yank started.
- *linewise* *characterwise*
+ *linewise* *charwise* *characterwise*
The operator either affects whole lines, or the characters between the start
and end position. Generally, motions that move between lines affect lines
(are linewise), and motions that move within a line affect characters (are
-characterwise). However, there are some exceptions.
+charwise). However, there are some exceptions.
*exclusive* *inclusive*
Character motion is either inclusive or exclusive. When inclusive, the
@@ -106,10 +106,10 @@ This cannot be repeated: >
d:if 1<CR>
call search("f")<CR>
endif<CR>
-Note that when using ":" any motion becomes characterwise exclusive.
+Note that when using ":" any motion becomes charwise exclusive.
*forced-motion*
-FORCING A MOTION TO BE LINEWISE, CHARACTERWISE OR BLOCKWISE
+FORCING A MOTION TO BE LINEWISE, CHARWISE OR BLOCKWISE
When a motion is not of the type you would like to use, you can force another
type by using "v", "V" or CTRL-V just after the operator.
@@ -121,22 +121,22 @@ deletes from the cursor position until the character below the cursor >
d<C-V>j
deletes the character under the cursor and the character below the cursor. >
-Be careful with forcing a linewise movement to be used characterwise or
-blockwise, the column may not always be defined.
+Be careful with forcing a linewise movement to be used charwise or blockwise,
+the column may not always be defined.
*o_v*
v When used after an operator, before the motion command: Force
- the operator to work characterwise, also when the motion is
+ the operator to work charwise, also when the motion is
linewise. If the motion was linewise, it will become
|exclusive|.
- If the motion already was characterwise, toggle
+ If the motion already was charwise, toggle
inclusive/exclusive. This can be used to make an exclusive
motion inclusive and an inclusive motion exclusive.
*o_V*
V When used after an operator, before the motion command: Force
the operator to work linewise, also when the motion is
- characterwise.
+ charwise.
*o_CTRL-V*
CTRL-V When used after an operator, before the motion command: Force
@@ -508,36 +508,36 @@ aw "a word", select [count] words (see |word|).
Leading or trailing white space is included, but not
counted.
When used in Visual linewise mode "aw" switches to
- Visual characterwise mode.
+ Visual charwise mode.
*v_iw* *iw*
iw "inner word", select [count] words (see |word|).
White space between words is counted too.
When used in Visual linewise mode "iw" switches to
- Visual characterwise mode.
+ Visual charwise mode.
*v_aW* *aW*
aW "a WORD", select [count] WORDs (see |WORD|).
Leading or trailing white space is included, but not
counted.
When used in Visual linewise mode "aW" switches to
- Visual characterwise mode.
+ Visual charwise mode.
*v_iW* *iW*
iW "inner WORD", select [count] WORDs (see |WORD|).
White space between words is counted too.
When used in Visual linewise mode "iW" switches to
- Visual characterwise mode.
+ Visual charwise mode.
*v_as* *as*
as "a sentence", select [count] sentences (see
|sentence|).
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
*v_is* *is*
is "inner sentence", select [count] sentences (see
|sentence|).
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
*v_ap* *ap*
ap "a paragraph", select [count] paragraphs (see
@@ -558,14 +558,14 @@ a[ "a [] block", select [count] '[' ']' blocks. This
goes backwards to the [count] unclosed '[', and finds
the matching ']'. The enclosed text is selected,
including the '[' and ']'.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
i] *v_i]* *v_i[* *i]* *i[*
i[ "inner [] block", select [count] '[' ']' blocks. This
goes backwards to the [count] unclosed '[', and finds
the matching ']'. The enclosed text is selected,
excluding the '[' and ']'.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
a) *v_a)* *a)* *a(*
a( *vab* *v_ab* *v_a(* *ab*
@@ -573,54 +573,54 @@ ab "a block", select [count] blocks, from "[count] [(" to
the matching ')', including the '(' and ')' (see
|[(|). Does not include white space outside of the
parenthesis.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
i) *v_i)* *i)* *i(*
i( *vib* *v_ib* *v_i(* *ib*
ib "inner block", select [count] blocks, from "[count] [("
to the matching ')', excluding the '(' and ')' (see
|[(|).
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
a> *v_a>* *v_a<* *a>* *a<*
a< "a <> block", select [count] <> blocks, from the
[count]'th unmatched '<' backwards to the matching
'>', including the '<' and '>'.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
i> *v_i>* *v_i<* *i>* *i<*
i< "inner <> block", select [count] <> blocks, from
the [count]'th unmatched '<' backwards to the matching
'>', excluding the '<' and '>'.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
*v_at* *at*
at "a tag block", select [count] tag blocks, from the
[count]'th unmatched "<aaa>" backwards to the matching
"</aaa>", including the "<aaa>" and "</aaa>".
See |tag-blocks| about the details.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
*v_it* *it*
it "inner tag block", select [count] tag blocks, from the
[count]'th unmatched "<aaa>" backwards to the matching
"</aaa>", excluding the "<aaa>" and "</aaa>".
See |tag-blocks| about the details.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
a} *v_a}* *a}* *a{*
a{ *v_aB* *v_a{* *aB*
aB "a Block", select [count] Blocks, from "[count] [{" to
the matching '}', including the '{' and '}' (see
|[{|).
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
i} *v_i}* *i}* *i{*
i{ *v_iB* *v_i{* *iB*
iB "inner Block", select [count] Blocks, from "[count] [{"
to the matching '}', excluding the '{' and '}' (see
|[{|).
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
a" *v_aquote* *aquote*
a' *v_a'* *a'*
@@ -634,7 +634,7 @@ a` *v_a`* *a`*
start of the line.
Any trailing white space is included, unless there is
none, then leading white space is included.
- When used in Visual mode it is made characterwise.
+ When used in Visual mode it is made charwise.
Repeating this object in Visual mode another string is
included. A count is currently not used.
diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt
index 7dbbb2d424..e3f0d593a7 100644
--- a/runtime/doc/starting.txt
+++ b/runtime/doc/starting.txt
@@ -1270,7 +1270,7 @@ exactly four MessagePack objects:
Key Type Def Description ~
rt UInteger 0 Register type:
No Description ~
- 0 |characterwise-register|
+ 0 |charwise-register|
1 |linewise-register|
2 |blockwise-register|
rw UInteger 0 Register width. Only valid
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index a2f19593ae..d5f4a59ab3 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -201,8 +201,8 @@ the editor.
sent from Nvim, like for |ui-cmdline|.
["mode_change", mode, mode_idx]
- The mode changed. The first parameter `mode` is a string representing
- the current mode. `mode_idx` is an index into the array received in
+ Editor mode changed. The `mode` parameter is a string representing
+ the current mode. `mode_idx` is an index into the array emitted in
the `mode_info_set` event. UIs should change the cursor style
according to the properties specified in the corresponding item. The
set of modes reported will change in new versions of Nvim, for
@@ -211,11 +211,11 @@ the editor.
["mouse_on"]
["mouse_off"]
- Tells the client whether mouse support, as determined by |'mouse'|
- option, is considered to be active in the current mode. This is mostly
- useful for a terminal frontend, or other situations where Nvim mouse
- would conflict with other usages of the mouse. It is safe for a client
- to ignore this and always send mouse events.
+ |'mouse'| was enabled/disabled in the current editor mode. Useful for
+ a terminal UI, or other situations where Nvim mouse would conflict
+ with other usages of the mouse. UIs may ignore this and always send
+ mouse input, because 'mouse' decides the behavior of |nvim_input()|
+ implicitly.
["busy_start"]
["busy_stop"]
diff --git a/runtime/doc/visual.txt b/runtime/doc/visual.txt
index ccbbc092ec..0052382044 100644
--- a/runtime/doc/visual.txt
+++ b/runtime/doc/visual.txt
@@ -48,7 +48,7 @@ position.
==============================================================================
2. Starting and stopping Visual mode *visual-start*
- *v* *characterwise-visual*
+ *v* *charwise-visual*
[count]v Start Visual mode per character.
With [count] select the same number of characters or
lines as used for the last Visual operation, but at
@@ -74,7 +74,7 @@ position.
If you use <Esc>, click the left mouse button or use any command that
does a jump to another buffer while in Visual mode, the highlighting stops
-and no text is affected. Also when you hit "v" in characterwise Visual mode,
+and no text is affected. Also when you hit "v" in charwise Visual mode,
"CTRL-V" in blockwise Visual mode or "V" in linewise Visual mode. If you hit
CTRL-Z the highlighting stops and the editor is suspended or a new shell is
started |CTRL-Z|.
@@ -477,7 +477,7 @@ Commands in Select mode:
Otherwise, typed characters are handled as in Visual mode.
When using an operator in Select mode, and the selection is linewise, the
-selected lines are operated upon, but like in characterwise selection. For
+selected lines are operated upon, but like in charwise selection. For
example, when a whole line is deleted, it can later be pasted in the middle of
a line.
@@ -510,7 +510,7 @@ gV Avoid the automatic reselection of the Visual area
selection.
*gh*
-gh Start Select mode, characterwise. This is like "v",
+gh Start Select mode, charwise. This is like "v",
but starts Select mode instead of Visual mode.
Mnemonic: "get highlighted".
diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim
index b3b23833ba..7c535dc839 100644
--- a/runtime/ftplugin/man.vim
+++ b/runtime/ftplugin/man.vim
@@ -1,4 +1,4 @@
-" Maintainer: Anmol Sethi <anmol@aubble.com>
+" Maintainer: Anmol Sethi <hi@nhooyr.io>
" Previous Maintainer: SungHyun Nam <goweol@gmail.com>
if exists('b:did_ftplugin') || &filetype !=# 'man'
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 0c1a145c04..82f4fda66f 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1,9 +1,10 @@
-local builtin_callbacks = require 'vim.lsp.builtin_callbacks'
+local default_callbacks = require 'vim.lsp.callbacks'
local log = require 'vim.lsp.log'
local lsp_rpc = require 'vim.lsp.rpc'
local protocol = require 'vim.lsp.protocol'
local util = require 'vim.lsp.util'
+local vim = vim
local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option
= vim.api.nvim_err_writeln, vim.api.nvim_buf_get_lines, vim.api.nvim_command, vim.api.nvim_buf_get_option
local uv = vim.loop
@@ -12,7 +13,8 @@ local validate = vim.validate
local lsp = {
protocol = protocol;
- builtin_callbacks = builtin_callbacks;
+ callbacks = default_callbacks;
+ buf = require'vim.lsp.buf';
util = util;
-- Allow raw RPC access.
rpc = lsp_rpc;
@@ -25,6 +27,11 @@ local lsp = {
-- TODO improve handling of scratch buffers with LSP attached.
+local function err_message(...)
+ nvim_err_writeln(table.concat(vim.tbl_flatten{...}))
+ nvim_command("redraw")
+end
+
local function resolve_bufnr(bufnr)
validate { bufnr = { bufnr, 'n', true } }
if bufnr == nil or bufnr == 0 then
@@ -92,11 +99,7 @@ local function for_each_buffer_client(bufnr, callback)
return
end
for client_id in pairs(client_ids) do
- -- This is unlikely to happen. Could only potentially happen in a race
- -- condition between literally a single statement.
- -- We could skip this error, but let's error for now.
local client = active_clients[client_id]
- -- or error(string.format("Client %d has already shut down.", client_id))
if client then
callback(client, client_id)
end
@@ -154,13 +157,13 @@ local function validate_client_config(config)
root_dir = { config.root_dir, is_dir, "directory" };
callbacks = { config.callbacks, "t", true };
capabilities = { config.capabilities, "t", true };
- -- cmd = { config.cmd, "s", false };
cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" };
cmd_env = { config.cmd_env, "f", true };
name = { config.name, 's', true };
on_error = { config.on_error, "f", true };
on_exit = { config.on_exit, "f", true };
on_init = { config.on_init, "f", true };
+ before_init = { config.before_init, "f", true };
offset_encoding = { config.offset_encoding, "s", true };
}
local cmd, cmd_args = validate_command(config.cmd)
@@ -261,6 +264,12 @@ end
-- possible errors. `vim.lsp.client_errors[code]` can be used to retrieve a
-- human understandable string.
--
+-- before_init(initialize_params, config): A function which is called *before*
+-- the request `initialize` is completed. `initialize_params` contains
+-- the parameters we are sending to the server and `config` is the config that
+-- was passed to `start_client()` for convenience. You can use this to modify
+-- parameters before they are sent.
+--
-- on_init(client, initialize_result): A function which is called after the
-- request `initialize` is completed. `initialize_result` contains
-- `capabilities` and anything else the server may send. For example, `clangd`
@@ -290,19 +299,19 @@ function lsp.start_client(config)
local client_id = next_client_id()
- local callbacks = tbl_extend("keep", config.callbacks or {}, builtin_callbacks)
- -- Copy metatable if it has one.
- if config.callbacks and config.callbacks.__metatable then
- setmetatable(callbacks, getmetatable(config.callbacks))
- end
+ local callbacks = config.callbacks or {}
local name = config.name or tostring(client_id)
local log_prefix = string.format("LSP[%s]", name)
local handlers = {}
+ local function resolve_callback(method)
+ return callbacks[method] or default_callbacks[method]
+ end
+
function handlers.notification(method, params)
local _ = log.debug() and log.debug('notification', method, params)
- local callback = callbacks[method]
+ local callback = resolve_callback(method)
if callback then
-- Method name is provided here for convenience.
callback(nil, method, params, client_id)
@@ -311,7 +320,7 @@ function lsp.start_client(config)
function handlers.server_request(method, params)
local _ = log.debug() and log.debug('server_request', method, params)
- local callback = callbacks[method]
+ local callback = resolve_callback(method)
if callback then
local _ = log.debug() and log.debug("server_request: found callback for", method)
return callback(nil, method, params, client_id)
@@ -322,12 +331,12 @@ function lsp.start_client(config)
function handlers.on_error(code, err)
local _ = log.error() and log.error(log_prefix, "on_error", { code = lsp.client_errors[code], err = err })
- nvim_err_writeln(string.format('%s: Error %s: %q', log_prefix, lsp.client_errors[code], vim.inspect(err)))
+ err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
if config.on_error then
local status, usererr = pcall(config.on_error, code, err)
if not status then
local _ = log.error() and log.error(log_prefix, "user on_error failed", { err = usererr })
- nvim_err_writeln(log_prefix.." user on_error failed: "..tostring(usererr))
+ err_message(log_prefix, ' user on_error failed: ', tostring(usererr))
end
end
end
@@ -335,9 +344,19 @@ function lsp.start_client(config)
function handlers.on_exit(code, signal)
active_clients[client_id] = nil
uninitialized_clients[client_id] = nil
- for _, client_ids in pairs(all_buffer_active_clients) do
+ local active_buffers = {}
+ for bufnr, client_ids in pairs(all_buffer_active_clients) do
+ if client_ids[client_id] then
+ table.insert(active_buffers, bufnr)
+ end
client_ids[client_id] = nil
end
+ -- Buffer level cleanup
+ vim.schedule(function()
+ for _, bufnr in ipairs(active_buffers) do
+ util.buf_clear_diagnostics(bufnr)
+ end
+ end)
if config.on_exit then
pcall(config.on_exit, code, signal, client_id)
end
@@ -379,7 +398,6 @@ function lsp.start_client(config)
-- 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 = vim.uri_from_fname(vim.fn.expand("%:p:h"));
-- User provided initialization options.
initializationOptions = config.init_options;
-- The capabilities provided by the client (editor or tool)
@@ -403,11 +421,15 @@ function lsp.start_client(config)
-- }
workspaceFolders = nil;
}
+ 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)
rpc.request('initialize', initialize_params, function(init_err, result)
assert(not init_err, tostring(init_err))
assert(result, "server sent empty result")
- rpc.notify('initialized', {})
+ rpc.notify('initialized', {[vim.type_idx]=vim.types.dictionary})
client.initialized = true
uninitialized_clients[client_id] = nil
client.server_capabilities = assert(result.capabilities, "initialize result doesn't contain capabilities")
@@ -439,15 +461,15 @@ function lsp.start_client(config)
local function unsupported_method(method)
local msg = "server doesn't support "..method
local _ = log.warn() and log.warn(msg)
- nvim_err_writeln(msg)
+ err_message(msg)
return lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound, msg)
end
--- Checks capabilities before rpc.request-ing.
function client.request(method, params, callback)
if not callback then
- callback = client.callbacks[method]
- or error(string.format("request callback is empty and no default was found for client %s", client.name))
+ callback = resolve_callback(method)
+ or error("not found: request callback for client "..client.name)
end
local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback)
-- TODO keep these checks or just let it go anyway?
@@ -851,7 +873,7 @@ function lsp.omnifunc(findstart, base)
position = {
-- 0-indexed for both line and character
line = pos[1] - 1,
- character = pos[2],
+ character = vim.str_utfindex(line, pos[2]),
};
-- The completion context. This is only available if the client specifies
-- to send this using `ClientCapabilities.textDocument.completion.contextSupport === true`
@@ -876,134 +898,8 @@ function lsp.omnifunc(findstart, base)
end
end
----
---- FileType based configuration utility
----
-
-local all_filetype_configs = {}
-
--- Lookup a filetype config client by its name.
-function lsp.get_filetype_client_by_name(name)
- local config = all_filetype_configs[name]
- if config.client_id then
- return active_clients[config.client_id]
- end
-end
-
-local function start_filetype_config(config)
- config.client_id = lsp.start_client(config)
- nvim_command(string.format(
- "autocmd FileType %s silent lua vim.lsp.buf_attach_client(0, %d)",
- table.concat(config.filetypes, ','),
- config.client_id))
- return config.client_id
-end
-
--- Easy configuration option for common LSP use-cases.
--- This will lazy initialize the client when the filetypes specified are
--- encountered and attach to those buffers.
---
--- The configuration options are the same as |vim.lsp.start_client()|, but
--- with a few additions and distinctions:
---
--- Additional parameters:
--- - filetype: {string} or {list} of filetypes to attach to.
--- - name: A unique string among all other servers configured with
--- |vim.lsp.add_filetype_config|.
---
--- Differences:
--- - root_dir: will default to |getcwd()|
---
-function lsp.add_filetype_config(config)
- -- Additional defaults.
- -- Keep a copy of the user's input for debugging reasons.
- local user_config = config
- config = tbl_extend("force", {}, user_config)
- config.root_dir = config.root_dir or uv.cwd()
- -- Validate config.
- validate_client_config(config)
- validate {
- name = { config.name, 's' };
- }
- assert(config.filetype, "config must have 'filetype' key")
-
- local filetypes
- if type(config.filetype) == 'string' then
- filetypes = { config.filetype }
- elseif type(config.filetype) == 'table' then
- filetypes = config.filetype
- assert(not tbl_isempty(filetypes), "config.filetype must not be an empty table")
- else
- error("config.filetype must be a string or a list of strings")
- end
-
- if all_filetype_configs[config.name] then
- -- If the client exists, then it is likely that they are doing some kind of
- -- reload flow, so let's not throw an error here.
- if all_filetype_configs[config.name].client_id then
- -- TODO log here? It might be unnecessarily annoying.
- return
- end
- error(string.format('A configuration with the name %q already exists. They must be unique', config.name))
- end
-
- all_filetype_configs[config.name] = tbl_extend("keep", config, {
- client_id = nil;
- filetypes = filetypes;
- user_config = user_config;
- })
-
- nvim_command(string.format(
- "autocmd FileType %s ++once silent lua vim.lsp._start_filetype_config_client(%q)",
- table.concat(filetypes, ','),
- config.name))
-end
-
--- Create a copy of an existing configuration, and override config with values
--- from new_config.
--- This is useful if you wish you create multiple LSPs with different root_dirs
--- or other use cases.
---
--- You can specify a new unique name, but if you do not, a unique name will be
--- created like `name-dup_count`.
---
--- existing_name: the name of the existing config to copy.
--- new_config: the new configuration options. @see |vim.lsp.start_client()|.
--- @returns string the new name.
-function lsp.copy_filetype_config(existing_name, new_config)
- local config = all_filetype_configs[existing_name]
- or error(string.format("Configuration with name %q doesn't exist", existing_name))
- config = tbl_extend("force", config, new_config or {})
- config.client_id = nil
- config.original_config_name = existing_name
-
- -- If the user didn't rename it, we will.
- if config.name == existing_name then
- -- Create a new, unique name.
- local duplicate_count = 0
- for _, conf in pairs(all_filetype_configs) do
- if conf.original_config_name == existing_name then
- duplicate_count = duplicate_count + 1
- end
- end
- config.name = string.format("%s-%d", existing_name, duplicate_count + 1)
- end
- print("New config name:", config.name)
- lsp.add_filetype_config(config)
- return config.name
-end
-
--- Autocmd handler to actually start the client when an applicable filetype is
--- encountered.
-function lsp._start_filetype_config_client(name)
- local config = all_filetype_configs[name]
- -- If it exists and is running, don't make it again.
- if config.client_id and active_clients[config.client_id] then
- -- TODO log here?
- return
- end
- lsp.buf_attach_client(0, start_filetype_config(config))
- return config.client_id
+function lsp.client_is_stopped(client_id)
+ return active_clients[client_id] == nil
end
---
@@ -1030,7 +926,7 @@ end
-- Print some debug information about all LSP related things.
-- The output of this function should not be relied upon and may change.
function lsp.print_debug_info()
- print(vim.inspect({ clients = active_clients, filetype_configs = all_filetype_configs }))
+ print(vim.inspect({ clients = active_clients }))
end
-- Log level dictionary with reverse lookup as well.
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
new file mode 100644
index 0000000000..a6a05fb095
--- /dev/null
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -0,0 +1,136 @@
+local vim = vim
+local validate = vim.validate
+local api = vim.api
+local vfn = vim.fn
+local util = require 'vim.lsp.util'
+local list_extend = vim.list_extend
+
+local M = {}
+
+local function ok_or_nil(status, ...)
+ if not status then return end
+ return ...
+end
+local function npcall(fn, ...)
+ return ok_or_nil(pcall(fn, ...))
+end
+
+local function request(method, params, callback)
+ validate {
+ method = {method, 's'};
+ callback = {callback, 'f', true};
+ }
+ return vim.lsp.buf_request(0, method, params, callback)
+end
+
+function M.hover()
+ local params = util.make_position_params()
+ request('textDocument/hover', params)
+end
+
+function M.peek_definition()
+ local params = util.make_position_params()
+ request('textDocument/peekDefinition', params)
+end
+
+
+function M.declaration()
+ local params = util.make_position_params()
+ request('textDocument/declaration', params)
+end
+
+function M.definition()
+ local params = util.make_position_params()
+ request('textDocument/definition', params)
+end
+
+function M.type_definition()
+ local params = util.make_position_params()
+ request('textDocument/typeDefinition', params)
+end
+
+function M.implementation()
+ local params = util.make_position_params()
+ request('textDocument/implementation', params)
+end
+
+function M.signature_help()
+ local params = util.make_position_params()
+ request('textDocument/signatureHelp', params)
+end
+
+-- TODO(ashkan) ?
+function M.completion(context)
+ local params = util.make_position_params()
+ params.context = context
+ return request('textDocument/completion', params)
+end
+
+function M.formatting(options)
+ validate { options = {options, 't', true} }
+ options = vim.tbl_extend('keep', options or {}, {
+ tabSize = vim.bo.tabstop;
+ insertSpaces = vim.bo.expandtab;
+ })
+ local params = {
+ textDocument = { uri = vim.uri_from_bufnr(0) };
+ options = options;
+ }
+ return request('textDocument/formatting', params)
+end
+
+function M.range_formatting(options, start_pos, end_pos)
+ validate {
+ options = {options, 't', true};
+ start_pos = {start_pos, 't', true};
+ end_pos = {end_pos, 't', true};
+ }
+ options = vim.tbl_extend('keep', options or {}, {
+ tabSize = vim.bo.tabstop;
+ insertSpaces = vim.bo.expandtab;
+ })
+ local A = list_extend({}, start_pos or api.nvim_buf_get_mark(0, '<'))
+ local B = list_extend({}, end_pos or api.nvim_buf_get_mark(0, '>'))
+ -- convert to 0-index
+ A[1] = A[1] - 1
+ B[1] = B[1] - 1
+ -- account for encoding.
+ if A[2] > 0 then
+ A = {A[1], util.character_offset(0, A[1], A[2])}
+ end
+ if B[2] > 0 then
+ B = {B[1], util.character_offset(0, B[1], B[2])}
+ end
+ local params = {
+ textDocument = { uri = vim.uri_from_bufnr(0) };
+ range = {
+ start = { line = A[1]; character = A[2]; };
+ ["end"] = { line = B[1]; character = B[2]; };
+ };
+ options = options;
+ }
+ return request('textDocument/rangeFormatting', params)
+end
+
+function M.rename(new_name)
+ -- TODO(ashkan) use prepareRename
+ -- * result: [`Range`](#range) \| `{ range: Range, placeholder: string }` \| `null` describing the range of the string to rename and optionally a placeholder text of the string content to be renamed. If `null` is returned then it is deemed that a 'textDocument/rename' request is not valid at the given position.
+ local params = util.make_position_params()
+ new_name = new_name or npcall(vfn.input, "New Name: ")
+ if not (new_name and #new_name > 0) then return end
+ params.newName = new_name
+ request('textDocument/rename', params)
+end
+
+function M.references(context)
+ validate { context = { context, 't', true } }
+ local params = util.make_position_params()
+ params.context = context or {
+ includeDeclaration = true;
+ }
+ params[vim.type_idx] = vim.types.dictionary
+ request('textDocument/references', params)
+end
+
+return M
+-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/builtin_callbacks.lua b/runtime/lua/vim/lsp/builtin_callbacks.lua
deleted file mode 100644
index cc739ce3ad..0000000000
--- a/runtime/lua/vim/lsp/builtin_callbacks.lua
+++ /dev/null
@@ -1,296 +0,0 @@
---- Implements the following default callbacks:
---
--- vim.api.nvim_buf_set_lines(0, 0, 0, false, vim.tbl_keys(vim.lsp.builtin_callbacks))
---
-
--- textDocument/completion
--- textDocument/declaration
--- textDocument/definition
--- textDocument/hover
--- textDocument/implementation
--- textDocument/publishDiagnostics
--- textDocument/rename
--- textDocument/signatureHelp
--- textDocument/typeDefinition
--- TODO codeLens/resolve
--- TODO completionItem/resolve
--- TODO documentLink/resolve
--- TODO textDocument/codeAction
--- TODO textDocument/codeLens
--- TODO textDocument/documentHighlight
--- TODO textDocument/documentLink
--- TODO textDocument/documentSymbol
--- TODO textDocument/formatting
--- TODO textDocument/onTypeFormatting
--- TODO textDocument/rangeFormatting
--- TODO textDocument/references
--- window/logMessage
--- window/showMessage
-
-local log = require 'vim.lsp.log'
-local protocol = require 'vim.lsp.protocol'
-local util = require 'vim.lsp.util'
-local api = vim.api
-
-local function split_lines(value)
- return vim.split(value, '\n', true)
-end
-
-local builtin_callbacks = {}
-
--- textDocument/completion
--- https://microsoft.github.io/language-server-protocol/specification#textDocument_completion
-builtin_callbacks['textDocument/completion'] = function(_, _, result)
- if not result or vim.tbl_isempty(result) then
- return
- end
- local pos = api.nvim_win_get_cursor(0)
- local row, col = pos[1], pos[2]
- local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1])
- local line_to_cursor = line:sub(col+1)
-
- local matches = util.text_document_completion_list_to_complete_items(result, line_to_cursor)
- local match_result = vim.fn.matchstrpos(line_to_cursor, '\\k\\+$')
- local match_start, match_finish = match_result[2], match_result[3]
-
- vim.fn.complete(col + 1 - (match_finish - match_start), matches)
-end
-
--- textDocument/rename
-builtin_callbacks['textDocument/rename'] = function(_, _, result)
- if not result then return end
- util.workspace_apply_workspace_edit(result)
-end
-
-local function uri_to_bufnr(uri)
- return vim.fn.bufadd((vim.uri_to_fname(uri)))
-end
-
-builtin_callbacks['textDocument/publishDiagnostics'] = function(_, _, result)
- if not result then return end
- local uri = result.uri
- local bufnr = uri_to_bufnr(uri)
- if not bufnr then
- api.nvim_err_writeln(string.format("LSP.publishDiagnostics: Couldn't find buffer for %s", uri))
- return
- end
- util.buf_clear_diagnostics(bufnr)
- util.buf_diagnostics_save_positions(bufnr, result.diagnostics)
- util.buf_diagnostics_underline(bufnr, result.diagnostics)
- util.buf_diagnostics_virtual_text(bufnr, result.diagnostics)
- -- util.buf_loclist(bufnr, result.diagnostics)
-end
-
--- textDocument/hover
--- https://microsoft.github.io/language-server-protocol/specification#textDocument_hover
--- @params MarkedString | MarkedString[] | MarkupContent
-builtin_callbacks['textDocument/hover'] = function(_, _, result)
- if result == nil or vim.tbl_isempty(result) then
- return
- end
-
- if result.contents ~= nil then
- local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
- if vim.tbl_isempty(markdown_lines) then
- markdown_lines = { 'No information available' }
- end
- util.open_floating_preview(markdown_lines, 'markdown')
- end
-end
-
-builtin_callbacks['textDocument/peekDefinition'] = function(_, _, result)
- if result == nil or vim.tbl_isempty(result) then return end
- -- TODO(ashkan) what to do with multiple locations?
- result = result[1]
- local bufnr = uri_to_bufnr(result.uri)
- assert(bufnr)
- local start = result.range.start
- local finish = result.range["end"]
- util.open_floating_peek_preview(bufnr, start, finish, { offset_x = 1 })
- util.open_floating_preview({"*Peek:*", string.rep(" ", finish.character - start.character + 1) }, 'markdown', { offset_y = -(finish.line - start.line) })
-end
-
---- Convert SignatureHelp response to preview contents.
--- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp
-local function signature_help_to_preview_contents(input)
- if not input.signatures then
- return
- end
- --The active signature. If omitted or the value lies outside the range of
- --`signatures` the value defaults to zero or is ignored if `signatures.length
- --=== 0`. Whenever possible implementors should make an active decision about
- --the active signature and shouldn't rely on a default value.
- local contents = {}
- local active_signature = input.activeSignature or 0
- -- If the activeSignature is not inside the valid range, then clip it.
- if active_signature >= #input.signatures then
- active_signature = 0
- end
- local signature = input.signatures[active_signature + 1]
- if not signature then
- return
- end
- vim.list_extend(contents, split_lines(signature.label))
- if signature.documentation then
- util.convert_input_to_markdown_lines(signature.documentation, contents)
- end
- if input.parameters then
- local active_parameter = input.activeParameter or 0
- -- If the activeParameter is not inside the valid range, then clip it.
- if active_parameter >= #input.parameters then
- active_parameter = 0
- end
- local parameter = signature.parameters and signature.parameters[active_parameter]
- if parameter then
- --[=[
- --Represents a parameter of a callable-signature. A parameter can
- --have a label and a doc-comment.
- interface ParameterInformation {
- --The label of this parameter information.
- --
- --Either a string or an inclusive start and exclusive end offsets within its containing
- --signature label. (see SignatureInformation.label). The offsets are based on a UTF-16
- --string representation as `Position` and `Range` does.
- --
- --*Note*: a label of type string should be a substring of its containing signature label.
- --Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`.
- label: string | [number, number];
- --The human-readable doc-comment of this parameter. Will be shown
- --in the UI but can be omitted.
- documentation?: string | MarkupContent;
- }
- --]=]
- -- TODO highlight parameter
- if parameter.documentation then
- util.convert_input_to_markdown_lines(parameter.documentation, contents)
- end
- end
- end
- return contents
-end
-
--- textDocument/signatureHelp
--- https://microsoft.github.io/language-server-protocol/specification#textDocument_signatureHelp
-builtin_callbacks['textDocument/signatureHelp'] = function(_, _, result)
- if result == nil or vim.tbl_isempty(result) then
- return
- end
-
- -- TODO show empty popup when signatures is empty?
- if #result.signatures > 0 then
- local markdown_lines = signature_help_to_preview_contents(result)
- if vim.tbl_isempty(markdown_lines) then
- markdown_lines = { 'No signature available' }
- end
- util.open_floating_preview(markdown_lines, 'markdown')
- end
-end
-
-local function update_tagstack()
- local bufnr = api.nvim_get_current_buf()
- local line = vim.fn.line('.')
- local col = vim.fn.col('.')
- local tagname = vim.fn.expand('<cWORD>')
- local item = { bufnr = bufnr, from = { bufnr, line, col, 0 }, tagname = tagname }
- local winid = vim.fn.win_getid()
- local tagstack = vim.fn.gettagstack(winid)
-
- local action
-
- if tagstack.length == tagstack.curidx then
- action = 'r'
- tagstack.items[tagstack.curidx] = item
- elseif tagstack.length > tagstack.curidx then
- action = 'r'
- if tagstack.curidx > 1 then
- tagstack.items = table.insert(tagstack.items[tagstack.curidx - 1], item)
- else
- tagstack.items = { item }
- end
- else
- action = 'a'
- tagstack.items = { item }
- end
-
- tagstack.curidx = tagstack.curidx + 1
- vim.fn.settagstack(winid, tagstack, action)
-end
-
-local function handle_location(result)
- -- We can sometimes get a list of locations, so set the first value as the
- -- only value we want to handle
- -- TODO(ashkan) was this correct^? We could use location lists.
- if result[1] ~= nil then
- result = result[1]
- end
- if result.uri == nil then
- api.nvim_err_writeln('[LSP] Could not find a valid location')
- return
- end
- local result_file = vim.uri_to_fname(result.uri)
- local bufnr = vim.fn.bufadd(result_file)
- update_tagstack()
- api.nvim_set_current_buf(bufnr)
- local start = result.range.start
- api.nvim_win_set_cursor(0, {start.line + 1, start.character})
-end
-
-local function location_callback(_, method, result)
- if result == nil or vim.tbl_isempty(result) then
- local _ = log.info() and log.info(method, 'No location found')
- return nil
- end
- handle_location(result)
- return true
-end
-
-local location_callbacks = {
- -- https://microsoft.github.io/language-server-protocol/specification#textDocument_declaration
- 'textDocument/declaration';
- -- https://microsoft.github.io/language-server-protocol/specification#textDocument_definition
- 'textDocument/definition';
- -- https://microsoft.github.io/language-server-protocol/specification#textDocument_implementation
- 'textDocument/implementation';
- -- https://microsoft.github.io/language-server-protocol/specification#textDocument_typeDefinition
- 'textDocument/typeDefinition';
-}
-
-for _, location_method in ipairs(location_callbacks) do
- builtin_callbacks[location_method] = location_callback
-end
-
-local function log_message(_, _, result, client_id)
- local message_type = result.type
- local message = result.message
- 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
- api.nvim_err_writeln(string.format("LSP[%s] client has shut down after sending the message", client_name))
- end
- if message_type == protocol.MessageType.Error then
- -- Might want to not use err_writeln,
- -- but displaying a message with red highlights or something
- api.nvim_err_writeln(string.format("LSP[%s] %s", client_name, message))
- else
- local message_type_name = protocol.MessageType[message_type]
- api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message))
- end
- return result
-end
-
-builtin_callbacks['window/showMessage'] = log_message
-builtin_callbacks['window/logMessage'] = log_message
-
--- Add boilerplate error validation and logging for all of these.
-for k, fn in pairs(builtin_callbacks) do
- builtin_callbacks[k] = function(err, method, params, client_id)
- local _ = log.debug() and log.debug('builtin_callback', method, { params = params, client_id = client_id, err = err })
- if err then
- error(tostring(err))
- end
- return fn(err, method, params, client_id)
- end
-end
-
-return builtin_callbacks
--- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua
new file mode 100644
index 0000000000..4fc3f74519
--- /dev/null
+++ b/runtime/lua/vim/lsp/callbacks.lua
@@ -0,0 +1,223 @@
+local log = require 'vim.lsp.log'
+local protocol = require 'vim.lsp.protocol'
+local util = require 'vim.lsp.util'
+local vim = vim
+local api = vim.api
+
+local M = {}
+
+local function err_message(...)
+ api.nvim_err_writeln(table.concat(vim.tbl_flatten{...}))
+ api.nvim_command("redraw")
+end
+
+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
+ print("Workspace edit", workspace_edit.label)
+ end
+ util.apply_workspace_edit(workspace_edit.edit)
+end
+
+M['textDocument/publishDiagnostics'] = function(_, _, result)
+ if not result then return end
+ local uri = result.uri
+ local bufnr = vim.uri_to_bufnr(uri)
+ if not bufnr then
+ err_message("LSP.publishDiagnostics: Couldn't find buffer for ", uri)
+ return
+ end
+ util.buf_clear_diagnostics(bufnr)
+ util.buf_diagnostics_save_positions(bufnr, result.diagnostics)
+ util.buf_diagnostics_underline(bufnr, result.diagnostics)
+ util.buf_diagnostics_virtual_text(bufnr, result.diagnostics)
+ -- util.set_loclist(result.diagnostics)
+end
+
+M['textDocument/references'] = function(_, _, result)
+ if not result then return end
+ util.set_qflist(result)
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+end
+
+M['textDocument/rename'] = function(_, _, result)
+ if not result then return end
+ util.apply_workspace_edit(result)
+end
+
+M['textDocument/rangeFormatting'] = function(_, _, result)
+ if not result then return end
+ util.apply_text_edits(result)
+end
+
+M['textDocument/formatting'] = function(_, _, result)
+ if not result then return end
+ util.apply_text_edits(result)
+end
+
+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])
+ local line_to_cursor = line:sub(col+1)
+
+ local matches = util.text_document_completion_list_to_complete_items(result, line_to_cursor)
+ vim.fn.complete(col, matches)
+end
+
+M['textDocument/hover'] = function(_, method, result)
+ util.focusable_preview(method, function()
+ if not (result and result.contents) then
+ return { 'No information available' }
+ end
+ local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
+ markdown_lines = util.trim_empty_lines(markdown_lines)
+ if vim.tbl_isempty(markdown_lines) then
+ return { 'No information available' }
+ end
+ return markdown_lines, util.try_trim_markdown_code_blocks(markdown_lines)
+ end)
+end
+
+local function location_callback(_, method, result)
+ if result == nil or vim.tbl_isempty(result) then
+ local _ = log.info() and log.info(method, 'No location found')
+ return nil
+ end
+ util.jump_to_location(result[1])
+ if #result > 1 then
+ util.set_qflist(result)
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+ end
+end
+
+M['textDocument/declaration'] = location_callback
+M['textDocument/definition'] = location_callback
+M['textDocument/typeDefinition'] = location_callback
+M['textDocument/implementation'] = location_callback
+
+--- Convert SignatureHelp response to preview contents.
+-- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp
+local function signature_help_to_preview_contents(input)
+ if not input.signatures then
+ return
+ end
+ --The active signature. If omitted or the value lies outside the range of
+ --`signatures` the value defaults to zero or is ignored if `signatures.length
+ --=== 0`. Whenever possible implementors should make an active decision about
+ --the active signature and shouldn't rely on a default value.
+ local contents = {}
+ local active_signature = input.activeSignature or 0
+ -- If the activeSignature is not inside the valid range, then clip it.
+ if active_signature >= #input.signatures then
+ active_signature = 0
+ end
+ local signature = input.signatures[active_signature + 1]
+ if not signature then
+ return
+ end
+ vim.list_extend(contents, vim.split(signature.label, '\n', true))
+ if signature.documentation then
+ util.convert_input_to_markdown_lines(signature.documentation, contents)
+ end
+ if input.parameters then
+ local active_parameter = input.activeParameter or 0
+ -- If the activeParameter is not inside the valid range, then clip it.
+ if active_parameter >= #input.parameters then
+ active_parameter = 0
+ end
+ local parameter = signature.parameters and signature.parameters[active_parameter]
+ if parameter then
+ --[=[
+ --Represents a parameter of a callable-signature. A parameter can
+ --have a label and a doc-comment.
+ interface ParameterInformation {
+ --The label of this parameter information.
+ --
+ --Either a string or an inclusive start and exclusive end offsets within its containing
+ --signature label. (see SignatureInformation.label). The offsets are based on a UTF-16
+ --string representation as `Position` and `Range` does.
+ --
+ --*Note*: a label of type string should be a substring of its containing signature label.
+ --Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`.
+ label: string | [number, number];
+ --The human-readable doc-comment of this parameter. Will be shown
+ --in the UI but can be omitted.
+ documentation?: string | MarkupContent;
+ }
+ --]=]
+ -- TODO highlight parameter
+ if parameter.documentation then
+ util.convert_input_to_markdown_lines(parameter.documentation, contents)
+ end
+ end
+ end
+ return contents
+end
+
+M['textDocument/signatureHelp'] = function(_, method, result)
+ util.focusable_preview(method, function()
+ if not (result and result.signatures and result.signatures[1]) then
+ return { 'No signature available' }
+ end
+ -- TODO show popup when signatures is empty?
+ local lines = signature_help_to_preview_contents(result)
+ lines = util.trim_empty_lines(lines)
+ if vim.tbl_isempty(lines) then
+ return { 'No signature available' }
+ end
+ return lines, util.try_trim_markdown_code_blocks(lines)
+ end)
+end
+
+M['textDocument/peekDefinition'] = function(_, _, result, _)
+ if not (result and result[1]) then return end
+ local loc = result[1]
+ local bufnr = vim.uri_to_bufnr(loc.uri) or error("not found: "..tostring(loc.uri))
+ local start = loc.range.start
+ local finish = loc.range["end"]
+ util.open_floating_peek_preview(bufnr, start, finish, { offset_x = 1 })
+ local headbuf = util.open_floating_preview({"Peek:"}, nil, {
+ offset_y = -(finish.line - start.line);
+ width = finish.character - start.character + 2;
+ })
+ -- TODO(ashkan) change highlight group?
+ api.nvim_buf_add_highlight(headbuf, -1, 'Keyword', 0, -1)
+end
+
+local function log_message(_, _, result, client_id)
+ local message_type = result.type
+ local message = result.message
+ 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
+ if message_type == protocol.MessageType.Error then
+ err_message("LSP[", client_name, "] ", message)
+ else
+ local message_type_name = protocol.MessageType[message_type]
+ api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message))
+ end
+ return result
+end
+
+M['window/showMessage'] = log_message
+M['window/logMessage'] = log_message
+
+-- Add boilerplate error validation and logging for all of these.
+for k, fn in pairs(M) do
+ M[k] = function(err, method, params, client_id)
+ local _ = log.debug() and log.debug('default_callback', method, { params = params, client_id = client_id, err = err })
+ if err then
+ error(tostring(err))
+ end
+ return fn(err, method, params, client_id)
+ end
+end
+
+return M
+-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 1413a88ce2..ead90cc75a 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -10,7 +10,6 @@ end
--[=[
-- Useful for interfacing with:
--- https://github.com/microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md
-- https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specifications/specification-3-14.md
function transform_schema_comments()
nvim.command [[silent! '<,'>g/\/\*\*\|\*\/\|^$/d]]
@@ -681,19 +680,6 @@ function protocol.make_client_capabilities()
}
end
-function protocol.make_text_document_position_params()
- local position = vim.api.nvim_win_get_cursor(0)
- return {
- textDocument = {
- uri = vim.uri_from_bufnr()
- };
- position = {
- line = position[1] - 1;
- character = position[2];
- }
- }
-end
-
--[=[
export interface DocumentFilter {
--A language id, like `typescript`.
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index e0ec8863d6..a558f66a42 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -340,6 +340,7 @@ local function create_and_start_client(cmd, cmd_args, handlers, extra_spawn_para
local decoded, err = json_decode(body)
if not decoded then
on_error(client_errors.INVALID_SERVER_JSON, err)
+ return
end
local _ = log.debug() and log.debug("decoded", decoded)
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index f96e0f01a8..6e0d3fd4ee 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -1,6 +1,8 @@
local protocol = require 'vim.lsp.protocol'
+local vim = vim
local validate = vim.validate
local api = vim.api
+local list_extend = vim.list_extend
local M = {}
@@ -9,7 +11,13 @@ local function split_lines(value)
return split(value, '\n', true)
end
-local list_extend = vim.list_extend
+local function ok_or_nil(status, ...)
+ if not status then return end
+ return ...
+end
+local function npcall(fn, ...)
+ return ok_or_nil(pcall(fn, ...))
+end
--- Find the longest shared prefix between prefix and word.
-- e.g. remove_prefix("123tes", "testing") == "ting"
@@ -26,11 +34,90 @@ local function remove_prefix(prefix, word)
return word:sub(prefix_length + 1)
end
-local function resolve_bufnr(bufnr)
- if bufnr == nil or bufnr == 0 then
- return api.nvim_get_current_buf()
+-- TODO(ashkan) @performance this could do less copying.
+function M.set_lines(lines, A, B, new_lines)
+ -- 0-indexing to 1-indexing
+ local i_0 = A[1] + 1
+ local i_n = B[1] + 1
+ if not (i_0 >= 1 and i_0 <= #lines and i_n >= 1 and i_n <= #lines) then
+ error("Invalid range: "..vim.inspect{A = A; B = B; #lines, new_lines})
+ end
+ local prefix = ""
+ local suffix = lines[i_n]:sub(B[2]+1)
+ if A[2] > 0 then
+ prefix = lines[i_0]:sub(1, A[2])
+ end
+ local n = i_n - i_0 + 1
+ if n ~= #new_lines then
+ for _ = 1, n - #new_lines do table.remove(lines, i_0) end
+ for _ = 1, #new_lines - n do table.insert(lines, i_0, '') end
+ end
+ for i = 1, #new_lines do
+ lines[i - 1 + i_0] = new_lines[i]
+ end
+ if #suffix > 0 then
+ local i = i_0 + #new_lines - 1
+ lines[i] = lines[i]..suffix
+ end
+ if #prefix > 0 then
+ lines[i_0] = prefix..lines[i_0]
+ end
+ return lines
+end
+
+local function sort_by_key(fn)
+ return function(a,b)
+ local ka, kb = fn(a), fn(b)
+ assert(#ka == #kb)
+ for i = 1, #ka do
+ if ka[i] ~= kb[i] then
+ return ka[i] < kb[i]
+ end
+ end
+ -- every value must have been equal here, which means it's not less than.
+ return false
+ end
+end
+local edit_sort_key = sort_by_key(function(e)
+ return {e.A[1], e.A[2], e.i}
+end)
+
+function M.apply_text_edits(text_edits, bufnr)
+ if not next(text_edits) then return end
+ local start_line, finish_line = math.huge, -1
+ local cleaned = {}
+ for i, e in ipairs(text_edits) do
+ 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 = {e.range.start.line; e.range.start.character};
+ B = {e.range["end"].line; e.range["end"].character};
+ lines = vim.split(e.newText, '\n', true);
+ })
+ 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[#lines] ~= 0 then
+ table.insert(lines, '')
+ 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
- return bufnr
+ api.nvim_buf_set_lines(bufnr, start_line, finish_line + 1, false, lines)
end
-- local valid_windows_path_characters = "[^<>:\"/\\|?*]"
@@ -40,30 +127,6 @@ end
-- function M.glob_to_regex(glob)
-- end
---- Apply the TextEdit response.
--- @params TextEdit [table] see https://microsoft.github.io/language-server-protocol/specification
-function M.text_document_apply_text_edit(text_edit, bufnr)
- bufnr = resolve_bufnr(bufnr)
- local range = text_edit.range
- local start = range.start
- local finish = range['end']
- local new_lines = split_lines(text_edit.newText)
- if start.character == 0 and finish.character == 0 then
- api.nvim_buf_set_lines(bufnr, start.line, finish.line, false, new_lines)
- return
- end
- api.nvim_err_writeln('apply_text_edit currently only supports character ranges starting at 0')
- error('apply_text_edit currently only supports character ranges starting at 0')
- return
- -- TODO test and finish this support for character ranges.
--- local lines = api.nvim_buf_get_lines(0, start.line, finish.line + 1, false)
--- local suffix = lines[#lines]:sub(finish.character+2)
--- local prefix = lines[1]:sub(start.character+2)
--- new_lines[#new_lines] = new_lines[#new_lines]..suffix
--- new_lines[1] = prefix..new_lines[1]
--- api.nvim_buf_set_lines(0, start.line, finish.line, false, new_lines)
-end
-
-- textDocument/completion response returns one of CompletionItem[], CompletionList or null.
-- https://microsoft.github.io/language-server-protocol/specification#textDocument_completion
function M.extract_completion_items(result)
@@ -78,18 +141,15 @@ end
--- Apply the TextDocumentEdit response.
-- @params TextDocumentEdit [table] see https://microsoft.github.io/language-server-protocol/specification
-function M.text_document_apply_text_document_edit(text_document_edit, bufnr)
- -- local text_document = text_document_edit.textDocument
- -- TODO use text_document_version?
- -- local text_document_version = text_document.version
-
- -- TODO technically, you could do this without doing multiple buf_get/set
- -- by getting the full region (smallest line and largest line) and doing
- -- the edits on the buffer, and then applying the buffer at the end.
- -- I'm not sure if that's better.
- for _, text_edit in ipairs(text_document_edit.edits) do
- M.text_document_apply_text_edit(text_edit, bufnr)
+function M.apply_text_document_edit(text_document_edit)
+ local text_document = text_document_edit.textDocument
+ local bufnr = vim.uri_to_bufnr(text_document.uri)
+ -- TODO(ashkan) check this is correct.
+ if api.nvim_buf_get_changedtick(bufnr) > text_document.version then
+ print("Buffer ", text_document.uri, " newer than edits.")
+ return
end
+ M.apply_text_edits(text_document_edit.edits, bufnr)
end
function M.get_current_line_to_cursor()
@@ -145,32 +205,27 @@ function M.text_document_completion_list_to_complete_items(result, line_prefix)
end
-- @params WorkspaceEdit [table] see https://microsoft.github.io/language-server-protocol/specification
-function M.workspace_apply_workspace_edit(workspace_edit)
+function M.apply_workspace_edit(workspace_edit)
if workspace_edit.documentChanges then
for _, change in ipairs(workspace_edit.documentChanges) do
if change.kind then
-- TODO(ashkan) handle CreateFile/RenameFile/DeleteFile
error(string.format("Unsupported change: %q", vim.inspect(change)))
else
- M.text_document_apply_text_document_edit(change)
+ M.apply_text_document_edit(change)
end
end
return
end
- if workspace_edit.changes == nil or #workspace_edit.changes == 0 then
+ local all_changes = workspace_edit.changes
+ if not (all_changes and not vim.tbl_isempty(all_changes)) then
return
end
- for uri, changes in pairs(workspace_edit.changes) do
- local fname = vim.uri_to_fname(uri)
- -- TODO improve this approach. Try to edit open buffers without switching.
- -- Not sure how to handle files which aren't open. This is deprecated
- -- anyway, so I guess it could be left as is.
- api.nvim_command('edit '..fname)
- for _, change in ipairs(changes) do
- M.text_document_apply_text_edit(change)
- end
+ for uri, changes in pairs(all_changes) do
+ local bufnr = vim.uri_to_bufnr(uri)
+ M.apply_text_edits(changes, bufnr)
end
end
@@ -255,35 +310,77 @@ function M.make_floating_popup_options(width, height, opts)
}
end
+function M.jump_to_location(location)
+ if location.uri == nil then return end
+ local bufnr = vim.uri_to_bufnr(location.uri)
+ -- TODO(ashkan) use tagfunc here to update tagstack.
+ api.nvim_set_current_buf(bufnr)
+ local row = location.range.start.line
+ local col = location.range.start.character
+ local line = api.nvim_buf_get_lines(0, row, row+1, true)[1]
+ col = vim.str_byteindex(line, col)
+ api.nvim_win_set_cursor(0, {row + 1, col})
+ return true
+end
+
+local function find_window_by_var(name, value)
+ for _, win in ipairs(api.nvim_list_wins()) do
+ if npcall(api.nvim_win_get_var, win, name) == value then
+ return win
+ end
+ end
+end
+
+-- Check if a window with `unique_name` tagged is associated with the current
+-- buffer. If not, make a new preview.
+--
+-- fn()'s return values will be passed directly to open_floating_preview in the
+-- case that a new floating window should be created.
+function M.focusable_preview(unique_name, fn)
+ if npcall(api.nvim_win_get_var, 0, unique_name) then
+ return api.nvim_command("wincmd p")
+ end
+ local bufnr = api.nvim_get_current_buf()
+ do
+ local win = find_window_by_var(unique_name, bufnr)
+ if win then
+ api.nvim_set_current_win(win)
+ api.nvim_command("stopinsert")
+ return
+ end
+ end
+ local pbufnr, pwinnr = M.open_floating_preview(fn())
+ api.nvim_win_set_var(pwinnr, unique_name, bufnr)
+ return pbufnr, pwinnr
+end
+
function M.open_floating_preview(contents, filetype, opts)
validate {
contents = { contents, 't' };
filetype = { filetype, 's', true };
opts = { opts, 't', true };
}
+ opts = opts or {}
-- Trim empty lines from the end.
- for i = #contents, 1, -1 do
- if #contents[i] == 0 then
- table.remove(contents)
- else
- break
+ contents = M.trim_empty_lines(contents)
+
+ local width = opts.width
+ local height = opts.height or #contents
+ if not width then
+ width = 0
+ for i, line in ipairs(contents) do
+ -- Clean up the input and add left pad.
+ line = " "..line:gsub("\r", "")
+ -- TODO(ashkan) use nvim_strdisplaywidth if/when that is introduced.
+ local line_width = vim.fn.strdisplaywidth(line)
+ width = math.max(line_width, width)
+ contents[i] = line
end
+ -- Add right padding of 1 each.
+ width = width + 1
end
- local width = 0
- local height = #contents
- for i, line in ipairs(contents) do
- -- Clean up the input and add left pad.
- line = " "..line:gsub("\r", "")
- -- TODO(ashkan) use nvim_strdisplaywidth if/when that is introduced.
- local line_width = vim.fn.strdisplaywidth(line)
- width = math.max(line_width, width)
- contents[i] = line
- end
- -- Add right padding of 1 each.
- width = width + 1
-
local floating_bufnr = api.nvim_create_buf(false, true)
if filetype then
api.nvim_buf_set_option(floating_bufnr, 'filetype', filetype)
@@ -295,7 +392,8 @@ function M.open_floating_preview(contents, filetype, opts)
end
api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents)
api.nvim_buf_set_option(floating_bufnr, 'modifiable', false)
- api.nvim_command("autocmd CursorMoved <buffer> ++once lua pcall(vim.api.nvim_win_close, "..floating_winnr..", true)")
+ -- TODO make InsertCharPre disappearing optional?
+ api.nvim_command("autocmd CursorMoved,BufHidden,InsertCharPre <buffer> ++once lua pcall(vim.api.nvim_win_close, "..floating_winnr..", true)")
return floating_bufnr, floating_winnr
end
@@ -527,30 +625,141 @@ do
end
end
-function M.buf_loclist(bufnr, locations)
- local targetwin
- for _, winnr in ipairs(api.nvim_list_wins()) do
- local winbuf = api.nvim_win_get_buf(winnr)
- if winbuf == bufnr then
- targetwin = winnr
- break
- end
- end
- if not targetwin then return end
+local position_sort = sort_by_key(function(v)
+ return {v.line, v.character}
+end)
+-- Returns the items with the byte position calculated correctly and in sorted
+-- order.
+function M.locations_to_items(locations)
local items = {}
- local path = api.nvim_buf_get_name(bufnr)
+ local grouped = setmetatable({}, {
+ __index = function(t, k)
+ local v = {}
+ rawset(t, k, v)
+ return v
+ end;
+ })
for _, d in ipairs(locations) do
- -- TODO: URL parsing here?
local start = d.range.start
- table.insert(items, {
- filename = path,
- lnum = start.line + 1,
- col = start.character + 1,
- text = d.message,
- })
+ local fname = assert(vim.uri_to_fname(d.uri))
+ table.insert(grouped[fname], start)
+ end
+ local keys = vim.tbl_keys(grouped)
+ table.sort(keys)
+ -- TODO(ashkan) I wish we could do this lazily.
+ for _, fname in ipairs(keys) do
+ local rows = grouped[fname]
+ table.sort(rows, position_sort)
+ local i = 0
+ for line in io.lines(fname) do
+ for _, pos in ipairs(rows) do
+ local row = pos.line
+ if i == row then
+ local col
+ if pos.character > #line then
+ col = #line
+ else
+ col = vim.str_byteindex(line, pos.character)
+ end
+ table.insert(items, {
+ filename = fname,
+ lnum = row + 1,
+ col = col + 1;
+ text = line;
+ })
+ end
+ end
+ i = i + 1
+ end
+ end
+ return items
+end
+
+-- locations is Location[]
+-- Only sets for the current window.
+function M.set_loclist(locations)
+ vim.fn.setloclist(0, {}, ' ', {
+ title = 'Language Server';
+ items = M.locations_to_items(locations);
+ })
+end
+
+-- locations is Location[]
+function M.set_qflist(locations)
+ vim.fn.setqflist({}, ' ', {
+ title = 'Language Server';
+ items = M.locations_to_items(locations);
+ })
+end
+
+-- Remove empty lines from the beginning and end.
+function M.trim_empty_lines(lines)
+ local start = 1
+ for i = 1, #lines do
+ if #lines[i] > 0 then
+ start = i
+ break
+ end
+ end
+ local finish = 1
+ for i = #lines, 1, -1 do
+ if #lines[i] > 0 then
+ finish = i
+ break
+ end
+ end
+ return vim.list_extend({}, lines, start, finish)
+end
+
+-- Accepts markdown lines and tries to reduce it to a filetype if it is
+-- just a single code block.
+-- Note: This modifies the input.
+--
+-- Returns: filetype or 'markdown' if it was unchanged.
+function M.try_trim_markdown_code_blocks(lines)
+ local language_id = lines[1]:match("^```(.*)")
+ if language_id then
+ local has_inner_code_fence = false
+ for i = 2, (#lines - 1) do
+ local line = lines[i]
+ if line:sub(1,3) == '```' then
+ has_inner_code_fence = true
+ break
+ end
+ end
+ -- No inner code fences + starting with code fence = hooray.
+ if not has_inner_code_fence then
+ table.remove(lines, 1)
+ table.remove(lines)
+ return language_id
+ end
+ end
+ return 'markdown'
+end
+
+local str_utfindex = vim.str_utfindex
+function M.make_position_params()
+ local row, col = unpack(api.nvim_win_get_cursor(0))
+ row = row - 1
+ local line = api.nvim_buf_get_lines(0, row, row+1, true)[1]
+ col = str_utfindex(line, col)
+ return {
+ textDocument = { uri = vim.uri_from_bufnr(0) };
+ position = { line = row; character = col; }
+ }
+end
+
+-- @param buf buffer handle or 0 for current.
+-- @param row 0-indexed line
+-- @param col 0-indexed byte offset in line
+function M.character_offset(buf, row, col)
+ local line = api.nvim_buf_get_lines(buf, row, row+1, true)[1]
+ -- If the col is past the EOL, use the line length.
+ if col > #line then
+ return str_utfindex(line)
end
- vim.fn.setloclist(targetwin, items, ' ', 'Language Server')
+ return str_utfindex(line, col)
end
return M
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index ff89acc524..631dd04c35 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -4,7 +4,7 @@
-- test-suite. If, in the future, Nvim itself is used to run the test-suite
-- instead of "vanilla Lua", these functions could move to src/nvim/lua/vim.lua
-local vim = {}
+local vim = vim or {}
--- Returns a deep copy of the given object. Non-table objects are copied as
--- in a typical Lua assignment, whereas table objects are copied recursively.
@@ -228,16 +228,24 @@ end
--- Extends a list-like table with the values of another list-like table.
---
---NOTE: This *mutates* dst!
---@see |extend()|
+--- NOTE: This mutates dst!
+---
+--@see |vim.tbl_extend()|
---
---@param dst The list which will be modified and appended to.
---@param src The list from which values will be inserted.
-function vim.list_extend(dst, src)
- assert(type(dst) == 'table', "dst must be a table")
- assert(type(src) == 'table', "src must be a table")
- for _, v in ipairs(src) do
- table.insert(dst, v)
+--@param dst list which will be modified and appended to.
+--@param src list from which values will be inserted.
+--@param start Start index on src. defaults to 1
+--@param finish Final index on src. defaults to #src
+--@returns dst
+function vim.list_extend(dst, src, start, finish)
+ vim.validate {
+ dst = {dst, 't'};
+ src = {src, 't'};
+ start = {start, 'n', true};
+ finish = {finish, 'n', true};
+ }
+ for i = start or 1, finish or #src do
+ table.insert(dst, src[i])
end
return dst
end
diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua
index 0a6e0fcb97..1065f84f4c 100644
--- a/runtime/lua/vim/uri.lua
+++ b/runtime/lua/vim/uri.lua
@@ -77,13 +77,18 @@ local function uri_to_fname(uri)
else
uri = uri:gsub('^file://', '')
end
-
return uri_decode(uri)
end
+-- Return or create a buffer for a uri.
+local function uri_to_bufnr(uri)
+ return vim.fn.bufadd((uri_to_fname(uri)))
+end
+
return {
uri_from_fname = uri_from_fname,
uri_from_bufnr = uri_from_bufnr,
uri_to_fname = uri_to_fname,
+ uri_to_bufnr = uri_to_bufnr,
}
-- vim:sw=2 ts=2 et
diff --git a/runtime/nvim.appdata.xml b/runtime/nvim.appdata.xml
index ceffc7ba98..025de1b5a9 100644
--- a/runtime/nvim.appdata.xml
+++ b/runtime/nvim.appdata.xml
@@ -26,6 +26,7 @@
</screenshots>
<releases>
+ <release date="2019-11-06" version="0.4.3"/>
<release date="2019-09-15" version="0.4.2"/>
<release date="2019-09-15" version="0.4.1"/>
<release date="2019-09-15" version="0.4.0"/>
diff --git a/runtime/plugin/man.vim b/runtime/plugin/man.vim
index e18a5528bb..e762eb3664 100644
--- a/runtime/plugin/man.vim
+++ b/runtime/plugin/man.vim
@@ -1,4 +1,4 @@
-" Maintainer: Anmol Sethi <anmol@aubble.com>
+" Maintainer: Anmol Sethi <hi@nhooyr.io>
if exists('g:loaded_man')
finish
diff --git a/runtime/syntax/man.vim b/runtime/syntax/man.vim
index 6afe56a6e3..7ac02c3f63 100644
--- a/runtime/syntax/man.vim
+++ b/runtime/syntax/man.vim
@@ -1,4 +1,4 @@
-" Maintainer: Anmol Sethi <anmol@aubble.com>
+" Maintainer: Anmol Sethi <hi@nhooyr.io>
" Previous Maintainer: SungHyun Nam <goweol@gmail.com>
if exists('b:current_syntax')
@@ -30,6 +30,7 @@ endif
if !exists('b:man_sect')
call man#init_pager()
endif
+
if b:man_sect =~# '^[023]'
syntax case match
syntax include @c $VIMRUNTIME/syntax/c.vim
diff --git a/scripts/release.sh b/scripts/release.sh
index 67268ba9bf..5b4902a2d7 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -93,6 +93,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
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index b00ac866b7..bc8e64dd41 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -52,7 +52,8 @@ set(UNICODE_TABLES_GENERATOR ${GENERATOR_DIR}/gen_unicode_tables.lua)
set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode)
set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h)
set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h)
-set(VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua)
+set(LUA_VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua)
+set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua)
set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua)
set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json)
set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint")
@@ -317,11 +318,13 @@ add_custom_command(
add_custom_command(
OUTPUT ${VIM_MODULE_FILE}
- COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_SOURCE}
- ${VIM_MODULE_FILE} vim_module
+ COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_FILE}
+ ${LUA_VIM_MODULE_SOURCE} vim_module
+ ${LUA_SHARED_MODULE_SOURCE} shared_module
DEPENDS
${CHAR_BLOB_GENERATOR}
- ${VIM_MODULE_SOURCE}
+ ${LUA_VIM_MODULE_SOURCE}
+ ${LUA_SHARED_MODULE_SOURCE}
)
list(APPEND NVIM_GENERATED_SOURCES
@@ -462,6 +465,7 @@ install_helper(TARGETS nvim)
set_property(TARGET nvim APPEND PROPERTY
INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS})
+set_property(TARGET nvim PROPERTY ENABLE_EXPORTS TRUE)
if(ENABLE_LTO AND (POLICY CMP0069))
include(CheckIPOSupported)
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index a5f8b0974e..8f5718d97e 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1013,10 +1013,10 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
/// Returns position for a given extmark id
///
-/// @param buffer The buffer handle
-/// @param namespace a identifier returned previously with nvim_create_namespace
-/// @param id the extmark id
-/// @param[out] err Details of an error that may have occurred
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id
+/// @param[out] err Error details, if any
/// @return (row, col) tuple or empty list () if extmark id was absent
ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
Integer id, Error *err)
@@ -1044,30 +1044,50 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
return rv;
}
-/// List extmarks in a range (inclusive)
-///
-/// range ends can be specified as (row, col) tuples, as well as extmark
-/// ids in the same namespace. In addition, 0 and -1 works as shorthands
-/// for (0,0) and (-1,-1) respectively, so that all marks in the buffer can be
-/// queried as:
-///
-/// all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
-///
-/// If end is a lower position than start, then the range will be traversed
-/// backwards. This is mostly useful with limited amount, to be able to get the
-/// first marks prior to a given position.
-///
-/// @param buffer The buffer handle
-/// @param ns_id An id returned previously from nvim_create_namespace
-/// @param start One of: extmark id, (row, col) or 0, -1 for buffer ends
-/// @param end One of: extmark id, (row, col) or 0, -1 for buffer ends
-/// @param opts additional options. Supports the keys:
-/// - amount: Maximum number of marks to return
-/// @param[out] err Details of an error that may have occurred
-/// @return [[extmark_id, row, col], ...]
-Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
- Object start, Object end, Dictionary opts,
- Error *err)
+/// Gets extmarks in "traversal order" from a |charwise| region defined by
+/// buffer positions (inclusive, 0-indexed |api-indexing|).
+///
+/// Region can be given as (row,col) tuples, or valid extmark ids (whose
+/// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1)
+/// respectively, thus the following are equivalent:
+///
+/// <pre>
+/// nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+/// nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {})
+/// </pre>
+///
+/// If `end` is less than `start`, traversal works backwards. (Useful
+/// with `limit`, to get the first marks prior to a given position.)
+///
+/// Example:
+///
+/// <pre>
+/// local a = vim.api
+/// local pos = a.nvim_win_get_cursor(0)
+/// local ns = a.nvim_create_namespace('my-plugin')
+/// -- Create new extmark at line 1, column 1.
+/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {})
+/// -- Create new extmark at line 3, column 1.
+/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {})
+/// -- Get extmarks only from line 3.
+/// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
+/// -- Get all marks in this buffer + namespace.
+/// local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {})
+/// print(vim.inspect(ms))
+/// </pre>
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param start Start of range, given as (row, col) or valid extmark id
+/// (whose position defines the bound)
+/// @param end End of range, given as (row, col) or valid extmark id
+/// (whose position defines the bound)
+/// @param opts Optional parameters. Keys:
+/// - limit: Maximum number of marks to return
+/// @param[out] err Error details, if any
+/// @return List of [extmark_id, row, col] tuples in "traversal order".
+Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
+ Object end, Dictionary opts, Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
@@ -1081,17 +1101,17 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
return rv;
}
- Integer amount = -1;
+ Integer limit = -1;
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
- if (strequal("amount", k.data)) {
+ if (strequal("limit", k.data)) {
if (v->type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "amount is not an integer");
+ api_set_error(err, kErrorTypeValidation, "limit is not an integer");
return rv;
}
- amount = v->data.integer;
+ limit = v->data.integer;
v->data.integer = LUA_NOREF;
} else {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
@@ -1099,7 +1119,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
}
}
- if (amount == 0) {
+ if (limit == 0) {
return rv;
}
@@ -1108,13 +1128,13 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
linenr_T l_lnum;
colnr_T l_col;
- if (!set_extmark_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) {
+ if (!extmark_get_index_from_obj(buf, ns_id, start, &l_lnum, &l_col, err)) {
return rv;
}
linenr_T u_lnum;
colnr_T u_col;
- if (!set_extmark_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) {
+ if (!extmark_get_index_from_obj(buf, ns_id, end, &u_lnum, &u_col, err)) {
return rv;
}
@@ -1129,9 +1149,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
}
- ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_lnum, l_col,
- u_lnum, u_col, (int64_t)amount,
- reverse);
+ ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_lnum, l_col, u_lnum,
+ u_col, (int64_t)limit, reverse);
for (size_t i = 0; i < kv_size(marks); i++) {
Array mark = ARRAY_DICT_INIT;
@@ -1146,26 +1165,23 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
return rv;
}
-/// Create or update an extmark at a position
+/// Creates or updates an extmark.
///
-/// If an invalid namespace is given, an error will be raised.
-///
-/// To create a new extmark, pass in id=0. The new extmark id will be
-/// returned. To move an existing mark, pass in its id.
+/// To create a new extmark, pass id=0. The extmark id will be returned.
+// To move an existing mark, pass its id.
///
/// It is also allowed to create a new mark by passing in a previously unused
/// id, but the caller must then keep track of existing and unused ids itself.
-/// This is mainly useful over RPC, to avoid needing to wait for the return
-/// value.
-///
-/// @param buffer The buffer handle
-/// @param ns_id a identifier returned previously with nvim_create_namespace
-/// @param id The extmark's id or 0 to create a new mark.
-/// @param line The row to set the extmark to.
-/// @param col The column to set the extmark to.
-/// @param opts Optional parameters. Currently not used.
-/// @param[out] err Details of an error that may have occurred
-/// @return the id of the extmark.
+/// (Useful over RPC, to avoid waiting for the return value.)
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id, or 0 to create new
+/// @param line Line number where to place the mark
+/// @param col Column where to place the mark
+/// @param opts Optional parameters. Currently not used.
+/// @param[out] err Error details, if any
+/// @return Id of the created/updated extmark
Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
Integer line, Integer col,
Dictionary opts, Error *err)
@@ -1191,7 +1207,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
api_set_error(err, kErrorTypeValidation, "line value outside range");
return 0;
} else if (line < buf->b_ml.ml_line_count) {
- len = STRLEN(ml_get_buf(curbuf, (linenr_T)line+1, false));
+ len = STRLEN(ml_get_buf(buf, (linenr_T)line+1, false));
}
if (col == -1) {
@@ -1217,13 +1233,13 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
return (Integer)id_num;
}
-/// Remove an extmark
+/// Removes an extmark.
///
-/// @param buffer The buffer handle
-/// @param ns_id a identifier returned previously with nvim_create_namespace
-/// @param id The extmarks's id
-/// @param[out] err Details of an error that may have occurred
-/// @return true on success, false if the extmark was not found.
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param id Extmark id
+/// @param[out] err Error details, if any
+/// @return true if the extmark was found, else false
Boolean nvim_buf_del_extmark(Buffer buffer,
Integer ns_id,
Integer id,
@@ -1309,7 +1325,8 @@ Integer nvim_buf_add_highlight(Buffer buffer,
return ns_id;
}
-/// Clears namespaced objects, highlights and virtual text, from a line range
+/// Clears namespaced objects (highlights, extmarks, virtual text) from
+/// a region.
///
/// Lines are 0-indexed. |api-indexing| To clear the namespace in the entire
/// buffer, specify line_start=0 and line_end=-1.
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index fbfdb27827..b8d62e42a1 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1512,7 +1512,7 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
// If throw == true then an error will be raised if nothing
// was found
// Returns NULL if something went wrong
-Extmark *extmark_from_id_or_pos(Buffer buffer, Integer namespace, Object id,
+Extmark *extmark_from_id_or_pos(Buffer buffer, Integer ns, Object id,
Error *err, bool throw)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -1536,7 +1536,7 @@ Extmark *extmark_from_id_or_pos(Buffer buffer, Integer namespace, Object id,
}
return NULL;
}
- extmark = extmark_from_pos(buf, (uint64_t)namespace, row, col);
+ extmark = extmark_from_pos(buf, (uint64_t)ns, row, col);
} else if (id.type != kObjectTypeInteger) {
if (throw) {
api_set_error(err, kErrorTypeValidation,
@@ -1550,7 +1550,7 @@ Extmark *extmark_from_id_or_pos(Buffer buffer, Integer namespace, Object id,
return NULL;
} else {
extmark = extmark_from_id(buf,
- (uint64_t)namespace,
+ (uint64_t)ns,
(uint64_t)id.data.integer);
}
@@ -1572,17 +1572,17 @@ bool ns_initialized(uint64_t ns)
return ns < (uint64_t)next_namespace_id;
}
-/// Get line and column from extmark object
+/// Gets the line and column of an extmark.
///
-/// Extmarks may be queried from position or name or even special names
-/// in the future such as "cursor". This function sets the line and col
-/// to make the extmark functions recognize what's required
+/// Extmarks may be queried by position, name or even special names
+/// in the future such as "cursor".
///
-/// @param[out] lnum lnum to be set
-/// @param[out] colnr col to be set
-bool set_extmark_index_from_obj(buf_T *buf, Integer namespace,
- Object obj, linenr_T *lnum, colnr_T *colnr,
- Error *err)
+/// @param[out] lnum extmark line
+/// @param[out] colnr extmark column
+///
+/// @return true if the extmark was found, else false
+bool extmark_get_index_from_obj(buf_T *buf, Integer ns, Object obj, linenr_T
+ *lnum, colnr_T *colnr, Error *err)
{
// Check if it is mark id
if (obj.type == kObjectTypeInteger) {
@@ -1600,7 +1600,7 @@ bool set_extmark_index_from_obj(buf_T *buf, Integer namespace,
return false;
}
- Extmark *extmark = extmark_from_id(buf, (uint64_t)namespace, (uint64_t)id);
+ Extmark *extmark = extmark_from_id(buf, (uint64_t)ns, (uint64_t)id);
if (extmark) {
*lnum = extmark->line->lnum;
*colnr = extmark->col;
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 3535bc3186..9adc61b843 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1291,7 +1291,7 @@ theend:
/// @param lines |readfile()|-style list of lines. |channel-lines|
/// @param type Edit behavior: any |getregtype()| result, or:
/// - "b" |blockwise-visual| mode (may include width, e.g. "b3")
-/// - "c" |characterwise| mode
+/// - "c" |charwise| mode
/// - "l" |linewise| mode
/// - "" guess by contents, see |setreg()|
/// @param after Insert after cursor (like |p|), or before (like |P|).
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 0cd02ce36b..e3b8e9cc6d 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -411,11 +411,11 @@ bool buf_valid(buf_T *buf)
/// caller should get a new buffer very soon!
/// The 'bufhidden' option can force freeing and deleting.
/// @param abort_if_last
-/// If TRUE, do not close the buffer if autocommands cause
+/// If true, do not close the buffer if autocommands cause
/// there to be only one window with this buffer. e.g. when
/// ":quit" is supposed to close the window but autocommands
/// close all other windows.
-void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
+void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
{
bool unload_buf = (action != 0);
bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE);
@@ -1238,7 +1238,7 @@ do_buffer(
return FAIL;
}
} else {
- EMSG2(_("E89: %s will be killed(add ! to override)"),
+ EMSG2(_("E89: %s will be killed (add ! to override)"),
(char *)buf->b_fname);
return FAIL;
}
@@ -2694,7 +2694,7 @@ setfname(
buf_T *buf,
char_u *ffname,
char_u *sfname,
- int message // give message when buffer already exists
+ bool message // give message when buffer already exists
)
{
buf_T *obuf = NULL;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 4725246764..0c3b467612 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -1587,7 +1587,7 @@ int rename_buffer(char_u *new_fname)
xfname = curbuf->b_fname;
curbuf->b_ffname = NULL;
curbuf->b_sfname = NULL;
- if (setfname(curbuf, new_fname, NULL, TRUE) == FAIL) {
+ if (setfname(curbuf, new_fname, NULL, true) == FAIL) {
curbuf->b_ffname = fname;
curbuf->b_sfname = sfname;
return FAIL;
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 641edf4610..f18ebffa0a 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -5133,9 +5133,11 @@ static void uc_list(char_u *name, size_t name_len)
ucmd_T *cmd;
int len;
uint32_t a;
- garray_T *gap;
- gap = &curbuf->b_ucmds;
+ // In cmdwin, the alternative buffer should be used.
+ garray_T *gap = (cmdwin_type != 0 && get_cmdline_type() == NUL)
+ ? &prevwin->w_buffer->b_ucmds
+ : &curbuf->b_ucmds;
for (;; ) {
for (i = 0; i < gap->ga_len; ++i) {
cmd = USER_CMD_GA(gap, i);
@@ -5984,13 +5986,21 @@ char_u *get_user_cmd_addr_type(expand_T *xp, int idx)
/*
* Function given to ExpandGeneric() to obtain the list of user command names.
*/
-char_u *get_user_commands(expand_T *xp, int idx)
+char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (idx < curbuf->b_ucmds.ga_len)
- return USER_CMD_GA(&curbuf->b_ucmds, idx)->uc_name;
- idx -= curbuf->b_ucmds.ga_len;
- if (idx < ucmds.ga_len)
+ // In cmdwin, the alternative buffer should be used.
+ const buf_T *const buf = (cmdwin_type != 0 && get_cmdline_type() == NUL)
+ ? prevwin->w_buffer
+ : curbuf;
+
+ if (idx < buf->b_ucmds.ga_len) {
+ return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
+ }
+ idx -= buf->b_ucmds.ga_len;
+ if (idx < ucmds.ga_len) {
return USER_CMD(idx)->uc_name;
+ }
return NULL;
}
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 9e2671ca5e..7948da5e6b 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -5008,19 +5008,24 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
hashtab_T found_ht;
hash_init(&found_ht);
for (s = path; ; s = e) {
+ e = vim_strchr(s, ENV_SEPCHAR);
+ if (e == NULL) {
+ e = s + STRLEN(s);
+ }
+
if (*s == NUL) {
if (did_curdir) {
break;
}
// Find directories in the current directory, path is empty.
did_curdir = true;
- } else if (*s == '.') {
+ flags |= EW_DIR;
+ } else if (STRNCMP(s, ".", e - s) == 0) {
did_curdir = true;
- }
-
- e = vim_strchr(s, ENV_SEPCHAR);
- if (e == NULL) {
- e = s + STRLEN(s);
+ flags |= EW_DIR;
+ } else {
+ // Do not match directories inside a $PATH item.
+ flags &= ~EW_DIR;
}
l = (size_t)(e - s);
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index fcf15638c7..f518e59acc 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -3731,8 +3731,9 @@ static int set_rw_fname(char_u *fname, char_u *sfname)
return FAIL;
}
- if (setfname(curbuf, fname, sfname, FALSE) == OK)
+ if (setfname(curbuf, fname, sfname, false) == OK) {
curbuf->b_flags |= BF_NOTEDITED;
+ }
/* ....and a new named one is created */
apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, curbuf);
diff --git a/src/nvim/generators/gen_char_blob.lua b/src/nvim/generators/gen_char_blob.lua
index 1702add2e4..a7dad50d48 100644
--- a/src/nvim/generators/gen_char_blob.lua
+++ b/src/nvim/generators/gen_char_blob.lua
@@ -1,49 +1,59 @@
if arg[1] == '--help' then
print('Usage:')
- print(' gencharblob.lua source target varname')
+ print(' '..arg[0]..' target source varname [source varname]...')
print('')
print('Generates C file with big uint8_t blob.')
print('Blob will be stored in a static const array named varname.')
os.exit()
end
-assert(#arg == 3)
+assert(#arg >= 3 and (#arg - 1) % 2 == 0)
-local source_file = arg[1]
-local target_file = arg[2]
-local varname = arg[3]
-
-local source = io.open(source_file, 'r')
+local target_file = arg[1] or error('Need a target file')
local target = io.open(target_file, 'w')
target:write('#include <stdint.h>\n\n')
-target:write(('static const uint8_t %s[] = {\n'):format(varname))
-
-local num_bytes = 0
-local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line
-target:write(' ')
-
-local increase_num_bytes
-increase_num_bytes = function()
- num_bytes = num_bytes + 1
- if num_bytes == MAX_NUM_BYTES then
- num_bytes = 0
- target:write('\n ')
+
+local varnames = {}
+for argi = 2, #arg, 2 do
+ local source_file = arg[argi]
+ local varname = arg[argi + 1]
+ if varnames[varname] then
+ error(string.format("varname %q is already specified for file %q", varname, varnames[varname]))
end
-end
+ varnames[varname] = source_file
+
+ local source = io.open(source_file, 'r')
+ or error(string.format("source_file %q doesn't exist", source_file))
+
+ target:write(('static const uint8_t %s[] = {\n'):format(varname))
-for line in source:lines() do
- for i = 1,string.len(line) do
- local byte = string.byte(line, i)
- assert(byte ~= 0)
- target:write(string.format(' %3u,', byte))
+ local num_bytes = 0
+ local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line
+ target:write(' ')
+
+ local increase_num_bytes
+ increase_num_bytes = function()
+ num_bytes = num_bytes + 1
+ if num_bytes == MAX_NUM_BYTES then
+ num_bytes = 0
+ target:write('\n ')
+ end
+ end
+
+ for line in source:lines() do
+ for i = 1, string.len(line) do
+ local byte = line:byte(i)
+ assert(byte ~= 0)
+ target:write(string.format(' %3u,', byte))
+ increase_num_bytes()
+ end
+ target:write(string.format(' %3u,', string.byte('\n', 1)))
increase_num_bytes()
end
- target:write(string.format(' %3u,', string.byte('\n', 1)))
- increase_num_bytes()
-end
-target:write(' 0};\n')
+ target:write(' 0};\n')
+ source:close()
+end
-source:close()
target:close()
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 093c130c5f..5450f62f54 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -268,12 +268,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
#endif
// vim
- const char *code = (char *)&vim_module[0];
- if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua")
- || lua_pcall(lstate, 0, LUA_MULTRET, 0)) {
- nlua_error(lstate, _("E5106: Error while creating vim module: %.*s"));
- return 1;
- }
+ lua_newtable(lstate);
// vim.api
nlua_add_api_functions(lstate);
// vim.types, vim.type_idx, vim.val_idx
@@ -334,6 +329,24 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_setglobal(lstate, "vim");
+ {
+ const char *code = (char *)&shared_module[0];
+ if (luaL_loadbuffer(lstate, code, strlen(code), "@shared.lua")
+ || lua_pcall(lstate, 0, 0, 0)) {
+ nlua_error(lstate, _("E5106: Error while creating shared module: %.*s"));
+ return 1;
+ }
+ }
+
+ {
+ const char *code = (char *)&vim_module[0];
+ if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua")
+ || lua_pcall(lstate, 0, 0, 0)) {
+ nlua_error(lstate, _("E5106: Error while creating vim module: %.*s"));
+ return 1;
+ }
+ }
+
return 0;
}
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 1665a55aff..e13b44a8ed 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -33,35 +33,35 @@
-- - https://github.com/bakpakin/Fennel (pretty print, repl)
-- - https://github.com/howl-editor/howl/tree/master/lib/howl/util
+local vim = vim
+assert(vim)
-- Internal-only until comments in #8107 are addressed.
-- Returns:
-- {errcode}, {output}
-local function _system(cmd)
- local out = vim.api.nvim_call_function('system', { cmd })
- local err = vim.api.nvim_get_vvar('shell_error')
+function vim._system(cmd)
+ local out = vim.fn.system(cmd)
+ local err = vim.v.shell_error
return err, out
end
-- Gets process info from the `ps` command.
-- Used by nvim_get_proc() as a fallback.
-local function _os_proc_info(pid)
+function vim._os_proc_info(pid)
if pid == nil or pid <= 0 or type(pid) ~= 'number' then
error('invalid pid')
end
local cmd = { 'ps', '-p', pid, '-o', 'comm=', }
- local err, name = _system(cmd)
- if 1 == err and string.gsub(name, '%s*', '') == '' then
+ local err, name = vim._system(cmd)
+ if 1 == err and vim.trim(name) == '' then
return {} -- Process not found.
elseif 0 ~= err then
- local args_str = vim.api.nvim_call_function('string', { cmd })
- error('command failed: '..args_str)
+ error('command failed: '..vim.fn.string(cmd))
end
- local _, ppid = _system({ 'ps', '-p', pid, '-o', 'ppid=', })
+ local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=', })
-- Remove trailing whitespace.
- name = string.gsub(string.gsub(name, '%s+$', ''), '^.*/', '')
- ppid = string.gsub(ppid, '%s+$', '')
- ppid = tonumber(ppid) == nil and -1 or tonumber(ppid)
+ name = vim.trim(name):gsub('^.*/', '')
+ ppid = tonumber(ppid) or -1
return {
name = name,
pid = pid,
@@ -71,20 +71,19 @@ end
-- Gets process children from the `pgrep` command.
-- Used by nvim_get_proc_children() as a fallback.
-local function _os_proc_children(ppid)
+function vim._os_proc_children(ppid)
if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then
error('invalid ppid')
end
local cmd = { 'pgrep', '-P', ppid, }
- local err, rv = _system(cmd)
- if 1 == err and string.gsub(rv, '%s*', '') == '' then
+ local err, rv = vim._system(cmd)
+ if 1 == err and vim.trim(rv) == '' then
return {} -- Process not found.
elseif 0 ~= err then
- local args_str = vim.api.nvim_call_function('string', { cmd })
- error('command failed: '..args_str)
+ error('command failed: '..vim.fn.string(cmd))
end
local children = {}
- for s in string.gmatch(rv, '%S+') do
+ for s in rv:gmatch('%S+') do
local i = tonumber(s)
if i ~= nil then
table.insert(children, i)
@@ -98,7 +97,7 @@ end
-- Last inserted paths. Used to clear out items from package.[c]path when they
-- are no longer in &runtimepath.
local last_nvim_paths = {}
-local function _update_package_paths()
+function vim._update_package_paths()
local cur_nvim_paths = {}
local rtps = vim.api.nvim_list_runtime_paths()
local sep = package.config:sub(1, 1)
@@ -162,35 +161,35 @@ local function inspect(object, options) -- luacheck: no unused
error(object, options) -- Stub for gen_vimdoc.py
end
---- Paste handler, invoked by |nvim_paste()| when a conforming UI
---- (such as the |TUI|) pastes text into the editor.
----
---- Example: To remove ANSI color codes when pasting:
---- <pre>
---- vim.paste = (function(overridden)
---- return function(lines, phase)
---- for i,line in ipairs(lines) do
---- -- Scrub ANSI color codes from paste input.
---- lines[i] = line:gsub('\27%[[0-9;mK]+', '')
---- end
---- overridden(lines, phase)
---- end
---- end)(vim.paste)
---- </pre>
----
---@see |paste|
----
---@param lines |readfile()|-style list of lines to paste. |channel-lines|
---@param phase -1: "non-streaming" paste: the call contains all lines.
---- If paste is "streamed", `phase` indicates the stream state:
---- - 1: starts the paste (exactly once)
---- - 2: continues the paste (zero or more times)
---- - 3: ends the paste (exactly once)
---@returns false if client should cancel the paste.
-local function paste(lines, phase) end -- luacheck: no unused
-paste = (function()
+do
local tdots, tick, got_line1 = 0, 0, false
- return function(lines, phase)
+
+ --- Paste handler, invoked by |nvim_paste()| when a conforming UI
+ --- (such as the |TUI|) pastes text into the editor.
+ ---
+ --- Example: To remove ANSI color codes when pasting:
+ --- <pre>
+ --- vim.paste = (function(overridden)
+ --- return function(lines, phase)
+ --- for i,line in ipairs(lines) do
+ --- -- Scrub ANSI color codes from paste input.
+ --- lines[i] = line:gsub('\27%[[0-9;mK]+', '')
+ --- end
+ --- overridden(lines, phase)
+ --- end
+ --- end)(vim.paste)
+ --- </pre>
+ ---
+ --@see |paste|
+ ---
+ --@param lines |readfile()|-style list of lines to paste. |channel-lines|
+ --@param phase -1: "non-streaming" paste: the call contains all lines.
+ --- If paste is "streamed", `phase` indicates the stream state:
+ --- - 1: starts the paste (exactly once)
+ --- - 2: continues the paste (zero or more times)
+ --- - 3: ends the paste (exactly once)
+ --@returns false if client should cancel the paste.
+ function vim.paste(lines, phase)
local call = vim.api.nvim_call_function
local now = vim.loop.now()
local mode = call('mode', {}):sub(1,1)
@@ -230,20 +229,33 @@ paste = (function()
end
return true -- Paste will not continue if not returning `true`.
end
-end)()
+end
--- Defers callback `cb` until the Nvim API is safe to call.
---
---@see |lua-loop-callbacks|
---@see |vim.schedule()|
---@see |vim.in_fast_event()|
-local function schedule_wrap(cb)
+function vim.schedule_wrap(cb)
return (function (...)
local args = {...}
vim.schedule(function() cb(unpack(args)) end)
end)
end
+-- vim.fn.{func}(...)
+vim.fn = setmetatable({}, {
+ __index = function(t, key)
+ local function _fn(...)
+ return vim.call(key, ...)
+ end
+ t[key] = _fn
+ return _fn
+ end
+})
+
+-- These are for loading runtime modules lazily since they aren't available in
+-- the nvim binary as specified in executor.c
local function __index(t, key)
if key == 'inspect' then
t.inspect = require('vim.inspect')
@@ -251,10 +263,6 @@ local function __index(t, key)
elseif key == 'treesitter' then
t.treesitter = require('vim.treesitter')
return t.treesitter
- elseif require('vim.shared')[key] ~= nil then
- -- Expose all `vim.shared` functions on the `vim` module.
- t[key] = require('vim.shared')[key]
- return t[key]
elseif require('vim.uri')[key] ~= nil then
-- Expose all `vim.uri` functions on the `vim` module.
t[key] = require('vim.uri')[key]
@@ -265,29 +273,102 @@ local function __index(t, key)
end
end
+setmetatable(vim, {
+ __index = __index
+})
--- vim.fn.{func}(...)
-local function _fn_index(t, key)
- local function _fn(...)
- return vim.call(key, ...)
+do
+ local a = vim.api
+ local validate = vim.validate
+ local function make_meta_accessor(get, set, del)
+ validate {
+ get = {get, 'f'};
+ set = {set, 'f'};
+ del = {del, 'f', true};
+ }
+ local mt = {}
+ if del then
+ function mt:__newindex(k, v)
+ if v == nil then
+ return del(k)
+ end
+ return set(k, v)
+ end
+ else
+ function mt:__newindex(k, v)
+ return set(k, v)
+ end
+ end
+ function mt:__index(k)
+ return get(k)
+ end
+ return setmetatable({}, mt)
+ end
+ local function pcall_ret(status, ...)
+ if status then return ... end
+ end
+ local function nil_wrap(fn)
+ return function(...)
+ return pcall_ret(pcall(fn, ...))
+ end
+ end
+ vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var)
+ vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar)
+ vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option)
+ vim.env = make_meta_accessor(vim.fn.getenv, vim.fn.setenv)
+ -- TODO(ashkan) if/when these are available from an API, generate them
+ -- instead of hardcoding.
+ local window_options = {
+ arab = true; arabic = true; breakindent = true; breakindentopt = true;
+ bri = true; briopt = true; cc = true; cocu = true;
+ cole = true; colorcolumn = true; concealcursor = true; conceallevel = true;
+ crb = true; cuc = true; cul = true; cursorbind = true;
+ cursorcolumn = true; cursorline = true; diff = true; fcs = true;
+ fdc = true; fde = true; fdi = true; fdl = true;
+ fdm = true; fdn = true; fdt = true; fen = true;
+ fillchars = true; fml = true; fmr = true; foldcolumn = true;
+ foldenable = true; foldexpr = true; foldignore = true; foldlevel = true;
+ foldmarker = true; foldmethod = true; foldminlines = true; foldnestmax = true;
+ foldtext = true; lbr = true; lcs = true; linebreak = true;
+ list = true; listchars = true; nu = true; number = true;
+ numberwidth = true; nuw = true; previewwindow = true; pvw = true;
+ relativenumber = true; rightleft = true; rightleftcmd = true; rl = true;
+ rlc = true; rnu = true; scb = true; scl = true;
+ scr = true; scroll = true; scrollbind = true; signcolumn = true;
+ spell = true; statusline = true; stl = true; wfh = true;
+ wfw = true; winbl = true; winblend = true; winfixheight = true;
+ winfixwidth = true; winhighlight = true; winhl = true; wrap = true;
+ }
+ local function new_buf_opt_accessor(bufnr)
+ local function get(k)
+ if window_options[k] then
+ return a.nvim_err_writeln(k.." is a window option, not a buffer option")
+ end
+ if bufnr == nil and type(k) == "number" then
+ return new_buf_opt_accessor(k)
+ end
+ return a.nvim_buf_get_option(bufnr or 0, k)
+ end
+ local function set(k, v)
+ if window_options[k] then
+ return a.nvim_err_writeln(k.." is a window option, not a buffer option")
+ end
+ return a.nvim_buf_set_option(bufnr or 0, k, v)
+ end
+ return make_meta_accessor(get, set)
+ end
+ vim.bo = new_buf_opt_accessor(nil)
+ local function new_win_opt_accessor(winnr)
+ local function get(k)
+ if winnr == nil and type(k) == "number" then
+ return new_win_opt_accessor(k)
+ end
+ return a.nvim_win_get_option(winnr or nil, k)
+ end
+ local function set(k, v) return a.nvim_win_set_option(winnr or nil, k, v) end
+ return make_meta_accessor(get, set)
end
- t[key] = _fn
- return _fn
+ vim.wo = new_win_opt_accessor(nil)
end
-local fn = setmetatable({}, {__index=_fn_index})
-
-local module = {
- _update_package_paths = _update_package_paths,
- _os_proc_children = _os_proc_children,
- _os_proc_info = _os_proc_info,
- _system = _system,
- paste = paste,
- schedule_wrap = schedule_wrap,
- fn=fn,
-}
-
-setmetatable(module, {
- __index = __index
-})
return module
diff --git a/src/nvim/main.c b/src/nvim/main.c
index e39eec4038..c8959d9ad1 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -1516,7 +1516,7 @@ static void create_windows(mparm_T *parmp)
/* We can't close the window, it would disturb what
* happens next. Clear the file name and set the arg
* index to -1 to delete it later. */
- setfname(curbuf, NULL, NULL, FALSE);
+ setfname(curbuf, NULL, NULL, false);
curwin->w_arg_idx = -1;
swap_exists_action = SEA_NONE;
} else
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 2824d57f49..e5ba17a0a7 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -540,7 +540,7 @@ void ml_open_file(buf_T *buf)
/// file, or reading into an existing buffer, create a swap file now.
///
/// @param newfile reading file into new buffer
-void check_need_swap(int newfile)
+void check_need_swap(bool newfile)
{
int old_msg_silent = msg_silent; // might be reset by an E325 message
msg_silent = 0; // If swap dialog prompts for input, user needs to see it!
@@ -937,8 +937,9 @@ void ml_recover(bool checkext)
*/
if (directly) {
expand_env(b0p->b0_fname, NameBuff, MAXPATHL);
- if (setfname(curbuf, NameBuff, NULL, TRUE) == FAIL)
+ if (setfname(curbuf, NameBuff, NULL, true) == FAIL) {
goto theend;
+ }
}
home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL, TRUE);
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index d0aa0653cb..deb7ee6342 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -508,31 +508,30 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
return NULL;
}
-/*
- * setmouse() - switch mouse on/off depending on current mode and 'mouse'
- */
+/// Set UI mouse depending on current mode and 'mouse'.
+///
+/// Emits mouse_on/mouse_off UI event (unless 'mouse' is empty).
void setmouse(void)
{
- int checkfor;
-
ui_cursor_shape();
- /* be quick when mouse is off */
- if (*p_mouse == NUL)
+ // Be quick when mouse is off.
+ if (*p_mouse == NUL) {
return;
+ }
- if (VIsual_active)
+ int checkfor = MOUSE_NORMAL; // assume normal mode
+ if (VIsual_active) {
checkfor = MOUSE_VISUAL;
- else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE)
+ } else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE) {
checkfor = MOUSE_RETURN;
- else if (State & INSERT)
+ } else if (State & INSERT) {
checkfor = MOUSE_INSERT;
- else if (State & CMDLINE)
+ } else if (State & CMDLINE) {
checkfor = MOUSE_COMMAND;
- else if (State == CONFIRM || State == EXTERNCMD)
- checkfor = ' '; /* don't use mouse for ":confirm" or ":!cmd" */
- else
- checkfor = MOUSE_NORMAL; /* assume normal mode */
+ } else if (State == CONFIRM || State == EXTERNCMD) {
+ checkfor = ' '; // don't use mouse for ":confirm" or ":!cmd"
+ }
if (mouse_has(checkfor)) {
ui_call_mouse_on();
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 2ef2c3101f..b9dbcc6805 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1422,12 +1422,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
if (oap->motion_type == kMTLineWise) {
oap->inclusive = false;
} else if (oap->motion_type == kMTCharWise) {
- // If the motion already was characterwise, toggle "inclusive"
+ // If the motion already was charwise, toggle "inclusive"
oap->inclusive = !oap->inclusive;
}
oap->motion_type = kMTCharWise;
} else if (oap->motion_force == Ctrl_V) {
- // Change line- or characterwise motion into Visual block mode.
+ // Change line- or charwise motion into Visual block mode.
if (!VIsual_active) {
VIsual_active = true;
VIsual = oap->start;
@@ -1516,7 +1516,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
}
// In Select mode, a linewise selection is operated upon like a
- // characterwise selection.
+ // charwise selection.
// Special case: gH<Del> deletes the last line.
if (VIsual_select && VIsual_mode == 'V'
&& cap->oap->op_type != OP_DELETE) {
@@ -4588,7 +4588,7 @@ static void nv_colon(cmdarg_T *cap)
nv_operator(cap);
} else {
if (cap->oap->op_type != OP_NOP) {
- // Using ":" as a movement is characterwise exclusive.
+ // Using ":" as a movement is charwise exclusive.
cap->oap->motion_type = kMTCharWise;
cap->oap->inclusive = false;
} else if (cap->count0 && !is_cmdkey) {
@@ -6372,8 +6372,8 @@ static void nv_visual(cmdarg_T *cap)
if (cap->cmdchar == Ctrl_Q)
cap->cmdchar = Ctrl_V;
- /* 'v', 'V' and CTRL-V can be used while an operator is pending to make it
- * characterwise, linewise, or blockwise. */
+ // 'v', 'V' and CTRL-V can be used while an operator is pending to make it
+ // charwise, linewise, or blockwise.
if (cap->oap->op_type != OP_NOP) {
motion_force = cap->oap->motion_force = cap->cmdchar;
finish_op = false; // operator doesn't finish now but later
@@ -7887,15 +7887,17 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
cap->oap->regname = regname;
}
- /* When deleted a linewise Visual area, put the register as
- * lines to avoid it joined with the next line. When deletion was
- * characterwise, split a line when putting lines. */
- if (VIsual_mode == 'V')
+ // When deleted a linewise Visual area, put the register as
+ // lines to avoid it joined with the next line. When deletion was
+ // charwise, split a line when putting lines.
+ if (VIsual_mode == 'V') {
flags |= PUT_LINE;
- else if (VIsual_mode == 'v')
+ } else if (VIsual_mode == 'v') {
flags |= PUT_LINE_SPLIT;
- if (VIsual_mode == Ctrl_V && dir == FORWARD)
+ }
+ if (VIsual_mode == Ctrl_V && dir == FORWARD) {
flags |= PUT_LINE_FORWARD;
+ }
dir = BACKWARD;
if ((VIsual_mode != 'V'
&& curwin->w_cursor.col < curbuf->b_op_start.col)
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index f2d35d5e43..294c65ca03 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -5204,8 +5204,7 @@ void write_reg_contents_lst(int name, char_u **strings,
/// write_reg_contents_ex - store `str` in register `name`
///
-/// If `str` ends in '\n' or '\r', use linewise, otherwise use
-/// characterwise.
+/// If `str` ends in '\n' or '\r', use linewise, otherwise use charwise.
///
/// @warning when `name` is '/', `len` and `must_append` are ignored. This
/// means that `str` MUST be NUL-terminated.
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 25933d497e..68ab310329 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -5195,6 +5195,13 @@ void ui_refresh_options(void)
}
ui_call_option_set(name, value);
}
+ if (p_mouse != NULL) {
+ if (*p_mouse == NUL) {
+ ui_call_mouse_off();
+ } else {
+ setmouse();
+ }
+ }
}
/*
diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po
index 61ad72d7e0..79048eac39 100644
--- a/src/nvim/po/af.po
+++ b/src/nvim/po/af.po
@@ -100,7 +100,7 @@ msgid "E88: Cannot go before first buffer"
msgstr "E88: Kan nie vóór eerste buffer gaan nie"
#, fuzzy, c-format
-#~ msgid "E89: %s will be killed(add ! to override)"
+#~ msgid "E89: %s will be killed (add ! to override)"
#~ msgstr "E189: \"%s\" bestaan (gebruik ! om te dwing)"
#, c-format
diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po
index 4612988c95..f568a34b3c 100644
--- a/src/nvim/po/fi.po
+++ b/src/nvim/po/fi.po
@@ -323,7 +323,7 @@ msgid "E88: Cannot go before first buffer"
msgstr "E88: Ensimmäisen puskurin ohi ei voi edetä"
#, fuzzy, c-format
-#~ msgid "E89: %s will be killed(add ! to override)"
+#~ msgid "E89: %s will be killed (add ! to override)"
#~ msgstr "E189: %s on jo olemassa (lisää komentoon ! ohittaaksesi)"
#, fuzzy, c-format
diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po
index 211d38e53a..19ea8e897a 100644
--- a/src/nvim/po/uk.po
+++ b/src/nvim/po/uk.po
@@ -87,8 +87,8 @@ msgid "E88: Cannot go before first buffer"
msgstr "E88: Це вже найперший буфер"
#, c-format
-msgid "E89: %s will be killed(add ! to override)"
-msgstr "E89: «%s» буде вбито(! щоб не зважати)"
+msgid "E89: %s will be killed (add ! to override)"
+msgstr "E89: «%s» буде вбито (! щоб не зважати)"
#, c-format
msgid ""
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index ed57b28029..194cc5781b 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -1,9 +1,7 @@
// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-/*
- * quickfix.c: functions for quickfix mode, using a file with error messages
- */
+// quickfix.c: functions for quickfix mode, using a file with error messages
#include <assert.h>
#include <inttypes.h>
@@ -53,9 +51,7 @@ struct dir_stack_T {
char_u *dirname;
};
-/*
- * For each error the next struct is allocated and linked in a list.
- */
+// For each error the next struct is allocated and linked in a list.
typedef struct qfline_S qfline_T;
struct qfline_S {
qfline_T *qf_next; ///< pointer to next error in the list
@@ -74,9 +70,7 @@ struct qfline_S {
char_u qf_valid; ///< valid error message detected
};
-/*
- * There is a stack of error lists.
- */
+// There is a stack of error lists.
#define LISTCOUNT 10
#define INVALID_QFIDX (-1)
@@ -120,15 +114,13 @@ typedef struct qf_list_S {
/// Quickfix/Location list stack definition
/// Contains a list of quickfix/location lists (qf_list_T)
struct qf_info_S {
- /*
- * Count of references to this list. Used only for location lists.
- * When a location list window reference this list, qf_refcount
- * will be 2. Otherwise, qf_refcount will be 1. When qf_refcount
- * reaches 0, the list is freed.
- */
+ // Count of references to this list. Used only for location lists.
+ // When a location list window reference this list, qf_refcount
+ // will be 2. Otherwise, qf_refcount will be 1. When qf_refcount
+ // reaches 0, the list is freed.
int qf_refcount;
- int qf_listcount; /* current number of lists */
- int qf_curlist; /* current error list */
+ int qf_listcount; // current number of lists
+ int qf_curlist; // current error list
qf_list_T qf_lists[LISTCOUNT];
qfltype_T qfl_type; // type of list
};
@@ -138,31 +130,29 @@ static unsigned last_qf_id = 0; // Last Used quickfix list id
#define FMT_PATTERNS 11 // maximum number of % recognized
-/*
- * Structure used to hold the info of one part of 'errorformat'
- */
+// Structure used to hold the info of one part of 'errorformat'
typedef struct efm_S efm_T;
struct efm_S {
- regprog_T *prog; /* pre-formatted part of 'errorformat' */
- efm_T *next; /* pointer to next (NULL if last) */
- char_u addr[FMT_PATTERNS]; /* indices of used % patterns */
- char_u prefix; /* prefix of this format line: */
- /* 'D' enter directory */
- /* 'X' leave directory */
- /* 'A' start of multi-line message */
- /* 'E' error message */
- /* 'W' warning message */
- /* 'I' informational message */
- /* 'C' continuation line */
- /* 'Z' end of multi-line message */
- /* 'G' general, unspecific message */
- /* 'P' push file (partial) message */
- /* 'Q' pop/quit file (partial) message */
- /* 'O' overread (partial) message */
- char_u flags; /* additional flags given in prefix */
- /* '-' do not include this line */
- /* '+' include whole line in message */
- int conthere; /* %> used */
+ regprog_T *prog; // pre-formatted part of 'errorformat'
+ efm_T *next; // pointer to next (NULL if last)
+ char_u addr[FMT_PATTERNS]; // indices of used % patterns
+ char_u prefix; // prefix of this format line:
+ // 'D' enter directory
+ // 'X' leave directory
+ // 'A' start of multi-line message
+ // 'E' error message
+ // 'W' warning message
+ // 'I' informational message
+ // 'C' continuation line
+ // 'Z' end of multi-line message
+ // 'G' general, unspecific message
+ // 'P' push file (partial) message
+ // 'Q' pop/quit file (partial) message
+ // 'O' overread (partial) message
+ char_u flags; // additional flags given in prefix
+ // '-' do not include this line
+ // '+' include whole line in message
+ int conthere; // %> used
};
/// List of location lists to be deleted.
@@ -221,7 +211,7 @@ static char_u *e_no_more_items = (char_u *)N_("E553: No more items");
// Quickfix window check helper macro
#define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL)
-/* Location list window check helper macro */
+// Location list window check helper macro
#define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)
// Quickfix and location list stack check helper macros
@@ -1156,16 +1146,12 @@ qf_init_ext(
goto error2;
}
- /*
- * got_int is reset here, because it was probably set when killing the
- * ":make" command, but we still want to read the errorfile then.
- */
- got_int = FALSE;
+ // got_int is reset here, because it was probably set when killing the
+ // ":make" command, but we still want to read the errorfile then.
+ got_int = false;
- /*
- * Read the lines in the error file one by one.
- * Try to recognize one of the error formats in each line.
- */
+ // Read the lines in the error file one by one.
+ // Try to recognize one of the error formats in each line.
while (!got_int) {
status = qf_init_process_nextline(qfl, fmt_first, &state, &fields);
if (status == QF_END_OF_INPUT) { // end of input
@@ -1263,10 +1249,8 @@ static void qf_new_list(qf_info_T *qi, const char_u *qf_title)
qf_free(&qi->qf_lists[--qi->qf_listcount]);
}
- /*
- * When the stack is full, remove to oldest entry
- * Otherwise, add a new entry.
- */
+ // When the stack is full, remove to oldest entry
+ // Otherwise, add a new entry.
if (qi->qf_listcount == LISTCOUNT) {
qf_free(&qi->qf_lists[0]);
for (i = 1; i < LISTCOUNT; i++) {
@@ -1714,7 +1698,7 @@ static void ll_free_all(qf_info_T **pqi)
qi = *pqi;
if (qi == NULL)
return;
- *pqi = NULL; /* Remove reference to this list */
+ *pqi = NULL; // Remove reference to this list
qi->qf_refcount--;
if (qi->qf_refcount < 1) {
@@ -1739,7 +1723,7 @@ void qf_free_all(win_T *wp)
qf_info_T *qi = &ql_info;
if (wp != NULL) {
- /* location list */
+ // location list
ll_free_all(&wp->w_llist);
ll_free_all(&wp->w_llist_ref);
} else {
@@ -1892,14 +1876,13 @@ static qf_info_T *qf_alloc_stack(qfltype_T qfltype)
static qf_info_T *ll_get_or_alloc_list(win_T *wp)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
- if (IS_LL_WINDOW(wp))
- /* For a location list window, use the referenced location list */
+ if (IS_LL_WINDOW(wp)) {
+ // For a location list window, use the referenced location list
return wp->w_llist_ref;
+ }
- /*
- * For a non-location list window, w_llist_ref should not point to a
- * location list.
- */
+ // For a non-location list window, w_llist_ref should not point to a
+ // location list.
ll_free_all(&wp->w_llist_ref);
if (wp->w_llist == NULL) {
@@ -2133,22 +2116,21 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
{
struct dir_stack_T *ds_ptr;
- /* allocate new stack element and hook it in */
+ // allocate new stack element and hook it in
struct dir_stack_T *ds_new = xmalloc(sizeof(struct dir_stack_T));
ds_new->next = *stackptr;
*stackptr = ds_new;
- /* store directory on the stack */
+ // store directory on the stack
if (vim_isAbsName(dirbuf)
|| (*stackptr)->next == NULL
- || (*stackptr && is_file_stack))
+ || (*stackptr && is_file_stack)) {
(*stackptr)->dirname = vim_strsave(dirbuf);
- else {
- /* Okay we don't have an absolute path.
- * dirbuf must be a subdir of one of the directories on the stack.
- * Let's search...
- */
+ } else {
+ // Okay we don't have an absolute path.
+ // dirbuf must be a subdir of one of the directories on the stack.
+ // Let's search...
ds_new = (*stackptr)->next;
(*stackptr)->dirname = NULL;
while (ds_new) {
@@ -2161,7 +2143,7 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
ds_new = ds_new->next;
}
- /* clean up all dirs we already left */
+ // clean up all dirs we already left
while ((*stackptr)->next != ds_new) {
ds_ptr = (*stackptr)->next;
(*stackptr)->next = (*stackptr)->next->next;
@@ -2169,7 +2151,7 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
xfree(ds_ptr);
}
- /* Nothing found -> it must be on top level */
+ // Nothing found -> it must be on top level
if (ds_new == NULL) {
xfree((*stackptr)->dirname);
(*stackptr)->dirname = vim_strsave(dirbuf);
@@ -2187,18 +2169,16 @@ static char_u *qf_push_dir(char_u *dirbuf, struct dir_stack_T **stackptr,
}
-/*
- * pop dirbuf from the directory stack and return previous directory or NULL if
- * stack is empty
- */
+// pop dirbuf from the directory stack and return previous directory or NULL if
+// stack is empty
static char_u *qf_pop_dir(struct dir_stack_T **stackptr)
{
struct dir_stack_T *ds_ptr;
- /* TODO: Should we check if dirbuf is the directory on top of the stack?
- * What to do if it isn't? */
+ // TODO(vim): Should we check if dirbuf is the directory on top of the stack?
+ // What to do if it isn't?
- /* pop top element and free it */
+ // pop top element and free it
if (*stackptr != NULL) {
ds_ptr = *stackptr;
*stackptr = (*stackptr)->next;
@@ -2206,13 +2186,11 @@ static char_u *qf_pop_dir(struct dir_stack_T **stackptr)
xfree(ds_ptr);
}
- /* return NEW top element as current dir or NULL if stack is empty*/
+ // return NEW top element as current dir or NULL if stack is empty
return *stackptr ? (*stackptr)->dirname : NULL;
}
-/*
- * clean up directory stack
- */
+// clean up directory stack
static void qf_clean_dir_stack(struct dir_stack_T **stackptr)
{
struct dir_stack_T *ds_ptr;
@@ -2887,10 +2865,8 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
}
}
- /*
- * If there is a file name,
- * read the wanted file if needed, and check autowrite etc.
- */
+ // If there is a file name,
+ // read the wanted file if needed, and check autowrite etc.
old_curbuf = curbuf;
old_lnum = curwin->w_cursor.lnum;
@@ -2938,8 +2914,8 @@ theend:
qfl->qf_index = qf_index;
}
if (p_swb != old_swb && opened_window) {
- /* Restore old 'switchbuf' value, but not when an autocommand or
- * modeline has changed the value. */
+ // Restore old 'switchbuf' value, but not when an autocommand or
+ // modeline has changed the value.
if (p_swb == empty_option) {
p_swb = old_swb;
swb_flags = old_swb_flags;
@@ -3038,10 +3014,8 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel)
ui_flush(); // show one line at a time
}
-/*
- * ":clist": list all errors
- * ":llist": list all locations
- */
+// ":clist": list all errors
+// ":llist": list all locations
void qf_list(exarg_T *eap)
{
qf_list_T *qfl;
@@ -3116,10 +3090,8 @@ void qf_list(exarg_T *eap)
}
}
-/*
- * Remove newlines and leading whitespace from an error message.
- * Put the result in "buf[bufsize]".
- */
+// Remove newlines and leading whitespace from an error message.
+// Put the result in "buf[bufsize]".
static void qf_fmt_text(const char_u *restrict text, char_u *restrict buf,
int bufsize)
FUNC_ATTR_NONNULL_ALL
@@ -3277,9 +3249,7 @@ static void qf_free(qf_list_T *qfl)
qfl->qf_changedtick = 0L;
}
-/*
- * qf_mark_adjust: adjust marks
- */
+// qf_mark_adjust: adjust marks
bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount,
long amount_after)
{
@@ -3321,21 +3291,19 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, long amount,
return found_one;
}
-/*
- * Make a nice message out of the error character and the error number:
- * char number message
- * e or E 0 " error"
- * w or W 0 " warning"
- * i or I 0 " info"
- * 0 0 ""
- * other 0 " c"
- * e or E n " error n"
- * w or W n " warning n"
- * i or I n " info n"
- * 0 n " error n"
- * other n " c n"
- * 1 x "" :helpgrep
- */
+// Make a nice message out of the error character and the error number:
+// char number message
+// e or E 0 " error"
+// w or W 0 " warning"
+// i or I 0 " info"
+// 0 0 ""
+// other 0 " c"
+// e or E n " error n"
+// w or W n " warning n"
+// i or I n " info n"
+// 0 n " error n"
+// other n " c n"
+// 1 x "" :helpgrep
static char_u *qf_types(int c, int nr)
{
static char_u buf[20];
@@ -3396,12 +3364,10 @@ void qf_view_result(bool split)
do_cmdline_cmd((IS_LL_WINDOW(curwin) ? ".ll" : ".cc"));
}
-/*
- * ":cwindow": open the quickfix window if we have errors to display,
- * close it if not.
- * ":lwindow": open the location list window if we have locations to display,
- * close it if not.
- */
+// ":cwindow": open the quickfix window if we have errors to display,
+// close it if not.
+// ":lwindow": open the location list window if we have locations to display,
+// close it if not.
void ex_cwindow(exarg_T *eap)
{
qf_info_T *qi;
@@ -3414,14 +3380,12 @@ void ex_cwindow(exarg_T *eap)
qfl = qf_get_curlist(qi);
- /* Look for an existing quickfix window. */
+ // Look for an existing quickfix window.
win = qf_find_win(qi);
- /*
- * If a quickfix window is open but we have no errors to display,
- * close the window. If a quickfix window is not open, then open
- * it if we have errors; otherwise, leave it closed.
- */
+ // If a quickfix window is open but we have no errors to display,
+ // close the window. If a quickfix window is not open, then open
+ // it if we have errors; otherwise, leave it closed.
if (qf_stack_empty(qi)
|| qfl->qf_nonevalid
|| qf_list_empty(qf_get_curlist(qi))) {
@@ -3433,10 +3397,8 @@ void ex_cwindow(exarg_T *eap)
}
}
-/*
- * ":cclose": close the window showing the list of errors.
- * ":lclose": close the window showing the location list
- */
+// ":cclose": close the window showing the list of errors.
+// ":lclose": close the window showing the location list
void ex_cclose(exarg_T *eap)
{
win_T *win = NULL;
@@ -3446,7 +3408,7 @@ void ex_cclose(exarg_T *eap)
return;
}
- /* Find existing quickfix window and close it. */
+ // Find existing quickfix window and close it.
win = qf_find_win(qi);
if (win != NULL) {
win_close(win, false);
@@ -3642,38 +3604,32 @@ void ex_cbottom(exarg_T *eap)
}
}
-/*
- * Return the number of the current entry (line number in the quickfix
- * window).
- */
+// Return the number of the current entry (line number in the quickfix
+// window).
linenr_T qf_current_entry(win_T *wp)
{
qf_info_T *qi = &ql_info;
- if (IS_LL_WINDOW(wp))
- /* In the location list window, use the referenced location list */
+ if (IS_LL_WINDOW(wp)) {
+ // In the location list window, use the referenced location list
qi = wp->w_llist_ref;
+ }
return qf_get_curlist(qi)->qf_index;
}
-/*
- * Update the cursor position in the quickfix window to the current error.
- * Return TRUE if there is a quickfix window.
- */
-static int
-qf_win_pos_update (
+// Update the cursor position in the quickfix window to the current error.
+// Return TRUE if there is a quickfix window.
+static int qf_win_pos_update(
qf_info_T *qi,
- int old_qf_index /* previous qf_index or zero */
+ int old_qf_index // previous qf_index or zero
)
{
win_T *win;
int qf_index = qf_get_curlist(qi)->qf_index;
- /*
- * Put the cursor on the current error in the quickfix window, so that
- * it's viewable.
- */
+ // Put the cursor on the current error in the quickfix window, so that
+ // it's viewable.
win = qf_find_win(qi);
if (win != NULL
&& qf_index <= win->w_buffer->b_ml.ml_line_count
@@ -3725,10 +3681,8 @@ static win_T *qf_find_win(const qf_info_T *qi)
return NULL;
}
-/*
- * Find a quickfix buffer.
- * Searches in windows opened in all the tabs.
- */
+// Find a quickfix buffer.
+// Searches in windows opened in all the tabs.
static buf_T *qf_find_buf(const qf_info_T *qi)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
@@ -3754,16 +3708,14 @@ static void qf_update_win_titlevar(qf_info_T *qi)
}
}
-/*
- * Find the quickfix buffer. If it exists, update the contents.
- */
+// Find the quickfix buffer. If it exists, update the contents.
static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
{
buf_T *buf;
win_T *win;
aco_save_T aco;
- /* Check if a buffer for the quickfix list exists. Update it. */
+ // Check if a buffer for the quickfix list exists. Update it.
buf = qf_find_buf(qi);
if (buf != NULL) {
linenr_T old_line_count = buf->b_ml.ml_line_count;
@@ -3936,7 +3888,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
redraw_curbuf_later(NOT_VALID);
}
- /* Restore KeyTyped, setting 'filetype' may reset it. */
+ // Restore KeyTyped, setting 'filetype' may reset it.
KeyTyped = old_KeyTyped;
}
@@ -3990,9 +3942,7 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit)
}
}
-/*
- * Return TRUE when using ":vimgrep" for ":grep".
- */
+// Return TRUE when using ":vimgrep" for ":grep".
int grep_internal(cmdidx_T cmdidx)
{
return (cmdidx == CMD_grep
@@ -4055,9 +4005,7 @@ static char *make_get_fullcmd(const char_u *makecmd, const char_u *fname)
return cmd;
}
-/*
- * Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd"
- */
+// Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd"
void ex_make(exarg_T *eap)
{
char_u *fname;
@@ -4128,11 +4076,9 @@ cleanup:
xfree(cmd);
}
-/*
- * Return the name for the errorfile, in allocated memory.
- * Find a new unique name when 'makeef' contains "##".
- * Returns NULL for error.
- */
+// Return the name for the errorfile, in allocated memory.
+// Find a new unique name when 'makeef' contains "##".
+// Returns NULL for error.
static char_u *get_mef_name(void)
{
char_u *p;
@@ -4154,7 +4100,7 @@ static char_u *get_mef_name(void)
if (*p == NUL)
return vim_strsave(p_mef);
- /* Keep trying until the name doesn't exist yet. */
+ // Keep trying until the name doesn't exist yet.
for (;; ) {
if (start == -1) {
start = (int)os_get_pid();
@@ -4703,10 +4649,8 @@ static char_u * cfile_get_auname(cmdidx_T cmdidx)
}
-/*
- * ":cfile"/":cgetfile"/":caddfile" commands.
- * ":lfile"/":lgetfile"/":laddfile" commands.
- */
+// ":cfile"/":cgetfile"/":caddfile" commands.
+// ":lfile"/":lgetfile"/":laddfile" commands.
void ex_cfile(exarg_T *eap)
{
win_T *wp = NULL;
@@ -4947,12 +4891,10 @@ static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy,
}
}
-/*
- * ":vimgrep {pattern} file(s)"
- * ":vimgrepadd {pattern} file(s)"
- * ":lvimgrep {pattern} file(s)"
- * ":lvimgrepadd {pattern} file(s)"
- */
+// ":vimgrep {pattern} file(s)"
+// ":vimgrepadd {pattern} file(s)"
+// ":lvimgrep {pattern} file(s)"
+// ":lvimgrepadd {pattern} file(s)"
void ex_vimgrep(exarg_T *eap)
{
regmmatch_T regmatch;
@@ -4994,7 +4936,7 @@ void ex_vimgrep(exarg_T *eap)
else
tomatch = MAXLNUM;
- /* Get the search pattern: either white-separated or enclosed in // */
+ // Get the search pattern: either white-separated or enclosed in //
regmatch.regprog = NULL;
char_u *title = vim_strsave(qf_cmdtitle(*eap->cmdlinep));
p = skip_vimgrep_pat(eap->arg, &s, &flags);
@@ -5021,9 +4963,10 @@ void ex_vimgrep(exarg_T *eap)
qf_new_list(qi, title);
}
- /* parse the list of arguments */
- if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL)
+ // parse the list of arguments
+ if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) {
goto theend;
+ }
if (fcount == 0) {
EMSG(_(e_nomatch));
goto theend;
@@ -5032,8 +4975,8 @@ void ex_vimgrep(exarg_T *eap)
dirname_start = xmalloc(MAXPATHL);
dirname_now = xmalloc(MAXPATHL);
- /* Remember the current directory, because a BufRead autocommand that does
- * ":lcd %:p:h" changes the meaning of short path names. */
+ // Remember the current directory, because a BufRead autocommand that does
+ // ":lcd %:p:h" changes the meaning of short path names.
os_dirname(dirname_start, MAXPATHL);
incr_quickfix_busy();
@@ -5046,15 +4989,15 @@ void ex_vimgrep(exarg_T *eap)
for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) {
fname = path_try_shorten_fname(fnames[fi]);
if (time(NULL) > seconds) {
- /* Display the file name every second or so, show the user we are
- * working on it. */
+ // Display the file name every second or so, show the user we are
+ // working on it.
seconds = time(NULL);
vgr_display_fname(fname);
}
buf = buflist_findname_exp(fnames[fi]);
if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
- /* Remember that a buffer with this name already exists. */
+ // Remember that a buffer with this name already exists.
duplicate_name = (buf != NULL);
using_dummy = TRUE;
redraw_for_dummy = TRUE;
@@ -5087,20 +5030,20 @@ void ex_vimgrep(exarg_T *eap)
if (found_match && first_match_buf == NULL)
first_match_buf = buf;
if (duplicate_name) {
- /* Never keep a dummy buffer if there is another buffer
- * with the same name. */
+ // Never keep a dummy buffer if there is another buffer
+ // with the same name.
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
} else if (!cmdmod.hide
- || buf->b_p_bh[0] == 'u' /* "unload" */
- || buf->b_p_bh[0] == 'w' /* "wipe" */
- || buf->b_p_bh[0] == 'd') { /* "delete" */
- /* When no match was found we don't need to remember the
- * buffer, wipe it out. If there was a match and it
- * wasn't the first one or we won't jump there: only
- * unload the buffer.
- * Ignore 'hidden' here, because it may lead to having too
- * many swap files. */
+ || buf->b_p_bh[0] == 'u' // "unload"
+ || buf->b_p_bh[0] == 'w' // "wipe"
+ || buf->b_p_bh[0] == 'd') { // "delete"
+ // When no match was found we don't need to remember the
+ // buffer, wipe it out. If there was a match and it
+ // wasn't the first one or we won't jump there: only
+ // unload the buffer.
+ // Ignore 'hidden' here, because it may lead to having too
+ // many swap files.
if (!found_match) {
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
@@ -5124,10 +5067,10 @@ void ex_vimgrep(exarg_T *eap)
target_dir = vim_strsave(dirname_now);
}
- /* The buffer is still loaded, the Filetype autocommands
- * need to be done now, in that buffer. And the modelines
- * need to be done (again). But not the window-local
- * options! */
+ // The buffer is still loaded, the Filetype autocommands
+ // need to be done now, in that buffer. And the modelines
+ // need to be done (again). But not the window-local
+ // options!
aucmd_prepbuf(&aco, buf);
apply_autocmds(EVENT_FILETYPE, buf->b_p_ft,
buf->b_fname, TRUE, buf);
@@ -5171,8 +5114,8 @@ void ex_vimgrep(exarg_T *eap)
decr_quickfix_busy();
- /* If we loaded a dummy buffer into the current window, the autocommands
- * may have messed up things, need to redraw and recompute folds. */
+ // If we loaded a dummy buffer into the current window, the autocommands
+ // may have messed up things, need to redraw and recompute folds.
if (redraw_for_dummy) {
foldUpdateAll(curwin);
}
@@ -5185,18 +5128,16 @@ theend:
vim_regfree(regmatch.regprog);
}
-/*
- * Restore current working directory to "dirname_start" if they differ, taking
- * into account whether it is set locally or globally.
- */
+// Restore current working directory to "dirname_start" if they differ, taking
+// into account whether it is set locally or globally.
static void restore_start_dir(char_u *dirname_start)
{
char_u *dirname_now = xmalloc(MAXPATHL);
os_dirname(dirname_now, MAXPATHL);
if (STRCMP(dirname_start, dirname_now) != 0) {
- /* If the directory has changed, change it back by building up an
- * appropriate ex command and executing it. */
+ // If the directory has changed, change it back by building up an
+ // appropriate ex command and executing it.
exarg_T ea = {
.arg = dirname_start,
.cmdidx = (curwin->w_localdir == NULL) ? CMD_cd : CMD_lcd,
@@ -5206,23 +5147,21 @@ static void restore_start_dir(char_u *dirname_start)
xfree(dirname_now);
}
-/*
- * Load file "fname" into a dummy buffer and return the buffer pointer,
- * placing the directory resulting from the buffer load into the
- * "resulting_dir" pointer. "resulting_dir" must be allocated by the caller
- * prior to calling this function. Restores directory to "dirname_start" prior
- * to returning, if autocmds or the 'autochdir' option have changed it.
- *
- * If creating the dummy buffer does not fail, must call unload_dummy_buffer()
- * or wipe_dummy_buffer() later!
- *
- * Returns NULL if it fails.
- */
+// Load file "fname" into a dummy buffer and return the buffer pointer,
+// placing the directory resulting from the buffer load into the
+// "resulting_dir" pointer. "resulting_dir" must be allocated by the caller
+// prior to calling this function. Restores directory to "dirname_start" prior
+// to returning, if autocmds or the 'autochdir' option have changed it.
+//
+// If creating the dummy buffer does not fail, must call unload_dummy_buffer()
+// or wipe_dummy_buffer() later!
+//
+// Returns NULL if it fails.
static buf_T *
load_dummy_buffer (
char_u *fname,
- char_u *dirname_start, /* in: old directory */
- char_u *resulting_dir /* out: new directory */
+ char_u *dirname_start, // in: old directory
+ char_u *resulting_dir // out: new directory
)
{
buf_T *newbuf;
@@ -5239,24 +5178,24 @@ load_dummy_buffer (
}
set_bufref(&newbufref, newbuf);
- /* Init the options. */
+ // Init the options.
buf_copy_options(newbuf, BCO_ENTER | BCO_NOHELP);
- /* need to open the memfile before putting the buffer in a window */
+ // need to open the memfile before putting the buffer in a window
if (ml_open(newbuf) == OK) {
// Make sure this buffer isn't wiped out by autocommands.
newbuf->b_locked++;
// set curwin/curbuf to buf and save a few things
aucmd_prepbuf(&aco, newbuf);
- /* Need to set the filename for autocommands. */
- (void)setfname(curbuf, fname, NULL, FALSE);
+ // Need to set the filename for autocommands.
+ (void)setfname(curbuf, fname, NULL, false);
- /* Create swap file now to avoid the ATTENTION message. */
- check_need_swap(TRUE);
+ // Create swap file now to avoid the ATTENTION message.
+ check_need_swap(true);
- /* Remove the "dummy" flag, otherwise autocommands may not
- * work. */
+ // Remove the "dummy" flag, otherwise autocommands may not
+ // work.
curbuf->b_flags &= ~BF_DUMMY;
newbuf_to_wipe.br_buf = NULL;
@@ -5289,11 +5228,9 @@ load_dummy_buffer (
newbuf->b_flags |= BF_DUMMY;
}
- /*
- * When autocommands/'autochdir' option changed directory: go back.
- * Let the caller know what the resulting dir was first, in case it is
- * important.
- */
+ // When autocommands/'autochdir' option changed directory: go back.
+ // Let the caller know what the resulting dir was first, in case it is
+ // important.
os_dirname(resulting_dir, MAXPATHL);
restore_start_dir(dirname_start);
@@ -5307,42 +5244,38 @@ load_dummy_buffer (
return newbuf;
}
-/*
- * Wipe out the dummy buffer that load_dummy_buffer() created. Restores
- * directory to "dirname_start" prior to returning, if autocmds or the
- * 'autochdir' option have changed it.
- */
+// Wipe out the dummy buffer that load_dummy_buffer() created. Restores
+// directory to "dirname_start" prior to returning, if autocmds or the
+// 'autochdir' option have changed it.
static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start)
{
- if (curbuf != buf) { /* safety check */
+ if (curbuf != buf) { // safety check
cleanup_T cs;
- /* Reset the error/interrupt/exception state here so that aborting()
- * returns FALSE when wiping out the buffer. Otherwise it doesn't
- * work when got_int is set. */
+ // Reset the error/interrupt/exception state here so that aborting()
+ // returns FALSE when wiping out the buffer. Otherwise it doesn't
+ // work when got_int is set.
enter_cleanup(&cs);
wipe_buffer(buf, FALSE);
- /* Restore the error/interrupt/exception state if not discarded by a
- * new aborting error, interrupt, or uncaught exception. */
+ // Restore the error/interrupt/exception state if not discarded by a
+ // new aborting error, interrupt, or uncaught exception.
leave_cleanup(&cs);
- /* When autocommands/'autochdir' option changed directory: go back. */
+ // When autocommands/'autochdir' option changed directory: go back.
restore_start_dir(dirname_start);
}
}
-/*
- * Unload the dummy buffer that load_dummy_buffer() created. Restores
- * directory to "dirname_start" prior to returning, if autocmds or the
- * 'autochdir' option have changed it.
- */
+// Unload the dummy buffer that load_dummy_buffer() created. Restores
+// directory to "dirname_start" prior to returning, if autocmds or the
+// 'autochdir' option have changed it.
static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start)
{
- if (curbuf != buf) { /* safety check */
- close_buffer(NULL, buf, DOBUF_UNLOAD, FALSE);
+ if (curbuf != buf) { // safety check
+ close_buffer(NULL, buf, DOBUF_UNLOAD, false);
- /* When autocommands/'autochdir' option changed directory: go back. */
+ // When autocommands/'autochdir' option changed directory: go back.
restore_start_dir(dirname_start);
}
}
@@ -6297,14 +6230,12 @@ static int cbuffer_process_args(exarg_T *eap,
return OK;
}
-/*
- * ":[range]cbuffer [bufnr]" command.
- * ":[range]caddbuffer [bufnr]" command.
- * ":[range]cgetbuffer [bufnr]" command.
- * ":[range]lbuffer [bufnr]" command.
- * ":[range]laddbuffer [bufnr]" command.
- * ":[range]lgetbuffer [bufnr]" command.
- */
+// ":[range]cbuffer [bufnr]" command.
+// ":[range]caddbuffer [bufnr]" command.
+// ":[range]cgetbuffer [bufnr]" command.
+// ":[range]lbuffer [bufnr]" command.
+// ":[range]laddbuffer [bufnr]" command.
+// ":[range]lgetbuffer [bufnr]" command.
void ex_cbuffer(exarg_T *eap)
{
buf_T *buf = NULL;
@@ -6405,8 +6336,8 @@ void ex_cexpr(exarg_T *eap)
qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
- /* Evaluate the expression. When the result is a string or a list we can
- * use it to fill the errorlist. */
+ // Evaluate the expression. When the result is a string or a list we can
+ // use it to fill the errorlist.
typval_T tv;
if (eval0(eap->arg, &tv, NULL, true) != FAIL) {
if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL)
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 81bc078a88..b195c1d96b 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -156,7 +156,7 @@ char *get_mode(void)
buf[0] = 'n';
if (finish_op) {
buf[1] = 'o';
- // to be able to detect force-linewise/blockwise/characterwise operations
+ // to be able to detect force-linewise/blockwise/charwise operations
buf[2] = (char)motion_force;
} else if (restart_edit == 'I' || restart_edit == 'R'
|| restart_edit == 'V') {
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 0a3e6ae625..635ee7984a 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -301,7 +301,7 @@ func Test_getcompletion()
call assert_equal([], l)
let l = getcompletion('.', 'shellcmd')
- call assert_equal(['./', '../'], l[0:1])
+ call assert_equal(['./', '../'], filter(l, 'v:val =~ "\\./"'))
call assert_equal(-1, match(l[2:], '^\.\.\?/$'))
let root = has('win32') ? 'C:\\' : '/'
let l = getcompletion(root, 'shellcmd')
@@ -375,6 +375,29 @@ func Test_getcompletion()
call assert_fails('call getcompletion("", "burp")', 'E475:')
endfunc
+func Test_shellcmd_completion()
+ let save_path = $PATH
+
+ call mkdir('Xpathdir/Xpathsubdir', 'p')
+ call writefile([''], 'Xpathdir/Xfile.exe')
+ call setfperm('Xpathdir/Xfile.exe', 'rwx------')
+
+ " Set PATH to example directory without trailing slash.
+ let $PATH = getcwd() . '/Xpathdir'
+
+ " Test for the ":!<TAB>" case. Previously, this would include subdirs of
+ " dirs in the PATH, even though they won't be executed. We check that only
+ " subdirs of the PWD and executables from the PATH are included in the
+ " suggestions.
+ let actual = getcompletion('X', 'shellcmd')
+ let expected = map(filter(glob('*', 0, 1), 'isdirectory(v:val) && v:val[0] == "X"'), 'v:val . "/"')
+ call insert(expected, 'Xfile.exe')
+ call assert_equal(expected, actual)
+
+ call delete('Xpathdir', 'rf')
+ let $PATH = save_path
+endfunc
+
func Test_expand_star_star()
call mkdir('a/b', 'p')
call writefile(['asdfasdf'], 'a/b/fileXname')
@@ -492,8 +515,9 @@ func Test_cmdline_complete_user_names()
let names = system('net user')
if names =~ 'Administrator'
" Trying completion of :e ~A should complete to Administrator.
+ " There could be other names starting with "A" before Administrator.
call feedkeys(':e ~A' . "\<c-a>\<c-B>\"\<cr>", 'tx')
- call assert_match('^"e \~Administrator', @:)
+ call assert_match('^"e \~.*Administrator', @:)
endif
endif
endfunc
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index 7f52481ba8..52ec281d82 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -285,3 +285,21 @@ func Test_compl_feedkeys()
bwipe!
set completeopt&
endfunc
+
+func Test_compl_in_cmdwin()
+ set wildmenu wildchar=<Tab>
+ com! -nargs=1 -complete=command GetInput let input = <q-args>
+ com! -buffer TestCommand echo 'TestCommand'
+
+ let input = ''
+ call feedkeys("q:iGetInput T\<C-x>\<C-v>\<CR>", 'tx!')
+ call assert_equal('TestCommand', input)
+
+ let input = ''
+ call feedkeys("q::GetInput T\<Tab>\<CR>:q\<CR>", 'tx!')
+ call assert_equal('T', input)
+
+ delcom TestCommand
+ delcom GetInput
+ set wildmenu& wildchar&
+endfunc
diff --git a/test/functional/api/mark_extended_spec.lua b/test/functional/api/mark_extended_spec.lua
index 1f6c00b7d2..edb0f8ac2b 100644
--- a/test/functional/api/mark_extended_spec.lua
+++ b/test/functional/api/mark_extended_spec.lua
@@ -5,6 +5,7 @@ local request = helpers.request
local eq = helpers.eq
local ok = helpers.ok
local curbufmeths = helpers.curbufmeths
+local bufmeths = helpers.bufmeths
local pcall_err = helpers.pcall_err
local insert = helpers.insert
local feed = helpers.feed
@@ -36,7 +37,7 @@ local function get_extmarks(ns_id, start, end_, opts)
return curbufmeths.get_extmarks(ns_id, start, end_, opts)
end
-describe('Extmarks buffer api', function()
+describe('API/extmarks', function()
local screen
local marks, positions, ns_string2, ns_string, init_text, row, col
local ns, ns2
@@ -153,26 +154,26 @@ describe('Extmarks buffer api', function()
end
-- next with mark id
- rv = get_extmarks(ns, marks[1], {-1, -1}, {amount=1})
+ rv = get_extmarks(ns, marks[1], {-1, -1}, {limit=1})
eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
- rv = get_extmarks(ns, marks[2], {-1, -1}, {amount=1})
+ rv = get_extmarks(ns, marks[2], {-1, -1}, {limit=1})
eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
-- next with positional when mark exists at position
- rv = get_extmarks(ns, positions[1], {-1, -1}, {amount=1})
+ rv = get_extmarks(ns, positions[1], {-1, -1}, {limit=1})
eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
-- next with positional index (no mark at position)
- rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {-1, -1}, {amount=1})
+ rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {-1, -1}, {limit=1})
eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
-- next with Extremity index
- rv = get_extmarks(ns, {0,0}, {-1, -1}, {amount=1})
+ rv = get_extmarks(ns, {0,0}, {-1, -1}, {limit=1})
eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
-- nextrange with mark id
rv = get_extmarks(ns, marks[1], marks[3])
eq({marks[1], positions[1][1], positions[1][2]}, rv[1])
eq({marks[2], positions[2][1], positions[2][2]}, rv[2])
- -- nextrange with amount
- rv = get_extmarks(ns, marks[1], marks[3], {amount=2})
+ -- nextrange with `limit`
+ rv = get_extmarks(ns, marks[1], marks[3], {limit=2})
eq(2, table.getn(rv))
-- nextrange with positional when mark exists at position
rv = get_extmarks(ns, positions[1], positions[3])
@@ -196,18 +197,18 @@ describe('Extmarks buffer api', function()
eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
-- prev with mark id
- rv = get_extmarks(ns, marks[3], {0, 0}, {amount=1})
+ rv = get_extmarks(ns, marks[3], {0, 0}, {limit=1})
eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
- rv = get_extmarks(ns, marks[2], {0, 0}, {amount=1})
+ rv = get_extmarks(ns, marks[2], {0, 0}, {limit=1})
eq({{marks[2], positions[2][1], positions[2][2]}}, rv)
-- prev with positional when mark exists at position
- rv = get_extmarks(ns, positions[3], {0, 0}, {amount=1})
+ rv = get_extmarks(ns, positions[3], {0, 0}, {limit=1})
eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
-- prev with positional index (no mark at position)
- rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {0, 0}, {amount=1})
+ rv = get_extmarks(ns, {positions[1][1], positions[1][2] +1}, {0, 0}, {limit=1})
eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
-- prev with Extremity index
- rv = get_extmarks(ns, {-1,-1}, {0,0}, {amount=1})
+ rv = get_extmarks(ns, {-1,-1}, {0,0}, {limit=1})
eq({{marks[3], positions[3][1], positions[3][2]}}, rv)
-- prevrange with mark id
@@ -215,8 +216,8 @@ describe('Extmarks buffer api', function()
eq({marks[3], positions[3][1], positions[3][2]}, rv[1])
eq({marks[2], positions[2][1], positions[2][2]}, rv[2])
eq({marks[1], positions[1][1], positions[1][2]}, rv[3])
- -- prevrange with amount
- rv = get_extmarks(ns, marks[3], marks[1], {amount=2})
+ -- prevrange with limit
+ rv = get_extmarks(ns, marks[3], marks[1], {limit=2})
eq(2, table.getn(rv))
-- prevrange with positional when mark exists at position
rv = get_extmarks(ns, positions[3], positions[1])
@@ -241,7 +242,7 @@ describe('Extmarks buffer api', function()
eq({{marks[1], positions[1][1], positions[1][2]}}, rv)
end)
- it('querying for information with amount #extmarks', function()
+ it('querying for information with limit #extmarks', function()
-- add some more marks
for i, m in ipairs(marks) do
if positions[i] ~= nil then
@@ -250,19 +251,19 @@ describe('Extmarks buffer api', function()
end
end
- local rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=1})
+ local rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=1})
eq(1, table.getn(rv))
- rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=2})
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=2})
eq(2, table.getn(rv))
- rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=3})
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=3})
eq(3, table.getn(rv))
-- now in reverse
- rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=1})
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=1})
eq(1, table.getn(rv))
- rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=2})
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=2})
eq(2, table.getn(rv))
- rv = get_extmarks(ns, {0, 0}, {-1, -1}, {amount=3})
+ rv = get_extmarks(ns, {0, 0}, {-1, -1}, {limit=3})
eq(3, table.getn(rv))
end)
@@ -295,9 +296,9 @@ describe('Extmarks buffer api', function()
rv)
end)
- it('get_marks amount 0 returns nothing #extmarks', function()
+ it('get_marks limit=0 returns nothing #extmarks', function()
set_extmark(ns, marks[1], positions[1][1], positions[1][2])
- local rv = get_extmarks(ns, {-1, -1}, {-1, -1}, {amount=0})
+ local rv = get_extmarks(ns, {-1, -1}, {-1, -1}, {limit=0})
eq({}, rv)
end)
@@ -730,7 +731,7 @@ describe('Extmarks buffer api', function()
-- Test updates
feed('o<esc>')
set_extmark(ns, marks[1], positions[1][1], positions[1][2])
- rv = get_extmarks(ns, marks[1], marks[1], {amount=1})
+ rv = get_extmarks(ns, marks[1], marks[1], {limit=1})
eq(1, table.getn(rv))
feed("u")
feed("<c-r>")
@@ -771,23 +772,23 @@ describe('Extmarks buffer api', function()
set_extmark(ns2, marks[2], positions[2][1], positions[2][2])
set_extmark(ns2, marks[3], positions[3][1], positions[3][2])
- -- get_next (amount set)
- rv = get_extmarks(ns, {0, 0}, positions[2], {amount=1})
+ -- get_next (limit set)
+ rv = get_extmarks(ns, {0, 0}, positions[2], {limit=1})
eq(1, table.getn(rv))
- rv = get_extmarks(ns2, {0, 0}, positions[2], {amount=1})
+ rv = get_extmarks(ns2, {0, 0}, positions[2], {limit=1})
eq(1, table.getn(rv))
- -- get_prev (amount set)
- rv = get_extmarks(ns, positions[1], {0, 0}, {amount=1})
+ -- get_prev (limit set)
+ rv = get_extmarks(ns, positions[1], {0, 0}, {limit=1})
eq(1, table.getn(rv))
- rv = get_extmarks(ns2, positions[1], {0, 0}, {amount=1})
+ rv = get_extmarks(ns2, positions[1], {0, 0}, {limit=1})
eq(1, table.getn(rv))
- -- get_next (amount not set)
+ -- get_next (no limit)
rv = get_extmarks(ns, positions[1], positions[2])
eq(2, table.getn(rv))
rv = get_extmarks(ns2, positions[1], positions[2])
eq(2, table.getn(rv))
- -- get_prev (amount not set)
+ -- get_prev (no limit)
rv = get_extmarks(ns, positions[2], positions[1])
eq(2, table.getn(rv))
rv = get_extmarks(ns2, positions[2], positions[1])
@@ -1250,7 +1251,7 @@ describe('Extmarks buffer api', function()
eq("col value outside range", pcall_err(set_extmark, ns, marks[1], 0, invalid_col))
end)
- it('when line > line_count, throw error #extmarks', function()
+ it('fails when line > line_count #extmarks', function()
local invalid_col = init_text:len() + 1
local invalid_lnum = 3
eq('line value outside range', pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col))
@@ -1258,10 +1259,9 @@ describe('Extmarks buffer api', function()
end)
it('bug from check_col in extmark_set #extmarks_sub', function()
- -- This bug was caused by extmark_set always using
- -- check_col. check_col always uses the current buffer.
- -- This wasn't working during undo so we now use
- -- check_col and check_lnum only when they are required.
+ -- This bug was caused by extmark_set always using check_col. check_col
+ -- always uses the current buffer. This wasn't working during undo so we
+ -- now use check_col and check_lnum only when they are required.
feed('A<cr>67890<cr>xx<esc>')
feed('A<cr>12345<cr>67890<cr>xx<esc>')
set_extmark(ns, marks[1], 3, 4)
@@ -1275,6 +1275,13 @@ describe('Extmarks buffer api', function()
local id = set_extmark(ns, 0, 0, 2)
eq({{id, 0, 2}}, get_extmarks(ns,0, -1))
end)
+
+ it('can set a mark to other buffer', function()
+ local buf = request('nvim_create_buf', 0, 1)
+ request('nvim_buf_set_lines', buf, 0, -1, 1, {"", ""})
+ local id = bufmeths.set_extmark(buf, ns, 0, 1, 0, {})
+ eq({{id, 1, 0}}, bufmeths.get_extmarks(buf, ns, 0, -1, {}))
+ end)
end)
describe('Extmarks buffer api with many marks', function()
diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua
index 063d382790..d828bdf948 100644
--- a/test/functional/api/proc_spec.lua
+++ b/test/functional/api/proc_spec.lua
@@ -10,7 +10,7 @@ local request = helpers.request
local retry = helpers.retry
local NIL = helpers.NIL
-describe('api', function()
+describe('API', function()
before_each(clear)
describe('nvim_get_proc_children', function()
diff --git a/test/functional/cmdline/ctrl_r_spec.lua b/test/functional/cmdline/ctrl_r_spec.lua
index d2dad23e98..a0f3955282 100644
--- a/test/functional/cmdline/ctrl_r_spec.lua
+++ b/test/functional/cmdline/ctrl_r_spec.lua
@@ -15,7 +15,7 @@ describe('cmdline CTRL-R', function()
-- <CR> inserted between lines, NOT after the final line.
eq('line1abc\rline2somemoretext', funcs.getcmdline())
- -- Yank 2 lines characterwise, then paste to cmdline.
+ -- Yank 2 lines charwise, then paste to cmdline.
feed([[<C-\><C-N>gg05lyvj:<C-R>0]])
-- <CR> inserted between lines, NOT after the final line.
eq('abc\rline2', funcs.getcmdline())
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
index 028f2dcd52..720a33d430 100644
--- a/test/functional/lua/vim_spec.lua
+++ b/test/functional/lua/vim_spec.lua
@@ -353,10 +353,14 @@ describe('lua stdlib', function()
it('vim.list_extend', function()
eq({1,2,3}, exec_lua [[ return vim.list_extend({1}, {2,3}) ]])
- eq('Error executing lua: .../shared.lua: src must be a table',
+ eq('Error executing lua: .../shared.lua: src: expected table, got nil',
pcall_err(exec_lua, [[ return vim.list_extend({1}, nil) ]]))
eq({1,2}, exec_lua [[ return vim.list_extend({1}, {2;a=1}) ]])
eq(true, exec_lua [[ local a = {1} return vim.list_extend(a, {2;a=1}) == a ]])
+ eq({2}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 1) ]])
+ eq({}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 2) ]])
+ eq({}, exec_lua [[ return vim.list_extend({}, {2;a=1}, 1, -1) ]])
+ eq({2}, exec_lua [[ return vim.list_extend({}, {2;a=1}, -1, 2) ]])
end)
it('vim.tbl_add_reverse_lookup', function()
@@ -549,4 +553,69 @@ describe('lua stdlib', function()
eq(false, exec_lua("return vim.is_callable('foo')"))
eq(false, exec_lua("return vim.is_callable({})"))
end)
+
+ it('vim.g', function()
+ exec_lua [[
+ vim.api.nvim_set_var("testing", "hi")
+ vim.api.nvim_set_var("other", 123)
+ ]]
+ eq('hi', funcs.luaeval "vim.g.testing")
+ eq(123, funcs.luaeval "vim.g.other")
+ eq(NIL, funcs.luaeval "vim.g.nonexistant")
+ end)
+
+ it('vim.env', function()
+ exec_lua [[
+ vim.fn.setenv("A", 123)
+ ]]
+ eq('123', funcs.luaeval "vim.env.A")
+ eq(NIL, funcs.luaeval "vim.env.B")
+ end)
+
+ it('vim.v', function()
+ eq(funcs.luaeval "vim.api.nvim_get_vvar('progpath')", funcs.luaeval "vim.v.progpath")
+ eq(false, funcs.luaeval "vim.v['false']")
+ eq(NIL, funcs.luaeval "vim.v.null")
+ end)
+
+ it('vim.bo', function()
+ eq('', funcs.luaeval "vim.bo.filetype")
+ exec_lua [[
+ vim.api.nvim_buf_set_option(0, "filetype", "markdown")
+ BUF = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_option(BUF, "modifiable", false)
+ ]]
+ eq(false, funcs.luaeval "vim.bo.modified")
+ eq('markdown', funcs.luaeval "vim.bo.filetype")
+ eq(false, funcs.luaeval "vim.bo[BUF].modifiable")
+ exec_lua [[
+ vim.bo.filetype = ''
+ vim.bo[BUF].modifiable = true
+ ]]
+ eq('', funcs.luaeval "vim.bo.filetype")
+ eq(true, funcs.luaeval "vim.bo[BUF].modifiable")
+ matches("^Error executing lua: .*: Invalid option name: 'nosuchopt'$",
+ pcall_err(exec_lua, 'return vim.bo.nosuchopt'))
+ matches("^Error executing lua: .*: Expected lua string$",
+ pcall_err(exec_lua, 'return vim.bo[0][0].autoread'))
+ end)
+
+ it('vim.wo', function()
+ eq('', funcs.luaeval "vim.bo.filetype")
+ exec_lua [[
+ vim.api.nvim_win_set_option(0, "cole", 2)
+ BUF = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_option(BUF, "modifiable", false)
+ ]]
+ eq(2, funcs.luaeval "vim.wo.cole")
+ exec_lua [[
+ vim.wo.conceallevel = 0
+ vim.bo[BUF].modifiable = true
+ ]]
+ eq(0, funcs.luaeval "vim.wo.cole")
+ matches("^Error executing lua: .*: Invalid option name: 'notanopt'$",
+ pcall_err(exec_lua, 'return vim.wo.notanopt'))
+ matches("^Error executing lua: .*: Expected lua string$",
+ pcall_err(exec_lua, 'return vim.wo[0][0].list'))
+ end)
end)
diff --git a/test/functional/normal/put_spec.lua b/test/functional/normal/put_spec.lua
index 40a4f051e3..357fafec44 100644
--- a/test/functional/normal/put_spec.lua
+++ b/test/functional/normal/put_spec.lua
@@ -307,7 +307,7 @@ describe('put command', function()
-- }}}
-- Conversion functions {{{
- local function convert_characterwise(expect_base, conversion_table,
+ local function convert_charwise(expect_base, conversion_table,
virtualedit_end, visual_put)
expect_base = dedent(expect_base)
-- There is no difference between 'P' and 'p' when VIsual_active
@@ -335,7 +335,7 @@ describe('put command', function()
expect_base = expect_base:gsub('(test_stringx?)"', '%1.')
end
return expect_base
- end -- convert_characterwise()
+ end -- convert_charwise()
local function make_back(string)
local prev_line
@@ -500,7 +500,7 @@ describe('put command', function()
local function run_normal_mode_tests(test_string, base_map, extra_setup,
virtualedit_end, selection_string)
local function convert_closure(e, c)
- return convert_characterwise(e, c, virtualedit_end, selection_string)
+ return convert_charwise(e, c, virtualedit_end, selection_string)
end
local function expect_normal_creator(expect_base, conversion_table)
local test_expect = expect_creator(convert_closure, expect_base, conversion_table)
diff --git a/test/functional/plugin/lsp/util_spec.lua b/test/functional/plugin/lsp/util_spec.lua
new file mode 100644
index 0000000000..1cf0e48be4
--- /dev/null
+++ b/test/functional/plugin/lsp/util_spec.lua
@@ -0,0 +1,76 @@
+local helpers = require('test.functional.helpers')(after_each)
+local eq = helpers.eq
+local exec_lua = helpers.exec_lua
+local dedent = helpers.dedent
+local insert = helpers.insert
+local clear = helpers.clear
+
+describe('LSP util', function()
+ local test_text = dedent([[
+ First line of text
+ Second line of text
+ Third line of text
+ Fourth line of text]])
+
+ local function reset()
+ clear()
+ insert(test_text)
+ end
+
+ before_each(reset)
+
+ local function make_edit(y_0, x_0, y_1, x_1, text)
+ return {
+ range = {
+ start = { line = y_0, character = x_0 };
+ ["end"] = { line = y_1, character = x_1 };
+ };
+ newText = type(text) == 'table' and table.concat(text, '\n') or (text or "");
+ }
+ end
+
+ local function buf_lines(bufnr)
+ return exec_lua("return vim.api.nvim_buf_get_lines((...), 0, -1, false)", bufnr)
+ end
+
+ describe('apply_edits', function()
+ it('should apply simple edits', function()
+ local edits = {
+ make_edit(0, 0, 0, 0, {"123"});
+ make_edit(1, 0, 1, 1, {"2"});
+ make_edit(2, 0, 2, 2, {"3"});
+ }
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1)
+ eq({
+ '123First line of text';
+ '2econd line of text';
+ '3ird line of text';
+ 'Fourth line of text';
+ }, buf_lines(1))
+ end)
+
+ it('should apply complex edits', 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 ", 0, #"First line of text", {"guy"});
+ make_edit(1, 0, 1, #'Second', {"baz"});
+ make_edit(2, #'Th', 2, #"Third", {"e next"});
+ make_edit(3, #'', 3, #"Fourth", {"another line of text", "before this"});
+ make_edit(3, #'Fourth', 3, #"Fourth line of text", {"!"});
+ }
+ 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)
+ end)
+end)
diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua
index ea71f5eae9..31007b92b1 100644
--- a/test/functional/ui/options_spec.lua
+++ b/test/functional/ui/options_spec.lua
@@ -5,7 +5,7 @@ local command = helpers.command
local eq = helpers.eq
local shallowcopy = helpers.shallowcopy
-describe('ui receives option updates', function()
+describe('UI receives option updates', function()
local screen
local function reset(opts, ...)
@@ -47,6 +47,33 @@ describe('ui receives option updates', function()
end)
end)
+ it('on attach #11372', function()
+ clear()
+ local evs = {}
+ screen = Screen.new(20,5)
+ -- Override mouse_on/mouse_off handlers.
+ function screen._handle_mouse_on()
+ table.insert(evs, 'mouse_on')
+ end
+ function screen._handle_mouse_off()
+ table.insert(evs, 'mouse_off')
+ end
+ screen:attach()
+ screen:expect(function()
+ eq({'mouse_off'}, evs)
+ end)
+ command("set mouse=nvi")
+ screen:expect(function()
+ eq({'mouse_off','mouse_on'}, evs)
+ end)
+ screen:detach()
+ eq({'mouse_off','mouse_on'}, evs)
+ screen:attach()
+ screen:expect(function()
+ eq({'mouse_off','mouse_on','mouse_on'}, evs)
+ end)
+ end)
+
it("when setting options", function()
local expected = reset()
local defaults = shallowcopy(expected)
diff --git a/test/helpers.lua b/test/helpers.lua
index 3f29a28c0d..98f003f208 100644
--- a/test/helpers.lua
+++ b/test/helpers.lua
@@ -99,6 +99,9 @@ function module.pcall_err(fn, ...)
-- to this:
-- Error executing lua: .../foo.lua:186: Expected string, got number
errmsg = errmsg:gsub([[lua: [a-zA-Z]?:?[^:]-[/\]([^:/\]+):%d+: ]], 'lua: .../%1: ')
+ -- Compiled modules will not have a path and will just be a name like
+ -- shared.lua:186, so strip the number.
+ errmsg = errmsg:gsub([[lua: ([^:/\ ]+):%d+: ]], 'lua: .../%1: ')
-- ^ Windows drive-letter (C:)
return errmsg
end