aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/highlight.lua29
-rw-r--r--runtime/lua/vim/lsp.lua65
-rw-r--r--runtime/lua/vim/lsp/callbacks.lua11
-rw-r--r--runtime/lua/vim/lsp/log.lua2
-rw-r--r--runtime/lua/vim/lsp/protocol.lua25
-rw-r--r--runtime/lua/vim/lsp/util.lua307
-rw-r--r--runtime/lua/vim/shared.lua2
-rw-r--r--runtime/lua/vim/treesitter.lua5
8 files changed, 318 insertions, 128 deletions
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index 5c98c626a4..69c3c8a4dc 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -2,12 +2,34 @@ local api = vim.api
local highlight = {}
+--- Highlight range between two positions
+---
+--@param bufnr number of buffer to apply highlighting to
+--@param ns namespace to add highlight to
+--@param higroup highlight group to use for highlighting
+--@param rtype type of range (:help setreg, default charwise)
+--@param inclusive boolean indicating whether the range is end-inclusive (default false)
+function highlight.range(bufnr, ns, higroup, start, finish, rtype, inclusive)
+ rtype = rtype or 'v'
+ inclusive = inclusive or false
+
+ -- sanity check
+ if start[2] < 0 or finish[2] < start[2] then return end
+
+ local region = vim.region(bufnr, start, finish, rtype, inclusive)
+ for linenr, cols in pairs(region) do
+ api.nvim_buf_add_highlight(bufnr, ns, higroup, linenr, cols[1], cols[2])
+ end
+
+end
+
--- Highlight the yanked region
---
+---
--- use from init.vim via
--- au TextYankPost * lua require'vim.highlight'.on_yank()
--- customize highlight group and timeout via
--- au TextYankPost * lua require'vim.highlight'.on_yank("IncSearch", 500)
+---
-- @param higroup highlight group for yanked region
-- @param timeout time in ms before highlight is cleared
-- @param event event structure
@@ -27,10 +49,7 @@ function highlight.on_yank(higroup, timeout, event)
pos1 = {pos1[2] - 1, pos1[3] - 1 + pos1[4]}
pos2 = {pos2[2] - 1, pos2[3] - 1 + pos2[4]}
- local region = vim.region(bufnr, pos1, pos2, event.regtype, event.inclusive)
- for linenr, cols in pairs(region) do
- api.nvim_buf_add_highlight(bufnr, yank_ns, higroup, linenr, cols[1], cols[2])
- end
+ highlight.range(bufnr, yank_ns, higroup, pos1, pos2, event.regtype, event.inclusive)
vim.defer_fn(
function() api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1) end,
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 61da2130c8..2fbc51481f 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -46,31 +46,6 @@ local function is_dir(filename)
return stat and stat.type == 'directory' or false
end
--- TODO Use vim.wait when that is available, but provide an alternative for now.
-local wait = vim.wait or function(timeout_ms, condition, interval)
- validate {
- timeout_ms = { timeout_ms, 'n' };
- condition = { condition, 'f' };
- interval = { interval, 'n', true };
- }
- assert(timeout_ms > 0, "timeout_ms must be > 0")
- local _ = log.debug() and log.debug("wait.fallback", timeout_ms)
- interval = interval or 200
- local interval_cmd = "sleep "..interval.."m"
- local timeout = timeout_ms + uv.now()
- -- TODO is there a better way to sync this?
- while true do
- uv.update_time()
- if condition() then
- return 0
- end
- if uv.now() >= timeout then
- return -1
- end
- nvim_command(interval_cmd)
- -- vim.loop.sleep(10)
- end
-end
local wait_result_reason = { [-1] = "timeout"; [-2] = "interrupted"; [-3] = "error" }
local valid_encodings = {
@@ -122,19 +97,19 @@ local function validate_encoding(encoding)
end
function lsp._cmd_parts(input)
- local cmd, cmd_args
- if vim.tbl_islist(input) then
- cmd = input[1]
- cmd_args = {}
- -- Don't mutate our input.
- for i, v in ipairs(input) do
- assert(type(v) == 'string', "input arguments must be strings")
- if i > 1 then
- table.insert(cmd_args, v)
- end
+ vim.validate{cmd={
+ input,
+ function() return vim.tbl_islist(input) end,
+ "list"}}
+
+ local cmd = input[1]
+ local cmd_args = {}
+ -- Don't mutate our input.
+ for i, v in ipairs(input) do
+ vim.validate{["cmd argument"]={v, "s"}}
+ if i > 1 then
+ table.insert(cmd_args, v)
end
- else
- error("cmd type must be list.")
end
return cmd, cmd_args
end
@@ -524,7 +499,7 @@ function lsp.start_client(config)
function client.request(method, params, callback, bufnr)
if not callback then
callback = resolve_callback(method)
- or error("not found: request callback for client "..client.name)
+ or error(string.format("not found: %q request callback for client %q.", method, client.name))
end
local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback, bufnr)
-- TODO keep these checks or just let it go anyway?
@@ -532,6 +507,8 @@ function lsp.start_client(config)
or (not client.resolved_capabilities.signature_help and method == 'textDocument/signatureHelp')
or (not client.resolved_capabilities.goto_definition and method == 'textDocument/definition')
or (not client.resolved_capabilities.implementation and method == 'textDocument/implementation')
+ or (not client.resolved_capabilities.declaration and method == 'textDocument/declaration')
+ 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')
then
@@ -810,8 +787,8 @@ function lsp._vim_exit_handler()
for _, client in pairs(active_clients) do
client.stop()
end
- local wait_result = wait(500, function() return tbl_isempty(active_clients) end, 50)
- if wait_result ~= 0 then
+
+ if not vim.wait(500, function() return tbl_isempty(active_clients) end, 50) then
for _, client in pairs(active_clients) do
client.stop(true)
end
@@ -889,12 +866,14 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
for _ in pairs(client_request_ids) do
expected_result_count = expected_result_count + 1
end
- local wait_result = wait(timeout_ms or 100, function()
+
+ local wait_result, reason = vim.wait(timeout_ms or 100, function()
return result_count >= expected_result_count
end, 10)
- if wait_result ~= 0 then
+
+ if not wait_result then
cancel()
- return nil, wait_result_reason[wait_result]
+ return nil, wait_result_reason[reason]
end
return request_results
end
diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua
index 7c51fc2cc2..4b14f0132d 100644
--- a/runtime/lua/vim/lsp/callbacks.lua
+++ b/runtime/lua/vim/lsp/callbacks.lua
@@ -72,6 +72,17 @@ M['textDocument/publishDiagnostics'] = function(_, _, result)
err_message("LSP.publishDiagnostics: Couldn't find buffer for ", uri)
return
end
+
+ -- Unloaded buffers should not handle diagnostics.
+ -- When the buffer is loaded, we'll call on_attach, which sends textDocument/didOpen.
+ -- This should trigger another publish of the diagnostics.
+ --
+ -- In particular, this stops a ton of spam when first starting a server for current
+ -- unloaded buffers.
+ if not api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+
util.buf_clear_diagnostics(bufnr)
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index 78aabf08ce..696ce43a59 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -24,7 +24,7 @@ do
local function path_join(...)
return table.concat(vim.tbl_flatten{...}, path_sep)
end
- local logfilename = path_join(vim.fn.stdpath('data'), 'vim-lsp.log')
+ local logfilename = path_join(vim.fn.stdpath('data'), 'lsp.log')
--- Return the log filename.
function log.get_filename()
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 877d11411b..64911fe7bb 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -633,8 +633,7 @@ function protocol.make_client_capabilities()
dynamicRegistration = false;
completionItem = {
- -- TODO(tjdevries): Is it possible to implement this in plain lua?
- snippetSupport = false;
+ snippetSupport = true;
commitCharactersSupport = false;
preselectSupport = false;
deprecatedSupport = false;
@@ -924,6 +923,28 @@ function protocol.resolve_capabilities(server_capabilities)
error("The server sent invalid codeActionProvider")
end
+ if server_capabilities.declarationProvider == nil then
+ general_properties.declaration = false
+ elseif type(server_capabilities.declarationProvider) == 'boolean' then
+ general_properties.declaration = server_capabilities.declarationProvider
+ elseif type(server_capabilities.declarationProvider) == 'table' then
+ -- TODO: support more detailed declarationProvider options.
+ general_properties.declaration = false
+ else
+ error("The server sent invalid declarationProvider")
+ end
+
+ if server_capabilities.typeDefinitionProvider == nil then
+ general_properties.type_definition = false
+ elseif type(server_capabilities.typeDefinitionProvider) == 'boolean' then
+ general_properties.type_definition = server_capabilities.typeDefinitionProvider
+ elseif type(server_capabilities.typeDefinitionProvider) == 'table' then
+ -- TODO: support more detailed typeDefinitionProvider options.
+ general_properties.type_definition = false
+ else
+ error("The server sent invalid typeDefinitionProvider")
+ end
+
if server_capabilities.implementationProvider == nil then
general_properties.implementation = false
elseif type(server_capabilities.implementationProvider) == 'boolean' then
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 5c6d183ac1..49e2557c16 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -3,6 +3,7 @@ local vim = vim
local validate = vim.validate
local api = vim.api
local list_extend = vim.list_extend
+local highlight = require 'vim.highlight'
local M = {}
@@ -94,17 +95,23 @@ local edit_sort_key = sort_by_key(function(e)
return {e.A[1], e.A[2], e.i}
end)
-local function get_line_byte_from_line_character(bufnr, lnum, cnum)
- -- Skip check when the byte and character position is the same
- if cnum > 0 then
- local lines = api.nvim_buf_get_lines(bufnr, lnum, lnum+1, false)
-
+--- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position
+-- Returns a zero-indexed column, since set_lines() does the conversion to
+-- 1-indexed
+local function get_line_byte_from_position(bufnr, position)
+ -- LSP's line and characters are 0-indexed
+ -- Vim's line and columns are 1-indexed
+ local col = position.character
+ -- When on the first character, we can ignore the difference between byte and
+ -- character
+ if col > 0 then
+ local line = position.line
+ local lines = api.nvim_buf_get_lines(bufnr, line, line + 1, false)
if #lines > 0 then
- return vim.str_byteindex(lines[1], cnum)
+ return vim.str_byteindex(lines[1], col)
end
end
-
- return cnum
+ return col
end
function M.apply_text_edits(text_edits, bufnr)
@@ -117,15 +124,9 @@ function M.apply_text_edits(text_edits, bufnr)
for i, e in ipairs(text_edits) do
-- adjust start and end column for UTF-16 encoding of non-ASCII characters
local start_row = e.range.start.line
- local start_col = get_line_byte_from_line_character(
- bufnr,
- start_row,
- e.range.start.character)
+ local start_col = get_line_byte_from_position(bufnr, e.range.start)
local end_row = e.range["end"].line
- local end_col = get_line_byte_from_line_character(
- bufnr,
- end_row,
- e.range["end"].character)
+ local end_col = get_line_byte_from_position(bufnr, e.range['end'])
start_line = math.min(e.range.start.line, start_line)
finish_line = math.max(e.range["end"].line, finish_line)
-- TODO(ashkan) sanity check ranges for overlap.
@@ -199,6 +200,66 @@ function M.get_current_line_to_cursor()
return line:sub(pos[2]+1)
end
+local function parse_snippet_rec(input, inner)
+ local res = ""
+
+ local close, closeend = nil, nil
+ if inner then
+ close, closeend = input:find("}", 1, true)
+ while close ~= nil and input:sub(close-1,close-1) == "\\" do
+ close, closeend = input:find("}", closeend+1, true)
+ end
+ end
+
+ local didx = input:find('$', 1, true)
+ if didx == nil and close == nil then
+ return input, ""
+ elseif close ~=nil and (didx == nil or close < didx) then
+ -- No inner placeholders
+ return input:sub(0, close-1), input:sub(closeend+1)
+ end
+
+ res = res .. input:sub(0, didx-1)
+ input = input:sub(didx+1)
+
+ local tabstop, tabstopend = input:find('^%d+')
+ local placeholder, placeholderend = input:find('^{%d+:')
+ local choice, choiceend = input:find('^{%d+|')
+
+ if tabstop then
+ input = input:sub(tabstopend+1)
+ elseif choice then
+ input = input:sub(choiceend+1)
+ close, closeend = input:find("|}", 1, true)
+
+ res = res .. input:sub(0, close-1)
+ input = input:sub(closeend+1)
+ elseif placeholder then
+ -- TODO: add support for variables
+ input = input:sub(placeholderend+1)
+
+ -- placeholders and variables are recursive
+ while input ~= "" do
+ local r, tail = parse_snippet_rec(input, true)
+ r = r:gsub("\\}", "}")
+
+ res = res .. r
+ input = tail
+ end
+ else
+ res = res .. "$"
+ end
+
+ return res, input
+end
+
+-- Parse completion entries, consuming snippet tokens
+function M.parse_snippet(input)
+ local res, _ = parse_snippet_rec(input, false)
+
+ return res
+end
+
-- Sort by CompletionItem.sortText
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
local function sort_completion_items(items)
@@ -213,14 +274,22 @@ end
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
local function get_completion_word(item)
if item.textEdit ~= nil and item.textEdit.newText ~= nil then
- return item.textEdit.newText
+ if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then
+ return item.textEdit.newText
+ else
+ return M.parse_snippet(item.textEdit.newText)
+ end
elseif item.insertText ~= nil then
- return item.insertText
+ if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then
+ return item.insertText
+ else
+ return M.parse_snippet(item.insertText)
+ end
end
return item.label
end
--- Some lanuguage servers return complementary candidates whose prefixes do not match are also returned.
+-- Some language servers return complementary candidates whose prefixes do not match are also returned.
-- So we exclude completion candidates whose prefix does not match.
local function remove_unmatch_completion_items(items, prefix)
return vim.tbl_filter(function(item)
@@ -474,13 +543,33 @@ function M.jump_to_location(location)
api.nvim_buf_set_option(0, 'buflisted', true)
local range = location.range or location.targetSelectionRange
local row = range.start.line
- local col = range.start.character
- local line = api.nvim_buf_get_lines(0, row, row+1, true)[1]
- col = vim.str_byteindex(line, col)
+ local col = get_line_byte_from_position(0, range.start)
api.nvim_win_set_cursor(0, {row + 1, col})
return true
end
+--- Preview a location in a floating windows
+---
+--- behavior depends on type of location:
+--- - for Location, range is shown (e.g., function definition)
+--- - for LocationLink, targetRange is shown (e.g., body of function definition)
+---
+--@param location a single Location or LocationLink
+--@return bufnr,winnr buffer and window number of floating window or nil
+function M.preview_location(location)
+ -- location may be LocationLink or Location (more useful for the former)
+ local uri = location.targetUri or location.uri
+ if uri == nil then return end
+ local bufnr = vim.uri_to_bufnr(uri)
+ if not api.nvim_buf_is_loaded(bufnr) then
+ vim.fn.bufload(bufnr)
+ end
+ local range = location.targetRange or location.range
+ local contents = api.nvim_buf_get_lines(bufnr, range.start.line, range["end"].line+1, false)
+ local filetype = api.nvim_buf_get_option(bufnr, 'filetype')
+ return M.open_floating_preview(contents, filetype)
+end
+
local function find_window_by_var(name, value)
for _, win in ipairs(api.nvim_list_wins()) do
if npcall(api.nvim_win_get_var, win, name) == value then
@@ -525,13 +614,53 @@ function M.focusable_preview(unique_name, fn)
end)
end
--- Convert markdown into syntax highlighted regions by stripping the code
--- blocks and converting them into highlighted code.
--- This will by default insert a blank line separator after those code block
--- regions to improve readability.
+--- Trim empty lines from input and pad left and right with spaces
+---
+--@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)
+--@return contents table of trimmed and padded lines
+function M._trim_and_pad(contents, opts)
+ validate {
+ contents = { contents, 't' };
+ opts = { opts, 't', true };
+ }
+ opts = opts or {}
+ local left_padding = (" "):rep(opts.pad_left or 1)
+ local right_padding = (" "):rep(opts.pad_right or 1)
+ contents = M.trim_empty_lines(contents)
+ for i, line in ipairs(contents) do
+ contents[i] = string.format('%s%s%s', left_padding, line:gsub("\r", ""), right_padding)
+ end
+ return contents
+end
+
+
+
+--- Convert markdown into syntax highlighted regions by stripping the code
+--- blocks and converting them into highlighted code.
+--- This will by default insert a blank line separator after those code block
+--- regions to improve readability.
+--- The result is shown in a floating preview
+--- TODO: refactor to separate stripping/converting and make use of open_floating_preview
+---
+--@param contents table of lines to show in window
+--@param opts dictionary with optional fields
+-- - 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
+-- - separator insert separator after code block
+--@return width,height size of float
function M.fancy_floating_markdown(contents, opts)
- local pad_left = opts and opts.pad_left
- local pad_right = opts and opts.pad_right
+ validate {
+ contents = { contents, 't' };
+ opts = { opts, 't', true };
+ }
+ opts = opts or {}
+
local stripped = {}
local highlights = {}
do
@@ -565,31 +694,27 @@ function M.fancy_floating_markdown(contents, opts)
end
end
end
- local width = 0
- for i, v in ipairs(stripped) do
- v = v:gsub("\r", "")
- if pad_left then v = (" "):rep(pad_left)..v end
- if pad_right then v = v..(" "):rep(pad_right) end
- stripped[i] = v
- width = math.max(width, #v)
- end
- if opts and opts.max_width then
- width = math.min(opts.max_width, width)
- end
- -- TODO(ashkan): decide how to make this customizable.
- local insert_separator = true
+ -- Clean up and add padding
+ stripped = M._trim_and_pad(stripped, opts)
+
+ -- Compute size of float needed to show (wrapped) lines
+ opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0))
+ local width, height = M._make_floating_popup_size(stripped, opts)
+
+ -- Insert blank line separator after code block
+ local insert_separator = opts.separator or true
if insert_separator then
for i, h in ipairs(highlights) do
h.start = h.start + i - 1
h.finish = h.finish + i - 1
if h.finish + 1 <= #stripped then
table.insert(stripped, h.finish + 1, string.rep("─", width))
+ height = height + 1
end
end
end
-- Make the floating window.
- local height = #stripped
local bufnr = api.nvim_create_buf(false, true)
local winnr = api.nvim_open_win(bufnr, false, M.make_floating_popup_options(width, height, opts))
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped)
@@ -601,7 +726,7 @@ function M.fancy_floating_markdown(contents, opts)
vim.cmd("ownsyntax markdown")
local idx = 1
- local function highlight_region(ft, start, finish)
+ local function apply_syntax_to_region(ft, start, finish)
if ft == '' then return end
local name = ft..idx
idx = idx + 1
@@ -617,8 +742,8 @@ function M.fancy_floating_markdown(contents, opts)
-- make sure that regions between code blocks are definitely markdown.
-- local ph = {start = 0; finish = 1;}
for _, h in ipairs(highlights) do
- -- highlight_region('markdown', ph.finish, h.start)
- highlight_region(h.ft, h.start, h.finish)
+ -- apply_syntax_to_region('markdown', ph.finish, h.start)
+ apply_syntax_to_region(h.ft, h.start, h.finish)
-- ph = h
end
@@ -630,33 +755,81 @@ function M.close_preview_autocmd(events, winnr)
api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)")
end
-function M.open_floating_preview(contents, filetype, opts)
+--- Compute size of float needed to show contents (with optional wrapping)
+---
+--@param contents table of lines to show in window
+--@param opts dictionary with optional fields
+-- - height of floating window
+-- - width of floating window
+-- - wrap_at character to wrap at for computing height
+--@return width,height size of float
+function M._make_floating_popup_size(contents, opts)
validate {
contents = { contents, 't' };
- filetype = { filetype, 's', true };
opts = { opts, 't', true };
}
opts = opts or {}
- -- Trim empty lines from the end.
- contents = M.trim_empty_lines(contents)
-
local width = opts.width
- local height = opts.height or #contents
+ local height = opts.height
+ local line_widths = {}
+
if not width then
width = 0
for i, line in ipairs(contents) do
- -- Clean up the input and add left pad.
- line = " "..line:gsub("\r", "")
-- TODO(ashkan) use nvim_strdisplaywidth if/when that is introduced.
- local line_width = vim.fn.strdisplaywidth(line)
- width = math.max(line_width, width)
- contents[i] = line
+ line_widths[i] = vim.fn.strdisplaywidth(line)
+ width = math.max(line_widths[i], width)
end
- -- Add right padding of 1 each.
- width = width + 1
end
+ if not height then
+ height = #contents
+ local wrap_at = opts.wrap_at
+ if wrap_at and width > wrap_at then
+ height = 0
+ if vim.tbl_isempty(line_widths) then
+ for _, line in ipairs(contents) do
+ local line_width = vim.fn.strdisplaywidth(line)
+ height = height + math.ceil(line_width/wrap_at)
+ end
+ else
+ for i = 1, #contents do
+ height = height + math.ceil(line_widths[i]/wrap_at)
+ end
+ end
+ end
+ end
+
+ return width, height
+end
+
+--- Show contents in a floating window
+---
+--@param contents table of lines to show in window
+--@param filetype string of filetype to set for opened buffer
+--@param opts dictionary with optional fields
+-- - 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
+--@return bufnr,winnr buffer and window number of floating window or nil
+function M.open_floating_preview(contents, filetype, opts)
+ validate {
+ contents = { contents, 't' };
+ filetype = { filetype, 's', true };
+ opts = { opts, 't', true };
+ }
+ opts = opts or {}
+
+ -- Clean up input: trim empty lines from the end, pad
+ contents = M._trim_and_pad(contents, opts)
+
+ -- Compute size of float needed to show (wrapped) lines
+ opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0))
+ local width, height = M._make_floating_popup_size(contents, opts)
+
local floating_bufnr = api.nvim_create_buf(false, true)
if filetype then
api.nvim_buf_set_option(floating_bufnr, 'filetype', filetype)
@@ -672,19 +845,6 @@ function M.open_floating_preview(contents, filetype, opts)
return floating_bufnr, floating_winnr
end
-local function highlight_range(bufnr, ns, hiname, start, finish)
- if start[1] == finish[1] then
- -- TODO care about encoding here since this is in byte index?
- api.nvim_buf_add_highlight(bufnr, ns, hiname, start[1], start[2], finish[2])
- else
- api.nvim_buf_add_highlight(bufnr, ns, hiname, start[1], start[2], -1)
- for line = start[1] + 1, finish[1] - 1 do
- api.nvim_buf_add_highlight(bufnr, ns, hiname, line, 0, -1)
- end
- api.nvim_buf_add_highlight(bufnr, ns, hiname, finish[1], 0, finish[2])
- end
-end
-
do
local diagnostic_ns = api.nvim_create_namespace("vim_lsp_diagnostics")
local reference_ns = api.nvim_create_namespace("vim_lsp_references")
@@ -818,8 +978,7 @@ do
[protocol.DiagnosticSeverity.Hint]='Hint',
}
- -- TODO care about encoding here since this is in byte index?
- highlight_range(bufnr, diagnostic_ns,
+ highlight.range(bufnr, diagnostic_ns,
underline_highlight_name..hlmap[diagnostic.severity],
{start.line, start.character},
{finish.line, finish.character}
@@ -843,7 +1002,7 @@ do
[protocol.DocumentHighlightKind.Write] = "LspReferenceWrite";
}
local kind = reference["kind"] or protocol.DocumentHighlightKind.Text
- highlight_range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos)
+ highlight.range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos)
end
end
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 2135bfc837..384d22cb89 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -79,7 +79,7 @@ function vim.gsplit(s, sep, plain)
end
return function()
- if done then
+ if done or (s == '' and sep == '') then
return
end
if sep == '' then
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index d3b78a7f73..c502e45bd0 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -162,16 +162,17 @@ function Query:match_preds(match, pattern, bufnr)
-- Here we only want to return if a predicate DOES NOT match, and
-- continue on the other case. This way unknown predicates will not be considered,
-- which allows some testing and easier user extensibility (#12173).
+ -- Also, tree-sitter strips the leading # from predicates for us.
if pred[1] == "eq?" then
local node = match[pred[2]]
local node_text = get_node_text(node, bufnr)
local str
if type(pred[3]) == "string" then
- -- (eq? @aa "foo")
+ -- (#eq? @aa "foo")
str = pred[3]
else
- -- (eq? @aa @bb)
+ -- (#eq? @aa @bb)
str = get_node_text(match[pred[3]], bufnr)
end