mirror of
https://github.com/neovim/neovim.git
synced 2025-10-26 12:27:24 +00:00
Merge pull request #18487 from clason/stylua
CI: format and lint runtime with Stylua
This commit is contained in:
@@ -41,7 +41,7 @@ P.take_until = function(targets, specials)
|
||||
parsed = true,
|
||||
value = {
|
||||
raw = table.concat(raw, ''),
|
||||
esc = table.concat(esc, '')
|
||||
esc = table.concat(esc, ''),
|
||||
},
|
||||
pos = new_pos,
|
||||
}
|
||||
@@ -248,48 +248,66 @@ S.format = P.any(
|
||||
capture_index = values[3],
|
||||
}, Node)
|
||||
end),
|
||||
P.map(P.seq(S.dollar, S.open, S.int, S.colon, S.slash, P.any(
|
||||
P.token('upcase'),
|
||||
P.token('downcase'),
|
||||
P.token('capitalize'),
|
||||
P.token('camelcase'),
|
||||
P.token('pascalcase')
|
||||
), S.close), function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.FORMAT,
|
||||
capture_index = values[3],
|
||||
modifier = values[6],
|
||||
}, Node)
|
||||
end),
|
||||
P.map(P.seq(S.dollar, S.open, S.int, S.colon, P.any(
|
||||
P.seq(S.question, P.take_until({ ':' }, { '\\' }), S.colon, P.take_until({ '}' }, { '\\' })),
|
||||
P.seq(S.plus, P.take_until({ '}' }, { '\\' })),
|
||||
P.seq(S.minus, P.take_until({ '}' }, { '\\' }))
|
||||
), S.close), function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.FORMAT,
|
||||
capture_index = values[3],
|
||||
if_text = values[5][2].esc,
|
||||
else_text = (values[5][4] or {}).esc,
|
||||
}, Node)
|
||||
end)
|
||||
P.map(
|
||||
P.seq(
|
||||
S.dollar,
|
||||
S.open,
|
||||
S.int,
|
||||
S.colon,
|
||||
S.slash,
|
||||
P.any(P.token('upcase'), P.token('downcase'), P.token('capitalize'), P.token('camelcase'), P.token('pascalcase')),
|
||||
S.close
|
||||
),
|
||||
function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.FORMAT,
|
||||
capture_index = values[3],
|
||||
modifier = values[6],
|
||||
}, Node)
|
||||
end
|
||||
),
|
||||
P.map(
|
||||
P.seq(
|
||||
S.dollar,
|
||||
S.open,
|
||||
S.int,
|
||||
S.colon,
|
||||
P.any(
|
||||
P.seq(S.question, P.take_until({ ':' }, { '\\' }), S.colon, P.take_until({ '}' }, { '\\' })),
|
||||
P.seq(S.plus, P.take_until({ '}' }, { '\\' })),
|
||||
P.seq(S.minus, P.take_until({ '}' }, { '\\' }))
|
||||
),
|
||||
S.close
|
||||
),
|
||||
function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.FORMAT,
|
||||
capture_index = values[3],
|
||||
if_text = values[5][2].esc,
|
||||
else_text = (values[5][4] or {}).esc,
|
||||
}, Node)
|
||||
end
|
||||
)
|
||||
)
|
||||
|
||||
S.transform = P.map(P.seq(
|
||||
S.slash,
|
||||
P.take_until({ '/' }, { '\\' }),
|
||||
S.slash,
|
||||
P.many(P.any(S.format, S.text({ '$', '/' }, { '\\' }))),
|
||||
S.slash,
|
||||
P.opt(P.pattern('[ig]+'))
|
||||
), function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.TRANSFORM,
|
||||
pattern = values[2].raw,
|
||||
format = values[4],
|
||||
option = values[6],
|
||||
}, Node)
|
||||
end)
|
||||
S.transform = P.map(
|
||||
P.seq(
|
||||
S.slash,
|
||||
P.take_until({ '/' }, { '\\' }),
|
||||
S.slash,
|
||||
P.many(P.any(S.format, S.text({ '$', '/' }, { '\\' }))),
|
||||
S.slash,
|
||||
P.opt(P.pattern('[ig]+'))
|
||||
),
|
||||
function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.TRANSFORM,
|
||||
pattern = values[2].raw,
|
||||
format = values[4],
|
||||
option = values[6],
|
||||
}, Node)
|
||||
end
|
||||
)
|
||||
|
||||
S.tabstop = P.any(
|
||||
P.map(P.seq(S.dollar, S.int), function(values)
|
||||
@@ -314,34 +332,38 @@ S.tabstop = P.any(
|
||||
)
|
||||
|
||||
S.placeholder = P.any(
|
||||
P.map(P.seq(S.dollar, S.open, S.int, S.colon, P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), S.close), function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.PLACEHOLDER,
|
||||
tabstop = values[3],
|
||||
children = values[5],
|
||||
}, Node)
|
||||
end)
|
||||
P.map(
|
||||
P.seq(S.dollar, S.open, S.int, S.colon, P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), S.close),
|
||||
function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.PLACEHOLDER,
|
||||
tabstop = values[3],
|
||||
children = values[5],
|
||||
}, Node)
|
||||
end
|
||||
)
|
||||
)
|
||||
|
||||
S.choice = P.map(P.seq(
|
||||
S.dollar,
|
||||
S.open,
|
||||
S.int,
|
||||
S.pipe,
|
||||
P.many(
|
||||
P.map(P.seq(S.text({ ',', '|' }), P.opt(S.comma)), function(values)
|
||||
S.choice = P.map(
|
||||
P.seq(
|
||||
S.dollar,
|
||||
S.open,
|
||||
S.int,
|
||||
S.pipe,
|
||||
P.many(P.map(P.seq(S.text({ ',', '|' }), P.opt(S.comma)), function(values)
|
||||
return values[1].esc
|
||||
end)
|
||||
end)),
|
||||
S.pipe,
|
||||
S.close
|
||||
),
|
||||
S.pipe,
|
||||
S.close
|
||||
), function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.CHOICE,
|
||||
tabstop = values[3],
|
||||
items = values[5],
|
||||
}, Node)
|
||||
end)
|
||||
function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.CHOICE,
|
||||
tabstop = values[3],
|
||||
items = values[5],
|
||||
}, Node)
|
||||
end
|
||||
)
|
||||
|
||||
S.variable = P.any(
|
||||
P.map(P.seq(S.dollar, S.var), function(values)
|
||||
@@ -363,13 +385,16 @@ S.variable = P.any(
|
||||
transform = values[4],
|
||||
}, Node)
|
||||
end),
|
||||
P.map(P.seq(S.dollar, S.open, S.var, S.colon, P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), S.close), function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.VARIABLE,
|
||||
name = values[3],
|
||||
children = values[5],
|
||||
}, Node)
|
||||
end)
|
||||
P.map(
|
||||
P.seq(S.dollar, S.open, S.var, S.colon, P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), S.close),
|
||||
function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.VARIABLE,
|
||||
name = values[3],
|
||||
children = values[5],
|
||||
}, Node)
|
||||
end
|
||||
)
|
||||
)
|
||||
|
||||
S.snippet = P.map(P.many(P.any(S.toplevel, S.text({ '$' }, { '}', '\\' }))), function(values)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
local vim = vim
|
||||
local validate = vim.validate
|
||||
local vfn = vim.fn
|
||||
local util = require 'vim.lsp.util'
|
||||
local util = require('vim.lsp.util')
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -9,7 +9,9 @@ local M = {}
|
||||
--- Returns nil if {status} is false or nil, otherwise returns the rest of the
|
||||
--- arguments.
|
||||
local function ok_or_nil(status, ...)
|
||||
if not status then return end
|
||||
if not status then
|
||||
return
|
||||
end
|
||||
return ...
|
||||
end
|
||||
|
||||
@@ -39,10 +41,10 @@ end
|
||||
---
|
||||
---@see |vim.lsp.buf_request()|
|
||||
local function request(method, params, handler)
|
||||
validate {
|
||||
method = {method, 's'};
|
||||
handler = {handler, 'f', true};
|
||||
}
|
||||
validate({
|
||||
method = { method, 's' },
|
||||
handler = { handler, 'f', true },
|
||||
})
|
||||
return vim.lsp.buf_request(0, method, params, handler)
|
||||
end
|
||||
|
||||
@@ -51,7 +53,7 @@ end
|
||||
---
|
||||
---@returns `true` if server responds.
|
||||
function M.server_ready()
|
||||
return not not vim.lsp.buf_notify(0, "window/progress", {})
|
||||
return not not vim.lsp.buf_notify(0, 'window/progress', {})
|
||||
end
|
||||
|
||||
--- Displays hover information about the symbol under the cursor in a floating
|
||||
@@ -117,9 +119,9 @@ end
|
||||
--
|
||||
---@returns The client that the user selected or nil
|
||||
local function select_client(method, on_choice)
|
||||
validate {
|
||||
validate({
|
||||
on_choice = { on_choice, 'function', false },
|
||||
}
|
||||
})
|
||||
local clients = vim.tbl_values(vim.lsp.buf_get_clients())
|
||||
clients = vim.tbl_filter(function(client)
|
||||
return client.supports_method(method)
|
||||
@@ -191,24 +193,21 @@ function M.format(options)
|
||||
if options.filter then
|
||||
clients = options.filter(clients)
|
||||
elseif options.id then
|
||||
clients = vim.tbl_filter(
|
||||
function(client) return client.id == options.id end,
|
||||
clients
|
||||
)
|
||||
clients = vim.tbl_filter(function(client)
|
||||
return client.id == options.id
|
||||
end, clients)
|
||||
elseif options.name then
|
||||
clients = vim.tbl_filter(
|
||||
function(client) return client.name == options.name end,
|
||||
clients
|
||||
)
|
||||
clients = vim.tbl_filter(function(client)
|
||||
return client.name == options.name
|
||||
end, clients)
|
||||
end
|
||||
|
||||
clients = vim.tbl_filter(
|
||||
function(client) return client.supports_method("textDocument/formatting") end,
|
||||
clients
|
||||
)
|
||||
clients = vim.tbl_filter(function(client)
|
||||
return client.supports_method('textDocument/formatting')
|
||||
end, clients)
|
||||
|
||||
if #clients == 0 then
|
||||
vim.notify("[LSP] Format request failed, no matching language servers.")
|
||||
vim.notify('[LSP] Format request failed, no matching language servers.')
|
||||
end
|
||||
|
||||
if options.async then
|
||||
@@ -218,7 +217,7 @@ function M.format(options)
|
||||
return
|
||||
end
|
||||
local params = util.make_formatting_params(options.formatting_options)
|
||||
client.request("textDocument/formatting", params, function(...)
|
||||
client.request('textDocument/formatting', params, function(...)
|
||||
local handler = client.handlers['textDocument/formatting'] or vim.lsp.handlers['textDocument/formatting']
|
||||
handler(...)
|
||||
do_format(next(clients, idx))
|
||||
@@ -229,11 +228,11 @@ function M.format(options)
|
||||
local timeout_ms = options.timeout_ms or 1000
|
||||
for _, client in pairs(clients) do
|
||||
local params = util.make_formatting_params(options.formatting_options)
|
||||
local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, bufnr)
|
||||
local result, err = client.request_sync('textDocument/formatting', params, timeout_ms, bufnr)
|
||||
if result and result.result then
|
||||
util.apply_text_edits(result.result, bufnr, client.offset_encoding)
|
||||
elseif err then
|
||||
vim.notify(string.format("[LSP][%s] %s", client.name, err), vim.log.levels.WARN)
|
||||
vim.notify(string.format('[LSP][%s] %s', client.name, err), vim.log.levels.WARN)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -310,7 +309,7 @@ end
|
||||
---the remaining clients in the order as they occur in the `order` list.
|
||||
function M.formatting_seq_sync(options, timeout_ms, order)
|
||||
vim.notify_once('vim.lsp.buf.formatting_seq_sync is deprecated. Use vim.lsp.buf.format instead', vim.log.levels.WARN)
|
||||
local clients = vim.tbl_values(vim.lsp.buf_get_clients());
|
||||
local clients = vim.tbl_values(vim.lsp.buf_get_clients())
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
|
||||
-- sort the clients according to `order`
|
||||
@@ -326,13 +325,18 @@ function M.formatting_seq_sync(options, timeout_ms, order)
|
||||
|
||||
-- loop through the clients and make synchronous formatting requests
|
||||
for _, client in pairs(clients) do
|
||||
if vim.tbl_get(client.server_capabilities, "documentFormattingProvider") then
|
||||
if vim.tbl_get(client.server_capabilities, 'documentFormattingProvider') then
|
||||
local params = util.make_formatting_params(options)
|
||||
local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, vim.api.nvim_get_current_buf())
|
||||
local result, err = client.request_sync(
|
||||
'textDocument/formatting',
|
||||
params,
|
||||
timeout_ms,
|
||||
vim.api.nvim_get_current_buf()
|
||||
)
|
||||
if result and result.result then
|
||||
util.apply_text_edits(result.result, bufnr, client.offset_encoding)
|
||||
elseif err then
|
||||
vim.notify(string.format("vim.lsp.buf.formatting_seq_sync: (%s) %s", client.name, err), vim.log.levels.WARN)
|
||||
vim.notify(string.format('vim.lsp.buf.formatting_seq_sync: (%s) %s', client.name, err), vim.log.levels.WARN)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -377,20 +381,18 @@ function M.rename(new_name, options)
|
||||
if options.filter then
|
||||
clients = options.filter(clients)
|
||||
elseif options.name then
|
||||
clients = vim.tbl_filter(
|
||||
function(client) return client.name == options.name end,
|
||||
clients
|
||||
)
|
||||
clients = vim.tbl_filter(function(client)
|
||||
return client.name == options.name
|
||||
end, clients)
|
||||
end
|
||||
|
||||
-- Clients must at least support rename, prepareRename is optional
|
||||
clients = vim.tbl_filter(
|
||||
function(client) return client.supports_method("textDocument/rename") end,
|
||||
clients
|
||||
)
|
||||
clients = vim.tbl_filter(function(client)
|
||||
return client.supports_method('textDocument/rename')
|
||||
end, clients)
|
||||
|
||||
if #clients == 0 then
|
||||
vim.notify("[LSP] Rename, no matching language servers with rename capability.")
|
||||
vim.notify('[LSP] Rename, no matching language servers with rename capability.')
|
||||
end
|
||||
|
||||
local win = vim.api.nvim_get_current_win()
|
||||
@@ -427,7 +429,7 @@ function M.rename(new_name, options)
|
||||
end, bufnr)
|
||||
end
|
||||
|
||||
if client.supports_method("textDocument/prepareRename") then
|
||||
if client.supports_method('textDocument/prepareRename') then
|
||||
local params = util.make_position_params(win, client.offset_encoding)
|
||||
client.request('textDocument/prepareRename', params, function(err, result)
|
||||
if err or result == nil then
|
||||
@@ -446,7 +448,7 @@ function M.rename(new_name, options)
|
||||
end
|
||||
|
||||
local prompt_opts = {
|
||||
prompt = "New Name: "
|
||||
prompt = 'New Name: ',
|
||||
}
|
||||
-- result: Range | { range: Range, placeholder: string }
|
||||
if result.placeholder then
|
||||
@@ -466,15 +468,15 @@ function M.rename(new_name, options)
|
||||
end)
|
||||
end, bufnr)
|
||||
else
|
||||
assert(client.supports_method("textDocument/rename"), 'Client must support textDocument/rename')
|
||||
assert(client.supports_method('textDocument/rename'), 'Client must support textDocument/rename')
|
||||
if new_name then
|
||||
rename(new_name)
|
||||
return
|
||||
end
|
||||
|
||||
local prompt_opts = {
|
||||
prompt = "New Name: ",
|
||||
default = cword
|
||||
prompt = 'New Name: ',
|
||||
default = cword,
|
||||
}
|
||||
vim.ui.input(prompt_opts, function(input)
|
||||
if not input or #input == 0 then
|
||||
@@ -493,10 +495,10 @@ end
|
||||
---@param context (table) Context for the request
|
||||
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
|
||||
function M.references(context)
|
||||
validate { context = { context, 't', true } }
|
||||
validate({ context = { context, 't', true } })
|
||||
local params = util.make_position_params()
|
||||
params.context = context or {
|
||||
includeDeclaration = true;
|
||||
includeDeclaration = true,
|
||||
}
|
||||
request('textDocument/references', params)
|
||||
end
|
||||
@@ -510,14 +512,16 @@ end
|
||||
|
||||
---@private
|
||||
local function pick_call_hierarchy_item(call_hierarchy_items)
|
||||
if not call_hierarchy_items then return end
|
||||
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 pairs(call_hierarchy_items) do
|
||||
local entry = item.detail or item.name
|
||||
table.insert(items, string.format("%d. %s", i, entry))
|
||||
table.insert(items, string.format('%d. %s', i, entry))
|
||||
end
|
||||
local choice = vim.fn.inputlist(items)
|
||||
if choice < 1 or choice > #items then
|
||||
@@ -539,8 +543,8 @@ local function call_hierarchy(method)
|
||||
if client then
|
||||
client.request(method, { item = call_hierarchy_item }, nil, ctx.bufnr)
|
||||
else
|
||||
vim.notify(string.format(
|
||||
'Client with id=%d disappeared during call hierarchy request', ctx.client_id),
|
||||
vim.notify(
|
||||
string.format('Client with id=%d disappeared during call hierarchy request', ctx.client_id),
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
end
|
||||
@@ -576,20 +580,25 @@ end
|
||||
--- Add the folder at path to the workspace folders. If {path} is
|
||||
--- not provided, the user will be prompted for a path using |input()|.
|
||||
function M.add_workspace_folder(workspace_folder)
|
||||
workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h'), 'dir')
|
||||
vim.api.nvim_command("redraw")
|
||||
if not (workspace_folder and #workspace_folder > 0) then return end
|
||||
if vim.fn.isdirectory(workspace_folder) == 0 then
|
||||
print(workspace_folder, " is not a valid directory")
|
||||
workspace_folder = workspace_folder or npcall(vfn.input, 'Workspace Folder: ', vfn.expand('%:p:h'), 'dir')
|
||||
vim.api.nvim_command('redraw')
|
||||
if not (workspace_folder and #workspace_folder > 0) then
|
||||
return
|
||||
end
|
||||
local params = util.make_workspace_params({{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}, {{}})
|
||||
if vim.fn.isdirectory(workspace_folder) == 0 then
|
||||
print(workspace_folder, ' is not a valid directory')
|
||||
return
|
||||
end
|
||||
local params = util.make_workspace_params(
|
||||
{ { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } },
|
||||
{ {} }
|
||||
)
|
||||
for _, client in pairs(vim.lsp.buf_get_clients()) do
|
||||
local found = false
|
||||
for _, folder in pairs(client.workspace_folders or {}) do
|
||||
if folder.name == workspace_folder then
|
||||
found = true
|
||||
print(workspace_folder, "is already part of this workspace")
|
||||
print(workspace_folder, 'is already part of this workspace')
|
||||
break
|
||||
end
|
||||
end
|
||||
@@ -607,10 +616,15 @@ end
|
||||
--- {path} is not provided, the user will be prompted for
|
||||
--- a path using |input()|.
|
||||
function M.remove_workspace_folder(workspace_folder)
|
||||
workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h'))
|
||||
vim.api.nvim_command("redraw")
|
||||
if not (workspace_folder and #workspace_folder > 0) then return end
|
||||
local params = util.make_workspace_params({{}}, {{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}})
|
||||
workspace_folder = workspace_folder or npcall(vfn.input, 'Workspace Folder: ', vfn.expand('%:p:h'))
|
||||
vim.api.nvim_command('redraw')
|
||||
if not (workspace_folder and #workspace_folder > 0) then
|
||||
return
|
||||
end
|
||||
local params = util.make_workspace_params(
|
||||
{ {} },
|
||||
{ { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } }
|
||||
)
|
||||
for _, client in pairs(vim.lsp.buf_get_clients()) do
|
||||
for idx, folder in pairs(client.workspace_folders) do
|
||||
if folder.name == workspace_folder then
|
||||
@@ -620,7 +634,7 @@ function M.remove_workspace_folder(workspace_folder)
|
||||
end
|
||||
end
|
||||
end
|
||||
print(workspace_folder, "is not currently part of the workspace")
|
||||
print(workspace_folder, 'is not currently part of the workspace')
|
||||
end
|
||||
|
||||
--- Lists all symbols in the current workspace in the quickfix window.
|
||||
@@ -631,11 +645,11 @@ end
|
||||
---
|
||||
---@param query (string, optional)
|
||||
function M.workspace_symbol(query)
|
||||
query = query or npcall(vfn.input, "Query: ")
|
||||
query = query or npcall(vfn.input, 'Query: ')
|
||||
if query == nil then
|
||||
return
|
||||
end
|
||||
local params = {query = query}
|
||||
local params = { query = query }
|
||||
request('workspace/symbol', params)
|
||||
end
|
||||
|
||||
@@ -665,7 +679,6 @@ function M.clear_references()
|
||||
util.buf_clear_references()
|
||||
end
|
||||
|
||||
|
||||
---@private
|
||||
--
|
||||
--- This is not public because the main extension point is
|
||||
@@ -728,10 +741,11 @@ local function on_code_action_results(results, ctx, options)
|
||||
--
|
||||
local client = vim.lsp.get_client_by_id(action_tuple[1])
|
||||
local action = action_tuple[2]
|
||||
if not action.edit
|
||||
and client
|
||||
and vim.tbl_get(client.server_capabilities, "codeActionProvider", "resolveProvider") then
|
||||
|
||||
if
|
||||
not action.edit
|
||||
and client
|
||||
and vim.tbl_get(client.server_capabilities, 'codeActionProvider', 'resolveProvider')
|
||||
then
|
||||
client.request('codeAction/resolve', action, function(err, resolved_action)
|
||||
if err then
|
||||
vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR)
|
||||
@@ -761,7 +775,6 @@ local function on_code_action_results(results, ctx, options)
|
||||
}, on_user_choice)
|
||||
end
|
||||
|
||||
|
||||
--- Requests code actions from all clients and calls the handler exactly once
|
||||
--- with all aggregated results
|
||||
---@private
|
||||
@@ -769,7 +782,7 @@ local function code_action_request(params, options)
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local method = 'textDocument/codeAction'
|
||||
vim.lsp.buf_request_all(bufnr, method, params, function(results)
|
||||
local ctx = { bufnr = bufnr, method = method, params = params}
|
||||
local ctx = { bufnr = bufnr, method = method, params = params }
|
||||
on_code_action_results(results, ctx, options)
|
||||
end)
|
||||
end
|
||||
@@ -794,7 +807,7 @@ end
|
||||
--- (after filtering), the action is applied without user query.
|
||||
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
|
||||
function M.code_action(options)
|
||||
validate { options = { options, 't', true } }
|
||||
validate({ options = { options, 't', true } })
|
||||
options = options or {}
|
||||
-- Detect old API call code_action(context) which should now be
|
||||
-- code_action({ context = context} )
|
||||
@@ -826,7 +839,7 @@ end
|
||||
---@param end_pos ({number, number}, optional) mark-indexed position.
|
||||
---Defaults to the end of the last visual selection.
|
||||
function M.range_code_action(context, start_pos, end_pos)
|
||||
validate { context = { context, 't', true } }
|
||||
validate({ context = { context, 't', true } })
|
||||
context = context or {}
|
||||
if not context.diagnostics then
|
||||
context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics()
|
||||
@@ -841,16 +854,16 @@ end
|
||||
---@param command_params table A valid `ExecuteCommandParams` object
|
||||
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
|
||||
function M.execute_command(command_params)
|
||||
validate {
|
||||
validate({
|
||||
command = { command_params.command, 's' },
|
||||
arguments = { command_params.arguments, 't', true }
|
||||
}
|
||||
arguments = { command_params.arguments, 't', true },
|
||||
})
|
||||
command_params = {
|
||||
command=command_params.command,
|
||||
arguments=command_params.arguments,
|
||||
workDoneToken=command_params.workDoneToken,
|
||||
command = command_params.command,
|
||||
arguments = command_params.arguments,
|
||||
workDoneToken = command_params.workDoneToken,
|
||||
}
|
||||
request('workspace/executeCommand', command_params )
|
||||
request('workspace/executeCommand', command_params)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
@@ -12,7 +12,7 @@ local lens_cache_by_buf = setmetatable({}, {
|
||||
__index = function(t, b)
|
||||
local key = b > 0 and b or api.nvim_get_current_buf()
|
||||
return rawget(t, key)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local namespaces = setmetatable({}, {
|
||||
@@ -20,13 +20,12 @@ local namespaces = setmetatable({}, {
|
||||
local value = api.nvim_create_namespace('vim_lsp_codelens:' .. key)
|
||||
rawset(t, key, value)
|
||||
return value
|
||||
end;
|
||||
end,
|
||||
})
|
||||
|
||||
---@private
|
||||
M.__namespaces = namespaces
|
||||
|
||||
|
||||
---@private
|
||||
local function execute_lens(lens, bufnr, client_id)
|
||||
local line = lens.range.start.line
|
||||
@@ -44,10 +43,14 @@ local function execute_lens(lens, bufnr, client_id)
|
||||
local command_provider = client.server_capabilities.executeCommandProvider
|
||||
local commands = type(command_provider) == 'table' and command_provider.commands or {}
|
||||
if not vim.tbl_contains(commands, command.command) then
|
||||
vim.notify(string.format(
|
||||
"Language server does not support command `%s`. This command may require a client extension.", command.command),
|
||||
vim.log.levels.WARN)
|
||||
return
|
||||
vim.notify(
|
||||
string.format(
|
||||
'Language server does not support command `%s`. This command may require a client extension.',
|
||||
command.command
|
||||
),
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
return
|
||||
end
|
||||
client.request('workspace/executeCommand', command, function(...)
|
||||
local result = vim.lsp.handlers['workspace/executeCommand'](...)
|
||||
@@ -56,14 +59,15 @@ local function execute_lens(lens, bufnr, client_id)
|
||||
end, bufnr)
|
||||
end
|
||||
|
||||
|
||||
--- Return all lenses for the given buffer
|
||||
---
|
||||
---@param bufnr number Buffer number. 0 can be used for the current buffer.
|
||||
---@return table (`CodeLens[]`)
|
||||
function M.get(bufnr)
|
||||
local lenses_by_client = lens_cache_by_buf[bufnr or 0]
|
||||
if not lenses_by_client then return {} end
|
||||
if not lenses_by_client then
|
||||
return {}
|
||||
end
|
||||
local lenses = {}
|
||||
for _, client_lenses in pairs(lenses_by_client) do
|
||||
vim.list_extend(lenses, client_lenses)
|
||||
@@ -71,7 +75,6 @@ function M.get(bufnr)
|
||||
return lenses
|
||||
end
|
||||
|
||||
|
||||
--- Run the code lens in the current line
|
||||
---
|
||||
function M.run()
|
||||
@@ -82,7 +85,7 @@ function M.run()
|
||||
for client, lenses in pairs(lenses_by_client) do
|
||||
for _, lens in pairs(lenses) do
|
||||
if lens.range.start.line == (line - 1) then
|
||||
table.insert(options, {client=client, lens=lens})
|
||||
table.insert(options, { client = client, lens = lens })
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -105,7 +108,6 @@ function M.run()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Display the lenses using virtual text
|
||||
---
|
||||
---@param lenses table of lenses to display (`CodeLens[] | null`)
|
||||
@@ -133,19 +135,20 @@ function M.display(lenses, bufnr, client_id)
|
||||
local num_line_lenses = #line_lenses
|
||||
for j, lens in ipairs(line_lenses) do
|
||||
local text = lens.command and lens.command.title or 'Unresolved lens ...'
|
||||
table.insert(chunks, {text, 'LspCodeLens' })
|
||||
table.insert(chunks, { text, 'LspCodeLens' })
|
||||
if j < num_line_lenses then
|
||||
table.insert(chunks, {' | ', 'LspCodeLensSeparator' })
|
||||
table.insert(chunks, { ' | ', 'LspCodeLensSeparator' })
|
||||
end
|
||||
end
|
||||
if #chunks > 0 then
|
||||
api.nvim_buf_set_extmark(bufnr, ns, i, 0, { virt_text = chunks,
|
||||
hl_mode="combine" })
|
||||
api.nvim_buf_set_extmark(bufnr, ns, i, 0, {
|
||||
virt_text = chunks,
|
||||
hl_mode = 'combine',
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Store lenses for a specific buffer and client
|
||||
---
|
||||
---@param lenses table of lenses to store (`CodeLens[] | null`)
|
||||
@@ -158,16 +161,17 @@ function M.save(lenses, bufnr, client_id)
|
||||
lens_cache_by_buf[bufnr] = lenses_by_client
|
||||
local ns = namespaces[client_id]
|
||||
api.nvim_buf_attach(bufnr, false, {
|
||||
on_detach = function(b) lens_cache_by_buf[b] = nil end,
|
||||
on_detach = function(b)
|
||||
lens_cache_by_buf[b] = nil
|
||||
end,
|
||||
on_lines = function(_, b, _, first_lnum, last_lnum)
|
||||
api.nvim_buf_clear_namespace(b, ns, first_lnum, last_lnum)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
lenses_by_client[client_id] = lenses
|
||||
end
|
||||
|
||||
|
||||
---@private
|
||||
local function resolve_lenses(lenses, bufnr, client_id, callback)
|
||||
lenses = lenses or {}
|
||||
@@ -201,8 +205,7 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
|
||||
ns,
|
||||
lens.range.start.line,
|
||||
0,
|
||||
{ virt_text = {{ lens.command.title, 'LspCodeLens' }},
|
||||
hl_mode="combine" }
|
||||
{ virt_text = { { lens.command.title, 'LspCodeLens' } }, hl_mode = 'combine' }
|
||||
)
|
||||
end
|
||||
countdown()
|
||||
@@ -211,13 +214,12 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- |lsp-handler| for the method `textDocument/codeLens`
|
||||
---
|
||||
function M.on_codelens(err, result, ctx, _)
|
||||
if err then
|
||||
active_refreshes[ctx.bufnr] = nil
|
||||
local _ = log.error() and log.error("codelens", err)
|
||||
local _ = log.error() and log.error('codelens', err)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -232,7 +234,6 @@ function M.on_codelens(err, result, ctx, _)
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
--- Refresh the codelens for the current buffer
|
||||
---
|
||||
--- It is recommended to trigger this using an autocmd or via keymap.
|
||||
@@ -243,7 +244,7 @@ end
|
||||
---
|
||||
function M.refresh()
|
||||
local params = {
|
||||
textDocument = util.make_text_document_params()
|
||||
textDocument = util.make_text_document_params(),
|
||||
}
|
||||
local bufnr = api.nvim_get_current_buf()
|
||||
if active_refreshes[bufnr] then
|
||||
@@ -253,5 +254,4 @@ function M.refresh()
|
||||
vim.lsp.buf_request(0, 'textDocument/codeLens', params, M.on_codelens)
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
|
||||
@@ -50,12 +50,12 @@ end
|
||||
|
||||
---@private
|
||||
local function line_byte_from_position(lines, lnum, col, offset_encoding)
|
||||
if not lines or offset_encoding == "utf-8" then
|
||||
if not lines or offset_encoding == 'utf-8' then
|
||||
return col
|
||||
end
|
||||
|
||||
local line = lines[lnum + 1]
|
||||
local ok, result = pcall(vim.str_byteindex, line, col, offset_encoding == "utf-16")
|
||||
local ok, result = pcall(vim.str_byteindex, line, col, offset_encoding == 'utf-16')
|
||||
if ok then
|
||||
return result
|
||||
end
|
||||
@@ -75,7 +75,7 @@ local function get_buf_lines(bufnr)
|
||||
return
|
||||
end
|
||||
|
||||
local content = f:read("*a")
|
||||
local content = f:read('*a')
|
||||
if not content then
|
||||
-- Some LSP servers report diagnostics at a directory level, in which case
|
||||
-- io.read() returns nil
|
||||
@@ -83,7 +83,7 @@ local function get_buf_lines(bufnr)
|
||||
return
|
||||
end
|
||||
|
||||
local lines = vim.split(content, "\n")
|
||||
local lines = vim.split(content, '\n')
|
||||
f:close()
|
||||
return lines
|
||||
end
|
||||
@@ -92,10 +92,10 @@ end
|
||||
local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
|
||||
local buf_lines = get_buf_lines(bufnr)
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
local offset_encoding = client and client.offset_encoding or "utf-16"
|
||||
local offset_encoding = client and client.offset_encoding or 'utf-16'
|
||||
return vim.tbl_map(function(diagnostic)
|
||||
local start = diagnostic.range.start
|
||||
local _end = diagnostic.range["end"]
|
||||
local _end = diagnostic.range['end']
|
||||
return {
|
||||
lnum = start.line,
|
||||
col = line_byte_from_position(buf_lines, start.line, start.character, offset_encoding),
|
||||
@@ -122,14 +122,14 @@ end
|
||||
---@private
|
||||
local function diagnostic_vim_to_lsp(diagnostics)
|
||||
return vim.tbl_map(function(diagnostic)
|
||||
return vim.tbl_extend("keep", {
|
||||
return vim.tbl_extend('keep', {
|
||||
-- "keep" the below fields over any duplicate fields in diagnostic.user_data.lsp
|
||||
range = {
|
||||
start = {
|
||||
line = diagnostic.lnum,
|
||||
character = diagnostic.col,
|
||||
},
|
||||
["end"] = {
|
||||
['end'] = {
|
||||
line = diagnostic.end_lnum,
|
||||
character = diagnostic.end_col,
|
||||
},
|
||||
@@ -148,10 +148,10 @@ local _client_namespaces = {}
|
||||
---
|
||||
---@param client_id number The id of the LSP client
|
||||
function M.get_namespace(client_id)
|
||||
vim.validate { client_id = { client_id, 'n' } }
|
||||
vim.validate({ client_id = { client_id, 'n' } })
|
||||
if not _client_namespaces[client_id] then
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
local name = string.format("vim.lsp.%s.%d", client and client.name or "unknown", client_id)
|
||||
local name = string.format('vim.lsp.%s.%d', client and client.name or 'unknown', client_id)
|
||||
_client_namespaces[client_id] = vim.api.nvim_create_namespace(name)
|
||||
end
|
||||
return _client_namespaces[client_id]
|
||||
@@ -203,7 +203,7 @@ function M.on_publish_diagnostics(_, result, ctx, config)
|
||||
for _, opt in pairs(config) do
|
||||
if type(opt) == 'table' then
|
||||
if not opt.severity and opt.severity_limit then
|
||||
opt.severity = {min=severity_lsp_to_vim(opt.severity_limit)}
|
||||
opt.severity = { min = severity_lsp_to_vim(opt.severity_limit) }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -240,7 +240,6 @@ end
|
||||
|
||||
-- Deprecated Functions {{{
|
||||
|
||||
|
||||
--- Save diagnostics to the current buffer.
|
||||
---
|
||||
---@deprecated Prefer |vim.diagnostic.set()|
|
||||
@@ -251,7 +250,7 @@ end
|
||||
---@param client_id number
|
||||
---@private
|
||||
function M.save(diagnostics, bufnr, client_id)
|
||||
vim.deprecate('vim.lsp.diagnostic.save', 'vim.diagnostic.set', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.save', 'vim.diagnostic.set', '0.8')
|
||||
local namespace = M.get_namespace(client_id)
|
||||
vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
|
||||
end
|
||||
@@ -265,14 +264,14 @@ end
|
||||
--- If nil, diagnostics of all clients are included.
|
||||
---@return table with diagnostics grouped by bufnr (bufnr: Diagnostic[])
|
||||
function M.get_all(client_id)
|
||||
vim.deprecate('vim.lsp.diagnostic.get_all', 'vim.diagnostic.get', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.get_all', 'vim.diagnostic.get', '0.8')
|
||||
local result = {}
|
||||
local namespace
|
||||
if client_id then
|
||||
namespace = M.get_namespace(client_id)
|
||||
end
|
||||
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
|
||||
local diagnostics = diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, {namespace = namespace}))
|
||||
local diagnostics = diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, { namespace = namespace }))
|
||||
result[bufnr] = diagnostics
|
||||
end
|
||||
return result
|
||||
@@ -287,8 +286,10 @@ end
|
||||
--- Else, return just the diagnostics associated with the client_id.
|
||||
---@param predicate function|nil Optional function for filtering diagnostics
|
||||
function M.get(bufnr, client_id, predicate)
|
||||
vim.deprecate('vim.lsp.diagnostic.get', 'vim.diagnostic.get', '0.8' )
|
||||
predicate = predicate or function() return true end
|
||||
vim.deprecate('vim.lsp.diagnostic.get', 'vim.diagnostic.get', '0.8')
|
||||
predicate = predicate or function()
|
||||
return true
|
||||
end
|
||||
if client_id == nil then
|
||||
local all_diagnostics = {}
|
||||
vim.lsp.for_each_buffer_client(bufnr, function(_, iter_client_id, _)
|
||||
@@ -301,7 +302,7 @@ function M.get(bufnr, client_id, predicate)
|
||||
end
|
||||
|
||||
local namespace = M.get_namespace(client_id)
|
||||
return diagnostic_vim_to_lsp(vim.tbl_filter(predicate, vim.diagnostic.get(bufnr, {namespace=namespace})))
|
||||
return diagnostic_vim_to_lsp(vim.tbl_filter(predicate, vim.diagnostic.get(bufnr, { namespace = namespace })))
|
||||
end
|
||||
|
||||
--- Get the diagnostics by line
|
||||
@@ -325,7 +326,7 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
|
||||
if opts.severity then
|
||||
opts.severity = severity_lsp_to_vim(opts.severity)
|
||||
elseif opts.severity_limit then
|
||||
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
|
||||
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
|
||||
end
|
||||
|
||||
if client_id then
|
||||
@@ -349,7 +350,7 @@ end
|
||||
---@param severity DiagnosticSeverity
|
||||
---@param client_id number the client id
|
||||
function M.get_count(bufnr, severity, client_id)
|
||||
vim.deprecate('vim.lsp.diagnostic.get_count', 'vim.diagnostic.get', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.get_count', 'vim.diagnostic.get', '0.8')
|
||||
severity = severity_lsp_to_vim(severity)
|
||||
local opts = { severity = severity }
|
||||
if client_id ~= nil then
|
||||
@@ -366,15 +367,15 @@ end
|
||||
---@param opts table See |vim.lsp.diagnostic.goto_next()|
|
||||
---@return table Previous diagnostic
|
||||
function M.get_prev(opts)
|
||||
vim.deprecate('vim.lsp.diagnostic.get_prev', 'vim.diagnostic.get_prev', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.get_prev', 'vim.diagnostic.get_prev', '0.8')
|
||||
if opts then
|
||||
if opts.severity then
|
||||
opts.severity = severity_lsp_to_vim(opts.severity)
|
||||
elseif opts.severity_limit then
|
||||
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
|
||||
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
|
||||
end
|
||||
end
|
||||
return diagnostic_vim_to_lsp({vim.diagnostic.get_prev(opts)})[1]
|
||||
return diagnostic_vim_to_lsp({ vim.diagnostic.get_prev(opts) })[1]
|
||||
end
|
||||
|
||||
--- Return the pos, {row, col}, for the prev diagnostic in the current buffer.
|
||||
@@ -384,12 +385,12 @@ end
|
||||
---@param opts table See |vim.lsp.diagnostic.goto_next()|
|
||||
---@return table Previous diagnostic position
|
||||
function M.get_prev_pos(opts)
|
||||
vim.deprecate('vim.lsp.diagnostic.get_prev_pos', 'vim.diagnostic.get_prev_pos', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.get_prev_pos', 'vim.diagnostic.get_prev_pos', '0.8')
|
||||
if opts then
|
||||
if opts.severity then
|
||||
opts.severity = severity_lsp_to_vim(opts.severity)
|
||||
elseif opts.severity_limit then
|
||||
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
|
||||
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
|
||||
end
|
||||
end
|
||||
return vim.diagnostic.get_prev_pos(opts)
|
||||
@@ -401,12 +402,12 @@ end
|
||||
---
|
||||
---@param opts table See |vim.lsp.diagnostic.goto_next()|
|
||||
function M.goto_prev(opts)
|
||||
vim.deprecate('vim.lsp.diagnostic.goto_prev', 'vim.diagnostic.goto_prev', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.goto_prev', 'vim.diagnostic.goto_prev', '0.8')
|
||||
if opts then
|
||||
if opts.severity then
|
||||
opts.severity = severity_lsp_to_vim(opts.severity)
|
||||
elseif opts.severity_limit then
|
||||
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
|
||||
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
|
||||
end
|
||||
end
|
||||
return vim.diagnostic.goto_prev(opts)
|
||||
@@ -419,15 +420,15 @@ end
|
||||
---@param opts table See |vim.lsp.diagnostic.goto_next()|
|
||||
---@return table Next diagnostic
|
||||
function M.get_next(opts)
|
||||
vim.deprecate('vim.lsp.diagnostic.get_next', 'vim.diagnostic.get_next', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.get_next', 'vim.diagnostic.get_next', '0.8')
|
||||
if opts then
|
||||
if opts.severity then
|
||||
opts.severity = severity_lsp_to_vim(opts.severity)
|
||||
elseif opts.severity_limit then
|
||||
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
|
||||
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
|
||||
end
|
||||
end
|
||||
return diagnostic_vim_to_lsp({vim.diagnostic.get_next(opts)})[1]
|
||||
return diagnostic_vim_to_lsp({ vim.diagnostic.get_next(opts) })[1]
|
||||
end
|
||||
|
||||
--- Return the pos, {row, col}, for the next diagnostic in the current buffer.
|
||||
@@ -437,12 +438,12 @@ end
|
||||
---@param opts table See |vim.lsp.diagnostic.goto_next()|
|
||||
---@return table Next diagnostic position
|
||||
function M.get_next_pos(opts)
|
||||
vim.deprecate('vim.lsp.diagnostic.get_next_pos', 'vim.diagnostic.get_next_pos', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.get_next_pos', 'vim.diagnostic.get_next_pos', '0.8')
|
||||
if opts then
|
||||
if opts.severity then
|
||||
opts.severity = severity_lsp_to_vim(opts.severity)
|
||||
elseif opts.severity_limit then
|
||||
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
|
||||
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
|
||||
end
|
||||
end
|
||||
return vim.diagnostic.get_next_pos(opts)
|
||||
@@ -452,12 +453,12 @@ end
|
||||
---
|
||||
---@deprecated Prefer |vim.diagnostic.goto_next()|
|
||||
function M.goto_next(opts)
|
||||
vim.deprecate('vim.lsp.diagnostic.goto_next', 'vim.diagnostic.goto_next', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.goto_next', 'vim.diagnostic.goto_next', '0.8')
|
||||
if opts then
|
||||
if opts.severity then
|
||||
opts.severity = severity_lsp_to_vim(opts.severity)
|
||||
elseif opts.severity_limit then
|
||||
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
|
||||
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
|
||||
end
|
||||
end
|
||||
return vim.diagnostic.goto_next(opts)
|
||||
@@ -476,10 +477,10 @@ end
|
||||
--- - severity_limit (DiagnosticSeverity):
|
||||
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
|
||||
function M.set_signs(diagnostics, bufnr, client_id, _, opts)
|
||||
vim.deprecate('vim.lsp.diagnostic.set_signs', nil , '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.set_signs', nil, '0.8')
|
||||
local namespace = M.get_namespace(client_id)
|
||||
if opts and not opts.severity and opts.severity_limit then
|
||||
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
|
||||
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
|
||||
end
|
||||
|
||||
vim.diagnostic._set_signs(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts)
|
||||
@@ -497,10 +498,10 @@ end
|
||||
--- - severity_limit (DiagnosticSeverity):
|
||||
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
|
||||
function M.set_underline(diagnostics, bufnr, client_id, _, opts)
|
||||
vim.deprecate('vim.lsp.diagnostic.set_underline', nil , '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.set_underline', nil, '0.8')
|
||||
local namespace = M.get_namespace(client_id)
|
||||
if opts and not opts.severity and opts.severity_limit then
|
||||
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
|
||||
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
|
||||
end
|
||||
return vim.diagnostic._set_underline(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts)
|
||||
end
|
||||
@@ -519,10 +520,10 @@ end
|
||||
--- - severity_limit (DiagnosticSeverity):
|
||||
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
|
||||
function M.set_virtual_text(diagnostics, bufnr, client_id, _, opts)
|
||||
vim.deprecate('vim.lsp.diagnostic.set_virtual_text', nil , '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.set_virtual_text', nil, '0.8')
|
||||
local namespace = M.get_namespace(client_id)
|
||||
if opts and not opts.severity and opts.severity_limit then
|
||||
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
|
||||
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
|
||||
end
|
||||
return vim.diagnostic._set_virtual_text(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts)
|
||||
end
|
||||
@@ -538,7 +539,7 @@ end
|
||||
---@return an array of [text, hl_group] arrays. This can be passed directly to
|
||||
--- the {virt_text} option of |nvim_buf_set_extmark()|.
|
||||
function M.get_virtual_text_chunks_for_line(bufnr, _, line_diags, opts)
|
||||
vim.deprecate('vim.lsp.diagnostic.get_virtual_text_chunks_for_line', nil, '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.get_virtual_text_chunks_for_line', nil, '0.8')
|
||||
return vim.diagnostic._get_virt_text_chunks(diagnostic_lsp_to_vim(line_diags, bufnr), opts)
|
||||
end
|
||||
|
||||
@@ -556,14 +557,14 @@ end
|
||||
---@param position table|nil The (0,0)-indexed position
|
||||
---@return table {popup_bufnr, win_id}
|
||||
function M.show_position_diagnostics(opts, buf_nr, position)
|
||||
vim.deprecate('vim.lsp.diagnostic.show_position_diagnostics', 'vim.diagnostic.open_float', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.show_position_diagnostics', 'vim.diagnostic.open_float', '0.8')
|
||||
opts = opts or {}
|
||||
opts.scope = "cursor"
|
||||
opts.scope = 'cursor'
|
||||
opts.pos = position
|
||||
if opts.severity then
|
||||
opts.severity = severity_lsp_to_vim(opts.severity)
|
||||
elseif opts.severity_limit then
|
||||
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
|
||||
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
|
||||
end
|
||||
return vim.diagnostic.open_float(buf_nr, opts)
|
||||
end
|
||||
@@ -580,9 +581,9 @@ end
|
||||
---@param client_id number|nil the client id
|
||||
---@return table {popup_bufnr, win_id}
|
||||
function M.show_line_diagnostics(opts, buf_nr, line_nr, client_id)
|
||||
vim.deprecate('vim.lsp.diagnostic.show_line_diagnostics', 'vim.diagnostic.open_float', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.show_line_diagnostics', 'vim.diagnostic.open_float', '0.8')
|
||||
opts = opts or {}
|
||||
opts.scope = "line"
|
||||
opts.scope = 'line'
|
||||
opts.pos = line_nr
|
||||
if client_id then
|
||||
opts.namespace = M.get_namespace(client_id)
|
||||
@@ -604,7 +605,7 @@ end
|
||||
--- client. The default is to redraw diagnostics for all attached
|
||||
--- clients.
|
||||
function M.redraw(bufnr, client_id)
|
||||
vim.deprecate('vim.lsp.diagnostic.redraw', 'vim.diagnostic.show', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.redraw', 'vim.diagnostic.show', '0.8')
|
||||
bufnr = get_bufnr(bufnr)
|
||||
if not client_id then
|
||||
return vim.lsp.for_each_buffer_client(bufnr, function(client)
|
||||
@@ -632,12 +633,12 @@ end
|
||||
--- - {workspace}: (boolean, default true)
|
||||
--- - Set the list with workspace diagnostics
|
||||
function M.set_qflist(opts)
|
||||
vim.deprecate('vim.lsp.diagnostic.set_qflist', 'vim.diagnostic.setqflist', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.set_qflist', 'vim.diagnostic.setqflist', '0.8')
|
||||
opts = opts or {}
|
||||
if opts.severity then
|
||||
opts.severity = severity_lsp_to_vim(opts.severity)
|
||||
elseif opts.severity_limit then
|
||||
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
|
||||
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
|
||||
end
|
||||
if opts.client_id then
|
||||
opts.client_id = nil
|
||||
@@ -664,12 +665,12 @@ end
|
||||
--- - {workspace}: (boolean, default false)
|
||||
--- - Set the list with workspace diagnostics
|
||||
function M.set_loclist(opts)
|
||||
vim.deprecate('vim.lsp.diagnostic.set_loclist', 'vim.diagnostic.setloclist', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.set_loclist', 'vim.diagnostic.setloclist', '0.8')
|
||||
opts = opts or {}
|
||||
if opts.severity then
|
||||
opts.severity = severity_lsp_to_vim(opts.severity)
|
||||
elseif opts.severity_limit then
|
||||
opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
|
||||
opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
|
||||
end
|
||||
if opts.client_id then
|
||||
opts.client_id = nil
|
||||
@@ -692,7 +693,7 @@ end
|
||||
-- send diagnostic information and the client will still process it. The
|
||||
-- diagnostics are simply not displayed to the user.
|
||||
function M.disable(bufnr, client_id)
|
||||
vim.deprecate('vim.lsp.diagnostic.disable', 'vim.diagnostic.disable', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.disable', 'vim.diagnostic.disable', '0.8')
|
||||
if not client_id then
|
||||
return vim.lsp.for_each_buffer_client(bufnr, function(client)
|
||||
M.disable(bufnr, client.id)
|
||||
@@ -713,7 +714,7 @@ end
|
||||
--- client. The default is to enable diagnostics for all attached
|
||||
--- clients.
|
||||
function M.enable(bufnr, client_id)
|
||||
vim.deprecate('vim.lsp.diagnostic.enable', 'vim.diagnostic.enable', '0.8' )
|
||||
vim.deprecate('vim.lsp.diagnostic.enable', 'vim.diagnostic.enable', '0.8')
|
||||
if not client_id then
|
||||
return vim.lsp.for_each_buffer_client(bufnr, function(client)
|
||||
M.enable(bufnr, client.id)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
local log = require 'vim.lsp.log'
|
||||
local protocol = require 'vim.lsp.protocol'
|
||||
local util = require 'vim.lsp.util'
|
||||
local log = require('vim.lsp.log')
|
||||
local protocol = require('vim.lsp.protocol')
|
||||
local util = require('vim.lsp.util')
|
||||
local vim = vim
|
||||
local api = vim.api
|
||||
|
||||
@@ -12,8 +12,8 @@ local M = {}
|
||||
--- Writes to error buffer.
|
||||
---@param ... (table of strings) Will be concatenated before being written
|
||||
local function err_message(...)
|
||||
vim.notify(table.concat(vim.tbl_flatten{...}), vim.log.levels.ERROR)
|
||||
api.nvim_command("redraw")
|
||||
vim.notify(table.concat(vim.tbl_flatten({ ... })), vim.log.levels.ERROR)
|
||||
api.nvim_command('redraw')
|
||||
end
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
|
||||
@@ -25,15 +25,17 @@ end
|
||||
local function progress_handler(_, result, ctx, _)
|
||||
local client_id = ctx.client_id
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
local client_name = client and client.name or string.format("id=%d", client_id)
|
||||
local client_name = client and client.name or string.format('id=%d', client_id)
|
||||
if not client then
|
||||
err_message("LSP[", client_name, "] client has shut down during progress update")
|
||||
err_message('LSP[', client_name, '] client has shut down during progress update')
|
||||
return vim.NIL
|
||||
end
|
||||
local val = result.value -- unspecified yet
|
||||
local token = result.token -- string or number
|
||||
local val = result.value -- unspecified yet
|
||||
local token = result.token -- string or number
|
||||
|
||||
if type(val) ~= 'table' then val = { content=val } end
|
||||
if type(val) ~= 'table' then
|
||||
val = { content = val }
|
||||
end
|
||||
if val.kind then
|
||||
if val.kind == 'begin' then
|
||||
client.messages.progress[token] = {
|
||||
@@ -42,11 +44,11 @@ local function progress_handler(_, result, ctx, _)
|
||||
percentage = val.percentage,
|
||||
}
|
||||
elseif val.kind == 'report' then
|
||||
client.messages.progress[token].message = val.message;
|
||||
client.messages.progress[token].percentage = val.percentage;
|
||||
client.messages.progress[token].message = val.message
|
||||
client.messages.progress[token].percentage = val.percentage
|
||||
elseif val.kind == 'end' then
|
||||
if client.messages.progress[token] == nil then
|
||||
err_message("LSP[", client_name, "] received `end` message with no corresponding `begin`")
|
||||
err_message('LSP[', client_name, '] received `end` message with no corresponding `begin`')
|
||||
else
|
||||
client.messages.progress[token].message = val.message
|
||||
client.messages.progress[token].done = true
|
||||
@@ -57,20 +59,20 @@ local function progress_handler(_, result, ctx, _)
|
||||
client.messages.progress[token].done = true
|
||||
end
|
||||
|
||||
vim.api.nvim_command("doautocmd <nomodeline> User LspProgressUpdate")
|
||||
vim.api.nvim_command('doautocmd <nomodeline> User LspProgressUpdate')
|
||||
end
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
|
||||
M['$/progress'] = progress_handler
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
|
||||
M['window/workDoneProgress/create'] = function(_, result, ctx)
|
||||
M['window/workDoneProgress/create'] = function(_, result, ctx)
|
||||
local client_id = ctx.client_id
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
local token = result.token -- string or number
|
||||
local client_name = client and client.name or string.format("id=%d", client_id)
|
||||
local token = result.token -- string or number
|
||||
local client_name = client and client.name or string.format('id=%d', client_id)
|
||||
if not client then
|
||||
err_message("LSP[", client_name, "] client has shut down while creating progress report")
|
||||
err_message('LSP[', client_name, '] client has shut down while creating progress report')
|
||||
return vim.NIL
|
||||
end
|
||||
client.messages.progress[token] = {}
|
||||
@@ -79,20 +81,19 @@ end
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
|
||||
M['window/showMessageRequest'] = function(_, result)
|
||||
|
||||
local actions = result.actions
|
||||
print(result.message)
|
||||
local option_strings = {result.message, "\nRequest Actions:"}
|
||||
local option_strings = { result.message, '\nRequest Actions:' }
|
||||
for i, action in ipairs(actions) do
|
||||
local title = action.title:gsub('\r\n', '\\r\\n')
|
||||
title = title:gsub('\n', '\\n')
|
||||
table.insert(option_strings, string.format("%d. %s", i, title))
|
||||
table.insert(option_strings, string.format('%d. %s', i, title))
|
||||
end
|
||||
|
||||
-- window/showMessageRequest can return either MessageActionItem[] or null.
|
||||
local choice = vim.fn.inputlist(option_strings)
|
||||
if choice < 1 or choice > #actions then
|
||||
return vim.NIL
|
||||
return vim.NIL
|
||||
else
|
||||
return actions[choice]
|
||||
end
|
||||
@@ -101,11 +102,11 @@ end
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
|
||||
M['client/registerCapability'] = function(_, _, ctx)
|
||||
local client_id = ctx.client_id
|
||||
local warning_tpl = "The language server %s triggers a registerCapability "..
|
||||
"handler despite dynamicRegistration set to false. "..
|
||||
"Report upstream, this warning is harmless"
|
||||
local warning_tpl = 'The language server %s triggers a registerCapability '
|
||||
.. 'handler despite dynamicRegistration set to false. '
|
||||
.. 'Report upstream, this warning is harmless'
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
local client_name = client and client.name or string.format("id=%d", client_id)
|
||||
local client_name = client and client.name or string.format('id=%d', client_id)
|
||||
local warning = string.format(warning_tpl, client_name)
|
||||
log.warn(warning)
|
||||
return vim.NIL
|
||||
@@ -113,17 +114,19 @@ end
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
|
||||
M['workspace/applyEdit'] = function(_, workspace_edit, ctx)
|
||||
if not workspace_edit then return end
|
||||
if not workspace_edit then
|
||||
return
|
||||
end
|
||||
-- TODO(ashkan) Do something more with label?
|
||||
local client_id = ctx.client_id
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
if workspace_edit.label then
|
||||
print("Workspace edit", workspace_edit.label)
|
||||
print('Workspace edit', workspace_edit.label)
|
||||
end
|
||||
local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit, client.offset_encoding)
|
||||
return {
|
||||
applied = status;
|
||||
failureReason = result;
|
||||
applied = status,
|
||||
failureReason = result,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -132,7 +135,7 @@ M['workspace/configuration'] = function(_, result, ctx)
|
||||
local client_id = ctx.client_id
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
if not client then
|
||||
err_message("LSP[", client_id, "] client has shut down after sending a workspace/configuration request")
|
||||
err_message('LSP[', client_id, '] client has shut down after sending a workspace/configuration request')
|
||||
return
|
||||
end
|
||||
if not result.items then
|
||||
@@ -158,7 +161,7 @@ M['workspace/workspaceFolders'] = function(_, _, ctx)
|
||||
local client_id = ctx.client_id
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
if not client then
|
||||
err_message("LSP[id=", client_id, "] client has shut down after sending the message")
|
||||
err_message('LSP[id=', client_id, '] client has shut down after sending the message')
|
||||
return
|
||||
end
|
||||
return client.workspace_folders or vim.NIL
|
||||
@@ -172,7 +175,6 @@ M['textDocument/codeLens'] = function(...)
|
||||
return require('vim.lsp.codelens').on_codelens(...)
|
||||
end
|
||||
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
|
||||
M['textDocument/references'] = function(_, result, ctx, config)
|
||||
if not result or vim.tbl_isempty(result) then
|
||||
@@ -182,23 +184,22 @@ M['textDocument/references'] = function(_, result, ctx, config)
|
||||
config = config or {}
|
||||
if config.loclist then
|
||||
vim.fn.setloclist(0, {}, ' ', {
|
||||
title = 'References';
|
||||
items = util.locations_to_items(result, client.offset_encoding);
|
||||
context = ctx;
|
||||
title = 'References',
|
||||
items = util.locations_to_items(result, client.offset_encoding),
|
||||
context = ctx,
|
||||
})
|
||||
api.nvim_command("lopen")
|
||||
api.nvim_command('lopen')
|
||||
else
|
||||
vim.fn.setqflist({}, ' ', {
|
||||
title = 'References';
|
||||
items = util.locations_to_items(result, client.offset_encoding);
|
||||
context = ctx;
|
||||
title = 'References',
|
||||
items = util.locations_to_items(result, client.offset_encoding),
|
||||
context = ctx,
|
||||
})
|
||||
api.nvim_command("botright copen")
|
||||
api.nvim_command('botright copen')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@private
|
||||
--- Return a function that converts LSP responses to list items and opens the list
|
||||
---
|
||||
@@ -218,27 +219,26 @@ local function response_to_list(map_result, entity, title_fn)
|
||||
config = config or {}
|
||||
if config.loclist then
|
||||
vim.fn.setloclist(0, {}, ' ', {
|
||||
title = title_fn(ctx);
|
||||
items = map_result(result, ctx.bufnr);
|
||||
context = ctx;
|
||||
title = title_fn(ctx),
|
||||
items = map_result(result, ctx.bufnr),
|
||||
context = ctx,
|
||||
})
|
||||
api.nvim_command("lopen")
|
||||
api.nvim_command('lopen')
|
||||
else
|
||||
vim.fn.setqflist({}, ' ', {
|
||||
title = title_fn(ctx);
|
||||
items = map_result(result, ctx.bufnr);
|
||||
context = ctx;
|
||||
title = title_fn(ctx),
|
||||
items = map_result(result, ctx.bufnr),
|
||||
context = ctx,
|
||||
})
|
||||
api.nvim_command("botright copen")
|
||||
api.nvim_command('botright copen')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
|
||||
M['textDocument/documentSymbol'] = response_to_list(util.symbols_to_items, 'document symbols', function(ctx)
|
||||
local fname = vim.fn.fnamemodify(vim.uri_to_fname(ctx.params.textDocument.uri), ":.")
|
||||
local fname = vim.fn.fnamemodify(vim.uri_to_fname(ctx.params.textDocument.uri), ':.')
|
||||
return string.format('Symbols in %s', fname)
|
||||
end)
|
||||
|
||||
@@ -249,36 +249,44 @@ end)
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
|
||||
M['textDocument/rename'] = function(_, result, ctx, _)
|
||||
if not result then return end
|
||||
if not result then
|
||||
return
|
||||
end
|
||||
local client = vim.lsp.get_client_by_id(ctx.client_id)
|
||||
util.apply_workspace_edit(result, client.offset_encoding)
|
||||
end
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
|
||||
M['textDocument/rangeFormatting'] = function(_, result, ctx, _)
|
||||
if not result then return end
|
||||
if not result then
|
||||
return
|
||||
end
|
||||
local client = vim.lsp.get_client_by_id(ctx.client_id)
|
||||
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
|
||||
end
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
|
||||
M['textDocument/formatting'] = function(_, result, ctx, _)
|
||||
if not result then return end
|
||||
if not result then
|
||||
return
|
||||
end
|
||||
local client = vim.lsp.get_client_by_id(ctx.client_id)
|
||||
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
|
||||
end
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
|
||||
M['textDocument/completion'] = function(_, result, _, _)
|
||||
if vim.tbl_isempty(result or {}) then return end
|
||||
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 line = assert(api.nvim_buf_get_lines(0, row - 1, row, false)[1])
|
||||
local line_to_cursor = line:sub(col + 1)
|
||||
local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
|
||||
local prefix = line_to_cursor:sub(textMatch+1)
|
||||
local prefix = line_to_cursor:sub(textMatch + 1)
|
||||
|
||||
local matches = util.text_document_completion_list_to_complete_items(result, prefix)
|
||||
vim.fn.complete(textMatch+1, matches)
|
||||
vim.fn.complete(textMatch + 1, matches)
|
||||
end
|
||||
|
||||
--- |lsp-handler| for the method "textDocument/hover"
|
||||
@@ -307,7 +315,7 @@ function M.hover(_, result, ctx, config)
|
||||
vim.notify('No information available')
|
||||
return
|
||||
end
|
||||
return util.open_floating_preview(markdown_lines, "markdown", config)
|
||||
return util.open_floating_preview(markdown_lines, 'markdown', config)
|
||||
end
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
|
||||
@@ -335,9 +343,9 @@ local function location_handler(_, result, ctx, _)
|
||||
if #result > 1 then
|
||||
vim.fn.setqflist({}, ' ', {
|
||||
title = 'LSP locations',
|
||||
items = util.locations_to_items(result, client.offset_encoding)
|
||||
items = util.locations_to_items(result, client.offset_encoding),
|
||||
})
|
||||
api.nvim_command("botright copen")
|
||||
api.nvim_command('botright copen')
|
||||
end
|
||||
else
|
||||
util.jump_to_location(result, client.offset_encoding)
|
||||
@@ -379,7 +387,7 @@ function M.signature_help(_, result, ctx, config)
|
||||
return
|
||||
end
|
||||
local client = vim.lsp.get_client_by_id(ctx.client_id)
|
||||
local triggers = vim.tbl_get(client.server_capabilities, "signatureHelpProvider", "triggerCharacters")
|
||||
local triggers = vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
|
||||
local ft = api.nvim_buf_get_option(ctx.bufnr, 'filetype')
|
||||
local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers)
|
||||
lines = util.trim_empty_lines(lines)
|
||||
@@ -389,9 +397,9 @@ function M.signature_help(_, result, ctx, config)
|
||||
end
|
||||
return
|
||||
end
|
||||
local fbuf, fwin = util.open_floating_preview(lines, "markdown", config)
|
||||
local fbuf, fwin = util.open_floating_preview(lines, 'markdown', config)
|
||||
if hl then
|
||||
api.nvim_buf_add_highlight(fbuf, -1, "LspSignatureActiveParameter", 0, unpack(hl))
|
||||
api.nvim_buf_add_highlight(fbuf, -1, 'LspSignatureActiveParameter', 0, unpack(hl))
|
||||
end
|
||||
return fbuf, fwin
|
||||
end
|
||||
@@ -401,10 +409,14 @@ M['textDocument/signatureHelp'] = M.signature_help
|
||||
|
||||
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
|
||||
M['textDocument/documentHighlight'] = function(_, result, ctx, _)
|
||||
if not result then return end
|
||||
if not result then
|
||||
return
|
||||
end
|
||||
local client_id = ctx.client_id
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
if not client then return end
|
||||
if not client then
|
||||
return
|
||||
end
|
||||
util.buf_highlight_references(ctx.bufnr, result, client.offset_encoding)
|
||||
end
|
||||
|
||||
@@ -417,7 +429,9 @@ end
|
||||
---@returns `CallHierarchyOutgoingCall[]` if {direction} is `"to"`,
|
||||
local make_call_hierarchy_handler = function(direction)
|
||||
return function(_, result)
|
||||
if not result then return end
|
||||
if not result then
|
||||
return
|
||||
end
|
||||
local items = {}
|
||||
for _, call_hierarchy_call in pairs(result) do
|
||||
local call_hierarchy_item = call_hierarchy_call[direction]
|
||||
@@ -430,8 +444,8 @@ local make_call_hierarchy_handler = function(direction)
|
||||
})
|
||||
end
|
||||
end
|
||||
vim.fn.setqflist({}, ' ', {title = 'LSP call hierarchy', items = items})
|
||||
api.nvim_command("botright copen")
|
||||
vim.fn.setqflist({}, ' ', { title = 'LSP call hierarchy', items = items })
|
||||
api.nvim_command('botright copen')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -447,15 +461,15 @@ M['window/logMessage'] = function(_, result, ctx, _)
|
||||
local message = result.message
|
||||
local client_id = ctx.client_id
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
local client_name = client and client.name or string.format("id=%d", client_id)
|
||||
local client_name = client and client.name or string.format('id=%d', client_id)
|
||||
if not client then
|
||||
err_message("LSP[", client_name, "] client has shut down after sending ", message)
|
||||
err_message('LSP[', client_name, '] client has shut down after sending ', message)
|
||||
end
|
||||
if message_type == protocol.MessageType.Error then
|
||||
log.error(message)
|
||||
elseif message_type == protocol.MessageType.Warning then
|
||||
log.warn(message)
|
||||
elseif message_type == protocol.MessageType.Info or message_type == protocol.MessageType.Log then
|
||||
elseif message_type == protocol.MessageType.Info or message_type == protocol.MessageType.Log then
|
||||
log.info(message)
|
||||
else
|
||||
log.debug(message)
|
||||
@@ -469,15 +483,15 @@ M['window/showMessage'] = function(_, result, ctx, _)
|
||||
local message = result.message
|
||||
local client_id = ctx.client_id
|
||||
local client = vim.lsp.get_client_by_id(client_id)
|
||||
local client_name = client and client.name or string.format("id=%d", client_id)
|
||||
local client_name = client and client.name or string.format('id=%d', client_id)
|
||||
if not client then
|
||||
err_message("LSP[", client_name, "] client has shut down after sending ", message)
|
||||
err_message('LSP[', client_name, '] client has shut down after sending ', message)
|
||||
end
|
||||
if message_type == protocol.MessageType.Error then
|
||||
err_message("LSP[", client_name, "] ", message)
|
||||
err_message('LSP[', client_name, '] ', message)
|
||||
else
|
||||
local message_type_name = protocol.MessageType[message_type]
|
||||
api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message))
|
||||
api.nvim_out_write(string.format('LSP[%s][%s] %s\n', client_name, message_type_name, message))
|
||||
end
|
||||
return result
|
||||
end
|
||||
@@ -485,9 +499,13 @@ end
|
||||
-- Add boilerplate error validation and logging for all of these.
|
||||
for k, fn in pairs(M) do
|
||||
M[k] = function(err, result, ctx, config)
|
||||
local _ = log.trace() and log.trace('default_handler', ctx.method, {
|
||||
err = err, result = result, ctx=vim.inspect(ctx), config = config
|
||||
})
|
||||
local _ = log.trace()
|
||||
and log.trace('default_handler', ctx.method, {
|
||||
err = err,
|
||||
result = result,
|
||||
ctx = vim.inspect(ctx),
|
||||
config = config,
|
||||
})
|
||||
|
||||
if err then
|
||||
-- LSP spec:
|
||||
@@ -499,7 +517,7 @@ for k, fn in pairs(M) do
|
||||
-- Per LSP, don't show ContentModified error to the user.
|
||||
if err.code ~= protocol.ErrorCodes.ContentModified then
|
||||
local client = vim.lsp.get_client_by_id(ctx.client_id)
|
||||
local client_name = client and client.name or string.format("client_id=%d", ctx.client_id)
|
||||
local client_name = client and client.name or string.format('client_id=%d', ctx.client_id)
|
||||
|
||||
err_message(client_name .. ': ' .. tostring(err.code) .. ': ' .. err.message)
|
||||
end
|
||||
|
||||
@@ -8,20 +8,19 @@ function M.check()
|
||||
local log = require('vim.lsp.log')
|
||||
local current_log_level = log.get_level()
|
||||
local log_level_string = log.levels[current_log_level]
|
||||
report_info(string.format("LSP log level : %s", log_level_string))
|
||||
report_info(string.format('LSP log level : %s', log_level_string))
|
||||
|
||||
if current_log_level < log.levels.WARN then
|
||||
report_warn(string.format("Log level %s will cause degraded performance and high disk usage", log_level_string))
|
||||
report_warn(string.format('Log level %s will cause degraded performance and high disk usage', log_level_string))
|
||||
end
|
||||
|
||||
local log_path = vim.lsp.get_log_path()
|
||||
report_info(string.format("Log path: %s", log_path))
|
||||
report_info(string.format('Log path: %s', log_path))
|
||||
|
||||
local log_size = vim.loop.fs_stat(log_path).size
|
||||
|
||||
local report_fn = (log_size / 1000000 > 100 and report_warn or report_info)
|
||||
report_fn(string.format("Log size: %d KB", log_size / 1000 ))
|
||||
report_fn(string.format('Log size: %d KB', log_size / 1000))
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
|
||||
@@ -14,21 +14,23 @@ log.levels = vim.deepcopy(vim.log.levels)
|
||||
|
||||
-- Default log level is warn.
|
||||
local current_log_level = log.levels.WARN
|
||||
local log_date_format = "%F %H:%M:%S"
|
||||
local format_func = function(arg) return vim.inspect(arg, {newline=''}) end
|
||||
local log_date_format = '%F %H:%M:%S'
|
||||
local format_func = function(arg)
|
||||
return vim.inspect(arg, { newline = '' })
|
||||
end
|
||||
|
||||
do
|
||||
local path_sep = vim.loop.os_uname().version:match("Windows") and "\\" or "/"
|
||||
local path_sep = vim.loop.os_uname().version:match('Windows') and '\\' or '/'
|
||||
---@private
|
||||
local function path_join(...)
|
||||
return table.concat(vim.tbl_flatten{...}, path_sep)
|
||||
return table.concat(vim.tbl_flatten({ ... }), path_sep)
|
||||
end
|
||||
local logfilename = path_join(vim.fn.stdpath('cache'), 'lsp.log')
|
||||
|
||||
-- TODO: Ideally the directory should be created in open_logfile(), right
|
||||
-- before opening the log file, but open_logfile() can be called from libuv
|
||||
-- callbacks, where using fn.mkdir() is not allowed.
|
||||
vim.fn.mkdir(vim.fn.stdpath('cache'), "p")
|
||||
vim.fn.mkdir(vim.fn.stdpath('cache'), 'p')
|
||||
|
||||
--- Returns the log filename.
|
||||
---@returns (string) log filename
|
||||
@@ -41,28 +43,28 @@ do
|
||||
--- Opens log file. Returns true if file is open, false on error
|
||||
local function open_logfile()
|
||||
-- Try to open file only once
|
||||
if logfile then return true end
|
||||
if openerr then return false end
|
||||
if logfile then
|
||||
return true
|
||||
end
|
||||
if openerr then
|
||||
return false
|
||||
end
|
||||
|
||||
logfile, openerr = io.open(logfilename, "a+")
|
||||
logfile, openerr = io.open(logfilename, 'a+')
|
||||
if not logfile then
|
||||
local err_msg = string.format("Failed to open LSP client log file: %s", openerr)
|
||||
local err_msg = string.format('Failed to open LSP client log file: %s', openerr)
|
||||
vim.notify(err_msg, vim.log.levels.ERROR)
|
||||
return false
|
||||
end
|
||||
|
||||
local log_info = vim.loop.fs_stat(logfilename)
|
||||
if log_info and log_info.size > 1e9 then
|
||||
local warn_msg = string.format(
|
||||
"LSP client log is large (%d MB): %s",
|
||||
log_info.size / (1000 * 1000),
|
||||
logfilename
|
||||
)
|
||||
local warn_msg = string.format('LSP client log is large (%d MB): %s', log_info.size / (1000 * 1000), logfilename)
|
||||
vim.notify(warn_msg)
|
||||
end
|
||||
|
||||
-- Start message for logging
|
||||
logfile:write(string.format("[START][%s] LSP logging initiated\n", os.date(log_date_format)))
|
||||
logfile:write(string.format('[START][%s] LSP logging initiated\n', os.date(log_date_format)))
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -83,24 +85,36 @@ do
|
||||
-- ```
|
||||
--
|
||||
-- This way you can avoid string allocations if the log level isn't high enough.
|
||||
if level ~= "OFF" then
|
||||
if level ~= 'OFF' then
|
||||
log[level:lower()] = function(...)
|
||||
local argc = select("#", ...)
|
||||
if levelnr < current_log_level then return false end
|
||||
if argc == 0 then return true end
|
||||
if not open_logfile() then return false end
|
||||
local info = debug.getinfo(2, "Sl")
|
||||
local header = string.format("[%s][%s] ...%s:%s", level, os.date(log_date_format), string.sub(info.short_src, #info.short_src - 15), info.currentline)
|
||||
local argc = select('#', ...)
|
||||
if levelnr < current_log_level then
|
||||
return false
|
||||
end
|
||||
if argc == 0 then
|
||||
return true
|
||||
end
|
||||
if not open_logfile() then
|
||||
return false
|
||||
end
|
||||
local info = debug.getinfo(2, 'Sl')
|
||||
local header = string.format(
|
||||
'[%s][%s] ...%s:%s',
|
||||
level,
|
||||
os.date(log_date_format),
|
||||
string.sub(info.short_src, #info.short_src - 15),
|
||||
info.currentline
|
||||
)
|
||||
local parts = { header }
|
||||
for i = 1, argc do
|
||||
local arg = select(i, ...)
|
||||
if arg == nil then
|
||||
table.insert(parts, "nil")
|
||||
table.insert(parts, 'nil')
|
||||
else
|
||||
table.insert(parts, format_func(arg))
|
||||
end
|
||||
end
|
||||
logfile:write(table.concat(parts, '\t'), "\n")
|
||||
logfile:write(table.concat(parts, '\t'), '\n')
|
||||
logfile:flush()
|
||||
end
|
||||
end
|
||||
@@ -115,10 +129,10 @@ vim.tbl_add_reverse_lookup(log.levels)
|
||||
---@param level (string or number) One of `vim.lsp.log.levels`
|
||||
function log.set_level(level)
|
||||
if type(level) == 'string' then
|
||||
current_log_level = assert(log.levels[level:upper()], string.format("Invalid log level: %q", level))
|
||||
current_log_level = assert(log.levels[level:upper()], string.format('Invalid log level: %q', level))
|
||||
else
|
||||
assert(type(level) == 'number', "level must be a number or string")
|
||||
assert(log.levels[level], string.format("Invalid log level: %d", level))
|
||||
assert(type(level) == 'number', 'level must be a number or string')
|
||||
assert(log.levels[level], string.format('Invalid log level: %d', level))
|
||||
current_log_level = level
|
||||
end
|
||||
end
|
||||
@@ -132,7 +146,7 @@ end
|
||||
--- Sets formatting function used to format logs
|
||||
---@param handle function function to apply to logging arguments, pass vim.inspect for multi-line formatting
|
||||
function log.set_format_func(handle)
|
||||
assert(handle == vim.inspect or type(handle) == 'function', "handle must be a function")
|
||||
assert(handle == vim.inspect or type(handle) == 'function', 'handle must be a function')
|
||||
format_func = handle
|
||||
end
|
||||
|
||||
|
||||
@@ -23,150 +23,150 @@ end
|
||||
local constants = {
|
||||
DiagnosticSeverity = {
|
||||
-- Reports an error.
|
||||
Error = 1;
|
||||
Error = 1,
|
||||
-- Reports a warning.
|
||||
Warning = 2;
|
||||
Warning = 2,
|
||||
-- Reports an information.
|
||||
Information = 3;
|
||||
Information = 3,
|
||||
-- Reports a hint.
|
||||
Hint = 4;
|
||||
};
|
||||
Hint = 4,
|
||||
},
|
||||
|
||||
DiagnosticTag = {
|
||||
-- Unused or unnecessary code
|
||||
Unnecessary = 1;
|
||||
Unnecessary = 1,
|
||||
-- Deprecated or obsolete code
|
||||
Deprecated = 2;
|
||||
};
|
||||
Deprecated = 2,
|
||||
},
|
||||
|
||||
MessageType = {
|
||||
-- An error message.
|
||||
Error = 1;
|
||||
Error = 1,
|
||||
-- A warning message.
|
||||
Warning = 2;
|
||||
Warning = 2,
|
||||
-- An information message.
|
||||
Info = 3;
|
||||
Info = 3,
|
||||
-- A log message.
|
||||
Log = 4;
|
||||
};
|
||||
Log = 4,
|
||||
},
|
||||
|
||||
-- The file event type.
|
||||
FileChangeType = {
|
||||
-- The file got created.
|
||||
Created = 1;
|
||||
Created = 1,
|
||||
-- The file got changed.
|
||||
Changed = 2;
|
||||
Changed = 2,
|
||||
-- The file got deleted.
|
||||
Deleted = 3;
|
||||
};
|
||||
Deleted = 3,
|
||||
},
|
||||
|
||||
-- The kind of a completion entry.
|
||||
CompletionItemKind = {
|
||||
Text = 1;
|
||||
Method = 2;
|
||||
Function = 3;
|
||||
Constructor = 4;
|
||||
Field = 5;
|
||||
Variable = 6;
|
||||
Class = 7;
|
||||
Interface = 8;
|
||||
Module = 9;
|
||||
Property = 10;
|
||||
Unit = 11;
|
||||
Value = 12;
|
||||
Enum = 13;
|
||||
Keyword = 14;
|
||||
Snippet = 15;
|
||||
Color = 16;
|
||||
File = 17;
|
||||
Reference = 18;
|
||||
Folder = 19;
|
||||
EnumMember = 20;
|
||||
Constant = 21;
|
||||
Struct = 22;
|
||||
Event = 23;
|
||||
Operator = 24;
|
||||
TypeParameter = 25;
|
||||
};
|
||||
Text = 1,
|
||||
Method = 2,
|
||||
Function = 3,
|
||||
Constructor = 4,
|
||||
Field = 5,
|
||||
Variable = 6,
|
||||
Class = 7,
|
||||
Interface = 8,
|
||||
Module = 9,
|
||||
Property = 10,
|
||||
Unit = 11,
|
||||
Value = 12,
|
||||
Enum = 13,
|
||||
Keyword = 14,
|
||||
Snippet = 15,
|
||||
Color = 16,
|
||||
File = 17,
|
||||
Reference = 18,
|
||||
Folder = 19,
|
||||
EnumMember = 20,
|
||||
Constant = 21,
|
||||
Struct = 22,
|
||||
Event = 23,
|
||||
Operator = 24,
|
||||
TypeParameter = 25,
|
||||
},
|
||||
|
||||
-- How a completion was triggered
|
||||
CompletionTriggerKind = {
|
||||
-- Completion was triggered by typing an identifier (24x7 code
|
||||
-- complete), manual invocation (e.g Ctrl+Space) or via API.
|
||||
Invoked = 1;
|
||||
Invoked = 1,
|
||||
-- Completion was triggered by a trigger character specified by
|
||||
-- the `triggerCharacters` properties of the `CompletionRegistrationOptions`.
|
||||
TriggerCharacter = 2;
|
||||
TriggerCharacter = 2,
|
||||
-- Completion was re-triggered as the current completion list is incomplete.
|
||||
TriggerForIncompleteCompletions = 3;
|
||||
};
|
||||
TriggerForIncompleteCompletions = 3,
|
||||
},
|
||||
|
||||
-- A document highlight kind.
|
||||
DocumentHighlightKind = {
|
||||
-- A textual occurrence.
|
||||
Text = 1;
|
||||
Text = 1,
|
||||
-- Read-access of a symbol, like reading a variable.
|
||||
Read = 2;
|
||||
Read = 2,
|
||||
-- Write-access of a symbol, like writing to a variable.
|
||||
Write = 3;
|
||||
};
|
||||
Write = 3,
|
||||
},
|
||||
|
||||
-- A symbol kind.
|
||||
SymbolKind = {
|
||||
File = 1;
|
||||
Module = 2;
|
||||
Namespace = 3;
|
||||
Package = 4;
|
||||
Class = 5;
|
||||
Method = 6;
|
||||
Property = 7;
|
||||
Field = 8;
|
||||
Constructor = 9;
|
||||
Enum = 10;
|
||||
Interface = 11;
|
||||
Function = 12;
|
||||
Variable = 13;
|
||||
Constant = 14;
|
||||
String = 15;
|
||||
Number = 16;
|
||||
Boolean = 17;
|
||||
Array = 18;
|
||||
Object = 19;
|
||||
Key = 20;
|
||||
Null = 21;
|
||||
EnumMember = 22;
|
||||
Struct = 23;
|
||||
Event = 24;
|
||||
Operator = 25;
|
||||
TypeParameter = 26;
|
||||
};
|
||||
File = 1,
|
||||
Module = 2,
|
||||
Namespace = 3,
|
||||
Package = 4,
|
||||
Class = 5,
|
||||
Method = 6,
|
||||
Property = 7,
|
||||
Field = 8,
|
||||
Constructor = 9,
|
||||
Enum = 10,
|
||||
Interface = 11,
|
||||
Function = 12,
|
||||
Variable = 13,
|
||||
Constant = 14,
|
||||
String = 15,
|
||||
Number = 16,
|
||||
Boolean = 17,
|
||||
Array = 18,
|
||||
Object = 19,
|
||||
Key = 20,
|
||||
Null = 21,
|
||||
EnumMember = 22,
|
||||
Struct = 23,
|
||||
Event = 24,
|
||||
Operator = 25,
|
||||
TypeParameter = 26,
|
||||
},
|
||||
|
||||
-- Represents reasons why a text document is saved.
|
||||
TextDocumentSaveReason = {
|
||||
-- Manually triggered, e.g. by the user pressing save, by starting debugging,
|
||||
-- or by an API call.
|
||||
Manual = 1;
|
||||
Manual = 1,
|
||||
-- Automatic after a delay.
|
||||
AfterDelay = 2;
|
||||
AfterDelay = 2,
|
||||
-- When the editor lost focus.
|
||||
FocusOut = 3;
|
||||
};
|
||||
FocusOut = 3,
|
||||
},
|
||||
|
||||
ErrorCodes = {
|
||||
-- Defined by JSON RPC
|
||||
ParseError = -32700;
|
||||
InvalidRequest = -32600;
|
||||
MethodNotFound = -32601;
|
||||
InvalidParams = -32602;
|
||||
InternalError = -32603;
|
||||
serverErrorStart = -32099;
|
||||
serverErrorEnd = -32000;
|
||||
ServerNotInitialized = -32002;
|
||||
UnknownErrorCode = -32001;
|
||||
ParseError = -32700,
|
||||
InvalidRequest = -32600,
|
||||
MethodNotFound = -32601,
|
||||
InvalidParams = -32602,
|
||||
InternalError = -32603,
|
||||
serverErrorStart = -32099,
|
||||
serverErrorEnd = -32000,
|
||||
ServerNotInitialized = -32002,
|
||||
UnknownErrorCode = -32001,
|
||||
-- Defined by the protocol.
|
||||
RequestCancelled = -32800;
|
||||
ContentModified = -32801;
|
||||
};
|
||||
RequestCancelled = -32800,
|
||||
ContentModified = -32801,
|
||||
},
|
||||
|
||||
-- Describes the content type that a client supports in various
|
||||
-- result literals like `Hover`, `ParameterInfo` or `CompletionItem`.
|
||||
@@ -175,88 +175,88 @@ local constants = {
|
||||
-- are reserved for internal usage.
|
||||
MarkupKind = {
|
||||
-- Plain text is supported as a content format
|
||||
PlainText = 'plaintext';
|
||||
PlainText = 'plaintext',
|
||||
-- Markdown is supported as a content format
|
||||
Markdown = 'markdown';
|
||||
};
|
||||
Markdown = 'markdown',
|
||||
},
|
||||
|
||||
ResourceOperationKind = {
|
||||
-- Supports creating new files and folders.
|
||||
Create = 'create';
|
||||
Create = 'create',
|
||||
-- Supports renaming existing files and folders.
|
||||
Rename = 'rename';
|
||||
Rename = 'rename',
|
||||
-- Supports deleting existing files and folders.
|
||||
Delete = 'delete';
|
||||
};
|
||||
Delete = 'delete',
|
||||
},
|
||||
|
||||
FailureHandlingKind = {
|
||||
-- Applying the workspace change is simply aborted if one of the changes provided
|
||||
-- fails. All operations executed before the failing operation stay executed.
|
||||
Abort = 'abort';
|
||||
Abort = 'abort',
|
||||
-- All operations are executed transactionally. That means they either all
|
||||
-- succeed or no changes at all are applied to the workspace.
|
||||
Transactional = 'transactional';
|
||||
Transactional = 'transactional',
|
||||
-- If the workspace edit contains only textual file changes they are executed transactionally.
|
||||
-- If resource changes (create, rename or delete file) are part of the change the failure
|
||||
-- handling strategy is abort.
|
||||
TextOnlyTransactional = 'textOnlyTransactional';
|
||||
TextOnlyTransactional = 'textOnlyTransactional',
|
||||
-- The client tries to undo the operations already executed. But there is no
|
||||
-- guarantee that this succeeds.
|
||||
Undo = 'undo';
|
||||
};
|
||||
Undo = 'undo',
|
||||
},
|
||||
|
||||
-- Known error codes for an `InitializeError`;
|
||||
InitializeError = {
|
||||
-- If the protocol version provided by the client can't be handled by the server.
|
||||
-- @deprecated This initialize error got replaced by client capabilities. There is
|
||||
-- no version handshake in version 3.0x
|
||||
unknownProtocolVersion = 1;
|
||||
};
|
||||
unknownProtocolVersion = 1,
|
||||
},
|
||||
|
||||
-- Defines how the host (editor) should sync document changes to the language server.
|
||||
TextDocumentSyncKind = {
|
||||
-- Documents should not be synced at all.
|
||||
None = 0;
|
||||
None = 0,
|
||||
-- Documents are synced by always sending the full content
|
||||
-- of the document.
|
||||
Full = 1;
|
||||
Full = 1,
|
||||
-- Documents are synced by sending the full content on open.
|
||||
-- After that only incremental updates to the document are
|
||||
-- send.
|
||||
Incremental = 2;
|
||||
};
|
||||
Incremental = 2,
|
||||
},
|
||||
|
||||
WatchKind = {
|
||||
-- Interested in create events.
|
||||
Create = 1;
|
||||
Create = 1,
|
||||
-- Interested in change events
|
||||
Change = 2;
|
||||
Change = 2,
|
||||
-- Interested in delete events
|
||||
Delete = 4;
|
||||
};
|
||||
Delete = 4,
|
||||
},
|
||||
|
||||
-- Defines whether the insert text in a completion item should be interpreted as
|
||||
-- plain text or a snippet.
|
||||
InsertTextFormat = {
|
||||
-- The primary text to be inserted is treated as a plain string.
|
||||
PlainText = 1;
|
||||
PlainText = 1,
|
||||
-- The primary text to be inserted is treated as a snippet.
|
||||
--
|
||||
-- A snippet can define tab stops and placeholders with `$1`, `$2`
|
||||
-- and `${3:foo};`. `$0` defines the final tab stop, it defaults to
|
||||
-- the end of the snippet. Placeholders with equal identifiers are linked,
|
||||
-- that is typing in one will update others too.
|
||||
Snippet = 2;
|
||||
};
|
||||
Snippet = 2,
|
||||
},
|
||||
|
||||
-- A set of predefined code action kinds
|
||||
CodeActionKind = {
|
||||
-- Empty kind.
|
||||
Empty = '';
|
||||
Empty = '',
|
||||
-- Base kind for quickfix actions
|
||||
QuickFix = 'quickfix';
|
||||
QuickFix = 'quickfix',
|
||||
-- Base kind for refactoring actions
|
||||
Refactor = 'refactor';
|
||||
Refactor = 'refactor',
|
||||
-- Base kind for refactoring extraction actions
|
||||
--
|
||||
-- Example extract actions:
|
||||
@@ -266,7 +266,7 @@ local constants = {
|
||||
-- - Extract variable
|
||||
-- - Extract interface from class
|
||||
-- - ...
|
||||
RefactorExtract = 'refactor.extract';
|
||||
RefactorExtract = 'refactor.extract',
|
||||
-- Base kind for refactoring inline actions
|
||||
--
|
||||
-- Example inline actions:
|
||||
@@ -275,7 +275,7 @@ local constants = {
|
||||
-- - Inline variable
|
||||
-- - Inline constant
|
||||
-- - ...
|
||||
RefactorInline = 'refactor.inline';
|
||||
RefactorInline = 'refactor.inline',
|
||||
-- Base kind for refactoring rewrite actions
|
||||
--
|
||||
-- Example rewrite actions:
|
||||
@@ -286,14 +286,14 @@ local constants = {
|
||||
-- - Make method static
|
||||
-- - Move method to base class
|
||||
-- - ...
|
||||
RefactorRewrite = 'refactor.rewrite';
|
||||
RefactorRewrite = 'refactor.rewrite',
|
||||
-- Base kind for source actions
|
||||
--
|
||||
-- Source code actions apply to the entire file.
|
||||
Source = 'source';
|
||||
Source = 'source',
|
||||
-- Base kind for an organize imports source action
|
||||
SourceOrganizeImports = 'source.organizeImports';
|
||||
};
|
||||
SourceOrganizeImports = 'source.organizeImports',
|
||||
},
|
||||
}
|
||||
|
||||
for k, v in pairs(constants) do
|
||||
@@ -620,19 +620,19 @@ function protocol.make_client_capabilities()
|
||||
return {
|
||||
textDocument = {
|
||||
synchronization = {
|
||||
dynamicRegistration = false;
|
||||
dynamicRegistration = false,
|
||||
|
||||
-- TODO(ashkan) Send textDocument/willSave before saving (BufWritePre)
|
||||
willSave = false;
|
||||
willSave = false,
|
||||
|
||||
-- TODO(ashkan) Implement textDocument/willSaveWaitUntil
|
||||
willSaveWaitUntil = false;
|
||||
willSaveWaitUntil = false,
|
||||
|
||||
-- Send textDocument/didSave after saving (BufWritePost)
|
||||
didSave = true;
|
||||
};
|
||||
didSave = true,
|
||||
},
|
||||
codeAction = {
|
||||
dynamicRegistration = false;
|
||||
dynamicRegistration = false,
|
||||
|
||||
codeActionLiteralSupport = {
|
||||
codeActionKind = {
|
||||
@@ -640,138 +640,146 @@ function protocol.make_client_capabilities()
|
||||
local res = vim.tbl_values(protocol.CodeActionKind)
|
||||
table.sort(res)
|
||||
return res
|
||||
end)();
|
||||
};
|
||||
};
|
||||
isPreferredSupport = true;
|
||||
dataSupport = true;
|
||||
end)(),
|
||||
},
|
||||
},
|
||||
isPreferredSupport = true,
|
||||
dataSupport = true,
|
||||
resolveSupport = {
|
||||
properties = { 'edit', }
|
||||
};
|
||||
};
|
||||
properties = { 'edit' },
|
||||
},
|
||||
},
|
||||
completion = {
|
||||
dynamicRegistration = false;
|
||||
dynamicRegistration = false,
|
||||
completionItem = {
|
||||
-- Until we can actually expand snippet, move cursor and allow for true snippet experience,
|
||||
-- this should be disabled out of the box.
|
||||
-- However, users can turn this back on if they have a snippet plugin.
|
||||
snippetSupport = false;
|
||||
snippetSupport = false,
|
||||
|
||||
commitCharactersSupport = false;
|
||||
preselectSupport = false;
|
||||
deprecatedSupport = false;
|
||||
documentationFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
|
||||
};
|
||||
commitCharactersSupport = false,
|
||||
preselectSupport = false,
|
||||
deprecatedSupport = false,
|
||||
documentationFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
|
||||
},
|
||||
completionItemKind = {
|
||||
valueSet = (function()
|
||||
local res = {}
|
||||
for k in ipairs(protocol.CompletionItemKind) do
|
||||
if type(k) == 'number' then table.insert(res, k) end
|
||||
if type(k) == 'number' then
|
||||
table.insert(res, k)
|
||||
end
|
||||
end
|
||||
return res
|
||||
end)();
|
||||
};
|
||||
end)(),
|
||||
},
|
||||
|
||||
-- TODO(tjdevries): Implement this
|
||||
contextSupport = false;
|
||||
};
|
||||
contextSupport = false,
|
||||
},
|
||||
declaration = {
|
||||
linkSupport = true;
|
||||
};
|
||||
linkSupport = true,
|
||||
},
|
||||
definition = {
|
||||
linkSupport = true;
|
||||
};
|
||||
linkSupport = true,
|
||||
},
|
||||
implementation = {
|
||||
linkSupport = true;
|
||||
};
|
||||
linkSupport = true,
|
||||
},
|
||||
typeDefinition = {
|
||||
linkSupport = true;
|
||||
};
|
||||
linkSupport = true,
|
||||
},
|
||||
hover = {
|
||||
dynamicRegistration = false;
|
||||
contentFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
|
||||
};
|
||||
dynamicRegistration = false,
|
||||
contentFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
|
||||
},
|
||||
signatureHelp = {
|
||||
dynamicRegistration = false;
|
||||
dynamicRegistration = false,
|
||||
signatureInformation = {
|
||||
activeParameterSupport = true;
|
||||
documentationFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
|
||||
activeParameterSupport = true,
|
||||
documentationFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
|
||||
parameterInformation = {
|
||||
labelOffsetSupport = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
labelOffsetSupport = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
references = {
|
||||
dynamicRegistration = false;
|
||||
};
|
||||
dynamicRegistration = false,
|
||||
},
|
||||
documentHighlight = {
|
||||
dynamicRegistration = false
|
||||
};
|
||||
dynamicRegistration = false,
|
||||
},
|
||||
documentSymbol = {
|
||||
dynamicRegistration = false;
|
||||
dynamicRegistration = false,
|
||||
symbolKind = {
|
||||
valueSet = (function()
|
||||
local res = {}
|
||||
for k in ipairs(protocol.SymbolKind) do
|
||||
if type(k) == 'number' then table.insert(res, k) end
|
||||
if type(k) == 'number' then
|
||||
table.insert(res, k)
|
||||
end
|
||||
end
|
||||
return res
|
||||
end)();
|
||||
};
|
||||
hierarchicalDocumentSymbolSupport = true;
|
||||
};
|
||||
end)(),
|
||||
},
|
||||
hierarchicalDocumentSymbolSupport = true,
|
||||
},
|
||||
rename = {
|
||||
dynamicRegistration = false;
|
||||
prepareSupport = true;
|
||||
};
|
||||
dynamicRegistration = false,
|
||||
prepareSupport = true,
|
||||
},
|
||||
publishDiagnostics = {
|
||||
relatedInformation = true;
|
||||
relatedInformation = true,
|
||||
tagSupport = {
|
||||
valueSet = (function()
|
||||
local res = {}
|
||||
for k in ipairs(protocol.DiagnosticTag) do
|
||||
if type(k) == 'number' then table.insert(res, k) end
|
||||
if type(k) == 'number' then
|
||||
table.insert(res, k)
|
||||
end
|
||||
end
|
||||
return res
|
||||
end)();
|
||||
};
|
||||
};
|
||||
};
|
||||
end)(),
|
||||
},
|
||||
},
|
||||
},
|
||||
workspace = {
|
||||
symbol = {
|
||||
dynamicRegistration = false;
|
||||
dynamicRegistration = false,
|
||||
symbolKind = {
|
||||
valueSet = (function()
|
||||
local res = {}
|
||||
for k in ipairs(protocol.SymbolKind) do
|
||||
if type(k) == 'number' then table.insert(res, k) end
|
||||
if type(k) == 'number' then
|
||||
table.insert(res, k)
|
||||
end
|
||||
end
|
||||
return res
|
||||
end)();
|
||||
};
|
||||
hierarchicalWorkspaceSymbolSupport = true;
|
||||
};
|
||||
workspaceFolders = true;
|
||||
applyEdit = true;
|
||||
end)(),
|
||||
},
|
||||
hierarchicalWorkspaceSymbolSupport = true,
|
||||
},
|
||||
workspaceFolders = true,
|
||||
applyEdit = true,
|
||||
workspaceEdit = {
|
||||
resourceOperations = {'rename', 'create', 'delete',},
|
||||
};
|
||||
};
|
||||
resourceOperations = { 'rename', 'create', 'delete' },
|
||||
},
|
||||
},
|
||||
callHierarchy = {
|
||||
dynamicRegistration = false;
|
||||
};
|
||||
experimental = nil;
|
||||
dynamicRegistration = false,
|
||||
},
|
||||
experimental = nil,
|
||||
window = {
|
||||
workDoneProgress = true;
|
||||
workDoneProgress = true,
|
||||
showMessage = {
|
||||
messageActionItem = {
|
||||
additionalPropertiesSupport = false;
|
||||
};
|
||||
};
|
||||
additionalPropertiesSupport = false,
|
||||
},
|
||||
},
|
||||
showDocument = {
|
||||
support = false;
|
||||
};
|
||||
};
|
||||
support = false,
|
||||
},
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
@@ -791,12 +799,12 @@ function protocol.resolve_capabilities(server_capabilities)
|
||||
willSaveWaitUntil = false,
|
||||
save = {
|
||||
includeText = false,
|
||||
}
|
||||
},
|
||||
}
|
||||
elseif type(textDocumentSync) == 'number' then
|
||||
-- Backwards compatibility
|
||||
if not TextDocumentSyncKind[textDocumentSync] then
|
||||
return nil, "Invalid server TextDocumentSyncKind for textDocumentSync"
|
||||
return nil, 'Invalid server TextDocumentSyncKind for textDocumentSync'
|
||||
end
|
||||
server_capabilities.textDocumentSync = {
|
||||
openClose = true,
|
||||
@@ -805,10 +813,10 @@ function protocol.resolve_capabilities(server_capabilities)
|
||||
willSaveWaitUntil = false,
|
||||
save = {
|
||||
includeText = false,
|
||||
}
|
||||
},
|
||||
}
|
||||
elseif type(textDocumentSync) ~= 'table' then
|
||||
return nil, string.format("Invalid type for textDocumentSync: %q", type(textDocumentSync))
|
||||
return nil, string.format('Invalid type for textDocumentSync: %q', type(textDocumentSync))
|
||||
end
|
||||
return server_capabilities
|
||||
end
|
||||
@@ -827,39 +835,41 @@ function protocol._resolve_capabilities_compat(server_capabilities)
|
||||
if textDocumentSync == nil then
|
||||
-- Defaults if omitted.
|
||||
text_document_sync_properties = {
|
||||
text_document_open_close = false;
|
||||
text_document_did_change = TextDocumentSyncKind.None;
|
||||
-- text_document_did_change = false;
|
||||
text_document_will_save = false;
|
||||
text_document_will_save_wait_until = false;
|
||||
text_document_save = false;
|
||||
text_document_save_include_text = false;
|
||||
text_document_open_close = false,
|
||||
text_document_did_change = TextDocumentSyncKind.None,
|
||||
-- text_document_did_change = false;
|
||||
text_document_will_save = false,
|
||||
text_document_will_save_wait_until = false,
|
||||
text_document_save = false,
|
||||
text_document_save_include_text = false,
|
||||
}
|
||||
elseif type(textDocumentSync) == 'number' then
|
||||
-- Backwards compatibility
|
||||
if not TextDocumentSyncKind[textDocumentSync] then
|
||||
return nil, "Invalid server TextDocumentSyncKind for textDocumentSync"
|
||||
return nil, 'Invalid server TextDocumentSyncKind for textDocumentSync'
|
||||
end
|
||||
text_document_sync_properties = {
|
||||
text_document_open_close = true;
|
||||
text_document_did_change = textDocumentSync;
|
||||
text_document_will_save = false;
|
||||
text_document_will_save_wait_until = false;
|
||||
text_document_save = true;
|
||||
text_document_save_include_text = false;
|
||||
text_document_open_close = true,
|
||||
text_document_did_change = textDocumentSync,
|
||||
text_document_will_save = false,
|
||||
text_document_will_save_wait_until = false,
|
||||
text_document_save = true,
|
||||
text_document_save_include_text = false,
|
||||
}
|
||||
elseif type(textDocumentSync) == 'table' then
|
||||
text_document_sync_properties = {
|
||||
text_document_open_close = if_nil(textDocumentSync.openClose, false);
|
||||
text_document_did_change = if_nil(textDocumentSync.change, TextDocumentSyncKind.None);
|
||||
text_document_will_save = if_nil(textDocumentSync.willSave, false);
|
||||
text_document_will_save_wait_until = if_nil(textDocumentSync.willSaveWaitUntil, false);
|
||||
text_document_save = if_nil(textDocumentSync.save, false);
|
||||
text_document_save_include_text = if_nil(type(textDocumentSync.save) == 'table'
|
||||
and textDocumentSync.save.includeText, false);
|
||||
text_document_open_close = if_nil(textDocumentSync.openClose, false),
|
||||
text_document_did_change = if_nil(textDocumentSync.change, TextDocumentSyncKind.None),
|
||||
text_document_will_save = if_nil(textDocumentSync.willSave, false),
|
||||
text_document_will_save_wait_until = if_nil(textDocumentSync.willSaveWaitUntil, false),
|
||||
text_document_save = if_nil(textDocumentSync.save, false),
|
||||
text_document_save_include_text = if_nil(
|
||||
type(textDocumentSync.save) == 'table' and textDocumentSync.save.includeText,
|
||||
false
|
||||
),
|
||||
}
|
||||
else
|
||||
return nil, string.format("Invalid type for textDocumentSync: %q", type(textDocumentSync))
|
||||
return nil, string.format('Invalid type for textDocumentSync: %q', type(textDocumentSync))
|
||||
end
|
||||
end
|
||||
general_properties.completion = server_capabilities.completionProvider ~= nil
|
||||
@@ -889,16 +899,18 @@ function protocol._resolve_capabilities_compat(server_capabilities)
|
||||
general_properties.code_lens = true
|
||||
general_properties.code_lens_resolve = server_capabilities.codeLensProvider.resolveProvider or false
|
||||
else
|
||||
error("The server sent invalid codeLensProvider")
|
||||
error('The server sent invalid codeLensProvider')
|
||||
end
|
||||
|
||||
if server_capabilities.codeActionProvider == nil then
|
||||
general_properties.code_action = false
|
||||
elseif type(server_capabilities.codeActionProvider) == 'boolean'
|
||||
or type(server_capabilities.codeActionProvider) == 'table' then
|
||||
elseif
|
||||
type(server_capabilities.codeActionProvider) == 'boolean'
|
||||
or type(server_capabilities.codeActionProvider) == 'table'
|
||||
then
|
||||
general_properties.code_action = server_capabilities.codeActionProvider
|
||||
else
|
||||
error("The server sent invalid codeActionProvider")
|
||||
error('The server sent invalid codeActionProvider')
|
||||
end
|
||||
|
||||
if server_capabilities.declarationProvider == nil then
|
||||
@@ -908,7 +920,7 @@ function protocol._resolve_capabilities_compat(server_capabilities)
|
||||
elseif type(server_capabilities.declarationProvider) == 'table' then
|
||||
general_properties.declaration = server_capabilities.declarationProvider
|
||||
else
|
||||
error("The server sent invalid declarationProvider")
|
||||
error('The server sent invalid declarationProvider')
|
||||
end
|
||||
|
||||
if server_capabilities.typeDefinitionProvider == nil then
|
||||
@@ -918,7 +930,7 @@ function protocol._resolve_capabilities_compat(server_capabilities)
|
||||
elseif type(server_capabilities.typeDefinitionProvider) == 'table' then
|
||||
general_properties.type_definition = server_capabilities.typeDefinitionProvider
|
||||
else
|
||||
error("The server sent invalid typeDefinitionProvider")
|
||||
error('The server sent invalid typeDefinitionProvider')
|
||||
end
|
||||
|
||||
if server_capabilities.implementationProvider == nil then
|
||||
@@ -928,7 +940,7 @@ function protocol._resolve_capabilities_compat(server_capabilities)
|
||||
elseif type(server_capabilities.implementationProvider) == 'table' then
|
||||
general_properties.implementation = server_capabilities.implementationProvider
|
||||
else
|
||||
error("The server sent invalid implementationProvider")
|
||||
error('The server sent invalid implementationProvider')
|
||||
end
|
||||
|
||||
local workspace = server_capabilities.workspace
|
||||
@@ -936,45 +948,45 @@ function protocol._resolve_capabilities_compat(server_capabilities)
|
||||
if workspace == nil or workspace.workspaceFolders == nil then
|
||||
-- Defaults if omitted.
|
||||
workspace_properties = {
|
||||
workspace_folder_properties = {
|
||||
supported = false;
|
||||
changeNotifications=false;
|
||||
}
|
||||
workspace_folder_properties = {
|
||||
supported = false,
|
||||
changeNotifications = false,
|
||||
},
|
||||
}
|
||||
elseif type(workspace.workspaceFolders) == 'table' then
|
||||
workspace_properties = {
|
||||
workspace_folder_properties = {
|
||||
supported = if_nil(workspace.workspaceFolders.supported, false);
|
||||
changeNotifications = if_nil(workspace.workspaceFolders.changeNotifications, false);
|
||||
|
||||
}
|
||||
supported = if_nil(workspace.workspaceFolders.supported, false),
|
||||
changeNotifications = if_nil(workspace.workspaceFolders.changeNotifications, false),
|
||||
},
|
||||
}
|
||||
else
|
||||
error("The server sent invalid workspace")
|
||||
error('The server sent invalid workspace')
|
||||
end
|
||||
|
||||
local signature_help_properties
|
||||
if server_capabilities.signatureHelpProvider == nil then
|
||||
signature_help_properties = {
|
||||
signature_help = false;
|
||||
signature_help_trigger_characters = {};
|
||||
signature_help = false,
|
||||
signature_help_trigger_characters = {},
|
||||
}
|
||||
elseif type(server_capabilities.signatureHelpProvider) == 'table' then
|
||||
signature_help_properties = {
|
||||
signature_help = true;
|
||||
signature_help = true,
|
||||
-- The characters that trigger signature help automatically.
|
||||
signature_help_trigger_characters = server_capabilities.signatureHelpProvider.triggerCharacters or {};
|
||||
signature_help_trigger_characters = server_capabilities.signatureHelpProvider.triggerCharacters or {},
|
||||
}
|
||||
else
|
||||
error("The server sent invalid signatureHelpProvider")
|
||||
error('The server sent invalid signatureHelpProvider')
|
||||
end
|
||||
|
||||
local capabilities = vim.tbl_extend("error"
|
||||
, text_document_sync_properties
|
||||
, signature_help_properties
|
||||
, workspace_properties
|
||||
, general_properties
|
||||
)
|
||||
local capabilities = vim.tbl_extend(
|
||||
'error',
|
||||
text_document_sync_properties,
|
||||
signature_help_properties,
|
||||
workspace_properties,
|
||||
general_properties
|
||||
)
|
||||
|
||||
return capabilities
|
||||
end
|
||||
|
||||
@@ -32,9 +32,9 @@ local function env_merge(env)
|
||||
-- Merge.
|
||||
env = vim.tbl_extend('force', vim.fn.environ(), env)
|
||||
local final_env = {}
|
||||
for k,v in pairs(env) do
|
||||
for k, v in pairs(env) do
|
||||
assert(type(k) == 'string', 'env must be a dict')
|
||||
table.insert(final_env, k..'='..tostring(v))
|
||||
table.insert(final_env, k .. '=' .. tostring(v))
|
||||
end
|
||||
return final_env
|
||||
end
|
||||
@@ -45,10 +45,12 @@ end
|
||||
---@param encoded_message (string)
|
||||
---@returns (table) table containing encoded message and `Content-Length` attribute
|
||||
local function format_message_with_content_length(encoded_message)
|
||||
return table.concat {
|
||||
'Content-Length: '; tostring(#encoded_message); '\r\n\r\n';
|
||||
encoded_message;
|
||||
}
|
||||
return table.concat({
|
||||
'Content-Length: ',
|
||||
tostring(#encoded_message),
|
||||
'\r\n\r\n',
|
||||
encoded_message,
|
||||
})
|
||||
end
|
||||
|
||||
---@private
|
||||
@@ -65,23 +67,25 @@ local function parse_headers(header)
|
||||
if line == '' then
|
||||
break
|
||||
end
|
||||
local key, value = line:match("^%s*(%S+)%s*:%s*(.+)%s*$")
|
||||
local key, value = line:match('^%s*(%S+)%s*:%s*(.+)%s*$')
|
||||
if key then
|
||||
key = key:lower():gsub('%-', '_')
|
||||
headers[key] = value
|
||||
else
|
||||
local _ = log.error() and log.error("invalid header line %q", line)
|
||||
error(string.format("invalid header line %q", line))
|
||||
local _ = log.error() and log.error('invalid header line %q', line)
|
||||
error(string.format('invalid header line %q', line))
|
||||
end
|
||||
end
|
||||
headers.content_length = tonumber(headers.content_length)
|
||||
or error(string.format("Content-Length not found in headers. %q", header))
|
||||
or error(string.format('Content-Length not found in headers. %q', header))
|
||||
return headers
|
||||
end
|
||||
|
||||
-- This is the start of any possible header patterns. The gsub converts it to a
|
||||
-- case insensitive pattern.
|
||||
local header_start_pattern = ("content"):gsub("%w", function(c) return "["..c..c:upper().."]" end)
|
||||
local header_start_pattern = ('content'):gsub('%w', function(c)
|
||||
return '[' .. c .. c:upper() .. ']'
|
||||
end)
|
||||
|
||||
---@private
|
||||
--- The actual workhorse.
|
||||
@@ -100,17 +104,16 @@ local function request_parser_loop()
|
||||
-- be searching for.
|
||||
-- TODO(ashkan) I'd like to remove this, but it seems permanent :(
|
||||
local buffer_start = buffer:find(header_start_pattern)
|
||||
local headers = parse_headers(buffer:sub(buffer_start, start-1))
|
||||
local headers = parse_headers(buffer:sub(buffer_start, start - 1))
|
||||
local content_length = headers.content_length
|
||||
-- Use table instead of just string to buffer the message. It prevents
|
||||
-- a ton of strings allocating.
|
||||
-- ref. http://www.lua.org/pil/11.6.html
|
||||
local body_chunks = {buffer:sub(finish+1)}
|
||||
local body_chunks = { buffer:sub(finish + 1) }
|
||||
local body_length = #body_chunks[1]
|
||||
-- Keep waiting for data until we have enough.
|
||||
while body_length < content_length do
|
||||
local chunk = coroutine.yield()
|
||||
or error("Expected more data for the body. The server may have died.") -- TODO hmm.
|
||||
local chunk = coroutine.yield() or error('Expected more data for the body. The server may have died.') -- TODO hmm.
|
||||
table.insert(body_chunks, chunk)
|
||||
body_length = body_length + #chunk
|
||||
end
|
||||
@@ -123,25 +126,24 @@ local function request_parser_loop()
|
||||
end
|
||||
local body = table.concat(body_chunks)
|
||||
-- Yield our data.
|
||||
buffer = rest..(coroutine.yield(headers, body)
|
||||
or error("Expected more data for the body. The server may have died.")) -- TODO hmm.
|
||||
buffer = rest
|
||||
.. (coroutine.yield(headers, body) or error('Expected more data for the body. The server may have died.')) -- TODO hmm.
|
||||
else
|
||||
-- Get more data since we don't have enough.
|
||||
buffer = buffer..(coroutine.yield()
|
||||
or error("Expected more data for the header. The server may have died.")) -- TODO hmm.
|
||||
buffer = buffer .. (coroutine.yield() or error('Expected more data for the header. The server may have died.')) -- TODO hmm.
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Mapping of error codes used by the client
|
||||
local client_errors = {
|
||||
INVALID_SERVER_MESSAGE = 1;
|
||||
INVALID_SERVER_JSON = 2;
|
||||
NO_RESULT_CALLBACK_FOUND = 3;
|
||||
READ_ERROR = 4;
|
||||
NOTIFICATION_HANDLER_ERROR = 5;
|
||||
SERVER_REQUEST_HANDLER_ERROR = 6;
|
||||
SERVER_RESULT_CALLBACK_ERROR = 7;
|
||||
INVALID_SERVER_MESSAGE = 1,
|
||||
INVALID_SERVER_JSON = 2,
|
||||
NO_RESULT_CALLBACK_FOUND = 3,
|
||||
READ_ERROR = 4,
|
||||
NOTIFICATION_HANDLER_ERROR = 5,
|
||||
SERVER_REQUEST_HANDLER_ERROR = 6,
|
||||
SERVER_RESULT_CALLBACK_ERROR = 7,
|
||||
}
|
||||
|
||||
client_errors = vim.tbl_add_reverse_lookup(client_errors)
|
||||
@@ -151,26 +153,26 @@ client_errors = vim.tbl_add_reverse_lookup(client_errors)
|
||||
---@param err (table) The error object
|
||||
---@returns (string) The formatted error message
|
||||
local function format_rpc_error(err)
|
||||
validate {
|
||||
err = { err, 't' };
|
||||
}
|
||||
validate({
|
||||
err = { err, 't' },
|
||||
})
|
||||
|
||||
-- There is ErrorCodes in the LSP specification,
|
||||
-- but in ResponseError.code it is not used and the actual type is number.
|
||||
local code
|
||||
if protocol.ErrorCodes[err.code] then
|
||||
code = string.format("code_name = %s,", protocol.ErrorCodes[err.code])
|
||||
code = string.format('code_name = %s,', protocol.ErrorCodes[err.code])
|
||||
else
|
||||
code = string.format("code_name = unknown, code = %s,", err.code)
|
||||
code = string.format('code_name = unknown, code = %s,', err.code)
|
||||
end
|
||||
|
||||
local message_parts = {"RPC[Error]", code}
|
||||
local message_parts = { 'RPC[Error]', code }
|
||||
if err.message then
|
||||
table.insert(message_parts, "message =")
|
||||
table.insert(message_parts, string.format("%q", err.message))
|
||||
table.insert(message_parts, 'message =')
|
||||
table.insert(message_parts, string.format('%q', err.message))
|
||||
end
|
||||
if err.data then
|
||||
table.insert(message_parts, "data =")
|
||||
table.insert(message_parts, 'data =')
|
||||
table.insert(message_parts, vim.inspect(err.data))
|
||||
end
|
||||
return table.concat(message_parts, ' ')
|
||||
@@ -185,11 +187,11 @@ local function rpc_response_error(code, message, data)
|
||||
-- TODO should this error or just pick a sane error (like InternalError)?
|
||||
local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code')
|
||||
return setmetatable({
|
||||
code = code;
|
||||
message = message or code_name;
|
||||
data = data;
|
||||
code = code,
|
||||
message = message or code_name,
|
||||
data = data,
|
||||
}, {
|
||||
__tostring = format_rpc_error;
|
||||
__tostring = format_rpc_error,
|
||||
})
|
||||
end
|
||||
|
||||
@@ -220,7 +222,7 @@ end
|
||||
---@param signal (number): Number describing the signal used to terminate (if
|
||||
---any)
|
||||
function default_dispatchers.on_exit(code, signal)
|
||||
local _ = log.info() and log.info("client_exit", { code = code, signal = signal })
|
||||
local _ = log.info() and log.info('client_exit', { code = code, signal = signal })
|
||||
end
|
||||
---@private
|
||||
--- Default dispatcher for client errors.
|
||||
@@ -258,15 +260,15 @@ end
|
||||
--- - {handle} A handle for low-level interaction with the LSP server process
|
||||
--- |vim.loop|.
|
||||
local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
local _ = log.info() and log.info("Starting RPC client", {cmd = cmd, args = cmd_args, extra = extra_spawn_params})
|
||||
validate {
|
||||
cmd = { cmd, 's' };
|
||||
cmd_args = { cmd_args, 't' };
|
||||
dispatchers = { dispatchers, 't', true };
|
||||
}
|
||||
local _ = log.info() and log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params })
|
||||
validate({
|
||||
cmd = { cmd, 's' },
|
||||
cmd_args = { cmd_args, 't' },
|
||||
dispatchers = { dispatchers, 't', true },
|
||||
})
|
||||
|
||||
if extra_spawn_params and extra_spawn_params.cwd then
|
||||
assert(is_dir(extra_spawn_params.cwd), "cwd must be a directory")
|
||||
assert(is_dir(extra_spawn_params.cwd), 'cwd must be a directory')
|
||||
end
|
||||
if dispatchers then
|
||||
local user_dispatchers = dispatchers
|
||||
@@ -275,11 +277,11 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
local user_dispatcher = user_dispatchers[dispatch_name]
|
||||
if user_dispatcher then
|
||||
if type(user_dispatcher) ~= 'function' then
|
||||
error(string.format("dispatcher.%s must be a function", dispatch_name))
|
||||
error(string.format('dispatcher.%s must be a function', dispatch_name))
|
||||
end
|
||||
-- server_request is wrapped elsewhere.
|
||||
if not (dispatch_name == 'server_request'
|
||||
or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason.
|
||||
if
|
||||
not (dispatch_name == 'server_request' or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason.
|
||||
then
|
||||
user_dispatcher = schedule_wrap(user_dispatcher)
|
||||
end
|
||||
@@ -317,9 +319,9 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
dispatchers.on_exit(code, signal)
|
||||
end
|
||||
local spawn_params = {
|
||||
args = cmd_args;
|
||||
stdio = {stdin, stdout, stderr};
|
||||
detached = true;
|
||||
args = cmd_args,
|
||||
stdio = { stdin, stdout, stderr },
|
||||
detached = true,
|
||||
}
|
||||
if extra_spawn_params then
|
||||
spawn_params.cwd = extra_spawn_params.cwd
|
||||
@@ -330,11 +332,11 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
end
|
||||
handle, pid = uv.spawn(cmd, spawn_params, onexit)
|
||||
if handle == nil then
|
||||
local msg = string.format("Spawning language server with cmd: `%s` failed", cmd)
|
||||
if string.match(pid, "ENOENT") then
|
||||
msg = msg .. ". The language server is either not installed, missing from PATH, or not executable."
|
||||
local msg = string.format('Spawning language server with cmd: `%s` failed', cmd)
|
||||
if string.match(pid, 'ENOENT') then
|
||||
msg = msg .. '. The language server is either not installed, missing from PATH, or not executable.'
|
||||
else
|
||||
msg = msg .. string.format(" with error message: %s", pid)
|
||||
msg = msg .. string.format(' with error message: %s', pid)
|
||||
end
|
||||
vim.notify(msg, vim.log.levels.WARN)
|
||||
return
|
||||
@@ -348,8 +350,10 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
---@param payload table
|
||||
---@returns true if the payload could be scheduled, false if the main event-loop is in the process of closing.
|
||||
local function encode_and_send(payload)
|
||||
local _ = log.debug() and log.debug("rpc.send", payload)
|
||||
if handle == nil or handle:is_closing() then return false end
|
||||
local _ = log.debug() and log.debug('rpc.send', payload)
|
||||
if handle == nil or handle:is_closing() then
|
||||
return false
|
||||
end
|
||||
local encoded = vim.json.encode(payload)
|
||||
stdin:write(format_message_with_content_length(encoded))
|
||||
return true
|
||||
@@ -363,22 +367,22 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
---@param params (table): Parameters for the invoked LSP method
|
||||
---@returns (bool) `true` if notification could be sent, `false` if not
|
||||
local function notify(method, params)
|
||||
return encode_and_send {
|
||||
jsonrpc = "2.0";
|
||||
method = method;
|
||||
params = params;
|
||||
}
|
||||
return encode_and_send({
|
||||
jsonrpc = '2.0',
|
||||
method = method,
|
||||
params = params,
|
||||
})
|
||||
end
|
||||
|
||||
---@private
|
||||
--- sends an error object to the remote LSP process.
|
||||
local function send_response(request_id, err, result)
|
||||
return encode_and_send {
|
||||
id = request_id;
|
||||
jsonrpc = "2.0";
|
||||
error = err;
|
||||
result = result;
|
||||
}
|
||||
return encode_and_send({
|
||||
id = request_id,
|
||||
jsonrpc = '2.0',
|
||||
error = err,
|
||||
result = result,
|
||||
})
|
||||
end
|
||||
|
||||
-- FIXME: DOC: Should be placed on the RPC client object returned by
|
||||
@@ -392,18 +396,18 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
---@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending
|
||||
---@returns (bool, number) `(true, message_id)` if request could be sent, `false` if not
|
||||
local function request(method, params, callback, notify_reply_callback)
|
||||
validate {
|
||||
callback = { callback, 'f' };
|
||||
notify_reply_callback = { notify_reply_callback, 'f', true };
|
||||
}
|
||||
validate({
|
||||
callback = { callback, 'f' },
|
||||
notify_reply_callback = { notify_reply_callback, 'f', true },
|
||||
})
|
||||
message_index = message_index + 1
|
||||
local message_id = message_index
|
||||
local result = encode_and_send {
|
||||
id = message_id;
|
||||
jsonrpc = "2.0";
|
||||
method = method;
|
||||
params = params;
|
||||
}
|
||||
local result = encode_and_send({
|
||||
id = message_id,
|
||||
jsonrpc = '2.0',
|
||||
method = method,
|
||||
params = params,
|
||||
})
|
||||
if result then
|
||||
if message_callbacks then
|
||||
message_callbacks[message_id] = schedule_wrap(callback)
|
||||
@@ -421,7 +425,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
|
||||
stderr:read_start(function(_err, chunk)
|
||||
if chunk then
|
||||
local _ = log.error() and log.error("rpc", cmd, "stderr", chunk)
|
||||
local _ = log.error() and log.error('rpc', cmd, 'stderr', chunk)
|
||||
end
|
||||
end)
|
||||
|
||||
@@ -455,7 +459,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
on_error(client_errors.INVALID_SERVER_JSON, decoded)
|
||||
return
|
||||
end
|
||||
local _ = log.debug() and log.debug("rpc.receive", decoded)
|
||||
local _ = log.debug() and log.debug('rpc.receive', decoded)
|
||||
|
||||
if type(decoded.method) == 'string' and decoded.id then
|
||||
local err
|
||||
@@ -463,17 +467,30 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
-- we can still use the result.
|
||||
schedule(function()
|
||||
local status, result
|
||||
status, result, err = try_call(client_errors.SERVER_REQUEST_HANDLER_ERROR,
|
||||
dispatchers.server_request, decoded.method, decoded.params)
|
||||
local _ = log.debug() and log.debug("server_request: callback result", { status = status, result = result, err = err })
|
||||
status, result, err = try_call(
|
||||
client_errors.SERVER_REQUEST_HANDLER_ERROR,
|
||||
dispatchers.server_request,
|
||||
decoded.method,
|
||||
decoded.params
|
||||
)
|
||||
local _ = log.debug()
|
||||
and log.debug('server_request: callback result', { status = status, result = result, err = err })
|
||||
if status then
|
||||
if not (result or err) then
|
||||
-- TODO this can be a problem if `null` is sent for result. needs vim.NIL
|
||||
error(string.format("method %q: either a result or an error must be sent to the server in response", decoded.method))
|
||||
error(
|
||||
string.format(
|
||||
'method %q: either a result or an error must be sent to the server in response',
|
||||
decoded.method
|
||||
)
|
||||
)
|
||||
end
|
||||
if err then
|
||||
assert(type(err) == 'table', "err must be a table. Use rpc_response_error to help format errors.")
|
||||
local code_name = assert(protocol.ErrorCodes[err.code], "Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.")
|
||||
assert(type(err) == 'table', 'err must be a table. Use rpc_response_error to help format errors.')
|
||||
local code_name = assert(
|
||||
protocol.ErrorCodes[err.code],
|
||||
'Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.'
|
||||
)
|
||||
err.message = err.message or code_name
|
||||
end
|
||||
else
|
||||
@@ -483,18 +500,17 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
end
|
||||
send_response(decoded.id, err, result)
|
||||
end)
|
||||
-- This works because we are expecting vim.NIL here
|
||||
-- This works because we are expecting vim.NIL here
|
||||
elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then
|
||||
|
||||
-- We sent a number, so we expect a number.
|
||||
local result_id = tonumber(decoded.id)
|
||||
|
||||
-- Notify the user that a response was received for the request
|
||||
local notify_reply_callback = notify_reply_callbacks and notify_reply_callbacks[result_id]
|
||||
if notify_reply_callback then
|
||||
validate {
|
||||
notify_reply_callback = { notify_reply_callback, 'f' };
|
||||
}
|
||||
validate({
|
||||
notify_reply_callback = { notify_reply_callback, 'f' },
|
||||
})
|
||||
notify_reply_callback(result_id)
|
||||
notify_reply_callbacks[result_id] = nil
|
||||
end
|
||||
@@ -503,7 +519,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
if decoded.error then
|
||||
local mute_error = false
|
||||
if decoded.error.code == protocol.ErrorCodes.RequestCancelled then
|
||||
local _ = log.debug() and log.debug("Received cancellation ack", decoded)
|
||||
local _ = log.debug() and log.debug('Received cancellation ack', decoded)
|
||||
mute_error = true
|
||||
end
|
||||
|
||||
@@ -523,24 +539,22 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
local callback = message_callbacks and message_callbacks[result_id]
|
||||
if callback then
|
||||
message_callbacks[result_id] = nil
|
||||
validate {
|
||||
callback = { callback, 'f' };
|
||||
}
|
||||
validate({
|
||||
callback = { callback, 'f' },
|
||||
})
|
||||
if decoded.error then
|
||||
decoded.error = setmetatable(decoded.error, {
|
||||
__tostring = format_rpc_error;
|
||||
__tostring = format_rpc_error,
|
||||
})
|
||||
end
|
||||
try_call(client_errors.SERVER_RESULT_CALLBACK_ERROR,
|
||||
callback, decoded.error, decoded.result)
|
||||
try_call(client_errors.SERVER_RESULT_CALLBACK_ERROR, callback, decoded.error, decoded.result)
|
||||
else
|
||||
on_error(client_errors.NO_RESULT_CALLBACK_FOUND, decoded)
|
||||
local _ = log.error() and log.error("No callback found for server response id "..result_id)
|
||||
local _ = log.error() and log.error('No callback found for server response id ' .. result_id)
|
||||
end
|
||||
elseif type(decoded.method) == 'string' then
|
||||
-- Notification
|
||||
try_call(client_errors.NOTIFICATION_HANDLER_ERROR,
|
||||
dispatchers.notification, decoded.method, decoded.params)
|
||||
try_call(client_errors.NOTIFICATION_HANDLER_ERROR, dispatchers.notification, decoded.method, decoded.params)
|
||||
else
|
||||
-- Invalid server message
|
||||
on_error(client_errors.INVALID_SERVER_MESSAGE, decoded)
|
||||
@@ -556,7 +570,9 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
return
|
||||
end
|
||||
-- This should signal that we are done reading from the client.
|
||||
if not chunk then return end
|
||||
if not chunk then
|
||||
return
|
||||
end
|
||||
-- Flush anything in the parser by looping until we don't get a result
|
||||
-- anymore.
|
||||
while true do
|
||||
@@ -574,17 +590,17 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
end)
|
||||
|
||||
return {
|
||||
pid = pid;
|
||||
handle = handle;
|
||||
request = request;
|
||||
notify = notify
|
||||
pid = pid,
|
||||
handle = handle,
|
||||
request = request,
|
||||
notify = notify,
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
start = start;
|
||||
rpc_response_error = rpc_response_error;
|
||||
format_rpc_error = format_rpc_error;
|
||||
client_errors = client_errors;
|
||||
start = start,
|
||||
rpc_response_error = rpc_response_error,
|
||||
format_rpc_error = format_rpc_error,
|
||||
client_errors = client_errors,
|
||||
}
|
||||
-- vim:sw=2 ts=2 et
|
||||
|
||||
@@ -79,7 +79,7 @@ local function compute_line_length(line, offset_encoding)
|
||||
local length
|
||||
local _
|
||||
if offset_encoding == 'utf-16' then
|
||||
_, length = str_utfindex(line)
|
||||
_, length = str_utfindex(line)
|
||||
elseif offset_encoding == 'utf-32' then
|
||||
length, _ = str_utfindex(line)
|
||||
else
|
||||
@@ -100,7 +100,7 @@ local function align_end_position(line, byte, offset_encoding)
|
||||
-- If on the first byte, or an empty string: the trivial case
|
||||
if byte == 1 or #line == 0 then
|
||||
char = byte
|
||||
-- Called in the case of extending an empty line "" -> "a"
|
||||
-- Called in the case of extending an empty line "" -> "a"
|
||||
elseif byte == #line + 1 then
|
||||
char = compute_line_length(line, offset_encoding) + 1
|
||||
else
|
||||
@@ -175,12 +175,12 @@ local function compute_start_range(prev_lines, curr_lines, firstline, lastline,
|
||||
end
|
||||
|
||||
-- Convert byte to codepoint if applicable
|
||||
if start_byte_idx == 1 or (#prev_line == 0 and start_byte_idx == 1)then
|
||||
if start_byte_idx == 1 or (#prev_line == 0 and start_byte_idx == 1) then
|
||||
byte_idx = start_byte_idx
|
||||
char_idx = 1
|
||||
elseif start_byte_idx == #prev_line + 1 then
|
||||
byte_idx = start_byte_idx
|
||||
char_idx = compute_line_length(prev_line, offset_encoding) + 1
|
||||
char_idx = compute_line_length(prev_line, offset_encoding) + 1
|
||||
else
|
||||
byte_idx = start_byte_idx + str_utf_start(prev_line, start_byte_idx)
|
||||
char_idx = byte_to_utf(prev_line, byte_idx, offset_encoding)
|
||||
@@ -203,14 +203,30 @@ end
|
||||
---@param new_lastline integer
|
||||
---@param offset_encoding string
|
||||
---@returns (int, int) end_line_idx and end_col_idx of range
|
||||
local function compute_end_range(prev_lines, curr_lines, start_range, firstline, lastline, new_lastline, offset_encoding)
|
||||
local function compute_end_range(
|
||||
prev_lines,
|
||||
curr_lines,
|
||||
start_range,
|
||||
firstline,
|
||||
lastline,
|
||||
new_lastline,
|
||||
offset_encoding
|
||||
)
|
||||
-- If firstline == new_lastline, the first change occurred on a line that was deleted.
|
||||
-- In this case, the last_byte...
|
||||
if firstline == new_lastline then
|
||||
return { line_idx = (lastline - new_lastline + firstline), byte_idx = 1, char_idx = 1 }, { line_idx = firstline, byte_idx = 1, char_idx = 1 }
|
||||
return { line_idx = (lastline - new_lastline + firstline), byte_idx = 1, char_idx = 1 }, {
|
||||
line_idx = firstline,
|
||||
byte_idx = 1,
|
||||
char_idx = 1,
|
||||
}
|
||||
end
|
||||
if firstline == lastline then
|
||||
return { line_idx = firstline, byte_idx = 1, char_idx = 1 }, { line_idx = new_lastline - lastline + firstline, byte_idx = 1, char_idx = 1 }
|
||||
return { line_idx = firstline, byte_idx = 1, char_idx = 1 }, {
|
||||
line_idx = new_lastline - lastline + firstline,
|
||||
byte_idx = 1,
|
||||
char_idx = 1,
|
||||
}
|
||||
end
|
||||
-- Compare on last line, at minimum will be the start range
|
||||
local start_line_idx = start_range.line_idx
|
||||
@@ -239,9 +255,7 @@ local function compute_end_range(prev_lines, curr_lines, start_range, firstline,
|
||||
end
|
||||
for idx = 0, max_length do
|
||||
byte_offset = idx
|
||||
if
|
||||
str_byte(prev_line, prev_line_length - byte_offset) ~= str_byte(curr_line, curr_line_length - byte_offset)
|
||||
then
|
||||
if str_byte(prev_line, prev_line_length - byte_offset) ~= str_byte(curr_line, curr_line_length - byte_offset) then
|
||||
break
|
||||
end
|
||||
end
|
||||
@@ -281,14 +295,13 @@ end
|
||||
---@param end_range table new_end_range returned by last_difference
|
||||
---@returns string text extracted from defined region
|
||||
local function extract_text(lines, start_range, end_range, line_ending)
|
||||
if not lines[start_range.line_idx] then
|
||||
return ""
|
||||
end
|
||||
if not lines[start_range.line_idx] then
|
||||
return ''
|
||||
end
|
||||
-- Trivial case: start and end range are the same line, directly grab changed text
|
||||
if start_range.line_idx == end_range.line_idx then
|
||||
-- string.sub is inclusive, end_range is not
|
||||
return string.sub(lines[start_range.line_idx], start_range.byte_idx, end_range.byte_idx - 1)
|
||||
|
||||
else
|
||||
-- Handle deletion case
|
||||
-- Collect the changed portion of the first changed line
|
||||
@@ -303,7 +316,7 @@ local function extract_text(lines, start_range, end_range, line_ending)
|
||||
-- Collect the changed portion of the last changed line.
|
||||
table.insert(result, string.sub(lines[end_range.line_idx], 1, end_range.byte_idx - 1))
|
||||
else
|
||||
table.insert(result, "")
|
||||
table.insert(result, '')
|
||||
end
|
||||
|
||||
-- Add line ending between all lines
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user