aboutsummaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/autoload/health/provider.vim10
-rw-r--r--runtime/autoload/tutor.vim4
-rw-r--r--runtime/delmenu.vim30
-rw-r--r--runtime/doc/api.txt7
-rw-r--r--runtime/doc/channel.txt77
-rw-r--r--runtime/doc/eval.txt29
-rw-r--r--runtime/doc/lsp.txt61
-rw-r--r--runtime/doc/lua.txt14
-rw-r--r--runtime/doc/options.txt8
-rw-r--r--runtime/doc/provider.txt2
-rw-r--r--runtime/doc/treesitter.txt524
-rw-r--r--runtime/doc/usr_41.txt1
-rw-r--r--runtime/filetype.vim19
-rw-r--r--runtime/lua/vim/lsp.lua360
-rw-r--r--runtime/lua/vim/lsp/buf.lua111
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua14
-rw-r--r--runtime/lua/vim/lsp/rpc.lua2
-rw-r--r--runtime/lua/vim/lsp/util.lua107
-rw-r--r--runtime/lua/vim/treesitter.lua38
-rw-r--r--runtime/lua/vim/treesitter/health.lua4
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua22
-rw-r--r--runtime/lua/vim/treesitter/language.lua20
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua226
-rw-r--r--runtime/lua/vim/treesitter/query.lua173
-rw-r--r--runtime/menu.vim10
25 files changed, 1446 insertions, 427 deletions
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim
index 112dd4354f..de540405e6 100644
--- a/runtime/autoload/health/provider.vim
+++ b/runtime/autoload/health/provider.vim
@@ -400,8 +400,6 @@ function! s:check_python(version) abort
endfor
endif
- let pip = 'pip' . (a:version == 2 ? '' : '3')
-
if empty(python_exe)
" No Python executable can import 'neovim'. Check if any Python executable
" can import 'pynvim'. If so, that Python failed to import 'neovim' as
@@ -413,9 +411,9 @@ function! s:check_python(version) abort
\ 'Detected pip upgrade failure: Python executable can import "pynvim" but '
\ . 'not "neovim": '. pynvim_exe,
\ "Use that Python version to reinstall \"pynvim\" and optionally \"neovim\".\n"
- \ . pip ." uninstall pynvim neovim\n"
- \ . pip ." install pynvim\n"
- \ . pip ." install neovim # only if needed by third-party software")
+ \ . pynvim_exe ." -m pip uninstall pynvim neovim\n"
+ \ . pynvim_exe ." -m pip install pynvim\n"
+ \ . pynvim_exe ." -m pip install neovim # only if needed by third-party software")
endif
else
let [pyversion, current, latest, status] = s:version_info(python_exe)
@@ -440,7 +438,7 @@ function! s:check_python(version) abort
if s:is_bad_response(current)
call health#report_error(
\ "pynvim is not installed.\nError: ".current,
- \ ['Run in shell: '. pip .' install pynvim'])
+ \ ['Run in shell: '. python_exe .' -m pip install pynvim'])
endif
if s:is_bad_response(latest)
diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim
index 6afe64de84..abf5c5e2c8 100644
--- a/runtime/autoload/tutor.vim
+++ b/runtime/autoload/tutor.vim
@@ -104,6 +104,10 @@ function! tutor#CheckLine(line)
if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect')
let bufn = bufnr('%')
let ctext = getline(a:line)
+ let signs = sign_getplaced('.', {'lnum': a:line})[0].signs
+ if !empty(signs)
+ call sign_unplace('', {'id': signs[0].id})
+ endif
if b:tutor_metadata['expect'][string(a:line)] == -1 || ctext ==# b:tutor_metadata['expect'][string(a:line)]
exe "sign place ".b:tutor_sign_id." line=".a:line." name=tutorok buffer=".bufn
else
diff --git a/runtime/delmenu.vim b/runtime/delmenu.vim
index 81df87d346..5c20290152 100644
--- a/runtime/delmenu.vim
+++ b/runtime/delmenu.vim
@@ -2,24 +2,30 @@
" Warning: This also deletes all menus defined by the user!
"
" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2001 May 27
+" Last Change: 2019 Dec 10
aunmenu *
-silent! unlet did_install_default_menus
-silent! unlet did_install_syntax_menu
-if exists("did_menu_trans")
+unlet! g:did_install_default_menus
+unlet! g:did_install_syntax_menu
+
+if exists('g:did_menu_trans')
menutrans clear
- unlet did_menu_trans
+ unlet g:did_menu_trans
endif
-silent! unlet find_help_dialog
+unlet! g:find_help_dialog
-silent! unlet menutrans_help_dialog
-silent! unlet menutrans_path_dialog
-silent! unlet menutrans_tags_dialog
-silent! unlet menutrans_textwidth_dialog
-silent! unlet menutrans_fileformat_dialog
-silent! unlet menutrans_no_file
+unlet! g:menutrans_fileformat_choices
+unlet! g:menutrans_fileformat_dialog
+unlet! g:menutrans_help_dialog
+unlet! g:menutrans_no_file
+unlet! g:menutrans_path_dialog
+unlet! g:menutrans_set_lang_to
+unlet! g:menutrans_spell_add_ARG_to_word_list
+unlet! g:menutrans_spell_change_ARG_to
+unlet! g:menutrans_spell_ignore_ARG
+unlet! g:menutrans_tags_dialog
+unlet! g:menutrans_textwidth_dialog
" vim: set sw=2 :
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 0c17fa1669..0d85d6b539 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -1212,6 +1212,9 @@ nvim_open_term({buffer}, {opts}) *nvim_open_term()*
{buffer} the buffer to use (expected to be empty)
{opts} Optional parameters. Reserved for future use.
+ Return: ~
+ Channel id, or 0 on error
+
nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
Open a new window.
@@ -2280,6 +2283,10 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts})
color
• "blend": blend with background text color.
+ • hl_eol : when true, for a multiline highlight
+ covering the EOL of a line, continue the
+ highlight for the rest of the screen line
+ (just like for diff and cursorline highlight).
• ephemeral : for use with
|nvim_set_decoration_provider| callbacks. The
mark will only be used for the current redraw
diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt
index 967f4b26f2..656bb10c45 100644
--- a/runtime/doc/channel.txt
+++ b/runtime/doc/channel.txt
@@ -174,4 +174,81 @@ Put this in `uppercase.vim` and run: >
nvim --headless --cmd "source uppercase.vim"
==============================================================================
+5. Using a prompt buffer *prompt-buffer*
+
+If you want to type input for the job in a Vim window you have a few options:
+- Use a normal buffer and handle all possible commands yourself.
+ This will be complicated, since there are so many possible commands.
+- Use a terminal window. This works well if what you type goes directly to
+ the job and the job output is directly displayed in the window.
+ See |terminal|.
+- Use a window with a prompt buffer. This works well when entering a line for
+ the job in Vim while displaying (possibly filtered) output from the job.
+
+A prompt buffer is created by setting 'buftype' to "prompt". You would
+normally only do that in a newly created buffer.
+
+The user can edit and enter one line of text at the very last line of the
+buffer. When pressing Enter in the prompt line the callback set with
+|prompt_setcallback()| is invoked. It would normally send the line to a job.
+Another callback would receive the output from the job and display it in the
+buffer, below the prompt (and above the next prompt).
+
+Only the text in the last line, after the prompt, is editable. The rest of the
+buffer is not modifiable with Normal mode commands. It can be modified by
+calling functions, such as |append()|. Using other commands may mess up the
+buffer.
+
+After setting 'buftype' to "prompt" Vim does not automatically start Insert
+mode, use `:startinsert` if you want to enter Insert mode, so that the user
+can start typing a line.
+
+The text of the prompt can be set with the |prompt_setprompt()| function. If
+no prompt is set with |prompt_setprompt()|, "% " is used. You can get the
+effective prompt text for a buffer, with |prompt_getprompt()|.
+
+The user can go to Normal mode and navigate through the buffer. This can be
+useful to see older output or copy text.
+
+Any command that starts Insert mode, such as "a", "i", "A" and "I", will move
+the cursor to the last line. "A" will move to the end of the line, "I" to the
+start of the line.
+
+Here is an example for Unix. It starts a shell in the background and prompts
+for the next shell command. Output from the shell is displayed above the
+prompt. >
+
+ " Function handling a line of text that has been typed.
+ func TextEntered(text)
+ " Send the text to a shell with Enter appended.
+ call chansend(g:shell_job, [a:text, ''])
+ endfunc
+
+ " Function handling output from the shell: Added above the prompt.
+ func GotOutput(channel, msg, name)
+ call append(line("$") - 1, a:msg)
+ endfunc
+
+ " Function handling the shell exit: close the window.
+ func JobExit(job, status, event)
+ quit!
+ endfunc
+
+ " Start a shell in the background.
+ let shell_job = jobstart(["/bin/sh"], #{
+ \ on_stdout: function('GotOutput'),
+ \ on_stderr: function('GotOutput'),
+ \ on_exit: function('JobExit'),
+ \ })
+
+ new
+ set buftype=prompt
+ let buf = bufnr('')
+ call prompt_setcallback(buf, function("TextEntered"))
+ call prompt_setprompt(buf, "shell command: ")
+
+ " start accepting shell commands
+ startinsert
+<
+
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index c3736d9a3e..b8dcfd0ff4 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -2309,6 +2309,7 @@ perleval({expr}) any evaluate |perl| expression
pow({x}, {y}) Float {x} to the power of {y}
prevnonblank({lnum}) Number line nr of non-blank line <= {lnum}
printf({fmt}, {expr1}...) String format text
+prompt_getprompt({buf}) String get prompt text
prompt_setcallback({buf}, {expr}) none set prompt callback function
prompt_setinterrupt({buf}, {text}) none set prompt interrupt function
prompt_setprompt({buf}, {text}) none set prompt text
@@ -5361,9 +5362,8 @@ input({opts})
prompt "" Same as {prompt} in the first form.
default "" Same as {text} in the first form.
completion nothing Same as {completion} in the first form.
- cancelreturn "" Same as {cancelreturn} from
- |inputdialog()|. Also works with
- input().
+ cancelreturn "" The value returned when the dialog is
+ cancelled.
highlight nothing Highlight handler: |Funcref|.
The highlighting set with |:echohl| is used for the prompt.
@@ -6747,6 +6747,13 @@ printf({fmt}, {expr1} ...) *printf()*
of "%" items. If there are not sufficient or too many
arguments an error is given. Up to 18 arguments can be used.
+prompt_getprompt({buf}) *prompt_getprompt()*
+ Returns the effective prompt text for buffer {buf}. {buf} can
+ be a buffer name or number. See |prompt-buffer|.
+
+ If the buffer doesn't exist or isn't a prompt buffer, an empty
+ string is returned.
+
prompt_setcallback({buf}, {expr}) *prompt_setcallback()*
Set prompt callback for buffer {buf} to {expr}. When {expr}
is an empty string the callback is removed. This has only
@@ -10099,6 +10106,8 @@ This function can then be called with: >
The recursiveness of user functions is restricted with the |'maxfuncdepth'|
option.
+It is also possible to use `:eval`. It does not support a range.
+
AUTOMATICALLY LOADING FUNCTIONS ~
*autoload-functions*
@@ -10532,6 +10541,20 @@ text...
Unlock the internal variable {name}. Does the
opposite of |:lockvar|.
+ *:eval*
+:eval {expr} Evaluate {expr} and discard the result. Example: >
+ :eval append(Filter(Getlist()), '$')
+
+< The expression is supposed to have a side effect,
+ since the resulting value is not used. In the example
+ the `append()` call appends the List with text to the
+ buffer. This is similar to `:call` but works with any
+ expression.
+
+ The command can be shortened to `:ev` or `:eva`, but
+ these are hard to recognize and therefore not to be
+ used.
+
:if {expr1} *:if* *:end* *:endif* *:en* *E171* *E579* *E580*
:en[dif] Execute the commands until the next matching ":else"
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 3c0dbf96c5..5c2ee568c5 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -592,14 +592,34 @@ buf_request({bufnr}, {method}, {params}, {handler})
You could instead iterate all clients and call their
`cancel_request()` methods.
+ *vim.lsp.buf_request_all()*
+buf_request_all({bufnr}, {method}, {params}, {callback})
+ Sends an async request for all active clients attached to the
+ buffer. Executes the callback on the combined result.
+ Parameters are the same as |vim.lsp.buf_request()| but the
+ return result and callback are different.
+
+ Parameters: ~
+ {bufnr} (number) Buffer handle, or 0 for current.
+ {method} (string) LSP method name
+ {params} (optional, table) Parameters to send to the
+ server
+ {callback} (function) The callback to call when all
+ requests are finished.
+
+ Return: ~
+ (function) A function that will cancel all requests which
+ is the same as the one returned from `buf_request` .
+
*vim.lsp.buf_request_sync()*
buf_request_sync({bufnr}, {method}, {params}, {timeout_ms})
- Sends a request to a server and waits for the response.
+ Sends a request to all server and waits for the response of
+ all of them.
- Calls |vim.lsp.buf_request()| but blocks Nvim while awaiting
- the result. Parameters are the same as |vim.lsp.buf_request()|
- but the return result is different. Wait maximum of
- {timeout_ms} (default 100) ms.
+ Calls |vim.lsp.buf_request_all()| but blocks Nvim while
+ awaiting the result. Parameters are the same as
+ |vim.lsp.buf_request()| but the return result is different.
+ Wait maximum of {timeout_ms} (default 100) ms.
Parameters: ~
{bufnr} (number) Buffer handle, or 0 for current.
@@ -678,6 +698,9 @@ client_is_stopped({client_id}) *vim.lsp.client_is_stopped()*
Return: ~
true if client is stopped, false otherwise.
+flush({client}) *vim.lsp.flush()*
+ TODO: Documentation
+
get_active_clients() *vim.lsp.get_active_clients()*
Gets all active clients.
@@ -708,6 +731,15 @@ get_log_path() *vim.lsp.get_log_path()*
Return: ~
(String) Path to logfile.
+init({client}, {bufnr}) *vim.lsp.init()*
+ client_id → state
+
+ state pending_change?: function that the timer starts to
+ trigger didChange pending_changes: list of tables with the
+ pending changesets; for incremental_sync only
+ use_incremental_sync: bool buffers?: table (bufnr → lines);
+ for incremental sync only timer?: uv_timer
+
omnifunc({findstart}, {base}) *vim.lsp.omnifunc()*
Implements 'omnifunc' compatible LSP completion.
@@ -727,6 +759,16 @@ omnifunc({findstart}, {base}) *vim.lsp.omnifunc()*
|complete-items|
|CompleteDone|
+ *vim.lsp.prepare()*
+prepare({bufnr}, {firstline}, {new_lastline}, {changedtick})
+ TODO: Documentation
+
+reset({client_id}) *vim.lsp.reset()*
+ TODO: Documentation
+
+reset_buf({client}, {bufnr}) *vim.lsp.reset_buf()*
+ TODO: Documentation
+
set_log_level({level}) *vim.lsp.set_log_level()*
Sets the global log level for LSP logging.
@@ -849,6 +891,11 @@ start_client({config}) *vim.lsp.start_client()*
• allow_incremental_sync (bool, default
true): Allow using incremental sync
for buffer edits
+ • debounce_text_changes (number,
+ default nil): Debounce didChange
+ notifications to the server by the
+ given number in milliseconds. No
+ debounce occurs if nil
Return: ~
Client id. |vim.lsp.get_client_by_id()| Note: client may
@@ -1311,6 +1358,10 @@ on_publish_diagnostics({_}, {_}, {params}, {client_id}, {_}, {config})
• Update diagnostics in InsertMode or wait
until InsertLeave
+ • severity_sort: (default=false)
+ • Sort diagnostics (and thus signs and virtual
+ text)
+
reset({client_id}, {buffer_client_map}) *vim.lsp.diagnostic.reset()*
Clear diagnotics and diagnostic cache
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index c2fc25431c..be01966d42 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -18,7 +18,8 @@ an idea of what lurks beneath: >
Nvim includes a "standard library" |lua-stdlib| for Lua. It complements the
"editor stdlib" (|functions| and Ex commands) and the |API|, all of which can
-be used from Lua code.
+be used from Lua code. A good overview of using Lua in neovim is given by
+https://github.com/nanotee/nvim-lua-guide.
Module conflicts are resolved by "last wins". For example if both of these
are on 'runtimepath':
@@ -831,6 +832,7 @@ LUA-VIMSCRIPT BRIDGE *lua-vimscript*
Nvim Lua provides an interface to Vimscript variables and functions, and
editor commands and options.
+See also https://github.com/nanotee/nvim-lua-guide.
vim.call({func}, {...}) *vim.call()*
Invokes |vim-function| or |user-function| {func} with arguments {...}.
@@ -839,10 +841,18 @@ vim.call({func}, {...}) *vim.call()*
vim.fn[func]({...})
vim.cmd({cmd}) *vim.cmd()*
- Invokes an Ex command (the ":" commands, Vimscript statements).
+ Executes multiple lines of Vimscript at once. It is an alias to
+ |nvim_exec()|, where `output` is set to false. Thus it works identical
+ to |:source|.
See also |ex-cmd-index|.
Example: >
vim.cmd('echo 42')
+ vim.cmd([[
+ augroup My_group
+ autocmd!
+ autocmd FileType c setlocal cindent
+ augroup END
+ ]])
vim.fn.{func}({...}) *vim.fn*
Invokes |vim-function| or |user-function| {func} with arguments {...}.
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 04310ca8d4..63a9db9d0d 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -4818,7 +4818,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|xdg| ($XDG_CONFIG_DIRS, defaults to /etc/xdg). This also contains
preferences from system administrator.
3. Data home directory, for plugins installed by user.
- Given by `stdpath("data")`. |$XDG_DATA_HOME|
+ Given by `stdpath("data")/site`. |$XDG_DATA_HOME|
4. nvim/site subdirectories for each directory in $XDG_DATA_DIRS.
This is for plugins which were installed by system administrator,
but are not part of the Nvim distribution. XDG_DATA_DIRS defaults
@@ -5555,6 +5555,12 @@ A jump table for the options with a short description can be found at |Q_op|.
"number" display signs in the 'number' column. If the number
column is not present, then behaves like 'auto'.
+ Note regarding 'orphaned signs': with signcolumn numbers higher than
+ 1, deleting lines will also remove the associated signs automatically,
+ in contrast to the default Vim behavior of keeping and grouping them.
+ This is done in order for the signcolumn appearence not appear weird
+ during line deletion.
+
*'smartcase'* *'scs'* *'nosmartcase'* *'noscs'*
'smartcase' 'scs' boolean (default off)
diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt
index f944689d0b..be895f9e4e 100644
--- a/runtime/doc/provider.txt
+++ b/runtime/doc/provider.txt
@@ -88,7 +88,7 @@ Example using pyenv: >
pyenv install 3.4.4
pyenv virtualenv 3.4.4 py3nvim
pyenv activate py3nvim
- pip install pynvim
+ python3 -m pip install pynvim
pyenv which python # Note the path
The last command reports the interpreter path, add it to your init.vim: >
let g:python3_host_prog = '/path/to/py3nvim/bin/python'
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index 343f4a62c2..1f4b5d3097 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -102,14 +102,14 @@ tsnode:field({name}) *tsnode:field()*
tsnode:child_count() *tsnode:child_count()*
Get the node's number of children.
-tsnode:child({index}) *tsnode:child()*
+tsnode:child({index}) *tsnode:child()*
Get the node's child at the given {index}, where zero represents the
first child.
-tsnode:named_child_count() *tsnode:named_child_count()*
+tsnode:named_child_count() *tsnode:named_child_count()*
Get the node's number of named children.
-tsnode:named_child({index}) *tsnode:named_child()*
+tsnode:named_child({index}) *tsnode:named_child()*
Get the node's named child at the given {index}, where zero represents
the first named child.
@@ -146,7 +146,7 @@ tsnode:has_error() *tsnode:has_error()*
tsnode:sexpr() *tsnode:sexpr()*
Get an S-expression representing the node as a string.
-tsnode:id() *tsnode:id()*
+tsnode:id() *tsnode:id()*
Get an unique identier for the node inside its own tree.
No guarantees are made about this identifer's internal representation,
@@ -156,16 +156,16 @@ tsnode:id() *tsnode:id()*
NB: the id is not guaranteed to be unique for nodes from different trees.
tsnode:descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
- *tsnode:descendant_for_range()*
+ *tsnode:descendant_for_range()*
Get the smallest node within this node that spans the given range of
(row, column) positions
tsnode:named_descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
- *tsnode:named_descendant_for_range()*
+ *tsnode:named_descendant_for_range()*
Get the smallest named node within this node that spans the given
range of (row, column) positions
-Query methods *lua-treesitter-query*
+Query *lua-treesitter-query*
Tree-sitter queries are supported, with some limitations. Currently, the only
supported match predicate is `eq?` (both comparing a capture against a string
@@ -178,65 +178,6 @@ and predicates. A `capture` allows you to associate names with a specific
node in a pattern. A `predicate` adds arbitrary metadata and conditional data
to a match.
-vim.treesitter.parse_query({lang}, {query})
- *vim.treesitter.parse_query()*
- Parse {query} as a string. (If the query is in a file, the caller
- should read the contents into a string before calling).
-
- Returns a `Query` (see |lua-treesitter-query|) object which can be used to
- search nodes in the syntax tree for the patterns defined in {query}
- using `iter_*` methods below. Exposes `info` and `captures` with
- additional information about the {query}.
- - `captures` contains the list of unique capture names defined in
- {query}.
- -` info.captures` also points to `captures`.
- - `info.patterns` contains information about predicates.
-
-
-query:iter_captures({node}, {bufnr}, {start_row}, {end_row})
- *query:iter_captures()*
- Iterate over all captures from all matches inside {node}.
- {bufnr} is needed if the query contains predicates, then the caller
- must ensure to use a freshly parsed tree consistent with the current
- text of the buffer. {start_row} and {end_row} can be used to limit
- matches inside a row range (this is typically used with root node
- as the node, i e to get syntax highlight matches in the current
- viewport). When omitted the start and end row values are used from
- the given node.
-
- The iterator returns three values, a numeric id identifying the capture,
- the captured node, and metadata from any directives processing the match.
- The following example shows how to get captures by name:
->
- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
- local name = query.captures[id] -- name of the capture in the query
- -- typically useful info about the node:
- local type = node:type() -- type of the captured node
- local row1, col1, row2, col2 = node:range() -- range of the capture
- ... use the info here ...
- end
-<
-query:iter_matches({node}, {bufnr}, {start_row}, {end_row})
- *query:iter_matches()*
- Iterate over all matches within a node. The arguments are the same as
- for |query:iter_captures()| but the iterated values are different:
- an (1-based) index of the pattern in the query, a table mapping
- capture indices to nodes, and metadata from any directives processing the match.
- If the query has more than one pattern the capture table might be sparse,
- and e.g. `pairs()` method should be used over `ipairs`.
- Here an example iterating over all captures in every match:
->
- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
- for id, node in pairs(match) do
- local name = query.captures[id]
- -- `node` was captured by the `name` capture in the match
-
- local node_data = metadata[id] -- Node level metadata
-
- ... use the info here ...
- end
- end
-
Treesitter Query Predicates *lua-treesitter-predicates*
When writing queries for treesitter, one might use `predicates`, that is,
@@ -298,28 +239,6 @@ Here is a list of built-in directives:
`({capture_id}, {start_row}, {start_col}, {end_row}, {end_col}, {key?})`
The default key is "offset".
- *vim.treesitter.query.add_predicate()*
-vim.treesitter.query.add_predicate({name}, {handler})
-
-This adds a predicate with the name {name} to be used in queries.
-{handler} should be a function whose signature will be : >
- handler(match, pattern, bufnr, predicate)
-<
- *vim.treesitter.query.list_predicates()*
-vim.treesitter.query.list_predicates()
-
-This lists the currently available predicates to use in queries.
-
- *vim.treesitter.query.add_directive()*
-vim.treesitter.query.add_directive({name}, {handler})
-
-This adds a directive with the name {name} to be used in queries.
-{handler} should be a function whose signature will be : >
- handler(match, pattern, bufnr, predicate, metadata)
-Handlers can set match level data by setting directly on the metadata object `metadata.key = value`
-Handlers can set node level data by using the capture id on the metadata table
-`metadata[capture_id].key = value`
-
Treesitter syntax highlighting (WIP) *lua-treesitter-highlight*
NOTE: This is a partially implemented feature, and not usable as a default
@@ -364,5 +283,434 @@ identical identifiers, highlighting both as |hl-WarningMsg|: >
((binary_expression left: (identifier) @WarningMsg.left right: (identifier) @WarningMsg.right)
(eq? @WarningMsg.left @WarningMsg.right))
+<
+
+==============================================================================
+Lua module: vim.treesitter *lua-treesitter-core*
+
+get_parser({bufnr}, {lang}, {opts}) *get_parser()*
+ Gets the parser for this bufnr / ft combination.
+
+ If needed this will create the parser. Unconditionnally attach
+ the provided callback
+
+ Parameters: ~
+ {bufnr} The buffer the parser should be tied to
+ {lang} The filetype of this parser
+ {opts} Options object to pass to the created language
+ tree
+
+ Return: ~
+ The parser
+
+get_string_parser({str}, {lang}, {opts}) *get_string_parser()*
+ Gets a string parser
+
+ Parameters: ~
+ {str} The string to parse
+ {lang} The language of this string
+ {opts} Options to pass to the created language tree
+
+
+==============================================================================
+Lua module: vim.treesitter.language *treesitter-language*
+
+inspect_language({lang}) *inspect_language()*
+ Inspects the provided language.
+
+ Inspecting provides some useful informations on the language
+ like node names, ...
+
+ Parameters: ~
+ {lang} The language.
+
+require_language({lang}, {path}, {silent}) *require_language()*
+ Asserts that the provided language is installed, and
+ optionally provide a path for the parser
+
+ Parsers are searched in the `parser` runtime directory.
+
+ Parameters: ~
+ {lang} The language the parser should parse
+ {path} Optional path the parser is located at
+ {silent} Don't throw an error if language not found
+
+
+==============================================================================
+Lua module: vim.treesitter.query *treesitter-query*
+
+add_directive({name}, {handler}, {force}) *add_directive()*
+ Adds a new directive to be used in queries
+
+ Parameters: ~
+ {name} the name of the directive, without leading #
+ {handler} the handler function to be used signature will
+ be (match, pattern, bufnr, predicate)
+
+add_predicate({name}, {handler}, {force}) *add_predicate()*
+ Adds a new predicate to be used in queries
+
+ Parameters: ~
+ {name} the name of the predicate, without leading #
+ {handler} the handler function to be used signature will
+ be (match, pattern, bufnr, predicate)
+
+get_node_text({node}, {source}) *get_node_text()*
+ Gets the text corresponding to a given node
+
+ Parameters: ~
+ {node} the node
+ {bsource} The buffer or string from which the node is
+ extracted
+
+get_query({lang}, {query_name}) *get_query()*
+ Returns the runtime query {query_name} for {lang}.
+
+ Parameters: ~
+ {lang} The language to use for the query
+ {query_name} The name of the query (i.e. "highlights")
+
+ Return: ~
+ The corresponding query, parsed.
+
+ *get_query_files()*
+get_query_files({lang}, {query_name}, {is_included})
+ Gets the list of files used to make up a query
+
+ Parameters: ~
+ {lang} The language
+ {query_name} The name of the query to load
+ {is_included} Internal parameter, most of the time left
+ as `nil`
+
+list_predicates() *list_predicates()*
+ TODO: Documentation
+
+parse_query({lang}, {query}) *parse_query()*
+ Parse {query} as a string. (If the query is in a file, the
+ caller should read the contents into a string before calling).
+
+ Returns a `Query` (see |lua-treesitter-query|) object which
+ can be used to search nodes in the syntax tree for the
+ patterns defined in {query} using `iter_*` methods below.
+
+ Exposes `info` and `captures` with additional information about the {query}.
+ • `captures` contains the list of unique capture names defined
+ in {query}. - `info.captures` also points to `captures` .
+ • `info.patterns` contains information about predicates.
+
+ Parameters: ~
+ {lang} The language
+ {query} A string containing the query (s-expr syntax)
+
+ Return: ~
+ The query
+
+ *Query:iter_captures()*
+Query:iter_captures({self}, {node}, {source}, {start}, {stop})
+ Iterate over all captures from all matches inside {node}
+
+ {source} is needed if the query contains predicates, then the
+ caller must ensure to use a freshly parsed tree consistent
+ with the current text of the buffer (if relevent). {start_row}
+ and {end_row} can be used to limit matches inside a row range
+ (this is typically used with root node as the node, i e to get
+ syntax highlight matches in the current viewport). When
+ omitted the start and end row values are used from the given
+ node.
+
+ The iterator returns three values, a numeric id identifying
+ the capture, the captured node, and metadata from any
+ directives processing the match. The following example shows
+ how to get captures by name:
+>
+
+ for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
+ local name = query.captures[id] -- name of the capture in the query
+ -- typically useful info about the node:
+ local type = node:type() -- type of the captured node
+ local row1, col1, row2, col2 = node:range() -- range of the capture
+ ... use the info here ...
+ end
+<
+
+ Parameters: ~
+ {node} The node under which the search will occur
+ {source} The source buffer or string to exctract text
+ from
+ {start} The starting line of the search
+ {stop} The stopping line of the search (end-exclusive)
+ {self}
+
+ Return: ~
+ The matching capture id
+ The captured node
+
+ *Query:iter_matches()*
+Query:iter_matches({self}, {node}, {source}, {start}, {stop})
+ Iterates the matches of self on a given range.
+
+ Iterate over all matches within a node. The arguments are the
+ same as for |query:iter_captures()| but the iterated values
+ are different: an (1-based) index of the pattern in the query,
+ a table mapping capture indices to nodes, and metadata from
+ any directives processing the match. If the query has more
+ than one pattern the capture table might be sparse, and e.g.
+ `pairs()` method should be used over `ipairs` . Here an
+ example iterating over all captures in every match:
+>
+
+ for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
+ for id, node in pairs(match) do
+ local name = query.captures[id]
+ -- `node` was captured by the `name` capture in the match
+
+ local node_data = metadata[id] -- Node level metadata
+
+ ... use the info here ...
+ end
+ end
+<
+
+ Parameters: ~
+ {node} The node under which the search will occur
+ {source} The source buffer or string to search
+ {start} The starting line of the search
+ {stop} The stopping line of the search (end-exclusive)
+ {self}
+
+ Return: ~
+ The matching pattern id
+ The matching match
+
+set_query({lang}, {query_name}, {text}) *set_query()*
+ Sets the runtime query {query_name} for {lang}
+
+ This allows users to override any runtime files and/or
+ configuration set by plugins.
+
+ Parameters: ~
+ {lang} string: The language to use for the query
+ {query_name} string: The name of the query (i.e.
+ "highlights")
+ {text} string: The query text (unparsed).
+
+
+==============================================================================
+Lua module: vim.treesitter.highlighter *treesitter-highlighter*
+
+new({tree}, {opts}) *highlighter.new()*
+ Creates a new highlighter using
+
+ Parameters: ~
+ {tree} The language tree to use for highlighting
+ {opts} Table used to configure the highlighter
+ • queries: Table to overwrite queries used by the
+ highlighter
+
+TSHighlighter:destroy({self}) *TSHighlighter:destroy()*
+ Removes all internal references to the highlighter
+
+ Parameters: ~
+ {self}
+
+TSHighlighter:get_query({self}, {lang}) *TSHighlighter:get_query()*
+ Gets the query used for
+
+ Parameters: ~
+ {lang} A language used by the highlighter.
+ {self}
+
+
+==============================================================================
+Lua module: vim.treesitter.languagetree *treesitter-languagetree*
+
+LanguageTree:add_child({self}, {lang}) *LanguageTree:add_child()*
+ Adds a child language to this tree.
+
+ If the language already exists as a child, it will first be
+ removed.
+
+ Parameters: ~
+ {lang} The language to add.
+ {self}
+
+LanguageTree:children({self}) *LanguageTree:children()*
+ Returns a map of language to child tree.
+
+ Parameters: ~
+ {self}
+
+LanguageTree:contains({self}, {range}) *LanguageTree:contains()*
+ Determines wether This goes down the tree to recursively check childs.
+
+ Parameters: ~
+ {range} is contained in this language tree
+
+ Parameters: ~
+ {range} A range, that is a `{ start_line, start_col,
+ end_line, end_col }` table.
+ {self}
+
+LanguageTree:destroy({self}) *LanguageTree:destroy()*
+ Destroys this language tree and all its children.
+
+ Any cleanup logic should be performed here. Note, this DOES
+ NOT remove this tree from a parent. `remove_child` must be called on the parent to remove it.
+
+ Parameters: ~
+ {self}
+
+ *LanguageTree:for_each_child()*
+LanguageTree:for_each_child({self}, {fn}, {include_self})
+ Invokes the callback for each LanguageTree and it's children
+ recursively
+
+ Parameters: ~
+ {fn} The function to invoke. This is invoked
+ with arguments (tree: LanguageTree, lang:
+ string)
+ {include_self} Whether to include the invoking tree in
+ the results.
+ {self}
+
+LanguageTree:for_each_tree({self}, {fn}) *LanguageTree:for_each_tree()*
+ Invokes the callback for each treesitter trees recursively.
+
+ Note, this includes the invoking language tree's trees as
+ well.
+
+ Parameters: ~
+ {fn} The callback to invoke. The callback is invoked
+ with arguments (tree: TSTree, languageTree:
+ LanguageTree)
+ {self}
+
+LanguageTree:included_regions({self}) *LanguageTree:included_regions()*
+ Gets the set of included regions
+
+ Parameters: ~
+ {self}
+
+LanguageTree:invalidate({self}, {reload}) *LanguageTree:invalidate()*
+ Invalidates this parser and all its children
+
+ Parameters: ~
+ {self}
+
+LanguageTree:is_valid({self}) *LanguageTree:is_valid()*
+ Determines whether this tree is valid. If the tree is invalid, `parse()` must be called to get the an updated tree.
+
+ Parameters: ~
+ {self}
+
+LanguageTree:lang({self}) *LanguageTree:lang()*
+ Gets the language of this tree node.
+
+ Parameters: ~
+ {self}
+
+ *LanguageTree:language_for_range()*
+LanguageTree:language_for_range({self}, {range})
+ Gets the appropriate language that contains
+
+ Parameters: ~
+ {range} A text range, see |LanguageTree:contains|
+ {self}
+
+LanguageTree:parse({self}) *LanguageTree:parse()*
+ Parses all defined regions using a treesitter parser for the
+ language this tree represents. This will run the injection
+ query for this language to determine if any child languages
+ should be created.
+
+ Parameters: ~
+ {self}
+
+LanguageTree:register_cbs({self}, {cbs}) *LanguageTree:register_cbs()*
+ Registers callbacks for the parser
+
+ Parameters: ~
+ {cbs} An `nvim_buf_attach` -like table argument with the following keys : `on_bytes` : see `nvim_buf_attach` , but this will be called after the parsers callback. `on_changedtree` : a callback that will be called every time the
+ tree has syntactical changes. it will only be
+ passed one argument, that is a table of the ranges
+ (as node ranges) that changed. `on_child_added` : emitted when a child is added to the tree. `on_child_removed` : emitted when a child is removed from the tree.
+ {self}
+
+LanguageTree:remove_child({self}, {lang}) *LanguageTree:remove_child()*
+ Removes a child language from this tree.
+
+ Parameters: ~
+ {lang} The language to remove.
+ {self}
+
+ *LanguageTree:set_included_regions()*
+LanguageTree:set_included_regions({self}, {regions})
+ Sets the included regions that should be parsed by this
+ parser. A region is a set of nodes and/or ranges that will be
+ parsed in the same context.
+
+ For example, `{ { node1 }, { node2} }` is two separate
+ regions. This will be parsed by the parser in two different
+ contexts... thus resulting in two separate trees.
+
+ `{ { node1, node2 } }` is a single region consisting of two
+ nodes. This will be parsed by the parser in a single
+ context... thus resulting in a single tree.
+
+ This allows for embedded languages to be parsed together
+ across different nodes, which is useful for templating
+ languages like ERB and EJS.
+
+ Note, this call invalidates the tree and requires it to be
+ parsed again.
+
+ Parameters: ~
+ {regions} A list of regions this tree should manage and
+ parse.
+ {self}
+
+LanguageTree:source({self}) *LanguageTree:source()*
+ Returns the source content of the language tree (bufnr or
+ string).
+
+ Parameters: ~
+ {self}
+
+LanguageTree:trees({self}) *LanguageTree:trees()*
+ Returns all trees this language tree contains. Does not
+ include child languages.
+
+ Parameters: ~
+ {self}
+
+new({source}, {lang}, {opts}) *languagetree.new()*
+ Represents a single treesitter parser for a language. The
+ language can contain child languages with in its range, hence
+ the tree.
+
+ Parameters: ~
+ {source} Can be a bufnr or a string of text to
+ parse
+ {lang} The language this tree represents
+ {opts} Options table
+ {opts.injections} A table of language to injection query
+ strings. This is useful for overriding
+ the built-in runtime file searching for
+ the injection language query per
+ language.
+
+
+==============================================================================
+Lua module: vim.treesitter.health *treesitter-health*
+
+check_health() *check_health()*
+ TODO: Documentation
+
+list_parsers() *list_parsers()*
+ Lists the parsers currently installed
+
+ Return: ~
+ A list of parsers
vim:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 21f5dcc815..41948f577e 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -978,6 +978,7 @@ Tags: *tag-functions*
settagstack() modify the tag stack of a window
Prompt Buffer: *promptbuffer-functions*
+ prompt_getprompt() get the effective prompt text for a buffer
prompt_setcallback() set prompt callback for a buffer
prompt_setinterrupt() set interrupt callback for a buffer
prompt_setprompt() set the prompt text for a buffer
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index b7157a14e7..724a96cb01 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -413,6 +413,10 @@ endif
" Lynx config files
au BufNewFile,BufRead lynx.cfg setf lynx
+" Modula-3 configuration language (must be before *.cfg and *makefile)
+au BufNewFile,BufRead *.quake,cm3.cfg setf m3quake
+au BufNewFile,BufRead m3makefile,m3overrides setf m3build
+
" Quake
au BufNewFile,BufRead *baseq[2-3]/*.cfg,*id1/*.cfg setf quake
au BufNewFile,BufRead *quake[1-3]/*.cfg setf quake
@@ -592,7 +596,7 @@ au BufNewFile,BufRead *.fan,*.fwt setf fan
au BufNewFile,BufRead *.factor setf factor
" Fennel
-autocmd BufRead,BufNewFile *.fnl setf fennel
+autocmd BufRead,BufNewFile *.fnl setf fennel
" Fetchmail RC file
au BufNewFile,BufRead .fetchmailrc setf fetchmail
@@ -636,7 +640,7 @@ au BufNewFile,BufRead *.mo,*.gdmo setf gdmo
au BufNewFile,BufRead *.ged,lltxxxxx.txt setf gedcom
" Gift (Moodle)
-autocmd BufRead,BufNewFile *.gift setf gift
+autocmd BufRead,BufNewFile *.gift setf gift
" Git
au BufNewFile,BufRead COMMIT_EDITMSG,MERGE_MSG,TAG_EDITMSG setf gitcommit
@@ -707,7 +711,7 @@ au BufNewFile,BufRead .gtkrc,gtkrc setf gtkrc
au BufNewFile,BufRead *.haml setf haml
" Hamster Classic | Playground files
-au BufNewFile,BufRead *.hsm setf hamster
+au BufNewFile,BufRead *.hsm setf hamster
" Haskell
au BufNewFile,BufRead *.hs,*.hsc,*.hs-boot setf haskell
@@ -1041,10 +1045,10 @@ au BufNewFile,BufRead *.mod
\ setf modsim3 |
\ endif
-" Modula 2 (.md removed in favor of Markdown)
+" Modula-2 (.md removed in favor of Markdown)
au BufNewFile,BufRead *.m2,*.DEF,*.MOD,*.mi setf modula2
-" Modula 3 (.m3, .i3, .mg, .ig)
+" Modula-3 (.m3, .i3, .mg, .ig)
au BufNewFile,BufRead *.[mi][3g] setf modula3
" Monk
@@ -1334,12 +1338,15 @@ au BufNewFile,BufRead *.pdb setf prolog
" Promela
au BufNewFile,BufRead *.pml setf promela
+" Property Specification Language (PSL)
+au BufNewFile,BufRead *.psl setf psl
+
" Google protocol buffers
au BufNewFile,BufRead *.proto setf proto
au BufNewFile,BufRead *.pbtxt setf pbtxt
" Poke
-au BufNewFile,BufRead *.pk setf poke
+au BufNewFile,BufRead *.pk setf poke
" Protocols
au BufNewFile,BufRead */etc/protocols setf protocols
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 563ffc479e..93ec9ed624 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -232,6 +232,12 @@ local function validate_client_config(config)
flags = { config.flags, "t", true };
get_language_id = { config.get_language_id, "f", true };
}
+ assert(
+ (not config.flags
+ or not config.flags.debounce_text_changes
+ or type(config.flags.debounce_text_changes) == 'number'),
+ "flags.debounce_text_changes must be nil or a number with the debounce time in milliseconds"
+ )
local cmd, cmd_args = lsp._cmd_parts(config.cmd)
local offset_encoding = valid_encodings.UTF16
@@ -260,21 +266,171 @@ local function buf_get_full_text(bufnr)
end
--@private
+--- Memoizes a function. On first run, the function return value is saved and
+--- immediately returned on subsequent runs. If the function returns a multival,
+--- only the first returned value will be memoized and returned. The function will only be run once,
+--- even if it has side-effects.
+---
+--@param fn (function) Function to run
+--@returns (function) Memoized function
+local function once(fn)
+ local value
+ local ran = false
+ return function(...)
+ if not ran then
+ value = fn(...)
+ ran = true
+ end
+ return value
+ end
+end
+
+
+local changetracking = {}
+do
+ --- client_id → state
+ ---
+ --- state
+ --- pending_change?: function that the timer starts to trigger didChange
+ --- pending_changes: list of tables with the pending changesets; for incremental_sync only
+ --- use_incremental_sync: bool
+ --- buffers?: table (bufnr → lines); for incremental sync only
+ --- timer?: uv_timer
+ local state_by_client = {}
+
+ function changetracking.init(client, bufnr)
+ local state = state_by_client[client.id]
+ if not state then
+ state = {
+ pending_changes = {};
+ use_incremental_sync = (
+ if_nil(client.config.flags.allow_incremental_sync, true)
+ and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental
+ );
+ }
+ state_by_client[client.id] = state
+ end
+ if not state.use_incremental_sync then
+ return
+ end
+ if not state.buffers then
+ state.buffers = {}
+ end
+ state.buffers[bufnr] = nvim_buf_get_lines(bufnr, 0, -1, true)
+ end
+
+ function changetracking.reset_buf(client, bufnr)
+ local state = state_by_client[client.id]
+ if state then
+ changetracking._reset_timer(state)
+ if state.buffers then
+ state.buffers[bufnr] = nil
+ end
+ end
+ end
+
+ function changetracking.reset(client_id)
+ local state = state_by_client[client_id]
+ if state then
+ state_by_client[client_id] = nil
+ changetracking._reset_timer(state)
+ end
+ end
+
+ function changetracking.prepare(bufnr, firstline, new_lastline, changedtick)
+ local incremental_changes = function(client)
+ local cached_buffers = state_by_client[client.id].buffers
+ local lines = nvim_buf_get_lines(bufnr, 0, -1, true)
+ local startline = math.min(firstline + 1, math.min(#cached_buffers[bufnr], #lines))
+ local endline = math.min(-(#lines - new_lastline), -1)
+ local incremental_change = vim.lsp.util.compute_diff(
+ cached_buffers[bufnr], lines, startline, endline, client.offset_encoding or 'utf-16')
+ cached_buffers[bufnr] = lines
+ return incremental_change
+ end
+ local full_changes = once(function()
+ return {
+ text = buf_get_full_text(bufnr);
+ };
+ end)
+ local uri = vim.uri_from_bufnr(bufnr)
+ return function(client)
+ if client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.None then
+ return
+ end
+ local state = state_by_client[client.id]
+ local debounce = client.config.flags.debounce_text_changes
+ if not debounce then
+ local changes = state.use_incremental_sync and incremental_changes(client) or full_changes()
+ client.notify("textDocument/didChange", {
+ textDocument = {
+ uri = uri;
+ version = changedtick;
+ };
+ contentChanges = { changes, }
+ })
+ return
+ end
+ changetracking._reset_timer(state)
+ if state.use_incremental_sync then
+ -- This must be done immediately and cannot be delayed
+ -- The contents would further change and startline/endline may no longer fit
+ table.insert(state.pending_changes, incremental_changes(client))
+ end
+ state.pending_change = function()
+ state.pending_change = nil
+ if client.is_stopped() then
+ return
+ end
+ local contentChanges
+ if state.use_incremental_sync then
+ contentChanges = state.pending_changes
+ state.pending_changes = {}
+ else
+ contentChanges = { full_changes(), }
+ end
+ client.notify("textDocument/didChange", {
+ textDocument = {
+ uri = uri;
+ version = changedtick;
+ };
+ contentChanges = contentChanges
+ })
+ end
+ state.timer = vim.loop.new_timer()
+ -- Must use schedule_wrap because `full_changes()` calls nvim_buf_get_lines
+ state.timer:start(debounce, 0, vim.schedule_wrap(state.pending_change))
+ end
+ end
+
+ function changetracking._reset_timer(state)
+ if state.timer then
+ state.timer:stop()
+ state.timer:close()
+ state.timer = nil
+ end
+ end
+
+ --- Flushes any outstanding change notification.
+ function changetracking.flush(client)
+ local state = state_by_client[client.id]
+ if state then
+ changetracking._reset_timer(state)
+ if state.pending_change then
+ state.pending_change()
+ end
+ end
+ end
+end
+
+
+--@private
--- Default handler for the 'textDocument/didOpen' LSP notification.
---
--@param bufnr (Number) Number of the buffer, or 0 for current
--@param client Client object
local function text_document_did_open_handler(bufnr, client)
- local use_incremental_sync = (
- if_nil(client.config.flags.allow_incremental_sync, true)
- and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental
- )
- if use_incremental_sync then
- if not client._cached_buffers then
- client._cached_buffers = {}
- end
- client._cached_buffers[bufnr] = nvim_buf_get_lines(bufnr, 0, -1, true)
- end
+ changetracking.init(client, bufnr)
if not client.resolved_capabilities.text_document_open_close then
return
end
@@ -327,6 +483,13 @@ end
--- result. You can use this with `client.cancel_request(request_id)`
--- to cancel the request.
---
+--- - request_sync(method, params, timeout_ms, bufnr)
+--- Sends a request to the server and synchronously waits for the response.
+--- This is a wrapper around {client.request}
+--- Returns: { err=err, result=result }, a dictionary, where `err` and `result` come from
+--- the |lsp-handler|. On timeout, cancel or error, returns `(nil, err)` where `err` is a
+--- string describing the failure reason. If the request was unsuccessful returns `nil`.
+---
--- - notify(method, params)
--- Sends a notification to an LSP server.
--- Returns: a boolean to indicate if the notification was successful. If
@@ -469,6 +632,9 @@ end
--- server in the initialize request. Invalid/empty values will default to "off"
--@param flags: A table with flags for the client. The current (experimental) flags are:
--- - allow_incremental_sync (bool, default true): Allow using incremental sync for buffer edits
+--- - debounce_text_changes (number, default nil): Debounce didChange
+--- notifications to the server by the given number in milliseconds. No debounce
+--- occurs if nil
---
--@returns Client id. |vim.lsp.get_client_by_id()| Note: client may not be
--- fully initialized. Use `on_init` to do any actions once
@@ -563,6 +729,7 @@ function lsp.start_client(config)
uninitialized_clients[client_id] = nil
lsp.diagnostic.reset(client_id, all_buffer_active_clients)
+ changetracking.reset(client_id)
all_client_active_buffers[client_id] = nil
for _, client_ids in pairs(all_buffer_active_clients) do
client_ids[client_id] = nil
@@ -721,6 +888,9 @@ function lsp.start_client(config)
handler = resolve_handler(method)
or error(string.format("not found: %q request handler for client %q.", method, client.name))
end
+ -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
+ changetracking.flush(client)
+
local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr)
return rpc.request(method, params, function(err, result)
handler(err, method, result, client_id, bufnr)
@@ -728,6 +898,42 @@ function lsp.start_client(config)
end
--@private
+ --- Sends a request to the server and synchronously waits for the response.
+ ---
+ --- This is a wrapper around {client.request}
+ ---
+ --@param method (string) LSP method name.
+ --@param params (table) LSP request params.
+ --@param timeout_ms (number, optional, default=1000) Maximum time in
+ ---milliseconds to wait for a result.
+ --@param bufnr (number) Buffer handle (0 for current).
+ --@returns { err=err, result=result }, a dictionary, where `err` and `result` come from the |lsp-handler|.
+ ---On timeout, cancel or error, returns `(nil, err)` where `err` is a
+ ---string describing the failure reason. If the request was unsuccessful
+ ---returns `nil`.
+ --@see |vim.lsp.buf_request_sync()|
+ function client.request_sync(method, params, timeout_ms, bufnr)
+ local request_result = nil
+ local function _sync_handler(err, _, result)
+ request_result = { err = err, result = result }
+ end
+
+ local success, request_id = client.request(method, params, _sync_handler,
+ bufnr)
+ if not success then return nil end
+
+ local wait_result, reason = vim.wait(timeout_ms or 1000, function()
+ return request_result ~= nil
+ end, 10)
+
+ if not wait_result then
+ client.cancel_request(request_id)
+ return nil, wait_result_reason[reason]
+ end
+ return request_result
+ end
+
+ --@private
--- Sends a notification to an LSP server.
---
--@param method (string) LSP method name.
@@ -753,7 +959,7 @@ function lsp.start_client(config)
-- Track this so that we can escalate automatically if we've alredy tried a
-- graceful shutdown
- local tried_graceful_shutdown = false
+ local graceful_shutdown_failed = false
--@private
--- Stops a client, optionally with force.
---
@@ -765,6 +971,7 @@ function lsp.start_client(config)
function client.stop(force)
lsp.diagnostic.reset(client_id, all_buffer_active_clients)
+ changetracking.reset(client_id)
all_client_active_buffers[client_id] = nil
for _, client_ids in pairs(all_buffer_active_clients) do
client_ids[client_id] = nil
@@ -774,11 +981,10 @@ function lsp.start_client(config)
if handle:is_closing() then
return
end
- if force or (not client.initialized) or tried_graceful_shutdown then
+ if force or (not client.initialized) or graceful_shutdown_failed then
handle:kill(15)
return
end
- tried_graceful_shutdown = true
-- Sending a signal after a process has exited is acceptable.
rpc.request('shutdown', nil, function(err, _)
if err == nil then
@@ -786,6 +992,7 @@ function lsp.start_client(config)
else
-- If there was an error in the shutdown request, then term to be safe.
handle:kill(15)
+ graceful_shutdown_failed = true
end
end)
end
@@ -816,20 +1023,6 @@ function lsp.start_client(config)
end
--@private
---- Memoizes a function. On first run, the function return value is saved and
---- immediately returned on subsequent runs.
----
---@param fn (function) Function to run
---@returns (function) Memoized function
-local function once(fn)
- local value
- return function(...)
- if not value then value = fn(...) end
- return value
- end
-end
-
---@private
--@fn text_document_did_change_handler(_, bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size)
--- Notify all attached clients that a buffer has changed.
local text_document_did_change_handler
@@ -848,45 +1041,9 @@ do
if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
return
end
-
util.buf_versions[bufnr] = changedtick
-
- local incremental_changes = function(client)
- local lines = nvim_buf_get_lines(bufnr, 0, -1, true)
- local startline = math.min(firstline + 1, math.min(#client._cached_buffers[bufnr], #lines))
- local endline = math.min(-(#lines - new_lastline), -1)
- local incremental_change = vim.lsp.util.compute_diff(
- client._cached_buffers[bufnr], lines, startline, endline, client.offset_encoding or "utf-16")
- client._cached_buffers[bufnr] = lines
- return incremental_change
- end
-
- local full_changes = once(function()
- return {
- text = buf_get_full_text(bufnr);
- };
- end)
-
- local uri = vim.uri_from_bufnr(bufnr)
- for_each_buffer_client(bufnr, function(client)
- local allow_incremental_sync = if_nil(client.config.flags.allow_incremental_sync, true)
- local text_document_did_change = client.resolved_capabilities.text_document_did_change
- local changes
- if text_document_did_change == protocol.TextDocumentSyncKind.None then
- return
- elseif not allow_incremental_sync or text_document_did_change == protocol.TextDocumentSyncKind.Full then
- changes = full_changes(client)
- elseif text_document_did_change == protocol.TextDocumentSyncKind.Incremental then
- changes = incremental_changes(client)
- end
- client.notify("textDocument/didChange", {
- textDocument = {
- uri = uri;
- version = changedtick;
- };
- contentChanges = { changes; }
- })
- end)
+ local compute_change_and_notify = changetracking.prepare(bufnr, firstline, new_lastline, changedtick)
+ for_each_buffer_client(bufnr, compute_change_and_notify)
end
end
@@ -956,9 +1113,7 @@ function lsp.buf_attach_client(bufnr, client_id)
if client.resolved_capabilities.text_document_open_close then
client.notify('textDocument/didClose', params)
end
- if client._cached_buffers then
- client._cached_buffers[bufnr] = nil
- end
+ changetracking.reset_buf(client, bufnr)
end)
util.buf_versions[bufnr] = nil
all_buffer_active_clients[bufnr] = nil
@@ -1133,42 +1288,77 @@ function lsp.buf_request(bufnr, method, params, handler)
return client_request_ids, _cancel_all_requests
end
---- Sends a request to a server and waits for the response.
+---Sends an async request for all active clients attached to the buffer.
+---Executes the callback on the combined result.
+---Parameters are the same as |vim.lsp.buf_request()| but the return result and callback are
+---different.
---
---- Calls |vim.lsp.buf_request()| but blocks Nvim while awaiting the result.
+--@param bufnr (number) Buffer handle, or 0 for current.
+--@param method (string) LSP method name
+--@param params (optional, table) Parameters to send to the server
+--@param callback (function) The callback to call when all requests are finished.
+-- Unlike `buf_request`, this will collect all the responses from each server instead of handling them.
+-- A map of client_id:request_result will be provided to the callback
+--
+--@returns (function) A function that will cancel all requests which is the same as the one returned from `buf_request`.
+function lsp.buf_request_all(bufnr, method, params, callback)
+ local request_results = {}
+ local result_count = 0
+ local expected_result_count = 0
+ local cancel, client_request_ids
+
+ local set_expected_result_count = once(function()
+ for _ in pairs(client_request_ids) do
+ expected_result_count = expected_result_count + 1
+ end
+ end)
+
+ local function _sync_handler(err, _, result, client_id)
+ request_results[client_id] = { error = err, result = result }
+ result_count = result_count + 1
+ set_expected_result_count()
+
+ if result_count >= expected_result_count then
+ callback(request_results)
+ end
+ end
+
+ client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _sync_handler)
+
+ return cancel
+end
+
+--- Sends a request to all server and waits for the response of all of them.
+---
+--- Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the result.
--- Parameters are the same as |vim.lsp.buf_request()| but the return result is
---- different. Wait maximum of {timeout_ms} (default 100) ms.
+--- different. Wait maximum of {timeout_ms} (default 1000) ms.
---
--@param bufnr (number) Buffer handle, or 0 for current.
--@param method (string) LSP method name
--@param params (optional, table) Parameters to send to the server
---@param timeout_ms (optional, number, default=100) Maximum time in
+--@param timeout_ms (optional, number, default=1000) Maximum time in
--- milliseconds to wait for a result.
---
--@returns Map of client_id:request_result. On timeout, cancel or error,
--- returns `(nil, err)` where `err` is a string describing the failure
--- reason.
function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
- local request_results = {}
- local result_count = 0
- local function _sync_handler(err, _, result, client_id)
- request_results[client_id] = { error = err, result = result }
- result_count = result_count + 1
- end
- local client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _sync_handler)
- local expected_result_count = 0
- for _ in pairs(client_request_ids) do
- expected_result_count = expected_result_count + 1
- end
+ local request_results
+
+ local cancel = lsp.buf_request_all(bufnr, method, params, function(it)
+ request_results = it
+ end)
- local wait_result, reason = vim.wait(timeout_ms or 100, function()
- return result_count >= expected_result_count
+ local wait_result, reason = vim.wait(timeout_ms or 1000, function()
+ return request_results ~= nil
end, 10)
if not wait_result then
cancel()
return nil, wait_result_reason[reason]
end
+
return request_results
end
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 31116985e2..341a3e82fc 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -111,6 +111,39 @@ function M.completion(context)
return request('textDocument/completion', params)
end
+--@private
+--- If there is more than one client that supports the given method,
+--- asks the user to select one.
+--
+--@returns The client that the user selected or nil
+local function select_client(method)
+ local clients = vim.tbl_values(vim.lsp.buf_get_clients());
+ clients = vim.tbl_filter(function (client)
+ return client.supports_method(method)
+ end, clients)
+ -- better UX when choices are always in the same order (between restarts)
+ table.sort(clients, function (a, b) return a.name < b.name end)
+
+ if #clients > 1 then
+ local choices = {}
+ for k,v in ipairs(clients) do
+ table.insert(choices, string.format("%d %s", k, v.name))
+ end
+ local user_choice = vim.fn.confirm(
+ "Select a language server:",
+ table.concat(choices, "\n"),
+ 0,
+ "Question"
+ )
+ if user_choice == 0 then return nil end
+ return clients[user_choice]
+ elseif #clients < 1 then
+ return nil
+ else
+ return clients[1]
+ end
+end
+
--- Formats the current buffer.
---
--@param options (optional, table) Can be used to specify FormattingOptions.
@@ -119,8 +152,11 @@ end
--
--@see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting
function M.formatting(options)
+ local client = select_client("textDocument/formatting")
+ if client == nil then return end
+
local params = util.make_formatting_params(options)
- return request('textDocument/formatting', params)
+ return client.request("textDocument/formatting", params)
end
--- Performs |vim.lsp.buf.formatting()| synchronously.
@@ -134,14 +170,62 @@ end
---
--@param options Table with valid `FormattingOptions` entries
--@param timeout_ms (number) Request timeout
+--@see |vim.lsp.buf.formatting_seq_sync|
function M.formatting_sync(options, timeout_ms)
+ local client = select_client("textDocument/formatting")
+ if client == nil then return end
+
local params = util.make_formatting_params(options)
- local result = vim.lsp.buf_request_sync(0, "textDocument/formatting", params, timeout_ms)
- if not result or vim.tbl_isempty(result) then return end
- local _, formatting_result = next(result)
- result = formatting_result.result
- if not result then return end
- vim.lsp.util.apply_text_edits(result)
+ local result, err = client.request_sync("textDocument/formatting", params, timeout_ms)
+ if result and result.result then
+ util.apply_text_edits(result.result)
+ elseif err then
+ vim.notify("vim.lsp.buf.formatting_sync: " .. err, vim.log.levels.WARN)
+ end
+end
+
+--- Formats the current buffer by sequentially requesting formatting from attached clients.
+---
+--- Useful when multiple clients with formatting capability are attached.
+---
+--- Since it's synchronous, can be used for running on save, to make sure buffer is formatted
+--- prior to being saved. {timeout_ms} is passed on to the |vim.lsp.client| `request_sync` method.
+--- Example:
+--- <pre>
+--- vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]]
+--- </pre>
+---
+--@param options (optional, table) `FormattingOptions` entries
+--@param timeout_ms (optional, number) Request timeout
+--@param order (optional, table) List of client names. Formatting is requested from clients
+---in the following order: first all clients that are not in the `order` list, then
+---the remaining clients in the order as they occur in the `order` list.
+function M.formatting_seq_sync(options, timeout_ms, order)
+ local clients = vim.tbl_values(vim.lsp.buf_get_clients());
+
+ -- sort the clients according to `order`
+ for _, client_name in ipairs(order or {}) do
+ -- if the client exists, move to the end of the list
+ for i, client in ipairs(clients) do
+ if client.name == client_name then
+ table.insert(clients, table.remove(clients, i))
+ break
+ end
+ end
+ end
+
+ -- loop through the clients and make synchronous formatting requests
+ for _, client in ipairs(clients) do
+ if client.resolved_capabilities.document_formatting then
+ local params = util.make_formatting_params(options)
+ local result, err = client.request_sync("textDocument/formatting", params, timeout_ms)
+ if result and result.result then
+ util.apply_text_edits(result.result)
+ elseif err then
+ vim.notify(string.format("vim.lsp.buf.formatting_seq_sync: (%s) %s", client.name, err), vim.log.levels.WARN)
+ end
+ end
+ end
end
--- Formats a given range.
@@ -152,15 +236,12 @@ end
--@param end_pos ({number, number}, optional) mark-indexed position.
---Defaults to the end of the last visual selection.
function M.range_formatting(options, start_pos, end_pos)
- validate { options = {options, 't', true} }
- local sts = vim.bo.softtabstop;
- options = vim.tbl_extend('keep', options or {}, {
- tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop;
- insertSpaces = vim.bo.expandtab;
- })
+ local client = select_client("textDocument/rangeFormatting")
+ if client == nil then return end
+
local params = util.make_given_range_params(start_pos, end_pos)
- params.options = options
- return request('textDocument/rangeFormatting', params)
+ params.options = util.make_formatting_params(options).options
+ return client.request("textDocument/rangeFormatting", params)
end
--- Renames all references to the symbol under the cursor.
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index bd7ef9cfdc..6f2f846a3b 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -406,9 +406,7 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
line_diagnostics = filter_by_severity_limit(opts.severity_limit, line_diagnostics)
end
- if opts.severity_sort then
- table.sort(line_diagnostics, function(a, b) return a.severity < b.severity end)
- end
+ table.sort(line_diagnostics, function(a, b) return a.severity < b.severity end)
return line_diagnostics
end
@@ -997,6 +995,8 @@ end
--- - See |vim.lsp.diagnostic.set_signs()|
--- - update_in_insert: (default=false)
--- - Update diagnostics in InsertMode or wait until InsertLeave
+--- - severity_sort: (default=false)
+--- - Sort diagnostics (and thus signs and virtual text)
function M.on_publish_diagnostics(_, _, params, client_id, _, config)
local uri = params.uri
local bufnr = vim.uri_to_bufnr(uri)
@@ -1007,6 +1007,10 @@ function M.on_publish_diagnostics(_, _, params, client_id, _, config)
local diagnostics = params.diagnostics
+ if config and if_nil(config.severity_sort, false) then
+ table.sort(diagnostics, function(a, b) return a.severity > b.severity end)
+ end
+
-- Always save the diagnostics, even if the buf is not loaded.
-- Language servers may report compile or build errors via diagnostics
-- Users should be able to find these, even if they're in files which
@@ -1034,6 +1038,7 @@ function M.display(diagnostics, bufnr, client_id, config)
underline = true,
virtual_text = true,
update_in_insert = false,
+ severity_sort = false,
}, config)
-- TODO(tjdevries): Consider how we can make this a "standardized" kind of thing for |lsp-handlers|.
@@ -1116,7 +1121,6 @@ end
---@return table {popup_bufnr, win_id}
function M.show_line_diagnostics(opts, bufnr, line_nr, client_id)
opts = opts or {}
- opts.severity_sort = if_nil(opts.severity_sort, true)
local show_header = if_nil(opts.show_header, true)
@@ -1140,7 +1144,7 @@ function M.show_line_diagnostics(opts, bufnr, line_nr, client_id)
local message_lines = vim.split(diagnostic.message, '\n', true)
table.insert(lines, prefix..message_lines[1])
- table.insert(highlights, {#prefix + 1, hiname})
+ table.insert(highlights, {#prefix, hiname})
for j = 2, #message_lines do
table.insert(lines, message_lines[j])
table.insert(highlights, {0, hiname})
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 1aa8326514..0cabd1a0d4 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -518,7 +518,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
send_response(decoded.id, err, result)
end)
-- This works because we are expecting vim.NIL here
- elseif decoded.id and (decoded.result or decoded.error) then
+ elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then
-- Server Result
decoded.error = convert_NIL(decoded.error)
decoded.result = convert_NIL(decoded.result)
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index a070cb5306..ce8468aa8a 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -18,6 +18,40 @@ end
local M = {}
+local default_border = {
+ {"", "NormalFloat"},
+ {"", "NormalFloat"},
+ {"", "NormalFloat"},
+ {" ", "NormalFloat"},
+ {"", "NormalFloat"},
+ {"", "NormalFloat"},
+ {"", "NormalFloat"},
+ {" ", "NormalFloat"},
+}
+
+--@private
+-- Check the border given by opts or the default border for the additional
+-- size it adds to a float.
+--@returns size of border in height and width
+local function get_border_size(opts)
+ local border = opts and opts.border or default_border
+ local height = 0
+ local width = 0
+
+ if type(border) == 'string' then
+ -- 'single', 'double', etc.
+ height = 2
+ width = 2
+ else
+ height = height + vim.fn.strdisplaywidth(border[2][1]) -- top
+ height = height + vim.fn.strdisplaywidth(border[6][1]) -- bottom
+ width = width + vim.fn.strdisplaywidth(border[4][1]) -- right
+ width = width + vim.fn.strdisplaywidth(border[8][1]) -- left
+ end
+
+ return { height = height, width = width }
+end
+
--@private
local function split_lines(value)
return split(value, '\n', true)
@@ -436,6 +470,7 @@ function M.apply_text_document_edit(text_document_edit, index)
-- `VersionedTextDocumentIdentifier`s version may be null
-- https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier
if should_check_version and (text_document.version
+ and text_document.version > 0
and M.buf_versions[bufnr]
and M.buf_versions[bufnr] > text_document.version) then
print("Buffer ", text_document.uri, " newer than edits.")
@@ -856,7 +891,7 @@ function M.make_floating_popup_options(width, height, opts)
else
anchor = anchor..'S'
height = math.min(lines_above, height)
- row = 0
+ row = -get_border_size(opts).height
end
if vim.fn.wincol() + width <= api.nvim_get_option('columns') then
@@ -875,19 +910,27 @@ function M.make_floating_popup_options(width, height, opts)
row = row + (opts.offset_y or 0),
style = 'minimal',
width = width,
- border = opts.border or {
- {"", "NormalFloat"},
- {"", "NormalFloat"},
- {"", "NormalFloat"},
- {" ", "NormalFloat"},
- {"", "NormalFloat"},
- {"", "NormalFloat"},
- {"", "NormalFloat"},
- {" ", "NormalFloat"}
- },
+ border = opts.border or default_border,
}
end
+local function _should_add_to_tagstack(new_item)
+ local stack = vim.fn.gettagstack()
+
+ -- Check if we're at the bottom of the tagstack.
+ if stack.curidx <= 1 then return true end
+
+ local top_item = stack.items[stack.curidx-1]
+
+ -- Check if the item at the top of the tagstack is exactly the
+ -- same as the one we want to push.
+ if top_item.tagname ~= new_item.tagname then return true end
+ for i, v in ipairs(top_item.from) do
+ if v ~= new_item.from[i] then return true end
+ end
+ return false
+end
+
--- Jumps to a location.
---
--@param location (`Location`|`LocationLink`)
@@ -896,22 +939,36 @@ function M.jump_to_location(location)
-- location may be Location or LocationLink
local uri = location.uri or location.targetUri
if uri == nil then return end
- local bufnr = vim.uri_to_bufnr(uri)
- -- Save position in jumplist
- vim.cmd "normal! m'"
- -- Push a new item into tagstack
- local from = {vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0}
- local items = {{tagname=vim.fn.expand('<cword>'), from=from}}
- vim.fn.settagstack(vim.fn.win_getid(), {items=items}, 't')
+ local from_bufnr = vim.fn.bufnr('%')
+ local from = {from_bufnr, vim.fn.line('.'), vim.fn.col('.'), 0}
+ local item = {tagname=vim.fn.expand('<cword>'), from=from}
+
+ -- Save position in jumplist
+ vim.cmd("mark '")
--- Jump to new location (adjusting for UTF-16 encoding of characters)
+ local bufnr = vim.uri_to_bufnr(uri)
api.nvim_set_current_buf(bufnr)
api.nvim_buf_set_option(0, 'buflisted', true)
local range = location.range or location.targetSelectionRange
local row = range.start.line
local col = get_line_byte_from_position(0, range.start)
+ -- This prevents the tagstack to be filled with items that provide
+ -- no motion when CTRL-T is pressed because they're both the source
+ -- and the destination.
+ local motionless =
+ bufnr == from_bufnr and
+ row+1 == from[2] and col+1 == from[3]
+ if not motionless and _should_add_to_tagstack(item) then
+ local winid = vim.fn.win_getid()
+ local items = {item}
+ vim.fn.settagstack(winid, {items=items}, 't')
+ end
+
+ -- Jump to new location
api.nvim_win_set_cursor(0, {row + 1, col})
+
return true
end
@@ -1185,6 +1242,20 @@ function M._make_floating_popup_size(contents, opts)
width = math.max(line_widths[i], width)
end
end
+
+ local border_width = get_border_size(opts).width
+ local screen_width = api.nvim_win_get_width(0)
+ width = math.min(width, screen_width)
+
+ -- make sure borders are always inside the screen
+ if width + border_width > screen_width then
+ width = width - (width + border_width - screen_width)
+ end
+
+ if wrap_at and wrap_at > width then
+ wrap_at = width
+ end
+
if max_width then
width = math.min(width, max_width)
wrap_at = math.min(wrap_at or max_width, max_width)
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index f223c7b8c8..de997b2d86 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -25,12 +25,12 @@ setmetatable(M, {
})
--- Creates a new parser.
---
--- It is not recommended to use this, use vim.treesitter.get_parser() instead.
---
--- @param bufnr The buffer the parser will be tied to
--- @param lang The language of the parser
--- @param opts Options to pass to the language tree
+---
+--- It is not recommended to use this, use vim.treesitter.get_parser() instead.
+---
+--- @param bufnr The buffer the parser will be tied to
+--- @param lang The language of the parser
+--- @param opts Options to pass to the created language tree
function M._create_parser(bufnr, lang, opts)
language.require_language(lang)
if bufnr == 0 then
@@ -41,10 +41,12 @@ function M._create_parser(bufnr, lang, opts)
local self = LanguageTree.new(bufnr, lang, opts)
+ ---@private
local function bytes_cb(_, ...)
self:_on_bytes(...)
end
+ ---@private
local function detach_cb(_, ...)
if parsers[bufnr] == self then
parsers[bufnr] = nil
@@ -52,6 +54,7 @@ function M._create_parser(bufnr, lang, opts)
self:_on_detach(...)
end
+ ---@private
local function reload_cb(_, ...)
self:_on_reload(...)
end
@@ -64,15 +67,15 @@ function M._create_parser(bufnr, lang, opts)
end
--- Gets the parser for this bufnr / ft combination.
---
--- If needed this will create the parser.
--- Unconditionnally attach the provided callback
---
--- @param bufnr The buffer the parser should be tied to
--- @param ft The filetype of this parser
--- @param opts Options object to pass to the parser
---
--- @returns The parser
+---
+--- If needed this will create the parser.
+--- Unconditionnally attach the provided callback
+---
+--- @param bufnr The buffer the parser should be tied to
+--- @param lang The filetype of this parser
+--- @param opts Options object to pass to the created language tree
+---
+--- @returns The parser
function M.get_parser(bufnr, lang, opts)
opts = opts or {}
@@ -92,6 +95,11 @@ function M.get_parser(bufnr, lang, opts)
return parsers[bufnr]
end
+--- Gets a string parser
+---
+--- @param str The string to parse
+--- @param lang The language of this string
+--- @param opts Options to pass to the created language tree
function M.get_string_parser(str, lang, opts)
vim.validate {
str = { str, 'string' },
diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua
index dd0b11a6c7..e031ba1bd6 100644
--- a/runtime/lua/vim/treesitter/health.lua
+++ b/runtime/lua/vim/treesitter/health.lua
@@ -1,10 +1,14 @@
local M = {}
local ts = vim.treesitter
+--- Lists the parsers currently installed
+---
+---@return A list of parsers
function M.list_parsers()
return vim.api.nvim_get_runtime_file('parser/*', true)
end
+--- Performs a healthcheck for treesitter integration
function M.check_health()
local report_info = vim.fn['health#report_info']
local report_ok = vim.fn['health#report_ok']
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index fe7e1052c9..84b6a5f135 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -70,11 +70,13 @@ TSHighlighter.hl_map = {
["include"] = "Include",
}
+---@private
local function is_highlight_name(capture_name)
local firstc = string.sub(capture_name, 1, 1)
return firstc ~= string.lower(firstc)
end
+---@private
function TSHighlighterQuery.new(lang, query_string)
local self = setmetatable({}, { __index = TSHighlighterQuery })
@@ -99,10 +101,12 @@ function TSHighlighterQuery.new(lang, query_string)
return self
end
+---@private
function TSHighlighterQuery:query()
return self._query
end
+---@private
--- Get the hl from capture.
--- Returns a tuple { highlight_name: string, is_builtin: bool }
function TSHighlighterQuery:_get_hl_from_capture(capture)
@@ -116,6 +120,11 @@ function TSHighlighterQuery:_get_hl_from_capture(capture)
end
end
+--- Creates a new highlighter using @param tree
+---
+--- @param tree The language tree to use for highlighting
+--- @param opts Table used to configure the highlighter
+--- - queries: Table to overwrite queries used by the highlighter
function TSHighlighter.new(tree, opts)
local self = setmetatable({}, TSHighlighter)
@@ -165,12 +174,14 @@ function TSHighlighter.new(tree, opts)
return self
end
+--- Removes all internal references to the highlighter
function TSHighlighter:destroy()
if TSHighlighter.active[self.bufnr] then
TSHighlighter.active[self.bufnr] = nil
end
end
+---@private
function TSHighlighter:get_highlight_state(tstree)
if not self._highlight_states[tstree] then
self._highlight_states[tstree] = {
@@ -182,24 +193,31 @@ function TSHighlighter:get_highlight_state(tstree)
return self._highlight_states[tstree]
end
+---@private
function TSHighlighter:reset_highlight_state()
self._highlight_states = {}
end
+---@private
function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end)
a.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1)
end
+---@private
function TSHighlighter:on_detach()
self:destroy()
end
+---@private
function TSHighlighter:on_changedtree(changes)
for _, ch in ipairs(changes or {}) do
a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3]+1)
end
end
+--- Gets the query used for @param lang
+---
+--- @param lang A language used by the highlighter.
function TSHighlighter:get_query(lang)
if not self._queries[lang] then
self._queries[lang] = TSHighlighterQuery.new(lang)
@@ -208,6 +226,7 @@ function TSHighlighter:get_query(lang)
return self._queries[lang]
end
+---@private
local function on_line_impl(self, buf, line)
self.tree:for_each_tree(function(tstree, tree)
if not tstree then return end
@@ -251,6 +270,7 @@ local function on_line_impl(self, buf, line)
end, true)
end
+---@private
function TSHighlighter._on_line(_, _win, buf, line, _)
local self = TSHighlighter.active[buf]
if not self then return end
@@ -258,6 +278,7 @@ function TSHighlighter._on_line(_, _win, buf, line, _)
on_line_impl(self, buf, line)
end
+---@private
function TSHighlighter._on_buf(_, buf)
local self = TSHighlighter.active[buf]
if self then
@@ -265,6 +286,7 @@ function TSHighlighter._on_buf(_, buf)
end
end
+---@private
function TSHighlighter._on_win(_, _win, buf, _topline)
local self = TSHighlighter.active[buf]
if not self then
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index eed28e0e41..6dc37c7848 100644
--- a/runtime/lua/vim/treesitter/language.lua
+++ b/runtime/lua/vim/treesitter/language.lua
@@ -3,12 +3,12 @@ local a = vim.api
local M = {}
--- Asserts that the provided language is installed, and optionally provide a path for the parser
---
--- Parsers are searched in the `parser` runtime directory.
---
--- @param lang The language the parser should parse
--- @param path Optional path the parser is located at
--- @param silent Don't throw an error if language not found
+---
+--- Parsers are searched in the `parser` runtime directory.
+---
+--- @param lang The language the parser should parse
+--- @param path Optional path the parser is located at
+--- @param silent Don't throw an error if language not found
function M.require_language(lang, path, silent)
if vim._ts_has_language(lang) then
return true
@@ -37,10 +37,10 @@ function M.require_language(lang, path, silent)
end
--- Inspects the provided language.
---
--- Inspecting provides some useful informations on the language like node names, ...
---
--- @param lang The language.
+---
+--- Inspecting provides some useful informations on the language like node names, ...
+---
+--- @param lang The language.
function M.inspect_language(lang)
M.require_language(lang)
return vim._ts_inspect_language(lang)
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 4168c1e365..899d90e464 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -5,21 +5,26 @@ local language = require'vim.treesitter.language'
local LanguageTree = {}
LanguageTree.__index = LanguageTree
--- Represents a single treesitter parser for a language.
--- The language can contain child languages with in its range,
--- hence the tree.
---
--- @param source Can be a bufnr or a string of text to parse
--- @param lang The language this tree represents
--- @param opts Options table
--- @param opts.queries A table of language to injection query strings.
--- This is useful for overriding the built-in runtime file
--- searching for the injection language query per language.
+--- Represents a single treesitter parser for a language.
+--- The language can contain child languages with in its range,
+--- hence the tree.
+---
+--- @param source Can be a bufnr or a string of text to parse
+--- @param lang The language this tree represents
+--- @param opts Options table
+--- @param opts.injections A table of language to injection query strings.
+--- This is useful for overriding the built-in runtime file
+--- searching for the injection language query per language.
function LanguageTree.new(source, lang, opts)
language.require_language(lang)
opts = opts or {}
- local custom_queries = opts.queries or {}
+ if opts.queries then
+ a.nvim_err_writeln("'queries' is no longer supported. Use 'injections' now")
+ opts.injections = opts.queries
+ end
+
+ local injections = opts.injections or {}
local self = setmetatable({
_source = source,
_lang = lang,
@@ -27,8 +32,8 @@ function LanguageTree.new(source, lang, opts)
_regions = {},
_trees = {},
_opts = opts,
- _injection_query = custom_queries[lang]
- and query.parse_query(lang, custom_queries[lang])
+ _injection_query = injections[lang]
+ and query.parse_query(lang, injections[lang])
or query.get_query(lang, "injections"),
_valid = false,
_parser = vim._create_ts_parser(lang),
@@ -45,7 +50,7 @@ function LanguageTree.new(source, lang, opts)
return self
end
--- Invalidates this parser and all its children
+--- Invalidates this parser and all its children
function LanguageTree:invalidate(reload)
self._valid = false
@@ -59,38 +64,38 @@ function LanguageTree:invalidate(reload)
end
end
--- Returns all trees this language tree contains.
--- Does not include child languages.
+--- Returns all trees this language tree contains.
+--- Does not include child languages.
function LanguageTree:trees()
return self._trees
end
--- Gets the language of this tree layer.
+--- Gets the language of this tree node.
function LanguageTree:lang()
return self._lang
end
--- Determines whether this tree is valid.
--- If the tree is invalid, `parse()` must be called
--- to get the an updated tree.
+--- Determines whether this tree is valid.
+--- If the tree is invalid, `parse()` must be called
+--- to get the an updated tree.
function LanguageTree:is_valid()
return self._valid
end
--- Returns a map of language to child tree.
+--- Returns a map of language to child tree.
function LanguageTree:children()
return self._children
end
--- Returns the source content of the language tree (bufnr or string).
+--- Returns the source content of the language tree (bufnr or string).
function LanguageTree:source()
return self._source
end
--- Parses all defined regions using a treesitter parser
--- for the language this tree represents.
--- This will run the injection query for this language to
--- determine if any child languages should be created.
+--- Parses all defined regions using a treesitter parser
+--- for the language this tree represents.
+--- This will run the injection query for this language to
+--- determine if any child languages should be created.
function LanguageTree:parse()
if self._valid then
return self._trees
@@ -164,9 +169,10 @@ function LanguageTree:parse()
return self._trees, changes
end
--- Invokes the callback for each LanguageTree and it's children recursively
--- @param fn The function to invoke. This is invoked with arguments (tree: LanguageTree, lang: string)
--- @param include_self Whether to include the invoking tree in the results.
+--- Invokes the callback for each LanguageTree and it's children recursively
+---
+--- @param fn The function to invoke. This is invoked with arguments (tree: LanguageTree, lang: string)
+--- @param include_self Whether to include the invoking tree in the results.
function LanguageTree:for_each_child(fn, include_self)
if include_self then
fn(self, self._lang)
@@ -177,10 +183,12 @@ function LanguageTree:for_each_child(fn, include_self)
end
end
--- Invokes the callback for each treesitter trees recursively.
--- Note, this includes the invoking language tree's trees as well.
--- @param fn The callback to invoke. The callback is invoked with arguments
--- (tree: TSTree, languageTree: LanguageTree)
+--- Invokes the callback for each treesitter trees recursively.
+---
+--- Note, this includes the invoking language tree's trees as well.
+---
+--- @param fn The callback to invoke. The callback is invoked with arguments
+--- (tree: TSTree, languageTree: LanguageTree)
function LanguageTree:for_each_tree(fn)
for _, tree in ipairs(self._trees) do
fn(tree, self)
@@ -191,9 +199,11 @@ function LanguageTree:for_each_tree(fn)
end
end
--- Adds a child language to this tree.
--- If the language already exists as a child, it will first be removed.
--- @param lang The language to add.
+--- Adds a child language to this tree.
+---
+--- If the language already exists as a child, it will first be removed.
+---
+--- @param lang The language to add.
function LanguageTree:add_child(lang)
if self._children[lang] then
self:remove_child(lang)
@@ -207,8 +217,9 @@ function LanguageTree:add_child(lang)
return self._children[lang]
end
--- Removes a child language from this tree.
--- @param lang The language to remove.
+--- Removes a child language from this tree.
+---
+--- @param lang The language to remove.
function LanguageTree:remove_child(lang)
local child = self._children[lang]
@@ -220,10 +231,11 @@ function LanguageTree:remove_child(lang)
end
end
--- Destroys this language tree and all its children.
--- Any cleanup logic should be performed here.
--- Note, this DOES NOT remove this tree from a parent.
--- `remove_child` must be called on the parent to remove it.
+--- Destroys this language tree and all its children.
+---
+--- Any cleanup logic should be performed here.
+--- Note, this DOES NOT remove this tree from a parent.
+--- `remove_child` must be called on the parent to remove it.
function LanguageTree:destroy()
-- Cleanup here
for _, child in ipairs(self._children) do
@@ -231,23 +243,23 @@ function LanguageTree:destroy()
end
end
--- Sets the included regions that should be parsed by this parser.
--- A region is a set of nodes and/or ranges that will be parsed in the same context.
---
--- For example, `{ { node1 }, { node2} }` is two separate regions.
--- This will be parsed by the parser in two different contexts... thus resulting
--- in two separate trees.
---
--- `{ { node1, node2 } }` is a single region consisting of two nodes.
--- This will be parsed by the parser in a single context... thus resulting
--- in a single tree.
---
--- This allows for embedded languages to be parsed together across different
--- nodes, which is useful for templating languages like ERB and EJS.
---
--- Note, this call invalidates the tree and requires it to be parsed again.
---
--- @param regions A list of regions this tree should manage and parse.
+--- Sets the included regions that should be parsed by this parser.
+--- A region is a set of nodes and/or ranges that will be parsed in the same context.
+---
+--- For example, `{ { node1 }, { node2} }` is two separate regions.
+--- This will be parsed by the parser in two different contexts... thus resulting
+--- in two separate trees.
+---
+--- `{ { node1, node2 } }` is a single region consisting of two nodes.
+--- This will be parsed by the parser in a single context... thus resulting
+--- in a single tree.
+---
+--- This allows for embedded languages to be parsed together across different
+--- nodes, which is useful for templating languages like ERB and EJS.
+---
+--- Note, this call invalidates the tree and requires it to be parsed again.
+---
+--- @param regions A list of regions this tree should manage and parse.
function LanguageTree:set_included_regions(regions)
-- TODO(vigoux): I don't think string parsers are useful for now
if type(self._source) == "number" then
@@ -276,16 +288,18 @@ function LanguageTree:set_included_regions(regions)
self:invalidate()
end
--- Gets the set of included regions
+--- Gets the set of included regions
function LanguageTree:included_regions()
return self._regions
end
--- Gets language injection points by language.
--- This is where most of the injection processing occurs.
--- TODO: Allow for an offset predicate to tailor the injection range
--- instead of using the entire nodes range.
--- @private
+--- Gets language injection points by language.
+---
+--- This is where most of the injection processing occurs.
+---
+--- TODO: Allow for an offset predicate to tailor the injection range
+--- instead of using the entire nodes range.
+--- @private
function LanguageTree:_get_injections()
if not self._injection_query then return {} end
@@ -297,33 +311,50 @@ function LanguageTree:_get_injections()
for pattern, match, metadata in self._injection_query:iter_matches(root_node, self._source, start_line, end_line+1) do
local lang = nil
- local injection_node = nil
- local combined = false
+ local ranges = {}
+ local combined = metadata.combined
+
+ -- Directives can configure how injections are captured as well as actual node captures.
+ -- This allows more advanced processing for determining ranges and language resolution.
+ if metadata.content then
+ local content = metadata.content
+
+ -- Allow for captured nodes to be used
+ if type(content) == "number" then
+ content = {match[content]}
+ end
+
+ if content then
+ vim.list_extend(ranges, content)
+ end
+ end
+
+ if metadata.language then
+ lang = metadata.language
+ end
-- You can specify the content and language together
-- using a tag with the language, for example
-- @javascript
for id, node in pairs(match) do
- local data = metadata[id]
local name = self._injection_query.captures[id]
- local offset_range = data and data.offset
-- Lang should override any other language tag
- if name == "language" then
+ if name == "language" and not lang then
lang = query.get_node_text(node, self._source)
elseif name == "combined" then
combined = true
- elseif name == "content" then
- injection_node = offset_range or node
+ elseif name == "content" and #ranges == 0 then
+ table.insert(ranges, node)
-- Ignore any tags that start with "_"
-- Allows for other tags to be used in matches
elseif string.sub(name, 1, 1) ~= "_" then
- if lang == nil then
+ if not lang then
lang = name
end
- if not injection_node then
- injection_node = offset_range or node
+ if #ranges == 0 then
+ table.insert(ranges, node)
end
end
end
@@ -337,21 +368,21 @@ function LanguageTree:_get_injections()
injections[tree_index][lang] = {}
end
- -- Key by pattern so we can either combine each node to parse in the same
- -- context or treat each node independently.
+ -- Key this by pattern. If combined is set to true all captures of this pattern
+ -- will be parsed by treesitter as the same "source".
+ -- If combined is false, each "region" will be parsed as a single source.
if not injections[tree_index][lang][pattern] then
- injections[tree_index][lang][pattern] = { combined = combined, nodes = {} }
+ injections[tree_index][lang][pattern] = { combined = combined, regions = {} }
end
- table.insert(injections[tree_index][lang][pattern].nodes, injection_node)
+ table.insert(injections[tree_index][lang][pattern].regions, ranges)
end
end
local result = {}
-- Generate a map by lang of node lists.
- -- Each list is a set of ranges that should be parsed
- -- together.
+ -- Each list is a set of ranges that should be parsed together.
for _, lang_map in ipairs(injections) do
for lang, patterns in pairs(lang_map) do
if not result[lang] then
@@ -360,10 +391,10 @@ function LanguageTree:_get_injections()
for _, entry in pairs(patterns) do
if entry.combined then
- table.insert(result[lang], entry.nodes)
+ table.insert(result[lang], vim.tbl_flatten(entry.regions))
else
- for _, node in ipairs(entry.nodes) do
- table.insert(result[lang], {node})
+ for _, ranges in ipairs(entry.regions) do
+ table.insert(result[lang], ranges)
end
end
end
@@ -373,12 +404,14 @@ function LanguageTree:_get_injections()
return result
end
+---@private
function LanguageTree:_do_callback(cb_name, ...)
for _, cb in ipairs(self._callbacks[cb_name]) do
cb(...)
end
end
+---@private
function LanguageTree:_on_bytes(bufnr, changed_tick,
start_row, start_col, start_byte,
old_row, old_col, old_byte,
@@ -403,24 +436,26 @@ function LanguageTree:_on_bytes(bufnr, changed_tick,
new_row, new_col, new_byte)
end
+---@private
function LanguageTree:_on_reload()
self:invalidate(true)
end
+---@private
function LanguageTree:_on_detach(...)
self:invalidate(true)
self:_do_callback('detach', ...)
end
--- Registers callbacks for the parser
--- @param cbs An `nvim_buf_attach`-like table argument with the following keys :
--- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback.
--- `on_changedtree` : a callback that will be called every time the tree has syntactical changes.
--- it will only be passed one argument, that is a table of the ranges (as node ranges) that
--- changed.
--- `on_child_added` : emitted when a child is added to the tree.
--- `on_child_removed` : emitted when a child is removed from the tree.
+--- @param cbs An `nvim_buf_attach`-like table argument with the following keys :
+--- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback.
+--- `on_changedtree` : a callback that will be called every time the tree has syntactical changes.
+--- it will only be passed one argument, that is a table of the ranges (as node ranges) that
+--- changed.
+--- `on_child_added` : emitted when a child is added to the tree.
+--- `on_child_removed` : emitted when a child is removed from the tree.
function LanguageTree:register_cbs(cbs)
if not cbs then return end
@@ -445,6 +480,7 @@ function LanguageTree:register_cbs(cbs)
end
end
+---@private
local function tree_contains(tree, range)
local start_row, start_col, end_row, end_col = tree:root():range()
local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2])
@@ -457,6 +493,11 @@ local function tree_contains(tree, range)
return false
end
+--- Determines wether @param range is contained in this language tree
+---
+--- This goes down the tree to recursively check childs.
+---
+--- @param range A range, that is a `{ start_line, start_col, end_line, end_col }` table.
function LanguageTree:contains(range)
for _, tree in pairs(self._trees) do
if tree_contains(tree, range) then
@@ -467,6 +508,9 @@ function LanguageTree:contains(range)
return false
end
+--- Gets the appropriate language that contains @param range
+---
+--- @param range A text range, see |LanguageTree:contains|
function LanguageTree:language_for_range(range)
for _, child in pairs(self._children) do
if child:contains(range) then
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index f40e1d5294..db6d7e4dc0 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -8,6 +8,7 @@ Query.__index = Query
local M = {}
+---@private
local function dedupe_files(files)
local result = {}
local seen = {}
@@ -22,6 +23,7 @@ local function dedupe_files(files)
return result
end
+---@private
local function safe_read(filename, read_quantifier)
local file, err = io.open(filename, 'r')
if not file then
@@ -32,6 +34,11 @@ local function safe_read(filename, read_quantifier)
return content
end
+--- Gets the list of files used to make up a query
+---
+--- @param lang The language
+--- @param query_name The name of the query to load
+--- @param is_included Internal parameter, most of the time left as `nil`
function M.get_query_files(lang, query_name, is_included)
local query_path = string.format('queries/%s/%s.scm', lang, query_name)
local lang_files = dedupe_files(a.nvim_get_runtime_file(query_path, true))
@@ -79,6 +86,7 @@ function M.get_query_files(lang, query_name, is_included)
return query_files
end
+---@private
local function read_query_files(filenames)
local contents = {}
@@ -89,17 +97,6 @@ local function read_query_files(filenames)
return table.concat(contents, '')
end
-local match_metatable = {
- __index = function(tbl, key)
- rawset(tbl, key, {})
- return tbl[key]
- end
-}
-
-local function new_match_metadata()
- return setmetatable({}, match_metatable)
-end
-
--- The explicitly set queries from |vim.treesitter.query.set_query()|
local explicit_queries = setmetatable({}, {
__index = function(t, k)
@@ -114,19 +111,20 @@ local explicit_queries = setmetatable({}, {
---
--- This allows users to override any runtime files and/or configuration
--- set by plugins.
----@param lang string: The language to use for the query
----@param query_name string: The name of the query (i.e. "highlights")
----@param text string: The query text (unparsed).
+---
+--- @param lang string: The language to use for the query
+--- @param query_name string: The name of the query (i.e. "highlights")
+--- @param text string: The query text (unparsed).
function M.set_query(lang, query_name, text)
explicit_queries[lang][query_name] = M.parse_query(lang, text)
end
--- Returns the runtime query {query_name} for {lang}.
---
--- @param lang The language to use for the query
--- @param query_name The name of the query (i.e. "highlights")
---
--- @return The corresponding query, parsed.
+---
+--- @param lang The language to use for the query
+--- @param query_name The name of the query (i.e. "highlights")
+---
+--- @return The corresponding query, parsed.
function M.get_query(lang, query_name)
if explicit_queries[lang][query_name] then
return explicit_queries[lang][query_name]
@@ -140,12 +138,23 @@ function M.get_query(lang, query_name)
end
end
---- Parses a query.
---
--- @param language The language
--- @param query A string containing the query (s-expr syntax)
---
--- @returns The query
+--- Parse {query} as a string. (If the query is in a file, the caller
+--- should read the contents into a string before calling).
+---
+--- Returns a `Query` (see |lua-treesitter-query|) object which can be used to
+--- search nodes in the syntax tree for the patterns defined in {query}
+--- using `iter_*` methods below.
+---
+--- Exposes `info` and `captures` with additional information about the {query}.
+--- - `captures` contains the list of unique capture names defined in
+--- {query}.
+--- -` info.captures` also points to `captures`.
+--- - `info.patterns` contains information about predicates.
+---
+--- @param lang The language
+--- @param query A string containing the query (s-expr syntax)
+---
+--- @returns The query
function M.parse_query(lang, query)
language.require_language(lang)
local self = setmetatable({}, Query)
@@ -158,8 +167,9 @@ end
-- TODO(vigoux): support multiline nodes too
--- Gets the text corresponding to a given node
--- @param node the node
--- @param bufnr the buffer from which the node is extracted.
+---
+--- @param node the node
+--- @param bsource The buffer or string from which the node is extracted
function M.get_node_text(node, source)
local start_row, start_col, start_byte = node:start()
local end_row, end_col, end_byte = node:end_()
@@ -211,6 +221,7 @@ local predicate_handlers = {
["match?"] = (function()
local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true}
+ ---@private
local function check_magic(str)
if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then
return str
@@ -259,7 +270,7 @@ predicate_handlers["vim-match?"] = predicate_handlers["match?"]
-- Directives store metadata or perform side effects against a match.
-- Directives should always end with a `!`.
-- Directive handler receive the following arguments
--- (match, pattern, bufnr, predicate)
+-- (match, pattern, bufnr, predicate, metadata)
local directive_handlers = {
["set!"] = function(_, _, _, pred, metadata)
if #pred == 4 then
@@ -279,7 +290,6 @@ local directive_handlers = {
local start_col_offset = pred[4] or 0
local end_row_offset = pred[5] or 0
local end_col_offset = pred[6] or 0
- local key = pred[7] or "offset"
range[1] = range[1] + start_row_offset
range[2] = range[2] + start_col_offset
@@ -288,16 +298,16 @@ local directive_handlers = {
-- If this produces an invalid range, we just skip it.
if range[1] < range[3] or (range[1] == range[3] and range[2] <= range[4]) then
- metadata[pred[2]][key] = range
+ metadata.content = {range}
end
end
}
--- Adds a new predicate to be used in queries
---
--- @param name the name of the predicate, without leading #
--- @param handler the handler function to be used
--- signature will be (match, pattern, bufnr, predicate)
+---
+--- @param name the name of the predicate, without leading #
+--- @param handler the handler function to be used
+--- signature will be (match, pattern, bufnr, predicate)
function M.add_predicate(name, handler, force)
if predicate_handlers[name] and not force then
error(string.format("Overriding %s", name))
@@ -307,10 +317,10 @@ function M.add_predicate(name, handler, force)
end
--- Adds a new directive to be used in queries
---
--- @param name the name of the directive, without leading #
--- @param handler the handler function to be used
--- signature will be (match, pattern, bufnr, predicate)
+---
+--- @param name the name of the directive, without leading #
+--- @param handler the handler function to be used
+--- signature will be (match, pattern, bufnr, predicate)
function M.add_directive(name, handler, force)
if directive_handlers[name] and not force then
error(string.format("Overriding %s", name))
@@ -324,14 +334,17 @@ function M.list_predicates()
return vim.tbl_keys(predicate_handlers)
end
+---@private
local function xor(x, y)
return (x or y) and not (x and y)
end
+---@private
local function is_directive(name)
return string.sub(name, -1) == "!"
end
+---@private
function Query:match_preds(match, pattern, source)
local preds = self.info.patterns[pattern]
@@ -370,7 +383,7 @@ function Query:match_preds(match, pattern, source)
return true
end
---- Applies directives against a match and pattern.
+---@private
function Query:apply_directives(match, pattern, source, metadata)
local preds = self.info.patterns[pattern]
@@ -392,6 +405,7 @@ end
--- Returns the start and stop value if set else the node's range.
-- When the node's range is used, the stop is incremented by 1
-- to make the search inclusive.
+---@private
local function value_or_node_range(start, stop, node)
if start == nil and stop == nil then
local node_start, _, node_stop, _ = node:range()
@@ -401,15 +415,36 @@ local function value_or_node_range(start, stop, node)
return start, stop
end
---- Iterates of the captures of self on a given range.
---
--- @param node The node under which the search will occur
--- @param buffer The source buffer to search
--- @param start The starting line of the search
--- @param stop The stopping line of the search (end-exclusive)
---
--- @returns The matching capture id
--- @returns The captured node
+--- Iterate over all captures from all matches inside {node}
+---
+--- {source} is needed if the query contains predicates, then the caller
+--- must ensure to use a freshly parsed tree consistent with the current
+--- text of the buffer (if relevent). {start_row} and {end_row} can be used to limit
+--- matches inside a row range (this is typically used with root node
+--- as the node, i e to get syntax highlight matches in the current
+--- viewport). When omitted the start and end row values are used from the given node.
+---
+--- The iterator returns three values, a numeric id identifying the capture,
+--- the captured node, and metadata from any directives processing the match.
+--- The following example shows how to get captures by name:
+---
+--- <pre>
+--- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
+--- local name = query.captures[id] -- name of the capture in the query
+--- -- typically useful info about the node:
+--- local type = node:type() -- type of the captured node
+--- local row1, col1, row2, col2 = node:range() -- range of the capture
+--- ... use the info here ...
+--- end
+--- </pre>
+---
+--- @param node The node under which the search will occur
+--- @param source The source buffer or string to exctract text from
+--- @param start The starting line of the search
+--- @param stop The stopping line of the search (end-exclusive)
+---
+--- @returns The matching capture id
+--- @returns The captured node
function Query:iter_captures(node, source, start, stop)
if type(source) == "number" and source == 0 then
source = vim.api.nvim_get_current_buf()
@@ -418,9 +453,10 @@ function Query:iter_captures(node, source, start, stop)
start, stop = value_or_node_range(start, stop, node)
local raw_iter = node:_rawquery(self.query, true, start, stop)
+ ---@private
local function iter()
local capture, captured_node, match = raw_iter()
- local metadata = new_match_metadata()
+ local metadata = {}
if match ~= nil then
local active = self:match_preds(match, match.pattern, source)
@@ -437,14 +473,35 @@ function Query:iter_captures(node, source, start, stop)
end
--- Iterates the matches of self on a given range.
---
--- @param node The node under which the search will occur
--- @param buffer The source buffer to search
--- @param start The starting line of the search
--- @param stop The stopping line of the search (end-exclusive)
---
--- @returns The matching pattern id
--- @returns The matching match
+---
+--- Iterate over all matches within a node. The arguments are the same as
+--- for |query:iter_captures()| but the iterated values are different:
+--- an (1-based) index of the pattern in the query, a table mapping
+--- capture indices to nodes, and metadata from any directives processing the match.
+--- If the query has more than one pattern the capture table might be sparse,
+--- and e.g. `pairs()` method should be used over `ipairs`.
+--- Here an example iterating over all captures in every match:
+---
+--- <pre>
+--- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
+--- for id, node in pairs(match) do
+--- local name = query.captures[id]
+--- -- `node` was captured by the `name` capture in the match
+---
+--- local node_data = metadata[id] -- Node level metadata
+---
+--- ... use the info here ...
+--- end
+--- end
+--- </pre>
+---
+--- @param node The node under which the search will occur
+--- @param source The source buffer or string to search
+--- @param start The starting line of the search
+--- @param stop The stopping line of the search (end-exclusive)
+---
+--- @returns The matching pattern id
+--- @returns The matching match
function Query:iter_matches(node, source, start, stop)
if type(source) == "number" and source == 0 then
source = vim.api.nvim_get_current_buf()
@@ -455,7 +512,7 @@ function Query:iter_matches(node, source, start, stop)
local raw_iter = node:_rawquery(self.query, false, start, stop)
local function iter()
local pattern, match = raw_iter()
- local metadata = new_match_metadata()
+ local metadata = {}
if match ~= nil then
local active = self:match_preds(match, pattern, source)
diff --git a/runtime/menu.vim b/runtime/menu.vim
index cd56eb5583..78306a57b8 100644
--- a/runtime/menu.vim
+++ b/runtime/menu.vim
@@ -2,7 +2,7 @@
" You can also use this as a start for your own set of menus.
"
" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2019 Jan 27
+" Last Change: 2019 Dec 10
" Note that ":an" (short for ":anoremenu") is often used to make a menu work
" in all modes and avoid side effects from mappings defined by the user.
@@ -690,11 +690,11 @@ func! s:BMShow(...)
let g:bmenu_priority = a:1
endif
- " remove old menu, if exists; keep one entry to avoid a torn off menu to
- " disappear.
- silent! unmenu &Buffers
+ " Remove old menu, if exists; keep one entry to avoid a torn off menu to
+ " disappear. Use try/catch to avoid setting v:errmsg
+ try | unmenu &Buffers | catch | endtry
exe 'noremenu ' . g:bmenu_priority . ".1 &Buffers.Dummy l"
- silent! unmenu! &Buffers
+ try | unmenu! &Buffers | catch | endtry
" create new menu; set 'cpo' to include the <CR>
let cpo_save = &cpo