From c83380cf80561d45c067db5e70c7275eda7671af Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 14:21:57 -0800 Subject: Add lsp.buf and hover implementation. --- runtime/lua/vim/lsp/buf.lua | 85 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 runtime/lua/vim/lsp/buf.lua (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua new file mode 100644 index 0000000000..6398bcca74 --- /dev/null +++ b/runtime/lua/vim/lsp/buf.lua @@ -0,0 +1,85 @@ +local validate = vim.validate +local api = vim.api +local util = require 'vim.lsp.util' +local protocol = require 'vim.lsp.protocol' + +local M = {} + +local function resolve_bufnr(bufnr) + validate { bufnr = { bufnr, 'n', true } } + if bufnr == nil or bufnr == 0 then + return api.nvim_get_current_buf() + end + return bufnr +end + +local function ok_or_nil(status, ...) + if not status then return end + return ... +end +local function npcall(fn, ...) + return ok_or_nil(pcall(fn, ...)) +end + +local function find_window_by_var(name, value) + for _, win in ipairs(api.nvim_list_wins()) do + if npcall(api.nvim_win_get_var, win, name) == value then + return win + end + end +end + +local hover_window_tag = 'lsp_hover' +function M.hover(bufnr) + if npcall(api.nvim_win_get_var, 0, hover_window_tag) then + api.nvim_command("wincmd p") + return + end + + bufnr = resolve_bufnr(bufnr) + do + local win = find_window_by_var(hover_window_tag, bufnr) + if win then + api.nvim_set_current_win(win) + return + end + end + local params = protocol.make_text_document_position_params() + vim.lsp.buf_request(bufnr, 'textDocument/hover', params, function(_, _, result, _) + if result == nil or vim.tbl_isempty(result) then + return + end + + if result.contents ~= nil then + local markdown_lines = util.convert_input_to_markdown_lines(result.contents) + markdown_lines = util.trim_empty_lines(markdown_lines) + if vim.tbl_isempty(markdown_lines) then + markdown_lines = { 'No information available' } + end + local filetype = util.try_trim_markdown_code_blocks(markdown_lines) + local _, winnr = util.open_floating_preview(markdown_lines, filetype) + api.nvim_win_set_var(winnr, hover_window_tag, bufnr) + end + end) +end + +function M.signature_help() +end + +function M.declaration() +end + +function M.type_definition() +end + +function M.implementation() +end + +-- TODO(ashkan) ? +function M.completion() +end + +function M.range_formatting() +end + +return M -- cgit From 2d580756ca707945c01703d404e10f4bf412a72c Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 15:35:18 -0800 Subject: Add everything to lsp.buf and get rid of autoload. --- runtime/lua/vim/lsp/buf.lua | 161 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 137 insertions(+), 24 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 6398bcca74..5f54196dcc 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -1,7 +1,9 @@ local validate = vim.validate local api = vim.api +local vfn = vim.fn local util = require 'vim.lsp.util' local protocol = require 'vim.lsp.protocol' +local log = require 'vim.lsp.log' local M = {} @@ -29,54 +31,165 @@ local function find_window_by_var(name, value) end end -local hover_window_tag = 'lsp_hover' -function M.hover(bufnr) - if npcall(api.nvim_win_get_var, 0, hover_window_tag) then - api.nvim_command("wincmd p") - return +local function request(method, params, callback) + -- TODO(ashkan) enable this. + -- callback = vim.lsp.default_callback[method] or callback + validate { + method = {method, 's'}; + callback = {callback, 'f'}; + } + return vim.lsp.buf_request(0, method, params, function(err, _, result, client_id) + if err then error(tostring(err)) end + return callback(err, method, result, client_id) + end) +end + +local function focusable_preview(method, params, fn) + if npcall(api.nvim_win_get_var, 0, method) then + return api.nvim_command("wincmd p") end - bufnr = resolve_bufnr(bufnr) + local bufnr = api.nvim_get_current_buf() do - local win = find_window_by_var(hover_window_tag, bufnr) + local win = find_window_by_var(method, bufnr) if win then - api.nvim_set_current_win(win) - return + return api.nvim_set_current_win(win) end end - local params = protocol.make_text_document_position_params() - vim.lsp.buf_request(bufnr, 'textDocument/hover', params, function(_, _, result, _) - if result == nil or vim.tbl_isempty(result) then - return + return request(method, params, function(_, _, result, _) + -- TODO(ashkan) could show error in preview... + local lines, filetype, opts = fn(result) + if lines then + local _, winnr = util.open_floating_preview(lines, filetype, opts) + api.nvim_win_set_var(winnr, method, bufnr) end + end) +end + +function M.hover() + local params = protocol.make_text_document_position_params() + focusable_preview('textDocument/hover', params, function(result) + if not (result and result.contents) then return end - if result.contents ~= nil then - local markdown_lines = util.convert_input_to_markdown_lines(result.contents) - markdown_lines = util.trim_empty_lines(markdown_lines) - if vim.tbl_isempty(markdown_lines) then - markdown_lines = { 'No information available' } - end - local filetype = util.try_trim_markdown_code_blocks(markdown_lines) - local _, winnr = util.open_floating_preview(markdown_lines, filetype) - api.nvim_win_set_var(winnr, hover_window_tag, bufnr) + local markdown_lines = util.convert_input_to_markdown_lines(result.contents) + markdown_lines = util.trim_empty_lines(markdown_lines) + if vim.tbl_isempty(markdown_lines) then + return { 'No information available' } end + return markdown_lines, util.try_trim_markdown_code_blocks(markdown_lines) end) end -function M.signature_help() +function M.peek_definition() + local params = protocol.make_text_document_position_params() + request('textDocument/peekDefinition', params, function(_, _, result, _) + if not (result and result[1]) then return end + local loc = result[1] + local bufnr = vim.uri_to_bufnr(loc.uri) or error("couldn't find file "..tostring(loc.uri)) + local start = loc.range.start + local finish = loc.range["end"] + util.open_floating_peek_preview(bufnr, start, finish, { offset_x = 1 }) + local headbuf = util.open_floating_preview({"Peek:"}, nil, { + offset_y = -(finish.line - start.line); + width = finish.character - start.character + 2; + }) + -- TODO(ashkan) change highlight group? + api.nvim_buf_add_highlight(headbuf, -1, 'Keyword', 0, -1) + end) +end + + +local function update_tagstack() + local bufnr = api.nvim_get_current_buf() + local line = vfn.line('.') + local col = vfn.col('.') + local tagname = vfn.expand('') + local item = { bufnr = bufnr, from = { bufnr, line, col, 0 }, tagname = tagname } + local winid = vfn.win_getid() + local tagstack = vfn.gettagstack(winid) + local action + if tagstack.length == tagstack.curidx then + action = 'r' + tagstack.items[tagstack.curidx] = item + elseif tagstack.length > tagstack.curidx then + action = 'r' + if tagstack.curidx > 1 then + tagstack.items = table.insert(tagstack.items[tagstack.curidx - 1], item) + else + tagstack.items = { item } + end + else + action = 'a' + tagstack.items = { item } + end + tagstack.curidx = tagstack.curidx + 1 + vfn.settagstack(winid, tagstack, action) +end +local function handle_location(result) + -- We can sometimes get a list of locations, so set the first value as the + -- only value we want to handle + -- TODO(ashkan) was this correct^? We could use location lists. + if result[1] ~= nil then + result = result[1] + end + if result.uri == nil then + api.nvim_err_writeln('[LSP] Could not find a valid location') + return + end + local result_file = vim.uri_to_fname(result.uri) + local bufnr = vfn.bufadd(result_file) + update_tagstack() + api.nvim_set_current_buf(bufnr) + local start = result.range.start + api.nvim_win_set_cursor(0, {start.line + 1, start.character}) + return true +end +local function location_callback(_, method, result) + if result == nil or vim.tbl_isempty(result) then + local _ = log.info() and log.info(method, 'No location found') + return nil + end + return handle_location(result) end function M.declaration() + local params = protocol.make_text_document_position_params() + request('textDocument/declaration', params, location_callback) +end + +function M.definition() + local params = protocol.make_text_document_position_params() + request('textDocument/definition', params, location_callback) end function M.type_definition() + local params = protocol.make_text_document_position_params() + request('textDocument/typeDefinition', params, location_callback) end function M.implementation() + local params = protocol.make_text_document_position_params() + request('textDocument/implementation', params, location_callback) +end + +function M.signature_help() + local params = protocol.make_text_document_position_params() + request('textDocument/signatureHelp', params, location_callback) end -- TODO(ashkan) ? -function M.completion() +function M.completion(context) + local params = protocol.make_text_document_position_params() + params.context = context + return request('textDocument/completion', params, function(_, _, result) + if vim.tbl_isempty(result or {}) then return end + local row, col = unpack(api.nvim_win_get_cursor(0)) + local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1]) + local line_to_cursor = line:sub(col+1) + + local matches = util.text_document_completion_list_to_complete_items(result, line_to_cursor) + vim.fn.complete(col, matches) + end) end function M.range_formatting() -- cgit From a4b7004f489030d9ee7e3bbfc156ab540744279b Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 16:03:32 -0800 Subject: Move everything to buf & default_callbacks - Rename builtin_callbacks to default_callbacks and slightly change its semantics: - No longer contains the default implementations. Instead, any default_callbacks will be used in preference for our .buf methods. - Add this to the docs. --- runtime/lua/vim/lsp/buf.lua | 86 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 5f54196dcc..e8a38aa6ef 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -33,7 +33,7 @@ end local function request(method, params, callback) -- TODO(ashkan) enable this. - -- callback = vim.lsp.default_callback[method] or callback + -- callback = vim.lsp.default_callbacks[method] or callback validate { method = {method, 's'}; callback = {callback, 'f'}; @@ -172,9 +172,78 @@ function M.implementation() request('textDocument/implementation', params, location_callback) end +--- Convert SignatureHelp response to preview contents. +-- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp +local function signature_help_to_preview_contents(input) + if not input.signatures then + return + end + --The active signature. If omitted or the value lies outside the range of + --`signatures` the value defaults to zero or is ignored if `signatures.length + --=== 0`. Whenever possible implementors should make an active decision about + --the active signature and shouldn't rely on a default value. + local contents = {} + local active_signature = input.activeSignature or 0 + -- If the activeSignature is not inside the valid range, then clip it. + if active_signature >= #input.signatures then + active_signature = 0 + end + local signature = input.signatures[active_signature + 1] + if not signature then + return + end + vim.list_extend(contents, split_lines(signature.label)) + if signature.documentation then + util.convert_input_to_markdown_lines(signature.documentation, contents) + end + if input.parameters then + local active_parameter = input.activeParameter or 0 + -- If the activeParameter is not inside the valid range, then clip it. + if active_parameter >= #input.parameters then + active_parameter = 0 + end + local parameter = signature.parameters and signature.parameters[active_parameter] + if parameter then + --[=[ + --Represents a parameter of a callable-signature. A parameter can + --have a label and a doc-comment. + interface ParameterInformation { + --The label of this parameter information. + -- + --Either a string or an inclusive start and exclusive end offsets within its containing + --signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 + --string representation as `Position` and `Range` does. + -- + --*Note*: a label of type string should be a substring of its containing signature label. + --Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. + label: string | [number, number]; + --The human-readable doc-comment of this parameter. Will be shown + --in the UI but can be omitted. + documentation?: string | MarkupContent; + } + --]=] + -- TODO highlight parameter + if parameter.documentation then + util.convert_input_to_markdown_lines(parameter.documentation, contents) + end + end + end + return contents +end + function M.signature_help() local params = protocol.make_text_document_position_params() - request('textDocument/signatureHelp', params, location_callback) + focusable_preview('textDocument/signatureHelp', params, function(result) + if not (result and result.signatures and result.signatures[1]) then return end + + -- TODO show empty popup when signatures is empty? + local lines = signature_help_to_preview_contents(result) + lines = util.trim_empty_lines(lines) + if vim.tbl_isempty(lines) then + return { 'No signature available' } + end + return lines, util.try_trim_markdown_code_blocks(lines) + end) end -- TODO(ashkan) ? @@ -195,4 +264,17 @@ end function M.range_formatting() end +function M.rename(new_name) + -- TODO(ashkan) use prepareRename + -- * result: [`Range`](#range) \| `{ range: Range, placeholder: string }` \| `null` describing the range of the string to rename and optionally a placeholder text of the string content to be renamed. If `null` is returned then it is deemed that a 'textDocument/rename' request is not valid at the given position. + local params = protocol.make_text_document_position_params() + new_name = new_name or npcall(vfn.input, "New Name: ") + if not (new_name and #new_name > 0) then return end + params.newName = new_name + request('textDocument/rename', params, function(_, _, result) + if not result then return end + util.workspace_apply_workspace_edit(result) + end) +end + return M -- cgit From c40f8600d2418dfdfeacbba3efe11ae7c6c70ad3 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 16:16:13 -0800 Subject: Change error writer to not be annoying. --- runtime/lua/vim/lsp/buf.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index e8a38aa6ef..ff045cbbfc 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -23,6 +23,11 @@ local function npcall(fn, ...) return ok_or_nil(pcall(fn, ...)) end +local function err_message(...) + api.nvim_err_writeln(table.concat(vim.tbl_flatten{...})) + api.nvim_command("redraw") +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 @@ -133,7 +138,7 @@ local function handle_location(result) result = result[1] end if result.uri == nil then - api.nvim_err_writeln('[LSP] Could not find a valid location') + err_message('[LSP] Could not find a valid location') return end local result_file = vim.uri_to_fname(result.uri) -- cgit From 1e16b3cf281bec73ccbd155dd11b2db048d1219a Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 16:16:36 -0800 Subject: Spaces not tabs. --- runtime/lua/vim/lsp/buf.lua | 201 ++++++++++++++++++++++---------------------- 1 file changed, 101 insertions(+), 100 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index ff045cbbfc..8b21370800 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -16,11 +16,11 @@ local function resolve_bufnr(bufnr) end local function ok_or_nil(status, ...) - if not status then return end - return ... + if not status then return end + return ... end local function npcall(fn, ...) - return ok_or_nil(pcall(fn, ...)) + return ok_or_nil(pcall(fn, ...)) end local function err_message(...) @@ -29,78 +29,78 @@ local function err_message(...) end local function find_window_by_var(name, value) - for _, win in ipairs(api.nvim_list_wins()) do - if npcall(api.nvim_win_get_var, win, name) == value then - return win - end - end + for _, win in ipairs(api.nvim_list_wins()) do + if npcall(api.nvim_win_get_var, win, name) == value then + return win + end + end end local function request(method, params, callback) - -- TODO(ashkan) enable this. - -- callback = vim.lsp.default_callbacks[method] or callback - validate { - method = {method, 's'}; - callback = {callback, 'f'}; - } - return vim.lsp.buf_request(0, method, params, function(err, _, result, client_id) - if err then error(tostring(err)) end - return callback(err, method, result, client_id) - end) + -- TODO(ashkan) enable this. + -- callback = vim.lsp.default_callbacks[method] or callback + validate { + method = {method, 's'}; + callback = {callback, 'f'}; + } + return vim.lsp.buf_request(0, method, params, function(err, _, result, client_id) + if err then error(tostring(err)) end + return callback(err, method, result, client_id) + end) end local function focusable_preview(method, params, fn) - if npcall(api.nvim_win_get_var, 0, method) then - return api.nvim_command("wincmd p") - end + if npcall(api.nvim_win_get_var, 0, method) then + return api.nvim_command("wincmd p") + end - local bufnr = api.nvim_get_current_buf() - do - local win = find_window_by_var(method, bufnr) - if win then - return api.nvim_set_current_win(win) - end - end - return request(method, params, function(_, _, result, _) - -- TODO(ashkan) could show error in preview... - local lines, filetype, opts = fn(result) - if lines then - local _, winnr = util.open_floating_preview(lines, filetype, opts) - api.nvim_win_set_var(winnr, method, bufnr) - end - end) + local bufnr = api.nvim_get_current_buf() + do + local win = find_window_by_var(method, bufnr) + if win then + return api.nvim_set_current_win(win) + end + end + return request(method, params, function(_, _, result, _) + -- TODO(ashkan) could show error in preview... + local lines, filetype, opts = fn(result) + if lines then + local _, winnr = util.open_floating_preview(lines, filetype, opts) + api.nvim_win_set_var(winnr, method, bufnr) + end + end) end function M.hover() - local params = protocol.make_text_document_position_params() - focusable_preview('textDocument/hover', params, function(result) - if not (result and result.contents) then return end + local params = protocol.make_text_document_position_params() + focusable_preview('textDocument/hover', params, function(result) + if not (result and result.contents) then return end - local markdown_lines = util.convert_input_to_markdown_lines(result.contents) - markdown_lines = util.trim_empty_lines(markdown_lines) - if vim.tbl_isempty(markdown_lines) then - return { 'No information available' } - end - return markdown_lines, util.try_trim_markdown_code_blocks(markdown_lines) - end) + local markdown_lines = util.convert_input_to_markdown_lines(result.contents) + markdown_lines = util.trim_empty_lines(markdown_lines) + if vim.tbl_isempty(markdown_lines) then + return { 'No information available' } + end + return markdown_lines, util.try_trim_markdown_code_blocks(markdown_lines) + end) end function M.peek_definition() - local params = protocol.make_text_document_position_params() - request('textDocument/peekDefinition', params, function(_, _, result, _) - if not (result and result[1]) then return end - local loc = result[1] - local bufnr = vim.uri_to_bufnr(loc.uri) or error("couldn't find file "..tostring(loc.uri)) - local start = loc.range.start - local finish = loc.range["end"] - util.open_floating_peek_preview(bufnr, start, finish, { offset_x = 1 }) - local headbuf = util.open_floating_preview({"Peek:"}, nil, { - offset_y = -(finish.line - start.line); - width = finish.character - start.character + 2; - }) - -- TODO(ashkan) change highlight group? - api.nvim_buf_add_highlight(headbuf, -1, 'Keyword', 0, -1) - end) + local params = protocol.make_text_document_position_params() + request('textDocument/peekDefinition', params, function(_, _, result, _) + if not (result and result[1]) then return end + local loc = result[1] + local bufnr = vim.uri_to_bufnr(loc.uri) or error("couldn't find file "..tostring(loc.uri)) + local start = loc.range.start + local finish = loc.range["end"] + util.open_floating_peek_preview(bufnr, start, finish, { offset_x = 1 }) + local headbuf = util.open_floating_preview({"Peek:"}, nil, { + offset_y = -(finish.line - start.line); + width = finish.character - start.character + 2; + }) + -- TODO(ashkan) change highlight group? + api.nvim_buf_add_highlight(headbuf, -1, 'Keyword', 0, -1) + end) end @@ -158,23 +158,23 @@ local function location_callback(_, method, result) end function M.declaration() - local params = protocol.make_text_document_position_params() - request('textDocument/declaration', params, location_callback) + local params = protocol.make_text_document_position_params() + request('textDocument/declaration', params, location_callback) end function M.definition() - local params = protocol.make_text_document_position_params() - request('textDocument/definition', params, location_callback) + local params = protocol.make_text_document_position_params() + request('textDocument/definition', params, location_callback) end function M.type_definition() - local params = protocol.make_text_document_position_params() - request('textDocument/typeDefinition', params, location_callback) + local params = protocol.make_text_document_position_params() + request('textDocument/typeDefinition', params, location_callback) end function M.implementation() - local params = protocol.make_text_document_position_params() - request('textDocument/implementation', params, location_callback) + local params = protocol.make_text_document_position_params() + request('textDocument/implementation', params, location_callback) end --- Convert SignatureHelp response to preview contents. @@ -237,49 +237,50 @@ local function signature_help_to_preview_contents(input) end function M.signature_help() - local params = protocol.make_text_document_position_params() - focusable_preview('textDocument/signatureHelp', params, function(result) - if not (result and result.signatures and result.signatures[1]) then return end + local params = protocol.make_text_document_position_params() + focusable_preview('textDocument/signatureHelp', params, function(result) + if not (result and result.signatures and result.signatures[1]) then return end - -- TODO show empty popup when signatures is empty? - local lines = signature_help_to_preview_contents(result) - lines = util.trim_empty_lines(lines) - if vim.tbl_isempty(lines) then - return { 'No signature available' } - end - return lines, util.try_trim_markdown_code_blocks(lines) - end) + -- TODO show empty popup when signatures is empty? + local lines = signature_help_to_preview_contents(result) + lines = util.trim_empty_lines(lines) + if vim.tbl_isempty(lines) then + return { 'No signature available' } + end + return lines, util.try_trim_markdown_code_blocks(lines) + end) end -- TODO(ashkan) ? function M.completion(context) - local params = protocol.make_text_document_position_params() - params.context = context - return request('textDocument/completion', params, function(_, _, result) - if vim.tbl_isempty(result or {}) then return end - local row, col = unpack(api.nvim_win_get_cursor(0)) - local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1]) - local line_to_cursor = line:sub(col+1) + local params = protocol.make_text_document_position_params() + params.context = context + return request('textDocument/completion', params, function(_, _, result) + if vim.tbl_isempty(result or {}) then return end + local row, col = unpack(api.nvim_win_get_cursor(0)) + local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1]) + local line_to_cursor = line:sub(col+1) - local matches = util.text_document_completion_list_to_complete_items(result, line_to_cursor) - vim.fn.complete(col, matches) - end) + local matches = util.text_document_completion_list_to_complete_items(result, line_to_cursor) + vim.fn.complete(col, matches) + end) end function M.range_formatting() end function M.rename(new_name) - -- TODO(ashkan) use prepareRename - -- * result: [`Range`](#range) \| `{ range: Range, placeholder: string }` \| `null` describing the range of the string to rename and optionally a placeholder text of the string content to be renamed. If `null` is returned then it is deemed that a 'textDocument/rename' request is not valid at the given position. - local params = protocol.make_text_document_position_params() - new_name = new_name or npcall(vfn.input, "New Name: ") - if not (new_name and #new_name > 0) then return end - params.newName = new_name - request('textDocument/rename', params, function(_, _, result) - if not result then return end - util.workspace_apply_workspace_edit(result) - end) + -- 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 = protocol.make_text_document_position_params() + new_name = new_name or npcall(vfn.input, "New Name: ") + if not (new_name and #new_name > 0) then return end + params.newName = new_name + request('textDocument/rename', params, function(_, _, result) + if not result then return end + util.workspace_apply_workspace_edit(result) + end) end return M +-- vim:sw=2 ts=2 et -- cgit From 93beae4f31d42dc70c874020011220444d7f979c Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 16:35:11 -0800 Subject: Fix rename support. --- runtime/lua/vim/lsp/buf.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 8b21370800..01174a1e48 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -44,6 +44,7 @@ local function request(method, params, callback) callback = {callback, 'f'}; } return vim.lsp.buf_request(0, method, params, function(err, _, result, client_id) + local _ = log.debug() and log.debug("vim.lsp.buf", method, client_id, err, result) if err then error(tostring(err)) end return callback(err, method, result, client_id) end) -- cgit From 4c7ef3754a7679a7f095fa9e739a665ead4c71b4 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 16:37:23 -0800 Subject: Satisfy lualint. --- runtime/lua/vim/lsp/buf.lua | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 01174a1e48..1c2e60f1ca 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -7,14 +7,6 @@ local log = require 'vim.lsp.log' local M = {} -local function resolve_bufnr(bufnr) - validate { bufnr = { bufnr, 'n', true } } - if bufnr == nil or bufnr == 0 then - return api.nvim_get_current_buf() - end - return bufnr -end - local function ok_or_nil(status, ...) if not status then return end return ... @@ -198,7 +190,7 @@ local function signature_help_to_preview_contents(input) if not signature then return end - vim.list_extend(contents, split_lines(signature.label)) + vim.list_extend(contents, vim.split(signature.label, '\n', true)) if signature.documentation then util.convert_input_to_markdown_lines(signature.documentation, contents) end -- cgit From 6fc409d5939500b038ba281ac1929fd053f35310 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 20:51:44 -0800 Subject: Add full text_edit implementation. - Implements textDocument/formatting, textDocument/rangeFormatting, workspace/applyEdit. TODO: - still has edge cases around replacement probably. Only tested with inserts on the same position. --- runtime/lua/vim/lsp/buf.lua | 122 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 1c2e60f1ca..ea88207b5b 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -259,7 +259,127 @@ function M.completion(context) end) end -function M.range_formatting() +local function apply_edit_to_lines(lines, start_pos, end_pos, new_lines) + -- 0-indexing to 1-indexing makes things look a bit worse. + local i_0 = start_pos[1] + 1 + local i_n = end_pos[1] + 1 + local n = i_n - i_0 + 1 + if not lines[i_0] or not lines[i_n] then + error(vim.inspect{#lines, i_0, i_n, n, start_pos, end_pos, new_lines}) + end + local prefix = "" + local suffix = lines[i_n]:sub(end_pos[2]+1) + lines[i_n] = lines[i_n]:sub(1, end_pos[2]+1) + if start_pos[2] > 0 then + prefix = lines[i_0]:sub(1, start_pos[2]) + -- lines[i_0] = lines[i_0]:sub(start.character+1) + end + -- TODO(ashkan) figure out how to avoid copy here. likely by changing algo. + new_lines = vim.list_extend({}, new_lines) + if #suffix > 0 then + new_lines[#new_lines] = new_lines[#new_lines]..suffix + end + if #prefix > 0 then + new_lines[1] = prefix..new_lines[1] + end + if #new_lines >= n then + for i = 1, n do + lines[i + i_0 - 1] = new_lines[i] + end + for i = n+1,#new_lines do + table.insert(lines, i_n + 1, new_lines[i]) + end + else + for i = 1, #new_lines do + lines[i + i_0 - 1] = new_lines[i] + end + for _ = #new_lines+1, n do + table.remove(lines, i_0 + #new_lines + 1) + end + end +end + +local function apply_text_edits(text_edits, bufnr) + if not next(text_edits) then return end + -- nvim.print("Start", #text_edits) + local start_line, finish_line = math.huge, -1 + local cleaned = {} + for _, e in ipairs(text_edits) do + start_line = math.min(e.range.start.line, start_line) + finish_line = math.max(e.range["end"].line, finish_line) + table.insert(cleaned, { + A = {e.range.start.line; e.range.start.character}; + B = {e.range["end"].line; e.range["end"].character}; + lines = vim.split(e.newText, '\n', true); + }) + end + local lines = api.nvim_buf_get_lines(bufnr, start_line, finish_line + 1, false) + for i, e in ipairs(cleaned) do + -- nvim.print(i, "e", e.A, e.B, #e.lines[#e.lines], e.lines) + local y = 0 + local x = 0 + -- TODO(ashkan) this could be done in O(n) with dynamic programming + for j = 1, i-1 do + local o = cleaned[j] + -- nvim.print(i, "o", o.A, o.B, x, y, #o.lines[#o.lines], o.lines) + if o.A[1] <= e.A[1] and o.A[2] <= e.A[2] then + y = y - (o.B[1] - o.A[1] + 1) + #o.lines + -- Same line + if #o.lines > 1 then + x = -e.A[2] + #o.lines[#o.lines] + else + if o.A[1] == e.A[1] then + -- Try to account for insertions. + -- TODO how to account for deletions? + x = x - (o.B[2] - o.A[2]) + #o.lines[#o.lines] + end + end + end + end + local A = {e.A[1] + y - start_line, e.A[2] + x} + local B = {e.B[1] + y - start_line, e.B[2] + x} + -- if x ~= 0 or y ~= 0 then + -- nvim.print(i, "_", e.A, e.B, y, x, A, B, e.lines) + -- end + apply_edit_to_lines(lines, A, B, e.lines) + end + api.nvim_buf_set_lines(bufnr, start_line, finish_line + 1, false, lines) +end + +function M.formatting(options) + validate { options = {options, 't', true} } + local params = { + textDocument = { uri = vim.uri_from_bufnr(0) }; + options = options or {}; + } + params.options[vim.type_idx] = vim.types.dictionary + return request('textDocument/formatting', params, function(_, _, result) + if not result then return end + apply_text_edits(result) + end) +end + +function M.range_formatting(options, start_pos, end_pos) + validate { + options = {options, 't', true}; + start_pos = {start_pos, 't', true}; + end_pos = {end_pos, 't', true}; + } + start_pos = start_pos or vim.api.nvim_buf_get_mark(0, '<') + end_pos = end_pos or vim.api.nvim_buf_get_mark(0, '>') + local params = { + textDocument = { uri = vim.uri_from_bufnr(0) }; + range = { + start = { line = start_pos[1]; character = start_pos[2]; }; + ["end"] = { line = end_pos[1]; character = end_pos[2]; }; + }; + options = options or {}; + } + params.options[vim.type_idx] = vim.types.dictionary + return request('textDocument/rangeFormatting', params, function(_, _, result) + if not result then return end + apply_text_edits(result) + end) end function M.rename(new_name) -- cgit From 6d9f48ddcf3a4d0fbed0674ce180bcfb9340bb22 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 20:57:21 -0800 Subject: Fix reference in rename. --- runtime/lua/vim/lsp/buf.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index ea88207b5b..68af34dac6 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -391,7 +391,7 @@ function M.rename(new_name) params.newName = new_name request('textDocument/rename', params, function(_, _, result) if not result then return end - util.workspace_apply_workspace_edit(result) + util.apply_workspace_edit(result) end) end -- cgit From 7bf766ad0903574b6202af0d2e11d306d656b491 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Wed, 20 Nov 2019 20:59:12 -0800 Subject: Use the apply_text_edits from util. --- runtime/lua/vim/lsp/buf.lua | 91 +-------------------------------------------- 1 file changed, 2 insertions(+), 89 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 68af34dac6..260bca281f 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -259,93 +259,6 @@ function M.completion(context) end) end -local function apply_edit_to_lines(lines, start_pos, end_pos, new_lines) - -- 0-indexing to 1-indexing makes things look a bit worse. - local i_0 = start_pos[1] + 1 - local i_n = end_pos[1] + 1 - local n = i_n - i_0 + 1 - if not lines[i_0] or not lines[i_n] then - error(vim.inspect{#lines, i_0, i_n, n, start_pos, end_pos, new_lines}) - end - local prefix = "" - local suffix = lines[i_n]:sub(end_pos[2]+1) - lines[i_n] = lines[i_n]:sub(1, end_pos[2]+1) - if start_pos[2] > 0 then - prefix = lines[i_0]:sub(1, start_pos[2]) - -- lines[i_0] = lines[i_0]:sub(start.character+1) - end - -- TODO(ashkan) figure out how to avoid copy here. likely by changing algo. - new_lines = vim.list_extend({}, new_lines) - if #suffix > 0 then - new_lines[#new_lines] = new_lines[#new_lines]..suffix - end - if #prefix > 0 then - new_lines[1] = prefix..new_lines[1] - end - if #new_lines >= n then - for i = 1, n do - lines[i + i_0 - 1] = new_lines[i] - end - for i = n+1,#new_lines do - table.insert(lines, i_n + 1, new_lines[i]) - end - else - for i = 1, #new_lines do - lines[i + i_0 - 1] = new_lines[i] - end - for _ = #new_lines+1, n do - table.remove(lines, i_0 + #new_lines + 1) - end - end -end - -local function apply_text_edits(text_edits, bufnr) - if not next(text_edits) then return end - -- nvim.print("Start", #text_edits) - local start_line, finish_line = math.huge, -1 - local cleaned = {} - for _, e in ipairs(text_edits) do - start_line = math.min(e.range.start.line, start_line) - finish_line = math.max(e.range["end"].line, finish_line) - table.insert(cleaned, { - A = {e.range.start.line; e.range.start.character}; - B = {e.range["end"].line; e.range["end"].character}; - lines = vim.split(e.newText, '\n', true); - }) - end - local lines = api.nvim_buf_get_lines(bufnr, start_line, finish_line + 1, false) - for i, e in ipairs(cleaned) do - -- nvim.print(i, "e", e.A, e.B, #e.lines[#e.lines], e.lines) - local y = 0 - local x = 0 - -- TODO(ashkan) this could be done in O(n) with dynamic programming - for j = 1, i-1 do - local o = cleaned[j] - -- nvim.print(i, "o", o.A, o.B, x, y, #o.lines[#o.lines], o.lines) - if o.A[1] <= e.A[1] and o.A[2] <= e.A[2] then - y = y - (o.B[1] - o.A[1] + 1) + #o.lines - -- Same line - if #o.lines > 1 then - x = -e.A[2] + #o.lines[#o.lines] - else - if o.A[1] == e.A[1] then - -- Try to account for insertions. - -- TODO how to account for deletions? - x = x - (o.B[2] - o.A[2]) + #o.lines[#o.lines] - end - end - end - end - local A = {e.A[1] + y - start_line, e.A[2] + x} - local B = {e.B[1] + y - start_line, e.B[2] + x} - -- if x ~= 0 or y ~= 0 then - -- nvim.print(i, "_", e.A, e.B, y, x, A, B, e.lines) - -- end - apply_edit_to_lines(lines, A, B, e.lines) - end - api.nvim_buf_set_lines(bufnr, start_line, finish_line + 1, false, lines) -end - function M.formatting(options) validate { options = {options, 't', true} } local params = { @@ -355,7 +268,7 @@ function M.formatting(options) params.options[vim.type_idx] = vim.types.dictionary return request('textDocument/formatting', params, function(_, _, result) if not result then return end - apply_text_edits(result) + util.apply_text_edits(result) end) end @@ -378,7 +291,7 @@ function M.range_formatting(options, start_pos, end_pos) params.options[vim.type_idx] = vim.types.dictionary return request('textDocument/rangeFormatting', params, function(_, _, result) if not result then return end - apply_text_edits(result) + util.apply_text_edits(result) end) end -- cgit From bcae04f6c62b23104e85bb08c54a16d0cdc33852 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Thu, 21 Nov 2019 15:19:06 -0800 Subject: Updates - Use correct implementation of text_edits. - Send indent options to rangeFormatting and formatting. - Remove references to vim bindings and filetype from lsp.txt - Add more examples to docs. - Add before_init to allow changing initialize_params. --- runtime/lua/vim/lsp/buf.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 260bca281f..dbc141a906 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -261,11 +261,14 @@ end function M.formatting(options) validate { options = {options, 't', true} } + options = vim.tbl_extend('keep', options or {}, { + tabSize = api.nvim_buf_get_option(0, 'tabstop'); + insertSpaces = api.nvim_buf_get_option(0, 'expandtab'); + }) local params = { textDocument = { uri = vim.uri_from_bufnr(0) }; - options = options or {}; + options = options; } - params.options[vim.type_idx] = vim.types.dictionary return request('textDocument/formatting', params, function(_, _, result) if not result then return end util.apply_text_edits(result) @@ -278,6 +281,10 @@ function M.range_formatting(options, start_pos, end_pos) start_pos = {start_pos, 't', true}; end_pos = {end_pos, 't', true}; } + options = vim.tbl_extend('keep', options or {}, { + tabSize = api.nvim_buf_get_option(0, 'tabstop'); + insertSpaces = api.nvim_buf_get_option(0, 'expandtab'); + }) start_pos = start_pos or vim.api.nvim_buf_get_mark(0, '<') end_pos = end_pos or vim.api.nvim_buf_get_mark(0, '>') local params = { @@ -286,9 +293,8 @@ function M.range_formatting(options, start_pos, end_pos) start = { line = start_pos[1]; character = start_pos[2]; }; ["end"] = { line = end_pos[1]; character = end_pos[2]; }; }; - options = options or {}; + options = options; } - params.options[vim.type_idx] = vim.types.dictionary return request('textDocument/rangeFormatting', params, function(_, _, result) if not result then return end util.apply_text_edits(result) -- cgit From da07e71b189a51008a93783b5f7894dc495c277e Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Thu, 21 Nov 2019 15:34:28 -0800 Subject: Account for character length in jump position. --- runtime/lua/vim/lsp/buf.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index dbc141a906..0b4ac6ca9b 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -134,12 +134,14 @@ local function handle_location(result) err_message('[LSP] Could not find a valid location') return end - local result_file = vim.uri_to_fname(result.uri) - local bufnr = vfn.bufadd(result_file) + local bufnr = vim.uri_to_bufnr(result.uri) update_tagstack() api.nvim_set_current_buf(bufnr) - local start = result.range.start - api.nvim_win_set_cursor(0, {start.line + 1, start.character}) + local row = result.range.start.line + local col = result.range.start.character + local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] + col = #line:sub(1, col) + api.nvim_win_set_cursor(0, {row + 1, col}) return true end local function location_callback(_, method, result) -- cgit From 43d73ee884f97861c44adab4797a8fc185af1436 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Thu, 21 Nov 2019 15:41:32 -0800 Subject: Fix position params for encoding. --- runtime/lua/vim/lsp/buf.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 0b4ac6ca9b..ed26e80c3c 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -65,7 +65,7 @@ local function focusable_preview(method, params, fn) end function M.hover() - local params = protocol.make_text_document_position_params() + local params = util.make_position_params() focusable_preview('textDocument/hover', params, function(result) if not (result and result.contents) then return end @@ -79,7 +79,7 @@ function M.hover() end function M.peek_definition() - local params = protocol.make_text_document_position_params() + local params = util.make_position_params() request('textDocument/peekDefinition', params, function(_, _, result, _) if not (result and result[1]) then return end local loc = result[1] @@ -153,22 +153,22 @@ local function location_callback(_, method, result) end function M.declaration() - local params = protocol.make_text_document_position_params() + local params = util.make_position_params() request('textDocument/declaration', params, location_callback) end function M.definition() - local params = protocol.make_text_document_position_params() + local params = util.make_position_params() request('textDocument/definition', params, location_callback) end function M.type_definition() - local params = protocol.make_text_document_position_params() + local params = util.make_position_params() request('textDocument/typeDefinition', params, location_callback) end function M.implementation() - local params = protocol.make_text_document_position_params() + local params = util.make_position_params() request('textDocument/implementation', params, location_callback) end @@ -232,7 +232,7 @@ local function signature_help_to_preview_contents(input) end function M.signature_help() - local params = protocol.make_text_document_position_params() + local params = util.make_position_params() focusable_preview('textDocument/signatureHelp', params, function(result) if not (result and result.signatures and result.signatures[1]) then return end @@ -248,7 +248,7 @@ end -- TODO(ashkan) ? function M.completion(context) - local params = protocol.make_text_document_position_params() + local params = util.make_position_params() params.context = context return request('textDocument/completion', params, function(_, _, result) if vim.tbl_isempty(result or {}) then return end @@ -306,7 +306,7 @@ end function M.rename(new_name) -- TODO(ashkan) use prepareRename -- * result: [`Range`](#range) \| `{ range: Range, placeholder: string }` \| `null` describing the range of the string to rename and optionally a placeholder text of the string content to be renamed. If `null` is returned then it is deemed that a 'textDocument/rename' request is not valid at the given position. - local params = protocol.make_text_document_position_params() + local params = util.make_position_params() new_name = new_name or npcall(vfn.input, "New Name: ") if not (new_name and #new_name > 0) then return end params.newName = new_name -- cgit From a3d67dac5f9de6099890ae4b9fab2b70b2279a29 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Thu, 21 Nov 2019 16:23:12 -0800 Subject: Fix encoding translation in other places. --- runtime/lua/vim/lsp/buf.lua | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index ed26e80c3c..b4e0b9cbfc 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -2,8 +2,8 @@ local validate = vim.validate local api = vim.api local vfn = vim.fn local util = require 'vim.lsp.util' -local protocol = require 'vim.lsp.protocol' local log = require 'vim.lsp.log' +local list_extend = vim.list_extend local M = {} @@ -192,7 +192,7 @@ local function signature_help_to_preview_contents(input) if not signature then return end - vim.list_extend(contents, vim.split(signature.label, '\n', true)) + list_extend(contents, vim.split(signature.label, '\n', true)) if signature.documentation then util.convert_input_to_markdown_lines(signature.documentation, contents) end @@ -287,13 +287,23 @@ function M.range_formatting(options, start_pos, end_pos) tabSize = api.nvim_buf_get_option(0, 'tabstop'); insertSpaces = api.nvim_buf_get_option(0, 'expandtab'); }) - start_pos = start_pos or vim.api.nvim_buf_get_mark(0, '<') - end_pos = end_pos or vim.api.nvim_buf_get_mark(0, '>') + local A = list_extend({}, start_pos or api.nvim_buf_get_mark(0, '<')) + local B = list_extend({}, end_pos or api.nvim_buf_get_mark(0, '>')) + -- convert to 0-index + A[1] = A[1] - 1 + B[1] = B[1] - 1 + -- account for encoding. + if A[2] > 0 then + A = {A[1], util.character_offset(0, unpack(A))} + end + if B[2] > 0 then + B = {B[1], util.character_offset(0, unpack(B))} + end local params = { textDocument = { uri = vim.uri_from_bufnr(0) }; range = { - start = { line = start_pos[1]; character = start_pos[2]; }; - ["end"] = { line = end_pos[1]; character = end_pos[2]; }; + start = { line = A[1]; character = A[2]; }; + ["end"] = { line = B[1]; character = B[2]; }; }; options = options; } -- cgit From d410812311f7b462420690455914ea1316953b3a Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Sat, 23 Nov 2019 16:14:24 -0800 Subject: UI tweaks. - Hide diagnostics on client exit - Stop insert on popup focus. - Hide popup on insertchar (for signature_help) --- runtime/lua/vim/lsp/buf.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index b4e0b9cbfc..79d43fda4a 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -51,7 +51,9 @@ local function focusable_preview(method, params, fn) do local win = find_window_by_var(method, bufnr) if win then - return api.nvim_set_current_win(win) + api.nvim_set_current_win(win) + api.nvim_command("stopinsert") + return end end return request(method, params, function(_, _, result, _) @@ -234,7 +236,9 @@ end function M.signature_help() local params = util.make_position_params() focusable_preview('textDocument/signatureHelp', params, function(result) - if not (result and result.signatures and result.signatures[1]) then return end + if not (result and result.signatures and result.signatures[1]) then + return { 'No signature available' } + end -- TODO show empty popup when signatures is empty? local lines = signature_help_to_preview_contents(result) -- cgit From b35f6aa9dd686d082405132e3455cd0aff334361 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Sun, 24 Nov 2019 03:01:18 -0800 Subject: Add support for textDocument/references. Add set_qflist and set_loclist. - Also add locations_to_items, which calculates byte offsets for character positions in files and avoids unnecessary operations. --- runtime/lua/vim/lsp/buf.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 79d43fda4a..51295e5570 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -1,3 +1,4 @@ +local vim = vim local validate = vim.validate local api = vim.api local vfn = vim.fn @@ -330,5 +331,19 @@ function M.rename(new_name) end) end +function M.references(context) + validate { context = { context, 't', true } } + local params = util.make_position_params() + params.context = context or { + includeDeclaration = true; + } + params[vim.type_idx] = vim.types.dictionary + request('textDocument/references', params, function(_, _, result) + if not result then return end + util.set_qflist(result) + vim.api.nvim_command("copen") + end) +end + return M -- vim:sw=2 ts=2 et -- cgit From 6e8c5779cf960893850501e4871dc9be671db298 Mon Sep 17 00:00:00 2001 From: Ashkan Kiani Date: Tue, 26 Nov 2019 05:59:40 -0800 Subject: LSP: Move default buf callbacks to vim.lsp.callbacks (#11452) - In the process, refactored focusable_preview to a util function. - Add text for locations_to_items of the current line. - Improve location callback to handle multiple return values by using set_qflist. - Remove update_tagstack and leave note for future travelers. --- runtime/lua/vim/lsp/buf.lua | 253 ++++---------------------------------------- 1 file changed, 20 insertions(+), 233 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 51295e5570..a6a05fb095 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -3,7 +3,6 @@ local validate = vim.validate local api = vim.api local vfn = vim.fn local util = require 'vim.lsp.util' -local log = require 'vim.lsp.log' local list_extend = vim.list_extend local M = {} @@ -16,270 +15,68 @@ local function npcall(fn, ...) return ok_or_nil(pcall(fn, ...)) end -local function err_message(...) - api.nvim_err_writeln(table.concat(vim.tbl_flatten{...})) - api.nvim_command("redraw") -end - -local function find_window_by_var(name, value) - for _, win in ipairs(api.nvim_list_wins()) do - if npcall(api.nvim_win_get_var, win, name) == value then - return win - end - end -end - local function request(method, params, callback) - -- TODO(ashkan) enable this. - -- callback = vim.lsp.default_callbacks[method] or callback validate { method = {method, 's'}; - callback = {callback, 'f'}; + callback = {callback, 'f', true}; } - return vim.lsp.buf_request(0, method, params, function(err, _, result, client_id) - local _ = log.debug() and log.debug("vim.lsp.buf", method, client_id, err, result) - if err then error(tostring(err)) end - return callback(err, method, result, client_id) - end) -end - -local function focusable_preview(method, params, fn) - if npcall(api.nvim_win_get_var, 0, method) then - return api.nvim_command("wincmd p") - end - - local bufnr = api.nvim_get_current_buf() - do - local win = find_window_by_var(method, bufnr) - if win then - api.nvim_set_current_win(win) - api.nvim_command("stopinsert") - return - end - end - return request(method, params, function(_, _, result, _) - -- TODO(ashkan) could show error in preview... - local lines, filetype, opts = fn(result) - if lines then - local _, winnr = util.open_floating_preview(lines, filetype, opts) - api.nvim_win_set_var(winnr, method, bufnr) - end - end) + return vim.lsp.buf_request(0, method, params, callback) end function M.hover() local params = util.make_position_params() - focusable_preview('textDocument/hover', params, function(result) - if not (result and result.contents) then return end - - local markdown_lines = util.convert_input_to_markdown_lines(result.contents) - markdown_lines = util.trim_empty_lines(markdown_lines) - if vim.tbl_isempty(markdown_lines) then - return { 'No information available' } - end - return markdown_lines, util.try_trim_markdown_code_blocks(markdown_lines) - end) + request('textDocument/hover', params) end function M.peek_definition() local params = util.make_position_params() - request('textDocument/peekDefinition', params, function(_, _, result, _) - if not (result and result[1]) then return end - local loc = result[1] - local bufnr = vim.uri_to_bufnr(loc.uri) or error("couldn't find file "..tostring(loc.uri)) - local start = loc.range.start - local finish = loc.range["end"] - util.open_floating_peek_preview(bufnr, start, finish, { offset_x = 1 }) - local headbuf = util.open_floating_preview({"Peek:"}, nil, { - offset_y = -(finish.line - start.line); - width = finish.character - start.character + 2; - }) - -- TODO(ashkan) change highlight group? - api.nvim_buf_add_highlight(headbuf, -1, 'Keyword', 0, -1) - end) + request('textDocument/peekDefinition', params) end -local function update_tagstack() - local bufnr = api.nvim_get_current_buf() - local line = vfn.line('.') - local col = vfn.col('.') - local tagname = vfn.expand('') - local item = { bufnr = bufnr, from = { bufnr, line, col, 0 }, tagname = tagname } - local winid = vfn.win_getid() - local tagstack = vfn.gettagstack(winid) - local action - if tagstack.length == tagstack.curidx then - action = 'r' - tagstack.items[tagstack.curidx] = item - elseif tagstack.length > tagstack.curidx then - action = 'r' - if tagstack.curidx > 1 then - tagstack.items = table.insert(tagstack.items[tagstack.curidx - 1], item) - else - tagstack.items = { item } - end - else - action = 'a' - tagstack.items = { item } - end - tagstack.curidx = tagstack.curidx + 1 - vfn.settagstack(winid, tagstack, action) -end -local function handle_location(result) - -- We can sometimes get a list of locations, so set the first value as the - -- only value we want to handle - -- TODO(ashkan) was this correct^? We could use location lists. - if result[1] ~= nil then - result = result[1] - end - if result.uri == nil then - err_message('[LSP] Could not find a valid location') - return - end - local bufnr = vim.uri_to_bufnr(result.uri) - update_tagstack() - api.nvim_set_current_buf(bufnr) - local row = result.range.start.line - local col = result.range.start.character - local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] - col = #line:sub(1, col) - api.nvim_win_set_cursor(0, {row + 1, col}) - return true -end -local function location_callback(_, method, result) - if result == nil or vim.tbl_isempty(result) then - local _ = log.info() and log.info(method, 'No location found') - return nil - end - return handle_location(result) -end - function M.declaration() local params = util.make_position_params() - request('textDocument/declaration', params, location_callback) + request('textDocument/declaration', params) end function M.definition() local params = util.make_position_params() - request('textDocument/definition', params, location_callback) + request('textDocument/definition', params) end function M.type_definition() local params = util.make_position_params() - request('textDocument/typeDefinition', params, location_callback) + request('textDocument/typeDefinition', params) end function M.implementation() local params = util.make_position_params() - request('textDocument/implementation', params, location_callback) -end - ---- Convert SignatureHelp response to preview contents. --- https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#textDocument_signatureHelp -local function signature_help_to_preview_contents(input) - if not input.signatures then - return - end - --The active signature. If omitted or the value lies outside the range of - --`signatures` the value defaults to zero or is ignored if `signatures.length - --=== 0`. Whenever possible implementors should make an active decision about - --the active signature and shouldn't rely on a default value. - local contents = {} - local active_signature = input.activeSignature or 0 - -- If the activeSignature is not inside the valid range, then clip it. - if active_signature >= #input.signatures then - active_signature = 0 - end - local signature = input.signatures[active_signature + 1] - if not signature then - return - end - list_extend(contents, vim.split(signature.label, '\n', true)) - if signature.documentation then - util.convert_input_to_markdown_lines(signature.documentation, contents) - end - if input.parameters then - local active_parameter = input.activeParameter or 0 - -- If the activeParameter is not inside the valid range, then clip it. - if active_parameter >= #input.parameters then - active_parameter = 0 - end - local parameter = signature.parameters and signature.parameters[active_parameter] - if parameter then - --[=[ - --Represents a parameter of a callable-signature. A parameter can - --have a label and a doc-comment. - interface ParameterInformation { - --The label of this parameter information. - -- - --Either a string or an inclusive start and exclusive end offsets within its containing - --signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 - --string representation as `Position` and `Range` does. - -- - --*Note*: a label of type string should be a substring of its containing signature label. - --Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. - label: string | [number, number]; - --The human-readable doc-comment of this parameter. Will be shown - --in the UI but can be omitted. - documentation?: string | MarkupContent; - } - --]=] - -- TODO highlight parameter - if parameter.documentation then - util.convert_input_to_markdown_lines(parameter.documentation, contents) - end - end - end - return contents + request('textDocument/implementation', params) end function M.signature_help() local params = util.make_position_params() - focusable_preview('textDocument/signatureHelp', params, function(result) - if not (result and result.signatures and result.signatures[1]) then - return { 'No signature available' } - end - - -- TODO show empty popup when signatures is empty? - local lines = signature_help_to_preview_contents(result) - lines = util.trim_empty_lines(lines) - if vim.tbl_isempty(lines) then - return { 'No signature available' } - end - return lines, util.try_trim_markdown_code_blocks(lines) - end) + request('textDocument/signatureHelp', params) end -- TODO(ashkan) ? function M.completion(context) local params = util.make_position_params() params.context = context - return request('textDocument/completion', params, function(_, _, result) - if vim.tbl_isempty(result or {}) then return end - local row, col = unpack(api.nvim_win_get_cursor(0)) - local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1]) - local line_to_cursor = line:sub(col+1) - - local matches = util.text_document_completion_list_to_complete_items(result, line_to_cursor) - vim.fn.complete(col, matches) - end) + return request('textDocument/completion', params) end function M.formatting(options) validate { options = {options, 't', true} } options = vim.tbl_extend('keep', options or {}, { - tabSize = api.nvim_buf_get_option(0, 'tabstop'); - insertSpaces = api.nvim_buf_get_option(0, 'expandtab'); + tabSize = vim.bo.tabstop; + insertSpaces = vim.bo.expandtab; }) local params = { textDocument = { uri = vim.uri_from_bufnr(0) }; options = options; } - return request('textDocument/formatting', params, function(_, _, result) - if not result then return end - util.apply_text_edits(result) - end) + return request('textDocument/formatting', params) end function M.range_formatting(options, start_pos, end_pos) @@ -289,8 +86,8 @@ function M.range_formatting(options, start_pos, end_pos) end_pos = {end_pos, 't', true}; } options = vim.tbl_extend('keep', options or {}, { - tabSize = api.nvim_buf_get_option(0, 'tabstop'); - insertSpaces = api.nvim_buf_get_option(0, 'expandtab'); + tabSize = vim.bo.tabstop; + insertSpaces = vim.bo.expandtab; }) local A = list_extend({}, start_pos or api.nvim_buf_get_mark(0, '<')) local B = list_extend({}, end_pos or api.nvim_buf_get_mark(0, '>')) @@ -299,10 +96,10 @@ function M.range_formatting(options, start_pos, end_pos) B[1] = B[1] - 1 -- account for encoding. if A[2] > 0 then - A = {A[1], util.character_offset(0, unpack(A))} + A = {A[1], util.character_offset(0, A[1], A[2])} end if B[2] > 0 then - B = {B[1], util.character_offset(0, unpack(B))} + B = {B[1], util.character_offset(0, B[1], B[2])} end local params = { textDocument = { uri = vim.uri_from_bufnr(0) }; @@ -312,10 +109,7 @@ function M.range_formatting(options, start_pos, end_pos) }; options = options; } - return request('textDocument/rangeFormatting', params, function(_, _, result) - if not result then return end - util.apply_text_edits(result) - end) + return request('textDocument/rangeFormatting', params) end function M.rename(new_name) @@ -325,10 +119,7 @@ function M.rename(new_name) new_name = new_name or npcall(vfn.input, "New Name: ") if not (new_name and #new_name > 0) then return end params.newName = new_name - request('textDocument/rename', params, function(_, _, result) - if not result then return end - util.apply_workspace_edit(result) - end) + request('textDocument/rename', params) end function M.references(context) @@ -338,11 +129,7 @@ function M.references(context) includeDeclaration = true; } params[vim.type_idx] = vim.types.dictionary - request('textDocument/references', params, function(_, _, result) - if not result then return end - util.set_qflist(result) - vim.api.nvim_command("copen") - end) + request('textDocument/references', params) end return M -- cgit From 5d5b068d5bdd2fe3787eb619097a5af08ef929e2 Mon Sep 17 00:00:00 2001 From: Jesse-Bakker Date: Tue, 11 Feb 2020 07:53:14 +0100 Subject: LSP: Refine formatting tabSize #11834 Use the logic explained in the softtabstop help section for defining the tabSize parameter in formatting requests. This means that: - if softtabstop is 0, tabstop is used - if softtabstop < 0, shiftwidth is used - if softtabstop > 0, softtabstop is used When inserting spaces instead of tabs, softtabstop is used in vim. Therefor it would be more logical to use it when formatting instead of the current tabstop. --- runtime/lua/vim/lsp/buf.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index a6a05fb095..19deb5df45 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -68,8 +68,9 @@ end function M.formatting(options) validate { options = {options, 't', true} } + local sts = vim.bo.softtabstop; options = vim.tbl_extend('keep', options or {}, { - tabSize = vim.bo.tabstop; + tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop; insertSpaces = vim.bo.expandtab; }) local params = { @@ -85,8 +86,9 @@ function M.range_formatting(options, start_pos, end_pos) start_pos = {start_pos, 't', true}; end_pos = {end_pos, 't', true}; } + local sts = vim.bo.softtabstop; options = vim.tbl_extend('keep', options or {}, { - tabSize = vim.bo.tabstop; + tabSize = (sts > 0 and sts) or (sts < 0 and vim.bo.shiftwidth) or vim.bo.tabstop; insertSpaces = vim.bo.expandtab; }) local A = list_extend({}, start_pos or api.nvim_buf_get_mark(0, '<')) -- cgit From ca8699378c765017575c102f3da8347833159a6c Mon Sep 17 00:00:00 2001 From: Alvaro Muñoz Date: Wed, 26 Feb 2020 20:10:16 +0100 Subject: LSP: implement documentHighlight (#11638) * implement documentHighlight * fix bug * document highlight groups * use uppercase for help section title * documentation --- runtime/lua/vim/lsp/buf.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 19deb5df45..52fa2ec93b 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -134,5 +134,23 @@ function M.references(context) request('textDocument/references', params) end +--- Send request to server to resolve document highlights for the +--- current text document position. This request can be associated +--- to key mapping or to events such as `CursorHold`, eg: +--- +---
+--- vim.api.nvim_command [[autocmd CursorHold   lua vim.lsp.buf.document_highlight()]]
+--- vim.api.nvim_command [[autocmd CursorHoldI  lua vim.lsp.buf.document_highlight()]]
+--- vim.api.nvim_command [[autocmd CursorMoved  lua vim.lsp.buf.clear_references()]]
+--- 
+function M.document_highlight() + local params = util.make_position_params() + request('textDocument/documentHighlight', params) +end + +function M.clear_references() + util.buf_clear_references() +end + return M -- vim:sw=2 ts=2 et -- cgit From ad745f9da289a56035b8c759cc8fac1b40a44558 Mon Sep 17 00:00:00 2001 From: Alvaro Muñoz Date: Wed, 26 Feb 2020 20:22:14 +0100 Subject: add support to show diagnostics count in statusline (#11641) * add support to show diagnostics count in statusline * documentation --- runtime/lua/vim/lsp/buf.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 52fa2ec93b..82aeccd4db 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -23,6 +23,10 @@ local function request(method, params, callback) return vim.lsp.buf_request(0, method, params, callback) end +function M.server_ready() + return not not vim.lsp.buf_notify(0, "window/progress", {}) +end + function M.hover() local params = util.make_position_params() request('textDocument/hover', params) -- cgit From f157fdef7eccba6601c6d3b1475847466b60c25b Mon Sep 17 00:00:00 2001 From: Hirokazu Hata Date: Sat, 22 Feb 2020 21:14:10 +0900 Subject: lsp: add bufnr to callback function arguments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DocumentSymbol type doesn't have location field. So when we'll add 'textDocument/documentSymbol’ handler, we can't decide which file have we jump to. --- runtime/lua/vim/lsp/buf.lua | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 82aeccd4db..fc9e10cb73 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -138,6 +138,11 @@ function M.references(context) request('textDocument/references', params) end +function M.document_symbol() + local params = { textDocument = util.make_text_document_params() } + request('textDocument/documentSymbol', params) +end + --- Send request to server to resolve document highlights for the --- current text document position. This request can be associated --- to key mapping or to events such as `CursorHold`, eg: -- cgit From 68511924d02af03812ae4b56aea6d94009f8096e Mon Sep 17 00:00:00 2001 From: Thore Weilbier Date: Sun, 26 Apr 2020 06:47:48 +0200 Subject: LSP: remove obsolete "peek definition" code #12178 The method with the name 'textDocument/peekDefinition' is not part of the official language server protocol specification. Therefore no language server can/will support this. Thereby all related code and documentation as been removed. --- runtime/lua/vim/lsp/buf.lua | 6 ------ 1 file changed, 6 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index fc9e10cb73..587d1f52e9 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -32,12 +32,6 @@ function M.hover() request('textDocument/hover', params) end -function M.peek_definition() - local params = util.make_position_params() - request('textDocument/peekDefinition', params) -end - - function M.declaration() local params = util.make_position_params() request('textDocument/declaration', params) -- cgit From ea347b18d883999ed9a8d2e7c00068058135232f Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 2 May 2020 17:56:05 +0200 Subject: lsp: add workspace/symbol (#12224) * lsp: add workspace/symbol * refactor symbol callback * set hierarchical symbol support to true * add documentation and default mapping Co-authored-by: Hirokazu Hata --- runtime/lua/vim/lsp/buf.lua | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 587d1f52e9..0b45951a56 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -137,6 +137,12 @@ function M.document_symbol() request('textDocument/documentSymbol', params) end +function M.workspace_symbol(query) + query = query or npcall(vfn.input, "Query: ") + local params = {query = query} + request('workspace/symbol', params) +end + --- Send request to server to resolve document highlights for the --- current text document position. This request can be associated --- to key mapping or to events such as `CursorHold`, eg: -- cgit From f559e5249e3aa155687b335272da8f0c73255ee4 Mon Sep 17 00:00:00 2001 From: Jesse Date: Sat, 16 May 2020 01:18:59 +0200 Subject: LSP: Add textDocument/codeAction support (#11607) * Add textDocument/codeAction * Add callback for workspace/executeCommand * Escape newlines in codeAction titles * Return empty list in get_line_diagnostics if no buffer diagnostics * Add stub documentation * Validate context parameter in code_action * Add support for edit in CodeAction responses * Group diagnostics by line in vim.lsp.util.get_line_diagnostics() * Advertise code action literal support --- runtime/lua/vim/lsp/buf.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 0b45951a56..7a819f3c3d 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -161,5 +161,21 @@ function M.clear_references() util.buf_clear_references() end +function M.code_action(context) + validate { context = { context, 't', true } } + context = context or { diagnostics = util.get_line_diagnostics() } + local params = util.make_range_params() + params.context = context + request('textDocument/codeAction', params) +end + +function M.execute_command(command) + validate { + command = { command.command, 's' }, + arguments = { command.arguments, 't', true } + } + request('workspace/executeCommand', command) +end + return M -- vim:sw=2 ts=2 et -- cgit From ebee9ebe2be2bd584efefc4064a8b3a07e0505e0 Mon Sep 17 00:00:00 2001 From: David Lukes Date: Tue, 5 May 2020 17:23:45 +0200 Subject: lsp: Add sync variant of LSP formatting Also, factor out a `vim.lsp.util.get_effective_tabstop()` helper and add tests for it. --- runtime/lua/vim/lsp/buf.lua | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 7a819f3c3d..81d78c8b98 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -65,19 +65,18 @@ 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 +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}; -- cgit From 48ac77a14c65c18f1f4cd4ac8e657c645e9ca101 Mon Sep 17 00:00:00 2001 From: cbarrete <62146989+cbarrete@users.noreply.github.com> Date: Sat, 27 Jun 2020 20:10:19 +0200 Subject: LSP: Set current name as default rename text (#12553) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we don't know what the server considers to be a symbol, `cword` is the best bet in most cases. Co-authored-by: Cédric Barreteau <> --- runtime/lua/vim/lsp/buf.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 81d78c8b98..e1ddfb7efa 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -115,7 +115,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('')) if not (new_name and #new_name > 0) then return end params.newName = new_name request('textDocument/rename', params) -- cgit From 7b529e7912517af078e005dd7b06b3d042be9cb7 Mon Sep 17 00:00:00 2001 From: TJ DeVries Date: Thu, 2 Jul 2020 07:09:17 -0400 Subject: doc: fix scripts and regenerate (#12506) * Fix some small doc issues * doc: fixup * doc: fixup * Fix lint and rebase * Remove bad advice * Ugh, stupid mpack files... * Don't let people include these for now until they specifically want to * Prevent duplicate tag --- runtime/lua/vim/lsp/buf.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index e1ddfb7efa..839e00c67d 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 @@ -69,6 +72,10 @@ function M.formatting(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) @@ -136,6 +143,12 @@ function M.document_symbol() request('textDocument/documentSymbol', params) 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} -- cgit From 08efa7037e05ce229150d17db11b1b1c2419631f Mon Sep 17 00:00:00 2001 From: cbarrete <62146989+cbarrete@users.noreply.github.com> Date: Sat, 18 Jul 2020 21:10:09 +0200 Subject: lsp: Add support for call hierarchies (#12556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * LSP: Add support for call hierarchies * LSP: Add support for call hierarchies * LSP: Add support for call hierarchies * LSP: Jump to call location Jump to the call site instead of jumping to the definition of the caller/callee. * LSP: add tests for the call hierarchy callbacks * Fix linting error Co-authored-by: Cédric Barreteau <> --- runtime/lua/vim/lsp/buf.lua | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 839e00c67d..476bb3ba6f 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -143,6 +143,38 @@ 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. --- -- cgit From 56f3b95180ba011d9228c30c44eee9a9ab0fef84 Mon Sep 17 00:00:00 2001 From: cbarrete <62146989+cbarrete@users.noreply.github.com> Date: Sun, 19 Jul 2020 23:16:12 +0200 Subject: doc: Add documentation for some `vim.lsp.buf` functions (#12552) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add documentation for some `vim.lsp.buf` functions * Add inline Lua documentation * Use generated documentation for LSP buffer functions Co-authored-by: Cédric Barreteau <> --- runtime/lua/vim/lsp/buf.lua | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim/lsp/buf.lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 476bb3ba6f..2e27617997 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -30,43 +30,63 @@ function M.server_ready() return not not vim.lsp.buf_notify(0, "window/progress", {}) end +--- Displays hover information about the symbol under the cursor in a floating +--- window. Calling the function twice will jump into the floating window. function M.hover() local params = util.make_position_params() request('textDocument/hover', params) end +--- Jumps to the declaration of the symbol under the cursor. +--- function M.declaration() local params = util.make_position_params() request('textDocument/declaration', params) end +--- Jumps to the definition of the symbol under the cursor. +--- function M.definition() local params = util.make_position_params() request('textDocument/definition', params) end +--- Jumps to the definition of the type of the symbol under the cursor. +--- function M.type_definition() local params = util.make_position_params() request('textDocument/typeDefinition', params) end +--- Lists all the implementations for the symbol under the cursor in the +--- quickfix window. function M.implementation() local params = util.make_position_params() request('textDocument/implementation', params) end +--- Displays signature information about the symbol under the cursor in a +--- floating window. function M.signature_help() local params = util.make_position_params() request('textDocument/signatureHelp', params) end --- TODO(ashkan) ? +--- Retrieves the completion items at the current cursor position. Can only be +--- called in Insert mode. function M.completion(context) local params = util.make_position_params() params.context = context return request('textDocument/completion', params) end +--- Formats the current buffer. +--- +--- The optional {options} table can be used to specify FormattingOptions, a +--- list of which is available at +--- https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting. +--- Some unspecified options will be automatically derived from the current +--- Neovim options. function M.formatting(options) local params = util.make_formatting_params(options) return request('textDocument/formatting', params) @@ -118,6 +138,8 @@ function M.range_formatting(options, start_pos, end_pos) return request('textDocument/rangeFormatting', params) end +--- Renames all references to the symbol under the cursor. If {new_name} is not +--- provided, the user will be prompted for a new name using |input()|. 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. @@ -128,6 +150,8 @@ function M.rename(new_name) request('textDocument/rename', params) end +--- Lists all the references to the symbol under the cursor in the quickfix window. +--- function M.references(context) validate { context = { context, 't', true } } local params = util.make_position_params() @@ -138,6 +162,8 @@ function M.references(context) request('textDocument/references', params) end +--- Lists all symbols in the current buffer in the quickfix window. +--- function M.document_symbol() local params = { textDocument = util.make_text_document_params() } request('textDocument/documentSymbol', params) -- cgit