Merge pull request #18487 from clason/stylua

CI: format and lint runtime with Stylua
This commit is contained in:
Christian Clason
2022-05-11 08:54:24 +02:00
committed by GitHub
41 changed files with 4223 additions and 3571 deletions

View File

@@ -86,6 +86,13 @@ jobs:
name: clint-full name: clint-full
run: ./ci/run_lint.sh clint-full run: ./ci/run_lint.sh clint-full
- if: "!cancelled()"
name: stylua
uses: JohnnyMorganz/stylua-action@1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --check runtime/
- if: "!cancelled()" - if: "!cancelled()"
name: lualint name: lualint
run: ./ci/run_lint.sh lualint run: ./ci/run_lint.sh lualint

6
.stylua.toml Normal file
View File

@@ -0,0 +1,6 @@
column_width = 120
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 2
quote_style = "AutoPreferSingle"
call_parentheses = "Always"

3
.styluaignore Normal file
View File

@@ -0,0 +1,3 @@
/scripts
/src
/test

View File

@@ -146,9 +146,16 @@ functionaltest: | nvim
functionaltest-lua: | nvim functionaltest-lua: | nvim
+$(BUILD_TOOL) -C build functionaltest-lua +$(BUILD_TOOL) -C build functionaltest-lua
stylua:
stylua --check runtime/
lualint: | build/.ran-cmake deps lualint: | build/.ran-cmake deps
$(BUILD_TOOL) -C build lualint $(BUILD_TOOL) -C build lualint
_opt_stylua:
@command -v stylua && { $(MAKE) stylua; exit $$?; } \
|| echo "SKIP: stylua (stylua not found)"
shlint: shlint:
@shellcheck --version | head -n 2 @shellcheck --version | head -n 2
shellcheck scripts/vim-patch.sh shellcheck scripts/vim-patch.sh
@@ -214,7 +221,7 @@ appimage:
appimage-%: appimage-%:
bash scripts/genappimage.sh $* bash scripts/genappimage.sh $*
lint: check-single-includes clint lualint _opt_pylint _opt_shlint _opt_commitlint lint: check-single-includes clint _opt_stylua lualint _opt_pylint _opt_shlint _opt_commitlint
# Generic pattern rules, allowing for `make build/bin/nvim` etc. # Generic pattern rules, allowing for `make build/bin/nvim` etc.
# Does not work with "Unix Makefiles". # Does not work with "Unix Makefiles".
@@ -226,4 +233,4 @@ $(DEPS_BUILD_DIR)/%: phony_force
$(BUILD_TOOL) -C $(DEPS_BUILD_DIR) $(patsubst $(DEPS_BUILD_DIR)/%,%,$@) $(BUILD_TOOL) -C $(DEPS_BUILD_DIR) $(patsubst $(DEPS_BUILD_DIR)/%,%,$@)
endif endif
.PHONY: test lualint pylint shlint functionaltest unittest lint clint clean distclean nvim libnvim cmake deps install appimage checkprefix commitlint .PHONY: test stylua lualint pylint shlint functionaltest unittest lint clint clean distclean nvim libnvim cmake deps install appimage checkprefix commitlint

View File

@@ -7,23 +7,23 @@ if vim.g.do_filetype_lua ~= 1 then
return return
end end
vim.api.nvim_create_augroup("filetypedetect", {clear = false}) vim.api.nvim_create_augroup('filetypedetect', { clear = false })
vim.api.nvim_create_autocmd({"BufRead", "BufNewFile"}, { vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, {
group = "filetypedetect", group = 'filetypedetect',
callback = function() callback = function()
vim.filetype.match(vim.fn.expand("<afile>")) vim.filetype.match(vim.fn.expand('<afile>'))
end, end,
}) })
-- These *must* be sourced after the autocommand above is created -- These *must* be sourced after the autocommand above is created
if not vim.g.did_load_ftdetect then if not vim.g.did_load_ftdetect then
vim.cmd [[ vim.cmd([[
augroup filetypedetect augroup filetypedetect
runtime! ftdetect/*.vim runtime! ftdetect/*.vim
runtime! ftdetect/*.lua runtime! ftdetect/*.lua
augroup END augroup END
]] ]])
end end
-- Set a marker so that the ftdetect scripts are not sourced a second time by filetype.vim -- 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 filetype.vim is disabled, set up the autocmd to use scripts.vim
if vim.g.did_load_filetypes then if vim.g.did_load_filetypes then
vim.api.nvim_create_autocmd({"BufRead", "BufNewFile"}, { vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, {
group = "filetypedetect", group = 'filetypedetect',
command = "if !did_filetype() && expand('<amatch>') !~ g:ft_ignore_pat | runtime! scripts.vim | endif", command = "if !did_filetype() && expand('<amatch>') !~ g:ft_ignore_pat | runtime! scripts.vim | endif",
}) })
vim.api.nvim_create_autocmd("StdinReadPost", { vim.api.nvim_create_autocmd('StdinReadPost', {
group = "filetypedetect", group = 'filetypedetect',
command = "if !did_filetype() | runtime! scripts.vim | endif", command = 'if !did_filetype() | runtime! scripts.vim | endif',
}) })
end end
if not vim.g.ft_ignore_pat then 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 end

View File

@@ -3,4 +3,4 @@
-- Last Change: 2022 Mar 29 -- Last Change: 2022 Mar 29
-- it's a lisp! -- it's a lisp!
vim.cmd [[ runtime! ftplugin/lisp.vim ]] vim.cmd([[ runtime! ftplugin/lisp.vim ]])

View File

@@ -3,4 +3,4 @@
-- Last Change: 2022 Mar 29 -- Last Change: 2022 Mar 29
-- it's a lisp! -- it's a lisp!
vim.cmd [[ runtime! indent/lisp.vim ]] vim.cmd([[ runtime! indent/lisp.vim ]])

View File

@@ -8,7 +8,7 @@ local function highlight_line(line, linenr)
local overstrike, escape = false, false local overstrike, escape = false, false
local hls = {} -- Store highlight groups as { attr, start, final } local hls = {} -- Store highlight groups as { attr, start, final }
local NONE, BOLD, UNDERLINE, ITALIC = 0, 1, 2, 3 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 attr = NONE
local byte = 0 -- byte offset 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 -- 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 -- a leading byte (0xc0 and above) and continuation bytes (0x80 to 0xbf, or
-- decimal 128 to 191). -- 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 if overstrike then
local last_hl = hls[#hls] local last_hl = hls[#hls]
if char == prev_char then 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 -- We only want to match against SGR sequences, which consist of ESC
-- followed by '[', then a series of parameter and intermediate bytes in -- 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) -- 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 -- Ignore escape sequences with : characters, as specified by ITU's T.416
-- Open Document Architecture and interchange format. -- Open Document Architecture and interchange format.
if sgr and not string.find(sgr, ":") then if sgr and not string.find(sgr, ':') then
local match local match
while sgr and #sgr > 0 do while sgr and #sgr > 0 do
-- Match against SGR parameters, which may be separated by ';' -- 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 add_attr_hl(match + 0) -- coerce to number
end end
escape = false 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 -- Stop looking if this isn't a partial CSI sequence
escape = false escape = false
end end
elseif char == "\027" then elseif char == '\027' then
escape = true escape = true
prev_char = '' prev_char = ''
elseif char == "\b" then elseif char == '\b' then
overstrike = true overstrike = true
prev_char = chars[#chars] prev_char = chars[#chars]
byte = byte - #prev_char byte = byte - #prev_char
@@ -143,7 +143,7 @@ local function highlight_line(line, linenr)
hl_groups[hl.attr], hl_groups[hl.attr],
linenr - 1, linenr - 1,
hl.start, hl.start,
hl.final hl.final,
} }
end end
end end
@@ -152,8 +152,8 @@ local function highlight_line(line, linenr)
end end
local function highlight_man_page() local function highlight_man_page()
local mod = vim.api.nvim_buf_get_option(0, "modifiable") local mod = vim.api.nvim_buf_get_option(0, 'modifiable')
vim.api.nvim_buf_set_option(0, "modifiable", true) vim.api.nvim_buf_set_option(0, 'modifiable', true)
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
for i, line in ipairs(lines) do for i, line in ipairs(lines) do
@@ -166,7 +166,7 @@ local function highlight_man_page()
end end
buf_hls = {} buf_hls = {}
vim.api.nvim_buf_set_option(0, "modifiable", mod) vim.api.nvim_buf_set_option(0, 'modifiable', mod)
end end
return { highlight_man_page = highlight_man_page } return { highlight_man_page = highlight_man_page }

View File

@@ -5,13 +5,17 @@ local F = {}
---@param a ---@param a
---@param b ---@param b
function F.if_nil(a, b) function F.if_nil(a, b)
if a == nil then return b end if a == nil then
return b
end
return a return a
end end
-- Use in combination with pcall -- Use in combination with pcall
function F.ok_or_nil(status, ...) function F.ok_or_nil(status, ...)
if not status then return end if not status then
return
end
return ... return ...
end end

View File

@@ -40,26 +40,28 @@ local vim = assert(vim)
-- These are for loading runtime modules lazily since they aren't available in -- These are for loading runtime modules lazily since they aren't available in
-- the nvim binary as specified in executor.c -- the nvim binary as specified in executor.c
for k,v in pairs { for k, v in pairs({
treesitter=true; treesitter = true,
filetype = true; filetype = true,
F=true; F = true,
lsp=true; lsp = true,
highlight=true; highlight = true,
diagnostic=true; diagnostic = true,
keymap=true; keymap = true,
ui=true; ui = true,
} do vim._submodules[k] = v end }) do
vim._submodules[k] = v
end
vim.log = { vim.log = {
levels = { levels = {
TRACE = 0; TRACE = 0,
DEBUG = 1; DEBUG = 1,
INFO = 2; INFO = 2,
WARN = 3; WARN = 3,
ERROR = 4; ERROR = 4,
OFF = 5; OFF = 5,
} },
} }
-- Internal-only until comments in #8107 are addressed. -- 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 if pid == nil or pid <= 0 or type(pid) ~= 'number' then
error('invalid pid') error('invalid pid')
end end
local cmd = { 'ps', '-p', pid, '-o', 'comm=', } local cmd = { 'ps', '-p', pid, '-o', 'comm=' }
local err, name = vim._system(cmd) local err, name = vim._system(cmd)
if 1 == err and vim.trim(name) == '' then if 1 == err and vim.trim(name) == '' then
return {} -- Process not found. return {} -- Process not found.
elseif 0 ~= err then elseif 0 ~= err then
error('command failed: ' .. vim.fn.string(cmd)) error('command failed: ' .. vim.fn.string(cmd))
end end
local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=', }) local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=' })
-- Remove trailing whitespace. -- Remove trailing whitespace.
name = vim.trim(name):gsub('^.*/', '') name = vim.trim(name):gsub('^.*/', '')
ppid = tonumber(ppid) or -1 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 if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then
error('invalid ppid') error('invalid ppid')
end end
local cmd = { 'pgrep', '-P', ppid, } local cmd = { 'pgrep', '-P', ppid }
local err, rv = vim._system(cmd) local err, rv = vim._system(cmd)
if 1 == err and vim.trim(rv) == '' then if 1 == err and vim.trim(rv) == '' then
return {} -- Process not found. return {} -- Process not found.
@@ -252,11 +254,13 @@ end
---@see |vim.schedule()| ---@see |vim.schedule()|
---@see |vim.in_fast_event()| ---@see |vim.in_fast_event()|
function vim.schedule_wrap(cb) function vim.schedule_wrap(cb)
return (function (...) return function(...)
local args = vim.F.pack_len(...) 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 end
end
-- vim.fn.{func}(...) -- vim.fn.{func}(...)
vim.fn = setmetatable({}, { vim.fn = setmetatable({}, {
@@ -264,7 +268,7 @@ vim.fn = setmetatable({}, {
local _fn local _fn
if vim.api[key] ~= nil then if vim.api[key] ~= nil then
_fn = function() _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 end
else else
_fn = function(...) _fn = function(...)
@@ -273,7 +277,7 @@ vim.fn = setmetatable({}, {
end end
t[key] = _fn t[key] = _fn
return _fn return _fn
end end,
}) })
vim.funcref = function(viml_func_name) vim.funcref = function(viml_func_name)
@@ -291,9 +295,9 @@ do
--@private --@private
local function make_dict_accessor(scope, handle) local function make_dict_accessor(scope, handle)
validate { validate({
scope = {scope, 's'}; scope = { scope, 's' },
} })
local mt = {} local mt = {}
function mt:__newindex(k, v) function mt:__newindex(k, v)
return vim._setvar(scope, handle or 0, 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) c2 = vim.str_byteindex(bufline, c2)
end end
else 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 c2 = (l == pos2[1]) and (pos2[2] + (inclusive and 1 or 0)) or -1
end end
table.insert(region, l, { c1, c2 }) table.insert(region, l, { c1, c2 })
@@ -372,19 +376,22 @@ end
---@param timeout Number of milliseconds to wait before calling `fn` ---@param timeout Number of milliseconds to wait before calling `fn`
---@return timer luv timer object ---@return timer luv timer object
function vim.defer_fn(fn, timeout) function vim.defer_fn(fn, timeout)
vim.validate { fn = { fn, 'c', true}; } vim.validate({ fn = { fn, 'c', true } })
local timer = vim.loop.new_timer() 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:stop()
timer:close() timer:close()
fn() fn()
end)) end)
)
return timer return timer
end end
--- Display a notification to the user. --- Display a notification to the user.
--- ---
--- This function can be overridden by plugins to display notifications using a --- 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 return #on_key_cbs
end end
vim.validate { vim.validate({
fn = { fn, 'c', true }, 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 if ns_id == nil or ns_id == 0 then
ns_id = vim.api.nvim_create_namespace('') ns_id = vim.api.nvim_create_namespace('')
@@ -481,10 +488,13 @@ function vim._on_key(char)
end end
if failed_ns_ids[1] then if failed_ns_ids[1] then
error(string.format( error(
string.format(
"Error executing 'on_key' with ns_ids '%s'\n Messages: %s", "Error executing 'on_key' with ns_ids '%s'\n Messages: %s",
table.concat(failed_ns_ids, ", "), table.concat(failed_ns_ids, ', '),
table.concat(failed_messages, "\n"))) table.concat(failed_messages, '\n')
)
)
end end
end end
@@ -511,8 +521,10 @@ function vim._expand_pat(pat, env)
-- Probably just need to do a smarter match than just `:match` -- Probably just need to do a smarter match than just `:match`
-- Get the last part of the pattern -- Get the last part of the pattern
local last_part = pat:match("[%w.:_%[%]'\"]+$") local last_part = pat:match('[%w.:_%[%]\'"]+$')
if not last_part then return {}, 0 end if not last_part then
return {}, 0
end
local parts, search_index = vim._expand_pat_get_parts(last_part) 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 -- Normally, we just have a string
-- Just attempt to get the string directly from the environment -- Just attempt to get the string directly from the environment
if type(part) == "string" then if type(part) == 'string' then
key = part key = part
else else
-- However, sometimes you want to use a variable, and complete on it -- 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) local field = rawget(final_env, key)
if field == nil then if field == nil then
local mt = getmetatable(final_env) 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) field = rawget(mt.__index, key)
elseif final_env == vim and vim._submodules[key] then elseif final_env == vim and vim._submodules[key] then
field = vim[key] field = vim[key]
@@ -571,17 +583,17 @@ function vim._expand_pat(pat, env)
---@private ---@private
local function insert_keys(obj) local function insert_keys(obj)
for k, _ in pairs(obj) do 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) table.insert(keys, k)
end end
end end
end end
if type(final_env) == "table" then if type(final_env) == 'table' then
insert_keys(final_env) insert_keys(final_env)
end end
local mt = getmetatable(final_env) 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) insert_keys(mt.__index)
end end
if final_env == vim then if final_env == vim then
@@ -602,12 +614,12 @@ vim._expand_pat_get_parts = function(lua_string)
for idx = 1, #lua_string do for idx = 1, #lua_string do
local s = lua_string:sub(idx, idx) 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) table.insert(parts, accumulator)
accumulator = '' accumulator = ''
search_index = idx + 1 search_index = idx + 1
elseif s == "[" then elseif s == '[' then
in_brackets = true in_brackets = true
table.insert(parts, accumulator) table.insert(parts, accumulator)
@@ -619,7 +631,7 @@ vim._expand_pat_get_parts = function(lua_string)
in_brackets = false in_brackets = false
search_index = idx + 1 search_index = idx + 1
if string_char == "VAR" then if string_char == 'VAR' then
table.insert(parts, { accumulator }) table.insert(parts, { accumulator })
accumulator = '' accumulator = ''
@@ -631,7 +643,7 @@ vim._expand_pat_get_parts = function(lua_string)
if s == '"' or s == "'" then if s == '"' or s == "'" then
string_char = s string_char = s
elseif s ~= ' ' then elseif s ~= ' ' then
string_char = "VAR" string_char = 'VAR'
accumulator = s accumulator = s
end end
elseif string_char then elseif string_char then
@@ -649,7 +661,9 @@ vim._expand_pat_get_parts = function(lua_string)
end end
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 return parts, search_index
end end
@@ -677,14 +691,14 @@ function vim._cs_remote(rcid, server_addr, connect_error, args)
local function connection_failure_errmsg(consequence) local function connection_failure_errmsg(consequence)
local explanation local explanation
if server_addr == '' then if server_addr == '' then
explanation = "No server specified with --server" explanation = 'No server specified with --server'
else else
explanation = "Failed to connect to '" .. server_addr .. "'" explanation = "Failed to connect to '" .. server_addr .. "'"
if connect_error ~= "" then if connect_error ~= '' then
explanation = explanation .. ": " .. connect_error explanation = explanation .. ': ' .. connect_error
end end
end end
return "E247: " .. explanation .. ". " .. consequence return 'E247: ' .. explanation .. '. ' .. consequence
end end
local f_silent = false local f_silent = false
@@ -718,11 +732,13 @@ function vim._cs_remote(rcid, server_addr, connect_error, args)
if rcid == 0 then if rcid == 0 then
if not f_silent 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 end
else else
local command = {} local command = {}
if f_tab then table.insert(command, 'tab') end if f_tab then
table.insert(command, 'tab')
end
table.insert(command, 'drop') table.insert(command, 'drop')
for i = 2, #args do for i = 2, #args do
table.insert(command, vim.fn.fnameescape(args[i])) table.insert(command, vim.fn.fnameescape(args[i]))
@@ -746,7 +762,7 @@ end
--- from. Defaults to "Nvim". --- from. Defaults to "Nvim".
function vim.deprecate(name, alternative, version, plugin) function vim.deprecate(name, alternative, version, plugin)
local message = name .. ' is deprecated' local message = name .. ' is deprecated'
plugin = plugin or "Nvim" plugin = plugin or 'Nvim'
message = alternative and (message .. ', use ' .. alternative .. ' instead.') or message message = alternative and (message .. ', use ' .. alternative .. ' instead.') or message
message = message .. ' See :h deprecated\nThis function will be removed in ' .. plugin .. ' version ' .. version message = message .. ' See :h deprecated\nThis function will be removed in ' .. plugin .. ' version ' .. version
vim.notify_once(message, vim.log.levels.WARN) vim.notify_once(message, vim.log.levels.WARN)

View File

@@ -17,7 +17,7 @@ end
function vim._load_package(name) function vim._load_package(name)
local basename = name:gsub('%.', '/') 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 }) local found = vim.api.nvim__get_runtime(paths, false, { is_lua = true })
if #found > 0 then if #found > 0 then
local f, err = loadfile(found[1]) local f, err = loadfile(found[1])
@@ -26,7 +26,7 @@ function vim._load_package(name)
local so_paths = {} local so_paths = {}
for _, trail in ipairs(vim._so_trails) do 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) table.insert(so_paths, path)
end end
@@ -37,9 +37,9 @@ function vim._load_package(name)
-- b) replace all dots by underscores -- b) replace all dots by underscores
-- c) prepend "luaopen_" -- c) prepend "luaopen_"
-- So "foo-bar.baz" should result in "luaopen_bar_baz" -- 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 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) return f or error(err)
end end
return nil return nil
@@ -49,7 +49,7 @@ end
table.insert(package.loaders, 2, vim._load_package) table.insert(package.loaders, 2, vim._load_package)
-- builtin functions which always should be available -- builtin functions which always should be available
require'vim.shared' require('vim.shared')
vim._submodules = { inspect = true } vim._submodules = { inspect = true }
@@ -67,7 +67,7 @@ setmetatable(vim, {
return t[key] return t[key]
end end
end end
end end,
}) })
--- <Docs described in |vim.empty_dict()| > --- <Docs described in |vim.empty_dict()| >
@@ -79,5 +79,5 @@ end
-- only on main thread: functions for interacting with editor state -- only on main thread: functions for interacting with editor state
if not vim.is_thread() then if not vim.is_thread() then
require'vim._editor' require('vim._editor')
end end

View File

@@ -13,7 +13,9 @@ local SET_TYPES = setmetatable({
local options_info = {} local options_info = {}
for _, v in pairs(a.nvim_get_all_options_info()) do for _, v in pairs(a.nvim_get_all_options_info()) do
options_info[v.name] = v 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 end
local get_scoped_options = function(scope) local get_scoped_options = function(scope)
@@ -27,19 +29,21 @@ local get_scoped_options = function(scope)
return result return result
end end
local buf_options = get_scoped_options("buf") local buf_options = get_scoped_options('buf')
local glb_options = get_scoped_options("global") local glb_options = get_scoped_options('global')
local win_options = get_scoped_options("win") local win_options = get_scoped_options('win')
local function make_meta_accessor(get, set, del, validator) local function make_meta_accessor(get, set, del, validator)
validator = validator or function() return true end validator = validator or function()
return true
end
validate { validate({
get = {get, 'f'}; get = { get, 'f' },
set = {set, 'f'}; set = { set, 'f' },
del = {del, 'f', true}; del = { del, 'f', true },
validator = {validator, 'f'}; validator = { validator, 'f' },
} })
local mt = {} local mt = {}
function mt:__newindex(k, v) function mt:__newindex(k, v)
@@ -73,7 +77,7 @@ end, vim.fn.setenv)
do -- buffer option accessor do -- buffer option accessor
local function new_buf_opt_accessor(bufnr) local function new_buf_opt_accessor(bufnr)
local function get(k) 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) return new_buf_opt_accessor(k)
end end
@@ -103,7 +107,7 @@ end
do -- window option accessor do -- window option accessor
local function new_win_opt_accessor(winnr) local function new_win_opt_accessor(winnr)
local function get(k) 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) return new_win_opt_accessor(k)
end end
return a.nvim_win_get_option(winnr or 0, k) return a.nvim_win_get_option(winnr or 0, k)
@@ -131,17 +135,19 @@ end
-- vim global option -- vim global option
-- this ONLY sets the global option. like `setglobal` -- this ONLY sets the global option. like `setglobal`
vim.go = make_meta_accessor( vim.go = make_meta_accessor(function(k)
function(k) return a.nvim_get_option_value(k, {scope = "global"}) end, return a.nvim_get_option_value(k, { scope = 'global' })
function(k, v) return a.nvim_set_option_value(k, v, {scope = "global"}) end end, function(k, v)
) return a.nvim_set_option_value(k, v, { scope = 'global' })
end)
-- vim `set` style options. -- vim `set` style options.
-- it has no additional metamethod magic. -- it has no additional metamethod magic.
vim.o = make_meta_accessor( vim.o = make_meta_accessor(function(k)
function(k) return a.nvim_get_option_value(k, {}) end, return a.nvim_get_option_value(k, {})
function(k, v) return a.nvim_set_option_value(k, v, {}) end end, function(k, v)
) return a.nvim_set_option_value(k, v, {})
end)
---@brief [[ ---@brief [[
--- vim.opt, vim.opt_local and vim.opt_global implementation --- 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 --- Preserves the order and does not mutate the original list
local remove_duplicate_values = function(t) local remove_duplicate_values = function(t)
local result, seen = {}, {} 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 for _, v in ipairs(t) do
if not seen[v] then if not seen[v] then
table.insert(result, v) table.insert(result, v)
@@ -184,24 +192,28 @@ local OptionTypes = setmetatable({
MAP = 4, MAP = 4,
SET = 5, SET = 5,
}, { }, {
__index = function(_, k) error("Not a valid OptionType: " .. k) end, __index = function(_, k)
__newindex = function(_, k) error("Cannot set a new OptionType: " .. k) end, 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. --- Convert a vimoption_T style dictionary to the correct OptionType associated with it.
---@return OptionType ---@return OptionType
local get_option_type = function(name, info) local get_option_type = function(name, info)
if info.type == "boolean" then if info.type == 'boolean' then
return OptionTypes.BOOLEAN return OptionTypes.BOOLEAN
elseif info.type == "number" then elseif info.type == 'number' then
return OptionTypes.NUMBER return OptionTypes.NUMBER
elseif info.type == "string" then elseif info.type == 'string' then
if not info.commalist and not info.flaglist then if not info.commalist and not info.flaglist then
return OptionTypes.STRING return OptionTypes.STRING
end end
if key_value_options[name] then 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 return OptionTypes.MAP
end end
@@ -211,13 +223,12 @@ local get_option_type = function(name, info)
return OptionTypes.ARRAY return OptionTypes.ARRAY
end end
error("Fallthrough in OptionTypes") error('Fallthrough in OptionTypes')
else else
error("Not a known info.type:" .. info.type) error('Not a known info.type:' .. info.type)
end end
end end
-- Check whether the OptionTypes is allowed for vim.opt -- Check whether the OptionTypes is allowed for vim.opt
-- If it does not match, throw an error which indicates which option causes the error. -- If it does not match, throw an error which indicates which option causes the error.
local function assert_valid_value(name, value, types) local function assert_valid_value(name, value, types)
@@ -228,16 +239,18 @@ local function assert_valid_value(name, value, types)
end end
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 end
local valid_types = { local valid_types = {
[OptionTypes.BOOLEAN] = { "boolean" }, [OptionTypes.BOOLEAN] = { 'boolean' },
[OptionTypes.NUMBER] = { "number" }, [OptionTypes.NUMBER] = { 'number' },
[OptionTypes.STRING] = { "string" }, [OptionTypes.STRING] = { 'string' },
[OptionTypes.SET] = { "string", "table" }, [OptionTypes.SET] = { 'string', 'table' },
[OptionTypes.ARRAY] = { "string", "table" }, [OptionTypes.ARRAY] = { 'string', 'table' },
[OptionTypes.MAP] = { "string", "table" }, [OptionTypes.MAP] = { 'string', 'table' },
} }
--- Convert a lua value to a vimoption_T value --- 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. -- Map of functions to take a Lua style value and convert to vimoption_T style value.
-- Each function takes (info, lua_value) -> vim_value -- Each function takes (info, lua_value) -> vim_value
local to_vim_value = { local to_vim_value = {
[OptionTypes.BOOLEAN] = function(_, value) return value end, [OptionTypes.BOOLEAN] = function(_, value)
[OptionTypes.NUMBER] = function(_, value) return value end, return value
[OptionTypes.STRING] = function(_, value) return value end, end,
[OptionTypes.NUMBER] = function(_, value)
return value
end,
[OptionTypes.STRING] = function(_, value)
return value
end,
[OptionTypes.SET] = function(info, value) [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 if info.flaglist and info.commalist then
local keys = {} local keys = {}
@@ -261,7 +282,7 @@ local convert_value_to_vim = (function()
end end
table.sort(keys) table.sort(keys)
return table.concat(keys, ",") return table.concat(keys, ',')
else else
local result = '' local result = ''
for k, v in pairs(value) do for k, v in pairs(value) do
@@ -275,23 +296,27 @@ local convert_value_to_vim = (function()
end, end,
[OptionTypes.ARRAY] = function(info, value) [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 if not info.allows_duplicates then
value = remove_duplicate_values(value) value = remove_duplicate_values(value)
end end
return table.concat(value, ",") return table.concat(value, ',')
end, end,
[OptionTypes.MAP] = function(_, value) [OptionTypes.MAP] = function(_, value)
if type(value) == "string" then return value end if type(value) == 'string' then
return value
end
local result = {} local result = {}
for opt_key, opt_value in pairs(value) do 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 end
table.sort(result) table.sort(result)
return table.concat(result, ",") return table.concat(result, ',')
end, 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. -- Map of OptionType to functions that take vimoption_T values and convert to lua values.
-- Each function takes (info, vim_value) -> lua_value -- Each function takes (info, vim_value) -> lua_value
local to_lua_value = { local to_lua_value = {
[OptionTypes.BOOLEAN] = function(_, value) return value end, [OptionTypes.BOOLEAN] = function(_, value)
[OptionTypes.NUMBER] = function(_, value) return value end, return value
[OptionTypes.STRING] = function(_, value) return value end, end,
[OptionTypes.NUMBER] = function(_, value)
return value
end,
[OptionTypes.STRING] = function(_, value)
return value
end,
[OptionTypes.ARRAY] = function(info, value) [OptionTypes.ARRAY] = function(info, value)
if type(value) == "table" then if type(value) == 'table' then
if not info.allows_duplicates then if not info.allows_duplicates then
value = remove_duplicate_values(value) value = remove_duplicate_values(value)
end end
@@ -332,41 +363,43 @@ local convert_value_to_lua = (function()
end end
-- Handles unescaped commas in a list. -- Handles unescaped commas in a list.
if string.find(value, ",,,") then if string.find(value, ',,,') then
local comma_split = vim.split(value, ",,,") local comma_split = vim.split(value, ',,,')
local left = comma_split[1] local left = comma_split[1]
local right = comma_split[2] local right = comma_split[2]
local result = {} local result = {}
vim.list_extend(result, vim.split(left, ",")) vim.list_extend(result, vim.split(left, ','))
table.insert(result, ",") table.insert(result, ',')
vim.list_extend(result, vim.split(right, ",")) vim.list_extend(result, vim.split(right, ','))
table.sort(result) table.sort(result)
return result return result
end end
if string.find(value, ",^,,", 1, true) then if string.find(value, ',^,,', 1, true) then
local comma_split = vim.split(value, ",^,,", true) local comma_split = vim.split(value, ',^,,', true)
local left = comma_split[1] local left = comma_split[1]
local right = comma_split[2] local right = comma_split[2]
local result = {} local result = {}
vim.list_extend(result, vim.split(left, ",")) vim.list_extend(result, vim.split(left, ','))
table.insert(result, "^,") table.insert(result, '^,')
vim.list_extend(result, vim.split(right, ",")) vim.list_extend(result, vim.split(right, ','))
table.sort(result) table.sort(result)
return result return result
end end
return vim.split(value, ",") return vim.split(value, ',')
end, end,
[OptionTypes.SET] = function(info, value) [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, -- Empty strings mean that there is nothing there,
-- so empty table should be returned. -- so empty table should be returned.
@@ -374,10 +407,10 @@ local convert_value_to_lua = (function()
return {} return {}
end 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 if info.flaglist and info.commalist then
local split_value = vim.split(value, ",") local split_value = vim.split(value, ',')
local result = {} local result = {}
for _, v in ipairs(split_value) do for _, v in ipairs(split_value) do
result[v] = true result[v] = true
@@ -395,15 +428,17 @@ local convert_value_to_lua = (function()
end, end,
[OptionTypes.MAP] = function(info, raw_value) [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 result = {}
local comma_split = vim.split(raw_value, ",") local comma_split = vim.split(raw_value, ',')
for _, key_value_str in ipairs(comma_split) do 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) key = vim.trim(key)
result[key] = value result[key] = value
@@ -443,17 +478,21 @@ local prepend_value = (function()
end, end,
[OptionTypes.MAP] = function(left, right) [OptionTypes.MAP] = function(left, right)
return vim.tbl_extend("force", left, right) return vim.tbl_extend('force', left, right)
end, end,
[OptionTypes.SET] = function(left, right) [OptionTypes.SET] = function(left, right)
return vim.tbl_extend("force", left, right) return vim.tbl_extend('force', left, right)
end, end,
} }
return function(name, info, current, new) return function(name, info, current, new)
return value_mutator( 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
end)() end)()
@@ -478,17 +517,21 @@ local add_value = (function()
end, end,
[OptionTypes.MAP] = function(left, right) [OptionTypes.MAP] = function(left, right)
return vim.tbl_extend("force", left, right) return vim.tbl_extend('force', left, right)
end, end,
[OptionTypes.SET] = function(left, right) [OptionTypes.SET] = function(left, right)
return vim.tbl_extend("force", left, right) return vim.tbl_extend('force', left, right)
end, end,
} }
return function(name, info, current, new) return function(name, info, current, new)
return value_mutator( 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
end)() end)()
@@ -518,11 +561,11 @@ local remove_value = (function()
end, end,
[OptionTypes.STRING] = function() [OptionTypes.STRING] = function()
error("Subtraction not supported for strings.") error('Subtraction not supported for strings.')
end, end,
[OptionTypes.ARRAY] = function(left, right) [OptionTypes.ARRAY] = function(left, right)
if type(right) == "string" then if type(right) == 'string' then
remove_one_item(left, right) remove_one_item(left, right)
else else
for _, v in ipairs(right) do for _, v in ipairs(right) do
@@ -534,7 +577,7 @@ local remove_value = (function()
end, end,
[OptionTypes.MAP] = function(left, right) [OptionTypes.MAP] = function(left, right)
if type(right) == "string" then if type(right) == 'string' then
left[right] = nil left[right] = nil
else else
for _, v in ipairs(right) do for _, v in ipairs(right) do
@@ -546,7 +589,7 @@ local remove_value = (function()
end, end,
[OptionTypes.SET] = function(left, right) [OptionTypes.SET] = function(left, right)
if type(right) == "string" then if type(right) == 'string' then
left[right] = nil left[right] = nil
else else
for _, v in ipairs(right) do for _, v in ipairs(right) do
@@ -567,9 +610,9 @@ local create_option_metatable = function(set_type)
local set_mt, option_mt local set_mt, option_mt
local make_option = function(name, value) 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.") assert(name == value._name, "must be the same value, otherwise that's weird.")
value = value._value value = value._value
@@ -584,9 +627,9 @@ local create_option_metatable = function(set_type)
local scope local scope
if set_type == SET_TYPES.GLOBAL then if set_type == SET_TYPES.GLOBAL then
scope = "global" scope = 'global'
elseif set_type == SET_TYPES.LOCAL then elseif set_type == SET_TYPES.LOCAL then
scope = "local" scope = 'local'
end end
option_mt = { option_mt = {
@@ -625,7 +668,7 @@ local create_option_metatable = function(set_type)
__sub = function(self, right) __sub = function(self, right)
return make_option(self._name, remove_value(self._name, self._info, self._value, right)) return make_option(self._name, remove_value(self._name, self._info, self._value, right))
end end,
} }
option_mt.__index = option_mt option_mt.__index = option_mt

View File

@@ -7,6 +7,6 @@
local lua_version = _VERSION:sub(-3) 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 unpack = table.unpack -- luacheck: ignore 121 143
end end

View File

@@ -28,7 +28,7 @@ local global_diagnostic_options = {
M.handlers = setmetatable({}, { M.handlers = setmetatable({}, {
__newindex = function(t, name, handler) __newindex = function(t, name, handler)
vim.validate { handler = {handler, "t" } } vim.validate({ handler = { handler, 't' } })
rawset(t, name, handler) rawset(t, name, handler)
if global_diagnostic_options[name] == nil then if global_diagnostic_options[name] == nil then
global_diagnostic_options[name] = true 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 -- Metatable that automatically creates an empty table when assigning to a missing key
local bufnr_and_namespace_cacher_mt = { local bufnr_and_namespace_cacher_mt = {
__index = function(t, bufnr) __index = function(t, bufnr)
assert(bufnr > 0, "Invalid buffer number") assert(bufnr > 0, 'Invalid buffer number')
t[bufnr] = {} t[bufnr] = {}
return t[bufnr] return t[bufnr]
end, end,
@@ -47,11 +47,11 @@ local bufnr_and_namespace_cacher_mt = {
local diagnostic_cache = setmetatable({}, { local diagnostic_cache = setmetatable({}, {
__index = function(t, bufnr) __index = function(t, bufnr)
assert(bufnr > 0, "Invalid buffer number") assert(bufnr > 0, 'Invalid buffer number')
vim.api.nvim_buf_attach(bufnr, false, { vim.api.nvim_buf_attach(bufnr, false, {
on_detach = function() on_detach = function()
rawset(t, bufnr, nil) -- clear cache rawset(t, bufnr, nil) -- clear cache
end end,
}) })
t[bufnr] = {} t[bufnr] = {}
return t[bufnr] return t[bufnr]
@@ -68,7 +68,7 @@ local all_namespaces = {}
---@private ---@private
local function to_severity(severity) local function to_severity(severity)
if type(severity) == 'string' then 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 end
return severity return severity
end end
@@ -79,15 +79,19 @@ local function filter_by_severity(severity, diagnostics)
return diagnostics return diagnostics
end end
if type(severity) ~= "table" then if type(severity) ~= 'table' then
severity = to_severity(severity) 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 end
local min_severity = to_severity(severity.min) or M.severity.HINT local min_severity = to_severity(severity.min) or M.severity.HINT
local max_severity = to_severity(severity.max) or M.severity.ERROR 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 end
---@private ---@private
@@ -113,17 +117,17 @@ local function prefix_source(diagnostics)
end end
local t = vim.deepcopy(d) 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 return t
end, diagnostics) end, diagnostics)
end end
---@private ---@private
local function reformat_diagnostics(format, diagnostics) local function reformat_diagnostics(format, diagnostics)
vim.validate { vim.validate({
format = { format, 'f' }, format = { format, 'f' },
diagnostics = { diagnostics, 't' }, diagnostics = { diagnostics, 't' },
} })
local formatted = vim.deepcopy(diagnostics) local formatted = vim.deepcopy(diagnostics)
for _, diagnostic in ipairs(formatted) do for _, diagnostic in ipairs(formatted) do
@@ -135,11 +139,11 @@ end
---@private ---@private
local function enabled_value(option, namespace) local function enabled_value(option, namespace)
local ns = namespace and M.get_namespace(namespace) or {} 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] return ns.opts[option]
end end
if type(global_diagnostic_options[option]) == "table" then if type(global_diagnostic_options[option]) == 'table' then
return global_diagnostic_options[option] return global_diagnostic_options[option]
end end
@@ -162,7 +166,7 @@ local function resolve_optional_value(option, value, namespace, bufnr)
elseif type(value) == 'table' then elseif type(value) == 'table' then
return value return value
else else
error("Unexpected option type: " .. vim.inspect(value)) error('Unexpected option type: ' .. vim.inspect(value))
end end
end end
@@ -181,10 +185,10 @@ end
-- Default diagnostic highlights -- Default diagnostic highlights
local diagnostic_severities = { local diagnostic_severities = {
[M.severity.ERROR] = { ctermfg = 1, guifg = "Red" }; [M.severity.ERROR] = { ctermfg = 1, guifg = 'Red' },
[M.severity.WARN] = { ctermfg = 3, guifg = "Orange" }; [M.severity.WARN] = { ctermfg = 3, guifg = 'Orange' },
[M.severity.INFO] = { ctermfg = 4, guifg = "LightBlue" }; [M.severity.INFO] = { ctermfg = 4, guifg = 'LightBlue' },
[M.severity.HINT] = { ctermfg = 7, guifg = "LightGrey" }; [M.severity.HINT] = { ctermfg = 7, guifg = 'LightGrey' },
} }
-- Make a map from DiagnosticSeverity -> Highlight Name -- 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 for k in pairs(diagnostic_severities) do
local name = M.severity[k] local name = M.severity[k]
name = name:sub(1, 1) .. name:sub(2):lower() name = name:sub(1, 1) .. name:sub(2):lower()
result[k] = "Diagnostic" .. base_name .. name result[k] = 'Diagnostic' .. base_name .. name
end end
return result return result
end end
local virtual_text_highlight_map = make_highlight_map("VirtualText") local virtual_text_highlight_map = make_highlight_map('VirtualText')
local underline_highlight_map = make_highlight_map("Underline") local underline_highlight_map = make_highlight_map('Underline')
local floating_highlight_map = make_highlight_map("Floating") local floating_highlight_map = make_highlight_map('Floating')
local sign_highlight_map = make_highlight_map("Sign") local sign_highlight_map = make_highlight_map('Sign')
---@private ---@private
local define_default_signs = (function() local define_default_signs = (function()
@@ -244,7 +248,7 @@ local function is_disabled(namespace, bufnr)
return true return true
end end
if type(diagnostic_disabled[bufnr]) == "table" then if type(diagnostic_disabled[bufnr]) == 'table' then
return diagnostic_disabled[bufnr][namespace] return diagnostic_disabled[bufnr][namespace]
end end
return diagnostic_disabled[bufnr] return diagnostic_disabled[bufnr]
@@ -271,8 +275,8 @@ end
---@private ---@private
local function set_diagnostic_cache(namespace, bufnr, diagnostics) local function set_diagnostic_cache(namespace, bufnr, diagnostics)
for _, diagnostic in ipairs(diagnostics) do for _, diagnostic in ipairs(diagnostics) do
assert(diagnostic.lnum, "Diagnostic line number is required") assert(diagnostic.lnum, 'Diagnostic line number is required')
assert(diagnostic.col, "Diagnostic column is required") assert(diagnostic.col, 'Diagnostic column is required')
diagnostic.severity = diagnostic.severity and to_severity(diagnostic.severity) or M.severity.ERROR diagnostic.severity = diagnostic.severity and to_severity(diagnostic.severity) or M.severity.ERROR
diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum
diagnostic.end_col = diagnostic.end_col or diagnostic.col diagnostic.end_col = diagnostic.end_col or diagnostic.col
@@ -314,10 +318,17 @@ local function save_extmarks(namespace, bufnr)
end, end,
on_detach = function() on_detach = function()
diagnostic_cache_extmarks[bufnr] = nil diagnostic_cache_extmarks[bufnr] = nil
end}) end,
})
diagnostic_attached_buffers[bufnr] = true diagnostic_attached_buffers[bufnr] = true
end 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 end
local registered_autocmds = {} local registered_autocmds = {}
@@ -325,11 +336,11 @@ local registered_autocmds = {}
---@private ---@private
local function make_augroup_key(namespace, bufnr) local function make_augroup_key(namespace, bufnr)
local ns = M.get_namespace(namespace) 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 end
--- Table of autocmd events to fire the update for displaying new diagnostic information --- 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 ---@private
local function schedule_display(namespace, bufnr, args) 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) local key = make_augroup_key(namespace, bufnr)
if not registered_autocmds[key] then if not registered_autocmds[key] then
vim.cmd(string.format([[augroup %s vim.cmd(string.format(
[[augroup %s
au! au!
autocmd %s <buffer=%s> lua vim.diagnostic._execute_scheduled_display(%s, %s) autocmd %s <buffer=%s> lua vim.diagnostic._execute_scheduled_display(%s, %s)
augroup END]], augroup END]],
key, key,
table.concat(insert_leave_auto_cmds, ","), table.concat(insert_leave_auto_cmds, ','),
bufnr, bufnr,
namespace, namespace,
bufnr)) bufnr
))
registered_autocmds[key] = true registered_autocmds[key] = true
end end
end end
@@ -355,9 +368,12 @@ local function clear_scheduled_display(namespace, bufnr)
local key = make_augroup_key(namespace, bufnr) local key = make_augroup_key(namespace, bufnr)
if registered_autocmds[key] then if registered_autocmds[key] then
vim.cmd(string.format([[augroup %s vim.cmd(string.format(
[[augroup %s
au! au!
augroup END]], key)) augroup END]],
key
))
registered_autocmds[key] = nil registered_autocmds[key] = nil
end end
end end
@@ -382,7 +398,7 @@ local function get_diagnostics(bufnr, opts, clamp)
if not opts.lnum or d.lnum == opts.lnum then if not opts.lnum or d.lnum == opts.lnum then
if clamp and vim.api.nvim_buf_is_loaded(b) then if clamp and vim.api.nvim_buf_is_loaded(b) then
local line_count = buf_line_count[b] - 1 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 = vim.deepcopy(d)
d.lnum = math.max(math.min(d.lnum, line_count), 0) d.lnum = math.max(math.min(d.lnum, line_count), 0)
d.end_lnum = math.max(math.min(d.end_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) local function set_list(loclist, opts)
opts = opts or {} opts = opts or {}
local open = vim.F.if_nil(opts.open, true) 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 winnr = opts.winnr or 0
local bufnr local bufnr
if loclist then if loclist then
@@ -447,7 +463,7 @@ local function set_list(loclist, opts)
vim.fn.setqflist({}, ' ', { title = title, items = items }) vim.fn.setqflist({}, ' ', { title = title, items = items })
end end
if open then 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
end end
@@ -457,7 +473,7 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
bufnr = get_bufnr(bufnr) bufnr = get_bufnr(bufnr)
local wrap = vim.F.if_nil(opts.wrap, true) local wrap = vim.F.if_nil(opts.wrap, true)
local line_count = vim.api.nvim_buf_line_count(bufnr) 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) local line_diagnostics = diagnostic_lines(diagnostics)
for i = 0, line_count do for i = 0, line_count do
local offset = i * (search_forward and 1 or -1) 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 line_length = #vim.api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
local sort_diagnostics, is_next local sort_diagnostics, is_next
if search_forward then if search_forward then
sort_diagnostics = function(a, b) return a.col < b.col end sort_diagnostics = function(a, b)
is_next = function(d) return math.min(d.col, line_length - 1) > position[2] end return a.col < b.col
end
is_next = function(d)
return math.min(d.col, line_length - 1) > position[2]
end
else else
sort_diagnostics = function(a, b) return a.col > b.col end sort_diagnostics = function(a, b)
is_next = function(d) return math.min(d.col, line_length - 1) < position[2] end return a.col > b.col
end
is_next = function(d)
return math.min(d.col, line_length - 1) < position[2]
end
end end
table.sort(line_diagnostics[lnum], sort_diagnostics) table.sort(line_diagnostics[lnum], sort_diagnostics)
if i == 0 then 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() local win_id = opts.win_id or vim.api.nvim_get_current_win()
if not pos then 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 return
end end
@@ -509,19 +533,17 @@ local function diagnostic_move_pos(opts, pos)
vim.cmd("normal! m'") vim.cmd("normal! m'")
vim.api.nvim_win_set_cursor(win_id, { pos[1] + 1, pos[2] }) vim.api.nvim_win_set_cursor(win_id, { pos[1] + 1, pos[2] })
-- Open folds under the cursor -- Open folds under the cursor
vim.cmd("normal! zv") vim.cmd('normal! zv')
end) end)
if float then if float then
local float_opts = type(float) == "table" and float or {} local float_opts = type(float) == 'table' and float or {}
vim.schedule(function() vim.schedule(function()
M.open_float( M.open_float(vim.tbl_extend('keep', float_opts, {
vim.tbl_extend("keep", float_opts, {
bufnr = vim.api.nvim_win_get_buf(win_id), bufnr = vim.api.nvim_win_get_buf(win_id),
scope = "cursor", scope = 'cursor',
focus = false, focus = false,
}) }))
)
end) end)
end end
end end
@@ -599,10 +621,10 @@ end
---@param namespace number|nil Update the options for the given namespace. When omitted, update the ---@param namespace number|nil Update the options for the given namespace. When omitted, update the
--- global diagnostic options. --- global diagnostic options.
function M.config(opts, namespace) function M.config(opts, namespace)
vim.validate { vim.validate({
opts = { opts, 't', true }, opts = { opts, 't', true },
namespace = { namespace, 'n', true }, namespace = { namespace, 'n', true },
} })
local t local t
if namespace then if namespace then
@@ -645,16 +667,16 @@ end
---@param diagnostics table A list of diagnostic items |diagnostic-structure| ---@param diagnostics table A list of diagnostic items |diagnostic-structure|
---@param opts table|nil Display options to pass to |vim.diagnostic.show()| ---@param opts table|nil Display options to pass to |vim.diagnostic.show()|
function M.set(namespace, bufnr, diagnostics, opts) function M.set(namespace, bufnr, diagnostics, opts)
vim.validate { vim.validate({
namespace = { namespace, 'n' }, namespace = { namespace, 'n' },
bufnr = { bufnr, 'n' }, bufnr = { bufnr, 'n' },
diagnostics = { diagnostics = {
diagnostics, diagnostics,
vim.tbl_islist, vim.tbl_islist,
"a list of diagnostics", 'a list of diagnostics',
}, },
opts = { opts, 't', true }, opts = { opts, 't', true },
} })
bufnr = get_bufnr(bufnr) bufnr = get_bufnr(bufnr)
@@ -668,7 +690,7 @@ function M.set(namespace, bufnr, diagnostics, opts)
M.show(namespace, bufnr, nil, opts) M.show(namespace, bufnr, nil, opts)
end end
vim.api.nvim_exec_autocmds("DiagnosticChanged", { vim.api.nvim_exec_autocmds('DiagnosticChanged', {
modeline = false, modeline = false,
buffer = bufnr, buffer = bufnr,
}) })
@@ -679,7 +701,7 @@ end
---@param namespace number Diagnostic namespace ---@param namespace number Diagnostic namespace
---@return table Namespace metadata ---@return table Namespace metadata
function M.get_namespace(namespace) function M.get_namespace(namespace)
vim.validate { namespace = { namespace, 'n' } } vim.validate({ namespace = { namespace, 'n' } })
if not all_namespaces[namespace] then if not all_namespaces[namespace] then
local name local name
for k, v in pairs(vim.api.nvim_get_namespaces()) do for k, v in pairs(vim.api.nvim_get_namespaces()) do
@@ -689,7 +711,7 @@ function M.get_namespace(namespace)
end end
end end
assert(name, "namespace does not exist or is anonymous") assert(name, 'namespace does not exist or is anonymous')
all_namespaces[namespace] = { all_namespaces[namespace] = {
name = name, name = name,
@@ -717,10 +739,10 @@ end
--- - severity: See |diagnostic-severity|. --- - severity: See |diagnostic-severity|.
---@return table A list of diagnostic items |diagnostic-structure|. ---@return table A list of diagnostic items |diagnostic-structure|.
function M.get(bufnr, opts) function M.get(bufnr, opts)
vim.validate { vim.validate({
bufnr = { bufnr, 'n', true }, bufnr = { bufnr, 'n', true },
opts = { opts, 't', true }, opts = { opts, 't', true },
} })
return get_diagnostics(bufnr, opts, false) return get_diagnostics(bufnr, opts, false)
end end
@@ -755,10 +777,7 @@ end
--- Move to the previous diagnostic in the current buffer. --- Move to the previous diagnostic in the current buffer.
---@param opts table See |vim.diagnostic.goto_next()| ---@param opts table See |vim.diagnostic.goto_next()|
function M.goto_prev(opts) function M.goto_prev(opts)
return diagnostic_move_pos( return diagnostic_move_pos(opts, M.get_prev_pos(opts))
opts,
M.get_prev_pos(opts)
)
end end
--- Get the next diagnostic closest to the cursor position. --- Get the next diagnostic closest to the cursor position.
@@ -803,24 +822,21 @@ end
--- the "scope" option). --- the "scope" option).
--- - win_id: (number, default 0) Window ID --- - win_id: (number, default 0) Window ID
function M.goto_next(opts) function M.goto_next(opts)
return diagnostic_move_pos( return diagnostic_move_pos(opts, M.get_next_pos(opts))
opts,
M.get_next_pos(opts)
)
end end
M.handlers.signs = { M.handlers.signs = {
show = function(namespace, bufnr, diagnostics, opts) show = function(namespace, bufnr, diagnostics, opts)
vim.validate { vim.validate({
namespace = { namespace, 'n' }, namespace = { namespace, 'n' },
bufnr = { bufnr, 'n' }, bufnr = { bufnr, 'n' },
diagnostics = { diagnostics = {
diagnostics, diagnostics,
vim.tbl_islist, vim.tbl_islist,
"a list of diagnostics", 'a list of diagnostics',
}, },
opts = { opts, 't', true }, opts = { opts, 't', true },
} })
bufnr = get_bufnr(bufnr) bufnr = get_bufnr(bufnr)
opts = opts or {} opts = opts or {}
@@ -835,7 +851,7 @@ M.handlers.signs = {
local priority = opts.signs and opts.signs.priority or 10 local priority = opts.signs and opts.signs.priority or 10
local get_priority local get_priority
if opts.severity_sort then 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) get_priority = function(severity)
return priority + (severity - vim.diagnostic.severity.ERROR) return priority + (severity - vim.diagnostic.severity.ERROR)
end end
@@ -852,21 +868,15 @@ M.handlers.signs = {
local ns = M.get_namespace(namespace) local ns = M.get_namespace(namespace)
if not ns.user_data.sign_group then 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 end
local sign_group = ns.user_data.sign_group local sign_group = ns.user_data.sign_group
for _, diagnostic in ipairs(diagnostics) do for _, diagnostic in ipairs(diagnostics) do
vim.fn.sign_place( vim.fn.sign_place(0, sign_group, sign_highlight_map[diagnostic.severity], bufnr, {
0,
sign_group,
sign_highlight_map[diagnostic.severity],
bufnr,
{
priority = get_priority(diagnostic.severity), priority = get_priority(diagnostic.severity),
lnum = diagnostic.lnum + 1 lnum = diagnostic.lnum + 1,
} })
)
end end
end, end,
hide = function(namespace, bufnr) hide = function(namespace, bufnr)
@@ -879,16 +889,16 @@ M.handlers.signs = {
M.handlers.underline = { M.handlers.underline = {
show = function(namespace, bufnr, diagnostics, opts) show = function(namespace, bufnr, diagnostics, opts)
vim.validate { vim.validate({
namespace = { namespace, 'n' }, namespace = { namespace, 'n' },
bufnr = { bufnr, 'n' }, bufnr = { bufnr, 'n' },
diagnostics = { diagnostics = {
diagnostics, diagnostics,
vim.tbl_islist, vim.tbl_islist,
"a list of diagnostics", 'a list of diagnostics',
}, },
opts = { opts, 't', true }, opts = { opts, 't', true },
} })
bufnr = get_bufnr(bufnr) bufnr = get_bufnr(bufnr)
opts = opts or {} opts = opts or {}
@@ -899,7 +909,7 @@ M.handlers.underline = {
local ns = M.get_namespace(namespace) local ns = M.get_namespace(namespace)
if not ns.user_data.underline_ns then 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 end
local underline_ns = ns.user_data.underline_ns local underline_ns = ns.user_data.underline_ns
@@ -928,21 +938,21 @@ M.handlers.underline = {
diagnostic_cache_extmarks[bufnr][ns.user_data.underline_ns] = {} diagnostic_cache_extmarks[bufnr][ns.user_data.underline_ns] = {}
vim.api.nvim_buf_clear_namespace(bufnr, ns.user_data.underline_ns, 0, -1) vim.api.nvim_buf_clear_namespace(bufnr, ns.user_data.underline_ns, 0, -1)
end end
end end,
} }
M.handlers.virtual_text = { M.handlers.virtual_text = {
show = function(namespace, bufnr, diagnostics, opts) show = function(namespace, bufnr, diagnostics, opts)
vim.validate { vim.validate({
namespace = { namespace, 'n' }, namespace = { namespace, 'n' },
bufnr = { bufnr, 'n' }, bufnr = { bufnr, 'n' },
diagnostics = { diagnostics = {
diagnostics, diagnostics,
vim.tbl_islist, vim.tbl_islist,
"a list of diagnostics", 'a list of diagnostics',
}, },
opts = { opts, 't', true }, opts = { opts, 't', true },
} })
bufnr = get_bufnr(bufnr) bufnr = get_bufnr(bufnr)
opts = opts or {} opts = opts or {}
@@ -952,10 +962,7 @@ M.handlers.virtual_text = {
if opts.virtual_text.format then if opts.virtual_text.format then
diagnostics = reformat_diagnostics(opts.virtual_text.format, diagnostics) diagnostics = reformat_diagnostics(opts.virtual_text.format, diagnostics)
end end
if if opts.virtual_text.source and (opts.virtual_text.source ~= 'if_many' or count_sources(bufnr) > 1) then
opts.virtual_text.source
and (opts.virtual_text.source ~= "if_many" or count_sources(bufnr) > 1)
then
diagnostics = prefix_source(diagnostics) diagnostics = prefix_source(diagnostics)
end end
if opts.virtual_text.severity then if opts.virtual_text.severity then
@@ -965,7 +972,7 @@ M.handlers.virtual_text = {
local ns = M.get_namespace(namespace) local ns = M.get_namespace(namespace)
if not ns.user_data.virt_text_ns then 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 end
local virt_text_ns = ns.user_data.virt_text_ns local virt_text_ns = ns.user_data.virt_text_ns
@@ -978,7 +985,7 @@ M.handlers.virtual_text = {
if virt_texts then if virt_texts then
vim.api.nvim_buf_set_extmark(bufnr, virt_text_ns, line, 0, { vim.api.nvim_buf_set_extmark(bufnr, virt_text_ns, line, 0, {
hl_mode = "combine", hl_mode = 'combine',
virt_text = virt_texts, virt_text = virt_texts,
}) })
end end
@@ -1006,11 +1013,11 @@ function M._get_virt_text_chunks(line_diags, opts)
end end
opts = opts or {} opts = opts or {}
local prefix = opts.prefix or "" local prefix = opts.prefix or ''
local spacing = opts.spacing or 4 local spacing = opts.spacing or 4
-- Create a little more space between virtual text and contents -- 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 for i = 1, #line_diags - 1 do
table.insert(virt_texts, { prefix, virtual_text_highlight_map[line_diags[i].severity] }) 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): Allow different servers to be shown first somehow?
-- TODO(tjdevries): Display server name associated with these? -- TODO(tjdevries): Display server name associated with these?
if last.message then if last.message then
table.insert( table.insert(virt_texts, {
virt_texts, string.format('%s %s', prefix, last.message:gsub('\r', ''):gsub('\n', ' ')),
{ virtual_text_highlight_map[last.severity],
string.format("%s %s", prefix, last.message:gsub("\r", ""):gsub("\n", " ")), })
virtual_text_highlight_map[last.severity]
}
)
return virt_texts return virt_texts
end end
@@ -1066,10 +1070,10 @@ end
---@param bufnr number|nil Buffer number, or 0 for current buffer. When ---@param bufnr number|nil Buffer number, or 0 for current buffer. When
--- omitted, hide diagnostics in all buffers. --- omitted, hide diagnostics in all buffers.
function M.hide(namespace, bufnr) function M.hide(namespace, bufnr)
vim.validate { vim.validate({
namespace = { namespace, 'n', true }, namespace = { namespace, 'n', true },
bufnr = { bufnr, 'n', true }, bufnr = { bufnr, 'n', true },
} })
local buffers = bufnr and { get_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache) local buffers = bufnr and { get_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache)
for _, iter_bufnr in ipairs(buffers) do for _, iter_bufnr in ipairs(buffers) do
@@ -1098,19 +1102,21 @@ end
--- or {bufnr} is nil. --- or {bufnr} is nil.
---@param opts table|nil Display options. See |vim.diagnostic.config()|. ---@param opts table|nil Display options. See |vim.diagnostic.config()|.
function M.show(namespace, bufnr, diagnostics, opts) function M.show(namespace, bufnr, diagnostics, opts)
vim.validate { vim.validate({
namespace = { namespace, 'n', true }, namespace = { namespace, 'n', true },
bufnr = { bufnr, 'n', true }, bufnr = { bufnr, 'n', true },
diagnostics = { diagnostics = {
diagnostics, diagnostics,
function(v) return v == nil or vim.tbl_islist(v) end, function(v)
"a list of diagnostics", return v == nil or vim.tbl_islist(v)
end,
'a list of diagnostics',
}, },
opts = { opts, 't', true }, opts = { opts, 't', true },
} })
if not bufnr or not namespace then 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 if not bufnr then
for iter_bufnr in pairs(diagnostic_cache) do for iter_bufnr in pairs(diagnostic_cache) do
M.show(namespace, iter_bufnr, nil, opts) M.show(namespace, iter_bufnr, nil, opts)
@@ -1150,10 +1156,14 @@ function M.show(namespace, bufnr, diagnostics, opts)
end end
if vim.F.if_nil(opts.severity_sort, false) then if vim.F.if_nil(opts.severity_sort, false) 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
table.sort(diagnostics, function(a, b) return a.severity < b.severity end) table.sort(diagnostics, function(a, b)
return a.severity < b.severity
end)
else 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
end end
@@ -1209,13 +1219,13 @@ end
function M.open_float(opts, ...) function M.open_float(opts, ...)
-- Support old (bufnr, opts) signature -- Support old (bufnr, opts) signature
local bufnr local bufnr
if opts == nil or type(opts) == "number" then if opts == nil or type(opts) == 'number' then
bufnr = opts bufnr = opts
opts = ... opts = ...
else else
vim.validate { vim.validate({
opts = { opts, 't', true }, opts = { opts, 't', true },
} })
end end
opts = opts or {} 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 -- 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. -- options table that inherits missing keys from the global configuration before resolving.
local t = global_diagnostic_options.float 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 opts = get_resolved_options({ float = float_opts }, nil, bufnr).float
end 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 local lnum, col
if scope == "line" or scope == "cursor" then if scope == 'line' or scope == 'cursor' then
if not opts.pos then if not opts.pos then
local pos = vim.api.nvim_win_get_cursor(0) local pos = vim.api.nvim_win_get_cursor(0)
lnum = pos[1] - 1 lnum = pos[1] - 1
col = pos[2] col = pos[2]
elseif type(opts.pos) == "number" then elseif type(opts.pos) == 'number' then
lnum = opts.pos lnum = opts.pos
elseif type(opts.pos) == "table" then elseif type(opts.pos) == 'table' then
lnum, col = unpack(opts.pos) lnum, col = unpack(opts.pos)
else else
error("Invalid value for option 'pos'") error("Invalid value for option 'pos'")
end end
elseif scope ~= "buffer" then elseif scope ~= 'buffer' then
error("Invalid value for option 'scope'") error("Invalid value for option 'scope'")
end end
local diagnostics = get_diagnostics(bufnr, opts, true) local diagnostics = get_diagnostics(bufnr, opts, true)
if scope == "line" then if scope == 'line' then
diagnostics = vim.tbl_filter(function(d) diagnostics = vim.tbl_filter(function(d)
return d.lnum == lnum return d.lnum == lnum
end, diagnostics) end, diagnostics)
elseif scope == "cursor" then elseif scope == 'cursor' then
-- LSP servers can send diagnostics with `end_col` past the length of the line -- 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] local line_length = #vim.api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
diagnostics = vim.tbl_filter(function(d) diagnostics = vim.tbl_filter(function(d)
return d.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)
and math.min(d.col, line_length - 1) <= col
and (d.end_col >= col or d.end_lnum > lnum)
end, diagnostics) end, diagnostics)
end 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) local severity_sort = vim.F.if_nil(opts.severity_sort, global_diagnostic_options.severity_sort)
if severity_sort then if severity_sort then
if type(severity_sort) == "table" and severity_sort.reverse then if type(severity_sort) == 'table' and severity_sort.reverse then
table.sort(diagnostics, function(a, b) return a.severity > b.severity end) table.sort(diagnostics, function(a, b)
return a.severity > b.severity
end)
else 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
end end
local lines = {} local lines = {}
local highlights = {} local highlights = {}
local header = if_nil(opts.header, "Diagnostics:") local header = if_nil(opts.header, 'Diagnostics:')
if header then if header then
vim.validate { header = { header, function(v) vim.validate({
return type(v) == "string" or type(v) == "table" header = {
end, "'string' or 'table'" } } header,
if type(header) == "table" then 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 -- 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(lines, header[1])
table.insert(highlights, {0, header[2] or "Bold"}) table.insert(highlights, { 0, header[2] or 'Bold' })
end end
elseif #header > 0 then elseif #header > 0 then
table.insert(lines, header) table.insert(lines, header)
table.insert(highlights, {0, "Bold"}) table.insert(highlights, { 0, 'Bold' })
end end
end end
@@ -1302,30 +1320,36 @@ function M.open_float(opts, ...)
diagnostics = reformat_diagnostics(opts.format, diagnostics) diagnostics = reformat_diagnostics(opts.format, diagnostics)
end 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) diagnostics = prefix_source(diagnostics)
end end
local prefix_opt = if_nil(opts.prefix, (scope == "cursor" and #diagnostics <= 1) and "" or function(_, i) local prefix_opt = if_nil(opts.prefix, (scope == 'cursor' and #diagnostics <= 1) and '' or function(_, i)
return string.format("%d. ", i) return string.format('%d. ', i)
end) end)
local prefix, prefix_hl_group local prefix, prefix_hl_group
if prefix_opt then if prefix_opt then
vim.validate { prefix = { prefix_opt, function(v) vim.validate({
return type(v) == "string" or type(v) == "table" or type(v) == "function" prefix = {
end, "'string' or 'table' or 'function'" } } prefix_opt,
if type(prefix_opt) == "string" then function(v)
prefix, prefix_hl_group = prefix_opt, "NormalFloat" return type(v) == 'string' or type(v) == 'table' or type(v) == 'function'
elseif type(prefix_opt) == "table" then end,
prefix, prefix_hl_group = prefix_opt[1] or "", prefix_opt[2] or "NormalFloat" "'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
end end
for i, diagnostic in ipairs(diagnostics) do 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_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 end
local hiname = floating_highlight_map[diagnostic.severity] local hiname = floating_highlight_map[diagnostic.severity]
local message_lines = vim.split(diagnostic.message, '\n') 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, ---@param bufnr number|nil Remove diagnostics for the given buffer. When omitted,
--- diagnostics are removed for all buffers. --- diagnostics are removed for all buffers.
function M.reset(namespace, bufnr) function M.reset(namespace, bufnr)
vim.validate { vim.validate({
namespace = { namespace, 'n', true }, namespace = { namespace, 'n', true },
bufnr = { bufnr, 'n', true }, bufnr = { bufnr, 'n', true },
} })
local buffers = bufnr and { get_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache) local buffers = bufnr and { get_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache)
for _, iter_bufnr in ipairs(buffers) do for _, iter_bufnr in ipairs(buffers) do
@@ -1378,7 +1402,7 @@ function M.reset(namespace, bufnr)
M.hide(iter_namespace, iter_bufnr) M.hide(iter_namespace, iter_bufnr)
end end
vim.api.nvim_exec_autocmds("DiagnosticChanged", { vim.api.nvim_exec_autocmds('DiagnosticChanged', {
modeline = false, modeline = false,
buffer = iter_bufnr, buffer = iter_bufnr,
}) })
@@ -1414,7 +1438,7 @@ end
--- omitted, disable diagnostics in all buffers. --- omitted, disable diagnostics in all buffers.
---@param namespace number|nil Only disable diagnostics for the given namespace. ---@param namespace number|nil Only disable diagnostics for the given namespace.
function M.disable(bufnr, 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 bufnr == nil then
if namespace == nil then if namespace == nil then
-- Disable everything (including as yet non-existing buffers and -- 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 -- its metatable to always return true. This metatable is removed
-- in enable() -- in enable()
diagnostic_disabled = setmetatable({}, { diagnostic_disabled = setmetatable({}, {
__index = function() return true end, __index = function()
return true
end,
}) })
else else
local ns = M.get_namespace(namespace) local ns = M.get_namespace(namespace)
@@ -1433,7 +1459,7 @@ function M.disable(bufnr, namespace)
if namespace == nil then if namespace == nil then
diagnostic_disabled[bufnr] = true diagnostic_disabled[bufnr] = true
else else
if type(diagnostic_disabled[bufnr]) ~= "table" then if type(diagnostic_disabled[bufnr]) ~= 'table' then
diagnostic_disabled[bufnr] = {} diagnostic_disabled[bufnr] = {}
end end
diagnostic_disabled[bufnr][namespace] = true diagnostic_disabled[bufnr][namespace] = true
@@ -1449,7 +1475,7 @@ end
--- omitted, enable diagnostics in all buffers. --- omitted, enable diagnostics in all buffers.
---@param namespace number|nil Only enable diagnostics for the given namespace. ---@param namespace number|nil Only enable diagnostics for the given namespace.
function M.enable(bufnr, 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 bufnr == nil then
if namespace == nil then if namespace == nil then
-- Enable everything by setting diagnostic_disabled to an empty table -- Enable everything by setting diagnostic_disabled to an empty table
@@ -1463,7 +1489,7 @@ function M.enable(bufnr, namespace)
if namespace == nil then if namespace == nil then
diagnostic_disabled[bufnr] = nil diagnostic_disabled[bufnr] = nil
else else
if type(diagnostic_disabled[bufnr]) ~= "table" then if type(diagnostic_disabled[bufnr]) ~= 'table' then
return return
end end
diagnostic_disabled[bufnr][namespace] = nil diagnostic_disabled[bufnr][namespace] = nil
@@ -1500,13 +1526,13 @@ end
--- ERROR. --- ERROR.
---@return diagnostic |diagnostic-structure| or `nil` if {pat} fails to match {str}. ---@return diagnostic |diagnostic-structure| or `nil` if {pat} fails to match {str}.
function M.match(str, pat, groups, severity_map, defaults) function M.match(str, pat, groups, severity_map, defaults)
vim.validate { vim.validate({
str = { str, 's' }, str = { str, 's' },
pat = { pat, 's' }, pat = { pat, 's' },
groups = { groups, 't' }, groups = { groups, 't' },
severity_map = { severity_map, 't', true }, severity_map = { severity_map, 't', true },
defaults = { defaults, 't', true }, defaults = { defaults, 't', true },
} })
severity_map = severity_map or M.severity 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 for i, match in ipairs(matches) do
local field = groups[i] local field = groups[i]
if field == "severity" then if field == 'severity' then
match = severity_map[match] 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 match = assert(tonumber(match)) - 1
end end
diagnostic[field] = match diagnostic[field] = match
end 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.severity = diagnostic.severity or M.severity.ERROR
diagnostic.col = diagnostic.col or 0 diagnostic.col = diagnostic.col or 0
diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum 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|. ---@param diagnostics table List of diagnostics |diagnostic-structure|.
---@return array of quickfix list items |setqflist-what| ---@return array of quickfix list items |setqflist-what|
function M.toqflist(diagnostics) function M.toqflist(diagnostics)
vim.validate { vim.validate({
diagnostics = { diagnostics = {
diagnostics, diagnostics,
vim.tbl_islist, vim.tbl_islist,
"a list of diagnostics", 'a list of diagnostics',
}, },
} })
local list = {} local list = {}
for _, v in ipairs(diagnostics) do for _, v in ipairs(diagnostics) do
@@ -1584,13 +1610,13 @@ end
--- |getloclist()|. --- |getloclist()|.
---@return array of diagnostics |diagnostic-structure| ---@return array of diagnostics |diagnostic-structure|
function M.fromqflist(list) function M.fromqflist(list)
vim.validate { vim.validate({
list = { list = {
list, list,
vim.tbl_islist, vim.tbl_islist,
"a list of quickfix items", 'a list of quickfix items',
}, },
} })
local diagnostics = {} local diagnostics = {}
for _, item in ipairs(list) do for _, item in ipairs(list) do
@@ -1599,7 +1625,7 @@ function M.fromqflist(list)
local col = math.max(0, item.col - 1) local col = math.max(0, item.col - 1)
local end_lnum = item.end_lnum > 0 and (item.end_lnum - 1) or lnum 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 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, { table.insert(diagnostics, {
bufnr = item.bufnr, bufnr = item.bufnr,
lnum = lnum, lnum = lnum,

File diff suppressed because it is too large Load Diff

View File

@@ -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) local lines = vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
opts = opts or {} opts = opts or {}
return opts.concat and (table.concat(lines) or "") or lines return opts.concat and (table.concat(lines) or '') or lines
end end
---@private ---@private
@@ -35,23 +35,23 @@ function M.bindzone(path, bufnr) end
function M.btm(bufnr) function M.btm(bufnr)
if vim.g.dosbatch_syntax_for_btm and vim.g.dosbatch_syntax_for_btm ~= 0 then 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 else
vim.bo[bufnr].filetype = "btm" vim.bo[bufnr].filetype = 'btm'
end end
end end
-- Returns true if file content looks like RAPID -- Returns true if file content looks like RAPID
local function is_rapid(bufnr, extension) local function is_rapid(bufnr, extension)
if extension == "cfg" then if extension == 'cfg' then
local line = getlines(bufnr, 1):lower() 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 end
local first = "^%s*module%s+%S+%s*" local first = '^%s*module%s+%S+%s*'
-- Called from mod, prg or sys functions -- Called from mod, prg or sys functions
for _, line in ipairs(getlines(bufnr, 1, -1)) do for _, line in ipairs(getlines(bufnr, 1, -1)) do
if not line:find("^%s*$") then if not line:find('^%s*$') then
return findany(line:lower(), { "^%s*%%%%%%", first .. "(", first .. "$" }) return findany(line:lower(), { '^%s*%%%%%%', first .. '(', first .. '$' })
end end
end end
-- Only found blank lines -- Only found blank lines
@@ -61,10 +61,10 @@ end
function M.cfg(bufnr) function M.cfg(bufnr)
if vim.g.filetype_cfg then if vim.g.filetype_cfg then
vim.bo[bufnr].filetype = vim.g.filetype_cfg vim.bo[bufnr].filetype = vim.g.filetype_cfg
elseif is_rapid(bufnr, "cfg") then elseif is_rapid(bufnr, 'cfg') then
vim.bo[bufnr].filetype = "rapid" vim.bo[bufnr].filetype = 'rapid'
else else
vim.bo[bufnr].filetype = "cfg" vim.bo[bufnr].filetype = 'cfg'
end end
end end
@@ -85,23 +85,23 @@ function M.e(path, bufnr) end
-- If not found, assume SGML. -- If not found, assume SGML.
function M.ent(bufnr) function M.ent(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 5)) do for _, line in ipairs(getlines(bufnr, 1, 5)) do
if line:find("^%s*[#{]") then if line:find('^%s*[#{]') then
vim.bo[bufnr].filetype = "cl" vim.bo[bufnr].filetype = 'cl'
return 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, -- Not a blank line, not a comment, and not a block start,
-- so doesn't look like valid cl code. -- so doesn't look like valid cl code.
break break
end end
end end
vim.bo[bufnr].filetype = "dtd" vim.bo[bufnr].filetype = 'dtd'
end end
function M.euphoria(bufnr) function M.euphoria(bufnr)
if vim.g.filetype_euphoria then if vim.g.filetype_euphoria then
vim.bo[bufnr].filetype = vim.g.filetype_euphoria vim.bo[bufnr].filetype = vim.g.filetype_euphoria
else else
vim.bo[bufnr].filetype = "euphoria3" vim.bo[bufnr].filetype = 'euphoria3'
end end
end end
@@ -111,12 +111,12 @@ function M.ex(bufnr)
else else
for _, line in ipairs(getlines(bufnr, 1, 100)) do 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? -- TODO: in the Vim regex, \> is used to match the end of the word, can this be omitted?
if findany(line, { "^%-%-", "^ifdef", "^include" }) then if findany(line, { '^%-%-', '^ifdef', '^include' }) then
vim.bo[bufnr].filetype = "euphoria3" vim.bo[bufnr].filetype = 'euphoria3'
return return
end end
end end
vim.bo[bufnr].filetype = "elixir" vim.bo[bufnr].filetype = 'elixir'
end end
end end
@@ -126,10 +126,10 @@ end
function M.foam(bufnr) function M.foam(bufnr)
local foam_file = false local foam_file = false
for _, line in ipairs(getlines(bufnr, 1, 15)) do for _, line in ipairs(getlines(bufnr, 1, 15)) do
if line:find("^FoamFile") then if line:find('^FoamFile') then
foam_file = true foam_file = true
elseif foam_file and line:find("^%s*object") then elseif foam_file and line:find('^%s*object') then
vim.bo[bufnr].filetype = "foam" vim.bo[bufnr].filetype = 'foam'
return return
end end
end end
@@ -141,10 +141,10 @@ function M.frm(bufnr)
else else
-- Always ignore case -- Always ignore case
local lines = getlines(bufnr, 1, 5, { concat = true }):lower() local lines = getlines(bufnr, 1, 5, { concat = true }):lower()
if findany(lines, { "vb_name", "begin vb%.form", "begin vb%.mdiform" }) then if findany(lines, { 'vb_name', 'begin vb%.form', 'begin vb%.mdiform' }) then
vim.bo[bufnr].filetype = "vb" vim.bo[bufnr].filetype = 'vb'
else else
vim.bo[bufnr].filetype = "form" vim.bo[bufnr].filetype = 'form'
end end
end end
end end
@@ -153,21 +153,21 @@ function M.fs(path, bufnr) end
function M.header(bufnr) function M.header(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 200)) do 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 if vim.g.c_syntax_for_h then
vim.bo[bufnr].filetype = "objc" vim.bo[bufnr].filetype = 'objc'
else else
vim.bo[bufnr].filetype = "objcpp" vim.bo[bufnr].filetype = 'objcpp'
end end
return return
end end
end end
if vim.g.c_syntax_for_h then 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 elseif vim.g.ch_syntax_for_h then
vim.bo[bufnr].filetype = "ch" vim.bo[bufnr].filetype = 'ch'
else else
vim.bo[bufnr].filetype = "cpp" vim.bo[bufnr].filetype = 'cpp'
end end
end end
@@ -176,22 +176,22 @@ function M.idl(bufnr)
-- Always ignore case -- Always ignore case
line = line:lower() line = line:lower()
if findany(line, { '^%s*import%s+"unknwn"%.idl', '^%s*import%s+"objidl"%.idl' }) then 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 return
end end
end end
vim.bo[bufnr].filetype = "idl" vim.bo[bufnr].filetype = 'idl'
end end
function M.inc(path, bufnr) end function M.inc(path, bufnr) end
function M.inp(bufnr) function M.inp(bufnr)
if getlines(bufnr, 1):find("^%*") then if getlines(bufnr, 1):find('^%*') then
vim.bo[bufnr].filetype = "abaqus" vim.bo[bufnr].filetype = 'abaqus'
else else
for _, line in ipairs(getlines(bufnr, 1, 500)) do for _, line in ipairs(getlines(bufnr, 1, 500)) do
if line:lower():find("^header surface data") then if line:lower():find('^header surface data') then
vim.bo[bufnr].filetype = "trasys" vim.bo[bufnr].filetype = 'trasys'
return return
end end
end end
@@ -208,32 +208,32 @@ function M.m(path, bufnr) end
-- MS message text files use ';', Sendmail files use '#' or 'dnl' -- MS message text files use ';', Sendmail files use '#' or 'dnl'
function M.mc(bufnr) function M.mc(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 20)) do 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 -- Sendmail .mc file
vim.bo[bufnr].filetype = "m4" vim.bo[bufnr].filetype = 'm4'
return return
elseif line:find("^%s*;") then elseif line:find('^%s*;') then
vim.bo[bufnr].filetype = "msmessages" vim.bo[bufnr].filetype = 'msmessages'
return return
end end
end end
-- Default: Sendmail .mc file -- Default: Sendmail .mc file
vim.bo[bufnr].filetype = "m4" vim.bo[bufnr].filetype = 'm4'
end end
function M.mm(path, bufnr) end function M.mm(path, bufnr) end
function M.mms(bufnr) function M.mms(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 20)) do for _, line in ipairs(getlines(bufnr, 1, 20)) do
if findany(line, { "^%s*%%", "^%s*//", "^%*" }) then if findany(line, { '^%s*%%', '^%s*//', '^%*' }) then
vim.bo[bufnr].filetype = "mmix" vim.bo[bufnr].filetype = 'mmix'
return return
elseif line:find("^%s*#") then elseif line:find('^%s*#') then
vim.bo[bufnr].filetype = "make" vim.bo[bufnr].filetype = 'make'
return return
end end
end end
vim.bo[bufnr].filetype = "mmix" vim.bo[bufnr].filetype = 'mmix'
end end
function M.mod(path, bufnr) 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. -- that case it is probably an nroff file: 'filetype' is set and 1 is returned.
function M.nroff(bufnr) function M.nroff(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 5)) do for _, line in ipairs(getlines(bufnr, 1, 5)) do
if line:find("^%.") then if line:find('^%.') then
vim.bo[bufnr].filetype = "nroff" vim.bo[bufnr].filetype = 'nroff'
return 1 return 1
end end
end end
@@ -264,10 +264,10 @@ function M.progress_cweb(bufnr)
if vim.g.filetype_w then if vim.g.filetype_w then
vim.bo[bufnr].filetype = vim.g.filetype_w vim.bo[bufnr].filetype = vim.g.filetype_w
else else
if getlines(bufnr, 1):find("^&ANALYZE") or getlines(bufnr, 3):find("^&GLOBAL%-DEFINE") then if getlines(bufnr, 1):find('^&ANALYZE') or getlines(bufnr, 3):find('^&GLOBAL%-DEFINE') then
vim.bo[bufnr].filetype = "progress" vim.bo[bufnr].filetype = 'progress'
else else
vim.bo[bufnr].filetype = "cweb" vim.bo[bufnr].filetype = 'cweb'
end end
end end
end end
@@ -280,20 +280,20 @@ function M.r(bufnr)
local lines = getlines(bufnr, 1, 50) local lines = getlines(bufnr, 1, 50)
-- TODO: \< / \> which match the beginning / end of a word -- TODO: \< / \> which match the beginning / end of a word
-- Rebol is easy to recognize, check for that first -- Rebol is easy to recognize, check for that first
if table.concat(lines):lower():find("rebol") then if table.concat(lines):lower():find('rebol') then
vim.bo[bufnr].filetype = "rebol" vim.bo[bufnr].filetype = 'rebol'
return return
end end
for _, line in ipairs(lines) do for _, line in ipairs(lines) do
-- R has # comments -- R has # comments
if line:find("^%s*#") then if line:find('^%s*#') then
vim.bo[bufnr].filetype = "r" vim.bo[bufnr].filetype = 'r'
return return
end end
-- Rexx has /* comments */ -- Rexx has /* comments */
if line:find("^%s*/%*") then if line:find('^%s*/%*') then
vim.bo[bufnr].filetype = "rexx" vim.bo[bufnr].filetype = 'rexx'
return return
end end
end end
@@ -303,14 +303,14 @@ function M.r(bufnr)
vim.bo[bufnr].filetype = vim.g.filetype_r vim.bo[bufnr].filetype = vim.g.filetype_r
else else
-- Rexx used to be the default, but R appears to be much more popular. -- 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
end end
function M.redif(bufnr) function M.redif(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 5)) do for _, line in ipairs(getlines(bufnr, 1, 5)) do
if line:lower():find("^template%-type:") then if line:lower():find('^template%-type:') then
vim.bo[bufnr].filetype = "redif" vim.bo[bufnr].filetype = 'redif'
end end
end end
end end
@@ -321,24 +321,29 @@ function M.rules(path, bufnr) end
-- detection between scala and SuperCollider -- detection between scala and SuperCollider
function M.sc(bufnr) function M.sc(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 25)) do 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 if
vim.bo[bufnr].filetype = "supercollider" 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 return
end end
end end
vim.bo[bufnr].filetype = "scala" vim.bo[bufnr].filetype = 'scala'
end end
-- This function checks the first line of file extension "scd" to resolve -- This function checks the first line of file extension "scd" to resolve
-- detection between scdoc and SuperCollider -- detection between scdoc and SuperCollider
function M.scd(bufnr) function M.scd(bufnr)
local first = "^%S+%(%d[0-9A-Za-z]*%)" local first = '^%S+%(%d[0-9A-Za-z]*%)'
local opt = [[%s+"[^"]*"]] local opt = [[%s+"[^"]*"]]
local line = getlines(bufnr, 1) local line = getlines(bufnr, 1)
if findany(line, { first .. "$", first .. opt .. "$", first .. opt .. opt .. "$" }) then if findany(line, { first .. '$', first .. opt .. '$', first .. opt .. opt .. '$' }) then
vim.bo[bufnr].filetype = "scdoc" vim.bo[bufnr].filetype = 'scdoc'
else else
vim.bo[bufnr].filetype = "supercollider" vim.bo[bufnr].filetype = 'supercollider'
end end
end end
@@ -350,7 +355,7 @@ function M.sql(bufnr)
if vim.g.filetype_sql then if vim.g.filetype_sql then
vim.bo[bufnr].filetype = vim.g.filetype_sql vim.bo[bufnr].filetype = vim.g.filetype_sql
else else
vim.bo[bufnr].filetype = "sql" vim.bo[bufnr].filetype = 'sql'
end end
end end
@@ -365,46 +370,46 @@ function M.tf(bufnr)
for _, line in ipairs(getlines(bufnr, 1, -1)) do for _, line in ipairs(getlines(bufnr, 1, -1)) do
-- Assume terraform file on a non-empty line (not whitespace-only) -- Assume terraform file on a non-empty line (not whitespace-only)
-- and when the first non-whitespace character is not a ; or / -- and when the first non-whitespace character is not a ; or /
if not line:find("^%s*$") and not line:find("^%s*[;/]") then if not line:find('^%s*$') and not line:find('^%s*[;/]') then
vim.bo[bufnr].filetype = "terraform" vim.bo[bufnr].filetype = 'terraform'
return return
end end
end end
vim.bo[bufnr].filetype = "tf" vim.bo[bufnr].filetype = 'tf'
end end
function M.xml(bufnr) function M.xml(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 100)) do for _, line in ipairs(getlines(bufnr, 1, 100)) do
line = line:lower() 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"]]) local is_docbook5 = line:find([[ xmlns="http://docbook.org/ns/docbook"]])
if is_docbook4 or is_docbook5 then 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.b[bufnr].docbk_ver = is_docbook4 and 4 or 5
vim.bo[bufnr].filetype = "docbk" vim.bo[bufnr].filetype = 'docbk'
return return
end end
if line:find([[xmlns:xbl="http://www.mozilla.org/xbl"]]) then if line:find([[xmlns:xbl="http://www.mozilla.org/xbl"]]) then
vim.bo[bufnr].filetype = "xbl" vim.bo[bufnr].filetype = 'xbl'
return return
end end
end end
vim.bo[bufnr].filetype = "xml" vim.bo[bufnr].filetype = 'xml'
end end
function M.y(bufnr) function M.y(bufnr)
for _, line in ipairs(getlines(bufnr, 1, 100)) do for _, line in ipairs(getlines(bufnr, 1, 100)) do
if line:find("^%s*%%") then if line:find('^%s*%%') then
vim.bo[bufnr].filetype = "yacc" vim.bo[bufnr].filetype = 'yacc'
return return
end end
-- TODO: in the Vim regex, \> is used to match the end of the word after "class", -- TODO: in the Vim regex, \> is used to match the end of the word after "class",
-- can this be omitted? -- can this be omitted?
if findany(line, { "^%s*#", "^%class", "^%s*#%s*include" }) then if findany(line, { '^%s*#', '^%class', '^%s*#%s*include' }) then
vim.bo[bufnr].filetype = "racc" vim.bo[bufnr].filetype = 'racc'
end end
end end
vim.bo[bufnr].filetype = "yacc" vim.bo[bufnr].filetype = 'yacc'
end end
-- luacheck: pop -- luacheck: pop

View File

@@ -14,14 +14,14 @@ function M.create(higroup, hi_info, default)
local options = {} local options = {}
-- TODO: Add validation -- TODO: Add validation
for k, v in pairs(hi_info) do 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 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 end
---@private ---@private
function M.link(higroup, link_to, force) 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 end
--- Highlight range between two positions --- Highlight range between two positions
@@ -37,7 +37,7 @@ end
-- - priority number indicating priority of highlight (default priorities.user) -- - priority number indicating priority of highlight (default priorities.user)
function M.range(bufnr, ns, higroup, start, finish, opts) function M.range(bufnr, ns, higroup, start, finish, opts)
opts = opts or {} opts = opts or {}
local regtype = opts.regtype or "v" local regtype = opts.regtype or 'v'
local inclusive = opts.inclusive or false local inclusive = opts.inclusive or false
local priority = opts.priority or M.priorities.user local priority = opts.priority or M.priorities.user
@@ -63,7 +63,7 @@ function M.range(bufnr, ns, higroup, start, finish, opts)
end end
end end
local yank_ns = api.nvim_create_namespace("hlyank") local yank_ns = api.nvim_create_namespace('hlyank')
--- Highlight the yanked region --- Highlight the yanked region
--- ---
--- use from init.vim via --- use from init.vim via
@@ -87,10 +87,10 @@ function M.on_yank(opts)
if t == nil then if t == nil then
return true return true
else else
return type(t) == "table" return type(t) == 'table'
end end
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 {} opts = opts or {}
@@ -98,17 +98,17 @@ function M.on_yank(opts)
local on_macro = opts.on_macro or false local on_macro = opts.on_macro or false
local on_visual = (opts.on_visual ~= 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 return
end end
if event.operator ~= "y" or event.regtype == "" then if event.operator ~= 'y' or event.regtype == '' then
return return
end end
if not on_visual and event.visual then if not on_visual and event.visual then
return return
end end
local higroup = opts.higroup or "IncSearch" local higroup = opts.higroup or 'IncSearch'
local timeout = opts.timeout or 150 local timeout = opts.timeout or 150
local bufnr = api.nvim_get_current_buf() local bufnr = api.nvim_get_current_buf()

View File

@@ -1,7 +1,7 @@
local inspect = { local inspect = {
_VERSION = "inspect.lua 3.1.0", _VERSION = 'inspect.lua 3.1.0',
_URL = "http://github.com/kikito/inspect.lua", _URL = 'http://github.com/kikito/inspect.lua',
_DESCRIPTION = "human-readable representations of tables", _DESCRIPTION = 'human-readable representations of tables',
_LICENSE = [[ _LICENSE = [[
MIT LICENSE MIT LICENSE
@@ -30,12 +30,12 @@ local inspect = {
inspect.KEY = setmetatable({}, { inspect.KEY = setmetatable({}, {
__tostring = function() __tostring = function()
return "inspect.KEY" return 'inspect.KEY'
end, end,
}) })
inspect.METATABLE = setmetatable({}, { inspect.METATABLE = setmetatable({}, {
__tostring = function() __tostring = function()
return "inspect.METATABLE" return 'inspect.METATABLE'
end, end,
}) })
@@ -61,52 +61,52 @@ end
-- \a => '\\a', \0 => '\\0', 31 => '\31' -- \a => '\\a', \0 => '\\0', 31 => '\31'
local shortControlCharEscapes = { local shortControlCharEscapes = {
["\a"] = "\\a", ['\a'] = '\\a',
["\b"] = "\\b", ['\b'] = '\\b',
["\f"] = "\\f", ['\f'] = '\\f',
["\n"] = "\\n", ['\n'] = '\\n',
["\r"] = "\\r", ['\r'] = '\\r',
["\t"] = "\\t", ['\t'] = '\\t',
["\v"] = "\\v", ['\v'] = '\\v',
["\127"] = "\\127", ['\127'] = '\\127',
} }
local longControlCharEscapes = { ["\127"] = "\127" } local longControlCharEscapes = { ['\127'] = '\127' }
for i = 0, 31 do for i = 0, 31 do
local ch = char(i) local ch = char(i)
if not shortControlCharEscapes[ch] then if not shortControlCharEscapes[ch] then
shortControlCharEscapes[ch] = "\\" .. i shortControlCharEscapes[ch] = '\\' .. i
longControlCharEscapes[ch] = fmt("\\%03d", i) longControlCharEscapes[ch] = fmt('\\%03d', i)
end end
end end
local function escape(str) 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 end
local function isIdentifier(str) 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 end
local flr = math.floor local flr = math.floor
local function isSequenceKey(k, sequenceLength) 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 end
local defaultTypeOrders = { local defaultTypeOrders = {
["number"] = 1, ['number'] = 1,
["boolean"] = 2, ['boolean'] = 2,
["string"] = 3, ['string'] = 3,
["table"] = 4, ['table'] = 4,
["function"] = 5, ['function'] = 5,
["userdata"] = 6, ['userdata'] = 6,
["thread"] = 7, ['thread'] = 7,
} }
local function sortKeys(a, b) local function sortKeys(a, b)
local ta, tb = type(a), type(b) local ta, tb = type(a), type(b)
-- strings and numbers are sorted numerically/alphabetically -- 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 return a < b
end end
@@ -137,7 +137,7 @@ local function getKeys(t)
end end
local function countCycles(x, cycles) local function countCycles(x, cycles)
if type(x) == "table" then if type(x) == 'table' then
if cycles[x] then if cycles[x] then
cycles[x] = cycles[x] + 1 cycles[x] = cycles[x] + 1
else else
@@ -173,7 +173,7 @@ local function processRecursive(process, item, path, visited)
end end
local processed = process(item, path) local processed = process(item, path)
if type(processed) == "table" then if type(processed) == 'table' then
local processedCopy = {} local processedCopy = {}
visited[item] = processedCopy visited[item] = processedCopy
local processedKey local processedKey
@@ -186,7 +186,7 @@ local function processRecursive(process, item, path, visited)
end end
local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)
if type(mt) ~= "table" then if type(mt) ~= 'table' then
mt = nil mt = nil
end end
setmetatable(processedCopy, mt) setmetatable(processedCopy, mt)
@@ -222,27 +222,27 @@ end
function Inspector:putValue(v) function Inspector:putValue(v)
local buf = self.buf local buf = self.buf
local tv = type(v) local tv = type(v)
if tv == "string" then if tv == 'string' then
puts(buf, smartQuote(escape(v))) puts(buf, smartQuote(escape(v)))
elseif elseif
tv == "number" tv == 'number'
or tv == "boolean" or tv == 'boolean'
or tv == "nil" or tv == 'nil'
or tv == "cdata" or tv == 'cdata'
or tv == "ctype" or tv == 'ctype'
or (vim and v == vim.NIL) or (vim and v == vim.NIL)
then then
puts(buf, tostring(v)) 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 local t = v
if t == inspect.KEY or t == inspect.METATABLE then if t == inspect.KEY or t == inspect.METATABLE then
puts(buf, tostring(t)) puts(buf, tostring(t))
elseif self.level >= self.depth then elseif self.level >= self.depth then
puts(buf, "{...}") puts(buf, '{...}')
else else
if self.cycles[t] > 1 then if self.cycles[t] > 1 then
puts(buf, fmt("<%d>", self:getId(t))) puts(buf, fmt('<%d>', self:getId(t)))
end end
local keys, keysLen, seqLen = getKeys(t) local keys, keysLen, seqLen = getKeys(t)
@@ -253,15 +253,15 @@ function Inspector:putValue(v)
return return
end end
puts(buf, "{") puts(buf, '{')
self.level = self.level + 1 self.level = self.level + 1
for i = 1, seqLen + keysLen do for i = 1, seqLen + keysLen do
if i > 1 then if i > 1 then
puts(buf, ",") puts(buf, ',')
end end
if i <= seqLen then if i <= seqLen then
puts(buf, " ") puts(buf, ' ')
self:putValue(t[i]) self:putValue(t[i])
else else
local k = keys[i - seqLen] local k = keys[i - seqLen]
@@ -269,36 +269,36 @@ function Inspector:putValue(v)
if isIdentifier(k) then if isIdentifier(k) then
puts(buf, k) puts(buf, k)
else else
puts(buf, "[") puts(buf, '[')
self:putValue(k) self:putValue(k)
puts(buf, "]") puts(buf, ']')
end end
puts(buf, " = ") puts(buf, ' = ')
self:putValue(t[k]) self:putValue(t[k])
end end
end end
if type(mt) == "table" then if type(mt) == 'table' then
if seqLen + keysLen > 0 then if seqLen + keysLen > 0 then
puts(buf, ",") puts(buf, ',')
end end
tabify(self) tabify(self)
puts(buf, "<metatable> = ") puts(buf, '<metatable> = ')
self:putValue(mt) self:putValue(mt)
end end
self.level = self.level - 1 self.level = self.level - 1
if keysLen > 0 or type(mt) == "table" then if keysLen > 0 or type(mt) == 'table' then
tabify(self) tabify(self)
elseif seqLen > 0 then elseif seqLen > 0 then
puts(buf, " ") puts(buf, ' ')
end end
puts(buf, "}") puts(buf, '}')
end end
else else
puts(buf, fmt("<%s %d>", tv, self:getId(v))) puts(buf, fmt('<%s %d>', tv, self:getId(v)))
end end
end end
@@ -306,8 +306,8 @@ function inspect.inspect(root, options)
options = options or {} options = options or {}
local depth = options.depth or math.huge local depth = options.depth or math.huge
local newline = options.newline or "\n" local newline = options.newline or '\n'
local indent = options.indent or " " local indent = options.indent or ' '
local process = options.process local process = options.process
if process then if process then

View File

@@ -49,15 +49,15 @@ local keymap = {}
--- Default `false`. --- Default `false`.
---@see |nvim_set_keymap()| ---@see |nvim_set_keymap()|
function keymap.set(mode, lhs, rhs, opts) function keymap.set(mode, lhs, rhs, opts)
vim.validate { vim.validate({
mode = { mode, { 's', 't' } }, mode = { mode, { 's', 't' } },
lhs = { lhs, 's' }, lhs = { lhs, 's' },
rhs = { rhs, { 's', 'f' } }, rhs = { rhs, { 's', 'f' } },
opts = {opts, 't', true} opts = { opts, 't', true },
} })
opts = vim.deepcopy(opts) or {} 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 mode = type(mode) == 'string' and { mode } or mode
if is_rhs_luaref and opts.expr then if is_rhs_luaref and opts.expr then
@@ -118,11 +118,11 @@ end
---@see |vim.keymap.set()| ---@see |vim.keymap.set()|
--- ---
function keymap.del(modes, lhs, opts) function keymap.del(modes, lhs, opts)
vim.validate { vim.validate({
mode = { modes, { 's', 't' } }, mode = { modes, { 's', 't' } },
lhs = { lhs, 's' }, lhs = { lhs, 's' },
opts = {opts, 't', true} opts = { opts, 't', true },
} })
opts = opts or {} opts = opts or {}
modes = type(modes) == 'string' and { modes } or modes modes = type(modes) == 'string' and { modes } or modes

View File

@@ -1,58 +1,58 @@
local if_nil = vim.F.if_nil local if_nil = vim.F.if_nil
local default_handlers = require 'vim.lsp.handlers' local default_handlers = require('vim.lsp.handlers')
local log = require 'vim.lsp.log' local log = require('vim.lsp.log')
local lsp_rpc = require 'vim.lsp.rpc' local lsp_rpc = require('vim.lsp.rpc')
local protocol = require 'vim.lsp.protocol' local protocol = require('vim.lsp.protocol')
local util = require 'vim.lsp.util' local util = require('vim.lsp.util')
local sync = require 'vim.lsp.sync' local sync = require('vim.lsp.sync')
local vim = vim local vim = vim
local nvim_err_writeln, nvim_buf_get_lines, nvim_command, 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 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 uv = vim.loop
local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend
local validate = vim.validate local validate = vim.validate
local lsp = { local lsp = {
protocol = protocol; protocol = protocol,
handlers = default_handlers; handlers = default_handlers,
buf = require'vim.lsp.buf'; buf = require('vim.lsp.buf'),
diagnostic = require'vim.lsp.diagnostic'; diagnostic = require('vim.lsp.diagnostic'),
codelens = require'vim.lsp.codelens'; codelens = require('vim.lsp.codelens'),
util = util; util = util,
-- Allow raw RPC access. -- Allow raw RPC access.
rpc = lsp_rpc; rpc = lsp_rpc,
-- Export these directly from 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. -- maps request name to the required server_capability in the client.
lsp._request_name_to_capability = { lsp._request_name_to_capability = {
['textDocument/hover'] = { 'hoverProvider' }; ['textDocument/hover'] = { 'hoverProvider' },
['textDocument/signatureHelp'] = { 'signatureHelpProvider' }; ['textDocument/signatureHelp'] = { 'signatureHelpProvider' },
['textDocument/definition'] = { 'definitionProvider' }; ['textDocument/definition'] = { 'definitionProvider' },
['textDocument/implementation'] = { 'implementationProvider' }; ['textDocument/implementation'] = { 'implementationProvider' },
['textDocument/declaration'] = { 'declarationProvider' }; ['textDocument/declaration'] = { 'declarationProvider' },
['textDocument/typeDefinition'] = { 'typeDefinitionProvider' }; ['textDocument/typeDefinition'] = { 'typeDefinitionProvider' },
['textDocument/documentSymbol'] = { 'documentSymbolProvider' }; ['textDocument/documentSymbol'] = { 'documentSymbolProvider' },
['textDocument/prepareCallHierarchy'] = { 'callHierarchyProvider' }; ['textDocument/prepareCallHierarchy'] = { 'callHierarchyProvider' },
['textDocument/rename'] = { 'renameProvider' }; ['textDocument/rename'] = { 'renameProvider' },
['textDocument/prepareRename'] = { 'renameProvider', 'prepareProvider'} ; ['textDocument/prepareRename'] = { 'renameProvider', 'prepareProvider' },
['textDocument/codeAction'] = { 'codeActionProvider' }; ['textDocument/codeAction'] = { 'codeActionProvider' },
['textDocument/codeLens'] = { 'codeLensProvider' }; ['textDocument/codeLens'] = { 'codeLensProvider' },
['codeLens/resolve'] = { 'codeLensProvider', 'resolveProvider' }; ['codeLens/resolve'] = { 'codeLensProvider', 'resolveProvider' },
['workspace/executeCommand'] = { 'executeCommandProvider' }; ['workspace/executeCommand'] = { 'executeCommandProvider' },
['workspace/symbol'] = { 'workspaceSymbolProvider' }; ['workspace/symbol'] = { 'workspaceSymbolProvider' },
['textDocument/references'] = { 'referencesProvider' }; ['textDocument/references'] = { 'referencesProvider' },
['textDocument/rangeFormatting'] = { 'documentRangeFormattingProvider' }; ['textDocument/rangeFormatting'] = { 'documentRangeFormattingProvider' },
['textDocument/formatting'] = { 'documentFormattingProvider' }; ['textDocument/formatting'] = { 'documentFormattingProvider' },
['textDocument/completion'] = { 'completionProvider' }; ['textDocument/completion'] = { 'completionProvider' },
['textDocument/documentHighlight'] = { 'documentHighlightProvider' }; ['textDocument/documentHighlight'] = { 'documentHighlightProvider' },
} }
-- TODO improve handling of scratch buffers with LSP attached. -- 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 ---@param {...} (List of strings) List to write to the buffer
local function err_message(...) local function err_message(...)
nvim_err_writeln(table.concat(vim.tbl_flatten{...})) nvim_err_writeln(table.concat(vim.tbl_flatten({ ... })))
nvim_command("redraw") nvim_command('redraw')
end end
---@private ---@private
@@ -73,7 +73,7 @@ end
---buffer if not given. ---buffer if not given.
---@returns bufnr (number) Number of requested buffer ---@returns bufnr (number) Number of requested buffer
local function resolve_bufnr(bufnr) local function resolve_bufnr(bufnr)
validate { bufnr = { bufnr, 'n', true } } validate({ bufnr = { bufnr, 'n', true } })
if bufnr == nil or bufnr == 0 then if bufnr == nil or bufnr == 0 then
return vim.api.nvim_get_current_buf() return vim.api.nvim_get_current_buf()
end end
@@ -85,7 +85,10 @@ end
--- supported in any of the servers registered for the current buffer. --- supported in any of the servers registered for the current buffer.
---@param method (string) name of the method ---@param method (string) name of the method
function lsp._unsupported_method(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) log.warn(msg)
return msg return msg
end end
@@ -96,23 +99,29 @@ end
---@param filename (string) path to check ---@param filename (string) path to check
---@returns true if {filename} exists and is a directory, false otherwise ---@returns true if {filename} exists and is a directory, false otherwise
local function is_dir(filename) local function is_dir(filename)
validate{filename={filename,'s'}} validate({ filename = { filename, 's' } })
local stat = uv.fs_stat(filename) local stat = uv.fs_stat(filename)
return stat and stat.type == 'directory' or false return stat and stat.type == 'directory' or false
end end
local wait_result_reason = { [-1] = "timeout"; [-2] = "interrupted"; [-3] = "error" } local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'error' }
local valid_encodings = { local valid_encodings = {
["utf-8"] = 'utf-8'; ["utf-16"] = 'utf-16'; ["utf-32"] = 'utf-32'; ['utf-8'] = 'utf-8',
["utf8"] = 'utf-8'; ["utf16"] = 'utf-16'; ["utf32"] = 'utf-32'; ['utf-16'] = 'utf-16',
UTF8 = 'utf-8'; UTF16 = 'utf-16'; UTF32 = 'utf-32'; ['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 = { local format_line_ending = {
["unix"] = '\n', ['unix'] = '\n',
["dos"] = '\r\n', ['dos'] = '\r\n',
["mac"] = '\r', ['mac'] = '\r',
} }
---@private ---@private
@@ -138,10 +147,10 @@ local uninitialized_clients = {}
---@private ---@private
local function for_each_buffer_client(bufnr, fn, restrict_client_ids) local function for_each_buffer_client(bufnr, fn, restrict_client_ids)
validate { validate({
fn = { fn, 'f' }; fn = { fn, 'f' },
restrict_client_ids = { restrict_client_ids, 't' , true}; restrict_client_ids = { restrict_client_ids, 't', true },
} })
bufnr = resolve_bufnr(bufnr) bufnr = resolve_bufnr(bufnr)
local client_ids = all_buffer_active_clients[bufnr] local client_ids = all_buffer_active_clients[bufnr]
if not client_ids or tbl_isempty(client_ids) then 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|. -- 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 -- Can be used to look up the string from a the number or the number
-- from the string. -- from the string.
lsp.client_errors = tbl_extend("error", lsp_rpc.client_errors, vim.tbl_add_reverse_lookup { lsp.client_errors = tbl_extend(
ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1; 'error',
lsp_rpc.client_errors,
vim.tbl_add_reverse_lookup({
ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1,
}) })
)
---@private ---@private
--- Normalizes {encoding} to valid LSP encoding names. --- 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 ---@param encoding (string) Encoding to normalize
---@returns (string) normalized encoding name ---@returns (string) normalized encoding name
local function validate_encoding(encoding) local function validate_encoding(encoding)
validate { validate({
encoding = { encoding, 's' }; encoding = { encoding, 's' },
} })
return valid_encodings[encoding:lower()] return valid_encodings[encoding:lower()]
or error(string.format("Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding)) or error(string.format("Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding))
end end
@@ -194,16 +207,19 @@ end
---@returns (string) the command ---@returns (string) the command
---@returns (list of strings) its arguments ---@returns (list of strings) its arguments
function lsp._cmd_parts(input) function lsp._cmd_parts(input)
vim.validate{cmd={ vim.validate({ cmd = {
input, input,
function() return vim.tbl_islist(input) end, function()
"list"}} return vim.tbl_islist(input)
end,
'list',
} })
local cmd = input[1] local cmd = input[1]
local cmd_args = {} local cmd_args = {}
-- Don't mutate our input. -- Don't mutate our input.
for i, v in ipairs(input) do for i, v in ipairs(input) do
vim.validate{["cmd argument"]={v, "s"}} vim.validate({ ['cmd argument'] = { v, 's' } })
if i > 1 then if i > 1 then
table.insert(cmd_args, v) table.insert(cmd_args, v)
end end
@@ -233,31 +249,29 @@ end
--- ---
---@see |vim.lsp.start_client()| ---@see |vim.lsp.start_client()|
local function validate_client_config(config) local function validate_client_config(config)
validate { validate({
config = { config, 't' }; config = { config, 't' },
} })
validate { validate({
handlers = { config.handlers, "t", true }; handlers = { config.handlers, 't', true },
capabilities = { config.capabilities, "t", true }; capabilities = { config.capabilities, 't', true },
cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" }; cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), 'directory' },
cmd_env = { config.cmd_env, "t", true }; cmd_env = { config.cmd_env, 't', true },
detached = { config.detached, "b", true }; detached = { config.detached, 'b', true },
name = { config.name, 's', true }; name = { config.name, 's', true },
on_error = { config.on_error, "f", true }; on_error = { config.on_error, 'f', true },
on_exit = { config.on_exit, "f", true }; on_exit = { config.on_exit, 'f', true },
on_init = { config.on_init, "f", true }; on_init = { config.on_init, 'f', true },
settings = { config.settings, "t", true }; settings = { config.settings, 't', true },
commands = { config.commands, 't', true }; commands = { config.commands, 't', true },
before_init = { config.before_init, "f", true }; before_init = { config.before_init, 'f', true },
offset_encoding = { config.offset_encoding, "s", true }; offset_encoding = { config.offset_encoding, 's', true },
flags = { config.flags, "t", true }; flags = { config.flags, 't', true },
get_language_id = { config.get_language_id, "f", true }; get_language_id = { config.get_language_id, 'f', true },
} })
assert( assert(
(not config.flags (not config.flags or not config.flags.debounce_text_changes or type(config.flags.debounce_text_changes) == 'number'),
or not config.flags.debounce_text_changes 'flags.debounce_text_changes must be a number with the debounce time in milliseconds'
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) local cmd, cmd_args = lsp._cmd_parts(config.cmd)
@@ -267,9 +281,9 @@ local function validate_client_config(config)
end end
return { return {
cmd = cmd; cmd = cmd,
cmd_args = cmd_args; cmd_args = cmd_args,
offset_encoding = offset_encoding; offset_encoding = offset_encoding,
} }
end end
@@ -329,14 +343,15 @@ do
function changetracking.init(client, bufnr) function changetracking.init(client, bufnr)
local use_incremental_sync = ( local use_incremental_sync = (
if_nil(client.config.flags.allow_incremental_sync, true) 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] local state = state_by_client[client.id]
if not state then if not state then
state = { state = {
buffers = {}; buffers = {},
debounce = client.config.flags.debounce_text_changes or 150, 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 state_by_client[client.id] = state
end end
@@ -405,7 +420,6 @@ do
---@private ---@private
function changetracking.prepare(bufnr, firstline, lastline, new_lastline) function changetracking.prepare(bufnr, firstline, lastline, new_lastline)
local incremental_changes = function(client, buf_state) local incremental_changes = function(client, buf_state)
local prev_lines = buf_state.lines local prev_lines = buf_state.lines
local curr_lines = buf_state.lines_tmp local curr_lines = buf_state.lines_tmp
@@ -426,7 +440,14 @@ do
local line_ending = buf_get_line_ending(bufnr) local line_ending = buf_get_line_ending(bufnr)
local incremental_change = sync.compute_diff( 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. -- 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, -- At this point the prev_lines table is useless, but its internal storage has already been allocated,
@@ -443,12 +464,14 @@ do
end end
local full_changes = once(function() local full_changes = once(function()
return { return {
text = buf_get_full_text(bufnr); text = buf_get_full_text(bufnr),
}; }
end) end)
local uri = vim.uri_from_bufnr(bufnr) local uri = vim.uri_from_bufnr(bufnr)
return function(client) 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 return
end end
local state = state_by_client[client.id] local state = state_by_client[client.id]
@@ -467,7 +490,7 @@ do
return return
end end
local changes = state.use_incremental_sync and buf_state.pending_changes or { full_changes() } local changes = state.use_incremental_sync and buf_state.pending_changes or { full_changes() }
client.notify("textDocument/didChange", { client.notify('textDocument/didChange', {
textDocument = { textDocument = {
uri = uri, uri = uri,
version = util.buf_versions[bufnr], version = util.buf_versions[bufnr],
@@ -519,7 +542,6 @@ do
end end
end end
---@private ---@private
--- Default handler for the 'textDocument/didOpen' LSP notification. --- Default handler for the 'textDocument/didOpen' LSP notification.
--- ---
@@ -527,7 +549,7 @@ end
---@param client Client object ---@param client Client object
local function text_document_did_open_handler(bufnr, client) local function text_document_did_open_handler(bufnr, client)
changetracking.init(client, bufnr) 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 return
end end
if not vim.api.nvim_buf_is_loaded(bufnr) then 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 = { local params = {
textDocument = { textDocument = {
version = 0; version = 0,
uri = vim.uri_from_bufnr(bufnr); uri = vim.uri_from_bufnr(bufnr),
languageId = client.config.get_language_id(bufnr, filetype); languageId = client.config.get_language_id(bufnr, filetype),
text = buf_get_full_text(bufnr); text = buf_get_full_text(bufnr),
} },
} }
client.notify('textDocument/didOpen', params) client.notify('textDocument/didOpen', params)
util.buf_versions[bufnr] = params.textDocument.version 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. -- 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, -- It is possible to pass in something that will calculate a different filetype,
-- to be sent by the client. -- 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 client_id = next_client_id()
local handlers = config.handlers or {} local handlers = config.handlers or {}
local name = config.name or tostring(client_id) 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 = {} local dispatch = {}
@@ -807,10 +831,10 @@ function lsp.start_client(config)
local _ = log.trace() and log.trace('server_request', method, params) local _ = log.trace() and log.trace('server_request', method, params)
local handler = resolve_handler(method) local handler = resolve_handler(method)
if handler then 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 }) return handler(nil, params, { method = method, client_id = client_id })
end 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) return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
end end
@@ -822,12 +846,12 @@ function lsp.start_client(config)
---@see |vim.lsp.rpc.client_errors| for possible errors. Use ---@see |vim.lsp.rpc.client_errors| for possible errors. Use
---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name. ---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name.
function dispatch.on_error(code, err) 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)) err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
if config.on_error then if config.on_error then
local status, usererr = pcall(config.on_error, code, err) local status, usererr = pcall(config.on_error, code, err)
if not status then 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)) err_message(log_prefix, ' user on_error failed: ', tostring(usererr))
end end
end end
@@ -853,7 +877,7 @@ function lsp.start_client(config)
end end
if code ~= 0 or (signal ~= 0 and signal ~= 15) then 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.schedule(function()
vim.notify(msg, vim.log.levels.WARN) vim.notify(msg, vim.log.levels.WARN)
end) end)
@@ -862,38 +886,41 @@ function lsp.start_client(config)
-- Start the RPC client. -- Start the RPC client.
local rpc = lsp_rpc.start(cmd, cmd_args, dispatch, { local rpc = lsp_rpc.start(cmd, cmd_args, dispatch, {
cwd = config.cmd_cwd; cwd = config.cmd_cwd,
env = config.cmd_env; env = config.cmd_env,
detached = config.detached; detached = config.detached,
}) })
-- Return nil if client fails to start -- Return nil if client fails to start
if not rpc then return end if not rpc then
return
end
local client = { local client = {
id = client_id; id = client_id,
name = name; name = name,
rpc = rpc; rpc = rpc,
offset_encoding = offset_encoding; offset_encoding = offset_encoding,
config = config; config = config,
attached_buffers = {}; attached_buffers = {},
handlers = handlers; handlers = handlers,
commands = config.commands or {}; commands = config.commands or {},
requests = {}; requests = {},
-- for $/progress report -- 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. -- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.
uninitialized_clients[client_id] = client; uninitialized_clients[client_id] = client
---@private ---@private
local function initialize() local function initialize()
local valid_traces = { local valid_traces = {
off = 'off'; messages = 'messages'; verbose = 'verbose'; off = 'off',
messages = 'messages',
verbose = 'verbose',
} }
local version = vim.version() local version = vim.version()
@@ -902,10 +929,12 @@ function lsp.start_client(config)
local root_path local root_path
if config.workspace_folders or config.root_dir then if config.workspace_folders or config.root_dir then
if config.root_dir and not config.workspace_folders then if config.root_dir and not config.workspace_folders then
workspace_folders = {{ workspace_folders = {
uri = vim.uri_from_fname(config.root_dir); {
name = string.format("%s", config.root_dir); uri = vim.uri_from_fname(config.root_dir),
}}; name = string.format('%s', config.root_dir),
},
}
else else
workspace_folders = config.workspace_folders workspace_folders = config.workspace_folders
end end
@@ -922,41 +951,41 @@ function lsp.start_client(config)
-- the process has not been started by another process. If the parent -- the process has not been started by another process. If the parent
-- process is not alive then the server should exit (see exit notification) -- process is not alive then the server should exit (see exit notification)
-- its process. -- its process.
processId = uv.getpid(); processId = uv.getpid(),
-- Information about the client -- Information about the client
-- since 3.15.0 -- since 3.15.0
clientInfo = { clientInfo = {
name = "Neovim", name = 'Neovim',
version = string.format("%s.%s.%s", version.major, version.minor, version.patch) version = string.format('%s.%s.%s', version.major, version.minor, version.patch),
}; },
-- The rootPath of the workspace. Is null if no folder is open. -- The rootPath of the workspace. Is null if no folder is open.
-- --
-- @deprecated in favour of rootUri. -- @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 -- The rootUri of the workspace. Is null if no folder is open. If both
-- `rootPath` and `rootUri` are set `rootUri` wins. -- `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. -- The workspace folders configured in the client when the server starts.
-- This property is only available if the client supports workspace folders. -- This property is only available if the client supports workspace folders.
-- It can be `null` if the client supports workspace folders but none are -- It can be `null` if the client supports workspace folders but none are
-- configured. -- configured.
workspaceFolders = workspace_folders or vim.NIL; workspaceFolders = workspace_folders or vim.NIL,
-- User provided initialization options. -- User provided initialization options.
initializationOptions = config.init_options; initializationOptions = config.init_options,
-- The capabilities provided by the client (editor or tool) -- 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"). -- The initial trace setting. If omitted trace is disabled ("off").
-- trace = "off" | "messages" | "verbose"; -- trace = "off" | "messages" | "verbose";
trace = valid_traces[config.trace] or 'off'; trace = valid_traces[config.trace] or 'off',
} }
if config.before_init then if config.before_init then
-- TODO(ashkan) handle errors here. -- TODO(ashkan) handle errors here.
pcall(config.before_init, initialize_params, config) pcall(config.before_init, initialize_params, config)
end 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) rpc.request('initialize', initialize_params, function(init_err, result)
assert(not init_err, tostring(init_err)) 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()) rpc.notify('initialized', vim.empty_dict())
client.initialized = true client.initialized = true
uninitialized_clients[client_id] = nil uninitialized_clients[client_id] = nil
@@ -973,10 +1002,13 @@ function lsp.start_client(config)
local mt = {} local mt = {}
mt.__index = function(table, key) mt.__index = function(table, key)
if key == 'resolved_capabilities' then if key == 'resolved_capabilities' then
vim.notify_once("[LSP] Accessing client.resolved_capabilities is deprecated, " .. vim.notify_once(
"update your plugins or configuration to access client.server_capabilities instead." .. '[LSP] Accessing client.resolved_capabilities is deprecated, '
"The new key/value pairs in server_capabilities directly match those " .. .. 'update your plugins or configuration to access client.server_capabilities instead.'
"defined in the language server protocol", vim.log.levels.WARN) .. '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)) rawset(table, key, protocol._resolve_capabilities_compat(client.server_capabilities))
return rawget(table, key) return rawget(table, key)
else else
@@ -1004,7 +1036,8 @@ function lsp.start_client(config)
pcall(handlers.on_error, lsp.client_errors.ON_INIT_CALLBACK_ERROR, err) pcall(handlers.on_error, lsp.client_errors.ON_INIT_CALLBACK_ERROR, err)
end end
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. -- Only assign after initialized.
active_clients[client_id] = client active_clients[client_id] = client
@@ -1039,22 +1072,22 @@ function lsp.start_client(config)
function client.request(method, params, handler, bufnr) function client.request(method, params, handler, bufnr)
if not handler then if not handler then
handler = resolve_handler(method) 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 end
-- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
changetracking.flush(client, bufnr) changetracking.flush(client, bufnr)
bufnr = resolve_bufnr(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) local success, request_id = rpc.request(method, params, function(err, result)
handler(err, result, { method = method, client_id = client_id, bufnr = bufnr, params = params }) handler(err, result, { method = method, client_id = client_id, bufnr = bufnr, params = params })
end, function(request_id) end, function(request_id)
client.requests[request_id] = nil client.requests[request_id] = nil
nvim_command("doautocmd <nomodeline> User LspRequest") nvim_command('doautocmd <nomodeline> User LspRequest')
end) end)
if success then if success then
client.requests[request_id] = { type = 'pending', bufnr = bufnr, method = method } client.requests[request_id] = { type = 'pending', bufnr = bufnr, method = method }
nvim_command("doautocmd <nomodeline> User LspRequest") nvim_command('doautocmd <nomodeline> User LspRequest')
end end
return success, request_id return success, request_id
@@ -1081,9 +1114,10 @@ function lsp.start_client(config)
request_result = { err = err, result = result } request_result = { err = err, result = result }
end end
local success, request_id = client.request(method, params, _sync_handler, local success, request_id = client.request(method, params, _sync_handler, bufnr)
bufnr) if not success then
if not success then return nil end return nil
end
local wait_result, reason = vim.wait(timeout_ms or 1000, function() local wait_result, reason = vim.wait(timeout_ms or 1000, function()
return request_result ~= nil return request_result ~= nil
@@ -1118,13 +1152,13 @@ function lsp.start_client(config)
---@returns true if any client returns true; false otherwise ---@returns true if any client returns true; false otherwise
---@see |vim.lsp.client.notify()| ---@see |vim.lsp.client.notify()|
function client.cancel_request(id) function client.cancel_request(id)
validate{id = {id, 'n'}} validate({ id = { id, 'n' } })
local request = client.requests[id] local request = client.requests[id]
if request and request.type == 'pending' then if request and request.type == 'pending' then
request.type = 'cancel' request.type = 'cancel'
nvim_command("doautocmd <nomodeline> User LspRequest") nvim_command('doautocmd <nomodeline> User LspRequest')
end end
return rpc.notify("$/cancelRequest", { id = id }) return rpc.notify('$/cancelRequest', { id = id })
end end
-- Track this so that we can escalate automatically if we've already tried a -- 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) ---@param force (bool, optional)
function client.stop(force) function client.stop(force)
lsp.diagnostic.reset(client_id, all_buffer_active_clients) lsp.diagnostic.reset(client_id, all_buffer_active_clients)
changetracking.reset(client_id) changetracking.reset(client_id)
for _, client_ids in pairs(all_buffer_active_clients) do for _, client_ids in pairs(all_buffer_active_clients) do
@@ -1150,7 +1183,7 @@ function lsp.start_client(config)
if handle:is_closing() then if handle:is_closing() then
return return
end 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) handle:kill(15)
return return
end end
@@ -1198,7 +1231,6 @@ end
local text_document_did_change_handler local text_document_did_change_handler
do do
text_document_did_change_handler = function(_, bufnr, changedtick, firstline, lastline, new_lastline) 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 -- 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 if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
return true return true
@@ -1215,17 +1247,17 @@ function lsp._text_document_did_save_handler(bufnr)
local uri = vim.uri_from_bufnr(bufnr) local uri = vim.uri_from_bufnr(bufnr)
local text = once(buf_get_full_text) local text = once(buf_get_full_text)
for_each_buffer_client(bufnr, function(client) 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 if save_capability then
local included_text 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) included_text = text(bufnr)
end end
client.notify('textDocument/didSave', { client.notify('textDocument/didSave', {
textDocument = { textDocument = {
uri = uri; uri = uri,
}; },
text = included_text; text = included_text,
}) })
end end
end) end)
@@ -1239,15 +1271,13 @@ end
---@param bufnr (number) Buffer handle, or 0 for current ---@param bufnr (number) Buffer handle, or 0 for current
---@param client_id (number) Client id ---@param client_id (number) Client id
function lsp.buf_attach_client(bufnr, client_id) function lsp.buf_attach_client(bufnr, client_id)
validate { validate({
bufnr = {bufnr, 'n', true}; bufnr = { bufnr, 'n', true },
client_id = {client_id, 'n'}; client_id = { client_id, 'n' },
} })
bufnr = resolve_bufnr(bufnr) bufnr = resolve_bufnr(bufnr)
if not vim.api.nvim_buf_is_loaded(bufnr) then if not vim.api.nvim_buf_is_loaded(bufnr) then
local _ = log.warn() and log.warn( local _ = log.warn() and log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr))
string.format("buf_attach_client called on unloaded buffer (id: %d): ", bufnr)
)
return false return false
end end
local buffer_client_ids = all_buffer_active_clients[bufnr] 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) vim.api.nvim_exec(string.format(buf_did_save_autocommand, client_id, bufnr, bufnr), false)
-- First time, so attach and set up stuff. -- First time, so attach and set up stuff.
vim.api.nvim_buf_attach(bufnr, false, { vim.api.nvim_buf_attach(bufnr, false, {
on_lines = text_document_did_change_handler; on_lines = text_document_did_change_handler,
on_reload = function() on_reload = function()
local params = { textDocument = { uri = uri; } } local params = { textDocument = { uri = uri } }
for_each_buffer_client(bufnr, function(client, _) for_each_buffer_client(bufnr, function(client, _)
changetracking.reset_buf(client, bufnr) 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) client.notify('textDocument/didClose', params)
end end
text_document_did_open_handler(bufnr, client) text_document_did_open_handler(bufnr, client)
end) end)
end; end,
on_detach = function() on_detach = function()
local params = { textDocument = { uri = uri; } } local params = { textDocument = { uri = uri } }
for_each_buffer_client(bufnr, function(client, _) for_each_buffer_client(bufnr, function(client, _)
changetracking.reset_buf(client, bufnr) 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) client.notify('textDocument/didClose', params)
end end
end) end)
util.buf_versions[bufnr] = nil util.buf_versions[bufnr] = nil
all_buffer_active_clients[bufnr] = nil all_buffer_active_clients[bufnr] = nil
end; end,
-- TODO if we know all of the potential clients ahead of time, then we -- TODO if we know all of the potential clients ahead of time, then we
-- could conditionally set this. -- could conditionally set this.
-- utf_sizes = size_index > 1; -- utf_sizes = size_index > 1;
utf_sizes = true; utf_sizes = true,
}) })
end 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. -- This is our first time attaching this client to this buffer.
buffer_client_ids[client_id] = true buffer_client_ids[client_id] = true
@@ -1315,25 +1347,23 @@ end
---@param bufnr number Buffer handle, or 0 for current ---@param bufnr number Buffer handle, or 0 for current
---@param client_id number Client id ---@param client_id number Client id
function lsp.buf_detach_client(bufnr, client_id) function lsp.buf_detach_client(bufnr, client_id)
validate { validate({
bufnr = {bufnr, 'n', true}; bufnr = { bufnr, 'n', true },
client_id = {client_id, 'n'}; client_id = { client_id, 'n' },
} })
bufnr = resolve_bufnr(bufnr) bufnr = resolve_bufnr(bufnr)
local client = lsp.get_client_by_id(client_id) local client = lsp.get_client_by_id(client_id)
if not client or not client.attached_buffers[bufnr] then if not client or not client.attached_buffers[bufnr] then
vim.notify( vim.notify(string.format('Buffer (id: %d) is not attached to client (id: %d). Cannot detach.', client_id, bufnr))
string.format('Buffer (id: %d) is not attached to client (id: %d). Cannot detach.', client_id, bufnr)
)
return return
end end
changetracking.reset_buf(client, bufnr) 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 uri = vim.uri_from_bufnr(bufnr)
local params = { textDocument = { uri = uri; } } local params = { textDocument = { uri = uri } }
client.notify('textDocument/didClose', params) client.notify('textDocument/didClose', params)
end end
@@ -1349,7 +1379,6 @@ function lsp.buf_detach_client(bufnr, client_id)
vim.diagnostic.reset(namespace, bufnr) vim.diagnostic.reset(namespace, bufnr)
vim.notify(string.format('Detached buffer (id: %d) from client (id: %d)', bufnr, client_id)) vim.notify(string.format('Detached buffer (id: %d) from client (id: %d)', bufnr, client_id))
end end
--- Checks if a buffer is attached for a particular client. --- Checks if a buffer is attached for a particular client.
@@ -1414,7 +1443,7 @@ function lsp.get_active_clients()
end end
function lsp._vim_exit_handler() function lsp._vim_exit_handler()
log.info("exit_handler", active_clients) log.info('exit_handler', active_clients)
for _, client in pairs(uninitialized_clients) do for _, client in pairs(uninitialized_clients) do
client.stop(true) client.stop(true)
end end
@@ -1466,8 +1495,7 @@ function lsp._vim_exit_handler()
end end
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 --- Sends an async request for all active clients attached to the
--- buffer. --- 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 --- - Function which can be used to cancel all the requests. You could instead
--- iterate all clients and call their `cancel_request()` methods. --- iterate all clients and call their `cancel_request()` methods.
function lsp.buf_request(bufnr, method, params, handler) function lsp.buf_request(bufnr, method, params, handler)
validate { validate({
bufnr = { bufnr, 'n', true }; bufnr = { bufnr, 'n', true },
method = { method, 's' }; method = { method, 's' },
handler = { handler, 'f', true }; handler = { handler, 'f', true },
} })
local supported_clients = {} local supported_clients = {}
local method_supported = false 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 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 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.notify(lsp._unsupported_method(method), vim.log.levels.ERROR)
vim.api.nvim_command("redraw") vim.api.nvim_command('redraw')
return {}, function() end return {}, function() end
end end
@@ -1607,18 +1635,19 @@ end
--- ---
---@returns true if any client returns true; false otherwise ---@returns true if any client returns true; false otherwise
function lsp.buf_notify(bufnr, method, params) function lsp.buf_notify(bufnr, method, params)
validate { validate({
bufnr = { bufnr, 'n', true }; bufnr = { bufnr, 'n', true },
method = { method, 's' }; method = { method, 's' },
} })
local resp = false local resp = false
for_each_buffer_client(bufnr, function(client, _client_id, _resolved_bufnr) 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) end)
return resp return resp
end end
---@private ---@private
local function adjust_start_col(lnum, line, items, encoding) local function adjust_start_col(lnum, line, items, encoding)
local min_start_char = nil local min_start_char = nil
@@ -1650,7 +1679,7 @@ end
--- - findstart=0: column where the completion starts, or -2 or -3 --- - findstart=0: column where the completion starts, or -2 or -3
--- - findstart=1: list of matches (actually just calls |complete()|) --- - findstart=1: list of matches (actually just calls |complete()|)
function lsp.omnifunc(findstart, base) 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 bufnr = resolve_bufnr()
local has_buffer_clients = not tbl_isempty(all_buffer_active_clients[bufnr] or {}) local has_buffer_clients = not tbl_isempty(all_buffer_active_clients[bufnr] or {})
@@ -1663,12 +1692,12 @@ function lsp.omnifunc(findstart, base)
end end
-- Then, perform standard completion request -- 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 pos = vim.api.nvim_win_get_cursor(0)
local line = vim.api.nvim_get_current_line() local line = vim.api.nvim_get_current_line()
local line_to_cursor = line:sub(1, pos[2]) 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 -- Get the start position of the current keyword
local textMatch = vim.fn.match(line_to_cursor, '\\k*$') local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
@@ -1677,7 +1706,9 @@ function lsp.omnifunc(findstart, base)
local items = {} local items = {}
lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, result, ctx) 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`. -- Completion response items may be relative to a position different than `textMatch`.
-- Concrete example, with sumneko/lua-language-server: -- Concrete example, with sumneko/lua-language-server:
@@ -1734,14 +1765,14 @@ function lsp.formatexpr(opts)
if start_line > 0 and end_line > 0 then if start_line > 0 and end_line > 0 then
local params = { local params = {
textDocument = util.make_text_document_params(); textDocument = util.make_text_document_params(),
range = { range = {
start = { line = start_line - 1; character = 0; }; start = { line = start_line - 1, character = 0 },
["end"] = { line = end_line - 1; character = 0; }; ['end'] = { line = end_line - 1, character = 0 },
}; },
}; }
params.options = util.make_formatting_params().options 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. -- Apply the text edits from one and only one of the clients.
for client_id, response in pairs(client_results) do 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 if type(level) == 'string' or type(level) == 'number' then
log.set_level(level) log.set_level(level)
else else
error(string.format("Invalid log level: %q", level)) error(string.format('Invalid log level: %q', level))
end end
end end
@@ -1845,7 +1876,7 @@ end
---@param override_config (table) Table containing the keys to override behavior of the {handler} ---@param override_config (table) Table containing the keys to override behavior of the {handler}
function lsp.with(handler, override_config) function lsp.with(handler, override_config)
return function(err, result, ctx, 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
end end
@@ -1860,12 +1891,16 @@ function lsp._with_extend(name, options, user_config)
local resulting_config = {} local resulting_config = {}
for k, v in pairs(user_config) do for k, v in pairs(user_config) do
if options[k] == nil then if options[k] == nil then
error(debug.traceback(string.format( error(
"Invalid option for `%s`: %s. Valid options are:\n%s", debug.traceback(
string.format(
'Invalid option for `%s`: %s. Valid options are:\n%s',
name, name,
k, k,
vim.inspect(vim.tbl_keys(options)) vim.inspect(vim.tbl_keys(options))
))) )
)
)
end end
resulting_config[k] = v resulting_config[k] = v
@@ -1880,7 +1915,6 @@ function lsp._with_extend(name, options, user_config)
return resulting_config return resulting_config
end end
--- Registry for client side commands. --- Registry for client side commands.
--- This is an extension point for plugins to handle custom commands which are --- This is an extension point for plugins to handle custom commands which are
--- not part of the core language server protocol specification. --- not part of the core language server protocol specification.
@@ -1902,12 +1936,11 @@ end
--- The second argument is the `ctx` of |lsp-handler| --- The second argument is the `ctx` of |lsp-handler|
lsp.commands = setmetatable({}, { lsp.commands = setmetatable({}, {
__newindex = function(tbl, key, value) __newindex = function(tbl, key, value)
assert(type(key) == 'string', "The key for commands in `vim.lsp.commands` must be a string") 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(value) == 'function', 'Command added to `vim.lsp.commands` must be a function')
rawset(tbl, key, value) rawset(tbl, key, value)
end; end,
}) })
return lsp return lsp
-- vim:sw=2 ts=2 et -- vim:sw=2 ts=2 et

View File

@@ -41,7 +41,7 @@ P.take_until = function(targets, specials)
parsed = true, parsed = true,
value = { value = {
raw = table.concat(raw, ''), raw = table.concat(raw, ''),
esc = table.concat(esc, '') esc = table.concat(esc, ''),
}, },
pos = new_pos, pos = new_pos,
} }
@@ -248,48 +248,66 @@ S.format = P.any(
capture_index = values[3], capture_index = values[3],
}, Node) }, Node)
end), end),
P.map(P.seq(S.dollar, S.open, S.int, S.colon, S.slash, P.any( P.map(
P.token('upcase'), P.seq(
P.token('downcase'), S.dollar,
P.token('capitalize'), S.open,
P.token('camelcase'), S.int,
P.token('pascalcase') S.colon,
), S.close), function(values) 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({ return setmetatable({
type = Node.Type.FORMAT, type = Node.Type.FORMAT,
capture_index = values[3], capture_index = values[3],
modifier = values[6], modifier = values[6],
}, Node) }, Node)
end), end
P.map(P.seq(S.dollar, S.open, S.int, S.colon, P.any( ),
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.question, P.take_until({ ':' }, { '\\' }), S.colon, P.take_until({ '}' }, { '\\' })),
P.seq(S.plus, P.take_until({ '}' }, { '\\' })), P.seq(S.plus, P.take_until({ '}' }, { '\\' })),
P.seq(S.minus, P.take_until({ '}' }, { '\\' })) P.seq(S.minus, P.take_until({ '}' }, { '\\' }))
), S.close), function(values) ),
S.close
),
function(values)
return setmetatable({ return setmetatable({
type = Node.Type.FORMAT, type = Node.Type.FORMAT,
capture_index = values[3], capture_index = values[3],
if_text = values[5][2].esc, if_text = values[5][2].esc,
else_text = (values[5][4] or {}).esc, else_text = (values[5][4] or {}).esc,
}, Node) }, Node)
end) end
)
) )
S.transform = P.map(P.seq( S.transform = P.map(
P.seq(
S.slash, S.slash,
P.take_until({ '/' }, { '\\' }), P.take_until({ '/' }, { '\\' }),
S.slash, S.slash,
P.many(P.any(S.format, S.text({ '$', '/' }, { '\\' }))), P.many(P.any(S.format, S.text({ '$', '/' }, { '\\' }))),
S.slash, S.slash,
P.opt(P.pattern('[ig]+')) P.opt(P.pattern('[ig]+'))
), function(values) ),
function(values)
return setmetatable({ return setmetatable({
type = Node.Type.TRANSFORM, type = Node.Type.TRANSFORM,
pattern = values[2].raw, pattern = values[2].raw,
format = values[4], format = values[4],
option = values[6], option = values[6],
}, Node) }, Node)
end) end
)
S.tabstop = P.any( S.tabstop = P.any(
P.map(P.seq(S.dollar, S.int), function(values) P.map(P.seq(S.dollar, S.int), function(values)
@@ -314,34 +332,38 @@ S.tabstop = P.any(
) )
S.placeholder = 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({ return setmetatable({
type = Node.Type.PLACEHOLDER, type = Node.Type.PLACEHOLDER,
tabstop = values[3], tabstop = values[3],
children = values[5], children = values[5],
}, Node) }, Node)
end) end
)
) )
S.choice = P.map(P.seq( S.choice = P.map(
P.seq(
S.dollar, S.dollar,
S.open, S.open,
S.int, S.int,
S.pipe, S.pipe,
P.many( P.many(P.map(P.seq(S.text({ ',', '|' }), P.opt(S.comma)), function(values)
P.map(P.seq(S.text({ ',', '|' }), P.opt(S.comma)), function(values)
return values[1].esc return values[1].esc
end) end)),
),
S.pipe, S.pipe,
S.close S.close
), function(values) ),
function(values)
return setmetatable({ return setmetatable({
type = Node.Type.CHOICE, type = Node.Type.CHOICE,
tabstop = values[3], tabstop = values[3],
items = values[5], items = values[5],
}, Node) }, Node)
end) end
)
S.variable = P.any( S.variable = P.any(
P.map(P.seq(S.dollar, S.var), function(values) P.map(P.seq(S.dollar, S.var), function(values)
@@ -363,13 +385,16 @@ S.variable = P.any(
transform = values[4], transform = values[4],
}, Node) }, Node)
end), 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({ return setmetatable({
type = Node.Type.VARIABLE, type = Node.Type.VARIABLE,
name = values[3], name = values[3],
children = values[5], children = values[5],
}, Node) }, Node)
end) end
)
) )
S.snippet = P.map(P.many(P.any(S.toplevel, S.text({ '$' }, { '}', '\\' }))), function(values) S.snippet = P.map(P.many(P.any(S.toplevel, S.text({ '$' }, { '}', '\\' }))), function(values)

View File

@@ -1,7 +1,7 @@
local vim = vim local vim = vim
local validate = vim.validate local validate = vim.validate
local vfn = vim.fn local vfn = vim.fn
local util = require 'vim.lsp.util' local util = require('vim.lsp.util')
local M = {} local M = {}
@@ -9,7 +9,9 @@ local M = {}
--- Returns nil if {status} is false or nil, otherwise returns the rest of the --- Returns nil if {status} is false or nil, otherwise returns the rest of the
--- arguments. --- arguments.
local function ok_or_nil(status, ...) local function ok_or_nil(status, ...)
if not status then return end if not status then
return
end
return ... return ...
end end
@@ -39,10 +41,10 @@ end
--- ---
---@see |vim.lsp.buf_request()| ---@see |vim.lsp.buf_request()|
local function request(method, params, handler) local function request(method, params, handler)
validate { validate({
method = {method, 's'}; method = { method, 's' },
handler = {handler, 'f', true}; handler = { handler, 'f', true },
} })
return vim.lsp.buf_request(0, method, params, handler) return vim.lsp.buf_request(0, method, params, handler)
end end
@@ -51,7 +53,7 @@ end
--- ---
---@returns `true` if server responds. ---@returns `true` if server responds.
function M.server_ready() 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 end
--- Displays hover information about the symbol under the cursor in a floating --- 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 ---@returns The client that the user selected or nil
local function select_client(method, on_choice) local function select_client(method, on_choice)
validate { validate({
on_choice = { on_choice, 'function', false }, on_choice = { on_choice, 'function', false },
} })
local clients = vim.tbl_values(vim.lsp.buf_get_clients()) local clients = vim.tbl_values(vim.lsp.buf_get_clients())
clients = vim.tbl_filter(function(client) clients = vim.tbl_filter(function(client)
return client.supports_method(method) return client.supports_method(method)
@@ -191,24 +193,21 @@ function M.format(options)
if options.filter then if options.filter then
clients = options.filter(clients) clients = options.filter(clients)
elseif options.id then elseif options.id then
clients = vim.tbl_filter( clients = vim.tbl_filter(function(client)
function(client) return client.id == options.id end, return client.id == options.id
clients end, clients)
)
elseif options.name then elseif options.name then
clients = vim.tbl_filter( clients = vim.tbl_filter(function(client)
function(client) return client.name == options.name end, return client.name == options.name
clients end, clients)
)
end end
clients = vim.tbl_filter( clients = vim.tbl_filter(function(client)
function(client) return client.supports_method("textDocument/formatting") end, return client.supports_method('textDocument/formatting')
clients end, clients)
)
if #clients == 0 then 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 end
if options.async then if options.async then
@@ -218,7 +217,7 @@ function M.format(options)
return return
end end
local params = util.make_formatting_params(options.formatting_options) 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'] local handler = client.handlers['textDocument/formatting'] or vim.lsp.handlers['textDocument/formatting']
handler(...) handler(...)
do_format(next(clients, idx)) do_format(next(clients, idx))
@@ -229,11 +228,11 @@ function M.format(options)
local timeout_ms = options.timeout_ms or 1000 local timeout_ms = options.timeout_ms or 1000
for _, client in pairs(clients) do for _, client in pairs(clients) do
local params = util.make_formatting_params(options.formatting_options) 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 if result and result.result then
util.apply_text_edits(result.result, bufnr, client.offset_encoding) util.apply_text_edits(result.result, bufnr, client.offset_encoding)
elseif err then 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 end
end end
@@ -310,7 +309,7 @@ end
---the remaining clients in the order as they occur in the `order` list. ---the remaining clients in the order as they occur in the `order` list.
function M.formatting_seq_sync(options, timeout_ms, order) 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) 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() local bufnr = vim.api.nvim_get_current_buf()
-- sort the clients according to `order` -- 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 -- loop through the clients and make synchronous formatting requests
for _, client in pairs(clients) do 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 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 if result and result.result then
util.apply_text_edits(result.result, bufnr, client.offset_encoding) util.apply_text_edits(result.result, bufnr, client.offset_encoding)
elseif err then 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 end
end end
@@ -377,20 +381,18 @@ function M.rename(new_name, options)
if options.filter then if options.filter then
clients = options.filter(clients) clients = options.filter(clients)
elseif options.name then elseif options.name then
clients = vim.tbl_filter( clients = vim.tbl_filter(function(client)
function(client) return client.name == options.name end, return client.name == options.name
clients end, clients)
)
end end
-- Clients must at least support rename, prepareRename is optional -- Clients must at least support rename, prepareRename is optional
clients = vim.tbl_filter( clients = vim.tbl_filter(function(client)
function(client) return client.supports_method("textDocument/rename") end, return client.supports_method('textDocument/rename')
clients end, clients)
)
if #clients == 0 then 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 end
local win = vim.api.nvim_get_current_win() local win = vim.api.nvim_get_current_win()
@@ -427,7 +429,7 @@ function M.rename(new_name, options)
end, bufnr) end, bufnr)
end 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) local params = util.make_position_params(win, client.offset_encoding)
client.request('textDocument/prepareRename', params, function(err, result) client.request('textDocument/prepareRename', params, function(err, result)
if err or result == nil then if err or result == nil then
@@ -446,7 +448,7 @@ function M.rename(new_name, options)
end end
local prompt_opts = { local prompt_opts = {
prompt = "New Name: " prompt = 'New Name: ',
} }
-- result: Range | { range: Range, placeholder: string } -- result: Range | { range: Range, placeholder: string }
if result.placeholder then if result.placeholder then
@@ -466,15 +468,15 @@ function M.rename(new_name, options)
end) end)
end, bufnr) end, bufnr)
else 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 if new_name then
rename(new_name) rename(new_name)
return return
end end
local prompt_opts = { local prompt_opts = {
prompt = "New Name: ", prompt = 'New Name: ',
default = cword default = cword,
} }
vim.ui.input(prompt_opts, function(input) vim.ui.input(prompt_opts, function(input)
if not input or #input == 0 then if not input or #input == 0 then
@@ -493,10 +495,10 @@ end
---@param context (table) Context for the request ---@param context (table) Context for the request
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
function M.references(context) function M.references(context)
validate { context = { context, 't', true } } validate({ context = { context, 't', true } })
local params = util.make_position_params() local params = util.make_position_params()
params.context = context or { params.context = context or {
includeDeclaration = true; includeDeclaration = true,
} }
request('textDocument/references', params) request('textDocument/references', params)
end end
@@ -510,14 +512,16 @@ end
---@private ---@private
local function pick_call_hierarchy_item(call_hierarchy_items) 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 if #call_hierarchy_items == 1 then
return call_hierarchy_items[1] return call_hierarchy_items[1]
end end
local items = {} local items = {}
for i, item in pairs(call_hierarchy_items) do for i, item in pairs(call_hierarchy_items) do
local entry = item.detail or item.name 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 end
local choice = vim.fn.inputlist(items) local choice = vim.fn.inputlist(items)
if choice < 1 or choice > #items then if choice < 1 or choice > #items then
@@ -539,8 +543,8 @@ local function call_hierarchy(method)
if client then if client then
client.request(method, { item = call_hierarchy_item }, nil, ctx.bufnr) client.request(method, { item = call_hierarchy_item }, nil, ctx.bufnr)
else else
vim.notify(string.format( vim.notify(
'Client with id=%d disappeared during call hierarchy request', ctx.client_id), string.format('Client with id=%d disappeared during call hierarchy request', ctx.client_id),
vim.log.levels.WARN vim.log.levels.WARN
) )
end end
@@ -576,20 +580,25 @@ end
--- Add the folder at path to the workspace folders. If {path} is --- Add the folder at path to the workspace folders. If {path} is
--- not provided, the user will be prompted for a path using |input()|. --- not provided, the user will be prompted for a path using |input()|.
function M.add_workspace_folder(workspace_folder) function M.add_workspace_folder(workspace_folder)
workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h'), 'dir') workspace_folder = workspace_folder or npcall(vfn.input, 'Workspace Folder: ', vfn.expand('%:p:h'), 'dir')
vim.api.nvim_command("redraw") vim.api.nvim_command('redraw')
if not (workspace_folder and #workspace_folder > 0) then return end if not (workspace_folder and #workspace_folder > 0) then
if vim.fn.isdirectory(workspace_folder) == 0 then
print(workspace_folder, " is not a valid directory")
return return
end 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 for _, client in pairs(vim.lsp.buf_get_clients()) do
local found = false local found = false
for _, folder in pairs(client.workspace_folders or {}) do for _, folder in pairs(client.workspace_folders or {}) do
if folder.name == workspace_folder then if folder.name == workspace_folder then
found = true found = true
print(workspace_folder, "is already part of this workspace") print(workspace_folder, 'is already part of this workspace')
break break
end end
end end
@@ -607,10 +616,15 @@ end
--- {path} is not provided, the user will be prompted for --- {path} is not provided, the user will be prompted for
--- a path using |input()|. --- a path using |input()|.
function M.remove_workspace_folder(workspace_folder) function M.remove_workspace_folder(workspace_folder)
workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h')) workspace_folder = workspace_folder or npcall(vfn.input, 'Workspace Folder: ', vfn.expand('%:p:h'))
vim.api.nvim_command("redraw") vim.api.nvim_command('redraw')
if not (workspace_folder and #workspace_folder > 0) then return end if not (workspace_folder and #workspace_folder > 0) then
local params = util.make_workspace_params({{}}, {{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}) 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 _, client in pairs(vim.lsp.buf_get_clients()) do
for idx, folder in pairs(client.workspace_folders) do for idx, folder in pairs(client.workspace_folders) do
if folder.name == workspace_folder then if folder.name == workspace_folder then
@@ -620,7 +634,7 @@ function M.remove_workspace_folder(workspace_folder)
end end
end end
end end
print(workspace_folder, "is not currently part of the workspace") print(workspace_folder, 'is not currently part of the workspace')
end end
--- Lists all symbols in the current workspace in the quickfix window. --- Lists all symbols in the current workspace in the quickfix window.
@@ -631,7 +645,7 @@ end
--- ---
---@param query (string, optional) ---@param query (string, optional)
function M.workspace_symbol(query) function M.workspace_symbol(query)
query = query or npcall(vfn.input, "Query: ") query = query or npcall(vfn.input, 'Query: ')
if query == nil then if query == nil then
return return
end end
@@ -665,7 +679,6 @@ function M.clear_references()
util.buf_clear_references() util.buf_clear_references()
end end
---@private ---@private
-- --
--- This is not public because the main extension point is --- 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 client = vim.lsp.get_client_by_id(action_tuple[1])
local action = action_tuple[2] local action = action_tuple[2]
if not action.edit if
not action.edit
and client 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) client.request('codeAction/resolve', action, function(err, resolved_action)
if err then if err then
vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR) 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) }, on_user_choice)
end end
--- Requests code actions from all clients and calls the handler exactly once --- Requests code actions from all clients and calls the handler exactly once
--- with all aggregated results --- with all aggregated results
---@private ---@private
@@ -794,7 +807,7 @@ end
--- (after filtering), the action is applied without user query. --- (after filtering), the action is applied without user query.
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
function M.code_action(options) function M.code_action(options)
validate { options = { options, 't', true } } validate({ options = { options, 't', true } })
options = options or {} options = options or {}
-- Detect old API call code_action(context) which should now be -- Detect old API call code_action(context) which should now be
-- code_action({ context = context} ) -- code_action({ context = context} )
@@ -826,7 +839,7 @@ end
---@param end_pos ({number, number}, optional) mark-indexed position. ---@param end_pos ({number, number}, optional) mark-indexed position.
---Defaults to the end of the last visual selection. ---Defaults to the end of the last visual selection.
function M.range_code_action(context, start_pos, end_pos) function M.range_code_action(context, start_pos, end_pos)
validate { context = { context, 't', true } } validate({ context = { context, 't', true } })
context = context or {} context = context or {}
if not context.diagnostics then if not context.diagnostics then
context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics() context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics()
@@ -841,10 +854,10 @@ end
---@param command_params table A valid `ExecuteCommandParams` object ---@param command_params table A valid `ExecuteCommandParams` object
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
function M.execute_command(command_params) function M.execute_command(command_params)
validate { validate({
command = { command_params.command, 's' }, command = { command_params.command, 's' },
arguments = { command_params.arguments, 't', true } arguments = { command_params.arguments, 't', true },
} })
command_params = { command_params = {
command = command_params.command, command = command_params.command,
arguments = command_params.arguments, arguments = command_params.arguments,

View File

@@ -12,7 +12,7 @@ local lens_cache_by_buf = setmetatable({}, {
__index = function(t, b) __index = function(t, b)
local key = b > 0 and b or api.nvim_get_current_buf() local key = b > 0 and b or api.nvim_get_current_buf()
return rawget(t, key) return rawget(t, key)
end end,
}) })
local namespaces = setmetatable({}, { local namespaces = setmetatable({}, {
@@ -20,13 +20,12 @@ local namespaces = setmetatable({}, {
local value = api.nvim_create_namespace('vim_lsp_codelens:' .. key) local value = api.nvim_create_namespace('vim_lsp_codelens:' .. key)
rawset(t, key, value) rawset(t, key, value)
return value return value
end; end,
}) })
---@private ---@private
M.__namespaces = namespaces M.__namespaces = namespaces
---@private ---@private
local function execute_lens(lens, bufnr, client_id) local function execute_lens(lens, bufnr, client_id)
local line = lens.range.start.line 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 command_provider = client.server_capabilities.executeCommandProvider
local commands = type(command_provider) == 'table' and command_provider.commands or {} local commands = type(command_provider) == 'table' and command_provider.commands or {}
if not vim.tbl_contains(commands, command.command) then if not vim.tbl_contains(commands, command.command) then
vim.notify(string.format( vim.notify(
"Language server does not support command `%s`. This command may require a client extension.", command.command), string.format(
vim.log.levels.WARN) 'Language server does not support command `%s`. This command may require a client extension.',
command.command
),
vim.log.levels.WARN
)
return return
end end
client.request('workspace/executeCommand', command, function(...) client.request('workspace/executeCommand', command, function(...)
@@ -56,14 +59,15 @@ local function execute_lens(lens, bufnr, client_id)
end, bufnr) end, bufnr)
end end
--- Return all lenses for the given buffer --- Return all lenses for the given buffer
--- ---
---@param bufnr number Buffer number. 0 can be used for the current buffer. ---@param bufnr number Buffer number. 0 can be used for the current buffer.
---@return table (`CodeLens[]`) ---@return table (`CodeLens[]`)
function M.get(bufnr) function M.get(bufnr)
local lenses_by_client = lens_cache_by_buf[bufnr or 0] 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 = {} local lenses = {}
for _, client_lenses in pairs(lenses_by_client) do for _, client_lenses in pairs(lenses_by_client) do
vim.list_extend(lenses, client_lenses) vim.list_extend(lenses, client_lenses)
@@ -71,7 +75,6 @@ function M.get(bufnr)
return lenses return lenses
end end
--- Run the code lens in the current line --- Run the code lens in the current line
--- ---
function M.run() function M.run()
@@ -105,7 +108,6 @@ function M.run()
end end
end end
--- Display the lenses using virtual text --- Display the lenses using virtual text
--- ---
---@param lenses table of lenses to display (`CodeLens[] | null`) ---@param lenses table of lenses to display (`CodeLens[] | null`)
@@ -139,13 +141,14 @@ function M.display(lenses, bufnr, client_id)
end end
end end
if #chunks > 0 then if #chunks > 0 then
api.nvim_buf_set_extmark(bufnr, ns, i, 0, { virt_text = chunks, api.nvim_buf_set_extmark(bufnr, ns, i, 0, {
hl_mode="combine" }) virt_text = chunks,
hl_mode = 'combine',
})
end end
end end
end end
--- Store lenses for a specific buffer and client --- Store lenses for a specific buffer and client
--- ---
---@param lenses table of lenses to store (`CodeLens[] | null`) ---@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 lens_cache_by_buf[bufnr] = lenses_by_client
local ns = namespaces[client_id] local ns = namespaces[client_id]
api.nvim_buf_attach(bufnr, false, { 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) on_lines = function(_, b, _, first_lnum, last_lnum)
api.nvim_buf_clear_namespace(b, ns, first_lnum, last_lnum) api.nvim_buf_clear_namespace(b, ns, first_lnum, last_lnum)
end end,
}) })
end end
lenses_by_client[client_id] = lenses lenses_by_client[client_id] = lenses
end end
---@private ---@private
local function resolve_lenses(lenses, bufnr, client_id, callback) local function resolve_lenses(lenses, bufnr, client_id, callback)
lenses = lenses or {} lenses = lenses or {}
@@ -201,8 +205,7 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
ns, ns,
lens.range.start.line, lens.range.start.line,
0, 0,
{ virt_text = {{ lens.command.title, 'LspCodeLens' }}, { virt_text = { { lens.command.title, 'LspCodeLens' } }, hl_mode = 'combine' }
hl_mode="combine" }
) )
end end
countdown() countdown()
@@ -211,13 +214,12 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
end end
end end
--- |lsp-handler| for the method `textDocument/codeLens` --- |lsp-handler| for the method `textDocument/codeLens`
--- ---
function M.on_codelens(err, result, ctx, _) function M.on_codelens(err, result, ctx, _)
if err then if err then
active_refreshes[ctx.bufnr] = nil active_refreshes[ctx.bufnr] = nil
local _ = log.error() and log.error("codelens", err) local _ = log.error() and log.error('codelens', err)
return return
end end
@@ -232,7 +234,6 @@ function M.on_codelens(err, result, ctx, _)
end) end)
end end
--- Refresh the codelens for the current buffer --- Refresh the codelens for the current buffer
--- ---
--- It is recommended to trigger this using an autocmd or via keymap. --- It is recommended to trigger this using an autocmd or via keymap.
@@ -243,7 +244,7 @@ end
--- ---
function M.refresh() function M.refresh()
local params = { local params = {
textDocument = util.make_text_document_params() textDocument = util.make_text_document_params(),
} }
local bufnr = api.nvim_get_current_buf() local bufnr = api.nvim_get_current_buf()
if active_refreshes[bufnr] then if active_refreshes[bufnr] then
@@ -253,5 +254,4 @@ function M.refresh()
vim.lsp.buf_request(0, 'textDocument/codeLens', params, M.on_codelens) vim.lsp.buf_request(0, 'textDocument/codeLens', params, M.on_codelens)
end end
return M return M

View File

@@ -50,12 +50,12 @@ end
---@private ---@private
local function line_byte_from_position(lines, lnum, col, offset_encoding) 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 return col
end end
local line = lines[lnum + 1] 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 if ok then
return result return result
end end
@@ -75,7 +75,7 @@ local function get_buf_lines(bufnr)
return return
end end
local content = f:read("*a") local content = f:read('*a')
if not content then if not content then
-- Some LSP servers report diagnostics at a directory level, in which case -- Some LSP servers report diagnostics at a directory level, in which case
-- io.read() returns nil -- io.read() returns nil
@@ -83,7 +83,7 @@ local function get_buf_lines(bufnr)
return return
end end
local lines = vim.split(content, "\n") local lines = vim.split(content, '\n')
f:close() f:close()
return lines return lines
end end
@@ -92,10 +92,10 @@ end
local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
local buf_lines = get_buf_lines(bufnr) local buf_lines = get_buf_lines(bufnr)
local client = vim.lsp.get_client_by_id(client_id) 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) return vim.tbl_map(function(diagnostic)
local start = diagnostic.range.start local start = diagnostic.range.start
local _end = diagnostic.range["end"] local _end = diagnostic.range['end']
return { return {
lnum = start.line, lnum = start.line,
col = line_byte_from_position(buf_lines, start.line, start.character, offset_encoding), col = line_byte_from_position(buf_lines, start.line, start.character, offset_encoding),
@@ -122,14 +122,14 @@ end
---@private ---@private
local function diagnostic_vim_to_lsp(diagnostics) local function diagnostic_vim_to_lsp(diagnostics)
return vim.tbl_map(function(diagnostic) 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 -- "keep" the below fields over any duplicate fields in diagnostic.user_data.lsp
range = { range = {
start = { start = {
line = diagnostic.lnum, line = diagnostic.lnum,
character = diagnostic.col, character = diagnostic.col,
}, },
["end"] = { ['end'] = {
line = diagnostic.end_lnum, line = diagnostic.end_lnum,
character = diagnostic.end_col, character = diagnostic.end_col,
}, },
@@ -148,10 +148,10 @@ local _client_namespaces = {}
--- ---
---@param client_id number The id of the LSP client ---@param client_id number The id of the LSP client
function M.get_namespace(client_id) 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 if not _client_namespaces[client_id] then
local client = vim.lsp.get_client_by_id(client_id) 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) _client_namespaces[client_id] = vim.api.nvim_create_namespace(name)
end end
return _client_namespaces[client_id] return _client_namespaces[client_id]
@@ -240,7 +240,6 @@ end
-- Deprecated Functions {{{ -- Deprecated Functions {{{
--- Save diagnostics to the current buffer. --- Save diagnostics to the current buffer.
--- ---
---@deprecated Prefer |vim.diagnostic.set()| ---@deprecated Prefer |vim.diagnostic.set()|
@@ -288,7 +287,9 @@ end
---@param predicate function|nil Optional function for filtering diagnostics ---@param predicate function|nil Optional function for filtering diagnostics
function M.get(bufnr, client_id, predicate) function M.get(bufnr, client_id, predicate)
vim.deprecate('vim.lsp.diagnostic.get', 'vim.diagnostic.get', '0.8') 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 if client_id == nil then
local all_diagnostics = {} local all_diagnostics = {}
vim.lsp.for_each_buffer_client(bufnr, function(_, iter_client_id, _) 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) function M.show_position_diagnostics(opts, buf_nr, position)
vim.deprecate('vim.lsp.diagnostic.show_position_diagnostics', 'vim.diagnostic.open_float', '0.8') vim.deprecate('vim.lsp.diagnostic.show_position_diagnostics', 'vim.diagnostic.open_float', '0.8')
opts = opts or {} opts = opts or {}
opts.scope = "cursor" opts.scope = 'cursor'
opts.pos = position opts.pos = position
if opts.severity then if opts.severity then
opts.severity = severity_lsp_to_vim(opts.severity) 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) function M.show_line_diagnostics(opts, buf_nr, line_nr, client_id)
vim.deprecate('vim.lsp.diagnostic.show_line_diagnostics', 'vim.diagnostic.open_float', '0.8') vim.deprecate('vim.lsp.diagnostic.show_line_diagnostics', 'vim.diagnostic.open_float', '0.8')
opts = opts or {} opts = opts or {}
opts.scope = "line" opts.scope = 'line'
opts.pos = line_nr opts.pos = line_nr
if client_id then if client_id then
opts.namespace = M.get_namespace(client_id) opts.namespace = M.get_namespace(client_id)

View File

@@ -1,6 +1,6 @@
local log = require 'vim.lsp.log' local log = require('vim.lsp.log')
local protocol = require 'vim.lsp.protocol' local protocol = require('vim.lsp.protocol')
local util = require 'vim.lsp.util' local util = require('vim.lsp.util')
local vim = vim local vim = vim
local api = vim.api local api = vim.api
@@ -12,8 +12,8 @@ local M = {}
--- Writes to error buffer. --- Writes to error buffer.
---@param ... (table of strings) Will be concatenated before being written ---@param ... (table of strings) Will be concatenated before being written
local function err_message(...) local function err_message(...)
vim.notify(table.concat(vim.tbl_flatten{...}), vim.log.levels.ERROR) vim.notify(table.concat(vim.tbl_flatten({ ... })), vim.log.levels.ERROR)
api.nvim_command("redraw") api.nvim_command('redraw')
end end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand --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 function progress_handler(_, result, ctx, _)
local client_id = ctx.client_id local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(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 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 return vim.NIL
end end
local val = result.value -- unspecified yet local val = result.value -- unspecified yet
local token = result.token -- string or number 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 then
if val.kind == 'begin' then if val.kind == 'begin' then
client.messages.progress[token] = { client.messages.progress[token] = {
@@ -42,11 +44,11 @@ local function progress_handler(_, result, ctx, _)
percentage = val.percentage, percentage = val.percentage,
} }
elseif val.kind == 'report' then elseif val.kind == 'report' then
client.messages.progress[token].message = val.message; client.messages.progress[token].message = val.message
client.messages.progress[token].percentage = val.percentage; client.messages.progress[token].percentage = val.percentage
elseif val.kind == 'end' then elseif val.kind == 'end' then
if client.messages.progress[token] == nil 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 else
client.messages.progress[token].message = val.message client.messages.progress[token].message = val.message
client.messages.progress[token].done = true client.messages.progress[token].done = true
@@ -57,7 +59,7 @@ local function progress_handler(_, result, ctx, _)
client.messages.progress[token].done = true client.messages.progress[token].done = true
end end
vim.api.nvim_command("doautocmd <nomodeline> User LspProgressUpdate") vim.api.nvim_command('doautocmd <nomodeline> User LspProgressUpdate')
end end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress --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_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id) local client = vim.lsp.get_client_by_id(client_id)
local token = result.token -- string or number 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 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 return vim.NIL
end end
client.messages.progress[token] = {} client.messages.progress[token] = {}
@@ -79,14 +81,13 @@ end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
M['window/showMessageRequest'] = function(_, result) M['window/showMessageRequest'] = function(_, result)
local actions = result.actions local actions = result.actions
print(result.message) print(result.message)
local option_strings = {result.message, "\nRequest Actions:"} local option_strings = { result.message, '\nRequest Actions:' }
for i, action in ipairs(actions) do for i, action in ipairs(actions) do
local title = action.title:gsub('\r\n', '\\r\\n') local title = action.title:gsub('\r\n', '\\r\\n')
title = title:gsub('\n', '\\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 end
-- window/showMessageRequest can return either MessageActionItem[] or null. -- 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 --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
M['client/registerCapability'] = function(_, _, ctx) M['client/registerCapability'] = function(_, _, ctx)
local client_id = ctx.client_id local client_id = ctx.client_id
local warning_tpl = "The language server %s triggers a registerCapability ".. local warning_tpl = 'The language server %s triggers a registerCapability '
"handler despite dynamicRegistration set to false. ".. .. 'handler despite dynamicRegistration set to false. '
"Report upstream, this warning is harmless" .. 'Report upstream, this warning is harmless'
local client = vim.lsp.get_client_by_id(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)
local warning = string.format(warning_tpl, client_name) local warning = string.format(warning_tpl, client_name)
log.warn(warning) log.warn(warning)
return vim.NIL return vim.NIL
@@ -113,17 +114,19 @@ end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
M['workspace/applyEdit'] = function(_, workspace_edit, ctx) 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? -- TODO(ashkan) Do something more with label?
local client_id = ctx.client_id local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id) local client = vim.lsp.get_client_by_id(client_id)
if workspace_edit.label then if workspace_edit.label then
print("Workspace edit", workspace_edit.label) print('Workspace edit', workspace_edit.label)
end end
local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit, client.offset_encoding) local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit, client.offset_encoding)
return { return {
applied = status; applied = status,
failureReason = result; failureReason = result,
} }
end end
@@ -132,7 +135,7 @@ M['workspace/configuration'] = function(_, result, ctx)
local client_id = ctx.client_id local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id) local client = vim.lsp.get_client_by_id(client_id)
if not client then 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 return
end end
if not result.items then if not result.items then
@@ -158,7 +161,7 @@ M['workspace/workspaceFolders'] = function(_, _, ctx)
local client_id = ctx.client_id local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id) local client = vim.lsp.get_client_by_id(client_id)
if not client then 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 return
end end
return client.workspace_folders or vim.NIL return client.workspace_folders or vim.NIL
@@ -172,7 +175,6 @@ M['textDocument/codeLens'] = function(...)
return require('vim.lsp.codelens').on_codelens(...) return require('vim.lsp.codelens').on_codelens(...)
end end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
M['textDocument/references'] = function(_, result, ctx, config) M['textDocument/references'] = function(_, result, ctx, config)
if not result or vim.tbl_isempty(result) then if not result or vim.tbl_isempty(result) then
@@ -182,23 +184,22 @@ M['textDocument/references'] = function(_, result, ctx, config)
config = config or {} config = config or {}
if config.loclist then if config.loclist then
vim.fn.setloclist(0, {}, ' ', { vim.fn.setloclist(0, {}, ' ', {
title = 'References'; title = 'References',
items = util.locations_to_items(result, client.offset_encoding); items = util.locations_to_items(result, client.offset_encoding),
context = ctx; context = ctx,
}) })
api.nvim_command("lopen") api.nvim_command('lopen')
else else
vim.fn.setqflist({}, ' ', { vim.fn.setqflist({}, ' ', {
title = 'References'; title = 'References',
items = util.locations_to_items(result, client.offset_encoding); items = util.locations_to_items(result, client.offset_encoding),
context = ctx; context = ctx,
}) })
api.nvim_command("botright copen") api.nvim_command('botright copen')
end end
end end
end end
---@private ---@private
--- Return a function that converts LSP responses to list items and opens the list --- 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 {} config = config or {}
if config.loclist then if config.loclist then
vim.fn.setloclist(0, {}, ' ', { vim.fn.setloclist(0, {}, ' ', {
title = title_fn(ctx); title = title_fn(ctx),
items = map_result(result, ctx.bufnr); items = map_result(result, ctx.bufnr),
context = ctx; context = ctx,
}) })
api.nvim_command("lopen") api.nvim_command('lopen')
else else
vim.fn.setqflist({}, ' ', { vim.fn.setqflist({}, ' ', {
title = title_fn(ctx); title = title_fn(ctx),
items = map_result(result, ctx.bufnr); items = map_result(result, ctx.bufnr),
context = ctx; context = ctx,
}) })
api.nvim_command("botright copen") api.nvim_command('botright copen')
end end
end end
end end
end end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol --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) 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) return string.format('Symbols in %s', fname)
end) end)
@@ -249,28 +249,36 @@ end)
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
M['textDocument/rename'] = function(_, result, ctx, _) 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) local client = vim.lsp.get_client_by_id(ctx.client_id)
util.apply_workspace_edit(result, client.offset_encoding) util.apply_workspace_edit(result, client.offset_encoding)
end end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
M['textDocument/rangeFormatting'] = function(_, result, ctx, _) 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) local client = vim.lsp.get_client_by_id(ctx.client_id)
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding) util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
M['textDocument/formatting'] = function(_, result, ctx, _) 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) local client = vim.lsp.get_client_by_id(ctx.client_id)
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding) util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
M['textDocument/completion'] = function(_, result, _, _) 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 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 = assert(api.nvim_buf_get_lines(0, row - 1, row, false)[1])
local line_to_cursor = line:sub(col + 1) local line_to_cursor = line:sub(col + 1)
@@ -307,7 +315,7 @@ function M.hover(_, result, ctx, config)
vim.notify('No information available') vim.notify('No information available')
return return
end end
return util.open_floating_preview(markdown_lines, "markdown", config) return util.open_floating_preview(markdown_lines, 'markdown', config)
end end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover --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 if #result > 1 then
vim.fn.setqflist({}, ' ', { vim.fn.setqflist({}, ' ', {
title = 'LSP locations', 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 end
else else
util.jump_to_location(result, client.offset_encoding) util.jump_to_location(result, client.offset_encoding)
@@ -379,7 +387,7 @@ function M.signature_help(_, result, ctx, config)
return return
end end
local client = vim.lsp.get_client_by_id(ctx.client_id) 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 ft = api.nvim_buf_get_option(ctx.bufnr, 'filetype')
local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers) local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers)
lines = util.trim_empty_lines(lines) lines = util.trim_empty_lines(lines)
@@ -389,9 +397,9 @@ function M.signature_help(_, result, ctx, config)
end end
return return
end end
local fbuf, fwin = util.open_floating_preview(lines, "markdown", config) local fbuf, fwin = util.open_floating_preview(lines, 'markdown', config)
if hl then 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 end
return fbuf, fwin return fbuf, fwin
end end
@@ -401,10 +409,14 @@ M['textDocument/signatureHelp'] = M.signature_help
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
M['textDocument/documentHighlight'] = function(_, result, ctx, _) 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_id = ctx.client_id
local client = vim.lsp.get_client_by_id(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) util.buf_highlight_references(ctx.bufnr, result, client.offset_encoding)
end end
@@ -417,7 +429,9 @@ end
---@returns `CallHierarchyOutgoingCall[]` if {direction} is `"to"`, ---@returns `CallHierarchyOutgoingCall[]` if {direction} is `"to"`,
local make_call_hierarchy_handler = function(direction) local make_call_hierarchy_handler = function(direction)
return function(_, result) return function(_, result)
if not result then return end if not result then
return
end
local items = {} local items = {}
for _, call_hierarchy_call in pairs(result) do for _, call_hierarchy_call in pairs(result) do
local call_hierarchy_item = call_hierarchy_call[direction] local call_hierarchy_item = call_hierarchy_call[direction]
@@ -431,7 +445,7 @@ local make_call_hierarchy_handler = function(direction)
end end
end end
vim.fn.setqflist({}, ' ', { title = 'LSP call hierarchy', items = items }) vim.fn.setqflist({}, ' ', { title = 'LSP call hierarchy', items = items })
api.nvim_command("botright copen") api.nvim_command('botright copen')
end end
end end
@@ -447,9 +461,9 @@ M['window/logMessage'] = function(_, result, ctx, _)
local message = result.message local message = result.message
local client_id = ctx.client_id local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(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 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 end
if message_type == protocol.MessageType.Error then if message_type == protocol.MessageType.Error then
log.error(message) log.error(message)
@@ -469,15 +483,15 @@ M['window/showMessage'] = function(_, result, ctx, _)
local message = result.message local message = result.message
local client_id = ctx.client_id local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(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 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 end
if message_type == protocol.MessageType.Error then if message_type == protocol.MessageType.Error then
err_message("LSP[", client_name, "] ", message) err_message('LSP[', client_name, '] ', message)
else else
local message_type_name = protocol.MessageType[message_type] 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 end
return result return result
end end
@@ -485,8 +499,12 @@ end
-- Add boilerplate error validation and logging for all of these. -- Add boilerplate error validation and logging for all of these.
for k, fn in pairs(M) do for k, fn in pairs(M) do
M[k] = function(err, result, ctx, config) M[k] = function(err, result, ctx, config)
local _ = log.trace() and log.trace('default_handler', ctx.method, { local _ = log.trace()
err = err, result = result, ctx=vim.inspect(ctx), config = config and log.trace('default_handler', ctx.method, {
err = err,
result = result,
ctx = vim.inspect(ctx),
config = config,
}) })
if err then if err then
@@ -499,7 +517,7 @@ for k, fn in pairs(M) do
-- Per LSP, don't show ContentModified error to the user. -- Per LSP, don't show ContentModified error to the user.
if err.code ~= protocol.ErrorCodes.ContentModified then if err.code ~= protocol.ErrorCodes.ContentModified then
local client = vim.lsp.get_client_by_id(ctx.client_id) 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) err_message(client_name .. ': ' .. tostring(err.code) .. ': ' .. err.message)
end end

View File

@@ -8,20 +8,19 @@ function M.check()
local log = require('vim.lsp.log') local log = require('vim.lsp.log')
local current_log_level = log.get_level() local current_log_level = log.get_level()
local log_level_string = log.levels[current_log_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 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 end
local log_path = vim.lsp.get_log_path() 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 log_size = vim.loop.fs_stat(log_path).size
local report_fn = (log_size / 1000000 > 100 and report_warn or report_info) 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 end
return M return M

View File

@@ -14,21 +14,23 @@ log.levels = vim.deepcopy(vim.log.levels)
-- Default log level is warn. -- Default log level is warn.
local current_log_level = log.levels.WARN local current_log_level = log.levels.WARN
local log_date_format = "%F %H:%M:%S" local log_date_format = '%F %H:%M:%S'
local format_func = function(arg) return vim.inspect(arg, {newline=''}) end local format_func = function(arg)
return vim.inspect(arg, { newline = '' })
end
do 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 ---@private
local function path_join(...) local function path_join(...)
return table.concat(vim.tbl_flatten{...}, path_sep) return table.concat(vim.tbl_flatten({ ... }), path_sep)
end end
local logfilename = path_join(vim.fn.stdpath('cache'), 'lsp.log') local logfilename = path_join(vim.fn.stdpath('cache'), 'lsp.log')
-- TODO: Ideally the directory should be created in open_logfile(), right -- TODO: Ideally the directory should be created in open_logfile(), right
-- before opening the log file, but open_logfile() can be called from libuv -- before opening the log file, but open_logfile() can be called from libuv
-- callbacks, where using fn.mkdir() is not allowed. -- 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 the log filename.
---@returns (string) log filename ---@returns (string) log filename
@@ -41,28 +43,28 @@ do
--- Opens log file. Returns true if file is open, false on error --- Opens log file. Returns true if file is open, false on error
local function open_logfile() local function open_logfile()
-- Try to open file only once -- Try to open file only once
if logfile then return true end if logfile then
if openerr then return false end 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 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) vim.notify(err_msg, vim.log.levels.ERROR)
return false return false
end end
local log_info = vim.loop.fs_stat(logfilename) local log_info = vim.loop.fs_stat(logfilename)
if log_info and log_info.size > 1e9 then if log_info and log_info.size > 1e9 then
local warn_msg = string.format( local warn_msg = string.format('LSP client log is large (%d MB): %s', log_info.size / (1000 * 1000), logfilename)
"LSP client log is large (%d MB): %s",
log_info.size / (1000 * 1000),
logfilename
)
vim.notify(warn_msg) vim.notify(warn_msg)
end end
-- Start message for logging -- 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 return true
end end
@@ -83,24 +85,36 @@ do
-- ``` -- ```
-- --
-- This way you can avoid string allocations if the log level isn't high enough. -- 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(...) log[level:lower()] = function(...)
local argc = select("#", ...) local argc = select('#', ...)
if levelnr < current_log_level then return false end if levelnr < current_log_level then
if argc == 0 then return true end return false
if not open_logfile() then return false end end
local info = debug.getinfo(2, "Sl") if argc == 0 then
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) 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 } local parts = { header }
for i = 1, argc do for i = 1, argc do
local arg = select(i, ...) local arg = select(i, ...)
if arg == nil then if arg == nil then
table.insert(parts, "nil") table.insert(parts, 'nil')
else else
table.insert(parts, format_func(arg)) table.insert(parts, format_func(arg))
end end
end end
logfile:write(table.concat(parts, '\t'), "\n") logfile:write(table.concat(parts, '\t'), '\n')
logfile:flush() logfile:flush()
end end
end end
@@ -115,10 +129,10 @@ vim.tbl_add_reverse_lookup(log.levels)
---@param level (string or number) One of `vim.lsp.log.levels` ---@param level (string or number) One of `vim.lsp.log.levels`
function log.set_level(level) function log.set_level(level)
if type(level) == 'string' then 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 else
assert(type(level) == 'number', "level must be a number or string") assert(type(level) == 'number', 'level must be a number or string')
assert(log.levels[level], string.format("Invalid log level: %d", level)) assert(log.levels[level], string.format('Invalid log level: %d', level))
current_log_level = level current_log_level = level
end end
end end
@@ -132,7 +146,7 @@ end
--- Sets formatting function used to format logs --- Sets formatting function used to format logs
---@param handle function function to apply to logging arguments, pass vim.inspect for multi-line formatting ---@param handle function function to apply to logging arguments, pass vim.inspect for multi-line formatting
function log.set_format_func(handle) 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 format_func = handle
end end

View File

@@ -23,150 +23,150 @@ end
local constants = { local constants = {
DiagnosticSeverity = { DiagnosticSeverity = {
-- Reports an error. -- Reports an error.
Error = 1; Error = 1,
-- Reports a warning. -- Reports a warning.
Warning = 2; Warning = 2,
-- Reports an information. -- Reports an information.
Information = 3; Information = 3,
-- Reports a hint. -- Reports a hint.
Hint = 4; Hint = 4,
}; },
DiagnosticTag = { DiagnosticTag = {
-- Unused or unnecessary code -- Unused or unnecessary code
Unnecessary = 1; Unnecessary = 1,
-- Deprecated or obsolete code -- Deprecated or obsolete code
Deprecated = 2; Deprecated = 2,
}; },
MessageType = { MessageType = {
-- An error message. -- An error message.
Error = 1; Error = 1,
-- A warning message. -- A warning message.
Warning = 2; Warning = 2,
-- An information message. -- An information message.
Info = 3; Info = 3,
-- A log message. -- A log message.
Log = 4; Log = 4,
}; },
-- The file event type. -- The file event type.
FileChangeType = { FileChangeType = {
-- The file got created. -- The file got created.
Created = 1; Created = 1,
-- The file got changed. -- The file got changed.
Changed = 2; Changed = 2,
-- The file got deleted. -- The file got deleted.
Deleted = 3; Deleted = 3,
}; },
-- The kind of a completion entry. -- The kind of a completion entry.
CompletionItemKind = { CompletionItemKind = {
Text = 1; Text = 1,
Method = 2; Method = 2,
Function = 3; Function = 3,
Constructor = 4; Constructor = 4,
Field = 5; Field = 5,
Variable = 6; Variable = 6,
Class = 7; Class = 7,
Interface = 8; Interface = 8,
Module = 9; Module = 9,
Property = 10; Property = 10,
Unit = 11; Unit = 11,
Value = 12; Value = 12,
Enum = 13; Enum = 13,
Keyword = 14; Keyword = 14,
Snippet = 15; Snippet = 15,
Color = 16; Color = 16,
File = 17; File = 17,
Reference = 18; Reference = 18,
Folder = 19; Folder = 19,
EnumMember = 20; EnumMember = 20,
Constant = 21; Constant = 21,
Struct = 22; Struct = 22,
Event = 23; Event = 23,
Operator = 24; Operator = 24,
TypeParameter = 25; TypeParameter = 25,
}; },
-- How a completion was triggered -- How a completion was triggered
CompletionTriggerKind = { CompletionTriggerKind = {
-- Completion was triggered by typing an identifier (24x7 code -- Completion was triggered by typing an identifier (24x7 code
-- complete), manual invocation (e.g Ctrl+Space) or via API. -- complete), manual invocation (e.g Ctrl+Space) or via API.
Invoked = 1; Invoked = 1,
-- Completion was triggered by a trigger character specified by -- Completion was triggered by a trigger character specified by
-- the `triggerCharacters` properties of the `CompletionRegistrationOptions`. -- the `triggerCharacters` properties of the `CompletionRegistrationOptions`.
TriggerCharacter = 2; TriggerCharacter = 2,
-- Completion was re-triggered as the current completion list is incomplete. -- Completion was re-triggered as the current completion list is incomplete.
TriggerForIncompleteCompletions = 3; TriggerForIncompleteCompletions = 3,
}; },
-- A document highlight kind. -- A document highlight kind.
DocumentHighlightKind = { DocumentHighlightKind = {
-- A textual occurrence. -- A textual occurrence.
Text = 1; Text = 1,
-- Read-access of a symbol, like reading a variable. -- Read-access of a symbol, like reading a variable.
Read = 2; Read = 2,
-- Write-access of a symbol, like writing to a variable. -- Write-access of a symbol, like writing to a variable.
Write = 3; Write = 3,
}; },
-- A symbol kind. -- A symbol kind.
SymbolKind = { SymbolKind = {
File = 1; File = 1,
Module = 2; Module = 2,
Namespace = 3; Namespace = 3,
Package = 4; Package = 4,
Class = 5; Class = 5,
Method = 6; Method = 6,
Property = 7; Property = 7,
Field = 8; Field = 8,
Constructor = 9; Constructor = 9,
Enum = 10; Enum = 10,
Interface = 11; Interface = 11,
Function = 12; Function = 12,
Variable = 13; Variable = 13,
Constant = 14; Constant = 14,
String = 15; String = 15,
Number = 16; Number = 16,
Boolean = 17; Boolean = 17,
Array = 18; Array = 18,
Object = 19; Object = 19,
Key = 20; Key = 20,
Null = 21; Null = 21,
EnumMember = 22; EnumMember = 22,
Struct = 23; Struct = 23,
Event = 24; Event = 24,
Operator = 25; Operator = 25,
TypeParameter = 26; TypeParameter = 26,
}; },
-- Represents reasons why a text document is saved. -- Represents reasons why a text document is saved.
TextDocumentSaveReason = { TextDocumentSaveReason = {
-- Manually triggered, e.g. by the user pressing save, by starting debugging, -- Manually triggered, e.g. by the user pressing save, by starting debugging,
-- or by an API call. -- or by an API call.
Manual = 1; Manual = 1,
-- Automatic after a delay. -- Automatic after a delay.
AfterDelay = 2; AfterDelay = 2,
-- When the editor lost focus. -- When the editor lost focus.
FocusOut = 3; FocusOut = 3,
}; },
ErrorCodes = { ErrorCodes = {
-- Defined by JSON RPC -- Defined by JSON RPC
ParseError = -32700; ParseError = -32700,
InvalidRequest = -32600; InvalidRequest = -32600,
MethodNotFound = -32601; MethodNotFound = -32601,
InvalidParams = -32602; InvalidParams = -32602,
InternalError = -32603; InternalError = -32603,
serverErrorStart = -32099; serverErrorStart = -32099,
serverErrorEnd = -32000; serverErrorEnd = -32000,
ServerNotInitialized = -32002; ServerNotInitialized = -32002,
UnknownErrorCode = -32001; UnknownErrorCode = -32001,
-- Defined by the protocol. -- Defined by the protocol.
RequestCancelled = -32800; RequestCancelled = -32800,
ContentModified = -32801; ContentModified = -32801,
}; },
-- Describes the content type that a client supports in various -- Describes the content type that a client supports in various
-- result literals like `Hover`, `ParameterInfo` or `CompletionItem`. -- result literals like `Hover`, `ParameterInfo` or `CompletionItem`.
@@ -175,88 +175,88 @@ local constants = {
-- are reserved for internal usage. -- are reserved for internal usage.
MarkupKind = { MarkupKind = {
-- Plain text is supported as a content format -- Plain text is supported as a content format
PlainText = 'plaintext'; PlainText = 'plaintext',
-- Markdown is supported as a content format -- Markdown is supported as a content format
Markdown = 'markdown'; Markdown = 'markdown',
}; },
ResourceOperationKind = { ResourceOperationKind = {
-- Supports creating new files and folders. -- Supports creating new files and folders.
Create = 'create'; Create = 'create',
-- Supports renaming existing files and folders. -- Supports renaming existing files and folders.
Rename = 'rename'; Rename = 'rename',
-- Supports deleting existing files and folders. -- Supports deleting existing files and folders.
Delete = 'delete'; Delete = 'delete',
}; },
FailureHandlingKind = { FailureHandlingKind = {
-- Applying the workspace change is simply aborted if one of the changes provided -- Applying the workspace change is simply aborted if one of the changes provided
-- fails. All operations executed before the failing operation stay executed. -- fails. All operations executed before the failing operation stay executed.
Abort = 'abort'; Abort = 'abort',
-- All operations are executed transactionally. That means they either all -- All operations are executed transactionally. That means they either all
-- succeed or no changes at all are applied to the workspace. -- 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 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 -- If resource changes (create, rename or delete file) are part of the change the failure
-- handling strategy is abort. -- handling strategy is abort.
TextOnlyTransactional = 'textOnlyTransactional'; TextOnlyTransactional = 'textOnlyTransactional',
-- The client tries to undo the operations already executed. But there is no -- The client tries to undo the operations already executed. But there is no
-- guarantee that this succeeds. -- guarantee that this succeeds.
Undo = 'undo'; Undo = 'undo',
}; },
-- Known error codes for an `InitializeError`; -- Known error codes for an `InitializeError`;
InitializeError = { InitializeError = {
-- If the protocol version provided by the client can't be handled by the server. -- 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 -- @deprecated This initialize error got replaced by client capabilities. There is
-- no version handshake in version 3.0x -- no version handshake in version 3.0x
unknownProtocolVersion = 1; unknownProtocolVersion = 1,
}; },
-- Defines how the host (editor) should sync document changes to the language server. -- Defines how the host (editor) should sync document changes to the language server.
TextDocumentSyncKind = { TextDocumentSyncKind = {
-- Documents should not be synced at all. -- Documents should not be synced at all.
None = 0; None = 0,
-- Documents are synced by always sending the full content -- Documents are synced by always sending the full content
-- of the document. -- of the document.
Full = 1; Full = 1,
-- Documents are synced by sending the full content on open. -- Documents are synced by sending the full content on open.
-- After that only incremental updates to the document are -- After that only incremental updates to the document are
-- send. -- send.
Incremental = 2; Incremental = 2,
}; },
WatchKind = { WatchKind = {
-- Interested in create events. -- Interested in create events.
Create = 1; Create = 1,
-- Interested in change events -- Interested in change events
Change = 2; Change = 2,
-- Interested in delete events -- Interested in delete events
Delete = 4; Delete = 4,
}; },
-- Defines whether the insert text in a completion item should be interpreted as -- Defines whether the insert text in a completion item should be interpreted as
-- plain text or a snippet. -- plain text or a snippet.
InsertTextFormat = { InsertTextFormat = {
-- The primary text to be inserted is treated as a plain string. -- 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. -- The primary text to be inserted is treated as a snippet.
-- --
-- A snippet can define tab stops and placeholders with `$1`, `$2` -- A snippet can define tab stops and placeholders with `$1`, `$2`
-- and `${3:foo};`. `$0` defines the final tab stop, it defaults to -- and `${3:foo};`. `$0` defines the final tab stop, it defaults to
-- the end of the snippet. Placeholders with equal identifiers are linked, -- the end of the snippet. Placeholders with equal identifiers are linked,
-- that is typing in one will update others too. -- that is typing in one will update others too.
Snippet = 2; Snippet = 2,
}; },
-- A set of predefined code action kinds -- A set of predefined code action kinds
CodeActionKind = { CodeActionKind = {
-- Empty kind. -- Empty kind.
Empty = ''; Empty = '',
-- Base kind for quickfix actions -- Base kind for quickfix actions
QuickFix = 'quickfix'; QuickFix = 'quickfix',
-- Base kind for refactoring actions -- Base kind for refactoring actions
Refactor = 'refactor'; Refactor = 'refactor',
-- Base kind for refactoring extraction actions -- Base kind for refactoring extraction actions
-- --
-- Example extract actions: -- Example extract actions:
@@ -266,7 +266,7 @@ local constants = {
-- - Extract variable -- - Extract variable
-- - Extract interface from class -- - Extract interface from class
-- - ... -- - ...
RefactorExtract = 'refactor.extract'; RefactorExtract = 'refactor.extract',
-- Base kind for refactoring inline actions -- Base kind for refactoring inline actions
-- --
-- Example inline actions: -- Example inline actions:
@@ -275,7 +275,7 @@ local constants = {
-- - Inline variable -- - Inline variable
-- - Inline constant -- - Inline constant
-- - ... -- - ...
RefactorInline = 'refactor.inline'; RefactorInline = 'refactor.inline',
-- Base kind for refactoring rewrite actions -- Base kind for refactoring rewrite actions
-- --
-- Example rewrite actions: -- Example rewrite actions:
@@ -286,14 +286,14 @@ local constants = {
-- - Make method static -- - Make method static
-- - Move method to base class -- - Move method to base class
-- - ... -- - ...
RefactorRewrite = 'refactor.rewrite'; RefactorRewrite = 'refactor.rewrite',
-- Base kind for source actions -- Base kind for source actions
-- --
-- Source code actions apply to the entire file. -- Source code actions apply to the entire file.
Source = 'source'; Source = 'source',
-- Base kind for an organize imports source action -- Base kind for an organize imports source action
SourceOrganizeImports = 'source.organizeImports'; SourceOrganizeImports = 'source.organizeImports',
}; },
} }
for k, v in pairs(constants) do for k, v in pairs(constants) do
@@ -620,19 +620,19 @@ function protocol.make_client_capabilities()
return { return {
textDocument = { textDocument = {
synchronization = { synchronization = {
dynamicRegistration = false; dynamicRegistration = false,
-- TODO(ashkan) Send textDocument/willSave before saving (BufWritePre) -- TODO(ashkan) Send textDocument/willSave before saving (BufWritePre)
willSave = false; willSave = false,
-- TODO(ashkan) Implement textDocument/willSaveWaitUntil -- TODO(ashkan) Implement textDocument/willSaveWaitUntil
willSaveWaitUntil = false; willSaveWaitUntil = false,
-- Send textDocument/didSave after saving (BufWritePost) -- Send textDocument/didSave after saving (BufWritePost)
didSave = true; didSave = true,
}; },
codeAction = { codeAction = {
dynamicRegistration = false; dynamicRegistration = false,
codeActionLiteralSupport = { codeActionLiteralSupport = {
codeActionKind = { codeActionKind = {
@@ -640,138 +640,146 @@ function protocol.make_client_capabilities()
local res = vim.tbl_values(protocol.CodeActionKind) local res = vim.tbl_values(protocol.CodeActionKind)
table.sort(res) table.sort(res)
return res return res
end)(); end)(),
}; },
}; },
isPreferredSupport = true; isPreferredSupport = true,
dataSupport = true; dataSupport = true,
resolveSupport = { resolveSupport = {
properties = { 'edit', } properties = { 'edit' },
}; },
}; },
completion = { completion = {
dynamicRegistration = false; dynamicRegistration = false,
completionItem = { completionItem = {
-- Until we can actually expand snippet, move cursor and allow for true snippet experience, -- Until we can actually expand snippet, move cursor and allow for true snippet experience,
-- this should be disabled out of the box. -- this should be disabled out of the box.
-- However, users can turn this back on if they have a snippet plugin. -- However, users can turn this back on if they have a snippet plugin.
snippetSupport = false; snippetSupport = false,
commitCharactersSupport = false; commitCharactersSupport = false,
preselectSupport = false; preselectSupport = false,
deprecatedSupport = false; deprecatedSupport = false,
documentationFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText }; documentationFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
}; },
completionItemKind = { completionItemKind = {
valueSet = (function() valueSet = (function()
local res = {} local res = {}
for k in ipairs(protocol.CompletionItemKind) do 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 end
return res return res
end)(); end)(),
}; },
-- TODO(tjdevries): Implement this -- TODO(tjdevries): Implement this
contextSupport = false; contextSupport = false,
}; },
declaration = { declaration = {
linkSupport = true; linkSupport = true,
}; },
definition = { definition = {
linkSupport = true; linkSupport = true,
}; },
implementation = { implementation = {
linkSupport = true; linkSupport = true,
}; },
typeDefinition = { typeDefinition = {
linkSupport = true; linkSupport = true,
}; },
hover = { hover = {
dynamicRegistration = false; dynamicRegistration = false,
contentFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText }; contentFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
}; },
signatureHelp = { signatureHelp = {
dynamicRegistration = false; dynamicRegistration = false,
signatureInformation = { signatureInformation = {
activeParameterSupport = true; activeParameterSupport = true,
documentationFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText }; documentationFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
parameterInformation = { parameterInformation = {
labelOffsetSupport = true; labelOffsetSupport = true,
}; },
}; },
}; },
references = { references = {
dynamicRegistration = false; dynamicRegistration = false,
}; },
documentHighlight = { documentHighlight = {
dynamicRegistration = false dynamicRegistration = false,
}; },
documentSymbol = { documentSymbol = {
dynamicRegistration = false; dynamicRegistration = false,
symbolKind = { symbolKind = {
valueSet = (function() valueSet = (function()
local res = {} local res = {}
for k in ipairs(protocol.SymbolKind) do 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 end
return res return res
end)(); end)(),
}; },
hierarchicalDocumentSymbolSupport = true; hierarchicalDocumentSymbolSupport = true,
}; },
rename = { rename = {
dynamicRegistration = false; dynamicRegistration = false,
prepareSupport = true; prepareSupport = true,
}; },
publishDiagnostics = { publishDiagnostics = {
relatedInformation = true; relatedInformation = true,
tagSupport = { tagSupport = {
valueSet = (function() valueSet = (function()
local res = {} local res = {}
for k in ipairs(protocol.DiagnosticTag) do 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 end
return res return res
end)(); end)(),
}; },
}; },
}; },
workspace = { workspace = {
symbol = { symbol = {
dynamicRegistration = false; dynamicRegistration = false,
symbolKind = { symbolKind = {
valueSet = (function() valueSet = (function()
local res = {} local res = {}
for k in ipairs(protocol.SymbolKind) do 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 end
return res return res
end)(); end)(),
}; },
hierarchicalWorkspaceSymbolSupport = true; hierarchicalWorkspaceSymbolSupport = true,
}; },
workspaceFolders = true; workspaceFolders = true,
applyEdit = true; applyEdit = true,
workspaceEdit = { workspaceEdit = {
resourceOperations = {'rename', 'create', 'delete',}, resourceOperations = { 'rename', 'create', 'delete' },
}; },
}; },
callHierarchy = { callHierarchy = {
dynamicRegistration = false; dynamicRegistration = false,
}; },
experimental = nil; experimental = nil,
window = { window = {
workDoneProgress = true; workDoneProgress = true,
showMessage = { showMessage = {
messageActionItem = { messageActionItem = {
additionalPropertiesSupport = false; additionalPropertiesSupport = false,
}; },
}; },
showDocument = { showDocument = {
support = false; support = false,
}; },
}; },
} }
end end
@@ -791,12 +799,12 @@ function protocol.resolve_capabilities(server_capabilities)
willSaveWaitUntil = false, willSaveWaitUntil = false,
save = { save = {
includeText = false, includeText = false,
} },
} }
elseif type(textDocumentSync) == 'number' then elseif type(textDocumentSync) == 'number' then
-- Backwards compatibility -- Backwards compatibility
if not TextDocumentSyncKind[textDocumentSync] then if not TextDocumentSyncKind[textDocumentSync] then
return nil, "Invalid server TextDocumentSyncKind for textDocumentSync" return nil, 'Invalid server TextDocumentSyncKind for textDocumentSync'
end end
server_capabilities.textDocumentSync = { server_capabilities.textDocumentSync = {
openClose = true, openClose = true,
@@ -805,10 +813,10 @@ function protocol.resolve_capabilities(server_capabilities)
willSaveWaitUntil = false, willSaveWaitUntil = false,
save = { save = {
includeText = false, includeText = false,
} },
} }
elseif type(textDocumentSync) ~= 'table' then 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 end
return server_capabilities return server_capabilities
end end
@@ -827,39 +835,41 @@ function protocol._resolve_capabilities_compat(server_capabilities)
if textDocumentSync == nil then if textDocumentSync == nil then
-- Defaults if omitted. -- Defaults if omitted.
text_document_sync_properties = { text_document_sync_properties = {
text_document_open_close = false; text_document_open_close = false,
text_document_did_change = TextDocumentSyncKind.None; text_document_did_change = TextDocumentSyncKind.None,
-- text_document_did_change = false; -- text_document_did_change = false;
text_document_will_save = false; text_document_will_save = false,
text_document_will_save_wait_until = false; text_document_will_save_wait_until = false,
text_document_save = false; text_document_save = false,
text_document_save_include_text = false; text_document_save_include_text = false,
} }
elseif type(textDocumentSync) == 'number' then elseif type(textDocumentSync) == 'number' then
-- Backwards compatibility -- Backwards compatibility
if not TextDocumentSyncKind[textDocumentSync] then if not TextDocumentSyncKind[textDocumentSync] then
return nil, "Invalid server TextDocumentSyncKind for textDocumentSync" return nil, 'Invalid server TextDocumentSyncKind for textDocumentSync'
end end
text_document_sync_properties = { text_document_sync_properties = {
text_document_open_close = true; text_document_open_close = true,
text_document_did_change = textDocumentSync; text_document_did_change = textDocumentSync,
text_document_will_save = false; text_document_will_save = false,
text_document_will_save_wait_until = false; text_document_will_save_wait_until = false,
text_document_save = true; text_document_save = true,
text_document_save_include_text = false; text_document_save_include_text = false,
} }
elseif type(textDocumentSync) == 'table' then elseif type(textDocumentSync) == 'table' then
text_document_sync_properties = { text_document_sync_properties = {
text_document_open_close = if_nil(textDocumentSync.openClose, false); text_document_open_close = if_nil(textDocumentSync.openClose, false),
text_document_did_change = if_nil(textDocumentSync.change, TextDocumentSyncKind.None); text_document_did_change = if_nil(textDocumentSync.change, TextDocumentSyncKind.None),
text_document_will_save = if_nil(textDocumentSync.willSave, false); text_document_will_save = if_nil(textDocumentSync.willSave, false),
text_document_will_save_wait_until = if_nil(textDocumentSync.willSaveWaitUntil, false); text_document_will_save_wait_until = if_nil(textDocumentSync.willSaveWaitUntil, false),
text_document_save = if_nil(textDocumentSync.save, false); text_document_save = if_nil(textDocumentSync.save, false),
text_document_save_include_text = if_nil(type(textDocumentSync.save) == 'table' text_document_save_include_text = if_nil(
and textDocumentSync.save.includeText, false); type(textDocumentSync.save) == 'table' and textDocumentSync.save.includeText,
false
),
} }
else 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
end end
general_properties.completion = server_capabilities.completionProvider ~= nil 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 = true
general_properties.code_lens_resolve = server_capabilities.codeLensProvider.resolveProvider or false general_properties.code_lens_resolve = server_capabilities.codeLensProvider.resolveProvider or false
else else
error("The server sent invalid codeLensProvider") error('The server sent invalid codeLensProvider')
end end
if server_capabilities.codeActionProvider == nil then if server_capabilities.codeActionProvider == nil then
general_properties.code_action = false general_properties.code_action = false
elseif type(server_capabilities.codeActionProvider) == 'boolean' elseif
or type(server_capabilities.codeActionProvider) == 'table' then type(server_capabilities.codeActionProvider) == 'boolean'
or type(server_capabilities.codeActionProvider) == 'table'
then
general_properties.code_action = server_capabilities.codeActionProvider general_properties.code_action = server_capabilities.codeActionProvider
else else
error("The server sent invalid codeActionProvider") error('The server sent invalid codeActionProvider')
end end
if server_capabilities.declarationProvider == nil then if server_capabilities.declarationProvider == nil then
@@ -908,7 +920,7 @@ function protocol._resolve_capabilities_compat(server_capabilities)
elseif type(server_capabilities.declarationProvider) == 'table' then elseif type(server_capabilities.declarationProvider) == 'table' then
general_properties.declaration = server_capabilities.declarationProvider general_properties.declaration = server_capabilities.declarationProvider
else else
error("The server sent invalid declarationProvider") error('The server sent invalid declarationProvider')
end end
if server_capabilities.typeDefinitionProvider == nil then if server_capabilities.typeDefinitionProvider == nil then
@@ -918,7 +930,7 @@ function protocol._resolve_capabilities_compat(server_capabilities)
elseif type(server_capabilities.typeDefinitionProvider) == 'table' then elseif type(server_capabilities.typeDefinitionProvider) == 'table' then
general_properties.type_definition = server_capabilities.typeDefinitionProvider general_properties.type_definition = server_capabilities.typeDefinitionProvider
else else
error("The server sent invalid typeDefinitionProvider") error('The server sent invalid typeDefinitionProvider')
end end
if server_capabilities.implementationProvider == nil then if server_capabilities.implementationProvider == nil then
@@ -928,7 +940,7 @@ function protocol._resolve_capabilities_compat(server_capabilities)
elseif type(server_capabilities.implementationProvider) == 'table' then elseif type(server_capabilities.implementationProvider) == 'table' then
general_properties.implementation = server_capabilities.implementationProvider general_properties.implementation = server_capabilities.implementationProvider
else else
error("The server sent invalid implementationProvider") error('The server sent invalid implementationProvider')
end end
local workspace = server_capabilities.workspace local workspace = server_capabilities.workspace
@@ -937,43 +949,43 @@ function protocol._resolve_capabilities_compat(server_capabilities)
-- Defaults if omitted. -- Defaults if omitted.
workspace_properties = { workspace_properties = {
workspace_folder_properties = { workspace_folder_properties = {
supported = false; supported = false,
changeNotifications=false; changeNotifications = false,
} },
} }
elseif type(workspace.workspaceFolders) == 'table' then elseif type(workspace.workspaceFolders) == 'table' then
workspace_properties = { workspace_properties = {
workspace_folder_properties = { workspace_folder_properties = {
supported = if_nil(workspace.workspaceFolders.supported, false); supported = if_nil(workspace.workspaceFolders.supported, false),
changeNotifications = if_nil(workspace.workspaceFolders.changeNotifications, false); changeNotifications = if_nil(workspace.workspaceFolders.changeNotifications, false),
},
}
} }
else else
error("The server sent invalid workspace") error('The server sent invalid workspace')
end end
local signature_help_properties local signature_help_properties
if server_capabilities.signatureHelpProvider == nil then if server_capabilities.signatureHelpProvider == nil then
signature_help_properties = { signature_help_properties = {
signature_help = false; signature_help = false,
signature_help_trigger_characters = {}; signature_help_trigger_characters = {},
} }
elseif type(server_capabilities.signatureHelpProvider) == 'table' then elseif type(server_capabilities.signatureHelpProvider) == 'table' then
signature_help_properties = { signature_help_properties = {
signature_help = true; signature_help = true,
-- The characters that trigger signature help automatically. -- 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 else
error("The server sent invalid signatureHelpProvider") error('The server sent invalid signatureHelpProvider')
end end
local capabilities = vim.tbl_extend("error" local capabilities = vim.tbl_extend(
, text_document_sync_properties 'error',
, signature_help_properties text_document_sync_properties,
, workspace_properties signature_help_properties,
, general_properties workspace_properties,
general_properties
) )
return capabilities return capabilities

View File

@@ -45,10 +45,12 @@ end
---@param encoded_message (string) ---@param encoded_message (string)
---@returns (table) table containing encoded message and `Content-Length` attribute ---@returns (table) table containing encoded message and `Content-Length` attribute
local function format_message_with_content_length(encoded_message) local function format_message_with_content_length(encoded_message)
return table.concat { return table.concat({
'Content-Length: '; tostring(#encoded_message); '\r\n\r\n'; 'Content-Length: ',
encoded_message; tostring(#encoded_message),
} '\r\n\r\n',
encoded_message,
})
end end
---@private ---@private
@@ -65,23 +67,25 @@ local function parse_headers(header)
if line == '' then if line == '' then
break break
end end
local key, value = line:match("^%s*(%S+)%s*:%s*(.+)%s*$") local key, value = line:match('^%s*(%S+)%s*:%s*(.+)%s*$')
if key then if key then
key = key:lower():gsub('%-', '_') key = key:lower():gsub('%-', '_')
headers[key] = value headers[key] = value
else else
local _ = log.error() and log.error("invalid header line %q", line) local _ = log.error() and log.error('invalid header line %q', line)
error(string.format("invalid header line %q", line)) error(string.format('invalid header line %q', line))
end end
end end
headers.content_length = tonumber(headers.content_length) 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 return headers
end end
-- This is the start of any possible header patterns. The gsub converts it to a -- This is the start of any possible header patterns. The gsub converts it to a
-- case insensitive pattern. -- 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 ---@private
--- The actual workhorse. --- The actual workhorse.
@@ -109,8 +113,7 @@ local function request_parser_loop()
local body_length = #body_chunks[1] local body_length = #body_chunks[1]
-- Keep waiting for data until we have enough. -- Keep waiting for data until we have enough.
while body_length < content_length do while body_length < content_length do
local chunk = coroutine.yield() local chunk = coroutine.yield() or error('Expected more data for the body. The server may have died.') -- TODO hmm.
or error("Expected more data for the body. The server may have died.") -- TODO hmm.
table.insert(body_chunks, chunk) table.insert(body_chunks, chunk)
body_length = body_length + #chunk body_length = body_length + #chunk
end end
@@ -123,25 +126,24 @@ local function request_parser_loop()
end end
local body = table.concat(body_chunks) local body = table.concat(body_chunks)
-- Yield our data. -- Yield our data.
buffer = rest..(coroutine.yield(headers, body) buffer = rest
or error("Expected more data for the body. The server may have died.")) -- TODO hmm. .. (coroutine.yield(headers, body) or error('Expected more data for the body. The server may have died.')) -- TODO hmm.
else else
-- Get more data since we don't have enough. -- Get more data since we don't have enough.
buffer = buffer..(coroutine.yield() buffer = buffer .. (coroutine.yield() or error('Expected more data for the header. The server may have died.')) -- TODO hmm.
or error("Expected more data for the header. The server may have died.")) -- TODO hmm.
end end
end end
end end
--- Mapping of error codes used by the client --- Mapping of error codes used by the client
local client_errors = { local client_errors = {
INVALID_SERVER_MESSAGE = 1; INVALID_SERVER_MESSAGE = 1,
INVALID_SERVER_JSON = 2; INVALID_SERVER_JSON = 2,
NO_RESULT_CALLBACK_FOUND = 3; NO_RESULT_CALLBACK_FOUND = 3,
READ_ERROR = 4; READ_ERROR = 4,
NOTIFICATION_HANDLER_ERROR = 5; NOTIFICATION_HANDLER_ERROR = 5,
SERVER_REQUEST_HANDLER_ERROR = 6; SERVER_REQUEST_HANDLER_ERROR = 6,
SERVER_RESULT_CALLBACK_ERROR = 7; SERVER_RESULT_CALLBACK_ERROR = 7,
} }
client_errors = vim.tbl_add_reverse_lookup(client_errors) 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 ---@param err (table) The error object
---@returns (string) The formatted error message ---@returns (string) The formatted error message
local function format_rpc_error(err) local function format_rpc_error(err)
validate { validate({
err = { err, 't' }; err = { err, 't' },
} })
-- There is ErrorCodes in the LSP specification, -- There is ErrorCodes in the LSP specification,
-- but in ResponseError.code it is not used and the actual type is number. -- but in ResponseError.code it is not used and the actual type is number.
local code local code
if protocol.ErrorCodes[err.code] then 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 else
code = string.format("code_name = unknown, code = %s,", err.code) code = string.format('code_name = unknown, code = %s,', err.code)
end end
local message_parts = {"RPC[Error]", code} local message_parts = { 'RPC[Error]', code }
if err.message then if err.message then
table.insert(message_parts, "message =") table.insert(message_parts, 'message =')
table.insert(message_parts, string.format("%q", err.message)) table.insert(message_parts, string.format('%q', err.message))
end end
if err.data then if err.data then
table.insert(message_parts, "data =") table.insert(message_parts, 'data =')
table.insert(message_parts, vim.inspect(err.data)) table.insert(message_parts, vim.inspect(err.data))
end end
return table.concat(message_parts, ' ') 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)? -- TODO should this error or just pick a sane error (like InternalError)?
local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code') local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code')
return setmetatable({ return setmetatable({
code = code; code = code,
message = message or code_name; message = message or code_name,
data = data; data = data,
}, { }, {
__tostring = format_rpc_error; __tostring = format_rpc_error,
}) })
end end
@@ -220,7 +222,7 @@ end
---@param signal (number): Number describing the signal used to terminate (if ---@param signal (number): Number describing the signal used to terminate (if
---any) ---any)
function default_dispatchers.on_exit(code, signal) 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 end
---@private ---@private
--- Default dispatcher for client errors. --- Default dispatcher for client errors.
@@ -258,15 +260,15 @@ end
--- - {handle} A handle for low-level interaction with the LSP server process --- - {handle} A handle for low-level interaction with the LSP server process
--- |vim.loop|. --- |vim.loop|.
local function start(cmd, cmd_args, dispatchers, extra_spawn_params) 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}) local _ = log.info() and log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params })
validate { validate({
cmd = { cmd, 's' }; cmd = { cmd, 's' },
cmd_args = { cmd_args, 't' }; cmd_args = { cmd_args, 't' },
dispatchers = { dispatchers, 't', true }; dispatchers = { dispatchers, 't', true },
} })
if extra_spawn_params and extra_spawn_params.cwd then 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 end
if dispatchers then if dispatchers then
local user_dispatchers = dispatchers 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] local user_dispatcher = user_dispatchers[dispatch_name]
if user_dispatcher then if user_dispatcher then
if type(user_dispatcher) ~= 'function' 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 end
-- server_request is wrapped elsewhere. -- server_request is wrapped elsewhere.
if not (dispatch_name == 'server_request' if
or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason. not (dispatch_name == 'server_request' or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason.
then then
user_dispatcher = schedule_wrap(user_dispatcher) user_dispatcher = schedule_wrap(user_dispatcher)
end end
@@ -317,9 +319,9 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
dispatchers.on_exit(code, signal) dispatchers.on_exit(code, signal)
end end
local spawn_params = { local spawn_params = {
args = cmd_args; args = cmd_args,
stdio = {stdin, stdout, stderr}; stdio = { stdin, stdout, stderr },
detached = true; detached = true,
} }
if extra_spawn_params then if extra_spawn_params then
spawn_params.cwd = extra_spawn_params.cwd spawn_params.cwd = extra_spawn_params.cwd
@@ -330,11 +332,11 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
end end
handle, pid = uv.spawn(cmd, spawn_params, onexit) handle, pid = uv.spawn(cmd, spawn_params, onexit)
if handle == nil then if handle == nil then
local msg = string.format("Spawning language server with cmd: `%s` failed", cmd) local msg = string.format('Spawning language server with cmd: `%s` failed', cmd)
if string.match(pid, "ENOENT") then if string.match(pid, 'ENOENT') then
msg = msg .. ". The language server is either not installed, missing from PATH, or not executable." msg = msg .. '. The language server is either not installed, missing from PATH, or not executable.'
else else
msg = msg .. string.format(" with error message: %s", pid) msg = msg .. string.format(' with error message: %s', pid)
end end
vim.notify(msg, vim.log.levels.WARN) vim.notify(msg, vim.log.levels.WARN)
return return
@@ -348,8 +350,10 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
---@param payload table ---@param payload table
---@returns true if the payload could be scheduled, false if the main event-loop is in the process of closing. ---@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 function encode_and_send(payload)
local _ = log.debug() and log.debug("rpc.send", payload) local _ = log.debug() and log.debug('rpc.send', payload)
if handle == nil or handle:is_closing() then return false end if handle == nil or handle:is_closing() then
return false
end
local encoded = vim.json.encode(payload) local encoded = vim.json.encode(payload)
stdin:write(format_message_with_content_length(encoded)) stdin:write(format_message_with_content_length(encoded))
return true 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 ---@param params (table): Parameters for the invoked LSP method
---@returns (bool) `true` if notification could be sent, `false` if not ---@returns (bool) `true` if notification could be sent, `false` if not
local function notify(method, params) local function notify(method, params)
return encode_and_send { return encode_and_send({
jsonrpc = "2.0"; jsonrpc = '2.0',
method = method; method = method,
params = params; params = params,
} })
end end
---@private ---@private
--- sends an error object to the remote LSP process. --- sends an error object to the remote LSP process.
local function send_response(request_id, err, result) local function send_response(request_id, err, result)
return encode_and_send { return encode_and_send({
id = request_id; id = request_id,
jsonrpc = "2.0"; jsonrpc = '2.0',
error = err; error = err,
result = result; result = result,
} })
end end
-- FIXME: DOC: Should be placed on the RPC client object returned by -- 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 ---@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 ---@returns (bool, number) `(true, message_id)` if request could be sent, `false` if not
local function request(method, params, callback, notify_reply_callback) local function request(method, params, callback, notify_reply_callback)
validate { validate({
callback = { callback, 'f' }; callback = { callback, 'f' },
notify_reply_callback = { notify_reply_callback, 'f', true }; notify_reply_callback = { notify_reply_callback, 'f', true },
} })
message_index = message_index + 1 message_index = message_index + 1
local message_id = message_index local message_id = message_index
local result = encode_and_send { local result = encode_and_send({
id = message_id; id = message_id,
jsonrpc = "2.0"; jsonrpc = '2.0',
method = method; method = method,
params = params; params = params,
} })
if result then if result then
if message_callbacks then if message_callbacks then
message_callbacks[message_id] = schedule_wrap(callback) 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) stderr:read_start(function(_err, chunk)
if chunk then 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
end) end)
@@ -455,7 +459,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
on_error(client_errors.INVALID_SERVER_JSON, decoded) on_error(client_errors.INVALID_SERVER_JSON, decoded)
return return
end 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 if type(decoded.method) == 'string' and decoded.id then
local err local err
@@ -463,17 +467,30 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
-- we can still use the result. -- we can still use the result.
schedule(function() schedule(function()
local status, result local status, result
status, result, err = try_call(client_errors.SERVER_REQUEST_HANDLER_ERROR, status, result, err = try_call(
dispatchers.server_request, decoded.method, decoded.params) client_errors.SERVER_REQUEST_HANDLER_ERROR,
local _ = log.debug() and log.debug("server_request: callback result", { status = status, result = result, err = err }) 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 status then
if not (result or err) then if not (result or err) then
-- TODO this can be a problem if `null` is sent for result. needs vim.NIL -- 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 end
if err then if err then
assert(type(err) == 'table', "err must be a table. 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.") 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 err.message = err.message or code_name
end end
else else
@@ -485,16 +502,15 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
end) end)
-- This works because we are expecting vim.NIL here -- This works because we are expecting vim.NIL here
elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then
-- We sent a number, so we expect a number. -- We sent a number, so we expect a number.
local result_id = tonumber(decoded.id) local result_id = tonumber(decoded.id)
-- Notify the user that a response was received for the request -- Notify the user that a response was received for the request
local notify_reply_callback = notify_reply_callbacks and notify_reply_callbacks[result_id] local notify_reply_callback = notify_reply_callbacks and notify_reply_callbacks[result_id]
if notify_reply_callback then if notify_reply_callback then
validate { validate({
notify_reply_callback = { notify_reply_callback, 'f' }; notify_reply_callback = { notify_reply_callback, 'f' },
} })
notify_reply_callback(result_id) notify_reply_callback(result_id)
notify_reply_callbacks[result_id] = nil notify_reply_callbacks[result_id] = nil
end end
@@ -503,7 +519,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
if decoded.error then if decoded.error then
local mute_error = false local mute_error = false
if decoded.error.code == protocol.ErrorCodes.RequestCancelled then 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 mute_error = true
end end
@@ -523,24 +539,22 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
local callback = message_callbacks and message_callbacks[result_id] local callback = message_callbacks and message_callbacks[result_id]
if callback then if callback then
message_callbacks[result_id] = nil message_callbacks[result_id] = nil
validate { validate({
callback = { callback, 'f' }; callback = { callback, 'f' },
} })
if decoded.error then if decoded.error then
decoded.error = setmetatable(decoded.error, { decoded.error = setmetatable(decoded.error, {
__tostring = format_rpc_error; __tostring = format_rpc_error,
}) })
end end
try_call(client_errors.SERVER_RESULT_CALLBACK_ERROR, try_call(client_errors.SERVER_RESULT_CALLBACK_ERROR, callback, decoded.error, decoded.result)
callback, decoded.error, decoded.result)
else else
on_error(client_errors.NO_RESULT_CALLBACK_FOUND, decoded) 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 end
elseif type(decoded.method) == 'string' then elseif type(decoded.method) == 'string' then
-- Notification -- Notification
try_call(client_errors.NOTIFICATION_HANDLER_ERROR, try_call(client_errors.NOTIFICATION_HANDLER_ERROR, dispatchers.notification, decoded.method, decoded.params)
dispatchers.notification, decoded.method, decoded.params)
else else
-- Invalid server message -- Invalid server message
on_error(client_errors.INVALID_SERVER_MESSAGE, decoded) on_error(client_errors.INVALID_SERVER_MESSAGE, decoded)
@@ -556,7 +570,9 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
return return
end end
-- This should signal that we are done reading from the client. -- 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 -- Flush anything in the parser by looping until we don't get a result
-- anymore. -- anymore.
while true do while true do
@@ -574,17 +590,17 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
end) end)
return { return {
pid = pid; pid = pid,
handle = handle; handle = handle,
request = request; request = request,
notify = notify notify = notify,
} }
end end
return { return {
start = start; start = start,
rpc_response_error = rpc_response_error; rpc_response_error = rpc_response_error,
format_rpc_error = format_rpc_error; format_rpc_error = format_rpc_error,
client_errors = client_errors; client_errors = client_errors,
} }
-- vim:sw=2 ts=2 et -- vim:sw=2 ts=2 et

View File

@@ -203,14 +203,30 @@ end
---@param new_lastline integer ---@param new_lastline integer
---@param offset_encoding string ---@param offset_encoding string
---@returns (int, int) end_line_idx and end_col_idx of range ---@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. -- If firstline == new_lastline, the first change occurred on a line that was deleted.
-- In this case, the last_byte... -- In this case, the last_byte...
if firstline == new_lastline then 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 end
if firstline == lastline then 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 end
-- Compare on last line, at minimum will be the start range -- Compare on last line, at minimum will be the start range
local start_line_idx = start_range.line_idx 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 end
for idx = 0, max_length do for idx = 0, max_length do
byte_offset = idx byte_offset = idx
if if str_byte(prev_line, prev_line_length - byte_offset) ~= str_byte(curr_line, curr_line_length - byte_offset) then
str_byte(prev_line, prev_line_length - byte_offset) ~= str_byte(curr_line, curr_line_length - byte_offset)
then
break break
end end
end end
@@ -282,13 +296,12 @@ end
---@returns string text extracted from defined region ---@returns string text extracted from defined region
local function extract_text(lines, start_range, end_range, line_ending) local function extract_text(lines, start_range, end_range, line_ending)
if not lines[start_range.line_idx] then if not lines[start_range.line_idx] then
return "" return ''
end end
-- Trivial case: start and end range are the same line, directly grab changed text -- Trivial case: start and end range are the same line, directly grab changed text
if start_range.line_idx == end_range.line_idx then if start_range.line_idx == end_range.line_idx then
-- string.sub is inclusive, end_range is not -- 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) return string.sub(lines[start_range.line_idx], start_range.byte_idx, end_range.byte_idx - 1)
else else
-- Handle deletion case -- Handle deletion case
-- Collect the changed portion of the first changed line -- 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. -- 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)) table.insert(result, string.sub(lines[end_range.line_idx], 1, end_range.byte_idx - 1))
else else
table.insert(result, "") table.insert(result, '')
end end
-- Add line ending between all lines -- Add line ending between all lines

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,9 @@ vim.deepcopy = (function()
local deepcopy_funcs = { local deepcopy_funcs = {
table = function(orig, cache) table = function(orig, cache)
if cache[orig] then return cache[orig] end if cache[orig] then
return cache[orig]
end
local copy = {} local copy = {}
cache[orig] = copy cache[orig] = copy
@@ -46,7 +48,7 @@ vim.deepcopy = (function()
if f then if f then
return f(orig, cache or {}) return f(orig, cache or {})
else else
error("Cannot deepcopy object of type "..type(orig)) error('Cannot deepcopy object of type ' .. type(orig))
end end
end end
end)() end)()
@@ -62,14 +64,14 @@ end)()
---@param plain If `true` use `sep` literally (passed to String.find) ---@param plain If `true` use `sep` literally (passed to String.find)
---@returns Iterator over the split components ---@returns Iterator over the split components
function vim.gsplit(s, sep, plain) 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 start = 1
local done = false local done = false
local function _pass(i, j, ...) local function _pass(i, j, ...)
if i then if i then
assert(j+1 > start, "Infinite loop detected") assert(j + 1 > start, 'Infinite loop detected')
local seg = s:sub(start, i - 1) local seg = s:sub(start, i - 1)
start = j + 1 start = j + 1
return seg, ... return seg, ...
@@ -119,7 +121,7 @@ function vim.split(s, sep, kwargs)
-- Support old signature for backward compatibility -- Support old signature for backward compatibility
plain = kwargs plain = kwargs
else else
vim.validate { kwargs = {kwargs, 't', true} } vim.validate({ kwargs = { kwargs, 't', true } })
kwargs = kwargs or {} kwargs = kwargs or {}
plain = kwargs.plain plain = kwargs.plain
trimempty = kwargs.trimempty trimempty = kwargs.trimempty
@@ -128,7 +130,7 @@ function vim.split(s, sep, kwargs)
local t = {} local t = {}
local skip = trimempty local skip = trimempty
for c in vim.gsplit(s, sep, plain) do for c in vim.gsplit(s, sep, plain) do
if c ~= "" then if c ~= '' then
skip = false skip = false
end end
@@ -139,7 +141,7 @@ function vim.split(s, sep, kwargs)
if trimempty then if trimempty then
for i = #t, 1, -1 do for i = #t, 1, -1 do
if t[i] ~= "" then if t[i] ~= '' then
break break
end end
table.remove(t, i) table.remove(t, i)
@@ -157,7 +159,7 @@ end
---@param t Table ---@param t Table
---@returns list of keys ---@returns list of keys
function vim.tbl_keys(t) 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 = {} local keys = {}
for k, _ in pairs(t) do for k, _ in pairs(t) do
@@ -172,7 +174,7 @@ end
---@param t Table ---@param t Table
---@returns list of values ---@returns list of values
function vim.tbl_values(t) 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 = {} local values = {}
for _, v in pairs(t) do for _, v in pairs(t) do
@@ -186,7 +188,7 @@ end
---@param func function or callable table ---@param func function or callable table
---@param t table ---@param t table
function vim.tbl_map(func, t) function vim.tbl_map(func, t)
vim.validate{func={func,'c'},t={t,'t'}} vim.validate({ func = { func, 'c' }, t = { t, 't' } })
local rettab = {} local rettab = {}
for k, v in pairs(t) do for k, v in pairs(t) do
@@ -200,7 +202,7 @@ end
---@param func function or callable table ---@param func function or callable table
---@param t table ---@param t table
function vim.tbl_filter(func, t) function vim.tbl_filter(func, t)
vim.validate{func={func,'c'},t={t,'t'}} vim.validate({ func = { func, 'c' }, t = { t, 't' } })
local rettab = {} local rettab = {}
for _, entry in pairs(t) do for _, entry in pairs(t) do
@@ -217,7 +219,7 @@ end
---@param value Value to compare ---@param value Value to compare
---@returns true if `t` contains `value` ---@returns true if `t` contains `value`
function vim.tbl_contains(t, value) function vim.tbl_contains(t, value)
vim.validate{t={t,'t'}} vim.validate({ t = { t, 't' } })
for _, v in ipairs(t) do for _, v in ipairs(t) do
if v == value then if v == value then
@@ -233,18 +235,18 @@ end
--- ---
---@param t Table to check ---@param t Table to check
function vim.tbl_isempty(t) 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 return next(t) == nil
end end
--- we only merge empty tables or tables that are not a list --- we only merge empty tables or tables that are not a list
---@private ---@private
local function can_merge(v) 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 end
local function tbl_extend(behavior, deep_extend, ...) 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)) error('invalid "behavior": ' .. tostring(behavior))
end end
@@ -259,7 +261,7 @@ local function tbl_extend(behavior, deep_extend, ...)
for i = 1, select('#', ...) do for i = 1, select('#', ...) do
local tbl = select(i, ...) local tbl = select(i, ...)
vim.validate{["after the second argument"] = {tbl,'t'}} vim.validate({ ['after the second argument'] = { tbl, 't' } })
if tbl then if tbl then
for k, v in pairs(tbl) do for k, v in pairs(tbl) do
if deep_extend and can_merge(v) and can_merge(ret[k]) then if deep_extend and can_merge(v) and can_merge(ret[k]) then
@@ -311,8 +313,12 @@ end
---@param b second value ---@param b second value
---@returns `true` if values are equals, else `false`. ---@returns `true` if values are equals, else `false`.
function vim.deep_equal(a, b) function vim.deep_equal(a, b)
if a == b then return true end if a == b then
if type(a) ~= type(b) then return false end return true
end
if type(a) ~= type(b) then
return false
end
if type(a) == 'table' then if type(a) == 'table' then
for k, v in pairs(a) do for k, v in pairs(a) do
if not vim.deep_equal(v, b[k]) then 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 for _, k in ipairs(keys) do
local v = o[k] local v = o[k]
if o[v] then 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 end
o[v] = k o[v] = k
end end
@@ -389,12 +401,12 @@ end
---@param finish Final index on src. defaults to #src ---@param finish Final index on src. defaults to #src
---@returns dst ---@returns dst
function vim.list_extend(dst, src, start, finish) function vim.list_extend(dst, src, start, finish)
vim.validate { vim.validate({
dst = {dst, 't'}; dst = { dst, 't' },
src = {src, 't'}; src = { src, 't' },
start = {start, 'n', true}; start = { start, 'n', true },
finish = {finish, 'n', true}; finish = { finish, 'n', true },
} })
for i = start or 1, finish or #src do for i = start or 1, finish or #src do
table.insert(dst, src[i]) table.insert(dst, src[i])
end end
@@ -414,7 +426,7 @@ function vim.tbl_flatten(t)
local n = #_t local n = #_t
for i = 1, n do for i = 1, n do
local v = _t[i] local v = _t[i]
if type(v) == "table" then if type(v) == 'table' then
_tbl_flatten(v) _tbl_flatten(v)
elseif v then elseif v then
table.insert(result, v) table.insert(result, v)
@@ -441,7 +453,7 @@ function vim.tbl_islist(t)
local count = 0 local count = 0
for k, _ in pairs(t) do for k, _ in pairs(t) do
if type(k) == "number" then if type(k) == 'number' then
count = count + 1 count = count + 1
else else
return false return false
@@ -471,10 +483,12 @@ end
---@param t Table ---@param t Table
---@returns Number that is the number of the value in table ---@returns Number that is the number of the value in table
function vim.tbl_count(t) function vim.tbl_count(t)
vim.validate{t={t,'t'}} vim.validate({ t = { t, 't' } })
local count = 0 local count = 0
for _ in pairs(t) do count = count + 1 end for _ in pairs(t) do
count = count + 1
end
return count return count
end end
@@ -498,7 +512,7 @@ end
---@param s String to trim ---@param s String to trim
---@returns String with whitespace removed from its beginning and end ---@returns String with whitespace removed from its beginning and end
function vim.trim(s) function vim.trim(s)
vim.validate{s={s,'s'}} vim.validate({ s = { s, 's' } })
return s:match('^%s*(.*%S)') or '' return s:match('^%s*(.*%S)') or ''
end end
@@ -508,7 +522,7 @@ end
---@param s String to escape ---@param s String to escape
---@returns %-escaped pattern string ---@returns %-escaped pattern string
function vim.pesc(s) function vim.pesc(s)
vim.validate{s={s,'s'}} vim.validate({ s = { s, 's' } })
return s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1') return s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')
end end
@@ -518,7 +532,7 @@ end
---@param prefix (string) a prefix ---@param prefix (string) a prefix
---@return (boolean) true if `prefix` is a prefix of s ---@return (boolean) true if `prefix` is a prefix of s
function vim.startswith(s, prefix) 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 return s:sub(1, #prefix) == prefix
end end
@@ -528,7 +542,7 @@ end
---@param suffix (string) a suffix ---@param suffix (string) a suffix
---@return (boolean) true if `suffix` is a suffix of s ---@return (boolean) true if `suffix` is a suffix of s
function vim.endswith(s, suffix) 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 return #suffix == 0 or s:sub(-#suffix) == suffix
end end
@@ -586,12 +600,18 @@ function vim.validate(opt) end -- luacheck: no unused
do do
local type_names = { local type_names = {
['table'] = 'table', t = 'table', ['table'] = 'table',
['string'] = 'string', s = 'string', t = 'table',
['number'] = 'number', n = 'number', ['string'] = 'string',
['boolean'] = 'boolean', b = 'boolean', s = 'string',
['function'] = 'function', f = 'function', ['number'] = 'number',
['callable'] = 'callable', c = 'callable', n = 'number',
['boolean'] = 'boolean',
b = 'boolean',
['function'] = 'function',
f = 'function',
['callable'] = 'callable',
c = 'callable',
['nil'] = 'nil', ['nil'] = 'nil',
['thread'] = 'thread', ['thread'] = 'thread',
['userdata'] = 'userdata', ['userdata'] = 'userdata',
@@ -624,9 +644,9 @@ do
-- Check user-provided validation function. -- Check user-provided validation function.
local valid, optional_message = types(val) local valid, optional_message = types(val)
if not valid then 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 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 end
return false, error_message return false, error_message
@@ -646,10 +666,10 @@ do
end end
end end
if not success then 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 end
else else
return false, string.format("invalid type name: %s", tostring(types)) return false, string.format('invalid type name: %s', tostring(types))
end end
end end
@@ -668,9 +688,13 @@ end
---@param f Any object ---@param f Any object
---@return true if `f` is callable, else false ---@return true if `f` is callable, else false
function vim.is_callable(f) 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) local m = getmetatable(f)
if m == nil then return false end if m == nil then
return false
end
return type(m.__call) == 'function' return type(m.__call) == 'function'
end end

View File

@@ -1,31 +1,31 @@
local a = vim.api local a = vim.api
local query = require'vim.treesitter.query' local query = require('vim.treesitter.query')
local language = require'vim.treesitter.language' local language = require('vim.treesitter.language')
local LanguageTree = require'vim.treesitter.languagetree' local LanguageTree = require('vim.treesitter.languagetree')
-- TODO(bfredl): currently we retain parsers for the lifetime of the buffer. -- 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 -- Consider use weak references to release parser if all plugins are done with
-- it. -- it.
local parsers = {} 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.language_version = vim._ts_get_language_version()
M.minimum_language_version = vim._ts_get_minimum_language_version() M.minimum_language_version = vim._ts_get_minimum_language_version()
setmetatable(M, { setmetatable(M, {
__index = function(t, k) __index = function(t, k)
if k == "highlighter" then if k == 'highlighter' then
t[k] = require'vim.treesitter.highlighter' t[k] = require('vim.treesitter.highlighter')
return t[k] return t[k]
elseif k == "language" then elseif k == 'language' then
t[k] = require"vim.treesitter.language" t[k] = require('vim.treesitter.language')
return t[k] return t[k]
elseif k == "query" then elseif k == 'query' then
t[k] = require"vim.treesitter.query" t[k] = require('vim.treesitter.query')
return t[k] return t[k]
end end
end end,
}) })
--- Creates a new parser. --- Creates a new parser.
@@ -63,7 +63,11 @@ function M._create_parser(bufnr, lang, opts)
self:_on_reload(...) self:_on_reload(...)
end 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() self:parse()
@@ -87,7 +91,7 @@ function M.get_parser(bufnr, lang, opts)
bufnr = a.nvim_get_current_buf() bufnr = a.nvim_get_current_buf()
end end
if lang == nil then if lang == nil then
lang = a.nvim_buf_get_option(bufnr, "filetype") lang = a.nvim_buf_get_option(bufnr, 'filetype')
end end
if parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then if parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then
@@ -105,10 +109,10 @@ end
---@param lang The language of this string ---@param lang The language of this string
---@param opts Options to pass to the created language tree ---@param opts Options to pass to the created language tree
function M.get_string_parser(str, lang, opts) function M.get_string_parser(str, lang, opts)
vim.validate { vim.validate({
str = { str, 'string' }, str = { str, 'string' },
lang = { lang, 'string' } lang = { lang, 'string' },
} })
language.require_language(lang) language.require_language(lang)
return LanguageTree.new(str, lang, opts) return LanguageTree.new(str, lang, opts)

View File

@@ -15,24 +15,22 @@ function M.check()
local report_error = vim.fn['health#report_error'] local report_error = vim.fn['health#report_error']
local parsers = M.list_parsers() 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 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) local is_loadable, ret = pcall(ts.language.require_language, parsername)
if not is_loadable then 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 elseif ret then
local lang = ts.language.inspect_language(parsername) local lang = ts.language.inspect_language(parsername)
report_ok(string.format("Loaded parser for %s: ABI version %d", report_ok(string.format('Loaded parser for %s: ABI version %d', parsername, lang._abi_version))
parsername, lang._abi_version))
else 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 end
end end
return M return M

View File

@@ -1,5 +1,5 @@
local a = vim.api local a = vim.api
local query = require"vim.treesitter.query" local query = require('vim.treesitter.query')
-- support reload for quick experimentation -- support reload for quick experimentation
local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {} local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
@@ -10,13 +10,13 @@ TSHighlighter.active = TSHighlighter.active or {}
local TSHighlighterQuery = {} local TSHighlighterQuery = {}
TSHighlighterQuery.__index = TSHighlighterQuery TSHighlighterQuery.__index = TSHighlighterQuery
local ns = a.nvim_create_namespace("treesitter/highlighter") local ns = a.nvim_create_namespace('treesitter/highlighter')
local _default_highlights = {} local _default_highlights = {}
local _link_default_highlight_once = function(from, to) local _link_default_highlight_once = function(from, to)
if not _default_highlights[from] then if not _default_highlights[from] then
_default_highlights[from] = true _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 end
return from return from
@@ -31,65 +31,65 @@ local subcapture_fallback = {
shortened = shortened:match('(.*)%.') shortened = shortened:match('(.*)%.')
rtn = shortened and rawget(self, shortened) rtn = shortened and rawget(self, shortened)
end end
rawset(self, capture, rtn or "__notfound") rawset(self, capture, rtn or '__notfound')
return rtn return rtn
end end,
} }
TSHighlighter.hl_map = setmetatable({ TSHighlighter.hl_map = setmetatable({
["error"] = "Error", ['error'] = 'Error',
["text.underline"] = "Underlined", ['text.underline'] = 'Underlined',
["todo"] = "Todo", ['todo'] = 'Todo',
["debug"] = "Debug", ['debug'] = 'Debug',
-- Miscs -- Miscs
["comment"] = "Comment", ['comment'] = 'Comment',
["punctuation.delimiter"] = "Delimiter", ['punctuation.delimiter'] = 'Delimiter',
["punctuation.bracket"] = "Delimiter", ['punctuation.bracket'] = 'Delimiter',
["punctuation.special"] = "Delimiter", ['punctuation.special'] = 'Delimiter',
-- Constants -- Constants
["constant"] = "Constant", ['constant'] = 'Constant',
["constant.builtin"] = "Special", ['constant.builtin'] = 'Special',
["constant.macro"] = "Define", ['constant.macro'] = 'Define',
["define"] = "Define", ['define'] = 'Define',
["macro"] = "Macro", ['macro'] = 'Macro',
["string"] = "String", ['string'] = 'String',
["string.regex"] = "String", ['string.regex'] = 'String',
["string.escape"] = "SpecialChar", ['string.escape'] = 'SpecialChar',
["character"] = "Character", ['character'] = 'Character',
["character.special"] = "SpecialChar", ['character.special'] = 'SpecialChar',
["number"] = "Number", ['number'] = 'Number',
["boolean"] = "Boolean", ['boolean'] = 'Boolean',
["float"] = "Float", ['float'] = 'Float',
-- Functions -- Functions
["function"] = "Function", ['function'] = 'Function',
["function.special"] = "Function", ['function.special'] = 'Function',
["function.builtin"] = "Special", ['function.builtin'] = 'Special',
["function.macro"] = "Macro", ['function.macro'] = 'Macro',
["parameter"] = "Identifier", ['parameter'] = 'Identifier',
["method"] = "Function", ['method'] = 'Function',
["field"] = "Identifier", ['field'] = 'Identifier',
["property"] = "Identifier", ['property'] = 'Identifier',
["constructor"] = "Special", ['constructor'] = 'Special',
-- Keywords -- Keywords
["conditional"] = "Conditional", ['conditional'] = 'Conditional',
["repeat"] = "Repeat", ['repeat'] = 'Repeat',
["label"] = "Label", ['label'] = 'Label',
["operator"] = "Operator", ['operator'] = 'Operator',
["keyword"] = "Keyword", ['keyword'] = 'Keyword',
["exception"] = "Exception", ['exception'] = 'Exception',
["type"] = "Type", ['type'] = 'Type',
["type.builtin"] = "Type", ['type.builtin'] = 'Type',
["type.qualifier"] = "Type", ['type.qualifier'] = 'Type',
["type.definition"] = "Typedef", ['type.definition'] = 'Typedef',
["storageclass"] = "StorageClass", ['storageclass'] = 'StorageClass',
["structure"] = "Structure", ['structure'] = 'Structure',
["include"] = "Include", ['include'] = 'Include',
["preproc"] = "PreProc", ['preproc'] = 'PreProc',
}, subcapture_fallback) }, subcapture_fallback)
---@private ---@private
@@ -113,13 +113,13 @@ function TSHighlighterQuery.new(lang, query_string)
rawset(table, capture, id) rawset(table, capture, id)
return id return id
end end,
}) })
if query_string then if query_string then
self._query = query.parse_query(lang, query_string) self._query = query.parse_query(lang, query_string)
else else
self._query = query.get_query(lang, "highlights") self._query = query.get_query(lang, 'highlights')
end end
return self return self
@@ -152,17 +152,23 @@ end
function TSHighlighter.new(tree, opts) function TSHighlighter.new(tree, opts)
local self = setmetatable({}, TSHighlighter) local self = setmetatable({}, TSHighlighter)
if type(tree:source()) ~= "number" then if type(tree:source()) ~= 'number' then
error("TSHighlighter can not be used with a string parser source.") error('TSHighlighter can not be used with a string parser source.')
end end
opts = opts or {} opts = opts or {}
self.tree = tree self.tree = tree
tree:register_cbs { tree:register_cbs({
on_changedtree = function(...) self:on_changedtree(...) end; on_changedtree = function(...)
on_bytes = function(...) self:on_bytes(...) end; self:on_changedtree(...)
on_detach = function(...) self:on_detach(...) end; end,
} on_bytes = function(...)
self:on_bytes(...)
end,
on_detach = function(...)
self:on_detach(...)
end,
})
self.bufnr = tree:source() self.bufnr = tree:source()
self.edit_count = 0 self.edit_count = 0
@@ -181,7 +187,7 @@ function TSHighlighter.new(tree, opts)
end end
end end
a.nvim_buf_set_option(self.bufnr, "syntax", "") a.nvim_buf_set_option(self.bufnr, 'syntax', '')
TSHighlighter.active[self.bufnr] = self 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 FileType autocmds. Later on we should integrate with the
-- `:syntax` and `set syntax=...` machinery properly. -- `:syntax` and `set syntax=...` machinery properly.
if vim.g.syntax_on ~= 1 then if vim.g.syntax_on ~= 1 then
vim.api.nvim_command("runtime! syntax/synload.vim") vim.api.nvim_command('runtime! syntax/synload.vim')
end end
self.tree:parse() self.tree:parse()
@@ -210,7 +216,7 @@ function TSHighlighter:get_highlight_state(tstree)
if not self._highlight_states[tstree] then if not self._highlight_states[tstree] then
self._highlight_states[tstree] = { self._highlight_states[tstree] = {
next_row = 0, next_row = 0,
iter = nil iter = nil,
} }
end end
@@ -253,19 +259,25 @@ end
---@private ---@private
local function on_line_impl(self, buf, line) local function on_line_impl(self, buf, line)
self.tree:for_each_tree(function(tstree, tree) 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_node = tstree:root()
local root_start_row, _, root_end_row, _ = root_node:range() local root_start_row, _, root_end_row, _ = root_node:range()
-- Only worry about trees within the line 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 state = self:get_highlight_state(tstree)
local highlighter_query = self:get_query(tree:lang()) local highlighter_query = self:get_query(tree:lang())
-- Some injected languages may not have highlight queries. -- 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 if state.iter == nil then
state.iter = highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1) 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 while line >= state.next_row do
local capture, node, metadata = state.iter() 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 start_row, start_col, end_row, end_col = node:range()
local hl = highlighter_query.hl_cache[capture] local hl = highlighter_query.hl_cache[capture]
if hl and end_row >= line then if hl and end_row >= line then
a.nvim_buf_set_extmark(buf, ns, start_row, start_col, a.nvim_buf_set_extmark(buf, ns, start_row, start_col, {
{ end_line = end_row, end_col = end_col, end_line = end_row,
end_col = end_col,
hl_group = hl, hl_group = hl,
ephemeral = true, ephemeral = true,
priority = tonumber(metadata.priority) or 100, -- Low but leaves room below priority = tonumber(metadata.priority) or 100, -- Low but leaves room below
@@ -298,7 +313,9 @@ end
---@private ---@private
function TSHighlighter._on_line(_, _win, buf, line, _) function TSHighlighter._on_line(_, _win, buf, line, _)
local self = TSHighlighter.active[buf] local self = TSHighlighter.active[buf]
if not self then return end if not self then
return
end
on_line_impl(self, buf, line) on_line_impl(self, buf, line)
end end
@@ -324,9 +341,9 @@ function TSHighlighter._on_win(_, _win, buf, _topline)
end end
a.nvim_set_decoration_provider(ns, { a.nvim_set_decoration_provider(ns, {
on_buf = TSHighlighter._on_buf; on_buf = TSHighlighter._on_buf,
on_win = TSHighlighter._on_win; on_win = TSHighlighter._on_win,
on_line = TSHighlighter._on_line; on_line = TSHighlighter._on_line,
}) })
return TSHighlighter return TSHighlighter

View File

@@ -28,7 +28,9 @@ function M.require_language(lang, path, silent)
end end
if silent then if silent then
return pcall(function() vim._ts_add_language(path, lang) end) return pcall(function()
vim._ts_add_language(path, lang)
end)
else else
vim._ts_add_language(path, lang) vim._ts_add_language(path, lang)
end end

View File

@@ -1,6 +1,6 @@
local a = vim.api local a = vim.api
local query = require'vim.treesitter.query' local query = require('vim.treesitter.query')
local language = require'vim.treesitter.language' local language = require('vim.treesitter.language')
local LanguageTree = {} local LanguageTree = {}
LanguageTree.__index = LanguageTree LanguageTree.__index = LanguageTree
@@ -32,9 +32,10 @@ function LanguageTree.new(source, lang, opts)
_regions = {}, _regions = {},
_trees = {}, _trees = {},
_opts = opts, _opts = opts,
_injection_query = injections[lang] _injection_query = injections[lang] and query.parse_query(lang, injections[lang]) or query.get_query(
and query.parse_query(lang, injections[lang]) lang,
or query.get_query(lang, "injections"), 'injections'
),
_valid = false, _valid = false,
_parser = vim._create_ts_parser(lang), _parser = vim._create_ts_parser(lang),
_callbacks = { _callbacks = {
@@ -42,11 +43,10 @@ function LanguageTree.new(source, lang, opts)
bytes = {}, bytes = {},
detach = {}, detach = {},
child_added = {}, child_added = {},
child_removed = {} child_removed = {},
}, },
}, LanguageTree) }, LanguageTree)
return self return self
end end
@@ -264,11 +264,11 @@ end
---@param regions A list of regions this tree should manage and parse. ---@param regions A list of regions this tree should manage and parse.
function LanguageTree:set_included_regions(regions) function LanguageTree:set_included_regions(regions)
-- TODO(vigoux): I don't think string parsers are useful for now -- 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) -- Transform the tables from 4 element long to 6 element long (with byte offset)
for _, region in ipairs(regions) do for _, region in ipairs(regions) do
for i, range in ipairs(region) 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) local start_row, start_col, end_row, end_col = unpack(range)
-- Easy case, this is a buffer parser -- Easy case, this is a buffer parser
-- TODO(vigoux): proper byte computation here, and account for EOL ? -- TODO(vigoux): proper byte computation here, and account for EOL ?
@@ -303,7 +303,9 @@ end
--- instead of using the entire nodes range. --- instead of using the entire nodes range.
---@private ---@private
function LanguageTree:_get_injections() function LanguageTree:_get_injections()
if not self._injection_query then return {} end if not self._injection_query then
return {}
end
local injections = {} local injections = {}
@@ -311,7 +313,9 @@ function LanguageTree:_get_injections()
local root_node = tree:root() local root_node = tree:root()
local start_line, _, end_line, _ = root_node:range() 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 lang = nil
local ranges = {} local ranges = {}
local combined = metadata.combined local combined = metadata.combined
@@ -322,7 +326,7 @@ function LanguageTree:_get_injections()
local content = metadata.content local content = metadata.content
-- Allow for captured nodes to be used -- Allow for captured nodes to be used
if type(content) == "number" then if type(content) == 'number' then
content = { match[content] } content = { match[content] }
end end
@@ -342,15 +346,15 @@ function LanguageTree:_get_injections()
local name = self._injection_query.captures[id] local name = self._injection_query.captures[id]
-- Lang should override any other language tag -- 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) lang = query.get_node_text(node, self._source)
elseif name == "combined" then elseif name == 'combined' then
combined = true combined = true
elseif name == "content" and #ranges == 0 then elseif name == 'content' and #ranges == 0 then
table.insert(ranges, node) table.insert(ranges, node)
-- Ignore any tags that start with "_" -- Ignore any tags that start with "_"
-- Allows for other tags to be used in matches -- 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 if not lang then
lang = name lang = name
end end
@@ -414,10 +418,19 @@ function LanguageTree:_do_callback(cb_name, ...)
end end
---@private ---@private
function LanguageTree:_on_bytes(bufnr, changed_tick, function LanguageTree:_on_bytes(
start_row, start_col, start_byte, bufnr,
old_row, old_col, old_byte, changed_tick,
new_row, new_col, new_byte) start_row,
start_col,
start_byte,
old_row,
old_col,
old_byte,
new_row,
new_col,
new_byte
)
self:invalidate() self:invalidate()
local old_end_col = old_col + ((old_row == 0) and start_col or 0) 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. -- Edit all trees recursively, together BEFORE emitting a bytes callback.
-- In most cases this callback should only be called from the root tree. -- In most cases this callback should only be called from the root tree.
self:for_each_tree(function(tree) self:for_each_tree(function(tree)
tree:edit(start_byte,start_byte+old_byte,start_byte+new_byte, tree:edit(
start_row, start_col, start_byte,
start_row+old_row, old_end_col, start_byte + old_byte,
start_row+new_row, new_end_col) start_byte + new_byte,
start_row,
start_col,
start_row + old_row,
old_end_col,
start_row + new_row,
new_end_col
)
end) end)
self:_do_callback('bytes', bufnr, changed_tick, self:_do_callback(
start_row, start_col, start_byte, 'bytes',
old_row, old_col, old_byte, bufnr,
new_row, new_col, new_byte) changed_tick,
start_row,
start_col,
start_byte,
old_row,
old_col,
old_byte,
new_row,
new_col,
new_byte
)
end end
---@private ---@private
@@ -443,7 +473,6 @@ function LanguageTree:_on_reload()
self:invalidate(true) self:invalidate(true)
end end
---@private ---@private
function LanguageTree:_on_detach(...) function LanguageTree:_on_detach(...)
self:invalidate(true) self:invalidate(true)
@@ -459,7 +488,9 @@ end
--- - `on_child_added` : emitted when a child is added to the tree. --- - `on_child_added` : emitted when a child is added to the tree.
--- - `on_child_removed` : emitted when a child is removed from the tree. --- - `on_child_removed` : emitted when a child is removed from the tree.
function LanguageTree:register_cbs(cbs) function LanguageTree:register_cbs(cbs)
if not cbs then return end if not cbs then
return
end
if cbs.on_changedtree then if cbs.on_changedtree then
table.insert(self._callbacks.changedtree, cbs.on_changedtree) table.insert(self._callbacks.changedtree, cbs.on_changedtree)

View File

@@ -1,5 +1,5 @@
local a = vim.api local a = vim.api
local language = require'vim.treesitter.language' local language = require('vim.treesitter.language')
-- query: pattern matching on trees -- query: pattern matching on trees
-- predicate matching is implemented in lua -- 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 query_path = string.format('queries/%s/%s.scm', lang, query_name)
local lang_files = dedupe_files(a.nvim_get_runtime_file(query_path, true)) 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 = {} local base_langs = {}
@@ -52,7 +54,7 @@ function M.get_query_files(lang, query_name, is_included)
-- ;+ inherits: ({language},)*{language} -- ;+ inherits: ({language},)*{language}
-- --
-- {language} ::= {lang} | ({lang}) -- {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 for _, file in ipairs(lang_files) do
local modeline = safe_read(file, '*l') local modeline = safe_read(file, '*l')
@@ -62,7 +64,7 @@ function M.get_query_files(lang, query_name, is_included)
if langlist then if langlist then
for _, incllang in ipairs(vim.split(langlist, ',', true)) do for _, incllang in ipairs(vim.split(langlist, ',', true)) do
local is_optional = incllang:match("%(.*%)") local is_optional = incllang:match('%(.*%)')
if is_optional then if is_optional then
if not is_included then if not is_included then
@@ -142,7 +144,7 @@ local query_cache = setmetatable({}, {
__index = function(tbl, key) __index = function(tbl, key)
rawset(tbl, key, {}) rawset(tbl, key, {})
return rawget(tbl, key) return rawget(tbl, key)
end end,
}) })
--- Parse {query} as a string. (If the query is in a file, the caller --- 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 start_row, start_col, start_byte = node:start()
local end_row, end_col, end_byte = node:end_() local end_row, end_col, end_byte = node:end_()
if type(source) == "number" then if type(source) == 'number' then
local lines local lines
local eof_row = a.nvim_buf_line_count(source) local eof_row = a.nvim_buf_line_count(source)
if start_row >= eof_row then if start_row >= eof_row then
@@ -208,8 +210,8 @@ function M.get_node_text(node, source)
end end
end end
return table.concat(lines, "\n") return table.concat(lines, '\n')
elseif type(source) == "string" then elseif type(source) == 'string' then
return source:sub(start_byte + 1, end_byte) return source:sub(start_byte + 1, end_byte)
end end
end end
@@ -217,12 +219,12 @@ end
-- Predicate handler receive the following arguments -- Predicate handler receive the following arguments
-- (match, pattern, bufnr, predicate) -- (match, pattern, bufnr, predicate)
local predicate_handlers = { local predicate_handlers = {
["eq?"] = function(match, _, source, predicate) ['eq?'] = function(match, _, source, predicate)
local node = match[predicate[2]] local node = match[predicate[2]]
local node_text = M.get_node_text(node, source) local node_text = M.get_node_text(node, source)
local str local str
if type(predicate[3]) == "string" then if type(predicate[3]) == 'string' then
-- (#eq? @aa "foo") -- (#eq? @aa "foo")
str = predicate[3] str = predicate[3]
else else
@@ -237,13 +239,13 @@ local predicate_handlers = {
return true return true
end, end,
["lua-match?"] = function(match, _, source, predicate) ['lua-match?'] = function(match, _, source, predicate)
local node = match[predicate[2]] local node = match[predicate[2]]
local regex = predicate[3] local regex = predicate[3]
return string.find(M.get_node_text(node, source), regex) return string.find(M.get_node_text(node, source), regex)
end, end,
["match?"] = (function() ['match?'] = (function()
local magic_prefixes = { ['\\v'] = true, ['\\m'] = true, ['\\M'] = true, ['\\V'] = true } local magic_prefixes = { ['\\v'] = true, ['\\m'] = true, ['\\M'] = true, ['\\V'] = true }
---@private ---@private
local function check_magic(str) local function check_magic(str)
@@ -258,7 +260,7 @@ local predicate_handlers = {
local res = vim.regex(check_magic(pattern)) local res = vim.regex(check_magic(pattern))
rawset(t, pattern, res) rawset(t, pattern, res)
return res return res
end end,
}) })
return function(match, _, source, pred) return function(match, _, source, pred)
@@ -268,7 +270,7 @@ local predicate_handlers = {
end end
end)(), end)(),
["contains?"] = function(match, _, source, predicate) ['contains?'] = function(match, _, source, predicate)
local node = match[predicate[2]] local node = match[predicate[2]]
local node_text = M.get_node_text(node, source) local node_text = M.get_node_text(node, source)
@@ -281,19 +283,19 @@ local predicate_handlers = {
return false return false
end, end,
["any-of?"] = function(match, _, source, predicate) ['any-of?'] = function(match, _, source, predicate)
local node = match[predicate[2]] local node = match[predicate[2]]
local node_text = M.get_node_text(node, source) local node_text = M.get_node_text(node, source)
-- Since 'predicate' will not be used by callers of this function, use it -- 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. -- 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 if not string_set then
string_set = {} string_set = {}
for i = 3, #predicate do for i = 3, #predicate do
string_set[predicate[i]] = true string_set[predicate[i]] = true
end end
predicate["string_set"] = string_set predicate['string_set'] = string_set
end end
return string_set[node_text] return string_set[node_text]
@@ -301,15 +303,14 @@ local predicate_handlers = {
} }
-- As we provide lua-match? also expose vim-match? -- 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 store metadata or perform side effects against a match.
-- Directives should always end with a `!`. -- Directives should always end with a `!`.
-- Directive handler receive the following arguments -- Directive handler receive the following arguments
-- (match, pattern, bufnr, predicate, metadata) -- (match, pattern, bufnr, predicate, metadata)
local directive_handlers = { local directive_handlers = {
["set!"] = function(_, _, _, pred, metadata) ['set!'] = function(_, _, _, pred, metadata)
if #pred == 4 then if #pred == 4 then
-- (#set! @capture "key" "value") -- (#set! @capture "key" "value")
local capture = pred[2] local capture = pred[2]
@@ -324,7 +325,7 @@ local directive_handlers = {
end, end,
-- Shifts the range of a node. -- Shifts the range of a node.
-- Example: (#offset! @_node 0 1 0 -1) -- Example: (#offset! @_node 0 1 0 -1)
["offset!"] = function(match, _, _, pred, metadata) ['offset!'] = function(match, _, _, pred, metadata)
local offset_node = match[pred[2]] local offset_node = match[pred[2]]
local range = { offset_node:range() } local range = { offset_node:range() }
local start_row_offset = pred[3] or 0 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 if range[1] < range[3] or (range[1] == range[3] and range[2] <= range[4]) then
metadata.content = { range } metadata.content = { range }
end end
end end,
} }
--- Adds a new predicate to be used in queries --- Adds a new predicate to be used in queries
@@ -351,7 +352,7 @@ local directive_handlers = {
--- signature will be (match, pattern, bufnr, predicate) --- signature will be (match, pattern, bufnr, predicate)
function M.add_predicate(name, handler, force) function M.add_predicate(name, handler, force)
if predicate_handlers[name] and not force then if predicate_handlers[name] and not force then
error(string.format("Overriding %s", name)) error(string.format('Overriding %s', name))
end end
predicate_handlers[name] = handler predicate_handlers[name] = handler
@@ -364,7 +365,7 @@ end
--- signature will be (match, pattern, bufnr, predicate) --- signature will be (match, pattern, bufnr, predicate)
function M.add_directive(name, handler, force) function M.add_directive(name, handler, force)
if directive_handlers[name] and not force then if directive_handlers[name] and not force then
error(string.format("Overriding %s", name)) error(string.format('Overriding %s', name))
end end
directive_handlers[name] = handler directive_handlers[name] = handler
@@ -387,7 +388,7 @@ end
---@private ---@private
local function is_directive(name) local function is_directive(name)
return string.sub(name, -1) == "!" return string.sub(name, -1) == '!'
end end
---@private ---@private
@@ -404,7 +405,7 @@ function Query:match_preds(match, pattern, source)
-- Skip over directives... they will get processed after all the predicates. -- Skip over directives... they will get processed after all the predicates.
if not is_directive(pred[1]) then 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) pred_name = string.sub(pred[1], 5)
is_not = true is_not = true
else else
@@ -415,7 +416,7 @@ function Query:match_preds(match, pattern, source)
local handler = predicate_handlers[pred_name] local handler = predicate_handlers[pred_name]
if not handler then if not handler then
error(string.format("No handler for %s", pred[1])) error(string.format('No handler for %s', pred[1]))
return false return false
end end
@@ -438,7 +439,7 @@ function Query:apply_directives(match, pattern, source, metadata)
local handler = directive_handlers[pred[1]] local handler = directive_handlers[pred[1]]
if not handler then if not handler then
error(string.format("No handler for %s", pred[1])) error(string.format('No handler for %s', pred[1]))
return return
end end
@@ -447,7 +448,6 @@ function Query:apply_directives(match, pattern, source, metadata)
end end
end end
--- Returns the start and stop value if set else the node's range. --- 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 -- When the node's range is used, the stop is incremented by 1
-- to make the search inclusive. -- to make the search inclusive.
@@ -492,7 +492,7 @@ end
---@returns The matching capture id ---@returns The matching capture id
---@returns The captured node ---@returns The captured node
function Query:iter_captures(node, source, start, stop) 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() source = vim.api.nvim_get_current_buf()
end end
@@ -549,7 +549,7 @@ end
---@returns The matching pattern id ---@returns The matching pattern id
---@returns The matching match ---@returns The matching match
function Query:iter_matches(node, source, start, stop) 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() source = vim.api.nvim_get_current_buf()
end end

View File

@@ -37,10 +37,10 @@ local M = {}
--- </pre> --- </pre>
function M.select(items, opts, on_choice) function M.select(items, opts, on_choice)
vim.validate { vim.validate({
items = { items, 'table', false }, items = { items, 'table', false },
on_choice = { on_choice, 'function', false }, on_choice = { on_choice, 'function', false },
} })
opts = opts or {} opts = opts or {}
local choices = { opts.prompt or 'Select one of:' } local choices = { opts.prompt or 'Select one of:' }
local format_item = opts.format_item or tostring local format_item = opts.format_item or tostring
@@ -83,9 +83,9 @@ end
--- end) --- end)
--- </pre> --- </pre>
function M.input(opts, on_confirm) function M.input(opts, on_confirm)
vim.validate { vim.validate({
on_confirm = { on_confirm, 'function', false }, on_confirm = { on_confirm, 'function', false },
} })
opts = opts or {} opts = opts or {}
local input = vim.fn.input(opts) local input = vim.fn.input(opts)

View File

@@ -3,7 +3,6 @@
-- https://tools.ietf.org/html/rfc2732 -- https://tools.ietf.org/html/rfc2732
-- https://tools.ietf.org/html/rfc2396 -- https://tools.ietf.org/html/rfc2396
local uri_decode local uri_decode
do do
local schar = string.char local schar = string.char
@@ -14,7 +13,7 @@ do
return schar(tonumber(hex, 16)) return schar(tonumber(hex, 16))
end end
uri_decode = function(str) 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
end end
@@ -23,33 +22,36 @@ do
local PATTERNS = { local PATTERNS = {
--- RFC 2396 --- RFC 2396
-- https://tools.ietf.org/html/rfc2396#section-2.2 -- https://tools.ietf.org/html/rfc2396#section-2.2
rfc2396 = "^A-Za-z0-9%-_.!~*'()"; rfc2396 = "^A-Za-z0-9%-_.!~*'()",
--- RFC 2732 --- RFC 2732
-- https://tools.ietf.org/html/rfc2732 -- https://tools.ietf.org/html/rfc2732
rfc2732 = "^A-Za-z0-9%-_.!~*'()[]"; rfc2732 = "^A-Za-z0-9%-_.!~*'()[]",
--- RFC 3986 --- RFC 3986
-- https://tools.ietf.org/html/rfc3986#section-2.2 -- https://tools.ietf.org/html/rfc3986#section-2.2
rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/"; rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/",
} }
local sbyte, tohex = string.byte local sbyte, tohex = string.byte
if jit then if jit then
tohex = require'bit'.tohex tohex = require('bit').tohex
else else
tohex = function(b) return string.format("%02x", b) end tohex = function(b)
return string.format('%02x', b)
end
end end
---@private ---@private
local function percent_encode_char(char) local function percent_encode_char(char)
return "%"..tohex(sbyte(char), 2) return '%' .. tohex(sbyte(char), 2)
end end
uri_encode = function(text, rfc) uri_encode = function(text, rfc)
if not text then return end if not text then
return
end
local pattern = PATTERNS[rfc] or PATTERNS.rfc3986 local pattern = PATTERNS[rfc] or PATTERNS.rfc3986
return text:gsub("(["..pattern.."])", percent_encode_char) return text:gsub('([' .. pattern .. '])', percent_encode_char)
end end
end end
---@private ---@private
local function is_windows_file_uri(uri) local function is_windows_file_uri(uri)
return uri:match('^file:/+[a-zA-Z]:') ~= nil return uri:match('^file:/+[a-zA-Z]:') ~= nil
@@ -59,16 +61,16 @@ end
---@param path string Path to file ---@param path string Path to file
---@return string URI ---@return string URI
local function uri_from_fname(path) 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 local is_windows = volume_path ~= nil
if is_windows then if is_windows then
path = volume_path..uri_encode(fname:gsub("\\", "/")) path = volume_path .. uri_encode(fname:gsub('\\', '/'))
else else
path = uri_encode(path) path = uri_encode(path)
end end
local uri_parts = {"file://"} local uri_parts = { 'file://' }
if is_windows then if is_windows then
table.insert(uri_parts, "/") table.insert(uri_parts, '/')
end end
table.insert(uri_parts, path) table.insert(uri_parts, path)
return table.concat(uri_parts) 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 ---@return string URI
local function uri_from_bufnr(bufnr) local function uri_from_bufnr(bufnr)
local fname = vim.api.nvim_buf_get_name(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 is_windows = volume_path ~= nil
local scheme local scheme
if is_windows then if is_windows then
fname = fname:gsub("\\", "/") fname = fname:gsub('\\', '/')
scheme = fname:match(WINDOWS_URI_SCHEME_PATTERN) scheme = fname:match(WINDOWS_URI_SCHEME_PATTERN)
else else
scheme = fname:match(URI_SCHEME_PATTERN) scheme = fname:match(URI_SCHEME_PATTERN)

View File

@@ -3,4 +3,4 @@
-- Last Change: 2022 Apr 13 -- Last Change: 2022 Apr 13
-- it's a lisp! -- it's a lisp!
vim.cmd [[ runtime! syntax/lisp.vim ]] vim.cmd([[ runtime! syntax/lisp.vim ]])