aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/highlight.lua37
-rw-r--r--runtime/lua/vim/lsp.lua15
-rw-r--r--runtime/lua/vim/lsp/buf.lua66
-rw-r--r--runtime/lua/vim/lsp/callbacks.lua27
-rw-r--r--runtime/lua/vim/lsp/protocol.lua7
-rw-r--r--runtime/lua/vim/lsp/rpc.lua6
-rw-r--r--runtime/lua/vim/lsp/util.lua167
-rw-r--r--runtime/lua/vim/shared.lua22
-rw-r--r--runtime/lua/vim/treesitter.lua38
-rw-r--r--runtime/lua/vim/tshighlighter.lua112
10 files changed, 348 insertions, 149 deletions
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index 69c3c8a4dc..ce0a3de520 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -23,24 +23,41 @@ function highlight.range(bufnr, ns, higroup, start, finish, rtype, inclusive)
end
+local yank_ns = api.nvim_create_namespace('hlyank')
--- Highlight the yanked region
---
--- use from init.vim via
---- au TextYankPost * lua require'vim.highlight'.on_yank()
+--- au TextYankPost * lua vim.highlight.on_yank()
--- customize highlight group and timeout via
---- au TextYankPost * lua require'vim.highlight'.on_yank("IncSearch", 500)
+--- au TextYankPost * lua vim.highlight.on_yank {higroup="IncSearch", timeout=150}
+--- customize conditions (here: do not highlight a visual selection) via
+--- au TextYankPost * lua vim.highlight.on_yank {on_visual=false}
---
--- @param higroup highlight group for yanked region
--- @param timeout time in ms before highlight is cleared
--- @param event event structure
-function highlight.on_yank(higroup, timeout, event)
- event = event or vim.v.event
+-- @param opts dictionary with options controlling the highlight:
+-- - higroup highlight group for yanked region (default "IncSearch")
+-- - timeout time in ms before highlight is cleared (default 150)
+-- - on_macro highlight when executing macro (default false)
+-- - on_visual highlight when yanking visual selection (default true)
+-- - event event structure (default vim.v.event)
+function highlight.on_yank(opts)
+ vim.validate {
+ opts = { opts,
+ function(t) if t == nil then return true else return type(t) == 'table' end end,
+ 'a table or nil to configure options (see `:h highlight.on_yank`)',
+ }}
+ opts = opts or {}
+ local event = opts.event or vim.v.event
+ local on_macro = opts.on_macro or false
+ local on_visual = (opts.on_visual ~= false)
+
+ if (not on_macro) and vim.fn.reg_executing() ~= '' then return end
if event.operator ~= 'y' or event.regtype == '' then return end
- higroup = higroup or "IncSearch"
- timeout = timeout or 500
+ if (not on_visual) and event.visual then return end
+
+ local higroup = opts.higroup or "IncSearch"
+ local timeout = opts.timeout or 150
local bufnr = api.nvim_get_current_buf()
- local yank_ns = api.nvim_create_namespace('')
api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1)
local pos1 = vim.fn.getpos("'[")
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 2fbc51481f..6fe1d15b7e 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -511,6 +511,7 @@ function lsp.start_client(config)
or (not client.resolved_capabilities.type_definition and method == 'textDocument/typeDefinition')
or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol')
or (not client.resolved_capabilities.workspace_symbol and method == 'textDocument/workspaceSymbol')
+ or (not client.resolved_capabilities.call_hierarchy and method == 'textDocument/prepareCallHierarchy')
then
callback(unsupported_method(method), method, nil, client_id, bufnr)
return
@@ -586,9 +587,7 @@ do
old_utf16_size)
local _ = log.debug() and log.debug("on_lines", bufnr, changedtick, firstline,
lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size, nvim_buf_get_lines(bufnr, firstline, new_lastline, true))
- if old_byte_size == 0 then
- return
- end
+
-- Don't do anything if there are no clients attached.
if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
return
@@ -879,11 +878,11 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
end
--- Send a notification to a server
--- @param bufnr [number] (optional): The number of the buffer
--- @param method [string]: Name of the request method
--- @param params [string]: Arguments to send to the server
---
--- @returns true if any client returns true; false otherwise
+--@param bufnr [number] (optional): The number of the buffer
+--@param method [string]: Name of the request method
+--@param params [string]: Arguments to send to the server
+---
+--@returns true if any client returns true; false otherwise
function lsp.buf_notify(bufnr, method, params)
validate {
bufnr = { bufnr, 'n', true };
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 7a819f3c3d..476bb3ba6f 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -23,6 +23,9 @@ local function request(method, params, callback)
return vim.lsp.buf_request(0, method, params, callback)
end
+--- Sends a notification through all clients associated with current buffer.
+--
+--@return `true` if server responds.
function M.server_ready()
return not not vim.lsp.buf_notify(0, "window/progress", {})
end
@@ -65,19 +68,22 @@ function M.completion(context)
end
function M.formatting(options)
- 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 params = {
- textDocument = { uri = vim.uri_from_bufnr(0) };
- options = options;
- }
+ local params = util.make_formatting_params(options)
return request('textDocument/formatting', params)
end
+--- Perform |vim.lsp.buf.formatting()| synchronously.
+---
+--- Useful for running on save, to make sure buffer is formatted prior to being
+--- saved. {timeout_ms} is passed on to |vim.lsp.buf_request_sync()|.
+function M.formatting_sync(options, timeout_ms)
+ local params = util.make_formatting_params(options)
+ local result = vim.lsp.buf_request_sync(0, "textDocument/formatting", params, timeout_ms)
+ if not result then return end
+ result = result[1].result
+ vim.lsp.util.apply_text_edits(result)
+end
+
function M.range_formatting(options, start_pos, end_pos)
validate {
options = {options, 't', true};
@@ -116,7 +122,7 @@ function M.rename(new_name)
-- TODO(ashkan) use prepareRename
-- * result: [`Range`](#range) \| `{ range: Range, placeholder: string }` \| `null` describing the range of the string to rename and optionally a placeholder text of the string content to be renamed. If `null` is returned then it is deemed that a 'textDocument/rename' request is not valid at the given position.
local params = util.make_position_params()
- new_name = new_name or npcall(vfn.input, "New Name: ")
+ new_name = new_name or npcall(vfn.input, "New Name: ", vfn.expand('<cword>'))
if not (new_name and #new_name > 0) then return end
params.newName = new_name
request('textDocument/rename', params)
@@ -137,6 +143,44 @@ function M.document_symbol()
request('textDocument/documentSymbol', params)
end
+local function pick_call_hierarchy_item(call_hierarchy_items)
+ if not call_hierarchy_items then return end
+ if #call_hierarchy_items == 1 then
+ return call_hierarchy_items[1]
+ end
+ local items = {}
+ for i, item in ipairs(call_hierarchy_items) do
+ local entry = item.detail or item.name
+ table.insert(items, string.format("%d. %s", i, entry))
+ end
+ local choice = vim.fn.inputlist(items)
+ if choice < 1 or choice > #items then
+ return
+ end
+ return choice
+end
+
+function M.incoming_calls()
+ local params = util.make_position_params()
+ request('textDocument/prepareCallHierarchy', params, function(_, _, result)
+ local call_hierarchy_item = pick_call_hierarchy_item(result)
+ vim.lsp.buf_request(0, 'callHierarchy/incomingCalls', { item = call_hierarchy_item })
+ end)
+end
+
+function M.outgoing_calls()
+ local params = util.make_position_params()
+ request('textDocument/prepareCallHierarchy', params, function(_, _, result)
+ local call_hierarchy_item = pick_call_hierarchy_item(result)
+ vim.lsp.buf_request(0, 'callHierarchy/outgoingCalls', { item = call_hierarchy_item })
+ end)
+end
+
+--- Lists all symbols in the current workspace in the quickfix window.
+---
+--- The list is filtered against the optional argument {query};
+--- if the argument is omitted from the call, the user is prompted to enter a string on the command line.
+--- An empty string means no filtering is done.
function M.workspace_symbol(query)
query = query or npcall(vfn.input, "Query: ")
local params = {query = query}
diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua
index 4b14f0132d..1ed58995d0 100644
--- a/runtime/lua/vim/lsp/callbacks.lua
+++ b/runtime/lua/vim/lsp/callbacks.lua
@@ -214,6 +214,33 @@ M['textDocument/documentHighlight'] = function(_, _, result, _)
util.buf_highlight_references(bufnr, result)
end
+-- direction is "from" for incoming calls and "to" for outgoing calls
+local make_call_hierarchy_callback = function(direction)
+ -- result is a CallHierarchy{Incoming,Outgoing}Call[]
+ return function(_, _, result)
+ if not result then return end
+ local items = {}
+ for _, call_hierarchy_call in pairs(result) do
+ local call_hierarchy_item = call_hierarchy_call[direction]
+ for _, range in pairs(call_hierarchy_call.fromRanges) do
+ table.insert(items, {
+ filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)),
+ text = call_hierarchy_item.name,
+ lnum = range.start.line + 1,
+ col = range.start.character + 1,
+ })
+ end
+ end
+ util.set_qflist(items)
+ api.nvim_command("copen")
+ api.nvim_command("wincmd p")
+ end
+end
+
+M['callHierarchy/incomingCalls'] = make_call_hierarchy_callback('from')
+
+M['callHierarchy/outgoingCalls'] = make_call_hierarchy_callback('to')
+
M['window/logMessage'] = function(_, _, result, client_id)
local message_type = result.type
local message = result.message
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 64911fe7bb..ef5e08680e 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -713,6 +713,9 @@ function protocol.make_client_capabilities()
};
applyEdit = true;
};
+ callHierarchy = {
+ dynamicRegistration = false;
+ };
experimental = nil;
}
end
@@ -897,7 +900,8 @@ function protocol.resolve_capabilities(server_capabilities)
text_document_will_save = ifnil(textDocumentSync.willSave, false);
text_document_will_save_wait_until = ifnil(textDocumentSync.willSaveWaitUntil, false);
text_document_save = ifnil(textDocumentSync.save, false);
- text_document_save_include_text = ifnil(textDocumentSync.save and textDocumentSync.save.includeText, false);
+ text_document_save_include_text = ifnil(type(textDocumentSync.save) == 'table'
+ and textDocumentSync.save.includeText, false);
}
else
return nil, string.format("Invalid type for textDocumentSync: %q", type(textDocumentSync))
@@ -911,6 +915,7 @@ function protocol.resolve_capabilities(server_capabilities)
general_properties.workspace_symbol = server_capabilities.workspaceSymbolProvider or false
general_properties.document_formatting = server_capabilities.documentFormattingProvider or false
general_properties.document_range_formatting = server_capabilities.documentRangeFormattingProvider or false
+ general_properties.call_hierarchy = server_capabilities.callHierarchyProvider or false
if server_capabilities.codeActionProvider == nil then
general_properties.code_action = false
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index dad1dc11f1..81c92bfe05 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -36,10 +36,12 @@ end
--- Merges current process env with the given env and returns the result as
--- a list of "k=v" strings.
---
+--- <pre>
--- Example:
---
---- { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", }
---- => { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", }
+--- in: { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", }
+--- out: { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", }
+--- </pre>
local function env_merge(env)
if env == nil then
return env
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 49e2557c16..52a6fe89f3 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -92,7 +92,7 @@ local function sort_by_key(fn)
end
end
local edit_sort_key = sort_by_key(function(e)
- return {e.A[1], e.A[2], e.i}
+ return {e.A[1], e.A[2], -e.i}
end)
--- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position
@@ -119,6 +119,7 @@ function M.apply_text_edits(text_edits, bufnr)
if not api.nvim_buf_is_loaded(bufnr) then
vim.fn.bufload(bufnr)
end
+ api.nvim_buf_set_option(bufnr, 'buflisted', true)
local start_line, finish_line = math.huge, -1
local cleaned = {}
for i, e in ipairs(text_edits) do
@@ -618,8 +619,10 @@ end
---
--@param contents table of lines to trim and pad
--@param opts dictionary with optional fields
--- - pad_left amount of columns to pad contents at left (default 1)
--- - pad_right amount of columns to pad contents at right (default 1)
+-- - pad_left number of columns to pad contents at left (default 1)
+-- - pad_right number of columns to pad contents at right (default 1)
+-- - pad_top number of lines to pad contents at top (default 0)
+-- - pad_bottom number of lines to pad contents at bottom (default 0)
--@return contents table of trimmed and padded lines
function M._trim_and_pad(contents, opts)
validate {
@@ -633,6 +636,16 @@ function M._trim_and_pad(contents, opts)
for i, line in ipairs(contents) do
contents[i] = string.format('%s%s%s', left_padding, line:gsub("\r", ""), right_padding)
end
+ if opts.pad_top then
+ for _ = 1, opts.pad_top do
+ table.insert(contents, 1, "")
+ end
+ end
+ if opts.pad_bottom then
+ for _ = 1, opts.pad_bottom do
+ table.insert(contents, "")
+ end
+ end
return contents
end
@@ -650,8 +663,12 @@ end
-- - height of floating window
-- - width of floating window
-- - wrap_at character to wrap at for computing height
--- - pad_left amount of columns to pad contents at left
--- - pad_right amount of columns to pad contents at right
+-- - max_width maximal width of floating window
+-- - max_height maximal height of floating window
+-- - pad_left number of columns to pad contents at left
+-- - pad_right number of columns to pad contents at right
+-- - pad_top number of lines to pad contents at top
+-- - pad_bottom number of lines to pad contents at bottom
-- - separator insert separator after code block
--@return width,height size of float
function M.fancy_floating_markdown(contents, opts)
@@ -762,6 +779,8 @@ end
-- - height of floating window
-- - width of floating window
-- - wrap_at character to wrap at for computing height
+-- - max_width maximal width of floating window
+-- - max_height maximal height of floating window
--@return width,height size of float
function M._make_floating_popup_size(contents, opts)
validate {
@@ -772,6 +791,9 @@ function M._make_floating_popup_size(contents, opts)
local width = opts.width
local height = opts.height
+ local wrap_at = opts.wrap_at
+ local max_width = opts.max_width
+ local max_height = opts.max_height
local line_widths = {}
if not width then
@@ -782,11 +804,14 @@ function M._make_floating_popup_size(contents, opts)
width = math.max(line_widths[i], width)
end
end
+ if max_width then
+ width = math.min(width, max_width)
+ wrap_at = math.min(wrap_at or max_width, max_width)
+ end
if not height then
height = #contents
- local wrap_at = opts.wrap_at
- if wrap_at and width > wrap_at then
+ if wrap_at and width >= wrap_at then
height = 0
if vim.tbl_isempty(line_widths) then
for _, line in ipairs(contents) do
@@ -795,11 +820,14 @@ function M._make_floating_popup_size(contents, opts)
end
else
for i = 1, #contents do
- height = height + math.ceil(line_widths[i]/wrap_at)
+ height = height + math.max(1, math.ceil(line_widths[i]/wrap_at))
end
end
end
end
+ if max_height then
+ height = math.min(height, max_height)
+ end
return width, height
end
@@ -812,8 +840,12 @@ end
-- - height of floating window
-- - width of floating window
-- - wrap_at character to wrap at for computing height
--- - pad_left amount of columns to pad contents at left
--- - pad_right amount of columns to pad contents at right
+-- - max_width maximal width of floating window
+-- - max_height maximal height of floating window
+-- - pad_left number of columns to pad contents at left
+-- - pad_right number of columns to pad contents at right
+-- - pad_top number of lines to pad contents at top
+-- - pad_bottom number of lines to pad contents at bottom
--@return bufnr,winnr buffer and window number of floating window or nil
function M.open_floating_preview(contents, filetype, opts)
validate {
@@ -841,7 +873,7 @@ function M.open_floating_preview(contents, filetype, opts)
end
api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents)
api.nvim_buf_set_option(floating_bufnr, 'modifiable', false)
- M.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden"}, floating_winnr)
+ M.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden", "BufLeave"}, floating_winnr)
return floating_bufnr, floating_winnr
end
@@ -859,6 +891,8 @@ do
local severity_highlights = {}
+ local severity_floating_highlights = {}
+
local default_severity_highlight = {
[protocol.DiagnosticSeverity.Error] = { guifg = "Red" };
[protocol.DiagnosticSeverity.Warning] = { guifg = "Orange" };
@@ -870,6 +904,7 @@ do
for severity, hi_info in pairs(default_severity_highlight) do
local severity_name = protocol.DiagnosticSeverity[severity]
local highlight_name = "LspDiagnostics"..severity_name
+ local floating_highlight_name = highlight_name.."Floating"
-- Try to fill in the foreground color with a sane default.
local cmd_parts = {"highlight", "default", highlight_name}
for k, v in pairs(hi_info) do
@@ -877,7 +912,9 @@ do
end
api.nvim_command(table.concat(cmd_parts, ' '))
api.nvim_command('highlight link ' .. highlight_name .. 'Sign ' .. highlight_name)
+ api.nvim_command('highlight link ' .. highlight_name .. 'Floating ' .. highlight_name)
severity_highlights[severity] = highlight_name
+ severity_floating_highlights[severity] = floating_highlight_name
end
function M.buf_clear_diagnostics(bufnr)
@@ -926,7 +963,7 @@ do
-- TODO(ashkan) make format configurable?
local prefix = string.format("%d. ", i)
- local hiname = severity_highlights[diagnostic.severity]
+ local hiname = severity_floating_highlights[diagnostic.severity]
assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity))
local message_lines = split_lines(diagnostic.message)
table.insert(lines, prefix..message_lines[1])
@@ -946,7 +983,9 @@ do
end
--- Saves the diagnostics (Diagnostic[]) into diagnostics_by_buf
- --
+ ---
+ --@param bufnr bufnr for which the diagnostics are for.
+ --@param diagnostics Diagnostics[] received from the language server.
function M.buf_diagnostics_save_positions(bufnr, diagnostics)
validate {
bufnr = {bufnr, 'n', true};
@@ -1038,6 +1077,29 @@ do
end
end
+ --- Returns the number of diagnostics of given kind for current buffer.
+ ---
+ --- Useful for showing diagnostic counts in statusline. eg:
+ ---
+ --- <pre>
+ --- function! LspStatus() abort
+ --- let sl = ''
+ --- if luaeval('not vim.tbl_isempty(vim.lsp.buf_get_clients(0))')
+ --- let sl.='%#MyStatuslineLSP#E:'
+ --- let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.util.buf_diagnostics_count([[Error]])")}'
+ --- let sl.='%#MyStatuslineLSP# W:'
+ --- let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.util.buf_diagnostics_count([[Warning]])")}'
+ --- else
+ --- let sl.='%#MyStatuslineLSPErrors#off'
+ --- endif
+ --- return sl
+ --- endfunction
+ --- let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus()
+ --- </pre>
+ ---
+ --@param kind Diagnostic severity kind: See |vim.lsp.protocol.DiagnosticSeverity|
+ ---
+ --@return Count of diagnostics
function M.buf_diagnostics_count(kind)
local bufnr = vim.api.nvim_get_current_buf()
local diagnostics = M.diagnostics_by_buf[bufnr]
@@ -1058,6 +1120,16 @@ do
[protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign";
}
+ --- Place signs for each diagnostic in the sign column.
+ ---
+ --- Sign characters can be customized with the following commands:
+ ---
+ --- <pre>
+ --- sign define LspDiagnosticsErrorSign text=E texthl=LspDiagnosticsError linehl= numhl=
+ --- sign define LspDiagnosticsWarningSign text=W texthl=LspDiagnosticsWarning linehl= numhl=
+ --- sign define LspDiagnosticsInformationSign text=I texthl=LspDiagnosticsInformation linehl= numhl=
+ --- sign define LspDiagnosticsHintSign text=H texthl=LspDiagnosticsHint linehl= numhl=
+ --- </pre>
function M.buf_diagnostics_signs(bufnr, diagnostics)
for _, diagnostic in ipairs(diagnostics) do
vim.fn.sign_place(0, sign_ns, diagnostic_severity_map[diagnostic.severity], bufnr, {lnum=(diagnostic.range.start.line+1)})
@@ -1083,40 +1155,31 @@ function M.locations_to_items(locations)
for _, d in ipairs(locations) do
-- locations may be Location or LocationLink
local uri = d.uri or d.targetUri
- local fname = assert(vim.uri_to_fname(uri))
local range = d.range or d.targetSelectionRange
- table.insert(grouped[fname], {start = range.start})
+ table.insert(grouped[uri], {start = range.start})
end
local keys = vim.tbl_keys(grouped)
table.sort(keys)
-- TODO(ashkan) I wish we could do this lazily.
- for _, fname in ipairs(keys) do
- local rows = grouped[fname]
-
+ for _, uri in ipairs(keys) do
+ local rows = grouped[uri]
table.sort(rows, position_sort)
- local i = 0
- for line in io.lines(fname) do
- for _, temp in ipairs(rows) do
- local pos = temp.start
- local row = pos.line
- if i == row then
- local col
- if pos.character > #line then
- col = #line
- else
- col = vim.str_byteindex(line, pos.character)
- end
- table.insert(items, {
- filename = fname,
- lnum = row + 1,
- col = col + 1;
- text = line;
- })
- end
- end
- i = i + 1
+ local bufnr = vim.uri_to_bufnr(uri)
+ vim.fn.bufload(bufnr)
+ local filename = vim.uri_to_fname(uri)
+ for _, temp in ipairs(rows) do
+ local pos = temp.start
+ local row = pos.line
+ local line = (api.nvim_buf_get_lines(bufnr, row, row + 1, false) or {""})[1]
+ local col = M.character_offset(bufnr, row, pos.character)
+ table.insert(items, {
+ filename = filename,
+ lnum = row + 1,
+ col = col + 1;
+ text = line;
+ })
end
end
return items
@@ -1145,7 +1208,7 @@ end
--- Convert symbols to quickfix list items
---
---@symbols DocumentSymbol[] or SymbolInformation[]
+--@param symbols DocumentSymbol[] or SymbolInformation[]
function M.symbols_to_items(symbols, bufnr)
local function _symbols_to_items(_symbols, _items, _bufnr)
for _, symbol in ipairs(_symbols) do
@@ -1254,6 +1317,30 @@ function M.make_text_document_params()
return { uri = vim.uri_from_bufnr(0) }
end
+--- Get visual width of tabstop.
+---
+--@see |softtabstop|
+--@param bufnr (optional, number): Buffer handle, defaults to current
+--@returns (number) tabstop visual width
+function M.get_effective_tabstop(bufnr)
+ validate { bufnr = {bufnr, 'n', true} }
+ local bo = bufnr and vim.bo[bufnr] or vim.bo
+ local sts = bo.softtabstop
+ return (sts > 0 and sts) or (sts < 0 and bo.shiftwidth) or bo.tabstop
+end
+
+function M.make_formatting_params(options)
+ validate { options = {options, 't', true} }
+ options = vim.tbl_extend('keep', options or {}, {
+ tabSize = M.get_effective_tabstop();
+ insertSpaces = vim.bo.expandtab;
+ })
+ return {
+ textDocument = { uri = vim.uri_from_bufnr(0) };
+ options = options;
+ }
+end
+
-- @param buf buffer handle or 0 for current.
-- @param row 0-indexed line
-- @param col 0-indexed byte offset in line
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 384d22cb89..6e427665f2 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -347,16 +347,16 @@ function vim.tbl_flatten(t)
return result
end
--- Determine whether a Lua table can be treated as an array.
---
--- An empty table `{}` will default to being treated as an array.
--- Use `vim.emtpy_dict()` to create a table treated as an
--- empty dict. Empty tables returned by `rpcrequest()` and
--- `vim.fn` functions can be checked using this function
--- whether they represent empty API arrays and vimL lists.
----
---@params Table
---@returns true: An array-like table, false: A dict-like or mixed table
+--- Determine whether a Lua table can be treated as an array.
+---
+--- An empty table `{}` will default to being treated as an array.
+--- Use `vim.emtpy_dict()` to create a table treated as an
+--- empty dict. Empty tables returned by `rpcrequest()` and
+--- `vim.fn` functions can be checked using this function
+--- whether they represent empty API arrays and vimL lists.
+---
+--@param t Table
+--@returns `true` if array-like table, else `false`.
function vim.tbl_islist(t)
if type(t) ~= 'table' then
return false
@@ -392,7 +392,7 @@ end
--- </pre>
---
--@see https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua
---@param Table
+--@param t Table
--@returns Number that is the number of the value in table
function vim.tbl_count(t)
vim.validate{t={t,'t'}}
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index c502e45bd0..927456708c 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -15,19 +15,33 @@ function Parser:parse()
local changes
self.tree, changes = self._parser:parse_buf(self.bufnr)
self.valid = true
- for _, cb in ipairs(self.change_cbs) do
- cb(changes)
+
+ if not vim.tbl_isempty(changes) then
+ for _, cb in ipairs(self.changedtree_cbs) do
+ cb(changes)
+ end
end
+
return self.tree, changes
end
-function Parser:_on_lines(bufnr, _, start_row, old_stop_row, stop_row, old_byte_size)
+function Parser:_on_lines(bufnr, changed_tick, start_row, old_stop_row, stop_row, old_byte_size)
local start_byte = a.nvim_buf_get_offset(bufnr,start_row)
local stop_byte = a.nvim_buf_get_offset(bufnr,stop_row)
local old_stop_byte = start_byte + old_byte_size
self._parser:edit(start_byte,old_stop_byte,stop_byte,
start_row,0,old_stop_row,0,stop_row,0)
self.valid = false
+
+ for _, cb in ipairs(self.lines_cbs) do
+ cb(bufnr, changed_tick, start_row, old_stop_row, stop_row, old_byte_size)
+ end
+end
+
+function Parser:set_included_ranges(ranges)
+ self._parser:set_included_ranges(ranges)
+ -- The buffer will need to be parsed again later
+ self.valid = false
end
local M = {
@@ -69,9 +83,13 @@ function M.create_parser(bufnr, lang, id)
if bufnr == 0 then
bufnr = a.nvim_get_current_buf()
end
+
+ vim.fn.bufload(bufnr)
+
local self = setmetatable({bufnr=bufnr, lang=lang, valid=false}, Parser)
self._parser = vim._create_ts_parser(lang)
- self.change_cbs = {}
+ self.changedtree_cbs = {}
+ self.lines_cbs = {}
self:parse()
-- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is
-- using it.
@@ -90,7 +108,7 @@ function M.create_parser(bufnr, lang, id)
return self
end
-function M.get_parser(bufnr, ft, cb)
+function M.get_parser(bufnr, ft, buf_attach_cbs)
if bufnr == nil or bufnr == 0 then
bufnr = a.nvim_get_current_buf()
end
@@ -102,9 +120,15 @@ function M.get_parser(bufnr, ft, cb)
if parsers[id] == nil then
parsers[id] = M.create_parser(bufnr, ft, id)
end
- if cb ~= nil then
- table.insert(parsers[id].change_cbs, cb)
+
+ if buf_attach_cbs and buf_attach_cbs.on_changedtree then
+ table.insert(parsers[id].changedtree_cbs, buf_attach_cbs.on_changedtree)
end
+
+ if buf_attach_cbs and buf_attach_cbs.on_lines then
+ table.insert(parsers[id].lines_cbs, buf_attach_cbs.on_lines)
+ end
+
return parsers[id]
end
diff --git a/runtime/lua/vim/tshighlighter.lua b/runtime/lua/vim/tshighlighter.lua
index 1440acf0d0..6465751ae8 100644
--- a/runtime/lua/vim/tshighlighter.lua
+++ b/runtime/lua/vim/tshighlighter.lua
@@ -3,6 +3,7 @@ local a = vim.api
-- support reload for quick experimentation
local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
TSHighlighter.__index = TSHighlighter
+local ts_hs_ns = a.nvim_create_namespace("treesitter_hl")
-- These are conventions defined by tree-sitter, though it
-- needs to be user extensible also.
@@ -24,9 +25,17 @@ TSHighlighter.hl_map = {
function TSHighlighter.new(query, bufnr, ft)
local self = setmetatable({}, TSHighlighter)
- self.parser = vim.treesitter.get_parser(bufnr, ft, function(...) self:on_change(...) end)
+ self.parser = vim.treesitter.get_parser(
+ bufnr,
+ ft,
+ {
+ on_changedtree = function(...) self:on_changedtree(...) end,
+ on_lines = function() self.root = self.parser:parse():root() end
+ }
+ )
+
self.buf = self.parser.bufnr
- -- TODO(bfredl): perhaps on_start should be called uncondionally, instead for only on mod?
+
local tree = self.parser:parse()
self.root = tree:root()
self:set_query(query)
@@ -34,11 +43,6 @@ function TSHighlighter.new(query, bufnr, ft)
self.redraw_count = 0
self.line_count = {}
a.nvim_buf_set_option(self.buf, "syntax", "")
- a.nvim__buf_set_luahl(self.buf, {
- on_start=function(...) return self:on_start(...) end,
- on_window=function(...) return self:on_window(...) end,
- on_line=function(...) return self:on_line(...) end,
- })
-- Tricky: if syntax hasn't been enabled, we need to reload color scheme
-- but use synload.vim rather than syntax.vim to not enable
@@ -50,73 +54,63 @@ function TSHighlighter.new(query, bufnr, ft)
return self
end
+local function is_highlight_name(capture_name)
+ local firstc = string.sub(capture_name, 1, 1)
+ return firstc ~= string.lower(firstc)
+end
+
+function TSHighlighter:get_hl_from_capture(capture)
+
+ local name = self.query.captures[capture]
+
+ if is_highlight_name(name) then
+ -- From "Normal.left" only keep "Normal"
+ return vim.split(name, '.', true)[1]
+ else
+ -- Default to false to avoid recomputing
+ return TSHighlighter.hl_map[name]
+ end
+end
+
function TSHighlighter:set_query(query)
if type(query) == "string" then
query = vim.treesitter.parse_query(self.parser.lang, query)
end
self.query = query
- self.id_map = {}
- for i, capture in ipairs(self.query.captures) do
- local hl = 0
- local firstc = string.sub(capture, 1, 1)
- local hl_group = self.hl_map[capture]
- if firstc ~= string.lower(firstc) then
- hl_group = vim.split(capture, '.', true)[1]
- end
- if hl_group then
- hl = a.nvim_get_hl_id_by_name(hl_group)
+ self.hl_cache = setmetatable({}, {
+ __index = function(table, capture)
+ local hl = self:get_hl_from_capture(capture)
+ rawset(table, capture, hl)
+
+ return hl
end
- self.id_map[i] = hl
- end
+ })
- a.nvim__buf_redraw_range(self.buf, 0, a.nvim_buf_line_count(self.buf))
+ self:on_changedtree({{self.root:range()}})
end
-function TSHighlighter:on_change(changes)
- for _, ch in ipairs(changes or {}) do
- a.nvim__buf_redraw_range(self.buf, ch[1], ch[3]+1)
- end
- self.edit_count = self.edit_count + 1
-end
+function TSHighlighter:on_changedtree(changes)
+ -- Get a fresh root
+ self.root = self.parser.tree:root()
-function TSHighlighter:on_start(_, _buf, _tick)
- local tree = self.parser:parse()
- self.root = tree:root()
-end
+ for _, ch in ipairs(changes or {}) do
+ -- Try to be as exact as possible
+ local changed_node = self.root:descendant_for_range(ch[1], ch[2], ch[3], ch[4])
-function TSHighlighter:on_window(_, _win, _buf, _topline, botline)
- self.iter = nil
- self.nextrow = 0
- self.botline = botline
- self.redraw_count = self.redraw_count + 1
-end
+ a.nvim_buf_clear_namespace(self.buf, ts_hs_ns, ch[1], ch[3])
-function TSHighlighter:on_line(_, _win, buf, line)
- if self.iter == nil then
- self.iter = self.query:iter_captures(self.root,buf,line,self.botline)
- end
- while line >= self.nextrow do
- local capture, node, match = self.iter()
- local active = true
- if capture == nil then
- break
- end
- if match ~= nil then
- active = self:run_pred(match)
- match.active = active
- end
- local start_row, start_col, end_row, end_col = node:range()
- local hl = self.id_map[capture]
- if hl > 0 and active and end_row >= line then
- a.nvim__put_attr(hl, start_row, start_col, end_row, end_col)
- end
- if start_row > line then
- self.nextrow = start_row
+ for capture, node in self.query:iter_captures(changed_node, self.buf, ch[1], ch[3] + 1) do
+ local start_row, start_col, end_row, end_col = node:range()
+ local hl = self.hl_cache[capture]
+ if hl then
+ a.nvim__buf_add_decoration(self.buf, ts_hs_ns, hl,
+ start_row, start_col,
+ end_row, end_col,
+ {})
+ end
end
end
- self.line_count[line] = (self.line_count[line] or 0) + 1
- --return tostring(self.line_count[line])
end
return TSHighlighter