mirror of
https://github.com/neovim/neovim.git
synced 2025-09-05 19:08:15 +00:00
chore: format runtime with stylua
This commit is contained in:
@@ -7,23 +7,23 @@ if vim.g.do_filetype_lua ~= 1 then
|
||||
return
|
||||
end
|
||||
|
||||
vim.api.nvim_create_augroup("filetypedetect", {clear = false})
|
||||
vim.api.nvim_create_augroup('filetypedetect', { clear = false })
|
||||
|
||||
vim.api.nvim_create_autocmd({"BufRead", "BufNewFile"}, {
|
||||
group = "filetypedetect",
|
||||
vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, {
|
||||
group = 'filetypedetect',
|
||||
callback = function()
|
||||
vim.filetype.match(vim.fn.expand("<afile>"))
|
||||
vim.filetype.match(vim.fn.expand('<afile>'))
|
||||
end,
|
||||
})
|
||||
|
||||
-- These *must* be sourced after the autocommand above is created
|
||||
if not vim.g.did_load_ftdetect then
|
||||
vim.cmd [[
|
||||
vim.cmd([[
|
||||
augroup filetypedetect
|
||||
runtime! ftdetect/*.vim
|
||||
runtime! ftdetect/*.lua
|
||||
augroup END
|
||||
]]
|
||||
]])
|
||||
end
|
||||
|
||||
-- Set a marker so that the ftdetect scripts are not sourced a second time by filetype.vim
|
||||
@@ -31,17 +31,17 @@ vim.g.did_load_ftdetect = 1
|
||||
|
||||
-- If filetype.vim is disabled, set up the autocmd to use scripts.vim
|
||||
if vim.g.did_load_filetypes then
|
||||
vim.api.nvim_create_autocmd({"BufRead", "BufNewFile"}, {
|
||||
group = "filetypedetect",
|
||||
vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, {
|
||||
group = 'filetypedetect',
|
||||
command = "if !did_filetype() && expand('<amatch>') !~ g:ft_ignore_pat | runtime! scripts.vim | endif",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("StdinReadPost", {
|
||||
group = "filetypedetect",
|
||||
command = "if !did_filetype() | runtime! scripts.vim | endif",
|
||||
vim.api.nvim_create_autocmd('StdinReadPost', {
|
||||
group = 'filetypedetect',
|
||||
command = 'if !did_filetype() | runtime! scripts.vim | endif',
|
||||
})
|
||||
end
|
||||
|
||||
if not vim.g.ft_ignore_pat then
|
||||
vim.g.ft_ignore_pat = "\\.\\(Z\\|gz\\|bz2\\|zip\\|tgz\\)$"
|
||||
vim.g.ft_ignore_pat = '\\.\\(Z\\|gz\\|bz2\\|zip\\|tgz\\)$'
|
||||
end
|
||||
|
@@ -3,4 +3,4 @@
|
||||
-- Last Change: 2022 Mar 29
|
||||
|
||||
-- it's a lisp!
|
||||
vim.cmd [[ runtime! ftplugin/lisp.vim ]]
|
||||
vim.cmd([[ runtime! ftplugin/lisp.vim ]])
|
||||
|
@@ -3,4 +3,4 @@
|
||||
-- Last Change: 2022 Mar 29
|
||||
|
||||
-- it's a lisp!
|
||||
vim.cmd [[ runtime! indent/lisp.vim ]]
|
||||
vim.cmd([[ runtime! indent/lisp.vim ]])
|
||||
|
@@ -8,7 +8,7 @@ local function highlight_line(line, linenr)
|
||||
local overstrike, escape = false, false
|
||||
local hls = {} -- Store highlight groups as { attr, start, final }
|
||||
local NONE, BOLD, UNDERLINE, ITALIC = 0, 1, 2, 3
|
||||
local hl_groups = {[BOLD]="manBold", [UNDERLINE]="manUnderline", [ITALIC]="manItalic"}
|
||||
local hl_groups = { [BOLD] = 'manBold', [UNDERLINE] = 'manUnderline', [ITALIC] = 'manItalic' }
|
||||
local attr = NONE
|
||||
local byte = 0 -- byte offset
|
||||
|
||||
@@ -63,7 +63,7 @@ local function highlight_line(line, linenr)
|
||||
-- can be represented in one byte. Any code point above that is represented by
|
||||
-- a leading byte (0xc0 and above) and continuation bytes (0x80 to 0xbf, or
|
||||
-- decimal 128 to 191).
|
||||
for char in line:gmatch("[^\128-\191][\128-\191]*") do
|
||||
for char in line:gmatch('[^\128-\191][\128-\191]*') do
|
||||
if overstrike then
|
||||
local last_hl = hls[#hls]
|
||||
if char == prev_char then
|
||||
@@ -106,25 +106,25 @@ local function highlight_line(line, linenr)
|
||||
-- We only want to match against SGR sequences, which consist of ESC
|
||||
-- followed by '[', then a series of parameter and intermediate bytes in
|
||||
-- the range 0x20 - 0x3f, then 'm'. (See ECMA-48, sections 5.4 & 8.3.117)
|
||||
local sgr = prev_char:match("^%[([\032-\063]*)m$")
|
||||
local sgr = prev_char:match('^%[([\032-\063]*)m$')
|
||||
-- Ignore escape sequences with : characters, as specified by ITU's T.416
|
||||
-- Open Document Architecture and interchange format.
|
||||
if sgr and not string.find(sgr, ":") then
|
||||
if sgr and not string.find(sgr, ':') then
|
||||
local match
|
||||
while sgr and #sgr > 0 do
|
||||
-- Match against SGR parameters, which may be separated by ';'
|
||||
match, sgr = sgr:match("^(%d*);?(.*)")
|
||||
match, sgr = sgr:match('^(%d*);?(.*)')
|
||||
add_attr_hl(match + 0) -- coerce to number
|
||||
end
|
||||
escape = false
|
||||
elseif not prev_char:match("^%[[\032-\063]*$") then
|
||||
elseif not prev_char:match('^%[[\032-\063]*$') then
|
||||
-- Stop looking if this isn't a partial CSI sequence
|
||||
escape = false
|
||||
end
|
||||
elseif char == "\027" then
|
||||
elseif char == '\027' then
|
||||
escape = true
|
||||
prev_char = ''
|
||||
elseif char == "\b" then
|
||||
elseif char == '\b' then
|
||||
overstrike = true
|
||||
prev_char = chars[#chars]
|
||||
byte = byte - #prev_char
|
||||
@@ -143,7 +143,7 @@ local function highlight_line(line, linenr)
|
||||
hl_groups[hl.attr],
|
||||
linenr - 1,
|
||||
hl.start,
|
||||
hl.final
|
||||
hl.final,
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -152,8 +152,8 @@ local function highlight_line(line, linenr)
|
||||
end
|
||||
|
||||
local function highlight_man_page()
|
||||
local mod = vim.api.nvim_buf_get_option(0, "modifiable")
|
||||
vim.api.nvim_buf_set_option(0, "modifiable", true)
|
||||
local mod = vim.api.nvim_buf_get_option(0, 'modifiable')
|
||||
vim.api.nvim_buf_set_option(0, 'modifiable', true)
|
||||
|
||||
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
||||
for i, line in ipairs(lines) do
|
||||
@@ -166,7 +166,7 @@ local function highlight_man_page()
|
||||
end
|
||||
buf_hls = {}
|
||||
|
||||
vim.api.nvim_buf_set_option(0, "modifiable", mod)
|
||||
vim.api.nvim_buf_set_option(0, 'modifiable', mod)
|
||||
end
|
||||
|
||||
return { highlight_man_page = highlight_man_page }
|
||||
|
@@ -5,13 +5,17 @@ local F = {}
|
||||
---@param a
|
||||
---@param b
|
||||
function F.if_nil(a, b)
|
||||
if a == nil then return b end
|
||||
if a == nil then
|
||||
return b
|
||||
end
|
||||
return a
|
||||
end
|
||||
|
||||
-- Use in combination with pcall
|
||||
function F.ok_or_nil(status, ...)
|
||||
if not status then return end
|
||||
if not status then
|
||||
return
|
||||
end
|
||||
return ...
|
||||
end
|
||||
|
||||
|
@@ -40,26 +40,28 @@ local vim = assert(vim)
|
||||
|
||||
-- These are for loading runtime modules lazily since they aren't available in
|
||||
-- the nvim binary as specified in executor.c
|
||||
for k,v in pairs {
|
||||
treesitter=true;
|
||||
filetype = true;
|
||||
F=true;
|
||||
lsp=true;
|
||||
highlight=true;
|
||||
diagnostic=true;
|
||||
keymap=true;
|
||||
ui=true;
|
||||
} do vim._submodules[k] = v end
|
||||
for k, v in pairs({
|
||||
treesitter = true,
|
||||
filetype = true,
|
||||
F = true,
|
||||
lsp = true,
|
||||
highlight = true,
|
||||
diagnostic = true,
|
||||
keymap = true,
|
||||
ui = true,
|
||||
}) do
|
||||
vim._submodules[k] = v
|
||||
end
|
||||
|
||||
vim.log = {
|
||||
levels = {
|
||||
TRACE = 0;
|
||||
DEBUG = 1;
|
||||
INFO = 2;
|
||||
WARN = 3;
|
||||
ERROR = 4;
|
||||
OFF = 5;
|
||||
}
|
||||
TRACE = 0,
|
||||
DEBUG = 1,
|
||||
INFO = 2,
|
||||
WARN = 3,
|
||||
ERROR = 4,
|
||||
OFF = 5,
|
||||
},
|
||||
}
|
||||
|
||||
-- Internal-only until comments in #8107 are addressed.
|
||||
@@ -77,14 +79,14 @@ function vim._os_proc_info(pid)
|
||||
if pid == nil or pid <= 0 or type(pid) ~= 'number' then
|
||||
error('invalid pid')
|
||||
end
|
||||
local cmd = { 'ps', '-p', pid, '-o', 'comm=', }
|
||||
local cmd = { 'ps', '-p', pid, '-o', 'comm=' }
|
||||
local err, name = vim._system(cmd)
|
||||
if 1 == err and vim.trim(name) == '' then
|
||||
return {} -- Process not found.
|
||||
elseif 0 ~= err then
|
||||
error('command failed: ' .. vim.fn.string(cmd))
|
||||
end
|
||||
local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=', })
|
||||
local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=' })
|
||||
-- Remove trailing whitespace.
|
||||
name = vim.trim(name):gsub('^.*/', '')
|
||||
ppid = tonumber(ppid) or -1
|
||||
@@ -101,7 +103,7 @@ function vim._os_proc_children(ppid)
|
||||
if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then
|
||||
error('invalid ppid')
|
||||
end
|
||||
local cmd = { 'pgrep', '-P', ppid, }
|
||||
local cmd = { 'pgrep', '-P', ppid }
|
||||
local err, rv = vim._system(cmd)
|
||||
if 1 == err and vim.trim(rv) == '' then
|
||||
return {} -- Process not found.
|
||||
@@ -252,11 +254,13 @@ end
|
||||
---@see |vim.schedule()|
|
||||
---@see |vim.in_fast_event()|
|
||||
function vim.schedule_wrap(cb)
|
||||
return (function (...)
|
||||
return function(...)
|
||||
local args = vim.F.pack_len(...)
|
||||
vim.schedule(function() cb(vim.F.unpack_len(args)) end)
|
||||
vim.schedule(function()
|
||||
cb(vim.F.unpack_len(args))
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
-- vim.fn.{func}(...)
|
||||
vim.fn = setmetatable({}, {
|
||||
@@ -264,7 +268,7 @@ vim.fn = setmetatable({}, {
|
||||
local _fn
|
||||
if vim.api[key] ~= nil then
|
||||
_fn = function()
|
||||
error(string.format("Tried to call API function with vim.fn: use vim.api.%s instead", key))
|
||||
error(string.format('Tried to call API function with vim.fn: use vim.api.%s instead', key))
|
||||
end
|
||||
else
|
||||
_fn = function(...)
|
||||
@@ -273,7 +277,7 @@ vim.fn = setmetatable({}, {
|
||||
end
|
||||
t[key] = _fn
|
||||
return _fn
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
vim.funcref = function(viml_func_name)
|
||||
@@ -291,9 +295,9 @@ do
|
||||
|
||||
--@private
|
||||
local function make_dict_accessor(scope, handle)
|
||||
validate {
|
||||
scope = {scope, 's'};
|
||||
}
|
||||
validate({
|
||||
scope = { scope, 's' },
|
||||
})
|
||||
local mt = {}
|
||||
function mt:__newindex(k, v)
|
||||
return vim._setvar(scope, handle or 0, k, v)
|
||||
@@ -355,7 +359,7 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive)
|
||||
c2 = vim.str_byteindex(bufline, c2)
|
||||
end
|
||||
else
|
||||
c1 = (l == pos1[1]) and (pos1[2]) or 0
|
||||
c1 = (l == pos1[1]) and pos1[2] or 0
|
||||
c2 = (l == pos2[1]) and (pos2[2] + (inclusive and 1 or 0)) or -1
|
||||
end
|
||||
table.insert(region, l, { c1, c2 })
|
||||
@@ -372,19 +376,22 @@ end
|
||||
---@param timeout Number of milliseconds to wait before calling `fn`
|
||||
---@return timer luv timer object
|
||||
function vim.defer_fn(fn, timeout)
|
||||
vim.validate { fn = { fn, 'c', true}; }
|
||||
vim.validate({ fn = { fn, 'c', true } })
|
||||
local timer = vim.loop.new_timer()
|
||||
timer:start(timeout, 0, vim.schedule_wrap(function()
|
||||
timer:start(
|
||||
timeout,
|
||||
0,
|
||||
vim.schedule_wrap(function()
|
||||
timer:stop()
|
||||
timer:close()
|
||||
|
||||
fn()
|
||||
end))
|
||||
end)
|
||||
)
|
||||
|
||||
return timer
|
||||
end
|
||||
|
||||
|
||||
--- Display a notification to the user.
|
||||
---
|
||||
--- This function can be overridden by plugins to display notifications using a
|
||||
@@ -453,10 +460,10 @@ function vim.on_key(fn, ns_id)
|
||||
return #on_key_cbs
|
||||
end
|
||||
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
fn = { fn, 'c', true },
|
||||
ns_id = { ns_id, 'n', true }
|
||||
}
|
||||
ns_id = { ns_id, 'n', true },
|
||||
})
|
||||
|
||||
if ns_id == nil or ns_id == 0 then
|
||||
ns_id = vim.api.nvim_create_namespace('')
|
||||
@@ -481,10 +488,13 @@ function vim._on_key(char)
|
||||
end
|
||||
|
||||
if failed_ns_ids[1] then
|
||||
error(string.format(
|
||||
error(
|
||||
string.format(
|
||||
"Error executing 'on_key' with ns_ids '%s'\n Messages: %s",
|
||||
table.concat(failed_ns_ids, ", "),
|
||||
table.concat(failed_messages, "\n")))
|
||||
table.concat(failed_ns_ids, ', '),
|
||||
table.concat(failed_messages, '\n')
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -511,8 +521,10 @@ function vim._expand_pat(pat, env)
|
||||
-- Probably just need to do a smarter match than just `:match`
|
||||
|
||||
-- Get the last part of the pattern
|
||||
local last_part = pat:match("[%w.:_%[%]'\"]+$")
|
||||
if not last_part then return {}, 0 end
|
||||
local last_part = pat:match('[%w.:_%[%]\'"]+$')
|
||||
if not last_part then
|
||||
return {}, 0
|
||||
end
|
||||
|
||||
local parts, search_index = vim._expand_pat_get_parts(last_part)
|
||||
|
||||
@@ -529,7 +541,7 @@ function vim._expand_pat(pat, env)
|
||||
|
||||
-- Normally, we just have a string
|
||||
-- Just attempt to get the string directly from the environment
|
||||
if type(part) == "string" then
|
||||
if type(part) == 'string' then
|
||||
key = part
|
||||
else
|
||||
-- However, sometimes you want to use a variable, and complete on it
|
||||
@@ -554,7 +566,7 @@ function vim._expand_pat(pat, env)
|
||||
local field = rawget(final_env, key)
|
||||
if field == nil then
|
||||
local mt = getmetatable(final_env)
|
||||
if mt and type(mt.__index) == "table" then
|
||||
if mt and type(mt.__index) == 'table' then
|
||||
field = rawget(mt.__index, key)
|
||||
elseif final_env == vim and vim._submodules[key] then
|
||||
field = vim[key]
|
||||
@@ -571,17 +583,17 @@ function vim._expand_pat(pat, env)
|
||||
---@private
|
||||
local function insert_keys(obj)
|
||||
for k, _ in pairs(obj) do
|
||||
if type(k) == "string" and string.sub(k,1,string.len(match_part)) == match_part then
|
||||
if type(k) == 'string' and string.sub(k, 1, string.len(match_part)) == match_part then
|
||||
table.insert(keys, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if type(final_env) == "table" then
|
||||
if type(final_env) == 'table' then
|
||||
insert_keys(final_env)
|
||||
end
|
||||
local mt = getmetatable(final_env)
|
||||
if mt and type(mt.__index) == "table" then
|
||||
if mt and type(mt.__index) == 'table' then
|
||||
insert_keys(mt.__index)
|
||||
end
|
||||
if final_env == vim then
|
||||
@@ -602,12 +614,12 @@ vim._expand_pat_get_parts = function(lua_string)
|
||||
for idx = 1, #lua_string do
|
||||
local s = lua_string:sub(idx, idx)
|
||||
|
||||
if not in_brackets and (s == "." or s == ":") then
|
||||
if not in_brackets and (s == '.' or s == ':') then
|
||||
table.insert(parts, accumulator)
|
||||
accumulator = ''
|
||||
|
||||
search_index = idx + 1
|
||||
elseif s == "[" then
|
||||
elseif s == '[' then
|
||||
in_brackets = true
|
||||
|
||||
table.insert(parts, accumulator)
|
||||
@@ -619,7 +631,7 @@ vim._expand_pat_get_parts = function(lua_string)
|
||||
in_brackets = false
|
||||
search_index = idx + 1
|
||||
|
||||
if string_char == "VAR" then
|
||||
if string_char == 'VAR' then
|
||||
table.insert(parts, { accumulator })
|
||||
accumulator = ''
|
||||
|
||||
@@ -631,7 +643,7 @@ vim._expand_pat_get_parts = function(lua_string)
|
||||
if s == '"' or s == "'" then
|
||||
string_char = s
|
||||
elseif s ~= ' ' then
|
||||
string_char = "VAR"
|
||||
string_char = 'VAR'
|
||||
accumulator = s
|
||||
end
|
||||
elseif string_char then
|
||||
@@ -649,7 +661,9 @@ vim._expand_pat_get_parts = function(lua_string)
|
||||
end
|
||||
end
|
||||
|
||||
parts = vim.tbl_filter(function(val) return #val > 0 end, parts)
|
||||
parts = vim.tbl_filter(function(val)
|
||||
return #val > 0
|
||||
end, parts)
|
||||
|
||||
return parts, search_index
|
||||
end
|
||||
@@ -677,14 +691,14 @@ function vim._cs_remote(rcid, server_addr, connect_error, args)
|
||||
local function connection_failure_errmsg(consequence)
|
||||
local explanation
|
||||
if server_addr == '' then
|
||||
explanation = "No server specified with --server"
|
||||
explanation = 'No server specified with --server'
|
||||
else
|
||||
explanation = "Failed to connect to '" .. server_addr .. "'"
|
||||
if connect_error ~= "" then
|
||||
explanation = explanation .. ": " .. connect_error
|
||||
if connect_error ~= '' then
|
||||
explanation = explanation .. ': ' .. connect_error
|
||||
end
|
||||
end
|
||||
return "E247: " .. explanation .. ". " .. consequence
|
||||
return 'E247: ' .. explanation .. '. ' .. consequence
|
||||
end
|
||||
|
||||
local f_silent = false
|
||||
@@ -718,11 +732,13 @@ function vim._cs_remote(rcid, server_addr, connect_error, args)
|
||||
|
||||
if rcid == 0 then
|
||||
if not f_silent then
|
||||
vim.notify(connection_failure_errmsg("Editing locally"), vim.log.levels.WARN)
|
||||
vim.notify(connection_failure_errmsg('Editing locally'), vim.log.levels.WARN)
|
||||
end
|
||||
else
|
||||
local command = {}
|
||||
if f_tab then table.insert(command, 'tab') end
|
||||
if f_tab then
|
||||
table.insert(command, 'tab')
|
||||
end
|
||||
table.insert(command, 'drop')
|
||||
for i = 2, #args do
|
||||
table.insert(command, vim.fn.fnameescape(args[i]))
|
||||
@@ -746,7 +762,7 @@ end
|
||||
--- from. Defaults to "Nvim".
|
||||
function vim.deprecate(name, alternative, version, plugin)
|
||||
local message = name .. ' is deprecated'
|
||||
plugin = plugin or "Nvim"
|
||||
plugin = plugin or 'Nvim'
|
||||
message = alternative and (message .. ', use ' .. alternative .. ' instead.') or message
|
||||
message = message .. ' See :h deprecated\nThis function will be removed in ' .. plugin .. ' version ' .. version
|
||||
vim.notify_once(message, vim.log.levels.WARN)
|
||||
|
@@ -17,7 +17,7 @@ end
|
||||
|
||||
function vim._load_package(name)
|
||||
local basename = name:gsub('%.', '/')
|
||||
local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
|
||||
local paths = { 'lua/' .. basename .. '.lua', 'lua/' .. basename .. '/init.lua' }
|
||||
local found = vim.api.nvim__get_runtime(paths, false, { is_lua = true })
|
||||
if #found > 0 then
|
||||
local f, err = loadfile(found[1])
|
||||
@@ -26,7 +26,7 @@ function vim._load_package(name)
|
||||
|
||||
local so_paths = {}
|
||||
for _, trail in ipairs(vim._so_trails) do
|
||||
local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
|
||||
local path = 'lua' .. trail:gsub('?', basename) -- so_trails contains a leading slash
|
||||
table.insert(so_paths, path)
|
||||
end
|
||||
|
||||
@@ -37,9 +37,9 @@ function vim._load_package(name)
|
||||
-- b) replace all dots by underscores
|
||||
-- c) prepend "luaopen_"
|
||||
-- So "foo-bar.baz" should result in "luaopen_bar_baz"
|
||||
local dash = name:find("-", 1, true)
|
||||
local dash = name:find('-', 1, true)
|
||||
local modname = dash and name:sub(dash + 1) or name
|
||||
local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
|
||||
local f, err = package.loadlib(found[1], 'luaopen_' .. modname:gsub('%.', '_'))
|
||||
return f or error(err)
|
||||
end
|
||||
return nil
|
||||
@@ -49,7 +49,7 @@ end
|
||||
table.insert(package.loaders, 2, vim._load_package)
|
||||
|
||||
-- builtin functions which always should be available
|
||||
require'vim.shared'
|
||||
require('vim.shared')
|
||||
|
||||
vim._submodules = { inspect = true }
|
||||
|
||||
@@ -67,7 +67,7 @@ setmetatable(vim, {
|
||||
return t[key]
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
--- <Docs described in |vim.empty_dict()| >
|
||||
@@ -79,5 +79,5 @@ end
|
||||
|
||||
-- only on main thread: functions for interacting with editor state
|
||||
if not vim.is_thread() then
|
||||
require'vim._editor'
|
||||
require('vim._editor')
|
||||
end
|
||||
|
@@ -13,7 +13,9 @@ local SET_TYPES = setmetatable({
|
||||
local options_info = {}
|
||||
for _, v in pairs(a.nvim_get_all_options_info()) do
|
||||
options_info[v.name] = v
|
||||
if v.shortname ~= "" then options_info[v.shortname] = v end
|
||||
if v.shortname ~= '' then
|
||||
options_info[v.shortname] = v
|
||||
end
|
||||
end
|
||||
|
||||
local get_scoped_options = function(scope)
|
||||
@@ -27,19 +29,21 @@ local get_scoped_options = function(scope)
|
||||
return result
|
||||
end
|
||||
|
||||
local buf_options = get_scoped_options("buf")
|
||||
local glb_options = get_scoped_options("global")
|
||||
local win_options = get_scoped_options("win")
|
||||
local buf_options = get_scoped_options('buf')
|
||||
local glb_options = get_scoped_options('global')
|
||||
local win_options = get_scoped_options('win')
|
||||
|
||||
local function make_meta_accessor(get, set, del, validator)
|
||||
validator = validator or function() return true end
|
||||
validator = validator or function()
|
||||
return true
|
||||
end
|
||||
|
||||
validate {
|
||||
get = {get, 'f'};
|
||||
set = {set, 'f'};
|
||||
del = {del, 'f', true};
|
||||
validator = {validator, 'f'};
|
||||
}
|
||||
validate({
|
||||
get = { get, 'f' },
|
||||
set = { set, 'f' },
|
||||
del = { del, 'f', true },
|
||||
validator = { validator, 'f' },
|
||||
})
|
||||
|
||||
local mt = {}
|
||||
function mt:__newindex(k, v)
|
||||
@@ -73,7 +77,7 @@ end, vim.fn.setenv)
|
||||
do -- buffer option accessor
|
||||
local function new_buf_opt_accessor(bufnr)
|
||||
local function get(k)
|
||||
if bufnr == nil and type(k) == "number" then
|
||||
if bufnr == nil and type(k) == 'number' then
|
||||
return new_buf_opt_accessor(k)
|
||||
end
|
||||
|
||||
@@ -103,7 +107,7 @@ end
|
||||
do -- window option accessor
|
||||
local function new_win_opt_accessor(winnr)
|
||||
local function get(k)
|
||||
if winnr == nil and type(k) == "number" then
|
||||
if winnr == nil and type(k) == 'number' then
|
||||
return new_win_opt_accessor(k)
|
||||
end
|
||||
return a.nvim_win_get_option(winnr or 0, k)
|
||||
@@ -131,17 +135,19 @@ end
|
||||
|
||||
-- vim global option
|
||||
-- this ONLY sets the global option. like `setglobal`
|
||||
vim.go = make_meta_accessor(
|
||||
function(k) return a.nvim_get_option_value(k, {scope = "global"}) end,
|
||||
function(k, v) return a.nvim_set_option_value(k, v, {scope = "global"}) end
|
||||
)
|
||||
vim.go = make_meta_accessor(function(k)
|
||||
return a.nvim_get_option_value(k, { scope = 'global' })
|
||||
end, function(k, v)
|
||||
return a.nvim_set_option_value(k, v, { scope = 'global' })
|
||||
end)
|
||||
|
||||
-- vim `set` style options.
|
||||
-- it has no additional metamethod magic.
|
||||
vim.o = make_meta_accessor(
|
||||
function(k) return a.nvim_get_option_value(k, {}) end,
|
||||
function(k, v) return a.nvim_set_option_value(k, v, {}) end
|
||||
)
|
||||
vim.o = make_meta_accessor(function(k)
|
||||
return a.nvim_get_option_value(k, {})
|
||||
end, function(k, v)
|
||||
return a.nvim_set_option_value(k, v, {})
|
||||
end)
|
||||
|
||||
---@brief [[
|
||||
--- vim.opt, vim.opt_local and vim.opt_global implementation
|
||||
@@ -154,7 +160,9 @@ vim.o = make_meta_accessor(
|
||||
--- Preserves the order and does not mutate the original list
|
||||
local remove_duplicate_values = function(t)
|
||||
local result, seen = {}, {}
|
||||
if type(t) == "function" then error(debug.traceback("asdf")) end
|
||||
if type(t) == 'function' then
|
||||
error(debug.traceback('asdf'))
|
||||
end
|
||||
for _, v in ipairs(t) do
|
||||
if not seen[v] then
|
||||
table.insert(result, v)
|
||||
@@ -184,24 +192,28 @@ local OptionTypes = setmetatable({
|
||||
MAP = 4,
|
||||
SET = 5,
|
||||
}, {
|
||||
__index = function(_, k) error("Not a valid OptionType: " .. k) end,
|
||||
__newindex = function(_, k) error("Cannot set a new OptionType: " .. k) end,
|
||||
__index = function(_, k)
|
||||
error('Not a valid OptionType: ' .. k)
|
||||
end,
|
||||
__newindex = function(_, k)
|
||||
error('Cannot set a new OptionType: ' .. k)
|
||||
end,
|
||||
})
|
||||
|
||||
--- Convert a vimoption_T style dictionary to the correct OptionType associated with it.
|
||||
---@return OptionType
|
||||
local get_option_type = function(name, info)
|
||||
if info.type == "boolean" then
|
||||
if info.type == 'boolean' then
|
||||
return OptionTypes.BOOLEAN
|
||||
elseif info.type == "number" then
|
||||
elseif info.type == 'number' then
|
||||
return OptionTypes.NUMBER
|
||||
elseif info.type == "string" then
|
||||
elseif info.type == 'string' then
|
||||
if not info.commalist and not info.flaglist then
|
||||
return OptionTypes.STRING
|
||||
end
|
||||
|
||||
if key_value_options[name] then
|
||||
assert(info.commalist, "Must be a comma list to use key:value style")
|
||||
assert(info.commalist, 'Must be a comma list to use key:value style')
|
||||
return OptionTypes.MAP
|
||||
end
|
||||
|
||||
@@ -211,13 +223,12 @@ local get_option_type = function(name, info)
|
||||
return OptionTypes.ARRAY
|
||||
end
|
||||
|
||||
error("Fallthrough in OptionTypes")
|
||||
error('Fallthrough in OptionTypes')
|
||||
else
|
||||
error("Not a known info.type:" .. info.type)
|
||||
error('Not a known info.type:' .. info.type)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Check whether the OptionTypes is allowed for vim.opt
|
||||
-- If it does not match, throw an error which indicates which option causes the error.
|
||||
local function assert_valid_value(name, value, types)
|
||||
@@ -228,16 +239,18 @@ local function assert_valid_value(name, value, types)
|
||||
end
|
||||
end
|
||||
|
||||
error(string.format("Invalid option type '%s' for '%s', should be %s", type_of_value, name, table.concat(types, " or ")))
|
||||
error(
|
||||
string.format("Invalid option type '%s' for '%s', should be %s", type_of_value, name, table.concat(types, ' or '))
|
||||
)
|
||||
end
|
||||
|
||||
local valid_types = {
|
||||
[OptionTypes.BOOLEAN] = { "boolean" },
|
||||
[OptionTypes.NUMBER] = { "number" },
|
||||
[OptionTypes.STRING] = { "string" },
|
||||
[OptionTypes.SET] = { "string", "table" },
|
||||
[OptionTypes.ARRAY] = { "string", "table" },
|
||||
[OptionTypes.MAP] = { "string", "table" },
|
||||
[OptionTypes.BOOLEAN] = { 'boolean' },
|
||||
[OptionTypes.NUMBER] = { 'number' },
|
||||
[OptionTypes.STRING] = { 'string' },
|
||||
[OptionTypes.SET] = { 'string', 'table' },
|
||||
[OptionTypes.ARRAY] = { 'string', 'table' },
|
||||
[OptionTypes.MAP] = { 'string', 'table' },
|
||||
}
|
||||
|
||||
--- Convert a lua value to a vimoption_T value
|
||||
@@ -245,12 +258,20 @@ local convert_value_to_vim = (function()
|
||||
-- Map of functions to take a Lua style value and convert to vimoption_T style value.
|
||||
-- Each function takes (info, lua_value) -> vim_value
|
||||
local to_vim_value = {
|
||||
[OptionTypes.BOOLEAN] = function(_, value) return value end,
|
||||
[OptionTypes.NUMBER] = function(_, value) return value end,
|
||||
[OptionTypes.STRING] = function(_, value) return value end,
|
||||
[OptionTypes.BOOLEAN] = function(_, value)
|
||||
return value
|
||||
end,
|
||||
[OptionTypes.NUMBER] = function(_, value)
|
||||
return value
|
||||
end,
|
||||
[OptionTypes.STRING] = function(_, value)
|
||||
return value
|
||||
end,
|
||||
|
||||
[OptionTypes.SET] = function(info, value)
|
||||
if type(value) == "string" then return value end
|
||||
if type(value) == 'string' then
|
||||
return value
|
||||
end
|
||||
|
||||
if info.flaglist and info.commalist then
|
||||
local keys = {}
|
||||
@@ -261,7 +282,7 @@ local convert_value_to_vim = (function()
|
||||
end
|
||||
|
||||
table.sort(keys)
|
||||
return table.concat(keys, ",")
|
||||
return table.concat(keys, ',')
|
||||
else
|
||||
local result = ''
|
||||
for k, v in pairs(value) do
|
||||
@@ -275,23 +296,27 @@ local convert_value_to_vim = (function()
|
||||
end,
|
||||
|
||||
[OptionTypes.ARRAY] = function(info, value)
|
||||
if type(value) == "string" then return value end
|
||||
if type(value) == 'string' then
|
||||
return value
|
||||
end
|
||||
if not info.allows_duplicates then
|
||||
value = remove_duplicate_values(value)
|
||||
end
|
||||
return table.concat(value, ",")
|
||||
return table.concat(value, ',')
|
||||
end,
|
||||
|
||||
[OptionTypes.MAP] = function(_, value)
|
||||
if type(value) == "string" then return value end
|
||||
if type(value) == 'string' then
|
||||
return value
|
||||
end
|
||||
|
||||
local result = {}
|
||||
for opt_key, opt_value in pairs(value) do
|
||||
table.insert(result, string.format("%s:%s", opt_key, opt_value))
|
||||
table.insert(result, string.format('%s:%s', opt_key, opt_value))
|
||||
end
|
||||
|
||||
table.sort(result)
|
||||
return table.concat(result, ",")
|
||||
return table.concat(result, ',')
|
||||
end,
|
||||
}
|
||||
|
||||
@@ -312,12 +337,18 @@ local convert_value_to_lua = (function()
|
||||
-- Map of OptionType to functions that take vimoption_T values and convert to lua values.
|
||||
-- Each function takes (info, vim_value) -> lua_value
|
||||
local to_lua_value = {
|
||||
[OptionTypes.BOOLEAN] = function(_, value) return value end,
|
||||
[OptionTypes.NUMBER] = function(_, value) return value end,
|
||||
[OptionTypes.STRING] = function(_, value) return value end,
|
||||
[OptionTypes.BOOLEAN] = function(_, value)
|
||||
return value
|
||||
end,
|
||||
[OptionTypes.NUMBER] = function(_, value)
|
||||
return value
|
||||
end,
|
||||
[OptionTypes.STRING] = function(_, value)
|
||||
return value
|
||||
end,
|
||||
|
||||
[OptionTypes.ARRAY] = function(info, value)
|
||||
if type(value) == "table" then
|
||||
if type(value) == 'table' then
|
||||
if not info.allows_duplicates then
|
||||
value = remove_duplicate_values(value)
|
||||
end
|
||||
@@ -332,41 +363,43 @@ local convert_value_to_lua = (function()
|
||||
end
|
||||
|
||||
-- Handles unescaped commas in a list.
|
||||
if string.find(value, ",,,") then
|
||||
local comma_split = vim.split(value, ",,,")
|
||||
if string.find(value, ',,,') then
|
||||
local comma_split = vim.split(value, ',,,')
|
||||
local left = comma_split[1]
|
||||
local right = comma_split[2]
|
||||
|
||||
local result = {}
|
||||
vim.list_extend(result, vim.split(left, ","))
|
||||
table.insert(result, ",")
|
||||
vim.list_extend(result, vim.split(right, ","))
|
||||
vim.list_extend(result, vim.split(left, ','))
|
||||
table.insert(result, ',')
|
||||
vim.list_extend(result, vim.split(right, ','))
|
||||
|
||||
table.sort(result)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
if string.find(value, ",^,,", 1, true) then
|
||||
local comma_split = vim.split(value, ",^,,", true)
|
||||
if string.find(value, ',^,,', 1, true) then
|
||||
local comma_split = vim.split(value, ',^,,', true)
|
||||
local left = comma_split[1]
|
||||
local right = comma_split[2]
|
||||
|
||||
local result = {}
|
||||
vim.list_extend(result, vim.split(left, ","))
|
||||
table.insert(result, "^,")
|
||||
vim.list_extend(result, vim.split(right, ","))
|
||||
vim.list_extend(result, vim.split(left, ','))
|
||||
table.insert(result, '^,')
|
||||
vim.list_extend(result, vim.split(right, ','))
|
||||
|
||||
table.sort(result)
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
return vim.split(value, ",")
|
||||
return vim.split(value, ',')
|
||||
end,
|
||||
|
||||
[OptionTypes.SET] = function(info, value)
|
||||
if type(value) == "table" then return value end
|
||||
if type(value) == 'table' then
|
||||
return value
|
||||
end
|
||||
|
||||
-- Empty strings mean that there is nothing there,
|
||||
-- so empty table should be returned.
|
||||
@@ -374,10 +407,10 @@ local convert_value_to_lua = (function()
|
||||
return {}
|
||||
end
|
||||
|
||||
assert(info.flaglist, "That is the only one I know how to handle")
|
||||
assert(info.flaglist, 'That is the only one I know how to handle')
|
||||
|
||||
if info.flaglist and info.commalist then
|
||||
local split_value = vim.split(value, ",")
|
||||
local split_value = vim.split(value, ',')
|
||||
local result = {}
|
||||
for _, v in ipairs(split_value) do
|
||||
result[v] = true
|
||||
@@ -395,15 +428,17 @@ local convert_value_to_lua = (function()
|
||||
end,
|
||||
|
||||
[OptionTypes.MAP] = function(info, raw_value)
|
||||
if type(raw_value) == "table" then return raw_value end
|
||||
if type(raw_value) == 'table' then
|
||||
return raw_value
|
||||
end
|
||||
|
||||
assert(info.commalist, "Only commas are supported currently")
|
||||
assert(info.commalist, 'Only commas are supported currently')
|
||||
|
||||
local result = {}
|
||||
|
||||
local comma_split = vim.split(raw_value, ",")
|
||||
local comma_split = vim.split(raw_value, ',')
|
||||
for _, key_value_str in ipairs(comma_split) do
|
||||
local key, value = unpack(vim.split(key_value_str, ":"))
|
||||
local key, value = unpack(vim.split(key_value_str, ':'))
|
||||
key = vim.trim(key)
|
||||
|
||||
result[key] = value
|
||||
@@ -443,17 +478,21 @@ local prepend_value = (function()
|
||||
end,
|
||||
|
||||
[OptionTypes.MAP] = function(left, right)
|
||||
return vim.tbl_extend("force", left, right)
|
||||
return vim.tbl_extend('force', left, right)
|
||||
end,
|
||||
|
||||
[OptionTypes.SET] = function(left, right)
|
||||
return vim.tbl_extend("force", left, right)
|
||||
return vim.tbl_extend('force', left, right)
|
||||
end,
|
||||
}
|
||||
|
||||
return function(name, info, current, new)
|
||||
return value_mutator(
|
||||
name, info, convert_value_to_lua(name, info, current), convert_value_to_lua(name, info, new), methods
|
||||
name,
|
||||
info,
|
||||
convert_value_to_lua(name, info, current),
|
||||
convert_value_to_lua(name, info, new),
|
||||
methods
|
||||
)
|
||||
end
|
||||
end)()
|
||||
@@ -478,17 +517,21 @@ local add_value = (function()
|
||||
end,
|
||||
|
||||
[OptionTypes.MAP] = function(left, right)
|
||||
return vim.tbl_extend("force", left, right)
|
||||
return vim.tbl_extend('force', left, right)
|
||||
end,
|
||||
|
||||
[OptionTypes.SET] = function(left, right)
|
||||
return vim.tbl_extend("force", left, right)
|
||||
return vim.tbl_extend('force', left, right)
|
||||
end,
|
||||
}
|
||||
|
||||
return function(name, info, current, new)
|
||||
return value_mutator(
|
||||
name, info, convert_value_to_lua(name, info, current), convert_value_to_lua(name, info, new), methods
|
||||
name,
|
||||
info,
|
||||
convert_value_to_lua(name, info, current),
|
||||
convert_value_to_lua(name, info, new),
|
||||
methods
|
||||
)
|
||||
end
|
||||
end)()
|
||||
@@ -518,11 +561,11 @@ local remove_value = (function()
|
||||
end,
|
||||
|
||||
[OptionTypes.STRING] = function()
|
||||
error("Subtraction not supported for strings.")
|
||||
error('Subtraction not supported for strings.')
|
||||
end,
|
||||
|
||||
[OptionTypes.ARRAY] = function(left, right)
|
||||
if type(right) == "string" then
|
||||
if type(right) == 'string' then
|
||||
remove_one_item(left, right)
|
||||
else
|
||||
for _, v in ipairs(right) do
|
||||
@@ -534,7 +577,7 @@ local remove_value = (function()
|
||||
end,
|
||||
|
||||
[OptionTypes.MAP] = function(left, right)
|
||||
if type(right) == "string" then
|
||||
if type(right) == 'string' then
|
||||
left[right] = nil
|
||||
else
|
||||
for _, v in ipairs(right) do
|
||||
@@ -546,7 +589,7 @@ local remove_value = (function()
|
||||
end,
|
||||
|
||||
[OptionTypes.SET] = function(left, right)
|
||||
if type(right) == "string" then
|
||||
if type(right) == 'string' then
|
||||
left[right] = nil
|
||||
else
|
||||
for _, v in ipairs(right) do
|
||||
@@ -567,9 +610,9 @@ local create_option_metatable = function(set_type)
|
||||
local set_mt, option_mt
|
||||
|
||||
local make_option = function(name, value)
|
||||
local info = assert(options_info[name], "Not a valid option name: " .. name)
|
||||
local info = assert(options_info[name], 'Not a valid option name: ' .. name)
|
||||
|
||||
if type(value) == "table" and getmetatable(value) == option_mt then
|
||||
if type(value) == 'table' and getmetatable(value) == option_mt then
|
||||
assert(name == value._name, "must be the same value, otherwise that's weird.")
|
||||
|
||||
value = value._value
|
||||
@@ -584,9 +627,9 @@ local create_option_metatable = function(set_type)
|
||||
|
||||
local scope
|
||||
if set_type == SET_TYPES.GLOBAL then
|
||||
scope = "global"
|
||||
scope = 'global'
|
||||
elseif set_type == SET_TYPES.LOCAL then
|
||||
scope = "local"
|
||||
scope = 'local'
|
||||
end
|
||||
|
||||
option_mt = {
|
||||
@@ -625,7 +668,7 @@ local create_option_metatable = function(set_type)
|
||||
|
||||
__sub = function(self, right)
|
||||
return make_option(self._name, remove_value(self._name, self._info, self._value, right))
|
||||
end
|
||||
end,
|
||||
}
|
||||
option_mt.__index = option_mt
|
||||
|
||||
|
@@ -7,6 +7,6 @@
|
||||
|
||||
local lua_version = _VERSION:sub(-3)
|
||||
|
||||
if lua_version >= "5.2" then
|
||||
if lua_version >= '5.2' then
|
||||
unpack = table.unpack -- luacheck: ignore 121 143
|
||||
end
|
||||
|
@@ -28,7 +28,7 @@ local global_diagnostic_options = {
|
||||
|
||||
M.handlers = setmetatable({}, {
|
||||
__newindex = function(t, name, handler)
|
||||
vim.validate { handler = {handler, "t" } }
|
||||
vim.validate({ handler = { handler, 't' } })
|
||||
rawset(t, name, handler)
|
||||
if global_diagnostic_options[name] == nil then
|
||||
global_diagnostic_options[name] = true
|
||||
@@ -39,7 +39,7 @@ M.handlers = setmetatable({}, {
|
||||
-- Metatable that automatically creates an empty table when assigning to a missing key
|
||||
local bufnr_and_namespace_cacher_mt = {
|
||||
__index = function(t, bufnr)
|
||||
assert(bufnr > 0, "Invalid buffer number")
|
||||
assert(bufnr > 0, 'Invalid buffer number')
|
||||
t[bufnr] = {}
|
||||
return t[bufnr]
|
||||
end,
|
||||
@@ -47,11 +47,11 @@ local bufnr_and_namespace_cacher_mt = {
|
||||
|
||||
local diagnostic_cache = setmetatable({}, {
|
||||
__index = function(t, bufnr)
|
||||
assert(bufnr > 0, "Invalid buffer number")
|
||||
assert(bufnr > 0, 'Invalid buffer number')
|
||||
vim.api.nvim_buf_attach(bufnr, false, {
|
||||
on_detach = function()
|
||||
rawset(t, bufnr, nil) -- clear cache
|
||||
end
|
||||
end,
|
||||
})
|
||||
t[bufnr] = {}
|
||||
return t[bufnr]
|
||||
@@ -68,7 +68,7 @@ local all_namespaces = {}
|
||||
---@private
|
||||
local function to_severity(severity)
|
||||
if type(severity) == 'string' then
|
||||
return assert(M.severity[string.upper(severity)], string.format("Invalid severity: %s", severity))
|
||||
return assert(M.severity[string.upper(severity)], string.format('Invalid severity: %s', severity))
|
||||
end
|
||||
return severity
|
||||
end
|
||||
@@ -79,15 +79,19 @@ local function filter_by_severity(severity, diagnostics)
|
||||
return diagnostics
|
||||
end
|
||||
|
||||
if type(severity) ~= "table" then
|
||||
if type(severity) ~= 'table' then
|
||||
severity = to_severity(severity)
|
||||
return vim.tbl_filter(function(t) return t.severity == severity end, diagnostics)
|
||||
return vim.tbl_filter(function(t)
|
||||
return t.severity == severity
|
||||
end, diagnostics)
|
||||
end
|
||||
|
||||
local min_severity = to_severity(severity.min) or M.severity.HINT
|
||||
local max_severity = to_severity(severity.max) or M.severity.ERROR
|
||||
|
||||
return vim.tbl_filter(function(t) return t.severity <= min_severity and t.severity >= max_severity end, diagnostics)
|
||||
return vim.tbl_filter(function(t)
|
||||
return t.severity <= min_severity and t.severity >= max_severity
|
||||
end, diagnostics)
|
||||
end
|
||||
|
||||
---@private
|
||||
@@ -113,17 +117,17 @@ local function prefix_source(diagnostics)
|
||||
end
|
||||
|
||||
local t = vim.deepcopy(d)
|
||||
t.message = string.format("%s: %s", d.source, d.message)
|
||||
t.message = string.format('%s: %s', d.source, d.message)
|
||||
return t
|
||||
end, diagnostics)
|
||||
end
|
||||
|
||||
---@private
|
||||
local function reformat_diagnostics(format, diagnostics)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
format = { format, 'f' },
|
||||
diagnostics = { diagnostics, 't' },
|
||||
}
|
||||
})
|
||||
|
||||
local formatted = vim.deepcopy(diagnostics)
|
||||
for _, diagnostic in ipairs(formatted) do
|
||||
@@ -135,11 +139,11 @@ end
|
||||
---@private
|
||||
local function enabled_value(option, namespace)
|
||||
local ns = namespace and M.get_namespace(namespace) or {}
|
||||
if ns.opts and type(ns.opts[option]) == "table" then
|
||||
if ns.opts and type(ns.opts[option]) == 'table' then
|
||||
return ns.opts[option]
|
||||
end
|
||||
|
||||
if type(global_diagnostic_options[option]) == "table" then
|
||||
if type(global_diagnostic_options[option]) == 'table' then
|
||||
return global_diagnostic_options[option]
|
||||
end
|
||||
|
||||
@@ -162,7 +166,7 @@ local function resolve_optional_value(option, value, namespace, bufnr)
|
||||
elseif type(value) == 'table' then
|
||||
return value
|
||||
else
|
||||
error("Unexpected option type: " .. vim.inspect(value))
|
||||
error('Unexpected option type: ' .. vim.inspect(value))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -181,10 +185,10 @@ end
|
||||
|
||||
-- Default diagnostic highlights
|
||||
local diagnostic_severities = {
|
||||
[M.severity.ERROR] = { ctermfg = 1, guifg = "Red" };
|
||||
[M.severity.WARN] = { ctermfg = 3, guifg = "Orange" };
|
||||
[M.severity.INFO] = { ctermfg = 4, guifg = "LightBlue" };
|
||||
[M.severity.HINT] = { ctermfg = 7, guifg = "LightGrey" };
|
||||
[M.severity.ERROR] = { ctermfg = 1, guifg = 'Red' },
|
||||
[M.severity.WARN] = { ctermfg = 3, guifg = 'Orange' },
|
||||
[M.severity.INFO] = { ctermfg = 4, guifg = 'LightBlue' },
|
||||
[M.severity.HINT] = { ctermfg = 7, guifg = 'LightGrey' },
|
||||
}
|
||||
|
||||
-- Make a map from DiagnosticSeverity -> Highlight Name
|
||||
@@ -194,16 +198,16 @@ local function make_highlight_map(base_name)
|
||||
for k in pairs(diagnostic_severities) do
|
||||
local name = M.severity[k]
|
||||
name = name:sub(1, 1) .. name:sub(2):lower()
|
||||
result[k] = "Diagnostic" .. base_name .. name
|
||||
result[k] = 'Diagnostic' .. base_name .. name
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
local virtual_text_highlight_map = make_highlight_map("VirtualText")
|
||||
local underline_highlight_map = make_highlight_map("Underline")
|
||||
local floating_highlight_map = make_highlight_map("Floating")
|
||||
local sign_highlight_map = make_highlight_map("Sign")
|
||||
local virtual_text_highlight_map = make_highlight_map('VirtualText')
|
||||
local underline_highlight_map = make_highlight_map('Underline')
|
||||
local floating_highlight_map = make_highlight_map('Floating')
|
||||
local sign_highlight_map = make_highlight_map('Sign')
|
||||
|
||||
---@private
|
||||
local define_default_signs = (function()
|
||||
@@ -244,7 +248,7 @@ local function is_disabled(namespace, bufnr)
|
||||
return true
|
||||
end
|
||||
|
||||
if type(diagnostic_disabled[bufnr]) == "table" then
|
||||
if type(diagnostic_disabled[bufnr]) == 'table' then
|
||||
return diagnostic_disabled[bufnr][namespace]
|
||||
end
|
||||
return diagnostic_disabled[bufnr]
|
||||
@@ -271,8 +275,8 @@ end
|
||||
---@private
|
||||
local function set_diagnostic_cache(namespace, bufnr, diagnostics)
|
||||
for _, diagnostic in ipairs(diagnostics) do
|
||||
assert(diagnostic.lnum, "Diagnostic line number is required")
|
||||
assert(diagnostic.col, "Diagnostic column is required")
|
||||
assert(diagnostic.lnum, 'Diagnostic line number is required')
|
||||
assert(diagnostic.col, 'Diagnostic column is required')
|
||||
diagnostic.severity = diagnostic.severity and to_severity(diagnostic.severity) or M.severity.ERROR
|
||||
diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum
|
||||
diagnostic.end_col = diagnostic.end_col or diagnostic.col
|
||||
@@ -314,10 +318,17 @@ local function save_extmarks(namespace, bufnr)
|
||||
end,
|
||||
on_detach = function()
|
||||
diagnostic_cache_extmarks[bufnr] = nil
|
||||
end})
|
||||
end,
|
||||
})
|
||||
diagnostic_attached_buffers[bufnr] = true
|
||||
end
|
||||
diagnostic_cache_extmarks[bufnr][namespace] = vim.api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, {details = true})
|
||||
diagnostic_cache_extmarks[bufnr][namespace] = vim.api.nvim_buf_get_extmarks(
|
||||
bufnr,
|
||||
namespace,
|
||||
0,
|
||||
-1,
|
||||
{ details = true }
|
||||
)
|
||||
end
|
||||
|
||||
local registered_autocmds = {}
|
||||
@@ -325,11 +336,11 @@ local registered_autocmds = {}
|
||||
---@private
|
||||
local function make_augroup_key(namespace, bufnr)
|
||||
local ns = M.get_namespace(namespace)
|
||||
return string.format("DiagnosticInsertLeave:%s:%s", bufnr, ns.name)
|
||||
return string.format('DiagnosticInsertLeave:%s:%s', bufnr, ns.name)
|
||||
end
|
||||
|
||||
--- Table of autocmd events to fire the update for displaying new diagnostic information
|
||||
local insert_leave_auto_cmds = { "InsertLeave", "CursorHoldI" }
|
||||
local insert_leave_auto_cmds = { 'InsertLeave', 'CursorHoldI' }
|
||||
|
||||
---@private
|
||||
local function schedule_display(namespace, bufnr, args)
|
||||
@@ -337,15 +348,17 @@ local function schedule_display(namespace, bufnr, args)
|
||||
|
||||
local key = make_augroup_key(namespace, bufnr)
|
||||
if not registered_autocmds[key] then
|
||||
vim.cmd(string.format([[augroup %s
|
||||
vim.cmd(string.format(
|
||||
[[augroup %s
|
||||
au!
|
||||
autocmd %s <buffer=%s> lua vim.diagnostic._execute_scheduled_display(%s, %s)
|
||||
augroup END]],
|
||||
key,
|
||||
table.concat(insert_leave_auto_cmds, ","),
|
||||
table.concat(insert_leave_auto_cmds, ','),
|
||||
bufnr,
|
||||
namespace,
|
||||
bufnr))
|
||||
bufnr
|
||||
))
|
||||
registered_autocmds[key] = true
|
||||
end
|
||||
end
|
||||
@@ -355,9 +368,12 @@ local function clear_scheduled_display(namespace, bufnr)
|
||||
local key = make_augroup_key(namespace, bufnr)
|
||||
|
||||
if registered_autocmds[key] then
|
||||
vim.cmd(string.format([[augroup %s
|
||||
vim.cmd(string.format(
|
||||
[[augroup %s
|
||||
au!
|
||||
augroup END]], key))
|
||||
augroup END]],
|
||||
key
|
||||
))
|
||||
registered_autocmds[key] = nil
|
||||
end
|
||||
end
|
||||
@@ -382,7 +398,7 @@ local function get_diagnostics(bufnr, opts, clamp)
|
||||
if not opts.lnum or d.lnum == opts.lnum then
|
||||
if clamp and vim.api.nvim_buf_is_loaded(b) then
|
||||
local line_count = buf_line_count[b] - 1
|
||||
if (d.lnum > line_count or d.end_lnum > line_count or d.lnum < 0 or d.end_lnum < 0) then
|
||||
if d.lnum > line_count or d.end_lnum > line_count or d.lnum < 0 or d.end_lnum < 0 then
|
||||
d = vim.deepcopy(d)
|
||||
d.lnum = math.max(math.min(d.lnum, line_count), 0)
|
||||
d.end_lnum = math.max(math.min(d.end_lnum, line_count), 0)
|
||||
@@ -431,7 +447,7 @@ end
|
||||
local function set_list(loclist, opts)
|
||||
opts = opts or {}
|
||||
local open = vim.F.if_nil(opts.open, true)
|
||||
local title = opts.title or "Diagnostics"
|
||||
local title = opts.title or 'Diagnostics'
|
||||
local winnr = opts.winnr or 0
|
||||
local bufnr
|
||||
if loclist then
|
||||
@@ -447,7 +463,7 @@ local function set_list(loclist, opts)
|
||||
vim.fn.setqflist({}, ' ', { title = title, items = items })
|
||||
end
|
||||
if open then
|
||||
vim.api.nvim_command(loclist and "lopen" or "botright copen")
|
||||
vim.api.nvim_command(loclist and 'lopen' or 'botright copen')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -457,7 +473,7 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
|
||||
bufnr = get_bufnr(bufnr)
|
||||
local wrap = vim.F.if_nil(opts.wrap, true)
|
||||
local line_count = vim.api.nvim_buf_line_count(bufnr)
|
||||
local diagnostics = get_diagnostics(bufnr, vim.tbl_extend("keep", opts, {namespace = namespace}), true)
|
||||
local diagnostics = get_diagnostics(bufnr, vim.tbl_extend('keep', opts, { namespace = namespace }), true)
|
||||
local line_diagnostics = diagnostic_lines(diagnostics)
|
||||
for i = 0, line_count do
|
||||
local offset = i * (search_forward and 1 or -1)
|
||||
@@ -472,11 +488,19 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
|
||||
local line_length = #vim.api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
|
||||
local sort_diagnostics, is_next
|
||||
if search_forward then
|
||||
sort_diagnostics = function(a, b) return a.col < b.col end
|
||||
is_next = function(d) return math.min(d.col, line_length - 1) > position[2] end
|
||||
sort_diagnostics = function(a, b)
|
||||
return a.col < b.col
|
||||
end
|
||||
is_next = function(d)
|
||||
return math.min(d.col, line_length - 1) > position[2]
|
||||
end
|
||||
else
|
||||
sort_diagnostics = function(a, b) return a.col > b.col end
|
||||
is_next = function(d) return math.min(d.col, line_length - 1) < position[2] end
|
||||
sort_diagnostics = function(a, b)
|
||||
return a.col > b.col
|
||||
end
|
||||
is_next = function(d)
|
||||
return math.min(d.col, line_length - 1) < position[2]
|
||||
end
|
||||
end
|
||||
table.sort(line_diagnostics[lnum], sort_diagnostics)
|
||||
if i == 0 then
|
||||
@@ -500,7 +524,7 @@ local function diagnostic_move_pos(opts, pos)
|
||||
local win_id = opts.win_id or vim.api.nvim_get_current_win()
|
||||
|
||||
if not pos then
|
||||
vim.api.nvim_echo({{"No more valid diagnostics to move to", "WarningMsg"}}, true, {})
|
||||
vim.api.nvim_echo({ { 'No more valid diagnostics to move to', 'WarningMsg' } }, true, {})
|
||||
return
|
||||
end
|
||||
|
||||
@@ -509,19 +533,17 @@ local function diagnostic_move_pos(opts, pos)
|
||||
vim.cmd("normal! m'")
|
||||
vim.api.nvim_win_set_cursor(win_id, { pos[1] + 1, pos[2] })
|
||||
-- Open folds under the cursor
|
||||
vim.cmd("normal! zv")
|
||||
vim.cmd('normal! zv')
|
||||
end)
|
||||
|
||||
if float then
|
||||
local float_opts = type(float) == "table" and float or {}
|
||||
local float_opts = type(float) == 'table' and float or {}
|
||||
vim.schedule(function()
|
||||
M.open_float(
|
||||
vim.tbl_extend("keep", float_opts, {
|
||||
M.open_float(vim.tbl_extend('keep', float_opts, {
|
||||
bufnr = vim.api.nvim_win_get_buf(win_id),
|
||||
scope = "cursor",
|
||||
scope = 'cursor',
|
||||
focus = false,
|
||||
})
|
||||
)
|
||||
}))
|
||||
end)
|
||||
end
|
||||
end
|
||||
@@ -599,10 +621,10 @@ end
|
||||
---@param namespace number|nil Update the options for the given namespace. When omitted, update the
|
||||
--- global diagnostic options.
|
||||
function M.config(opts, namespace)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
opts = { opts, 't', true },
|
||||
namespace = { namespace, 'n', true },
|
||||
}
|
||||
})
|
||||
|
||||
local t
|
||||
if namespace then
|
||||
@@ -645,16 +667,16 @@ end
|
||||
---@param diagnostics table A list of diagnostic items |diagnostic-structure|
|
||||
---@param opts table|nil Display options to pass to |vim.diagnostic.show()|
|
||||
function M.set(namespace, bufnr, diagnostics, opts)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
namespace = { namespace, 'n' },
|
||||
bufnr = { bufnr, 'n' },
|
||||
diagnostics = {
|
||||
diagnostics,
|
||||
vim.tbl_islist,
|
||||
"a list of diagnostics",
|
||||
'a list of diagnostics',
|
||||
},
|
||||
opts = { opts, 't', true },
|
||||
}
|
||||
})
|
||||
|
||||
bufnr = get_bufnr(bufnr)
|
||||
|
||||
@@ -668,7 +690,7 @@ function M.set(namespace, bufnr, diagnostics, opts)
|
||||
M.show(namespace, bufnr, nil, opts)
|
||||
end
|
||||
|
||||
vim.api.nvim_exec_autocmds("DiagnosticChanged", {
|
||||
vim.api.nvim_exec_autocmds('DiagnosticChanged', {
|
||||
modeline = false,
|
||||
buffer = bufnr,
|
||||
})
|
||||
@@ -679,7 +701,7 @@ end
|
||||
---@param namespace number Diagnostic namespace
|
||||
---@return table Namespace metadata
|
||||
function M.get_namespace(namespace)
|
||||
vim.validate { namespace = { namespace, 'n' } }
|
||||
vim.validate({ namespace = { namespace, 'n' } })
|
||||
if not all_namespaces[namespace] then
|
||||
local name
|
||||
for k, v in pairs(vim.api.nvim_get_namespaces()) do
|
||||
@@ -689,7 +711,7 @@ function M.get_namespace(namespace)
|
||||
end
|
||||
end
|
||||
|
||||
assert(name, "namespace does not exist or is anonymous")
|
||||
assert(name, 'namespace does not exist or is anonymous')
|
||||
|
||||
all_namespaces[namespace] = {
|
||||
name = name,
|
||||
@@ -717,10 +739,10 @@ end
|
||||
--- - severity: See |diagnostic-severity|.
|
||||
---@return table A list of diagnostic items |diagnostic-structure|.
|
||||
function M.get(bufnr, opts)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
bufnr = { bufnr, 'n', true },
|
||||
opts = { opts, 't', true },
|
||||
}
|
||||
})
|
||||
|
||||
return get_diagnostics(bufnr, opts, false)
|
||||
end
|
||||
@@ -755,10 +777,7 @@ end
|
||||
--- Move to the previous diagnostic in the current buffer.
|
||||
---@param opts table See |vim.diagnostic.goto_next()|
|
||||
function M.goto_prev(opts)
|
||||
return diagnostic_move_pos(
|
||||
opts,
|
||||
M.get_prev_pos(opts)
|
||||
)
|
||||
return diagnostic_move_pos(opts, M.get_prev_pos(opts))
|
||||
end
|
||||
|
||||
--- Get the next diagnostic closest to the cursor position.
|
||||
@@ -803,24 +822,21 @@ end
|
||||
--- the "scope" option).
|
||||
--- - win_id: (number, default 0) Window ID
|
||||
function M.goto_next(opts)
|
||||
return diagnostic_move_pos(
|
||||
opts,
|
||||
M.get_next_pos(opts)
|
||||
)
|
||||
return diagnostic_move_pos(opts, M.get_next_pos(opts))
|
||||
end
|
||||
|
||||
M.handlers.signs = {
|
||||
show = function(namespace, bufnr, diagnostics, opts)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
namespace = { namespace, 'n' },
|
||||
bufnr = { bufnr, 'n' },
|
||||
diagnostics = {
|
||||
diagnostics,
|
||||
vim.tbl_islist,
|
||||
"a list of diagnostics",
|
||||
'a list of diagnostics',
|
||||
},
|
||||
opts = { opts, 't', true },
|
||||
}
|
||||
})
|
||||
|
||||
bufnr = get_bufnr(bufnr)
|
||||
opts = opts or {}
|
||||
@@ -835,7 +851,7 @@ M.handlers.signs = {
|
||||
local priority = opts.signs and opts.signs.priority or 10
|
||||
local get_priority
|
||||
if opts.severity_sort then
|
||||
if type(opts.severity_sort) == "table" and opts.severity_sort.reverse then
|
||||
if type(opts.severity_sort) == 'table' and opts.severity_sort.reverse then
|
||||
get_priority = function(severity)
|
||||
return priority + (severity - vim.diagnostic.severity.ERROR)
|
||||
end
|
||||
@@ -852,21 +868,15 @@ M.handlers.signs = {
|
||||
|
||||
local ns = M.get_namespace(namespace)
|
||||
if not ns.user_data.sign_group then
|
||||
ns.user_data.sign_group = string.format("vim.diagnostic.%s", ns.name)
|
||||
ns.user_data.sign_group = string.format('vim.diagnostic.%s', ns.name)
|
||||
end
|
||||
|
||||
local sign_group = ns.user_data.sign_group
|
||||
for _, diagnostic in ipairs(diagnostics) do
|
||||
vim.fn.sign_place(
|
||||
0,
|
||||
sign_group,
|
||||
sign_highlight_map[diagnostic.severity],
|
||||
bufnr,
|
||||
{
|
||||
vim.fn.sign_place(0, sign_group, sign_highlight_map[diagnostic.severity], bufnr, {
|
||||
priority = get_priority(diagnostic.severity),
|
||||
lnum = diagnostic.lnum + 1
|
||||
}
|
||||
)
|
||||
lnum = diagnostic.lnum + 1,
|
||||
})
|
||||
end
|
||||
end,
|
||||
hide = function(namespace, bufnr)
|
||||
@@ -879,16 +889,16 @@ M.handlers.signs = {
|
||||
|
||||
M.handlers.underline = {
|
||||
show = function(namespace, bufnr, diagnostics, opts)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
namespace = { namespace, 'n' },
|
||||
bufnr = { bufnr, 'n' },
|
||||
diagnostics = {
|
||||
diagnostics,
|
||||
vim.tbl_islist,
|
||||
"a list of diagnostics",
|
||||
'a list of diagnostics',
|
||||
},
|
||||
opts = { opts, 't', true },
|
||||
}
|
||||
})
|
||||
|
||||
bufnr = get_bufnr(bufnr)
|
||||
opts = opts or {}
|
||||
@@ -899,7 +909,7 @@ M.handlers.underline = {
|
||||
|
||||
local ns = M.get_namespace(namespace)
|
||||
if not ns.user_data.underline_ns then
|
||||
ns.user_data.underline_ns = vim.api.nvim_create_namespace("")
|
||||
ns.user_data.underline_ns = vim.api.nvim_create_namespace('')
|
||||
end
|
||||
|
||||
local underline_ns = ns.user_data.underline_ns
|
||||
@@ -928,21 +938,21 @@ M.handlers.underline = {
|
||||
diagnostic_cache_extmarks[bufnr][ns.user_data.underline_ns] = {}
|
||||
vim.api.nvim_buf_clear_namespace(bufnr, ns.user_data.underline_ns, 0, -1)
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
M.handlers.virtual_text = {
|
||||
show = function(namespace, bufnr, diagnostics, opts)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
namespace = { namespace, 'n' },
|
||||
bufnr = { bufnr, 'n' },
|
||||
diagnostics = {
|
||||
diagnostics,
|
||||
vim.tbl_islist,
|
||||
"a list of diagnostics",
|
||||
'a list of diagnostics',
|
||||
},
|
||||
opts = { opts, 't', true },
|
||||
}
|
||||
})
|
||||
|
||||
bufnr = get_bufnr(bufnr)
|
||||
opts = opts or {}
|
||||
@@ -952,10 +962,7 @@ M.handlers.virtual_text = {
|
||||
if opts.virtual_text.format then
|
||||
diagnostics = reformat_diagnostics(opts.virtual_text.format, diagnostics)
|
||||
end
|
||||
if
|
||||
opts.virtual_text.source
|
||||
and (opts.virtual_text.source ~= "if_many" or count_sources(bufnr) > 1)
|
||||
then
|
||||
if opts.virtual_text.source and (opts.virtual_text.source ~= 'if_many' or count_sources(bufnr) > 1) then
|
||||
diagnostics = prefix_source(diagnostics)
|
||||
end
|
||||
if opts.virtual_text.severity then
|
||||
@@ -965,7 +972,7 @@ M.handlers.virtual_text = {
|
||||
|
||||
local ns = M.get_namespace(namespace)
|
||||
if not ns.user_data.virt_text_ns then
|
||||
ns.user_data.virt_text_ns = vim.api.nvim_create_namespace("")
|
||||
ns.user_data.virt_text_ns = vim.api.nvim_create_namespace('')
|
||||
end
|
||||
|
||||
local virt_text_ns = ns.user_data.virt_text_ns
|
||||
@@ -978,7 +985,7 @@ M.handlers.virtual_text = {
|
||||
|
||||
if virt_texts then
|
||||
vim.api.nvim_buf_set_extmark(bufnr, virt_text_ns, line, 0, {
|
||||
hl_mode = "combine",
|
||||
hl_mode = 'combine',
|
||||
virt_text = virt_texts,
|
||||
})
|
||||
end
|
||||
@@ -1006,11 +1013,11 @@ function M._get_virt_text_chunks(line_diags, opts)
|
||||
end
|
||||
|
||||
opts = opts or {}
|
||||
local prefix = opts.prefix or "■"
|
||||
local prefix = opts.prefix or '■'
|
||||
local spacing = opts.spacing or 4
|
||||
|
||||
-- Create a little more space between virtual text and contents
|
||||
local virt_texts = {{string.rep(" ", spacing)}}
|
||||
local virt_texts = { { string.rep(' ', spacing) } }
|
||||
|
||||
for i = 1, #line_diags - 1 do
|
||||
table.insert(virt_texts, { prefix, virtual_text_highlight_map[line_diags[i].severity] })
|
||||
@@ -1020,13 +1027,10 @@ function M._get_virt_text_chunks(line_diags, opts)
|
||||
-- TODO(tjdevries): Allow different servers to be shown first somehow?
|
||||
-- TODO(tjdevries): Display server name associated with these?
|
||||
if last.message then
|
||||
table.insert(
|
||||
virt_texts,
|
||||
{
|
||||
string.format("%s %s", prefix, last.message:gsub("\r", ""):gsub("\n", " ")),
|
||||
virtual_text_highlight_map[last.severity]
|
||||
}
|
||||
)
|
||||
table.insert(virt_texts, {
|
||||
string.format('%s %s', prefix, last.message:gsub('\r', ''):gsub('\n', ' ')),
|
||||
virtual_text_highlight_map[last.severity],
|
||||
})
|
||||
|
||||
return virt_texts
|
||||
end
|
||||
@@ -1066,10 +1070,10 @@ end
|
||||
---@param bufnr number|nil Buffer number, or 0 for current buffer. When
|
||||
--- omitted, hide diagnostics in all buffers.
|
||||
function M.hide(namespace, bufnr)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
namespace = { namespace, 'n', true },
|
||||
bufnr = { bufnr, 'n', true },
|
||||
}
|
||||
})
|
||||
|
||||
local buffers = bufnr and { get_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache)
|
||||
for _, iter_bufnr in ipairs(buffers) do
|
||||
@@ -1098,19 +1102,21 @@ end
|
||||
--- or {bufnr} is nil.
|
||||
---@param opts table|nil Display options. See |vim.diagnostic.config()|.
|
||||
function M.show(namespace, bufnr, diagnostics, opts)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
namespace = { namespace, 'n', true },
|
||||
bufnr = { bufnr, 'n', true },
|
||||
diagnostics = {
|
||||
diagnostics,
|
||||
function(v) return v == nil or vim.tbl_islist(v) end,
|
||||
"a list of diagnostics",
|
||||
function(v)
|
||||
return v == nil or vim.tbl_islist(v)
|
||||
end,
|
||||
'a list of diagnostics',
|
||||
},
|
||||
opts = { opts, 't', true },
|
||||
}
|
||||
})
|
||||
|
||||
if not bufnr or not namespace then
|
||||
assert(not diagnostics, "Cannot show diagnostics without a buffer and namespace")
|
||||
assert(not diagnostics, 'Cannot show diagnostics without a buffer and namespace')
|
||||
if not bufnr then
|
||||
for iter_bufnr in pairs(diagnostic_cache) do
|
||||
M.show(namespace, iter_bufnr, nil, opts)
|
||||
@@ -1150,10 +1156,14 @@ function M.show(namespace, bufnr, diagnostics, opts)
|
||||
end
|
||||
|
||||
if vim.F.if_nil(opts.severity_sort, false) then
|
||||
if type(opts.severity_sort) == "table" and opts.severity_sort.reverse then
|
||||
table.sort(diagnostics, function(a, b) return a.severity < b.severity end)
|
||||
if type(opts.severity_sort) == 'table' and opts.severity_sort.reverse then
|
||||
table.sort(diagnostics, function(a, b)
|
||||
return a.severity < b.severity
|
||||
end)
|
||||
else
|
||||
table.sort(diagnostics, function(a, b) return a.severity > b.severity end)
|
||||
table.sort(diagnostics, function(a, b)
|
||||
return a.severity > b.severity
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1209,13 +1219,13 @@ end
|
||||
function M.open_float(opts, ...)
|
||||
-- Support old (bufnr, opts) signature
|
||||
local bufnr
|
||||
if opts == nil or type(opts) == "number" then
|
||||
if opts == nil or type(opts) == 'number' then
|
||||
bufnr = opts
|
||||
opts = ...
|
||||
else
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
opts = { opts, 't', true },
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
opts = opts or {}
|
||||
@@ -1228,41 +1238,39 @@ function M.open_float(opts, ...)
|
||||
-- with its `opts` table which also includes "keyword" parameters. So we create a dedicated
|
||||
-- options table that inherits missing keys from the global configuration before resolving.
|
||||
local t = global_diagnostic_options.float
|
||||
local float_opts = vim.tbl_extend("keep", opts, type(t) == "table" and t or {})
|
||||
local float_opts = vim.tbl_extend('keep', opts, type(t) == 'table' and t or {})
|
||||
opts = get_resolved_options({ float = float_opts }, nil, bufnr).float
|
||||
end
|
||||
|
||||
local scope = ({l = "line", c = "cursor", b = "buffer"})[opts.scope] or opts.scope or "line"
|
||||
local scope = ({ l = 'line', c = 'cursor', b = 'buffer' })[opts.scope] or opts.scope or 'line'
|
||||
local lnum, col
|
||||
if scope == "line" or scope == "cursor" then
|
||||
if scope == 'line' or scope == 'cursor' then
|
||||
if not opts.pos then
|
||||
local pos = vim.api.nvim_win_get_cursor(0)
|
||||
lnum = pos[1] - 1
|
||||
col = pos[2]
|
||||
elseif type(opts.pos) == "number" then
|
||||
elseif type(opts.pos) == 'number' then
|
||||
lnum = opts.pos
|
||||
elseif type(opts.pos) == "table" then
|
||||
elseif type(opts.pos) == 'table' then
|
||||
lnum, col = unpack(opts.pos)
|
||||
else
|
||||
error("Invalid value for option 'pos'")
|
||||
end
|
||||
elseif scope ~= "buffer" then
|
||||
elseif scope ~= 'buffer' then
|
||||
error("Invalid value for option 'scope'")
|
||||
end
|
||||
|
||||
local diagnostics = get_diagnostics(bufnr, opts, true)
|
||||
|
||||
if scope == "line" then
|
||||
if scope == 'line' then
|
||||
diagnostics = vim.tbl_filter(function(d)
|
||||
return d.lnum == lnum
|
||||
end, diagnostics)
|
||||
elseif scope == "cursor" then
|
||||
elseif scope == 'cursor' then
|
||||
-- LSP servers can send diagnostics with `end_col` past the length of the line
|
||||
local line_length = #vim.api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
|
||||
diagnostics = vim.tbl_filter(function(d)
|
||||
return d.lnum == lnum
|
||||
and math.min(d.col, line_length - 1) <= col
|
||||
and (d.end_col >= col or d.end_lnum > lnum)
|
||||
return d.lnum == lnum and math.min(d.col, line_length - 1) <= col and (d.end_col >= col or d.end_lnum > lnum)
|
||||
end, diagnostics)
|
||||
end
|
||||
|
||||
@@ -1272,29 +1280,39 @@ function M.open_float(opts, ...)
|
||||
|
||||
local severity_sort = vim.F.if_nil(opts.severity_sort, global_diagnostic_options.severity_sort)
|
||||
if severity_sort then
|
||||
if type(severity_sort) == "table" and severity_sort.reverse then
|
||||
table.sort(diagnostics, function(a, b) return a.severity > b.severity end)
|
||||
if type(severity_sort) == 'table' and severity_sort.reverse then
|
||||
table.sort(diagnostics, function(a, b)
|
||||
return a.severity > b.severity
|
||||
end)
|
||||
else
|
||||
table.sort(diagnostics, function(a, b) return a.severity < b.severity end)
|
||||
table.sort(diagnostics, function(a, b)
|
||||
return a.severity < b.severity
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
local highlights = {}
|
||||
local header = if_nil(opts.header, "Diagnostics:")
|
||||
local header = if_nil(opts.header, 'Diagnostics:')
|
||||
if header then
|
||||
vim.validate { header = { header, function(v)
|
||||
return type(v) == "string" or type(v) == "table"
|
||||
end, "'string' or 'table'" } }
|
||||
if type(header) == "table" then
|
||||
vim.validate({
|
||||
header = {
|
||||
header,
|
||||
function(v)
|
||||
return type(v) == 'string' or type(v) == 'table'
|
||||
end,
|
||||
"'string' or 'table'",
|
||||
},
|
||||
})
|
||||
if type(header) == 'table' then
|
||||
-- Don't insert any lines for an empty string
|
||||
if string.len(if_nil(header[1], "")) > 0 then
|
||||
if string.len(if_nil(header[1], '')) > 0 then
|
||||
table.insert(lines, header[1])
|
||||
table.insert(highlights, {0, header[2] or "Bold"})
|
||||
table.insert(highlights, { 0, header[2] or 'Bold' })
|
||||
end
|
||||
elseif #header > 0 then
|
||||
table.insert(lines, header)
|
||||
table.insert(highlights, {0, "Bold"})
|
||||
table.insert(highlights, { 0, 'Bold' })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1302,30 +1320,36 @@ function M.open_float(opts, ...)
|
||||
diagnostics = reformat_diagnostics(opts.format, diagnostics)
|
||||
end
|
||||
|
||||
if opts.source and (opts.source ~= "if_many" or count_sources(bufnr) > 1) then
|
||||
if opts.source and (opts.source ~= 'if_many' or count_sources(bufnr) > 1) then
|
||||
diagnostics = prefix_source(diagnostics)
|
||||
end
|
||||
|
||||
local prefix_opt = if_nil(opts.prefix, (scope == "cursor" and #diagnostics <= 1) and "" or function(_, i)
|
||||
return string.format("%d. ", i)
|
||||
local prefix_opt = if_nil(opts.prefix, (scope == 'cursor' and #diagnostics <= 1) and '' or function(_, i)
|
||||
return string.format('%d. ', i)
|
||||
end)
|
||||
|
||||
local prefix, prefix_hl_group
|
||||
if prefix_opt then
|
||||
vim.validate { prefix = { prefix_opt, function(v)
|
||||
return type(v) == "string" or type(v) == "table" or type(v) == "function"
|
||||
end, "'string' or 'table' or 'function'" } }
|
||||
if type(prefix_opt) == "string" then
|
||||
prefix, prefix_hl_group = prefix_opt, "NormalFloat"
|
||||
elseif type(prefix_opt) == "table" then
|
||||
prefix, prefix_hl_group = prefix_opt[1] or "", prefix_opt[2] or "NormalFloat"
|
||||
vim.validate({
|
||||
prefix = {
|
||||
prefix_opt,
|
||||
function(v)
|
||||
return type(v) == 'string' or type(v) == 'table' or type(v) == 'function'
|
||||
end,
|
||||
"'string' or 'table' or 'function'",
|
||||
},
|
||||
})
|
||||
if type(prefix_opt) == 'string' then
|
||||
prefix, prefix_hl_group = prefix_opt, 'NormalFloat'
|
||||
elseif type(prefix_opt) == 'table' then
|
||||
prefix, prefix_hl_group = prefix_opt[1] or '', prefix_opt[2] or 'NormalFloat'
|
||||
end
|
||||
end
|
||||
|
||||
for i, diagnostic in ipairs(diagnostics) do
|
||||
if prefix_opt and type(prefix_opt) == "function" then
|
||||
if prefix_opt and type(prefix_opt) == 'function' then
|
||||
prefix, prefix_hl_group = prefix_opt(diagnostic, i, #diagnostics)
|
||||
prefix, prefix_hl_group = prefix or "", prefix_hl_group or "NormalFloat"
|
||||
prefix, prefix_hl_group = prefix or '', prefix_hl_group or 'NormalFloat'
|
||||
end
|
||||
local hiname = floating_highlight_map[diagnostic.severity]
|
||||
local message_lines = vim.split(diagnostic.message, '\n')
|
||||
@@ -1365,10 +1389,10 @@ end
|
||||
---@param bufnr number|nil Remove diagnostics for the given buffer. When omitted,
|
||||
--- diagnostics are removed for all buffers.
|
||||
function M.reset(namespace, bufnr)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
namespace = { namespace, 'n', true },
|
||||
bufnr = { bufnr, 'n', true },
|
||||
}
|
||||
})
|
||||
|
||||
local buffers = bufnr and { get_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache)
|
||||
for _, iter_bufnr in ipairs(buffers) do
|
||||
@@ -1378,7 +1402,7 @@ function M.reset(namespace, bufnr)
|
||||
M.hide(iter_namespace, iter_bufnr)
|
||||
end
|
||||
|
||||
vim.api.nvim_exec_autocmds("DiagnosticChanged", {
|
||||
vim.api.nvim_exec_autocmds('DiagnosticChanged', {
|
||||
modeline = false,
|
||||
buffer = iter_bufnr,
|
||||
})
|
||||
@@ -1414,7 +1438,7 @@ end
|
||||
--- omitted, disable diagnostics in all buffers.
|
||||
---@param namespace number|nil Only disable diagnostics for the given namespace.
|
||||
function M.disable(bufnr, namespace)
|
||||
vim.validate { bufnr = {bufnr, 'n', true}, namespace = {namespace, 'n', true} }
|
||||
vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } })
|
||||
if bufnr == nil then
|
||||
if namespace == nil then
|
||||
-- Disable everything (including as yet non-existing buffers and
|
||||
@@ -1422,7 +1446,9 @@ function M.disable(bufnr, namespace)
|
||||
-- its metatable to always return true. This metatable is removed
|
||||
-- in enable()
|
||||
diagnostic_disabled = setmetatable({}, {
|
||||
__index = function() return true end,
|
||||
__index = function()
|
||||
return true
|
||||
end,
|
||||
})
|
||||
else
|
||||
local ns = M.get_namespace(namespace)
|
||||
@@ -1433,7 +1459,7 @@ function M.disable(bufnr, namespace)
|
||||
if namespace == nil then
|
||||
diagnostic_disabled[bufnr] = true
|
||||
else
|
||||
if type(diagnostic_disabled[bufnr]) ~= "table" then
|
||||
if type(diagnostic_disabled[bufnr]) ~= 'table' then
|
||||
diagnostic_disabled[bufnr] = {}
|
||||
end
|
||||
diagnostic_disabled[bufnr][namespace] = true
|
||||
@@ -1449,7 +1475,7 @@ end
|
||||
--- omitted, enable diagnostics in all buffers.
|
||||
---@param namespace number|nil Only enable diagnostics for the given namespace.
|
||||
function M.enable(bufnr, namespace)
|
||||
vim.validate { bufnr = {bufnr, 'n', true}, namespace = {namespace, 'n', true} }
|
||||
vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } })
|
||||
if bufnr == nil then
|
||||
if namespace == nil then
|
||||
-- Enable everything by setting diagnostic_disabled to an empty table
|
||||
@@ -1463,7 +1489,7 @@ function M.enable(bufnr, namespace)
|
||||
if namespace == nil then
|
||||
diagnostic_disabled[bufnr] = nil
|
||||
else
|
||||
if type(diagnostic_disabled[bufnr]) ~= "table" then
|
||||
if type(diagnostic_disabled[bufnr]) ~= 'table' then
|
||||
return
|
||||
end
|
||||
diagnostic_disabled[bufnr][namespace] = nil
|
||||
@@ -1500,13 +1526,13 @@ end
|
||||
--- ERROR.
|
||||
---@return diagnostic |diagnostic-structure| or `nil` if {pat} fails to match {str}.
|
||||
function M.match(str, pat, groups, severity_map, defaults)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
str = { str, 's' },
|
||||
pat = { pat, 's' },
|
||||
groups = { groups, 't' },
|
||||
severity_map = { severity_map, 't', true },
|
||||
defaults = { defaults, 't', true },
|
||||
}
|
||||
})
|
||||
|
||||
severity_map = severity_map or M.severity
|
||||
|
||||
@@ -1518,15 +1544,15 @@ function M.match(str, pat, groups, severity_map, defaults)
|
||||
|
||||
for i, match in ipairs(matches) do
|
||||
local field = groups[i]
|
||||
if field == "severity" then
|
||||
if field == 'severity' then
|
||||
match = severity_map[match]
|
||||
elseif field == "lnum" or field == "end_lnum" or field == "col" or field == "end_col" then
|
||||
elseif field == 'lnum' or field == 'end_lnum' or field == 'col' or field == 'end_col' then
|
||||
match = assert(tonumber(match)) - 1
|
||||
end
|
||||
diagnostic[field] = match
|
||||
end
|
||||
|
||||
diagnostic = vim.tbl_extend("keep", diagnostic, defaults or {})
|
||||
diagnostic = vim.tbl_extend('keep', diagnostic, defaults or {})
|
||||
diagnostic.severity = diagnostic.severity or M.severity.ERROR
|
||||
diagnostic.col = diagnostic.col or 0
|
||||
diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum
|
||||
@@ -1547,13 +1573,13 @@ local errlist_type_map = {
|
||||
---@param diagnostics table List of diagnostics |diagnostic-structure|.
|
||||
---@return array of quickfix list items |setqflist-what|
|
||||
function M.toqflist(diagnostics)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
diagnostics = {
|
||||
diagnostics,
|
||||
vim.tbl_islist,
|
||||
"a list of diagnostics",
|
||||
'a list of diagnostics',
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
local list = {}
|
||||
for _, v in ipairs(diagnostics) do
|
||||
@@ -1584,13 +1610,13 @@ end
|
||||
--- |getloclist()|.
|
||||
---@return array of diagnostics |diagnostic-structure|
|
||||
function M.fromqflist(list)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
list = {
|
||||
list,
|
||||
vim.tbl_islist,
|
||||
"a list of quickfix items",
|
||||
'a list of quickfix items',
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
local diagnostics = {}
|
||||
for _, item in ipairs(list) do
|
||||
@@ -1599,7 +1625,7 @@ function M.fromqflist(list)
|
||||
local col = math.max(0, item.col - 1)
|
||||
local end_lnum = item.end_lnum > 0 and (item.end_lnum - 1) or lnum
|
||||
local end_col = item.end_col > 0 and (item.end_col - 1) or col
|
||||
local severity = item.type ~= "" and M.severity[item.type] or M.severity.ERROR
|
||||
local severity = item.type ~= '' and M.severity[item.type] or M.severity.ERROR
|
||||
table.insert(diagnostics, {
|
||||
bufnr = item.bufnr,
|
||||
lnum = lnum,
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@ local function getlines(bufnr, start_lnum, end_lnum, opts)
|
||||
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
|
||||
opts = opts or {}
|
||||
return opts.concat and (table.concat(lines) or "") or lines
|
||||
return opts.concat and (table.concat(lines) or '') or lines
|
||||
end
|
||||
|
||||
---@private
|
||||
@@ -35,23 +35,23 @@ function M.bindzone(path, bufnr) end
|
||||
|
||||
function M.btm(bufnr)
|
||||
if vim.g.dosbatch_syntax_for_btm and vim.g.dosbatch_syntax_for_btm ~= 0 then
|
||||
vim.bo[bufnr].filetype = "dosbatch"
|
||||
vim.bo[bufnr].filetype = 'dosbatch'
|
||||
else
|
||||
vim.bo[bufnr].filetype = "btm"
|
||||
vim.bo[bufnr].filetype = 'btm'
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns true if file content looks like RAPID
|
||||
local function is_rapid(bufnr, extension)
|
||||
if extension == "cfg" then
|
||||
if extension == 'cfg' then
|
||||
local line = getlines(bufnr, 1):lower()
|
||||
return findany(line, { "eio:cfg", "mmc:cfg", "moc:cfg", "proc:cfg", "sio:cfg", "sys:cfg" })
|
||||
return findany(line, { 'eio:cfg', 'mmc:cfg', 'moc:cfg', 'proc:cfg', 'sio:cfg', 'sys:cfg' })
|
||||
end
|
||||
local first = "^%s*module%s+%S+%s*"
|
||||
local first = '^%s*module%s+%S+%s*'
|
||||
-- Called from mod, prg or sys functions
|
||||
for _, line in ipairs(getlines(bufnr, 1, -1)) do
|
||||
if not line:find("^%s*$") then
|
||||
return findany(line:lower(), { "^%s*%%%%%%", first .. "(", first .. "$" })
|
||||
if not line:find('^%s*$') then
|
||||
return findany(line:lower(), { '^%s*%%%%%%', first .. '(', first .. '$' })
|
||||
end
|
||||
end
|
||||
-- Only found blank lines
|
||||
@@ -61,10 +61,10 @@ end
|
||||
function M.cfg(bufnr)
|
||||
if vim.g.filetype_cfg then
|
||||
vim.bo[bufnr].filetype = vim.g.filetype_cfg
|
||||
elseif is_rapid(bufnr, "cfg") then
|
||||
vim.bo[bufnr].filetype = "rapid"
|
||||
elseif is_rapid(bufnr, 'cfg') then
|
||||
vim.bo[bufnr].filetype = 'rapid'
|
||||
else
|
||||
vim.bo[bufnr].filetype = "cfg"
|
||||
vim.bo[bufnr].filetype = 'cfg'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -85,23 +85,23 @@ function M.e(path, bufnr) end
|
||||
-- If not found, assume SGML.
|
||||
function M.ent(bufnr)
|
||||
for _, line in ipairs(getlines(bufnr, 1, 5)) do
|
||||
if line:find("^%s*[#{]") then
|
||||
vim.bo[bufnr].filetype = "cl"
|
||||
if line:find('^%s*[#{]') then
|
||||
vim.bo[bufnr].filetype = 'cl'
|
||||
return
|
||||
elseif not line:find("^%s*$") then
|
||||
elseif not line:find('^%s*$') then
|
||||
-- Not a blank line, not a comment, and not a block start,
|
||||
-- so doesn't look like valid cl code.
|
||||
break
|
||||
end
|
||||
end
|
||||
vim.bo[bufnr].filetype = "dtd"
|
||||
vim.bo[bufnr].filetype = 'dtd'
|
||||
end
|
||||
|
||||
function M.euphoria(bufnr)
|
||||
if vim.g.filetype_euphoria then
|
||||
vim.bo[bufnr].filetype = vim.g.filetype_euphoria
|
||||
else
|
||||
vim.bo[bufnr].filetype = "euphoria3"
|
||||
vim.bo[bufnr].filetype = 'euphoria3'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -111,12 +111,12 @@ function M.ex(bufnr)
|
||||
else
|
||||
for _, line in ipairs(getlines(bufnr, 1, 100)) do
|
||||
-- TODO: in the Vim regex, \> is used to match the end of the word, can this be omitted?
|
||||
if findany(line, { "^%-%-", "^ifdef", "^include" }) then
|
||||
vim.bo[bufnr].filetype = "euphoria3"
|
||||
if findany(line, { '^%-%-', '^ifdef', '^include' }) then
|
||||
vim.bo[bufnr].filetype = 'euphoria3'
|
||||
return
|
||||
end
|
||||
end
|
||||
vim.bo[bufnr].filetype = "elixir"
|
||||
vim.bo[bufnr].filetype = 'elixir'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -126,10 +126,10 @@ end
|
||||
function M.foam(bufnr)
|
||||
local foam_file = false
|
||||
for _, line in ipairs(getlines(bufnr, 1, 15)) do
|
||||
if line:find("^FoamFile") then
|
||||
if line:find('^FoamFile') then
|
||||
foam_file = true
|
||||
elseif foam_file and line:find("^%s*object") then
|
||||
vim.bo[bufnr].filetype = "foam"
|
||||
elseif foam_file and line:find('^%s*object') then
|
||||
vim.bo[bufnr].filetype = 'foam'
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -141,10 +141,10 @@ function M.frm(bufnr)
|
||||
else
|
||||
-- Always ignore case
|
||||
local lines = getlines(bufnr, 1, 5, { concat = true }):lower()
|
||||
if findany(lines, { "vb_name", "begin vb%.form", "begin vb%.mdiform" }) then
|
||||
vim.bo[bufnr].filetype = "vb"
|
||||
if findany(lines, { 'vb_name', 'begin vb%.form', 'begin vb%.mdiform' }) then
|
||||
vim.bo[bufnr].filetype = 'vb'
|
||||
else
|
||||
vim.bo[bufnr].filetype = "form"
|
||||
vim.bo[bufnr].filetype = 'form'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -153,21 +153,21 @@ function M.fs(path, bufnr) end
|
||||
|
||||
function M.header(bufnr)
|
||||
for _, line in ipairs(getlines(bufnr, 1, 200)) do
|
||||
if findany(line, { "^@interface", "^@end", "^@class" }) then
|
||||
if findany(line, { '^@interface', '^@end', '^@class' }) then
|
||||
if vim.g.c_syntax_for_h then
|
||||
vim.bo[bufnr].filetype = "objc"
|
||||
vim.bo[bufnr].filetype = 'objc'
|
||||
else
|
||||
vim.bo[bufnr].filetype = "objcpp"
|
||||
vim.bo[bufnr].filetype = 'objcpp'
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
if vim.g.c_syntax_for_h then
|
||||
vim.bo[bufnr].filetype = "c"
|
||||
vim.bo[bufnr].filetype = 'c'
|
||||
elseif vim.g.ch_syntax_for_h then
|
||||
vim.bo[bufnr].filetype = "ch"
|
||||
vim.bo[bufnr].filetype = 'ch'
|
||||
else
|
||||
vim.bo[bufnr].filetype = "cpp"
|
||||
vim.bo[bufnr].filetype = 'cpp'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -176,22 +176,22 @@ function M.idl(bufnr)
|
||||
-- Always ignore case
|
||||
line = line:lower()
|
||||
if findany(line, { '^%s*import%s+"unknwn"%.idl', '^%s*import%s+"objidl"%.idl' }) then
|
||||
vim.bo[bufnr].filetype = "msidl"
|
||||
vim.bo[bufnr].filetype = 'msidl'
|
||||
return
|
||||
end
|
||||
end
|
||||
vim.bo[bufnr].filetype = "idl"
|
||||
vim.bo[bufnr].filetype = 'idl'
|
||||
end
|
||||
|
||||
function M.inc(path, bufnr) end
|
||||
|
||||
function M.inp(bufnr)
|
||||
if getlines(bufnr, 1):find("^%*") then
|
||||
vim.bo[bufnr].filetype = "abaqus"
|
||||
if getlines(bufnr, 1):find('^%*') then
|
||||
vim.bo[bufnr].filetype = 'abaqus'
|
||||
else
|
||||
for _, line in ipairs(getlines(bufnr, 1, 500)) do
|
||||
if line:lower():find("^header surface data") then
|
||||
vim.bo[bufnr].filetype = "trasys"
|
||||
if line:lower():find('^header surface data') then
|
||||
vim.bo[bufnr].filetype = 'trasys'
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -208,32 +208,32 @@ function M.m(path, bufnr) end
|
||||
-- MS message text files use ';', Sendmail files use '#' or 'dnl'
|
||||
function M.mc(bufnr)
|
||||
for _, line in ipairs(getlines(bufnr, 1, 20)) do
|
||||
if findany(line:lower(), { "^%s*#", "^%s*dnl" }) then
|
||||
if findany(line:lower(), { '^%s*#', '^%s*dnl' }) then
|
||||
-- Sendmail .mc file
|
||||
vim.bo[bufnr].filetype = "m4"
|
||||
vim.bo[bufnr].filetype = 'm4'
|
||||
return
|
||||
elseif line:find("^%s*;") then
|
||||
vim.bo[bufnr].filetype = "msmessages"
|
||||
elseif line:find('^%s*;') then
|
||||
vim.bo[bufnr].filetype = 'msmessages'
|
||||
return
|
||||
end
|
||||
end
|
||||
-- Default: Sendmail .mc file
|
||||
vim.bo[bufnr].filetype = "m4"
|
||||
vim.bo[bufnr].filetype = 'm4'
|
||||
end
|
||||
|
||||
function M.mm(path, bufnr) end
|
||||
|
||||
function M.mms(bufnr)
|
||||
for _, line in ipairs(getlines(bufnr, 1, 20)) do
|
||||
if findany(line, { "^%s*%%", "^%s*//", "^%*" }) then
|
||||
vim.bo[bufnr].filetype = "mmix"
|
||||
if findany(line, { '^%s*%%', '^%s*//', '^%*' }) then
|
||||
vim.bo[bufnr].filetype = 'mmix'
|
||||
return
|
||||
elseif line:find("^%s*#") then
|
||||
vim.bo[bufnr].filetype = "make"
|
||||
elseif line:find('^%s*#') then
|
||||
vim.bo[bufnr].filetype = 'make'
|
||||
return
|
||||
end
|
||||
end
|
||||
vim.bo[bufnr].filetype = "mmix"
|
||||
vim.bo[bufnr].filetype = 'mmix'
|
||||
end
|
||||
|
||||
function M.mod(path, bufnr) end
|
||||
@@ -242,8 +242,8 @@ function M.mod(path, bufnr) end
|
||||
-- that case it is probably an nroff file: 'filetype' is set and 1 is returned.
|
||||
function M.nroff(bufnr)
|
||||
for _, line in ipairs(getlines(bufnr, 1, 5)) do
|
||||
if line:find("^%.") then
|
||||
vim.bo[bufnr].filetype = "nroff"
|
||||
if line:find('^%.') then
|
||||
vim.bo[bufnr].filetype = 'nroff'
|
||||
return 1
|
||||
end
|
||||
end
|
||||
@@ -264,10 +264,10 @@ function M.progress_cweb(bufnr)
|
||||
if vim.g.filetype_w then
|
||||
vim.bo[bufnr].filetype = vim.g.filetype_w
|
||||
else
|
||||
if getlines(bufnr, 1):find("^&ANALYZE") or getlines(bufnr, 3):find("^&GLOBAL%-DEFINE") then
|
||||
vim.bo[bufnr].filetype = "progress"
|
||||
if getlines(bufnr, 1):find('^&ANALYZE') or getlines(bufnr, 3):find('^&GLOBAL%-DEFINE') then
|
||||
vim.bo[bufnr].filetype = 'progress'
|
||||
else
|
||||
vim.bo[bufnr].filetype = "cweb"
|
||||
vim.bo[bufnr].filetype = 'cweb'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -280,20 +280,20 @@ function M.r(bufnr)
|
||||
local lines = getlines(bufnr, 1, 50)
|
||||
-- TODO: \< / \> which match the beginning / end of a word
|
||||
-- Rebol is easy to recognize, check for that first
|
||||
if table.concat(lines):lower():find("rebol") then
|
||||
vim.bo[bufnr].filetype = "rebol"
|
||||
if table.concat(lines):lower():find('rebol') then
|
||||
vim.bo[bufnr].filetype = 'rebol'
|
||||
return
|
||||
end
|
||||
|
||||
for _, line in ipairs(lines) do
|
||||
-- R has # comments
|
||||
if line:find("^%s*#") then
|
||||
vim.bo[bufnr].filetype = "r"
|
||||
if line:find('^%s*#') then
|
||||
vim.bo[bufnr].filetype = 'r'
|
||||
return
|
||||
end
|
||||
-- Rexx has /* comments */
|
||||
if line:find("^%s*/%*") then
|
||||
vim.bo[bufnr].filetype = "rexx"
|
||||
if line:find('^%s*/%*') then
|
||||
vim.bo[bufnr].filetype = 'rexx'
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -303,14 +303,14 @@ function M.r(bufnr)
|
||||
vim.bo[bufnr].filetype = vim.g.filetype_r
|
||||
else
|
||||
-- Rexx used to be the default, but R appears to be much more popular.
|
||||
vim.bo[bufnr].filetype = "r"
|
||||
vim.bo[bufnr].filetype = 'r'
|
||||
end
|
||||
end
|
||||
|
||||
function M.redif(bufnr)
|
||||
for _, line in ipairs(getlines(bufnr, 1, 5)) do
|
||||
if line:lower():find("^template%-type:") then
|
||||
vim.bo[bufnr].filetype = "redif"
|
||||
if line:lower():find('^template%-type:') then
|
||||
vim.bo[bufnr].filetype = 'redif'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -321,24 +321,29 @@ function M.rules(path, bufnr) end
|
||||
-- detection between scala and SuperCollider
|
||||
function M.sc(bufnr)
|
||||
for _, line in ipairs(getlines(bufnr, 1, 25)) do
|
||||
if findany(line, { "[A-Za-z0-9]*%s:%s[A-Za-z0-9]", "var%s<", "classvar%s<", "%^this.*", "|%w*|", "%+%s%w*%s{", "%*ar%s" }) then
|
||||
vim.bo[bufnr].filetype = "supercollider"
|
||||
if
|
||||
findany(
|
||||
line,
|
||||
{ '[A-Za-z0-9]*%s:%s[A-Za-z0-9]', 'var%s<', 'classvar%s<', '%^this.*', '|%w*|', '%+%s%w*%s{', '%*ar%s' }
|
||||
)
|
||||
then
|
||||
vim.bo[bufnr].filetype = 'supercollider'
|
||||
return
|
||||
end
|
||||
end
|
||||
vim.bo[bufnr].filetype = "scala"
|
||||
vim.bo[bufnr].filetype = 'scala'
|
||||
end
|
||||
|
||||
-- This function checks the first line of file extension "scd" to resolve
|
||||
-- detection between scdoc and SuperCollider
|
||||
function M.scd(bufnr)
|
||||
local first = "^%S+%(%d[0-9A-Za-z]*%)"
|
||||
local first = '^%S+%(%d[0-9A-Za-z]*%)'
|
||||
local opt = [[%s+"[^"]*"]]
|
||||
local line = getlines(bufnr, 1)
|
||||
if findany(line, { first .. "$", first .. opt .. "$", first .. opt .. opt .. "$" }) then
|
||||
vim.bo[bufnr].filetype = "scdoc"
|
||||
if findany(line, { first .. '$', first .. opt .. '$', first .. opt .. opt .. '$' }) then
|
||||
vim.bo[bufnr].filetype = 'scdoc'
|
||||
else
|
||||
vim.bo[bufnr].filetype = "supercollider"
|
||||
vim.bo[bufnr].filetype = 'supercollider'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -350,7 +355,7 @@ function M.sql(bufnr)
|
||||
if vim.g.filetype_sql then
|
||||
vim.bo[bufnr].filetype = vim.g.filetype_sql
|
||||
else
|
||||
vim.bo[bufnr].filetype = "sql"
|
||||
vim.bo[bufnr].filetype = 'sql'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -365,46 +370,46 @@ function M.tf(bufnr)
|
||||
for _, line in ipairs(getlines(bufnr, 1, -1)) do
|
||||
-- Assume terraform file on a non-empty line (not whitespace-only)
|
||||
-- and when the first non-whitespace character is not a ; or /
|
||||
if not line:find("^%s*$") and not line:find("^%s*[;/]") then
|
||||
vim.bo[bufnr].filetype = "terraform"
|
||||
if not line:find('^%s*$') and not line:find('^%s*[;/]') then
|
||||
vim.bo[bufnr].filetype = 'terraform'
|
||||
return
|
||||
end
|
||||
end
|
||||
vim.bo[bufnr].filetype = "tf"
|
||||
vim.bo[bufnr].filetype = 'tf'
|
||||
end
|
||||
|
||||
function M.xml(bufnr)
|
||||
for _, line in ipairs(getlines(bufnr, 1, 100)) do
|
||||
line = line:lower()
|
||||
local is_docbook4 = line:find("<!doctype.*docbook")
|
||||
local is_docbook4 = line:find('<!doctype.*docbook')
|
||||
local is_docbook5 = line:find([[ xmlns="http://docbook.org/ns/docbook"]])
|
||||
if is_docbook4 or is_docbook5 then
|
||||
vim.b[bufnr].docbk_type = "xml"
|
||||
vim.b[bufnr].docbk_type = 'xml'
|
||||
vim.b[bufnr].docbk_ver = is_docbook4 and 4 or 5
|
||||
vim.bo[bufnr].filetype = "docbk"
|
||||
vim.bo[bufnr].filetype = 'docbk'
|
||||
return
|
||||
end
|
||||
if line:find([[xmlns:xbl="http://www.mozilla.org/xbl"]]) then
|
||||
vim.bo[bufnr].filetype = "xbl"
|
||||
vim.bo[bufnr].filetype = 'xbl'
|
||||
return
|
||||
end
|
||||
end
|
||||
vim.bo[bufnr].filetype = "xml"
|
||||
vim.bo[bufnr].filetype = 'xml'
|
||||
end
|
||||
|
||||
function M.y(bufnr)
|
||||
for _, line in ipairs(getlines(bufnr, 1, 100)) do
|
||||
if line:find("^%s*%%") then
|
||||
vim.bo[bufnr].filetype = "yacc"
|
||||
if line:find('^%s*%%') then
|
||||
vim.bo[bufnr].filetype = 'yacc'
|
||||
return
|
||||
end
|
||||
-- TODO: in the Vim regex, \> is used to match the end of the word after "class",
|
||||
-- can this be omitted?
|
||||
if findany(line, { "^%s*#", "^%class", "^%s*#%s*include" }) then
|
||||
vim.bo[bufnr].filetype = "racc"
|
||||
if findany(line, { '^%s*#', '^%class', '^%s*#%s*include' }) then
|
||||
vim.bo[bufnr].filetype = 'racc'
|
||||
end
|
||||
end
|
||||
vim.bo[bufnr].filetype = "yacc"
|
||||
vim.bo[bufnr].filetype = 'yacc'
|
||||
end
|
||||
|
||||
-- luacheck: pop
|
||||
|
@@ -14,14 +14,14 @@ function M.create(higroup, hi_info, default)
|
||||
local options = {}
|
||||
-- TODO: Add validation
|
||||
for k, v in pairs(hi_info) do
|
||||
table.insert(options, string.format("%s=%s", k, v))
|
||||
table.insert(options, string.format('%s=%s', k, v))
|
||||
end
|
||||
vim.cmd(string.format([[highlight %s %s %s]], default and "default" or "", higroup, table.concat(options, " ")))
|
||||
vim.cmd(string.format([[highlight %s %s %s]], default and 'default' or '', higroup, table.concat(options, ' ')))
|
||||
end
|
||||
|
||||
---@private
|
||||
function M.link(higroup, link_to, force)
|
||||
vim.cmd(string.format([[highlight%s link %s %s]], force and "!" or " default", higroup, link_to))
|
||||
vim.cmd(string.format([[highlight%s link %s %s]], force and '!' or ' default', higroup, link_to))
|
||||
end
|
||||
|
||||
--- Highlight range between two positions
|
||||
@@ -37,7 +37,7 @@ end
|
||||
-- - priority number indicating priority of highlight (default priorities.user)
|
||||
function M.range(bufnr, ns, higroup, start, finish, opts)
|
||||
opts = opts or {}
|
||||
local regtype = opts.regtype or "v"
|
||||
local regtype = opts.regtype or 'v'
|
||||
local inclusive = opts.inclusive or false
|
||||
local priority = opts.priority or M.priorities.user
|
||||
|
||||
@@ -63,7 +63,7 @@ function M.range(bufnr, ns, higroup, start, finish, opts)
|
||||
end
|
||||
end
|
||||
|
||||
local yank_ns = api.nvim_create_namespace("hlyank")
|
||||
local yank_ns = api.nvim_create_namespace('hlyank')
|
||||
--- Highlight the yanked region
|
||||
---
|
||||
--- use from init.vim via
|
||||
@@ -87,10 +87,10 @@ function M.on_yank(opts)
|
||||
if t == nil then
|
||||
return true
|
||||
else
|
||||
return type(t) == "table"
|
||||
return type(t) == 'table'
|
||||
end
|
||||
end,
|
||||
"a table or nil to configure options (see `:h highlight.on_yank`)",
|
||||
'a table or nil to configure options (see `:h highlight.on_yank`)',
|
||||
},
|
||||
})
|
||||
opts = opts or {}
|
||||
@@ -98,17 +98,17 @@ function M.on_yank(opts)
|
||||
local on_macro = opts.on_macro or false
|
||||
local on_visual = (opts.on_visual ~= false)
|
||||
|
||||
if not on_macro and vim.fn.reg_executing() ~= "" then
|
||||
if not on_macro and vim.fn.reg_executing() ~= '' then
|
||||
return
|
||||
end
|
||||
if event.operator ~= "y" or event.regtype == "" then
|
||||
if event.operator ~= 'y' or event.regtype == '' then
|
||||
return
|
||||
end
|
||||
if not on_visual and event.visual then
|
||||
return
|
||||
end
|
||||
|
||||
local higroup = opts.higroup or "IncSearch"
|
||||
local higroup = opts.higroup or 'IncSearch'
|
||||
local timeout = opts.timeout or 150
|
||||
|
||||
local bufnr = api.nvim_get_current_buf()
|
||||
|
@@ -1,7 +1,7 @@
|
||||
local inspect = {
|
||||
_VERSION = "inspect.lua 3.1.0",
|
||||
_URL = "http://github.com/kikito/inspect.lua",
|
||||
_DESCRIPTION = "human-readable representations of tables",
|
||||
_VERSION = 'inspect.lua 3.1.0',
|
||||
_URL = 'http://github.com/kikito/inspect.lua',
|
||||
_DESCRIPTION = 'human-readable representations of tables',
|
||||
_LICENSE = [[
|
||||
MIT LICENSE
|
||||
|
||||
@@ -30,12 +30,12 @@ local inspect = {
|
||||
|
||||
inspect.KEY = setmetatable({}, {
|
||||
__tostring = function()
|
||||
return "inspect.KEY"
|
||||
return 'inspect.KEY'
|
||||
end,
|
||||
})
|
||||
inspect.METATABLE = setmetatable({}, {
|
||||
__tostring = function()
|
||||
return "inspect.METATABLE"
|
||||
return 'inspect.METATABLE'
|
||||
end,
|
||||
})
|
||||
|
||||
@@ -61,52 +61,52 @@ end
|
||||
|
||||
-- \a => '\\a', \0 => '\\0', 31 => '\31'
|
||||
local shortControlCharEscapes = {
|
||||
["\a"] = "\\a",
|
||||
["\b"] = "\\b",
|
||||
["\f"] = "\\f",
|
||||
["\n"] = "\\n",
|
||||
["\r"] = "\\r",
|
||||
["\t"] = "\\t",
|
||||
["\v"] = "\\v",
|
||||
["\127"] = "\\127",
|
||||
['\a'] = '\\a',
|
||||
['\b'] = '\\b',
|
||||
['\f'] = '\\f',
|
||||
['\n'] = '\\n',
|
||||
['\r'] = '\\r',
|
||||
['\t'] = '\\t',
|
||||
['\v'] = '\\v',
|
||||
['\127'] = '\\127',
|
||||
}
|
||||
local longControlCharEscapes = { ["\127"] = "\127" }
|
||||
local longControlCharEscapes = { ['\127'] = '\127' }
|
||||
for i = 0, 31 do
|
||||
local ch = char(i)
|
||||
if not shortControlCharEscapes[ch] then
|
||||
shortControlCharEscapes[ch] = "\\" .. i
|
||||
longControlCharEscapes[ch] = fmt("\\%03d", i)
|
||||
shortControlCharEscapes[ch] = '\\' .. i
|
||||
longControlCharEscapes[ch] = fmt('\\%03d', i)
|
||||
end
|
||||
end
|
||||
|
||||
local function escape(str)
|
||||
return (gsub(gsub(gsub(str, "\\", "\\\\"), "(%c)%f[0-9]", longControlCharEscapes), "%c", shortControlCharEscapes))
|
||||
return (gsub(gsub(gsub(str, '\\', '\\\\'), '(%c)%f[0-9]', longControlCharEscapes), '%c', shortControlCharEscapes))
|
||||
end
|
||||
|
||||
local function isIdentifier(str)
|
||||
return type(str) == "string" and not not str:match("^[_%a][_%a%d]*$")
|
||||
return type(str) == 'string' and not not str:match('^[_%a][_%a%d]*$')
|
||||
end
|
||||
|
||||
local flr = math.floor
|
||||
local function isSequenceKey(k, sequenceLength)
|
||||
return type(k) == "number" and flr(k) == k and 1 <= k and k <= sequenceLength
|
||||
return type(k) == 'number' and flr(k) == k and 1 <= k and k <= sequenceLength
|
||||
end
|
||||
|
||||
local defaultTypeOrders = {
|
||||
["number"] = 1,
|
||||
["boolean"] = 2,
|
||||
["string"] = 3,
|
||||
["table"] = 4,
|
||||
["function"] = 5,
|
||||
["userdata"] = 6,
|
||||
["thread"] = 7,
|
||||
['number'] = 1,
|
||||
['boolean'] = 2,
|
||||
['string'] = 3,
|
||||
['table'] = 4,
|
||||
['function'] = 5,
|
||||
['userdata'] = 6,
|
||||
['thread'] = 7,
|
||||
}
|
||||
|
||||
local function sortKeys(a, b)
|
||||
local ta, tb = type(a), type(b)
|
||||
|
||||
-- strings and numbers are sorted numerically/alphabetically
|
||||
if ta == tb and (ta == "string" or ta == "number") then
|
||||
if ta == tb and (ta == 'string' or ta == 'number') then
|
||||
return a < b
|
||||
end
|
||||
|
||||
@@ -137,7 +137,7 @@ local function getKeys(t)
|
||||
end
|
||||
|
||||
local function countCycles(x, cycles)
|
||||
if type(x) == "table" then
|
||||
if type(x) == 'table' then
|
||||
if cycles[x] then
|
||||
cycles[x] = cycles[x] + 1
|
||||
else
|
||||
@@ -173,7 +173,7 @@ local function processRecursive(process, item, path, visited)
|
||||
end
|
||||
|
||||
local processed = process(item, path)
|
||||
if type(processed) == "table" then
|
||||
if type(processed) == 'table' then
|
||||
local processedCopy = {}
|
||||
visited[item] = processedCopy
|
||||
local processedKey
|
||||
@@ -186,7 +186,7 @@ local function processRecursive(process, item, path, visited)
|
||||
end
|
||||
|
||||
local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)
|
||||
if type(mt) ~= "table" then
|
||||
if type(mt) ~= 'table' then
|
||||
mt = nil
|
||||
end
|
||||
setmetatable(processedCopy, mt)
|
||||
@@ -222,27 +222,27 @@ end
|
||||
function Inspector:putValue(v)
|
||||
local buf = self.buf
|
||||
local tv = type(v)
|
||||
if tv == "string" then
|
||||
if tv == 'string' then
|
||||
puts(buf, smartQuote(escape(v)))
|
||||
elseif
|
||||
tv == "number"
|
||||
or tv == "boolean"
|
||||
or tv == "nil"
|
||||
or tv == "cdata"
|
||||
or tv == "ctype"
|
||||
tv == 'number'
|
||||
or tv == 'boolean'
|
||||
or tv == 'nil'
|
||||
or tv == 'cdata'
|
||||
or tv == 'ctype'
|
||||
or (vim and v == vim.NIL)
|
||||
then
|
||||
puts(buf, tostring(v))
|
||||
elseif tv == "table" and not self.ids[v] then
|
||||
elseif tv == 'table' and not self.ids[v] then
|
||||
local t = v
|
||||
|
||||
if t == inspect.KEY or t == inspect.METATABLE then
|
||||
puts(buf, tostring(t))
|
||||
elseif self.level >= self.depth then
|
||||
puts(buf, "{...}")
|
||||
puts(buf, '{...}')
|
||||
else
|
||||
if self.cycles[t] > 1 then
|
||||
puts(buf, fmt("<%d>", self:getId(t)))
|
||||
puts(buf, fmt('<%d>', self:getId(t)))
|
||||
end
|
||||
|
||||
local keys, keysLen, seqLen = getKeys(t)
|
||||
@@ -253,15 +253,15 @@ function Inspector:putValue(v)
|
||||
return
|
||||
end
|
||||
|
||||
puts(buf, "{")
|
||||
puts(buf, '{')
|
||||
self.level = self.level + 1
|
||||
|
||||
for i = 1, seqLen + keysLen do
|
||||
if i > 1 then
|
||||
puts(buf, ",")
|
||||
puts(buf, ',')
|
||||
end
|
||||
if i <= seqLen then
|
||||
puts(buf, " ")
|
||||
puts(buf, ' ')
|
||||
self:putValue(t[i])
|
||||
else
|
||||
local k = keys[i - seqLen]
|
||||
@@ -269,36 +269,36 @@ function Inspector:putValue(v)
|
||||
if isIdentifier(k) then
|
||||
puts(buf, k)
|
||||
else
|
||||
puts(buf, "[")
|
||||
puts(buf, '[')
|
||||
self:putValue(k)
|
||||
puts(buf, "]")
|
||||
puts(buf, ']')
|
||||
end
|
||||
puts(buf, " = ")
|
||||
puts(buf, ' = ')
|
||||
self:putValue(t[k])
|
||||
end
|
||||
end
|
||||
|
||||
if type(mt) == "table" then
|
||||
if type(mt) == 'table' then
|
||||
if seqLen + keysLen > 0 then
|
||||
puts(buf, ",")
|
||||
puts(buf, ',')
|
||||
end
|
||||
tabify(self)
|
||||
puts(buf, "<metatable> = ")
|
||||
puts(buf, '<metatable> = ')
|
||||
self:putValue(mt)
|
||||
end
|
||||
|
||||
self.level = self.level - 1
|
||||
|
||||
if keysLen > 0 or type(mt) == "table" then
|
||||
if keysLen > 0 or type(mt) == 'table' then
|
||||
tabify(self)
|
||||
elseif seqLen > 0 then
|
||||
puts(buf, " ")
|
||||
puts(buf, ' ')
|
||||
end
|
||||
|
||||
puts(buf, "}")
|
||||
puts(buf, '}')
|
||||
end
|
||||
else
|
||||
puts(buf, fmt("<%s %d>", tv, self:getId(v)))
|
||||
puts(buf, fmt('<%s %d>', tv, self:getId(v)))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -306,8 +306,8 @@ function inspect.inspect(root, options)
|
||||
options = options or {}
|
||||
|
||||
local depth = options.depth or math.huge
|
||||
local newline = options.newline or "\n"
|
||||
local indent = options.indent or " "
|
||||
local newline = options.newline or '\n'
|
||||
local indent = options.indent or ' '
|
||||
local process = options.process
|
||||
|
||||
if process then
|
||||
|
@@ -49,15 +49,15 @@ local keymap = {}
|
||||
--- Default `false`.
|
||||
---@see |nvim_set_keymap()|
|
||||
function keymap.set(mode, lhs, rhs, opts)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
mode = { mode, { 's', 't' } },
|
||||
lhs = { lhs, 's' },
|
||||
rhs = { rhs, { 's', 'f' } },
|
||||
opts = {opts, 't', true}
|
||||
}
|
||||
opts = { opts, 't', true },
|
||||
})
|
||||
|
||||
opts = vim.deepcopy(opts) or {}
|
||||
local is_rhs_luaref = type(rhs) == "function"
|
||||
local is_rhs_luaref = type(rhs) == 'function'
|
||||
mode = type(mode) == 'string' and { mode } or mode
|
||||
|
||||
if is_rhs_luaref and opts.expr then
|
||||
@@ -118,11 +118,11 @@ end
|
||||
---@see |vim.keymap.set()|
|
||||
---
|
||||
function keymap.del(modes, lhs, opts)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
mode = { modes, { 's', 't' } },
|
||||
lhs = { lhs, 's' },
|
||||
opts = {opts, 't', true}
|
||||
}
|
||||
opts = { opts, 't', true },
|
||||
})
|
||||
|
||||
opts = opts or {}
|
||||
modes = type(modes) == 'string' and { modes } or modes
|
||||
|
@@ -1,58 +1,58 @@
|
||||
local if_nil = vim.F.if_nil
|
||||
|
||||
local default_handlers = require 'vim.lsp.handlers'
|
||||
local log = require 'vim.lsp.log'
|
||||
local lsp_rpc = require 'vim.lsp.rpc'
|
||||
local protocol = require 'vim.lsp.protocol'
|
||||
local util = require 'vim.lsp.util'
|
||||
local sync = require 'vim.lsp.sync'
|
||||
local default_handlers = require('vim.lsp.handlers')
|
||||
local log = require('vim.lsp.log')
|
||||
local lsp_rpc = require('vim.lsp.rpc')
|
||||
local protocol = require('vim.lsp.protocol')
|
||||
local util = require('vim.lsp.util')
|
||||
local sync = require('vim.lsp.sync')
|
||||
|
||||
local vim = vim
|
||||
local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option
|
||||
= vim.api.nvim_err_writeln, vim.api.nvim_buf_get_lines, vim.api.nvim_command, vim.api.nvim_buf_get_option
|
||||
local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option =
|
||||
vim.api.nvim_err_writeln, vim.api.nvim_buf_get_lines, vim.api.nvim_command, vim.api.nvim_buf_get_option
|
||||
local uv = vim.loop
|
||||
local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend
|
||||
local validate = vim.validate
|
||||
|
||||
local lsp = {
|
||||
protocol = protocol;
|
||||
protocol = protocol,
|
||||
|
||||
handlers = default_handlers;
|
||||
handlers = default_handlers,
|
||||
|
||||
buf = require'vim.lsp.buf';
|
||||
diagnostic = require'vim.lsp.diagnostic';
|
||||
codelens = require'vim.lsp.codelens';
|
||||
util = util;
|
||||
buf = require('vim.lsp.buf'),
|
||||
diagnostic = require('vim.lsp.diagnostic'),
|
||||
codelens = require('vim.lsp.codelens'),
|
||||
util = util,
|
||||
|
||||
-- Allow raw RPC access.
|
||||
rpc = lsp_rpc;
|
||||
rpc = lsp_rpc,
|
||||
|
||||
-- Export these directly from rpc.
|
||||
rpc_response_error = lsp_rpc.rpc_response_error;
|
||||
rpc_response_error = lsp_rpc.rpc_response_error,
|
||||
}
|
||||
|
||||
-- maps request name to the required server_capability in the client.
|
||||
lsp._request_name_to_capability = {
|
||||
['textDocument/hover'] = { 'hoverProvider' };
|
||||
['textDocument/signatureHelp'] = { 'signatureHelpProvider' };
|
||||
['textDocument/definition'] = { 'definitionProvider' };
|
||||
['textDocument/implementation'] = { 'implementationProvider' };
|
||||
['textDocument/declaration'] = { 'declarationProvider' };
|
||||
['textDocument/typeDefinition'] = { 'typeDefinitionProvider' };
|
||||
['textDocument/documentSymbol'] = { 'documentSymbolProvider' };
|
||||
['textDocument/prepareCallHierarchy'] = { 'callHierarchyProvider' };
|
||||
['textDocument/rename'] = { 'renameProvider' };
|
||||
['textDocument/prepareRename'] = { 'renameProvider', 'prepareProvider'} ;
|
||||
['textDocument/codeAction'] = { 'codeActionProvider' };
|
||||
['textDocument/codeLens'] = { 'codeLensProvider' };
|
||||
['codeLens/resolve'] = { 'codeLensProvider', 'resolveProvider' };
|
||||
['workspace/executeCommand'] = { 'executeCommandProvider' };
|
||||
['workspace/symbol'] = { 'workspaceSymbolProvider' };
|
||||
['textDocument/references'] = { 'referencesProvider' };
|
||||
['textDocument/rangeFormatting'] = { 'documentRangeFormattingProvider' };
|
||||
['textDocument/formatting'] = { 'documentFormattingProvider' };
|
||||
['textDocument/completion'] = { 'completionProvider' };
|
||||
['textDocument/documentHighlight'] = { 'documentHighlightProvider' };
|
||||
['textDocument/hover'] = { 'hoverProvider' },
|
||||
['textDocument/signatureHelp'] = { 'signatureHelpProvider' },
|
||||
['textDocument/definition'] = { 'definitionProvider' },
|
||||
['textDocument/implementation'] = { 'implementationProvider' },
|
||||
['textDocument/declaration'] = { 'declarationProvider' },
|
||||
['textDocument/typeDefinition'] = { 'typeDefinitionProvider' },
|
||||
['textDocument/documentSymbol'] = { 'documentSymbolProvider' },
|
||||
['textDocument/prepareCallHierarchy'] = { 'callHierarchyProvider' },
|
||||
['textDocument/rename'] = { 'renameProvider' },
|
||||
['textDocument/prepareRename'] = { 'renameProvider', 'prepareProvider' },
|
||||
['textDocument/codeAction'] = { 'codeActionProvider' },
|
||||
['textDocument/codeLens'] = { 'codeLensProvider' },
|
||||
['codeLens/resolve'] = { 'codeLensProvider', 'resolveProvider' },
|
||||
['workspace/executeCommand'] = { 'executeCommandProvider' },
|
||||
['workspace/symbol'] = { 'workspaceSymbolProvider' },
|
||||
['textDocument/references'] = { 'referencesProvider' },
|
||||
['textDocument/rangeFormatting'] = { 'documentRangeFormattingProvider' },
|
||||
['textDocument/formatting'] = { 'documentFormattingProvider' },
|
||||
['textDocument/completion'] = { 'completionProvider' },
|
||||
['textDocument/documentHighlight'] = { 'documentHighlightProvider' },
|
||||
}
|
||||
|
||||
-- TODO improve handling of scratch buffers with LSP attached.
|
||||
@@ -62,8 +62,8 @@ lsp._request_name_to_capability = {
|
||||
---
|
||||
---@param {...} (List of strings) List to write to the buffer
|
||||
local function err_message(...)
|
||||
nvim_err_writeln(table.concat(vim.tbl_flatten{...}))
|
||||
nvim_command("redraw")
|
||||
nvim_err_writeln(table.concat(vim.tbl_flatten({ ... })))
|
||||
nvim_command('redraw')
|
||||
end
|
||||
|
||||
---@private
|
||||
@@ -73,7 +73,7 @@ end
|
||||
---buffer if not given.
|
||||
---@returns bufnr (number) Number of requested buffer
|
||||
local function resolve_bufnr(bufnr)
|
||||
validate { bufnr = { bufnr, 'n', true } }
|
||||
validate({ bufnr = { bufnr, 'n', true } })
|
||||
if bufnr == nil or bufnr == 0 then
|
||||
return vim.api.nvim_get_current_buf()
|
||||
end
|
||||
@@ -85,7 +85,10 @@ end
|
||||
--- supported in any of the servers registered for the current buffer.
|
||||
---@param method (string) name of the method
|
||||
function lsp._unsupported_method(method)
|
||||
local msg = string.format("method %s is not supported by any of the servers registered for the current buffer", method)
|
||||
local msg = string.format(
|
||||
'method %s is not supported by any of the servers registered for the current buffer',
|
||||
method
|
||||
)
|
||||
log.warn(msg)
|
||||
return msg
|
||||
end
|
||||
@@ -96,23 +99,29 @@ end
|
||||
---@param filename (string) path to check
|
||||
---@returns true if {filename} exists and is a directory, false otherwise
|
||||
local function is_dir(filename)
|
||||
validate{filename={filename,'s'}}
|
||||
validate({ filename = { filename, 's' } })
|
||||
local stat = uv.fs_stat(filename)
|
||||
return stat and stat.type == 'directory' or false
|
||||
end
|
||||
|
||||
local wait_result_reason = { [-1] = "timeout"; [-2] = "interrupted"; [-3] = "error" }
|
||||
local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'error' }
|
||||
|
||||
local valid_encodings = {
|
||||
["utf-8"] = 'utf-8'; ["utf-16"] = 'utf-16'; ["utf-32"] = 'utf-32';
|
||||
["utf8"] = 'utf-8'; ["utf16"] = 'utf-16'; ["utf32"] = 'utf-32';
|
||||
UTF8 = 'utf-8'; UTF16 = 'utf-16'; UTF32 = 'utf-32';
|
||||
['utf-8'] = 'utf-8',
|
||||
['utf-16'] = 'utf-16',
|
||||
['utf-32'] = 'utf-32',
|
||||
['utf8'] = 'utf-8',
|
||||
['utf16'] = 'utf-16',
|
||||
['utf32'] = 'utf-32',
|
||||
UTF8 = 'utf-8',
|
||||
UTF16 = 'utf-16',
|
||||
UTF32 = 'utf-32',
|
||||
}
|
||||
|
||||
local format_line_ending = {
|
||||
["unix"] = '\n',
|
||||
["dos"] = '\r\n',
|
||||
["mac"] = '\r',
|
||||
['unix'] = '\n',
|
||||
['dos'] = '\r\n',
|
||||
['mac'] = '\r',
|
||||
}
|
||||
|
||||
---@private
|
||||
@@ -138,10 +147,10 @@ local uninitialized_clients = {}
|
||||
|
||||
---@private
|
||||
local function for_each_buffer_client(bufnr, fn, restrict_client_ids)
|
||||
validate {
|
||||
fn = { fn, 'f' };
|
||||
restrict_client_ids = { restrict_client_ids, 't' , true};
|
||||
}
|
||||
validate({
|
||||
fn = { fn, 'f' },
|
||||
restrict_client_ids = { restrict_client_ids, 't', true },
|
||||
})
|
||||
bufnr = resolve_bufnr(bufnr)
|
||||
local client_ids = all_buffer_active_clients[bufnr]
|
||||
if not client_ids or tbl_isempty(client_ids) then
|
||||
@@ -169,9 +178,13 @@ end
|
||||
-- Error codes to be used with `on_error` from |vim.lsp.start_client|.
|
||||
-- Can be used to look up the string from a the number or the number
|
||||
-- from the string.
|
||||
lsp.client_errors = tbl_extend("error", lsp_rpc.client_errors, vim.tbl_add_reverse_lookup {
|
||||
ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1;
|
||||
lsp.client_errors = tbl_extend(
|
||||
'error',
|
||||
lsp_rpc.client_errors,
|
||||
vim.tbl_add_reverse_lookup({
|
||||
ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1,
|
||||
})
|
||||
)
|
||||
|
||||
---@private
|
||||
--- Normalizes {encoding} to valid LSP encoding names.
|
||||
@@ -179,9 +192,9 @@ lsp.client_errors = tbl_extend("error", lsp_rpc.client_errors, vim.tbl_add_rever
|
||||
---@param encoding (string) Encoding to normalize
|
||||
---@returns (string) normalized encoding name
|
||||
local function validate_encoding(encoding)
|
||||
validate {
|
||||
encoding = { encoding, 's' };
|
||||
}
|
||||
validate({
|
||||
encoding = { encoding, 's' },
|
||||
})
|
||||
return valid_encodings[encoding:lower()]
|
||||
or error(string.format("Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding))
|
||||
end
|
||||
@@ -194,16 +207,19 @@ end
|
||||
---@returns (string) the command
|
||||
---@returns (list of strings) its arguments
|
||||
function lsp._cmd_parts(input)
|
||||
vim.validate{cmd={
|
||||
vim.validate({ cmd = {
|
||||
input,
|
||||
function() return vim.tbl_islist(input) end,
|
||||
"list"}}
|
||||
function()
|
||||
return vim.tbl_islist(input)
|
||||
end,
|
||||
'list',
|
||||
} })
|
||||
|
||||
local cmd = input[1]
|
||||
local cmd_args = {}
|
||||
-- Don't mutate our input.
|
||||
for i, v in ipairs(input) do
|
||||
vim.validate{["cmd argument"]={v, "s"}}
|
||||
vim.validate({ ['cmd argument'] = { v, 's' } })
|
||||
if i > 1 then
|
||||
table.insert(cmd_args, v)
|
||||
end
|
||||
@@ -233,31 +249,29 @@ end
|
||||
---
|
||||
---@see |vim.lsp.start_client()|
|
||||
local function validate_client_config(config)
|
||||
validate {
|
||||
config = { config, 't' };
|
||||
}
|
||||
validate {
|
||||
handlers = { config.handlers, "t", true };
|
||||
capabilities = { config.capabilities, "t", true };
|
||||
cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" };
|
||||
cmd_env = { config.cmd_env, "t", true };
|
||||
detached = { config.detached, "b", true };
|
||||
name = { config.name, 's', true };
|
||||
on_error = { config.on_error, "f", true };
|
||||
on_exit = { config.on_exit, "f", true };
|
||||
on_init = { config.on_init, "f", true };
|
||||
settings = { config.settings, "t", true };
|
||||
commands = { config.commands, 't', true };
|
||||
before_init = { config.before_init, "f", true };
|
||||
offset_encoding = { config.offset_encoding, "s", true };
|
||||
flags = { config.flags, "t", true };
|
||||
get_language_id = { config.get_language_id, "f", true };
|
||||
}
|
||||
validate({
|
||||
config = { config, 't' },
|
||||
})
|
||||
validate({
|
||||
handlers = { config.handlers, 't', true },
|
||||
capabilities = { config.capabilities, 't', true },
|
||||
cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), 'directory' },
|
||||
cmd_env = { config.cmd_env, 't', true },
|
||||
detached = { config.detached, 'b', true },
|
||||
name = { config.name, 's', true },
|
||||
on_error = { config.on_error, 'f', true },
|
||||
on_exit = { config.on_exit, 'f', true },
|
||||
on_init = { config.on_init, 'f', true },
|
||||
settings = { config.settings, 't', true },
|
||||
commands = { config.commands, 't', true },
|
||||
before_init = { config.before_init, 'f', true },
|
||||
offset_encoding = { config.offset_encoding, 's', true },
|
||||
flags = { config.flags, 't', true },
|
||||
get_language_id = { config.get_language_id, 'f', true },
|
||||
})
|
||||
assert(
|
||||
(not config.flags
|
||||
or not config.flags.debounce_text_changes
|
||||
or type(config.flags.debounce_text_changes) == 'number'),
|
||||
"flags.debounce_text_changes must be a number with the debounce time in milliseconds"
|
||||
(not config.flags or not config.flags.debounce_text_changes or type(config.flags.debounce_text_changes) == 'number'),
|
||||
'flags.debounce_text_changes must be a number with the debounce time in milliseconds'
|
||||
)
|
||||
|
||||
local cmd, cmd_args = lsp._cmd_parts(config.cmd)
|
||||
@@ -267,9 +281,9 @@ local function validate_client_config(config)
|
||||
end
|
||||
|
||||
return {
|
||||
cmd = cmd;
|
||||
cmd_args = cmd_args;
|
||||
offset_encoding = offset_encoding;
|
||||
cmd = cmd,
|
||||
cmd_args = cmd_args,
|
||||
offset_encoding = offset_encoding,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -329,14 +343,15 @@ do
|
||||
function changetracking.init(client, bufnr)
|
||||
local use_incremental_sync = (
|
||||
if_nil(client.config.flags.allow_incremental_sync, true)
|
||||
and vim.tbl_get(client.server_capabilities, "textDocumentSync", "change") == protocol.TextDocumentSyncKind.Incremental
|
||||
and vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change')
|
||||
== protocol.TextDocumentSyncKind.Incremental
|
||||
)
|
||||
local state = state_by_client[client.id]
|
||||
if not state then
|
||||
state = {
|
||||
buffers = {};
|
||||
buffers = {},
|
||||
debounce = client.config.flags.debounce_text_changes or 150,
|
||||
use_incremental_sync = use_incremental_sync;
|
||||
use_incremental_sync = use_incremental_sync,
|
||||
}
|
||||
state_by_client[client.id] = state
|
||||
end
|
||||
@@ -405,7 +420,6 @@ do
|
||||
---@private
|
||||
function changetracking.prepare(bufnr, firstline, lastline, new_lastline)
|
||||
local incremental_changes = function(client, buf_state)
|
||||
|
||||
local prev_lines = buf_state.lines
|
||||
local curr_lines = buf_state.lines_tmp
|
||||
|
||||
@@ -426,7 +440,14 @@ do
|
||||
|
||||
local line_ending = buf_get_line_ending(bufnr)
|
||||
local incremental_change = sync.compute_diff(
|
||||
buf_state.lines, curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending)
|
||||
buf_state.lines,
|
||||
curr_lines,
|
||||
firstline,
|
||||
lastline,
|
||||
new_lastline,
|
||||
client.offset_encoding or 'utf-16',
|
||||
line_ending
|
||||
)
|
||||
|
||||
-- Double-buffering of lines tables is used to reduce the load on the garbage collector.
|
||||
-- At this point the prev_lines table is useless, but its internal storage has already been allocated,
|
||||
@@ -443,12 +464,14 @@ do
|
||||
end
|
||||
local full_changes = once(function()
|
||||
return {
|
||||
text = buf_get_full_text(bufnr);
|
||||
};
|
||||
text = buf_get_full_text(bufnr),
|
||||
}
|
||||
end)
|
||||
local uri = vim.uri_from_bufnr(bufnr)
|
||||
return function(client)
|
||||
if vim.tbl_get(client.server_capabilities, "textDocumentSync", "change") == protocol.TextDocumentSyncKind.None then
|
||||
if
|
||||
vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change') == protocol.TextDocumentSyncKind.None
|
||||
then
|
||||
return
|
||||
end
|
||||
local state = state_by_client[client.id]
|
||||
@@ -467,7 +490,7 @@ do
|
||||
return
|
||||
end
|
||||
local changes = state.use_incremental_sync and buf_state.pending_changes or { full_changes() }
|
||||
client.notify("textDocument/didChange", {
|
||||
client.notify('textDocument/didChange', {
|
||||
textDocument = {
|
||||
uri = uri,
|
||||
version = util.buf_versions[bufnr],
|
||||
@@ -519,7 +542,6 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@private
|
||||
--- Default handler for the 'textDocument/didOpen' LSP notification.
|
||||
---
|
||||
@@ -527,7 +549,7 @@ end
|
||||
---@param client Client object
|
||||
local function text_document_did_open_handler(bufnr, client)
|
||||
changetracking.init(client, bufnr)
|
||||
if not vim.tbl_get(client.server_capabilities, "textDocumentSync", "openClose") then
|
||||
if not vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
|
||||
return
|
||||
end
|
||||
if not vim.api.nvim_buf_is_loaded(bufnr) then
|
||||
@@ -537,11 +559,11 @@ local function text_document_did_open_handler(bufnr, client)
|
||||
|
||||
local params = {
|
||||
textDocument = {
|
||||
version = 0;
|
||||
uri = vim.uri_from_bufnr(bufnr);
|
||||
languageId = client.config.get_language_id(bufnr, filetype);
|
||||
text = buf_get_full_text(bufnr);
|
||||
}
|
||||
version = 0,
|
||||
uri = vim.uri_from_bufnr(bufnr),
|
||||
languageId = client.config.get_language_id(bufnr, filetype),
|
||||
text = buf_get_full_text(bufnr),
|
||||
},
|
||||
}
|
||||
client.notify('textDocument/didOpen', params)
|
||||
util.buf_versions[bufnr] = params.textDocument.version
|
||||
@@ -763,13 +785,15 @@ function lsp.start_client(config)
|
||||
-- By default, get_language_id just returns the exact filetype it is passed.
|
||||
-- It is possible to pass in something that will calculate a different filetype,
|
||||
-- to be sent by the client.
|
||||
config.get_language_id = config.get_language_id or function(_, filetype) return filetype end
|
||||
config.get_language_id = config.get_language_id or function(_, filetype)
|
||||
return filetype
|
||||
end
|
||||
|
||||
local client_id = next_client_id()
|
||||
|
||||
local handlers = config.handlers or {}
|
||||
local name = config.name or tostring(client_id)
|
||||
local log_prefix = string.format("LSP[%s]", name)
|
||||
local log_prefix = string.format('LSP[%s]', name)
|
||||
|
||||
local dispatch = {}
|
||||
|
||||
@@ -807,10 +831,10 @@ function lsp.start_client(config)
|
||||
local _ = log.trace() and log.trace('server_request', method, params)
|
||||
local handler = resolve_handler(method)
|
||||
if handler then
|
||||
local _ = log.trace() and log.trace("server_request: found handler for", method)
|
||||
local _ = log.trace() and log.trace('server_request: found handler for', method)
|
||||
return handler(nil, params, { method = method, client_id = client_id })
|
||||
end
|
||||
local _ = log.warn() and log.warn("server_request: no handler found for", method)
|
||||
local _ = log.warn() and log.warn('server_request: no handler found for', method)
|
||||
return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
|
||||
end
|
||||
|
||||
@@ -822,12 +846,12 @@ function lsp.start_client(config)
|
||||
---@see |vim.lsp.rpc.client_errors| for possible errors. Use
|
||||
---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name.
|
||||
function dispatch.on_error(code, err)
|
||||
local _ = log.error() and log.error(log_prefix, "on_error", { code = lsp.client_errors[code], err = err })
|
||||
local _ = log.error() and log.error(log_prefix, 'on_error', { code = lsp.client_errors[code], err = err })
|
||||
err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
|
||||
if config.on_error then
|
||||
local status, usererr = pcall(config.on_error, code, err)
|
||||
if not status then
|
||||
local _ = log.error() and log.error(log_prefix, "user on_error failed", { err = usererr })
|
||||
local _ = log.error() and log.error(log_prefix, 'user on_error failed', { err = usererr })
|
||||
err_message(log_prefix, ' user on_error failed: ', tostring(usererr))
|
||||
end
|
||||
end
|
||||
@@ -853,7 +877,7 @@ function lsp.start_client(config)
|
||||
end
|
||||
|
||||
if code ~= 0 or (signal ~= 0 and signal ~= 15) then
|
||||
local msg = string.format("Client %s quit with exit code %s and signal %s", client_id, code, signal)
|
||||
local msg = string.format('Client %s quit with exit code %s and signal %s', client_id, code, signal)
|
||||
vim.schedule(function()
|
||||
vim.notify(msg, vim.log.levels.WARN)
|
||||
end)
|
||||
@@ -862,38 +886,41 @@ function lsp.start_client(config)
|
||||
|
||||
-- Start the RPC client.
|
||||
local rpc = lsp_rpc.start(cmd, cmd_args, dispatch, {
|
||||
cwd = config.cmd_cwd;
|
||||
env = config.cmd_env;
|
||||
detached = config.detached;
|
||||
cwd = config.cmd_cwd,
|
||||
env = config.cmd_env,
|
||||
detached = config.detached,
|
||||
})
|
||||
|
||||
-- Return nil if client fails to start
|
||||
if not rpc then return end
|
||||
if not rpc then
|
||||
return
|
||||
end
|
||||
|
||||
local client = {
|
||||
id = client_id;
|
||||
name = name;
|
||||
rpc = rpc;
|
||||
offset_encoding = offset_encoding;
|
||||
config = config;
|
||||
attached_buffers = {};
|
||||
id = client_id,
|
||||
name = name,
|
||||
rpc = rpc,
|
||||
offset_encoding = offset_encoding,
|
||||
config = config,
|
||||
attached_buffers = {},
|
||||
|
||||
handlers = handlers;
|
||||
commands = config.commands or {};
|
||||
handlers = handlers,
|
||||
commands = config.commands or {},
|
||||
|
||||
requests = {};
|
||||
requests = {},
|
||||
-- for $/progress report
|
||||
messages = { name = name, messages = {}, progress = {}, status = {} };
|
||||
messages = { name = name, messages = {}, progress = {}, status = {} },
|
||||
}
|
||||
|
||||
|
||||
-- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.
|
||||
uninitialized_clients[client_id] = client;
|
||||
uninitialized_clients[client_id] = client
|
||||
|
||||
---@private
|
||||
local function initialize()
|
||||
local valid_traces = {
|
||||
off = 'off'; messages = 'messages'; verbose = 'verbose';
|
||||
off = 'off',
|
||||
messages = 'messages',
|
||||
verbose = 'verbose',
|
||||
}
|
||||
local version = vim.version()
|
||||
|
||||
@@ -902,10 +929,12 @@ function lsp.start_client(config)
|
||||
local root_path
|
||||
if config.workspace_folders or config.root_dir then
|
||||
if config.root_dir and not config.workspace_folders then
|
||||
workspace_folders = {{
|
||||
uri = vim.uri_from_fname(config.root_dir);
|
||||
name = string.format("%s", config.root_dir);
|
||||
}};
|
||||
workspace_folders = {
|
||||
{
|
||||
uri = vim.uri_from_fname(config.root_dir),
|
||||
name = string.format('%s', config.root_dir),
|
||||
},
|
||||
}
|
||||
else
|
||||
workspace_folders = config.workspace_folders
|
||||
end
|
||||
@@ -922,41 +951,41 @@ function lsp.start_client(config)
|
||||
-- the process has not been started by another process. If the parent
|
||||
-- process is not alive then the server should exit (see exit notification)
|
||||
-- its process.
|
||||
processId = uv.getpid();
|
||||
processId = uv.getpid(),
|
||||
-- Information about the client
|
||||
-- since 3.15.0
|
||||
clientInfo = {
|
||||
name = "Neovim",
|
||||
version = string.format("%s.%s.%s", version.major, version.minor, version.patch)
|
||||
};
|
||||
name = 'Neovim',
|
||||
version = string.format('%s.%s.%s', version.major, version.minor, version.patch),
|
||||
},
|
||||
-- The rootPath of the workspace. Is null if no folder is open.
|
||||
--
|
||||
-- @deprecated in favour of rootUri.
|
||||
rootPath = root_path or vim.NIL;
|
||||
rootPath = root_path or vim.NIL,
|
||||
-- The rootUri of the workspace. Is null if no folder is open. If both
|
||||
-- `rootPath` and `rootUri` are set `rootUri` wins.
|
||||
rootUri = root_uri or vim.NIL;
|
||||
rootUri = root_uri or vim.NIL,
|
||||
-- The workspace folders configured in the client when the server starts.
|
||||
-- This property is only available if the client supports workspace folders.
|
||||
-- It can be `null` if the client supports workspace folders but none are
|
||||
-- configured.
|
||||
workspaceFolders = workspace_folders or vim.NIL;
|
||||
workspaceFolders = workspace_folders or vim.NIL,
|
||||
-- User provided initialization options.
|
||||
initializationOptions = config.init_options;
|
||||
initializationOptions = config.init_options,
|
||||
-- The capabilities provided by the client (editor or tool)
|
||||
capabilities = config.capabilities or protocol.make_client_capabilities();
|
||||
capabilities = config.capabilities or protocol.make_client_capabilities(),
|
||||
-- The initial trace setting. If omitted trace is disabled ("off").
|
||||
-- trace = "off" | "messages" | "verbose";
|
||||
trace = valid_traces[config.trace] or 'off';
|
||||
trace = valid_traces[config.trace] or 'off',
|
||||
}
|
||||
if config.before_init then
|
||||
-- TODO(ashkan) handle errors here.
|
||||
pcall(config.before_init, initialize_params, config)
|
||||
end
|
||||
local _ = log.trace() and log.trace(log_prefix, "initialize_params", initialize_params)
|
||||
local _ = log.trace() and log.trace(log_prefix, 'initialize_params', initialize_params)
|
||||
rpc.request('initialize', initialize_params, function(init_err, result)
|
||||
assert(not init_err, tostring(init_err))
|
||||
assert(result, "server sent empty result")
|
||||
assert(result, 'server sent empty result')
|
||||
rpc.notify('initialized', vim.empty_dict())
|
||||
client.initialized = true
|
||||
uninitialized_clients[client_id] = nil
|
||||
@@ -973,10 +1002,13 @@ function lsp.start_client(config)
|
||||
local mt = {}
|
||||
mt.__index = function(table, key)
|
||||
if key == 'resolved_capabilities' then
|
||||
vim.notify_once("[LSP] Accessing client.resolved_capabilities is deprecated, " ..
|
||||
"update your plugins or configuration to access client.server_capabilities instead." ..
|
||||
"The new key/value pairs in server_capabilities directly match those " ..
|
||||
"defined in the language server protocol", vim.log.levels.WARN)
|
||||
vim.notify_once(
|
||||
'[LSP] Accessing client.resolved_capabilities is deprecated, '
|
||||
.. 'update your plugins or configuration to access client.server_capabilities instead.'
|
||||
.. 'The new key/value pairs in server_capabilities directly match those '
|
||||
.. 'defined in the language server protocol',
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
rawset(table, key, protocol._resolve_capabilities_compat(client.server_capabilities))
|
||||
return rawget(table, key)
|
||||
else
|
||||
@@ -1004,7 +1036,8 @@ function lsp.start_client(config)
|
||||
pcall(handlers.on_error, lsp.client_errors.ON_INIT_CALLBACK_ERROR, err)
|
||||
end
|
||||
end
|
||||
local _ = log.info() and log.info(log_prefix, "server_capabilities", { server_capabilities = client.server_capabilities })
|
||||
local _ = log.info()
|
||||
and log.info(log_prefix, 'server_capabilities', { server_capabilities = client.server_capabilities })
|
||||
|
||||
-- Only assign after initialized.
|
||||
active_clients[client_id] = client
|
||||
@@ -1039,22 +1072,22 @@ function lsp.start_client(config)
|
||||
function client.request(method, params, handler, bufnr)
|
||||
if not handler then
|
||||
handler = resolve_handler(method)
|
||||
or error(string.format("not found: %q request handler for client %q.", method, client.name))
|
||||
or error(string.format('not found: %q request handler for client %q.', method, client.name))
|
||||
end
|
||||
-- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
|
||||
changetracking.flush(client, bufnr)
|
||||
bufnr = resolve_bufnr(bufnr)
|
||||
local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr)
|
||||
local _ = log.debug() and log.debug(log_prefix, 'client.request', client_id, method, params, handler, bufnr)
|
||||
local success, request_id = rpc.request(method, params, function(err, result)
|
||||
handler(err, result, { method = method, client_id = client_id, bufnr = bufnr, params = params })
|
||||
end, function(request_id)
|
||||
client.requests[request_id] = nil
|
||||
nvim_command("doautocmd <nomodeline> User LspRequest")
|
||||
nvim_command('doautocmd <nomodeline> User LspRequest')
|
||||
end)
|
||||
|
||||
if success then
|
||||
client.requests[request_id] = { type = 'pending', bufnr = bufnr, method = method }
|
||||
nvim_command("doautocmd <nomodeline> User LspRequest")
|
||||
nvim_command('doautocmd <nomodeline> User LspRequest')
|
||||
end
|
||||
|
||||
return success, request_id
|
||||
@@ -1081,9 +1114,10 @@ function lsp.start_client(config)
|
||||
request_result = { err = err, result = result }
|
||||
end
|
||||
|
||||
local success, request_id = client.request(method, params, _sync_handler,
|
||||
bufnr)
|
||||
if not success then return nil end
|
||||
local success, request_id = client.request(method, params, _sync_handler, bufnr)
|
||||
if not success then
|
||||
return nil
|
||||
end
|
||||
|
||||
local wait_result, reason = vim.wait(timeout_ms or 1000, function()
|
||||
return request_result ~= nil
|
||||
@@ -1118,13 +1152,13 @@ function lsp.start_client(config)
|
||||
---@returns true if any client returns true; false otherwise
|
||||
---@see |vim.lsp.client.notify()|
|
||||
function client.cancel_request(id)
|
||||
validate{id = {id, 'n'}}
|
||||
validate({ id = { id, 'n' } })
|
||||
local request = client.requests[id]
|
||||
if request and request.type == 'pending' then
|
||||
request.type = 'cancel'
|
||||
nvim_command("doautocmd <nomodeline> User LspRequest")
|
||||
nvim_command('doautocmd <nomodeline> User LspRequest')
|
||||
end
|
||||
return rpc.notify("$/cancelRequest", { id = id })
|
||||
return rpc.notify('$/cancelRequest', { id = id })
|
||||
end
|
||||
|
||||
-- Track this so that we can escalate automatically if we've already tried a
|
||||
@@ -1139,7 +1173,6 @@ function lsp.start_client(config)
|
||||
---
|
||||
---@param force (bool, optional)
|
||||
function client.stop(force)
|
||||
|
||||
lsp.diagnostic.reset(client_id, all_buffer_active_clients)
|
||||
changetracking.reset(client_id)
|
||||
for _, client_ids in pairs(all_buffer_active_clients) do
|
||||
@@ -1150,7 +1183,7 @@ function lsp.start_client(config)
|
||||
if handle:is_closing() then
|
||||
return
|
||||
end
|
||||
if force or (not client.initialized) or graceful_shutdown_failed then
|
||||
if force or not client.initialized or graceful_shutdown_failed then
|
||||
handle:kill(15)
|
||||
return
|
||||
end
|
||||
@@ -1198,7 +1231,6 @@ end
|
||||
local text_document_did_change_handler
|
||||
do
|
||||
text_document_did_change_handler = function(_, bufnr, changedtick, firstline, lastline, new_lastline)
|
||||
|
||||
-- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached
|
||||
if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
|
||||
return true
|
||||
@@ -1215,17 +1247,17 @@ function lsp._text_document_did_save_handler(bufnr)
|
||||
local uri = vim.uri_from_bufnr(bufnr)
|
||||
local text = once(buf_get_full_text)
|
||||
for_each_buffer_client(bufnr, function(client)
|
||||
local save_capability = vim.tbl_get(client.server_capabilities, "textDocumentSync", "save")
|
||||
local save_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'save')
|
||||
if save_capability then
|
||||
local included_text
|
||||
if type(save_capability) == "table" and save_capability.includeText then
|
||||
if type(save_capability) == 'table' and save_capability.includeText then
|
||||
included_text = text(bufnr)
|
||||
end
|
||||
client.notify('textDocument/didSave', {
|
||||
textDocument = {
|
||||
uri = uri;
|
||||
};
|
||||
text = included_text;
|
||||
uri = uri,
|
||||
},
|
||||
text = included_text,
|
||||
})
|
||||
end
|
||||
end)
|
||||
@@ -1239,15 +1271,13 @@ end
|
||||
---@param bufnr (number) Buffer handle, or 0 for current
|
||||
---@param client_id (number) Client id
|
||||
function lsp.buf_attach_client(bufnr, client_id)
|
||||
validate {
|
||||
bufnr = {bufnr, 'n', true};
|
||||
client_id = {client_id, 'n'};
|
||||
}
|
||||
validate({
|
||||
bufnr = { bufnr, 'n', true },
|
||||
client_id = { client_id, 'n' },
|
||||
})
|
||||
bufnr = resolve_bufnr(bufnr)
|
||||
if not vim.api.nvim_buf_is_loaded(bufnr) then
|
||||
local _ = log.warn() and log.warn(
|
||||
string.format("buf_attach_client called on unloaded buffer (id: %d): ", bufnr)
|
||||
)
|
||||
local _ = log.warn() and log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr))
|
||||
return false
|
||||
end
|
||||
local buffer_client_ids = all_buffer_active_clients[bufnr]
|
||||
@@ -1266,36 +1296,38 @@ function lsp.buf_attach_client(bufnr, client_id)
|
||||
vim.api.nvim_exec(string.format(buf_did_save_autocommand, client_id, bufnr, bufnr), false)
|
||||
-- First time, so attach and set up stuff.
|
||||
vim.api.nvim_buf_attach(bufnr, false, {
|
||||
on_lines = text_document_did_change_handler;
|
||||
on_lines = text_document_did_change_handler,
|
||||
on_reload = function()
|
||||
local params = { textDocument = { uri = uri; } }
|
||||
local params = { textDocument = { uri = uri } }
|
||||
for_each_buffer_client(bufnr, function(client, _)
|
||||
changetracking.reset_buf(client, bufnr)
|
||||
if vim.tbl_get(client.server_capabilities, "textDocumentSync", "openClose") then
|
||||
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
|
||||
client.notify('textDocument/didClose', params)
|
||||
end
|
||||
text_document_did_open_handler(bufnr, client)
|
||||
end)
|
||||
end;
|
||||
end,
|
||||
on_detach = function()
|
||||
local params = { textDocument = { uri = uri; } }
|
||||
local params = { textDocument = { uri = uri } }
|
||||
for_each_buffer_client(bufnr, function(client, _)
|
||||
changetracking.reset_buf(client, bufnr)
|
||||
if vim.tbl_get(client.server_capabilities, "textDocumentSync", "openClose") then
|
||||
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
|
||||
client.notify('textDocument/didClose', params)
|
||||
end
|
||||
end)
|
||||
util.buf_versions[bufnr] = nil
|
||||
all_buffer_active_clients[bufnr] = nil
|
||||
end;
|
||||
end,
|
||||
-- TODO if we know all of the potential clients ahead of time, then we
|
||||
-- could conditionally set this.
|
||||
-- utf_sizes = size_index > 1;
|
||||
utf_sizes = true;
|
||||
utf_sizes = true,
|
||||
})
|
||||
end
|
||||
|
||||
if buffer_client_ids[client_id] then return end
|
||||
if buffer_client_ids[client_id] then
|
||||
return
|
||||
end
|
||||
-- This is our first time attaching this client to this buffer.
|
||||
buffer_client_ids[client_id] = true
|
||||
|
||||
@@ -1315,25 +1347,23 @@ end
|
||||
---@param bufnr number Buffer handle, or 0 for current
|
||||
---@param client_id number Client id
|
||||
function lsp.buf_detach_client(bufnr, client_id)
|
||||
validate {
|
||||
bufnr = {bufnr, 'n', true};
|
||||
client_id = {client_id, 'n'};
|
||||
}
|
||||
validate({
|
||||
bufnr = { bufnr, 'n', true },
|
||||
client_id = { client_id, 'n' },
|
||||
})
|
||||
bufnr = resolve_bufnr(bufnr)
|
||||
|
||||
local client = lsp.get_client_by_id(client_id)
|
||||
if not client or not client.attached_buffers[bufnr] then
|
||||
vim.notify(
|
||||
string.format('Buffer (id: %d) is not attached to client (id: %d). Cannot detach.', client_id, bufnr)
|
||||
)
|
||||
vim.notify(string.format('Buffer (id: %d) is not attached to client (id: %d). Cannot detach.', client_id, bufnr))
|
||||
return
|
||||
end
|
||||
|
||||
changetracking.reset_buf(client, bufnr)
|
||||
|
||||
if vim.tbl_get(client.server_capabilities, "textDocumentSync", "openClose") then
|
||||
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
|
||||
local uri = vim.uri_from_bufnr(bufnr)
|
||||
local params = { textDocument = { uri = uri; } }
|
||||
local params = { textDocument = { uri = uri } }
|
||||
client.notify('textDocument/didClose', params)
|
||||
end
|
||||
|
||||
@@ -1349,7 +1379,6 @@ function lsp.buf_detach_client(bufnr, client_id)
|
||||
vim.diagnostic.reset(namespace, bufnr)
|
||||
|
||||
vim.notify(string.format('Detached buffer (id: %d) from client (id: %d)', bufnr, client_id))
|
||||
|
||||
end
|
||||
|
||||
--- Checks if a buffer is attached for a particular client.
|
||||
@@ -1414,7 +1443,7 @@ function lsp.get_active_clients()
|
||||
end
|
||||
|
||||
function lsp._vim_exit_handler()
|
||||
log.info("exit_handler", active_clients)
|
||||
log.info('exit_handler', active_clients)
|
||||
for _, client in pairs(uninitialized_clients) do
|
||||
client.stop(true)
|
||||
end
|
||||
@@ -1466,8 +1495,7 @@ function lsp._vim_exit_handler()
|
||||
end
|
||||
end
|
||||
|
||||
nvim_command("autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()")
|
||||
|
||||
nvim_command('autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()')
|
||||
|
||||
--- Sends an async request for all active clients attached to the
|
||||
--- buffer.
|
||||
@@ -1483,11 +1511,11 @@ nvim_command("autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()")
|
||||
--- - Function which can be used to cancel all the requests. You could instead
|
||||
--- iterate all clients and call their `cancel_request()` methods.
|
||||
function lsp.buf_request(bufnr, method, params, handler)
|
||||
validate {
|
||||
bufnr = { bufnr, 'n', true };
|
||||
method = { method, 's' };
|
||||
handler = { handler, 'f', true };
|
||||
}
|
||||
validate({
|
||||
bufnr = { bufnr, 'n', true },
|
||||
method = { method, 's' },
|
||||
handler = { handler, 'f', true },
|
||||
})
|
||||
|
||||
local supported_clients = {}
|
||||
local method_supported = false
|
||||
@@ -1501,7 +1529,7 @@ function lsp.buf_request(bufnr, method, params, handler)
|
||||
-- if has client but no clients support the given method, notify the user
|
||||
if not tbl_isempty(all_buffer_active_clients[resolve_bufnr(bufnr)] or {}) and not method_supported then
|
||||
vim.notify(lsp._unsupported_method(method), vim.log.levels.ERROR)
|
||||
vim.api.nvim_command("redraw")
|
||||
vim.api.nvim_command('redraw')
|
||||
return {}, function() end
|
||||
end
|
||||
|
||||
@@ -1607,18 +1635,19 @@ end
|
||||
---
|
||||
---@returns true if any client returns true; false otherwise
|
||||
function lsp.buf_notify(bufnr, method, params)
|
||||
validate {
|
||||
bufnr = { bufnr, 'n', true };
|
||||
method = { method, 's' };
|
||||
}
|
||||
validate({
|
||||
bufnr = { bufnr, 'n', true },
|
||||
method = { method, 's' },
|
||||
})
|
||||
local resp = false
|
||||
for_each_buffer_client(bufnr, function(client, _client_id, _resolved_bufnr)
|
||||
if client.rpc.notify(method, params) then resp = true end
|
||||
if client.rpc.notify(method, params) then
|
||||
resp = true
|
||||
end
|
||||
end)
|
||||
return resp
|
||||
end
|
||||
|
||||
|
||||
---@private
|
||||
local function adjust_start_col(lnum, line, items, encoding)
|
||||
local min_start_char = nil
|
||||
@@ -1650,7 +1679,7 @@ end
|
||||
--- - findstart=0: column where the completion starts, or -2 or -3
|
||||
--- - findstart=1: list of matches (actually just calls |complete()|)
|
||||
function lsp.omnifunc(findstart, base)
|
||||
local _ = log.debug() and log.debug("omnifunc.findstart", { findstart = findstart, base = base })
|
||||
local _ = log.debug() and log.debug('omnifunc.findstart', { findstart = findstart, base = base })
|
||||
|
||||
local bufnr = resolve_bufnr()
|
||||
local has_buffer_clients = not tbl_isempty(all_buffer_active_clients[bufnr] or {})
|
||||
@@ -1663,12 +1692,12 @@ function lsp.omnifunc(findstart, base)
|
||||
end
|
||||
|
||||
-- Then, perform standard completion request
|
||||
local _ = log.info() and log.info("base ", base)
|
||||
local _ = log.info() and log.info('base ', base)
|
||||
|
||||
local pos = vim.api.nvim_win_get_cursor(0)
|
||||
local line = vim.api.nvim_get_current_line()
|
||||
local line_to_cursor = line:sub(1, pos[2])
|
||||
local _ = log.trace() and log.trace("omnifunc.line", pos, line)
|
||||
local _ = log.trace() and log.trace('omnifunc.line', pos, line)
|
||||
|
||||
-- Get the start position of the current keyword
|
||||
local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
|
||||
@@ -1677,7 +1706,9 @@ function lsp.omnifunc(findstart, base)
|
||||
|
||||
local items = {}
|
||||
lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, result, ctx)
|
||||
if err or not result or vim.fn.mode() ~= "i" then return end
|
||||
if err or not result or vim.fn.mode() ~= 'i' then
|
||||
return
|
||||
end
|
||||
|
||||
-- Completion response items may be relative to a position different than `textMatch`.
|
||||
-- Concrete example, with sumneko/lua-language-server:
|
||||
@@ -1734,14 +1765,14 @@ function lsp.formatexpr(opts)
|
||||
|
||||
if start_line > 0 and end_line > 0 then
|
||||
local params = {
|
||||
textDocument = util.make_text_document_params();
|
||||
textDocument = util.make_text_document_params(),
|
||||
range = {
|
||||
start = { line = start_line - 1; character = 0; };
|
||||
["end"] = { line = end_line - 1; character = 0; };
|
||||
};
|
||||
};
|
||||
start = { line = start_line - 1, character = 0 },
|
||||
['end'] = { line = end_line - 1, character = 0 },
|
||||
},
|
||||
}
|
||||
params.options = util.make_formatting_params().options
|
||||
local client_results = vim.lsp.buf_request_sync(0, "textDocument/rangeFormatting", params, timeout_ms)
|
||||
local client_results = vim.lsp.buf_request_sync(0, 'textDocument/rangeFormatting', params, timeout_ms)
|
||||
|
||||
-- Apply the text edits from one and only one of the clients.
|
||||
for client_id, response in pairs(client_results) do
|
||||
@@ -1815,7 +1846,7 @@ function lsp.set_log_level(level)
|
||||
if type(level) == 'string' or type(level) == 'number' then
|
||||
log.set_level(level)
|
||||
else
|
||||
error(string.format("Invalid log level: %q", level))
|
||||
error(string.format('Invalid log level: %q', level))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1845,7 +1876,7 @@ end
|
||||
---@param override_config (table) Table containing the keys to override behavior of the {handler}
|
||||
function lsp.with(handler, override_config)
|
||||
return function(err, result, ctx, config)
|
||||
return handler(err, result, ctx, vim.tbl_deep_extend("force", config or {}, override_config))
|
||||
return handler(err, result, ctx, vim.tbl_deep_extend('force', config or {}, override_config))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1860,12 +1891,16 @@ function lsp._with_extend(name, options, user_config)
|
||||
local resulting_config = {}
|
||||
for k, v in pairs(user_config) do
|
||||
if options[k] == nil then
|
||||
error(debug.traceback(string.format(
|
||||
"Invalid option for `%s`: %s. Valid options are:\n%s",
|
||||
error(
|
||||
debug.traceback(
|
||||
string.format(
|
||||
'Invalid option for `%s`: %s. Valid options are:\n%s',
|
||||
name,
|
||||
k,
|
||||
vim.inspect(vim.tbl_keys(options))
|
||||
)))
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
resulting_config[k] = v
|
||||
@@ -1880,7 +1915,6 @@ function lsp._with_extend(name, options, user_config)
|
||||
return resulting_config
|
||||
end
|
||||
|
||||
|
||||
--- Registry for client side commands.
|
||||
--- This is an extension point for plugins to handle custom commands which are
|
||||
--- not part of the core language server protocol specification.
|
||||
@@ -1902,12 +1936,11 @@ end
|
||||
--- The second argument is the `ctx` of |lsp-handler|
|
||||
lsp.commands = setmetatable({}, {
|
||||
__newindex = function(tbl, key, value)
|
||||
assert(type(key) == 'string', "The key for commands in `vim.lsp.commands` must be a string")
|
||||
assert(type(value) == 'function', "Command added to `vim.lsp.commands` must be a function")
|
||||
assert(type(key) == 'string', 'The key for commands in `vim.lsp.commands` must be a string')
|
||||
assert(type(value) == 'function', 'Command added to `vim.lsp.commands` must be a function')
|
||||
rawset(tbl, key, value)
|
||||
end;
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
return lsp
|
||||
-- vim:sw=2 ts=2 et
|
||||
|
@@ -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)
|
||||
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(
|
||||
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)
|
||||
),
|
||||
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)
|
||||
end
|
||||
)
|
||||
)
|
||||
|
||||
S.transform = P.map(P.seq(
|
||||
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)
|
||||
),
|
||||
function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.TRANSFORM,
|
||||
pattern = values[2].raw,
|
||||
format = values[4],
|
||||
option = values[6],
|
||||
}, Node)
|
||||
end)
|
||||
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)
|
||||
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)
|
||||
end
|
||||
)
|
||||
)
|
||||
|
||||
S.choice = P.map(P.seq(
|
||||
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)
|
||||
P.many(P.map(P.seq(S.text({ ',', '|' }), P.opt(S.comma)), function(values)
|
||||
return values[1].esc
|
||||
end)
|
||||
),
|
||||
end)),
|
||||
S.pipe,
|
||||
S.close
|
||||
), function(values)
|
||||
),
|
||||
function(values)
|
||||
return setmetatable({
|
||||
type = Node.Type.CHOICE,
|
||||
tabstop = values[3],
|
||||
items = values[5],
|
||||
}, Node)
|
||||
end)
|
||||
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)
|
||||
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)
|
||||
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,7 +645,7 @@ 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
|
||||
@@ -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
|
||||
if
|
||||
not action.edit
|
||||
and client
|
||||
and vim.tbl_get(client.server_capabilities, "codeActionProvider", "resolveProvider") then
|
||||
|
||||
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
|
||||
@@ -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,10 +854,10 @@ 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,
|
||||
|
@@ -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,9 +43,13 @@ 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)
|
||||
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(...)
|
||||
@@ -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()
|
||||
@@ -105,7 +108,6 @@ function M.run()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Display the lenses using virtual text
|
||||
---
|
||||
---@param lenses table of lenses to display (`CodeLens[] | null`)
|
||||
@@ -139,13 +141,14 @@ function M.display(lenses, bufnr, client_id)
|
||||
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]
|
||||
@@ -240,7 +240,6 @@ end
|
||||
|
||||
-- Deprecated Functions {{{
|
||||
|
||||
|
||||
--- Save diagnostics to the current buffer.
|
||||
---
|
||||
---@deprecated Prefer |vim.diagnostic.set()|
|
||||
@@ -288,7 +287,9 @@ end
|
||||
---@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
|
||||
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, _)
|
||||
@@ -558,7 +559,7 @@ end
|
||||
function M.show_position_diagnostics(opts, buf_nr, position)
|
||||
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)
|
||||
@@ -582,7 +583,7 @@ end
|
||||
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')
|
||||
opts = opts or {}
|
||||
opts.scope = "line"
|
||||
opts.scope = 'line'
|
||||
opts.pos = line_nr
|
||||
if client_id then
|
||||
opts.namespace = M.get_namespace(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
|
||||
|
||||
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,7 +59,7 @@ 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
|
||||
@@ -68,9 +70,9 @@ 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 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,14 +81,13 @@ 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.
|
||||
@@ -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,28 +249,36 @@ 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)
|
||||
@@ -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]
|
||||
@@ -431,7 +445,7 @@ local make_call_hierarchy_handler = function(direction)
|
||||
end
|
||||
end
|
||||
vim.fn.setqflist({}, ' ', { title = 'LSP call hierarchy', items = items })
|
||||
api.nvim_command("botright copen")
|
||||
api.nvim_command('botright copen')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -447,9 +461,9 @@ 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)
|
||||
@@ -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,8 +499,12 @@ 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
|
||||
@@ -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_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_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
|
||||
@@ -937,43 +949,43 @@ function protocol._resolve_capabilities_compat(server_capabilities)
|
||||
-- Defaults if omitted.
|
||||
workspace_properties = {
|
||||
workspace_folder_properties = {
|
||||
supported = false;
|
||||
changeNotifications=false;
|
||||
}
|
||||
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
|
||||
|
@@ -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.
|
||||
@@ -109,8 +113,7 @@ local function request_parser_loop()
|
||||
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
|
||||
@@ -485,16 +502,15 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||
end)
|
||||
-- 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
|
||||
|
@@ -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
|
||||
@@ -282,13 +296,12 @@ end
|
||||
---@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 ""
|
||||
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
@@ -24,7 +24,9 @@ vim.deepcopy = (function()
|
||||
|
||||
local deepcopy_funcs = {
|
||||
table = function(orig, cache)
|
||||
if cache[orig] then return cache[orig] end
|
||||
if cache[orig] then
|
||||
return cache[orig]
|
||||
end
|
||||
local copy = {}
|
||||
|
||||
cache[orig] = copy
|
||||
@@ -46,7 +48,7 @@ vim.deepcopy = (function()
|
||||
if f then
|
||||
return f(orig, cache or {})
|
||||
else
|
||||
error("Cannot deepcopy object of type "..type(orig))
|
||||
error('Cannot deepcopy object of type ' .. type(orig))
|
||||
end
|
||||
end
|
||||
end)()
|
||||
@@ -62,14 +64,14 @@ end)()
|
||||
---@param plain If `true` use `sep` literally (passed to String.find)
|
||||
---@returns Iterator over the split components
|
||||
function vim.gsplit(s, sep, plain)
|
||||
vim.validate{s={s,'s'},sep={sep,'s'},plain={plain,'b',true}}
|
||||
vim.validate({ s = { s, 's' }, sep = { sep, 's' }, plain = { plain, 'b', true } })
|
||||
|
||||
local start = 1
|
||||
local done = false
|
||||
|
||||
local function _pass(i, j, ...)
|
||||
if i then
|
||||
assert(j+1 > start, "Infinite loop detected")
|
||||
assert(j + 1 > start, 'Infinite loop detected')
|
||||
local seg = s:sub(start, i - 1)
|
||||
start = j + 1
|
||||
return seg, ...
|
||||
@@ -119,7 +121,7 @@ function vim.split(s, sep, kwargs)
|
||||
-- Support old signature for backward compatibility
|
||||
plain = kwargs
|
||||
else
|
||||
vim.validate { kwargs = {kwargs, 't', true} }
|
||||
vim.validate({ kwargs = { kwargs, 't', true } })
|
||||
kwargs = kwargs or {}
|
||||
plain = kwargs.plain
|
||||
trimempty = kwargs.trimempty
|
||||
@@ -128,7 +130,7 @@ function vim.split(s, sep, kwargs)
|
||||
local t = {}
|
||||
local skip = trimempty
|
||||
for c in vim.gsplit(s, sep, plain) do
|
||||
if c ~= "" then
|
||||
if c ~= '' then
|
||||
skip = false
|
||||
end
|
||||
|
||||
@@ -139,7 +141,7 @@ function vim.split(s, sep, kwargs)
|
||||
|
||||
if trimempty then
|
||||
for i = #t, 1, -1 do
|
||||
if t[i] ~= "" then
|
||||
if t[i] ~= '' then
|
||||
break
|
||||
end
|
||||
table.remove(t, i)
|
||||
@@ -157,7 +159,7 @@ end
|
||||
---@param t Table
|
||||
---@returns list of keys
|
||||
function vim.tbl_keys(t)
|
||||
assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
|
||||
assert(type(t) == 'table', string.format('Expected table, got %s', type(t)))
|
||||
|
||||
local keys = {}
|
||||
for k, _ in pairs(t) do
|
||||
@@ -172,7 +174,7 @@ end
|
||||
---@param t Table
|
||||
---@returns list of values
|
||||
function vim.tbl_values(t)
|
||||
assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
|
||||
assert(type(t) == 'table', string.format('Expected table, got %s', type(t)))
|
||||
|
||||
local values = {}
|
||||
for _, v in pairs(t) do
|
||||
@@ -186,7 +188,7 @@ end
|
||||
---@param func function or callable table
|
||||
---@param t table
|
||||
function vim.tbl_map(func, t)
|
||||
vim.validate{func={func,'c'},t={t,'t'}}
|
||||
vim.validate({ func = { func, 'c' }, t = { t, 't' } })
|
||||
|
||||
local rettab = {}
|
||||
for k, v in pairs(t) do
|
||||
@@ -200,7 +202,7 @@ end
|
||||
---@param func function or callable table
|
||||
---@param t table
|
||||
function vim.tbl_filter(func, t)
|
||||
vim.validate{func={func,'c'},t={t,'t'}}
|
||||
vim.validate({ func = { func, 'c' }, t = { t, 't' } })
|
||||
|
||||
local rettab = {}
|
||||
for _, entry in pairs(t) do
|
||||
@@ -217,7 +219,7 @@ end
|
||||
---@param value Value to compare
|
||||
---@returns true if `t` contains `value`
|
||||
function vim.tbl_contains(t, value)
|
||||
vim.validate{t={t,'t'}}
|
||||
vim.validate({ t = { t, 't' } })
|
||||
|
||||
for _, v in ipairs(t) do
|
||||
if v == value then
|
||||
@@ -233,18 +235,18 @@ end
|
||||
---
|
||||
---@param t Table to check
|
||||
function vim.tbl_isempty(t)
|
||||
assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
|
||||
assert(type(t) == 'table', string.format('Expected table, got %s', type(t)))
|
||||
return next(t) == nil
|
||||
end
|
||||
|
||||
--- we only merge empty tables or tables that are not a list
|
||||
---@private
|
||||
local function can_merge(v)
|
||||
return type(v) == "table" and (vim.tbl_isempty(v) or not vim.tbl_islist(v))
|
||||
return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.tbl_islist(v))
|
||||
end
|
||||
|
||||
local function tbl_extend(behavior, deep_extend, ...)
|
||||
if (behavior ~= 'error' and behavior ~= 'keep' and behavior ~= 'force') then
|
||||
if behavior ~= 'error' and behavior ~= 'keep' and behavior ~= 'force' then
|
||||
error('invalid "behavior": ' .. tostring(behavior))
|
||||
end
|
||||
|
||||
@@ -259,7 +261,7 @@ local function tbl_extend(behavior, deep_extend, ...)
|
||||
|
||||
for i = 1, select('#', ...) do
|
||||
local tbl = select(i, ...)
|
||||
vim.validate{["after the second argument"] = {tbl,'t'}}
|
||||
vim.validate({ ['after the second argument'] = { tbl, 't' } })
|
||||
if tbl then
|
||||
for k, v in pairs(tbl) do
|
||||
if deep_extend and can_merge(v) and can_merge(ret[k]) then
|
||||
@@ -311,8 +313,12 @@ end
|
||||
---@param b second value
|
||||
---@returns `true` if values are equals, else `false`.
|
||||
function vim.deep_equal(a, b)
|
||||
if a == b then return true end
|
||||
if type(a) ~= type(b) then return false end
|
||||
if a == b then
|
||||
return true
|
||||
end
|
||||
if type(a) ~= type(b) then
|
||||
return false
|
||||
end
|
||||
if type(a) == 'table' then
|
||||
for k, v in pairs(a) do
|
||||
if not vim.deep_equal(v, b[k]) then
|
||||
@@ -340,7 +346,13 @@ function vim.tbl_add_reverse_lookup(o)
|
||||
for _, k in ipairs(keys) do
|
||||
local v = o[k]
|
||||
if o[v] then
|
||||
error(string.format("The reverse lookup found an existing value for %q while processing key %q", tostring(v), tostring(k)))
|
||||
error(
|
||||
string.format(
|
||||
'The reverse lookup found an existing value for %q while processing key %q',
|
||||
tostring(v),
|
||||
tostring(k)
|
||||
)
|
||||
)
|
||||
end
|
||||
o[v] = k
|
||||
end
|
||||
@@ -389,12 +401,12 @@ end
|
||||
---@param finish Final index on src. defaults to #src
|
||||
---@returns dst
|
||||
function vim.list_extend(dst, src, start, finish)
|
||||
vim.validate {
|
||||
dst = {dst, 't'};
|
||||
src = {src, 't'};
|
||||
start = {start, 'n', true};
|
||||
finish = {finish, 'n', true};
|
||||
}
|
||||
vim.validate({
|
||||
dst = { dst, 't' },
|
||||
src = { src, 't' },
|
||||
start = { start, 'n', true },
|
||||
finish = { finish, 'n', true },
|
||||
})
|
||||
for i = start or 1, finish or #src do
|
||||
table.insert(dst, src[i])
|
||||
end
|
||||
@@ -414,7 +426,7 @@ function vim.tbl_flatten(t)
|
||||
local n = #_t
|
||||
for i = 1, n do
|
||||
local v = _t[i]
|
||||
if type(v) == "table" then
|
||||
if type(v) == 'table' then
|
||||
_tbl_flatten(v)
|
||||
elseif v then
|
||||
table.insert(result, v)
|
||||
@@ -441,7 +453,7 @@ function vim.tbl_islist(t)
|
||||
local count = 0
|
||||
|
||||
for k, _ in pairs(t) do
|
||||
if type(k) == "number" then
|
||||
if type(k) == 'number' then
|
||||
count = count + 1
|
||||
else
|
||||
return false
|
||||
@@ -471,10 +483,12 @@ end
|
||||
---@param t Table
|
||||
---@returns Number that is the number of the value in table
|
||||
function vim.tbl_count(t)
|
||||
vim.validate{t={t,'t'}}
|
||||
vim.validate({ t = { t, 't' } })
|
||||
|
||||
local count = 0
|
||||
for _ in pairs(t) do count = count + 1 end
|
||||
for _ in pairs(t) do
|
||||
count = count + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
@@ -498,7 +512,7 @@ end
|
||||
---@param s String to trim
|
||||
---@returns String with whitespace removed from its beginning and end
|
||||
function vim.trim(s)
|
||||
vim.validate{s={s,'s'}}
|
||||
vim.validate({ s = { s, 's' } })
|
||||
return s:match('^%s*(.*%S)') or ''
|
||||
end
|
||||
|
||||
@@ -508,7 +522,7 @@ end
|
||||
---@param s String to escape
|
||||
---@returns %-escaped pattern string
|
||||
function vim.pesc(s)
|
||||
vim.validate{s={s,'s'}}
|
||||
vim.validate({ s = { s, 's' } })
|
||||
return s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')
|
||||
end
|
||||
|
||||
@@ -518,7 +532,7 @@ end
|
||||
---@param prefix (string) a prefix
|
||||
---@return (boolean) true if `prefix` is a prefix of s
|
||||
function vim.startswith(s, prefix)
|
||||
vim.validate { s = {s, 's'}; prefix = {prefix, 's'}; }
|
||||
vim.validate({ s = { s, 's' }, prefix = { prefix, 's' } })
|
||||
return s:sub(1, #prefix) == prefix
|
||||
end
|
||||
|
||||
@@ -528,7 +542,7 @@ end
|
||||
---@param suffix (string) a suffix
|
||||
---@return (boolean) true if `suffix` is a suffix of s
|
||||
function vim.endswith(s, suffix)
|
||||
vim.validate { s = {s, 's'}; suffix = {suffix, 's'}; }
|
||||
vim.validate({ s = { s, 's' }, suffix = { suffix, 's' } })
|
||||
return #suffix == 0 or s:sub(-#suffix) == suffix
|
||||
end
|
||||
|
||||
@@ -586,12 +600,18 @@ function vim.validate(opt) end -- luacheck: no unused
|
||||
|
||||
do
|
||||
local type_names = {
|
||||
['table'] = 'table', t = 'table',
|
||||
['string'] = 'string', s = 'string',
|
||||
['number'] = 'number', n = 'number',
|
||||
['boolean'] = 'boolean', b = 'boolean',
|
||||
['function'] = 'function', f = 'function',
|
||||
['callable'] = 'callable', c = 'callable',
|
||||
['table'] = 'table',
|
||||
t = 'table',
|
||||
['string'] = 'string',
|
||||
s = 'string',
|
||||
['number'] = 'number',
|
||||
n = 'number',
|
||||
['boolean'] = 'boolean',
|
||||
b = 'boolean',
|
||||
['function'] = 'function',
|
||||
f = 'function',
|
||||
['callable'] = 'callable',
|
||||
c = 'callable',
|
||||
['nil'] = 'nil',
|
||||
['thread'] = 'thread',
|
||||
['userdata'] = 'userdata',
|
||||
@@ -624,9 +644,9 @@ do
|
||||
-- Check user-provided validation function.
|
||||
local valid, optional_message = types(val)
|
||||
if not valid then
|
||||
local error_message = string.format("%s: expected %s, got %s", param_name, (spec[3] or '?'), tostring(val))
|
||||
local error_message = string.format('%s: expected %s, got %s', param_name, (spec[3] or '?'), tostring(val))
|
||||
if optional_message ~= nil then
|
||||
error_message = error_message .. string.format(". Info: %s", optional_message)
|
||||
error_message = error_message .. string.format('. Info: %s', optional_message)
|
||||
end
|
||||
|
||||
return false, error_message
|
||||
@@ -646,10 +666,10 @@ do
|
||||
end
|
||||
end
|
||||
if not success then
|
||||
return false, string.format("%s: expected %s, got %s", param_name, table.concat(types, '|'), type(val))
|
||||
return false, string.format('%s: expected %s, got %s', param_name, table.concat(types, '|'), type(val))
|
||||
end
|
||||
else
|
||||
return false, string.format("invalid type name: %s", tostring(types))
|
||||
return false, string.format('invalid type name: %s', tostring(types))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -668,9 +688,13 @@ end
|
||||
---@param f Any object
|
||||
---@return true if `f` is callable, else false
|
||||
function vim.is_callable(f)
|
||||
if type(f) == 'function' then return true end
|
||||
if type(f) == 'function' then
|
||||
return true
|
||||
end
|
||||
local m = getmetatable(f)
|
||||
if m == nil then return false end
|
||||
if m == nil then
|
||||
return false
|
||||
end
|
||||
return type(m.__call) == 'function'
|
||||
end
|
||||
|
||||
|
@@ -1,31 +1,31 @@
|
||||
local a = vim.api
|
||||
local query = require'vim.treesitter.query'
|
||||
local language = require'vim.treesitter.language'
|
||||
local LanguageTree = require'vim.treesitter.languagetree'
|
||||
local query = require('vim.treesitter.query')
|
||||
local language = require('vim.treesitter.language')
|
||||
local LanguageTree = require('vim.treesitter.languagetree')
|
||||
|
||||
-- TODO(bfredl): currently we retain parsers for the lifetime of the buffer.
|
||||
-- Consider use weak references to release parser if all plugins are done with
|
||||
-- it.
|
||||
local parsers = {}
|
||||
|
||||
local M = vim.tbl_extend("error", query, language)
|
||||
local M = vim.tbl_extend('error', query, language)
|
||||
|
||||
M.language_version = vim._ts_get_language_version()
|
||||
M.minimum_language_version = vim._ts_get_minimum_language_version()
|
||||
|
||||
setmetatable(M, {
|
||||
__index = function(t, k)
|
||||
if k == "highlighter" then
|
||||
t[k] = require'vim.treesitter.highlighter'
|
||||
if k == 'highlighter' then
|
||||
t[k] = require('vim.treesitter.highlighter')
|
||||
return t[k]
|
||||
elseif k == "language" then
|
||||
t[k] = require"vim.treesitter.language"
|
||||
elseif k == 'language' then
|
||||
t[k] = require('vim.treesitter.language')
|
||||
return t[k]
|
||||
elseif k == "query" then
|
||||
t[k] = require"vim.treesitter.query"
|
||||
elseif k == 'query' then
|
||||
t[k] = require('vim.treesitter.query')
|
||||
return t[k]
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
--- Creates a new parser.
|
||||
@@ -63,7 +63,11 @@ function M._create_parser(bufnr, lang, opts)
|
||||
self:_on_reload(...)
|
||||
end
|
||||
|
||||
a.nvim_buf_attach(self:source(), false, {on_bytes=bytes_cb, on_detach=detach_cb, on_reload=reload_cb, preview=true})
|
||||
a.nvim_buf_attach(
|
||||
self:source(),
|
||||
false,
|
||||
{ on_bytes = bytes_cb, on_detach = detach_cb, on_reload = reload_cb, preview = true }
|
||||
)
|
||||
|
||||
self:parse()
|
||||
|
||||
@@ -87,7 +91,7 @@ function M.get_parser(bufnr, lang, opts)
|
||||
bufnr = a.nvim_get_current_buf()
|
||||
end
|
||||
if lang == nil then
|
||||
lang = a.nvim_buf_get_option(bufnr, "filetype")
|
||||
lang = a.nvim_buf_get_option(bufnr, 'filetype')
|
||||
end
|
||||
|
||||
if parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then
|
||||
@@ -105,10 +109,10 @@ end
|
||||
---@param lang The language of this string
|
||||
---@param opts Options to pass to the created language tree
|
||||
function M.get_string_parser(str, lang, opts)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
str = { str, 'string' },
|
||||
lang = { lang, 'string' }
|
||||
}
|
||||
lang = { lang, 'string' },
|
||||
})
|
||||
language.require_language(lang)
|
||||
|
||||
return LanguageTree.new(str, lang, opts)
|
||||
|
@@ -15,24 +15,22 @@ function M.check()
|
||||
local report_error = vim.fn['health#report_error']
|
||||
local parsers = M.list_parsers()
|
||||
|
||||
report_info(string.format("Runtime ABI version : %d", ts.language_version))
|
||||
report_info(string.format('Runtime ABI version : %d', ts.language_version))
|
||||
|
||||
for _, parser in pairs(parsers) do
|
||||
local parsername = vim.fn.fnamemodify(parser, ":t:r")
|
||||
local parsername = vim.fn.fnamemodify(parser, ':t:r')
|
||||
|
||||
local is_loadable, ret = pcall(ts.language.require_language, parsername)
|
||||
|
||||
if not is_loadable then
|
||||
report_error(string.format("Impossible to load parser for %s: %s", parsername, ret))
|
||||
report_error(string.format('Impossible to load parser for %s: %s', parsername, ret))
|
||||
elseif ret then
|
||||
local lang = ts.language.inspect_language(parsername)
|
||||
report_ok(string.format("Loaded parser for %s: ABI version %d",
|
||||
parsername, lang._abi_version))
|
||||
report_ok(string.format('Loaded parser for %s: ABI version %d', parsername, lang._abi_version))
|
||||
else
|
||||
report_error(string.format("Unable to load parser for %s", parsername))
|
||||
report_error(string.format('Unable to load parser for %s', parsername))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
local a = vim.api
|
||||
local query = require"vim.treesitter.query"
|
||||
local query = require('vim.treesitter.query')
|
||||
|
||||
-- support reload for quick experimentation
|
||||
local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
|
||||
@@ -10,13 +10,13 @@ TSHighlighter.active = TSHighlighter.active or {}
|
||||
local TSHighlighterQuery = {}
|
||||
TSHighlighterQuery.__index = TSHighlighterQuery
|
||||
|
||||
local ns = a.nvim_create_namespace("treesitter/highlighter")
|
||||
local ns = a.nvim_create_namespace('treesitter/highlighter')
|
||||
|
||||
local _default_highlights = {}
|
||||
local _link_default_highlight_once = function(from, to)
|
||||
if not _default_highlights[from] then
|
||||
_default_highlights[from] = true
|
||||
vim.cmd(string.format("highlight default link %s %s", from, to))
|
||||
vim.cmd(string.format('highlight default link %s %s', from, to))
|
||||
end
|
||||
|
||||
return from
|
||||
@@ -31,65 +31,65 @@ local subcapture_fallback = {
|
||||
shortened = shortened:match('(.*)%.')
|
||||
rtn = shortened and rawget(self, shortened)
|
||||
end
|
||||
rawset(self, capture, rtn or "__notfound")
|
||||
rawset(self, capture, rtn or '__notfound')
|
||||
return rtn
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
TSHighlighter.hl_map = setmetatable({
|
||||
["error"] = "Error",
|
||||
["text.underline"] = "Underlined",
|
||||
["todo"] = "Todo",
|
||||
["debug"] = "Debug",
|
||||
['error'] = 'Error',
|
||||
['text.underline'] = 'Underlined',
|
||||
['todo'] = 'Todo',
|
||||
['debug'] = 'Debug',
|
||||
|
||||
-- Miscs
|
||||
["comment"] = "Comment",
|
||||
["punctuation.delimiter"] = "Delimiter",
|
||||
["punctuation.bracket"] = "Delimiter",
|
||||
["punctuation.special"] = "Delimiter",
|
||||
['comment'] = 'Comment',
|
||||
['punctuation.delimiter'] = 'Delimiter',
|
||||
['punctuation.bracket'] = 'Delimiter',
|
||||
['punctuation.special'] = 'Delimiter',
|
||||
|
||||
-- Constants
|
||||
["constant"] = "Constant",
|
||||
["constant.builtin"] = "Special",
|
||||
["constant.macro"] = "Define",
|
||||
["define"] = "Define",
|
||||
["macro"] = "Macro",
|
||||
["string"] = "String",
|
||||
["string.regex"] = "String",
|
||||
["string.escape"] = "SpecialChar",
|
||||
["character"] = "Character",
|
||||
["character.special"] = "SpecialChar",
|
||||
["number"] = "Number",
|
||||
["boolean"] = "Boolean",
|
||||
["float"] = "Float",
|
||||
['constant'] = 'Constant',
|
||||
['constant.builtin'] = 'Special',
|
||||
['constant.macro'] = 'Define',
|
||||
['define'] = 'Define',
|
||||
['macro'] = 'Macro',
|
||||
['string'] = 'String',
|
||||
['string.regex'] = 'String',
|
||||
['string.escape'] = 'SpecialChar',
|
||||
['character'] = 'Character',
|
||||
['character.special'] = 'SpecialChar',
|
||||
['number'] = 'Number',
|
||||
['boolean'] = 'Boolean',
|
||||
['float'] = 'Float',
|
||||
|
||||
-- Functions
|
||||
["function"] = "Function",
|
||||
["function.special"] = "Function",
|
||||
["function.builtin"] = "Special",
|
||||
["function.macro"] = "Macro",
|
||||
["parameter"] = "Identifier",
|
||||
["method"] = "Function",
|
||||
["field"] = "Identifier",
|
||||
["property"] = "Identifier",
|
||||
["constructor"] = "Special",
|
||||
['function'] = 'Function',
|
||||
['function.special'] = 'Function',
|
||||
['function.builtin'] = 'Special',
|
||||
['function.macro'] = 'Macro',
|
||||
['parameter'] = 'Identifier',
|
||||
['method'] = 'Function',
|
||||
['field'] = 'Identifier',
|
||||
['property'] = 'Identifier',
|
||||
['constructor'] = 'Special',
|
||||
|
||||
-- Keywords
|
||||
["conditional"] = "Conditional",
|
||||
["repeat"] = "Repeat",
|
||||
["label"] = "Label",
|
||||
["operator"] = "Operator",
|
||||
["keyword"] = "Keyword",
|
||||
["exception"] = "Exception",
|
||||
['conditional'] = 'Conditional',
|
||||
['repeat'] = 'Repeat',
|
||||
['label'] = 'Label',
|
||||
['operator'] = 'Operator',
|
||||
['keyword'] = 'Keyword',
|
||||
['exception'] = 'Exception',
|
||||
|
||||
["type"] = "Type",
|
||||
["type.builtin"] = "Type",
|
||||
["type.qualifier"] = "Type",
|
||||
["type.definition"] = "Typedef",
|
||||
["storageclass"] = "StorageClass",
|
||||
["structure"] = "Structure",
|
||||
["include"] = "Include",
|
||||
["preproc"] = "PreProc",
|
||||
['type'] = 'Type',
|
||||
['type.builtin'] = 'Type',
|
||||
['type.qualifier'] = 'Type',
|
||||
['type.definition'] = 'Typedef',
|
||||
['storageclass'] = 'StorageClass',
|
||||
['structure'] = 'Structure',
|
||||
['include'] = 'Include',
|
||||
['preproc'] = 'PreProc',
|
||||
}, subcapture_fallback)
|
||||
|
||||
---@private
|
||||
@@ -113,13 +113,13 @@ function TSHighlighterQuery.new(lang, query_string)
|
||||
|
||||
rawset(table, capture, id)
|
||||
return id
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
if query_string then
|
||||
self._query = query.parse_query(lang, query_string)
|
||||
else
|
||||
self._query = query.get_query(lang, "highlights")
|
||||
self._query = query.get_query(lang, 'highlights')
|
||||
end
|
||||
|
||||
return self
|
||||
@@ -152,17 +152,23 @@ end
|
||||
function TSHighlighter.new(tree, opts)
|
||||
local self = setmetatable({}, TSHighlighter)
|
||||
|
||||
if type(tree:source()) ~= "number" then
|
||||
error("TSHighlighter can not be used with a string parser source.")
|
||||
if type(tree:source()) ~= 'number' then
|
||||
error('TSHighlighter can not be used with a string parser source.')
|
||||
end
|
||||
|
||||
opts = opts or {}
|
||||
self.tree = tree
|
||||
tree:register_cbs {
|
||||
on_changedtree = function(...) self:on_changedtree(...) end;
|
||||
on_bytes = function(...) self:on_bytes(...) end;
|
||||
on_detach = function(...) self:on_detach(...) end;
|
||||
}
|
||||
tree:register_cbs({
|
||||
on_changedtree = function(...)
|
||||
self:on_changedtree(...)
|
||||
end,
|
||||
on_bytes = function(...)
|
||||
self:on_bytes(...)
|
||||
end,
|
||||
on_detach = function(...)
|
||||
self:on_detach(...)
|
||||
end,
|
||||
})
|
||||
|
||||
self.bufnr = tree:source()
|
||||
self.edit_count = 0
|
||||
@@ -181,7 +187,7 @@ function TSHighlighter.new(tree, opts)
|
||||
end
|
||||
end
|
||||
|
||||
a.nvim_buf_set_option(self.bufnr, "syntax", "")
|
||||
a.nvim_buf_set_option(self.bufnr, 'syntax', '')
|
||||
|
||||
TSHighlighter.active[self.bufnr] = self
|
||||
|
||||
@@ -190,7 +196,7 @@ function TSHighlighter.new(tree, opts)
|
||||
-- syntax FileType autocmds. Later on we should integrate with the
|
||||
-- `:syntax` and `set syntax=...` machinery properly.
|
||||
if vim.g.syntax_on ~= 1 then
|
||||
vim.api.nvim_command("runtime! syntax/synload.vim")
|
||||
vim.api.nvim_command('runtime! syntax/synload.vim')
|
||||
end
|
||||
|
||||
self.tree:parse()
|
||||
@@ -210,7 +216,7 @@ function TSHighlighter:get_highlight_state(tstree)
|
||||
if not self._highlight_states[tstree] then
|
||||
self._highlight_states[tstree] = {
|
||||
next_row = 0,
|
||||
iter = nil
|
||||
iter = nil,
|
||||
}
|
||||
end
|
||||
|
||||
@@ -253,19 +259,25 @@ end
|
||||
---@private
|
||||
local function on_line_impl(self, buf, line)
|
||||
self.tree:for_each_tree(function(tstree, tree)
|
||||
if not tstree then return end
|
||||
if not tstree then
|
||||
return
|
||||
end
|
||||
|
||||
local root_node = tstree:root()
|
||||
local root_start_row, _, root_end_row, _ = root_node:range()
|
||||
|
||||
-- Only worry about trees within the line range
|
||||
if root_start_row > line or root_end_row < line then return end
|
||||
if root_start_row > line or root_end_row < line then
|
||||
return
|
||||
end
|
||||
|
||||
local state = self:get_highlight_state(tstree)
|
||||
local highlighter_query = self:get_query(tree:lang())
|
||||
|
||||
-- Some injected languages may not have highlight queries.
|
||||
if not highlighter_query:query() then return end
|
||||
if not highlighter_query:query() then
|
||||
return
|
||||
end
|
||||
|
||||
if state.iter == nil then
|
||||
state.iter = highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1)
|
||||
@@ -274,14 +286,17 @@ local function on_line_impl(self, buf, line)
|
||||
while line >= state.next_row do
|
||||
local capture, node, metadata = state.iter()
|
||||
|
||||
if capture == nil then break end
|
||||
if capture == nil then
|
||||
break
|
||||
end
|
||||
|
||||
local start_row, start_col, end_row, end_col = node:range()
|
||||
local hl = highlighter_query.hl_cache[capture]
|
||||
|
||||
if hl and end_row >= line then
|
||||
a.nvim_buf_set_extmark(buf, ns, start_row, start_col,
|
||||
{ end_line = end_row, end_col = end_col,
|
||||
a.nvim_buf_set_extmark(buf, ns, start_row, start_col, {
|
||||
end_line = end_row,
|
||||
end_col = end_col,
|
||||
hl_group = hl,
|
||||
ephemeral = true,
|
||||
priority = tonumber(metadata.priority) or 100, -- Low but leaves room below
|
||||
@@ -298,7 +313,9 @@ end
|
||||
---@private
|
||||
function TSHighlighter._on_line(_, _win, buf, line, _)
|
||||
local self = TSHighlighter.active[buf]
|
||||
if not self then return end
|
||||
if not self then
|
||||
return
|
||||
end
|
||||
|
||||
on_line_impl(self, buf, line)
|
||||
end
|
||||
@@ -324,9 +341,9 @@ function TSHighlighter._on_win(_, _win, buf, _topline)
|
||||
end
|
||||
|
||||
a.nvim_set_decoration_provider(ns, {
|
||||
on_buf = TSHighlighter._on_buf;
|
||||
on_win = TSHighlighter._on_win;
|
||||
on_line = TSHighlighter._on_line;
|
||||
on_buf = TSHighlighter._on_buf,
|
||||
on_win = TSHighlighter._on_win,
|
||||
on_line = TSHighlighter._on_line,
|
||||
})
|
||||
|
||||
return TSHighlighter
|
||||
|
@@ -28,7 +28,9 @@ function M.require_language(lang, path, silent)
|
||||
end
|
||||
|
||||
if silent then
|
||||
return pcall(function() vim._ts_add_language(path, lang) end)
|
||||
return pcall(function()
|
||||
vim._ts_add_language(path, lang)
|
||||
end)
|
||||
else
|
||||
vim._ts_add_language(path, lang)
|
||||
end
|
||||
|
@@ -1,6 +1,6 @@
|
||||
local a = vim.api
|
||||
local query = require'vim.treesitter.query'
|
||||
local language = require'vim.treesitter.language'
|
||||
local query = require('vim.treesitter.query')
|
||||
local language = require('vim.treesitter.language')
|
||||
|
||||
local LanguageTree = {}
|
||||
LanguageTree.__index = LanguageTree
|
||||
@@ -32,9 +32,10 @@ function LanguageTree.new(source, lang, opts)
|
||||
_regions = {},
|
||||
_trees = {},
|
||||
_opts = opts,
|
||||
_injection_query = injections[lang]
|
||||
and query.parse_query(lang, injections[lang])
|
||||
or query.get_query(lang, "injections"),
|
||||
_injection_query = injections[lang] and query.parse_query(lang, injections[lang]) or query.get_query(
|
||||
lang,
|
||||
'injections'
|
||||
),
|
||||
_valid = false,
|
||||
_parser = vim._create_ts_parser(lang),
|
||||
_callbacks = {
|
||||
@@ -42,11 +43,10 @@ function LanguageTree.new(source, lang, opts)
|
||||
bytes = {},
|
||||
detach = {},
|
||||
child_added = {},
|
||||
child_removed = {}
|
||||
child_removed = {},
|
||||
},
|
||||
}, LanguageTree)
|
||||
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -264,11 +264,11 @@ end
|
||||
---@param regions A list of regions this tree should manage and parse.
|
||||
function LanguageTree:set_included_regions(regions)
|
||||
-- TODO(vigoux): I don't think string parsers are useful for now
|
||||
if type(self._source) == "number" then
|
||||
if type(self._source) == 'number' then
|
||||
-- Transform the tables from 4 element long to 6 element long (with byte offset)
|
||||
for _, region in ipairs(regions) do
|
||||
for i, range in ipairs(region) do
|
||||
if type(range) == "table" and #range == 4 then
|
||||
if type(range) == 'table' and #range == 4 then
|
||||
local start_row, start_col, end_row, end_col = unpack(range)
|
||||
-- Easy case, this is a buffer parser
|
||||
-- TODO(vigoux): proper byte computation here, and account for EOL ?
|
||||
@@ -303,7 +303,9 @@ end
|
||||
--- instead of using the entire nodes range.
|
||||
---@private
|
||||
function LanguageTree:_get_injections()
|
||||
if not self._injection_query then return {} end
|
||||
if not self._injection_query then
|
||||
return {}
|
||||
end
|
||||
|
||||
local injections = {}
|
||||
|
||||
@@ -311,7 +313,9 @@ function LanguageTree:_get_injections()
|
||||
local root_node = tree:root()
|
||||
local start_line, _, end_line, _ = root_node:range()
|
||||
|
||||
for pattern, match, metadata in self._injection_query:iter_matches(root_node, self._source, start_line, end_line+1) do
|
||||
for pattern, match, metadata in
|
||||
self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1)
|
||||
do
|
||||
local lang = nil
|
||||
local ranges = {}
|
||||
local combined = metadata.combined
|
||||
@@ -322,7 +326,7 @@ function LanguageTree:_get_injections()
|
||||
local content = metadata.content
|
||||
|
||||
-- Allow for captured nodes to be used
|
||||
if type(content) == "number" then
|
||||
if type(content) == 'number' then
|
||||
content = { match[content] }
|
||||
end
|
||||
|
||||
@@ -342,15 +346,15 @@ function LanguageTree:_get_injections()
|
||||
local name = self._injection_query.captures[id]
|
||||
|
||||
-- Lang should override any other language tag
|
||||
if name == "language" and not lang then
|
||||
if name == 'language' and not lang then
|
||||
lang = query.get_node_text(node, self._source)
|
||||
elseif name == "combined" then
|
||||
elseif name == 'combined' then
|
||||
combined = true
|
||||
elseif name == "content" and #ranges == 0 then
|
||||
elseif name == 'content' and #ranges == 0 then
|
||||
table.insert(ranges, node)
|
||||
-- Ignore any tags that start with "_"
|
||||
-- Allows for other tags to be used in matches
|
||||
elseif string.sub(name, 1, 1) ~= "_" then
|
||||
elseif string.sub(name, 1, 1) ~= '_' then
|
||||
if not lang then
|
||||
lang = name
|
||||
end
|
||||
@@ -414,10 +418,19 @@ function LanguageTree:_do_callback(cb_name, ...)
|
||||
end
|
||||
|
||||
---@private
|
||||
function LanguageTree:_on_bytes(bufnr, changed_tick,
|
||||
start_row, start_col, start_byte,
|
||||
old_row, old_col, old_byte,
|
||||
new_row, new_col, new_byte)
|
||||
function LanguageTree:_on_bytes(
|
||||
bufnr,
|
||||
changed_tick,
|
||||
start_row,
|
||||
start_col,
|
||||
start_byte,
|
||||
old_row,
|
||||
old_col,
|
||||
old_byte,
|
||||
new_row,
|
||||
new_col,
|
||||
new_byte
|
||||
)
|
||||
self:invalidate()
|
||||
|
||||
local old_end_col = old_col + ((old_row == 0) and start_col or 0)
|
||||
@@ -426,16 +439,33 @@ function LanguageTree:_on_bytes(bufnr, changed_tick,
|
||||
-- Edit all trees recursively, together BEFORE emitting a bytes callback.
|
||||
-- In most cases this callback should only be called from the root tree.
|
||||
self:for_each_tree(function(tree)
|
||||
tree:edit(start_byte,start_byte+old_byte,start_byte+new_byte,
|
||||
start_row, start_col,
|
||||
start_row+old_row, old_end_col,
|
||||
start_row+new_row, new_end_col)
|
||||
tree:edit(
|
||||
start_byte,
|
||||
start_byte + old_byte,
|
||||
start_byte + new_byte,
|
||||
start_row,
|
||||
start_col,
|
||||
start_row + old_row,
|
||||
old_end_col,
|
||||
start_row + new_row,
|
||||
new_end_col
|
||||
)
|
||||
end)
|
||||
|
||||
self:_do_callback('bytes', bufnr, changed_tick,
|
||||
start_row, start_col, start_byte,
|
||||
old_row, old_col, old_byte,
|
||||
new_row, new_col, new_byte)
|
||||
self:_do_callback(
|
||||
'bytes',
|
||||
bufnr,
|
||||
changed_tick,
|
||||
start_row,
|
||||
start_col,
|
||||
start_byte,
|
||||
old_row,
|
||||
old_col,
|
||||
old_byte,
|
||||
new_row,
|
||||
new_col,
|
||||
new_byte
|
||||
)
|
||||
end
|
||||
|
||||
---@private
|
||||
@@ -443,7 +473,6 @@ function LanguageTree:_on_reload()
|
||||
self:invalidate(true)
|
||||
end
|
||||
|
||||
|
||||
---@private
|
||||
function LanguageTree:_on_detach(...)
|
||||
self:invalidate(true)
|
||||
@@ -459,7 +488,9 @@ end
|
||||
--- - `on_child_added` : emitted when a child is added to the tree.
|
||||
--- - `on_child_removed` : emitted when a child is removed from the tree.
|
||||
function LanguageTree:register_cbs(cbs)
|
||||
if not cbs then return end
|
||||
if not cbs then
|
||||
return
|
||||
end
|
||||
|
||||
if cbs.on_changedtree then
|
||||
table.insert(self._callbacks.changedtree, cbs.on_changedtree)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
local a = vim.api
|
||||
local language = require'vim.treesitter.language'
|
||||
local language = require('vim.treesitter.language')
|
||||
|
||||
-- query: pattern matching on trees
|
||||
-- predicate matching is implemented in lua
|
||||
@@ -43,7 +43,9 @@ function M.get_query_files(lang, query_name, is_included)
|
||||
local query_path = string.format('queries/%s/%s.scm', lang, query_name)
|
||||
local lang_files = dedupe_files(a.nvim_get_runtime_file(query_path, true))
|
||||
|
||||
if #lang_files == 0 then return {} end
|
||||
if #lang_files == 0 then
|
||||
return {}
|
||||
end
|
||||
|
||||
local base_langs = {}
|
||||
|
||||
@@ -52,7 +54,7 @@ function M.get_query_files(lang, query_name, is_included)
|
||||
-- ;+ inherits: ({language},)*{language}
|
||||
--
|
||||
-- {language} ::= {lang} | ({lang})
|
||||
local MODELINE_FORMAT = "^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$"
|
||||
local MODELINE_FORMAT = '^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$'
|
||||
|
||||
for _, file in ipairs(lang_files) do
|
||||
local modeline = safe_read(file, '*l')
|
||||
@@ -62,7 +64,7 @@ function M.get_query_files(lang, query_name, is_included)
|
||||
|
||||
if langlist then
|
||||
for _, incllang in ipairs(vim.split(langlist, ',', true)) do
|
||||
local is_optional = incllang:match("%(.*%)")
|
||||
local is_optional = incllang:match('%(.*%)')
|
||||
|
||||
if is_optional then
|
||||
if not is_included then
|
||||
@@ -142,7 +144,7 @@ local query_cache = setmetatable({}, {
|
||||
__index = function(tbl, key)
|
||||
rawset(tbl, key, {})
|
||||
return rawget(tbl, key)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
--- Parse {query} as a string. (If the query is in a file, the caller
|
||||
@@ -185,7 +187,7 @@ function M.get_node_text(node, source)
|
||||
local start_row, start_col, start_byte = node:start()
|
||||
local end_row, end_col, end_byte = node:end_()
|
||||
|
||||
if type(source) == "number" then
|
||||
if type(source) == 'number' then
|
||||
local lines
|
||||
local eof_row = a.nvim_buf_line_count(source)
|
||||
if start_row >= eof_row then
|
||||
@@ -208,8 +210,8 @@ function M.get_node_text(node, source)
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(lines, "\n")
|
||||
elseif type(source) == "string" then
|
||||
return table.concat(lines, '\n')
|
||||
elseif type(source) == 'string' then
|
||||
return source:sub(start_byte + 1, end_byte)
|
||||
end
|
||||
end
|
||||
@@ -217,12 +219,12 @@ end
|
||||
-- Predicate handler receive the following arguments
|
||||
-- (match, pattern, bufnr, predicate)
|
||||
local predicate_handlers = {
|
||||
["eq?"] = function(match, _, source, predicate)
|
||||
['eq?'] = function(match, _, source, predicate)
|
||||
local node = match[predicate[2]]
|
||||
local node_text = M.get_node_text(node, source)
|
||||
|
||||
local str
|
||||
if type(predicate[3]) == "string" then
|
||||
if type(predicate[3]) == 'string' then
|
||||
-- (#eq? @aa "foo")
|
||||
str = predicate[3]
|
||||
else
|
||||
@@ -237,13 +239,13 @@ local predicate_handlers = {
|
||||
return true
|
||||
end,
|
||||
|
||||
["lua-match?"] = function(match, _, source, predicate)
|
||||
['lua-match?'] = function(match, _, source, predicate)
|
||||
local node = match[predicate[2]]
|
||||
local regex = predicate[3]
|
||||
return string.find(M.get_node_text(node, source), regex)
|
||||
end,
|
||||
|
||||
["match?"] = (function()
|
||||
['match?'] = (function()
|
||||
local magic_prefixes = { ['\\v'] = true, ['\\m'] = true, ['\\M'] = true, ['\\V'] = true }
|
||||
---@private
|
||||
local function check_magic(str)
|
||||
@@ -258,7 +260,7 @@ local predicate_handlers = {
|
||||
local res = vim.regex(check_magic(pattern))
|
||||
rawset(t, pattern, res)
|
||||
return res
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
return function(match, _, source, pred)
|
||||
@@ -268,7 +270,7 @@ local predicate_handlers = {
|
||||
end
|
||||
end)(),
|
||||
|
||||
["contains?"] = function(match, _, source, predicate)
|
||||
['contains?'] = function(match, _, source, predicate)
|
||||
local node = match[predicate[2]]
|
||||
local node_text = M.get_node_text(node, source)
|
||||
|
||||
@@ -281,19 +283,19 @@ local predicate_handlers = {
|
||||
return false
|
||||
end,
|
||||
|
||||
["any-of?"] = function(match, _, source, predicate)
|
||||
['any-of?'] = function(match, _, source, predicate)
|
||||
local node = match[predicate[2]]
|
||||
local node_text = M.get_node_text(node, source)
|
||||
|
||||
-- Since 'predicate' will not be used by callers of this function, use it
|
||||
-- to store a string set built from the list of words to check against.
|
||||
local string_set = predicate["string_set"]
|
||||
local string_set = predicate['string_set']
|
||||
if not string_set then
|
||||
string_set = {}
|
||||
for i = 3, #predicate do
|
||||
string_set[predicate[i]] = true
|
||||
end
|
||||
predicate["string_set"] = string_set
|
||||
predicate['string_set'] = string_set
|
||||
end
|
||||
|
||||
return string_set[node_text]
|
||||
@@ -301,15 +303,14 @@ local predicate_handlers = {
|
||||
}
|
||||
|
||||
-- As we provide lua-match? also expose vim-match?
|
||||
predicate_handlers["vim-match?"] = predicate_handlers["match?"]
|
||||
|
||||
predicate_handlers['vim-match?'] = predicate_handlers['match?']
|
||||
|
||||
-- Directives store metadata or perform side effects against a match.
|
||||
-- Directives should always end with a `!`.
|
||||
-- Directive handler receive the following arguments
|
||||
-- (match, pattern, bufnr, predicate, metadata)
|
||||
local directive_handlers = {
|
||||
["set!"] = function(_, _, _, pred, metadata)
|
||||
['set!'] = function(_, _, _, pred, metadata)
|
||||
if #pred == 4 then
|
||||
-- (#set! @capture "key" "value")
|
||||
local capture = pred[2]
|
||||
@@ -324,7 +325,7 @@ local directive_handlers = {
|
||||
end,
|
||||
-- Shifts the range of a node.
|
||||
-- Example: (#offset! @_node 0 1 0 -1)
|
||||
["offset!"] = function(match, _, _, pred, metadata)
|
||||
['offset!'] = function(match, _, _, pred, metadata)
|
||||
local offset_node = match[pred[2]]
|
||||
local range = { offset_node:range() }
|
||||
local start_row_offset = pred[3] or 0
|
||||
@@ -341,7 +342,7 @@ local directive_handlers = {
|
||||
if range[1] < range[3] or (range[1] == range[3] and range[2] <= range[4]) then
|
||||
metadata.content = { range }
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
--- Adds a new predicate to be used in queries
|
||||
@@ -351,7 +352,7 @@ local directive_handlers = {
|
||||
--- signature will be (match, pattern, bufnr, predicate)
|
||||
function M.add_predicate(name, handler, force)
|
||||
if predicate_handlers[name] and not force then
|
||||
error(string.format("Overriding %s", name))
|
||||
error(string.format('Overriding %s', name))
|
||||
end
|
||||
|
||||
predicate_handlers[name] = handler
|
||||
@@ -364,7 +365,7 @@ end
|
||||
--- signature will be (match, pattern, bufnr, predicate)
|
||||
function M.add_directive(name, handler, force)
|
||||
if directive_handlers[name] and not force then
|
||||
error(string.format("Overriding %s", name))
|
||||
error(string.format('Overriding %s', name))
|
||||
end
|
||||
|
||||
directive_handlers[name] = handler
|
||||
@@ -387,7 +388,7 @@ end
|
||||
|
||||
---@private
|
||||
local function is_directive(name)
|
||||
return string.sub(name, -1) == "!"
|
||||
return string.sub(name, -1) == '!'
|
||||
end
|
||||
|
||||
---@private
|
||||
@@ -404,7 +405,7 @@ function Query:match_preds(match, pattern, source)
|
||||
|
||||
-- Skip over directives... they will get processed after all the predicates.
|
||||
if not is_directive(pred[1]) then
|
||||
if string.sub(pred[1], 1, 4) == "not-" then
|
||||
if string.sub(pred[1], 1, 4) == 'not-' then
|
||||
pred_name = string.sub(pred[1], 5)
|
||||
is_not = true
|
||||
else
|
||||
@@ -415,7 +416,7 @@ function Query:match_preds(match, pattern, source)
|
||||
local handler = predicate_handlers[pred_name]
|
||||
|
||||
if not handler then
|
||||
error(string.format("No handler for %s", pred[1]))
|
||||
error(string.format('No handler for %s', pred[1]))
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -438,7 +439,7 @@ function Query:apply_directives(match, pattern, source, metadata)
|
||||
local handler = directive_handlers[pred[1]]
|
||||
|
||||
if not handler then
|
||||
error(string.format("No handler for %s", pred[1]))
|
||||
error(string.format('No handler for %s', pred[1]))
|
||||
return
|
||||
end
|
||||
|
||||
@@ -447,7 +448,6 @@ function Query:apply_directives(match, pattern, source, metadata)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Returns the start and stop value if set else the node's range.
|
||||
-- When the node's range is used, the stop is incremented by 1
|
||||
-- to make the search inclusive.
|
||||
@@ -492,7 +492,7 @@ end
|
||||
---@returns The matching capture id
|
||||
---@returns The captured node
|
||||
function Query:iter_captures(node, source, start, stop)
|
||||
if type(source) == "number" and source == 0 then
|
||||
if type(source) == 'number' and source == 0 then
|
||||
source = vim.api.nvim_get_current_buf()
|
||||
end
|
||||
|
||||
@@ -549,7 +549,7 @@ end
|
||||
---@returns The matching pattern id
|
||||
---@returns The matching match
|
||||
function Query:iter_matches(node, source, start, stop)
|
||||
if type(source) == "number" and source == 0 then
|
||||
if type(source) == 'number' and source == 0 then
|
||||
source = vim.api.nvim_get_current_buf()
|
||||
end
|
||||
|
||||
|
@@ -37,10 +37,10 @@ local M = {}
|
||||
--- </pre>
|
||||
|
||||
function M.select(items, opts, on_choice)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
items = { items, 'table', false },
|
||||
on_choice = { on_choice, 'function', false },
|
||||
}
|
||||
})
|
||||
opts = opts or {}
|
||||
local choices = { opts.prompt or 'Select one of:' }
|
||||
local format_item = opts.format_item or tostring
|
||||
@@ -83,9 +83,9 @@ end
|
||||
--- end)
|
||||
--- </pre>
|
||||
function M.input(opts, on_confirm)
|
||||
vim.validate {
|
||||
vim.validate({
|
||||
on_confirm = { on_confirm, 'function', false },
|
||||
}
|
||||
})
|
||||
|
||||
opts = opts or {}
|
||||
local input = vim.fn.input(opts)
|
||||
|
@@ -3,7 +3,6 @@
|
||||
-- https://tools.ietf.org/html/rfc2732
|
||||
-- https://tools.ietf.org/html/rfc2396
|
||||
|
||||
|
||||
local uri_decode
|
||||
do
|
||||
local schar = string.char
|
||||
@@ -14,7 +13,7 @@ do
|
||||
return schar(tonumber(hex, 16))
|
||||
end
|
||||
uri_decode = function(str)
|
||||
return str:gsub("%%([a-fA-F0-9][a-fA-F0-9])", hex_to_char)
|
||||
return str:gsub('%%([a-fA-F0-9][a-fA-F0-9])', hex_to_char)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,33 +22,36 @@ do
|
||||
local PATTERNS = {
|
||||
--- RFC 2396
|
||||
-- https://tools.ietf.org/html/rfc2396#section-2.2
|
||||
rfc2396 = "^A-Za-z0-9%-_.!~*'()";
|
||||
rfc2396 = "^A-Za-z0-9%-_.!~*'()",
|
||||
--- RFC 2732
|
||||
-- https://tools.ietf.org/html/rfc2732
|
||||
rfc2732 = "^A-Za-z0-9%-_.!~*'()[]";
|
||||
rfc2732 = "^A-Za-z0-9%-_.!~*'()[]",
|
||||
--- RFC 3986
|
||||
-- https://tools.ietf.org/html/rfc3986#section-2.2
|
||||
rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/";
|
||||
rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/",
|
||||
}
|
||||
local sbyte, tohex = string.byte
|
||||
if jit then
|
||||
tohex = require'bit'.tohex
|
||||
tohex = require('bit').tohex
|
||||
else
|
||||
tohex = function(b) return string.format("%02x", b) end
|
||||
tohex = function(b)
|
||||
return string.format('%02x', b)
|
||||
end
|
||||
end
|
||||
|
||||
---@private
|
||||
local function percent_encode_char(char)
|
||||
return "%"..tohex(sbyte(char), 2)
|
||||
return '%' .. tohex(sbyte(char), 2)
|
||||
end
|
||||
uri_encode = function(text, rfc)
|
||||
if not text then return end
|
||||
if not text then
|
||||
return
|
||||
end
|
||||
local pattern = PATTERNS[rfc] or PATTERNS.rfc3986
|
||||
return text:gsub("(["..pattern.."])", percent_encode_char)
|
||||
return text:gsub('([' .. pattern .. '])', percent_encode_char)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@private
|
||||
local function is_windows_file_uri(uri)
|
||||
return uri:match('^file:/+[a-zA-Z]:') ~= nil
|
||||
@@ -59,16 +61,16 @@ end
|
||||
---@param path string Path to file
|
||||
---@return string URI
|
||||
local function uri_from_fname(path)
|
||||
local volume_path, fname = path:match("^([a-zA-Z]:)(.*)")
|
||||
local volume_path, fname = path:match('^([a-zA-Z]:)(.*)')
|
||||
local is_windows = volume_path ~= nil
|
||||
if is_windows then
|
||||
path = volume_path..uri_encode(fname:gsub("\\", "/"))
|
||||
path = volume_path .. uri_encode(fname:gsub('\\', '/'))
|
||||
else
|
||||
path = uri_encode(path)
|
||||
end
|
||||
local uri_parts = {"file://"}
|
||||
local uri_parts = { 'file://' }
|
||||
if is_windows then
|
||||
table.insert(uri_parts, "/")
|
||||
table.insert(uri_parts, '/')
|
||||
end
|
||||
table.insert(uri_parts, path)
|
||||
return table.concat(uri_parts)
|
||||
@@ -82,11 +84,11 @@ local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):[a-zA-Z]:.*'
|
||||
---@return string URI
|
||||
local function uri_from_bufnr(bufnr)
|
||||
local fname = vim.api.nvim_buf_get_name(bufnr)
|
||||
local volume_path = fname:match("^([a-zA-Z]:).*")
|
||||
local volume_path = fname:match('^([a-zA-Z]:).*')
|
||||
local is_windows = volume_path ~= nil
|
||||
local scheme
|
||||
if is_windows then
|
||||
fname = fname:gsub("\\", "/")
|
||||
fname = fname:gsub('\\', '/')
|
||||
scheme = fname:match(WINDOWS_URI_SCHEME_PATTERN)
|
||||
else
|
||||
scheme = fname:match(URI_SCHEME_PATTERN)
|
||||
|
@@ -3,4 +3,4 @@
|
||||
-- Last Change: 2022 Apr 13
|
||||
|
||||
-- it's a lisp!
|
||||
vim.cmd [[ runtime! syntax/lisp.vim ]]
|
||||
vim.cmd([[ runtime! syntax/lisp.vim ]])
|
||||
|
Reference in New Issue
Block a user