aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua')
-rw-r--r--runtime/lua/vim/highlight.lua60
-rw-r--r--runtime/lua/vim/lsp.lua63
-rw-r--r--runtime/lua/vim/lsp/callbacks.lua6
-rw-r--r--runtime/lua/vim/lsp/log.lua2
-rw-r--r--runtime/lua/vim/lsp/protocol.lua3
-rw-r--r--runtime/lua/vim/lsp/util.lua121
-rw-r--r--runtime/lua/vim/shared.lua2
-rw-r--r--runtime/lua/vim/uri.lua10
8 files changed, 193 insertions, 74 deletions
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
new file mode 100644
index 0000000000..69c3c8a4dc
--- /dev/null
+++ b/runtime/lua/vim/highlight.lua
@@ -0,0 +1,60 @@
+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
+function highlight.on_yank(higroup, timeout, event)
+ event = event or vim.v.event
+ if event.operator ~= 'y' or event.regtype == '' then return end
+ higroup = higroup or "IncSearch"
+ timeout = timeout or 500
+
+ 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("'[")
+ local pos2 = vim.fn.getpos("']")
+
+ pos1 = {pos1[2] - 1, pos1[3] - 1 + pos1[4]}
+ pos2 = {pos2[2] - 1, pos2[3] - 1 + pos2[4]}
+
+ 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,
+ timeout
+ )
+end
+
+return highlight
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 61da2130c8..84812b8c64 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?
@@ -810,8 +785,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 +864,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 37e9f1e5c1..7c51fc2cc2 100644
--- a/runtime/lua/vim/lsp/callbacks.lua
+++ b/runtime/lua/vim/lsp/callbacks.lua
@@ -242,12 +242,12 @@ end
-- Add boilerplate error validation and logging for all of these.
for k, fn in pairs(M) do
- M[k] = function(err, method, params, client_id)
- local _ = log.debug() and log.debug('default_callback', method, { params = params, client_id = client_id, err = err })
+ M[k] = function(err, method, params, client_id, bufnr)
+ log.debug('default_callback', method, { params = params, client_id = client_id, err = err, bufnr = bufnr })
if err then
error(tostring(err))
end
- return fn(err, method, params, client_id)
+ return fn(err, method, params, client_id, bufnr)
end
end
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..7d5f8f5ef1 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;
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 1b099fb3f4..02d233fb7b 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 = {}
@@ -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,9 +274,17 @@ 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
@@ -479,6 +548,28 @@ function M.jump_to_location(location)
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
@@ -599,7 +690,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
@@ -615,8 +706,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
@@ -670,19 +761,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")
@@ -755,7 +833,7 @@ do
local lines = {"Diagnostics:"}
local highlights = {{0, "Bold"}}
local line_diagnostics = M.get_line_diagnostics()
- if not line_diagnostics then return end
+ if vim.tbl_isempty(line_diagnostics) then return end
for i, diagnostic in ipairs(line_diagnostics) do
-- for i, mark in ipairs(marks) do
@@ -816,8 +894,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}
@@ -841,7 +918,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..5dc8d6dc10 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 == '' then
return
end
if sep == '' then
diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua
index e28cc9e20f..9c3535c676 100644
--- a/runtime/lua/vim/uri.lua
+++ b/runtime/lua/vim/uri.lua
@@ -65,9 +65,11 @@ local function uri_from_fname(path)
return table.concat(uri_parts)
end
+local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9+-.]*)://.*'
+
local function uri_from_bufnr(bufnr)
local fname = vim.api.nvim_buf_get_name(bufnr)
- local scheme = fname:match("^([a-z]+)://.*")
+ local scheme = fname:match(URI_SCHEME_PATTERN)
if scheme then
return fname
else
@@ -76,6 +78,10 @@ local function uri_from_bufnr(bufnr)
end
local function uri_to_fname(uri)
+ local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri)
+ if scheme ~= 'file' then
+ return uri
+ end
uri = uri_decode(uri)
-- TODO improve this.
if is_windows_file_uri(uri) then
@@ -89,7 +95,7 @@ end
-- Return or create a buffer for a uri.
local function uri_to_bufnr(uri)
- local scheme = assert(uri:match("^([a-z]+)://.*"), 'Uri must contain a scheme: ' .. uri)
+ local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri)
if scheme == 'file' then
return vim.fn.bufadd(uri_to_fname(uri))
else