aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua')
-rw-r--r--runtime/lua/health.lua29
-rw-r--r--runtime/lua/man.lua28
-rw-r--r--runtime/lua/vim/F.lua10
-rw-r--r--runtime/lua/vim/_editor.lua854
-rw-r--r--runtime/lua/vim/_init_packages.lua83
-rw-r--r--runtime/lua/vim/_meta.lua297
-rw-r--r--runtime/lua/vim/compat.lua4
-rw-r--r--runtime/lua/vim/diagnostic.lua571
-rw-r--r--runtime/lua/vim/filetype.lua3671
-rw-r--r--runtime/lua/vim/filetype/detect.lua1630
-rw-r--r--runtime/lua/vim/fs.lua209
-rw-r--r--runtime/lua/vim/health.lua49
-rw-r--r--runtime/lua/vim/highlight.lua134
-rw-r--r--runtime/lua/vim/inspect.lua383
-rw-r--r--runtime/lua/vim/keymap.lua57
-rw-r--r--runtime/lua/vim/lsp.lua985
-rw-r--r--runtime/lua/vim/lsp/_snippet.lua247
-rw-r--r--runtime/lua/vim/lsp/buf.lua545
-rw-r--r--runtime/lua/vim/lsp/codelens.lua66
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua503
-rw-r--r--runtime/lua/vim/lsp/handlers.lua263
-rw-r--r--runtime/lua/vim/lsp/health.lua17
-rw-r--r--runtime/lua/vim/lsp/log.lua118
-rw-r--r--runtime/lua/vim/lsp/protocol.lua584
-rw-r--r--runtime/lua/vim/lsp/rpc.lua272
-rw-r--r--runtime/lua/vim/lsp/sync.lua107
-rw-r--r--runtime/lua/vim/lsp/tagfunc.lua7
-rw-r--r--runtime/lua/vim/lsp/util.lua929
-rw-r--r--runtime/lua/vim/shared.lua323
-rw-r--r--runtime/lua/vim/treesitter.lua51
-rw-r--r--runtime/lua/vim/treesitter/health.lua14
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua195
-rw-r--r--runtime/lua/vim/treesitter/language.lua8
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua177
-rw-r--r--runtime/lua/vim/treesitter/query.lua195
-rw-r--r--runtime/lua/vim/ui.lua41
-rw-r--r--runtime/lua/vim/uri.lua36
37 files changed, 9240 insertions, 4452 deletions
diff --git a/runtime/lua/health.lua b/runtime/lua/health.lua
index 142a353bf2..40e2b3c3e7 100644
--- a/runtime/lua/health.lua
+++ b/runtime/lua/health.lua
@@ -1,23 +1,6 @@
-local M = {}
-
-function M.report_start(msg)
- vim.fn['health#report_start'](msg)
-end
-
-function M.report_info(msg)
- vim.fn['health#report_info'](msg)
-end
-
-function M.report_ok(msg)
- vim.fn['health#report_ok'](msg)
-end
-
-function M.report_warn(msg, ...)
- vim.fn['health#report_warn'](msg, ...)
-end
-
-function M.report_error(msg, ...)
- vim.fn['health#report_error'](msg, ...)
-end
-
-return M
+return setmetatable({}, {
+ __index = function(_, k)
+ vim.deprecate("require('health')", 'vim.health', '0.9', false)
+ return vim.health[k]
+ end,
+})
diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua
index ba6b9d09c9..5da3d2a92f 100644
--- a/runtime/lua/man.lua
+++ b/runtime/lua/man.lua
@@ -8,7 +8,7 @@ local function highlight_line(line, linenr)
local overstrike, escape = false, false
local hls = {} -- Store highlight groups as { attr, start, final }
local NONE, BOLD, UNDERLINE, ITALIC = 0, 1, 2, 3
- local hl_groups = {[BOLD]="manBold", [UNDERLINE]="manUnderline", [ITALIC]="manItalic"}
+ local hl_groups = { [BOLD] = 'manBold', [UNDERLINE] = 'manUnderline', [ITALIC] = 'manItalic' }
local attr = NONE
local byte = 0 -- byte offset
@@ -47,7 +47,7 @@ local function highlight_line(line, linenr)
end
if continue_hl then
- hls[#hls + 1] = {attr=attr, start=byte, final=-1}
+ hls[#hls + 1] = { attr = attr, start = byte, final = -1 }
else
if attr == NONE then
for a, _ in pairs(hl_groups) do
@@ -63,7 +63,7 @@ local function highlight_line(line, linenr)
-- can be represented in one byte. Any code point above that is represented by
-- a leading byte (0xc0 and above) and continuation bytes (0x80 to 0xbf, or
-- decimal 128 to 191).
- for char in line:gmatch("[^\128-\191][\128-\191]*") do
+ for char in line:gmatch('[^\128-\191][\128-\191]*') do
if overstrike then
local last_hl = hls[#hls]
if char == prev_char then
@@ -93,7 +93,7 @@ local function highlight_line(line, linenr)
if last_hl and last_hl.attr == attr and last_hl.final == byte then
last_hl.final = byte + #char
else
- hls[#hls + 1] = {attr=attr, start=byte, final=byte + #char}
+ hls[#hls + 1] = { attr = attr, start = byte, final = byte + #char }
end
overstrike = false
@@ -106,25 +106,25 @@ local function highlight_line(line, linenr)
-- We only want to match against SGR sequences, which consist of ESC
-- followed by '[', then a series of parameter and intermediate bytes in
-- the range 0x20 - 0x3f, then 'm'. (See ECMA-48, sections 5.4 & 8.3.117)
- local sgr = prev_char:match("^%[([\032-\063]*)m$")
+ local sgr = prev_char:match('^%[([\032-\063]*)m$')
-- Ignore escape sequences with : characters, as specified by ITU's T.416
-- Open Document Architecture and interchange format.
- if sgr and not string.find(sgr, ":") then
+ if sgr and not string.find(sgr, ':') then
local match
while sgr and #sgr > 0 do
-- Match against SGR parameters, which may be separated by ';'
- match, sgr = sgr:match("^(%d*);?(.*)")
+ match, sgr = sgr:match('^(%d*);?(.*)')
add_attr_hl(match + 0) -- coerce to number
end
escape = false
- elseif not prev_char:match("^%[[\032-\063]*$") then
+ elseif not prev_char:match('^%[[\032-\063]*$') then
-- Stop looking if this isn't a partial CSI sequence
escape = false
end
- elseif char == "\027" then
+ elseif char == '\027' then
escape = true
prev_char = ''
- elseif char == "\b" then
+ elseif char == '\b' then
overstrike = true
prev_char = chars[#chars]
byte = byte - #prev_char
@@ -143,7 +143,7 @@ local function highlight_line(line, linenr)
hl_groups[hl.attr],
linenr - 1,
hl.start,
- hl.final
+ hl.final,
}
end
end
@@ -152,8 +152,8 @@ local function highlight_line(line, linenr)
end
local function highlight_man_page()
- local mod = vim.api.nvim_buf_get_option(0, "modifiable")
- vim.api.nvim_buf_set_option(0, "modifiable", true)
+ local mod = vim.api.nvim_buf_get_option(0, 'modifiable')
+ vim.api.nvim_buf_set_option(0, 'modifiable', true)
local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false)
for i, line in ipairs(lines) do
@@ -166,7 +166,7 @@ local function highlight_man_page()
end
buf_hls = {}
- vim.api.nvim_buf_set_option(0, "modifiable", mod)
+ vim.api.nvim_buf_set_option(0, 'modifiable', mod)
end
return { highlight_man_page = highlight_man_page }
diff --git a/runtime/lua/vim/F.lua b/runtime/lua/vim/F.lua
index 9327c652db..bca5ddf68b 100644
--- a/runtime/lua/vim/F.lua
+++ b/runtime/lua/vim/F.lua
@@ -5,13 +5,17 @@ local F = {}
---@param a
---@param b
function F.if_nil(a, b)
- if a == nil then return b end
+ if a == nil then
+ return b
+ end
return a
end
-- Use in combination with pcall
function F.ok_or_nil(status, ...)
- if not status then return end
+ if not status then
+ return
+ end
return ...
end
@@ -29,7 +33,7 @@ end
--- like {...} except preserve the length explicitly
function F.pack_len(...)
- return {n=select('#', ...), ...}
+ return { n = select('#', ...), ... }
end
--- like unpack() but use the length set by F.pack_len if present
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
new file mode 100644
index 0000000000..442d7b07d8
--- /dev/null
+++ b/runtime/lua/vim/_editor.lua
@@ -0,0 +1,854 @@
+-- Nvim-Lua stdlib: the `vim` module (:help lua-stdlib)
+--
+-- Lua code lives in one of three places:
+-- 1. runtime/lua/vim/ (the runtime): For "nice to have" features, e.g. the
+-- `inspect` and `lpeg` modules.
+-- 2. runtime/lua/vim/shared.lua: pure lua functions which always
+-- are available. Used in the test runner, as well as worker threads
+-- and processes launched from Nvim.
+-- 3. runtime/lua/vim/_editor.lua: Code which directly interacts with
+-- the Nvim editor state. Only available in the main thread.
+--
+-- Guideline: "If in doubt, put it in the runtime".
+--
+-- Most functions should live directly in `vim.`, not in submodules.
+-- The only "forbidden" names are those claimed by legacy `if_lua`:
+-- $ vim
+-- :lua for k,v in pairs(vim) do print(k) end
+-- buffer
+-- open
+-- window
+-- lastline
+-- firstline
+-- type
+-- line
+-- eval
+-- dict
+-- beep
+-- list
+-- command
+--
+-- Reference (#6580):
+-- - https://github.com/luafun/luafun
+-- - https://github.com/rxi/lume
+-- - http://leafo.net/lapis/reference/utilities.html
+-- - https://github.com/torch/paths
+-- - https://github.com/bakpakin/Fennel (pretty print, repl)
+-- - https://github.com/howl-editor/howl/tree/master/lib/howl/util
+
+local vim = assert(vim)
+
+-- These are for loading runtime modules lazily since they aren't available in
+-- the nvim binary as specified in executor.c
+for k, v in pairs({
+ treesitter = true,
+ filetype = true,
+ F = true,
+ lsp = true,
+ highlight = true,
+ diagnostic = true,
+ keymap = true,
+ ui = true,
+ health = true,
+ fs = true,
+}) do
+ vim._submodules[k] = v
+end
+
+vim.log = {
+ levels = {
+ TRACE = 0,
+ DEBUG = 1,
+ INFO = 2,
+ WARN = 3,
+ ERROR = 4,
+ OFF = 5,
+ },
+}
+
+-- Internal-only until comments in #8107 are addressed.
+-- Returns:
+-- {errcode}, {output}
+function vim._system(cmd)
+ local out = vim.fn.system(cmd)
+ local err = vim.v.shell_error
+ return err, out
+end
+
+-- Gets process info from the `ps` command.
+-- Used by nvim_get_proc() as a fallback.
+function vim._os_proc_info(pid)
+ if pid == nil or pid <= 0 or type(pid) ~= 'number' then
+ error('invalid pid')
+ end
+ local cmd = { 'ps', '-p', pid, '-o', 'comm=' }
+ local err, name = vim._system(cmd)
+ if 1 == err and vim.trim(name) == '' then
+ return {} -- Process not found.
+ elseif 0 ~= err then
+ error('command failed: ' .. vim.fn.string(cmd))
+ end
+ local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=' })
+ -- Remove trailing whitespace.
+ name = vim.trim(name):gsub('^.*/', '')
+ ppid = tonumber(ppid) or -1
+ return {
+ name = name,
+ pid = pid,
+ ppid = ppid,
+ }
+end
+
+-- Gets process children from the `pgrep` command.
+-- Used by nvim_get_proc_children() as a fallback.
+function vim._os_proc_children(ppid)
+ if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then
+ error('invalid ppid')
+ end
+ local cmd = { 'pgrep', '-P', ppid }
+ local err, rv = vim._system(cmd)
+ if 1 == err and vim.trim(rv) == '' then
+ return {} -- Process not found.
+ elseif 0 ~= err then
+ error('command failed: ' .. vim.fn.string(cmd))
+ end
+ local children = {}
+ for s in rv:gmatch('%S+') do
+ local i = tonumber(s)
+ if i ~= nil then
+ table.insert(children, i)
+ end
+ end
+ return children
+end
+
+-- TODO(ZyX-I): Create compatibility layer.
+
+--- Return a human-readable representation of the given object.
+---
+---@see https://github.com/kikito/inspect.lua
+---@see https://github.com/mpeterv/vinspect
+local function inspect(object, options) -- luacheck: no unused
+ error(object, options) -- Stub for gen_vimdoc.py
+end
+
+do
+ local tdots, tick, got_line1, undo_started, trailing_nl = 0, 0, false, false, false
+
+ --- Paste handler, invoked by |nvim_paste()| when a conforming UI
+ --- (such as the |TUI|) pastes text into the editor.
+ ---
+ --- Example: To remove ANSI color codes when pasting:
+ --- <pre>
+ --- vim.paste = (function(overridden)
+ --- return function(lines, phase)
+ --- for i,line in ipairs(lines) do
+ --- -- Scrub ANSI color codes from paste input.
+ --- lines[i] = line:gsub('\27%[[0-9;mK]+', '')
+ --- end
+ --- overridden(lines, phase)
+ --- end
+ --- end)(vim.paste)
+ --- </pre>
+ ---
+ ---@see |paste|
+ ---
+ ---@param lines |readfile()|-style list of lines to paste. |channel-lines|
+ ---@param phase -1: "non-streaming" paste: the call contains all lines.
+ --- If paste is "streamed", `phase` indicates the stream state:
+ --- - 1: starts the paste (exactly once)
+ --- - 2: continues the paste (zero or more times)
+ --- - 3: ends the paste (exactly once)
+ ---@returns false if client should cancel the paste.
+ function vim.paste(lines, phase)
+ local now = vim.loop.now()
+ local is_first_chunk = phase < 2
+ local is_last_chunk = phase == -1 or phase == 3
+ if is_first_chunk then -- Reset flags.
+ tdots, tick, got_line1, undo_started, trailing_nl = now, 0, false, false, false
+ end
+ if #lines == 0 then
+ lines = { '' }
+ end
+ if #lines == 1 and lines[1] == '' and not is_last_chunk then
+ -- An empty chunk can cause some edge cases in streamed pasting,
+ -- so don't do anything unless it is the last chunk.
+ return true
+ end
+ -- Note: mode doesn't always start with "c" in cmdline mode, so use getcmdtype() instead.
+ if vim.fn.getcmdtype() ~= '' then -- cmdline-mode: paste only 1 line.
+ if not got_line1 then
+ got_line1 = (#lines > 1)
+ -- Escape control characters
+ local line1 = lines[1]:gsub('(%c)', '\022%1')
+ -- nvim_input() is affected by mappings,
+ -- so use nvim_feedkeys() with "n" flag to ignore mappings.
+ vim.api.nvim_feedkeys(line1, 'n', true)
+ end
+ return true
+ end
+ local mode = vim.api.nvim_get_mode().mode
+ if undo_started then
+ vim.api.nvim_command('undojoin')
+ end
+ if mode:find('^i') or mode:find('^n?t') then -- Insert mode or Terminal buffer
+ vim.api.nvim_put(lines, 'c', false, true)
+ elseif phase < 2 and mode:find('^R') and not mode:find('^Rv') then -- Replace mode
+ -- TODO: implement Replace mode streamed pasting
+ -- TODO: support Virtual Replace mode
+ local nchars = 0
+ for _, line in ipairs(lines) do
+ nchars = nchars + line:len()
+ end
+ local row, col = unpack(vim.api.nvim_win_get_cursor(0))
+ local bufline = vim.api.nvim_buf_get_lines(0, row - 1, row, true)[1]
+ local firstline = lines[1]
+ firstline = bufline:sub(1, col) .. firstline
+ lines[1] = firstline
+ lines[#lines] = lines[#lines] .. bufline:sub(col + nchars + 1, bufline:len())
+ vim.api.nvim_buf_set_lines(0, row - 1, row, false, lines)
+ elseif mode:find('^[nvV\22sS\19]') then -- Normal or Visual or Select mode
+ if mode:find('^n') then -- Normal mode
+ -- When there was a trailing new line in the previous chunk,
+ -- the cursor is on the first character of the next line,
+ -- so paste before the cursor instead of after it.
+ vim.api.nvim_put(lines, 'c', not trailing_nl, false)
+ else -- Visual or Select mode
+ vim.api.nvim_command([[exe "silent normal! \<Del>"]])
+ local del_start = vim.fn.getpos("'[")
+ local cursor_pos = vim.fn.getpos('.')
+ if mode:find('^[VS]') then -- linewise
+ if cursor_pos[2] < del_start[2] then -- replacing lines at eof
+ -- create a new line
+ vim.api.nvim_put({ '' }, 'l', true, true)
+ end
+ vim.api.nvim_put(lines, 'c', false, false)
+ else
+ -- paste after cursor when replacing text at eol, otherwise paste before cursor
+ vim.api.nvim_put(lines, 'c', cursor_pos[3] < del_start[3], false)
+ end
+ end
+ -- put cursor at the end of the text instead of one character after it
+ vim.fn.setpos('.', vim.fn.getpos("']"))
+ trailing_nl = lines[#lines] == ''
+ else -- Don't know what to do in other modes
+ return false
+ end
+ undo_started = true
+ if phase ~= -1 and (now - tdots >= 100) then
+ local dots = ('.'):rep(tick % 4)
+ tdots = now
+ tick = tick + 1
+ -- Use :echo because Lua print('') is a no-op, and we want to clear the
+ -- message when there are zero dots.
+ vim.api.nvim_command(('echo "%s"'):format(dots))
+ end
+ if is_last_chunk then
+ vim.api.nvim_command('redraw' .. (tick > 1 and '|echo ""' or ''))
+ end
+ return true -- Paste will not continue if not returning `true`.
+ end
+end
+
+--- Defers callback `cb` until the Nvim API is safe to call.
+---
+---@see |lua-loop-callbacks|
+---@see |vim.schedule()|
+---@see |vim.in_fast_event()|
+function vim.schedule_wrap(cb)
+ return function(...)
+ local args = vim.F.pack_len(...)
+ vim.schedule(function()
+ cb(vim.F.unpack_len(args))
+ end)
+ end
+end
+
+-- vim.fn.{func}(...)
+vim.fn = setmetatable({}, {
+ __index = function(t, key)
+ local _fn
+ if vim.api[key] ~= nil then
+ _fn = function()
+ error(string.format('Tried to call API function with vim.fn: use vim.api.%s instead', key))
+ end
+ else
+ _fn = function(...)
+ return vim.call(key, ...)
+ end
+ end
+ t[key] = _fn
+ return _fn
+ end,
+})
+
+vim.funcref = function(viml_func_name)
+ return vim.fn[viml_func_name]
+end
+
+--- Execute Vim script commands.
+---
+--- Example:
+--- <pre>
+--- vim.cmd('echo 42')
+--- vim.cmd([[
+--- augroup My_group
+--- autocmd!
+--- autocmd FileType c setlocal cindent
+--- augroup END
+--- ]])
+--- vim.cmd({ cmd = 'echo', args = { '"foo"' } })
+--- </pre>
+---
+---@param command string|table Command(s) to execute.
+--- If a string, executes multiple lines of Vim script at once. In this
+--- case, it is an alias to |nvim_exec()|, where `output` is set to
+--- false. Thus it works identical to |:source|.
+--- If a table, executes a single command. In this case, it is an alias
+--- to |nvim_cmd()| where `opts` is empty.
+---@see |ex-cmd-index|
+function vim.cmd(command)
+ if type(command) == 'table' then
+ return vim.api.nvim_cmd(command, {})
+ else
+ return vim.api.nvim_exec(command, false)
+ end
+end
+
+-- These are the vim.env/v/g/o/bo/wo variable magic accessors.
+do
+ local validate = vim.validate
+
+ --@private
+ local function make_dict_accessor(scope, handle)
+ validate({
+ scope = { scope, 's' },
+ })
+ local mt = {}
+ function mt:__newindex(k, v)
+ return vim._setvar(scope, handle or 0, k, v)
+ end
+ function mt:__index(k)
+ if handle == nil and type(k) == 'number' then
+ return make_dict_accessor(scope, k)
+ end
+ return vim._getvar(scope, handle or 0, k)
+ end
+ return setmetatable({}, mt)
+ end
+
+ vim.g = make_dict_accessor('g', false)
+ vim.v = make_dict_accessor('v', false)
+ vim.b = make_dict_accessor('b')
+ vim.w = make_dict_accessor('w')
+ vim.t = make_dict_accessor('t')
+end
+
+--- Get a table of lines with start, end columns for a region marked by two points
+---
+---@param bufnr number of buffer
+---@param pos1 (line, column) tuple marking beginning of region
+---@param pos2 (line, column) tuple marking end of region
+---@param regtype type of selection (:help setreg)
+---@param inclusive boolean indicating whether the selection is end-inclusive
+---@return region lua table of the form {linenr = {startcol,endcol}}
+function vim.region(bufnr, pos1, pos2, regtype, inclusive)
+ if not vim.api.nvim_buf_is_loaded(bufnr) then
+ vim.fn.bufload(bufnr)
+ end
+
+ -- check that region falls within current buffer
+ local buf_line_count = vim.api.nvim_buf_line_count(bufnr)
+ pos1[1] = math.min(pos1[1], buf_line_count - 1)
+ pos2[1] = math.min(pos2[1], buf_line_count - 1)
+
+ -- in case of block selection, columns need to be adjusted for non-ASCII characters
+ -- TODO: handle double-width characters
+ local bufline
+ if regtype:byte() == 22 then
+ bufline = vim.api.nvim_buf_get_lines(bufnr, pos1[1], pos1[1] + 1, true)[1]
+ pos1[2] = vim.str_utfindex(bufline, pos1[2])
+ end
+
+ local region = {}
+ for l = pos1[1], pos2[1] do
+ local c1, c2
+ if regtype:byte() == 22 then -- block selection: take width from regtype
+ c1 = pos1[2]
+ c2 = c1 + regtype:sub(2)
+ -- and adjust for non-ASCII characters
+ bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1]
+ if c1 < #bufline then
+ c1 = vim.str_byteindex(bufline, c1)
+ end
+ if c2 < #bufline then
+ c2 = vim.str_byteindex(bufline, c2)
+ end
+ else
+ c1 = (l == pos1[1]) and pos1[2] or 0
+ c2 = (l == pos2[1]) and (pos2[2] + (inclusive and 1 or 0)) or -1
+ end
+ table.insert(region, l, { c1, c2 })
+ end
+ return region
+end
+
+--- Defers calling `fn` until `timeout` ms passes.
+---
+--- Use to do a one-shot timer that calls `fn`
+--- Note: The {fn} is |schedule_wrap|ped automatically, so API functions are
+--- safe to call.
+---@param fn Callback to call once `timeout` expires
+---@param timeout Number of milliseconds to wait before calling `fn`
+---@return timer luv timer object
+function vim.defer_fn(fn, timeout)
+ vim.validate({ fn = { fn, 'c', true } })
+ local timer = vim.loop.new_timer()
+ timer:start(
+ timeout,
+ 0,
+ vim.schedule_wrap(function()
+ if not timer:is_closing() then
+ timer:close()
+ end
+
+ fn()
+ end)
+ )
+
+ return timer
+end
+
+--- Display a notification to the user.
+---
+--- This function can be overridden by plugins to display notifications using a
+--- custom provider (such as the system notification provider). By default,
+--- writes to |:messages|.
+---
+---@param msg string Content of the notification to show to the user.
+---@param level number|nil One of the values from |vim.log.levels|.
+---@param opts table|nil Optional parameters. Unused by default.
+function vim.notify(msg, level, opts) -- luacheck: no unused args
+ if level == vim.log.levels.ERROR then
+ vim.api.nvim_err_writeln(msg)
+ elseif level == vim.log.levels.WARN then
+ vim.api.nvim_echo({ { msg, 'WarningMsg' } }, true, {})
+ else
+ vim.api.nvim_echo({ { msg } }, true, {})
+ end
+end
+
+do
+ local notified = {}
+
+ --- Display a notification only one time.
+ ---
+ --- Like |vim.notify()|, but subsequent calls with the same message will not
+ --- display a notification.
+ ---
+ ---@param msg string Content of the notification to show to the user.
+ ---@param level number|nil One of the values from |vim.log.levels|.
+ ---@param opts table|nil Optional parameters. Unused by default.
+ ---@return boolean true if message was displayed, else false
+ function vim.notify_once(msg, level, opts)
+ if not notified[msg] then
+ vim.notify(msg, level, opts)
+ notified[msg] = true
+ return true
+ end
+ return false
+ end
+end
+
+---@private
+function vim.register_keystroke_callback()
+ error('vim.register_keystroke_callback is deprecated, instead use: vim.on_key')
+end
+
+local on_key_cbs = {}
+
+--- Adds Lua function {fn} with namespace id {ns_id} as a listener to every,
+--- yes every, input key.
+---
+--- The Nvim command-line option |-w| is related but does not support callbacks
+--- and cannot be toggled dynamically.
+---
+---@param fn function: Callback function. It should take one string argument.
+--- On each key press, Nvim passes the key char to fn(). |i_CTRL-V|
+--- If {fn} is nil, it removes the callback for the associated {ns_id}
+---@param ns_id number? Namespace ID. If nil or 0, generates and returns a new
+--- |nvim_create_namespace()| id.
+---
+---@return number Namespace id associated with {fn}. Or count of all callbacks
+---if on_key() is called without arguments.
+---
+---@note {fn} will be removed if an error occurs while calling.
+---@note {fn} will not be cleared by |nvim_buf_clear_namespace()|
+---@note {fn} will receive the keys after mappings have been evaluated
+function vim.on_key(fn, ns_id)
+ if fn == nil and ns_id == nil then
+ return #on_key_cbs
+ end
+
+ vim.validate({
+ fn = { fn, 'c', true },
+ ns_id = { ns_id, 'n', true },
+ })
+
+ if ns_id == nil or ns_id == 0 then
+ ns_id = vim.api.nvim_create_namespace('')
+ end
+
+ on_key_cbs[ns_id] = fn
+ return ns_id
+end
+
+--- Executes the on_key callbacks.
+---@private
+function vim._on_key(char)
+ local failed_ns_ids = {}
+ local failed_messages = {}
+ for k, v in pairs(on_key_cbs) do
+ local ok, err_msg = pcall(v, char)
+ if not ok then
+ vim.on_key(nil, k)
+ table.insert(failed_ns_ids, k)
+ table.insert(failed_messages, err_msg)
+ end
+ end
+
+ if failed_ns_ids[1] then
+ error(
+ string.format(
+ "Error executing 'on_key' with ns_ids '%s'\n Messages: %s",
+ table.concat(failed_ns_ids, ', '),
+ table.concat(failed_messages, '\n')
+ )
+ )
+ end
+end
+
+--- Generate a list of possible completions for the string.
+--- String starts with ^ and then has the pattern.
+---
+--- 1. Can we get it to just return things in the global namespace with that name prefix
+--- 2. Can we get it to return things from global namespace even with `print(` in front.
+function vim._expand_pat(pat, env)
+ env = env or _G
+
+ pat = string.sub(pat, 2, #pat)
+
+ if pat == '' then
+ local result = vim.tbl_keys(env)
+ table.sort(result)
+ return result, 0
+ end
+
+ -- TODO: We can handle spaces in [] ONLY.
+ -- We should probably do that at some point, just for cooler completion.
+ -- TODO: We can suggest the variable names to go in []
+ -- This would be difficult as well.
+ -- Probably just need to do a smarter match than just `:match`
+
+ -- Get the last part of the pattern
+ local last_part = pat:match('[%w.:_%[%]\'"]+$')
+ if not last_part then
+ return {}, 0
+ end
+
+ local parts, search_index = vim._expand_pat_get_parts(last_part)
+
+ local match_part = string.sub(last_part, search_index, #last_part)
+ local prefix_match_pat = string.sub(pat, 1, #pat - #match_part) or ''
+
+ local final_env = env
+
+ for _, part in ipairs(parts) do
+ if type(final_env) ~= 'table' then
+ return {}, 0
+ end
+ local key
+
+ -- Normally, we just have a string
+ -- Just attempt to get the string directly from the environment
+ if type(part) == 'string' then
+ key = part
+ else
+ -- However, sometimes you want to use a variable, and complete on it
+ -- With this, you have the power.
+
+ -- MY_VAR = "api"
+ -- vim[MY_VAR]
+ -- -> _G[MY_VAR] -> "api"
+ local result_key = part[1]
+ if not result_key then
+ return {}, 0
+ end
+
+ local result = rawget(env, result_key)
+
+ if result == nil then
+ return {}, 0
+ end
+
+ key = result
+ end
+ local field = rawget(final_env, key)
+ if field == nil then
+ local mt = getmetatable(final_env)
+ if mt and type(mt.__index) == 'table' then
+ field = rawget(mt.__index, key)
+ elseif final_env == vim and vim._submodules[key] then
+ field = vim[key]
+ end
+ end
+ final_env = field
+
+ if not final_env then
+ return {}, 0
+ end
+ end
+
+ local keys = {}
+ ---@private
+ local function insert_keys(obj)
+ for k, _ in pairs(obj) do
+ if type(k) == 'string' and string.sub(k, 1, string.len(match_part)) == match_part then
+ keys[k] = true
+ end
+ end
+ end
+
+ if type(final_env) == 'table' then
+ insert_keys(final_env)
+ end
+ local mt = getmetatable(final_env)
+ if mt and type(mt.__index) == 'table' then
+ insert_keys(mt.__index)
+ end
+ if final_env == vim then
+ insert_keys(vim._submodules)
+ end
+
+ keys = vim.tbl_keys(keys)
+ table.sort(keys)
+
+ return keys, #prefix_match_pat
+end
+
+vim._expand_pat_get_parts = function(lua_string)
+ local parts = {}
+
+ local accumulator, search_index = '', 1
+ local in_brackets, bracket_end = false, -1
+ local string_char = nil
+ for idx = 1, #lua_string do
+ local s = lua_string:sub(idx, idx)
+
+ if not in_brackets and (s == '.' or s == ':') then
+ table.insert(parts, accumulator)
+ accumulator = ''
+
+ search_index = idx + 1
+ elseif s == '[' then
+ in_brackets = true
+
+ table.insert(parts, accumulator)
+ accumulator = ''
+
+ search_index = idx + 1
+ elseif in_brackets then
+ if idx == bracket_end then
+ in_brackets = false
+ search_index = idx + 1
+
+ if string_char == 'VAR' then
+ table.insert(parts, { accumulator })
+ accumulator = ''
+
+ string_char = nil
+ end
+ elseif not string_char then
+ bracket_end = string.find(lua_string, ']', idx, true)
+
+ if s == '"' or s == "'" then
+ string_char = s
+ elseif s ~= ' ' then
+ string_char = 'VAR'
+ accumulator = s
+ end
+ elseif string_char then
+ if string_char ~= s then
+ accumulator = accumulator .. s
+ else
+ table.insert(parts, accumulator)
+ accumulator = ''
+
+ string_char = nil
+ end
+ end
+ else
+ accumulator = accumulator .. s
+ end
+ end
+
+ parts = vim.tbl_filter(function(val)
+ return #val > 0
+ end, parts)
+
+ return parts, search_index
+end
+
+---Prints given arguments in human-readable format.
+---Example:
+---<pre>
+--- -- Print highlight group Normal and store it's contents in a variable.
+--- local hl_normal = vim.pretty_print(vim.api.nvim_get_hl_by_name("Normal", true))
+---</pre>
+---@see |vim.inspect()|
+---@return given arguments.
+function vim.pretty_print(...)
+ local objects = {}
+ for i = 1, select('#', ...) do
+ local v = select(i, ...)
+ table.insert(objects, vim.inspect(v))
+ end
+
+ print(table.concat(objects, ' '))
+ return ...
+end
+
+function vim._cs_remote(rcid, server_addr, connect_error, args)
+ local function connection_failure_errmsg(consequence)
+ local explanation
+ if server_addr == '' then
+ explanation = 'No server specified with --server'
+ else
+ explanation = "Failed to connect to '" .. server_addr .. "'"
+ if connect_error ~= '' then
+ explanation = explanation .. ': ' .. connect_error
+ end
+ end
+ return 'E247: ' .. explanation .. '. ' .. consequence
+ end
+
+ local f_silent = false
+ local f_tab = false
+
+ local subcmd = string.sub(args[1], 10)
+ if subcmd == 'tab' then
+ f_tab = true
+ elseif subcmd == 'silent' then
+ f_silent = true
+ elseif
+ subcmd == 'wait'
+ or subcmd == 'wait-silent'
+ or subcmd == 'tab-wait'
+ or subcmd == 'tab-wait-silent'
+ then
+ return { errmsg = 'E5600: Wait commands not yet implemented in nvim' }
+ elseif subcmd == 'tab-silent' then
+ f_tab = true
+ f_silent = true
+ elseif subcmd == 'send' then
+ if rcid == 0 then
+ return { errmsg = connection_failure_errmsg('Send failed.') }
+ end
+ vim.fn.rpcrequest(rcid, 'nvim_input', args[2])
+ return { should_exit = true, tabbed = false }
+ elseif subcmd == 'expr' then
+ if rcid == 0 then
+ return { errmsg = connection_failure_errmsg('Send expression failed.') }
+ end
+ print(vim.fn.rpcrequest(rcid, 'nvim_eval', args[2]))
+ return { should_exit = true, tabbed = false }
+ elseif subcmd ~= '' then
+ return { errmsg = 'Unknown option argument: ' .. args[1] }
+ end
+
+ if rcid == 0 then
+ if not f_silent then
+ vim.notify(connection_failure_errmsg('Editing locally'), vim.log.levels.WARN)
+ end
+ else
+ local command = {}
+ if f_tab then
+ table.insert(command, 'tab')
+ end
+ table.insert(command, 'drop')
+ for i = 2, #args do
+ table.insert(command, vim.fn.fnameescape(args[i]))
+ end
+ vim.fn.rpcrequest(rcid, 'nvim_command', table.concat(command, ' '))
+ end
+
+ return {
+ should_exit = rcid ~= 0,
+ tabbed = f_tab,
+ }
+end
+
+--- Display a deprecation notification to the user.
+---
+---@param name string Deprecated function.
+---@param alternative string|nil Preferred alternative function.
+---@param version string Version in which the deprecated function will
+--- be removed.
+---@param plugin string|nil Plugin name that the function will be removed
+--- from. Defaults to "Nvim".
+---@param backtrace boolean|nil Prints backtrace. Defaults to true.
+function vim.deprecate(name, alternative, version, plugin, backtrace)
+ local message = name .. ' is deprecated'
+ plugin = plugin or 'Nvim'
+ message = alternative and (message .. ', use ' .. alternative .. ' instead.') or message
+ message = message
+ .. ' See :h deprecated\nThis function will be removed in '
+ .. plugin
+ .. ' version '
+ .. version
+ if vim.notify_once(message, vim.log.levels.WARN) and backtrace ~= false then
+ vim.notify(debug.traceback('', 2):sub(2), vim.log.levels.WARN)
+ end
+end
+
+--- Create builtin mappings (incl. menus).
+--- Called once on startup.
+function vim._init_default_mappings()
+ -- mappings
+
+ --@private
+ local function map(mode, lhs, rhs)
+ vim.api.nvim_set_keymap(mode, lhs, rhs, { noremap = true, desc = 'Nvim builtin' })
+ end
+
+ map('n', 'Y', 'y$')
+ -- Use normal! <C-L> to prevent inserting raw <C-L> when using i_<C-O>. #17473
+ map('n', '<C-L>', '<Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>')
+ map('i', '<C-U>', '<C-G>u<C-U>')
+ map('i', '<C-W>', '<C-G>u<C-W>')
+ map('x', '*', 'y/\\V<C-R>"<CR>')
+ map('x', '#', 'y?\\V<C-R>"<CR>')
+ -- Use : instead of <Cmd> so that ranges are supported. #19365
+ map('n', '&', ':&&<CR>')
+
+ -- menus
+
+ -- TODO VimScript, no l10n
+ vim.cmd([[
+ aunmenu *
+ vnoremenu PopUp.Cut "+x
+ vnoremenu PopUp.Copy "+y
+ anoremenu PopUp.Paste "+gP
+ vnoremenu PopUp.Paste "+P
+ vnoremenu PopUp.Delete "_x
+ nnoremenu PopUp.Select\ All ggVG
+ vnoremenu PopUp.Select\ All gg0oG$
+ inoremenu PopUp.Select\ All <C-Home><C-O>VG
+ anoremenu PopUp.-1- <Nop>
+ anoremenu PopUp.How-to\ disable\ mouse <Cmd>help disable-mouse<CR>
+ ]])
+end
+
+require('vim._meta')
+
+return vim
diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua
new file mode 100644
index 0000000000..7e3c73667e
--- /dev/null
+++ b/runtime/lua/vim/_init_packages.lua
@@ -0,0 +1,83 @@
+-- prevents luacheck from making lints for setting things on vim
+local vim = assert(vim)
+
+local pathtrails = {}
+vim._so_trails = {}
+for s in (package.cpath .. ';'):gmatch('[^;]*;') do
+ s = s:sub(1, -2) -- Strip trailing semicolon
+ -- Find out path patterns. pathtrail should contain something like
+ -- /?.so, \?.dll. This allows not to bother determining what correct
+ -- suffixes are.
+ local pathtrail = s:match('[/\\][^/\\]*%?.*$')
+ if pathtrail and not pathtrails[pathtrail] then
+ pathtrails[pathtrail] = true
+ table.insert(vim._so_trails, pathtrail)
+ end
+end
+
+function vim._load_package(name)
+ local basename = name:gsub('%.', '/')
+ local paths = { 'lua/' .. basename .. '.lua', 'lua/' .. basename .. '/init.lua' }
+ local found = vim.api.nvim__get_runtime(paths, false, { is_lua = true })
+ if #found > 0 then
+ local f, err = loadfile(found[1])
+ return f or error(err)
+ end
+
+ local so_paths = {}
+ for _, trail in ipairs(vim._so_trails) do
+ local path = 'lua' .. trail:gsub('?', basename) -- so_trails contains a leading slash
+ table.insert(so_paths, path)
+ end
+
+ found = vim.api.nvim__get_runtime(so_paths, false, { is_lua = true })
+ if #found > 0 then
+ -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
+ -- a) strip prefix up to and including the first dash, if any
+ -- b) replace all dots by underscores
+ -- c) prepend "luaopen_"
+ -- So "foo-bar.baz" should result in "luaopen_bar_baz"
+ local dash = name:find('-', 1, true)
+ local modname = dash and name:sub(dash + 1) or name
+ local f, err = package.loadlib(found[1], 'luaopen_' .. modname:gsub('%.', '_'))
+ return f or error(err)
+ end
+ return nil
+end
+
+-- Insert vim._load_package after the preloader at position 2
+table.insert(package.loaders, 2, vim._load_package)
+
+-- builtin functions which always should be available
+require('vim.shared')
+
+vim._submodules = { inspect = true }
+
+-- These are for loading runtime modules in the vim namespace lazily.
+setmetatable(vim, {
+ __index = function(t, key)
+ if vim._submodules[key] then
+ t[key] = require('vim.' .. key)
+ return t[key]
+ elseif vim.startswith(key, 'uri_') then
+ local val = require('vim.uri')[key]
+ if val ~= nil then
+ -- Expose all `vim.uri` functions on the `vim` module.
+ t[key] = val
+ return t[key]
+ end
+ end
+ end,
+})
+
+--- <Docs described in |vim.empty_dict()| >
+---@private
+--- TODO: should be in vim.shared when vim.shared always uses nvim-lua
+function vim.empty_dict()
+ return setmetatable({}, vim._empty_dict_mt)
+end
+
+-- only on main thread: functions for interacting with editor state
+if not vim.is_thread() then
+ require('vim._editor')
+end
diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua
index 522e26caa7..f1652718ee 100644
--- a/runtime/lua/vim/_meta.lua
+++ b/runtime/lua/vim/_meta.lua
@@ -5,41 +5,55 @@ local a = vim.api
local validate = vim.validate
local SET_TYPES = setmetatable({
- SET = 0,
- LOCAL = 1,
+ SET = 0,
+ LOCAL = 1,
GLOBAL = 2,
}, { __index = error })
-local options_info = {}
-for _, v in pairs(a.nvim_get_all_options_info()) do
- options_info[v.name] = v
- if v.shortname ~= "" then options_info[v.shortname] = v end
-end
+local options_info = nil
+local buf_options = nil
+local glb_options = nil
+local win_options = nil
-local get_scoped_options = function(scope)
- local result = {}
- for name, option_info in pairs(options_info) do
- if option_info.scope == scope then
- result[name] = true
+local function _setup()
+ if options_info ~= nil then
+ return
+ end
+ options_info = {}
+ for _, v in pairs(a.nvim_get_all_options_info()) do
+ options_info[v.name] = v
+ if v.shortname ~= '' then
+ options_info[v.shortname] = v
end
end
- return result
-end
+ local function get_scoped_options(scope)
+ local result = {}
+ for name, option_info in pairs(options_info) do
+ if option_info.scope == scope then
+ result[name] = true
+ end
+ end
+
+ return result
+ end
-local buf_options = get_scoped_options("buf")
-local glb_options = get_scoped_options("global")
-local win_options = get_scoped_options("win")
+ buf_options = get_scoped_options('buf')
+ glb_options = get_scoped_options('global')
+ win_options = get_scoped_options('win')
+end
local function make_meta_accessor(get, set, del, validator)
- validator = validator or function() return true end
+ validator = validator or function()
+ return true
+ end
- validate {
- get = {get, 'f'};
- set = {set, 'f'};
- del = {del, 'f', true};
- validator = {validator, 'f'};
- }
+ validate({
+ get = { get, 'f' },
+ set = { set, 'f' },
+ del = { del, 'f', true },
+ validator = { validator, 'f' },
+ })
local mt = {}
function mt:__newindex(k, v)
@@ -73,23 +87,28 @@ end, vim.fn.setenv)
do -- buffer option accessor
local function new_buf_opt_accessor(bufnr)
local function get(k)
- if bufnr == nil and type(k) == "number" then
+ if bufnr == nil and type(k) == 'number' then
return new_buf_opt_accessor(k)
end
- return a.nvim_buf_get_option(bufnr or 0, k)
+ return a.nvim_get_option_value(k, { buf = bufnr or 0 })
end
local function set(k, v)
- return a.nvim_buf_set_option(bufnr or 0, k, v)
+ return a.nvim_set_option_value(k, v, { buf = bufnr or 0 })
end
return make_meta_accessor(get, set, nil, function(k)
if type(k) == 'string' then
+ _setup()
if win_options[k] then
- error(string.format([['%s' is a window option, not a buffer option. See ":help %s"]], k, k))
+ error(
+ string.format([['%s' is a window option, not a buffer option. See ":help %s"]], k, k)
+ )
elseif glb_options[k] then
- error(string.format([['%s' is a global option, not a buffer option. See ":help %s"]], k, k))
+ error(
+ string.format([['%s' is a global option, not a buffer option. See ":help %s"]], k, k)
+ )
end
end
@@ -103,22 +122,27 @@ end
do -- window option accessor
local function new_win_opt_accessor(winnr)
local function get(k)
- if winnr == nil and type(k) == "number" then
+ if winnr == nil and type(k) == 'number' then
return new_win_opt_accessor(k)
end
- return a.nvim_win_get_option(winnr or 0, k)
+ return a.nvim_get_option_value(k, { win = winnr or 0 })
end
local function set(k, v)
- return a.nvim_win_set_option(winnr or 0, k, v)
+ return a.nvim_set_option_value(k, v, { win = winnr or 0 })
end
return make_meta_accessor(get, set, nil, function(k)
if type(k) == 'string' then
+ _setup()
if buf_options[k] then
- error(string.format([['%s' is a buffer option, not a window option. See ":help %s"]], k, k))
+ error(
+ string.format([['%s' is a buffer option, not a window option. See ":help %s"]], k, k)
+ )
elseif glb_options[k] then
- error(string.format([['%s' is a global option, not a window option. See ":help %s"]], k, k))
+ error(
+ string.format([['%s' is a global option, not a window option. See ":help %s"]], k, k)
+ )
end
end
@@ -131,17 +155,19 @@ end
-- vim global option
-- this ONLY sets the global option. like `setglobal`
-vim.go = make_meta_accessor(
- function(k) return a.nvim_get_option_value(k, {scope = "global"}) end,
- function(k, v) return a.nvim_set_option_value(k, v, {scope = "global"}) end
-)
+vim.go = make_meta_accessor(function(k)
+ return a.nvim_get_option_value(k, { scope = 'global' })
+end, function(k, v)
+ return a.nvim_set_option_value(k, v, { scope = 'global' })
+end)
-- vim `set` style options.
-- it has no additional metamethod magic.
-vim.o = make_meta_accessor(
- function(k) return a.nvim_get_option_value(k, {}) end,
- function(k, v) return a.nvim_set_option_value(k, v, {}) end
-)
+vim.o = make_meta_accessor(function(k)
+ return a.nvim_get_option_value(k, {})
+end, function(k, v)
+ return a.nvim_set_option_value(k, v, {})
+end)
---@brief [[
--- vim.opt, vim.opt_local and vim.opt_global implementation
@@ -154,7 +180,9 @@ vim.o = make_meta_accessor(
--- Preserves the order and does not mutate the original list
local remove_duplicate_values = function(t)
local result, seen = {}, {}
- if type(t) == "function" then error(debug.traceback("asdf")) end
+ if type(t) == 'function' then
+ error(debug.traceback('asdf'))
+ end
for _, v in ipairs(t) do
if not seen[v] then
table.insert(result, v)
@@ -171,37 +199,41 @@ end
local key_value_options = {
fillchars = true,
listchars = true,
- winhl = true,
+ winhl = true,
}
---@class OptionTypes
--- Option Type Enum
local OptionTypes = setmetatable({
BOOLEAN = 0,
- NUMBER = 1,
- STRING = 2,
- ARRAY = 3,
- MAP = 4,
- SET = 5,
+ NUMBER = 1,
+ STRING = 2,
+ ARRAY = 3,
+ MAP = 4,
+ SET = 5,
}, {
- __index = function(_, k) error("Not a valid OptionType: " .. k) end,
- __newindex = function(_, k) error("Cannot set a new OptionType: " .. k) end,
+ __index = function(_, k)
+ error('Not a valid OptionType: ' .. k)
+ end,
+ __newindex = function(_, k)
+ error('Cannot set a new OptionType: ' .. k)
+ end,
})
--- Convert a vimoption_T style dictionary to the correct OptionType associated with it.
---@return OptionType
local get_option_type = function(name, info)
- if info.type == "boolean" then
+ if info.type == 'boolean' then
return OptionTypes.BOOLEAN
- elseif info.type == "number" then
+ elseif info.type == 'number' then
return OptionTypes.NUMBER
- elseif info.type == "string" then
+ elseif info.type == 'string' then
if not info.commalist and not info.flaglist then
return OptionTypes.STRING
end
if key_value_options[name] then
- assert(info.commalist, "Must be a comma list to use key:value style")
+ assert(info.commalist, 'Must be a comma list to use key:value style')
return OptionTypes.MAP
end
@@ -211,13 +243,12 @@ local get_option_type = function(name, info)
return OptionTypes.ARRAY
end
- error("Fallthrough in OptionTypes")
+ error('Fallthrough in OptionTypes')
else
- error("Not a known info.type:" .. info.type)
+ error('Not a known info.type:' .. info.type)
end
end
-
-- Check whether the OptionTypes is allowed for vim.opt
-- If it does not match, throw an error which indicates which option causes the error.
local function assert_valid_value(name, value, types)
@@ -228,16 +259,23 @@ local function assert_valid_value(name, value, types)
end
end
- error(string.format("Invalid option type '%s' for '%s', should be %s", type_of_value, name, table.concat(types, " or ")))
+ error(
+ string.format(
+ "Invalid option type '%s' for '%s', should be %s",
+ type_of_value,
+ name,
+ table.concat(types, ' or ')
+ )
+ )
end
local valid_types = {
- [OptionTypes.BOOLEAN] = { "boolean" },
- [OptionTypes.NUMBER] = { "number" },
- [OptionTypes.STRING] = { "string" },
- [OptionTypes.SET] = { "string", "table" },
- [OptionTypes.ARRAY] = { "string", "table" },
- [OptionTypes.MAP] = { "string", "table" },
+ [OptionTypes.BOOLEAN] = { 'boolean' },
+ [OptionTypes.NUMBER] = { 'number' },
+ [OptionTypes.STRING] = { 'string' },
+ [OptionTypes.SET] = { 'string', 'table' },
+ [OptionTypes.ARRAY] = { 'string', 'table' },
+ [OptionTypes.MAP] = { 'string', 'table' },
}
--- Convert a lua value to a vimoption_T value
@@ -245,12 +283,20 @@ local convert_value_to_vim = (function()
-- Map of functions to take a Lua style value and convert to vimoption_T style value.
-- Each function takes (info, lua_value) -> vim_value
local to_vim_value = {
- [OptionTypes.BOOLEAN] = function(_, value) return value end,
- [OptionTypes.NUMBER] = function(_, value) return value end,
- [OptionTypes.STRING] = function(_, value) return value end,
+ [OptionTypes.BOOLEAN] = function(_, value)
+ return value
+ end,
+ [OptionTypes.NUMBER] = function(_, value)
+ return value
+ end,
+ [OptionTypes.STRING] = function(_, value)
+ return value
+ end,
[OptionTypes.SET] = function(info, value)
- if type(value) == "string" then return value end
+ if type(value) == 'string' then
+ return value
+ end
if info.flaglist and info.commalist then
local keys = {}
@@ -261,7 +307,7 @@ local convert_value_to_vim = (function()
end
table.sort(keys)
- return table.concat(keys, ",")
+ return table.concat(keys, ',')
else
local result = ''
for k, v in pairs(value) do
@@ -275,23 +321,27 @@ local convert_value_to_vim = (function()
end,
[OptionTypes.ARRAY] = function(info, value)
- if type(value) == "string" then return value end
+ if type(value) == 'string' then
+ return value
+ end
if not info.allows_duplicates then
value = remove_duplicate_values(value)
end
- return table.concat(value, ",")
+ return table.concat(value, ',')
end,
[OptionTypes.MAP] = function(_, value)
- if type(value) == "string" then return value end
+ if type(value) == 'string' then
+ return value
+ end
local result = {}
for opt_key, opt_value in pairs(value) do
- table.insert(result, string.format("%s:%s", opt_key, opt_value))
+ table.insert(result, string.format('%s:%s', opt_key, opt_value))
end
table.sort(result)
- return table.concat(result, ",")
+ return table.concat(result, ',')
end,
}
@@ -312,12 +362,18 @@ local convert_value_to_lua = (function()
-- Map of OptionType to functions that take vimoption_T values and convert to lua values.
-- Each function takes (info, vim_value) -> lua_value
local to_lua_value = {
- [OptionTypes.BOOLEAN] = function(_, value) return value end,
- [OptionTypes.NUMBER] = function(_, value) return value end,
- [OptionTypes.STRING] = function(_, value) return value end,
+ [OptionTypes.BOOLEAN] = function(_, value)
+ return value
+ end,
+ [OptionTypes.NUMBER] = function(_, value)
+ return value
+ end,
+ [OptionTypes.STRING] = function(_, value)
+ return value
+ end,
[OptionTypes.ARRAY] = function(info, value)
- if type(value) == "table" then
+ if type(value) == 'table' then
if not info.allows_duplicates then
value = remove_duplicate_values(value)
end
@@ -332,41 +388,43 @@ local convert_value_to_lua = (function()
end
-- Handles unescaped commas in a list.
- if string.find(value, ",,,") then
- local comma_split = vim.split(value, ",,,")
+ if string.find(value, ',,,') then
+ local comma_split = vim.split(value, ',,,')
local left = comma_split[1]
local right = comma_split[2]
local result = {}
- vim.list_extend(result, vim.split(left, ","))
- table.insert(result, ",")
- vim.list_extend(result, vim.split(right, ","))
+ vim.list_extend(result, vim.split(left, ','))
+ table.insert(result, ',')
+ vim.list_extend(result, vim.split(right, ','))
table.sort(result)
return result
end
- if string.find(value, ",^,,", 1, true) then
- local comma_split = vim.split(value, ",^,,", true)
+ if string.find(value, ',^,,', 1, true) then
+ local comma_split = vim.split(value, ',^,,', true)
local left = comma_split[1]
local right = comma_split[2]
local result = {}
- vim.list_extend(result, vim.split(left, ","))
- table.insert(result, "^,")
- vim.list_extend(result, vim.split(right, ","))
+ vim.list_extend(result, vim.split(left, ','))
+ table.insert(result, '^,')
+ vim.list_extend(result, vim.split(right, ','))
table.sort(result)
return result
end
- return vim.split(value, ",")
+ return vim.split(value, ',')
end,
[OptionTypes.SET] = function(info, value)
- if type(value) == "table" then return value end
+ if type(value) == 'table' then
+ return value
+ end
-- Empty strings mean that there is nothing there,
-- so empty table should be returned.
@@ -374,10 +432,10 @@ local convert_value_to_lua = (function()
return {}
end
- assert(info.flaglist, "That is the only one I know how to handle")
+ assert(info.flaglist, 'That is the only one I know how to handle')
if info.flaglist and info.commalist then
- local split_value = vim.split(value, ",")
+ local split_value = vim.split(value, ',')
local result = {}
for _, v in ipairs(split_value) do
result[v] = true
@@ -395,15 +453,17 @@ local convert_value_to_lua = (function()
end,
[OptionTypes.MAP] = function(info, raw_value)
- if type(raw_value) == "table" then return raw_value end
+ if type(raw_value) == 'table' then
+ return raw_value
+ end
- assert(info.commalist, "Only commas are supported currently")
+ assert(info.commalist, 'Only commas are supported currently')
local result = {}
- local comma_split = vim.split(raw_value, ",")
+ local comma_split = vim.split(raw_value, ',')
for _, key_value_str in ipairs(comma_split) do
- local key, value = unpack(vim.split(key_value_str, ":"))
+ local key, value = unpack(vim.split(key_value_str, ':'))
key = vim.trim(key)
result[key] = value
@@ -443,17 +503,21 @@ local prepend_value = (function()
end,
[OptionTypes.MAP] = function(left, right)
- return vim.tbl_extend("force", left, right)
+ return vim.tbl_extend('force', left, right)
end,
[OptionTypes.SET] = function(left, right)
- return vim.tbl_extend("force", left, right)
+ return vim.tbl_extend('force', left, right)
end,
}
return function(name, info, current, new)
return value_mutator(
- name, info, convert_value_to_lua(name, info, current), convert_value_to_lua(name, info, new), methods
+ name,
+ info,
+ convert_value_to_lua(name, info, current),
+ convert_value_to_lua(name, info, new),
+ methods
)
end
end)()
@@ -478,17 +542,21 @@ local add_value = (function()
end,
[OptionTypes.MAP] = function(left, right)
- return vim.tbl_extend("force", left, right)
+ return vim.tbl_extend('force', left, right)
end,
[OptionTypes.SET] = function(left, right)
- return vim.tbl_extend("force", left, right)
+ return vim.tbl_extend('force', left, right)
end,
}
return function(name, info, current, new)
return value_mutator(
- name, info, convert_value_to_lua(name, info, current), convert_value_to_lua(name, info, new), methods
+ name,
+ info,
+ convert_value_to_lua(name, info, current),
+ convert_value_to_lua(name, info, new),
+ methods
)
end
end)()
@@ -518,11 +586,11 @@ local remove_value = (function()
end,
[OptionTypes.STRING] = function()
- error("Subtraction not supported for strings.")
+ error('Subtraction not supported for strings.')
end,
[OptionTypes.ARRAY] = function(left, right)
- if type(right) == "string" then
+ if type(right) == 'string' then
remove_one_item(left, right)
else
for _, v in ipairs(right) do
@@ -534,7 +602,7 @@ local remove_value = (function()
end,
[OptionTypes.MAP] = function(left, right)
- if type(right) == "string" then
+ if type(right) == 'string' then
left[right] = nil
else
for _, v in ipairs(right) do
@@ -546,7 +614,7 @@ local remove_value = (function()
end,
[OptionTypes.SET] = function(left, right)
- if type(right) == "string" then
+ if type(right) == 'string' then
left[right] = nil
else
for _, v in ipairs(right) do
@@ -567,9 +635,10 @@ local create_option_metatable = function(set_type)
local set_mt, option_mt
local make_option = function(name, value)
- local info = assert(options_info[name], "Not a valid option name: " .. name)
+ _setup()
+ local info = assert(options_info[name], 'Not a valid option name: ' .. name)
- if type(value) == "table" and getmetatable(value) == option_mt then
+ if type(value) == 'table' and getmetatable(value) == option_mt then
assert(name == value._name, "must be the same value, otherwise that's weird.")
value = value._value
@@ -584,9 +653,9 @@ local create_option_metatable = function(set_type)
local scope
if set_type == SET_TYPES.GLOBAL then
- scope = "global"
+ scope = 'global'
elseif set_type == SET_TYPES.LOCAL then
- scope = "local"
+ scope = 'local'
end
option_mt = {
@@ -594,7 +663,7 @@ local create_option_metatable = function(set_type)
-- opt[my_option] = value
_set = function(self)
local value = convert_value_to_vim(self._name, self._info, self._value)
- a.nvim_set_option_value(self._name, value, {scope = scope})
+ a.nvim_set_option_value(self._name, value, { scope = scope })
return self
end,
@@ -625,13 +694,13 @@ local create_option_metatable = function(set_type)
__sub = function(self, right)
return make_option(self._name, remove_value(self._name, self._info, self._value, right))
- end
+ end,
}
option_mt.__index = option_mt
set_mt = {
__index = function(_, k)
- return make_option(k, a.nvim_get_option_value(k, {scope = scope}))
+ return make_option(k, a.nvim_get_option_value(k, { scope = scope }))
end,
__newindex = function(_, k, v)
diff --git a/runtime/lua/vim/compat.lua b/runtime/lua/vim/compat.lua
index 168979bb95..2c9786d491 100644
--- a/runtime/lua/vim/compat.lua
+++ b/runtime/lua/vim/compat.lua
@@ -7,6 +7,6 @@
local lua_version = _VERSION:sub(-3)
-if lua_version >= "5.2" then
- unpack = table.unpack -- luacheck: ignore 121 143
+if lua_version >= '5.2' then
+ unpack = table.unpack -- luacheck: ignore 121 143
end
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 417b661155..ae20b5c517 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -28,9 +28,9 @@ local global_diagnostic_options = {
M.handlers = setmetatable({}, {
__newindex = function(t, name, handler)
- vim.validate { handler = {handler, "t" } }
+ vim.validate({ handler = { handler, 't' } })
rawset(t, name, handler)
- if not global_diagnostic_options[name] then
+ if global_diagnostic_options[name] == nil then
global_diagnostic_options[name] = true
end
end,
@@ -39,7 +39,7 @@ M.handlers = setmetatable({}, {
-- Metatable that automatically creates an empty table when assigning to a missing key
local bufnr_and_namespace_cacher_mt = {
__index = function(t, bufnr)
- assert(bufnr > 0, "Invalid buffer number")
+ assert(bufnr > 0, 'Invalid buffer number')
t[bufnr] = {}
return t[bufnr]
end,
@@ -47,11 +47,11 @@ local bufnr_and_namespace_cacher_mt = {
local diagnostic_cache = setmetatable({}, {
__index = function(t, bufnr)
- assert(bufnr > 0, "Invalid buffer number")
+ assert(bufnr > 0, 'Invalid buffer number')
vim.api.nvim_buf_attach(bufnr, false, {
on_detach = function()
rawset(t, bufnr, nil) -- clear cache
- end
+ end,
})
t[bufnr] = {}
return t[bufnr]
@@ -68,7 +68,10 @@ local all_namespaces = {}
---@private
local function to_severity(severity)
if type(severity) == 'string' then
- return assert(M.severity[string.upper(severity)], string.format("Invalid severity: %s", severity))
+ return assert(
+ M.severity[string.upper(severity)],
+ string.format('Invalid severity: %s', severity)
+ )
end
return severity
end
@@ -79,15 +82,19 @@ local function filter_by_severity(severity, diagnostics)
return diagnostics
end
- if type(severity) ~= "table" then
+ if type(severity) ~= 'table' then
severity = to_severity(severity)
- return vim.tbl_filter(function(t) return t.severity == severity end, diagnostics)
+ return vim.tbl_filter(function(t)
+ return t.severity == severity
+ end, diagnostics)
end
local min_severity = to_severity(severity.min) or M.severity.HINT
local max_severity = to_severity(severity.max) or M.severity.ERROR
- return vim.tbl_filter(function(t) return t.severity <= min_severity and t.severity >= max_severity end, diagnostics)
+ return vim.tbl_filter(function(t)
+ return t.severity <= min_severity and t.severity >= max_severity
+ end, diagnostics)
end
---@private
@@ -113,17 +120,17 @@ local function prefix_source(diagnostics)
end
local t = vim.deepcopy(d)
- t.message = string.format("%s: %s", d.source, d.message)
+ t.message = string.format('%s: %s', d.source, d.message)
return t
end, diagnostics)
end
---@private
local function reformat_diagnostics(format, diagnostics)
- vim.validate {
- format = {format, 'f'},
- diagnostics = {diagnostics, 't'},
- }
+ vim.validate({
+ format = { format, 'f' },
+ diagnostics = { diagnostics, 't' },
+ })
local formatted = vim.deepcopy(diagnostics)
for _, diagnostic in ipairs(formatted) do
@@ -135,11 +142,11 @@ end
---@private
local function enabled_value(option, namespace)
local ns = namespace and M.get_namespace(namespace) or {}
- if ns.opts and type(ns.opts[option]) == "table" then
+ if ns.opts and type(ns.opts[option]) == 'table' then
return ns.opts[option]
end
- if type(global_diagnostic_options[option]) == "table" then
+ if type(global_diagnostic_options[option]) == 'table' then
return global_diagnostic_options[option]
end
@@ -162,7 +169,7 @@ local function resolve_optional_value(option, value, namespace, bufnr)
elseif type(value) == 'table' then
return value
else
- error("Unexpected option type: " .. vim.inspect(value))
+ error('Unexpected option type: ' .. vim.inspect(value))
end
end
@@ -181,10 +188,10 @@ end
-- Default diagnostic highlights
local diagnostic_severities = {
- [M.severity.ERROR] = { ctermfg = 1, guifg = "Red" };
- [M.severity.WARN] = { ctermfg = 3, guifg = "Orange" };
- [M.severity.INFO] = { ctermfg = 4, guifg = "LightBlue" };
- [M.severity.HINT] = { ctermfg = 7, guifg = "LightGrey" };
+ [M.severity.ERROR] = { ctermfg = 1, guifg = 'Red' },
+ [M.severity.WARN] = { ctermfg = 3, guifg = 'Orange' },
+ [M.severity.INFO] = { ctermfg = 4, guifg = 'LightBlue' },
+ [M.severity.HINT] = { ctermfg = 7, guifg = 'LightGrey' },
}
-- Make a map from DiagnosticSeverity -> Highlight Name
@@ -194,16 +201,16 @@ local function make_highlight_map(base_name)
for k in pairs(diagnostic_severities) do
local name = M.severity[k]
name = name:sub(1, 1) .. name:sub(2):lower()
- result[k] = "Diagnostic" .. base_name .. name
+ result[k] = 'Diagnostic' .. base_name .. name
end
return result
end
-local virtual_text_highlight_map = make_highlight_map("VirtualText")
-local underline_highlight_map = make_highlight_map("Underline")
-local floating_highlight_map = make_highlight_map("Floating")
-local sign_highlight_map = make_highlight_map("Sign")
+local virtual_text_highlight_map = make_highlight_map('VirtualText')
+local underline_highlight_map = make_highlight_map('Underline')
+local floating_highlight_map = make_highlight_map('Floating')
+local sign_highlight_map = make_highlight_map('Sign')
---@private
local define_default_signs = (function()
@@ -244,7 +251,7 @@ local function is_disabled(namespace, bufnr)
return true
end
- if type(diagnostic_disabled[bufnr]) == "table" then
+ if type(diagnostic_disabled[bufnr]) == 'table' then
return diagnostic_disabled[bufnr][namespace]
end
return diagnostic_disabled[bufnr]
@@ -271,9 +278,10 @@ end
---@private
local function set_diagnostic_cache(namespace, bufnr, diagnostics)
for _, diagnostic in ipairs(diagnostics) do
- assert(diagnostic.lnum, "Diagnostic line number is required")
- assert(diagnostic.col, "Diagnostic column is required")
- diagnostic.severity = diagnostic.severity and to_severity(diagnostic.severity) or M.severity.ERROR
+ assert(diagnostic.lnum, 'Diagnostic line number is required')
+ assert(diagnostic.col, 'Diagnostic column is required')
+ diagnostic.severity = diagnostic.severity and to_severity(diagnostic.severity)
+ or M.severity.ERROR
diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum
diagnostic.end_col = diagnostic.end_col or diagnostic.col
diagnostic.namespace = namespace
@@ -285,7 +293,7 @@ end
---@private
local function restore_extmarks(bufnr, last)
for ns, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do
- local extmarks_current = vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {details = true})
+ local extmarks_current = vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true })
local found = {}
for _, extmark in ipairs(extmarks_current) do
-- nvim_buf_set_lines will move any extmark to the line after the last
@@ -314,10 +322,12 @@ local function save_extmarks(namespace, bufnr)
end,
on_detach = function()
diagnostic_cache_extmarks[bufnr] = nil
- end})
+ end,
+ })
diagnostic_attached_buffers[bufnr] = true
end
- diagnostic_cache_extmarks[bufnr][namespace] = vim.api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, {details = true})
+ diagnostic_cache_extmarks[bufnr][namespace] =
+ vim.api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, { details = true })
end
local registered_autocmds = {}
@@ -325,11 +335,37 @@ local registered_autocmds = {}
---@private
local function make_augroup_key(namespace, bufnr)
local ns = M.get_namespace(namespace)
- return string.format("DiagnosticInsertLeave:%s:%s", bufnr, ns.name)
+ return string.format('DiagnosticInsertLeave:%s:%s', bufnr, ns.name)
+end
+
+---@private
+local function execute_scheduled_display(namespace, bufnr)
+ local args = bufs_waiting_to_update[bufnr][namespace]
+ if not args then
+ return
+ end
+
+ -- Clear the args so we don't display unnecessarily.
+ bufs_waiting_to_update[bufnr][namespace] = nil
+
+ M.show(namespace, bufnr, nil, args)
+end
+
+--- @deprecated
+--- Callback scheduled when leaving Insert mode.
+---
+--- called from the Vimscript autocommand.
+---
+--- See @ref schedule_display()
+---
+---@private
+function M._execute_scheduled_display(namespace, bufnr)
+ vim.deprecate('vim.diagnostic._execute_scheduled_display', nil, '0.9')
+ execute_scheduled_display(namespace, bufnr)
end
--- Table of autocmd events to fire the update for displaying new diagnostic information
-local insert_leave_auto_cmds = { "InsertLeave", "CursorHoldI" }
+local insert_leave_auto_cmds = { 'InsertLeave', 'CursorHoldI' }
---@private
local function schedule_display(namespace, bufnr, args)
@@ -337,15 +373,15 @@ local function schedule_display(namespace, bufnr, args)
local key = make_augroup_key(namespace, bufnr)
if not registered_autocmds[key] then
- vim.cmd(string.format([[augroup %s
- au!
- autocmd %s <buffer=%s> lua vim.diagnostic._execute_scheduled_display(%s, %s)
- augroup END]],
- key,
- table.concat(insert_leave_auto_cmds, ","),
- bufnr,
- namespace,
- bufnr))
+ local group = vim.api.nvim_create_augroup(key, { clear = true })
+ vim.api.nvim_create_autocmd(insert_leave_auto_cmds, {
+ group = group,
+ buffer = bufnr,
+ callback = function()
+ execute_scheduled_display(namespace, bufnr)
+ end,
+ desc = 'vim.diagnostic: display diagnostics',
+ })
registered_autocmds[key] = true
end
end
@@ -355,9 +391,7 @@ local function clear_scheduled_display(namespace, bufnr)
local key = make_augroup_key(namespace, bufnr)
if registered_autocmds[key] then
- vim.cmd(string.format([[augroup %s
- au!
- augroup END]], key))
+ vim.api.nvim_del_augroup_by_name(key)
registered_autocmds[key] = nil
end
end
@@ -382,10 +416,19 @@ local function get_diagnostics(bufnr, opts, clamp)
if not opts.lnum or d.lnum == opts.lnum then
if clamp and vim.api.nvim_buf_is_loaded(b) then
local line_count = buf_line_count[b] - 1
- if (d.lnum > line_count or d.end_lnum > line_count or d.lnum < 0 or d.end_lnum < 0) then
+ if
+ d.lnum > line_count
+ or d.end_lnum > line_count
+ or d.lnum < 0
+ or d.end_lnum < 0
+ or d.col < 0
+ or d.end_col < 0
+ then
d = vim.deepcopy(d)
d.lnum = math.max(math.min(d.lnum, line_count), 0)
d.end_lnum = math.max(math.min(d.end_lnum, line_count), 0)
+ d.col = math.max(d.col, 0)
+ d.end_col = math.max(d.end_col, 0)
end
end
table.insert(diagnostics, d)
@@ -431,7 +474,7 @@ end
local function set_list(loclist, opts)
opts = opts or {}
local open = vim.F.if_nil(opts.open, true)
- local title = opts.title or "Diagnostics"
+ local title = opts.title or 'Diagnostics'
local winnr = opts.winnr or 0
local bufnr
if loclist then
@@ -447,7 +490,7 @@ local function set_list(loclist, opts)
vim.fn.setqflist({}, ' ', { title = title, items = items })
end
if open then
- vim.api.nvim_command(loclist and "lopen" or "copen")
+ vim.api.nvim_command(loclist and 'lopen' or 'botright copen')
end
end
@@ -457,7 +500,8 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
bufnr = get_bufnr(bufnr)
local wrap = vim.F.if_nil(opts.wrap, true)
local line_count = vim.api.nvim_buf_line_count(bufnr)
- local diagnostics = get_diagnostics(bufnr, vim.tbl_extend("keep", opts, {namespace = namespace}), true)
+ local diagnostics =
+ get_diagnostics(bufnr, vim.tbl_extend('keep', opts, { namespace = namespace }), true)
local line_diagnostics = diagnostic_lines(diagnostics)
for i = 0, line_count do
local offset = i * (search_forward and 1 or -1)
@@ -469,14 +513,22 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
lnum = (lnum + line_count) % line_count
end
if line_diagnostics[lnum] and not vim.tbl_isempty(line_diagnostics[lnum]) then
- 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
if search_forward then
- sort_diagnostics = function(a, b) return a.col < b.col end
- is_next = function(d) return math.min(d.col, line_length - 1) > position[2] end
+ sort_diagnostics = function(a, b)
+ return a.col < b.col
+ end
+ is_next = function(d)
+ return math.min(d.col, line_length - 1) > position[2]
+ end
else
- sort_diagnostics = function(a, b) return a.col > b.col end
- is_next = function(d) return math.min(d.col, line_length - 1) < position[2] end
+ sort_diagnostics = function(a, b)
+ return a.col > b.col
+ end
+ is_next = function(d)
+ return math.min(d.col, line_length - 1) < position[2]
+ end
end
table.sort(line_diagnostics[lnum], sort_diagnostics)
if i == 0 then
@@ -500,28 +552,26 @@ local function diagnostic_move_pos(opts, pos)
local win_id = opts.win_id or vim.api.nvim_get_current_win()
if not pos then
- vim.api.nvim_echo({{"No more valid diagnostics to move to", "WarningMsg"}}, true, {})
+ vim.api.nvim_echo({ { 'No more valid diagnostics to move to', 'WarningMsg' } }, true, {})
return
end
vim.api.nvim_win_call(win_id, function()
-- Save position in the window's jumplist
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
- vim.cmd("normal! zv")
+ vim.cmd('normal! zv')
end)
if float then
- local float_opts = type(float) == "table" and float or {}
+ local float_opts = type(float) == 'table' and float or {}
vim.schedule(function()
- M.open_float(
- vim.tbl_extend("keep", float_opts, {
- bufnr = vim.api.nvim_win_get_buf(win_id),
- scope = "cursor",
- focus = false,
- })
- )
+ M.open_float(vim.tbl_extend('keep', float_opts, {
+ bufnr = vim.api.nvim_win_get_buf(win_id),
+ scope = 'cursor',
+ focus = false,
+ }))
end)
end
end
@@ -552,7 +602,8 @@ end
--- - `table`: Enable this feature with overrides. Use an empty table to use default values.
--- - `function`: Function with signature (namespace, bufnr) that returns any of the above.
---
----@param opts table Configuration table with the following keys:
+---@param opts table|nil When omitted or "nil", retrieve the current configuration. Otherwise, a
+--- configuration table with the following keys:
--- - underline: (default true) Use underline for diagnostics. Options:
--- * severity: Only underline diagnostics matching the given severity
--- |diagnostic-severity|
@@ -598,10 +649,10 @@ end
---@param namespace number|nil Update the options for the given namespace. When omitted, update the
--- global diagnostic options.
function M.config(opts, namespace)
- vim.validate {
- opts = { opts, 't' },
+ vim.validate({
+ opts = { opts, 't', true },
namespace = { namespace, 'n', true },
- }
+ })
local t
if namespace then
@@ -611,10 +662,13 @@ function M.config(opts, namespace)
t = global_diagnostic_options
end
- for opt in pairs(global_diagnostic_options) do
- if opts[opt] ~= nil then
- t[opt] = opts[opt]
- end
+ if not opts then
+ -- Return current config
+ return vim.deepcopy(t)
+ end
+
+ for k, v in pairs(opts) do
+ t[k] = v
end
if namespace then
@@ -641,16 +695,16 @@ end
---@param diagnostics table A list of diagnostic items |diagnostic-structure|
---@param opts table|nil Display options to pass to |vim.diagnostic.show()|
function M.set(namespace, bufnr, diagnostics, opts)
- vim.validate {
- namespace = {namespace, 'n'},
- bufnr = {bufnr, 'n'},
+ vim.validate({
+ namespace = { namespace, 'n' },
+ bufnr = { bufnr, 'n' },
diagnostics = {
diagnostics,
vim.tbl_islist,
- "a list of diagnostics",
+ 'a list of diagnostics',
},
- opts = {opts, 't', true},
- }
+ opts = { opts, 't', true },
+ })
bufnr = get_bufnr(bufnr)
@@ -664,14 +718,10 @@ function M.set(namespace, bufnr, diagnostics, opts)
M.show(namespace, bufnr, nil, opts)
end
- vim.api.nvim_buf_call(bufnr, function()
- vim.api.nvim_command(
- string.format(
- "doautocmd <nomodeline> DiagnosticChanged %s",
- vim.fn.fnameescape(vim.api.nvim_buf_get_name(bufnr))
- )
- )
- end)
+ vim.api.nvim_exec_autocmds('DiagnosticChanged', {
+ modeline = false,
+ buffer = bufnr,
+ })
end
--- Get namespace metadata.
@@ -679,7 +729,7 @@ end
---@param namespace number Diagnostic namespace
---@return table Namespace metadata
function M.get_namespace(namespace)
- vim.validate { namespace = { namespace, 'n' } }
+ vim.validate({ namespace = { namespace, 'n' } })
if not all_namespaces[namespace] then
local name
for k, v in pairs(vim.api.nvim_get_namespaces()) do
@@ -689,7 +739,7 @@ function M.get_namespace(namespace)
end
end
- assert(name, "namespace does not exist or is anonymous")
+ assert(name, 'namespace does not exist or is anonymous')
all_namespaces[namespace] = {
name = name,
@@ -717,10 +767,10 @@ end
--- - severity: See |diagnostic-severity|.
---@return table A list of diagnostic items |diagnostic-structure|.
function M.get(bufnr, opts)
- vim.validate {
+ vim.validate({
bufnr = { bufnr, 'n', true },
opts = { opts, 't', true },
- }
+ })
return get_diagnostics(bufnr, opts, false)
end
@@ -749,16 +799,13 @@ function M.get_prev_pos(opts)
return false
end
- return {prev.lnum, prev.col}
+ return { prev.lnum, prev.col }
end
--- Move to the previous diagnostic in the current buffer.
---@param opts table See |vim.diagnostic.goto_next()|
function M.goto_prev(opts)
- return diagnostic_move_pos(
- opts,
- M.get_prev_pos(opts)
- )
+ return diagnostic_move_pos(opts, M.get_prev_pos(opts))
end
--- Get the next diagnostic closest to the cursor position.
@@ -785,7 +832,7 @@ function M.get_next_pos(opts)
return false
end
- return {next.lnum, next.col}
+ return { next.lnum, next.col }
end
--- Move to the next diagnostic.
@@ -803,26 +850,24 @@ end
--- the "scope" option).
--- - win_id: (number, default 0) Window ID
function M.goto_next(opts)
- return diagnostic_move_pos(
- opts,
- M.get_next_pos(opts)
- )
+ return diagnostic_move_pos(opts, M.get_next_pos(opts))
end
M.handlers.signs = {
show = function(namespace, bufnr, diagnostics, opts)
- vim.validate {
- namespace = {namespace, 'n'},
- bufnr = {bufnr, 'n'},
+ vim.validate({
+ namespace = { namespace, 'n' },
+ bufnr = { bufnr, 'n' },
diagnostics = {
diagnostics,
vim.tbl_islist,
- "a list of diagnostics",
+ 'a list of diagnostics',
},
- opts = {opts, 't', true},
- }
+ opts = { opts, 't', true },
+ })
bufnr = get_bufnr(bufnr)
+ opts = opts or {}
if opts.signs and opts.signs.severity then
diagnostics = filter_by_severity(opts.signs.severity, diagnostics)
@@ -834,7 +879,7 @@ M.handlers.signs = {
local priority = opts.signs and opts.signs.priority or 10
local get_priority
if opts.severity_sort then
- if type(opts.severity_sort) == "table" and opts.severity_sort.reverse then
+ if type(opts.severity_sort) == 'table' and opts.severity_sort.reverse then
get_priority = function(severity)
return priority + (severity - vim.diagnostic.severity.ERROR)
end
@@ -851,45 +896,40 @@ M.handlers.signs = {
local ns = M.get_namespace(namespace)
if not ns.user_data.sign_group then
- ns.user_data.sign_group = string.format("vim.diagnostic.%s", ns.name)
+ ns.user_data.sign_group = string.format('vim.diagnostic.%s', ns.name)
end
local sign_group = ns.user_data.sign_group
for _, diagnostic in ipairs(diagnostics) do
- vim.fn.sign_place(
- 0,
- sign_group,
- sign_highlight_map[diagnostic.severity],
- bufnr,
- {
- priority = get_priority(diagnostic.severity),
- lnum = diagnostic.lnum + 1
- }
- )
+ vim.fn.sign_place(0, sign_group, sign_highlight_map[diagnostic.severity], bufnr, {
+ priority = get_priority(diagnostic.severity),
+ lnum = diagnostic.lnum + 1,
+ })
end
end,
hide = function(namespace, bufnr)
local ns = M.get_namespace(namespace)
if ns.user_data.sign_group then
- vim.fn.sign_unplace(ns.user_data.sign_group, {buffer=bufnr})
+ vim.fn.sign_unplace(ns.user_data.sign_group, { buffer = bufnr })
end
end,
}
M.handlers.underline = {
show = function(namespace, bufnr, diagnostics, opts)
- vim.validate {
- namespace = {namespace, 'n'},
- bufnr = {bufnr, 'n'},
+ vim.validate({
+ namespace = { namespace, 'n' },
+ bufnr = { bufnr, 'n' },
diagnostics = {
diagnostics,
vim.tbl_islist,
- "a list of diagnostics",
+ 'a list of diagnostics',
},
- opts = {opts, 't', true},
- }
+ opts = { opts, 't', true },
+ })
bufnr = get_bufnr(bufnr)
+ opts = opts or {}
if opts.underline and opts.underline.severity then
diagnostics = filter_by_severity(opts.underline.severity, diagnostics)
@@ -897,7 +937,7 @@ M.handlers.underline = {
local ns = M.get_namespace(namespace)
if not ns.user_data.underline_ns then
- ns.user_data.underline_ns = vim.api.nvim_create_namespace("")
+ ns.user_data.underline_ns = vim.api.nvim_create_namespace('')
end
local underline_ns = ns.user_data.underline_ns
@@ -914,7 +954,8 @@ M.handlers.underline = {
underline_ns,
higroup,
{ diagnostic.lnum, diagnostic.col },
- { diagnostic.end_lnum, diagnostic.end_col }
+ { diagnostic.end_lnum, diagnostic.end_col },
+ { priority = vim.highlight.priorities.diagnostics }
)
end
save_extmarks(underline_ns, bufnr)
@@ -925,23 +966,24 @@ M.handlers.underline = {
diagnostic_cache_extmarks[bufnr][ns.user_data.underline_ns] = {}
vim.api.nvim_buf_clear_namespace(bufnr, ns.user_data.underline_ns, 0, -1)
end
- end
+ end,
}
M.handlers.virtual_text = {
show = function(namespace, bufnr, diagnostics, opts)
- vim.validate {
- namespace = {namespace, 'n'},
- bufnr = {bufnr, 'n'},
+ vim.validate({
+ namespace = { namespace, 'n' },
+ bufnr = { bufnr, 'n' },
diagnostics = {
diagnostics,
vim.tbl_islist,
- "a list of diagnostics",
+ 'a list of diagnostics',
},
- opts = {opts, 't', true},
- }
+ opts = { opts, 't', true },
+ })
bufnr = get_bufnr(bufnr)
+ opts = opts or {}
local severity
if opts.virtual_text then
@@ -950,7 +992,7 @@ M.handlers.virtual_text = {
end
if
opts.virtual_text.source
- and (opts.virtual_text.source ~= "if_many" or count_sources(bufnr) > 1)
+ and (opts.virtual_text.source ~= 'if_many' or count_sources(bufnr) > 1)
then
diagnostics = prefix_source(diagnostics)
end
@@ -961,7 +1003,7 @@ M.handlers.virtual_text = {
local ns = M.get_namespace(namespace)
if not ns.user_data.virt_text_ns then
- ns.user_data.virt_text_ns = vim.api.nvim_create_namespace("")
+ ns.user_data.virt_text_ns = vim.api.nvim_create_namespace('')
end
local virt_text_ns = ns.user_data.virt_text_ns
@@ -974,7 +1016,7 @@ M.handlers.virtual_text = {
if virt_texts then
vim.api.nvim_buf_set_extmark(bufnr, virt_text_ns, line, 0, {
- hl_mode = "combine",
+ hl_mode = 'combine',
virt_text = virt_texts,
})
end
@@ -1002,52 +1044,29 @@ function M._get_virt_text_chunks(line_diags, opts)
end
opts = opts or {}
- local prefix = opts.prefix or "â– "
+ local prefix = opts.prefix or 'â– '
local spacing = opts.spacing or 4
-- Create a little more space between virtual text and contents
- local virt_texts = {{string.rep(" ", spacing)}}
+ local virt_texts = { { string.rep(' ', spacing) } }
for i = 1, #line_diags - 1 do
- table.insert(virt_texts, {prefix, virtual_text_highlight_map[line_diags[i].severity]})
+ table.insert(virt_texts, { prefix, virtual_text_highlight_map[line_diags[i].severity] })
end
local last = line_diags[#line_diags]
-- TODO(tjdevries): Allow different servers to be shown first somehow?
-- TODO(tjdevries): Display server name associated with these?
if last.message then
- table.insert(
- virt_texts,
- {
- string.format("%s %s", prefix, last.message:gsub("\r", ""):gsub("\n", " ")),
- virtual_text_highlight_map[last.severity]
- }
- )
+ table.insert(virt_texts, {
+ string.format('%s %s', prefix, last.message:gsub('\r', ''):gsub('\n', ' ')),
+ virtual_text_highlight_map[last.severity],
+ })
return virt_texts
end
end
---- Callback scheduled when leaving Insert mode.
----
---- This function must be exported publicly so that it is available to be
---- called from the Vimscript autocommand.
----
---- See @ref schedule_display()
----
----@private
-function M._execute_scheduled_display(namespace, bufnr)
- local args = bufs_waiting_to_update[bufnr][namespace]
- if not args then
- return
- end
-
- -- Clear the args so we don't display unnecessarily.
- bufs_waiting_to_update[bufnr][namespace] = nil
-
- M.show(namespace, bufnr, nil, args)
-end
-
--- Hide currently displayed diagnostics.
---
--- This only clears the decorations displayed in the buffer. Diagnostics can
@@ -1062,14 +1081,14 @@ end
---@param bufnr number|nil Buffer number, or 0 for current buffer. When
--- omitted, hide diagnostics in all buffers.
function M.hide(namespace, bufnr)
- vim.validate {
+ vim.validate({
namespace = { namespace, 'n', true },
bufnr = { bufnr, 'n', true },
- }
+ })
- local buffers = bufnr and {get_bufnr(bufnr)} or vim.tbl_keys(diagnostic_cache)
+ local buffers = bufnr and { get_bufnr(bufnr) } or vim.tbl_keys(diagnostic_cache)
for _, iter_bufnr in ipairs(buffers) do
- local namespaces = namespace and {namespace} or vim.tbl_keys(diagnostic_cache[iter_bufnr])
+ local namespaces = namespace and { namespace } or vim.tbl_keys(diagnostic_cache[iter_bufnr])
for _, iter_namespace in ipairs(namespaces) do
for _, handler in pairs(M.handlers) do
if handler.hide then
@@ -1094,19 +1113,21 @@ end
--- or {bufnr} is nil.
---@param opts table|nil Display options. See |vim.diagnostic.config()|.
function M.show(namespace, bufnr, diagnostics, opts)
- vim.validate {
+ vim.validate({
namespace = { namespace, 'n', true },
bufnr = { bufnr, 'n', true },
diagnostics = {
diagnostics,
- function(v) return v == nil or vim.tbl_islist(v) end,
- "a list of diagnostics",
+ function(v)
+ return v == nil or vim.tbl_islist(v)
+ end,
+ 'a list of diagnostics',
},
opts = { opts, 't', true },
- }
+ })
if not bufnr or not namespace then
- assert(not diagnostics, "Cannot show diagnostics without a buffer and namespace")
+ assert(not diagnostics, 'Cannot show diagnostics without a buffer and namespace')
if not bufnr then
for iter_bufnr in pairs(diagnostic_cache) do
M.show(namespace, iter_bufnr, nil, opts)
@@ -1127,7 +1148,7 @@ function M.show(namespace, bufnr, diagnostics, opts)
M.hide(namespace, bufnr)
- diagnostics = diagnostics or get_diagnostics(bufnr, {namespace=namespace}, true)
+ diagnostics = diagnostics or get_diagnostics(bufnr, { namespace = namespace }, true)
if not diagnostics or vim.tbl_isempty(diagnostics) then
return
@@ -1146,10 +1167,14 @@ function M.show(namespace, bufnr, diagnostics, opts)
end
if vim.F.if_nil(opts.severity_sort, false) then
- if type(opts.severity_sort) == "table" and opts.severity_sort.reverse then
- table.sort(diagnostics, function(a, b) return a.severity < b.severity end)
+ if type(opts.severity_sort) == 'table' and opts.severity_sort.reverse then
+ table.sort(diagnostics, function(a, b)
+ return a.severity < b.severity
+ end)
else
- table.sort(diagnostics, function(a, b) return a.severity > b.severity end)
+ table.sort(diagnostics, function(a, b)
+ return a.severity > b.severity
+ end)
end
end
@@ -1205,53 +1230,54 @@ end
function M.open_float(opts, ...)
-- Support old (bufnr, opts) signature
local bufnr
- if opts == nil or type(opts) == "number" then
+ if opts == nil or type(opts) == 'number' then
bufnr = opts
opts = ...
else
- vim.validate {
+ vim.validate({
opts = { opts, 't', true },
- }
+ })
end
opts = opts or {}
bufnr = get_bufnr(bufnr or opts.bufnr)
- local scope = ({l = "line", c = "cursor", b = "buffer"})[opts.scope] or opts.scope or "line"
+
+ do
+ -- Resolve options with user settings from vim.diagnostic.config
+ -- Unlike the other decoration functions (e.g. set_virtual_text, set_signs, etc.) `open_float`
+ -- does not have a dedicated table for configuration options; instead, the options are mixed in
+ -- with its `opts` table which also includes "keyword" parameters. So we create a dedicated
+ -- options table that inherits missing keys from the global configuration before resolving.
+ local t = global_diagnostic_options.float
+ local float_opts = vim.tbl_extend('keep', opts, type(t) == 'table' and t or {})
+ opts = get_resolved_options({ float = float_opts }, nil, bufnr).float
+ end
+
+ local scope = ({ l = 'line', c = 'cursor', b = 'buffer' })[opts.scope] or opts.scope or 'line'
local lnum, col
- if scope == "line" or scope == "cursor" then
+ if scope == 'line' or scope == 'cursor' then
if not opts.pos then
local pos = vim.api.nvim_win_get_cursor(0)
lnum = pos[1] - 1
col = pos[2]
- elseif type(opts.pos) == "number" then
+ elseif type(opts.pos) == 'number' then
lnum = opts.pos
- elseif type(opts.pos) == "table" then
+ elseif type(opts.pos) == 'table' then
lnum, col = unpack(opts.pos)
else
error("Invalid value for option 'pos'")
end
- elseif scope ~= "buffer" then
+ elseif scope ~= 'buffer' then
error("Invalid value for option 'scope'")
end
- do
- -- Resolve options with user settings from vim.diagnostic.config
- -- Unlike the other decoration functions (e.g. set_virtual_text, set_signs, etc.) `open_float`
- -- does not have a dedicated table for configuration options; instead, the options are mixed in
- -- with its `opts` table which also includes "keyword" parameters. So we create a dedicated
- -- options table that inherits missing keys from the global configuration before resolving.
- local t = global_diagnostic_options.float
- local float_opts = vim.tbl_extend("keep", opts, type(t) == "table" and t or {})
- opts = get_resolved_options({ float = float_opts }, nil, bufnr).float
- end
-
local diagnostics = get_diagnostics(bufnr, opts, true)
- if scope == "line" then
+ if scope == 'line' then
diagnostics = vim.tbl_filter(function(d)
return d.lnum == lnum
end, diagnostics)
- elseif scope == "cursor" then
+ elseif scope == 'cursor' then
-- LSP servers can send diagnostics with `end_col` past the length of the line
local line_length = #vim.api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
diagnostics = vim.tbl_filter(function(d)
@@ -1267,29 +1293,39 @@ function M.open_float(opts, ...)
local severity_sort = vim.F.if_nil(opts.severity_sort, global_diagnostic_options.severity_sort)
if severity_sort then
- if type(severity_sort) == "table" and severity_sort.reverse then
- table.sort(diagnostics, function(a, b) return a.severity > b.severity end)
+ if type(severity_sort) == 'table' and severity_sort.reverse then
+ table.sort(diagnostics, function(a, b)
+ return a.severity > b.severity
+ end)
else
- table.sort(diagnostics, function(a, b) return a.severity < b.severity end)
+ table.sort(diagnostics, function(a, b)
+ return a.severity < b.severity
+ end)
end
end
local lines = {}
local highlights = {}
- local header = if_nil(opts.header, "Diagnostics:")
+ local header = if_nil(opts.header, 'Diagnostics:')
if header then
- vim.validate { header = { header, function(v)
- return type(v) == "string" or type(v) == "table"
- end, "'string' or 'table'" } }
- if type(header) == "table" then
+ vim.validate({
+ header = {
+ header,
+ function(v)
+ return type(v) == 'string' or type(v) == 'table'
+ end,
+ "'string' or 'table'",
+ },
+ })
+ if type(header) == 'table' then
-- Don't insert any lines for an empty string
- if string.len(if_nil(header[1], "")) > 0 then
+ if string.len(if_nil(header[1], '')) > 0 then
table.insert(lines, header[1])
- table.insert(highlights, {0, header[2] or "Bold"})
+ table.insert(highlights, { 0, header[2] or 'Bold' })
end
elseif #header > 0 then
table.insert(lines, header)
- table.insert(highlights, {0, "Bold"})
+ table.insert(highlights, { 0, 'Bold' })
end
end
@@ -1297,38 +1333,45 @@ function M.open_float(opts, ...)
diagnostics = reformat_diagnostics(opts.format, diagnostics)
end
- if opts.source and (opts.source ~= "if_many" or count_sources(bufnr) > 1) then
+ if opts.source and (opts.source ~= 'if_many' or count_sources(bufnr) > 1) then
diagnostics = prefix_source(diagnostics)
end
- local prefix_opt = if_nil(opts.prefix, (scope == "cursor" and #diagnostics <= 1) and "" or function(_, i)
- return string.format("%d. ", i)
- end)
+ local prefix_opt =
+ if_nil(opts.prefix, (scope == 'cursor' and #diagnostics <= 1) and '' or function(_, i)
+ return string.format('%d. ', i)
+ end)
local prefix, prefix_hl_group
if prefix_opt then
- vim.validate { prefix = { prefix_opt, function(v)
- return type(v) == "string" or type(v) == "table" or type(v) == "function"
- end, "'string' or 'table' or 'function'" } }
- if type(prefix_opt) == "string" then
- prefix, prefix_hl_group = prefix_opt, "NormalFloat"
- elseif type(prefix_opt) == "table" then
- prefix, prefix_hl_group = prefix_opt[1] or "", prefix_opt[2] or "NormalFloat"
+ vim.validate({
+ prefix = {
+ prefix_opt,
+ function(v)
+ return type(v) == 'string' or type(v) == 'table' or type(v) == 'function'
+ end,
+ "'string' or 'table' or 'function'",
+ },
+ })
+ if type(prefix_opt) == 'string' then
+ prefix, prefix_hl_group = prefix_opt, 'NormalFloat'
+ elseif type(prefix_opt) == 'table' then
+ prefix, prefix_hl_group = prefix_opt[1] or '', prefix_opt[2] or 'NormalFloat'
end
end
for i, diagnostic in ipairs(diagnostics) do
- if prefix_opt and type(prefix_opt) == "function" then
+ if prefix_opt and type(prefix_opt) == 'function' then
prefix, prefix_hl_group = prefix_opt(diagnostic, i, #diagnostics)
- prefix, prefix_hl_group = prefix or "", prefix_hl_group or "NormalFloat"
+ prefix, prefix_hl_group = prefix or '', prefix_hl_group or 'NormalFloat'
end
local hiname = floating_highlight_map[diagnostic.severity]
local message_lines = vim.split(diagnostic.message, '\n')
- table.insert(lines, prefix..message_lines[1])
- table.insert(highlights, {#prefix, hiname, prefix_hl_group})
+ table.insert(lines, prefix .. message_lines[1])
+ table.insert(highlights, { #prefix, hiname, prefix_hl_group })
for j = 2, #message_lines do
table.insert(lines, string.rep(' ', #prefix) .. message_lines[j])
- table.insert(highlights, {0, hiname})
+ table.insert(highlights, { 0, hiname })
end
end
@@ -1340,9 +1383,9 @@ function M.open_float(opts, ...)
for i, hi in ipairs(highlights) do
local prefixlen, hiname, prefix_hiname = unpack(hi)
if prefix_hiname then
- vim.api.nvim_buf_add_highlight(float_bufnr, -1, prefix_hiname, i-1, 0, prefixlen)
+ vim.api.nvim_buf_add_highlight(float_bufnr, -1, prefix_hiname, i - 1, 0, prefixlen)
end
- vim.api.nvim_buf_add_highlight(float_bufnr, -1, hiname, i-1, prefixlen, -1)
+ vim.api.nvim_buf_add_highlight(float_bufnr, -1, hiname, i - 1, prefixlen, -1)
end
return float_bufnr, winnr
@@ -1360,27 +1403,23 @@ end
---@param bufnr number|nil Remove diagnostics for the given buffer. When omitted,
--- diagnostics are removed for all buffers.
function M.reset(namespace, bufnr)
- vim.validate {
- namespace = {namespace, 'n', true},
- bufnr = {bufnr, 'n', true},
- }
+ vim.validate({
+ namespace = { namespace, '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
- local namespaces = namespace and {namespace} or vim.tbl_keys(diagnostic_cache[iter_bufnr])
+ local namespaces = namespace and { namespace } or vim.tbl_keys(diagnostic_cache[iter_bufnr])
for _, iter_namespace in ipairs(namespaces) do
diagnostic_cache[iter_bufnr][iter_namespace] = nil
M.hide(iter_namespace, iter_bufnr)
end
- vim.api.nvim_buf_call(iter_bufnr, function()
- vim.api.nvim_command(
- string.format(
- "doautocmd <nomodeline> DiagnosticChanged %s",
- vim.fn.fnameescape(vim.api.nvim_buf_get_name(iter_bufnr))
- )
- )
- end)
+ vim.api.nvim_exec_autocmds('DiagnosticChanged', {
+ modeline = false,
+ buffer = iter_bufnr,
+ })
end
end
@@ -1413,7 +1452,7 @@ end
--- omitted, disable diagnostics in all buffers.
---@param namespace number|nil Only disable diagnostics for the given namespace.
function M.disable(bufnr, namespace)
- vim.validate { bufnr = {bufnr, 'n', true}, namespace = {namespace, 'n', true} }
+ vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } })
if bufnr == nil then
if namespace == nil then
-- Disable everything (including as yet non-existing buffers and
@@ -1421,7 +1460,9 @@ function M.disable(bufnr, namespace)
-- its metatable to always return true. This metatable is removed
-- in enable()
diagnostic_disabled = setmetatable({}, {
- __index = function() return true end,
+ __index = function()
+ return true
+ end,
})
else
local ns = M.get_namespace(namespace)
@@ -1432,7 +1473,7 @@ function M.disable(bufnr, namespace)
if namespace == nil then
diagnostic_disabled[bufnr] = true
else
- if type(diagnostic_disabled[bufnr]) ~= "table" then
+ if type(diagnostic_disabled[bufnr]) ~= 'table' then
diagnostic_disabled[bufnr] = {}
end
diagnostic_disabled[bufnr][namespace] = true
@@ -1448,7 +1489,7 @@ end
--- omitted, enable diagnostics in all buffers.
---@param namespace number|nil Only enable diagnostics for the given namespace.
function M.enable(bufnr, namespace)
- vim.validate { bufnr = {bufnr, 'n', true}, namespace = {namespace, 'n', true} }
+ vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } })
if bufnr == nil then
if namespace == nil then
-- Enable everything by setting diagnostic_disabled to an empty table
@@ -1462,7 +1503,7 @@ function M.enable(bufnr, namespace)
if namespace == nil then
diagnostic_disabled[bufnr] = nil
else
- if type(diagnostic_disabled[bufnr]) ~= "table" then
+ if type(diagnostic_disabled[bufnr]) ~= 'table' then
return
end
diagnostic_disabled[bufnr][namespace] = nil
@@ -1499,33 +1540,33 @@ end
--- ERROR.
---@return diagnostic |diagnostic-structure| or `nil` if {pat} fails to match {str}.
function M.match(str, pat, groups, severity_map, defaults)
- vim.validate {
+ vim.validate({
str = { str, 's' },
pat = { pat, 's' },
groups = { groups, 't' },
severity_map = { severity_map, 't', true },
defaults = { defaults, 't', true },
- }
+ })
severity_map = severity_map or M.severity
local diagnostic = {}
- local matches = {string.match(str, pat)}
+ local matches = { string.match(str, pat) }
if vim.tbl_isempty(matches) then
return
end
for i, match in ipairs(matches) do
local field = groups[i]
- if field == "severity" then
+ if field == 'severity' then
match = severity_map[match]
- elseif field == "lnum" or field == "end_lnum" or field == "col" or field == "end_col" then
+ elseif field == 'lnum' or field == 'end_lnum' or field == 'col' or field == 'end_col' then
match = assert(tonumber(match)) - 1
end
diagnostic[field] = match
end
- diagnostic = vim.tbl_extend("keep", diagnostic, defaults or {})
+ diagnostic = vim.tbl_extend('keep', diagnostic, defaults or {})
diagnostic.severity = diagnostic.severity or M.severity.ERROR
diagnostic.col = diagnostic.col or 0
diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum
@@ -1546,13 +1587,13 @@ local errlist_type_map = {
---@param diagnostics table List of diagnostics |diagnostic-structure|.
---@return array of quickfix list items |setqflist-what|
function M.toqflist(diagnostics)
- vim.validate {
+ vim.validate({
diagnostics = {
diagnostics,
vim.tbl_islist,
- "a list of diagnostics",
+ 'a list of diagnostics',
},
- }
+ })
local list = {}
for _, v in ipairs(diagnostics) do
@@ -1583,13 +1624,13 @@ end
--- |getloclist()|.
---@return array of diagnostics |diagnostic-structure|
function M.fromqflist(list)
- vim.validate {
+ vim.validate({
list = {
list,
vim.tbl_islist,
- "a list of quickfix items",
+ 'a list of quickfix items',
},
- }
+ })
local diagnostics = {}
for _, item in ipairs(list) do
@@ -1598,7 +1639,7 @@ function M.fromqflist(list)
local col = math.max(0, item.col - 1)
local end_lnum = item.end_lnum > 0 and (item.end_lnum - 1) or lnum
local end_col = item.end_col > 0 and (item.end_col - 1) or col
- local severity = item.type ~= "" and M.severity[item.type] or M.severity.ERROR
+ local severity = item.type ~= '' and M.severity[item.type] or M.severity.ERROR
table.insert(diagnostics, {
bufnr = item.bufnr,
lnum = lnum,
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 3d91abc406..70c8cd15eb 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -3,1361 +3,2187 @@ local api = vim.api
local M = {}
---@private
-local function starsetf(ft)
- return {function(path)
- if not vim.g.fg_ignore_pat then
- return ft
- end
+local function starsetf(ft, opts)
+ return {
+ function(path, bufnr)
+ local f = type(ft) == 'function' and ft(path, bufnr) or ft
+ if not vim.g.ft_ignore_pat then
+ return f
+ end
- local re = vim.regex(vim.g.fg_ignore_pat)
- if re:match_str(path) then
- return ft
+ local re = vim.regex(vim.g.ft_ignore_pat)
+ if not re:match_str(path) then
+ return f
+ end
+ end,
+ {
+ -- Starset matches should have lowest priority by default
+ priority = (opts and opts.priority) or -math.huge,
+ },
+ }
+end
+
+---@private
+--- Get a single line or line range from the buffer.
+--- If only start_lnum is specified, return a single line as a string.
+--- If both start_lnum and end_lnum are omitted, return all lines from the buffer.
+---
+---@param bufnr number|nil The buffer to get the lines from
+---@param start_lnum number|nil The line number of the first line (inclusive, 1-based)
+---@param end_lnum number|nil The line number of the last line (inclusive, 1-based)
+---@return table<string>|string Array of lines, or string when end_lnum is omitted
+function M.getlines(bufnr, start_lnum, end_lnum)
+ if end_lnum then
+ -- Return a line range
+ return api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
+ end
+ if start_lnum then
+ -- Return a single line
+ return api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1] or ''
+ else
+ -- Return all lines
+ return api.nvim_buf_get_lines(bufnr, 0, -1, false)
+ end
+end
+
+---@private
+--- Check whether a string matches any of the given Lua patterns.
+---
+---@param s string The string to check
+---@param patterns table<string> A list of Lua patterns
+---@return boolean `true` if s matched a pattern, else `false`
+function M.findany(s, patterns)
+ if s == nil then
+ return false
+ end
+ for _, v in ipairs(patterns) do
+ if s:find(v) then
+ return true
end
- end, {
- -- Starset matches should always have lowest priority
- priority = -math.huge,
- }}
+ end
+ return false
end
---@private
-local function getline(bufnr, lnum)
- return api.nvim_buf_get_lines(bufnr, lnum-1, lnum, false)[1]
+--- Get the next non-whitespace line in the buffer.
+---
+---@param bufnr number The buffer to get the line from
+---@param start_lnum number The line number of the first line to start from (inclusive, 1-based)
+---@return string|nil The first non-blank line if found or `nil` otherwise
+function M.nextnonblank(bufnr, start_lnum)
+ for _, line in ipairs(M.getlines(bufnr, start_lnum, -1)) do
+ if not line:find('^%s*$') then
+ return line
+ end
+ end
+ return nil
end
--- Filetypes based on file extension
+---@private
+--- Check whether the given string matches the Vim regex pattern.
+M.matchregex = (function()
+ local cache = {}
+ return function(s, pattern)
+ if s == nil then
+ return nil
+ end
+ if not cache[pattern] then
+ cache[pattern] = vim.regex(pattern)
+ end
+ return cache[pattern]:match_str(s)
+ end
+end)()
+
-- luacheck: push no unused args
+-- luacheck: push ignore 122
+
+-- Filetypes based on file extension
+---@diagnostic disable: unused-local
local extension = {
-- BEGIN EXTENSION
- ["8th"] = "8th",
- ["a65"] = "a65",
- aap = "aap",
- abap = "abap",
- abc = "abc",
- abl = "abel",
- wrm = "acedb",
- ads = "ada",
- ada = "ada",
- gpr = "ada",
- adb = "ada",
- tdf = "ahdl",
- aidl = "aidl",
- aml = "aml",
- run = "ampl",
- scpt = "applescript",
- ino = "arduino",
- pde = "arduino",
- art = "art",
- asciidoc = "asciidoc",
- adoc = "asciidoc",
- ["asn1"] = "asn",
- asn = "asn",
- atl = "atlas",
- as = "atlas",
- ahk = "autohotkey",
- ["au3"] = "autoit",
- ave = "ave",
- gawk = "awk",
- awk = "awk",
- ref = "b",
- imp = "b",
- mch = "b",
- bc = "bc",
- bdf = "bdf",
- beancount = "beancount",
- bib = "bib",
- bl = "blank",
- bsdl = "bsdl",
- bst = "bst",
- bzl = "bzl",
- bazel = "bzl",
- BUILD = "bzl",
- qc = "c",
- cabal = "cabal",
- cdl = "cdl",
- toc = "cdrtoc",
- cfc = "cf",
- cfm = "cf",
- cfi = "cf",
- cfg = "cfg",
- hgrc = "cfg",
- chf = "ch",
- chai = "chaiscript",
- chs = "chaskell",
- chopro = "chordpro",
- crd = "chordpro",
- crdpro = "chordpro",
- cho = "chordpro",
- chordpro = "chordpro",
- eni = "cl",
- dcl = "clean",
- icl = "clean",
- cljx = "clojure",
- clj = "clojure",
- cljc = "clojure",
- cljs = "clojure",
- cmake = "cmake",
- cmod = "cmod",
- lib = "cobol",
- cob = "cobol",
- cbl = "cobol",
- atg = "coco",
- recipe = "conaryrecipe",
- mklx = "context",
- mkiv = "context",
- mkii = "context",
- mkxl = "context",
- mkvi = "context",
- moc = "cpp",
- hh = "cpp",
- tlh = "cpp",
- inl = "cpp",
- ipp = "cpp",
- ["c++"] = "cpp",
- C = "cpp",
- cxx = "cpp",
- H = "cpp",
- tcc = "cpp",
- hxx = "cpp",
- hpp = "cpp",
- cpp = function()
- if vim.g.cynlib_syntax_for_cc then
- return "cynlib"
+ ['8th'] = '8th',
+ ['a65'] = 'a65',
+ aap = 'aap',
+ abap = 'abap',
+ abc = 'abc',
+ abl = 'abel',
+ wrm = 'acedb',
+ ads = 'ada',
+ ada = 'ada',
+ gpr = 'ada',
+ adb = 'ada',
+ tdf = 'ahdl',
+ aidl = 'aidl',
+ aml = 'aml',
+ run = 'ampl',
+ scpt = 'applescript',
+ ino = 'arduino',
+ pde = 'arduino',
+ art = 'art',
+ asciidoc = 'asciidoc',
+ adoc = 'asciidoc',
+ asa = function(path, bufnr)
+ if vim.g.filetype_asa then
+ return vim.g.filetype_asa
end
- return "cpp"
- end,
- crm = "crm",
- csx = "cs",
- cs = "cs",
- csc = "csc",
- csdl = "csdl",
- fdr = "csp",
- csp = "csp",
- css = "css",
- con = "cterm",
- feature = "cucumber",
- cuh = "cuda",
- cu = "cuda",
- pld = "cupl",
- si = "cuplsim",
- cyn = "cynpp",
- dart = "dart",
- drt = "dart",
- ds = "datascript",
- dcd = "dcd",
- def = "def",
- desc = "desc",
- directory = "desktop",
- desktop = "desktop",
- diff = "diff",
- rej = "diff",
- Dockerfile = "dockerfile",
- sys = "dosbatch",
- bat = "dosbatch",
- wrap = "dosini",
- ini = "dosini",
- dot = "dot",
- gv = "dot",
- drac = "dracula",
- drc = "dracula",
- dtd = "dtd",
- dts = "dts",
- dtsi = "dts",
- dylan = "dylan",
- intr = "dylanintr",
- lid = "dylanlid",
- ecd = "ecd",
- eex = "eelixir",
- leex = "eelixir",
- exs = "elixir",
- elm = "elm",
- epp = "epuppet",
- erl = "erlang",
- hrl = "erlang",
- yaws = "erlang",
- erb = "eruby",
- rhtml = "eruby",
- ec = "esqlc",
- EC = "esqlc",
- strl = "esterel",
- exp = "expect",
- factor = "factor",
- fal = "falcon",
- fan = "fan",
- fwt = "fan",
- fnl = "fennel",
- ["m4gl"] = "fgl",
- ["4gl"] = "fgl",
- ["4gh"] = "fgl",
- fish = "fish",
- focexec = "focexec",
- fex = "focexec",
- fth = "forth",
- ft = "forth",
- FOR = "fortran",
- ["f77"] = "fortran",
- ["f03"] = "fortran",
- fortran = "fortran",
- ["F95"] = "fortran",
- ["f90"] = "fortran",
- ["F03"] = "fortran",
- fpp = "fortran",
- FTN = "fortran",
- ftn = "fortran",
- ["for"] = "fortran",
- ["F90"] = "fortran",
- ["F77"] = "fortran",
- ["f95"] = "fortran",
- FPP = "fortran",
- f = "fortran",
- F = "fortran",
- ["F08"] = "fortran",
- ["f08"] = "fortran",
- fpc = "fpcmake",
- fsl = "framescript",
- bi = "freebasic",
- fb = "freebasic",
- fsi = "fsharp",
- fsx = "fsharp",
- gdmo = "gdmo",
- mo = "gdmo",
- ged = "gedcom",
- gmi = "gemtext",
- gemini = "gemtext",
- gift = "gift",
- gpi = "gnuplot",
- go = "go",
- gp = "gp",
- gs = "grads",
- gretl = "gretl",
- gradle = "groovy",
- groovy = "groovy",
- gsp = "gsp",
- haml = "haml",
- hsm = "hamster",
- ["hs-boot"] = "haskell",
- hsig = "haskell",
- hsc = "haskell",
- hs = "haskell",
- ht = "haste",
- htpp = "hastepreproc",
- hb = "hb",
- sum = "hercules",
- errsum = "hercules",
- ev = "hercules",
- vc = "hercules",
- hex = "hex",
- ["h32"] = "hex",
- hog = "hog",
- hws = "hollywood",
- htt = "httest",
- htb = "httest",
- iba = "ibasic",
- ibi = "ibasic",
- icn = "icon",
- inf = "inform",
- INF = "inform",
- ii = "initng",
- iss = "iss",
- mst = "ist",
- ist = "ist",
- ijs = "j",
- JAL = "jal",
- jal = "jal",
- jpr = "jam",
- jpl = "jam",
- jav = "java",
- java = "java",
- jj = "javacc",
- jjt = "javacc",
- es = "javascript",
- mjs = "javascript",
- javascript = "javascript",
- js = "javascript",
- cjs = "javascript",
- jsx = "javascriptreact",
- clp = "jess",
- jgr = "jgraph",
- ["j73"] = "jovial",
- jov = "jovial",
- jovial = "jovial",
- properties = "jproperties",
- slnf = "json",
- json = "json",
- jsonp = "json",
- webmanifest = "json",
- ipynb = "json",
- ["json-patch"] = "json",
- jsonc = "jsonc",
- jsp = "jsp",
- jl = "julia",
- kv = "kivy",
- kix = "kix",
- kts = "kotlin",
- kt = "kotlin",
- ktm = "kotlin",
- ks = "kscript",
- k = "kwt",
- ACE = "lace",
- ace = "lace",
- latte = "latte",
- lte = "latte",
- ld = "ld",
- ldif = "ldif",
- less = "less",
- lex = "lex",
- lxx = "lex",
- ["l++"] = "lex",
- l = "lex",
- lhs = "lhaskell",
- ll = "lifelines",
- liquid = "liquid",
- cl = "lisp",
- L = "lisp",
- lisp = "lisp",
- el = "lisp",
- lsp = "lisp",
- asd = "lisp",
- lt = "lite",
- lite = "lite",
- lgt = "logtalk",
- lotos = "lotos",
- lot = "lotos",
- lout = "lout",
- lou = "lout",
- ulpc = "lpc",
- lpc = "lpc",
- sig = "lprolog",
- lsl = "lsl",
- lss = "lss",
- nse = "lua",
- rockspec = "lua",
- lua = "lua",
- quake = "m3quake",
- at = "m4",
- eml = "mail",
- mk = "make",
- mak = "make",
- dsp = "make",
- page = "mallard",
- map = "map",
- mws = "maple",
- mpl = "maple",
- mv = "maple",
- mkdn = "markdown",
- md = "markdown",
- mdwn = "markdown",
- mkd = "markdown",
- markdown = "markdown",
- mdown = "markdown",
- mhtml = "mason",
- comp = "mason",
- mason = "mason",
- master = "master",
- mas = "master",
- mel = "mel",
- mf = "mf",
- mgl = "mgl",
- mgp = "mgp",
- my = "mib",
- mib = "mib",
- mix = "mix",
- mixal = "mix",
- nb = "mma",
- mmp = "mmp",
- DEF = "modula2",
- ["m2"] = "modula2",
- MOD = "modula2",
- mi = "modula2",
- ssc = "monk",
- monk = "monk",
- tsc = "monk",
- isc = "monk",
- moo = "moo",
- mp = "mp",
- mof = "msidl",
- odl = "msidl",
- msql = "msql",
- mu = "mupad",
- mush = "mush",
- mysql = "mysql",
- ["n1ql"] = "n1ql",
- nql = "n1ql",
- nanorc = "nanorc",
- ncf = "ncf",
- nginx = "nginx",
- ninja = "ninja",
- nqc = "nqc",
- roff = "nroff",
- tmac = "nroff",
- man = "nroff",
- mom = "nroff",
- nr = "nroff",
- tr = "nroff",
- nsi = "nsis",
- nsh = "nsis",
- obj = "obj",
- mlt = "ocaml",
- mly = "ocaml",
- mll = "ocaml",
- mlp = "ocaml",
- mlip = "ocaml",
- mli = "ocaml",
- ml = "ocaml",
- occ = "occam",
- xom = "omnimark",
- xin = "omnimark",
- opam = "opam",
- ["or"] = "openroad",
- ora = "ora",
- pxsl = "papp",
- papp = "papp",
- pxml = "papp",
- pas = "pascal",
- lpr = "pascal",
- dpr = "pascal",
- pbtxt = "pbtxt",
- g = "pccts",
- pcmk = "pcmk",
- pdf = "pdf",
- plx = "perl",
- psgi = "perl",
- al = "perl",
- ctp = "php",
- php = "php",
- phtml = "php",
- pike = "pike",
- pmod = "pike",
- rcp = "pilrc",
- pli = "pli",
- ["pl1"] = "pli",
- ["p36"] = "plm",
- plm = "plm",
- pac = "plm",
- plp = "plp",
- pls = "plsql",
- plsql = "plsql",
- po = "po",
- pot = "po",
- pod = "pod",
- pk = "poke",
- ps = "postscr",
- epsi = "postscr",
- afm = "postscr",
- epsf = "postscr",
- eps = "postscr",
- pfa = "postscr",
- ai = "postscr",
- pov = "pov",
- ppd = "ppd",
- it = "ppwiz",
- ih = "ppwiz",
- action = "privoxy",
- pc = "proc",
- pdb = "prolog",
- pml = "promela",
- proto = "proto",
- ["psd1"] = "ps1",
- ["psm1"] = "ps1",
- ["ps1"] = "ps1",
- pssc = "ps1",
- ["ps1xml"] = "ps1xml",
- psf = "psf",
- psl = "psl",
- arr = "pyret",
- pxd = "pyrex",
- pyx = "pyrex",
- pyw = "python",
- py = "python",
- pyi = "python",
- ptl = "python",
- rad = "radiance",
- mat = "radiance",
- ["pod6"] = "raku",
- rakudoc = "raku",
- rakutest = "raku",
- rakumod = "raku",
- ["pm6"] = "raku",
- raku = "raku",
- ["t6"] = "raku",
- ["p6"] = "raku",
- raml = "raml",
- rbs = "rbs",
- rego = "rego",
- rem = "remind",
- remind = "remind",
- frt = "reva",
- testUnit = "rexx",
- rex = "rexx",
- orx = "rexx",
- rexx = "rexx",
- jrexx = "rexx",
- rxj = "rexx",
- rexxj = "rexx",
- testGroup = "rexx",
- rxo = "rexx",
- Rd = "rhelp",
- rd = "rhelp",
- rib = "rib",
- Rmd = "rmd",
- rmd = "rmd",
- smd = "rmd",
- Smd = "rmd",
- rnc = "rnc",
- rng = "rng",
- rnw = "rnoweb",
- snw = "rnoweb",
- Rnw = "rnoweb",
- Snw = "rnoweb",
- rsc = "routeros",
- x = "rpcgen",
- rpl = "rpl",
- Srst = "rrst",
- srst = "rrst",
- Rrst = "rrst",
- rrst = "rrst",
- rst = "rst",
- rtf = "rtf",
- rjs = "ruby",
- rxml = "ruby",
- rb = "ruby",
- rant = "ruby",
- ru = "ruby",
- rbw = "ruby",
- gemspec = "ruby",
- builder = "ruby",
- rake = "ruby",
- rs = "rust",
- sas = "sas",
- sass = "sass",
- sa = "sather",
- sbt = "sbt",
- scala = "scala",
- sc = "scala",
- scd = "scdoc",
- ss = "scheme",
- scm = "scheme",
- sld = "scheme",
- rkt = "scheme",
- rktd = "scheme",
- rktl = "scheme",
- sce = "scilab",
- sci = "scilab",
- scss = "scss",
- sd = "sd",
- sdc = "sdc",
- pr = "sdl",
- sdl = "sdl",
- sed = "sed",
- sexp = "sexplib",
- sieve = "sieve",
- siv = "sieve",
- sil = "sil",
- sim = "simula",
- ["s85"] = "sinda",
- sin = "sinda",
- ssm = "sisu",
- sst = "sisu",
- ssi = "sisu",
- ["_sst"] = "sisu",
- ["-sst"] = "sisu",
- il = "skill",
- ils = "skill",
- cdf = "skill",
- sl = "slang",
- ice = "slice",
- score = "slrnsc",
- tpl = "smarty",
- ihlp = "smcl",
- smcl = "smcl",
- hlp = "smcl",
- smith = "smith",
- smt = "smith",
- sml = "sml",
- spt = "snobol4",
- sno = "snobol4",
- sln = "solution",
- sparql = "sparql",
- rq = "sparql",
- spec = "spec",
- spice = "spice",
- sp = "spice",
- spd = "spup",
- spdata = "spup",
- speedup = "spup",
- spi = "spyce",
- spy = "spyce",
- tyc = "sql",
- typ = "sql",
- pkb = "sql",
- tyb = "sql",
- pks = "sql",
- sqlj = "sqlj",
- sqi = "sqr",
- sqr = "sqr",
- nut = "squirrel",
- ["s28"] = "srec",
- ["s37"] = "srec",
- srec = "srec",
- mot = "srec",
- ["s19"] = "srec",
- st = "st",
- imata = "stata",
- ["do"] = "stata",
- mata = "stata",
- ado = "stata",
- stp = "stp",
- svelte = "svelte",
- svg = "svg",
- swift = "swift",
- svh = "systemverilog",
- sv = "systemverilog",
- tak = "tak",
- task = "taskedit",
- tm = "tcl",
- tcl = "tcl",
- itk = "tcl",
- itcl = "tcl",
- tk = "tcl",
- jacl = "tcl",
- tmpl = "template",
- ti = "terminfo",
- dtx = "tex",
- ltx = "tex",
- bbl = "tex",
- latex = "tex",
- sty = "tex",
- texi = "texinfo",
- txi = "texinfo",
- texinfo = "texinfo",
- text = "text",
- tf = "tf",
- tli = "tli",
- toml = "toml",
- tpp = "tpp",
- treetop = "treetop",
- slt = "tsalt",
- tsscl = "tsscl",
- tssgm = "tssgm",
- tssop = "tssop",
- tutor = "tutor",
- twig = "twig",
- ts = function(path, bufnr)
- if getline(bufnr, 1):find("<%?xml") then
- return "xml"
- else
- return "typescript"
+ return 'aspvbs'
+ end,
+ asm = function(path, bufnr)
+ return require('vim.filetype.detect').asm(bufnr)
+ end,
+ lst = function(path, bufnr)
+ return require('vim.filetype.detect').asm(bufnr)
+ end,
+ mac = function(path, bufnr)
+ return require('vim.filetype.detect').asm(bufnr)
+ end,
+ ['asn1'] = 'asn',
+ asn = 'asn',
+ asp = function(path, bufnr)
+ return require('vim.filetype.detect').asp(bufnr)
+ end,
+ atl = 'atlas',
+ as = 'atlas',
+ ahk = 'autohotkey',
+ ['au3'] = 'autoit',
+ ave = 'ave',
+ gawk = 'awk',
+ awk = 'awk',
+ ref = 'b',
+ imp = 'b',
+ mch = 'b',
+ bas = function(path, bufnr)
+ return require('vim.filetype.detect').bas(bufnr)
+ end,
+ bi = function(path, bufnr)
+ return require('vim.filetype.detect').bas(bufnr)
+ end,
+ bm = function(path, bufnr)
+ return require('vim.filetype.detect').bas(bufnr)
+ end,
+ bc = 'bc',
+ bdf = 'bdf',
+ beancount = 'beancount',
+ bib = 'bib',
+ com = function(path, bufnr)
+ return require('vim.filetype.detect').bindzone(bufnr, 'dcl')
+ end,
+ db = function(path, bufnr)
+ return require('vim.filetype.detect').bindzone(bufnr, '')
+ end,
+ bicep = 'bicep',
+ bb = 'bitbake',
+ bbappend = 'bitbake',
+ bbclass = 'bitbake',
+ bl = 'blank',
+ bsdl = 'bsdl',
+ bst = 'bst',
+ btm = function(path, bufnr)
+ return (vim.g.dosbatch_syntax_for_btm and vim.g.dosbatch_syntax_for_btm ~= 0) and 'dosbatch'
+ or 'btm'
+ end,
+ bzl = 'bzl',
+ bazel = 'bzl',
+ BUILD = 'bzl',
+ qc = 'c',
+ cabal = 'cabal',
+ cdl = 'cdl',
+ toc = 'cdrtoc',
+ cfc = 'cf',
+ cfm = 'cf',
+ cfi = 'cf',
+ hgrc = 'cfg',
+ chf = 'ch',
+ chai = 'chaiscript',
+ ch = function(path, bufnr)
+ return require('vim.filetype.detect').change(bufnr)
+ end,
+ chs = 'chaskell',
+ chopro = 'chordpro',
+ crd = 'chordpro',
+ crdpro = 'chordpro',
+ cho = 'chordpro',
+ chordpro = 'chordpro',
+ eni = 'cl',
+ icl = 'clean',
+ cljx = 'clojure',
+ clj = 'clojure',
+ cljc = 'clojure',
+ cljs = 'clojure',
+ cook = 'cook',
+ cmake = 'cmake',
+ cmod = 'cmod',
+ lib = 'cobol',
+ cob = 'cobol',
+ cbl = 'cobol',
+ atg = 'coco',
+ recipe = 'conaryrecipe',
+ hook = function(path, bufnr)
+ return M.getlines(bufnr, 1) == '[Trigger]' and 'conf'
+ end,
+ mklx = 'context',
+ mkiv = 'context',
+ mkii = 'context',
+ mkxl = 'context',
+ mkvi = 'context',
+ control = function(path, bufnr)
+ return require('vim.filetype.detect').control(bufnr)
+ end,
+ copyright = function(path, bufnr)
+ return require('vim.filetype.detect').copyright(bufnr)
+ end,
+ csh = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
+ end,
+ moc = 'cpp',
+ hh = 'cpp',
+ tlh = 'cpp',
+ inl = 'cpp',
+ ipp = 'cpp',
+ ['c++'] = 'cpp',
+ C = 'cpp',
+ cxx = 'cpp',
+ H = 'cpp',
+ tcc = 'cpp',
+ hxx = 'cpp',
+ hpp = 'cpp',
+ cpp = function(path, bufnr)
+ return vim.g.cynlib_syntax_for_cpp and 'cynlib' or 'cpp'
+ end,
+ cc = function(path, bufnr)
+ return vim.g.cynlib_syntax_for_cc and 'cynlib' or 'cpp'
+ end,
+ crm = 'crm',
+ csx = 'cs',
+ cs = 'cs',
+ csc = 'csc',
+ csdl = 'csdl',
+ cshtml = 'html',
+ fdr = 'csp',
+ csp = 'csp',
+ css = 'css',
+ csv = 'csv',
+ con = 'cterm',
+ feature = 'cucumber',
+ cuh = 'cuda',
+ cu = 'cuda',
+ pld = 'cupl',
+ si = 'cuplsim',
+ cyn = 'cynpp',
+ dart = 'dart',
+ drt = 'dart',
+ ds = 'datascript',
+ dcd = 'dcd',
+ decl = function(path, bufnr)
+ return require('vim.filetype.detect').decl(bufnr)
+ end,
+ dec = function(path, bufnr)
+ return require('vim.filetype.detect').decl(bufnr)
+ end,
+ dcl = function(path, bufnr)
+ return require('vim.filetype.detect').decl(bufnr) or 'clean'
+ end,
+ def = 'def',
+ desc = 'desc',
+ directory = 'desktop',
+ desktop = 'desktop',
+ diff = 'diff',
+ rej = 'diff',
+ Dockerfile = 'dockerfile',
+ dockerfile = 'dockerfile',
+ bat = 'dosbatch',
+ wrap = 'dosini',
+ ini = 'dosini',
+ dot = 'dot',
+ gv = 'dot',
+ drac = 'dracula',
+ drc = 'dracula',
+ dtd = 'dtd',
+ d = function(path, bufnr)
+ return require('vim.filetype.detect').dtrace(bufnr)
+ end,
+ dts = 'dts',
+ dtsi = 'dts',
+ dylan = 'dylan',
+ intr = 'dylanintr',
+ lid = 'dylanlid',
+ e = function(path, bufnr)
+ return require('vim.filetype.detect').e(bufnr)
+ end,
+ E = function(path, bufnr)
+ return require('vim.filetype.detect').e(bufnr)
+ end,
+ ecd = 'ecd',
+ edf = 'edif',
+ edif = 'edif',
+ edo = 'edif',
+ edn = function(path, bufnr)
+ return require('vim.filetype.detect').edn(bufnr)
+ end,
+ eex = 'eelixir',
+ leex = 'eelixir',
+ am = function(path, bufnr)
+ if not path:lower():find('makefile%.am$') then
+ return 'elf'
end
end,
- tsx = "typescriptreact",
- uc = "uc",
- uit = "uil",
- uil = "uil",
- sba = "vb",
- vb = "vb",
- dsm = "vb",
- ctl = "vb",
- vbs = "vb",
- vr = "vera",
- vri = "vera",
- vrh = "vera",
- v = "verilog",
- va = "verilogams",
- vams = "verilogams",
- vhdl = "vhdl",
- vst = "vhdl",
- vhd = "vhdl",
- hdl = "vhdl",
- vho = "vhdl",
- vbe = "vhdl",
- vim = "vim",
- vba = "vim",
- mar = "vmasm",
- cm = "voscm",
- wrl = "vrml",
- vroom = "vroom",
- vue = "vue",
- wat = "wast",
- wast = "wast",
- wm = "webmacro",
- wbt = "winbatch",
- wml = "wml",
- wsml = "wsml",
- ad = "xdefaults",
- xhtml = "xhtml",
- xht = "xhtml",
- msc = "xmath",
- msf = "xmath",
- ["psc1"] = "xml",
- tpm = "xml",
- xliff = "xml",
- atom = "xml",
- xul = "xml",
- cdxml = "xml",
- mpd = "xml",
- rss = "xml",
- fsproj = "xml",
- ui = "xml",
- vbproj = "xml",
- xlf = "xml",
- wsdl = "xml",
- csproj = "xml",
- wpl = "xml",
- xmi = "xml",
- ["xpm2"] = "xpm2",
- xqy = "xquery",
- xqm = "xquery",
- xquery = "xquery",
- xq = "xquery",
- xql = "xquery",
- xs = "xs",
- xsd = "xsd",
- xsl = "xslt",
- xslt = "xslt",
- yy = "yacc",
- ["y++"] = "yacc",
- yxx = "yacc",
- yml = "yaml",
- yaml = "yaml",
- ["z8a"] = "z8a",
- zig = "zig",
- zu = "zimbu",
- zut = "zimbutempl",
- zsh = "zsh",
- E = function() vim.fn["dist#ft#FTe"]() end,
- EU = function() vim.fn["dist#ft#EuphoriaCheck"]() end,
- EW = function() vim.fn["dist#ft#EuphoriaCheck"]() end,
- EX = function() vim.fn["dist#ft#EuphoriaCheck"]() end,
- EXU = function() vim.fn["dist#ft#EuphoriaCheck"]() end,
- EXW = function() vim.fn["dist#ft#EuphoriaCheck"]() end,
- PL = function() vim.fn["dist#ft#FTpl"]() end,
- R = function() vim.fn["dist#ft#FTr"]() end,
- asm = function() vim.fn["dist#ft#FTasm"]() end,
- bas = function() vim.fn["dist#ft#FTVB"]("basic") end,
- bash = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end,
- btm = function() vim.fn["dist#ft#FTbtm"]() end,
- c = function() vim.fn["dist#ft#FTlpc"]() end,
- ch = function() vim.fn["dist#ft#FTchange"]() end,
- com = function() vim.fn["dist#ft#BindzoneCheck"]('dcl') end,
- cpt = function() vim.fn["dist#ft#FThtml"]() end,
- csh = function() vim.fn["dist#ft#CSH"]() end,
- d = function() vim.fn["dist#ft#DtraceCheck"]() end,
- db = function() vim.fn["dist#ft#BindzoneCheck"]('') end,
- dtml = function() vim.fn["dist#ft#FThtml"]() end,
- e = function() vim.fn["dist#ft#FTe"]() end,
- ebuild = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end,
- eclass = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end,
- ent = function() vim.fn["dist#ft#FTent"]() end,
- env = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end,
- eu = function() vim.fn["dist#ft#EuphoriaCheck"]() end,
- ew = function() vim.fn["dist#ft#EuphoriaCheck"]() end,
- ex = function() vim.fn["dist#ft#ExCheck"]() end,
- exu = function() vim.fn["dist#ft#EuphoriaCheck"]() end,
- exw = function() vim.fn["dist#ft#EuphoriaCheck"]() end,
- frm = function() vim.fn["dist#ft#FTVB"]("form") end,
- fs = function() vim.fn["dist#ft#FTfs"]() end,
- h = function() vim.fn["dist#ft#FTheader"]() end,
- htm = function() vim.fn["dist#ft#FThtml"]() end,
- html = function() vim.fn["dist#ft#FThtml"]() end,
- i = function() vim.fn["dist#ft#FTprogress_asm"]() end,
- idl = function() vim.fn["dist#ft#FTidl"]() end,
- inc = function() vim.fn["dist#ft#FTinc"]() end,
- inp = function() vim.fn["dist#ft#Check_inp"]() end,
- ksh = function() vim.fn["dist#ft#SetFileTypeSH"]("ksh") end,
- lst = function() vim.fn["dist#ft#FTasm"]() end,
- m = function() vim.fn["dist#ft#FTm"]() end,
- mac = function() vim.fn["dist#ft#FTasm"]() end,
- mc = function() vim.fn["dist#ft#McSetf"]() end,
- mm = function() vim.fn["dist#ft#FTmm"]() end,
- mms = function() vim.fn["dist#ft#FTmms"]() end,
- p = function() vim.fn["dist#ft#FTprogress_pascal"]() end,
- pl = function() vim.fn["dist#ft#FTpl"]() end,
- pp = function() vim.fn["dist#ft#FTpp"]() end,
- pro = function() vim.fn["dist#ft#ProtoCheck"]('idlang') end,
- pt = function() vim.fn["dist#ft#FThtml"]() end,
- r = function() vim.fn["dist#ft#FTr"]() end,
- rdf = function() vim.fn["dist#ft#Redif"]() end,
- rules = function() vim.fn["dist#ft#FTRules"]() end,
- sh = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end,
- shtml = function() vim.fn["dist#ft#FThtml"]() end,
- sql = function() vim.fn["dist#ft#SQL"]() end,
- stm = function() vim.fn["dist#ft#FThtml"]() end,
- tcsh = function() vim.fn["dist#ft#SetFileTypeShell"]("tcsh") end,
- tex = function() vim.fn["dist#ft#FTtex"]() end,
- w = function() vim.fn["dist#ft#FTprogress_cweb"]() end,
- xml = function() vim.fn["dist#ft#FTxml"]() end,
- y = function() vim.fn["dist#ft#FTy"]() end,
- zsql = function() vim.fn["dist#ft#SQL"]() end,
+ exs = 'elixir',
+ elm = 'elm',
+ elv = 'elvish',
+ ent = function(path, bufnr)
+ return require('vim.filetype.detect').ent(bufnr)
+ end,
+ epp = 'epuppet',
+ erl = 'erlang',
+ hrl = 'erlang',
+ yaws = 'erlang',
+ erb = 'eruby',
+ rhtml = 'eruby',
+ ec = 'esqlc',
+ EC = 'esqlc',
+ strl = 'esterel',
+ eu = function(path, bufnr)
+ return vim.g.filetype_euphoria or 'euphoria3'
+ end,
+ EU = function(path, bufnr)
+ return vim.g.filetype_euphoria or 'euphoria3'
+ end,
+ ew = function(path, bufnr)
+ return vim.g.filetype_euphoria or 'euphoria3'
+ end,
+ EW = function(path, bufnr)
+ return vim.g.filetype_euphoria or 'euphoria3'
+ end,
+ EX = function(path, bufnr)
+ return vim.g.filetype_euphoria or 'euphoria3'
+ end,
+ exu = function(path, bufnr)
+ return vim.g.filetype_euphoria or 'euphoria3'
+ end,
+ EXU = function(path, bufnr)
+ return vim.g.filetype_euphoria or 'euphoria3'
+ end,
+ exw = function(path, bufnr)
+ return vim.g.filetype_euphoria or 'euphoria3'
+ end,
+ EXW = function(path, bufnr)
+ return vim.g.filetype_euphoria or 'euphoria3'
+ end,
+ ex = function(path, bufnr)
+ return require('vim.filetype.detect').ex(bufnr)
+ end,
+ exp = 'expect',
+ factor = 'factor',
+ fal = 'falcon',
+ fan = 'fan',
+ fwt = 'fan',
+ fnl = 'fennel',
+ ['m4gl'] = 'fgl',
+ ['4gl'] = 'fgl',
+ ['4gh'] = 'fgl',
+ fish = 'fish',
+ focexec = 'focexec',
+ fex = 'focexec',
+ fth = 'forth',
+ ft = 'forth',
+ FOR = 'fortran',
+ ['f77'] = 'fortran',
+ ['f03'] = 'fortran',
+ fortran = 'fortran',
+ ['F95'] = 'fortran',
+ ['f90'] = 'fortran',
+ ['F03'] = 'fortran',
+ fpp = 'fortran',
+ FTN = 'fortran',
+ ftn = 'fortran',
+ ['for'] = 'fortran',
+ ['F90'] = 'fortran',
+ ['F77'] = 'fortran',
+ ['f95'] = 'fortran',
+ FPP = 'fortran',
+ f = 'fortran',
+ F = 'fortran',
+ ['F08'] = 'fortran',
+ ['f08'] = 'fortran',
+ fpc = 'fpcmake',
+ fsl = 'framescript',
+ frm = function(path, bufnr)
+ return require('vim.filetype.detect').frm(bufnr)
+ end,
+ fb = 'freebasic',
+ fs = function(path, bufnr)
+ return require('vim.filetype.detect').fs(bufnr)
+ end,
+ fsi = 'fsharp',
+ fsx = 'fsharp',
+ fusion = 'fusion',
+ gdb = 'gdb',
+ gdmo = 'gdmo',
+ mo = 'gdmo',
+ tres = 'gdresource',
+ tscn = 'gdresource',
+ gd = 'gdscript',
+ ged = 'gedcom',
+ gmi = 'gemtext',
+ gemini = 'gemtext',
+ gift = 'gift',
+ gleam = 'gleam',
+ glsl = 'glsl',
+ gpi = 'gnuplot',
+ go = 'go',
+ gp = 'gp',
+ gs = 'grads',
+ gql = 'graphql',
+ graphql = 'graphql',
+ graphqls = 'graphql',
+ gretl = 'gretl',
+ gradle = 'groovy',
+ groovy = 'groovy',
+ gsp = 'gsp',
+ gjs = 'javascript.glimmer',
+ gts = 'typescript.glimmer',
+ hack = 'hack',
+ hackpartial = 'hack',
+ haml = 'haml',
+ hsm = 'hamster',
+ hbs = 'handlebars',
+ ha = 'hare',
+ ['hs-boot'] = 'haskell',
+ hsig = 'haskell',
+ hsc = 'haskell',
+ hs = 'haskell',
+ ht = 'haste',
+ htpp = 'hastepreproc',
+ hb = 'hb',
+ h = function(path, bufnr)
+ return require('vim.filetype.detect').header(bufnr)
+ end,
+ sum = 'hercules',
+ errsum = 'hercules',
+ ev = 'hercules',
+ vc = 'hercules',
+ hcl = 'hcl',
+ heex = 'heex',
+ hex = 'hex',
+ ['h32'] = 'hex',
+ hjson = 'hjson',
+ hog = 'hog',
+ hws = 'hollywood',
+ hoon = 'hoon',
+ cpt = function(path, bufnr)
+ return require('vim.filetype.detect').html(bufnr)
+ end,
+ dtml = function(path, bufnr)
+ return require('vim.filetype.detect').html(bufnr)
+ end,
+ htm = function(path, bufnr)
+ return require('vim.filetype.detect').html(bufnr)
+ end,
+ html = function(path, bufnr)
+ return require('vim.filetype.detect').html(bufnr)
+ end,
+ pt = function(path, bufnr)
+ return require('vim.filetype.detect').html(bufnr)
+ end,
+ shtml = function(path, bufnr)
+ return require('vim.filetype.detect').html(bufnr)
+ end,
+ stm = function(path, bufnr)
+ return require('vim.filetype.detect').html(bufnr)
+ end,
+ htt = 'httest',
+ htb = 'httest',
+ hw = function(path, bufnr)
+ return require('vim.filetype.detect').hw(bufnr)
+ end,
+ module = function(path, bufnr)
+ return require('vim.filetype.detect').hw(bufnr)
+ end,
+ pkg = function(path, bufnr)
+ return require('vim.filetype.detect').hw(bufnr)
+ end,
+ iba = 'ibasic',
+ ibi = 'ibasic',
+ icn = 'icon',
+ idl = function(path, bufnr)
+ return require('vim.filetype.detect').idl(bufnr)
+ end,
+ inc = function(path, bufnr)
+ return require('vim.filetype.detect').inc(bufnr)
+ end,
+ inf = 'inform',
+ INF = 'inform',
+ ii = 'initng',
+ inp = function(path, bufnr)
+ return require('vim.filetype.detect').inp(bufnr)
+ end,
+ ms = function(path, bufnr)
+ return require('vim.filetype.detect').nroff(bufnr) or 'xmath'
+ end,
+ iss = 'iss',
+ mst = 'ist',
+ ist = 'ist',
+ ijs = 'j',
+ JAL = 'jal',
+ jal = 'jal',
+ jpr = 'jam',
+ jpl = 'jam',
+ jav = 'java',
+ java = 'java',
+ jj = 'javacc',
+ jjt = 'javacc',
+ es = 'javascript',
+ mjs = 'javascript',
+ javascript = 'javascript',
+ js = 'javascript',
+ cjs = 'javascript',
+ jsx = 'javascriptreact',
+ clp = 'jess',
+ jgr = 'jgraph',
+ ['j73'] = 'jovial',
+ jov = 'jovial',
+ jovial = 'jovial',
+ properties = 'jproperties',
+ slnf = 'json',
+ json = 'json',
+ jsonp = 'json',
+ webmanifest = 'json',
+ ipynb = 'json',
+ ['json-patch'] = 'json',
+ json5 = 'json5',
+ jsonc = 'jsonc',
+ jsp = 'jsp',
+ jl = 'julia',
+ kv = 'kivy',
+ kix = 'kix',
+ kts = 'kotlin',
+ kt = 'kotlin',
+ ktm = 'kotlin',
+ ks = 'kscript',
+ k = 'kwt',
+ ACE = 'lace',
+ ace = 'lace',
+ latte = 'latte',
+ lte = 'latte',
+ ld = 'ld',
+ ldif = 'ldif',
+ journal = 'ledger',
+ ldg = 'ledger',
+ ledger = 'ledger',
+ less = 'less',
+ lex = 'lex',
+ lxx = 'lex',
+ ['l++'] = 'lex',
+ l = 'lex',
+ lhs = 'lhaskell',
+ ll = 'lifelines',
+ ly = 'lilypond',
+ ily = 'lilypond',
+ liquid = 'liquid',
+ cl = 'lisp',
+ L = 'lisp',
+ lisp = 'lisp',
+ el = 'lisp',
+ lsp = 'lisp',
+ asd = 'lisp',
+ lt = 'lite',
+ lite = 'lite',
+ lgt = 'logtalk',
+ lotos = 'lotos',
+ lot = 'lotos',
+ lout = 'lout',
+ lou = 'lout',
+ ulpc = 'lpc',
+ lpc = 'lpc',
+ c = function(path, bufnr)
+ return require('vim.filetype.detect').lpc(bufnr)
+ end,
+ lsl = 'lsl',
+ lss = 'lss',
+ nse = 'lua',
+ rockspec = 'lua',
+ lua = 'lua',
+ m = function(path, bufnr)
+ return require('vim.filetype.detect').m(bufnr)
+ end,
+ at = 'm4',
+ mc = function(path, bufnr)
+ return require('vim.filetype.detect').mc(bufnr)
+ end,
+ quake = 'm3quake',
+ ['m4'] = function(path, bufnr)
+ path = path:lower()
+ return not (path:find('html%.m4$') or path:find('fvwm2rc')) and 'm4'
+ end,
+ eml = 'mail',
+ mk = 'make',
+ mak = 'make',
+ dsp = 'make',
+ page = 'mallard',
+ map = 'map',
+ mws = 'maple',
+ mpl = 'maple',
+ mv = 'maple',
+ mkdn = 'markdown',
+ md = 'markdown',
+ mdwn = 'markdown',
+ mkd = 'markdown',
+ markdown = 'markdown',
+ mdown = 'markdown',
+ mhtml = 'mason',
+ comp = 'mason',
+ mason = 'mason',
+ master = 'master',
+ mas = 'master',
+ demo = 'maxima',
+ dm1 = 'maxima',
+ dm2 = 'maxima',
+ dm3 = 'maxima',
+ dmt = 'maxima',
+ wxm = 'maxima',
+ mel = 'mel',
+ mf = 'mf',
+ mgl = 'mgl',
+ mgp = 'mgp',
+ my = 'mib',
+ mib = 'mib',
+ mix = 'mix',
+ mixal = 'mix',
+ mm = function(path, bufnr)
+ return require('vim.filetype.detect').mm(bufnr)
+ end,
+ nb = 'mma',
+ mmp = 'mmp',
+ mms = function(path, bufnr)
+ return require('vim.filetype.detect').mms(bufnr)
+ end,
+ DEF = 'modula2',
+ ['m2'] = 'modula2',
+ mi = 'modula2',
+ ssc = 'monk',
+ monk = 'monk',
+ tsc = 'monk',
+ isc = 'monk',
+ moo = 'moo',
+ moon = 'moonscript',
+ mp = 'mp',
+ mof = 'msidl',
+ odl = 'msidl',
+ msql = 'msql',
+ mu = 'mupad',
+ mush = 'mush',
+ mysql = 'mysql',
+ ['n1ql'] = 'n1ql',
+ nql = 'n1ql',
+ nanorc = 'nanorc',
+ ncf = 'ncf',
+ nginx = 'nginx',
+ ninja = 'ninja',
+ nix = 'nix',
+ nqc = 'nqc',
+ roff = 'nroff',
+ tmac = 'nroff',
+ man = 'nroff',
+ mom = 'nroff',
+ nr = 'nroff',
+ tr = 'nroff',
+ nsi = 'nsis',
+ nsh = 'nsis',
+ obj = 'obj',
+ mlt = 'ocaml',
+ mly = 'ocaml',
+ mll = 'ocaml',
+ mlp = 'ocaml',
+ mlip = 'ocaml',
+ mli = 'ocaml',
+ ml = 'ocaml',
+ occ = 'occam',
+ xom = 'omnimark',
+ xin = 'omnimark',
+ opam = 'opam',
+ ['or'] = 'openroad',
+ scad = 'openscad',
+ ora = 'ora',
+ org = 'org',
+ org_archive = 'org',
+ pxsl = 'papp',
+ papp = 'papp',
+ pxml = 'papp',
+ pas = 'pascal',
+ lpr = 'pascal',
+ dpr = 'pascal',
+ pbtxt = 'pbtxt',
+ g = 'pccts',
+ pcmk = 'pcmk',
+ pdf = 'pdf',
+ plx = 'perl',
+ prisma = 'prisma',
+ psgi = 'perl',
+ al = 'perl',
+ ctp = 'php',
+ php = 'php',
+ phpt = 'php',
+ phtml = 'php',
+ pike = 'pike',
+ pmod = 'pike',
+ rcp = 'pilrc',
+ PL = function(path, bufnr)
+ return require('vim.filetype.detect').pl(bufnr)
+ end,
+ pli = 'pli',
+ ['pl1'] = 'pli',
+ ['p36'] = 'plm',
+ plm = 'plm',
+ pac = 'plm',
+ plp = 'plp',
+ pls = 'plsql',
+ plsql = 'plsql',
+ po = 'po',
+ pot = 'po',
+ pod = 'pod',
+ pk = 'poke',
+ ps = 'postscr',
+ epsi = 'postscr',
+ afm = 'postscr',
+ epsf = 'postscr',
+ eps = 'postscr',
+ pfa = 'postscr',
+ ai = 'postscr',
+ pov = 'pov',
+ ppd = 'ppd',
+ it = 'ppwiz',
+ ih = 'ppwiz',
+ action = 'privoxy',
+ pc = 'proc',
+ pdb = 'prolog',
+ pml = 'promela',
+ proto = 'proto',
+ ['psd1'] = 'ps1',
+ ['psm1'] = 'ps1',
+ ['ps1'] = 'ps1',
+ pssc = 'ps1',
+ ['ps1xml'] = 'ps1xml',
+ psf = 'psf',
+ psl = 'psl',
+ pug = 'pug',
+ arr = 'pyret',
+ pxd = 'pyrex',
+ pyx = 'pyrex',
+ pyw = 'python',
+ py = 'python',
+ pyi = 'python',
+ ptl = 'python',
+ ql = 'ql',
+ qll = 'ql',
+ R = function(path, bufnr)
+ return require('vim.filetype.detect').r(bufnr)
+ end,
+ rad = 'radiance',
+ mat = 'radiance',
+ ['pod6'] = 'raku',
+ rakudoc = 'raku',
+ rakutest = 'raku',
+ rakumod = 'raku',
+ ['pm6'] = 'raku',
+ raku = 'raku',
+ ['t6'] = 'raku',
+ ['p6'] = 'raku',
+ raml = 'raml',
+ rbs = 'rbs',
+ rego = 'rego',
+ rem = 'remind',
+ remind = 'remind',
+ res = 'rescript',
+ resi = 'rescript',
+ frt = 'reva',
+ testUnit = 'rexx',
+ rex = 'rexx',
+ orx = 'rexx',
+ rexx = 'rexx',
+ jrexx = 'rexx',
+ rxj = 'rexx',
+ rexxj = 'rexx',
+ testGroup = 'rexx',
+ rxo = 'rexx',
+ Rd = 'rhelp',
+ rd = 'rhelp',
+ rib = 'rib',
+ Rmd = 'rmd',
+ rmd = 'rmd',
+ smd = 'rmd',
+ Smd = 'rmd',
+ rnc = 'rnc',
+ rng = 'rng',
+ rnw = 'rnoweb',
+ snw = 'rnoweb',
+ Rnw = 'rnoweb',
+ Snw = 'rnoweb',
+ robot = 'robot',
+ resource = 'robot',
+ rsc = 'routeros',
+ x = 'rpcgen',
+ rpl = 'rpl',
+ Srst = 'rrst',
+ srst = 'rrst',
+ Rrst = 'rrst',
+ rrst = 'rrst',
+ rst = 'rst',
+ rtf = 'rtf',
+ rjs = 'ruby',
+ rxml = 'ruby',
+ rb = 'ruby',
+ rant = 'ruby',
+ ru = 'ruby',
+ rbw = 'ruby',
+ gemspec = 'ruby',
+ builder = 'ruby',
+ rake = 'ruby',
+ rs = 'rust',
+ sas = 'sas',
+ sass = 'sass',
+ sa = 'sather',
+ sbt = 'sbt',
+ scala = 'scala',
+ ss = 'scheme',
+ scm = 'scheme',
+ sld = 'scheme',
+ rkt = 'scheme',
+ rktd = 'scheme',
+ rktl = 'scheme',
+ sce = 'scilab',
+ sci = 'scilab',
+ scss = 'scss',
+ sd = 'sd',
+ sdc = 'sdc',
+ pr = 'sdl',
+ sdl = 'sdl',
+ sed = 'sed',
+ sexp = 'sexplib',
+ bash = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
+ end,
+ ebuild = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
+ end,
+ eclass = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
+ end,
+ env = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
+ end,
+ ksh = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'ksh')
+ end,
+ sh = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
+ end,
+ sieve = 'sieve',
+ siv = 'sieve',
+ sig = function(path, bufnr)
+ return require('vim.filetype.detect').sig(bufnr)
+ end,
+ sil = 'sil',
+ sim = 'simula',
+ ['s85'] = 'sinda',
+ sin = 'sinda',
+ ssm = 'sisu',
+ sst = 'sisu',
+ ssi = 'sisu',
+ ['_sst'] = 'sisu',
+ ['-sst'] = 'sisu',
+ il = 'skill',
+ ils = 'skill',
+ cdf = 'skill',
+ sl = 'slang',
+ ice = 'slice',
+ score = 'slrnsc',
+ sol = 'solidity',
+ tpl = 'smarty',
+ ihlp = 'smcl',
+ smcl = 'smcl',
+ hlp = 'smcl',
+ smith = 'smith',
+ smt = 'smith',
+ sml = 'sml',
+ spt = 'snobol4',
+ sno = 'snobol4',
+ sln = 'solution',
+ sparql = 'sparql',
+ rq = 'sparql',
+ spec = 'spec',
+ spice = 'spice',
+ sp = 'spice',
+ spd = 'spup',
+ spdata = 'spup',
+ speedup = 'spup',
+ spi = 'spyce',
+ spy = 'spyce',
+ tyc = 'sql',
+ typ = 'sql',
+ pkb = 'sql',
+ tyb = 'sql',
+ pks = 'sql',
+ sqlj = 'sqlj',
+ sqi = 'sqr',
+ sqr = 'sqr',
+ nut = 'squirrel',
+ ['s28'] = 'srec',
+ ['s37'] = 'srec',
+ srec = 'srec',
+ mot = 'srec',
+ ['s19'] = 'srec',
+ st = 'st',
+ imata = 'stata',
+ ['do'] = 'stata',
+ mata = 'stata',
+ ado = 'stata',
+ stp = 'stp',
+ quark = 'supercollider',
+ sface = 'surface',
+ svelte = 'svelte',
+ svg = 'svg',
+ swift = 'swift',
+ svh = 'systemverilog',
+ sv = 'systemverilog',
+ tak = 'tak',
+ task = 'taskedit',
+ tm = 'tcl',
+ tcl = 'tcl',
+ itk = 'tcl',
+ itcl = 'tcl',
+ tk = 'tcl',
+ jacl = 'tcl',
+ tl = 'teal',
+ tmpl = 'template',
+ ti = 'terminfo',
+ dtx = 'tex',
+ ltx = 'tex',
+ bbl = 'tex',
+ latex = 'tex',
+ sty = 'tex',
+ cls = function(path, bufnr)
+ return require('vim.filetype.detect').cls(bufnr)
+ end,
+ texi = 'texinfo',
+ txi = 'texinfo',
+ texinfo = 'texinfo',
+ text = 'text',
+ tfvars = 'terraform',
+ tla = 'tla',
+ tli = 'tli',
+ toml = 'toml',
+ tpp = 'tpp',
+ treetop = 'treetop',
+ slt = 'tsalt',
+ tsscl = 'tsscl',
+ tssgm = 'tssgm',
+ tssop = 'tssop',
+ tsv = 'tsv',
+ tutor = 'tutor',
+ twig = 'twig',
+ ts = function(path, bufnr)
+ return M.getlines(bufnr, 1):find('<%?xml') and 'xml' or 'typescript'
+ end,
+ tsx = 'typescriptreact',
+ uc = 'uc',
+ uit = 'uil',
+ uil = 'uil',
+ sba = 'vb',
+ vb = 'vb',
+ dsm = 'vb',
+ ctl = 'vb',
+ vbs = 'vb',
+ vr = 'vera',
+ vri = 'vera',
+ vrh = 'vera',
+ v = 'verilog',
+ va = 'verilogams',
+ vams = 'verilogams',
+ vhdl = 'vhdl',
+ vst = 'vhdl',
+ vhd = 'vhdl',
+ hdl = 'vhdl',
+ vho = 'vhdl',
+ vbe = 'vhdl',
+ vim = 'vim',
+ vba = 'vim',
+ mar = 'vmasm',
+ cm = 'voscm',
+ wrl = 'vrml',
+ vroom = 'vroom',
+ vue = 'vue',
+ wat = 'wast',
+ wast = 'wast',
+ wm = 'webmacro',
+ wbt = 'winbatch',
+ wml = 'wml',
+ wsml = 'wsml',
+ ad = 'xdefaults',
+ xhtml = 'xhtml',
+ xht = 'xhtml',
+ msc = 'xmath',
+ msf = 'xmath',
+ ['psc1'] = 'xml',
+ tpm = 'xml',
+ xliff = 'xml',
+ atom = 'xml',
+ xul = 'xml',
+ cdxml = 'xml',
+ mpd = 'xml',
+ rss = 'xml',
+ fsproj = 'xml',
+ ui = 'xml',
+ vbproj = 'xml',
+ xlf = 'xml',
+ wsdl = 'xml',
+ csproj = 'xml',
+ wpl = 'xml',
+ xmi = 'xml',
+ xpm = function(path, bufnr)
+ return M.getlines(bufnr, 1):find('XPM2') and 'xpm2' or 'xpm'
+ end,
+ ['xpm2'] = 'xpm2',
+ xqy = 'xquery',
+ xqm = 'xquery',
+ xquery = 'xquery',
+ xq = 'xquery',
+ xql = 'xquery',
+ xs = 'xs',
+ xsd = 'xsd',
+ xsl = 'xslt',
+ xslt = 'xslt',
+ yy = 'yacc',
+ ['y++'] = 'yacc',
+ yxx = 'yacc',
+ yml = 'yaml',
+ yaml = 'yaml',
+ yang = 'yang',
+ ['z8a'] = 'z8a',
+ zig = 'zig',
+ zu = 'zimbu',
+ zut = 'zimbutempl',
+ zsh = 'zsh',
+ vala = 'vala',
+ web = function(path, bufnr)
+ return require('vim.filetype.detect').web(bufnr)
+ end,
+ pl = function(path, bufnr)
+ return require('vim.filetype.detect').pl(bufnr)
+ end,
+ pp = function(path, bufnr)
+ return require('vim.filetype.detect').pp(bufnr)
+ end,
+ i = function(path, bufnr)
+ return require('vim.filetype.detect').progress_asm(bufnr)
+ end,
+ w = function(path, bufnr)
+ return require('vim.filetype.detect').progress_cweb(bufnr)
+ end,
+ p = function(path, bufnr)
+ return require('vim.filetype.detect').progress_pascal(bufnr)
+ end,
+ pro = function(path, bufnr)
+ return require('vim.filetype.detect').proto(bufnr, 'idlang')
+ end,
+ patch = function(path, bufnr)
+ return require('vim.filetype.detect').patch(bufnr)
+ end,
+ r = function(path, bufnr)
+ return require('vim.filetype.detect').r(bufnr)
+ end,
+ rdf = function(path, bufnr)
+ return require('vim.filetype.detect').redif(bufnr)
+ end,
+ rules = function(path, bufnr)
+ return require('vim.filetype.detect').rules(path)
+ end,
+ sc = function(path, bufnr)
+ return require('vim.filetype.detect').sc(bufnr)
+ end,
+ scd = function(path, bufnr)
+ return require('vim.filetype.detect').scd(bufnr)
+ end,
+ tcsh = function(path, bufnr)
+ return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
+ end,
+ sql = function(path, bufnr)
+ return vim.g.filetype_sql and vim.g.filetype_sql or 'sql'
+ end,
+ zsql = function(path, bufnr)
+ return vim.g.filetype_sql and vim.g.filetype_sql or 'sql'
+ end,
+ tex = function(path, bufnr)
+ return require('vim.filetype.detect').tex(path, bufnr)
+ end,
+ tf = function(path, bufnr)
+ return require('vim.filetype.detect').tf(bufnr)
+ end,
txt = function(path, bufnr)
- --helpfiles match *.txt, but should have a modeline as last line
- if not getline(bufnr, -1):match("vim:.*ft=help") then
- return "text"
+ return require('vim.filetype.detect').txt(bufnr)
+ end,
+ xml = function(path, bufnr)
+ return require('vim.filetype.detect').xml(bufnr)
+ end,
+ y = function(path, bufnr)
+ return require('vim.filetype.detect').y(bufnr)
+ end,
+ cmd = function(path, bufnr)
+ return M.getlines(bufnr, 1):find('^/%*') and 'rexx' or 'dosbatch'
+ end,
+ rul = function(path, bufnr)
+ return require('vim.filetype.detect').rul(bufnr)
+ end,
+ cpy = function(path, bufnr)
+ return M.getlines(bufnr, 1):find('^##') and 'python' or 'cobol'
+ end,
+ dsl = function(path, bufnr)
+ return M.getlines(bufnr, 1):find('^%s*<!') and 'dsl' or 'structurizr'
+ end,
+ smil = function(path, bufnr)
+ return M.getlines(bufnr, 1):find('<%?%s*xml.*%?>') and 'xml' or 'smil'
+ end,
+ smi = function(path, bufnr)
+ return require('vim.filetype.detect').smi(bufnr)
+ end,
+ install = function(path, bufnr)
+ return require('vim.filetype.detect').install(path, bufnr)
+ end,
+ pm = function(path, bufnr)
+ return require('vim.filetype.detect').pm(bufnr)
+ end,
+ me = function(path, bufnr)
+ return require('vim.filetype.detect').me(path)
+ end,
+ reg = function(path, bufnr)
+ return require('vim.filetype.detect').reg(bufnr)
+ end,
+ ttl = function(path, bufnr)
+ return require('vim.filetype.detect').ttl(bufnr)
+ end,
+ rc = function(path, bufnr)
+ if not path:find('/etc/Muttrc%.d/') then
+ return 'rc'
+ end
+ end,
+ rch = function(path, bufnr)
+ if not path:find('/etc/Muttrc%.d/') then
+ return 'rc'
end
end,
+ class = function(path, bufnr)
+ require('vim.filetype.detect').class(bufnr)
+ end,
+ sgml = function(path, bufnr)
+ return require('vim.filetype.detect').sgml(bufnr)
+ end,
+ sgm = function(path, bufnr)
+ return require('vim.filetype.detect').sgml(bufnr)
+ end,
+ t = function(path, bufnr)
+ local nroff = require('vim.filetype.detect').nroff(bufnr)
+ return nroff or require('vim.filetype.detect').perl(path, bufnr) or 'tads'
+ end,
+ -- Ignored extensions
+ bak = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+ end,
+ ['dpkg-bak'] = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+ end,
+ ['dpkg-dist'] = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+ end,
+ ['dpkg-old'] = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+ end,
+ ['dpkg-new'] = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+ end,
+ ['in'] = function(path, bufnr)
+ if vim.fs.basename(path) ~= 'configure.in' then
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+ end
+ end,
+ new = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+ end,
+ old = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+ end,
+ orig = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+ end,
+ pacsave = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+ end,
+ pacnew = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+ end,
+ rpmsave = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+ end,
+ rmpnew = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+ end,
-- END EXTENSION
}
local filename = {
-- BEGIN FILENAME
- ["a2psrc"] = "a2ps",
- ["/etc/a2ps.cfg"] = "a2ps",
- [".a2psrc"] = "a2ps",
- [".asoundrc"] = "alsaconf",
- ["/usr/share/alsa/alsa.conf"] = "alsaconf",
- ["/etc/asound.conf"] = "alsaconf",
- ["build.xml"] = "ant",
- [".htaccess"] = "apache",
- ["apt.conf"] = "aptconf",
- ["/.aptitude/config"] = "aptconf",
- ["=tagging-method"] = "arch",
- [".arch-inventory"] = "arch",
- ["GNUmakefile.am"] = "automake",
- ["named.root"] = "bindzone",
- WORKSPACE = "bzl",
- BUILD = "bzl",
- ["cabal.config"] = "cabalconfig",
- ["cabal.project"] = "cabalproject",
- calendar = "calendar",
- catalog = "catalog",
- ["/etc/cdrdao.conf"] = "cdrdaoconf",
- [".cdrdao"] = "cdrdaoconf",
- ["/etc/default/cdrdao"] = "cdrdaoconf",
- ["/etc/defaults/cdrdao"] = "cdrdaoconf",
- ["cfengine.conf"] = "cfengine",
- ["CMakeLists.txt"] = "cmake",
- ["auto.master"] = "conf",
- ["configure.in"] = "config",
- ["configure.ac"] = "config",
- [".cvsrc"] = "cvsrc",
- ["/debian/changelog"] = "debchangelog",
- ["changelog.dch"] = "debchangelog",
- ["changelog.Debian"] = "debchangelog",
- ["NEWS.dch"] = "debchangelog",
- ["NEWS.Debian"] = "debchangelog",
- ["/debian/control"] = "debcontrol",
- ["/debian/copyright"] = "debcopyright",
- ["/etc/apt/sources.list"] = "debsources",
- ["denyhosts.conf"] = "denyhosts",
- ["dict.conf"] = "dictconf",
- [".dictrc"] = "dictconf",
- ["/etc/DIR_COLORS"] = "dircolors",
- [".dir_colors"] = "dircolors",
- [".dircolors"] = "dircolors",
- ["/etc/dnsmasq.conf"] = "dnsmasq",
- Containerfile = "dockerfile",
- Dockerfile = "dockerfile",
- npmrc = "dosini",
- ["/etc/yum.conf"] = "dosini",
- ["/etc/pacman.conf"] = "dosini",
- [".npmrc"] = "dosini",
- [".editorconfig"] = "dosini",
- dune = "dune",
- jbuild = "dune",
- ["dune-workspace"] = "dune",
- ["dune-project"] = "dune",
- ["elinks.conf"] = "elinks",
- ["mix.lock"] = "elixir",
- ["filter-rules"] = "elmfilt",
- ["exim.conf"] = "exim",
- exports = "exports",
- [".fetchmailrc"] = "fetchmail",
- fvSchemes = function() vim.fn["dist#ft#FTfoam"]() end,
- fvSolution = function() vim.fn["dist#ft#FTfoam"]() end,
- fvConstraints = function() vim.fn["dist#ft#FTfoam"]() end,
- fvModels = function() vim.fn["dist#ft#FTfoam"]() end,
- fstab = "fstab",
- mtab = "fstab",
- [".gdbinit"] = "gdb",
- gdbinit = "gdb",
- ["lltxxxxx.txt"] = "gedcom",
- ["TAG_EDITMSG"] = "gitcommit",
- ["MERGE_MSG"] = "gitcommit",
- ["COMMIT_EDITMSG"] = "gitcommit",
- ["NOTES_EDITMSG"] = "gitcommit",
- ["EDIT_DESCRIPTION"] = "gitcommit",
- [".gitconfig"] = "gitconfig",
- [".gitmodules"] = "gitconfig",
- ["/.config/git/config"] = "gitconfig",
- ["/etc/gitconfig"] = "gitconfig",
- ["gitolite.conf"] = "gitolite",
- ["git-rebase-todo"] = "gitrebase",
- gkrellmrc = "gkrellmrc",
- [".gnashrc"] = "gnash",
- [".gnashpluginrc"] = "gnash",
- gnashpluginrc = "gnash",
- gnashrc = "gnash",
- [".gprc"] = "gp",
- ["/.gnupg/gpg.conf"] = "gpg",
- ["/.gnupg/options"] = "gpg",
- ["/var/backups/gshadow.bak"] = "group",
- ["/etc/gshadow"] = "group",
- ["/etc/group-"] = "group",
- ["/etc/gshadow.edit"] = "group",
- ["/etc/gshadow-"] = "group",
- ["/etc/group"] = "group",
- ["/var/backups/group.bak"] = "group",
- ["/etc/group.edit"] = "group",
- ["/boot/grub/menu.lst"] = "grub",
- ["/etc/grub.conf"] = "grub",
- ["/boot/grub/grub.conf"] = "grub",
- [".gtkrc"] = "gtkrc",
- gtkrc = "gtkrc",
- ["snort.conf"] = "hog",
- ["vision.conf"] = "hog",
- ["/etc/host.conf"] = "hostconf",
- ["/etc/hosts.allow"] = "hostsaccess",
- ["/etc/hosts.deny"] = "hostsaccess",
- ["/i3/config"] = "i3config",
- ["/sway/config"] = "i3config",
- ["/.sway/config"] = "i3config",
- ["/.i3/config"] = "i3config",
- ["/.icewm/menu"] = "icemenu",
- [".indent.pro"] = "indent",
- indentrc = "indent",
- inittab = "inittab",
- ["ipf.conf"] = "ipfilter",
- ["ipf6.conf"] = "ipfilter",
- ["ipf.rules"] = "ipfilter",
- [".eslintrc"] = "json",
- [".babelrc"] = "json",
- ["Pipfile.lock"] = "json",
- [".firebaserc"] = "json",
- [".prettierrc"] = "json",
- Kconfig = "kconfig",
- ["Kconfig.debug"] = "kconfig",
- ["lftp.conf"] = "lftp",
- [".lftprc"] = "lftp",
- ["/.libao"] = "libao",
- ["/etc/libao.conf"] = "libao",
- ["lilo.conf"] = "lilo",
- ["/etc/limits"] = "limits",
- [".emacs"] = "lisp",
- sbclrc = "lisp",
- [".sbclrc"] = "lisp",
- [".sawfishrc"] = "lisp",
- ["/etc/login.access"] = "loginaccess",
- ["/etc/login.defs"] = "logindefs",
- ["lynx.cfg"] = "lynx",
- ["m3overrides"] = "m3build",
- ["m3makefile"] = "m3build",
- ["cm3.cfg"] = "m3quake",
- [".followup"] = "mail",
- [".article"] = "mail",
- [".letter"] = "mail",
- ["/etc/aliases"] = "mailaliases",
- ["/etc/mail/aliases"] = "mailaliases",
- mailcap = "mailcap",
- [".mailcap"] = "mailcap",
- ["/etc/man.conf"] = "manconf",
- ["man.config"] = "manconf",
- ["meson.build"] = "meson",
- ["meson_options.txt"] = "meson",
- ["/etc/conf.modules"] = "modconf",
- ["/etc/modules"] = "modconf",
- ["/etc/modules.conf"] = "modconf",
- ["/.mplayer/config"] = "mplayerconf",
- ["mplayer.conf"] = "mplayerconf",
- mrxvtrc = "mrxvtrc",
- [".mrxvtrc"] = "mrxvtrc",
- ["/etc/nanorc"] = "nanorc",
- Neomuttrc = "neomuttrc",
- [".netrc"] = "netrc",
- [".ocamlinit"] = "ocaml",
- [".octaverc"] = "octave",
- octaverc = "octave",
- ["octave.conf"] = "octave",
- opam = "opam",
- ["/etc/pam.conf"] = "pamconf",
- ["pam_env.conf"] = "pamenv",
- [".pam_environment"] = "pamenv",
- ["/var/backups/passwd.bak"] = "passwd",
- ["/var/backups/shadow.bak"] = "passwd",
- ["/etc/passwd"] = "passwd",
- ["/etc/passwd-"] = "passwd",
- ["/etc/shadow.edit"] = "passwd",
- ["/etc/shadow-"] = "passwd",
- ["/etc/shadow"] = "passwd",
- ["/etc/passwd.edit"] = "passwd",
- ["pf.conf"] = "pf",
- ["main.cf"] = "pfmain",
- pinerc = "pine",
- [".pinercex"] = "pine",
- [".pinerc"] = "pine",
- pinercex = "pine",
- ["/etc/pinforc"] = "pinfo",
- ["/.pinforc"] = "pinfo",
- [".povrayrc"] = "povini",
- [".procmailrc"] = "procmail",
- [".procmail"] = "procmail",
- ["/etc/protocols"] = "protocols",
- [".pythonstartup"] = "python",
- [".pythonrc"] = "python",
- SConstruct = "python",
- ratpoisonrc = "ratpoison",
- [".ratpoisonrc"] = "ratpoison",
- v = "rcs",
- inputrc = "readline",
- [".inputrc"] = "readline",
- [".reminders"] = "remind",
- ["resolv.conf"] = "resolv",
- ["robots.txt"] = "robots",
- Gemfile = "ruby",
- Puppetfile = "ruby",
- [".irbrc"] = "ruby",
- irbrc = "ruby",
- ["smb.conf"] = "samba",
- screenrc = "screen",
- [".screenrc"] = "screen",
- ["/etc/sensors3.conf"] = "sensors",
- ["/etc/sensors.conf"] = "sensors",
- ["/etc/services"] = "services",
- ["/etc/serial.conf"] = "setserial",
- ["/etc/udev/cdsymlinks.conf"] = "sh",
- ["/etc/slp.conf"] = "slpconf",
- ["/etc/slp.reg"] = "slpreg",
- ["/etc/slp.spi"] = "slpspi",
- [".slrnrc"] = "slrnrc",
- ["sendmail.cf"] = "sm",
- ["squid.conf"] = "squid",
- ["/.ssh/config"] = "sshconfig",
- ["ssh_config"] = "sshconfig",
- ["sshd_config"] = "sshdconfig",
- ["/etc/sudoers"] = "sudoers",
- ["sudoers.tmp"] = "sudoers",
- ["/etc/sysctl.conf"] = "sysctl",
- tags = "tags",
- [".tclshrc"] = "tcl",
- [".wishrc"] = "tcl",
- ["tclsh.rc"] = "tcl",
- ["texmf.cnf"] = "texmf",
- COPYING = "text",
- README = "text",
- LICENSE = "text",
- AUTHORS = "text",
- tfrc = "tf",
- [".tfrc"] = "tf",
- ["tidy.conf"] = "tidy",
- tidyrc = "tidy",
- [".tidyrc"] = "tidy",
- ["/.cargo/config"] = "toml",
- Pipfile = "toml",
- ["Gopkg.lock"] = "toml",
- ["/.cargo/credentials"] = "toml",
- ["Cargo.lock"] = "toml",
- ["trustees.conf"] = "trustees",
- ["/etc/udev/udev.conf"] = "udevconf",
- ["/etc/updatedb.conf"] = "updatedb",
- ["fdrupstream.log"] = "upstreamlog",
- vgrindefs = "vgrindefs",
- [".exrc"] = "vim",
- ["_exrc"] = "vim",
- ["_viminfo"] = "viminfo",
- [".viminfo"] = "viminfo",
- [".wgetrc"] = "wget",
- wgetrc = "wget",
- [".wvdialrc"] = "wvdial",
- ["wvdial.conf"] = "wvdial",
- [".Xresources"] = "xdefaults",
- [".Xpdefaults"] = "xdefaults",
- ["xdm-config"] = "xdefaults",
- [".Xdefaults"] = "xdefaults",
- ["/etc/xinetd.conf"] = "xinetd",
- fglrxrc = "xml",
- ["/etc/blkid.tab"] = "xml",
- ["/etc/blkid.tab.old"] = "xml",
- ["/etc/zprofile"] = "zsh",
- [".zlogin"] = "zsh",
- [".zlogout"] = "zsh",
- [".zshrc"] = "zsh",
- [".zprofile"] = "zsh",
- [".zcompdump"] = "zsh",
- [".zshenv"] = "zsh",
- [".zfbfmarks"] = "zsh",
- [".alias"] = function() vim.fn["dist#ft#CSH"]() end,
- [".bashrc"] = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end,
- [".cshrc"] = function() vim.fn["dist#ft#CSH"]() end,
- [".kshrc"] = function() vim.fn["dist#ft#SetFileTypeSH"]("ksh") end,
- [".login"] = function() vim.fn["dist#ft#CSH"]() end,
- [".profile"] = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end,
- [".tcshrc"] = function() vim.fn["dist#ft#SetFileTypeShell"]("tcsh") end,
- ["/etc/profile"] = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end,
- APKBUILD = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end,
- PKGBUILD = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end,
- ["bash.bashrc"] = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end,
- bashrc = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end,
- crontab = starsetf('crontab'),
- ["csh.cshrc"] = function() vim.fn["dist#ft#CSH"]() end,
- ["csh.login"] = function() vim.fn["dist#ft#CSH"]() end,
- ["csh.logout"] = function() vim.fn["dist#ft#CSH"]() end,
- ["indent.pro"] = function() vim.fn["dist#ft#ProtoCheck"]('indent') end,
- ["tcsh.login"] = function() vim.fn["dist#ft#SetFileTypeShell"]("tcsh") end,
- ["tcsh.tcshrc"] = function() vim.fn["dist#ft#SetFileTypeShell"]("tcsh") end,
+ ['a2psrc'] = 'a2ps',
+ ['/etc/a2ps.cfg'] = 'a2ps',
+ ['.a2psrc'] = 'a2ps',
+ ['.asoundrc'] = 'alsaconf',
+ ['/usr/share/alsa/alsa.conf'] = 'alsaconf',
+ ['/etc/asound.conf'] = 'alsaconf',
+ ['build.xml'] = 'ant',
+ ['.htaccess'] = 'apache',
+ ['apt.conf'] = 'aptconf',
+ ['/.aptitude/config'] = 'aptconf',
+ ['=tagging-method'] = 'arch',
+ ['.arch-inventory'] = 'arch',
+ ['GNUmakefile.am'] = 'automake',
+ ['named.root'] = 'bindzone',
+ WORKSPACE = 'bzl',
+ BUILD = 'bzl',
+ ['cabal.project'] = 'cabalproject',
+ [vim.env.HOME .. '/cabal.config'] = 'cabalconfig',
+ ['cabal.config'] = 'cabalconfig',
+ calendar = 'calendar',
+ catalog = 'catalog',
+ ['/etc/cdrdao.conf'] = 'cdrdaoconf',
+ ['.cdrdao'] = 'cdrdaoconf',
+ ['/etc/default/cdrdao'] = 'cdrdaoconf',
+ ['/etc/defaults/cdrdao'] = 'cdrdaoconf',
+ ['cfengine.conf'] = 'cfengine',
+ ['CMakeLists.txt'] = 'cmake',
+ ['.alias'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
+ end,
+ ['.cshrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
+ end,
+ ['.login'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
+ end,
+ ['csh.cshrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
+ end,
+ ['csh.login'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
+ end,
+ ['csh.logout'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
+ end,
+ ['auto.master'] = 'conf',
+ ['configure.in'] = 'config',
+ ['configure.ac'] = 'config',
+ crontab = 'crontab',
+ ['.cvsrc'] = 'cvsrc',
+ ['/debian/changelog'] = 'debchangelog',
+ ['changelog.dch'] = 'debchangelog',
+ ['changelog.Debian'] = 'debchangelog',
+ ['NEWS.dch'] = 'debchangelog',
+ ['NEWS.Debian'] = 'debchangelog',
+ ['/debian/control'] = 'debcontrol',
+ ['/debian/copyright'] = 'debcopyright',
+ ['/etc/apt/sources.list'] = 'debsources',
+ ['denyhosts.conf'] = 'denyhosts',
+ ['dict.conf'] = 'dictconf',
+ ['.dictrc'] = 'dictconf',
+ ['/etc/DIR_COLORS'] = 'dircolors',
+ ['.dir_colors'] = 'dircolors',
+ ['.dircolors'] = 'dircolors',
+ ['/etc/dnsmasq.conf'] = 'dnsmasq',
+ Containerfile = 'dockerfile',
+ dockerfile = 'dockerfile',
+ Dockerfile = 'dockerfile',
+ npmrc = 'dosini',
+ ['/etc/yum.conf'] = 'dosini',
+ ['.npmrc'] = 'dosini',
+ ['.editorconfig'] = 'dosini',
+ ['/etc/pacman.conf'] = 'confini',
+ ['mpv.conf'] = 'confini',
+ dune = 'dune',
+ jbuild = 'dune',
+ ['dune-workspace'] = 'dune',
+ ['dune-project'] = 'dune',
+ ['elinks.conf'] = 'elinks',
+ ['mix.lock'] = 'elixir',
+ ['filter-rules'] = 'elmfilt',
+ ['exim.conf'] = 'exim',
+ exports = 'exports',
+ ['.fetchmailrc'] = 'fetchmail',
+ fvSchemes = function(path, bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
+ end,
+ fvSolution = function(path, bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
+ end,
+ fvConstraints = function(path, bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
+ end,
+ fvModels = function(path, bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
+ end,
+ fstab = 'fstab',
+ mtab = 'fstab',
+ ['.gdbinit'] = 'gdb',
+ gdbinit = 'gdb',
+ ['.gdbearlyinit'] = 'gdb',
+ gdbearlyinit = 'gdb',
+ ['lltxxxxx.txt'] = 'gedcom',
+ ['TAG_EDITMSG'] = 'gitcommit',
+ ['MERGE_MSG'] = 'gitcommit',
+ ['COMMIT_EDITMSG'] = 'gitcommit',
+ ['NOTES_EDITMSG'] = 'gitcommit',
+ ['EDIT_DESCRIPTION'] = 'gitcommit',
+ ['.gitconfig'] = 'gitconfig',
+ ['.gitmodules'] = 'gitconfig',
+ ['gitolite.conf'] = 'gitolite',
+ ['git-rebase-todo'] = 'gitrebase',
+ gkrellmrc = 'gkrellmrc',
+ ['.gnashrc'] = 'gnash',
+ ['.gnashpluginrc'] = 'gnash',
+ gnashpluginrc = 'gnash',
+ gnashrc = 'gnash',
+ ['.gnuplot'] = 'gnuplot',
+ ['go.work'] = 'gowork',
+ ['.gprc'] = 'gp',
+ ['/.gnupg/gpg.conf'] = 'gpg',
+ ['/.gnupg/options'] = 'gpg',
+ ['/var/backups/gshadow.bak'] = 'group',
+ ['/etc/gshadow'] = 'group',
+ ['/etc/group-'] = 'group',
+ ['/etc/gshadow.edit'] = 'group',
+ ['/etc/gshadow-'] = 'group',
+ ['/etc/group'] = 'group',
+ ['/var/backups/group.bak'] = 'group',
+ ['/etc/group.edit'] = 'group',
+ ['/boot/grub/menu.lst'] = 'grub',
+ ['/etc/grub.conf'] = 'grub',
+ ['/boot/grub/grub.conf'] = 'grub',
+ ['.gtkrc'] = 'gtkrc',
+ gtkrc = 'gtkrc',
+ ['snort.conf'] = 'hog',
+ ['vision.conf'] = 'hog',
+ ['/etc/host.conf'] = 'hostconf',
+ ['/etc/hosts.allow'] = 'hostsaccess',
+ ['/etc/hosts.deny'] = 'hostsaccess',
+ ['/i3/config'] = 'i3config',
+ ['/sway/config'] = 'i3config',
+ ['/.sway/config'] = 'i3config',
+ ['/.i3/config'] = 'i3config',
+ ['/.icewm/menu'] = 'icemenu',
+ ['.indent.pro'] = 'indent',
+ indentrc = 'indent',
+ inittab = 'inittab',
+ ['ipf.conf'] = 'ipfilter',
+ ['ipf6.conf'] = 'ipfilter',
+ ['ipf.rules'] = 'ipfilter',
+ ['.eslintrc'] = 'json',
+ ['.babelrc'] = 'json',
+ ['Pipfile.lock'] = 'json',
+ ['.firebaserc'] = 'json',
+ ['.prettierrc'] = 'json',
+ Kconfig = 'kconfig',
+ ['Kconfig.debug'] = 'kconfig',
+ ['lftp.conf'] = 'lftp',
+ ['.lftprc'] = 'lftp',
+ ['/.libao'] = 'libao',
+ ['/etc/libao.conf'] = 'libao',
+ ['lilo.conf'] = 'lilo',
+ ['/etc/limits'] = 'limits',
+ ['.emacs'] = 'lisp',
+ sbclrc = 'lisp',
+ ['.sbclrc'] = 'lisp',
+ ['.sawfishrc'] = 'lisp',
+ ['/etc/login.access'] = 'loginaccess',
+ ['/etc/login.defs'] = 'logindefs',
+ ['lynx.cfg'] = 'lynx',
+ ['m3overrides'] = 'm3build',
+ ['m3makefile'] = 'm3build',
+ ['cm3.cfg'] = 'm3quake',
+ ['.followup'] = 'mail',
+ ['.article'] = 'mail',
+ ['.letter'] = 'mail',
+ ['/etc/aliases'] = 'mailaliases',
+ ['/etc/mail/aliases'] = 'mailaliases',
+ mailcap = 'mailcap',
+ ['.mailcap'] = 'mailcap',
+ ['/etc/man.conf'] = 'manconf',
+ ['man.config'] = 'manconf',
+ ['maxima-init.mac'] = 'maxima',
+ ['meson.build'] = 'meson',
+ ['meson_options.txt'] = 'meson',
+ ['/etc/conf.modules'] = 'modconf',
+ ['/etc/modules'] = 'modconf',
+ ['/etc/modules.conf'] = 'modconf',
+ ['/.mplayer/config'] = 'mplayerconf',
+ ['mplayer.conf'] = 'mplayerconf',
+ mrxvtrc = 'mrxvtrc',
+ ['.mrxvtrc'] = 'mrxvtrc',
+ ['/etc/nanorc'] = 'nanorc',
+ Neomuttrc = 'neomuttrc',
+ ['.netrc'] = 'netrc',
+ NEWS = function(path, bufnr)
+ return require('vim.filetype.detect').news(bufnr)
+ end,
+ ['.ocamlinit'] = 'ocaml',
+ ['.octaverc'] = 'octave',
+ octaverc = 'octave',
+ ['octave.conf'] = 'octave',
+ opam = 'opam',
+ ['/etc/pam.conf'] = 'pamconf',
+ ['pam_env.conf'] = 'pamenv',
+ ['.pam_environment'] = 'pamenv',
+ ['/var/backups/passwd.bak'] = 'passwd',
+ ['/var/backups/shadow.bak'] = 'passwd',
+ ['/etc/passwd'] = 'passwd',
+ ['/etc/passwd-'] = 'passwd',
+ ['/etc/shadow.edit'] = 'passwd',
+ ['/etc/shadow-'] = 'passwd',
+ ['/etc/shadow'] = 'passwd',
+ ['/etc/passwd.edit'] = 'passwd',
+ ['pf.conf'] = 'pf',
+ ['main.cf'] = 'pfmain',
+ pinerc = 'pine',
+ ['.pinercex'] = 'pine',
+ ['.pinerc'] = 'pine',
+ pinercex = 'pine',
+ ['/etc/pinforc'] = 'pinfo',
+ ['/.pinforc'] = 'pinfo',
+ ['.povrayrc'] = 'povini',
+ ['printcap'] = function(path, bufnr)
+ return 'ptcap', function(b)
+ vim.b[b].ptcap_type = 'print'
+ end
+ end,
+ ['termcap'] = function(path, bufnr)
+ return 'ptcap', function(b)
+ vim.b[b].ptcap_type = 'term'
+ end
+ end,
+ ['.procmailrc'] = 'procmail',
+ ['.procmail'] = 'procmail',
+ ['indent.pro'] = function(path, bufnr)
+ return require('vim.filetype.detect').proto(bufnr, 'indent')
+ end,
+ ['/etc/protocols'] = 'protocols',
+ ['INDEX'] = function(path, bufnr)
+ return require('vim.filetype.detect').psf(bufnr)
+ end,
+ ['INFO'] = function(path, bufnr)
+ return require('vim.filetype.detect').psf(bufnr)
+ end,
+ ['.pythonstartup'] = 'python',
+ ['.pythonrc'] = 'python',
+ SConstruct = 'python',
+ ratpoisonrc = 'ratpoison',
+ ['.ratpoisonrc'] = 'ratpoison',
+ inputrc = 'readline',
+ ['.inputrc'] = 'readline',
+ ['.reminders'] = 'remind',
+ ['resolv.conf'] = 'resolv',
+ ['robots.txt'] = 'robots',
+ Gemfile = 'ruby',
+ Puppetfile = 'ruby',
+ ['.irbrc'] = 'ruby',
+ irbrc = 'ruby',
+ Vagrantfile = 'ruby',
+ ['smb.conf'] = 'samba',
+ screenrc = 'screen',
+ ['.screenrc'] = 'screen',
+ ['/etc/sensors3.conf'] = 'sensors',
+ ['/etc/sensors.conf'] = 'sensors',
+ ['/etc/services'] = 'services',
+ ['/etc/serial.conf'] = 'setserial',
+ ['/etc/udev/cdsymlinks.conf'] = 'sh',
+ ['bash.bashrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
+ end,
+ bashrc = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
+ end,
+ ['.bashrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
+ end,
+ ['.env'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
+ end,
+ ['.kshrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'ksh')
+ end,
+ ['.profile'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
+ end,
+ ['/etc/profile'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
+ end,
+ APKBUILD = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
+ end,
+ PKGBUILD = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
+ end,
+ ['.tcshrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
+ end,
+ ['tcsh.login'] = function(path, bufnr)
+ return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
+ end,
+ ['tcsh.tcshrc'] = function(path, bufnr)
+ return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
+ end,
+ ['/etc/slp.conf'] = 'slpconf',
+ ['/etc/slp.reg'] = 'slpreg',
+ ['/etc/slp.spi'] = 'slpspi',
+ ['.slrnrc'] = 'slrnrc',
+ ['sendmail.cf'] = 'sm',
+ ['squid.conf'] = 'squid',
+ ['ssh_config'] = 'sshconfig',
+ ['sshd_config'] = 'sshdconfig',
+ ['/etc/sudoers'] = 'sudoers',
+ ['sudoers.tmp'] = 'sudoers',
+ ['/etc/sysctl.conf'] = 'sysctl',
+ tags = 'tags',
+ ['pending.data'] = 'taskdata',
+ ['completed.data'] = 'taskdata',
+ ['undo.data'] = 'taskdata',
+ ['.tclshrc'] = 'tcl',
+ ['.wishrc'] = 'tcl',
+ ['tclsh.rc'] = 'tcl',
+ ['texmf.cnf'] = 'texmf',
+ COPYING = 'text',
+ README = 'text',
+ LICENSE = 'text',
+ AUTHORS = 'text',
+ tfrc = 'tf',
+ ['.tfrc'] = 'tf',
+ ['tidy.conf'] = 'tidy',
+ tidyrc = 'tidy',
+ ['.tidyrc'] = 'tidy',
+ ['.tmux.conf'] = 'tmux',
+ ['/.cargo/config'] = 'toml',
+ Pipfile = 'toml',
+ ['Gopkg.lock'] = 'toml',
+ ['/.cargo/credentials'] = 'toml',
+ ['Cargo.lock'] = 'toml',
+ ['trustees.conf'] = 'trustees',
+ ['/etc/udev/udev.conf'] = 'udevconf',
+ ['/etc/updatedb.conf'] = 'updatedb',
+ ['fdrupstream.log'] = 'upstreamlog',
+ vgrindefs = 'vgrindefs',
+ ['.exrc'] = 'vim',
+ ['_exrc'] = 'vim',
+ ['_viminfo'] = 'viminfo',
+ ['.viminfo'] = 'viminfo',
+ ['.wgetrc'] = 'wget',
+ ['.wget2rc'] = 'wget2',
+ wgetrc = 'wget',
+ wget2rc = 'wget2',
+ ['.wvdialrc'] = 'wvdial',
+ ['wvdial.conf'] = 'wvdial',
+ ['.Xresources'] = 'xdefaults',
+ ['.Xpdefaults'] = 'xdefaults',
+ ['xdm-config'] = 'xdefaults',
+ ['.Xdefaults'] = 'xdefaults',
+ ['xorg.conf'] = function(path, bufnr)
+ return 'xf86conf', function(b)
+ vim.b[b].xf86conf_xfree86_version = 4
+ end
+ end,
+ ['xorg.conf-4'] = function(path, bufnr)
+ return 'xf86conf', function(b)
+ vim.b[b].xf86conf_xfree86_version = 4
+ end
+ end,
+ ['XF86Config'] = function(path, bufnr)
+ return require('vim.filetype.detect').xfree86()
+ end,
+ ['/etc/xinetd.conf'] = 'xinetd',
+ fglrxrc = 'xml',
+ ['/etc/blkid.tab'] = 'xml',
+ ['/etc/blkid.tab.old'] = 'xml',
+ ['/etc/zprofile'] = 'zsh',
+ ['.zlogin'] = 'zsh',
+ ['.zlogout'] = 'zsh',
+ ['.zshrc'] = 'zsh',
+ ['.zprofile'] = 'zsh',
+ ['.zcompdump'] = 'zsh',
+ ['.zshenv'] = 'zsh',
+ ['.zfbfmarks'] = 'zsh',
-- END FILENAME
}
local pattern = {
-- BEGIN PATTERN
- [".*/etc/a2ps/.*%.cfg"] = "a2ps",
- [".*/etc/a2ps%.cfg"] = "a2ps",
- [".*/usr/share/alsa/alsa%.conf"] = "alsaconf",
- [".*/etc/asound%.conf"] = "alsaconf",
- [".*/etc/apache2/sites%-.*/.*%.com"] = "apache",
- [".*/etc/httpd/.*%.conf"] = "apache",
- [".*/%.aptitude/config"] = "aptconf",
- ["[mM]akefile%.am"] = "automake",
- [".*bsd"] = "bsdl",
- ["bzr_log%..*"] = "bzr",
- [".*enlightenment/.*%.cfg"] = "c",
- [".*/etc/defaults/cdrdao"] = "cdrdaoconf",
- [".*/etc/cdrdao%.conf"] = "cdrdaoconf",
- [".*/etc/default/cdrdao"] = "cdrdaoconf",
- [".*hgrc"] = "cfg",
- [".*%.%.ch"] = "chill",
- [".*%.cmake%.in"] = "cmake",
- [".*/debian/changelog"] = "debchangelog",
- [".*/debian/control"] = "debcontrol",
- [".*/debian/copyright"] = "debcopyright",
- [".*/etc/apt/sources%.list%.d/.*%.list"] = "debsources",
- [".*/etc/apt/sources%.list"] = "debsources",
- ["dictd.*%.conf"] = "dictdconf",
- [".*/etc/DIR_COLORS"] = "dircolors",
- [".*/etc/dnsmasq%.conf"] = "dnsmasq",
- ["php%.ini%-.*"] = "dosini",
- [".*/etc/pacman%.conf"] = "dosini",
- [".*/etc/yum%.conf"] = "dosini",
- [".*lvs"] = "dracula",
- [".*lpe"] = "dracula",
- [".*esmtprc"] = "esmtprc",
- [".*Eterm/.*%.cfg"] = "eterm",
- [".*%.git/modules/.*/config"] = "gitconfig",
- [".*%.git/config"] = "gitconfig",
- [".*/%.config/git/config"] = "gitconfig",
- ["%.gitsendemail%.msg%......."] = "gitsendemail",
- ["gkrellmrc_."] = "gkrellmrc",
- [".*/usr/.*/gnupg/options%.skel"] = "gpg",
- [".*/%.gnupg/options"] = "gpg",
- [".*/%.gnupg/gpg%.conf"] = "gpg",
- [".*/etc/group"] = "group",
- [".*/etc/gshadow"] = "group",
- [".*/etc/group%.edit"] = "group",
- [".*/var/backups/gshadow%.bak"] = "group",
- [".*/etc/group-"] = "group",
- [".*/etc/gshadow-"] = "group",
- [".*/var/backups/group%.bak"] = "group",
- [".*/etc/gshadow%.edit"] = "group",
- [".*/boot/grub/grub%.conf"] = "grub",
- [".*/boot/grub/menu%.lst"] = "grub",
- [".*/etc/grub%.conf"] = "grub",
- ["hg%-editor%-.*%.txt"] = "hgcommit",
- [".*/etc/host%.conf"] = "hostconf",
- [".*/etc/hosts%.deny"] = "hostsaccess",
- [".*/etc/hosts%.allow"] = "hostsaccess",
- [".*%.html%.m4"] = "htmlm4",
- [".*/%.i3/config"] = "i3config",
- [".*/sway/config"] = "i3config",
- [".*/i3/config"] = "i3config",
- [".*/%.sway/config"] = "i3config",
- [".*/%.icewm/menu"] = "icemenu",
- [".*/etc/initng/.*/.*%.i"] = "initng",
- [".*%.properties_.."] = "jproperties",
- [".*%.properties_.._.."] = "jproperties",
- [".*lftp/rc"] = "lftp",
- [".*/%.libao"] = "libao",
- [".*/etc/libao%.conf"] = "libao",
- [".*/etc/.*limits%.conf"] = "limits",
- [".*/etc/limits"] = "limits",
- [".*/etc/.*limits%.d/.*%.conf"] = "limits",
- [".*/LiteStep/.*/.*%.rc"] = "litestep",
- [".*/etc/login%.access"] = "loginaccess",
- [".*/etc/login%.defs"] = "logindefs",
- [".*/etc/mail/aliases"] = "mailaliases",
- [".*/etc/aliases"] = "mailaliases",
- [".*[mM]akefile"] = "make",
- [".*/etc/man%.conf"] = "manconf",
- [".*/etc/modules%.conf"] = "modconf",
- [".*/etc/conf%.modules"] = "modconf",
- [".*/etc/modules"] = "modconf",
- [".*%.[mi][3g]"] = "modula3",
- [".*/%.mplayer/config"] = "mplayerconf",
- ["rndc.*%.conf"] = "named",
- ["rndc.*%.key"] = "named",
- ["named.*%.conf"] = "named",
- [".*/etc/nanorc"] = "nanorc",
- [".*%.NS[ACGLMNPS]"] = "natural",
- ["nginx.*%.conf"] = "nginx",
- [".*/etc/nginx/.*"] = "nginx",
- [".*nginx%.conf"] = "nginx",
- [".*/nginx/.*%.conf"] = "nginx",
- [".*/usr/local/nginx/conf/.*"] = "nginx",
- [".*%.ml%.cppo"] = "ocaml",
- [".*%.mli%.cppo"] = "ocaml",
- [".*%.opam%.template"] = "opam",
- [".*%.[Oo][Pp][Ll]"] = "opl",
- [".*/etc/pam%.conf"] = "pamconf",
- [".*/etc/passwd-"] = "passwd",
- [".*/etc/shadow"] = "passwd",
- [".*/etc/shadow%.edit"] = "passwd",
- [".*/var/backups/shadow%.bak"] = "passwd",
- [".*/var/backups/passwd%.bak"] = "passwd",
- [".*/etc/passwd"] = "passwd",
- [".*/etc/passwd%.edit"] = "passwd",
- [".*/etc/shadow-"] = "passwd",
- [".*/%.pinforc"] = "pinfo",
- [".*/etc/pinforc"] = "pinfo",
- [".*/etc/protocols"] = "protocols",
- [".*baseq[2-3]/.*%.cfg"] = "quake",
- [".*quake[1-3]/.*%.cfg"] = "quake",
- [".*id1/.*%.cfg"] = "quake",
- ["[rR]antfile"] = "ruby",
- ["[rR]akefile"] = "ruby",
- [".*/etc/sensors%.conf"] = "sensors",
- [".*/etc/sensors3%.conf"] = "sensors",
- [".*/etc/services"] = "services",
- [".*/etc/serial%.conf"] = "setserial",
- [".*/etc/udev/cdsymlinks%.conf"] = "sh",
- [".*%._sst%.meta"] = "sisu",
- [".*%.%-sst%.meta"] = "sisu",
- [".*%.sst%.meta"] = "sisu",
- [".*/etc/slp%.conf"] = "slpconf",
- [".*/etc/slp%.reg"] = "slpreg",
- [".*/etc/slp%.spi"] = "slpspi",
- [".*/etc/ssh/ssh_config%.d/.*%.conf"] = "sshconfig",
- [".*/%.ssh/config"] = "sshconfig",
- [".*/etc/ssh/sshd_config%.d/.*%.conf"] = "sshdconfig",
- [".*/etc/sudoers"] = "sudoers",
- ["svn%-commit.*%.tmp"] = "svn",
- [".*%.swift%.gyb"] = "swiftgyb",
- [".*/etc/sysctl%.conf"] = "sysctl",
- [".*/etc/sysctl%.d/.*%.conf"] = "sysctl",
- [".*/etc/systemd/.*%.conf%.d/.*%.conf"] = "systemd",
- [".*/%.config/systemd/user/.*%.d/.*%.conf"] = "systemd",
- [".*/etc/systemd/system/.*%.d/.*%.conf"] = "systemd",
- [".*%.t%.html"] = "tilde",
- [".*/%.cargo/config"] = "toml",
- [".*/%.cargo/credentials"] = "toml",
- [".*/etc/udev/udev%.conf"] = "udevconf",
- [".*/etc/udev/permissions%.d/.*%.permissions"] = "udevperm",
- [".*/etc/updatedb%.conf"] = "updatedb",
- [".*/%.init/.*%.override"] = "upstart",
- [".*/usr/share/upstart/.*%.conf"] = "upstart",
- [".*/%.config/upstart/.*%.override"] = "upstart",
- [".*/etc/init/.*%.conf"] = "upstart",
- [".*/etc/init/.*%.override"] = "upstart",
- [".*/%.config/upstart/.*%.conf"] = "upstart",
- [".*/%.init/.*%.conf"] = "upstart",
- [".*/usr/share/upstart/.*%.override"] = "upstart",
- [".*%.ws[fc]"] = "wsh",
- [".*/etc/xinetd%.conf"] = "xinetd",
- [".*/etc/blkid%.tab"] = "xml",
- [".*/etc/blkid%.tab%.old"] = "xml",
- [".*%.vbproj%.user"] = "xml",
- [".*%.fsproj%.user"] = "xml",
- [".*%.csproj%.user"] = "xml",
- [".*/etc/xdg/menus/.*%.menu"] = "xml",
- [".*Xmodmap"] = "xmodmap",
- [".*/etc/zprofile"] = "zsh",
- ["%.bash[_-]aliases"] = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end,
- ["%.bash[_-]logout"] = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end,
- ["%.bash[_-]profile"] = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end,
- ["%.cshrc.*"] = function() vim.fn["dist#ft#CSH"]() end,
- ["%.gtkrc.*"] = starsetf('gtkrc'),
- ["%.kshrc.*"] = function() vim.fn["dist#ft#SetFileTypeSH"]("ksh") end,
- ["%.login.*"] = function() vim.fn["dist#ft#CSH"]() end,
- ["%.neomuttrc.*"] = starsetf('neomuttrc'),
- ["%.profile.*"] = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end,
- ["%.reminders.*"] = starsetf('remind'),
- ["%.tcshrc.*"] = function() vim.fn["dist#ft#SetFileTypeShell"]("tcsh") end,
- ["%.zcompdump.*"] = starsetf('zsh'),
- ["%.zlog.*"] = starsetf('zsh'),
- ["%.zsh.*"] = starsetf('zsh'),
- [".*%.[1-9]"] = function() vim.fn["dist#ft#FTnroff"]() end,
- [".*%.[aA]"] = function() vim.fn["dist#ft#FTasm"]() end,
- [".*%.[sS]"] = function() vim.fn["dist#ft#FTasm"]() end,
- [".*%.properties_.._.._.*"] = starsetf('jproperties'),
- [".*%.vhdl_[0-9].*"] = starsetf('vhdl'),
- [".*/%.fvwm/.*"] = starsetf('fvwm'),
- [".*/%.gitconfig%.d/.*"] = starsetf('gitconfig'),
- [".*/%.neomutt/neomuttrc.*"] = starsetf('neomuttrc'),
- [".*/Xresources/.*"] = starsetf('xdefaults'),
- [".*/app%-defaults/.*"] = starsetf('xdefaults'),
- [".*/bind/db%..*"] = starsetf('bindzone'),
- [".*/debian/patches/.*"] = function() vim.fn["dist#ft#Dep3patch"]() end,
- [".*/etc/Muttrc%.d/.*"] = starsetf('muttrc'),
- [".*/etc/apache2/.*%.conf.*"] = starsetf('apache'),
- [".*/etc/apache2/conf%..*/.*"] = starsetf('apache'),
- [".*/etc/apache2/mods%-.*/.*"] = starsetf('apache'),
- [".*/etc/apache2/sites%-.*/.*"] = starsetf('apache'),
- [".*/etc/cron%.d/.*"] = starsetf('crontab'),
- [".*/etc/dnsmasq%.d/.*"] = starsetf('dnsmasq'),
- [".*/etc/httpd/conf%..*/.*"] = starsetf('apache'),
- [".*/etc/httpd/conf%.d/.*%.conf.*"] = starsetf('apache'),
- [".*/etc/httpd/mods%-.*/.*"] = starsetf('apache'),
- [".*/etc/httpd/sites%-.*/.*"] = starsetf('apache'),
- [".*/etc/logcheck/.*%.d.*/.*"] = starsetf('logcheck'),
- [".*/etc/modprobe%..*"] = starsetf('modconf'),
- [".*/etc/pam%.d/.*"] = starsetf('pamconf'),
- [".*/etc/profile"] = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end,
- [".*/etc/proftpd/.*%.conf.*"] = starsetf('apachestyle'),
- [".*/etc/proftpd/conf%..*/.*"] = starsetf('apachestyle'),
- [".*/etc/sudoers%.d/.*"] = starsetf('sudoers'),
- [".*/etc/xinetd%.d/.*"] = starsetf('xinetd'),
- [".*/etc/yum%.repos%.d/.*"] = starsetf('dosini'),
- [".*/gitolite%-admin/conf/.*"] = starsetf('gitolite'),
- [".*/named/db%..*"] = starsetf('bindzone'),
- [".*/tmp/lltmp.*"] = starsetf('gedcom'),
- [".*asterisk.*/.*voicemail%.conf.*"] = starsetf('asteriskvm'),
- [".*asterisk/.*%.conf.*"] = starsetf('asterisk'),
- [".*vimrc.*"] = starsetf('vim'),
- [".*xmodmap.*"] = starsetf('xmodmap'),
- ["/etc/gitconfig%.d/.*"] = starsetf('gitconfig'),
- ["/etc/hostname%..*"] = starsetf('config'),
- ["Containerfile%..*"] = starsetf('dockerfile'),
- ["Dockerfile%..*"] = starsetf('dockerfile'),
- ["JAM.*%..*"] = starsetf('jam'),
- ["Kconfig%..*"] = starsetf('kconfig'),
- ["Neomuttrc.*"] = starsetf('neomuttrc'),
- ["Prl.*%..*"] = starsetf('jam'),
- ["Xresources.*"] = starsetf('xdefaults'),
- ["[mM]akefile.*"] = starsetf('make'),
- ["[rR]akefile.*"] = starsetf('ruby'),
- ["access%.conf.*"] = starsetf('apache'),
- ["apache%.conf.*"] = starsetf('apache'),
- ["apache2%.conf.*"] = starsetf('apache'),
- ["bash%-fc[-%.]"] = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end,
- ["cabal%.project%..*"] = starsetf('cabalproject'),
- ["crontab%..*"] = starsetf('crontab'),
- ["drac%..*"] = starsetf('dracula'),
- ["gtkrc.*"] = starsetf('gtkrc'),
- ["httpd%.conf.*"] = starsetf('apache'),
- ["lilo%.conf.*"] = starsetf('lilo'),
- ["neomuttrc.*"] = starsetf('neomuttrc'),
- ["proftpd%.conf.*"] = starsetf('apachestyle'),
- ["reportbug%-.*"] = starsetf('mail'),
- ["sgml%.catalog.*"] = starsetf('catalog'),
- ["srm%.conf.*"] = starsetf('apache'),
- ["tmac%..*"] = starsetf('nroff'),
- ["zlog.*"] = starsetf('zsh'),
- ["zsh.*"] = starsetf('zsh'),
- ["ae%d+%.txt"] = 'mail',
- ["[a-zA-Z0-9]*Dict"] = function() vim.fn["dist#ft#FTfoam"]() end,
- ["[a-zA-Z0-9]*Dict%..*"] = function() vim.fn["dist#ft#FTfoam"]() end,
- ["[a-zA-Z]*Properties"] = function() vim.fn["dist#ft#FTfoam"]() end,
- ["[a-zA-Z]*Properties%..*"] = function() vim.fn["dist#ft#FTfoam"]() end,
- [".*Transport%..*"] = function() vim.fn["dist#ft#FTfoam"]() end,
- [".*/constant/g"] = function() vim.fn["dist#ft#FTfoam"]() end,
- [".*/0/.*"] = function() vim.fn["dist#ft#FTfoam"]() end,
- [".*/0%.orig/.*"] = function() vim.fn["dist#ft#FTfoam"]() end,
+ ['.*/etc/a2ps/.*%.cfg'] = 'a2ps',
+ ['.*/etc/a2ps%.cfg'] = 'a2ps',
+ ['.*/usr/share/alsa/alsa%.conf'] = 'alsaconf',
+ ['.*/etc/asound%.conf'] = 'alsaconf',
+ ['.*/etc/apache2/sites%-.*/.*%.com'] = 'apache',
+ ['.*/etc/httpd/.*%.conf'] = 'apache',
+ ['.*/etc/apache2/.*%.conf.*'] = starsetf('apache'),
+ ['.*/etc/apache2/conf%..*/.*'] = starsetf('apache'),
+ ['.*/etc/apache2/mods%-.*/.*'] = starsetf('apache'),
+ ['.*/etc/apache2/sites%-.*/.*'] = starsetf('apache'),
+ ['access%.conf.*'] = starsetf('apache'),
+ ['apache%.conf.*'] = starsetf('apache'),
+ ['apache2%.conf.*'] = starsetf('apache'),
+ ['httpd%.conf.*'] = starsetf('apache'),
+ ['srm%.conf.*'] = starsetf('apache'),
+ ['.*/etc/httpd/conf%..*/.*'] = starsetf('apache'),
+ ['.*/etc/httpd/conf%.d/.*%.conf.*'] = starsetf('apache'),
+ ['.*/etc/httpd/mods%-.*/.*'] = starsetf('apache'),
+ ['.*/etc/httpd/sites%-.*/.*'] = starsetf('apache'),
+ ['.*/etc/proftpd/.*%.conf.*'] = starsetf('apachestyle'),
+ ['.*/etc/proftpd/conf%..*/.*'] = starsetf('apachestyle'),
+ ['proftpd%.conf.*'] = starsetf('apachestyle'),
+ ['.*asterisk/.*%.conf.*'] = starsetf('asterisk'),
+ ['.*asterisk.*/.*voicemail%.conf.*'] = starsetf('asteriskvm'),
+ ['.*/%.aptitude/config'] = 'aptconf',
+ ['.*%.[aA]'] = function(path, bufnr)
+ return require('vim.filetype.detect').asm(bufnr)
+ end,
+ ['.*%.[sS]'] = function(path, bufnr)
+ return require('vim.filetype.detect').asm(bufnr)
+ end,
+ ['[mM]akefile%.am'] = 'automake',
+ ['.*/bind/db%..*'] = starsetf('bindzone'),
+ ['.*/named/db%..*'] = starsetf('bindzone'),
+ ['.*/build/conf/.*%.conf'] = 'bitbake',
+ ['.*/meta/conf/.*%.conf'] = 'bitbake',
+ ['.*/meta%-.*/conf/.*%.conf'] = 'bitbake',
+ ['.*bsd'] = 'bsdl',
+ ['bzr_log%..*'] = 'bzr',
+ ['.*enlightenment/.*%.cfg'] = 'c',
+ ['cabal%.project%..*'] = starsetf('cabalproject'),
+ ['.*/%.calendar/.*'] = starsetf('calendar'),
+ ['.*/share/calendar/.*/calendar%..*'] = starsetf('calendar'),
+ ['.*/share/calendar/calendar%..*'] = starsetf('calendar'),
+ ['sgml%.catalog.*'] = starsetf('catalog'),
+ ['.*/etc/defaults/cdrdao'] = 'cdrdaoconf',
+ ['.*/etc/cdrdao%.conf'] = 'cdrdaoconf',
+ ['.*/etc/default/cdrdao'] = 'cdrdaoconf',
+ ['.*hgrc'] = 'cfg',
+ ['.*%.[Cc][Ff][Gg]'] = {
+ function(path, bufnr)
+ return require('vim.filetype.detect').cfg(bufnr)
+ end,
+ -- Decrease priority to avoid conflicts with more specific patterns
+ -- such as '.*/etc/a2ps/.*%.cfg', '.*enlightenment/.*%.cfg', etc.
+ { priority = -1 },
+ },
+ ['[cC]hange[lL]og.*'] = starsetf(function(path, bufnr)
+ require('vim.filetype.detect').changelog(bufnr)
+ end),
+ ['.*%.%.ch'] = 'chill',
+ ['.*%.cmake%.in'] = 'cmake',
+ -- */cmus/rc and */.cmus/rc
+ ['.*/%.?cmus/rc'] = 'cmusrc',
+ -- */cmus/*.theme and */.cmus/*.theme
+ ['.*/%.?cmus/.*%.theme'] = 'cmusrc',
+ ['.*/%.cmus/autosave'] = 'cmusrc',
+ ['.*/%.cmus/command%-history'] = 'cmusrc',
+ ['.*/etc/hostname%..*'] = starsetf('config'),
+ ['crontab%..*'] = starsetf('crontab'),
+ ['.*/etc/cron%.d/.*'] = starsetf('crontab'),
+ ['%.cshrc.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
+ end,
+ ['%.login.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').csh(path, bufnr)
+ end,
+ ['cvs%d+'] = 'cvs',
+ ['.*%.[Dd][Aa][Tt]'] = function(path, bufnr)
+ return require('vim.filetype.detect').dat(path, bufnr)
+ end,
+ ['.*/debian/patches/.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').dep3patch(path, bufnr)
+ end,
+ ['.*/etc/dnsmasq%.d/.*'] = starsetf('dnsmasq'),
+ ['Containerfile%..*'] = starsetf('dockerfile'),
+ ['Dockerfile%..*'] = starsetf('dockerfile'),
+ ['.*/etc/yum%.repos%.d/.*'] = starsetf('dosini'),
+ ['drac%..*'] = starsetf('dracula'),
+ ['.*/debian/changelog'] = 'debchangelog',
+ ['.*/debian/control'] = 'debcontrol',
+ ['.*/debian/copyright'] = 'debcopyright',
+ ['.*/etc/apt/sources%.list%.d/.*%.list'] = 'debsources',
+ ['.*/etc/apt/sources%.list'] = 'debsources',
+ ['.*%.directory'] = 'desktop',
+ ['.*%.desktop'] = 'desktop',
+ ['dictd.*%.conf'] = 'dictdconf',
+ ['.*/etc/DIR_COLORS'] = 'dircolors',
+ ['.*/etc/dnsmasq%.conf'] = 'dnsmasq',
+ ['php%.ini%-.*'] = 'dosini',
+ ['.*/etc/pacman%.conf'] = 'confini',
+ ['.*/etc/yum%.conf'] = 'dosini',
+ ['.*lvs'] = 'dracula',
+ ['.*lpe'] = 'dracula',
+ ['.*/dtrace/.*%.d'] = 'dtrace',
+ ['.*esmtprc'] = 'esmtprc',
+ ['.*Eterm/.*%.cfg'] = 'eterm',
+ ['[a-zA-Z0-9].*Dict'] = function(path, bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
+ end,
+ ['[a-zA-Z0-9].*Dict%..*'] = function(path, bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
+ end,
+ ['[a-zA-Z].*Properties'] = function(path, bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
+ end,
+ ['[a-zA-Z].*Properties%..*'] = function(path, bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
+ end,
+ ['.*Transport%..*'] = function(path, bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
+ end,
+ ['.*/constant/g'] = function(path, bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
+ end,
+ ['.*/0/.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
+ end,
+ ['.*/0%.orig/.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').foam(bufnr)
+ end,
+ ['.*/%.fvwm/.*'] = starsetf('fvwm'),
+ ['.*fvwmrc.*'] = starsetf(function(path, bufnr)
+ return 'fvwm', function(b)
+ vim.b[b].fvwm_version = 1
+ end
+ end),
+ ['.*fvwm95.*%.hook'] = starsetf(function(path, bufnr)
+ return 'fvwm', function(b)
+ vim.b[b].fvwm_version = 1
+ end
+ end),
+ ['.*fvwm2rc.*'] = starsetf(function(path, bufnr)
+ return require('vim.filetype.detect').fvwm(path)
+ end),
+ ['.*/tmp/lltmp.*'] = starsetf('gedcom'),
+ ['.*/etc/gitconfig%.d/.*'] = starsetf('gitconfig'),
+ ['.*/gitolite%-admin/conf/.*'] = starsetf('gitolite'),
+ ['tmac%..*'] = starsetf('nroff'),
+ ['.*/%.gitconfig%.d/.*'] = starsetf('gitconfig'),
+ ['.*%.git/.*'] = {
+ function(path, bufnr)
+ return require('vim.filetype.detect').git(bufnr)
+ end,
+ -- Decrease priority to run after simple pattern checks
+ { priority = -1 },
+ },
+ ['.*%.git/modules/.*/config'] = 'gitconfig',
+ ['.*%.git/modules/config'] = 'gitconfig',
+ ['.*%.git/config'] = 'gitconfig',
+ ['.*/etc/gitconfig'] = 'gitconfig',
+ ['.*/%.config/git/config'] = 'gitconfig',
+ ['.*%.git/config%.worktree'] = 'gitconfig',
+ ['.*%.git/worktrees/.*/config%.worktree'] = 'gitconfig',
+ ['.*/git/config'] = function(path, bufnr)
+ if vim.env.XDG_CONFIG_HOME and path:find(vim.env.XDG_CONFIG_HOME .. '/git/config') then
+ return 'gitconfig'
+ end
+ end,
+ ['%.gitsendemail%.msg%.......'] = 'gitsendemail',
+ ['gkrellmrc_.'] = 'gkrellmrc',
+ ['.*/usr/.*/gnupg/options%.skel'] = 'gpg',
+ ['.*/%.gnupg/options'] = 'gpg',
+ ['.*/%.gnupg/gpg%.conf'] = 'gpg',
+ ['.*/options'] = function(path, bufnr)
+ if vim.env.GNUPGHOME and path:find(vim.env.GNUPGHOME .. '/options') then
+ return 'gpg'
+ end
+ end,
+ ['.*/gpg%.conf'] = function(path, bufnr)
+ if vim.env.GNUPGHOME and path:find(vim.env.GNUPGHOME .. '/gpg%.conf') then
+ return 'gpg'
+ end
+ end,
+ ['.*/etc/group'] = 'group',
+ ['.*/etc/gshadow'] = 'group',
+ ['.*/etc/group%.edit'] = 'group',
+ ['.*/var/backups/gshadow%.bak'] = 'group',
+ ['.*/etc/group%-'] = 'group',
+ ['.*/etc/gshadow%-'] = 'group',
+ ['.*/var/backups/group%.bak'] = 'group',
+ ['.*/etc/gshadow%.edit'] = 'group',
+ ['.*/boot/grub/grub%.conf'] = 'grub',
+ ['.*/boot/grub/menu%.lst'] = 'grub',
+ ['.*/etc/grub%.conf'] = 'grub',
+ -- gtkrc* and .gtkrc*
+ ['%.?gtkrc.*'] = starsetf('gtkrc'),
+ [vim.env.VIMRUNTIME .. '/doc/.*%.txt'] = 'help',
+ ['hg%-editor%-.*%.txt'] = 'hgcommit',
+ ['.*/etc/host%.conf'] = 'hostconf',
+ ['.*/etc/hosts%.deny'] = 'hostsaccess',
+ ['.*/etc/hosts%.allow'] = 'hostsaccess',
+ ['.*%.html%.m4'] = 'htmlm4',
+ ['.*/%.i3/config'] = 'i3config',
+ ['.*/sway/config'] = 'i3config',
+ ['.*/i3/config'] = 'i3config',
+ ['.*/%.sway/config'] = 'i3config',
+ ['.*/%.icewm/menu'] = 'icemenu',
+ ['.*/etc/initng/.*/.*%.i'] = 'initng',
+ ['JAM.*%..*'] = starsetf('jam'),
+ ['Prl.*%..*'] = starsetf('jam'),
+ ['.*%.properties_..'] = 'jproperties',
+ ['.*%.properties_.._..'] = 'jproperties',
+ ['.*%.properties_.._.._.*'] = starsetf('jproperties'),
+ ['Kconfig%..*'] = starsetf('kconfig'),
+ ['.*%.[Ss][Uu][Bb]'] = 'krl',
+ ['lilo%.conf.*'] = starsetf('lilo'),
+ ['.*/etc/logcheck/.*%.d.*/.*'] = starsetf('logcheck'),
+ ['.*lftp/rc'] = 'lftp',
+ ['.*/%.libao'] = 'libao',
+ ['.*/etc/libao%.conf'] = 'libao',
+ ['.*/etc/.*limits%.conf'] = 'limits',
+ ['.*/etc/limits'] = 'limits',
+ ['.*/etc/.*limits%.d/.*%.conf'] = 'limits',
+ ['.*/LiteStep/.*/.*%.rc'] = 'litestep',
+ ['.*/etc/login%.access'] = 'loginaccess',
+ ['.*/etc/login%.defs'] = 'logindefs',
+ ['%.letter%.%d+'] = 'mail',
+ ['%.article%.%d+'] = 'mail',
+ ['/tmp/SLRN[0-9A-Z.]+'] = 'mail',
+ ['ae%d+%.txt'] = 'mail',
+ ['pico%.%d+'] = 'mail',
+ ['mutt%-.*%-%w+'] = 'mail',
+ ['muttng%-.*%-%w+'] = 'mail',
+ ['neomutt%-.*%-%w+'] = 'mail',
+ ['mutt' .. string.rep('[%w_-]', 6)] = 'mail',
+ ['neomutt' .. string.rep('[%w_-]', 6)] = 'mail',
+ ['snd%.%d+'] = 'mail',
+ ['reportbug%-.*'] = starsetf('mail'),
+ ['.*/etc/mail/aliases'] = 'mailaliases',
+ ['.*/etc/aliases'] = 'mailaliases',
+ ['.*[mM]akefile'] = 'make',
+ ['[mM]akefile.*'] = starsetf('make'),
+ ['.*/etc/man%.conf'] = 'manconf',
+ ['.*/log/auth'] = 'messages',
+ ['.*/log/cron'] = 'messages',
+ ['.*/log/daemon'] = 'messages',
+ ['.*/log/debug'] = 'messages',
+ ['.*/log/kern'] = 'messages',
+ ['.*/log/lpr'] = 'messages',
+ ['.*/log/mail'] = 'messages',
+ ['.*/log/messages'] = 'messages',
+ ['.*/log/news/news'] = 'messages',
+ ['.*/log/syslog'] = 'messages',
+ ['.*/log/user'] = 'messages',
+ ['.*/log/auth%.log'] = 'messages',
+ ['.*/log/cron%.log'] = 'messages',
+ ['.*/log/daemon%.log'] = 'messages',
+ ['.*/log/debug%.log'] = 'messages',
+ ['.*/log/kern%.log'] = 'messages',
+ ['.*/log/lpr%.log'] = 'messages',
+ ['.*/log/mail%.log'] = 'messages',
+ ['.*/log/messages%.log'] = 'messages',
+ ['.*/log/news/news%.log'] = 'messages',
+ ['.*/log/syslog%.log'] = 'messages',
+ ['.*/log/user%.log'] = 'messages',
+ ['.*/log/auth%.err'] = 'messages',
+ ['.*/log/cron%.err'] = 'messages',
+ ['.*/log/daemon%.err'] = 'messages',
+ ['.*/log/debug%.err'] = 'messages',
+ ['.*/log/kern%.err'] = 'messages',
+ ['.*/log/lpr%.err'] = 'messages',
+ ['.*/log/mail%.err'] = 'messages',
+ ['.*/log/messages%.err'] = 'messages',
+ ['.*/log/news/news%.err'] = 'messages',
+ ['.*/log/syslog%.err'] = 'messages',
+ ['.*/log/user%.err'] = 'messages',
+ ['.*/log/auth%.info'] = 'messages',
+ ['.*/log/cron%.info'] = 'messages',
+ ['.*/log/daemon%.info'] = 'messages',
+ ['.*/log/debug%.info'] = 'messages',
+ ['.*/log/kern%.info'] = 'messages',
+ ['.*/log/lpr%.info'] = 'messages',
+ ['.*/log/mail%.info'] = 'messages',
+ ['.*/log/messages%.info'] = 'messages',
+ ['.*/log/news/news%.info'] = 'messages',
+ ['.*/log/syslog%.info'] = 'messages',
+ ['.*/log/user%.info'] = 'messages',
+ ['.*/log/auth%.warn'] = 'messages',
+ ['.*/log/cron%.warn'] = 'messages',
+ ['.*/log/daemon%.warn'] = 'messages',
+ ['.*/log/debug%.warn'] = 'messages',
+ ['.*/log/kern%.warn'] = 'messages',
+ ['.*/log/lpr%.warn'] = 'messages',
+ ['.*/log/mail%.warn'] = 'messages',
+ ['.*/log/messages%.warn'] = 'messages',
+ ['.*/log/news/news%.warn'] = 'messages',
+ ['.*/log/syslog%.warn'] = 'messages',
+ ['.*/log/user%.warn'] = 'messages',
+ ['.*/log/auth%.crit'] = 'messages',
+ ['.*/log/cron%.crit'] = 'messages',
+ ['.*/log/daemon%.crit'] = 'messages',
+ ['.*/log/debug%.crit'] = 'messages',
+ ['.*/log/kern%.crit'] = 'messages',
+ ['.*/log/lpr%.crit'] = 'messages',
+ ['.*/log/mail%.crit'] = 'messages',
+ ['.*/log/messages%.crit'] = 'messages',
+ ['.*/log/news/news%.crit'] = 'messages',
+ ['.*/log/syslog%.crit'] = 'messages',
+ ['.*/log/user%.crit'] = 'messages',
+ ['.*/log/auth%.notice'] = 'messages',
+ ['.*/log/cron%.notice'] = 'messages',
+ ['.*/log/daemon%.notice'] = 'messages',
+ ['.*/log/debug%.notice'] = 'messages',
+ ['.*/log/kern%.notice'] = 'messages',
+ ['.*/log/lpr%.notice'] = 'messages',
+ ['.*/log/mail%.notice'] = 'messages',
+ ['.*/log/messages%.notice'] = 'messages',
+ ['.*/log/news/news%.notice'] = 'messages',
+ ['.*/log/syslog%.notice'] = 'messages',
+ ['.*/log/user%.notice'] = 'messages',
+ ['.*%.[Mm][Oo][Dd]'] = function(path, bufnr)
+ return require('vim.filetype.detect').mod(path, bufnr)
+ end,
+ ['.*/etc/modules%.conf'] = 'modconf',
+ ['.*/etc/conf%.modules'] = 'modconf',
+ ['.*/etc/modules'] = 'modconf',
+ ['.*/etc/modprobe%..*'] = starsetf('modconf'),
+ ['.*/etc/modutils/.*'] = starsetf(function(path, bufnr)
+ if vim.fn.executable(vim.fn.expand(path)) ~= 1 then
+ return 'modconf'
+ end
+ end),
+ ['.*%.[mi][3g]'] = 'modula3',
+ ['Muttrc'] = 'muttrc',
+ ['Muttngrc'] = 'muttrc',
+ ['.*/etc/Muttrc%.d/.*'] = starsetf('muttrc'),
+ ['.*/%.mplayer/config'] = 'mplayerconf',
+ ['Muttrc.*'] = starsetf('muttrc'),
+ ['Muttngrc.*'] = starsetf('muttrc'),
+ -- muttrc* and .muttrc*
+ ['%.?muttrc.*'] = starsetf('muttrc'),
+ -- muttngrc* and .muttngrc*
+ ['%.?muttngrc.*'] = starsetf('muttrc'),
+ ['.*/%.mutt/muttrc.*'] = starsetf('muttrc'),
+ ['.*/%.muttng/muttrc.*'] = starsetf('muttrc'),
+ ['.*/%.muttng/muttngrc.*'] = starsetf('muttrc'),
+ ['rndc.*%.conf'] = 'named',
+ ['rndc.*%.key'] = 'named',
+ ['named.*%.conf'] = 'named',
+ ['.*/etc/nanorc'] = 'nanorc',
+ ['.*%.NS[ACGLMNPS]'] = 'natural',
+ ['Neomuttrc.*'] = starsetf('neomuttrc'),
+ -- neomuttrc* and .neomuttrc*
+ ['%.?neomuttrc.*'] = starsetf('neomuttrc'),
+ ['.*/%.neomutt/neomuttrc.*'] = starsetf('neomuttrc'),
+ ['nginx.*%.conf'] = 'nginx',
+ ['.*/etc/nginx/.*'] = 'nginx',
+ ['.*nginx%.conf'] = 'nginx',
+ ['.*/nginx/.*%.conf'] = 'nginx',
+ ['.*/usr/local/nginx/conf/.*'] = 'nginx',
+ ['.*%.[1-9]'] = function(path, bufnr)
+ return require('vim.filetype.detect').nroff(bufnr)
+ end,
+ ['.*%.ml%.cppo'] = 'ocaml',
+ ['.*%.mli%.cppo'] = 'ocaml',
+ ['.*%.opam%.template'] = 'opam',
+ ['.*%.[Oo][Pp][Ll]'] = 'opl',
+ ['.*/etc/pam%.conf'] = 'pamconf',
+ ['.*/etc/pam%.d/.*'] = starsetf('pamconf'),
+ ['.*/etc/passwd%-'] = 'passwd',
+ ['.*/etc/shadow'] = 'passwd',
+ ['.*/etc/shadow%.edit'] = 'passwd',
+ ['.*/var/backups/shadow%.bak'] = 'passwd',
+ ['.*/var/backups/passwd%.bak'] = 'passwd',
+ ['.*/etc/passwd'] = 'passwd',
+ ['.*/etc/passwd%.edit'] = 'passwd',
+ ['.*/etc/shadow%-'] = 'passwd',
+ ['%.?gitolite%.rc'] = 'perl',
+ ['example%.gitolite%.rc'] = 'perl',
+ ['.*%.php%d'] = 'php',
+ ['.*/%.pinforc'] = 'pinfo',
+ ['.*/etc/pinforc'] = 'pinfo',
+ ['.*%.[Pp][Rr][Gg]'] = function(path, bufnr)
+ return require('vim.filetype.detect').prg(bufnr)
+ end,
+ ['.*/etc/protocols'] = 'protocols',
+ ['.*printcap.*'] = starsetf(function(path, bufnr)
+ return require('vim.filetype.detect').printcap('print')
+ end),
+ ['.*baseq[2-3]/.*%.cfg'] = 'quake',
+ ['.*quake[1-3]/.*%.cfg'] = 'quake',
+ ['.*id1/.*%.cfg'] = 'quake',
+ ['.*/queries/.*%.scm'] = 'query', -- tree-sitter queries (Neovim only)
+ ['.*,v'] = 'rcs',
+ ['%.reminders.*'] = starsetf('remind'),
+ ['[rR]akefile.*'] = starsetf('ruby'),
+ ['[rR]antfile'] = 'ruby',
+ ['[rR]akefile'] = 'ruby',
+ ['.*/etc/sensors%.d/[^.].*'] = starsetf('sensors'),
+ ['.*/etc/sensors%.conf'] = 'sensors',
+ ['.*/etc/sensors3%.conf'] = 'sensors',
+ ['.*/etc/services'] = 'services',
+ ['.*/etc/serial%.conf'] = 'setserial',
+ ['.*/etc/udev/cdsymlinks%.conf'] = 'sh',
+ ['%.bash[_%-]aliases'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
+ end,
+ ['%.bash[_%-]logout'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
+ end,
+ ['%.bash[_%-]profile'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
+ end,
+ ['%.kshrc.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'ksh')
+ end,
+ ['%.profile.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
+ end,
+ ['.*/etc/profile'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
+ end,
+ ['bash%-fc[%-%.]'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
+ end,
+ ['%.tcshrc.*'] = function(path, bufnr)
+ return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'tcsh')
+ end,
+ ['.*/etc/sudoers%.d/.*'] = starsetf('sudoers'),
+ ['.*%._sst%.meta'] = 'sisu',
+ ['.*%.%-sst%.meta'] = 'sisu',
+ ['.*%.sst%.meta'] = 'sisu',
+ ['.*/etc/slp%.conf'] = 'slpconf',
+ ['.*/etc/slp%.reg'] = 'slpreg',
+ ['.*/etc/slp%.spi'] = 'slpspi',
+ ['.*/etc/ssh/ssh_config%.d/.*%.conf'] = 'sshconfig',
+ ['.*/%.ssh/config'] = 'sshconfig',
+ ['.*/%.ssh/.*%.conf'] = 'sshconfig',
+ ['.*/etc/ssh/sshd_config%.d/.*%.conf'] = 'sshdconfig',
+ ['.*%.[Ss][Rr][Cc]'] = function(path, bufnr)
+ return require('vim.filetype.detect').src(bufnr)
+ end,
+ ['.*/etc/sudoers'] = 'sudoers',
+ ['svn%-commit.*%.tmp'] = 'svn',
+ ['.*%.swift%.gyb'] = 'swiftgyb',
+ ['.*%.[Ss][Yy][Ss]'] = function(path, bufnr)
+ return require('vim.filetype.detect').sys(bufnr)
+ end,
+ ['.*/etc/sysctl%.conf'] = 'sysctl',
+ ['.*/etc/sysctl%.d/.*%.conf'] = 'sysctl',
+ ['.*/systemd/.*%.automount'] = 'systemd',
+ ['.*/systemd/.*%.dnssd'] = 'systemd',
+ ['.*/systemd/.*%.link'] = 'systemd',
+ ['.*/systemd/.*%.mount'] = 'systemd',
+ ['.*/systemd/.*%.netdev'] = 'systemd',
+ ['.*/systemd/.*%.network'] = 'systemd',
+ ['.*/systemd/.*%.nspawn'] = 'systemd',
+ ['.*/systemd/.*%.path'] = 'systemd',
+ ['.*/systemd/.*%.service'] = 'systemd',
+ ['.*/systemd/.*%.slice'] = 'systemd',
+ ['.*/systemd/.*%.socket'] = 'systemd',
+ ['.*/systemd/.*%.swap'] = 'systemd',
+ ['.*/systemd/.*%.target'] = 'systemd',
+ ['.*/systemd/.*%.timer'] = 'systemd',
+ ['.*/etc/systemd/.*%.conf%.d/.*%.conf'] = 'systemd',
+ ['.*/%.config/systemd/user/.*%.d/.*%.conf'] = 'systemd',
+ ['.*/etc/systemd/system/.*%.d/.*%.conf'] = 'systemd',
+ ['.*/etc/systemd/system/.*%.d/%.#.*'] = 'systemd',
+ ['.*/etc/systemd/system/%.#.*'] = 'systemd',
+ ['.*/%.config/systemd/user/.*%.d/%.#.*'] = 'systemd',
+ ['.*/%.config/systemd/user/%.#.*'] = 'systemd',
+ ['.*termcap.*'] = starsetf(function(path, bufnr)
+ return require('vim.filetype.detect').printcap('term')
+ end),
+ ['.*%.t%.html'] = 'tilde',
+ ['%.?tmux.*%.conf'] = 'tmux',
+ ['%.?tmux.*%.conf.*'] = { 'tmux', { priority = -1 } },
+ ['.*/%.cargo/config'] = 'toml',
+ ['.*/%.cargo/credentials'] = 'toml',
+ ['.*/etc/udev/udev%.conf'] = 'udevconf',
+ ['.*/etc/udev/permissions%.d/.*%.permissions'] = 'udevperm',
+ ['.*/etc/updatedb%.conf'] = 'updatedb',
+ ['.*/%.init/.*%.override'] = 'upstart',
+ ['.*/usr/share/upstart/.*%.conf'] = 'upstart',
+ ['.*/%.config/upstart/.*%.override'] = 'upstart',
+ ['.*/etc/init/.*%.conf'] = 'upstart',
+ ['.*/etc/init/.*%.override'] = 'upstart',
+ ['.*/%.config/upstart/.*%.conf'] = 'upstart',
+ ['.*/%.init/.*%.conf'] = 'upstart',
+ ['.*/usr/share/upstart/.*%.override'] = 'upstart',
+ ['.*%.[Ll][Oo][Gg]'] = function(path, bufnr)
+ return require('vim.filetype.detect').log(path)
+ end,
+ ['.*%.vhdl_[0-9].*'] = starsetf('vhdl'),
+ ['.*%.ws[fc]'] = 'wsh',
+ ['.*/Xresources/.*'] = starsetf('xdefaults'),
+ ['.*/app%-defaults/.*'] = starsetf('xdefaults'),
+ ['.*/etc/xinetd%.conf'] = 'xinetd',
+ ['.*/etc/blkid%.tab'] = 'xml',
+ ['.*/etc/blkid%.tab%.old'] = 'xml',
+ ['.*%.vbproj%.user'] = 'xml',
+ ['.*%.fsproj%.user'] = 'xml',
+ ['.*%.csproj%.user'] = 'xml',
+ ['.*/etc/xdg/menus/.*%.menu'] = 'xml',
+ ['.*Xmodmap'] = 'xmodmap',
+ ['.*/etc/zprofile'] = 'zsh',
+ ['.*vimrc.*'] = starsetf('vim'),
+ ['Xresources.*'] = starsetf('xdefaults'),
+ ['.*/etc/xinetd%.d/.*'] = starsetf('xinetd'),
+ ['.*xmodmap.*'] = starsetf('xmodmap'),
+ ['.*/xorg%.conf%.d/.*%.conf'] = function(path, bufnr)
+ return 'xf86config', function(b)
+ vim.b[b].xf86conf_xfree86_version = 4
+ end
+ end,
+ -- Increase priority to run before the pattern below
+ ['XF86Config%-4.*'] = starsetf(function(path, bufnr)
+ return 'xf86conf', function(b)
+ vim.b[b].xf86conf_xfree86_version = 4
+ end
+ end, { priority = -math.huge + 1 }),
+ ['XF86Config.*'] = starsetf(function(path, bufnr)
+ return require('vim.filetype.detect').xfree86()
+ end),
+ ['%.zcompdump.*'] = starsetf('zsh'),
+ -- .zlog* and zlog*
+ ['%.?zlog.*'] = starsetf('zsh'),
+ -- .zsh* and zsh*
+ ['%.?zsh.*'] = starsetf('zsh'),
+ -- Ignored extension
+ ['.*~'] = function(path, bufnr)
+ local short = path:gsub('~$', '', 1)
+ if path ~= short and short ~= '' then
+ return M.match({ buf = bufnr, filename = vim.fn.fnameescape(short) })
+ end
+ end,
-- END PATTERN
}
-- luacheck: pop
+-- luacheck: pop
---@private
local function sort_by_priority(t)
local sorted = {}
for k, v in pairs(t) do
- local ft = type(v) == "table" and v[1] or v
- assert(type(ft) == "string" or type(ft) == "function", "Expected string or function for filetype")
+ local ft = type(v) == 'table' and v[1] or v
+ assert(
+ type(ft) == 'string' or type(ft) == 'function',
+ 'Expected string or function for filetype'
+ )
- local opts = (type(v) == "table" and type(v[2]) == "table") and v[2] or {}
+ local opts = (type(v) == 'table' and type(v[2]) == 'table') and v[2] or {}
if not opts.priority then
opts.priority = 0
end
@@ -1372,8 +2198,18 @@ end
local pattern_sorted = sort_by_priority(pattern)
---@private
-local function normalize_path(path)
- return (path:gsub("\\", "/"))
+local function normalize_path(path, as_pattern)
+ local normal = path:gsub('\\', '/')
+ if normal:find('^~') then
+ if as_pattern then
+ -- Escape Lua's metacharacters when $HOME is used in a pattern.
+ -- The rest of path should already be properly escaped.
+ normal = vim.pesc(vim.env.HOME) .. normal:sub(2)
+ else
+ normal = vim.env.HOME .. normal:sub(2)
+ end
+ end
+ return normal
end
--- Add new filetype mappings.
@@ -1381,7 +2217,7 @@ end
--- Filetype mappings can be added either by extension or by filename (either
--- the "tail" or the full file path). The full file path is checked first,
--- followed by the file name. If a match is not found using the filename, then
---- the filename is matched against the list of patterns (sorted by priority)
+--- the filename is matched against the list of |lua-patterns| (sorted by priority)
--- until a match is found. Lastly, if pattern matching does not find a
--- filetype, then the file extension is used.
---
@@ -1389,7 +2225,9 @@ end
--- filetype directly) or a function. If a function, it takes the full path and
--- buffer number of the file as arguments (along with captures from the matched
--- pattern, if any) and should return a string that will be used as the
---- buffer's filetype.
+--- buffer's filetype. Optionally, the function can return a second function
+--- value which, when called, modifies the state of the buffer. This can be used
+--- to, for example, set filetype-specific buffer variables.
---
--- Filename patterns can specify an optional priority to resolve cases when a
--- file path matches multiple patterns. Higher priorities are matched first.
@@ -1397,8 +2235,7 @@ end
---
--- See $VIMRUNTIME/lua/vim/filetype.lua for more examples.
---
---- Note that Lua filetype detection is only enabled when |g:do_filetype_lua| is
---- set to 1.
+--- Note that Lua filetype detection is disabled when |g:do_legacy_filetype| is set.
---
--- Example:
--- <pre>
@@ -1407,7 +2244,10 @@ end
--- foo = "fooscript",
--- bar = function(path, bufnr)
--- if some_condition() then
---- return "barscript"
+--- return "barscript", function(bufnr)
+--- -- Set a buffer variable
+--- vim.b[bufnr].barscript_version = 2
+--- end
--- end
--- return "bar"
--- end,
@@ -1431,6 +2271,25 @@ end
--- })
--- </pre>
---
+--- To add a fallback match on contents (see |new-filetype-scripts|), use
+--- <pre>
+--- vim.filetype.add {
+--- pattern = {
+--- ['.*'] = {
+--- priority = -math.huge,
+--- function(path, bufnr)
+--- local content = vim.filetype.getlines(bufnr, 1)
+--- if vim.filetype.matchregex(content, { [[^#!.*\\<mine\\>]] }) then
+--- return 'mine'
+--- elseif vim.filetype.matchregex(content, { [[\\<drawing\\>]] }) then
+--- return 'drawing'
+--- end
+--- end,
+--- },
+--- },
+--- }
+--- </pre>
+---
---@param filetypes table A table containing new filetype maps (see example).
function M.add(filetypes)
for k, v in pairs(filetypes.extension or {}) do
@@ -1442,7 +2301,7 @@ function M.add(filetypes)
end
for k, v in pairs(filetypes.pattern or {}) do
- pattern[normalize_path(k)] = v
+ pattern[normalize_path(k, true)] = v
end
if filetypes.pattern then
@@ -1452,27 +2311,34 @@ end
---@private
local function dispatch(ft, path, bufnr, ...)
- if type(ft) == "function" then
- ft = ft(path, bufnr, ...)
+ local on_detect
+ if type(ft) == 'function' then
+ if bufnr then
+ ft, on_detect = ft(path, bufnr, ...)
+ else
+ -- If bufnr is nil (meaning we are matching only against the filename), set it to an invalid
+ -- value (-1) and catch any errors from the filetype detection function. If the function tries
+ -- to use the buffer then it will fail, but this enables functions which do not need a buffer
+ -- to still work.
+ local ok
+ ok, ft, on_detect = pcall(ft, path, -1, ...)
+ if not ok then
+ return
+ end
+ end
end
- if type(ft) == "string" then
- api.nvim_buf_set_option(bufnr, "filetype", ft)
- return true
+ if type(ft) == 'string' then
+ return ft, on_detect
end
-
- -- Any non-falsey value (that is, anything other than 'nil' or 'false') will
- -- end filetype matching. This is useful for e.g. the dist#ft functions that
- -- return 0, but set the buffer's filetype themselves
- return ft
end
---@private
local function match_pattern(name, path, tail, pat)
-- If the pattern contains a / match against the full path, otherwise just the tail
- local fullpat = "^" .. pat .. "$"
+ local fullpat = '^' .. pat .. '$'
local matches
- if pat:find("/") then
+ if pat:find('/') then
-- Similar to |autocmd-pattern|, if the pattern contains a '/' then check for a match against
-- both the short file name (as typed) and the full file name (after expanding to full path
-- and resolving symlinks)
@@ -1483,28 +2349,87 @@ local function match_pattern(name, path, tail, pat)
return matches
end
---- Set the filetype for the given buffer from a file name.
+--- Perform filetype detection.
+---
+--- The filetype can be detected using one of three methods:
+--- 1. Using an existing buffer
+--- 2. Using only a file name
+--- 3. Using only file contents
+---
+--- Of these, option 1 provides the most accurate result as it uses both the buffer's filename and
+--- (optionally) the buffer contents. Options 2 and 3 can be used without an existing buffer, but
+--- may not always provide a match in cases where the filename (or contents) cannot unambiguously
+--- determine the filetype.
+---
+--- Each of the three options is specified using a key to the single argument of this function.
+--- Example:
+---
+--- <pre>
+--- -- Using a buffer number
+--- vim.filetype.match({ buf = 42 })
+---
+--- -- Override the filename of the given buffer
+--- vim.filetype.match({ buf = 42, filename = 'foo.c' })
---
----@param name string File name (can be an absolute or relative path)
----@param bufnr number|nil The buffer to set the filetype for. Defaults to the current buffer.
-function M.match(name, bufnr)
- -- When fired from the main filetypedetect autocommand the {bufnr} argument is omitted, so we use
- -- the current buffer. The {bufnr} argument is provided to allow extensibility in case callers
- -- wish to perform filetype detection on buffers other than the current one.
- bufnr = bufnr or api.nvim_get_current_buf()
+--- -- Using a filename without a buffer
+--- vim.filetype.match({ filename = 'main.lua' })
+---
+--- -- Using file contents
+--- vim.filetype.match({ contents = {'#!/usr/bin/env bash'} })
+--- </pre>
+---
+---@param args table Table specifying which matching strategy to use. Accepted keys are:
+--- * buf (number): Buffer number to use for matching. Mutually exclusive with
+--- {contents}
+--- * filename (string): Filename to use for matching. When {buf} is given,
+--- defaults to the filename of the given buffer number. The
+--- file need not actually exist in the filesystem. When used
+--- without {buf} only the name of the file is used for
+--- filetype matching. This may result in failure to detect
+--- the filetype in cases where the filename alone is not
+--- enough to disambiguate the filetype.
+--- * contents (table): An array of lines representing file contents to use for
+--- matching. Can be used with {filename}. Mutually exclusive
+--- with {buf}.
+---@return string|nil If a match was found, the matched filetype.
+---@return function|nil A function that modifies buffer state when called (for example, to set some
+--- filetype specific buffer variables). The function accepts a buffer number as
+--- its only argument.
+function M.match(args)
+ vim.validate({
+ arg = { args, 't' },
+ })
+
+ if not (args.buf or args.filename or args.contents) then
+ error('At least one of "buf", "filename", or "contents" must be given')
+ end
+
+ local bufnr = args.buf
+ local name = args.filename
+ local contents = args.contents
+
+ if bufnr and not name then
+ name = api.nvim_buf_get_name(bufnr)
+ end
- name = normalize_path(name)
+ if name then
+ name = normalize_path(name)
+ end
+
+ local ft, on_detect
-- First check for the simple case where the full path exists as a key
- local path = vim.fn.resolve(vim.fn.fnamemodify(name, ":p"))
- if dispatch(filename[path], path, bufnr) then
- return
+ local path = vim.fn.fnamemodify(name, ':p')
+ ft, on_detect = dispatch(filename[path], path, bufnr)
+ if ft then
+ return ft, on_detect
end
-- Next check against just the file name
- local tail = vim.fn.fnamemodify(name, ":t")
- if dispatch(filename[tail], path, bufnr) then
- return
+ local tail = vim.fn.fnamemodify(name, ':t')
+ ft, on_detect = dispatch(filename[tail], path, bufnr)
+ if ft then
+ return ft, on_detect
end
-- Next, check the file path against available patterns with non-negative priority
@@ -1517,34 +2442,50 @@ function M.match(name, bufnr)
break
end
- local ft = v[k][1]
+ local filetype = v[k][1]
local matches = match_pattern(name, path, tail, k)
if matches then
- if dispatch(ft, path, bufnr, matches) then
- return
+ ft, on_detect = dispatch(filetype, path, bufnr, matches)
+ if ft then
+ return ft, on_detect
end
end
end
-- Next, check file extension
- local ext = vim.fn.fnamemodify(name, ":e")
- if dispatch(extension[ext], path, bufnr) then
- return
+ local ext = vim.fn.fnamemodify(name, ':e')
+ ft, on_detect = dispatch(extension[ext], path, bufnr)
+ if ft then
+ return ft, on_detect
end
- -- Finally, check patterns with negative priority
+ -- Next, check patterns with negative priority
for i = j, #pattern_sorted do
local v = pattern_sorted[i]
local k = next(v)
- local ft = v[k][1]
+ local filetype = v[k][1]
local matches = match_pattern(name, path, tail, k)
if matches then
- if dispatch(ft, path, bufnr, matches) then
- return
+ ft, on_detect = dispatch(filetype, path, bufnr, matches)
+ if ft then
+ return ft, on_detect
end
end
end
+
+ -- Finally, check file contents
+ if contents or bufnr then
+ contents = contents or M.getlines(bufnr)
+ -- If name is nil, catch any errors from the contents filetype detection function.
+ -- If the function tries to use the filename that is nil then it will fail,
+ -- but this enables checks which do not need a filename to still work.
+ local ok
+ ok, ft = pcall(require('vim.filetype.detect').match_contents, contents, name)
+ if ok and ft then
+ return ft
+ end
+ end
end
return M
diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua
new file mode 100644
index 0000000000..8c10517687
--- /dev/null
+++ b/runtime/lua/vim/filetype/detect.lua
@@ -0,0 +1,1630 @@
+-- Contains filetype detection functions for use in filetype.lua that are either:
+-- * used more than once or
+-- * complex (e.g. check more than one line or use conditionals).
+-- Simple one-line checks, such as a check for a string in the first line are better inlined in filetype.lua.
+
+-- A few guidelines to follow when porting a new function:
+-- * Sort the function alphabetically and omit 'ft' or 'check' from the new function name.
+-- * Use ':find' instead of ':match' / ':sub' if possible.
+-- * When '=~' is used to match a pattern, there are two possibilities:
+-- - If the pattern only contains lowercase characters, treat the comparison as case-insensitive.
+-- - Otherwise, treat it as case-sensitive.
+-- (Basically, we apply 'smartcase': if upper case characters are used in the original pattern, then
+-- it's likely that case does matter).
+-- * When '\k', '\<' or '\>' is used in a pattern, use the 'matchregex' function.
+-- Note that vim.regex is case-sensitive by default, so add the '\c' flag if only lowercase letters
+-- are present in the pattern:
+-- Example:
+-- `if line =~ '^\s*unwind_protect\>'` => `if matchregex(line, [[\c^\s*unwind_protect\>]])`
+
+local M = {}
+
+local getlines = vim.filetype.getlines
+local findany = vim.filetype.findany
+local nextnonblank = vim.filetype.nextnonblank
+local matchregex = vim.filetype.matchregex
+
+-- luacheck: push no unused args
+-- luacheck: push ignore 122
+
+-- This function checks for the kind of assembly that is wanted by the user, or
+-- can be detected from the first five lines of the file.
+function M.asm(bufnr)
+ local syntax = vim.b[bufnr].asmsyntax
+ if not syntax or syntax == '' then
+ syntax = M.asm_syntax(bufnr)
+ end
+
+ -- If b:asmsyntax still isn't set, default to asmsyntax or GNU
+ if not syntax or syntax == '' then
+ if vim.g.asmsyntax and vim.g.asmsyntax ~= 0 then
+ syntax = vim.g.asmsyntax
+ else
+ syntax = 'asm'
+ end
+ end
+ return syntax, function(b)
+ vim.b[b].asmsyntax = syntax
+ end
+end
+
+-- Active Server Pages (with Perl or Visual Basic Script)
+function M.asp(bufnr)
+ if vim.g.filetype_asp then
+ return vim.g.filetype_asp
+ elseif table.concat(getlines(bufnr, 1, 3)):lower():find('perlscript') then
+ return 'aspperl'
+ else
+ return 'aspvbs'
+ end
+end
+
+-- Checks the first 5 lines for a asmsyntax=foo override.
+-- Only whitespace characters can be present immediately before or after this statement.
+function M.asm_syntax(bufnr)
+ local lines = ' ' .. table.concat(getlines(bufnr, 1, 5), ' '):lower() .. ' '
+ local match = lines:match('%sasmsyntax=([a-zA-Z0-9]+)%s')
+ if match then
+ return match
+ elseif findany(lines, { '%.title', '%.ident', '%.macro', '%.subtitle', '%.library' }) then
+ return 'vmasm'
+ end
+end
+
+local visual_basic_content =
+ { 'vb_name', 'begin vb%.form', 'begin vb%.mdiform', 'begin vb%.usercontrol' }
+
+-- See frm() for Visual Basic form file detection
+function M.bas(bufnr)
+ if vim.g.filetype_bas then
+ return vim.g.filetype_bas
+ end
+
+ -- Most frequent FreeBASIC-specific keywords in distro files
+ local fb_keywords =
+ [[\c^\s*\%(extern\|var\|enum\|private\|scope\|union\|byref\|operator\|constructor\|delete\|namespace\|public\|property\|with\|destructor\|using\)\>\%(\s*[:=(]\)\@!]]
+ local fb_preproc =
+ [[\c^\s*\%(#\s*\a\+\|option\s\+\%(byval\|dynamic\|escape\|\%(no\)\=gosub\|nokeyword\|private\|static\)\>\|\%(''\|rem\)\s*\$lang\>\|def\%(byte\|longint\|short\|ubyte\|uint\|ulongint\|ushort\)\>\)]]
+
+ local fb_comment = "^%s*/'"
+ -- OPTION EXPLICIT, without the leading underscore, is common to many dialects
+ local qb64_preproc = [[\c^\s*\%($\a\+\|option\s\+\%(_explicit\|_\=explicitarray\)\>\)]]
+
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ if findany(line:lower(), visual_basic_content) then
+ return 'vb'
+ elseif
+ line:find(fb_comment)
+ or matchregex(line, fb_preproc)
+ or matchregex(line, fb_keywords)
+ then
+ return 'freebasic'
+ elseif matchregex(line, qb64_preproc) then
+ return 'qb64'
+ end
+ end
+ return 'basic'
+end
+
+function M.bindzone(bufnr, default)
+ local lines = table.concat(getlines(bufnr, 1, 4))
+ if findany(lines, { '^; <<>> DiG [0-9%.]+.* <<>>', '%$ORIGIN', '%$TTL', 'IN%s+SOA' }) then
+ return 'bindzone'
+ end
+ return default
+end
+
+-- Returns true if file content looks like RAPID
+local function is_rapid(bufnr, extension)
+ if extension == 'cfg' then
+ local line = getlines(bufnr, 1):lower()
+ return findany(line, { 'eio:cfg', 'mmc:cfg', 'moc:cfg', 'proc:cfg', 'sio:cfg', 'sys:cfg' })
+ end
+ local line = nextnonblank(bufnr, 1)
+ if line then
+ -- Called from mod, prg or sys functions
+ return matchregex(line:lower(), [[\c\v^\s*%(\%{3}|module\s+\k+\s*%(\(|$))]])
+ end
+ return false
+end
+
+function M.cfg(bufnr)
+ if vim.g.filetype_cfg then
+ return vim.g.filetype_cfg
+ elseif is_rapid(bufnr, 'cfg') then
+ return 'rapid'
+ else
+ return 'cfg'
+ end
+end
+
+-- This function checks if one of the first ten lines start with a '@'. In
+-- that case it is probably a change file.
+-- If the first line starts with # or ! it's probably a ch file.
+-- If a line has "main", "include", "//" or "/*" it's probably ch.
+-- Otherwise CHILL is assumed.
+function M.change(bufnr)
+ local first_line = getlines(bufnr, 1)
+ if findany(first_line, { '^#', '^!' }) then
+ return 'ch'
+ end
+ for _, line in ipairs(getlines(bufnr, 1, 10)) do
+ if line:find('^@') then
+ return 'change'
+ end
+ if line:find('MODULE') then
+ return 'chill'
+ elseif findany(line:lower(), { 'main%s*%(', '#%s*include', '//' }) then
+ return 'ch'
+ end
+ end
+ return 'chill'
+end
+
+function M.changelog(bufnr)
+ local line = getlines(bufnr, 1):lower()
+ if line:find('; urgency=') then
+ return 'debchangelog'
+ end
+ return 'changelog'
+end
+
+function M.class(bufnr)
+ -- Check if not a Java class (starts with '\xca\xfe\xba\xbe')
+ if not getlines(bufnr, 1):find('^\202\254\186\190') then
+ return 'stata'
+ end
+end
+
+function M.cls(bufnr)
+ if vim.g.filetype_cls then
+ return vim.g.filetype_cls
+ end
+ local line = getlines(bufnr, 1)
+ if line:find('^%%') then
+ return 'tex'
+ elseif line:find('^#') and line:lower():find('rexx') then
+ return 'rexx'
+ elseif line == 'VERSION 1.0 CLASS' then
+ return 'vb'
+ else
+ return 'st'
+ end
+end
+
+function M.conf(path, bufnr)
+ if vim.fn.did_filetype() ~= 0 or path:find(vim.g.ft_ignore_pat) then
+ return
+ end
+ for _, line in ipairs(getlines(bufnr, 1, 5)) do
+ if line:find('^#') then
+ return 'conf'
+ end
+ end
+end
+
+-- Debian Control
+function M.control(bufnr)
+ if getlines(bufnr, 1):find('^Source:') then
+ return 'debcontrol'
+ end
+end
+
+-- Debian Copyright
+function M.copyright(bufnr)
+ if getlines(bufnr, 1):find('^Format:') then
+ return 'debcopyright'
+ end
+end
+
+function M.csh(path, bufnr)
+ if vim.fn.did_filetype() ~= 0 then
+ -- Filetype was already detected
+ return
+ end
+ local contents = getlines(bufnr)
+ if vim.g.filetype_csh then
+ return M.shell(path, contents, vim.g.filetype_csh)
+ elseif string.find(vim.o.shell, 'tcsh') then
+ return M.shell(path, contents, 'tcsh')
+ else
+ return M.shell(path, contents, 'csh')
+ end
+end
+
+local function cvs_diff(path, contents)
+ for _, line in ipairs(contents) do
+ if not line:find('^%? ') then
+ if matchregex(line, [[^Index:\s\+\f\+$]]) then
+ -- CVS diff
+ return 'diff'
+ elseif
+ -- Locale input files: Formal Definitions of Cultural Conventions
+ -- Filename must be like en_US, fr_FR@euro or en_US.UTF-8
+ findany(path, {
+ '%a%a_%a%a$',
+ '%a%a_%a%a[%.@]',
+ '%a%a_%a%ai18n$',
+ '%a%a_%a%aPOSIX$',
+ '%a%a_%a%atranslit_',
+ })
+ then
+ -- Only look at the first 100 lines
+ for line_nr = 1, 100 do
+ if not contents[line_nr] then
+ break
+ elseif
+ findany(contents[line_nr], {
+ '^LC_IDENTIFICATION$',
+ '^LC_CTYPE$',
+ '^LC_COLLATE$',
+ '^LC_MONETARY$',
+ '^LC_NUMERIC$',
+ '^LC_TIME$',
+ '^LC_MESSAGES$',
+ '^LC_PAPER$',
+ '^LC_TELEPHONE$',
+ '^LC_MEASUREMENT$',
+ '^LC_NAME$',
+ '^LC_ADDRESS$',
+ })
+ then
+ return 'fdcc'
+ end
+ end
+ end
+ end
+ end
+end
+
+function M.dat(path, bufnr)
+ local file_name = vim.fn.fnamemodify(path, ':t'):lower()
+ -- Innovation data processing
+ if findany(file_name, { '^upstream%.dat$', '^upstream%..*%.dat$', '^.*%.upstream%.dat$' }) then
+ return 'upstreamdat'
+ end
+ if vim.g.filetype_dat then
+ return vim.g.filetype_dat
+ end
+ -- Determine if a *.dat file is Kuka Robot Language
+ local line = nextnonblank(bufnr, 1)
+ if matchregex(line, [[\c\v^\s*%(\&\w+|defdat>)]]) then
+ return 'krl'
+ end
+end
+
+function M.decl(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 3)) do
+ if line:lower():find('^<!sgml') then
+ return 'sgmldecl'
+ end
+ end
+end
+
+-- This function is called for all files under */debian/patches/*, make sure not
+-- to non-dep3patch files, such as README and other text files.
+function M.dep3patch(path, bufnr)
+ local file_name = vim.fn.fnamemodify(path, ':t')
+ if file_name == 'series' then
+ return
+ end
+
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ if
+ findany(line, {
+ '^Description:',
+ '^Subject:',
+ '^Origin:',
+ '^Bug:',
+ '^Forwarded:',
+ '^Author:',
+ '^From:',
+ '^Reviewed%-by:',
+ '^Acked%-by:',
+ '^Last%-Updated:',
+ '^Applied%-Upstream:',
+ })
+ then
+ return 'dep3patch'
+ elseif line:find('^%-%-%-') then
+ -- End of headers found. stop processing
+ return
+ end
+ end
+end
+
+local function diff(contents)
+ if
+ contents[1]:find('^%-%-%- ') and contents[2]:find('^%+%+%+ ')
+ or contents[1]:find('^%* looking for ') and contents[2]:find('^%* comparing to ')
+ or contents[1]:find('^%*%*%* ') and contents[2]:find('^%-%-%- ')
+ or contents[1]:find('^=== ') and ((contents[2]:find('^' .. string.rep('=', 66)) and contents[3]:find(
+ '^%-%-% '
+ ) and contents[4]:find('^%+%+%+')) or (contents[2]:find('^%-%-%- ') and contents[3]:find(
+ '^%+%+%+ '
+ )))
+ or findany(contents[1], { '^=== removed', '^=== added', '^=== renamed', '^=== modified' })
+ then
+ return 'diff'
+ end
+end
+
+function M.dns_zone(contents)
+ if
+ findany(
+ contents[1] .. contents[2] .. contents[3] .. contents[4],
+ { '^; <<>> DiG [0-9%.]+.* <<>>', '%$ORIGIN', '%$TTL', 'IN%s+SOA' }
+ )
+ then
+ return 'bindzone'
+ end
+ -- BAAN
+ if -- Check for 1 to 80 '*' characters
+ contents[1]:find('|%*' .. string.rep('%*?', 79)) and contents[2]:find('VRC ')
+ or contents[2]:find('|%*' .. string.rep('%*?', 79)) and contents[3]:find('VRC ')
+ then
+ return 'baan'
+ end
+end
+
+function M.dtrace(bufnr)
+ if vim.fn.did_filetype() ~= 0 then
+ -- Filetype was already detected
+ return
+ end
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ if matchregex(line, [[\c^module\>\|^import\>]]) then
+ -- D files often start with a module and/or import statement.
+ return 'd'
+ elseif findany(line, { '^#!%S+dtrace', '#pragma%s+D%s+option', ':%S-:%S-:' }) then
+ return 'dtrace'
+ end
+ end
+ return 'd'
+end
+
+function M.e(bufnr)
+ if vim.g.filetype_euphoria then
+ return vim.g.filetype_euphoria
+ end
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ if findany(line, { "^%s*<'%s*$", "^%s*'>%s*$" }) then
+ return 'specman'
+ end
+ end
+ return 'eiffel'
+end
+
+function M.edn(bufnr)
+ local line = getlines(bufnr, 1)
+ if matchregex(line, [[\c^\s*(\s*edif\>]]) then
+ return 'edif'
+ else
+ return 'clojure'
+ end
+end
+
+-- This function checks for valid cl syntax in the first five lines.
+-- Look for either an opening comment, '#', or a block start, '{'.
+-- If not found, assume SGML.
+function M.ent(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 5)) do
+ if line:find('^%s*[#{]') then
+ return 'cl'
+ elseif not line:find('^%s*$') then
+ -- Not a blank line, not a comment, and not a block start,
+ -- so doesn't look like valid cl code.
+ break
+ end
+ end
+ return 'dtd'
+end
+
+function M.ex(bufnr)
+ if vim.g.filetype_euphoria then
+ return vim.g.filetype_euphoria
+ else
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ if matchregex(line, [[\c^--\|^ifdef\>\|^include\>]]) then
+ return 'euphoria3'
+ end
+ end
+ return 'elixir'
+ end
+end
+
+-- This function checks the first 15 lines for appearance of 'FoamFile'
+-- and then 'object' in a following line.
+-- In that case, it's probably an OpenFOAM file
+function M.foam(bufnr)
+ local foam_file = false
+ for _, line in ipairs(getlines(bufnr, 1, 15)) do
+ if line:find('^FoamFile') then
+ foam_file = true
+ elseif foam_file and line:find('^%s*object') then
+ return 'foam'
+ end
+ end
+end
+
+function M.frm(bufnr)
+ if vim.g.filetype_frm then
+ return vim.g.filetype_frm
+ end
+ local lines = table.concat(getlines(bufnr, 1, 5)):lower()
+ if findany(lines, visual_basic_content) then
+ return 'vb'
+ else
+ return 'form'
+ end
+end
+
+function M.fvwm(path)
+ if vim.fn.fnamemodify(path, ':e') == 'm4' then
+ return 'fvwm2m4'
+ end
+ return 'fvwm', function(bufnr)
+ vim.b[bufnr].fvwm_version = 2
+ end
+end
+
+-- Distinguish between Forth and F#.
+function M.fs(bufnr)
+ if vim.g.filetype_fs then
+ return vim.g.filetype_fs
+ end
+ local line = nextnonblank(bufnr, 1)
+ if findany(line, { '^%s*%.?%( ', '^%s*\\G? ', '^\\$', '^%s*: %S' }) then
+ return 'forth'
+ else
+ return 'fsharp'
+ end
+end
+
+function M.git(bufnr)
+ local line = getlines(bufnr, 1)
+ if matchregex(line, [[^\x\{40,\}\>\|^ref: ]]) then
+ return 'git'
+ end
+end
+
+function M.header(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 200)) do
+ if findany(line:lower(), { '^@interface', '^@end', '^@class' }) then
+ if vim.g.c_syntax_for_h then
+ return 'objc'
+ else
+ return 'objcpp'
+ end
+ end
+ end
+ if vim.g.c_syntax_for_h then
+ return 'c'
+ elseif vim.g.ch_syntax_for_h then
+ return 'ch'
+ else
+ return 'cpp'
+ end
+end
+
+function M.html(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 10)) do
+ if matchregex(line, [[\<DTD\s\+XHTML\s]]) then
+ return 'xhtml'
+ elseif matchregex(line, [[\c{%\s*\(extends\|block\|load\)\>\|{#\s\+]]) then
+ return 'htmldjango'
+ end
+ end
+ return 'html'
+end
+
+-- Virata Config Script File or Drupal module
+function M.hw(bufnr)
+ if getlines(bufnr, 1):lower():find('<%?php') then
+ return 'php'
+ end
+ return 'virata'
+end
+
+function M.idl(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 50)) do
+ if findany(line:lower(), { '^%s*import%s+"unknwn"%.idl', '^%s*import%s+"objidl"%.idl' }) then
+ return 'msidl'
+ end
+ end
+ return 'idl'
+end
+
+local pascal_comments = { '^%s*{', '^%s*%(%*', '^%s*//' }
+local pascal_keywords =
+ [[\c^\s*\%(program\|unit\|library\|uses\|begin\|procedure\|function\|const\|type\|var\)\>]]
+
+function M.inc(bufnr)
+ if vim.g.filetype_inc then
+ return vim.g.filetype_inc
+ end
+ local lines = table.concat(getlines(bufnr, 1, 3))
+ if lines:lower():find('perlscript') then
+ return 'aspperl'
+ elseif lines:find('<%%') then
+ return 'aspvbs'
+ elseif lines:find('<%?') then
+ return 'php'
+ -- Pascal supports // comments but they're vary rarely used for file
+ -- headers so assume POV-Ray
+ elseif findany(lines, { '^%s{', '^%s%(%*' }) or matchregex(lines, pascal_keywords) then
+ return 'pascal'
+ elseif findany(lines, { '^%s*inherit ', '^%s*require ', '^%s*%w+%s+= ' }) then
+ return 'bitbake'
+ else
+ local syntax = M.asm_syntax(bufnr)
+ if not syntax or syntax == '' then
+ return 'pov'
+ end
+ return syntax, function(b)
+ vim.b[b].asmsyntax = syntax
+ end
+ end
+end
+
+function M.inp(bufnr)
+ if getlines(bufnr, 1):find('^%*') then
+ return 'abaqus'
+ else
+ for _, line in ipairs(getlines(bufnr, 1, 500)) do
+ if line:lower():find('^header surface data') then
+ return 'trasys'
+ end
+ end
+ end
+end
+
+function M.install(path, bufnr)
+ if getlines(bufnr, 1):lower():find('<%?php') then
+ return 'php'
+ end
+ return M.sh(path, getlines(bufnr), 'bash')
+end
+
+-- Innovation Data Processing
+-- (refactor of filetype.vim since the patterns are case-insensitive)
+function M.log(path)
+ path = path:lower()
+ if
+ findany(
+ path,
+ { 'upstream%.log', 'upstream%..*%.log', '.*%.upstream%.log', 'upstream%-.*%.log' }
+ )
+ then
+ return 'upstreamlog'
+ elseif
+ findany(
+ path,
+ { 'upstreaminstall%.log', 'upstreaminstall%..*%.log', '.*%.upstreaminstall%.log' }
+ )
+ then
+ return 'upstreaminstalllog'
+ elseif findany(path, { 'usserver%.log', 'usserver%..*%.log', '.*%.usserver%.log' }) then
+ return 'usserverlog'
+ elseif findany(path, { 'usw2kagt%.log', 'usw2kagt%..*%.log', '.*%.usw2kagt%.log' }) then
+ return 'usw2kagtlog'
+ end
+end
+
+function M.lpc(bufnr)
+ if vim.g.lpc_syntax_for_c then
+ for _, line in ipairs(getlines(bufnr, 1, 12)) do
+ if
+ findany(line, {
+ '^//',
+ '^inherit',
+ '^private',
+ '^protected',
+ '^nosave',
+ '^string',
+ '^object',
+ '^mapping',
+ '^mixed',
+ })
+ then
+ return 'lpc'
+ end
+ end
+ end
+ return 'c'
+end
+
+function M.m(bufnr)
+ if vim.g.filetype_m then
+ return vim.g.filetype_m
+ end
+
+ -- Excluding end(for|function|if|switch|while) common to Murphi
+ local octave_block_terminators =
+ [[\<end\%(_try_catch\|classdef\|enumeration\|events\|methods\|parfor\|properties\)\>]]
+ local objc_preprocessor =
+ [[\c^\s*#\s*\%(import\|include\|define\|if\|ifn\=def\|undef\|line\|error\|pragma\)\>]]
+
+ -- Whether we've seen a multiline comment leader
+ local saw_comment = false
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ if line:find('^%s*/%*') then
+ -- /* ... */ is a comment in Objective C and Murphi, so we can't conclude
+ -- it's either of them yet, but track this as a hint in case we don't see
+ -- anything more definitive.
+ saw_comment = true
+ end
+ if
+ line:find('^%s*//')
+ or matchregex(line, [[\c^\s*@import\>]])
+ or matchregex(line, objc_preprocessor)
+ then
+ return 'objc'
+ end
+ if
+ findany(line, { '^%s*#', '^%s*%%!' })
+ or matchregex(line, [[\c^\s*unwind_protect\>]])
+ or matchregex(line, [[\c\%(^\|;\)\s*]] .. octave_block_terminators)
+ then
+ return 'octave'
+ elseif line:find('^%s*%%') then
+ return 'matlab'
+ elseif line:find('^%s*%(%*') then
+ return 'mma'
+ elseif matchregex(line, [[\c^\s*\(\(type\|var\)\>\|--\)]]) then
+ return 'murphi'
+ end
+ end
+
+ if saw_comment then
+ -- We didn't see anything definitive, but this looks like either Objective C
+ -- or Murphi based on the comment leader. Assume the former as it is more
+ -- common.
+ return 'objc'
+ else
+ -- Default is Matlab
+ return 'matlab'
+ end
+end
+
+local function m4(contents)
+ for _, line in ipairs(contents) do
+ if matchregex(line, [[^\s*dnl\>]]) then
+ return 'm4'
+ end
+ end
+ if vim.env.TERM == 'amiga' and findany(contents[1]:lower(), { '^;', '^%.bra' }) then
+ -- AmigaDos scripts
+ return 'amiga'
+ end
+end
+
+-- Rely on the file to start with a comment.
+-- MS message text files use ';', Sendmail files use '#' or 'dnl'
+function M.mc(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 20)) do
+ if findany(line:lower(), { '^%s*#', '^%s*dnl' }) then
+ -- Sendmail .mc file
+ return 'm4'
+ elseif line:find('^%s*;') then
+ return 'msmessages'
+ end
+ end
+ -- Default: Sendmail .mc file
+ return 'm4'
+end
+
+function M.me(path)
+ local filename = vim.fn.fnamemodify(path, ':t'):lower()
+ if filename ~= 'read.me' and filename ~= 'click.me' then
+ return 'nroff'
+ end
+end
+
+function M.mm(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 20)) do
+ if matchregex(line, [[\c^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\)]]) then
+ return 'objcpp'
+ end
+ end
+ return 'nroff'
+end
+
+function M.mms(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 20)) do
+ if findany(line, { '^%s*%%', '^%s*//', '^%*' }) then
+ return 'mmix'
+ elseif line:find('^%s*#') then
+ return 'make'
+ end
+ end
+ return 'mmix'
+end
+
+-- Returns true if file content looks like LambdaProlog
+local function is_lprolog(bufnr)
+ -- Skip apparent comments and blank lines, what looks like
+ -- LambdaProlog comment may be RAPID header
+ for _, line in ipairs(getlines(bufnr)) do
+ -- The second pattern matches a LambdaProlog comment
+ if not findany(line, { '^%s*$', '^%s*%%' }) then
+ -- The pattern must not catch a go.mod file
+ return matchregex(line, [[\c\<module\s\+\w\+\s*\.\s*\(%\|$\)]]) ~= nil
+ end
+ end
+end
+
+-- Determine if *.mod is ABB RAPID, LambdaProlog, Modula-2, Modsim III or go.mod
+function M.mod(path, bufnr)
+ if vim.g.filetype_mod then
+ return vim.g.filetype_mod
+ elseif is_lprolog(bufnr) then
+ return 'lprolog'
+ elseif matchregex(nextnonblank(bufnr, 1), [[\%(\<MODULE\s\+\w\+\s*;\|^\s*(\*\)]]) then
+ return 'modula2'
+ elseif is_rapid(bufnr) then
+ return 'rapid'
+ elseif matchregex(path, [[\c\<go\.mod$]]) then
+ return 'gomod'
+ else
+ -- Nothing recognized, assume modsim3
+ return 'modsim3'
+ end
+end
+
+function M.news(bufnr)
+ if getlines(bufnr, 1):lower():find('; urgency=') then
+ return 'debchangelog'
+ end
+end
+
+-- This function checks if one of the first five lines start with a dot. In
+-- that case it is probably an nroff file.
+function M.nroff(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 5)) do
+ if line:find('^%.') then
+ return 'nroff'
+ end
+ end
+end
+
+function M.patch(bufnr)
+ local firstline = getlines(bufnr, 1)
+ if string.find(firstline, '^From ' .. string.rep('%x', 40) .. '+ Mon Sep 17 00:00:00 2001$') then
+ return 'gitsendemail'
+ else
+ return 'diff'
+ end
+end
+
+-- If the file has an extension of 't' and is in a directory 't' or 'xt' then
+-- it is almost certainly a Perl test file.
+-- If the first line starts with '#' and contains 'perl' it's probably a Perl file.
+-- (Slow test) If a file contains a 'use' statement then it is almost certainly a Perl file.
+function M.perl(path, bufnr)
+ local dir_name = vim.fs.dirname(path)
+ if vim.fn.expand(path, '%:e') == 't' and (dir_name == 't' or dir_name == 'xt') then
+ return 'perl'
+ end
+ local first_line = getlines(bufnr, 1)
+ if first_line:find('^#') and first_line:lower():find('perl') then
+ return 'perl'
+ end
+ for _, line in ipairs(getlines(bufnr, 1, 30)) do
+ if matchregex(line, [[\c^use\s\s*\k]]) then
+ return 'perl'
+ end
+ end
+end
+
+function M.pl(bufnr)
+ if vim.g.filetype_pl then
+ return vim.g.filetype_pl
+ end
+ -- Recognize Prolog by specific text in the first non-empty line;
+ -- require a blank after the '%' because Perl uses "%list" and "%translate"
+ local line = nextnonblank(bufnr, 1)
+ if
+ line and line:find(':%-')
+ or matchregex(line, [[\c\<prolog\>]])
+ or findany(line, { '^%s*%%+%s', '^%s*%%+$', '^%s*/%*' })
+ then
+ return 'prolog'
+ else
+ return 'perl'
+ end
+end
+
+function M.pm(bufnr)
+ local line = getlines(bufnr, 1)
+ if line:find('XPM2') then
+ return 'xpm2'
+ elseif line:find('XPM') then
+ return 'xpm'
+ else
+ return 'perl'
+ end
+end
+
+function M.pp(bufnr)
+ if vim.g.filetype_pp then
+ return vim.g.filetype_pp
+ end
+ local line = nextnonblank(bufnr, 1)
+ if findany(line, pascal_comments) or matchregex(line, pascal_keywords) then
+ return 'pascal'
+ else
+ return 'puppet'
+ end
+end
+
+function M.prg(bufnr)
+ if vim.g.filetype_prg then
+ return vim.g.filetype_prg
+ elseif is_rapid(bufnr) then
+ return 'rapid'
+ else
+ -- Nothing recognized, assume Clipper
+ return 'clipper'
+ end
+end
+
+function M.printcap(ptcap_type)
+ if vim.fn.did_filetype() == 0 then
+ return 'ptcap', function(bufnr)
+ vim.b[bufnr].ptcap_type = ptcap_type
+ end
+ end
+end
+
+-- This function checks for an assembly comment in the first ten lines.
+-- If not found, assume Progress.
+function M.progress_asm(bufnr)
+ if vim.g.filetype_i then
+ return vim.g.filetype_i
+ end
+
+ for _, line in ipairs(getlines(bufnr, 1, 10)) do
+ if line:find('^%s*;') or line:find('^/%*') then
+ return M.asm(bufnr)
+ elseif not line:find('^%s*$') or line:find('^/%*') then
+ -- Not an empty line: doesn't look like valid assembly code
+ -- or it looks like a Progress /* comment.
+ break
+ end
+ end
+ return 'progress'
+end
+
+function M.progress_cweb(bufnr)
+ if vim.g.filetype_w then
+ return vim.g.filetype_w
+ else
+ if
+ getlines(bufnr, 1):lower():find('^&analyze')
+ or getlines(bufnr, 3):lower():find('^&global%-define')
+ then
+ return 'progress'
+ else
+ return 'cweb'
+ end
+ end
+end
+
+-- This function checks for valid Pascal syntax in the first 10 lines.
+-- Look for either an opening comment or a program start.
+-- If not found, assume Progress.
+function M.progress_pascal(bufnr)
+ if vim.g.filetype_p then
+ return vim.g.filetype_p
+ end
+ for _, line in ipairs(getlines(bufnr, 1, 10)) do
+ if findany(line, pascal_comments) or matchregex(line, pascal_keywords) then
+ return 'pascal'
+ elseif not line:find('^%s*$') or line:find('^/%*') then
+ -- Not an empty line: Doesn't look like valid Pascal code.
+ -- Or it looks like a Progress /* comment
+ break
+ end
+ end
+ return 'progress'
+end
+
+-- Distinguish between "default" and Cproto prototype file.
+function M.proto(bufnr, default)
+ -- Cproto files have a comment in the first line and a function prototype in
+ -- the second line, it always ends in ";". Indent files may also have
+ -- comments, thus we can't match comments to see the difference.
+ -- IDL files can have a single ';' in the second line, require at least one
+ -- character before the ';'.
+ if getlines(bufnr, 2):find('.;$') then
+ return 'cpp'
+ else
+ return default
+ end
+end
+
+-- Software Distributor Product Specification File (POSIX 1387.2-1995)
+function M.psf(bufnr)
+ local line = getlines(bufnr, 1):lower()
+ if
+ findany(line, {
+ '^%s*distribution%s*$',
+ '^%s*installed_software%s*$',
+ '^%s*root%s*$',
+ '^%s*bundle%s*$',
+ '^%s*product%s*$',
+ })
+ then
+ return 'psf'
+ end
+end
+
+function M.r(bufnr)
+ local lines = getlines(bufnr, 1, 50)
+ -- Rebol is easy to recognize, check for that first
+ if matchregex(table.concat(lines), [[\c\<rebol\>]]) then
+ return 'rebol'
+ end
+
+ for _, line in ipairs(lines) do
+ -- R has # comments
+ if line:find('^%s*#') then
+ return 'r'
+ end
+ -- Rexx has /* comments */
+ if line:find('^%s*/%*') then
+ return 'rexx'
+ end
+ end
+
+ -- Nothing recognized, use user default or assume R
+ if vim.g.filetype_r then
+ return vim.g.filetype_r
+ else
+ -- Rexx used to be the default, but R appears to be much more popular.
+ return 'r'
+ end
+end
+
+function M.redif(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 5)) do
+ if line:lower():find('^template%-type:') then
+ return 'redif'
+ end
+ end
+end
+
+function M.reg(bufnr)
+ local line = getlines(bufnr, 1):lower()
+ if
+ line:find('^regedit[0-9]*%s*$') or line:find('^windows registry editor version %d*%.%d*%s*$')
+ then
+ return 'registry'
+ end
+end
+
+-- Diva (with Skill) or InstallShield
+function M.rul(bufnr)
+ if table.concat(getlines(bufnr, 1, 6)):lower():find('installshield') then
+ return 'ishd'
+ end
+ return 'diva'
+end
+
+local udev_rules_pattern = '^%s*udev_rules%s*=%s*"([%^"]+)/*".*'
+function M.rules(path)
+ path = path:lower()
+ if
+ findany(path, {
+ '/etc/udev/.*%.rules$',
+ '/etc/udev/rules%.d/.*$.rules$',
+ '/usr/lib/udev/.*%.rules$',
+ '/usr/lib/udev/rules%.d/.*%.rules$',
+ '/lib/udev/.*%.rules$',
+ '/lib/udev/rules%.d/.*%.rules$',
+ })
+ then
+ return 'udevrules'
+ elseif path:find('^/etc/ufw/') then
+ -- Better than hog
+ return 'conf'
+ elseif findany(path, { '^/etc/polkit%-1/rules%.d', '/usr/share/polkit%-1/rules%.d' }) then
+ return 'javascript'
+ else
+ local ok, config_lines = pcall(vim.fn.readfile, '/etc/udev/udev.conf')
+ if not ok then
+ return 'hog'
+ end
+ local dir = vim.fn.expand(path, ':h')
+ for _, line in ipairs(config_lines) do
+ local match = line:match(udev_rules_pattern)
+ if match then
+ local udev_rules = line:gsub(udev_rules_pattern, match, 1)
+ if dir == udev_rules then
+ return 'udevrules'
+ end
+ end
+ end
+ return 'hog'
+ end
+end
+
+-- LambdaProlog and Standard ML signature files
+function M.sig(bufnr)
+ if vim.g.filetype_sig then
+ return vim.g.filetype_sig
+ end
+
+ local line = nextnonblank(bufnr, 1)
+
+ -- LambdaProlog comment or keyword
+ if findany(line, { '^%s*/%*', '^%s*%%', '^%s*sig%s+%a' }) then
+ return 'lprolog'
+ -- SML comment or keyword
+ elseif findany(line, { '^%s*%(%*', '^%s*signature%s+%a', '^%s*structure%s+%a' }) then
+ return 'sml'
+ end
+end
+
+-- This function checks the first 25 lines of file extension "sc" to resolve
+-- detection between scala and SuperCollider
+function M.sc(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 25)) do
+ if
+ findany(line, {
+ '[A-Za-z0-9]*%s:%s[A-Za-z0-9]',
+ 'var%s<',
+ 'classvar%s<',
+ '%^this.*',
+ '|%w*|',
+ '%+%s%w*%s{',
+ '%*ar%s',
+ })
+ then
+ return 'supercollider'
+ end
+ end
+ return 'scala'
+end
+
+-- This function checks the first line of file extension "scd" to resolve
+-- detection between scdoc and SuperCollider
+function M.scd(bufnr)
+ local first = '^%S+%(%d[0-9A-Za-z]*%)'
+ local opt = [[%s+"[^"]*"]]
+ local line = getlines(bufnr, 1)
+ if findany(line, { first .. '$', first .. opt .. '$', first .. opt .. opt .. '$' }) then
+ return 'scdoc'
+ else
+ return 'supercollider'
+ end
+end
+
+function M.sgml(bufnr)
+ local lines = table.concat(getlines(bufnr, 1, 5))
+ if lines:find('linuxdoc') then
+ return 'smgllnx'
+ elseif lines:find('<!DOCTYPE.*DocBook') then
+ return 'docbk',
+ function(b)
+ vim.b[b].docbk_type = 'sgml'
+ vim.b[b].docbk_ver = 4
+ end
+ else
+ return 'sgml'
+ end
+end
+
+function M.sh(path, contents, name)
+ -- Path may be nil, do not fail in that case
+ if vim.fn.did_filetype() ~= 0 or (path or ''):find(vim.g.ft_ignore_pat) then
+ -- Filetype was already detected or detection should be skipped
+ return
+ end
+
+ local on_detect
+
+ -- Get the name from the first line if not specified
+ name = name or contents[1]
+ if matchregex(name, [[\<csh\>]]) then
+ -- Some .sh scripts contain #!/bin/csh.
+ return M.shell(path, contents, 'csh')
+ -- Some .sh scripts contain #!/bin/tcsh.
+ elseif matchregex(name, [[\<tcsh\>]]) then
+ return M.shell(path, contents, 'tcsh')
+ -- Some .sh scripts contain #!/bin/zsh.
+ elseif matchregex(name, [[\<zsh\>]]) then
+ return M.shell(path, contents, 'zsh')
+ elseif matchregex(name, [[\<ksh\>]]) then
+ on_detect = function(b)
+ vim.b[b].is_kornshell = 1
+ vim.b[b].is_bash = nil
+ vim.b[b].is_sh = nil
+ end
+ elseif vim.g.bash_is_sh or matchregex(name, [[\<bash\>]]) or matchregex(name, [[\<bash2\>]]) then
+ on_detect = function(b)
+ vim.b[b].is_bash = 1
+ vim.b[b].is_kornshell = nil
+ vim.b[b].is_sh = nil
+ end
+ elseif matchregex(name, [[\<sh\>]]) then
+ on_detect = function(b)
+ vim.b[b].is_sh = 1
+ vim.b[b].is_kornshell = nil
+ vim.b[b].is_bash = nil
+ end
+ end
+ return M.shell(path, contents, 'sh'), on_detect
+end
+
+-- For shell-like file types, check for an "exec" command hidden in a comment, as used for Tcl.
+function M.shell(path, contents, name)
+ if vim.fn.did_filetype() ~= 0 or matchregex(path, vim.g.ft_ignore_pat) then
+ -- Filetype was already detected or detection should be skipped
+ return
+ end
+
+ local prev_line = ''
+ for line_nr, line in ipairs(contents) do
+ -- Skip the first line
+ if line_nr ~= 1 then
+ line = line:lower()
+ if line:find('%s*exec%s') and not prev_line:find('^%s*#.*\\$') then
+ -- Found an "exec" line after a comment with continuation
+ local n = line:gsub('%s*exec%s+([^ ]*/)?', '', 1)
+ if matchregex(n, [[\c\<tclsh\|\<wish]]) then
+ return 'tcl'
+ end
+ end
+ prev_line = line
+ end
+ end
+ return name
+end
+
+-- SMIL or SNMP MIB file
+function M.smi(bufnr)
+ local line = getlines(bufnr, 1)
+ if matchregex(line, [[\c\<smil\>]]) then
+ return 'smil'
+ else
+ return 'mib'
+ end
+end
+
+-- Determine if a *.src file is Kuka Robot Language
+function M.src(bufnr)
+ if vim.g.filetype_src then
+ return vim.g.filetype_src
+ end
+ local line = nextnonblank(bufnr, 1)
+ if matchregex(line, [[\c\v^\s*%(\&\w+|%(global\s+)?def%(fct)?>)]]) then
+ return 'krl'
+ end
+end
+
+function M.sys(bufnr)
+ if vim.g.filetype_sys then
+ return vim.g.filetype_sys
+ elseif is_rapid(bufnr) then
+ return 'rapid'
+ else
+ return 'bat'
+ end
+end
+
+-- Choose context, plaintex, or tex (LaTeX) based on these rules:
+-- 1. Check the first line of the file for "%&<format>".
+-- 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords.
+-- 3. Default to "plain" or to g:tex_flavor, can be set in user's vimrc.
+function M.tex(path, bufnr)
+ local format = getlines(bufnr, 1):find('^%%&%s*(%a+)')
+ if format then
+ format = format:lower():gsub('pdf', '', 1)
+ if format == 'tex' then
+ return 'tex'
+ elseif format == 'plaintex' then
+ return 'plaintex'
+ end
+ elseif path:lower():find('tex/context/.*/.*%.tex') then
+ return 'context'
+ else
+ local lpat = [[documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>]]
+ local cpat =
+ [[start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>]]
+
+ for i, l in ipairs(getlines(bufnr, 1, 1000)) do
+ -- Find first non-comment line
+ if not l:find('^%s*%%%S') then
+ -- Check the next thousand lines for a LaTeX or ConTeXt keyword.
+ for _, line in ipairs(getlines(bufnr, i + 1, i + 1000)) do
+ local lpat_match, cpat_match =
+ matchregex(line, [[\c^\s*\\\%(]] .. lpat .. [[\)\|^\s*\\\(]] .. cpat .. [[\)]])
+ if lpat_match then
+ return 'tex'
+ elseif cpat_match then
+ return 'context'
+ end
+ end
+ end
+ end
+ -- TODO: add AMSTeX, RevTex, others?
+ if not vim.g.tex_flavor or vim.g.tex_flavor == 'plain' then
+ return 'plaintex'
+ elseif vim.g.tex_flavor == 'context' then
+ return 'context'
+ else
+ -- Probably LaTeX
+ return 'tex'
+ end
+ end
+end
+
+-- Determine if a *.tf file is TF mud client or terraform
+function M.tf(bufnr)
+ for _, line in ipairs(getlines(bufnr)) do
+ -- Assume terraform file on a non-empty line (not whitespace-only)
+ -- and when the first non-whitespace character is not a ; or /
+ if not line:find('^%s*$') and not line:find('^%s*[;/]') then
+ return 'terraform'
+ end
+ end
+ return 'tf'
+end
+
+function M.ttl(bufnr)
+ local line = getlines(bufnr, 1):lower()
+ if line:find('^@?prefix') or line:find('^@?base') then
+ return 'turtle'
+ end
+ return 'teraterm'
+end
+
+function M.txt(bufnr)
+ -- helpfiles match *.txt, but should have a modeline as last line
+ if not getlines(bufnr, -1):find('vim:.*ft=help') then
+ return 'text'
+ end
+end
+
+-- WEB (*.web is also used for Winbatch: Guess, based on expecting "%" comment
+-- lines in a WEB file).
+function M.web(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 5)) do
+ if line:find('^%%') then
+ return 'web'
+ end
+ end
+ return 'winbatch'
+end
+
+-- XFree86 config
+function M.xfree86()
+ return 'xf86conf',
+ function(bufnr)
+ local line = getlines(bufnr, 1)
+ if matchregex(line, [[\<XConfigurator\>]]) then
+ vim.b[bufnr].xf86conf_xfree86_version = 3
+ end
+ end
+end
+
+function M.xml(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ local is_docbook4 = line:find('<!DOCTYPE.*DocBook')
+ line = line:lower()
+ local is_docbook5 = line:find([[ xmlns="http://docbook.org/ns/docbook"]])
+ if is_docbook4 or is_docbook5 then
+ return 'docbk',
+ function(b)
+ vim.b[b].docbk_type = 'xml'
+ vim.b[b].docbk_ver = is_docbook4 and 4 or 5
+ end
+ end
+ if line:find([[xmlns:xbl="http://www.mozilla.org/xbl"]]) then
+ return 'xbl'
+ end
+ end
+ return 'xml'
+end
+
+function M.y(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ if line:find('^%s*%%') then
+ return 'yacc'
+ end
+ if matchregex(line, [[\c^\s*\(#\|class\>\)]]) and not line:lower():find('^%s*#%s*include') then
+ return 'racc'
+ end
+ end
+ return 'yacc'
+end
+
+-- luacheck: pop
+-- luacheck: pop
+
+local patterns_hashbang = {
+ ['^zsh\\>'] = { 'zsh', { vim_regex = true } },
+ ['^\\(tclsh\\|wish\\|expectk\\|itclsh\\|itkwish\\)\\>'] = { 'tcl', { vim_regex = true } },
+ ['^expect\\>'] = { 'expect', { vim_regex = true } },
+ ['^gnuplot\\>'] = { 'gnuplot', { vim_regex = true } },
+ ['make\\>'] = { 'make', { vim_regex = true } },
+ ['^pike\\%(\\>\\|[0-9]\\)'] = { 'pike', { vim_regex = true } },
+ lua = 'lua',
+ perl = 'perl',
+ php = 'php',
+ python = 'python',
+ ['^groovy\\>'] = { 'groovy', { vim_regex = true } },
+ raku = 'raku',
+ ruby = 'ruby',
+ ['node\\(js\\)\\=\\>\\|js\\>'] = { 'javascript', { vim_regex = true } },
+ ['rhino\\>'] = { 'javascript', { vim_regex = true } },
+ -- BC calculator
+ ['^bc\\>'] = { 'bc', { vim_regex = true } },
+ ['sed\\>'] = { 'sed', { vim_regex = true } },
+ ocaml = 'ocaml',
+ -- Awk scripts; also finds "gawk"
+ ['awk\\>'] = { 'awk', { vim_regex = true } },
+ wml = 'wml',
+ scheme = 'scheme',
+ cfengine = 'cfengine',
+ escript = 'erlang',
+ haskell = 'haskell',
+ clojure = 'clojure',
+ ['scala\\>'] = { 'scala', { vim_regex = true } },
+ -- Free Pascal
+ ['instantfpc\\>'] = { 'pascal', { vim_regex = true } },
+ ['fennel\\>'] = { 'fennel', { vim_regex = true } },
+ -- MikroTik RouterOS script
+ ['rsc\\>'] = { 'routeros', { vim_regex = true } },
+ ['fish\\>'] = { 'fish', { vim_regex = true } },
+ ['gforth\\>'] = { 'forth', { vim_regex = true } },
+ ['icon\\>'] = { 'icon', { vim_regex = true } },
+ guile = 'scheme',
+}
+
+---@private
+-- File starts with "#!".
+local function match_from_hashbang(contents, path)
+ local first_line = contents[1]
+ -- Check for a line like "#!/usr/bin/env {options} bash". Turn it into
+ -- "#!/usr/bin/bash" to make matching easier.
+ -- Recognize only a few {options} that are commonly used.
+ if matchregex(first_line, [[^#!\s*\S*\<env\s]]) then
+ first_line = first_line:gsub('%S+=%S+', '')
+ first_line = first_line
+ :gsub('%-%-ignore%-environment', '', 1)
+ :gsub('%-%-split%-string', '', 1)
+ :gsub('%-[iS]', '', 1)
+ first_line = vim.fn.substitute(first_line, [[\<env\s\+]], '', '')
+ end
+
+ -- Get the program name.
+ -- Only accept spaces in PC style paths: "#!c:/program files/perl [args]".
+ -- If the word env is used, use the first word after the space:
+ -- "#!/usr/bin/env perl [path/args]"
+ -- If there is no path use the first word: "#!perl [path/args]".
+ -- Otherwise get the last word after a slash: "#!/usr/bin/perl [path/args]".
+ local name
+ if first_line:find('^#!%s*%a:[/\\]') then
+ name = vim.fn.substitute(first_line, [[^#!.*[/\\]\(\i\+\).*]], '\\1', '')
+ elseif matchregex(first_line, [[^#!.*\<env\>]]) then
+ name = vim.fn.substitute(first_line, [[^#!.*\<env\>\s\+\(\i\+\).*]], '\\1', '')
+ elseif matchregex(first_line, [[^#!\s*[^/\\ ]*\>\([^/\\]\|$\)]]) then
+ name = vim.fn.substitute(first_line, [[^#!\s*\([^/\\ ]*\>\).*]], '\\1', '')
+ else
+ name = vim.fn.substitute(first_line, [[^#!\s*\S*[/\\]\(\i\+\).*]], '\\1', '')
+ end
+
+ -- tcl scripts may have #!/bin/sh in the first line and "exec wish" in the
+ -- third line. Suggested by Steven Atkinson.
+ if contents[3] and contents[3]:find('^exec wish') then
+ name = 'wish'
+ end
+
+ if matchregex(name, [[^\(bash\d*\|\|ksh\d*\|sh\)\>]]) then
+ -- Bourne-like shell scripts: bash bash2 ksh ksh93 sh
+ return require('vim.filetype.detect').sh(path, contents, first_line)
+ elseif matchregex(name, [[^csh\>]]) then
+ return require('vim.filetype.detect').shell(path, contents, vim.g.filetype_csh or 'csh')
+ elseif matchregex(name, [[^tcsh\>]]) then
+ return require('vim.filetype.detect').shell(path, contents, 'tcsh')
+ end
+
+ for k, v in pairs(patterns_hashbang) do
+ local ft = type(v) == 'table' and v[1] or v
+ local opts = type(v) == 'table' and v[2] or {}
+ if opts.vim_regex and matchregex(name, k) or name:find(k) then
+ return ft
+ end
+ end
+end
+
+local patterns_text = {
+ ['^#compdef\\>'] = { 'zsh', { vim_regex = true } },
+ ['^#autoload\\>'] = { 'zsh', { vim_regex = true } },
+ -- ELM Mail files
+ ['^From [a-zA-Z][a-zA-Z_0-9%.=%-]*(@[^ ]*)? .* 19%d%d$'] = 'mail',
+ ['^From [a-zA-Z][a-zA-Z_0-9%.=%-]*(@[^ ]*)? .* 20%d%d$'] = 'mail',
+ ['^From %- .* 19%d%d$'] = 'mail',
+ ['^From %- .* 20%d%d$'] = 'mail',
+ -- Mason
+ ['^<[%%&].*>'] = 'mason',
+ -- Vim scripts (must have '" vim' as the first line to trigger this)
+ ['^" *[vV]im$['] = 'vim',
+ -- libcxx and libstdc++ standard library headers like ["iostream["] do not have
+ -- an extension, recognize the Emacs file mode.
+ ['%-%*%-.*[cC]%+%+.*%-%*%-'] = 'cpp',
+ ['^\\*\\* LambdaMOO Database, Format Version \\%([1-3]\\>\\)\\@!\\d\\+ \\*\\*$'] = {
+ 'moo',
+ { vim_regex = true },
+ },
+ -- Diff file:
+ -- - "diff" in first line (context diff)
+ -- - "Only in " in first line
+ -- - "--- " in first line and "+++ " in second line (unified diff).
+ -- - "*** " in first line and "--- " in second line (context diff).
+ -- - "# It was generated by makepatch " in the second line (makepatch diff).
+ -- - "Index: <filename>" in the first line (CVS file)
+ -- - "=== ", line of "=", "---", "+++ " (SVK diff)
+ -- - "=== ", "--- ", "+++ " (bzr diff, common case)
+ -- - "=== (removed|added|renamed|modified)" (bzr diff, alternative)
+ -- - "# HG changeset patch" in first line (Mercurial export format)
+ ['^\\(diff\\>\\|Only in \\|\\d\\+\\(,\\d\\+\\)\\=[cda]\\d\\+\\>\\|# It was generated by makepatch \\|Index:\\s\\+\\f\\+\\r\\=$\\|===== \\f\\+ \\d\\+\\.\\d\\+ vs edited\\|==== //\\f\\+#\\d\\+\\|# HG changeset patch\\)'] = {
+ 'diff',
+ { vim_regex = true },
+ },
+ function(contents)
+ return diff(contents)
+ end,
+ -- PostScript Files (must have %!PS as the first line, like a2ps output)
+ ['^%%![ \t]*PS'] = 'postscr',
+ function(contents)
+ return m4(contents)
+ end,
+ -- SiCAD scripts (must have procn or procd as the first line to trigger this)
+ ['^ *proc[nd] *$'] = { 'sicad', { ignore_case = true } },
+ ['^%*%*%*%* Purify'] = 'purifylog',
+ -- XML
+ ['<%?%s*xml.*%?>'] = 'xml',
+ -- XHTML (e.g.: PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN")
+ ['\\<DTD\\s\\+XHTML\\s'] = 'xhtml',
+ -- HTML (e.g.: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN")
+ -- Avoid "doctype html", used by slim.
+ ['\\c<!DOCTYPE\\s\\+html\\>'] = { 'html', { vim_regex = true } },
+ -- PDF
+ ['^%%PDF%-'] = 'pdf',
+ -- XXD output
+ ['^%x%x%x%x%x%x%x: %x%x ?%x%x ?%x%x ?%x%x '] = 'xxd',
+ -- RCS/CVS log output
+ ['^RCS file:'] = { 'rcslog', { start_lnum = 1, end_lnum = 2 } },
+ -- CVS commit
+ ['^CVS:'] = { 'cvs', { start_lnum = 2 } },
+ ['^CVS: '] = { 'cvs', { start_lnum = -1 } },
+ -- Prescribe
+ ['^!R!'] = 'prescribe',
+ -- Send-pr
+ ['^SEND%-PR:'] = 'sendpr',
+ -- SNNS files
+ ['^SNNS network definition file'] = 'snnsnet',
+ ['^SNNS pattern definition file'] = 'snnspat',
+ ['^SNNS result file'] = 'snnsres',
+ ['^%%.-[Vv]irata'] = { 'virata', { start_lnum = 1, end_lnum = 5 } },
+ ['[0-9:%.]* *execve%('] = 'strace',
+ ['^__libc_start_main'] = 'strace',
+ -- VSE JCL
+ ['^\\* $$ JOB\\>'] = { 'vsejcl', { vim_regex = true } },
+ ['^// *JOB\\>'] = { 'vsejcl', { vim_regex = true } },
+ -- TAK and SINDA
+ ['K & K Associates'] = { 'takout', { start_lnum = 4 } },
+ ['TAK 2000'] = { 'takout', { start_lnum = 2 } },
+ ['S Y S T E M S I M P R O V E D '] = { 'syndaout', { start_lnum = 3 } },
+ ['Run Date: '] = { 'takcmp', { start_lnum = 6 } },
+ ['Node File 1'] = { 'sindacmp', { start_lnum = 9 } },
+ function(contents)
+ require('vim.filetype.detect').dns_zone(contents)
+ end,
+ -- Valgrind
+ ['^==%d+== valgrind'] = 'valgrind',
+ ['^==%d+== Using valgrind'] = { 'valgrind', { start_lnum = 3 } },
+ -- Go docs
+ ['PACKAGE DOCUMENTATION$'] = 'godoc',
+ -- Renderman Interface Bytestream
+ ['^##RenderMan'] = 'rib',
+ -- Scheme scripts
+ ['exec%s%+%S*scheme'] = { 'scheme', { start_lnum = 1, end_lnum = 2 } },
+ -- Git output
+ ['^\\(commit\\|tree\\|object\\) \\x\\{40,\\}\\>\\|^tag \\S\\+$'] = {
+ 'git',
+ { vim_regex = true },
+ },
+ function(lines)
+ -- Gprof (gnu profiler)
+ if
+ lines[1] == 'Flat profile:'
+ and lines[2] == ''
+ and lines[3]:find('^Each sample counts as .* seconds%.$')
+ then
+ return 'gprof'
+ end
+ end,
+ -- Erlang terms
+ -- (See also: http://www.gnu.org/software/emacs/manual/html_node/emacs/Choosing-Modes.html#Choosing-Modes)
+ ['%-%*%-.*erlang.*%-%*%-'] = { 'erlang', { ignore_case = true } },
+ -- YAML
+ ['^%%YAML'] = 'yaml',
+ -- MikroTik RouterOS script
+ ['^#.*by RouterOS'] = 'routeros',
+ -- Sed scripts
+ -- #ncomment is allowed but most likely a false positive so require a space before any trailing comment text
+ ['^#n%s'] = 'sed',
+ ['^#n$'] = 'sed',
+}
+
+---@private
+-- File does not start with "#!".
+local function match_from_text(contents, path)
+ if contents[1]:find('^:$') then
+ -- Bourne-like shell scripts: sh ksh bash bash2
+ return M.sh(path, contents)
+ elseif
+ matchregex(
+ '\n' .. table.concat(contents, '\n'),
+ [[\n\s*emulate\s\+\%(-[LR]\s\+\)\=[ckz]\=sh\>]]
+ )
+ then
+ -- Z shell scripts
+ return 'zsh'
+ end
+
+ for k, v in pairs(patterns_text) do
+ if type(v) == 'string' then
+ -- Check the first line only
+ if contents[1]:find(k) then
+ return v
+ end
+ elseif type(v) == 'function' then
+ -- If filetype detection fails, continue with the next pattern
+ local ok, ft = pcall(v, contents)
+ if ok and ft then
+ return ft
+ end
+ else
+ local opts = type(v) == 'table' and v[2] or {}
+ if opts.start_lnum and opts.end_lnum then
+ assert(
+ not opts.ignore_case,
+ 'ignore_case=true is ignored when start_lnum is also present, needs refactor'
+ )
+ for i = opts.start_lnum, opts.end_lnum do
+ if not contents[i] then
+ break
+ elseif contents[i]:find(k) then
+ return v[1]
+ end
+ end
+ else
+ local line_nr = opts.start_lnum == -1 and #contents or opts.start_lnum or 1
+ if contents[line_nr] then
+ local line = opts.ignore_case and contents[line_nr]:lower() or contents[line_nr]
+ if opts.vim_regex and matchregex(line, k) or line:find(k) then
+ return v[1]
+ end
+ end
+ end
+ end
+ end
+ return cvs_diff(path, contents)
+end
+
+M.match_contents = function(contents, path)
+ local first_line = contents[1]
+ if first_line:find('^#!') then
+ return match_from_hashbang(contents, path)
+ else
+ return match_from_text(contents, path)
+ end
+end
+
+return M
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
new file mode 100644
index 0000000000..ce845eda15
--- /dev/null
+++ b/runtime/lua/vim/fs.lua
@@ -0,0 +1,209 @@
+local M = {}
+
+--- Iterate over all the parents of the given file or directory.
+---
+--- Example:
+--- <pre>
+--- local root_dir
+--- for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do
+--- if vim.fn.isdirectory(dir .. "/.git") == 1 then
+--- root_dir = dir
+--- break
+--- end
+--- end
+---
+--- if root_dir then
+--- print("Found git repository at", root_dir)
+--- end
+--- </pre>
+---
+---@param start (string) Initial file or directory.
+---@return (function) Iterator
+function M.parents(start)
+ return function(_, dir)
+ local parent = M.dirname(dir)
+ if parent == dir then
+ return nil
+ end
+
+ return parent
+ end,
+ nil,
+ start
+end
+
+--- Return the parent directory of the given file or directory
+---
+---@param file (string) File or directory
+---@return (string) Parent directory of {file}
+function M.dirname(file)
+ if file == nil then
+ return nil
+ end
+ return vim.fn.fnamemodify(file, ':h')
+end
+
+--- Return the basename of the given file or directory
+---
+---@param file (string) File or directory
+---@return (string) Basename of {file}
+function M.basename(file)
+ return vim.fn.fnamemodify(file, ':t')
+end
+
+--- Return an iterator over the files and directories located in {path}
+---
+---@param path (string) An absolute or relative path to the directory to iterate
+--- over. The path is first normalized |vim.fs.normalize()|.
+---@return Iterator over files and directories in {path}. Each iteration yields
+--- two values: name and type. Each "name" is the basename of the file or
+--- directory relative to {path}. Type is one of "file" or "directory".
+function M.dir(path)
+ return function(fs)
+ return vim.loop.fs_scandir_next(fs)
+ end,
+ vim.loop.fs_scandir(M.normalize(path))
+end
+
+--- Find files or directories in the given path.
+---
+--- Finds any files or directories given in {names} starting from {path}. If
+--- {upward} is "true" then the search traverses upward through parent
+--- directories; otherwise, the search traverses downward. Note that downward
+--- searches are recursive and may search through many directories! If {stop}
+--- is non-nil, then the search stops when the directory given in {stop} is
+--- reached. The search terminates when {limit} (default 1) matches are found.
+--- The search can be narrowed to find only files or or only directories by
+--- specifying {type} to be "file" or "directory", respectively.
+---
+---@param names (string|table) Names of the files and directories to find. Must
+--- be base names, paths and globs are not supported.
+---@param opts (table) Optional keyword arguments:
+--- - path (string): Path to begin searching from. If
+--- omitted, the current working directory is used.
+--- - upward (boolean, default false): If true, search
+--- upward through parent directories. Otherwise,
+--- search through child directories
+--- (recursively).
+--- - stop (string): Stop searching when this directory is
+--- reached. The directory itself is not searched.
+--- - type (string): Find only files ("file") or
+--- directories ("directory"). If omitted, both
+--- files and directories that match {name} are
+--- included.
+--- - limit (number, default 1): Stop the search after
+--- finding this many matches. Use `math.huge` to
+--- place no limit on the number of matches.
+---@return (table) The paths of all matching files or directories
+function M.find(names, opts)
+ opts = opts or {}
+ vim.validate({
+ names = { names, { 's', 't' } },
+ path = { opts.path, 's', true },
+ upward = { opts.upward, 'b', true },
+ stop = { opts.stop, 's', true },
+ type = { opts.type, 's', true },
+ limit = { opts.limit, 'n', true },
+ })
+
+ names = type(names) == 'string' and { names } or names
+
+ local path = opts.path or vim.loop.cwd()
+ local stop = opts.stop
+ local limit = opts.limit or 1
+
+ local matches = {}
+
+ ---@private
+ local function add(match)
+ matches[#matches + 1] = match
+ if #matches == limit then
+ return true
+ end
+ end
+
+ if opts.upward then
+ ---@private
+ local function test(p)
+ local t = {}
+ for _, name in ipairs(names) do
+ local f = p .. '/' .. name
+ local stat = vim.loop.fs_stat(f)
+ if stat and (not opts.type or opts.type == stat.type) then
+ t[#t + 1] = f
+ end
+ end
+
+ return t
+ end
+
+ for _, match in ipairs(test(path)) do
+ if add(match) then
+ return matches
+ end
+ end
+
+ for parent in M.parents(path) do
+ if stop and parent == stop then
+ break
+ end
+
+ for _, match in ipairs(test(parent)) do
+ if add(match) then
+ return matches
+ end
+ end
+ end
+ else
+ local dirs = { path }
+ while #dirs > 0 do
+ local dir = table.remove(dirs, 1)
+ if stop and dir == stop then
+ break
+ end
+
+ for other, type in M.dir(dir) do
+ local f = dir .. '/' .. other
+ for _, name in ipairs(names) do
+ if name == other and (not opts.type or opts.type == type) then
+ if add(f) then
+ return matches
+ end
+ end
+ end
+
+ if type == 'directory' then
+ dirs[#dirs + 1] = f
+ end
+ end
+ end
+ end
+
+ return matches
+end
+
+--- Normalize a path to a standard format. A tilde (~) character at the
+--- beginning of the path is expanded to the user's home directory and any
+--- backslash (\\) characters are converted to forward slashes (/). Environment
+--- variables are also expanded.
+---
+--- Example:
+--- <pre>
+--- vim.fs.normalize('C:\\Users\\jdoe')
+--- => 'C:/Users/jdoe'
+---
+--- vim.fs.normalize('~/src/neovim')
+--- => '/home/jdoe/src/neovim'
+---
+--- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim')
+--- => '/Users/jdoe/.config/nvim/init.vim'
+--- </pre>
+---
+---@param path (string) Path to normalize
+---@return (string) Normalized path
+function M.normalize(path)
+ vim.validate({ path = { path, 's' } })
+ return (path:gsub('^~/', vim.env.HOME .. '/'):gsub('%$([%w_]+)', vim.env):gsub('\\', '/'))
+end
+
+return M
diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua
new file mode 100644
index 0000000000..b875da0abc
--- /dev/null
+++ b/runtime/lua/vim/health.lua
@@ -0,0 +1,49 @@
+local M = {}
+
+function M.report_start(msg)
+ vim.fn['health#report_start'](msg)
+end
+
+function M.report_info(msg)
+ vim.fn['health#report_info'](msg)
+end
+
+function M.report_ok(msg)
+ vim.fn['health#report_ok'](msg)
+end
+
+function M.report_warn(msg, ...)
+ vim.fn['health#report_warn'](msg, ...)
+end
+
+function M.report_error(msg, ...)
+ vim.fn['health#report_error'](msg, ...)
+end
+
+local path2name = function(path)
+ if path:match('%.lua$') then
+ -- Lua: transform "../lua/vim/lsp/health.lua" into "vim.lsp"
+ return path:gsub('.-lua[%\\%/]', '', 1):gsub('[%\\%/]', '.'):gsub('%.health.-$', '')
+ else
+ -- Vim: transform "../autoload/health/provider.vim" into "provider"
+ return vim.fn.fnamemodify(path, ':t:r')
+ end
+end
+
+local PATTERNS = { '/autoload/health/*.vim', '/lua/**/**/health.lua', '/lua/**/**/health/init.lua' }
+-- :checkhealth completion function used by ex_getln.c get_healthcheck_names()
+M._complete = function()
+ local names = vim.tbl_flatten(vim.tbl_map(function(pattern)
+ return vim.tbl_map(path2name, vim.api.nvim_get_runtime_file(pattern, true))
+ end, PATTERNS))
+ -- Remove duplicates
+ local unique = {}
+ vim.tbl_map(function(f)
+ unique[f] = true
+ end, names)
+ -- vim.health is this file, which is not a healthcheck
+ unique['vim'] = nil
+ return vim.tbl_keys(unique)
+end
+
+return M
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index 236f3165f2..ddd504a0e0 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -1,45 +1,79 @@
local api = vim.api
-local highlight = {}
+local M = {}
+
+M.priorities = {
+ syntax = 50,
+ treesitter = 100,
+ diagnostics = 150,
+ user = 200,
+}
---@private
-function highlight.create(higroup, hi_info, default)
+function M.create(higroup, hi_info, default)
+ vim.deprecate('vim.highlight.create', 'vim.api.nvim_set_hl', '0.9')
local options = {}
-- TODO: Add validation
for k, v in pairs(hi_info) do
- table.insert(options, string.format("%s=%s", k, v))
+ table.insert(options, string.format('%s=%s', k, v))
end
- vim.cmd(string.format([[highlight %s %s %s]], default and "default" or "", higroup, table.concat(options, " ")))
+ vim.cmd(
+ string.format(
+ [[highlight %s %s %s]],
+ default and 'default' or '',
+ higroup,
+ table.concat(options, ' ')
+ )
+ )
end
---@private
-function highlight.link(higroup, link_to, force)
- vim.cmd(string.format([[highlight%s link %s %s]], force and "!" or " default", higroup, link_to))
+function M.link(higroup, link_to, force)
+ vim.deprecate('vim.highlight.link', 'vim.api.nvim_set_hl', '0.9')
+ vim.cmd(string.format([[highlight%s link %s %s]], force and '!' or ' default', higroup, link_to))
end
-
--- Highlight range between two positions
---
---@param bufnr number of buffer to apply highlighting to
---@param ns namespace to add highlight to
---@param higroup highlight group to use for highlighting
----@param rtype type of range (:help setreg, default charwise)
----@param inclusive boolean indicating whether the range is end-inclusive (default false)
-function highlight.range(bufnr, ns, higroup, start, finish, rtype, inclusive)
- rtype = rtype or 'v'
- inclusive = inclusive or false
+---@param start first position (tuple {line,col})
+---@param finish second position (tuple {line,col})
+---@param opts table with options:
+-- - regtype type of range (:help setreg, default charwise)
+-- - inclusive boolean indicating whether the range is end-inclusive (default false)
+-- - priority number indicating priority of highlight (default priorities.user)
+function M.range(bufnr, ns, higroup, start, finish, opts)
+ opts = opts or {}
+ local regtype = opts.regtype or 'v'
+ local inclusive = opts.inclusive or false
+ local priority = opts.priority or M.priorities.user
-- sanity check
- if start[2] < 0 or finish[1] < start[1] then return end
+ if start[2] < 0 or finish[1] < start[1] then
+ return
+ end
- local region = vim.region(bufnr, start, finish, rtype, inclusive)
+ local region = vim.region(bufnr, start, finish, regtype, inclusive)
for linenr, cols in pairs(region) do
- api.nvim_buf_add_highlight(bufnr, ns, higroup, linenr, cols[1], cols[2])
+ local end_row
+ if cols[2] == -1 then
+ end_row = linenr + 1
+ cols[2] = 0
+ end
+ api.nvim_buf_set_extmark(bufnr, ns, linenr, cols[1], {
+ hl_group = higroup,
+ end_row = end_row,
+ end_col = cols[2],
+ priority = priority,
+ strict = false,
+ })
end
-
end
local yank_ns = api.nvim_create_namespace('hlyank')
+local yank_timer
--- Highlight the yanked region
---
--- use from init.vim via
@@ -49,49 +83,71 @@ local yank_ns = api.nvim_create_namespace('hlyank')
--- customize conditions (here: do not highlight a visual selection) via
--- au TextYankPost * lua vim.highlight.on_yank {on_visual=false}
---
--- @param opts dictionary with options controlling the highlight:
+-- @param opts table with options controlling the highlight:
-- - higroup highlight group for yanked region (default "IncSearch")
-- - timeout time in ms before highlight is cleared (default 150)
-- - on_macro highlight when executing macro (default false)
-- - on_visual highlight when yanking visual selection (default true)
-- - event event structure (default vim.v.event)
-function highlight.on_yank(opts)
- vim.validate {
- opts = { opts,
- function(t) if t == nil then return true else return type(t) == 'table' end end,
- 'a table or nil to configure options (see `:h highlight.on_yank`)',
- }}
+function M.on_yank(opts)
+ vim.validate({
+ opts = {
+ opts,
+ function(t)
+ if t == nil then
+ return true
+ else
+ return type(t) == 'table'
+ end
+ end,
+ 'a table or nil to configure options (see `:h highlight.on_yank`)',
+ },
+ })
opts = opts or {}
local event = opts.event or vim.v.event
local on_macro = opts.on_macro or false
local on_visual = (opts.on_visual ~= false)
- if (not on_macro) and vim.fn.reg_executing() ~= '' then return end
- if event.operator ~= 'y' or event.regtype == '' then return end
- if (not on_visual) and event.visual then return end
+ if not on_macro and vim.fn.reg_executing() ~= '' then
+ return
+ end
+ if event.operator ~= 'y' or event.regtype == '' then
+ return
+ end
+ if not on_visual and event.visual then
+ return
+ end
- local higroup = opts.higroup or "IncSearch"
+ local higroup = opts.higroup or 'IncSearch'
local timeout = opts.timeout or 150
local bufnr = api.nvim_get_current_buf()
api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1)
+ if yank_timer then
+ yank_timer:close()
+ end
local pos1 = vim.fn.getpos("'[")
local pos2 = vim.fn.getpos("']")
- pos1 = {pos1[2] - 1, pos1[3] - 1 + pos1[4]}
- pos2 = {pos2[2] - 1, pos2[3] - 1 + pos2[4]}
-
- highlight.range(bufnr, yank_ns, higroup, pos1, pos2, event.regtype, event.inclusive)
+ pos1 = { pos1[2] - 1, pos1[3] - 1 + pos1[4] }
+ pos2 = { pos2[2] - 1, pos2[3] - 1 + pos2[4] }
- vim.defer_fn(
- function()
- if api.nvim_buf_is_valid(bufnr) then
- api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1)
- end
- end,
- timeout
+ M.range(
+ bufnr,
+ yank_ns,
+ higroup,
+ pos1,
+ pos2,
+ { regtype = event.regtype, inclusive = event.inclusive, priority = M.priorities.user }
)
+
+ yank_timer = vim.defer_fn(function()
+ yank_timer = nil
+ if api.nvim_buf_is_valid(bufnr) then
+ api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1)
+ end
+ end, timeout)
end
-return highlight
+return M
diff --git a/runtime/lua/vim/inspect.lua b/runtime/lua/vim/inspect.lua
index 0448ea487f..0a53fb203b 100644
--- a/runtime/lua/vim/inspect.lua
+++ b/runtime/lua/vim/inspect.lua
@@ -1,6 +1,6 @@
-local inspect ={
+local inspect = {
_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',
_LICENSE = [[
MIT LICENSE
@@ -25,13 +25,26 @@ local inspect ={
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- ]]
+ ]],
}
-local tostring = tostring
+inspect.KEY = setmetatable({}, {
+ __tostring = function()
+ return 'inspect.KEY'
+ end,
+})
+inspect.METATABLE = setmetatable({}, {
+ __tostring = function()
+ return 'inspect.METATABLE'
+ end,
+})
-inspect.KEY = setmetatable({}, {__tostring = function() return 'inspect.KEY' end})
-inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end})
+local tostring = tostring
+local rep = string.rep
+local match = string.match
+local char = string.char
+local gsub = string.gsub
+local fmt = string.format
local function rawpairs(t)
return next, t, nil
@@ -40,127 +53,130 @@ end
-- Apostrophizes the string if it has quotes, but not aphostrophes
-- Otherwise, it returns a regular quoted string
local function smartQuote(str)
- if str:match('"') and not str:match("'") then
+ if match(str, '"') and not match(str, "'") then
return "'" .. str .. "'"
end
- return '"' .. str:gsub('"', '\\"') .. '"'
+ return '"' .. gsub(str, '"', '\\"') .. '"'
end
-- \a => '\\a', \0 => '\\0', 31 => '\31'
local shortControlCharEscapes = {
- ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n",
- ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v"
+ ['\a'] = '\\a',
+ ['\b'] = '\\b',
+ ['\f'] = '\\f',
+ ['\n'] = '\\n',
+ ['\r'] = '\\r',
+ ['\t'] = '\\t',
+ ['\v'] = '\\v',
+ ['\127'] = '\\127',
}
-local longControlCharEscapes = {} -- \a => nil, \0 => \000, 31 => \031
-for i=0, 31 do
- local ch = string.char(i)
+local longControlCharEscapes = { ['\127'] = '\127' }
+for i = 0, 31 do
+ local ch = char(i)
if not shortControlCharEscapes[ch] then
- shortControlCharEscapes[ch] = "\\"..i
- longControlCharEscapes[ch] = string.format("\\%03d", i)
+ shortControlCharEscapes[ch] = '\\' .. i
+ longControlCharEscapes[ch] = fmt('\\%03d', i)
end
end
local function escape(str)
- return (str:gsub("\\", "\\\\")
- :gsub("(%c)%f[0-9]", longControlCharEscapes)
- :gsub("%c", shortControlCharEscapes))
+ return (
+ gsub(
+ gsub(gsub(str, '\\', '\\\\'), '(%c)%f[0-9]', longControlCharEscapes),
+ '%c',
+ shortControlCharEscapes
+ )
+ )
end
local function isIdentifier(str)
- return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" )
+ return type(str) == 'string' and not not str:match('^[_%a][_%a%d]*$')
end
+local flr = math.floor
local function isSequenceKey(k, sequenceLength)
- return type(k) == 'number'
- and 1 <= k
- and k <= sequenceLength
- and math.floor(k) == k
+ return type(k) == 'number' and flr(k) == k and 1 <= k and k <= sequenceLength
end
local defaultTypeOrders = {
- ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4,
- ['function'] = 5, ['userdata'] = 6, ['thread'] = 7
+ ['number'] = 1,
+ ['boolean'] = 2,
+ ['string'] = 3,
+ ['table'] = 4,
+ ['function'] = 5,
+ ['userdata'] = 6,
+ ['thread'] = 7,
}
local function sortKeys(a, b)
local ta, tb = type(a), type(b)
-- strings and numbers are sorted numerically/alphabetically
- if ta == tb and (ta == 'string' or ta == 'number') then return a < b end
+ if ta == tb and (ta == 'string' or ta == 'number') then
+ return a < b
+ end
- local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb]
+ local dta = defaultTypeOrders[ta] or 100
+ local dtb = defaultTypeOrders[tb] or 100
-- Two default types are compared according to the defaultTypeOrders table
- if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb]
- elseif dta then return true -- default types before custom ones
- elseif dtb then return false -- custom types after default ones
- end
-- custom types are sorted out alphabetically
- return ta < tb
+ return dta == dtb and ta < tb or dta < dtb
end
--- For implementation reasons, the behavior of rawlen & # is "undefined" when
--- tables aren't pure sequences. So we implement our own # operator.
-local function getSequenceLength(t)
- local len = 1
- local v = rawget(t,len)
- while v ~= nil do
- len = len + 1
- v = rawget(t,len)
+local function getKeys(t)
+ local seqLen = 1
+ while rawget(t, seqLen) ~= nil do
+ seqLen = seqLen + 1
end
- return len - 1
-end
+ seqLen = seqLen - 1
-local function getNonSequentialKeys(t)
- local keys, keysLength = {}, 0
- local sequenceLength = getSequenceLength(t)
- for k,_ in rawpairs(t) do
- if not isSequenceKey(k, sequenceLength) then
- keysLength = keysLength + 1
- keys[keysLength] = k
+ local keys, keysLen = {}, 0
+ for k in rawpairs(t) do
+ if not isSequenceKey(k, seqLen) then
+ keysLen = keysLen + 1
+ keys[keysLen] = k
end
end
table.sort(keys, sortKeys)
- return keys, keysLength, sequenceLength
+ return keys, keysLen, seqLen
end
-local function countTableAppearances(t, tableAppearances)
- tableAppearances = tableAppearances or {}
-
- if type(t) == 'table' then
- if not tableAppearances[t] then
- tableAppearances[t] = 1
- for k,v in rawpairs(t) do
- countTableAppearances(k, tableAppearances)
- countTableAppearances(v, tableAppearances)
- end
- countTableAppearances(getmetatable(t), tableAppearances)
+local function countCycles(x, cycles)
+ if type(x) == 'table' then
+ if cycles[x] then
+ cycles[x] = cycles[x] + 1
else
- tableAppearances[t] = tableAppearances[t] + 1
+ cycles[x] = 1
+ for k, v in rawpairs(x) do
+ countCycles(k, cycles)
+ countCycles(v, cycles)
+ end
+ countCycles(getmetatable(x), cycles)
end
end
-
- return tableAppearances
-end
-
-local copySequence = function(s)
- local copy, len = {}, #s
- for i=1, len do copy[i] = s[i] end
- return copy, len
end
-local function makePath(path, ...)
- local keys = {...}
- local newPath, len = copySequence(path)
- for i=1, #keys do
- newPath[len + i] = keys[i]
+local function makePath(path, a, b)
+ local newPath = {}
+ local len = #path
+ for i = 1, len do
+ newPath[i] = path[i]
end
+
+ newPath[len + 1] = a
+ newPath[len + 2] = b
+
return newPath
end
local function processRecursive(process, item, path, visited)
- if item == nil then return nil end
- if visited[item] then return visited[item] end
+ if item == nil then
+ return nil
+ end
+ if visited[item] then
+ return visited[item]
+ end
local processed = process(item, path)
if type(processed) == 'table' then
@@ -168,171 +184,166 @@ local function processRecursive(process, item, path, visited)
visited[item] = processedCopy
local processedKey
- for k,v in rawpairs(processed) do
+ for k, v in rawpairs(processed) do
processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited)
if processedKey ~= nil then
- processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited)
+ processedCopy[processedKey] =
+ processRecursive(process, v, makePath(path, processedKey), visited)
end
end
- local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)
- if type(mt) ~= 'table' then mt = nil end -- ignore not nil/table __metatable field
+ local mt =
+ processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited)
+ if type(mt) ~= 'table' then
+ mt = nil
+ end
setmetatable(processedCopy, mt)
processed = processedCopy
end
return processed
end
-
-
--------------------------------------------------------------------
-
-local Inspector = {}
-local Inspector_mt = {__index = Inspector}
-
-function Inspector:puts(...)
- local args = {...}
- local buffer = self.buffer
- local len = #buffer
- for i=1, #args do
- len = len + 1
- buffer[len] = args[i]
- end
+local function puts(buf, str)
+ buf.n = buf.n + 1
+ buf[buf.n] = str
end
-function Inspector:down(f)
- self.level = self.level + 1
- f()
- self.level = self.level - 1
-end
+local Inspector = {}
-function Inspector:tabify()
- self:puts(self.newline, string.rep(self.indent, self.level))
-end
+local Inspector_mt = { __index = Inspector }
-function Inspector:alreadyVisited(v)
- return self.ids[v] ~= nil
+local function tabify(inspector)
+ puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level))
end
function Inspector:getId(v)
local id = self.ids[v]
+ local ids = self.ids
if not id then
local tv = type(v)
- id = (self.maxIds[tv] or 0) + 1
- self.maxIds[tv] = id
- self.ids[v] = id
+ id = (ids[tv] or 0) + 1
+ ids[v], ids[tv] = id, id
end
return tostring(id)
end
-function Inspector:putKey(k)
- if isIdentifier(k) then return self:puts(k) end
- self:puts("[")
- self:putValue(k)
- self:puts("]")
-end
+function Inspector:putValue(v)
+ local buf = self.buf
+ local tv = type(v)
+ if tv == 'string' then
+ puts(buf, smartQuote(escape(v)))
+ elseif
+ tv == 'number'
+ or tv == 'boolean'
+ or tv == 'nil'
+ or tv == 'cdata'
+ or tv == 'ctype'
+ or (vim and v == vim.NIL)
+ then
+ puts(buf, tostring(v))
+ elseif tv == 'table' and not self.ids[v] then
+ local t = v
+
+ if t == inspect.KEY or t == inspect.METATABLE then
+ puts(buf, tostring(t))
+ elseif self.level >= self.depth then
+ puts(buf, '{...}')
+ else
+ if self.cycles[t] > 1 then
+ puts(buf, fmt('<%d>', self:getId(t)))
+ end
-function Inspector:putTable(t)
- if t == inspect.KEY or t == inspect.METATABLE then
- self:puts(tostring(t))
- elseif self:alreadyVisited(t) then
- self:puts('<table ', self:getId(t), '>')
- elseif self.level >= self.depth then
- self:puts('{...}')
- else
- if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end
-
- local nonSequentialKeys, nonSequentialKeysLength, sequenceLength = getNonSequentialKeys(t)
- local mt = getmetatable(t)
- if (vim and sequenceLength == 0 and nonSequentialKeysLength == 0
- and mt == vim._empty_dict_mt) then
- self:puts(tostring(t))
- return
- end
+ local keys, keysLen, seqLen = getKeys(t)
+ local mt = getmetatable(t)
- self:puts('{')
- self:down(function()
- local count = 0
- for i=1, sequenceLength do
- if count > 0 then self:puts(',') end
- self:puts(' ')
- self:putValue(t[i])
- count = count + 1
+ if vim and seqLen == 0 and keysLen == 0 and mt == vim._empty_dict_mt then
+ puts(buf, tostring(t))
+ return
end
- for i=1, nonSequentialKeysLength do
- local k = nonSequentialKeys[i]
- if count > 0 then self:puts(',') end
- self:tabify()
- self:putKey(k)
- self:puts(' = ')
- self:putValue(t[k])
- count = count + 1
+ puts(buf, '{')
+ self.level = self.level + 1
+
+ for i = 1, seqLen + keysLen do
+ if i > 1 then
+ puts(buf, ',')
+ end
+ if i <= seqLen then
+ puts(buf, ' ')
+ self:putValue(t[i])
+ else
+ local k = keys[i - seqLen]
+ tabify(self)
+ if isIdentifier(k) then
+ puts(buf, k)
+ else
+ puts(buf, '[')
+ self:putValue(k)
+ puts(buf, ']')
+ end
+ puts(buf, ' = ')
+ self:putValue(t[k])
+ end
end
if type(mt) == 'table' then
- if count > 0 then self:puts(',') end
- self:tabify()
- self:puts('<metatable> = ')
+ if seqLen + keysLen > 0 then
+ puts(buf, ',')
+ end
+ tabify(self)
+ puts(buf, '<metatable> = ')
self:putValue(mt)
end
- end)
- if nonSequentialKeysLength > 0 or type(mt) == 'table' then -- result is multi-lined. Justify closing }
- self:tabify()
- elseif sequenceLength > 0 then -- array tables have one extra space before closing }
- self:puts(' ')
- end
+ self.level = self.level - 1
- self:puts('}')
- end
-end
-
-function Inspector:putValue(v)
- local tv = type(v)
+ if keysLen > 0 or type(mt) == 'table' then
+ tabify(self)
+ elseif seqLen > 0 then
+ puts(buf, ' ')
+ end
- if tv == 'string' then
- self:puts(smartQuote(escape(v)))
- elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or
- tv == 'cdata' or tv == 'ctype' or (vim and v == vim.NIL) then
- self:puts(tostring(v))
- elseif tv == 'table' then
- self:putTable(v)
+ puts(buf, '}')
+ end
else
- self:puts('<', tv, ' ', self:getId(v), '>')
+ puts(buf, fmt('<%s %d>', tv, self:getId(v)))
end
end
--------------------------------------------------------------------
-
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 indent = options.indent or ' '
+ local indent = options.indent or ' '
local process = options.process
if process then
root = processRecursive(process, root, {}, {})
end
+ local cycles = {}
+ countCycles(root, cycles)
+
local inspector = setmetatable({
- depth = depth,
- level = 0,
- buffer = {},
- ids = {},
- maxIds = {},
- newline = newline,
- indent = indent,
- tableAppearances = countTableAppearances(root)
+ buf = { n = 0 },
+ ids = {},
+ cycles = cycles,
+ depth = depth,
+ level = 0,
+ newline = newline,
+ indent = indent,
}, Inspector_mt)
inspector:putValue(root)
- return table.concat(inspector.buffer)
+ return table.concat(inspector.buf)
end
-setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end })
+setmetatable(inspect, {
+ __call = function(_, root, options)
+ return inspect.inspect(root, options)
+ end,
+})
return inspect
diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua
index d53b790746..f4c2b507a9 100644
--- a/runtime/lua/vim/keymap.lua
+++ b/runtime/lua/vim/keymap.lua
@@ -25,8 +25,8 @@ local keymap = {}
--- vim.keymap.set('n', 'asdf', require('jkl').my_fun)
--- </pre>
---
---- the require('jkl') gets evaluated during this call in order to access the function. If you want to
---- avoid this cost at startup you can wrap it in a function, for example:
+--- the ``require('jkl')`` gets evaluated during this call in order to access the function.
+--- If you want to avoid this cost at startup you can wrap it in a function, for example:
--- <pre>
--- vim.keymap.set('n', 'asdf', function() return require('jkl').my_fun() end)
--- </pre>
@@ -35,39 +35,51 @@ local keymap = {}
--- Can also be list of modes to create mapping on multiple modes.
---@param lhs string Left-hand side |{lhs}| of the mapping.
---@param rhs string|function Right-hand side |{rhs}| of the mapping. Can also be a Lua function.
+--- If a Lua function and `opts.expr == true`, returning `nil` is
+--- equivalent to an empty string.
--
---@param opts table A table of |:map-arguments| such as "silent". In addition to the options
--- listed in |nvim_set_keymap()|, this table also accepts the following keys:
+--- - buffer: (number or boolean) Add a mapping to the given buffer. When "true"
+--- or 0, use the current buffer.
--- - replace_keycodes: (boolean, default true) When both this and expr is "true",
--- |nvim_replace_termcodes()| is applied to the result of Lua expr maps.
--- - remap: (boolean) Make the mapping recursive. This is the
--- inverse of the "noremap" option from |nvim_set_keymap()|.
---- Default `true` if `lhs` is a string starting with `<plug>` (case-insensitive), `false` otherwise.
+--- Default `false`.
---@see |nvim_set_keymap()|
function keymap.set(mode, lhs, rhs, opts)
- vim.validate {
- mode = {mode, {'s', 't'}},
- lhs = {lhs, 's'},
- rhs = {rhs, {'s', 'f'}},
- opts = {opts, 't', true}
- }
+ vim.validate({
+ mode = { mode, { 's', 't' } },
+ lhs = { lhs, 's' },
+ rhs = { rhs, { 's', 'f' } },
+ opts = { opts, 't', true },
+ })
opts = vim.deepcopy(opts) or {}
- local is_rhs_luaref = type(rhs) == "function"
- mode = type(mode) == 'string' and {mode} or mode
+ local is_rhs_luaref = type(rhs) == 'function'
+ mode = type(mode) == 'string' and { mode } or mode
- if is_rhs_luaref and opts.expr and opts.replace_keycodes ~= false then
+ if is_rhs_luaref and opts.expr then
local user_rhs = rhs
- rhs = function ()
- return vim.api.nvim_replace_termcodes(user_rhs(), true, true, true)
+ rhs = function()
+ local res = user_rhs()
+ if res == nil then
+ -- TODO(lewis6991): Handle this in C?
+ return ''
+ elseif opts.replace_keycodes ~= false then
+ return vim.api.nvim_replace_termcodes(res, true, true, true)
+ else
+ return res
+ end
end
end
-- clear replace_keycodes from opts table
opts.replace_keycodes = nil
if opts.remap == nil then
- -- remap by default on <plug> mappings and don't otherwise.
- opts.noremap = is_rhs_luaref or rhs:lower():match("^<plug>") == nil
+ -- default remap value is false
+ opts.noremap = true
else
-- remaps behavior is opposite of noremap option.
opts.noremap = not opts.remap
@@ -106,19 +118,18 @@ end
---@see |vim.keymap.set()|
---
function keymap.del(modes, lhs, opts)
- vim.validate {
- mode = {modes, {'s', 't'}},
- lhs = {lhs, 's'},
- opts = {opts, 't', true}
- }
+ vim.validate({
+ mode = { modes, { 's', 't' } },
+ lhs = { lhs, 's' },
+ opts = { opts, 't', true },
+ })
opts = opts or {}
- modes = type(modes) == 'string' and {modes} or modes
+ modes = type(modes) == 'string' and { modes } or modes
local buffer = false
if opts.buffer ~= nil then
buffer = opts.buffer == true and 0 or opts.buffer
- opts.buffer = nil
end
if buffer == false then
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 8b7eb4ac90..61586ca44f 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1,58 +1,62 @@
-local if_nil = vim.F.if_nil
-
-local default_handlers = require 'vim.lsp.handlers'
-local log = require 'vim.lsp.log'
-local lsp_rpc = require 'vim.lsp.rpc'
-local protocol = require 'vim.lsp.protocol'
-local util = require 'vim.lsp.util'
-local sync = require 'vim.lsp.sync'
+local default_handlers = require('vim.lsp.handlers')
+local log = require('vim.lsp.log')
+local lsp_rpc = require('vim.lsp.rpc')
+local protocol = require('vim.lsp.protocol')
+local util = require('vim.lsp.util')
+local sync = require('vim.lsp.sync')
local vim = vim
-local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option
- = vim.api.nvim_err_writeln, vim.api.nvim_buf_get_lines, vim.api.nvim_command, vim.api.nvim_buf_get_option
+local api = vim.api
+local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option, nvim_exec_autocmds =
+ api.nvim_err_writeln,
+ api.nvim_buf_get_lines,
+ api.nvim_command,
+ api.nvim_buf_get_option,
+ api.nvim_exec_autocmds
local uv = vim.loop
local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend
local validate = vim.validate
+local if_nil = vim.F.if_nil
local lsp = {
- protocol = protocol;
+ protocol = protocol,
- handlers = default_handlers;
+ handlers = default_handlers,
- buf = require'vim.lsp.buf';
- diagnostic = require'vim.lsp.diagnostic';
- codelens = require'vim.lsp.codelens';
- util = util;
+ buf = require('vim.lsp.buf'),
+ diagnostic = require('vim.lsp.diagnostic'),
+ codelens = require('vim.lsp.codelens'),
+ util = util,
-- Allow raw RPC access.
- rpc = lsp_rpc;
+ rpc = lsp_rpc,
-- Export these directly from rpc.
- rpc_response_error = lsp_rpc.rpc_response_error;
+ rpc_response_error = lsp_rpc.rpc_response_error,
}
--- maps request name to the required resolved_capability in the client.
+-- maps request name to the required server_capability in the client.
lsp._request_name_to_capability = {
- ['textDocument/hover'] = 'hover';
- ['textDocument/signatureHelp'] = 'signature_help';
- ['textDocument/definition'] = 'goto_definition';
- ['textDocument/implementation'] = 'implementation';
- ['textDocument/declaration'] = 'declaration';
- ['textDocument/typeDefinition'] = 'type_definition';
- ['textDocument/documentSymbol'] = 'document_symbol';
- ['textDocument/prepareCallHierarchy'] = 'call_hierarchy';
- ['textDocument/rename'] = 'rename';
- ['textDocument/prepareRename'] = 'rename';
- ['textDocument/codeAction'] = 'code_action';
- ['textDocument/codeLens'] = 'code_lens';
- ['codeLens/resolve'] = 'code_lens_resolve';
- ['workspace/executeCommand'] = 'execute_command';
- ['workspace/symbol'] = 'workspace_symbol';
- ['textDocument/references'] = 'find_references';
- ['textDocument/rangeFormatting'] = 'document_range_formatting';
- ['textDocument/formatting'] = 'document_formatting';
- ['textDocument/completion'] = 'completion';
- ['textDocument/documentHighlight'] = 'document_highlight';
+ ['textDocument/hover'] = { 'hoverProvider' },
+ ['textDocument/signatureHelp'] = { 'signatureHelpProvider' },
+ ['textDocument/definition'] = { 'definitionProvider' },
+ ['textDocument/implementation'] = { 'implementationProvider' },
+ ['textDocument/declaration'] = { 'declarationProvider' },
+ ['textDocument/typeDefinition'] = { 'typeDefinitionProvider' },
+ ['textDocument/documentSymbol'] = { 'documentSymbolProvider' },
+ ['textDocument/prepareCallHierarchy'] = { 'callHierarchyProvider' },
+ ['textDocument/rename'] = { 'renameProvider' },
+ ['textDocument/prepareRename'] = { 'renameProvider', 'prepareProvider' },
+ ['textDocument/codeAction'] = { 'codeActionProvider' },
+ ['textDocument/codeLens'] = { 'codeLensProvider' },
+ ['codeLens/resolve'] = { 'codeLensProvider', 'resolveProvider' },
+ ['workspace/executeCommand'] = { 'executeCommandProvider' },
+ ['workspace/symbol'] = { 'workspaceSymbolProvider' },
+ ['textDocument/references'] = { 'referencesProvider' },
+ ['textDocument/rangeFormatting'] = { 'documentRangeFormattingProvider' },
+ ['textDocument/formatting'] = { 'documentFormattingProvider' },
+ ['textDocument/completion'] = { 'completionProvider' },
+ ['textDocument/documentHighlight'] = { 'documentHighlightProvider' },
}
-- TODO improve handling of scratch buffers with LSP attached.
@@ -62,8 +66,8 @@ lsp._request_name_to_capability = {
---
---@param {...} (List of strings) List to write to the buffer
local function err_message(...)
- nvim_err_writeln(table.concat(vim.tbl_flatten{...}))
- nvim_command("redraw")
+ nvim_err_writeln(table.concat(vim.tbl_flatten({ ... })))
+ nvim_command('redraw')
end
---@private
@@ -73,9 +77,9 @@ end
---buffer if not given.
---@returns bufnr (number) Number of requested buffer
local function resolve_bufnr(bufnr)
- validate { bufnr = { bufnr, 'n', true } }
+ validate({ bufnr = { bufnr, 'n', true } })
if bufnr == nil or bufnr == 0 then
- return vim.api.nvim_get_current_buf()
+ return api.nvim_get_current_buf()
end
return bufnr
end
@@ -85,7 +89,10 @@ end
--- supported in any of the servers registered for the current buffer.
---@param method (string) name of the method
function lsp._unsupported_method(method)
- local msg = string.format("method %s is not supported by any of the servers registered for the current buffer", method)
+ local msg = string.format(
+ 'method %s is not supported by any of the servers registered for the current buffer',
+ method
+ )
log.warn(msg)
return msg
end
@@ -96,23 +103,29 @@ end
---@param filename (string) path to check
---@returns true if {filename} exists and is a directory, false otherwise
local function is_dir(filename)
- validate{filename={filename,'s'}}
+ validate({ filename = { filename, 's' } })
local stat = uv.fs_stat(filename)
return stat and stat.type == 'directory' or false
end
-local wait_result_reason = { [-1] = "timeout"; [-2] = "interrupted"; [-3] = "error" }
+local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'error' }
local valid_encodings = {
- ["utf-8"] = 'utf-8'; ["utf-16"] = 'utf-16'; ["utf-32"] = 'utf-32';
- ["utf8"] = 'utf-8'; ["utf16"] = 'utf-16'; ["utf32"] = 'utf-32';
- UTF8 = 'utf-8'; UTF16 = 'utf-16'; UTF32 = 'utf-32';
+ ['utf-8'] = 'utf-8',
+ ['utf-16'] = 'utf-16',
+ ['utf-32'] = 'utf-32',
+ ['utf8'] = 'utf-8',
+ ['utf16'] = 'utf-16',
+ ['utf32'] = 'utf-32',
+ UTF8 = 'utf-8',
+ UTF16 = 'utf-16',
+ UTF32 = 'utf-32',
}
local format_line_ending = {
- ["unix"] = '\n',
- ["dos"] = '\r\n',
- ["mac"] = '\r',
+ ['unix'] = '\n',
+ ['dos'] = '\r\n',
+ ['mac'] = '\r',
}
---@private
@@ -138,10 +151,10 @@ local uninitialized_clients = {}
---@private
local function for_each_buffer_client(bufnr, fn, restrict_client_ids)
- validate {
- fn = { fn, 'f' };
- restrict_client_ids = { restrict_client_ids, 't' , true};
- }
+ validate({
+ fn = { fn, 'f' },
+ restrict_client_ids = { restrict_client_ids, 't', true },
+ })
bufnr = resolve_bufnr(bufnr)
local client_ids = all_buffer_active_clients[bufnr]
if not client_ids or tbl_isempty(client_ids) then
@@ -169,9 +182,13 @@ end
-- Error codes to be used with `on_error` from |vim.lsp.start_client|.
-- Can be used to look up the string from a the number or the number
-- from the string.
-lsp.client_errors = tbl_extend("error", lsp_rpc.client_errors, vim.tbl_add_reverse_lookup {
- ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1;
-})
+lsp.client_errors = tbl_extend(
+ 'error',
+ lsp_rpc.client_errors,
+ vim.tbl_add_reverse_lookup({
+ ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1,
+ })
+)
---@private
--- Normalizes {encoding} to valid LSP encoding names.
@@ -179,11 +196,16 @@ lsp.client_errors = tbl_extend("error", lsp_rpc.client_errors, vim.tbl_add_rever
---@param encoding (string) Encoding to normalize
---@returns (string) normalized encoding name
local function validate_encoding(encoding)
- validate {
- encoding = { encoding, 's' };
- }
+ validate({
+ encoding = { encoding, 's' },
+ })
return valid_encodings[encoding:lower()]
- or error(string.format("Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'", encoding))
+ or error(
+ string.format(
+ "Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'",
+ encoding
+ )
+ )
end
---@internal
@@ -194,16 +216,21 @@ end
---@returns (string) the command
---@returns (list of strings) its arguments
function lsp._cmd_parts(input)
- vim.validate{cmd={
- input,
- function() return vim.tbl_islist(input) end,
- "list"}}
+ validate({
+ cmd = {
+ input,
+ function()
+ return vim.tbl_islist(input)
+ end,
+ 'list',
+ },
+ })
local cmd = input[1]
local cmd_args = {}
-- Don't mutate our input.
for i, v in ipairs(input) do
- vim.validate{["cmd argument"]={v, "s"}}
+ validate({ ['cmd argument'] = { v, 's' } })
if i > 1 then
table.insert(cmd_args, v)
end
@@ -233,30 +260,33 @@ end
---
---@see |vim.lsp.start_client()|
local function validate_client_config(config)
- validate {
- config = { config, 't' };
- }
- validate {
- handlers = { config.handlers, "t", true };
- capabilities = { config.capabilities, "t", true };
- cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" };
- cmd_env = { config.cmd_env, "t", true };
- name = { config.name, 's', true };
- on_error = { config.on_error, "f", true };
- on_exit = { config.on_exit, "f", true };
- on_init = { config.on_init, "f", true };
- settings = { config.settings, "t", true };
- commands = { config.commands, 't', true };
- before_init = { config.before_init, "f", true };
- offset_encoding = { config.offset_encoding, "s", true };
- flags = { config.flags, "t", true };
- get_language_id = { config.get_language_id, "f", true };
- }
+ validate({
+ config = { config, 't' },
+ })
+ validate({
+ handlers = { config.handlers, 't', true },
+ capabilities = { config.capabilities, 't', true },
+ cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), 'directory' },
+ cmd_env = { config.cmd_env, 't', true },
+ detached = { config.detached, 'b', true },
+ name = { config.name, 's', true },
+ on_error = { config.on_error, 'f', true },
+ on_exit = { config.on_exit, 'f', true },
+ on_init = { config.on_init, 'f', true },
+ settings = { config.settings, 't', true },
+ commands = { config.commands, 't', true },
+ before_init = { config.before_init, 'f', true },
+ offset_encoding = { config.offset_encoding, 's', true },
+ flags = { config.flags, 't', true },
+ get_language_id = { config.get_language_id, 'f', true },
+ })
assert(
- (not config.flags
+ (
+ not config.flags
or not config.flags.debounce_text_changes
- or type(config.flags.debounce_text_changes) == 'number'),
- "flags.debounce_text_changes must be a number with the debounce time in milliseconds"
+ 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)
@@ -266,9 +296,9 @@ local function validate_client_config(config)
end
return {
- cmd = cmd;
- cmd_args = cmd_args;
- offset_encoding = offset_encoding;
+ cmd = cmd,
+ cmd_args = cmd_args,
+ offset_encoding = offset_encoding,
}
end
@@ -328,14 +358,15 @@ do
function changetracking.init(client, bufnr)
local use_incremental_sync = (
if_nil(client.config.flags.allow_incremental_sync, true)
- and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental
+ and vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change')
+ == protocol.TextDocumentSyncKind.Incremental
)
local state = state_by_client[client.id]
if not state then
state = {
- buffers = {};
+ buffers = {},
debounce = client.config.flags.debounce_text_changes or 150,
- use_incremental_sync = use_incremental_sync;
+ use_incremental_sync = use_incremental_sync,
}
state_by_client[client.id] = state
end
@@ -344,6 +375,7 @@ do
state.buffers[bufnr] = buf_state
if use_incremental_sync then
buf_state.lines = nvim_buf_get_lines(bufnr, 0, -1, true)
+ buf_state.lines_tmp = {}
buf_state.pending_changes = {}
end
end
@@ -403,21 +435,59 @@ do
---@private
function changetracking.prepare(bufnr, firstline, lastline, new_lastline)
local incremental_changes = function(client, buf_state)
- local curr_lines = nvim_buf_get_lines(bufnr, 0, -1, true)
+ local prev_lines = buf_state.lines
+ local curr_lines = buf_state.lines_tmp
+
+ local changed_lines = nvim_buf_get_lines(bufnr, firstline, new_lastline, true)
+ for i = 1, firstline do
+ curr_lines[i] = prev_lines[i]
+ end
+ for i = firstline + 1, new_lastline do
+ curr_lines[i] = changed_lines[i - firstline]
+ end
+ for i = lastline + 1, #prev_lines do
+ curr_lines[i - lastline + new_lastline] = prev_lines[i]
+ end
+ if tbl_isempty(curr_lines) then
+ -- Can happen when deleting the entire contents of a buffer, see https://github.com/neovim/neovim/issues/16259.
+ curr_lines[1] = ''
+ end
+
local line_ending = buf_get_line_ending(bufnr)
local incremental_change = sync.compute_diff(
- buf_state.lines, curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending)
+ buf_state.lines,
+ curr_lines,
+ firstline,
+ lastline,
+ new_lastline,
+ client.offset_encoding or 'utf-16',
+ line_ending
+ )
+
+ -- Double-buffering of lines tables is used to reduce the load on the garbage collector.
+ -- At this point the prev_lines table is useless, but its internal storage has already been allocated,
+ -- so let's keep it around for the next didChange event, in which it will become the next
+ -- curr_lines table. Note that setting elements to nil doesn't actually deallocate slots in the
+ -- internal storage - it merely marks them as free, for the GC to deallocate them.
+ for i in ipairs(prev_lines) do
+ prev_lines[i] = nil
+ end
buf_state.lines = curr_lines
+ buf_state.lines_tmp = prev_lines
+
return incremental_change
end
local full_changes = once(function()
return {
- text = buf_get_full_text(bufnr);
- };
+ text = buf_get_full_text(bufnr),
+ }
end)
local uri = vim.uri_from_bufnr(bufnr)
return function(client)
- if client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.None then
+ if
+ vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change')
+ == protocol.TextDocumentSyncKind.None
+ then
return
end
local state = state_by_client[client.id]
@@ -430,13 +500,17 @@ do
table.insert(buf_state.pending_changes, incremental_changes(client, buf_state))
end
buf_state.pending_change = function()
+ if buf_state.pending_change == nil then
+ return
+ end
buf_state.pending_change = nil
buf_state.last_flush = uv.hrtime()
- if client.is_stopped() or not vim.api.nvim_buf_is_valid(bufnr) then
+ if client.is_stopped() or not api.nvim_buf_is_valid(bufnr) then
return
end
- local changes = state.use_incremental_sync and buf_state.pending_changes or { full_changes() }
- client.notify("textDocument/didChange", {
+ local changes = state.use_incremental_sync and buf_state.pending_changes
+ or { full_changes() }
+ client.notify('textDocument/didChange', {
textDocument = {
uri = uri,
version = util.buf_versions[bufnr],
@@ -448,7 +522,7 @@ do
if debounce == 0 then
buf_state.pending_change()
else
- local timer = vim.loop.new_timer()
+ local timer = uv.new_timer()
buf_state.timer = timer
-- Must use schedule_wrap because `full_changes()` calls nvim_buf_get_lines
timer:start(debounce, 0, vim.schedule_wrap(buf_state.pending_change))
@@ -488,29 +562,28 @@ do
end
end
-
---@private
--- Default handler for the 'textDocument/didOpen' LSP notification.
---
----@param bufnr (Number) Number of the buffer, or 0 for current
+---@param bufnr number Number of the buffer, or 0 for current
---@param client Client object
local function text_document_did_open_handler(bufnr, client)
changetracking.init(client, bufnr)
- if not client.resolved_capabilities.text_document_open_close then
+ if not vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
return
end
- if not vim.api.nvim_buf_is_loaded(bufnr) then
+ if not api.nvim_buf_is_loaded(bufnr) then
return
end
local filetype = nvim_buf_get_option(bufnr, 'filetype')
local params = {
textDocument = {
- version = 0;
- uri = vim.uri_from_bufnr(bufnr);
- languageId = client.config.get_language_id(bufnr, filetype);
- text = buf_get_full_text(bufnr);
- }
+ version = 0,
+ uri = vim.uri_from_bufnr(bufnr),
+ languageId = client.config.get_language_id(bufnr, filetype),
+ text = buf_get_full_text(bufnr),
+ },
}
client.notify('textDocument/didOpen', params)
util.buf_versions[bufnr] = params.textDocument.version
@@ -519,7 +592,7 @@ local function text_document_did_open_handler(bufnr, client)
vim.schedule(function()
-- Protect against a race where the buffer disappears
-- between `did_open_handler` and the scheduled function firing.
- if vim.api.nvim_buf_is_valid(bufnr) then
+ if api.nvim_buf_is_valid(bufnr) then
local namespace = vim.lsp.diagnostic.get_namespace(client.id)
vim.diagnostic.show(namespace, bufnr)
end
@@ -602,14 +675,86 @@ end
---
--- - {server_capabilities} (table): Response from the server sent on
--- `initialize` describing the server's capabilities.
----
---- - {resolved_capabilities} (table): Normalized table of
---- capabilities that we have detected based on the initialize
---- response from the server in `server_capabilities`.
function lsp.client()
error()
end
+--- Create a new LSP client and start a language server or reuses an already
+--- running client if one is found matching `name` and `root_dir`.
+--- Attaches the current buffer to the client.
+---
+--- Example:
+---
+--- <pre>
+--- vim.lsp.start({
+--- name = 'my-server-name',
+--- cmd = {'name-of-language-server-executable'},
+--- root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]),
+--- })
+--- </pre>
+---
+--- See |lsp.start_client| for all available options. The most important are:
+---
+--- `name` is an arbitrary name for the LSP client. It should be unique per
+--- language server.
+---
+--- `cmd` the command as list - used to start the language server.
+--- The command must be present in the `$PATH` environment variable or an
+--- absolute path to the executable. Shell constructs like `~` are *NOT* expanded.
+---
+--- `root_dir` path to the project root.
+--- By default this is used to decide if an existing client should be re-used.
+--- The example above uses |vim.fs.find| and |vim.fs.dirname| to detect the
+--- root by traversing the file system upwards starting
+--- from the current directory until either a `pyproject.toml` or `setup.py`
+--- file is found.
+---
+--- `workspace_folders` a list of { uri:string, name: string } tables.
+--- The project root folders used by the language server.
+--- If `nil` the property is derived from the `root_dir` for convenience.
+---
+--- Language servers use this information to discover metadata like the
+--- dependencies of your project and they tend to index the contents within the
+--- project folder.
+---
+---
+--- To ensure a language server is only started for languages it can handle,
+--- make sure to call |vim.lsp.start| within a |FileType| autocmd.
+--- Either use |:au|, |nvim_create_autocmd()| or put the call in a
+--- `ftplugin/<filetype_name>.lua` (See |ftplugin-name|)
+---
+---@param config table Same configuration as documented in |lsp.start_client()|
+---@param opts nil|table Optional keyword arguments:
+--- - reuse_client (fun(client: client, config: table): boolean)
+--- Predicate used to decide if a client should be re-used.
+--- Used on all running clients.
+--- The default implementation re-uses a client if name
+--- and root_dir matches.
+---@return number client_id
+function lsp.start(config, opts)
+ opts = opts or {}
+ local reuse_client = opts.reuse_client
+ or function(client, conf)
+ return client.config.root_dir == conf.root_dir and client.name == conf.name
+ end
+ config.name = config.name or (config.cmd[1] and vim.fs.basename(config.cmd[1])) or nil
+ local bufnr = api.nvim_get_current_buf()
+ for _, clients in ipairs({ uninitialized_clients, lsp.get_active_clients() }) do
+ for _, client in pairs(clients) do
+ if reuse_client(client, config) then
+ lsp.buf_attach_client(bufnr, client.id)
+ return client.id
+ end
+ end
+ end
+ local client_id = lsp.start_client(config)
+ if client_id == nil then
+ return nil -- lsp.start_client will have printed an error
+ end
+ lsp.buf_attach_client(bufnr, client_id)
+ return client_id
+end
+
-- FIXME: DOC: Currently all methods on the `vim.lsp.client` object are
-- documented twice: Here, and on the methods themselves (e.g.
-- `client.request()`). This is a workaround for the vimdoc generator script
@@ -637,6 +782,10 @@ end
--- { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; }
--- </pre>
---
+---@param detached: (boolean, default true) Daemonize the server process so that it runs in a
+--- separate process group from Nvim. Nvim will shutdown the process on exit, but if Nvim fails to
+--- exit cleanly this could leave behind orphaned server processes.
+---
---@param workspace_folders (table) List of workspace folders passed to the
--- language server. For backwards compatibility rootUri and rootPath will be
--- derived from the first workspace folder in this list. See `workspaceFolders` in
@@ -708,7 +857,7 @@ end
--- server in the initialize request. Invalid/empty values will default to "off"
---@param flags: A table with flags for the client. The current (experimental) flags are:
--- - allow_incremental_sync (bool, default true): Allow using incremental sync for buffer edits
---- - debounce_text_changes (number, default nil): Debounce didChange
+--- - debounce_text_changes (number, default 150): Debounce didChange
--- notifications to the server by the given number in milliseconds. No debounce
--- occurs if nil
--- - exit_timeout (number, default 500): Milliseconds to wait for server to
@@ -724,7 +873,8 @@ end
--- the client has been initialized.
function lsp.start_client(config)
local cleaned_config = validate_client_config(config)
- local cmd, cmd_args, offset_encoding = cleaned_config.cmd, cleaned_config.cmd_args, cleaned_config.offset_encoding
+ local cmd, cmd_args, offset_encoding =
+ cleaned_config.cmd, cleaned_config.cmd_args, cleaned_config.offset_encoding
config.flags = config.flags or {}
config.settings = config.settings or {}
@@ -732,13 +882,15 @@ function lsp.start_client(config)
-- By default, get_language_id just returns the exact filetype it is passed.
-- It is possible to pass in something that will calculate a different filetype,
-- to be sent by the client.
- config.get_language_id = config.get_language_id or function(_, filetype) return filetype end
+ config.get_language_id = config.get_language_id or function(_, filetype)
+ return filetype
+ end
local client_id = next_client_id()
local handlers = config.handlers or {}
local name = config.name or tostring(client_id)
- local log_prefix = string.format("LSP[%s]", name)
+ local log_prefix = string.format('LSP[%s]', name)
local dispatch = {}
@@ -763,7 +915,7 @@ function lsp.start_client(config)
local handler = resolve_handler(method)
if handler then
-- Method name is provided here for convenience.
- handler(nil, params, {method=method, client_id=client_id})
+ handler(nil, params, { method = method, client_id = client_id })
end
end
@@ -776,10 +928,10 @@ function lsp.start_client(config)
local _ = log.trace() and log.trace('server_request', method, params)
local handler = resolve_handler(method)
if handler then
- local _ = log.trace() and log.trace("server_request: found handler for", method)
- return handler(nil, params, {method=method, client_id=client_id})
+ local _ = log.trace() and log.trace('server_request: found handler for', method)
+ return handler(nil, params, { method = method, client_id = client_id })
end
- local _ = log.warn() and log.warn("server_request: no handler found for", method)
+ local _ = log.warn() and log.warn('server_request: no handler found for', method)
return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
end
@@ -791,18 +943,41 @@ function lsp.start_client(config)
---@see |vim.lsp.rpc.client_errors| for possible errors. Use
---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name.
function dispatch.on_error(code, err)
- local _ = log.error() and log.error(log_prefix, "on_error", { code = lsp.client_errors[code], err = err })
+ local _ = log.error()
+ and log.error(log_prefix, 'on_error', { code = lsp.client_errors[code], err = err })
err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
if config.on_error then
local status, usererr = pcall(config.on_error, code, err)
if not status then
- local _ = log.error() and log.error(log_prefix, "user on_error failed", { err = usererr })
+ local _ = log.error() and log.error(log_prefix, 'user on_error failed', { err = usererr })
err_message(log_prefix, ' user on_error failed: ', tostring(usererr))
end
end
end
---@private
+ local function set_defaults(client, bufnr)
+ if client.server_capabilities.definitionProvider and vim.bo[bufnr].tagfunc == '' then
+ vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc'
+ end
+ if client.server_capabilities.completionProvider and vim.bo[bufnr].omnifunc == '' then
+ vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc'
+ end
+ end
+
+ ---@private
+ --- Reset defaults set by `set_defaults`.
+ --- Must only be called if the last client attached to a buffer exits.
+ local function unset_defaults(bufnr)
+ if vim.bo[bufnr].tagfunc == 'v:lua.vim.lsp.tagfunc' then
+ vim.bo[bufnr].tagfunc = nil
+ end
+ if vim.bo[bufnr].omnifunc == 'v:lua.vim.lsp.omnifunc' then
+ vim.bo[bufnr].omnifunc = nil
+ end
+ end
+
+ ---@private
--- Invoked on client exit.
---
---@param code (number) exit code of the process
@@ -812,17 +987,35 @@ function lsp.start_client(config)
pcall(config.on_exit, code, signal, client_id)
end
+ for bufnr, client_ids in pairs(all_buffer_active_clients) do
+ if client_ids[client_id] then
+ vim.schedule(function()
+ nvim_exec_autocmds('LspDetach', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = client_id },
+ })
+
+ local namespace = vim.lsp.diagnostic.get_namespace(client_id)
+ vim.diagnostic.reset(namespace, bufnr)
+ end)
+
+ client_ids[client_id] = nil
+ end
+ if vim.tbl_isempty(client_ids) then
+ vim.schedule(function()
+ unset_defaults(bufnr)
+ end)
+ end
+ end
+
active_clients[client_id] = nil
uninitialized_clients[client_id] = nil
- lsp.diagnostic.reset(client_id, all_buffer_active_clients)
changetracking.reset(client_id)
- for _, client_ids in pairs(all_buffer_active_clients) do
- client_ids[client_id] = nil
- end
-
if code ~= 0 or (signal ~= 0 and signal ~= 15) then
- local msg = string.format("Client %s quit with exit code %s and signal %s", client_id, code, signal)
+ local msg =
+ string.format('Client %s quit with exit code %s and signal %s', client_id, code, signal)
vim.schedule(function()
vim.notify(msg, vim.log.levels.WARN)
end)
@@ -831,36 +1024,41 @@ function lsp.start_client(config)
-- Start the RPC client.
local rpc = lsp_rpc.start(cmd, cmd_args, dispatch, {
- cwd = config.cmd_cwd;
- env = config.cmd_env;
+ cwd = config.cmd_cwd,
+ env = config.cmd_env,
+ detached = config.detached,
})
-- Return nil if client fails to start
- if not rpc then return end
+ if not rpc then
+ return
+ end
local client = {
- id = client_id;
- name = name;
- rpc = rpc;
- offset_encoding = offset_encoding;
- config = config;
- attached_buffers = {};
+ id = client_id,
+ name = name,
+ rpc = rpc,
+ offset_encoding = offset_encoding,
+ config = config,
+ attached_buffers = {},
- handlers = handlers;
- commands = config.commands or {};
+ handlers = handlers,
+ commands = config.commands or {},
- requests = {};
+ requests = {},
-- for $/progress report
- messages = { name = name, messages = {}, progress = {}, status = {} };
+ messages = { name = name, messages = {}, progress = {}, status = {} },
}
-- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.
- uninitialized_clients[client_id] = client;
+ uninitialized_clients[client_id] = client
---@private
local function initialize()
local valid_traces = {
- off = 'off'; messages = 'messages'; verbose = 'verbose';
+ off = 'off',
+ messages = 'messages',
+ verbose = 'verbose',
}
local version = vim.version()
@@ -869,10 +1067,12 @@ function lsp.start_client(config)
local root_path
if config.workspace_folders or config.root_dir then
if config.root_dir and not config.workspace_folders then
- workspace_folders = {{
- uri = vim.uri_from_fname(config.root_dir);
- name = string.format("%s", config.root_dir);
- }};
+ workspace_folders = {
+ {
+ uri = vim.uri_from_fname(config.root_dir),
+ name = string.format('%s', config.root_dir),
+ },
+ }
else
workspace_folders = config.workspace_folders
end
@@ -889,68 +1089,102 @@ function lsp.start_client(config)
-- the process has not been started by another process. If the parent
-- process is not alive then the server should exit (see exit notification)
-- its process.
- processId = uv.getpid();
+ processId = uv.getpid(),
-- Information about the client
-- since 3.15.0
clientInfo = {
- name = "Neovim",
- version = string.format("%s.%s.%s", version.major, version.minor, version.patch)
- };
+ name = 'Neovim',
+ version = string.format('%s.%s.%s', version.major, version.minor, version.patch),
+ },
-- The rootPath of the workspace. Is null if no folder is open.
--
-- @deprecated in favour of rootUri.
- rootPath = root_path or vim.NIL;
+ rootPath = root_path or vim.NIL,
-- The rootUri of the workspace. Is null if no folder is open. If both
-- `rootPath` and `rootUri` are set `rootUri` wins.
- rootUri = root_uri or vim.NIL;
+ rootUri = root_uri or vim.NIL,
-- The workspace folders configured in the client when the server starts.
-- This property is only available if the client supports workspace folders.
-- It can be `null` if the client supports workspace folders but none are
-- configured.
- workspaceFolders = workspace_folders or vim.NIL;
+ workspaceFolders = workspace_folders or vim.NIL,
-- User provided initialization options.
- initializationOptions = config.init_options;
+ initializationOptions = config.init_options,
-- The capabilities provided by the client (editor or tool)
- capabilities = config.capabilities or protocol.make_client_capabilities();
+ capabilities = config.capabilities or protocol.make_client_capabilities(),
-- The initial trace setting. If omitted trace is disabled ("off").
-- trace = "off" | "messages" | "verbose";
- trace = valid_traces[config.trace] or 'off';
+ trace = valid_traces[config.trace] or 'off',
}
if config.before_init then
-- TODO(ashkan) handle errors here.
pcall(config.before_init, initialize_params, config)
end
- local _ = log.trace() and log.trace(log_prefix, "initialize_params", initialize_params)
+ local _ = log.trace() and log.trace(log_prefix, 'initialize_params', initialize_params)
rpc.request('initialize', initialize_params, function(init_err, result)
assert(not init_err, tostring(init_err))
- assert(result, "server sent empty result")
+ assert(result, 'server sent empty result')
rpc.notify('initialized', vim.empty_dict())
client.initialized = true
uninitialized_clients[client_id] = nil
client.workspace_folders = workspace_folders
-- TODO(mjlbach): Backwards compatibility, to be removed in 0.7
client.workspaceFolders = client.workspace_folders
- client.server_capabilities = assert(result.capabilities, "initialize result doesn't contain capabilities")
+
-- These are the cleaned up capabilities we use for dynamically deciding
-- when to send certain events to clients.
- client.resolved_capabilities = protocol.resolve_capabilities(client.server_capabilities)
+ client.server_capabilities =
+ assert(result.capabilities, "initialize result doesn't contain capabilities")
+ client.server_capabilities = protocol.resolve_capabilities(client.server_capabilities)
+
+ -- Deprecation wrapper: this will be removed in 0.8
+ local mt = {}
+ mt.__index = function(table, key)
+ if key == 'resolved_capabilities' then
+ vim.notify_once(
+ '[LSP] Accessing client.resolved_capabilities is deprecated, '
+ .. 'update your plugins or configuration to access client.server_capabilities instead.'
+ .. 'The new key/value pairs in server_capabilities directly match those '
+ .. 'defined in the language server protocol',
+ vim.log.levels.WARN
+ )
+ rawset(table, key, protocol._resolve_capabilities_compat(client.server_capabilities))
+ return rawget(table, key)
+ else
+ return rawget(table, key)
+ end
+ end
+ setmetatable(client, mt)
+
client.supports_method = function(method)
local required_capability = lsp._request_name_to_capability[method]
-- if we don't know about the method, assume that the client supports it.
if not required_capability then
return true
end
+ if vim.tbl_get(client.server_capabilities, unpack(required_capability)) then
+ return true
+ else
+ return false
+ end
+ end
- return client.resolved_capabilities[required_capability]
+ if next(config.settings) then
+ client.notify('workspace/didChangeConfiguration', { settings = config.settings })
end
+
if config.on_init then
local status, err = pcall(config.on_init, client, result)
if not status then
pcall(handlers.on_error, lsp.client_errors.ON_INIT_CALLBACK_ERROR, err)
end
end
- local _ = log.debug() and log.debug(log_prefix, "server_capabilities", client.server_capabilities)
- local _ = log.info() and log.info(log_prefix, "initialized", { resolved_capabilities = client.resolved_capabilities })
+ local _ = log.info()
+ and log.info(
+ log_prefix,
+ 'server_capabilities',
+ { server_capabilities = client.server_capabilities }
+ )
-- Only assign after initialized.
active_clients[client_id] = client
@@ -985,22 +1219,27 @@ function lsp.start_client(config)
function client.request(method, params, handler, bufnr)
if not handler then
handler = resolve_handler(method)
- or error(string.format("not found: %q request handler for client %q.", method, client.name))
+ or error(string.format('not found: %q request handler for client %q.', method, client.name))
end
-- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
changetracking.flush(client, bufnr)
bufnr = resolve_bufnr(bufnr)
- local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr)
+ local _ = log.debug()
+ and log.debug(log_prefix, 'client.request', client_id, method, params, handler, bufnr)
local success, request_id = rpc.request(method, params, function(err, result)
- handler(err, result, {method=method, client_id=client_id, bufnr=bufnr, params=params})
+ handler(
+ err,
+ result,
+ { method = method, client_id = client_id, bufnr = bufnr, params = params }
+ )
end, function(request_id)
client.requests[request_id] = nil
- nvim_command("doautocmd <nomodeline> User LspRequest")
+ nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
end)
if success then
- client.requests[request_id] = { type='pending', bufnr=bufnr, method=method }
- nvim_command("doautocmd <nomodeline> User LspRequest")
+ client.requests[request_id] = { type = 'pending', bufnr = bufnr, method = method }
+ nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
end
return success, request_id
@@ -1027,9 +1266,10 @@ function lsp.start_client(config)
request_result = { err = err, result = result }
end
- local success, request_id = client.request(method, params, _sync_handler,
- bufnr)
- if not success then return nil end
+ local success, request_id = client.request(method, params, _sync_handler, bufnr)
+ if not success then
+ return nil
+ end
local wait_result, reason = vim.wait(timeout_ms or 1000, function()
return request_result ~= nil
@@ -1064,13 +1304,13 @@ function lsp.start_client(config)
---@returns true if any client returns true; false otherwise
---@see |vim.lsp.client.notify()|
function client.cancel_request(id)
- validate{id = {id, 'n'}}
+ validate({ id = { id, 'n' } })
local request = client.requests[id]
if request and request.type == 'pending' then
request.type = 'cancel'
- nvim_command("doautocmd <nomodeline> User LspRequest")
+ nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
end
- return rpc.notify("$/cancelRequest", { id = id })
+ return rpc.notify('$/cancelRequest', { id = id })
end
-- Track this so that we can escalate automatically if we've already tried a
@@ -1085,18 +1325,11 @@ function lsp.start_client(config)
---
---@param force (bool, optional)
function client.stop(force)
-
- lsp.diagnostic.reset(client_id, all_buffer_active_clients)
- changetracking.reset(client_id)
- for _, client_ids in pairs(all_buffer_active_clients) do
- client_ids[client_id] = nil
- end
-
local handle = rpc.handle
if handle:is_closing() then
return
end
- if force or (not client.initialized) or graceful_shutdown_failed then
+ if force or not client.initialized or graceful_shutdown_failed then
handle:kill(15)
return
end
@@ -1126,6 +1359,15 @@ function lsp.start_client(config)
---@param bufnr (number) Buffer number
function client._on_attach(bufnr)
text_document_did_open_handler(bufnr, client)
+
+ set_defaults(client, bufnr)
+
+ nvim_exec_autocmds('LspAttach', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = client.id },
+ })
+
if config.on_attach then
-- TODO(ashkan) handle errors.
pcall(config.on_attach, client, bufnr)
@@ -1143,34 +1385,37 @@ end
--- Notify all attached clients that a buffer has changed.
local text_document_did_change_handler
do
- text_document_did_change_handler = function(_, bufnr, changedtick, firstline, lastline, new_lastline)
-
- -- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached
- if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
- return true
+ text_document_did_change_handler =
+ function(_, bufnr, changedtick, firstline, lastline, new_lastline)
+ -- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached
+ if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
+ return true
+ end
+ util.buf_versions[bufnr] = changedtick
+ local compute_change_and_notify =
+ changetracking.prepare(bufnr, firstline, lastline, new_lastline)
+ for_each_buffer_client(bufnr, compute_change_and_notify)
end
- util.buf_versions[bufnr] = changedtick
- local compute_change_and_notify = changetracking.prepare(bufnr, firstline, lastline, new_lastline)
- for_each_buffer_client(bufnr, compute_change_and_notify)
- end
end
--- Buffer lifecycle handler for textDocument/didSave
-function lsp._text_document_did_save_handler(bufnr)
+---@private
+---Buffer lifecycle handler for textDocument/didSave
+local function text_document_did_save_handler(bufnr)
bufnr = resolve_bufnr(bufnr)
local uri = vim.uri_from_bufnr(bufnr)
local text = once(buf_get_full_text)
- for_each_buffer_client(bufnr, function(client, _client_id)
- if client.resolved_capabilities.text_document_save then
+ for_each_buffer_client(bufnr, function(client)
+ local save_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'save')
+ if save_capability then
local included_text
- if client.resolved_capabilities.text_document_save_include_text then
+ if type(save_capability) == 'table' and save_capability.includeText then
included_text = text(bufnr)
end
client.notify('textDocument/didSave', {
textDocument = {
- uri = uri;
- };
- text = included_text;
+ uri = uri,
+ },
+ text = included_text,
})
end
end)
@@ -1184,15 +1429,14 @@ end
---@param bufnr (number) Buffer handle, or 0 for current
---@param client_id (number) Client id
function lsp.buf_attach_client(bufnr, client_id)
- validate {
- bufnr = {bufnr, 'n', true};
- client_id = {client_id, 'n'};
- }
+ validate({
+ bufnr = { bufnr, 'n', true },
+ client_id = { client_id, 'n' },
+ })
bufnr = resolve_bufnr(bufnr)
- if not vim.api.nvim_buf_is_loaded(bufnr) then
- local _ = log.warn() and log.warn(
- string.format("buf_attach_client called on unloaded buffer (id: %d): ", bufnr)
- )
+ if not api.nvim_buf_is_loaded(bufnr) then
+ local _ = log.warn()
+ and log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr))
return false
end
local buffer_client_ids = all_buffer_active_clients[bufnr]
@@ -1202,45 +1446,49 @@ function lsp.buf_attach_client(bufnr, client_id)
all_buffer_active_clients[bufnr] = buffer_client_ids
local uri = vim.uri_from_bufnr(bufnr)
- local buf_did_save_autocommand = [=[
- augroup lsp_c_%d_b_%d_did_save
- au!
- au BufWritePost <buffer=%d> lua vim.lsp._text_document_did_save_handler(0)
- augroup END
- ]=]
- vim.api.nvim_exec(string.format(buf_did_save_autocommand, client_id, bufnr, bufnr), false)
+ local augroup = ('lsp_c_%d_b_%d_did_save'):format(client_id, bufnr)
+ api.nvim_create_autocmd('BufWritePost', {
+ group = api.nvim_create_augroup(augroup, { clear = true }),
+ buffer = bufnr,
+ desc = 'vim.lsp: textDocument/didSave handler',
+ callback = function(ctx)
+ text_document_did_save_handler(ctx.buf)
+ end,
+ })
-- First time, so attach and set up stuff.
- vim.api.nvim_buf_attach(bufnr, false, {
- on_lines = text_document_did_change_handler;
+ api.nvim_buf_attach(bufnr, false, {
+ on_lines = text_document_did_change_handler,
on_reload = function()
- local params = { textDocument = { uri = uri; } }
+ local params = { textDocument = { uri = uri } }
for_each_buffer_client(bufnr, function(client, _)
changetracking.reset_buf(client, bufnr)
- if client.resolved_capabilities.text_document_open_close then
+ if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
client.notify('textDocument/didClose', params)
end
text_document_did_open_handler(bufnr, client)
end)
- end;
+ end,
on_detach = function()
- local params = { textDocument = { uri = uri; } }
+ local params = { textDocument = { uri = uri } }
for_each_buffer_client(bufnr, function(client, _)
changetracking.reset_buf(client, bufnr)
- if client.resolved_capabilities.text_document_open_close then
+ if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
client.notify('textDocument/didClose', params)
end
end)
util.buf_versions[bufnr] = nil
all_buffer_active_clients[bufnr] = nil
- end;
+ end,
-- TODO if we know all of the potential clients ahead of time, then we
-- could conditionally set this.
-- utf_sizes = size_index > 1;
- utf_sizes = true;
+ utf_sizes = true,
})
end
- if buffer_client_ids[client_id] then return end
+ if buffer_client_ids[client_id] then
+ return
+ end
-- This is our first time attaching this client to this buffer.
buffer_client_ids[client_id] = true
@@ -1260,25 +1508,35 @@ end
---@param bufnr number Buffer handle, or 0 for current
---@param client_id number Client id
function lsp.buf_detach_client(bufnr, client_id)
- validate {
- bufnr = {bufnr, 'n', true};
- client_id = {client_id, 'n'};
- }
+ validate({
+ bufnr = { bufnr, 'n', true },
+ client_id = { client_id, 'n' },
+ })
bufnr = resolve_bufnr(bufnr)
local client = lsp.get_client_by_id(client_id)
if not client or not client.attached_buffers[bufnr] then
vim.notify(
- string.format('Buffer (id: %d) is not attached to client (id: %d). Cannot detach.', client_id, bufnr)
+ string.format(
+ 'Buffer (id: %d) is not attached to client (id: %d). Cannot detach.',
+ client_id,
+ bufnr
+ )
)
return
end
+ nvim_exec_autocmds('LspDetach', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = client_id },
+ })
+
changetracking.reset_buf(client, bufnr)
- if client.resolved_capabilities.text_document_open_close then
+ if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
local uri = vim.uri_from_bufnr(bufnr)
- local params = { textDocument = { uri = uri; } }
+ local params = { textDocument = { uri = uri } }
client.notify('textDocument/didClose', params)
end
@@ -1294,7 +1552,6 @@ function lsp.buf_detach_client(bufnr, client_id)
vim.diagnostic.reset(namespace, bufnr)
vim.notify(string.format('Detached buffer (id: %d) from client (id: %d)', bufnr, client_id))
-
end
--- Checks if a buffer is attached for a particular client.
@@ -1339,7 +1596,7 @@ end
---@param client_id client id or |vim.lsp.client| object, or list thereof
---@param force boolean (optional) shutdown forcefully
function lsp.stop_client(client_id, force)
- local ids = type(client_id) == 'table' and client_id or {client_id}
+ local ids = type(client_id) == 'table' and client_id or { client_id }
for _, id in ipairs(ids) do
if type(id) == 'table' and id.stop ~= nil then
id.stop(force)
@@ -1351,68 +1608,90 @@ function lsp.stop_client(client_id, force)
end
end
---- Gets all active clients.
+--- Get active clients.
---
----@returns Table of |vim.lsp.client| objects
-function lsp.get_active_clients()
- return vim.tbl_values(active_clients)
-end
+---@param filter (table|nil) A table with key-value pairs used to filter the
+--- returned clients. The available keys are:
+--- - id (number): Only return clients with the given id
+--- - bufnr (number): Only return clients attached to this buffer
+--- - name (string): Only return clients with the given name
+---@returns (table) List of |vim.lsp.client| objects
+function lsp.get_active_clients(filter)
+ validate({ filter = { filter, 't', true } })
-function lsp._vim_exit_handler()
- log.info("exit_handler", active_clients)
- for _, client in pairs(uninitialized_clients) do
- client.stop(true)
- end
- -- TODO handle v:dying differently?
- if tbl_isempty(active_clients) then
- return
- end
- for _, client in pairs(active_clients) do
- client.stop()
- end
+ filter = filter or {}
- local timeouts = {}
- local max_timeout = 0
- local send_kill = false
+ local clients = {}
- for client_id, client in pairs(active_clients) do
- local timeout = if_nil(client.config.flags.exit_timeout, 500)
- if timeout then
- send_kill = true
- timeouts[client_id] = timeout
- max_timeout = math.max(timeout, max_timeout)
+ local t = filter.bufnr and (all_buffer_active_clients[resolve_bufnr(filter.bufnr)] or {})
+ or active_clients
+ for client_id in pairs(t) do
+ local client = active_clients[client_id]
+ if
+ (filter.id == nil or client.id == filter.id)
+ and (filter.name == nil or client.name == filter.name)
+ then
+ clients[#clients + 1] = client
end
end
+ return clients
+end
- local poll_time = 50
-
- ---@private
- local function check_clients_closed()
- for client_id, timeout in pairs(timeouts) do
- timeouts[client_id] = timeout - poll_time
+api.nvim_create_autocmd('VimLeavePre', {
+ desc = 'vim.lsp: exit handler',
+ callback = function()
+ log.info('exit_handler', active_clients)
+ for _, client in pairs(uninitialized_clients) do
+ client.stop(true)
+ end
+ -- TODO handle v:dying differently?
+ if tbl_isempty(active_clients) then
+ return
end
+ for _, client in pairs(active_clients) do
+ client.stop()
+ end
+
+ local timeouts = {}
+ local max_timeout = 0
+ local send_kill = false
- for client_id, _ in pairs(active_clients) do
- if timeouts[client_id] ~= nil and timeouts[client_id] > 0 then
- return false
+ for client_id, client in pairs(active_clients) do
+ local timeout = if_nil(client.config.flags.exit_timeout, 500)
+ if timeout then
+ send_kill = true
+ timeouts[client_id] = timeout
+ max_timeout = math.max(timeout, max_timeout)
end
end
- return true
- end
- if send_kill then
- if not vim.wait(max_timeout, check_clients_closed, poll_time) then
- for client_id, client in pairs(active_clients) do
- if timeouts[client_id] ~= nil then
- client.stop(true)
+ local poll_time = 50
+
+ ---@private
+ local function check_clients_closed()
+ for client_id, timeout in pairs(timeouts) do
+ timeouts[client_id] = timeout - poll_time
+ end
+
+ for client_id, _ in pairs(active_clients) do
+ if timeouts[client_id] ~= nil and timeouts[client_id] > 0 then
+ return false
end
end
+ return true
end
- end
-end
-
-nvim_command("autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()")
+ if send_kill then
+ if not vim.wait(max_timeout, check_clients_closed, poll_time) then
+ for client_id, client in pairs(active_clients) do
+ if timeouts[client_id] ~= nil then
+ client.stop(true)
+ end
+ end
+ end
+ end
+ end,
+})
--- Sends an async request for all active clients attached to the
--- buffer.
@@ -1428,11 +1707,11 @@ nvim_command("autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()")
--- - Function which can be used to cancel all the requests. You could instead
--- iterate all clients and call their `cancel_request()` methods.
function lsp.buf_request(bufnr, method, params, handler)
- validate {
- bufnr = { bufnr, 'n', true };
- method = { method, 's' };
- handler = { handler, 'f', true };
- }
+ validate({
+ bufnr = { bufnr, 'n', true },
+ method = { method, 's' },
+ handler = { handler, 'f', true },
+ })
local supported_clients = {}
local method_supported = false
@@ -1444,20 +1723,22 @@ function lsp.buf_request(bufnr, method, params, handler)
end)
-- 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.api.nvim_command("redraw")
+ nvim_command('redraw')
return {}, function() end
end
local client_request_ids = {}
for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr)
- local request_success, request_id = client.request(method, params, handler, resolved_bufnr)
- -- This could only fail if the client shut down in the time since we looked
- -- it up and we did the request, which should be rare.
- if request_success then
- client_request_ids[client_id] = request_id
- end
+ local request_success, request_id = client.request(method, params, handler, resolved_bufnr)
+ -- This could only fail if the client shut down in the time since we looked
+ -- it up and we did the request, which should be rare.
+ if request_success then
+ client_request_ids[client_id] = request_id
+ end
end, supported_clients)
local function _cancel_all_requests()
@@ -1488,7 +1769,7 @@ function lsp.buf_request_all(bufnr, method, params, callback)
local result_count = 0
local expected_result_count = 0
- local set_expected_result_count = once(function ()
+ local set_expected_result_count = once(function()
for_each_buffer_client(bufnr, function(client)
if client.supports_method(method) then
expected_result_count = expected_result_count + 1
@@ -1552,23 +1833,24 @@ end
---
---@returns true if any client returns true; false otherwise
function lsp.buf_notify(bufnr, method, params)
- validate {
- bufnr = { bufnr, 'n', true };
- method = { method, 's' };
- }
+ validate({
+ bufnr = { bufnr, 'n', true },
+ method = { method, 's' },
+ })
local resp = false
for_each_buffer_client(bufnr, function(client, _client_id, _resolved_bufnr)
- if client.rpc.notify(method, params) then resp = true end
+ if client.rpc.notify(method, params) then
+ resp = true
+ end
end)
return resp
end
-
---@private
local function adjust_start_col(lnum, line, items, encoding)
local min_start_char = nil
for _, item in pairs(items) do
- if item.textEdit and item.textEdit.range.start.line == lnum - 1 then
+ if item.filterText == nil and item.textEdit and item.textEdit.range.start.line == lnum - 1 then
if min_start_char and min_start_char ~= item.textEdit.range.start.character then
return nil
end
@@ -1595,7 +1877,7 @@ end
--- - findstart=0: column where the completion starts, or -2 or -3
--- - findstart=1: list of matches (actually just calls |complete()|)
function lsp.omnifunc(findstart, base)
- local _ = log.debug() and log.debug("omnifunc.findstart", { findstart = findstart, base = base })
+ local _ = log.debug() and log.debug('omnifunc.findstart', { findstart = findstart, base = base })
local bufnr = resolve_bufnr()
local has_buffer_clients = not tbl_isempty(all_buffer_active_clients[bufnr] or {})
@@ -1608,12 +1890,12 @@ function lsp.omnifunc(findstart, base)
end
-- Then, perform standard completion request
- local _ = log.info() and log.info("base ", base)
+ local _ = log.info() and log.info('base ', base)
- local pos = vim.api.nvim_win_get_cursor(0)
- local line = vim.api.nvim_get_current_line()
+ local pos = api.nvim_win_get_cursor(0)
+ local line = api.nvim_get_current_line()
local line_to_cursor = line:sub(1, pos[2])
- local _ = log.trace() and log.trace("omnifunc.line", pos, line)
+ local _ = log.trace() and log.trace('omnifunc.line', pos, line)
-- Get the start position of the current keyword
local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
@@ -1622,7 +1904,9 @@ function lsp.omnifunc(findstart, base)
local items = {}
lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, result, ctx)
- if err or not result or vim.fn.mode() ~= "i" then return end
+ if err or not result or vim.fn.mode() ~= 'i' then
+ return
+ end
-- Completion response items may be relative to a position different than `textMatch`.
-- Concrete example, with sumneko/lua-language-server:
@@ -1659,7 +1943,7 @@ end
---
--- Currently only supports a single client. This can be set via
--- `setlocal formatexpr=v:lua.vim.lsp.formatexpr()` but will typically or in `on_attach`
---- via `vim.api.nvim_buf_set_option(bufnr, 'formatexpr', 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})')`.
+--- via ``vim.api.nvim_buf_set_option(bufnr, 'formatexpr', 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})')``.
---
---@param opts table options for customizing the formatting expression which takes the
--- following optional keys:
@@ -1668,7 +1952,7 @@ function lsp.formatexpr(opts)
opts = opts or {}
local timeout_ms = opts.timeout_ms or 500
- if vim.tbl_contains({'i', 'R', 'ic', 'ix'}, vim.fn.mode()) then
+ if vim.tbl_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then
-- `formatexpr` is also called when exceeding `textwidth` in insert mode
-- fall back to internal formatting
return 1
@@ -1679,19 +1963,24 @@ function lsp.formatexpr(opts)
if start_line > 0 and end_line > 0 then
local params = {
- textDocument = util.make_text_document_params();
+ textDocument = util.make_text_document_params(),
range = {
- start = { line = start_line - 1; character = 0; };
- ["end"] = { line = end_line - 1; character = 0; };
- };
- };
+ start = { line = start_line - 1, character = 0 },
+ ['end'] = { line = end_line - 1, character = 0 },
+ },
+ }
params.options = util.make_formatting_params().options
- local client_results = vim.lsp.buf_request_sync(0, "textDocument/rangeFormatting", params, timeout_ms)
+ local client_results =
+ vim.lsp.buf_request_sync(0, 'textDocument/rangeFormatting', params, timeout_ms)
-- Apply the text edits from one and only one of the clients.
- for _, response in pairs(client_results) do
+ for client_id, response in pairs(client_results) do
if response.result then
- vim.lsp.util.apply_text_edits(response.result, 0)
+ vim.lsp.util.apply_text_edits(
+ response.result,
+ 0,
+ vim.lsp.get_client_by_id(client_id).offset_encoding
+ )
return 0
end
end
@@ -1728,26 +2017,28 @@ end
--- is a |vim.lsp.client| object.
---
---@param bufnr (optional, number): Buffer handle, or 0 for current
+---@returns (table) Table of (client_id, client) pairs
+---@deprecated Use |vim.lsp.get_active_clients()| instead.
function lsp.buf_get_clients(bufnr)
- bufnr = resolve_bufnr(bufnr)
- local result = {}
- for_each_buffer_client(bufnr, function(client, client_id)
- result[client_id] = client
- end)
- return result
+ local result = {}
+ for _, client in ipairs(lsp.get_active_clients({ bufnr = resolve_bufnr(bufnr) })) do
+ result[client.id] = client
+ end
+ return result
end
-- Log level dictionary with reverse lookup as well.
--
-- Can be used to lookup the number from the name or the
-- name from the number.
--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR"
+-- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"
-- Level numbers begin with "TRACE" at 0
lsp.log_levels = log.levels
--- Sets the global log level for LSP logging.
---
---- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR"
+--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"
+---
--- Level numbers begin with "TRACE" at 0
---
--- Use `lsp.log_levels` for reverse lookup.
@@ -1759,7 +2050,7 @@ function lsp.set_log_level(level)
if type(level) == 'string' or type(level) == 'number' then
log.set_level(level)
else
- error(string.format("Invalid log level: %q", level))
+ error(string.format('Invalid log level: %q', level))
end
end
@@ -1789,7 +2080,7 @@ end
---@param override_config (table) Table containing the keys to override behavior of the {handler}
function lsp.with(handler, override_config)
return function(err, result, ctx, config)
- return handler(err, result, ctx, vim.tbl_deep_extend("force", config or {}, override_config))
+ return handler(err, result, ctx, vim.tbl_deep_extend('force', config or {}, override_config))
end
end
@@ -1804,12 +2095,16 @@ function lsp._with_extend(name, options, user_config)
local resulting_config = {}
for k, v in pairs(user_config) do
if options[k] == nil then
- error(debug.traceback(string.format(
- "Invalid option for `%s`: %s. Valid options are:\n%s",
- name,
- k,
- vim.inspect(vim.tbl_keys(options))
- )))
+ error(
+ debug.traceback(
+ string.format(
+ 'Invalid option for `%s`: %s. Valid options are:\n%s',
+ name,
+ k,
+ vim.inspect(vim.tbl_keys(options))
+ )
+ )
+ )
end
resulting_config[k] = v
@@ -1824,7 +2119,6 @@ function lsp._with_extend(name, options, user_config)
return resulting_config
end
-
--- Registry for client side commands.
--- This is an extension point for plugins to handle custom commands which are
--- not part of the core language server protocol specification.
@@ -1846,12 +2140,11 @@ end
--- The second argument is the `ctx` of |lsp-handler|
lsp.commands = setmetatable({}, {
__newindex = function(tbl, key, value)
- assert(type(key) == 'string', "The key for commands in `vim.lsp.commands` must be a string")
- assert(type(value) == 'function', "Command added to `vim.lsp.commands` must be a function")
+ assert(type(key) == 'string', 'The key for commands in `vim.lsp.commands` must be a string')
+ assert(type(value) == 'function', 'Command added to `vim.lsp.commands` must be a function')
rawset(tbl, key, value)
- end;
+ end,
})
-
return lsp
-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/_snippet.lua b/runtime/lua/vim/lsp/_snippet.lua
index 0140b0aee3..3488639fb4 100644
--- a/runtime/lua/vim/lsp/_snippet.lua
+++ b/runtime/lua/vim/lsp/_snippet.lua
@@ -41,7 +41,7 @@ P.take_until = function(targets, specials)
parsed = true,
value = {
raw = table.concat(raw, ''),
- esc = table.concat(esc, '')
+ esc = table.concat(esc, ''),
},
pos = new_pos,
}
@@ -156,10 +156,10 @@ P.seq = function(...)
return function(input, pos)
local values = {}
local new_pos = pos
- for _, parser in ipairs(parsers) do
+ for i, parser in ipairs(parsers) do
local result = parser(input, new_pos)
if result.parsed then
- table.insert(values, result.value)
+ values[i] = result.value
new_pos = result.pos
else
return P.unmatch(pos)
@@ -248,49 +248,122 @@ S.format = P.any(
capture_index = values[3],
}, Node)
end),
- P.map(P.seq(S.dollar, S.open, S.int, S.colon, S.slash, P.any(
- P.token('upcase'),
- P.token('downcase'),
- P.token('capitalize'),
- P.token('camelcase'),
- P.token('pascalcase')
- ), S.close), function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- modifier = values[6],
- }, Node)
- end),
- P.map(P.seq(S.dollar, S.open, S.int, S.colon, P.any(
- P.seq(S.question, P.take_until({ ':' }, { '\\' }), S.colon, P.take_until({ '}' }, { '\\' })),
- P.seq(S.plus, P.take_until({ '}' }, { '\\' })),
- P.seq(S.minus, P.take_until({ '}' }, { '\\' }))
- ), S.close), function(values)
+ P.map(
+ P.seq(
+ S.dollar,
+ S.open,
+ S.int,
+ S.colon,
+ S.slash,
+ P.any(
+ P.token('upcase'),
+ P.token('downcase'),
+ P.token('capitalize'),
+ P.token('camelcase'),
+ P.token('pascalcase')
+ ),
+ S.close
+ ),
+ function(values)
+ return setmetatable({
+ type = Node.Type.FORMAT,
+ capture_index = values[3],
+ modifier = values[6],
+ }, Node)
+ end
+ ),
+ P.map(
+ P.seq(
+ S.dollar,
+ S.open,
+ S.int,
+ S.colon,
+ P.seq(
+ S.question,
+ P.opt(P.take_until({ ':' }, { '\\' })),
+ S.colon,
+ P.opt(P.take_until({ '}' }, { '\\' }))
+ ),
+ S.close
+ ),
+ function(values)
+ return setmetatable({
+ type = Node.Type.FORMAT,
+ capture_index = values[3],
+ if_text = values[5][2] and values[5][2].esc or '',
+ else_text = values[5][4] and values[5][4].esc or '',
+ }, Node)
+ end
+ ),
+ P.map(
+ P.seq(
+ S.dollar,
+ S.open,
+ S.int,
+ S.colon,
+ P.seq(S.plus, P.opt(P.take_until({ '}' }, { '\\' }))),
+ S.close
+ ),
+ function(values)
+ return setmetatable({
+ type = Node.Type.FORMAT,
+ capture_index = values[3],
+ if_text = values[5][2] and values[5][2].esc or '',
+ else_text = '',
+ }, Node)
+ end
+ ),
+ P.map(
+ P.seq(
+ S.dollar,
+ S.open,
+ S.int,
+ S.colon,
+ S.minus,
+ P.opt(P.take_until({ '}' }, { '\\' })),
+ S.close
+ ),
+ function(values)
+ return setmetatable({
+ type = Node.Type.FORMAT,
+ capture_index = values[3],
+ if_text = '',
+ else_text = values[6] and values[6].esc or '',
+ }, Node)
+ end
+ ),
+ P.map(
+ P.seq(S.dollar, S.open, S.int, S.colon, P.opt(P.take_until({ '}' }, { '\\' })), S.close),
+ function(values)
+ return setmetatable({
+ type = Node.Type.FORMAT,
+ capture_index = values[3],
+ if_text = '',
+ else_text = values[5] and values[5].esc or '',
+ }, Node)
+ end
+ )
+)
+
+S.transform = P.map(
+ P.seq(
+ S.slash,
+ P.take_until({ '/' }, { '\\' }),
+ S.slash,
+ P.many(P.any(S.format, S.text({ '$', '/' }, { '\\' }))),
+ S.slash,
+ P.opt(P.pattern('[ig]+'))
+ ),
+ function(values)
return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- if_text = values[5][2].esc,
- else_text = (values[5][4] or {}).esc,
+ type = Node.Type.TRANSFORM,
+ pattern = values[2].raw,
+ format = values[4],
+ option = values[6],
}, Node)
- end)
+ end
)
-S.transform = P.map(P.seq(
- S.slash,
- P.take_until({ '/' }, { '\\' }),
- S.slash,
- P.many(P.any(S.format, S.text({ '$', '/' }, { '\\' }))),
- S.slash,
- P.opt(P.pattern('[ig]+'))
-), function(values)
- return setmetatable({
- type = Node.Type.TRANSFORM,
- pattern = values[2].raw,
- format = values[4],
- option = values[6],
- }, Node)
-end)
-
S.tabstop = P.any(
P.map(P.seq(S.dollar, S.int), function(values)
return setmetatable({
@@ -314,34 +387,52 @@ S.tabstop = P.any(
)
S.placeholder = P.any(
- P.map(P.seq(S.dollar, S.open, S.int, S.colon, P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), S.close), function(values)
- return setmetatable({
- type = Node.Type.PLACEHOLDER,
- tabstop = values[3],
- children = values[5],
- }, Node)
- end)
+ P.map(
+ P.seq(
+ S.dollar,
+ S.open,
+ S.int,
+ S.colon,
+ P.opt(P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' })))),
+ S.close
+ ),
+ function(values)
+ return setmetatable({
+ type = Node.Type.PLACEHOLDER,
+ tabstop = values[3],
+ -- insert empty text if opt did not match.
+ children = values[5] or {
+ setmetatable({
+ type = Node.Type.TEXT,
+ raw = '',
+ esc = '',
+ }, Node),
+ },
+ }, Node)
+ end
+ )
)
-S.choice = P.map(P.seq(
- S.dollar,
- S.open,
- S.int,
- S.pipe,
- P.many(
- P.map(P.seq(S.text({ ',', '|' }), P.opt(S.comma)), function(values)
+S.choice = P.map(
+ P.seq(
+ S.dollar,
+ S.open,
+ S.int,
+ S.pipe,
+ P.many(P.map(P.seq(S.text({ ',', '|' }), P.opt(S.comma)), function(values)
return values[1].esc
- end)
+ end)),
+ S.pipe,
+ S.close
),
- S.pipe,
- S.close
-), function(values)
- return setmetatable({
- type = Node.Type.CHOICE,
- tabstop = values[3],
- items = values[5],
- }, Node)
-end)
+ function(values)
+ return setmetatable({
+ type = Node.Type.CHOICE,
+ tabstop = values[3],
+ items = values[5],
+ }, Node)
+ end
+)
S.variable = P.any(
P.map(P.seq(S.dollar, S.var), function(values)
@@ -363,13 +454,23 @@ S.variable = P.any(
transform = values[4],
}, Node)
end),
- P.map(P.seq(S.dollar, S.open, S.var, S.colon, P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))), S.close), function(values)
- return setmetatable({
- type = Node.Type.VARIABLE,
- name = values[3],
- children = values[5],
- }, Node)
- end)
+ P.map(
+ P.seq(
+ S.dollar,
+ S.open,
+ S.var,
+ S.colon,
+ P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))),
+ S.close
+ ),
+ function(values)
+ return setmetatable({
+ type = Node.Type.VARIABLE,
+ name = values[3],
+ children = values[5],
+ }, Node)
+ end
+ )
)
S.snippet = P.map(P.many(P.any(S.toplevel, S.text({ '$' }, { '}', '\\' }))), function(values)
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index c1d777ae6c..50a51e897c 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -1,30 +1,12 @@
local vim = vim
+local api = vim.api
local validate = vim.validate
-local vfn = vim.fn
-local util = require 'vim.lsp.util'
+local util = require('vim.lsp.util')
+local npcall = vim.F.npcall
local M = {}
---@private
---- Returns nil if {status} is false or nil, otherwise returns the rest of the
---- arguments.
-local function ok_or_nil(status, ...)
- if not status then return end
- return ...
-end
-
----@private
---- Swallows errors.
----
----@param fn Function to run
----@param ... Function arguments
----@returns Result of `fn(...)` if there are no errors, otherwise nil.
---- Returns nil if errors occur during {fn}, otherwise returns
-local function npcall(fn, ...)
- return ok_or_nil(pcall(fn, ...))
-end
-
----@private
--- Sends an async request to all active clients attached to the current
--- buffer.
---
@@ -39,10 +21,10 @@ end
---
---@see |vim.lsp.buf_request()|
local function request(method, params, handler)
- validate {
- method = {method, 's'};
- handler = {handler, 'f', true};
- }
+ validate({
+ method = { method, 's' },
+ handler = { handler, 'f', true },
+ })
return vim.lsp.buf_request(0, method, params, handler)
end
@@ -51,7 +33,7 @@ end
---
---@returns `true` if server responds.
function M.server_ready()
- return not not vim.lsp.buf_notify(0, "window/progress", {})
+ return not not vim.lsp.buf_notify(0, 'window/progress', {})
end
--- Displays hover information about the symbol under the cursor in a floating
@@ -61,26 +43,45 @@ function M.hover()
request('textDocument/hover', params)
end
+---@private
+local function request_with_options(name, params, options)
+ local req_handler
+ if options then
+ req_handler = function(err, result, ctx, config)
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
+ local handler = client.handlers[name] or vim.lsp.handlers[name]
+ handler(err, result, ctx, vim.tbl_extend('force', config or {}, options))
+ end
+ end
+ request(name, params, req_handler)
+end
+
--- Jumps to the declaration of the symbol under the cursor.
---@note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead.
---
-function M.declaration()
+---@param options table|nil additional options
+--- - reuse_win: (boolean) Jump to existing window if buffer is already open.
+function M.declaration(options)
local params = util.make_position_params()
- request('textDocument/declaration', params)
+ request_with_options('textDocument/declaration', params, options)
end
--- Jumps to the definition of the symbol under the cursor.
---
-function M.definition()
+---@param options table|nil additional options
+--- - reuse_win: (boolean) Jump to existing window if buffer is already open.
+function M.definition(options)
local params = util.make_position_params()
- request('textDocument/definition', params)
+ request_with_options('textDocument/definition', params, options)
end
--- Jumps to the definition of the type of the symbol under the cursor.
---
-function M.type_definition()
+---@param options table|nil additional options
+--- - reuse_win: (boolean) Jump to existing window if buffer is already open.
+function M.type_definition(options)
local params = util.make_position_params()
- request('textDocument/typeDefinition', params)
+ request_with_options('textDocument/typeDefinition', params, options)
end
--- Lists all the implementations for the symbol under the cursor in the
@@ -117,9 +118,9 @@ end
--
---@returns The client that the user selected or nil
local function select_client(method, on_choice)
- validate {
+ validate({
on_choice = { on_choice, 'function', false },
- }
+ })
local clients = vim.tbl_values(vim.lsp.buf_get_clients())
clients = vim.tbl_filter(function(client)
return client.supports_method(method)
@@ -143,16 +144,105 @@ local function select_client(method, on_choice)
end
end
+--- Formats a buffer using the attached (and optionally filtered) language
+--- server clients.
+---
+--- @param options table|nil Optional table which holds the following optional fields:
+--- - formatting_options (table|nil):
+--- Can be used to specify FormattingOptions. Some unspecified options will be
+--- automatically derived from the current Neovim options.
+--- @see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting
+--- - timeout_ms (integer|nil, default 1000):
+--- Time in milliseconds to block for formatting requests. No effect if async=true
+--- - bufnr (number|nil):
+--- Restrict formatting to the clients attached to the given buffer, defaults to the current
+--- buffer (0).
+---
+--- - filter (function|nil):
+--- Predicate used to filter clients. Receives a client as argument and must return a
+--- boolean. Clients matching the predicate are included. Example:
+---
+--- <pre>
+--- -- Never request typescript-language-server for formatting
+--- vim.lsp.buf.format {
+--- filter = function(client) return client.name ~= "tsserver" end
+--- }
+--- </pre>
+---
+--- - async boolean|nil
+--- If true the method won't block. Defaults to false.
+--- Editing the buffer while formatting asynchronous can lead to unexpected
+--- changes.
+---
+--- - id (number|nil):
+--- Restrict formatting to the client with ID (client.id) matching this field.
+--- - name (string|nil):
+--- Restrict formatting to the client with name (client.name) matching this field.
+
+function M.format(options)
+ options = options or {}
+ local bufnr = options.bufnr or api.nvim_get_current_buf()
+ local clients = vim.lsp.get_active_clients({
+ id = options.id,
+ bufnr = bufnr,
+ name = options.name,
+ })
+
+ if options.filter then
+ clients = vim.tbl_filter(options.filter, clients)
+ end
+
+ clients = vim.tbl_filter(function(client)
+ return client.supports_method('textDocument/formatting')
+ end, clients)
+
+ if #clients == 0 then
+ vim.notify('[LSP] Format request failed, no matching language servers.')
+ end
+
+ if options.async then
+ local do_format
+ do_format = function(idx, client)
+ if not client then
+ return
+ end
+ local params = util.make_formatting_params(options.formatting_options)
+ client.request('textDocument/formatting', params, function(...)
+ local handler = client.handlers['textDocument/formatting']
+ or vim.lsp.handlers['textDocument/formatting']
+ handler(...)
+ do_format(next(clients, idx))
+ end, bufnr)
+ end
+ do_format(next(clients))
+ else
+ local timeout_ms = options.timeout_ms or 1000
+ for _, client in pairs(clients) do
+ local params = util.make_formatting_params(options.formatting_options)
+ local result, err = client.request_sync('textDocument/formatting', params, timeout_ms, bufnr)
+ if result and result.result then
+ util.apply_text_edits(result.result, bufnr, client.offset_encoding)
+ elseif err then
+ vim.notify(string.format('[LSP][%s] %s', client.name, err), vim.log.levels.WARN)
+ end
+ end
+ end
+end
+
--- Formats the current buffer.
---
----@param options (optional, table) Can be used to specify FormattingOptions.
+---@param options (table|nil) Can be used to specify FormattingOptions.
--- Some unspecified options will be automatically derived from the current
--- Neovim options.
--
---@see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting
function M.formatting(options)
+ vim.notify_once(
+ 'vim.lsp.buf.formatting is deprecated. Use vim.lsp.buf.format { async = true } instead',
+ vim.log.levels.WARN
+ )
local params = util.make_formatting_params(options)
- local bufnr = vim.api.nvim_get_current_buf()
+ local bufnr = api.nvim_get_current_buf()
select_client('textDocument/formatting', function(client)
if client == nil then
return
@@ -171,12 +261,16 @@ end
--- autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync()
--- </pre>
---
----@param options Table with valid `FormattingOptions` entries
+---@param options table|nil with valid `FormattingOptions` entries
---@param timeout_ms (number) Request timeout
---@see |vim.lsp.buf.formatting_seq_sync|
function M.formatting_sync(options, timeout_ms)
+ vim.notify_once(
+ 'vim.lsp.buf.formatting_sync is deprecated. Use vim.lsp.buf.format instead',
+ vim.log.levels.WARN
+ )
local params = util.make_formatting_params(options)
- local bufnr = vim.api.nvim_get_current_buf()
+ local bufnr = api.nvim_get_current_buf()
select_client('textDocument/formatting', function(client)
if client == nil then
return
@@ -184,7 +278,7 @@ function M.formatting_sync(options, timeout_ms)
local result, err = client.request_sync('textDocument/formatting', params, timeout_ms, bufnr)
if result and result.result then
- util.apply_text_edits(result.result, bufnr)
+ util.apply_text_edits(result.result, bufnr, client.offset_encoding)
elseif err then
vim.notify('vim.lsp.buf.formatting_sync: ' .. err, vim.log.levels.WARN)
end
@@ -202,14 +296,18 @@ end
--- vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]]
--- </pre>
---
----@param options (optional, table) `FormattingOptions` entries
----@param timeout_ms (optional, number) Request timeout
----@param order (optional, table) List of client names. Formatting is requested from clients
+---@param options (table|nil) `FormattingOptions` entries
+---@param timeout_ms (number|nil) Request timeout
+---@param order (table|nil) List of client names. Formatting is requested from clients
---in the following order: first all clients that are not in the `order` list, then
---the remaining clients in the order as they occur in the `order` list.
function M.formatting_seq_sync(options, timeout_ms, order)
- local clients = vim.tbl_values(vim.lsp.buf_get_clients());
- local bufnr = vim.api.nvim_get_current_buf()
+ 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 bufnr = api.nvim_get_current_buf()
-- sort the clients according to `order`
for _, client_name in pairs(order or {}) do
@@ -224,13 +322,21 @@ function M.formatting_seq_sync(options, timeout_ms, order)
-- loop through the clients and make synchronous formatting requests
for _, client in pairs(clients) do
- if client.resolved_capabilities.document_formatting then
+ if vim.tbl_get(client.server_capabilities, 'documentFormattingProvider') then
local params = util.make_formatting_params(options)
- local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, vim.api.nvim_get_current_buf())
+ local result, err = client.request_sync(
+ 'textDocument/formatting',
+ params,
+ timeout_ms,
+ api.nvim_get_current_buf()
+ )
if result and result.result then
- util.apply_text_edits(result.result, bufnr)
+ util.apply_text_edits(result.result, bufnr, client.offset_encoding)
elseif err then
- vim.notify(string.format("vim.lsp.buf.formatting_seq_sync: (%s) %s", client.name, err), vim.log.levels.WARN)
+ vim.notify(
+ string.format('vim.lsp.buf.formatting_seq_sync: (%s) %s', client.name, err),
+ vim.log.levels.WARN
+ )
end
end
end
@@ -257,50 +363,133 @@ end
--- Renames all references to the symbol under the cursor.
---
----@param new_name (string) If not provided, the user will be prompted for a new
----name using |vim.ui.input()|.
-function M.rename(new_name)
- local opts = {
- prompt = "New Name: "
- }
+---@param new_name string|nil If not provided, the user will be prompted for a new
+--- name using |vim.ui.input()|.
+---@param options table|nil additional options
+--- - filter (function|nil):
+--- Predicate used to filter clients. Receives a client as argument and
+--- must return a boolean. Clients matching the predicate are included.
+--- - name (string|nil):
+--- Restrict clients used for rename to ones where client.name matches
+--- this field.
+function M.rename(new_name, options)
+ options = options or {}
+ local bufnr = options.bufnr or api.nvim_get_current_buf()
+ local clients = vim.lsp.get_active_clients({
+ bufnr = bufnr,
+ name = options.name,
+ })
+ if options.filter then
+ clients = vim.tbl_filter(options.filter, clients)
+ end
- ---@private
- local function on_confirm(input)
- if not (input and #input > 0) then return end
- local params = util.make_position_params()
- params.newName = input
- request('textDocument/rename', params)
+ -- Clients must at least support rename, prepareRename is optional
+ clients = vim.tbl_filter(function(client)
+ return client.supports_method('textDocument/rename')
+ end, clients)
+
+ if #clients == 0 then
+ vim.notify('[LSP] Rename, no matching language servers with rename capability.')
end
+ local win = api.nvim_get_current_win()
+
+ -- Compute early to account for cursor movements after going async
+ local cword = vim.fn.expand('<cword>')
+
---@private
- local function prepare_rename(err, result)
- if err == nil and result == nil then
- vim.notify('nothing to rename', vim.log.levels.INFO)
+ local function get_text_at_range(range, offset_encoding)
+ return api.nvim_buf_get_text(
+ bufnr,
+ range.start.line,
+ util._get_line_byte_from_position(bufnr, range.start, offset_encoding),
+ range['end'].line,
+ util._get_line_byte_from_position(bufnr, range['end'], offset_encoding),
+ {}
+ )[1]
+ end
+
+ local try_use_client
+ try_use_client = function(idx, client)
+ if not client then
return
end
- if result and result.placeholder then
- opts.default = result.placeholder
- if not new_name then npcall(vim.ui.input, opts, on_confirm) end
- elseif result and result.start and result['end'] and
- result.start.line == result['end'].line then
- local line = vfn.getline(result.start.line+1)
- local start_char = result.start.character+1
- local end_char = result['end'].character
- opts.default = string.sub(line, start_char, end_char)
- if not new_name then npcall(vim.ui.input, opts, on_confirm) end
+
+ ---@private
+ local function rename(name)
+ local params = util.make_position_params(win, client.offset_encoding)
+ params.newName = name
+ local handler = client.handlers['textDocument/rename']
+ or vim.lsp.handlers['textDocument/rename']
+ client.request('textDocument/rename', params, function(...)
+ handler(...)
+ try_use_client(next(clients, idx))
+ end, bufnr)
+ end
+
+ if client.supports_method('textDocument/prepareRename') then
+ local params = util.make_position_params(win, client.offset_encoding)
+ client.request('textDocument/prepareRename', params, function(err, result)
+ if err or result == nil then
+ if next(clients, idx) then
+ try_use_client(next(clients, idx))
+ else
+ local msg = err and ('Error on prepareRename: ' .. (err.message or ''))
+ or 'Nothing to rename'
+ vim.notify(msg, vim.log.levels.INFO)
+ end
+ return
+ end
+
+ if new_name then
+ rename(new_name)
+ return
+ end
+
+ local prompt_opts = {
+ prompt = 'New Name: ',
+ }
+ -- result: Range | { range: Range, placeholder: string }
+ if result.placeholder then
+ prompt_opts.default = result.placeholder
+ elseif result.start then
+ prompt_opts.default = get_text_at_range(result, client.offset_encoding)
+ elseif result.range then
+ prompt_opts.default = get_text_at_range(result.range, client.offset_encoding)
+ else
+ prompt_opts.default = cword
+ end
+ vim.ui.input(prompt_opts, function(input)
+ if not input or #input == 0 then
+ return
+ end
+ rename(input)
+ end)
+ end, bufnr)
else
- -- fallback to guessing symbol using <cword>
- --
- -- this can happen if the language server does not support prepareRename,
- -- returns an unexpected response, or requests for "default behavior"
- --
- -- see https://microsoft.github.io/language-server-protocol/specification#textDocument_prepareRename
- opts.default = vfn.expand('<cword>')
- if not new_name then npcall(vim.ui.input, opts, on_confirm) end
+ assert(
+ client.supports_method('textDocument/rename'),
+ 'Client must support textDocument/rename'
+ )
+ if new_name then
+ rename(new_name)
+ return
+ end
+
+ local prompt_opts = {
+ prompt = 'New Name: ',
+ default = cword,
+ }
+ vim.ui.input(prompt_opts, function(input)
+ if not input or #input == 0 then
+ return
+ end
+ rename(input)
+ end)
end
- if new_name then on_confirm(new_name) end
end
- request('textDocument/prepareRename', util.make_position_params(), prepare_rename)
+
+ try_use_client(next(clients))
end
--- Lists all the references to the symbol under the cursor in the quickfix window.
@@ -308,10 +497,10 @@ end
---@param context (table) Context for the request
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
function M.references(context)
- validate { context = { context, 't', true } }
+ validate({ context = { context, 't', true } })
local params = util.make_position_params()
params.context = context or {
- includeDeclaration = true;
+ includeDeclaration = true,
}
request('textDocument/references', params)
end
@@ -325,14 +514,16 @@ end
---@private
local function pick_call_hierarchy_item(call_hierarchy_items)
- if not call_hierarchy_items then return end
+ if not call_hierarchy_items then
+ return
+ end
if #call_hierarchy_items == 1 then
return call_hierarchy_items[1]
end
local items = {}
for i, item in pairs(call_hierarchy_items) do
local entry = item.detail or item.name
- table.insert(items, string.format("%d. %s", i, entry))
+ table.insert(items, string.format('%d. %s', i, entry))
end
local choice = vim.fn.inputlist(items)
if choice < 1 or choice > #items then
@@ -354,8 +545,8 @@ local function call_hierarchy(method)
if client then
client.request(method, { item = call_hierarchy_item }, nil, ctx.bufnr)
else
- vim.notify(string.format(
- 'Client with id=%d disappeared during call hierarchy request', ctx.client_id),
+ vim.notify(
+ string.format('Client with id=%d disappeared during call hierarchy request', ctx.client_id),
vim.log.levels.WARN
)
end
@@ -391,20 +582,26 @@ end
--- Add the folder at path to the workspace folders. If {path} is
--- not provided, the user will be prompted for a path using |input()|.
function M.add_workspace_folder(workspace_folder)
- workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h'), 'dir')
- vim.api.nvim_command("redraw")
- if not (workspace_folder and #workspace_folder > 0) then return end
+ workspace_folder = workspace_folder
+ or npcall(vim.fn.input, 'Workspace Folder: ', vim.fn.expand('%:p:h'), 'dir')
+ api.nvim_command('redraw')
+ if not (workspace_folder and #workspace_folder > 0) then
+ return
+ end
if vim.fn.isdirectory(workspace_folder) == 0 then
- print(workspace_folder, " is not a valid directory")
+ 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}}, {{}})
+ local params = util.make_workspace_params(
+ { { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } },
+ { {} }
+ )
for _, client in pairs(vim.lsp.buf_get_clients()) do
local found = false
for _, folder in pairs(client.workspace_folders or {}) do
if folder.name == workspace_folder then
found = true
- print(workspace_folder, "is already part of this workspace")
+ print(workspace_folder, 'is already part of this workspace')
break
end
end
@@ -422,10 +619,16 @@ end
--- {path} is not provided, the user will be prompted for
--- a path using |input()|.
function M.remove_workspace_folder(workspace_folder)
- workspace_folder = workspace_folder or npcall(vfn.input, "Workspace Folder: ", vfn.expand('%:p:h'))
- vim.api.nvim_command("redraw")
- if not (workspace_folder and #workspace_folder > 0) then return end
- local params = util.make_workspace_params({{}}, {{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}})
+ workspace_folder = workspace_folder
+ or npcall(vim.fn.input, 'Workspace Folder: ', vim.fn.expand('%:p:h'))
+ api.nvim_command('redraw')
+ if not (workspace_folder and #workspace_folder > 0) then
+ return
+ end
+ local params = util.make_workspace_params(
+ { {} },
+ { { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } }
+ )
for _, client in pairs(vim.lsp.buf_get_clients()) do
for idx, folder in pairs(client.workspace_folders) do
if folder.name == workspace_folder then
@@ -435,7 +638,7 @@ function M.remove_workspace_folder(workspace_folder)
end
end
end
- print(workspace_folder, "is not currently part of the workspace")
+ print(workspace_folder, 'is not currently part of the workspace')
end
--- Lists all symbols in the current workspace in the quickfix window.
@@ -446,8 +649,11 @@ end
---
---@param query (string, optional)
function M.workspace_symbol(query)
- query = query or npcall(vfn.input, "Query: ")
- local params = {query = query}
+ query = query or npcall(vim.fn.input, 'Query: ')
+ if query == nil then
+ return
+ end
+ local params = { query = query }
request('workspace/symbol', params)
end
@@ -477,7 +683,6 @@ function M.clear_references()
util.buf_clear_references()
end
-
---@private
--
--- This is not public because the main extension point is
@@ -488,11 +693,42 @@ end
--- from multiple clients to have 1 single UI prompt for the user, yet we still
--- need to be able to link a `CodeAction|Command` to the right client for
--- `codeAction/resolve`
-local function on_code_action_results(results, ctx)
+local function on_code_action_results(results, ctx, options)
local action_tuples = {}
+
+ ---@private
+ local function action_filter(a)
+ -- filter by specified action kind
+ if options and options.context and options.context.only then
+ if not a.kind then
+ return false
+ end
+ local found = false
+ for _, o in ipairs(options.context.only) do
+ -- action kinds are hierarchical with . as a separator: when requesting only
+ -- 'quickfix' this filter allows both 'quickfix' and 'quickfix.foo', for example
+ if a.kind:find('^' .. o .. '$') or a.kind:find('^' .. o .. '%.') then
+ found = true
+ break
+ end
+ end
+ if not found then
+ return false
+ end
+ end
+ -- filter by user function
+ if options and options.filter and not options.filter(a) then
+ return false
+ end
+ -- no filter removed this action
+ return true
+ end
+
for client_id, result in pairs(results) do
for _, action in pairs(result.result or {}) do
- table.insert(action_tuples, { client_id, action })
+ if action_filter(action) then
+ table.insert(action_tuples, { client_id, action })
+ end
end
end
if #action_tuples == 0 then
@@ -503,7 +739,7 @@ local function on_code_action_results(results, ctx)
---@private
local function apply_action(action, client)
if action.edit then
- util.apply_workspace_edit(action.edit)
+ util.apply_workspace_edit(action.edit, client.offset_encoding)
end
if action.command then
local command = type(action.command) == 'table' and action.command or action
@@ -513,7 +749,14 @@ local function on_code_action_results(results, ctx)
enriched_ctx.client_id = client.id
fn(command, enriched_ctx)
else
- M.execute_command(command)
+ -- Not using command directly to exclude extra properties,
+ -- see https://github.com/python-lsp/python-lsp-server/issues/146
+ local params = {
+ command = command.command,
+ arguments = command.arguments,
+ workDoneToken = command.workDoneToken,
+ }
+ client.request('workspace/executeCommand', params, nil, ctx.bufnr)
end
end
end
@@ -537,11 +780,11 @@ local function on_code_action_results(results, ctx)
--
local client = vim.lsp.get_client_by_id(action_tuple[1])
local action = action_tuple[2]
- if not action.edit
- and client
- and type(client.resolved_capabilities.code_action) == 'table'
- and client.resolved_capabilities.code_action.resolveProvider then
-
+ if
+ not action.edit
+ and client
+ and vim.tbl_get(client.server_capabilities, 'codeActionProvider', 'resolveProvider')
+ then
client.request('codeAction/resolve', action, function(err, resolved_action)
if err then
vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR)
@@ -554,6 +797,13 @@ local function on_code_action_results(results, ctx)
end
end
+ -- If options.apply is given, and there are just one remaining code action,
+ -- apply it directly without querying the user.
+ if options and options.apply and #action_tuples == 1 then
+ on_user_choice(action_tuples[1])
+ return
+ end
+
vim.ui.select(action_tuples, {
prompt = 'Code actions:',
kind = 'codeaction',
@@ -564,39 +814,53 @@ local function on_code_action_results(results, ctx)
}, on_user_choice)
end
-
--- Requests code actions from all clients and calls the handler exactly once
--- with all aggregated results
---@private
-local function code_action_request(params)
- local bufnr = vim.api.nvim_get_current_buf()
+local function code_action_request(params, options)
+ local bufnr = api.nvim_get_current_buf()
local method = 'textDocument/codeAction'
vim.lsp.buf_request_all(bufnr, method, params, function(results)
- on_code_action_results(results, { bufnr = bufnr, method = method, params = params })
+ local ctx = { bufnr = bufnr, method = method, params = params }
+ on_code_action_results(results, ctx, options)
end)
end
--- Selects a code action available at the current
--- cursor position.
---
----@param context table|nil `CodeActionContext` of the LSP specification:
---- - diagnostics: (table|nil)
---- LSP `Diagnostic[]`. Inferred from the current
---- position if not provided.
---- - only: (string|nil)
---- LSP `CodeActionKind` used to filter the code actions.
---- Most language servers support values like `refactor`
---- or `quickfix`.
+---@param options table|nil Optional table which holds the following optional fields:
+--- - context (table|nil):
+--- Corresponds to `CodeActionContext` of the LSP specification:
+--- - diagnostics (table|nil):
+--- LSP `Diagnostic[]`. Inferred from the current
+--- position if not provided.
+--- - only (table|nil):
+--- List of LSP `CodeActionKind`s used to filter the code actions.
+--- Most language servers support values like `refactor`
+--- or `quickfix`.
+--- - filter (function|nil):
+--- Predicate function taking an `CodeAction` and returning a boolean.
+--- - apply (boolean|nil):
+--- When set to `true`, and there is just one remaining action
+--- (after filtering), the action is applied without user query.
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
-function M.code_action(context)
- validate { context = { context, 't', true } }
- context = context or {}
+function M.code_action(options)
+ validate({ options = { options, 't', true } })
+ options = options or {}
+ -- Detect old API call code_action(context) which should now be
+ -- code_action({ context = context} )
+ if options.diagnostics or options.only then
+ options = { options = options }
+ end
+ local context = options.context or {}
if not context.diagnostics then
- context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics()
+ local bufnr = api.nvim_get_current_buf()
+ context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics(bufnr)
end
local params = util.make_range_params()
params.context = context
- code_action_request(params)
+ code_action_request(params, options)
end
--- Performs |vim.lsp.buf.code_action()| for a given range.
@@ -606,8 +870,8 @@ end
--- - diagnostics: (table|nil)
--- LSP `Diagnostic[]`. Inferred from the current
--- position if not provided.
---- - only: (string|nil)
---- LSP `CodeActionKind` used to filter the code actions.
+--- - only: (table|nil)
+--- List of LSP `CodeActionKind`s used to filter the code actions.
--- Most language servers support values like `refactor`
--- or `quickfix`.
---@param start_pos ({number, number}, optional) mark-indexed position.
@@ -615,10 +879,11 @@ end
---@param end_pos ({number, number}, optional) mark-indexed position.
---Defaults to the end of the last visual selection.
function M.range_code_action(context, start_pos, end_pos)
- validate { context = { context, 't', true } }
+ validate({ context = { context, 't', true } })
context = context or {}
if not context.diagnostics then
- context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics()
+ local bufnr = api.nvim_get_current_buf()
+ context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics(bufnr)
end
local params = util.make_given_range_params(start_pos, end_pos)
params.context = context
@@ -630,16 +895,16 @@ end
---@param command_params table A valid `ExecuteCommandParams` object
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
function M.execute_command(command_params)
- validate {
+ validate({
command = { command_params.command, 's' },
- arguments = { command_params.arguments, 't', true }
- }
+ arguments = { command_params.arguments, 't', true },
+ })
command_params = {
- command=command_params.command,
- arguments=command_params.arguments,
- workDoneToken=command_params.workDoneToken,
+ command = command_params.command,
+ arguments = command_params.arguments,
+ workDoneToken = command_params.workDoneToken,
}
- request('workspace/executeCommand', command_params )
+ request('workspace/executeCommand', command_params)
end
return M
diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index 9eb64c9a2e..4fa02c8db2 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -1,4 +1,5 @@
local util = require('vim.lsp.util')
+local log = require('vim.lsp.log')
local api = vim.api
local M = {}
@@ -11,7 +12,7 @@ local lens_cache_by_buf = setmetatable({}, {
__index = function(t, b)
local key = b > 0 and b or api.nvim_get_current_buf()
return rawget(t, key)
- end
+ end,
})
local namespaces = setmetatable({}, {
@@ -19,13 +20,12 @@ local namespaces = setmetatable({}, {
local value = api.nvim_create_namespace('vim_lsp_codelens:' .. key)
rawset(t, key, value)
return value
- end;
+ end,
})
---@private
M.__namespaces = namespaces
-
---@private
local function execute_lens(lens, bufnr, client_id)
local line = lens.range.start.line
@@ -43,10 +43,14 @@ local function execute_lens(lens, bufnr, client_id)
local command_provider = client.server_capabilities.executeCommandProvider
local commands = type(command_provider) == 'table' and command_provider.commands or {}
if not vim.tbl_contains(commands, command.command) then
- vim.notify(string.format(
- "Language server does not support command `%s`. This command may require a client extension.", command.command),
- vim.log.levels.WARN)
- return
+ vim.notify(
+ string.format(
+ 'Language server does not support command `%s`. This command may require a client extension.',
+ command.command
+ ),
+ vim.log.levels.WARN
+ )
+ return
end
client.request('workspace/executeCommand', command, function(...)
local result = vim.lsp.handlers['workspace/executeCommand'](...)
@@ -55,14 +59,15 @@ local function execute_lens(lens, bufnr, client_id)
end, bufnr)
end
-
--- Return all lenses for the given buffer
---
---@param bufnr number Buffer number. 0 can be used for the current buffer.
---@return table (`CodeLens[]`)
function M.get(bufnr)
local lenses_by_client = lens_cache_by_buf[bufnr or 0]
- if not lenses_by_client then return {} end
+ if not lenses_by_client then
+ return {}
+ end
local lenses = {}
for _, client_lenses in pairs(lenses_by_client) do
vim.list_extend(lenses, client_lenses)
@@ -70,7 +75,6 @@ function M.get(bufnr)
return lenses
end
-
--- Run the code lens in the current line
---
function M.run()
@@ -81,7 +85,7 @@ function M.run()
for client, lenses in pairs(lenses_by_client) do
for _, lens in pairs(lenses) do
if lens.range.start.line == (line - 1) then
- table.insert(options, {client=client, lens=lens})
+ table.insert(options, { client = client, lens = lens })
end
end
end
@@ -104,7 +108,6 @@ function M.run()
end
end
-
--- Display the lenses using virtual text
---
---@param lenses table of lenses to display (`CodeLens[] | null`)
@@ -130,21 +133,25 @@ function M.display(lenses, bufnr, client_id)
api.nvim_buf_clear_namespace(bufnr, ns, i, i + 1)
local chunks = {}
local num_line_lenses = #line_lenses
+ table.sort(line_lenses, function(a, b)
+ return a.range.start.character < b.range.start.character
+ end)
for j, lens in ipairs(line_lenses) do
local text = lens.command and lens.command.title or 'Unresolved lens ...'
- table.insert(chunks, {text, 'LspCodeLens' })
+ table.insert(chunks, { text, 'LspCodeLens' })
if j < num_line_lenses then
- table.insert(chunks, {' | ', 'LspCodeLensSeparator' })
+ table.insert(chunks, { ' | ', 'LspCodeLensSeparator' })
end
end
if #chunks > 0 then
- api.nvim_buf_set_extmark(bufnr, ns, i, 0, { virt_text = chunks,
- hl_mode="combine" })
+ api.nvim_buf_set_extmark(bufnr, ns, i, 0, {
+ virt_text = chunks,
+ hl_mode = 'combine',
+ })
end
end
end
-
--- Store lenses for a specific buffer and client
---
---@param lenses table of lenses to store (`CodeLens[] | null`)
@@ -157,16 +164,17 @@ function M.save(lenses, bufnr, client_id)
lens_cache_by_buf[bufnr] = lenses_by_client
local ns = namespaces[client_id]
api.nvim_buf_attach(bufnr, false, {
- on_detach = function(b) lens_cache_by_buf[b] = nil end,
+ on_detach = function(b)
+ lens_cache_by_buf[b] = nil
+ end,
on_lines = function(_, b, _, first_lnum, last_lnum)
api.nvim_buf_clear_namespace(b, ns, first_lnum, last_lnum)
- end
+ end,
})
end
lenses_by_client[client_id] = lenses
end
-
---@private
local function resolve_lenses(lenses, bufnr, client_id, callback)
lenses = lenses or {}
@@ -200,8 +208,7 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
ns,
lens.range.start.line,
0,
- { virt_text = {{ lens.command.title, 'LspCodeLens' }},
- hl_mode="combine" }
+ { virt_text = { { lens.command.title, 'LspCodeLens' } }, hl_mode = 'combine' }
)
end
countdown()
@@ -210,11 +217,14 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
end
end
-
--- |lsp-handler| for the method `textDocument/codeLens`
---
function M.on_codelens(err, result, ctx, _)
- assert(not err, vim.inspect(err))
+ if err then
+ active_refreshes[ctx.bufnr] = nil
+ local _ = log.error() and log.error('codelens', err)
+ return
+ end
M.save(result, ctx.bufnr, ctx.client_id)
@@ -222,12 +232,11 @@ function M.on_codelens(err, result, ctx, _)
-- once resolved.
M.display(result, ctx.bufnr, ctx.client_id)
resolve_lenses(result, ctx.bufnr, ctx.client_id, function()
- M.display(result, ctx.bufnr, ctx.client_id)
active_refreshes[ctx.bufnr] = nil
+ M.display(result, ctx.bufnr, ctx.client_id)
end)
end
-
--- Refresh the codelens for the current buffer
---
--- It is recommended to trigger this using an autocmd or via keymap.
@@ -238,15 +247,14 @@ end
---
function M.refresh()
local params = {
- textDocument = util.make_text_document_params()
+ textDocument = util.make_text_document_params(),
}
local bufnr = api.nvim_get_current_buf()
if active_refreshes[bufnr] then
return
end
active_refreshes[bufnr] = true
- vim.lsp.buf_request(0, 'textDocument/codeLens', params)
+ vim.lsp.buf_request(0, 'textDocument/codeLens', params, M.on_codelens)
end
-
return M
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index f38b469f3c..1f9d084e2b 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -22,17 +22,6 @@ local function get_client_id(client_id)
end
---@private
-local function get_bufnr(bufnr)
- if not bufnr then
- return vim.api.nvim_get_current_buf()
- elseif bufnr == 0 then
- return vim.api.nvim_get_current_buf()
- end
-
- return bufnr
-end
-
----@private
local function severity_lsp_to_vim(severity)
if type(severity) == 'string' then
severity = vim.lsp.protocol.DiagnosticSeverity[severity]
@@ -50,12 +39,12 @@ end
---@private
local function line_byte_from_position(lines, lnum, col, offset_encoding)
- if not lines or offset_encoding == "utf-8" then
+ if not lines or offset_encoding == 'utf-8' then
return col
end
local line = lines[lnum + 1]
- local ok, result = pcall(vim.str_byteindex, line, col, offset_encoding == "utf-16")
+ local ok, result = pcall(vim.str_byteindex, line, col, offset_encoding == 'utf-16')
if ok then
return result
end
@@ -75,7 +64,7 @@ local function get_buf_lines(bufnr)
return
end
- local content = f:read("*a")
+ local content = f:read('*a')
if not content then
-- Some LSP servers report diagnostics at a directory level, in which case
-- io.read() returns nil
@@ -83,7 +72,7 @@ local function get_buf_lines(bufnr)
return
end
- local lines = vim.split(content, "\n")
+ local lines = vim.split(content, '\n')
f:close()
return lines
end
@@ -92,10 +81,10 @@ end
local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
local buf_lines = get_buf_lines(bufnr)
local client = vim.lsp.get_client_by_id(client_id)
- local offset_encoding = client and client.offset_encoding or "utf-16"
+ local offset_encoding = client and client.offset_encoding or 'utf-16'
return vim.tbl_map(function(diagnostic)
local start = diagnostic.range.start
- local _end = diagnostic.range["end"]
+ local _end = diagnostic.range['end']
return {
lnum = start.line,
col = line_byte_from_position(buf_lines, start.line, start.character, offset_encoding),
@@ -104,8 +93,10 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
severity = severity_lsp_to_vim(diagnostic.severity),
message = diagnostic.message,
source = diagnostic.source,
+ code = diagnostic.code,
user_data = {
lsp = {
+ -- usage of user_data.lsp.code is deprecated in favor of the top-level code field
code = diagnostic.code,
codeDescription = diagnostic.codeDescription,
tags = diagnostic.tags,
@@ -120,13 +111,14 @@ end
---@private
local function diagnostic_vim_to_lsp(diagnostics)
return vim.tbl_map(function(diagnostic)
- return vim.tbl_extend("error", {
+ return vim.tbl_extend('keep', {
+ -- "keep" the below fields over any duplicate fields in diagnostic.user_data.lsp
range = {
start = {
line = diagnostic.lnum,
character = diagnostic.col,
},
- ["end"] = {
+ ['end'] = {
line = diagnostic.end_lnum,
character = diagnostic.end_col,
},
@@ -134,6 +126,7 @@ local function diagnostic_vim_to_lsp(diagnostics)
severity = severity_vim_to_lsp(diagnostic.severity),
message = diagnostic.message,
source = diagnostic.source,
+ code = diagnostic.code,
}, diagnostic.user_data and (diagnostic.user_data.lsp or {}) or {})
end, diagnostics)
end
@@ -144,10 +137,10 @@ local _client_namespaces = {}
---
---@param client_id number The id of the LSP client
function M.get_namespace(client_id)
- vim.validate { client_id = { client_id, 'n' } }
+ vim.validate({ client_id = { client_id, 'n' } })
if not _client_namespaces[client_id] then
local client = vim.lsp.get_client_by_id(client_id)
- local name = string.format("vim.lsp.%s.%d", client and client.name or "unknown", client_id)
+ local name = string.format('vim.lsp.%s.%d', client and client.name or 'unknown', client_id)
_client_namespaces[client_id] = vim.api.nvim_create_namespace(name)
end
return _client_namespaces[client_id]
@@ -168,8 +161,8 @@ end
--- },
--- -- Use a function to dynamically turn signs off
--- -- and on, using buffer local variables
---- signs = function(bufnr, client_id)
---- return vim.bo[bufnr].show_signs == false
+--- signs = function(namespace, bufnr)
+--- return vim.b[bufnr].show_signs == true
--- end,
--- -- Disable a feature
--- update_in_insert = false,
@@ -181,7 +174,12 @@ end
function M.on_publish_diagnostics(_, result, ctx, config)
local client_id = ctx.client_id
local uri = result.uri
- local bufnr = vim.uri_to_bufnr(uri)
+ local fname = vim.uri_to_fname(uri)
+ local diagnostics = result.diagnostics
+ if #diagnostics == 0 and vim.fn.bufexists(fname) == 0 then
+ return
+ end
+ local bufnr = vim.fn.bufadd(fname)
if not bufnr then
return
@@ -189,13 +187,12 @@ function M.on_publish_diagnostics(_, result, ctx, config)
client_id = get_client_id(client_id)
local namespace = M.get_namespace(client_id)
- local diagnostics = result.diagnostics
if config then
for _, opt in pairs(config) do
if type(opt) == 'table' then
if not opt.severity and opt.severity_limit then
- opt.severity = {min=severity_lsp_to_vim(opt.severity_limit)}
+ opt.severity = { min = severity_lsp_to_vim(opt.severity_limit) }
end
end
end
@@ -230,72 +227,6 @@ function M.reset(client_id, buffer_client_map)
end)
end
--- Deprecated Functions {{{
-
-
---- Save diagnostics to the current buffer.
----
----@deprecated Prefer |vim.diagnostic.set()|
----
---- Handles saving diagnostics from multiple clients in the same buffer.
----@param diagnostics Diagnostic[]
----@param bufnr number
----@param client_id number
----@private
-function M.save(diagnostics, bufnr, client_id)
- vim.notify_once('vim.lsp.diagnostic.save is deprecated. See :h deprecated', vim.log.levels.WARN)
- local namespace = M.get_namespace(client_id)
- vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
-end
--- }}}
-
---- Get all diagnostics for clients
----
----@deprecated Prefer |vim.diagnostic.get()|
----
----@param client_id number Restrict included diagnostics to the client
---- If nil, diagnostics of all clients are included.
----@return table with diagnostics grouped by bufnr (bufnr: Diagnostic[])
-function M.get_all(client_id)
- vim.notify_once('vim.lsp.diagnostic.get_all is deprecated. See :h deprecated', vim.log.levels.WARN)
- local result = {}
- local namespace
- if client_id then
- namespace = M.get_namespace(client_id)
- end
- for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
- local diagnostics = diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, {namespace = namespace}))
- result[bufnr] = diagnostics
- end
- return result
-end
-
---- Return associated diagnostics for bufnr
----
----@deprecated Prefer |vim.diagnostic.get()|
----
----@param bufnr number
----@param client_id number|nil If nil, then return all of the diagnostics.
---- Else, return just the diagnostics associated with the client_id.
----@param predicate function|nil Optional function for filtering diagnostics
-function M.get(bufnr, client_id, predicate)
- vim.notify_once('vim.lsp.diagnostic.get is deprecated. See :h deprecated', vim.log.levels.WARN)
- predicate = predicate or function() return true end
- if client_id == nil then
- local all_diagnostics = {}
- vim.lsp.for_each_buffer_client(bufnr, function(_, iter_client_id, _)
- local iter_diagnostics = vim.tbl_filter(predicate, M.get(bufnr, iter_client_id))
- for _, diagnostic in ipairs(iter_diagnostics) do
- table.insert(all_diagnostics, diagnostic)
- end
- end)
- return all_diagnostics
- end
-
- local namespace = M.get_namespace(client_id)
- return diagnostic_vim_to_lsp(vim.tbl_filter(predicate, vim.diagnostic.get(bufnr, {namespace=namespace})))
-end
-
--- Get the diagnostics by line
---
--- Marked private as this is used internally by the LSP subsystem, but
@@ -317,7 +248,7 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
if opts.severity then
opts.severity = severity_lsp_to_vim(opts.severity)
elseif opts.severity_limit then
- opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
+ opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
end
if client_id then
@@ -333,390 +264,4 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
return diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, opts))
end
---- Get the counts for a particular severity
----
----@deprecated Prefer |vim.diagnostic.get_count()|
----
----@param bufnr number The buffer number
----@param severity DiagnosticSeverity
----@param client_id number the client id
-function M.get_count(bufnr, severity, client_id)
- vim.notify_once('vim.lsp.diagnostic.get_count is deprecated. See :h deprecated', vim.log.levels.WARN)
- severity = severity_lsp_to_vim(severity)
- local opts = { severity = severity }
- if client_id ~= nil then
- opts.namespace = M.get_namespace(client_id)
- end
-
- return #vim.diagnostic.get(bufnr, opts)
-end
-
---- Get the previous diagnostic closest to the cursor_position
----
----@deprecated Prefer |vim.diagnostic.get_prev()|
----
----@param opts table See |vim.lsp.diagnostic.goto_next()|
----@return table Previous diagnostic
-function M.get_prev(opts)
- vim.notify_once('vim.lsp.diagnostic.get_prev is deprecated. See :h deprecated', vim.log.levels.WARN)
- if opts then
- if opts.severity then
- opts.severity = severity_lsp_to_vim(opts.severity)
- elseif opts.severity_limit then
- opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
- end
- end
- return diagnostic_vim_to_lsp({vim.diagnostic.get_prev(opts)})[1]
-end
-
---- Return the pos, {row, col}, for the prev diagnostic in the current buffer.
----
----@deprecated Prefer |vim.diagnostic.get_prev_pos()|
----
----@param opts table See |vim.lsp.diagnostic.goto_next()|
----@return table Previous diagnostic position
-function M.get_prev_pos(opts)
- vim.notify_once('vim.lsp.diagnostic.get_prev_pos is deprecated. See :h deprecated', vim.log.levels.WARN)
- if opts then
- if opts.severity then
- opts.severity = severity_lsp_to_vim(opts.severity)
- elseif opts.severity_limit then
- opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
- end
- end
- return vim.diagnostic.get_prev_pos(opts)
-end
-
---- Move to the previous diagnostic
----
----@deprecated Prefer |vim.diagnostic.goto_prev()|
----
----@param opts table See |vim.lsp.diagnostic.goto_next()|
-function M.goto_prev(opts)
- vim.notify_once('vim.lsp.diagnostic.goto_prev is deprecated. See :h deprecated', vim.log.levels.WARN)
- if opts then
- if opts.severity then
- opts.severity = severity_lsp_to_vim(opts.severity)
- elseif opts.severity_limit then
- opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
- end
- end
- return vim.diagnostic.goto_prev(opts)
-end
-
---- Get the next diagnostic closest to the cursor_position
----
----@deprecated Prefer |vim.diagnostic.get_next()|
----
----@param opts table See |vim.lsp.diagnostic.goto_next()|
----@return table Next diagnostic
-function M.get_next(opts)
- vim.notify_once('vim.lsp.diagnostic.get_next is deprecated. See :h deprecated', vim.log.levels.WARN)
- if opts then
- if opts.severity then
- opts.severity = severity_lsp_to_vim(opts.severity)
- elseif opts.severity_limit then
- opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
- end
- end
- return diagnostic_vim_to_lsp({vim.diagnostic.get_next(opts)})[1]
-end
-
---- Return the pos, {row, col}, for the next diagnostic in the current buffer.
----
----@deprecated Prefer |vim.diagnostic.get_next_pos()|
----
----@param opts table See |vim.lsp.diagnostic.goto_next()|
----@return table Next diagnostic position
-function M.get_next_pos(opts)
- vim.notify_once('vim.lsp.diagnostic.get_next_pos is deprecated. See :h deprecated', vim.log.levels.WARN)
- if opts then
- if opts.severity then
- opts.severity = severity_lsp_to_vim(opts.severity)
- elseif opts.severity_limit then
- opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
- end
- end
- return vim.diagnostic.get_next_pos(opts)
-end
-
---- Move to the next diagnostic
----
----@deprecated Prefer |vim.diagnostic.goto_next()|
-function M.goto_next(opts)
- vim.notify_once('vim.lsp.diagnostic.goto_next is deprecated. See :h deprecated', vim.log.levels.WARN)
- if opts then
- if opts.severity then
- opts.severity = severity_lsp_to_vim(opts.severity)
- elseif opts.severity_limit then
- opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
- end
- end
- return vim.diagnostic.goto_next(opts)
-end
-
---- Set signs for given diagnostics
----
----@deprecated Prefer |vim.diagnostic._set_signs()|
----
----@param diagnostics Diagnostic[]
----@param bufnr number The buffer number
----@param client_id number the client id
----@param sign_ns number|nil
----@param opts table Configuration for signs. Keys:
---- - priority: Set the priority of the signs.
---- - severity_limit (DiagnosticSeverity):
---- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
-function M.set_signs(diagnostics, bufnr, client_id, _, opts)
- vim.notify_once('vim.lsp.diagnostic.set_signs is deprecated. See :h deprecated', vim.log.levels.WARN)
- local namespace = M.get_namespace(client_id)
- if opts and not opts.severity and opts.severity_limit then
- opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
- end
-
- vim.diagnostic._set_signs(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts)
-end
-
---- Set underline for given diagnostics
----
----@deprecated Prefer |vim.diagnostic._set_underline()|
----
----@param diagnostics Diagnostic[]
----@param bufnr number: The buffer number
----@param client_id number: The client id
----@param diagnostic_ns number|nil: The namespace
----@param opts table: Configuration table:
---- - severity_limit (DiagnosticSeverity):
---- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
-function M.set_underline(diagnostics, bufnr, client_id, _, opts)
- vim.notify_once('vim.lsp.diagnostic.set_underline is deprecated. See :h deprecated', vim.log.levels.WARN)
- local namespace = M.get_namespace(client_id)
- if opts and not opts.severity and opts.severity_limit then
- opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
- end
- return vim.diagnostic._set_underline(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts)
-end
-
---- Set virtual text given diagnostics
----
----@deprecated Prefer |vim.diagnostic._set_virtual_text()|
----
----@param diagnostics Diagnostic[]
----@param bufnr number
----@param client_id number
----@param diagnostic_ns number
----@param opts table Options on how to display virtual text. Keys:
---- - prefix (string): Prefix to display before virtual text on line
---- - spacing (number): Number of spaces to insert before virtual text
---- - severity_limit (DiagnosticSeverity):
---- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
-function M.set_virtual_text(diagnostics, bufnr, client_id, _, opts)
- vim.notify_once('vim.lsp.diagnostic.set_virtual_text is deprecated. See :h deprecated', vim.log.levels.WARN)
- local namespace = M.get_namespace(client_id)
- if opts and not opts.severity and opts.severity_limit then
- opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
- end
- return vim.diagnostic._set_virtual_text(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id), opts)
-end
-
---- Default function to get text chunks to display using |nvim_buf_set_extmark()|.
----
----@deprecated Prefer |vim.diagnostic.get_virt_text_chunks()|
----
----@param bufnr number The buffer to display the virtual text in
----@param line number The line number to display the virtual text on
----@param line_diags Diagnostic[] The diagnostics associated with the line
----@param opts table See {opts} from |vim.lsp.diagnostic.set_virtual_text()|
----@return an array of [text, hl_group] arrays. This can be passed directly to
---- the {virt_text} option of |nvim_buf_set_extmark()|.
-function M.get_virtual_text_chunks_for_line(bufnr, _, line_diags, opts)
- vim.notify_once('vim.lsp.diagnostic.get_virtual_text_chunks_for_line is deprecated. See :h deprecated', vim.log.levels.WARN)
- return vim.diagnostic._get_virt_text_chunks(diagnostic_lsp_to_vim(line_diags, bufnr), opts)
-end
-
---- Open a floating window with the diagnostics from {position}
----
----@deprecated Prefer |vim.diagnostic.show_position_diagnostics()|
----
----@param opts table|nil Configuration keys
---- - severity: (DiagnosticSeverity, default nil)
---- - Only return diagnostics with this severity. Overrides severity_limit
---- - severity_limit: (DiagnosticSeverity, default nil)
---- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
---- - all opts for |show_diagnostics()| can be used here
----@param buf_nr number|nil The buffer number
----@param position table|nil The (0,0)-indexed position
----@return table {popup_bufnr, win_id}
-function M.show_position_diagnostics(opts, buf_nr, position)
- vim.notify_once('vim.lsp.diagnostic.show_position_diagnostics is deprecated. See :h deprecated', vim.log.levels.WARN)
- opts = opts or {}
- opts.scope = "cursor"
- opts.pos = position
- if opts.severity then
- opts.severity = severity_lsp_to_vim(opts.severity)
- elseif opts.severity_limit then
- opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
- end
- return vim.diagnostic.open_float(buf_nr, opts)
-end
-
---- Open a floating window with the diagnostics from {line_nr}
----
----@deprecated Prefer |vim.diagnostic.open_float()|
----
----@param opts table Configuration table
---- - all opts for |vim.lsp.diagnostic.get_line_diagnostics()| and
---- |show_diagnostics()| can be used here
----@param buf_nr number|nil The buffer number
----@param line_nr number|nil The line number
----@param client_id number|nil the client id
----@return table {popup_bufnr, win_id}
-function M.show_line_diagnostics(opts, buf_nr, line_nr, client_id)
- vim.notify_once('vim.lsp.diagnostic.show_line_diagnostics is deprecated. See :h deprecated', vim.log.levels.WARN)
- opts = opts or {}
- opts.scope = "line"
- opts.pos = line_nr
- if client_id then
- opts.namespace = M.get_namespace(client_id)
- end
- return vim.diagnostic.open_float(buf_nr, opts)
-end
-
---- Redraw diagnostics for the given buffer and client
----
----@deprecated Prefer |vim.diagnostic.show()|
----
---- This calls the "textDocument/publishDiagnostics" handler manually using
---- the cached diagnostics already received from the server. This can be useful
---- for redrawing diagnostics after making changes in diagnostics
---- configuration. |lsp-handler-configuration|
----
----@param bufnr (optional, number): Buffer handle, defaults to current
----@param client_id (optional, number): Redraw diagnostics for the given
---- client. The default is to redraw diagnostics for all attached
---- clients.
-function M.redraw(bufnr, client_id)
- vim.notify_once('vim.lsp.diagnostic.redraw is deprecated. See :h deprecated', vim.log.levels.WARN)
- bufnr = get_bufnr(bufnr)
- if not client_id then
- return vim.lsp.for_each_buffer_client(bufnr, function(client)
- M.redraw(bufnr, client.id)
- end)
- end
-
- local namespace = M.get_namespace(client_id)
- return vim.diagnostic.show(namespace, bufnr)
-end
-
---- Sets the quickfix list
----
----@deprecated Prefer |vim.diagnostic.setqflist()|
----
----@param opts table|nil Configuration table. Keys:
---- - {open}: (boolean, default true)
---- - Open quickfix list after set
---- - {client_id}: (number)
---- - If nil, will consider all clients attached to buffer.
---- - {severity}: (DiagnosticSeverity)
---- - Exclusive severity to consider. Overrides {severity_limit}
---- - {severity_limit}: (DiagnosticSeverity)
---- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
---- - {workspace}: (boolean, default true)
---- - Set the list with workspace diagnostics
-function M.set_qflist(opts)
- vim.notify_once('vim.lsp.diagnostic.set_qflist is deprecated. See :h deprecated', vim.log.levels.WARN)
- opts = opts or {}
- if opts.severity then
- opts.severity = severity_lsp_to_vim(opts.severity)
- elseif opts.severity_limit then
- opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
- end
- if opts.client_id then
- opts.client_id = nil
- opts.namespace = M.get_namespace(opts.client_id)
- end
- local workspace = vim.F.if_nil(opts.workspace, true)
- opts.bufnr = not workspace and 0
- return vim.diagnostic.setqflist(opts)
-end
-
---- Sets the location list
----
----@deprecated Prefer |vim.diagnostic.setloclist()|
----
----@param opts table|nil Configuration table. Keys:
---- - {open}: (boolean, default true)
---- - Open loclist after set
---- - {client_id}: (number)
---- - If nil, will consider all clients attached to buffer.
---- - {severity}: (DiagnosticSeverity)
---- - Exclusive severity to consider. Overrides {severity_limit}
---- - {severity_limit}: (DiagnosticSeverity)
---- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
---- - {workspace}: (boolean, default false)
---- - Set the list with workspace diagnostics
-function M.set_loclist(opts)
- vim.notify_once('vim.lsp.diagnostic.set_loclist is deprecated. See :h deprecated', vim.log.levels.WARN)
- opts = opts or {}
- if opts.severity then
- opts.severity = severity_lsp_to_vim(opts.severity)
- elseif opts.severity_limit then
- opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)}
- end
- if opts.client_id then
- opts.client_id = nil
- opts.namespace = M.get_namespace(opts.client_id)
- end
- local workspace = vim.F.if_nil(opts.workspace, false)
- opts.bufnr = not workspace and 0
- return vim.diagnostic.setloclist(opts)
-end
-
---- Disable diagnostics for the given buffer and client
----
----@deprecated Prefer |vim.diagnostic.disable()|
----
----@param bufnr (optional, number): Buffer handle, defaults to current
----@param client_id (optional, number): Disable diagnostics for the given
---- client. The default is to disable diagnostics for all attached
---- clients.
--- Note that when diagnostics are disabled for a buffer, the server will still
--- send diagnostic information and the client will still process it. The
--- diagnostics are simply not displayed to the user.
-function M.disable(bufnr, client_id)
- vim.notify_once('vim.lsp.diagnostic.disable is deprecated. See :h deprecated', vim.log.levels.WARN)
- if not client_id then
- return vim.lsp.for_each_buffer_client(bufnr, function(client)
- M.disable(bufnr, client.id)
- end)
- end
-
- bufnr = get_bufnr(bufnr)
- local namespace = M.get_namespace(client_id)
- return vim.diagnostic.disable(bufnr, namespace)
-end
-
---- Enable diagnostics for the given buffer and client
----
----@deprecated Prefer |vim.diagnostic.enable()|
----
----@param bufnr (optional, number): Buffer handle, defaults to current
----@param client_id (optional, number): Enable diagnostics for the given
---- client. The default is to enable diagnostics for all attached
---- clients.
-function M.enable(bufnr, client_id)
- vim.notify_once('vim.lsp.diagnostic.enable is deprecated. See :h deprecated', vim.log.levels.WARN)
- if not client_id then
- return vim.lsp.for_each_buffer_client(bufnr, function(client)
- M.enable(bufnr, client.id)
- end)
- end
-
- bufnr = get_bufnr(bufnr)
- local namespace = M.get_namespace(client_id)
- return vim.diagnostic.enable(bufnr, namespace)
-end
-
--- }}}
-
return M
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index a48302cc4b..3b869d8f5c 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -1,6 +1,6 @@
-local log = require 'vim.lsp.log'
-local protocol = require 'vim.lsp.protocol'
-local util = require 'vim.lsp.util'
+local log = require('vim.lsp.log')
+local protocol = require('vim.lsp.protocol')
+local util = require('vim.lsp.util')
local vim = vim
local api = vim.api
@@ -12,8 +12,8 @@ local M = {}
--- Writes to error buffer.
---@param ... (table of strings) Will be concatenated before being written
local function err_message(...)
- vim.notify(table.concat(vim.tbl_flatten{...}), vim.log.levels.ERROR)
- api.nvim_command("redraw")
+ vim.notify(table.concat(vim.tbl_flatten({ ... })), vim.log.levels.ERROR)
+ api.nvim_command('redraw')
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
@@ -25,51 +25,56 @@ end
local function progress_handler(_, result, ctx, _)
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
- local client_name = client and client.name or string.format("id=%d", client_id)
+ local client_name = client and client.name or string.format('id=%d', client_id)
if not client then
- err_message("LSP[", client_name, "] client has shut down after sending the message")
+ err_message('LSP[', client_name, '] client has shut down during progress update')
return vim.NIL
end
- local val = result.value -- unspecified yet
- local token = result.token -- string or number
-
+ local val = result.value -- unspecified yet
+ local token = result.token -- string or number
+ if type(val) ~= 'table' then
+ val = { content = val }
+ end
if val.kind then
if val.kind == 'begin' then
client.messages.progress[token] = {
title = val.title,
+ cancellable = val.cancellable,
message = val.message,
percentage = val.percentage,
}
elseif val.kind == 'report' then
- client.messages.progress[token].message = val.message;
- client.messages.progress[token].percentage = val.percentage;
+ client.messages.progress[token].cancellable = val.cancellable
+ client.messages.progress[token].message = val.message
+ client.messages.progress[token].percentage = val.percentage
elseif val.kind == 'end' then
if client.messages.progress[token] == nil then
- err_message("LSP[", client_name, "] received `end` message with no corresponding `begin`")
+ err_message('LSP[', client_name, '] received `end` message with no corresponding `begin`')
else
client.messages.progress[token].message = val.message
client.messages.progress[token].done = true
end
end
else
- table.insert(client.messages, {content = val, show_once = true, shown = 0})
+ client.messages.progress[token] = val
+ client.messages.progress[token].done = true
end
- vim.api.nvim_command("doautocmd <nomodeline> User LspProgressUpdate")
+ api.nvim_exec_autocmds('User', { pattern = 'LspProgressUpdate', modeline = false })
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
M['$/progress'] = progress_handler
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
-M['window/workDoneProgress/create'] = function(_, result, ctx)
+M['window/workDoneProgress/create'] = function(_, result, ctx)
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
- local token = result.token -- string or number
- local client_name = client and client.name or string.format("id=%d", client_id)
+ local token = result.token -- string or number
+ local client_name = client and client.name or string.format('id=%d', client_id)
if not client then
- err_message("LSP[", client_name, "] client has shut down after sending the message")
+ err_message('LSP[', client_name, '] client has shut down while creating progress report')
return vim.NIL
end
client.messages.progress[token] = {}
@@ -78,20 +83,19 @@ end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
M['window/showMessageRequest'] = function(_, result)
-
local actions = result.actions
print(result.message)
- local option_strings = {result.message, "\nRequest Actions:"}
+ local option_strings = { result.message, '\nRequest Actions:' }
for i, action in ipairs(actions) do
local title = action.title:gsub('\r\n', '\\r\\n')
title = title:gsub('\n', '\\n')
- table.insert(option_strings, string.format("%d. %s", i, title))
+ table.insert(option_strings, string.format('%d. %s', i, title))
end
-- window/showMessageRequest can return either MessageActionItem[] or null.
local choice = vim.fn.inputlist(option_strings)
if choice < 1 or choice > #actions then
- return vim.NIL
+ return vim.NIL
else
return actions[choice]
end
@@ -100,27 +104,32 @@ end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
M['client/registerCapability'] = function(_, _, ctx)
local client_id = ctx.client_id
- local warning_tpl = "The language server %s triggers a registerCapability "..
- "handler despite dynamicRegistration set to false. "..
- "Report upstream, this warning is harmless"
+ local warning_tpl = 'The language server %s triggers a registerCapability '
+ .. 'handler despite dynamicRegistration set to false. '
+ .. 'Report upstream, this warning is harmless'
local client = vim.lsp.get_client_by_id(client_id)
- local client_name = client and client.name or string.format("id=%d", client_id)
+ local client_name = client and client.name or string.format('id=%d', client_id)
local warning = string.format(warning_tpl, client_name)
log.warn(warning)
return vim.NIL
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
-M['workspace/applyEdit'] = function(_, workspace_edit)
- if not workspace_edit then return end
+M['workspace/applyEdit'] = function(_, workspace_edit, ctx)
+ if not workspace_edit then
+ return
+ end
-- TODO(ashkan) Do something more with label?
+ local client_id = ctx.client_id
+ local client = vim.lsp.get_client_by_id(client_id)
if workspace_edit.label then
- print("Workspace edit", workspace_edit.label)
+ print('Workspace edit', workspace_edit.label)
end
- local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit)
+ local status, result =
+ pcall(util.apply_workspace_edit, workspace_edit.edit, client.offset_encoding)
return {
- applied = status;
- failureReason = result;
+ applied = status,
+ failureReason = result,
}
end
@@ -129,7 +138,11 @@ M['workspace/configuration'] = function(_, result, ctx)
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
if not client then
- err_message("LSP[id=", client_id, "] client has shut down after sending the message")
+ err_message(
+ 'LSP[',
+ client_id,
+ '] client has shut down after sending a workspace/configuration request'
+ )
return
end
if not result.items then
@@ -139,7 +152,7 @@ M['workspace/configuration'] = function(_, result, ctx)
local response = {}
for _, item in ipairs(result.items) do
if item.section then
- local value = util.lookup_section(client.config.settings, item.section) or vim.NIL
+ local value = util.lookup_section(client.config.settings, item.section)
-- For empty sections with no explicit '' key, return settings as is
if value == vim.NIL and item.section == '' then
value = client.config.settings or vim.NIL
@@ -150,6 +163,17 @@ M['workspace/configuration'] = function(_, result, ctx)
return response
end
+--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_workspaceFolders
+M['workspace/workspaceFolders'] = function(_, _, ctx)
+ local client_id = ctx.client_id
+ local client = vim.lsp.get_client_by_id(client_id)
+ if not client then
+ err_message('LSP[id=', client_id, '] client has shut down after sending the message')
+ return
+ end
+ return client.workspace_folders or vim.NIL
+end
+
M['textDocument/publishDiagnostics'] = function(...)
return require('vim.lsp.diagnostic').on_publish_diagnostics(...)
end
@@ -158,7 +182,30 @@ M['textDocument/codeLens'] = function(...)
return require('vim.lsp.codelens').on_codelens(...)
end
-
+--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
+M['textDocument/references'] = function(_, result, ctx, config)
+ if not result or vim.tbl_isempty(result) then
+ vim.notify('No references found')
+ else
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
+ config = config or {}
+ if config.loclist then
+ vim.fn.setloclist(0, {}, ' ', {
+ title = 'References',
+ items = util.locations_to_items(result, client.offset_encoding),
+ context = ctx,
+ })
+ api.nvim_command('lopen')
+ else
+ vim.fn.setqflist({}, ' ', {
+ title = 'References',
+ items = util.locations_to_items(result, client.offset_encoding),
+ context = ctx,
+ })
+ api.nvim_command('botright copen')
+ end
+ end
+end
---@private
--- Return a function that converts LSP responses to list items and opens the list
@@ -169,69 +216,88 @@ end
--- loclist: (boolean) use the location list (default is to use the quickfix list)
---
---@param map_result function `((resp, bufnr) -> list)` to convert the response
----@param entity name of the resource used in a `not found` error message
-local function response_to_list(map_result, entity)
- return function(_,result, ctx, config)
+---@param entity string name of the resource used in a `not found` error message
+---@param title_fn function Function to call to generate list title
+local function response_to_list(map_result, entity, title_fn)
+ return function(_, result, ctx, config)
if not result or vim.tbl_isempty(result) then
vim.notify('No ' .. entity .. ' found')
else
config = config or {}
if config.loclist then
vim.fn.setloclist(0, {}, ' ', {
- title = 'Language Server';
- items = map_result(result, ctx.bufnr);
+ title = title_fn(ctx),
+ items = map_result(result, ctx.bufnr),
+ context = ctx,
})
- api.nvim_command("lopen")
+ api.nvim_command('lopen')
else
vim.fn.setqflist({}, ' ', {
- title = 'Language Server';
- items = map_result(result, ctx.bufnr);
+ title = title_fn(ctx),
+ items = map_result(result, ctx.bufnr),
+ context = ctx,
})
- api.nvim_command("botright copen")
+ api.nvim_command('botright copen')
end
end
end
end
-
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
-M['textDocument/references'] = response_to_list(util.locations_to_items, 'references')
-
--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')
+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), ':.')
+ return string.format('Symbols in %s', fname)
+ end
+)
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
-M['workspace/symbol'] = response_to_list(util.symbols_to_items, 'symbols')
+M['workspace/symbol'] = response_to_list(util.symbols_to_items, 'symbols', function(ctx)
+ return string.format("Symbols matching '%s'", ctx.params.query)
+end)
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
-M['textDocument/rename'] = function(_, result, _)
- if not result then return end
- util.apply_workspace_edit(result)
+M['textDocument/rename'] = function(_, result, ctx, _)
+ if not result then
+ return
+ end
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
+ util.apply_workspace_edit(result, client.offset_encoding)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
M['textDocument/rangeFormatting'] = function(_, result, ctx, _)
- if not result then return end
- util.apply_text_edits(result, ctx.bufnr)
+ if not result then
+ return
+ end
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
+ util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
M['textDocument/formatting'] = function(_, result, ctx, _)
- if not result then return end
- util.apply_text_edits(result, ctx.bufnr)
+ if not result then
+ return
+ end
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
+ util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
M['textDocument/completion'] = function(_, result, _, _)
- if vim.tbl_isempty(result or {}) then return end
+ if vim.tbl_isempty(result or {}) then
+ return
+ end
local row, col = unpack(api.nvim_win_get_cursor(0))
- local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1])
- local line_to_cursor = line:sub(col+1)
+ local line = assert(api.nvim_buf_get_lines(0, row - 1, row, false)[1])
+ local line_to_cursor = line:sub(col + 1)
local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
- local prefix = line_to_cursor:sub(textMatch+1)
+ local prefix = line_to_cursor:sub(textMatch + 1)
local matches = util.text_document_completion_list_to_complete_items(result, prefix)
- vim.fn.complete(textMatch+1, matches)
+ vim.fn.complete(textMatch + 1, matches)
end
--- |lsp-handler| for the method "textDocument/hover"
@@ -251,16 +317,16 @@ function M.hover(_, result, ctx, config)
config = config or {}
config.focus_id = ctx.method
if not (result and result.contents) then
- -- return { 'No information available' }
+ vim.notify('No information available')
return
end
local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
markdown_lines = util.trim_empty_lines(markdown_lines)
if vim.tbl_isempty(markdown_lines) then
- -- return { 'No information available' }
+ vim.notify('No information available')
return
end
- return util.open_floating_preview(markdown_lines, "markdown", config)
+ return util.open_floating_preview(markdown_lines, 'markdown', config)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
@@ -272,24 +338,30 @@ M['textDocument/hover'] = M.hover
---@param result (table) result of LSP method; a location or a list of locations.
---@param ctx (table) table containing the context of the request, including the method
---(`textDocument/definition` can return `Location` or `Location[]`
-local function location_handler(_, result, ctx, _)
+local function location_handler(_, result, ctx, config)
if result == nil or vim.tbl_isempty(result) then
local _ = log.info() and log.info(ctx.method, 'No location found')
return nil
end
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
+
+ config = config or {}
-- textDocument/definition can return Location or Location[]
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
if vim.tbl_islist(result) then
- util.jump_to_location(result[1])
+ util.jump_to_location(result[1], client.offset_encoding, config.reuse_win)
if #result > 1 then
- vim.fn.setqflist({}, ' ', {title = 'LSP locations', items = util.locations_to_items(result)})
- api.nvim_command("copen")
+ vim.fn.setqflist({}, ' ', {
+ title = 'LSP locations',
+ items = util.locations_to_items(result, client.offset_encoding),
+ })
+ api.nvim_command('botright copen')
end
else
- util.jump_to_location(result)
+ util.jump_to_location(result, client.offset_encoding, config.reuse_win)
end
end
@@ -328,7 +400,8 @@ function M.signature_help(_, result, ctx, config)
return
end
local client = vim.lsp.get_client_by_id(ctx.client_id)
- local triggers = client.resolved_capabilities.signature_help_trigger_characters
+ local triggers =
+ vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
local ft = api.nvim_buf_get_option(ctx.bufnr, 'filetype')
local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers)
lines = util.trim_empty_lines(lines)
@@ -338,9 +411,9 @@ function M.signature_help(_, result, ctx, config)
end
return
end
- local fbuf, fwin = util.open_floating_preview(lines, "markdown", config)
+ local fbuf, fwin = util.open_floating_preview(lines, 'markdown', config)
if hl then
- api.nvim_buf_add_highlight(fbuf, -1, "LspSignatureActiveParameter", 0, unpack(hl))
+ api.nvim_buf_add_highlight(fbuf, -1, 'LspSignatureActiveParameter', 0, unpack(hl))
end
return fbuf, fwin
end
@@ -350,10 +423,14 @@ M['textDocument/signatureHelp'] = M.signature_help
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
M['textDocument/documentHighlight'] = function(_, result, ctx, _)
- if not result then return end
+ if not result then
+ return
+ end
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
- if not client then return end
+ if not client then
+ return
+ end
util.buf_highlight_references(ctx.bufnr, result, client.offset_encoding)
end
@@ -366,7 +443,9 @@ end
---@returns `CallHierarchyOutgoingCall[]` if {direction} is `"to"`,
local make_call_hierarchy_handler = function(direction)
return function(_, result)
- if not result then return end
+ if not result then
+ return
+ end
local items = {}
for _, call_hierarchy_call in pairs(result) do
local call_hierarchy_item = call_hierarchy_call[direction]
@@ -379,8 +458,8 @@ local make_call_hierarchy_handler = function(direction)
})
end
end
- vim.fn.setqflist({}, ' ', {title = 'LSP call hierarchy', items = items})
- api.nvim_command("copen")
+ vim.fn.setqflist({}, ' ', { title = 'LSP call hierarchy', items = items })
+ api.nvim_command('botright copen')
end
end
@@ -396,15 +475,15 @@ M['window/logMessage'] = function(_, result, ctx, _)
local message = result.message
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
- local client_name = client and client.name or string.format("id=%d", client_id)
+ local client_name = client and client.name or string.format('id=%d', client_id)
if not client then
- err_message("LSP[", client_name, "] client has shut down after sending the message")
+ err_message('LSP[', client_name, '] client has shut down after sending ', message)
end
if message_type == protocol.MessageType.Error then
log.error(message)
elseif message_type == protocol.MessageType.Warning then
log.warn(message)
- elseif message_type == protocol.MessageType.Info or message_type == protocol.MessageType.Log then
+ elseif message_type == protocol.MessageType.Info or message_type == protocol.MessageType.Log then
log.info(message)
else
log.debug(message)
@@ -418,15 +497,15 @@ M['window/showMessage'] = function(_, result, ctx, _)
local message = result.message
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
- local client_name = client and client.name or string.format("id=%d", client_id)
+ local client_name = client and client.name or string.format('id=%d', client_id)
if not client then
- err_message("LSP[", client_name, "] client has shut down after sending the message")
+ err_message('LSP[', client_name, '] client has shut down after sending ', message)
end
if message_type == protocol.MessageType.Error then
- err_message("LSP[", client_name, "] ", message)
+ err_message('LSP[', client_name, '] ', message)
else
local message_type_name = protocol.MessageType[message_type]
- api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message))
+ api.nvim_out_write(string.format('LSP[%s][%s] %s\n', client_name, message_type_name, message))
end
return result
end
@@ -434,9 +513,13 @@ end
-- Add boilerplate error validation and logging for all of these.
for k, fn in pairs(M) do
M[k] = function(err, result, ctx, config)
- local _ = log.trace() and log.trace('default_handler', ctx.method, {
- err = err, result = result, ctx=vim.inspect(ctx), config = config
- })
+ local _ = log.trace()
+ and log.trace('default_handler', ctx.method, {
+ err = err,
+ result = result,
+ ctx = vim.inspect(ctx),
+ config = config,
+ })
if err then
-- LSP spec:
@@ -448,7 +531,7 @@ for k, fn in pairs(M) do
-- Per LSP, don't show ContentModified error to the user.
if err.code ~= protocol.ErrorCodes.ContentModified then
local client = vim.lsp.get_client_by_id(ctx.client_id)
- local client_name = client and client.name or string.format("client_id=%d", ctx.client_id)
+ local client_name = client and client.name or string.format('client_id=%d', ctx.client_id)
err_message(client_name .. ': ' .. tostring(err.code) .. ': ' .. err.message)
end
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index ed3eea59df..ba730e3d6d 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -8,20 +8,25 @@ function M.check()
local log = require('vim.lsp.log')
local current_log_level = log.get_level()
local log_level_string = log.levels[current_log_level]
- report_info(string.format("LSP log level : %s", log_level_string))
+ report_info(string.format('LSP log level : %s', log_level_string))
if current_log_level < log.levels.WARN then
- report_warn(string.format("Log level %s will cause degraded performance and high disk usage", log_level_string))
+ report_warn(
+ string.format(
+ 'Log level %s will cause degraded performance and high disk usage',
+ log_level_string
+ )
+ )
end
local log_path = vim.lsp.get_log_path()
- report_info(string.format("Log path: %s", log_path))
+ report_info(string.format('Log path: %s', log_path))
- local log_size = vim.loop.fs_stat(log_path).size
+ local log_file = vim.loop.fs_stat(log_path)
+ local log_size = log_file and log_file.size or 0
local report_fn = (log_size / 1000000 > 100 and report_warn or report_info)
- report_fn(string.format("Log size: %d KB", log_size / 1000 ))
+ report_fn(string.format('Log size: %d KB', log_size / 1000))
end
return M
-
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index e0b5653587..6c6ba0f206 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -8,22 +8,29 @@ local log = {}
-- Log level dictionary with reverse lookup as well.
--
-- Can be used to lookup the number from the name or the name from the number.
--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR"
+-- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"
-- Level numbers begin with "TRACE" at 0
log.levels = vim.deepcopy(vim.log.levels)
-- Default log level is warn.
local current_log_level = log.levels.WARN
-local log_date_format = "%F %H:%M:%S"
-local format_func = function(arg) return vim.inspect(arg, {newline=''}) end
+local log_date_format = '%F %H:%M:%S'
+local format_func = function(arg)
+ return vim.inspect(arg, { newline = '' })
+end
do
- local path_sep = vim.loop.os_uname().version:match("Windows") and "\\" or "/"
+ local path_sep = vim.loop.os_uname().version:match('Windows') and '\\' or '/'
---@private
local function path_join(...)
- return table.concat(vim.tbl_flatten{...}, path_sep)
+ return table.concat(vim.tbl_flatten({ ... }), path_sep)
end
- local logfilename = path_join(vim.fn.stdpath('cache'), 'lsp.log')
+ local logfilename = path_join(vim.fn.stdpath('log'), 'lsp.log')
+
+ -- TODO: Ideally the directory should be created in open_logfile(), right
+ -- before opening the log file, but open_logfile() can be called from libuv
+ -- callbacks, where using fn.mkdir() is not allowed.
+ vim.fn.mkdir(vim.fn.stdpath('log'), 'p')
--- Returns the log filename.
---@returns (string) log filename
@@ -31,21 +38,40 @@ do
return logfilename
end
- vim.fn.mkdir(vim.fn.stdpath('cache'), "p")
- local logfile = assert(io.open(logfilename, "a+"))
-
- local log_info = vim.loop.fs_stat(logfilename)
- if log_info and log_info.size > 1e9 then
- local warn_msg = string.format(
- "LSP client log is large (%d MB): %s",
- log_info.size / (1000 * 1000),
- logfilename
- )
- vim.notify(warn_msg)
+ local logfile, openerr
+ ---@private
+ --- Opens log file. Returns true if file is open, false on error
+ local function open_logfile()
+ -- Try to open file only once
+ if logfile then
+ return true
+ end
+ if openerr then
+ return false
+ end
+
+ logfile, openerr = io.open(logfilename, 'a+')
+ if not logfile then
+ local err_msg = string.format('Failed to open LSP client log file: %s', openerr)
+ vim.notify(err_msg, vim.log.levels.ERROR)
+ return false
+ end
+
+ local log_info = vim.loop.fs_stat(logfilename)
+ if log_info and log_info.size > 1e9 then
+ local warn_msg = string.format(
+ 'LSP client log is large (%d MB): %s',
+ log_info.size / (1000 * 1000),
+ logfilename
+ )
+ vim.notify(warn_msg)
+ end
+
+ -- Start message for logging
+ logfile:write(string.format('[START][%s] LSP logging initiated\n', os.date(log_date_format)))
+ return true
end
- -- Start message for logging
- logfile:write(string.format("[START][%s] LSP logging initiated\n", os.date(log_date_format)))
for level, levelnr in pairs(log.levels) do
-- Also export the log level on the root object.
log[level] = levelnr
@@ -63,23 +89,38 @@ do
-- ```
--
-- This way you can avoid string allocations if the log level isn't high enough.
- log[level:lower()] = function(...)
- local argc = select("#", ...)
- if levelnr < current_log_level then return false end
- if argc == 0 then return true end
- local info = debug.getinfo(2, "Sl")
- local header = string.format("[%s][%s] ...%s:%s", level, os.date(log_date_format), string.sub(info.short_src, #info.short_src - 15), info.currentline)
- local parts = { header }
- for i = 1, argc do
- local arg = select(i, ...)
- if arg == nil then
- table.insert(parts, "nil")
- else
- table.insert(parts, format_func(arg))
+ if level ~= 'OFF' then
+ log[level:lower()] = function(...)
+ local argc = select('#', ...)
+ if levelnr < current_log_level then
+ return false
+ end
+ if argc == 0 then
+ return true
+ end
+ if not open_logfile() then
+ return false
+ end
+ local info = debug.getinfo(2, 'Sl')
+ local header = string.format(
+ '[%s][%s] ...%s:%s',
+ level,
+ os.date(log_date_format),
+ string.sub(info.short_src, #info.short_src - 15),
+ info.currentline
+ )
+ local parts = { header }
+ for i = 1, argc do
+ local arg = select(i, ...)
+ if arg == nil then
+ table.insert(parts, 'nil')
+ else
+ table.insert(parts, format_func(arg))
+ end
end
+ logfile:write(table.concat(parts, '\t'), '\n')
+ logfile:flush()
end
- logfile:write(table.concat(parts, '\t'), "\n")
- logfile:flush()
end
end
end
@@ -92,10 +133,11 @@ vim.tbl_add_reverse_lookup(log.levels)
---@param level (string or number) One of `vim.lsp.log.levels`
function log.set_level(level)
if type(level) == 'string' then
- current_log_level = assert(log.levels[level:upper()], string.format("Invalid log level: %q", level))
+ current_log_level =
+ assert(log.levels[level:upper()], string.format('Invalid log level: %q', level))
else
- assert(type(level) == 'number', "level must be a number or string")
- assert(log.levels[level], string.format("Invalid log level: %d", level))
+ assert(type(level) == 'number', 'level must be a number or string')
+ assert(log.levels[level], string.format('Invalid log level: %d', level))
current_log_level = level
end
end
@@ -109,7 +151,7 @@ end
--- Sets formatting function used to format logs
---@param handle function function to apply to logging arguments, pass vim.inspect for multi-line formatting
function log.set_format_func(handle)
- assert(handle == vim.inspect or type(handle) == 'function', "handle must be a function")
+ assert(handle == vim.inspect or type(handle) == 'function', 'handle must be a function')
format_func = handle
end
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 86c9e2fd58..6ecb9959d5 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -1,7 +1,5 @@
-- Protocol for the Microsoft Language Server Protocol (mslsp)
-local if_nil = vim.F.if_nil
-
local protocol = {}
--[=[
@@ -25,150 +23,150 @@ end
local constants = {
DiagnosticSeverity = {
-- Reports an error.
- Error = 1;
+ Error = 1,
-- Reports a warning.
- Warning = 2;
+ Warning = 2,
-- Reports an information.
- Information = 3;
+ Information = 3,
-- Reports a hint.
- Hint = 4;
- };
+ Hint = 4,
+ },
DiagnosticTag = {
-- Unused or unnecessary code
- Unnecessary = 1;
+ Unnecessary = 1,
-- Deprecated or obsolete code
- Deprecated = 2;
- };
+ Deprecated = 2,
+ },
MessageType = {
-- An error message.
- Error = 1;
+ Error = 1,
-- A warning message.
- Warning = 2;
+ Warning = 2,
-- An information message.
- Info = 3;
+ Info = 3,
-- A log message.
- Log = 4;
- };
+ Log = 4,
+ },
-- The file event type.
FileChangeType = {
-- The file got created.
- Created = 1;
+ Created = 1,
-- The file got changed.
- Changed = 2;
+ Changed = 2,
-- The file got deleted.
- Deleted = 3;
- };
+ Deleted = 3,
+ },
-- The kind of a completion entry.
CompletionItemKind = {
- Text = 1;
- Method = 2;
- Function = 3;
- Constructor = 4;
- Field = 5;
- Variable = 6;
- Class = 7;
- Interface = 8;
- Module = 9;
- Property = 10;
- Unit = 11;
- Value = 12;
- Enum = 13;
- Keyword = 14;
- Snippet = 15;
- Color = 16;
- File = 17;
- Reference = 18;
- Folder = 19;
- EnumMember = 20;
- Constant = 21;
- Struct = 22;
- Event = 23;
- Operator = 24;
- TypeParameter = 25;
- };
+ Text = 1,
+ Method = 2,
+ Function = 3,
+ Constructor = 4,
+ Field = 5,
+ Variable = 6,
+ Class = 7,
+ Interface = 8,
+ Module = 9,
+ Property = 10,
+ Unit = 11,
+ Value = 12,
+ Enum = 13,
+ Keyword = 14,
+ Snippet = 15,
+ Color = 16,
+ File = 17,
+ Reference = 18,
+ Folder = 19,
+ EnumMember = 20,
+ Constant = 21,
+ Struct = 22,
+ Event = 23,
+ Operator = 24,
+ TypeParameter = 25,
+ },
-- How a completion was triggered
CompletionTriggerKind = {
-- Completion was triggered by typing an identifier (24x7 code
-- complete), manual invocation (e.g Ctrl+Space) or via API.
- Invoked = 1;
+ Invoked = 1,
-- Completion was triggered by a trigger character specified by
-- the `triggerCharacters` properties of the `CompletionRegistrationOptions`.
- TriggerCharacter = 2;
+ TriggerCharacter = 2,
-- Completion was re-triggered as the current completion list is incomplete.
- TriggerForIncompleteCompletions = 3;
- };
+ TriggerForIncompleteCompletions = 3,
+ },
-- A document highlight kind.
DocumentHighlightKind = {
-- A textual occurrence.
- Text = 1;
+ Text = 1,
-- Read-access of a symbol, like reading a variable.
- Read = 2;
+ Read = 2,
-- Write-access of a symbol, like writing to a variable.
- Write = 3;
- };
+ Write = 3,
+ },
-- A symbol kind.
SymbolKind = {
- File = 1;
- Module = 2;
- Namespace = 3;
- Package = 4;
- Class = 5;
- Method = 6;
- Property = 7;
- Field = 8;
- Constructor = 9;
- Enum = 10;
- Interface = 11;
- Function = 12;
- Variable = 13;
- Constant = 14;
- String = 15;
- Number = 16;
- Boolean = 17;
- Array = 18;
- Object = 19;
- Key = 20;
- Null = 21;
- EnumMember = 22;
- Struct = 23;
- Event = 24;
- Operator = 25;
- TypeParameter = 26;
- };
+ File = 1,
+ Module = 2,
+ Namespace = 3,
+ Package = 4,
+ Class = 5,
+ Method = 6,
+ Property = 7,
+ Field = 8,
+ Constructor = 9,
+ Enum = 10,
+ Interface = 11,
+ Function = 12,
+ Variable = 13,
+ Constant = 14,
+ String = 15,
+ Number = 16,
+ Boolean = 17,
+ Array = 18,
+ Object = 19,
+ Key = 20,
+ Null = 21,
+ EnumMember = 22,
+ Struct = 23,
+ Event = 24,
+ Operator = 25,
+ TypeParameter = 26,
+ },
-- Represents reasons why a text document is saved.
TextDocumentSaveReason = {
-- Manually triggered, e.g. by the user pressing save, by starting debugging,
-- or by an API call.
- Manual = 1;
+ Manual = 1,
-- Automatic after a delay.
- AfterDelay = 2;
+ AfterDelay = 2,
-- When the editor lost focus.
- FocusOut = 3;
- };
+ FocusOut = 3,
+ },
ErrorCodes = {
-- Defined by JSON RPC
- ParseError = -32700;
- InvalidRequest = -32600;
- MethodNotFound = -32601;
- InvalidParams = -32602;
- InternalError = -32603;
- serverErrorStart = -32099;
- serverErrorEnd = -32000;
- ServerNotInitialized = -32002;
- UnknownErrorCode = -32001;
+ ParseError = -32700,
+ InvalidRequest = -32600,
+ MethodNotFound = -32601,
+ InvalidParams = -32602,
+ InternalError = -32603,
+ serverErrorStart = -32099,
+ serverErrorEnd = -32000,
+ ServerNotInitialized = -32002,
+ UnknownErrorCode = -32001,
-- Defined by the protocol.
- RequestCancelled = -32800;
- ContentModified = -32801;
- };
+ RequestCancelled = -32800,
+ ContentModified = -32801,
+ },
-- Describes the content type that a client supports in various
-- result literals like `Hover`, `ParameterInfo` or `CompletionItem`.
@@ -177,88 +175,88 @@ local constants = {
-- are reserved for internal usage.
MarkupKind = {
-- Plain text is supported as a content format
- PlainText = 'plaintext';
+ PlainText = 'plaintext',
-- Markdown is supported as a content format
- Markdown = 'markdown';
- };
+ Markdown = 'markdown',
+ },
ResourceOperationKind = {
-- Supports creating new files and folders.
- Create = 'create';
+ Create = 'create',
-- Supports renaming existing files and folders.
- Rename = 'rename';
+ Rename = 'rename',
-- Supports deleting existing files and folders.
- Delete = 'delete';
- };
+ Delete = 'delete',
+ },
FailureHandlingKind = {
-- Applying the workspace change is simply aborted if one of the changes provided
-- fails. All operations executed before the failing operation stay executed.
- Abort = 'abort';
+ Abort = 'abort',
-- All operations are executed transactionally. That means they either all
-- succeed or no changes at all are applied to the workspace.
- Transactional = 'transactional';
+ Transactional = 'transactional',
-- If the workspace edit contains only textual file changes they are executed transactionally.
-- If resource changes (create, rename or delete file) are part of the change the failure
-- handling strategy is abort.
- TextOnlyTransactional = 'textOnlyTransactional';
+ TextOnlyTransactional = 'textOnlyTransactional',
-- The client tries to undo the operations already executed. But there is no
-- guarantee that this succeeds.
- Undo = 'undo';
- };
+ Undo = 'undo',
+ },
-- Known error codes for an `InitializeError`;
InitializeError = {
-- If the protocol version provided by the client can't be handled by the server.
-- @deprecated This initialize error got replaced by client capabilities. There is
-- no version handshake in version 3.0x
- unknownProtocolVersion = 1;
- };
+ unknownProtocolVersion = 1,
+ },
-- Defines how the host (editor) should sync document changes to the language server.
TextDocumentSyncKind = {
-- Documents should not be synced at all.
- None = 0;
+ None = 0,
-- Documents are synced by always sending the full content
-- of the document.
- Full = 1;
+ Full = 1,
-- Documents are synced by sending the full content on open.
-- After that only incremental updates to the document are
-- send.
- Incremental = 2;
- };
+ Incremental = 2,
+ },
WatchKind = {
-- Interested in create events.
- Create = 1;
+ Create = 1,
-- Interested in change events
- Change = 2;
+ Change = 2,
-- Interested in delete events
- Delete = 4;
- };
+ Delete = 4,
+ },
-- Defines whether the insert text in a completion item should be interpreted as
-- plain text or a snippet.
InsertTextFormat = {
-- The primary text to be inserted is treated as a plain string.
- PlainText = 1;
+ PlainText = 1,
-- The primary text to be inserted is treated as a snippet.
--
-- A snippet can define tab stops and placeholders with `$1`, `$2`
-- and `${3:foo};`. `$0` defines the final tab stop, it defaults to
-- the end of the snippet. Placeholders with equal identifiers are linked,
-- that is typing in one will update others too.
- Snippet = 2;
- };
+ Snippet = 2,
+ },
-- A set of predefined code action kinds
CodeActionKind = {
-- Empty kind.
- Empty = '';
+ Empty = '',
-- Base kind for quickfix actions
- QuickFix = 'quickfix';
+ QuickFix = 'quickfix',
-- Base kind for refactoring actions
- Refactor = 'refactor';
+ Refactor = 'refactor',
-- Base kind for refactoring extraction actions
--
-- Example extract actions:
@@ -268,7 +266,7 @@ local constants = {
-- - Extract variable
-- - Extract interface from class
-- - ...
- RefactorExtract = 'refactor.extract';
+ RefactorExtract = 'refactor.extract',
-- Base kind for refactoring inline actions
--
-- Example inline actions:
@@ -277,7 +275,7 @@ local constants = {
-- - Inline variable
-- - Inline constant
-- - ...
- RefactorInline = 'refactor.inline';
+ RefactorInline = 'refactor.inline',
-- Base kind for refactoring rewrite actions
--
-- Example rewrite actions:
@@ -288,14 +286,14 @@ local constants = {
-- - Make method static
-- - Move method to base class
-- - ...
- RefactorRewrite = 'refactor.rewrite';
+ RefactorRewrite = 'refactor.rewrite',
-- Base kind for source actions
--
-- Source code actions apply to the entire file.
- Source = 'source';
+ Source = 'source',
-- Base kind for an organize imports source action
- SourceOrganizeImports = 'source.organizeImports';
- };
+ SourceOrganizeImports = 'source.organizeImports',
+ },
}
for k, v in pairs(constants) do
@@ -622,19 +620,19 @@ function protocol.make_client_capabilities()
return {
textDocument = {
synchronization = {
- dynamicRegistration = false;
+ dynamicRegistration = false,
-- TODO(ashkan) Send textDocument/willSave before saving (BufWritePre)
- willSave = false;
+ willSave = false,
-- TODO(ashkan) Implement textDocument/willSaveWaitUntil
- willSaveWaitUntil = false;
+ willSaveWaitUntil = false,
-- Send textDocument/didSave after saving (BufWritePost)
- didSave = true;
- };
+ didSave = true,
+ },
codeAction = {
- dynamicRegistration = false;
+ dynamicRegistration = false,
codeActionLiteralSupport = {
codeActionKind = {
@@ -642,144 +640,193 @@ function protocol.make_client_capabilities()
local res = vim.tbl_values(protocol.CodeActionKind)
table.sort(res)
return res
- end)();
- };
- };
- dataSupport = true;
+ end)(),
+ },
+ },
+ isPreferredSupport = true,
+ dataSupport = true,
resolveSupport = {
- properties = { 'edit', }
- };
- };
+ properties = { 'edit' },
+ },
+ },
completion = {
- dynamicRegistration = false;
+ dynamicRegistration = false,
completionItem = {
-- Until we can actually expand snippet, move cursor and allow for true snippet experience,
-- this should be disabled out of the box.
-- However, users can turn this back on if they have a snippet plugin.
- snippetSupport = false;
+ snippetSupport = false,
- commitCharactersSupport = false;
- preselectSupport = false;
- deprecatedSupport = false;
- documentationFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
- };
+ commitCharactersSupport = false,
+ preselectSupport = false,
+ deprecatedSupport = false,
+ documentationFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
+ },
completionItemKind = {
valueSet = (function()
local res = {}
for k in ipairs(protocol.CompletionItemKind) do
- if type(k) == 'number' then table.insert(res, k) end
+ if type(k) == 'number' then
+ table.insert(res, k)
+ end
end
return res
- end)();
- };
+ end)(),
+ },
-- TODO(tjdevries): Implement this
- contextSupport = false;
- };
+ contextSupport = false,
+ },
declaration = {
- linkSupport = true;
- };
+ linkSupport = true,
+ },
definition = {
- linkSupport = true;
- };
+ linkSupport = true,
+ },
implementation = {
- linkSupport = true;
- };
+ linkSupport = true,
+ },
typeDefinition = {
- linkSupport = true;
- };
+ linkSupport = true,
+ },
hover = {
- dynamicRegistration = false;
- contentFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
- };
+ dynamicRegistration = false,
+ contentFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
+ },
signatureHelp = {
- dynamicRegistration = false;
+ dynamicRegistration = false,
signatureInformation = {
- activeParameterSupport = true;
- documentationFormat = { protocol.MarkupKind.Markdown; protocol.MarkupKind.PlainText };
+ activeParameterSupport = true,
+ documentationFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
parameterInformation = {
- labelOffsetSupport = true;
- };
- };
- };
+ labelOffsetSupport = true,
+ },
+ },
+ },
references = {
- dynamicRegistration = false;
- };
+ dynamicRegistration = false,
+ },
documentHighlight = {
- dynamicRegistration = false
- };
+ dynamicRegistration = false,
+ },
documentSymbol = {
- dynamicRegistration = false;
+ dynamicRegistration = false,
symbolKind = {
valueSet = (function()
local res = {}
for k in ipairs(protocol.SymbolKind) do
- if type(k) == 'number' then table.insert(res, k) end
+ if type(k) == 'number' then
+ table.insert(res, k)
+ end
end
return res
- end)();
- };
- hierarchicalDocumentSymbolSupport = true;
- };
+ end)(),
+ },
+ hierarchicalDocumentSymbolSupport = true,
+ },
rename = {
- dynamicRegistration = false;
- prepareSupport = true;
- };
+ dynamicRegistration = false,
+ prepareSupport = true,
+ },
publishDiagnostics = {
- relatedInformation = true;
+ relatedInformation = true,
tagSupport = {
valueSet = (function()
local res = {}
for k in ipairs(protocol.DiagnosticTag) do
- if type(k) == 'number' then table.insert(res, k) end
+ if type(k) == 'number' then
+ table.insert(res, k)
+ end
end
return res
- end)();
- };
- };
- };
+ end)(),
+ },
+ },
+ },
workspace = {
symbol = {
- dynamicRegistration = false;
+ dynamicRegistration = false,
symbolKind = {
valueSet = (function()
local res = {}
for k in ipairs(protocol.SymbolKind) do
- if type(k) == 'number' then table.insert(res, k) end
+ if type(k) == 'number' then
+ table.insert(res, k)
+ end
end
return res
- end)();
- };
- hierarchicalWorkspaceSymbolSupport = true;
- };
- workspaceFolders = true;
- applyEdit = true;
+ end)(),
+ },
+ hierarchicalWorkspaceSymbolSupport = true,
+ },
+ workspaceFolders = true,
+ applyEdit = true,
workspaceEdit = {
- resourceOperations = {'rename', 'create', 'delete',},
- };
- };
+ resourceOperations = { 'rename', 'create', 'delete' },
+ },
+ },
callHierarchy = {
- dynamicRegistration = false;
- };
- experimental = nil;
+ dynamicRegistration = false,
+ },
+ experimental = nil,
window = {
- workDoneProgress = true;
+ workDoneProgress = true,
showMessage = {
messageActionItem = {
- additionalPropertiesSupport = false;
- };
- };
+ additionalPropertiesSupport = false,
+ },
+ },
showDocument = {
- support = false;
- };
- };
+ support = false,
+ },
+ },
}
end
+local if_nil = vim.F.if_nil
--- Creates a normalized object describing LSP server capabilities.
---@param server_capabilities table Table of capabilities supported by the server
---@return table Normalized table of capabilities
function protocol.resolve_capabilities(server_capabilities)
+ local TextDocumentSyncKind = protocol.TextDocumentSyncKind
+ local textDocumentSync = server_capabilities.textDocumentSync
+ if textDocumentSync == nil then
+ -- Defaults if omitted.
+ server_capabilities.textDocumentSync = {
+ openClose = false,
+ change = TextDocumentSyncKind.None,
+ willSave = false,
+ willSaveWaitUntil = false,
+ save = {
+ includeText = false,
+ },
+ }
+ elseif type(textDocumentSync) == 'number' then
+ -- Backwards compatibility
+ if not TextDocumentSyncKind[textDocumentSync] then
+ return nil, 'Invalid server TextDocumentSyncKind for textDocumentSync'
+ end
+ server_capabilities.textDocumentSync = {
+ openClose = true,
+ change = textDocumentSync,
+ willSave = false,
+ willSaveWaitUntil = false,
+ save = {
+ includeText = false,
+ },
+ }
+ elseif type(textDocumentSync) ~= 'table' then
+ return nil, string.format('Invalid type for textDocumentSync: %q', type(textDocumentSync))
+ end
+ return server_capabilities
+end
+
+---@private
+--- Creates a normalized object describing LSP server capabilities.
+-- @deprecated access resolved_capabilities instead
+---@param server_capabilities table Table of capabilities supported by the server
+---@return table Normalized table of capabilities
+function protocol._resolve_capabilities_compat(server_capabilities)
local general_properties = {}
local text_document_sync_properties
do
@@ -788,39 +835,41 @@ function protocol.resolve_capabilities(server_capabilities)
if textDocumentSync == nil then
-- Defaults if omitted.
text_document_sync_properties = {
- text_document_open_close = false;
- text_document_did_change = TextDocumentSyncKind.None;
--- text_document_did_change = false;
- text_document_will_save = false;
- text_document_will_save_wait_until = false;
- text_document_save = false;
- text_document_save_include_text = false;
+ text_document_open_close = false,
+ text_document_did_change = TextDocumentSyncKind.None,
+ -- text_document_did_change = false;
+ text_document_will_save = false,
+ text_document_will_save_wait_until = false,
+ text_document_save = false,
+ text_document_save_include_text = false,
}
elseif type(textDocumentSync) == 'number' then
-- Backwards compatibility
if not TextDocumentSyncKind[textDocumentSync] then
- return nil, "Invalid server TextDocumentSyncKind for textDocumentSync"
+ return nil, 'Invalid server TextDocumentSyncKind for textDocumentSync'
end
text_document_sync_properties = {
- text_document_open_close = true;
- text_document_did_change = textDocumentSync;
- text_document_will_save = false;
- text_document_will_save_wait_until = false;
- text_document_save = true;
- text_document_save_include_text = false;
+ text_document_open_close = true,
+ text_document_did_change = textDocumentSync,
+ text_document_will_save = false,
+ text_document_will_save_wait_until = false,
+ text_document_save = true,
+ text_document_save_include_text = false,
}
elseif type(textDocumentSync) == 'table' then
text_document_sync_properties = {
- text_document_open_close = if_nil(textDocumentSync.openClose, false);
- text_document_did_change = if_nil(textDocumentSync.change, TextDocumentSyncKind.None);
- text_document_will_save = if_nil(textDocumentSync.willSave, false);
- text_document_will_save_wait_until = if_nil(textDocumentSync.willSaveWaitUntil, false);
- text_document_save = if_nil(textDocumentSync.save, false);
- text_document_save_include_text = if_nil(type(textDocumentSync.save) == 'table'
- and textDocumentSync.save.includeText, false);
+ text_document_open_close = if_nil(textDocumentSync.openClose, false),
+ text_document_did_change = if_nil(textDocumentSync.change, TextDocumentSyncKind.None),
+ text_document_will_save = if_nil(textDocumentSync.willSave, false),
+ text_document_will_save_wait_until = if_nil(textDocumentSync.willSaveWaitUntil, false),
+ text_document_save = if_nil(textDocumentSync.save, false),
+ text_document_save_include_text = if_nil(
+ type(textDocumentSync.save) == 'table' and textDocumentSync.save.includeText,
+ false
+ ),
}
else
- return nil, string.format("Invalid type for textDocumentSync: %q", type(textDocumentSync))
+ return nil, string.format('Invalid type for textDocumentSync: %q', type(textDocumentSync))
end
end
general_properties.completion = server_capabilities.completionProvider ~= nil
@@ -831,7 +880,8 @@ function protocol.resolve_capabilities(server_capabilities)
general_properties.document_symbol = server_capabilities.documentSymbolProvider or false
general_properties.workspace_symbol = server_capabilities.workspaceSymbolProvider or false
general_properties.document_formatting = server_capabilities.documentFormattingProvider or false
- general_properties.document_range_formatting = server_capabilities.documentRangeFormattingProvider or false
+ general_properties.document_range_formatting = server_capabilities.documentRangeFormattingProvider
+ or false
general_properties.call_hierarchy = server_capabilities.callHierarchyProvider or false
general_properties.execute_command = server_capabilities.executeCommandProvider ~= nil
@@ -848,18 +898,21 @@ function protocol.resolve_capabilities(server_capabilities)
general_properties.code_lens_resolve = false
elseif type(server_capabilities.codeLensProvider) == 'table' then
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
- error("The server sent invalid codeLensProvider")
+ error('The server sent invalid codeLensProvider')
end
if server_capabilities.codeActionProvider == nil then
general_properties.code_action = false
- elseif type(server_capabilities.codeActionProvider) == 'boolean'
- or type(server_capabilities.codeActionProvider) == 'table' then
+ elseif
+ type(server_capabilities.codeActionProvider) == 'boolean'
+ or type(server_capabilities.codeActionProvider) == 'table'
+ then
general_properties.code_action = server_capabilities.codeActionProvider
else
- error("The server sent invalid codeActionProvider")
+ error('The server sent invalid codeActionProvider')
end
if server_capabilities.declarationProvider == nil then
@@ -869,7 +922,7 @@ function protocol.resolve_capabilities(server_capabilities)
elseif type(server_capabilities.declarationProvider) == 'table' then
general_properties.declaration = server_capabilities.declarationProvider
else
- error("The server sent invalid declarationProvider")
+ error('The server sent invalid declarationProvider')
end
if server_capabilities.typeDefinitionProvider == nil then
@@ -879,7 +932,7 @@ function protocol.resolve_capabilities(server_capabilities)
elseif type(server_capabilities.typeDefinitionProvider) == 'table' then
general_properties.type_definition = server_capabilities.typeDefinitionProvider
else
- error("The server sent invalid typeDefinitionProvider")
+ error('The server sent invalid typeDefinitionProvider')
end
if server_capabilities.implementationProvider == nil then
@@ -889,7 +942,7 @@ function protocol.resolve_capabilities(server_capabilities)
elseif type(server_capabilities.implementationProvider) == 'table' then
general_properties.implementation = server_capabilities.implementationProvider
else
- error("The server sent invalid implementationProvider")
+ error('The server sent invalid implementationProvider')
end
local workspace = server_capabilities.workspace
@@ -897,45 +950,48 @@ function protocol.resolve_capabilities(server_capabilities)
if workspace == nil or workspace.workspaceFolders == nil then
-- Defaults if omitted.
workspace_properties = {
- workspace_folder_properties = {
- supported = false;
- changeNotifications=false;
- }
+ workspace_folder_properties = {
+ supported = false,
+ changeNotifications = false,
+ },
}
elseif type(workspace.workspaceFolders) == 'table' then
workspace_properties = {
workspace_folder_properties = {
- supported = if_nil(workspace.workspaceFolders.supported, false);
- changeNotifications = if_nil(workspace.workspaceFolders.changeNotifications, false);
-
- }
+ supported = if_nil(workspace.workspaceFolders.supported, false),
+ changeNotifications = if_nil(workspace.workspaceFolders.changeNotifications, false),
+ },
}
else
- error("The server sent invalid workspace")
+ error('The server sent invalid workspace')
end
local signature_help_properties
if server_capabilities.signatureHelpProvider == nil then
signature_help_properties = {
- signature_help = false;
- signature_help_trigger_characters = {};
+ signature_help = false,
+ signature_help_trigger_characters = {},
}
elseif type(server_capabilities.signatureHelpProvider) == 'table' then
signature_help_properties = {
- signature_help = true;
+ signature_help = true,
-- The characters that trigger signature help automatically.
- signature_help_trigger_characters = server_capabilities.signatureHelpProvider.triggerCharacters or {};
+ signature_help_trigger_characters = server_capabilities.signatureHelpProvider.triggerCharacters
+ or {},
}
else
- error("The server sent invalid signatureHelpProvider")
+ error('The server sent invalid signatureHelpProvider')
end
- return vim.tbl_extend("error"
- , text_document_sync_properties
- , signature_help_properties
- , workspace_properties
- , general_properties
- )
+ local capabilities = vim.tbl_extend(
+ 'error',
+ text_document_sync_properties,
+ signature_help_properties,
+ workspace_properties,
+ general_properties
+ )
+
+ return capabilities
end
return protocol
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 1ecac50df4..913eee19a2 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -4,12 +4,14 @@ local log = require('vim.lsp.log')
local protocol = require('vim.lsp.protocol')
local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedule_wrap
+local is_win = uv.os_uname().version:find('Windows')
+
---@private
--- Checks whether a given path exists and is a directory.
---@param filename (string) path to check
---@returns (bool)
local function is_dir(filename)
- local stat = vim.loop.fs_stat(filename)
+ local stat = uv.fs_stat(filename)
return stat and stat.type == 'directory' or false
end
@@ -32,9 +34,9 @@ local function env_merge(env)
-- Merge.
env = vim.tbl_extend('force', vim.fn.environ(), env)
local final_env = {}
- for k,v in pairs(env) do
+ for k, v in pairs(env) do
assert(type(k) == 'string', 'env must be a dict')
- table.insert(final_env, k..'='..tostring(v))
+ table.insert(final_env, k .. '=' .. tostring(v))
end
return final_env
end
@@ -45,10 +47,12 @@ end
---@param encoded_message (string)
---@returns (table) table containing encoded message and `Content-Length` attribute
local function format_message_with_content_length(encoded_message)
- return table.concat {
- 'Content-Length: '; tostring(#encoded_message); '\r\n\r\n';
- encoded_message;
- }
+ return table.concat({
+ 'Content-Length: ',
+ tostring(#encoded_message),
+ '\r\n\r\n',
+ encoded_message,
+ })
end
---@private
@@ -65,23 +69,25 @@ local function parse_headers(header)
if line == '' then
break
end
- local key, value = line:match("^%s*(%S+)%s*:%s*(.+)%s*$")
+ local key, value = line:match('^%s*(%S+)%s*:%s*(.+)%s*$')
if key then
key = key:lower():gsub('%-', '_')
headers[key] = value
else
- local _ = log.error() and log.error("invalid header line %q", line)
- error(string.format("invalid header line %q", line))
+ local _ = log.error() and log.error('invalid header line %q', line)
+ error(string.format('invalid header line %q', line))
end
end
headers.content_length = tonumber(headers.content_length)
- or error(string.format("Content-Length not found in headers. %q", header))
+ or error(string.format('Content-Length not found in headers. %q', header))
return headers
end
-- This is the start of any possible header patterns. The gsub converts it to a
-- case insensitive pattern.
-local header_start_pattern = ("content"):gsub("%w", function(c) return "["..c..c:upper().."]" end)
+local header_start_pattern = ('content'):gsub('%w', function(c)
+ return '[' .. c .. c:upper() .. ']'
+end)
---@private
--- The actual workhorse.
@@ -100,17 +106,17 @@ local function request_parser_loop()
-- be searching for.
-- TODO(ashkan) I'd like to remove this, but it seems permanent :(
local buffer_start = buffer:find(header_start_pattern)
- local headers = parse_headers(buffer:sub(buffer_start, start-1))
+ local headers = parse_headers(buffer:sub(buffer_start, start - 1))
local content_length = headers.content_length
-- Use table instead of just string to buffer the message. It prevents
-- a ton of strings allocating.
-- ref. http://www.lua.org/pil/11.6.html
- local body_chunks = {buffer:sub(finish+1)}
+ local body_chunks = { buffer:sub(finish + 1) }
local body_length = #body_chunks[1]
-- Keep waiting for data until we have enough.
while body_length < content_length do
local chunk = coroutine.yield()
- or error("Expected more data for the body. The server may have died.") -- TODO hmm.
+ or error('Expected more data for the body. The server may have died.') -- TODO hmm.
table.insert(body_chunks, chunk)
body_length = body_length + #chunk
end
@@ -123,25 +129,30 @@ local function request_parser_loop()
end
local body = table.concat(body_chunks)
-- Yield our data.
- buffer = rest..(coroutine.yield(headers, body)
- or error("Expected more data for the body. The server may have died.")) -- TODO hmm.
+ buffer = rest
+ .. (
+ coroutine.yield(headers, body)
+ or error('Expected more data for the body. The server may have died.')
+ ) -- TODO hmm.
else
-- Get more data since we don't have enough.
- buffer = buffer..(coroutine.yield()
- or error("Expected more data for the header. The server may have died.")) -- TODO hmm.
+ buffer = buffer
+ .. (
+ coroutine.yield() or error('Expected more data for the header. The server may have died.')
+ ) -- TODO hmm.
end
end
end
--- Mapping of error codes used by the client
local client_errors = {
- INVALID_SERVER_MESSAGE = 1;
- INVALID_SERVER_JSON = 2;
- NO_RESULT_CALLBACK_FOUND = 3;
- READ_ERROR = 4;
- NOTIFICATION_HANDLER_ERROR = 5;
- SERVER_REQUEST_HANDLER_ERROR = 6;
- SERVER_RESULT_CALLBACK_ERROR = 7;
+ INVALID_SERVER_MESSAGE = 1,
+ INVALID_SERVER_JSON = 2,
+ NO_RESULT_CALLBACK_FOUND = 3,
+ READ_ERROR = 4,
+ NOTIFICATION_HANDLER_ERROR = 5,
+ SERVER_REQUEST_HANDLER_ERROR = 6,
+ SERVER_RESULT_CALLBACK_ERROR = 7,
}
client_errors = vim.tbl_add_reverse_lookup(client_errors)
@@ -151,26 +162,26 @@ client_errors = vim.tbl_add_reverse_lookup(client_errors)
---@param err (table) The error object
---@returns (string) The formatted error message
local function format_rpc_error(err)
- validate {
- err = { err, 't' };
- }
+ validate({
+ err = { err, 't' },
+ })
-- There is ErrorCodes in the LSP specification,
-- but in ResponseError.code it is not used and the actual type is number.
local code
if protocol.ErrorCodes[err.code] then
- code = string.format("code_name = %s,", protocol.ErrorCodes[err.code])
+ code = string.format('code_name = %s,', protocol.ErrorCodes[err.code])
else
- code = string.format("code_name = unknown, code = %s,", err.code)
+ code = string.format('code_name = unknown, code = %s,', err.code)
end
- local message_parts = {"RPC[Error]", code}
+ local message_parts = { 'RPC[Error]', code }
if err.message then
- table.insert(message_parts, "message =")
- table.insert(message_parts, string.format("%q", err.message))
+ table.insert(message_parts, 'message =')
+ table.insert(message_parts, string.format('%q', err.message))
end
if err.data then
- table.insert(message_parts, "data =")
+ table.insert(message_parts, 'data =')
table.insert(message_parts, vim.inspect(err.data))
end
return table.concat(message_parts, ' ')
@@ -185,11 +196,11 @@ local function rpc_response_error(code, message, data)
-- TODO should this error or just pick a sane error (like InternalError)?
local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code')
return setmetatable({
- code = code;
- message = message or code_name;
- data = data;
+ code = code,
+ message = message or code_name,
+ data = data,
}, {
- __tostring = format_rpc_error;
+ __tostring = format_rpc_error,
})
end
@@ -220,7 +231,7 @@ end
---@param signal (number): Number describing the signal used to terminate (if
---any)
function default_dispatchers.on_exit(code, signal)
- local _ = log.info() and log.info("client_exit", { code = code, signal = signal })
+ local _ = log.info() and log.info('client_exit', { code = code, signal = signal })
end
---@private
--- Default dispatcher for client errors.
@@ -258,15 +269,16 @@ end
--- - {handle} A handle for low-level interaction with the LSP server process
--- |vim.loop|.
local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
- local _ = log.info() and log.info("Starting RPC client", {cmd = cmd, args = cmd_args, extra = extra_spawn_params})
- validate {
- cmd = { cmd, 's' };
- cmd_args = { cmd_args, 't' };
- dispatchers = { dispatchers, 't', true };
- }
+ local _ = log.info()
+ and log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params })
+ validate({
+ cmd = { cmd, 's' },
+ cmd_args = { cmd_args, 't' },
+ dispatchers = { dispatchers, 't', true },
+ })
if extra_spawn_params and extra_spawn_params.cwd then
- assert(is_dir(extra_spawn_params.cwd), "cwd must be a directory")
+ assert(is_dir(extra_spawn_params.cwd), 'cwd must be a directory')
end
if dispatchers then
local user_dispatchers = dispatchers
@@ -275,11 +287,11 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
local user_dispatcher = user_dispatchers[dispatch_name]
if user_dispatcher then
if type(user_dispatcher) ~= 'function' then
- error(string.format("dispatcher.%s must be a function", dispatch_name))
+ error(string.format('dispatcher.%s must be a function', dispatch_name))
end
-- server_request is wrapped elsewhere.
- if not (dispatch_name == 'server_request'
- or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason.
+ if
+ not (dispatch_name == 'server_request' or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason.
then
user_dispatcher = schedule_wrap(user_dispatcher)
end
@@ -317,20 +329,25 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
dispatchers.on_exit(code, signal)
end
local spawn_params = {
- args = cmd_args;
- stdio = {stdin, stdout, stderr};
+ args = cmd_args,
+ stdio = { stdin, stdout, stderr },
+ detached = not is_win,
}
if extra_spawn_params then
spawn_params.cwd = extra_spawn_params.cwd
spawn_params.env = env_merge(extra_spawn_params.env)
+ if extra_spawn_params.detached ~= nil then
+ spawn_params.detached = extra_spawn_params.detached
+ end
end
handle, pid = uv.spawn(cmd, spawn_params, onexit)
if handle == nil then
- local msg = string.format("Spawning language server with cmd: `%s` failed", cmd)
- if string.match(pid, "ENOENT") then
- msg = msg .. ". The language server is either not installed, missing from PATH, or not executable."
+ local msg = string.format('Spawning language server with cmd: `%s` failed', cmd)
+ if string.match(pid, 'ENOENT') then
+ msg = msg
+ .. '. The language server is either not installed, missing from PATH, or not executable.'
else
- msg = msg .. string.format(" with error message: %s", pid)
+ msg = msg .. string.format(' with error message: %s', pid)
end
vim.notify(msg, vim.log.levels.WARN)
return
@@ -344,8 +361,10 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
---@param payload table
---@returns true if the payload could be scheduled, false if the main event-loop is in the process of closing.
local function encode_and_send(payload)
- local _ = log.debug() and log.debug("rpc.send", payload)
- if handle == nil or handle:is_closing() then return false end
+ local _ = log.debug() and log.debug('rpc.send', payload)
+ if handle == nil or handle:is_closing() then
+ return false
+ end
local encoded = vim.json.encode(payload)
stdin:write(format_message_with_content_length(encoded))
return true
@@ -359,22 +378,22 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
---@param params (table): Parameters for the invoked LSP method
---@returns (bool) `true` if notification could be sent, `false` if not
local function notify(method, params)
- return encode_and_send {
- jsonrpc = "2.0";
- method = method;
- params = params;
- }
+ return encode_and_send({
+ jsonrpc = '2.0',
+ method = method,
+ params = params,
+ })
end
---@private
--- sends an error object to the remote LSP process.
local function send_response(request_id, err, result)
- return encode_and_send {
- id = request_id;
- jsonrpc = "2.0";
- error = err;
- result = result;
- }
+ return encode_and_send({
+ id = request_id,
+ jsonrpc = '2.0',
+ error = err,
+ result = result,
+ })
end
-- FIXME: DOC: Should be placed on the RPC client object returned by
@@ -385,21 +404,21 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
---@param method (string) The invoked LSP method
---@param params (table) Parameters for the invoked LSP method
---@param callback (function) Callback to invoke
- ---@param notify_reply_callback (function) 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
local function request(method, params, callback, notify_reply_callback)
- validate {
- callback = { callback, 'f' };
- notify_reply_callback = { notify_reply_callback, 'f', true };
- }
+ validate({
+ callback = { callback, 'f' },
+ notify_reply_callback = { notify_reply_callback, 'f', true },
+ })
message_index = message_index + 1
local message_id = message_index
- local result = encode_and_send {
- id = message_id;
- jsonrpc = "2.0";
- method = method;
- params = params;
- }
+ local result = encode_and_send({
+ id = message_id,
+ jsonrpc = '2.0',
+ method = method,
+ params = params,
+ })
if result then
if message_callbacks then
message_callbacks[message_id] = schedule_wrap(callback)
@@ -417,7 +436,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
stderr:read_start(function(_err, chunk)
if chunk then
- local _ = log.error() and log.error("rpc", cmd, "stderr", chunk)
+ local _ = log.error() and log.error('rpc', cmd, 'stderr', chunk)
end
end)
@@ -451,7 +470,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
on_error(client_errors.INVALID_SERVER_JSON, decoded)
return
end
- local _ = log.debug() and log.debug("rpc.receive", decoded)
+ local _ = log.debug() and log.debug('rpc.receive', decoded)
if type(decoded.method) == 'string' and decoded.id then
local err
@@ -459,17 +478,36 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
-- we can still use the result.
schedule(function()
local status, result
- status, result, err = try_call(client_errors.SERVER_REQUEST_HANDLER_ERROR,
- dispatchers.server_request, decoded.method, decoded.params)
- local _ = log.debug() and log.debug("server_request: callback result", { status = status, result = result, err = err })
+ status, result, err = try_call(
+ client_errors.SERVER_REQUEST_HANDLER_ERROR,
+ dispatchers.server_request,
+ decoded.method,
+ decoded.params
+ )
+ local _ = log.debug()
+ and log.debug(
+ 'server_request: callback result',
+ { status = status, result = result, err = err }
+ )
if status then
if not (result or err) then
-- TODO this can be a problem if `null` is sent for result. needs vim.NIL
- error(string.format("method %q: either a result or an error must be sent to the server in response", decoded.method))
+ error(
+ string.format(
+ 'method %q: either a result or an error must be sent to the server in response',
+ decoded.method
+ )
+ )
end
if err then
- assert(type(err) == 'table', "err must be a table. Use rpc_response_error to help format errors.")
- local code_name = assert(protocol.ErrorCodes[err.code], "Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.")
+ assert(
+ type(err) == 'table',
+ 'err must be a table. Use rpc_response_error to help format errors.'
+ )
+ local code_name = assert(
+ protocol.ErrorCodes[err.code],
+ 'Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.'
+ )
err.message = err.message or code_name
end
else
@@ -479,18 +517,17 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
end
send_response(decoded.id, err, result)
end)
- -- This works because we are expecting vim.NIL here
+ -- This works because we are expecting vim.NIL here
elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then
-
-- We sent a number, so we expect a number.
local result_id = tonumber(decoded.id)
-- Notify the user that a response was received for the request
local notify_reply_callback = notify_reply_callbacks and notify_reply_callbacks[result_id]
if notify_reply_callback then
- validate {
- notify_reply_callback = { notify_reply_callback, 'f' };
- }
+ validate({
+ notify_reply_callback = { notify_reply_callback, 'f' },
+ })
notify_reply_callback(result_id)
notify_reply_callbacks[result_id] = nil
end
@@ -499,7 +536,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
if decoded.error then
local mute_error = false
if decoded.error.code == protocol.ErrorCodes.RequestCancelled then
- local _ = log.debug() and log.debug("Received cancellation ack", decoded)
+ local _ = log.debug() and log.debug('Received cancellation ack', decoded)
mute_error = true
end
@@ -519,24 +556,33 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
local callback = message_callbacks and message_callbacks[result_id]
if callback then
message_callbacks[result_id] = nil
- validate {
- callback = { callback, 'f' };
- }
+ validate({
+ callback = { callback, 'f' },
+ })
if decoded.error then
decoded.error = setmetatable(decoded.error, {
- __tostring = format_rpc_error;
+ __tostring = format_rpc_error,
})
end
- try_call(client_errors.SERVER_RESULT_CALLBACK_ERROR,
- callback, decoded.error, decoded.result)
+ try_call(
+ client_errors.SERVER_RESULT_CALLBACK_ERROR,
+ callback,
+ decoded.error,
+ decoded.result
+ )
else
on_error(client_errors.NO_RESULT_CALLBACK_FOUND, decoded)
- local _ = log.error() and log.error("No callback found for server response id "..result_id)
+ local _ = log.error()
+ and log.error('No callback found for server response id ' .. result_id)
end
elseif type(decoded.method) == 'string' then
-- Notification
- try_call(client_errors.NOTIFICATION_HANDLER_ERROR,
- dispatchers.notification, decoded.method, decoded.params)
+ try_call(
+ client_errors.NOTIFICATION_HANDLER_ERROR,
+ dispatchers.notification,
+ decoded.method,
+ decoded.params
+ )
else
-- Invalid server message
on_error(client_errors.INVALID_SERVER_MESSAGE, decoded)
@@ -552,7 +598,9 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
return
end
-- This should signal that we are done reading from the client.
- if not chunk then return end
+ if not chunk then
+ return
+ end
-- Flush anything in the parser by looping until we don't get a result
-- anymore.
while true do
@@ -570,17 +618,17 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
end)
return {
- pid = pid;
- handle = handle;
- request = request;
- notify = notify
+ pid = pid,
+ handle = handle,
+ request = request,
+ notify = notify,
}
end
return {
- start = start;
- rpc_response_error = rpc_response_error;
- format_rpc_error = format_rpc_error;
- client_errors = client_errors;
+ start = start,
+ rpc_response_error = rpc_response_error,
+ format_rpc_error = format_rpc_error,
+ client_errors = client_errors,
}
-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua
index 0f4e5b572b..0d65e86b55 100644
--- a/runtime/lua/vim/lsp/sync.lua
+++ b/runtime/lua/vim/lsp/sync.lua
@@ -79,7 +79,7 @@ local function compute_line_length(line, offset_encoding)
local length
local _
if offset_encoding == 'utf-16' then
- _, length = str_utfindex(line)
+ _, length = str_utfindex(line)
elseif offset_encoding == 'utf-32' then
length, _ = str_utfindex(line)
else
@@ -100,7 +100,7 @@ local function align_end_position(line, byte, offset_encoding)
-- If on the first byte, or an empty string: the trivial case
if byte == 1 or #line == 0 then
char = byte
- -- Called in the case of extending an empty line "" -> "a"
+ -- Called in the case of extending an empty line "" -> "a"
elseif byte == #line + 1 then
char = compute_line_length(line, offset_encoding) + 1
else
@@ -130,14 +130,38 @@ end
---@param new_lastline integer new_lastline from on_lines, adjusted to 1-index
---@param offset_encoding string utf-8|utf-16|utf-32|nil (fallback to utf-8)
---@returns table<int, int> line_idx, byte_idx, and char_idx of first change position
-local function compute_start_range(prev_lines, curr_lines, firstline, lastline, new_lastline, offset_encoding)
+local function compute_start_range(
+ prev_lines,
+ curr_lines,
+ firstline,
+ lastline,
+ new_lastline,
+ offset_encoding
+)
+ local char_idx
+ local byte_idx
-- If firstline == lastline, no existing text is changed. All edit operations
-- occur on a new line pointed to by lastline. This occurs during insertion of
-- new lines(O), the new newline is inserted at the line indicated by
-- new_lastline.
+ if firstline == lastline then
+ local line_idx
+ local line = prev_lines[firstline - 1]
+ if line then
+ line_idx = firstline - 1
+ byte_idx = #line + 1
+ char_idx = compute_line_length(line, offset_encoding) + 1
+ else
+ line_idx = firstline
+ byte_idx = 1
+ char_idx = 1
+ end
+ return { line_idx = line_idx, byte_idx = byte_idx, char_idx = char_idx }
+ end
+
-- If firstline == new_lastline, the first change occurred on a line that was deleted.
-- In this case, the first byte change is also at the first byte of firstline
- if firstline == new_lastline or firstline == lastline then
+ if firstline == new_lastline then
return { line_idx = firstline, byte_idx = 1, char_idx = 1 }
end
@@ -158,14 +182,12 @@ local function compute_start_range(prev_lines, curr_lines, firstline, lastline,
end
-- Convert byte to codepoint if applicable
- local char_idx
- local byte_idx
- if start_byte_idx == 1 or (#prev_line == 0 and start_byte_idx == 1)then
+ if start_byte_idx == 1 or (#prev_line == 0 and start_byte_idx == 1) then
byte_idx = start_byte_idx
char_idx = 1
elseif start_byte_idx == #prev_line + 1 then
byte_idx = start_byte_idx
- char_idx = compute_line_length(prev_line, offset_encoding) + 1
+ char_idx = compute_line_length(prev_line, offset_encoding) + 1
else
byte_idx = start_byte_idx + str_utf_start(prev_line, start_byte_idx)
char_idx = byte_to_utf(prev_line, byte_idx, offset_encoding)
@@ -188,14 +210,30 @@ end
---@param new_lastline integer
---@param offset_encoding string
---@returns (int, int) end_line_idx and end_col_idx of range
-local function compute_end_range(prev_lines, curr_lines, start_range, firstline, lastline, new_lastline, offset_encoding)
+local function compute_end_range(
+ prev_lines,
+ curr_lines,
+ start_range,
+ firstline,
+ lastline,
+ new_lastline,
+ offset_encoding
+)
-- If firstline == new_lastline, the first change occurred on a line that was deleted.
-- In this case, the last_byte...
if firstline == new_lastline then
- return { line_idx = (lastline - new_lastline + firstline), byte_idx = 1, char_idx = 1 }, { line_idx = firstline, byte_idx = 1, char_idx = 1 }
+ return { line_idx = (lastline - new_lastline + firstline), byte_idx = 1, char_idx = 1 }, {
+ line_idx = firstline,
+ byte_idx = 1,
+ char_idx = 1,
+ }
end
if firstline == lastline then
- return { line_idx = firstline, byte_idx = 1, char_idx = 1 }, { line_idx = new_lastline - lastline + firstline, byte_idx = 1, char_idx = 1 }
+ return { line_idx = firstline, byte_idx = 1, char_idx = 1 }, {
+ line_idx = new_lastline - lastline + firstline,
+ byte_idx = 1,
+ char_idx = 1,
+ }
end
-- Compare on last line, at minimum will be the start range
local start_line_idx = start_range.line_idx
@@ -218,14 +256,18 @@ local function compute_end_range(prev_lines, curr_lines, start_range, firstline,
local max_length
if start_line_idx == prev_line_idx then
-- Search until beginning of difference
- max_length = min(prev_line_length - start_range.byte_idx, curr_line_length - start_range.byte_idx) + 1
+ max_length = min(
+ prev_line_length - start_range.byte_idx,
+ curr_line_length - start_range.byte_idx
+ ) + 1
else
max_length = min(prev_line_length, curr_line_length) + 1
end
for idx = 0, max_length do
byte_offset = idx
if
- str_byte(prev_line, prev_line_length - byte_offset) ~= str_byte(curr_line, curr_line_length - byte_offset)
+ str_byte(prev_line, prev_line_length - byte_offset)
+ ~= str_byte(curr_line, curr_line_length - byte_offset)
then
break
end
@@ -239,8 +281,10 @@ local function compute_end_range(prev_lines, curr_lines, start_range, firstline,
if prev_end_byte_idx == 0 then
prev_end_byte_idx = 1
end
- local prev_byte_idx, prev_char_idx = align_end_position(prev_line, prev_end_byte_idx, offset_encoding)
- local prev_end_range = { line_idx = prev_line_idx, byte_idx = prev_byte_idx, char_idx = prev_char_idx }
+ local prev_byte_idx, prev_char_idx =
+ align_end_position(prev_line, prev_end_byte_idx, offset_encoding)
+ local prev_end_range =
+ { line_idx = prev_line_idx, byte_idx = prev_byte_idx, char_idx = prev_char_idx }
local curr_end_range
-- Deletion event, new_range cannot be before start
@@ -252,8 +296,10 @@ local function compute_end_range(prev_lines, curr_lines, start_range, firstline,
if curr_end_byte_idx == 0 then
curr_end_byte_idx = 1
end
- local curr_byte_idx, curr_char_idx = align_end_position(curr_line, curr_end_byte_idx, offset_encoding)
- curr_end_range = { line_idx = curr_line_idx, byte_idx = curr_byte_idx, char_idx = curr_char_idx }
+ local curr_byte_idx, curr_char_idx =
+ align_end_position(curr_line, curr_end_byte_idx, offset_encoding)
+ curr_end_range =
+ { line_idx = curr_line_idx, byte_idx = curr_byte_idx, char_idx = curr_char_idx }
end
return prev_end_range, curr_end_range
@@ -266,14 +312,13 @@ end
---@param end_range table new_end_range returned by last_difference
---@returns string text extracted from defined region
local function extract_text(lines, start_range, end_range, line_ending)
- if not lines[start_range.line_idx] then
- return ""
- end
+ if not lines[start_range.line_idx] then
+ return ''
+ end
-- Trivial case: start and end range are the same line, directly grab changed text
if start_range.line_idx == end_range.line_idx then
-- string.sub is inclusive, end_range is not
return string.sub(lines[start_range.line_idx], start_range.byte_idx, end_range.byte_idx - 1)
-
else
-- Handle deletion case
-- Collect the changed portion of the first changed line
@@ -288,7 +333,7 @@ local function extract_text(lines, start_range, end_range, line_ending)
-- Collect the changed portion of the last changed line.
table.insert(result, string.sub(lines[end_range.line_idx], 1, end_range.byte_idx - 1))
else
- table.insert(result, "")
+ table.insert(result, '')
end
-- Add line ending between all lines
@@ -313,7 +358,10 @@ local function compute_range_length(lines, start_range, end_range, offset_encodi
local start_line = lines[start_range.line_idx]
local range_length
if start_line and #start_line > 0 then
- range_length = compute_line_length(start_line, offset_encoding) - start_range.char_idx + 1 + line_ending_length
+ range_length = compute_line_length(start_line, offset_encoding)
+ - start_range.char_idx
+ + 1
+ + line_ending_length
else
-- Length of newline character
range_length = line_ending_length
@@ -345,7 +393,15 @@ end
---@param new_lastline number line to begin search in new_lines for last difference
---@param offset_encoding string encoding requested by language server
---@returns table TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textDocumentContentChangeEvent
-function M.compute_diff(prev_lines, curr_lines, firstline, lastline, new_lastline, offset_encoding, line_ending)
+function M.compute_diff(
+ prev_lines,
+ curr_lines,
+ firstline,
+ lastline,
+ new_lastline,
+ offset_encoding,
+ line_ending
+)
-- Find the start of changes between the previous and current buffer. Common between both.
-- Sent to the server as the start of the changed range.
-- Used to grab the changed text from the latest buffer.
@@ -375,7 +431,8 @@ function M.compute_diff(prev_lines, curr_lines, firstline, lastline, new_lastlin
local text = extract_text(curr_lines, start_range, curr_end_range, line_ending)
-- Compute the range of the replaced text. Deprecated but still required for certain language servers
- local range_length = compute_range_length(prev_lines, start_range, prev_end_range, offset_encoding, line_ending)
+ local range_length =
+ compute_range_length(prev_lines, start_range, prev_end_range, offset_encoding, line_ending)
-- convert to 0 based indexing
local result = {
diff --git a/runtime/lua/vim/lsp/tagfunc.lua b/runtime/lua/vim/lsp/tagfunc.lua
index 5c55e8559f..49029f8599 100644
--- a/runtime/lua/vim/lsp/tagfunc.lua
+++ b/runtime/lua/vim/lsp/tagfunc.lua
@@ -1,5 +1,5 @@
local lsp = vim.lsp
-local util = vim.lsp.util
+local util = lsp.util
---@private
local function mk_tag_item(name, range, uri, offset_encoding)
@@ -15,7 +15,7 @@ end
---@private
local function query_definition(pattern)
- local params = lsp.util.make_position_params()
+ local params = util.make_position_params()
local results_by_client, err = lsp.buf_request_sync(0, 'textDocument/definition', params, 1000)
if err then
return {}
@@ -44,7 +44,8 @@ end
---@private
local function query_workspace_symbols(pattern)
- local results_by_client, err = lsp.buf_request_sync(0, 'workspace/symbol', { query = pattern }, 1000)
+ local results_by_client, err =
+ lsp.buf_request_sync(0, 'workspace/symbol', { query = pattern }, 1000)
if err then
return {}
end
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 89c5ebe8f7..70f5010256 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -1,10 +1,10 @@
-local protocol = require 'vim.lsp.protocol'
-local snippet = require 'vim.lsp._snippet'
+local protocol = require('vim.lsp.protocol')
+local snippet = require('vim.lsp._snippet')
local vim = vim
local validate = vim.validate
local api = vim.api
local list_extend = vim.list_extend
-local highlight = require 'vim.highlight'
+local highlight = require('vim.highlight')
local uv = vim.loop
local npcall = vim.F.npcall
@@ -13,14 +13,14 @@ local split = vim.split
local M = {}
local default_border = {
- {"", "NormalFloat"},
- {"", "NormalFloat"},
- {"", "NormalFloat"},
- {" ", "NormalFloat"},
- {"", "NormalFloat"},
- {"", "NormalFloat"},
- {"", "NormalFloat"},
- {" ", "NormalFloat"},
+ { '', 'NormalFloat' },
+ { '', 'NormalFloat' },
+ { '', 'NormalFloat' },
+ { ' ', 'NormalFloat' },
+ { '', 'NormalFloat' },
+ { '', 'NormalFloat' },
+ { '', 'NormalFloat' },
+ { ' ', 'NormalFloat' },
}
---@private
@@ -35,43 +35,70 @@ local function get_border_size(opts)
local width = 0
if type(border) == 'string' then
- local border_size = {none = {0, 0}, single = {2, 2}, double = {2, 2}, rounded = {2, 2}, solid = {2, 2}, shadow = {1, 1}}
+ local border_size = {
+ none = { 0, 0 },
+ single = { 2, 2 },
+ double = { 2, 2 },
+ rounded = { 2, 2 },
+ solid = { 2, 2 },
+ shadow = { 1, 1 },
+ }
if border_size[border] == nil then
- error(string.format("invalid floating preview border: %s. :help vim.api.nvim_open_win()", vim.inspect(border)))
+ error(
+ string.format(
+ 'invalid floating preview border: %s. :help vim.api.nvim_open_win()',
+ vim.inspect(border)
+ )
+ )
end
height, width = unpack(border_size[border])
else
if 8 % #border ~= 0 then
- error(string.format("invalid floating preview border: %s. :help vim.api.nvim_open_win()", vim.inspect(border)))
+ error(
+ string.format(
+ 'invalid floating preview border: %s. :help vim.api.nvim_open_win()',
+ vim.inspect(border)
+ )
+ )
end
---@private
local function border_width(id)
id = (id - 1) % #border + 1
- if type(border[id]) == "table" then
+ if type(border[id]) == 'table' then
-- border specified as a table of <character, highlight group>
return vim.fn.strdisplaywidth(border[id][1])
- elseif type(border[id]) == "string" then
+ elseif type(border[id]) == 'string' then
-- border specified as a list of border characters
return vim.fn.strdisplaywidth(border[id])
end
- error(string.format("invalid floating preview border: %s. :help vim.api.nvim_open_win()", vim.inspect(border)))
+ error(
+ string.format(
+ 'invalid floating preview border: %s. :help vim.api.nvim_open_win()',
+ vim.inspect(border)
+ )
+ )
end
---@private
local function border_height(id)
id = (id - 1) % #border + 1
- if type(border[id]) == "table" then
+ if type(border[id]) == 'table' then
-- border specified as a table of <character, highlight group>
return #border[id][1] > 0 and 1 or 0
- elseif type(border[id]) == "string" then
+ elseif type(border[id]) == 'string' then
-- border specified as a list of border characters
return #border[id] > 0 and 1 or 0
end
- error(string.format("invalid floating preview border: %s. :help vim.api.nvim_open_win()", vim.inspect(border)))
+ error(
+ string.format(
+ 'invalid floating preview border: %s. :help vim.api.nvim_open_win()',
+ vim.inspect(border)
+ )
+ )
end
- height = height + border_height(2) -- top
- height = height + border_height(6) -- bottom
- width = width + border_width(4) -- right
- width = width + border_width(8) -- left
+ height = height + border_height(2) -- top
+ height = height + border_height(6) -- bottom
+ width = width + border_width(4) -- right
+ width = width + border_width(8) -- left
end
return { height = height, width = width }
@@ -89,9 +116,15 @@ end
---@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16
---@return number `encoding` index of `index` in `line`
function M._str_utfindex_enc(line, index, encoding)
- if not encoding then encoding = 'utf-16' end
+ if not encoding then
+ encoding = 'utf-16'
+ end
if encoding == 'utf-8' then
- if index then return index else return #line end
+ if index then
+ return index
+ else
+ return #line
+ end
elseif encoding == 'utf-16' then
local _, col16 = vim.str_utfindex(line, index)
return col16
@@ -99,7 +132,7 @@ function M._str_utfindex_enc(line, index, encoding)
local col32, _ = vim.str_utfindex(line, index)
return col32
else
- error("Invalid encoding: " .. vim.inspect(encoding))
+ error('Invalid encoding: ' .. vim.inspect(encoding))
end
end
@@ -111,15 +144,21 @@ end
---@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16
---@return number byte (utf-8) index of `encoding` index `index` in `line`
function M._str_byteindex_enc(line, index, encoding)
- if not encoding then encoding = 'utf-16' end
+ if not encoding then
+ encoding = 'utf-16'
+ end
if encoding == 'utf-8' then
- if index then return index else return #line end
+ if index then
+ return index
+ else
+ return #line
+ end
elseif encoding == 'utf-16' then
return vim.str_byteindex(line, index, true)
elseif encoding == 'utf-32' then
return vim.str_byteindex(line, index)
else
- error("Invalid encoding: " .. vim.inspect(encoding))
+ error('Invalid encoding: ' .. vim.inspect(encoding))
end
end
@@ -142,34 +181,38 @@ function M.set_lines(lines, A, B, new_lines)
-- specifying a line number after what we would call the last line.
local i_n = math.min(B[1] + 1, #lines)
if not (i_0 >= 1 and i_0 <= #lines + 1 and i_n >= 1 and i_n <= #lines) then
- error("Invalid range: "..vim.inspect{A = A; B = B; #lines, new_lines})
+ error('Invalid range: ' .. vim.inspect({ A = A, B = B, #lines, new_lines }))
end
- local prefix = ""
- local suffix = lines[i_n]:sub(B[2]+1)
+ local prefix = ''
+ local suffix = lines[i_n]:sub(B[2] + 1)
if A[2] > 0 then
prefix = lines[i_0]:sub(1, A[2])
end
local n = i_n - i_0 + 1
if n ~= #new_lines then
- for _ = 1, n - #new_lines do table.remove(lines, i_0) end
- for _ = 1, #new_lines - n do table.insert(lines, i_0, '') end
+ for _ = 1, n - #new_lines do
+ table.remove(lines, i_0)
+ end
+ for _ = 1, #new_lines - n do
+ table.insert(lines, i_0, '')
+ end
end
for i = 1, #new_lines do
lines[i - 1 + i_0] = new_lines[i]
end
if #suffix > 0 then
local i = i_0 + #new_lines - 1
- lines[i] = lines[i]..suffix
+ lines[i] = lines[i] .. suffix
end
if #prefix > 0 then
- lines[i_0] = prefix..lines[i_0]
+ lines[i_0] = prefix .. lines[i_0]
end
return lines
end
---@private
local function sort_by_key(fn)
- return function(a,b)
+ return function(a, b)
local ka, kb = fn(a), fn(b)
assert(#ka == #kb)
for i = 1, #ka do
@@ -191,18 +234,18 @@ end
---@param rows number[] zero-indexed line numbers
---@return table<number string> a table mapping rows to lines
local function get_lines(bufnr, rows)
- rows = type(rows) == "table" and rows or { rows }
+ rows = type(rows) == 'table' and rows or { rows }
-- This is needed for bufload and bufloaded
if bufnr == 0 then
- bufnr = vim.api.nvim_get_current_buf()
+ bufnr = api.nvim_get_current_buf()
end
---@private
local function buf_lines()
local lines = {}
for _, row in pairs(rows) do
- lines[row] = (vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { "" })[1]
+ lines[row] = (api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { '' })[1]
end
return lines
end
@@ -211,7 +254,7 @@ local function get_lines(bufnr, rows)
-- load the buffer if this is not a file uri
-- Custom language server protocol extensions can result in servers sending URIs with custom schemes. Plugins are able to load these via `BufReadCmd` autocmds.
- if uri:sub(1, 4) ~= "file" then
+ if uri:sub(1, 4) ~= 'file' then
vim.fn.bufload(bufnr)
return buf_lines()
end
@@ -224,8 +267,10 @@ local function get_lines(bufnr, rows)
local filename = api.nvim_buf_get_name(bufnr)
-- get the data from the file
- local fd = uv.fs_open(filename, "r", 438)
- if not fd then return "" end
+ local fd = uv.fs_open(filename, 'r', 438)
+ if not fd then
+ return ''
+ end
local stat = uv.fs_fstat(fd)
local data = uv.fs_read(fd, stat.size, 0)
uv.fs_close(fd)
@@ -242,11 +287,13 @@ local function get_lines(bufnr, rows)
local found = 0
local lnum = 0
- for line in string.gmatch(data, "([^\n]*)\n?") do
+ for line in string.gmatch(data, '([^\n]*)\n?') do
if lines[lnum] == true then
lines[lnum] = line
found = found + 1
- if found == need then break end
+ if found == need then
+ break
+ end
end
lnum = lnum + 1
end
@@ -254,13 +301,12 @@ local function get_lines(bufnr, rows)
-- change any lines we didn't find to the empty string
for i, line in pairs(lines) do
if line == true then
- lines[i] = ""
+ lines[i] = ''
end
end
return lines
end
-
---@private
--- Gets the zero-indexed line from the given buffer.
--- Works on unloaded buffers by reading the file using libuv to bypass buf reading events.
@@ -273,11 +319,10 @@ local function get_line(bufnr, row)
return get_lines(bufnr, { row })[row]
end
-
---@private
--- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position
--- Returns a zero-indexed column, since set_lines() does the conversion to
----@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to utf-16
+---@param offset_encoding string utf-8|utf-16|utf-32
--- 1-indexed
local function get_line_byte_from_position(bufnr, position, offset_encoding)
-- LSP's line and characters are 0-indexed
@@ -286,7 +331,7 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding)
-- When on the first character, we can ignore the difference between byte and
-- character
if col > 0 then
- local line = get_line(bufnr, position.line)
+ local line = get_line(bufnr, position.line) or ''
local ok, result
ok, result = pcall(_str_byteindex_enc, line, col, offset_encoding)
if ok then
@@ -300,54 +345,27 @@ end
--- Process and return progress reports from lsp server
---@private
function M.get_progress_messages()
-
local new_messages = {}
- local msg_remove = {}
local progress_remove = {}
for _, client in ipairs(vim.lsp.get_active_clients()) do
- local messages = client.messages
- local data = messages
- for token, ctx in pairs(data.progress) do
-
- local new_report = {
- name = data.name,
- title = ctx.title or "empty title",
- message = ctx.message,
- percentage = ctx.percentage,
- done = ctx.done,
- progress = true,
- }
- table.insert(new_messages, new_report)
-
- if ctx.done then
- table.insert(progress_remove, {client = client, token = token})
- end
- end
-
- for i, msg in ipairs(data.messages) do
- if msg.show_once then
- msg.shown = msg.shown + 1
- if msg.shown > 1 then
- table.insert(msg_remove, {client = client, idx = i})
- end
- end
-
- table.insert(new_messages, {name = data.name, content = msg.content})
- end
+ local messages = client.messages
+ local data = messages
+ for token, ctx in pairs(data.progress) do
+ local new_report = {
+ name = data.name,
+ title = ctx.title or 'empty title',
+ message = ctx.message,
+ percentage = ctx.percentage,
+ done = ctx.done,
+ progress = true,
+ }
+ table.insert(new_messages, new_report)
- if next(data.status) ~= nil then
- table.insert(new_messages, {
- name = data.name,
- content = data.status.content,
- uri = data.status.uri,
- status = true
- })
+ if ctx.done then
+ table.insert(progress_remove, { client = client, token = token })
end
- for _, item in ipairs(msg_remove) do
- table.remove(client.messages, item.idx)
end
-
end
for _, item in ipairs(progress_remove) do
@@ -360,16 +378,17 @@ end
--- Applies a list of text edits to a buffer.
---@param text_edits table list of `TextEdit` objects
---@param bufnr number Buffer id
----@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to encoding of first client of `bufnr`
+---@param offset_encoding string utf-8|utf-16|utf-32
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit
function M.apply_text_edits(text_edits, bufnr, offset_encoding)
- validate {
- text_edits = { text_edits, 't', false };
- bufnr = { bufnr, 'number', false };
- offset_encoding = { offset_encoding, 'string', true };
- }
- offset_encoding = offset_encoding or M._get_offset_encoding(bufnr)
- if not next(text_edits) then return end
+ validate({
+ text_edits = { text_edits, 't', false },
+ bufnr = { bufnr, 'number', false },
+ offset_encoding = { offset_encoding, 'string', false },
+ })
+ if not next(text_edits) then
+ return
+ end
if not api.nvim_buf_is_loaded(bufnr) then
vim.fn.bufload(bufnr)
end
@@ -381,7 +400,11 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
index = index + 1
text_edit._index = index
- if text_edit.range.start.line > text_edit.range['end'].line or text_edit.range.start.line == text_edit.range['end'].line and text_edit.range.start.character > text_edit.range['end'].character then
+ if
+ text_edit.range.start.line > text_edit.range['end'].line
+ or text_edit.range.start.line == text_edit.range['end'].line
+ and text_edit.range.start.character > text_edit.range['end'].character
+ then
local start = text_edit.range.start
text_edit.range.start = text_edit.range['end']
text_edit.range['end'] = start
@@ -402,28 +425,9 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
end
end)
- -- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't accept it so we should fix it here.
- local has_eol_text_edit = false
- local max = vim.api.nvim_buf_line_count(bufnr)
- local len = _str_utfindex_enc(vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '', nil, offset_encoding)
- text_edits = vim.tbl_map(function(text_edit)
- if max <= text_edit.range.start.line then
- text_edit.range.start.line = max - 1
- text_edit.range.start.character = len
- text_edit.newText = '\n' .. text_edit.newText
- has_eol_text_edit = true
- end
- if max <= text_edit.range['end'].line then
- text_edit.range['end'].line = max - 1
- text_edit.range['end'].character = len
- has_eol_text_edit = true
- end
- return text_edit
- end, text_edits)
-
-- Some LSP servers are depending on the VSCode behavior.
-- The VSCode will re-locate the cursor position after applying TextEdit so we also do it.
- local is_current_buf = vim.api.nvim_get_current_buf() == bufnr
+ local is_current_buf = api.nvim_get_current_buf() == bufnr
local cursor = (function()
if not is_current_buf then
return {
@@ -431,7 +435,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
col = -1,
}
end
- local cursor = vim.api.nvim_win_get_cursor(0)
+ local cursor = api.nvim_win_get_cursor(0)
return {
row = cursor[1] - 1,
col = cursor[2],
@@ -440,16 +444,38 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
-- Apply text edits.
local is_cursor_fixed = false
+ local has_eol_text_edit = false
for _, text_edit in ipairs(text_edits) do
+ -- Normalize line ending
+ text_edit.newText, _ = string.gsub(text_edit.newText, '\r\n?', '\n')
+
+ -- Convert from LSP style ranges to Neovim style ranges.
local e = {
start_row = text_edit.range.start.line,
- start_col = get_line_byte_from_position(bufnr, text_edit.range.start),
+ start_col = get_line_byte_from_position(bufnr, text_edit.range.start, offset_encoding),
end_row = text_edit.range['end'].line,
- end_col = get_line_byte_from_position(bufnr, text_edit.range['end']),
- text = vim.split(text_edit.newText, '\n', true),
+ end_col = get_line_byte_from_position(bufnr, text_edit.range['end'], offset_encoding),
+ text = split(text_edit.newText, '\n', true),
}
- vim.api.nvim_buf_set_text(bufnr, e.start_row, e.start_col, e.end_row, e.end_col, e.text)
+ -- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't accept it so we should fix it here.
+ local max = api.nvim_buf_line_count(bufnr)
+ if max <= e.start_row or max <= e.end_row then
+ local len = #(get_line(bufnr, max - 1) or '')
+ if max <= e.start_row then
+ e.start_row = max - 1
+ e.start_col = len
+ table.insert(e.text, 1, '')
+ end
+ if max <= e.end_row then
+ e.end_row = max - 1
+ e.end_col = len
+ end
+ has_eol_text_edit = true
+ end
+ api.nvim_buf_set_text(bufnr, e.start_row, e.start_col, e.end_row, e.end_col, e.text)
+
+ -- Fix cursor position.
local row_count = (e.end_row - e.start_row) + 1
if e.end_row < cursor.row then
cursor.row = cursor.row + (#e.text - row_count)
@@ -464,21 +490,28 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
end
end
+ local max = api.nvim_buf_line_count(bufnr)
+
+ -- Apply fixed cursor position.
if is_cursor_fixed then
local is_valid_cursor = true
- is_valid_cursor = is_valid_cursor and cursor.row < vim.api.nvim_buf_line_count(bufnr)
- is_valid_cursor = is_valid_cursor and cursor.col <= #(vim.api.nvim_buf_get_lines(bufnr, cursor.row, cursor.row + 1, false)[1] or '')
+ is_valid_cursor = is_valid_cursor and cursor.row < max
+ is_valid_cursor = is_valid_cursor and cursor.col <= #(get_line(bufnr, max - 1) or '')
if is_valid_cursor then
- vim.api.nvim_win_set_cursor(0, { cursor.row + 1, cursor.col })
+ api.nvim_win_set_cursor(0, { cursor.row + 1, cursor.col })
end
end
-- Remove final line if needed
local fix_eol = has_eol_text_edit
- fix_eol = fix_eol and api.nvim_buf_get_option(bufnr, 'fixeol')
- fix_eol = fix_eol and (vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '') == ''
+ fix_eol = fix_eol
+ and (
+ api.nvim_buf_get_option(bufnr, 'eol')
+ or (api.nvim_buf_get_option(bufnr, 'fixeol') and not api.nvim_buf_get_option(bufnr, 'binary'))
+ )
+ fix_eol = fix_eol and get_line(bufnr, max - 1) == ''
if fix_eol then
- vim.api.nvim_buf_set_lines(bufnr, -2, -1, false, {})
+ api.nvim_buf_set_lines(bufnr, -2, -1, false, {})
end
end
@@ -514,9 +547,15 @@ end
---@param text_document_edit table: a `TextDocumentEdit` object
---@param index number: Optional index of the edit, if from a list of edits (or nil, if not from a list)
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit
-function M.apply_text_document_edit(text_document_edit, index)
+function M.apply_text_document_edit(text_document_edit, index, offset_encoding)
local text_document = text_document_edit.textDocument
local bufnr = vim.uri_to_bufnr(text_document.uri)
+ if offset_encoding == nil then
+ vim.notify_once(
+ 'apply_text_document_edit must be called with valid offset encoding',
+ vim.log.levels.WARN
+ )
+ end
-- For lists of text document edits,
-- do not check the version after the first edit.
@@ -527,15 +566,20 @@ function M.apply_text_document_edit(text_document_edit, index)
-- `VersionedTextDocumentIdentifier`s version may be null
-- https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier
- if should_check_version and (text_document.version
+ if
+ should_check_version
+ and (
+ text_document.version
and text_document.version > 0
and M.buf_versions[bufnr]
- and M.buf_versions[bufnr] > text_document.version) then
- print("Buffer ", text_document.uri, " newer than edits.")
+ and M.buf_versions[bufnr] > text_document.version
+ )
+ then
+ print('Buffer ', text_document.uri, ' newer than edits.')
return
end
- M.apply_text_edits(text_document_edit.edits, bufnr)
+ M.apply_text_edits(text_document_edit.edits, bufnr, offset_encoding)
end
--- Parses snippets in a completion entry.
@@ -567,16 +611,16 @@ end
--- precedence is as follows: textEdit.newText > insertText > label
--see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
local function get_completion_word(item)
- if item.textEdit ~= nil and item.textEdit.newText ~= nil and item.textEdit.newText ~= "" then
+ if item.textEdit ~= nil and item.textEdit.newText ~= nil and item.textEdit.newText ~= '' then
local insert_text_format = protocol.InsertTextFormat[item.insertTextFormat]
- if insert_text_format == "PlainText" or insert_text_format == nil then
+ if insert_text_format == 'PlainText' or insert_text_format == nil then
return item.textEdit.newText
else
return M.parse_snippet(item.textEdit.newText)
end
- elseif item.insertText ~= nil and item.insertText ~= "" then
+ elseif item.insertText ~= nil and item.insertText ~= '' then
local insert_text_format = protocol.InsertTextFormat[item.insertTextFormat]
- if insert_text_format == "PlainText" or insert_text_format == nil then
+ if insert_text_format == 'PlainText' or insert_text_format == nil then
return item.insertText
else
return M.parse_snippet(item.insertText)
@@ -604,7 +648,7 @@ end
---@returns (`vim.lsp.protocol.completionItemKind`)
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
function M._get_completion_item_kind_name(completion_item_kind)
- return protocol.CompletionItemKind[completion_item_kind] or "Unknown"
+ return protocol.CompletionItemKind[completion_item_kind] or 'Unknown'
end
--- Turns the result of a `textDocument/completion` request into vim-compatible
@@ -635,7 +679,7 @@ function M.text_document_completion_list_to_complete_items(result, prefix)
info = documentation
elseif type(documentation) == 'table' and type(documentation.value) == 'string' then
info = documentation.value
- -- else
+ -- else
-- TODO(ashkan) Validation handling here?
end
end
@@ -653,9 +697,9 @@ function M.text_document_completion_list_to_complete_items(result, prefix)
user_data = {
nvim = {
lsp = {
- completion_item = completion_item
- }
- }
+ completion_item = completion_item,
+ },
+ },
},
})
end
@@ -663,6 +707,15 @@ function M.text_document_completion_list_to_complete_items(result, prefix)
return matches
end
+---@private
+--- Like vim.fn.bufwinid except it works across tabpages.
+local function bufwinid(bufnr)
+ for _, win in ipairs(api.nvim_list_wins()) do
+ if api.nvim_win_get_buf(win) == bufnr then
+ return win
+ end
+ end
+end
--- Rename old_fname to new_fname
---
@@ -671,7 +724,7 @@ end
-- ignoreIfExists? bool
function M.rename(old_fname, new_fname, opts)
opts = opts or {}
- local target_exists = vim.loop.fs_stat(new_fname) ~= nil
+ local target_exists = uv.fs_stat(new_fname) ~= nil
if target_exists and not opts.overwrite or opts.ignoreIfExists then
vim.notify('Rename target already exists. Skipping rename.')
return
@@ -688,10 +741,9 @@ function M.rename(old_fname, new_fname, opts)
assert(ok, err)
local newbuf = vim.fn.bufadd(new_fname)
- for _, win in pairs(api.nvim_list_wins()) do
- if api.nvim_win_get_buf(win) == oldbuf then
- api.nvim_win_set_buf(win, newbuf)
- end
+ local win = bufwinid(oldbuf)
+ if win then
+ api.nvim_win_set_buf(win, newbuf)
end
api.nvim_buf_delete(oldbuf, { force = true })
end
@@ -712,11 +764,11 @@ end
local function delete_file(change)
local opts = change.options or {}
local fname = vim.uri_to_fname(change.uri)
- local stat = vim.loop.fs_stat(fname)
+ local stat = uv.fs_stat(fname)
if opts.ignoreIfNotExists and not stat then
return
end
- assert(stat, "Cannot delete not existing file or folder " .. fname)
+ assert(stat, 'Cannot delete not existing file or folder ' .. fname)
local flags
if stat and stat.type == 'directory' then
flags = opts.recursive and 'rf' or 'd'
@@ -729,28 +781,30 @@ local function delete_file(change)
api.nvim_buf_delete(bufnr, { force = true })
end
-
--- Applies a `WorkspaceEdit`.
---
----@param workspace_edit (table) `WorkspaceEdit`
+---@param workspace_edit table `WorkspaceEdit`
+---@param offset_encoding string utf-8|utf-16|utf-32 (required)
--see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
-function M.apply_workspace_edit(workspace_edit)
+function M.apply_workspace_edit(workspace_edit, offset_encoding)
+ if offset_encoding == nil then
+ vim.notify_once(
+ 'apply_workspace_edit must be called with valid offset encoding',
+ vim.log.levels.WARN
+ )
+ end
if workspace_edit.documentChanges then
for idx, change in ipairs(workspace_edit.documentChanges) do
- if change.kind == "rename" then
- M.rename(
- vim.uri_to_fname(change.oldUri),
- vim.uri_to_fname(change.newUri),
- change.options
- )
+ if change.kind == 'rename' then
+ M.rename(vim.uri_to_fname(change.oldUri), vim.uri_to_fname(change.newUri), change.options)
elseif change.kind == 'create' then
create_file(change)
elseif change.kind == 'delete' then
delete_file(change)
elseif change.kind then
- error(string.format("Unsupported change: %q", vim.inspect(change)))
+ error(string.format('Unsupported change: %q', vim.inspect(change)))
else
- M.apply_text_document_edit(change, idx)
+ M.apply_text_document_edit(change, idx, offset_encoding)
end
end
return
@@ -763,7 +817,7 @@ function M.apply_workspace_edit(workspace_edit)
for uri, changes in pairs(all_changes) do
local bufnr = vim.uri_to_bufnr(uri)
- M.apply_text_edits(changes, bufnr)
+ M.apply_text_edits(changes, bufnr, offset_encoding)
end
end
@@ -782,7 +836,7 @@ function M.convert_input_to_markdown_lines(input, contents)
if type(input) == 'string' then
list_extend(contents, split_lines(input))
else
- assert(type(input) == 'table', "Expected a table for Hover.contents")
+ assert(type(input) == 'table', 'Expected a table for Hover.contents')
-- MarkupContent
if input.kind then
-- The kind can be either plaintext or markdown.
@@ -791,22 +845,22 @@ function M.convert_input_to_markdown_lines(input, contents)
-- Some servers send input.value as empty, so let's ignore this :(
local value = input.value or ''
- if input.kind == "plaintext" then
+ if input.kind == 'plaintext' then
-- wrap this in a <text></text> block so that stylize_markdown
-- can properly process it as plaintext
- value = string.format("<text>\n%s\n</text>", value)
+ value = string.format('<text>\n%s\n</text>', value)
end
-- assert(type(value) == 'string')
list_extend(contents, split_lines(value))
- -- MarkupString variation 2
+ -- MarkupString variation 2
elseif input.language then
-- Some servers send input.value as empty, so let's ignore this :(
-- assert(type(input.value) == 'string')
- table.insert(contents, "```"..input.language)
+ table.insert(contents, '```' .. input.language)
list_extend(contents, split_lines(input.value or ''))
- table.insert(contents, "```")
- -- By deduction, this must be MarkedString[]
+ table.insert(contents, '```')
+ -- By deduction, this must be MarkedString[]
else
-- Use our existing logic to handle MarkedString
for _, marked_string in ipairs(input) do
@@ -839,7 +893,8 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers
local active_hl
local active_signature = signature_help.activeSignature or 0
-- If the activeSignature is not inside the valid range, then clip it.
- if active_signature >= #signature_help.signatures then
+ -- In 3.15 of the protocol, activeSignature was allowed to be negative
+ if active_signature >= #signature_help.signatures or active_signature < 0 then
active_signature = 0
end
local signature = signature_help.signatures[active_signature + 1]
@@ -849,16 +904,16 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers
local label = signature.label
if ft then
-- wrap inside a code block so stylize_markdown can render it properly
- label = ("```%s\n%s\n```"):format(ft, label)
+ label = ('```%s\n%s\n```'):format(ft, label)
end
- vim.list_extend(contents, vim.split(label, '\n', true))
+ list_extend(contents, split(label, '\n', true))
if signature.documentation then
M.convert_input_to_markdown_lines(signature.documentation, contents)
end
if signature.parameters and #signature.parameters > 0 then
local active_parameter = (signature.activeParameter or signature_help.activeParameter or 0)
- if active_parameter < 0
- then active_parameter = 0
+ if active_parameter < 0 then
+ active_parameter = 0
end
-- If the activeParameter is > #parameters, then set it to the last
@@ -888,7 +943,7 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers
}
--]=]
if parameter.label then
- if type(parameter.label) == "table" then
+ if type(parameter.label) == 'table' then
active_hl = parameter.label
else
local offset = 1
@@ -901,9 +956,11 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers
end
for p, param in pairs(signature.parameters) do
offset = signature.label:find(param.label, offset, true)
- if not offset then break end
+ if not offset then
+ break
+ end
if p == active_parameter + 1 then
- active_hl = {offset - 1, offset + #parameter.label - 1}
+ active_hl = { offset - 1, offset + #parameter.label - 1 }
break
end
offset = offset + #param.label + 1
@@ -931,14 +988,14 @@ end
--- - zindex (string or table) override `zindex`, defaults to 50
---@returns (table) Options
function M.make_floating_popup_options(width, height, opts)
- validate {
- opts = { opts, 't', true };
- }
+ validate({
+ opts = { opts, 't', true },
+ })
opts = opts or {}
- validate {
- ["opts.offset_x"] = { opts.offset_x, 'n', true };
- ["opts.offset_y"] = { opts.offset_y, 'n', true };
- }
+ validate({
+ ['opts.offset_x'] = { opts.offset_x, 'n', true },
+ ['opts.offset_y'] = { opts.offset_y, 'n', true },
+ })
local anchor = ''
local row, col
@@ -947,20 +1004,20 @@ function M.make_floating_popup_options(width, height, opts)
local lines_below = vim.fn.winheight(0) - lines_above
if lines_above < lines_below then
- anchor = anchor..'N'
+ anchor = anchor .. 'N'
height = math.min(lines_below, height)
row = 1
else
- anchor = anchor..'S'
+ anchor = anchor .. 'S'
height = math.min(lines_above, height)
row = 0
end
if vim.fn.wincol() + width + (opts.offset_x or 0) <= api.nvim_get_option('columns') then
- anchor = anchor..'W'
+ anchor = anchor .. 'W'
col = 0
else
- anchor = anchor..'E'
+ anchor = anchor .. 'E'
col = 1
end
@@ -980,30 +1037,45 @@ end
--- Jumps to a location.
---
----@param location (`Location`|`LocationLink`)
+---@param location table (`Location`|`LocationLink`)
+---@param offset_encoding string utf-8|utf-16|utf-32 (required)
+---@param reuse_win boolean Jump to existing window if buffer is already opened.
---@returns `true` if the jump succeeded
-function M.jump_to_location(location)
+function M.jump_to_location(location, offset_encoding, reuse_win)
-- location may be Location or LocationLink
local uri = location.uri or location.targetUri
- if uri == nil then return end
+ if uri == nil then
+ return
+ end
+ if offset_encoding == nil then
+ vim.notify_once(
+ 'jump_to_location must be called with valid offset encoding',
+ vim.log.levels.WARN
+ )
+ end
local bufnr = vim.uri_to_bufnr(uri)
-- Save position in jumplist
- vim.cmd "normal! m'"
+ vim.cmd("normal! m'")
-- Push a new item into tagstack
- local from = {vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0}
- local items = {{tagname=vim.fn.expand('<cword>'), from=from}}
- vim.fn.settagstack(vim.fn.win_getid(), {items=items}, 't')
+ local from = { vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0 }
+ local items = { { tagname = vim.fn.expand('<cword>'), from = from } }
+ vim.fn.settagstack(vim.fn.win_getid(), { items = items }, 't')
--- Jump to new location (adjusting for UTF-16 encoding of characters)
- api.nvim_set_current_buf(bufnr)
- api.nvim_buf_set_option(bufnr, 'buflisted', true)
+ local win = reuse_win and bufwinid(bufnr)
+ if win then
+ api.nvim_set_current_win(win)
+ else
+ api.nvim_buf_set_option(bufnr, 'buflisted', true)
+ api.nvim_set_current_buf(bufnr)
+ end
local range = location.range or location.targetSelectionRange
local row = range.start.line
- local col = get_line_byte_from_position(bufnr, range.start)
- api.nvim_win_set_cursor(0, {row + 1, col})
+ local col = get_line_byte_from_position(bufnr, range.start, offset_encoding)
+ api.nvim_win_set_cursor(0, { row + 1, col })
-- Open folds under the cursor
- vim.cmd("normal! zv")
+ vim.cmd('normal! zv')
return true
end
@@ -1018,22 +1090,24 @@ end
function M.preview_location(location, opts)
-- location may be LocationLink or Location (more useful for the former)
local uri = location.targetUri or location.uri
- if uri == nil then return end
+ if uri == nil then
+ return
+ end
local bufnr = vim.uri_to_bufnr(uri)
if not api.nvim_buf_is_loaded(bufnr) then
vim.fn.bufload(bufnr)
end
local range = location.targetRange or location.range
- local contents = api.nvim_buf_get_lines(bufnr, range.start.line, range["end"].line+1, false)
+ local contents = api.nvim_buf_get_lines(bufnr, range.start.line, range['end'].line + 1, false)
local syntax = api.nvim_buf_get_option(bufnr, 'syntax')
- if syntax == "" then
+ if syntax == '' then
-- When no syntax is set, we use filetype as fallback. This might not result
-- in a valid syntax definition. See also ft detection in stylize_markdown.
-- An empty syntax is more common now with TreeSitter, since TS disables syntax.
syntax = api.nvim_buf_get_option(bufnr, 'filetype')
end
opts = opts or {}
- opts.focus_id = "location"
+ opts.focus_id = 'location'
return M.open_floating_preview(contents, syntax, opts)
end
@@ -1054,20 +1128,20 @@ end
--- - pad_bottom number of lines to pad contents at bottom (default 0)
---@return contents table of trimmed and padded lines
function M._trim(contents, opts)
- validate {
- contents = { contents, 't' };
- opts = { opts, 't', true };
- }
+ validate({
+ contents = { contents, 't' },
+ opts = { opts, 't', true },
+ })
opts = opts or {}
contents = M.trim_empty_lines(contents)
if opts.pad_top then
for _ = 1, opts.pad_top do
- table.insert(contents, 1, "")
+ table.insert(contents, 1, '')
end
end
if opts.pad_bottom then
for _ = 1, opts.pad_bottom do
- table.insert(contents, "")
+ table.insert(contents, '')
end
end
return contents
@@ -1080,7 +1154,7 @@ end
local function get_markdown_fences()
local fences = {}
for _, fence in pairs(vim.g.markdown_fenced_languages or {}) do
- local lang, syntax = fence:match("^(.*)=(.*)$")
+ local lang, syntax = fence:match('^(.*)=(.*)$')
if lang then
fences[lang] = syntax
end
@@ -1109,28 +1183,28 @@ end
--- - separator insert separator after code block
---@returns width,height size of float
function M.stylize_markdown(bufnr, contents, opts)
- validate {
- contents = { contents, 't' };
- opts = { opts, 't', true };
- }
+ validate({
+ contents = { contents, 't' },
+ opts = { opts, 't', true },
+ })
opts = opts or {}
-- table of fence types to {ft, begin, end}
-- when ft is nil, we get the ft from the regex match
local matchers = {
- block = {nil, "```+([a-zA-Z0-9_]*)", "```+"},
- pre = {"", "<pre>", "</pre>"},
- code = {"", "<code>", "</code>"},
- text = {"plaintex", "<text>", "</text>"},
+ block = { nil, '```+([a-zA-Z0-9_]*)', '```+' },
+ pre = { '', '<pre>', '</pre>' },
+ code = { '', '<code>', '</code>' },
+ text = { 'text', '<text>', '</text>' },
}
local match_begin = function(line)
for type, pattern in pairs(matchers) do
- local ret = line:match(string.format("^%%s*%s%%s*$", pattern[2]))
+ local ret = line:match(string.format('^%%s*%s%%s*$', pattern[2]))
if ret then
return {
type = type,
- ft = pattern[1] or ret
+ ft = pattern[1] or ret,
}
end
end
@@ -1138,7 +1212,7 @@ function M.stylize_markdown(bufnr, contents, opts)
local match_end = function(line, match)
local pattern = matchers[match.type]
- return line:match(string.format("^%%s*%s%%s*$", pattern[3]))
+ return line:match(string.format('^%%s*%s%%s*$', pattern[3]))
end
-- Clean up
@@ -1168,25 +1242,34 @@ function M.stylize_markdown(bufnr, contents, opts)
i = i + 1
end
table.insert(highlights, {
- ft = match.ft;
- start = start + 1;
- finish = #stripped;
+ ft = match.ft,
+ start = start + 1,
+ finish = #stripped,
})
-- add a separator, but not on the last line
if add_sep and i < #contents then
- table.insert(stripped, "---")
+ table.insert(stripped, '---')
markdown_lines[#stripped] = true
end
else
-- strip any empty lines or separators prior to this separator in actual markdown
- if line:match("^---+$") then
- while markdown_lines[#stripped] and (stripped[#stripped]:match("^%s*$") or stripped[#stripped]:match("^---+$")) do
+ if line:match('^---+$') then
+ while
+ markdown_lines[#stripped]
+ and (stripped[#stripped]:match('^%s*$') or stripped[#stripped]:match('^---+$'))
+ do
markdown_lines[#stripped] = false
table.remove(stripped, #stripped)
end
end
-- add the line if its not an empty line following a separator
- if not (line:match("^%s*$") and markdown_lines[#stripped] and stripped[#stripped]:match("^---+$")) then
+ if
+ not (
+ line:match('^%s*$')
+ and markdown_lines[#stripped]
+ and stripped[#stripped]:match('^---+$')
+ )
+ then
table.insert(stripped, line)
markdown_lines[#stripped] = true
end
@@ -1196,18 +1279,18 @@ function M.stylize_markdown(bufnr, contents, opts)
end
-- Compute size of float needed to show (wrapped) lines
- opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0))
+ opts.wrap_at = opts.wrap_at or (vim.wo['wrap'] and api.nvim_win_get_width(0))
local width = M._make_floating_popup_size(stripped, opts)
- local sep_line = string.rep("─", math.min(width, opts.wrap_at or width))
+ local sep_line = string.rep('─', math.min(width, opts.wrap_at or width))
for l in pairs(markdown_lines) do
- if stripped[l]:match("^---+$") then
+ if stripped[l]:match('^---+$') then
stripped[l] = sep_line
end
end
- vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped)
+ api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped)
local idx = 1
---@private
@@ -1216,24 +1299,38 @@ function M.stylize_markdown(bufnr, contents, opts)
local langs = {}
local fences = get_markdown_fences()
local function apply_syntax_to_region(ft, start, finish)
- if ft == "" then
- vim.cmd(string.format("syntax region markdownCode start=+\\%%%dl+ end=+\\%%%dl+ keepend extend", start, finish + 1))
+ if ft == '' then
+ vim.cmd(
+ string.format(
+ 'syntax region markdownCode start=+\\%%%dl+ end=+\\%%%dl+ keepend extend',
+ start,
+ finish + 1
+ )
+ )
return
end
ft = fences[ft] or ft
- local name = ft..idx
+ local name = ft .. idx
idx = idx + 1
- local lang = "@"..ft:upper()
+ local lang = '@' .. ft:upper()
if not langs[lang] then
-- HACK: reset current_syntax, since some syntax files like markdown won't load if it is already set
- pcall(vim.api.nvim_buf_del_var, bufnr, "current_syntax")
+ pcall(api.nvim_buf_del_var, bufnr, 'current_syntax')
-- TODO(ashkan): better validation before this.
- if not pcall(vim.cmd, string.format("syntax include %s syntax/%s.vim", lang, ft)) then
+ if not pcall(vim.cmd, string.format('syntax include %s syntax/%s.vim', lang, ft)) then
return
end
langs[lang] = true
end
- vim.cmd(string.format("syntax region %s start=+\\%%%dl+ end=+\\%%%dl+ contains=%s keepend", name, start, finish + 1, lang))
+ vim.cmd(
+ string.format(
+ 'syntax region %s start=+\\%%%dl+ end=+\\%%%dl+ contains=%s keepend',
+ name,
+ start,
+ finish + 1,
+ lang
+ )
+ )
end
-- needs to run in the buffer for the regions to work
@@ -1244,13 +1341,13 @@ function M.stylize_markdown(bufnr, contents, opts)
local last = 1
for _, h in ipairs(highlights) do
if last < h.start then
- apply_syntax_to_region("lsp_markdown", last, h.start - 1)
+ apply_syntax_to_region('lsp_markdown', last, h.start - 1)
end
apply_syntax_to_region(h.ft, h.start, h.finish)
last = h.finish + 1
end
if last <= #stripped then
- apply_syntax_to_region("lsp_markdown", last, #stripped)
+ apply_syntax_to_region('lsp_markdown', last, #stripped)
end
end)
@@ -1258,6 +1355,24 @@ function M.stylize_markdown(bufnr, contents, opts)
end
---@private
+--- Closes the preview window
+---
+---@param winnr number window id of preview window
+---@param bufnrs table|nil optional list of ignored buffers
+local function close_preview_window(winnr, bufnrs)
+ vim.schedule(function()
+ -- exit if we are in one of ignored buffers
+ if bufnrs and vim.tbl_contains(bufnrs, api.nvim_get_current_buf()) then
+ return
+ end
+
+ local augroup = 'preview_window_' .. winnr
+ pcall(api.nvim_del_augroup_by_name, augroup)
+ pcall(api.nvim_win_close, winnr, true)
+ end)
+end
+
+---@private
--- Creates autocommands to close a preview window when events happen.
---
---@param events table list of events
@@ -1265,49 +1380,30 @@ end
---@param bufnrs table list of buffers where the preview window will remain visible
---@see |autocmd-events|
local function close_preview_autocmd(events, winnr, bufnrs)
- local augroup = 'preview_window_'..winnr
+ local augroup = api.nvim_create_augroup('preview_window_' .. winnr, {
+ clear = true,
+ })
-- close the preview window when entered a buffer that is not
-- the floating window buffer or the buffer that spawned it
- vim.cmd(string.format([[
- augroup %s
- autocmd!
- autocmd BufEnter * lua vim.lsp.util._close_preview_window(%d, {%s})
- augroup end
- ]], augroup, winnr, table.concat(bufnrs, ',')))
+ api.nvim_create_autocmd('BufEnter', {
+ group = augroup,
+ callback = function()
+ close_preview_window(winnr, bufnrs)
+ end,
+ })
if #events > 0 then
- vim.cmd(string.format([[
- augroup %s
- autocmd %s <buffer> lua vim.lsp.util._close_preview_window(%d)
- augroup end
- ]], augroup, table.concat(events, ','), winnr))
+ api.nvim_create_autocmd(events, {
+ group = augroup,
+ buffer = bufnrs[2],
+ callback = function()
+ close_preview_window(winnr)
+ end,
+ })
end
end
----@private
---- Closes the preview window
----
----@param winnr number window id of preview window
----@param bufnrs table|nil optional list of ignored buffers
-function M._close_preview_window(winnr, bufnrs)
- vim.schedule(function()
- -- exit if we are in one of ignored buffers
- if bufnrs and vim.tbl_contains(bufnrs, api.nvim_get_current_buf()) then
- return
- end
-
- local augroup = 'preview_window_'..winnr
- vim.cmd(string.format([[
- augroup %s
- autocmd!
- augroup end
- augroup! %s
- ]], augroup, augroup))
- pcall(vim.api.nvim_win_close, winnr, true)
- end)
-end
-
---@internal
--- Computes size of float needed to show contents (with optional wrapping)
---
@@ -1320,10 +1416,10 @@ end
--- - max_height maximal height of floating window
---@returns width,height size of float
function M._make_floating_popup_size(contents, opts)
- validate {
- contents = { contents, 't' };
- opts = { opts, 't', true };
- }
+ validate({
+ contents = { contents, 't' },
+ opts = { opts, 't', true },
+ })
opts = opts or {}
local width = opts.width
@@ -1367,11 +1463,11 @@ function M._make_floating_popup_size(contents, opts)
if vim.tbl_isempty(line_widths) then
for _, line in ipairs(contents) do
local line_width = vim.fn.strdisplaywidth(line)
- height = height + math.ceil(line_width/wrap_at)
+ height = height + math.ceil(line_width / wrap_at)
end
else
for i = 1, #contents do
- height = height + math.max(1, math.ceil(line_widths[i]/wrap_at))
+ height = height + math.max(1, math.ceil(line_widths[i] / wrap_at))
end
end
end
@@ -1391,7 +1487,7 @@ end
--- - height: (number) height of floating window
--- - width: (number) width of floating window
--- - wrap: (boolean, default true) wrap long lines
---- - wrap_at: (string) character to wrap at for computing height when wrap is enabled
+--- - wrap_at: (number) character to wrap at for computing height when wrap is enabled
--- - max_width: (number) maximal width of floating window
--- - max_height: (number) maximal height of floating window
--- - pad_top: (number) number of lines to pad contents at top
@@ -1405,16 +1501,16 @@ end
---@returns bufnr,winnr buffer and window number of the newly created floating
---preview window
function M.open_floating_preview(contents, syntax, opts)
- validate {
- contents = { contents, 't' };
- syntax = { syntax, 's', true };
- opts = { opts, 't', true };
- }
+ validate({
+ contents = { contents, 't' },
+ syntax = { syntax, 's', true },
+ opts = { opts, 't', true },
+ })
opts = opts or {}
opts.wrap = opts.wrap ~= false -- wrapping by default
- opts.stylize_markdown = opts.stylize_markdown ~= false
+ opts.stylize_markdown = opts.stylize_markdown ~= false and vim.g.syntax_on ~= nil
opts.focus = opts.focus ~= false
- opts.close_events = opts.close_events or {"CursorMoved", "CursorMovedI", "InsertCharPre"}
+ opts.close_events = opts.close_events or { 'CursorMoved', 'CursorMovedI', 'InsertCharPre' }
local bufnr = api.nvim_get_current_buf()
@@ -1423,7 +1519,7 @@ function M.open_floating_preview(contents, syntax, opts)
-- Go back to previous window if we are in a focusable one
local current_winnr = api.nvim_get_current_win()
if npcall(api.nvim_win_get_var, current_winnr, opts.focus_id) then
- api.nvim_command("wincmd p")
+ api.nvim_command('wincmd p')
return bufnr, current_winnr
end
do
@@ -1431,7 +1527,7 @@ function M.open_floating_preview(contents, syntax, opts)
if win and api.nvim_win_is_valid(win) and vim.fn.pumvisible() == 0 then
-- focus and return the existing buf, win
api.nvim_set_current_win(win)
- api.nvim_command("stopinsert")
+ api.nvim_command('stopinsert')
return api.nvim_win_get_buf(win), win
end
end
@@ -1439,14 +1535,13 @@ function M.open_floating_preview(contents, syntax, opts)
-- check if another floating preview already exists for this buffer
-- and close it if needed
- local existing_float = npcall(api.nvim_buf_get_var, bufnr, "lsp_floating_preview")
+ local existing_float = npcall(api.nvim_buf_get_var, bufnr, 'lsp_floating_preview')
if existing_float and api.nvim_win_is_valid(existing_float) then
api.nvim_win_close(existing_float, true)
end
local floating_bufnr = api.nvim_create_buf(false, true)
- local do_stylize = syntax == "markdown" and opts.stylize_markdown
-
+ local do_stylize = syntax == 'markdown' and opts.stylize_markdown
-- Clean up input: trim empty lines from the end, pad
contents = M._trim(contents, opts)
@@ -1482,26 +1577,32 @@ function M.open_floating_preview(contents, syntax, opts)
api.nvim_buf_set_option(floating_bufnr, 'modifiable', false)
api.nvim_buf_set_option(floating_bufnr, 'bufhidden', 'wipe')
- api.nvim_buf_set_keymap(floating_bufnr, "n", "q", "<cmd>bdelete<cr>", {silent = true, noremap = true, nowait = true})
- close_preview_autocmd(opts.close_events, floating_winnr, {floating_bufnr, bufnr})
+ api.nvim_buf_set_keymap(
+ floating_bufnr,
+ 'n',
+ 'q',
+ '<cmd>bdelete<cr>',
+ { silent = true, noremap = true, nowait = true }
+ )
+ close_preview_autocmd(opts.close_events, floating_winnr, { floating_bufnr, bufnr })
-- save focus_id
if opts.focus_id then
api.nvim_win_set_var(floating_winnr, opts.focus_id, bufnr)
end
- api.nvim_buf_set_var(bufnr, "lsp_floating_preview", floating_winnr)
+ api.nvim_buf_set_var(bufnr, 'lsp_floating_preview', floating_winnr)
return floating_bufnr, floating_winnr
end
do --[[ References ]]
- local reference_ns = api.nvim_create_namespace("vim_lsp_references")
+ local reference_ns = api.nvim_create_namespace('vim_lsp_references')
--- Removes document highlights from a buffer.
---
---@param bufnr number Buffer id
function M.buf_clear_references(bufnr)
- validate { bufnr = {bufnr, 'n', true} }
+ validate({ bufnr = { bufnr, 'n', true } })
api.nvim_buf_clear_namespace(bufnr or 0, reference_ns, 0, -1)
end
@@ -1509,35 +1610,50 @@ do --[[ References ]]
---
---@param bufnr number Buffer id
---@param references table List of `DocumentHighlight` objects to highlight
- ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32", or nil. Defaults to `offset_encoding` of first client of `bufnr`
+ ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32".
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#documentHighlight
function M.buf_highlight_references(bufnr, references, offset_encoding)
- validate { bufnr = {bufnr, 'n', true} }
- offset_encoding = offset_encoding or M._get_offset_encoding(bufnr)
+ validate({
+ bufnr = { bufnr, 'n', true },
+ offset_encoding = { offset_encoding, 'string', false },
+ })
for _, reference in ipairs(references) do
- local start_line, start_char = reference["range"]["start"]["line"], reference["range"]["start"]["character"]
- local end_line, end_char = reference["range"]["end"]["line"], reference["range"]["end"]["character"]
-
- local start_idx = get_line_byte_from_position(bufnr, { line = start_line, character = start_char }, offset_encoding)
- local end_idx = get_line_byte_from_position(bufnr, { line = start_line, character = end_char }, offset_encoding)
+ local start_line, start_char =
+ reference['range']['start']['line'], reference['range']['start']['character']
+ local end_line, end_char =
+ reference['range']['end']['line'], reference['range']['end']['character']
+
+ local start_idx = get_line_byte_from_position(
+ bufnr,
+ { line = start_line, character = start_char },
+ offset_encoding
+ )
+ local end_idx = get_line_byte_from_position(
+ bufnr,
+ { line = start_line, character = end_char },
+ offset_encoding
+ )
local document_highlight_kind = {
- [protocol.DocumentHighlightKind.Text] = "LspReferenceText";
- [protocol.DocumentHighlightKind.Read] = "LspReferenceRead";
- [protocol.DocumentHighlightKind.Write] = "LspReferenceWrite";
+ [protocol.DocumentHighlightKind.Text] = 'LspReferenceText',
+ [protocol.DocumentHighlightKind.Read] = 'LspReferenceRead',
+ [protocol.DocumentHighlightKind.Write] = 'LspReferenceWrite',
}
- local kind = reference["kind"] or protocol.DocumentHighlightKind.Text
- highlight.range(bufnr,
- reference_ns,
- document_highlight_kind[kind],
- { start_line, start_idx },
- { end_line, end_idx })
+ local kind = reference['kind'] or protocol.DocumentHighlightKind.Text
+ highlight.range(
+ bufnr,
+ reference_ns,
+ document_highlight_kind[kind],
+ { start_line, start_idx },
+ { end_line, end_idx },
+ { priority = vim.highlight.priorities.user }
+ )
end
end
end
local position_sort = sort_by_key(function(v)
- return {v.start.line, v.start.character}
+ return { v.start.line, v.start.character }
end)
--- Returns the items with the byte position calculated correctly and in sorted
@@ -1546,25 +1662,32 @@ end)
--- The result can be passed to the {list} argument of |setqflist()| or
--- |setloclist()|.
---
----@param locations (table) list of `Location`s or `LocationLink`s
+---@param locations table list of `Location`s or `LocationLink`s
+---@param offset_encoding string offset_encoding for locations utf-8|utf-16|utf-32
---@returns (table) list of items
-function M.locations_to_items(locations)
+function M.locations_to_items(locations, offset_encoding)
+ if offset_encoding == nil then
+ vim.notify_once(
+ 'locations_to_items must be called with valid offset encoding',
+ vim.log.levels.WARN
+ )
+ end
+
local items = {}
local grouped = setmetatable({}, {
__index = function(t, k)
local v = {}
rawset(t, k, v)
return v
- end;
+ end,
})
for _, d in ipairs(locations) do
-- locations may be Location or LocationLink
local uri = d.uri or d.targetUri
local range = d.range or d.targetSelectionRange
- table.insert(grouped[uri], {start = range.start})
+ table.insert(grouped[uri], { start = range.start })
end
-
local keys = vim.tbl_keys(grouped)
table.sort(keys)
-- TODO(ashkan) I wish we could do this lazily.
@@ -1587,53 +1710,24 @@ function M.locations_to_items(locations)
for _, temp in ipairs(rows) do
local pos = temp.start
local row = pos.line
- local line = lines[row] or ""
- local col = pos.character
+ local line = lines[row] or ''
+ local col = M._str_byteindex_enc(line, pos.character, offset_encoding)
table.insert(items, {
filename = filename,
lnum = row + 1,
- col = col + 1;
- text = line;
+ col = col + 1,
+ text = line,
})
end
end
return items
end
---- Fills target window's location list with given list of items.
---- Can be obtained with e.g. |vim.lsp.util.locations_to_items()|.
---- Defaults to current window.
----
----@deprecated Use |setloclist()|
----
----@param items (table) list of items
-function M.set_loclist(items, win_id)
- vim.api.nvim_echo({{'vim.lsp.util.set_loclist is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
- vim.fn.setloclist(win_id or 0, {}, ' ', {
- title = 'Language Server';
- items = items;
- })
-end
-
---- Fills quickfix list with given list of items.
---- Can be obtained with e.g. |vim.lsp.util.locations_to_items()|.
----
----@deprecated Use |setqflist()|
----
----@param items (table) list of items
-function M.set_qflist(items)
- vim.api.nvim_echo({{'vim.lsp.util.set_qflist is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
- vim.fn.setqflist({}, ' ', {
- title = 'Language Server';
- items = items;
- })
-end
-
-- According to LSP spec, if the client set "symbolKind.valueSet",
-- the client must handle it properly even if it receives a value outside the specification.
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
function M._get_symbol_kind_name(symbol_kind)
- return protocol.SymbolKind[symbol_kind] or "Unknown"
+ return protocol.SymbolKind[symbol_kind] or 'Unknown'
end
--- Converts symbols to quickfix list items.
@@ -1651,17 +1745,17 @@ function M.symbols_to_items(symbols, bufnr)
lnum = range.start.line + 1,
col = range.start.character + 1,
kind = kind,
- text = '['..kind..'] '..symbol.name,
+ text = '[' .. kind .. '] ' .. symbol.name,
})
elseif symbol.selectionRange then -- DocumentSymbole type
local kind = M._get_symbol_kind_name(symbol.kind)
table.insert(_items, {
-- bufnr = _bufnr,
- filename = vim.api.nvim_buf_get_name(_bufnr),
+ filename = api.nvim_buf_get_name(_bufnr),
lnum = symbol.selectionRange.start.line + 1,
col = symbol.selectionRange.start.character + 1,
kind = kind,
- text = '['..kind..'] '..symbol.name
+ text = '[' .. kind .. '] ' .. symbol.name,
})
if symbol.children then
for _, v in ipairs(_symbols_to_items(symbol.children, _items, _bufnr)) do
@@ -1695,7 +1789,7 @@ function M.trim_empty_lines(lines)
break
end
end
- return vim.list_extend({}, lines, start, finish)
+ return list_extend({}, lines, start, finish)
end
--- Accepts markdown lines and tries to reduce them to a filetype if they
@@ -1706,12 +1800,12 @@ end
---@param lines (table) list of lines
---@returns (string) filetype or 'markdown' if it was unchanged.
function M.try_trim_markdown_code_blocks(lines)
- local language_id = lines[1]:match("^```(.*)")
+ local language_id = lines[1]:match('^```(.*)')
if language_id then
local has_inner_code_fence = false
for i = 2, (#lines - 1) do
local line = lines[i]
- if line:sub(1,3) == '```' then
+ if line:sub(1, 3) == '```' then
has_inner_code_fence = true
break
end
@@ -1731,18 +1825,18 @@ end
---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window`
local function make_position_param(window, offset_encoding)
window = window or 0
- local buf = vim.api.nvim_win_get_buf(window)
+ local buf = api.nvim_win_get_buf(window)
local row, col = unpack(api.nvim_win_get_cursor(window))
offset_encoding = offset_encoding or M._get_offset_encoding(buf)
row = row - 1
- local line = api.nvim_buf_get_lines(buf, row, row+1, true)[1]
+ local line = api.nvim_buf_get_lines(buf, row, row + 1, true)[1]
if not line then
- return { line = 0; character = 0; }
+ return { line = 0, character = 0 }
end
col = _str_utfindex_enc(line, col, offset_encoding)
- return { line = row; character = col; }
+ return { line = row, character = col }
end
--- Creates a `TextDocumentPositionParams` object for the current buffer and cursor position.
@@ -1753,11 +1847,11 @@ end
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams
function M.make_position_params(window, offset_encoding)
window = window or 0
- local buf = vim.api.nvim_win_get_buf(window)
+ local buf = api.nvim_win_get_buf(window)
offset_encoding = offset_encoding or M._get_offset_encoding(buf)
return {
- textDocument = M.make_text_document_params(buf);
- position = make_position_param(window, offset_encoding)
+ textDocument = M.make_text_document_params(buf),
+ position = make_position_param(window, offset_encoding),
}
end
@@ -1765,18 +1859,30 @@ end
---@param bufnr (number) buffer handle or 0 for current, defaults to current
---@returns (string) encoding first client if there is one, nil otherwise
function M._get_offset_encoding(bufnr)
- validate {
- bufnr = {bufnr, 'n', true};
- }
+ validate({
+ bufnr = { bufnr, 'n', true },
+ })
local offset_encoding
for _, client in pairs(vim.lsp.buf_get_clients(bufnr)) do
- local this_offset_encoding = client.offset_encoding or "utf-16"
+ if client.offset_encoding == nil then
+ vim.notify_once(
+ string.format(
+ 'Client (id: %s) offset_encoding is nil. Do not unset offset_encoding.',
+ client.id
+ ),
+ vim.log.levels.ERROR
+ )
+ end
+ local this_offset_encoding = client.offset_encoding
if not offset_encoding then
offset_encoding = this_offset_encoding
elseif offset_encoding ~= this_offset_encoding then
- vim.notify("warning: multiple different client offset_encodings detected for buffer, this is not supported yet", vim.log.levels.WARN)
+ vim.notify(
+ 'warning: multiple different client offset_encodings detected for buffer, this is not supported yet',
+ vim.log.levels.WARN
+ )
end
end
@@ -1793,12 +1899,12 @@ end
---@returns { textDocument = { uri = `current_file_uri` }, range = { start =
---`current_position`, end = `current_position` } }
function M.make_range_params(window, offset_encoding)
- local buf = vim.api.nvim_win_get_buf(window or 0)
+ local buf = api.nvim_win_get_buf(window or 0)
offset_encoding = offset_encoding or M._get_offset_encoding(buf)
local position = make_position_param(window, offset_encoding)
return {
textDocument = M.make_text_document_params(buf),
- range = { start = position; ["end"] = position; }
+ range = { start = position, ['end'] = position },
}
end
@@ -1814,12 +1920,12 @@ end
---@returns { textDocument = { uri = `current_file_uri` }, range = { start =
---`start_position`, end = `end_position` } }
function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding)
- validate {
- start_pos = {start_pos, 't', true};
- end_pos = {end_pos, 't', true};
- offset_encoding = {offset_encoding, 's', true};
- }
- bufnr = bufnr or vim.api.nvim_get_current_buf()
+ validate({
+ start_pos = { start_pos, 't', true },
+ end_pos = { end_pos, 't', true },
+ offset_encoding = { offset_encoding, 's', true },
+ })
+ bufnr = bufnr or api.nvim_get_current_buf()
offset_encoding = offset_encoding or M._get_offset_encoding(bufnr)
local A = list_extend({}, start_pos or api.nvim_buf_get_mark(bufnr, '<'))
local B = list_extend({}, end_pos or api.nvim_buf_get_mark(bufnr, '>'))
@@ -1828,10 +1934,10 @@ function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding)
B[1] = B[1] - 1
-- account for offset_encoding.
if A[2] > 0 then
- A = {A[1], M.character_offset(bufnr, A[1], A[2], offset_encoding)}
+ A = { A[1], M.character_offset(bufnr, A[1], A[2], offset_encoding) }
end
if B[2] > 0 then
- B = {B[1], M.character_offset(bufnr, B[1], B[2], offset_encoding)}
+ B = { B[1], M.character_offset(bufnr, B[1], B[2], offset_encoding) }
end
-- we need to offset the end character position otherwise we loose the last
-- character of the selection, as LSP end position is exclusive
@@ -1842,9 +1948,9 @@ function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding)
return {
textDocument = M.make_text_document_params(bufnr),
range = {
- start = {line = A[1], character = A[2]},
- ['end'] = {line = B[1], character = B[2]}
- }
+ start = { line = A[1], character = A[2] },
+ ['end'] = { line = B[1], character = B[2] },
+ },
}
end
@@ -1861,34 +1967,34 @@ end
---@param added
---@param removed
function M.make_workspace_params(added, removed)
- return { event = { added = added; removed = removed; } }
+ return { event = { added = added, removed = removed } }
end
---- Returns visual width of tabstop.
+--- Returns indentation size.
---
----@see |softtabstop|
----@param bufnr (optional, number): Buffer handle, defaults to current
----@returns (number) tabstop visual width
+---@see |shiftwidth|
+---@param bufnr (number|nil): Buffer handle, defaults to current
+---@returns (number) indentation size
function M.get_effective_tabstop(bufnr)
- validate { bufnr = {bufnr, 'n', true} }
+ validate({ bufnr = { bufnr, 'n', true } })
local bo = bufnr and vim.bo[bufnr] or vim.bo
- local sts = bo.softtabstop
- return (sts > 0 and sts) or (sts < 0 and bo.shiftwidth) or bo.tabstop
+ local sw = bo.shiftwidth
+ return (sw == 0 and bo.tabstop) or sw
end
--- Creates a `DocumentFormattingParams` object for the current buffer and cursor position.
---
----@param options Table with valid `FormattingOptions` entries
+---@param options table|nil with valid `FormattingOptions` entries
---@returns `DocumentFormattingParams` object
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
function M.make_formatting_params(options)
- validate { options = {options, 't', true} }
+ validate({ options = { options, 't', true } })
options = vim.tbl_extend('keep', options or {}, {
- tabSize = M.get_effective_tabstop();
- insertSpaces = vim.bo.expandtab;
+ tabSize = M.get_effective_tabstop(),
+ insertSpaces = vim.bo.expandtab,
})
return {
- textDocument = { uri = vim.uri_from_bufnr(0) };
- options = options;
+ textDocument = { uri = vim.uri_from_bufnr(0) },
+ options = options,
}
end
@@ -1901,7 +2007,12 @@ end
---@returns (number, number) `offset_encoding` index of the character in line {row} column {col} in buffer {buf}
function M.character_offset(buf, row, col, offset_encoding)
local line = get_line(buf, row)
- offset_encoding = offset_encoding or M._get_offset_encoding(buf)
+ if offset_encoding == nil then
+ vim.notify_once(
+ 'character_offset must be called with valid offset encoding',
+ vim.log.levels.WARN
+ )
+ end
-- If the col is past the EOL, use the line length.
if col > #line then
return _str_utfindex_enc(line, nil, offset_encoding)
@@ -1917,8 +2028,8 @@ end
function M.lookup_section(settings, section)
for part in vim.gsplit(section, '.', true) do
settings = settings[part]
- if not settings then
- return
+ if settings == nil then
+ return vim.NIL
end
end
return settings
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index e170befa4c..d6c3e25b3b 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -1,8 +1,10 @@
-- Functions shared by Nvim and its test-suite.
--
--- The singular purpose of this module is to share code with the Nvim
--- test-suite. If, in the future, Nvim itself is used to run the test-suite
--- instead of "vanilla Lua", these functions could move to src/nvim/lua/vim.lua
+-- These are "pure" lua functions not depending of the state of the editor.
+-- Thus they should always be available whenever nvim-related lua code is run,
+-- regardless if it is code in the editor itself, or in worker threads/processes,
+-- or the test suite. (Eventually the test suite will be run in a worker process,
+-- so this wouldn't be a separate case to consider)
local vim = vim or {}
@@ -13,8 +15,8 @@ local vim = vim or {}
--- copied and will throw an error.
---
---@param orig table Table to copy
----@returns New table of copied keys and (nested) values.
-function vim.deepcopy(orig) end -- luacheck: no unused
+---@return table Table of copied keys and (nested) values.
+function vim.deepcopy(orig) end -- luacheck: no unused
vim.deepcopy = (function()
local function _id(v)
return v
@@ -22,7 +24,9 @@ vim.deepcopy = (function()
local deepcopy_funcs = {
table = function(orig, cache)
- if cache[orig] then return cache[orig] end
+ if cache[orig] then
+ return cache[orig]
+ end
local copy = {}
cache[orig] = copy
@@ -44,7 +48,7 @@ vim.deepcopy = (function()
if f then
return f(orig, cache or {})
else
- error("Cannot deepcopy object of type "..type(orig))
+ error('Cannot deepcopy object of type ' .. type(orig))
end
end
end)()
@@ -55,19 +59,19 @@ end)()
---@see https://www.lua.org/pil/20.2.html
---@see http://lua-users.org/wiki/StringLibraryTutorial
---
----@param s String to split
----@param sep Separator string or pattern
----@param plain If `true` use `sep` literally (passed to String.find)
----@returns Iterator over the split components
+---@param s string String to split
+---@param sep string Separator or pattern
+---@param plain boolean If `true` use `sep` literally (passed to string.find)
+---@return function Iterator over the split components
function vim.gsplit(s, sep, plain)
- vim.validate{s={s,'s'},sep={sep,'s'},plain={plain,'b',true}}
+ vim.validate({ s = { s, 's' }, sep = { sep, 's' }, plain = { plain, 'b', true } })
local start = 1
local done = false
local function _pass(i, j, ...)
if i then
- assert(j+1 > start, "Infinite loop detected")
+ assert(j + 1 > start, 'Infinite loop detected')
local seg = s:sub(start, i - 1)
start = j + 1
return seg, ...
@@ -85,7 +89,7 @@ function vim.gsplit(s, sep, plain)
if start == #s then
done = true
end
- return _pass(start+1, start)
+ return _pass(start + 1, start)
end
return _pass(s:find(sep, start, plain))
end
@@ -103,13 +107,13 @@ end
---
---@see |vim.gsplit()|
---
----@param s String to split
----@param sep Separator string or pattern
----@param kwargs Keyword arguments:
+---@param s string String to split
+---@param sep string Separator or pattern
+---@param kwargs table Keyword arguments:
--- - plain: (boolean) If `true` use `sep` literally (passed to string.find)
--- - trimempty: (boolean) If `true` remove empty items from the front
--- and back of the list
----@returns List-like table of the split components.
+---@return table List of split components
function vim.split(s, sep, kwargs)
local plain
local trimempty = false
@@ -117,7 +121,7 @@ function vim.split(s, sep, kwargs)
-- Support old signature for backward compatibility
plain = kwargs
else
- vim.validate { kwargs = {kwargs, 't', true} }
+ vim.validate({ kwargs = { kwargs, 't', true } })
kwargs = kwargs or {}
plain = kwargs.plain
trimempty = kwargs.trimempty
@@ -126,7 +130,7 @@ function vim.split(s, sep, kwargs)
local t = {}
local skip = trimempty
for c in vim.gsplit(s, sep, plain) do
- if c ~= "" then
+ if c ~= '' then
skip = false
end
@@ -137,7 +141,7 @@ function vim.split(s, sep, kwargs)
if trimempty then
for i = #t, 1, -1 do
- if t[i] ~= "" then
+ if t[i] ~= '' then
break
end
table.remove(t, i)
@@ -152,10 +156,10 @@ end
---
---@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua
---
----@param t Table
----@returns list of keys
+---@param t table Table
+---@return table List of keys
function vim.tbl_keys(t)
- assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
+ assert(type(t) == 'table', string.format('Expected table, got %s', type(t)))
local keys = {}
for k, _ in pairs(t) do
@@ -167,10 +171,10 @@ end
--- Return a list of all values used in a table.
--- However, the order of the return table of values is not guaranteed.
---
----@param t Table
----@returns list of values
+---@param t table Table
+---@return table List of values
function vim.tbl_values(t)
- assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
+ assert(type(t) == 'table', string.format('Expected table, got %s', type(t)))
local values = {}
for _, v in pairs(t) do
@@ -181,10 +185,11 @@ end
--- Apply a function to all values of a table.
---
----@param func function or callable table
----@param t table
+---@param func function|table Function or callable table
+---@param t table Table
+---@return table Table of transformed values
function vim.tbl_map(func, t)
- vim.validate{func={func,'c'},t={t,'t'}}
+ vim.validate({ func = { func, 'c' }, t = { t, 't' } })
local rettab = {}
for k, v in pairs(t) do
@@ -195,10 +200,11 @@ end
--- Filter a table using a predicate function
---
----@param func function or callable table
----@param t table
+---@param func function|table Function or callable table
+---@param t table Table
+---@return table Table of filtered values
function vim.tbl_filter(func, t)
- vim.validate{func={func,'c'},t={t,'t'}}
+ vim.validate({ func = { func, 'c' }, t = { t, 't' } })
local rettab = {}
for _, entry in pairs(t) do
@@ -211,13 +217,13 @@ end
--- Checks if a list-like (vector) table contains `value`.
---
----@param t Table to check
----@param value Value to compare
----@returns true if `t` contains `value`
+---@param t table Table to check
+---@param value any Value to compare
+---@return boolean `true` if `t` contains `value`
function vim.tbl_contains(t, value)
- vim.validate{t={t,'t'}}
+ vim.validate({ t = { t, 't' } })
- for _,v in ipairs(t) do
+ for _, v in ipairs(t) do
if v == value then
return true
end
@@ -229,25 +235,30 @@ end
---
---@see https://github.com/premake/premake-core/blob/master/src/base/table.lua
---
----@param t Table to check
+---@param t table Table to check
+---@return boolean `true` if `t` is empty
function vim.tbl_isempty(t)
- assert(type(t) == 'table', string.format("Expected table, got %s", type(t)))
+ assert(type(t) == 'table', string.format('Expected table, got %s', type(t)))
return next(t) == nil
end
---- we only merge empty tables or tables that are not a list
+--- We only merge empty tables or tables that are not a list
---@private
local function can_merge(v)
- return type(v) == "table" and (vim.tbl_isempty(v) or not vim.tbl_islist(v))
+ return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.tbl_islist(v))
end
local function tbl_extend(behavior, deep_extend, ...)
- if (behavior ~= 'error' and behavior ~= 'keep' and behavior ~= 'force') then
- error('invalid "behavior": '..tostring(behavior))
+ if behavior ~= 'error' and behavior ~= 'keep' and behavior ~= 'force' then
+ error('invalid "behavior": ' .. tostring(behavior))
end
if select('#', ...) < 2 then
- error('wrong number of arguments (given '..tostring(1 + select('#', ...))..', expected at least 3)')
+ error(
+ 'wrong number of arguments (given '
+ .. tostring(1 + select('#', ...))
+ .. ', expected at least 3)'
+ )
end
local ret = {}
@@ -257,15 +268,15 @@ local function tbl_extend(behavior, deep_extend, ...)
for i = 1, select('#', ...) do
local tbl = select(i, ...)
- vim.validate{["after the second argument"] = {tbl,'t'}}
+ vim.validate({ ['after the second argument'] = { tbl, 't' } })
if tbl then
for k, v in pairs(tbl) do
if deep_extend and can_merge(v) and can_merge(ret[k]) then
ret[k] = tbl_extend(behavior, true, ret[k], v)
elseif behavior ~= 'force' and ret[k] ~= nil then
if behavior == 'error' then
- error('key found in more than one map: '..k)
- end -- Else behavior is "keep".
+ error('key found in more than one map: ' .. k)
+ end -- Else behavior is "keep".
else
ret[k] = v
end
@@ -279,11 +290,12 @@ end
---
---@see |extend()|
---
----@param behavior Decides what to do if a key is found in more than one map:
+---@param behavior string Decides what to do if a key is found in more than one map:
--- - "error": raise an error
--- - "keep": use value from the leftmost map
--- - "force": use value from the rightmost map
----@param ... Two or more map-like tables.
+---@param ... table Two or more map-like tables
+---@return table Merged table
function vim.tbl_extend(behavior, ...)
return tbl_extend(behavior, false, ...)
end
@@ -292,25 +304,30 @@ end
---
---@see |tbl_extend()|
---
----@param behavior Decides what to do if a key is found in more than one map:
+---@param behavior string Decides what to do if a key is found in more than one map:
--- - "error": raise an error
--- - "keep": use value from the leftmost map
--- - "force": use value from the rightmost map
----@param ... Two or more map-like tables.
+---@param ... table Two or more map-like tables
+---@return table Merged table
function vim.tbl_deep_extend(behavior, ...)
return tbl_extend(behavior, true, ...)
end
--- Deep compare values for equality
---
---- Tables are compared recursively unless they both provide the `eq` methamethod.
+--- Tables are compared recursively unless they both provide the `eq` metamethod.
--- All other types are compared using the equality `==` operator.
----@param a first value
----@param b second value
----@returns `true` if values are equals, else `false`.
+---@param a any First value
+---@param b any Second value
+---@return boolean `true` if values are equals, else `false`
function vim.deep_equal(a, b)
- if a == b then return true end
- if type(a) ~= type(b) then return false end
+ if a == b then
+ return true
+ end
+ if type(a) ~= type(b) then
+ return false
+ end
if type(a) == 'table' then
for k, v in pairs(a) do
if not vim.deep_equal(v, b[k]) then
@@ -329,40 +346,77 @@ end
--- Add the reverse lookup values to an existing table.
--- For example:
---- `tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = 1 }`
---
---Do note that it *modifies* the input.
----@param o table The table to add the reverse to.
+--- ``tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = 1 }``
+---
+--- Note that this *modifies* the input.
+---@param o table Table to add the reverse to
+---@return table o
function vim.tbl_add_reverse_lookup(o)
local keys = vim.tbl_keys(o)
for _, k in ipairs(keys) do
local v = o[k]
if o[v] then
- error(string.format("The reverse lookup found an existing value for %q while processing key %q", tostring(v), tostring(k)))
+ error(
+ string.format(
+ 'The reverse lookup found an existing value for %q while processing key %q',
+ tostring(v),
+ tostring(k)
+ )
+ )
end
o[v] = k
end
return o
end
+--- Index into a table (first argument) via string keys passed as subsequent arguments.
+--- Return `nil` if the key does not exist.
+---
+--- Examples:
+--- <pre>
+--- vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true
+--- vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil
+--- </pre>
+---
+---@param o table Table to index
+---@param ... string Optional strings (0 or more, variadic) via which to index the table
+---
+---@return any Nested value indexed by key (if it exists), else nil
+function vim.tbl_get(o, ...)
+ local keys = { ... }
+ if #keys == 0 then
+ return
+ end
+ for i, k in ipairs(keys) do
+ if type(o[k]) ~= 'table' and next(keys, i) then
+ return nil
+ end
+ o = o[k]
+ if o == nil then
+ return
+ end
+ end
+ return o
+end
+
--- Extends a list-like table with the values of another list-like table.
---
--- NOTE: This mutates dst!
---
---@see |vim.tbl_extend()|
---
----@param dst list which will be modified and appended to.
----@param src list from which values will be inserted.
----@param start Start index on src. defaults to 1
----@param finish Final index on src. defaults to #src
----@returns dst
+---@param dst table List which will be modified and appended to
+---@param src table List from which values will be inserted
+---@param start number Start index on src. Defaults to 1
+---@param finish number Final index on src. Defaults to `#src`
+---@return table dst
function vim.list_extend(dst, src, start, finish)
- vim.validate {
- dst = {dst, 't'};
- src = {src, 't'};
- start = {start, 'n', true};
- finish = {finish, 'n', true};
- }
+ vim.validate({
+ dst = { dst, 't' },
+ src = { src, 't' },
+ start = { start, 'n', true },
+ finish = { finish, 'n', true },
+ })
for i = start or 1, finish or #src do
table.insert(dst, src[i])
end
@@ -374,15 +428,15 @@ end
---
---@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua
---
----@param t List-like table
----@returns Flattened copy of the given list-like table.
+---@param t table List-like table
+---@return table Flattened copy of the given list-like table
function vim.tbl_flatten(t)
local result = {}
local function _tbl_flatten(_t)
local n = #_t
for i = 1, n do
local v = _t[i]
- if type(v) == "table" then
+ if type(v) == 'table' then
_tbl_flatten(v)
elseif v then
table.insert(result, v)
@@ -399,8 +453,8 @@ end
--- |vim.empty_dict()| or returned as a dict-like |API| or Vimscript result,
--- for example from |rpcrequest()| or |vim.fn|.
---
----@param t Table
----@returns `true` if array-like table, else `false`.
+---@param t table Table
+---@return boolean `true` if array-like table, else `false`
function vim.tbl_islist(t)
if type(t) ~= 'table' then
return false
@@ -409,7 +463,7 @@ function vim.tbl_islist(t)
local count = 0
for k, _ in pairs(t) do
- if type(k) == "number" then
+ if type(k) == 'number' then
count = count + 1
else
return false
@@ -436,26 +490,28 @@ end
--- </pre>
---
---@see https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua
----@param t Table
----@returns Number that is the number of the value in table
+---@param t table Table
+---@return number Number of non-nil values in table
function vim.tbl_count(t)
- vim.validate{t={t,'t'}}
+ vim.validate({ t = { t, 't' } })
local count = 0
- for _ in pairs(t) do count = count + 1 end
+ for _ in pairs(t) do
+ count = count + 1
+ end
return count
end
--- Creates a copy of a table containing only elements from start to end (inclusive)
---
----@param list table table
----@param start integer Start range of slice
----@param finish integer End range of slice
----@returns Copy of table sliced from start to finish (inclusive)
+---@param list table Table
+---@param start number Start range of slice
+---@param finish number End range of slice
+---@return table Copy of table sliced from start to finish (inclusive)
function vim.list_slice(list, start, finish)
local new_list = {}
for i = start or 1, finish or #list do
- new_list[#new_list+1] = list[i]
+ new_list[#new_list + 1] = list[i]
end
return new_list
end
@@ -463,40 +519,40 @@ end
--- Trim whitespace (Lua pattern "%s") from both sides of a string.
---
---@see https://www.lua.org/pil/20.2.html
----@param s String to trim
----@returns String with whitespace removed from its beginning and end
+---@param s string String to trim
+---@return string String with whitespace removed from its beginning and end
function vim.trim(s)
- vim.validate{s={s,'s'}}
+ vim.validate({ s = { s, 's' } })
return s:match('^%s*(.*%S)') or ''
end
--- Escapes magic chars in a Lua pattern.
---
---@see https://github.com/rxi/lume
----@param s String to escape
----@returns %-escaped pattern string
+---@param s string String to escape
+---@return string %-escaped pattern string
function vim.pesc(s)
- vim.validate{s={s,'s'}}
+ vim.validate({ s = { s, 's' } })
return s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')
end
--- Tests if `s` starts with `prefix`.
---
----@param s (string) a string
----@param prefix (string) a prefix
----@return (boolean) true if `prefix` is a prefix of s
+---@param s string String
+---@param prefix string Prefix to match
+---@return boolean `true` if `prefix` is a prefix of `s`
function vim.startswith(s, prefix)
- vim.validate { s = {s, 's'}; prefix = {prefix, 's'}; }
+ vim.validate({ s = { s, 's' }, prefix = { prefix, 's' } })
return s:sub(1, #prefix) == prefix
end
--- Tests if `s` ends with `suffix`.
---
----@param s (string) a string
----@param suffix (string) a suffix
----@return (boolean) true if `suffix` is a suffix of s
+---@param s string String
+---@param suffix string Suffix to match
+---@return boolean `true` if `suffix` is a suffix of `s`
function vim.endswith(s, suffix)
- vim.validate { s = {s, 's'}; suffix = {suffix, 's'}; }
+ vim.validate({ s = { s, 's' }, suffix = { suffix, 's' } })
return #suffix == 0 or s:sub(-#suffix) == suffix
end
@@ -536,7 +592,7 @@ end
---
--- </pre>
---
----@param opt table of parameter names to validations. Each key is a parameter
+---@param opt table Names of parameters to validate. Each key is a parameter
--- name; each value is a tuple in one of these forms:
--- 1. (arg_value, type_name, optional)
--- - arg_value: argument value
@@ -550,18 +606,24 @@ end
--- only if the argument is valid. Can optionally return an additional
--- informative error message as the second returned value.
--- - msg: (optional) error string if validation fails
-function vim.validate(opt) end -- luacheck: no unused
+function vim.validate(opt) end -- luacheck: no unused
do
local type_names = {
- ['table'] = 'table', t = 'table',
- ['string'] = 'string', s = 'string',
- ['number'] = 'number', n = 'number',
- ['boolean'] = 'boolean', b = 'boolean',
- ['function'] = 'function', f = 'function',
- ['callable'] = 'callable', c = 'callable',
- ['nil'] = 'nil',
- ['thread'] = 'thread',
+ ['table'] = 'table',
+ t = 'table',
+ ['string'] = 'string',
+ s = 'string',
+ ['number'] = 'number',
+ n = 'number',
+ ['boolean'] = 'boolean',
+ b = 'boolean',
+ ['function'] = 'function',
+ f = 'function',
+ ['callable'] = 'callable',
+ c = 'callable',
+ ['nil'] = 'nil',
+ ['thread'] = 'thread',
['userdata'] = 'userdata',
}
@@ -580,21 +642,22 @@ do
return false, string.format('opt[%s]: expected table, got %s', param_name, type(spec))
end
- local val = spec[1] -- Argument value.
- local types = spec[2] -- Type name, or callable.
+ local val = spec[1] -- Argument value
+ local types = spec[2] -- Type name, or callable
local optional = (true == spec[3])
if type(types) == 'string' then
- types = {types}
+ types = { types }
end
if vim.is_callable(types) then
- -- Check user-provided validation function.
+ -- Check user-provided validation function
local valid, optional_message = types(val)
if not valid then
- local error_message = string.format("%s: expected %s, got %s", param_name, (spec[3] or '?'), tostring(val))
+ local error_message =
+ string.format('%s: expected %s, got %s', param_name, (spec[3] or '?'), tostring(val))
if optional_message ~= nil then
- error_message = error_message .. string.format(". Info: %s", optional_message)
+ error_message = error_message .. string.format('. Info: %s', optional_message)
end
return false, error_message
@@ -614,10 +677,16 @@ do
end
end
if not success then
- return false, string.format("%s: expected %s, got %s", param_name, table.concat(types, '|'), type(val))
+ return false,
+ string.format(
+ '%s: expected %s, got %s',
+ param_name,
+ table.concat(types, '|'),
+ type(val)
+ )
end
else
- return false, string.format("invalid type name: %s", tostring(types))
+ return false, string.format('invalid type name: %s', tostring(types))
end
end
@@ -633,12 +702,16 @@ do
end
--- Returns true if object `f` can be called as a function.
---
----@param f Any object
----@return true if `f` is callable, else false
+---@param f any Any object
+---@return boolean `true` if `f` is callable, else `false`
function vim.is_callable(f)
- if type(f) == 'function' then return true end
+ if type(f) == 'function' then
+ return true
+ end
local m = getmetatable(f)
- if m == nil then return false end
+ if m == nil then
+ return false
+ end
return type(m.__call) == 'function'
end
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 07f6418c0c..70f2c425ed 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -1,31 +1,32 @@
local a = vim.api
-local query = require'vim.treesitter.query'
-local language = require'vim.treesitter.language'
-local LanguageTree = require'vim.treesitter.languagetree'
+local query = require('vim.treesitter.query')
+local language = require('vim.treesitter.language')
+local LanguageTree = require('vim.treesitter.languagetree')
-- TODO(bfredl): currently we retain parsers for the lifetime of the buffer.
-- Consider use weak references to release parser if all plugins are done with
-- it.
local parsers = {}
-local M = vim.tbl_extend("error", query, language)
+local M = vim.tbl_extend('error', query, language)
M.language_version = vim._ts_get_language_version()
+M.minimum_language_version = vim._ts_get_minimum_language_version()
setmetatable(M, {
- __index = function (t, k)
- if k == "highlighter" then
- t[k] = require'vim.treesitter.highlighter'
- return t[k]
- elseif k == "language" then
- t[k] = require"vim.treesitter.language"
- return t[k]
- elseif k == "query" then
- t[k] = require"vim.treesitter.query"
- return t[k]
- end
- end
- })
+ __index = function(t, k)
+ if k == 'highlighter' then
+ t[k] = require('vim.treesitter.highlighter')
+ return t[k]
+ elseif k == 'language' then
+ t[k] = require('vim.treesitter.language')
+ return t[k]
+ elseif k == 'query' then
+ t[k] = require('vim.treesitter.query')
+ return t[k]
+ end
+ end,
+})
--- Creates a new parser.
---
@@ -62,7 +63,11 @@ function M._create_parser(bufnr, lang, opts)
self:_on_reload(...)
end
- a.nvim_buf_attach(self:source(), false, {on_bytes=bytes_cb, on_detach=detach_cb, on_reload=reload_cb, preview=true})
+ a.nvim_buf_attach(
+ self:source(),
+ false,
+ { on_bytes = bytes_cb, on_detach = detach_cb, on_reload = reload_cb, preview = true }
+ )
self:parse()
@@ -86,10 +91,10 @@ function M.get_parser(bufnr, lang, opts)
bufnr = a.nvim_get_current_buf()
end
if lang == nil then
- lang = a.nvim_buf_get_option(bufnr, "filetype")
+ lang = a.nvim_buf_get_option(bufnr, 'filetype')
end
- if parsers[bufnr] == nil then
+ if parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then
parsers[bufnr] = M._create_parser(bufnr, lang, opts)
end
@@ -104,10 +109,10 @@ end
---@param lang The language of this string
---@param opts Options to pass to the created language tree
function M.get_string_parser(str, lang, opts)
- vim.validate {
+ vim.validate({
str = { str, 'string' },
- lang = { lang, 'string' }
- }
+ lang = { lang, 'string' },
+ })
language.require_language(lang)
return LanguageTree.new(str, lang, opts)
diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua
index 53ccc6e88d..3bd59ca282 100644
--- a/runtime/lua/vim/treesitter/health.lua
+++ b/runtime/lua/vim/treesitter/health.lua
@@ -15,24 +15,24 @@ function M.check()
local report_error = vim.fn['health#report_error']
local parsers = M.list_parsers()
- report_info(string.format("Runtime ABI version : %d", ts.language_version))
+ report_info(string.format('Runtime ABI version : %d', ts.language_version))
for _, parser in pairs(parsers) do
- local parsername = vim.fn.fnamemodify(parser, ":t:r")
+ local parsername = vim.fn.fnamemodify(parser, ':t:r')
local is_loadable, ret = pcall(ts.language.require_language, parsername)
if not is_loadable then
- report_error(string.format("Impossible to load parser for %s: %s", parsername, ret))
+ report_error(string.format('Impossible to load parser for %s: %s', parsername, ret))
elseif ret then
local lang = ts.language.inspect_language(parsername)
- report_ok(string.format("Loaded parser for %s: ABI version %d",
- parsername, lang._abi_version))
+ report_ok(
+ string.format('Loaded parser for %s: ABI version %d', parsername, lang._abi_version)
+ )
else
- report_error(string.format("Unable to load parser for %s", parsername))
+ report_error(string.format('Unable to load parser for %s', parsername))
end
end
end
return M
-
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 22b528838c..e27a5fa9c3 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -1,5 +1,5 @@
local a = vim.api
-local query = require"vim.treesitter.query"
+local query = require('vim.treesitter.query')
-- support reload for quick experimentation
local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
@@ -10,64 +10,88 @@ TSHighlighter.active = TSHighlighter.active or {}
local TSHighlighterQuery = {}
TSHighlighterQuery.__index = TSHighlighterQuery
-local ns = a.nvim_create_namespace("treesitter/highlighter")
+local ns = a.nvim_create_namespace('treesitter/highlighter')
local _default_highlights = {}
local _link_default_highlight_once = function(from, to)
if not _default_highlights[from] then
_default_highlights[from] = true
- vim.cmd(string.format("highlight default link %s %s", from, to))
+ a.nvim_set_hl(0, from, { link = to, default = true })
end
return from
end
-TSHighlighter.hl_map = {
- ["error"] = "Error",
-
--- Miscs
- ["comment"] = "Comment",
- ["punctuation.delimiter"] = "Delimiter",
- ["punctuation.bracket"] = "Delimiter",
- ["punctuation.special"] = "Delimiter",
-
--- Constants
- ["constant"] = "Constant",
- ["constant.builtin"] = "Special",
- ["constant.macro"] = "Define",
- ["string"] = "String",
- ["string.regex"] = "String",
- ["string.escape"] = "SpecialChar",
- ["character"] = "Character",
- ["number"] = "Number",
- ["boolean"] = "Boolean",
- ["float"] = "Float",
-
--- Functions
- ["function"] = "Function",
- ["function.special"] = "Function",
- ["function.builtin"] = "Special",
- ["function.macro"] = "Macro",
- ["parameter"] = "Identifier",
- ["method"] = "Function",
- ["field"] = "Identifier",
- ["property"] = "Identifier",
- ["constructor"] = "Special",
-
--- Keywords
- ["conditional"] = "Conditional",
- ["repeat"] = "Repeat",
- ["label"] = "Label",
- ["operator"] = "Operator",
- ["keyword"] = "Keyword",
- ["exception"] = "Exception",
-
- ["type"] = "Type",
- ["type.builtin"] = "Type",
- ["structure"] = "Structure",
- ["include"] = "Include",
+-- If @definition.special does not exist use @definition instead
+local subcapture_fallback = {
+ __index = function(self, capture)
+ local rtn
+ local shortened = capture
+ while not rtn and shortened do
+ shortened = shortened:match('(.*)%.')
+ rtn = shortened and rawget(self, shortened)
+ end
+ rawset(self, capture, rtn or '__notfound')
+ return rtn
+ end,
}
+TSHighlighter.hl_map = setmetatable({
+ ['error'] = 'Error',
+ ['text.underline'] = 'Underlined',
+ ['todo'] = 'Todo',
+ ['debug'] = 'Debug',
+
+ -- Miscs
+ ['comment'] = 'Comment',
+ ['punctuation.delimiter'] = 'Delimiter',
+ ['punctuation.bracket'] = 'Delimiter',
+ ['punctuation.special'] = 'Delimiter',
+
+ -- Constants
+ ['constant'] = 'Constant',
+ ['constant.builtin'] = 'Special',
+ ['constant.macro'] = 'Define',
+ ['define'] = 'Define',
+ ['macro'] = 'Macro',
+ ['string'] = 'String',
+ ['string.regex'] = 'String',
+ ['string.escape'] = 'SpecialChar',
+ ['character'] = 'Character',
+ ['character.special'] = 'SpecialChar',
+ ['number'] = 'Number',
+ ['boolean'] = 'Boolean',
+ ['float'] = 'Float',
+
+ -- Functions
+ ['function'] = 'Function',
+ ['function.special'] = 'Function',
+ ['function.builtin'] = 'Special',
+ ['function.macro'] = 'Macro',
+ ['parameter'] = 'Identifier',
+ ['method'] = 'Function',
+ ['field'] = 'Identifier',
+ ['property'] = 'Identifier',
+ ['constructor'] = 'Special',
+
+ -- Keywords
+ ['conditional'] = 'Conditional',
+ ['repeat'] = 'Repeat',
+ ['label'] = 'Label',
+ ['operator'] = 'Operator',
+ ['keyword'] = 'Keyword',
+ ['exception'] = 'Exception',
+
+ ['type'] = 'Type',
+ ['type.builtin'] = 'Type',
+ ['type.qualifier'] = 'Type',
+ ['type.definition'] = 'Typedef',
+ ['storageclass'] = 'StorageClass',
+ ['structure'] = 'Structure',
+ ['include'] = 'Include',
+ ['preproc'] = 'PreProc',
+}, subcapture_fallback)
+
---@private
local function is_highlight_name(capture_name)
local firstc = string.sub(capture_name, 1, 1)
@@ -89,13 +113,13 @@ function TSHighlighterQuery.new(lang, query_string)
rawset(table, capture, id)
return id
- end
+ end,
})
if query_string then
self._query = query.parse_query(lang, query_string)
else
- self._query = query.get_query(lang, "highlights")
+ self._query = query.get_query(lang, 'highlights')
end
return self
@@ -128,17 +152,23 @@ end
function TSHighlighter.new(tree, opts)
local self = setmetatable({}, TSHighlighter)
- if type(tree:source()) ~= "number" then
- error("TSHighlighter can not be used with a string parser source.")
+ if type(tree:source()) ~= 'number' then
+ error('TSHighlighter can not be used with a string parser source.')
end
opts = opts or {}
self.tree = tree
- tree:register_cbs {
- on_changedtree = function(...) self:on_changedtree(...) end;
- on_bytes = function(...) self:on_bytes(...) end;
- on_detach = function(...) self:on_detach(...) end;
- }
+ tree:register_cbs({
+ on_changedtree = function(...)
+ self:on_changedtree(...)
+ end,
+ on_bytes = function(...)
+ self:on_bytes(...)
+ end,
+ on_detach = function(...)
+ self:on_detach(...)
+ end,
+ })
self.bufnr = tree:source()
self.edit_count = 0
@@ -157,7 +187,7 @@ function TSHighlighter.new(tree, opts)
end
end
- a.nvim_buf_set_option(self.bufnr, "syntax", "")
+ a.nvim_buf_set_option(self.bufnr, 'syntax', '')
TSHighlighter.active[self.bufnr] = self
@@ -166,7 +196,7 @@ function TSHighlighter.new(tree, opts)
-- syntax FileType autocmds. Later on we should integrate with the
-- `:syntax` and `set syntax=...` machinery properly.
if vim.g.syntax_on ~= 1 then
- vim.api.nvim_command("runtime! syntax/synload.vim")
+ vim.api.nvim_command('runtime! syntax/synload.vim')
end
self.tree:parse()
@@ -186,7 +216,7 @@ function TSHighlighter:get_highlight_state(tstree)
if not self._highlight_states[tstree] then
self._highlight_states[tstree] = {
next_row = 0,
- iter = nil
+ iter = nil,
}
end
@@ -211,7 +241,7 @@ end
---@private
function TSHighlighter:on_changedtree(changes)
for _, ch in ipairs(changes or {}) do
- a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3]+1)
+ a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3] + 1)
end
end
@@ -229,39 +259,50 @@ end
---@private
local function on_line_impl(self, buf, line)
self.tree:for_each_tree(function(tstree, tree)
- if not tstree then return end
+ if not tstree then
+ return
+ end
local root_node = tstree:root()
local root_start_row, _, root_end_row, _ = root_node:range()
-- Only worry about trees within the line range
- if root_start_row > line or root_end_row < line then return end
+ if root_start_row > line or root_end_row < line then
+ return
+ end
local state = self:get_highlight_state(tstree)
local highlighter_query = self:get_query(tree:lang())
-- Some injected languages may not have highlight queries.
- if not highlighter_query:query() then return end
+ if not highlighter_query:query() then
+ return
+ end
- if state.iter == nil then
- state.iter = highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1)
+ if state.iter == nil or state.next_row < line then
+ state.iter =
+ highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1)
end
while line >= state.next_row do
local capture, node, metadata = state.iter()
- if capture == nil then break end
+ if capture == nil then
+ break
+ end
local start_row, start_col, end_row, end_col = node:range()
local hl = highlighter_query.hl_cache[capture]
if hl and end_row >= line then
- a.nvim_buf_set_extmark(buf, ns, start_row, start_col,
- { end_line = end_row, end_col = end_col,
- hl_group = hl,
- ephemeral = true,
- priority = tonumber(metadata.priority) or 100 -- Low but leaves room below
- })
+ a.nvim_buf_set_extmark(buf, ns, start_row, start_col, {
+ end_line = end_row,
+ end_col = end_col,
+ hl_group = hl,
+ ephemeral = true,
+ priority = tonumber(metadata.priority) or 100, -- Low but leaves room below
+ conceal = metadata.conceal,
+ })
end
if start_row > line then
state.next_row = start_row
@@ -273,7 +314,9 @@ end
---@private
function TSHighlighter._on_line(_, _win, buf, line, _)
local self = TSHighlighter.active[buf]
- if not self then return end
+ if not self then
+ return
+ end
on_line_impl(self, buf, line)
end
@@ -299,9 +342,9 @@ function TSHighlighter._on_win(_, _win, buf, _topline)
end
a.nvim_set_decoration_provider(ns, {
- on_buf = TSHighlighter._on_buf;
- on_win = TSHighlighter._on_win;
- on_line = TSHighlighter._on_line;
+ on_buf = TSHighlighter._on_buf,
+ on_win = TSHighlighter._on_win,
+ on_line = TSHighlighter._on_line,
})
return TSHighlighter
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index 6f347ff25f..dfb6f5be84 100644
--- a/runtime/lua/vim/treesitter/language.lua
+++ b/runtime/lua/vim/treesitter/language.lua
@@ -14,7 +14,7 @@ function M.require_language(lang, path, silent)
return true
end
if path == nil then
- local fname = 'parser/' .. lang .. '.*'
+ local fname = 'parser/' .. vim.fn.fnameescape(lang) .. '.*'
local paths = a.nvim_get_runtime_file(fname, false)
if #paths == 0 then
if silent then
@@ -22,13 +22,15 @@ function M.require_language(lang, path, silent)
end
-- TODO(bfredl): help tag?
- error("no parser for '"..lang.."' language, see :help treesitter-parsers")
+ error("no parser for '" .. lang .. "' language, see :help treesitter-parsers")
end
path = paths[1]
end
if silent then
- return pcall(function() vim._ts_add_language(path, lang) end)
+ return pcall(function()
+ vim._ts_add_language(path, lang)
+ end)
else
vim._ts_add_language(path, lang)
end
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 85fd5cd8e0..4d3b0631a2 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -1,6 +1,6 @@
local a = vim.api
-local query = require'vim.treesitter.query'
-local language = require'vim.treesitter.language'
+local query = require('vim.treesitter.query')
+local language = require('vim.treesitter.language')
local LanguageTree = {}
LanguageTree.__index = LanguageTree
@@ -32,9 +32,8 @@ function LanguageTree.new(source, lang, opts)
_regions = {},
_trees = {},
_opts = opts,
- _injection_query = injections[lang]
- and query.parse_query(lang, injections[lang])
- or query.get_query(lang, "injections"),
+ _injection_query = injections[lang] and query.parse_query(lang, injections[lang])
+ or query.get_query(lang, 'injections'),
_valid = false,
_parser = vim._create_ts_parser(lang),
_callbacks = {
@@ -42,11 +41,10 @@ function LanguageTree.new(source, lang, opts)
bytes = {},
detach = {},
child_added = {},
- child_removed = {}
+ child_removed = {},
},
}, LanguageTree)
-
return self
end
@@ -76,8 +74,8 @@ function LanguageTree:lang()
end
--- Determines whether this tree is valid.
---- If the tree is invalid, `parse()` must be called
---- to get the updated tree.
+--- If the tree is invalid, call `parse()`.
+--- This will return the updated tree.
function LanguageTree:is_valid()
return self._valid
end
@@ -234,7 +232,9 @@ end
--- Destroys this language tree and all its children.
---
--- Any cleanup logic should be performed here.
---- Note, this DOES NOT remove this tree from a parent.
+---
+--- Note:
+--- This DOES NOT remove this tree from a parent. Instead,
--- `remove_child` must be called on the parent to remove it.
function LanguageTree:destroy()
-- Cleanup here
@@ -259,22 +259,27 @@ end
---
--- Note, this call invalidates the tree and requires it to be parsed again.
---
----@param regions A list of regions this tree should manage and parse.
+---@param regions (table) list of regions this tree should manage and parse.
function LanguageTree:set_included_regions(regions)
- -- TODO(vigoux): I don't think string parsers are useful for now
- if type(self._source) == "number" then
- -- Transform the tables from 4 element long to 6 element long (with byte offset)
- for _, region in ipairs(regions) do
- for i, range in ipairs(region) do
- if type(range) == "table" and #range == 4 then
- local start_row, start_col, end_row, end_col = unpack(range)
+ -- Transform the tables from 4 element long to 6 element long (with byte offset)
+ for _, region in ipairs(regions) do
+ for i, range in ipairs(region) do
+ if type(range) == 'table' and #range == 4 then
+ local start_row, start_col, end_row, end_col = unpack(range)
+ local start_byte = 0
+ local end_byte = 0
+ -- TODO(vigoux): proper byte computation here, and account for EOL ?
+ if type(self._source) == 'number' then
-- Easy case, this is a buffer parser
- -- TODO(vigoux): proper byte computation here, and account for EOL ?
- local start_byte = a.nvim_buf_get_offset(self._source, start_row) + start_col
- local end_byte = a.nvim_buf_get_offset(self._source, end_row) + end_col
-
- region[i] = { start_row, start_col, start_byte, end_row, end_col, end_byte }
+ start_byte = a.nvim_buf_get_offset(self._source, start_row) + start_col
+ end_byte = a.nvim_buf_get_offset(self._source, end_row) + end_col
+ elseif type(self._source) == 'string' then
+ -- string parser, single `\n` delimited string
+ start_byte = vim.fn.byteidx(self._source, start_col)
+ end_byte = vim.fn.byteidx(self._source, end_col)
end
+
+ region[i] = { start_row, start_col, start_byte, end_row, end_col, end_byte }
end
end
end
@@ -293,6 +298,14 @@ function LanguageTree:included_regions()
return self._regions
end
+---@private
+local function get_node_range(node, id, metadata)
+ if metadata[id] and metadata[id].range then
+ return metadata[id].range
+ end
+ return { node:range() }
+end
+
--- Gets language injection points by language.
---
--- This is where most of the injection processing occurs.
@@ -301,7 +314,9 @@ end
--- instead of using the entire nodes range.
---@private
function LanguageTree:_get_injections()
- if not self._injection_query then return {} end
+ if not self._injection_query then
+ return {}
+ end
local injections = {}
@@ -309,7 +324,9 @@ function LanguageTree:_get_injections()
local root_node = tree:root()
local start_line, _, end_line, _ = root_node:range()
- for pattern, match, metadata in self._injection_query:iter_matches(root_node, self._source, start_line, end_line+1) do
+ for pattern, match, metadata in
+ self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1)
+ do
local lang = nil
local ranges = {}
local combined = metadata.combined
@@ -320,11 +337,11 @@ function LanguageTree:_get_injections()
local content = metadata.content
-- Allow for captured nodes to be used
- if type(content) == "number" then
- content = {match[content]}
+ if type(content) == 'number' then
+ content = { match[content]:range() }
end
- if content then
+ if type(content) == 'table' and #content >= 4 then
vim.list_extend(ranges, content)
end
end
@@ -340,21 +357,21 @@ function LanguageTree:_get_injections()
local name = self._injection_query.captures[id]
-- Lang should override any other language tag
- if name == "language" and not lang then
+ if name == 'language' and not lang then
lang = query.get_node_text(node, self._source)
- elseif name == "combined" then
+ elseif name == 'combined' then
combined = true
- elseif name == "content" and #ranges == 0 then
- table.insert(ranges, node)
- -- Ignore any tags that start with "_"
- -- Allows for other tags to be used in matches
- elseif string.sub(name, 1, 1) ~= "_" then
+ elseif name == 'content' and #ranges == 0 then
+ table.insert(ranges, get_node_range(node, id, metadata))
+ -- Ignore any tags that start with "_"
+ -- Allows for other tags to be used in matches
+ elseif string.sub(name, 1, 1) ~= '_' then
if not lang then
lang = name
end
if #ranges == 0 then
- table.insert(ranges, node)
+ table.insert(ranges, get_node_range(node, id, metadata))
end
end
end
@@ -391,7 +408,10 @@ function LanguageTree:_get_injections()
for _, entry in pairs(patterns) do
if entry.combined then
- table.insert(result[lang], vim.tbl_flatten(entry.regions))
+ local regions = vim.tbl_map(function(e)
+ return vim.tbl_flatten(e)
+ end, entry.regions)
+ table.insert(result[lang], regions)
else
for _, ranges in ipairs(entry.regions) do
table.insert(result[lang], ranges)
@@ -412,10 +432,19 @@ function LanguageTree:_do_callback(cb_name, ...)
end
---@private
-function LanguageTree:_on_bytes(bufnr, changed_tick,
- start_row, start_col, start_byte,
- old_row, old_col, old_byte,
- new_row, new_col, new_byte)
+function LanguageTree:_on_bytes(
+ bufnr,
+ changed_tick,
+ start_row,
+ start_col,
+ start_byte,
+ old_row,
+ old_col,
+ old_byte,
+ new_row,
+ new_col,
+ new_byte
+)
self:invalidate()
local old_end_col = old_col + ((old_row == 0) and start_col or 0)
@@ -424,16 +453,33 @@ function LanguageTree:_on_bytes(bufnr, changed_tick,
-- Edit all trees recursively, together BEFORE emitting a bytes callback.
-- In most cases this callback should only be called from the root tree.
self:for_each_tree(function(tree)
- tree:edit(start_byte,start_byte+old_byte,start_byte+new_byte,
- start_row, start_col,
- start_row+old_row, old_end_col,
- start_row+new_row, new_end_col)
+ tree:edit(
+ start_byte,
+ start_byte + old_byte,
+ start_byte + new_byte,
+ start_row,
+ start_col,
+ start_row + old_row,
+ old_end_col,
+ start_row + new_row,
+ new_end_col
+ )
end)
- self:_do_callback('bytes', bufnr, changed_tick,
- start_row, start_col, start_byte,
- old_row, old_col, old_byte,
- new_row, new_col, new_byte)
+ self:_do_callback(
+ 'bytes',
+ bufnr,
+ changed_tick,
+ start_row,
+ start_col,
+ start_byte,
+ old_row,
+ old_col,
+ old_byte,
+ new_row,
+ new_col,
+ new_byte
+ )
end
---@private
@@ -441,23 +487,24 @@ function LanguageTree:_on_reload()
self:invalidate(true)
end
-
---@private
function LanguageTree:_on_detach(...)
self:invalidate(true)
self:_do_callback('detach', ...)
end
---- Registers callbacks for the parser
----@param cbs An `nvim_buf_attach`-like table argument with the following keys :
---- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback.
---- `on_changedtree` : a callback that will be called every time the tree has syntactical changes.
---- it will only be passed one argument, that is a table of the ranges (as node ranges) that
---- changed.
---- `on_child_added` : emitted when a child is added to the tree.
---- `on_child_removed` : emitted when a child is removed from the tree.
+--- Registers callbacks for the parser.
+---@param cbs table An |nvim_buf_attach()|-like table argument with the following keys :
+--- - `on_bytes` : see |nvim_buf_attach()|, but this will be called _after_ the parsers callback.
+--- - `on_changedtree` : a callback that will be called every time the tree has syntactical changes.
+--- It will only be passed one argument, which is a table of the ranges (as node ranges) that
+--- changed.
+--- - `on_child_added` : emitted when a child is added to the tree.
+--- - `on_child_removed` : emitted when a child is removed from the tree.
function LanguageTree:register_cbs(cbs)
- if not cbs then return end
+ if not cbs then
+ return
+ end
if cbs.on_changedtree then
table.insert(self._callbacks.changedtree, cbs.on_changedtree)
@@ -486,16 +533,10 @@ local function tree_contains(tree, range)
local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2])
local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4])
- if start_fits and end_fits then
- return true
- end
-
- return false
+ return start_fits and end_fits
end
---- Determines whether @param range is contained in this language tree
----
---- This goes down the tree to recursively check children.
+--- Determines whether {range} is contained in this language tree
---
---@param range A range, that is a `{ start_line, start_col, end_line, end_col }` table.
function LanguageTree:contains(range)
@@ -508,7 +549,7 @@ function LanguageTree:contains(range)
return false
end
---- Gets the appropriate language that contains @param range
+--- Gets the appropriate language that contains {range}
---
---@param range A text range, see |LanguageTree:contains|
function LanguageTree:language_for_range(range)
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index ebed502c92..103e85abfd 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -1,5 +1,5 @@
local a = vim.api
-local language = require'vim.treesitter.language'
+local language = require('vim.treesitter.language')
-- query: pattern matching on trees
-- predicate matching is implemented in lua
@@ -43,7 +43,9 @@ function M.get_query_files(lang, query_name, is_included)
local query_path = string.format('queries/%s/%s.scm', lang, query_name)
local lang_files = dedupe_files(a.nvim_get_runtime_file(query_path, true))
- if #lang_files == 0 then return {} end
+ if #lang_files == 0 then
+ return {}
+ end
local base_langs = {}
@@ -52,7 +54,7 @@ function M.get_query_files(lang, query_name, is_included)
-- ;+ inherits: ({language},)*{language}
--
-- {language} ::= {lang} | ({lang})
- local MODELINE_FORMAT = "^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$"
+ local MODELINE_FORMAT = '^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$'
for _, file in ipairs(lang_files) do
local modeline = safe_read(file, '*l')
@@ -62,7 +64,7 @@ function M.get_query_files(lang, query_name, is_included)
if langlist then
for _, incllang in ipairs(vim.split(langlist, ',', true)) do
- local is_optional = incllang:match("%(.*%)")
+ local is_optional = incllang:match('%(.*%)')
if is_optional then
if not is_included then
@@ -90,7 +92,7 @@ end
local function read_query_files(filenames)
local contents = {}
- for _,filename in ipairs(filenames) do
+ for _, filename in ipairs(filenames) do
table.insert(contents, safe_read(filename, '*a'))
end
@@ -138,30 +140,43 @@ function M.get_query(lang, query_name)
end
end
+local query_cache = setmetatable({}, {
+ __index = function(tbl, key)
+ rawset(tbl, key, {})
+ return rawget(tbl, key)
+ end,
+})
+
--- Parse {query} as a string. (If the query is in a file, the caller
---- should read the contents into a string before calling).
+--- should read the contents into a string before calling).
---
--- Returns a `Query` (see |lua-treesitter-query|) object which can be used to
--- search nodes in the syntax tree for the patterns defined in {query}
--- using `iter_*` methods below.
---
---- Exposes `info` and `captures` with additional information about the {query}.
+--- Exposes `info` and `captures` with additional context about {query}.
--- - `captures` contains the list of unique capture names defined in
--- {query}.
--- -` info.captures` also points to `captures`.
--- - `info.patterns` contains information about predicates.
---
----@param lang The language
----@param query A string containing the query (s-expr syntax)
+---@param lang string The language
+---@param query string A string containing the query (s-expr syntax)
---
---@returns The query
function M.parse_query(lang, query)
language.require_language(lang)
- local self = setmetatable({}, Query)
- self.query = vim._ts_parse_query(lang, query)
- self.info = self.query:inspect()
- self.captures = self.info.captures
- return self
+ local cached = query_cache[lang][query]
+ if cached then
+ return cached
+ else
+ local self = setmetatable({}, Query)
+ self.query = vim._ts_parse_query(lang, query)
+ self.info = self.query:inspect()
+ self.captures = self.info.captures
+ query_cache[lang][query] = self
+ return self
+ end
end
--- Gets the text corresponding to a given node
@@ -172,7 +187,7 @@ function M.get_node_text(node, source)
local start_row, start_col, start_byte = node:start()
local end_row, end_col, end_byte = node:end_()
- if type(source) == "number" then
+ if type(source) == 'number' then
local lines
local eof_row = a.nvim_buf_line_count(source)
if start_row >= eof_row then
@@ -186,56 +201,64 @@ function M.get_node_text(node, source)
lines = a.nvim_buf_get_lines(source, start_row, end_row + 1, true)
end
- if #lines == 1 then
- lines[1] = string.sub(lines[1], start_col+1, end_col)
- else
- lines[1] = string.sub(lines[1], start_col+1)
- lines[#lines] = string.sub(lines[#lines], 1, end_col)
+ if #lines > 0 then
+ if #lines == 1 then
+ lines[1] = string.sub(lines[1], start_col + 1, end_col)
+ else
+ lines[1] = string.sub(lines[1], start_col + 1)
+ lines[#lines] = string.sub(lines[#lines], 1, end_col)
+ end
end
- return table.concat(lines, "\n")
- elseif type(source) == "string" then
- return source:sub(start_byte+1, end_byte)
+ return table.concat(lines, '\n')
+ elseif type(source) == 'string' then
+ return source:sub(start_byte + 1, end_byte)
end
end
-- Predicate handler receive the following arguments
-- (match, pattern, bufnr, predicate)
local predicate_handlers = {
- ["eq?"] = function(match, _, source, predicate)
- local node = match[predicate[2]]
- local node_text = M.get_node_text(node, source)
-
- local str
- if type(predicate[3]) == "string" then
- -- (#eq? @aa "foo")
- str = predicate[3]
- else
- -- (#eq? @aa @bb)
- str = M.get_node_text(match[predicate[3]], source)
- end
+ ['eq?'] = function(match, _, source, predicate)
+ local node = match[predicate[2]]
+ if not node then
+ return true
+ end
+ local node_text = M.get_node_text(node, source)
- if node_text ~= str or str == nil then
- return false
- end
+ local str
+ if type(predicate[3]) == 'string' then
+ -- (#eq? @aa "foo")
+ str = predicate[3]
+ else
+ -- (#eq? @aa @bb)
+ str = M.get_node_text(match[predicate[3]], source)
+ end
- return true
+ if node_text ~= str or str == nil then
+ return false
+ end
+
+ return true
end,
- ["lua-match?"] = function(match, _, source, predicate)
- local node = match[predicate[2]]
- local regex = predicate[3]
- return string.find(M.get_node_text(node, source), regex)
+ ['lua-match?'] = function(match, _, source, predicate)
+ local node = match[predicate[2]]
+ if not node then
+ return true
+ end
+ local regex = predicate[3]
+ return string.find(M.get_node_text(node, source), regex)
end,
- ["match?"] = (function()
- local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true}
+ ['match?'] = (function()
+ local magic_prefixes = { ['\\v'] = true, ['\\m'] = true, ['\\M'] = true, ['\\V'] = true }
---@private
local function check_magic(str)
- if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then
+ if string.len(str) < 2 or magic_prefixes[string.sub(str, 1, 2)] then
return str
end
- return '\\v'..str
+ return '\\v' .. str
end
local compiled_vim_regexes = setmetatable({}, {
@@ -243,21 +266,27 @@ local predicate_handlers = {
local res = vim.regex(check_magic(pattern))
rawset(t, pattern, res)
return res
- end
+ end,
})
return function(match, _, source, pred)
local node = match[pred[2]]
+ if not node then
+ return true
+ end
local regex = compiled_vim_regexes[pred[3]]
return regex:match_str(M.get_node_text(node, source))
end
end)(),
- ["contains?"] = function(match, _, source, predicate)
+ ['contains?'] = function(match, _, source, predicate)
local node = match[predicate[2]]
+ if not node then
+ return true
+ end
local node_text = M.get_node_text(node, source)
- for i=3,#predicate do
+ for i = 3, #predicate do
if string.find(node_text, predicate[i], 1, true) then
return true
end
@@ -266,19 +295,22 @@ local predicate_handlers = {
return false
end,
- ["any-of?"] = function(match, _, source, predicate)
+ ['any-of?'] = function(match, _, source, predicate)
local node = match[predicate[2]]
+ if not node then
+ return true
+ end
local node_text = M.get_node_text(node, source)
-- Since 'predicate' will not be used by callers of this function, use it
-- to store a string set built from the list of words to check against.
- local string_set = predicate["string_set"]
+ local string_set = predicate['string_set']
if not string_set then
string_set = {}
- for i=3,#predicate do
+ for i = 3, #predicate do
string_set[predicate[i]] = true
end
- predicate["string_set"] = string_set
+ predicate['string_set'] = string_set
end
return string_set[node_text]
@@ -286,32 +318,33 @@ local predicate_handlers = {
}
-- As we provide lua-match? also expose vim-match?
-predicate_handlers["vim-match?"] = predicate_handlers["match?"]
-
+predicate_handlers['vim-match?'] = predicate_handlers['match?']
-- Directives store metadata or perform side effects against a match.
-- Directives should always end with a `!`.
-- Directive handler receive the following arguments
-- (match, pattern, bufnr, predicate, metadata)
local directive_handlers = {
- ["set!"] = function(_, _, _, pred, metadata)
+ ['set!'] = function(_, _, _, pred, metadata)
if #pred == 4 then
-- (#set! @capture "key" "value")
- local capture = pred[2]
- if not metadata[capture] then
- metadata[capture] = {}
+ local _, capture_id, key, value = unpack(pred)
+ if not metadata[capture_id] then
+ metadata[capture_id] = {}
end
- metadata[capture][pred[3]] = pred[4]
+ metadata[capture_id][key] = value
else
+ local _, key, value = unpack(pred)
-- (#set! "key" "value")
- metadata[pred[2]] = pred[3]
+ metadata[key] = value
end
end,
-- Shifts the range of a node.
-- Example: (#offset! @_node 0 1 0 -1)
- ["offset!"] = function(match, _, _, pred, metadata)
- local offset_node = match[pred[2]]
- local range = {offset_node:range()}
+ ['offset!'] = function(match, _, _, pred, metadata)
+ local capture_id = pred[2]
+ local offset_node = match[capture_id]
+ local range = { offset_node:range() }
local start_row_offset = pred[3] or 0
local start_col_offset = pred[4] or 0
local end_row_offset = pred[5] or 0
@@ -324,9 +357,12 @@ local directive_handlers = {
-- If this produces an invalid range, we just skip it.
if range[1] < range[3] or (range[1] == range[3] and range[2] <= range[4]) then
- metadata.content = {range}
+ if not metadata[capture_id] then
+ metadata[capture_id] = {}
+ end
+ metadata[capture_id].range = range
end
- end
+ end,
}
--- Adds a new predicate to be used in queries
@@ -336,7 +372,7 @@ local directive_handlers = {
--- signature will be (match, pattern, bufnr, predicate)
function M.add_predicate(name, handler, force)
if predicate_handlers[name] and not force then
- error(string.format("Overriding %s", name))
+ error(string.format('Overriding %s', name))
end
predicate_handlers[name] = handler
@@ -344,17 +380,23 @@ end
--- Adds a new directive to be used in queries
---
+--- Handlers can set match level data by setting directly on the
+--- metadata object `metadata.key = value`, additionally, handlers
+--- can set node level data by using the capture id on the
+--- metadata table `metadata[capture_id].key = value`
+---
---@param name the name of the directive, without leading #
---@param handler the handler function to be used
---- signature will be (match, pattern, bufnr, predicate)
+--- signature will be (match, pattern, bufnr, predicate, metadata)
function M.add_directive(name, handler, force)
if directive_handlers[name] and not force then
- error(string.format("Overriding %s", name))
+ error(string.format('Overriding %s', name))
end
directive_handlers[name] = handler
end
+--- Lists the currently available directives to use in queries.
---@return The list of supported directives.
function M.list_directives()
return vim.tbl_keys(directive_handlers)
@@ -372,7 +414,7 @@ end
---@private
local function is_directive(name)
- return string.sub(name, -1) == "!"
+ return string.sub(name, -1) == '!'
end
---@private
@@ -389,7 +431,7 @@ function Query:match_preds(match, pattern, source)
-- Skip over directives... they will get processed after all the predicates.
if not is_directive(pred[1]) then
- if string.sub(pred[1], 1, 4) == "not-" then
+ if string.sub(pred[1], 1, 4) == 'not-' then
pred_name = string.sub(pred[1], 5)
is_not = true
else
@@ -400,7 +442,7 @@ function Query:match_preds(match, pattern, source)
local handler = predicate_handlers[pred_name]
if not handler then
- error(string.format("No handler for %s", pred[1]))
+ error(string.format('No handler for %s', pred[1]))
return false
end
@@ -423,7 +465,7 @@ function Query:apply_directives(match, pattern, source, metadata)
local handler = directive_handlers[pred[1]]
if not handler then
- error(string.format("No handler for %s", pred[1]))
+ error(string.format('No handler for %s', pred[1]))
return
end
@@ -432,7 +474,6 @@ function Query:apply_directives(match, pattern, source, metadata)
end
end
-
--- Returns the start and stop value if set else the node's range.
-- When the node's range is used, the stop is incremented by 1
-- to make the search inclusive.
@@ -477,7 +518,7 @@ end
---@returns The matching capture id
---@returns The captured node
function Query:iter_captures(node, source, start, stop)
- if type(source) == "number" and source == 0 then
+ if type(source) == 'number' and source == 0 then
source = vim.api.nvim_get_current_buf()
end
@@ -534,7 +575,7 @@ end
---@returns The matching pattern id
---@returns The matching match
function Query:iter_matches(node, source, start, stop)
- if type(source) == "number" and source == 0 then
+ if type(source) == 'number' and source == 0 then
source = vim.api.nvim_get_current_buf()
end
diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua
index 9568b60fd0..6f1ce3089d 100644
--- a/runtime/lua/vim/ui.lua
+++ b/runtime/lua/vim/ui.lua
@@ -16,15 +16,33 @@ local M = {}
--- `items`, or the context in which select() was called.
---@param on_choice function ((item|nil, idx|nil) -> ())
--- Called once the user made a choice.
---- `idx` is the 1-based index of `item` within `item`.
+--- `idx` is the 1-based index of `item` within `items`.
--- `nil` if the user aborted the dialog.
+---
+---
+--- Example:
+--- <pre>
+--- vim.ui.select({ 'tabs', 'spaces' }, {
+--- prompt = 'Select tabs or spaces:',
+--- format_item = function(item)
+--- return "I'd like to choose " .. item
+--- end,
+--- }, function(choice)
+--- if choice == 'spaces' then
+--- vim.o.expandtab = true
+--- else
+--- vim.o.expandtab = false
+--- end
+--- end)
+--- </pre>
+
function M.select(items, opts, on_choice)
- vim.validate {
+ vim.validate({
items = { items, 'table', false },
on_choice = { on_choice, 'function', false },
- }
+ })
opts = opts or {}
- local choices = {opts.prompt or 'Select one of:'}
+ local choices = { opts.prompt or 'Select one of:' }
local format_item = opts.format_item or tostring
for i, item in pairs(items) do
table.insert(choices, string.format('%d: %s', i, format_item(item)))
@@ -41,7 +59,7 @@ end
---
---@param opts table Additional options. See |input()|
--- - prompt (string|nil)
---- Text of the prompt. Defaults to `Input: `.
+--- Text of the prompt
--- - default (string|nil)
--- Default reply to the input
--- - completion (string|nil)
@@ -57,12 +75,19 @@ end
--- Called once the user confirms or abort the input.
--- `input` is what the user typed.
--- `nil` if the user aborted the dialog.
+---
+--- Example:
+--- <pre>
+--- vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input)
+--- vim.o.shiftwidth = tonumber(input)
+--- end)
+--- </pre>
function M.input(opts, on_confirm)
- vim.validate {
+ vim.validate({
on_confirm = { on_confirm, 'function', false },
- }
+ })
- opts = opts or {}
+ opts = (opts and not vim.tbl_isempty(opts)) and opts or vim.empty_dict()
local input = vim.fn.input(opts)
if #input > 0 then
on_confirm(input)
diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua
index 11b661cd1a..d6b0b7410e 100644
--- a/runtime/lua/vim/uri.lua
+++ b/runtime/lua/vim/uri.lua
@@ -3,7 +3,6 @@
-- https://tools.ietf.org/html/rfc2732
-- https://tools.ietf.org/html/rfc2396
-
local uri_decode
do
local schar = string.char
@@ -14,7 +13,7 @@ do
return schar(tonumber(hex, 16))
end
uri_decode = function(str)
- return str:gsub("%%([a-fA-F0-9][a-fA-F0-9])", hex_to_char)
+ return str:gsub('%%([a-fA-F0-9][a-fA-F0-9])', hex_to_char)
end
end
@@ -23,33 +22,36 @@ do
local PATTERNS = {
--- RFC 2396
-- https://tools.ietf.org/html/rfc2396#section-2.2
- rfc2396 = "^A-Za-z0-9%-_.!~*'()";
+ rfc2396 = "^A-Za-z0-9%-_.!~*'()",
--- RFC 2732
-- https://tools.ietf.org/html/rfc2732
- rfc2732 = "^A-Za-z0-9%-_.!~*'()[]";
+ rfc2732 = "^A-Za-z0-9%-_.!~*'()[]",
--- RFC 3986
-- https://tools.ietf.org/html/rfc3986#section-2.2
- rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/";
+ rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/",
}
local sbyte, tohex = string.byte
if jit then
- tohex = require'bit'.tohex
+ tohex = require('bit').tohex
else
- tohex = function(b) return string.format("%02x", b) end
+ tohex = function(b)
+ return string.format('%02x', b)
+ end
end
---@private
local function percent_encode_char(char)
- return "%"..tohex(sbyte(char), 2)
+ return '%' .. tohex(sbyte(char), 2)
end
uri_encode = function(text, rfc)
- if not text then return end
+ if not text then
+ return
+ end
local pattern = PATTERNS[rfc] or PATTERNS.rfc3986
- return text:gsub("(["..pattern.."])", percent_encode_char)
+ return text:gsub('([' .. pattern .. '])', percent_encode_char)
end
end
-
---@private
local function is_windows_file_uri(uri)
return uri:match('^file:/+[a-zA-Z]:') ~= nil
@@ -59,16 +61,16 @@ end
---@param path string Path to file
---@return string URI
local function uri_from_fname(path)
- local volume_path, fname = path:match("^([a-zA-Z]:)(.*)")
+ local volume_path, fname = path:match('^([a-zA-Z]:)(.*)')
local is_windows = volume_path ~= nil
if is_windows then
- path = volume_path..uri_encode(fname:gsub("\\", "/"))
+ path = volume_path .. uri_encode(fname:gsub('\\', '/'))
else
path = uri_encode(path)
end
- local uri_parts = {"file://"}
+ local uri_parts = { 'file://' }
if is_windows then
- table.insert(uri_parts, "/")
+ table.insert(uri_parts, '/')
end
table.insert(uri_parts, path)
return table.concat(uri_parts)
@@ -82,11 +84,11 @@ local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):[a-zA-Z]:.*'
---@return string URI
local function uri_from_bufnr(bufnr)
local fname = vim.api.nvim_buf_get_name(bufnr)
- local volume_path = fname:match("^([a-zA-Z]:).*")
+ local volume_path = fname:match('^([a-zA-Z]:).*')
local is_windows = volume_path ~= nil
local scheme
if is_windows then
- fname = fname:gsub("\\", "/")
+ fname = fname:gsub('\\', '/')
scheme = fname:match(WINDOWS_URI_SCHEME_PATTERN)
else
scheme = fname:match(URI_SCHEME_PATTERN)