aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/_load_package.lua49
-rw-r--r--runtime/lua/vim/diagnostic.lua96
-rw-r--r--runtime/lua/vim/filetype.lua1625
-rw-r--r--runtime/lua/vim/highlight.lua114
-rw-r--r--runtime/lua/vim/keymap.lua135
-rw-r--r--runtime/lua/vim/lsp.lua253
-rw-r--r--runtime/lua/vim/lsp/buf.lua26
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua46
-rw-r--r--runtime/lua/vim/lsp/handlers.lua106
-rw-r--r--runtime/lua/vim/lsp/log.lua4
-rw-r--r--runtime/lua/vim/lsp/rpc.lua5
-rw-r--r--runtime/lua/vim/lsp/sync.lua2
-rw-r--r--runtime/lua/vim/lsp/util.lua153
-rw-r--r--runtime/lua/vim/shared.lua54
-rw-r--r--runtime/lua/vim/treesitter.lua1
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua18
-rw-r--r--runtime/lua/vim/treesitter/language.lua2
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua28
-rw-r--r--runtime/lua/vim/treesitter/query.lua43
-rw-r--r--runtime/lua/vim/ui.lua25
20 files changed, 2460 insertions, 325 deletions
diff --git a/runtime/lua/vim/_load_package.lua b/runtime/lua/vim/_load_package.lua
new file mode 100644
index 0000000000..525f603438
--- /dev/null
+++ b/runtime/lua/vim/_load_package.lua
@@ -0,0 +1,49 @@
+-- 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)
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 742ebf69b2..fcb1e61764 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -30,7 +30,7 @@ M.handlers = setmetatable({}, {
__newindex = function(t, name, handler)
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,
@@ -447,7 +447,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
@@ -552,17 +552,24 @@ 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|
---- - virtual_text: (default true) Use virtual text for diagnostics. Options:
+--- - virtual_text: (default true) Use virtual text for diagnostics. If multiple diagnostics
+--- are set for a namespace, one prefix per diagnostic + the last diagnostic
+--- message are shown.
+--- Options:
--- * severity: Only show virtual text for diagnostics matching the given
--- severity |diagnostic-severity|
--- * source: (boolean or string) Include the diagnostic source in virtual
--- text. Use "if_many" to only show sources if there is more than
--- one diagnostic source in the buffer. Otherwise, any truthy value
--- means to always show the diagnostic source.
+--- * spacing: (number) Amount of empty spaces inserted at the beginning
+--- of the virtual text.
+--- * prefix: (string) Prepend diagnostic message with prefix.
--- * format: (function) A function that takes a diagnostic as input and
--- returns a string. The return value is the text used to display
--- the diagnostic. Example:
@@ -593,7 +600,7 @@ end
--- global diagnostic options.
function M.config(opts, namespace)
vim.validate {
- opts = { opts, 't' },
+ opts = { opts, 't', true },
namespace = { namespace, 'n', true },
}
@@ -605,10 +612,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
@@ -638,7 +648,11 @@ function M.set(namespace, bufnr, diagnostics, opts)
vim.validate {
namespace = {namespace, 'n'},
bufnr = {bufnr, 'n'},
- diagnostics = {diagnostics, 't'},
+ diagnostics = {
+ diagnostics,
+ vim.tbl_islist,
+ "a list of diagnostics",
+ },
opts = {opts, 't', true},
}
@@ -804,11 +818,16 @@ M.handlers.signs = {
vim.validate {
namespace = {namespace, 'n'},
bufnr = {bufnr, 'n'},
- diagnostics = {diagnostics, 't'},
+ diagnostics = {
+ diagnostics,
+ vim.tbl_islist,
+ "a list of diagnostics",
+ },
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)
@@ -867,11 +886,16 @@ M.handlers.underline = {
vim.validate {
namespace = {namespace, 'n'},
bufnr = {bufnr, 'n'},
- diagnostics = {diagnostics, 't'},
+ diagnostics = {
+ diagnostics,
+ vim.tbl_islist,
+ "a list of diagnostics",
+ },
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)
@@ -896,7 +920,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)
@@ -915,11 +940,16 @@ M.handlers.virtual_text = {
vim.validate {
namespace = {namespace, 'n'},
bufnr = {bufnr, 'n'},
- diagnostics = {diagnostics, 't'},
+ diagnostics = {
+ diagnostics,
+ vim.tbl_islist,
+ "a list of diagnostics",
+ },
opts = {opts, 't', true},
}
bufnr = get_bufnr(bufnr)
+ opts = opts or {}
local severity
if opts.virtual_text then
@@ -1075,7 +1105,11 @@ function M.show(namespace, bufnr, diagnostics, opts)
vim.validate {
namespace = { namespace, 'n', true },
bufnr = { bufnr, 'n', true },
- diagnostics = { diagnostics, 't', true },
+ diagnostics = {
+ diagnostics,
+ function(v) return v == nil or vim.tbl_islist(v) end,
+ "a list of diagnostics",
+ },
opts = { opts, 't', true },
}
@@ -1346,16 +1380,16 @@ function M.reset(namespace, bufnr)
diagnostic_cache[iter_bufnr][iter_namespace] = nil
M.hide(iter_namespace, iter_bufnr)
end
- 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))
+ 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)
+ end)
+ end
end
--- Add all diagnostics to the quickfix list.
@@ -1520,7 +1554,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 { diagnostics = {diagnostics, 't'} }
+ vim.validate {
+ diagnostics = {
+ diagnostics,
+ vim.tbl_islist,
+ "a list of diagnostics",
+ },
+ }
local list = {}
for _, v in ipairs(diagnostics) do
@@ -1551,7 +1591,13 @@ end
--- |getloclist()|.
---@return array of diagnostics |diagnostic-structure|
function M.fromqflist(list)
- vim.validate { list = {list, 't'} }
+ vim.validate {
+ list = {
+ list,
+ vim.tbl_islist,
+ "a list of quickfix items",
+ },
+ }
local diagnostics = {}
for _, item in ipairs(list) do
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
new file mode 100644
index 0000000000..f5e4dabfb6
--- /dev/null
+++ b/runtime/lua/vim/filetype.lua
@@ -0,0 +1,1625 @@
+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 re = vim.regex(vim.g.fg_ignore_pat)
+ if re:match_str(path) then
+ return ft
+ end
+ end, {
+ -- Starset matches should always have lowest priority
+ priority = -math.huge,
+ }}
+end
+
+---@private
+local function getline(bufnr, lnum)
+ return api.nvim_buf_get_lines(bufnr, lnum-1, lnum, false)[1]
+end
+
+-- Filetypes based on file extension
+-- luacheck: push no unused args
+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",
+ bicep = "bicep",
+ 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(path, bufnr)
+ if vim.g.cynlib_syntax_for_cc then
+ return "cynlib"
+ end
+ return "cpp"
+ end,
+ cc = function(path, bufnr)
+ if vim.g.cynlib_syntax_for_cc then
+ return "cynlib"
+ 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",
+ fb = "freebasic",
+ fsi = "fsharp",
+ fsx = "fsharp",
+ fusion = "fusion",
+ gdmo = "gdmo",
+ mo = "gdmo",
+ tres = "gdresource",
+ tscn = "gdresource",
+ gd = "gdscript",
+ ged = "gedcom",
+ gmi = "gemtext",
+ gemini = "gemtext",
+ gift = "gift",
+ glsl = "glsl",
+ gpi = "gnuplot",
+ gnuplot = "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",
+ ["hs-boot"] = "haskell",
+ hsig = "haskell",
+ hsc = "haskell",
+ hs = "haskell",
+ ht = "haste",
+ htpp = "hastepreproc",
+ hb = "hb",
+ sum = "hercules",
+ errsum = "hercules",
+ ev = "hercules",
+ vc = "hercules",
+ hcl = "hcl",
+ heex = "heex",
+ hex = "hex",
+ ["h32"] = "hex",
+ hjson = "hjson",
+ 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",
+ 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",
+ 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",
+ 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",
+ ora = "ora",
+ 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",
+ 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",
+ pug = "pug",
+ arr = "pyret",
+ pxd = "pyrex",
+ pyx = "pyrex",
+ pyw = "python",
+ py = "python",
+ pyi = "python",
+ ptl = "python",
+ ql = "ql",
+ qll = "ql",
+ 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",
+ 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",
+ 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",
+ 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",
+ 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",
+ tutor = "tutor",
+ twig = "twig",
+ ts = function(path, bufnr)
+ if getline(bufnr, 1):find("<%?xml") then
+ return "xml"
+ else
+ return "typescript"
+ 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",
+ yang = "yang",
+ ["z8a"] = "z8a",
+ zig = "zig",
+ zu = "zimbu",
+ zut = "zimbutempl",
+ zsh = "zsh",
+ vala = "vala",
+ 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#FTbas"]() end,
+ bi = function() vim.fn["dist#ft#FTbas"]() end,
+ bm = function() vim.fn["dist#ft#FTbas"]() 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#FTfrm"]() 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,
+ patch = function(path, bufnr)
+ local firstline = getline(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,
+ 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,
+ tf = function() vim.fn["dist#ft#FTtf"]() 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,
+ 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"
+ end
+ 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",
+ ["gitolite.conf"] = "gitolite",
+ ["git-rebase-todo"] = "gitrebase",
+ gkrellmrc = "gkrellmrc",
+ [".gnashrc"] = "gnash",
+ [".gnashpluginrc"] = "gnash",
+ gnashpluginrc = "gnash",
+ gnashrc = "gnash",
+ ["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",
+ ["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",
+ [".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",
+ 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,
+ [".env"] = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) 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,
+ -- 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",
+ [".*/dtrace/.*%.d"] = "dtrace",
+ [".*esmtprc"] = "esmtprc",
+ [".*Eterm/.*%.cfg"] = "eterm",
+ [".*%.git/modules/.*/config"] = "gitconfig",
+ [".*%.git/config"] = "gitconfig",
+ [".*/etc/gitconfig"] = "gitconfig",
+ [".*/%.config/git/config"] = "gitconfig",
+ [".*%.git/config%.worktree"] = "gitconfig",
+ [".*%.git/worktrees/.*/config%.worktree"] = "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",
+ ["%.?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",
+ [".*%.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',
+ ["snd%.%d+"] = "mail",
+ ["%.letter%.%d+"] = "mail",
+ ["%.article%.%d+"] = "mail",
+ ["pico%.%d+"] = "mail",
+ ["mutt%-.*%-%w+"] = "mail",
+ ["neomutt%-.*%-%w+"] = "mail",
+ ["muttng%-.*%-%w+"] = "mail",
+ ["mutt" .. string.rep("[%w_-]", 6)] = "mail",
+ ["neomutt" .. string.rep("[%w_-]", 6)] = "mail",
+ ["/tmp/SLRN[0-9A-Z.]+"] = "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/sensors%.d/[^.].*"] = starsetf('sensors'),
+ [".*%.git/.*"] = function(path, bufnr)
+ local firstline = getline(bufnr, 1)
+ if firstline:find("^" .. string.rep("%x", 40) .. "+ ") or firstline:sub(1, 5) == "ref: " then
+ return "git"
+ end
+ end,
+ -- END PATTERN
+}
+-- 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 opts = (type(v) == "table" and type(v[2]) == "table") and v[2] or {}
+ if not opts.priority then
+ opts.priority = 0
+ end
+ table.insert(sorted, { [k] = { ft, opts } })
+ end
+ table.sort(sorted, function(a, b)
+ return a[next(a)][2].priority > b[next(b)][2].priority
+ end)
+ return sorted
+end
+
+local pattern_sorted = sort_by_priority(pattern)
+
+---@private
+local function normalize_path(path)
+ return (path:gsub("\\", "/"):gsub("^~", vim.env.HOME))
+end
+
+--- Add new filetype mappings.
+---
+--- 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)
+--- until a match is found. Lastly, if pattern matching does not find a
+--- filetype, then the file extension is used.
+---
+--- The filetype can be either a string (in which case it is used as the
+--- 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.
+---
+--- Filename patterns can specify an optional priority to resolve cases when a
+--- file path matches multiple patterns. Higher priorities are matched first.
+--- When omitted, the priority defaults to 0.
+---
+--- 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.
+---
+--- Example:
+--- <pre>
+--- vim.filetype.add({
+--- extension = {
+--- foo = "fooscript",
+--- bar = function(path, bufnr)
+--- if some_condition() then
+--- return "barscript"
+--- end
+--- return "bar"
+--- end,
+--- },
+--- filename = {
+--- [".foorc"] = "toml",
+--- ["/etc/foo/config"] = "toml",
+--- },
+--- pattern = {
+--- [".*/etc/foo/.*"] = "fooscript",
+--- -- Using an optional priority
+--- [".*/etc/foo/.*%.conf"] = { "dosini", { priority = 10 } },
+--- ["README.(%a+)$"] = function(path, bufnr, ext)
+--- if ext == "md" then
+--- return "markdown"
+--- elseif ext == "rst" then
+--- return "rst"
+--- 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
+ extension[k] = v
+ end
+
+ for k, v in pairs(filetypes.filename or {}) do
+ filename[normalize_path(k)] = v
+ end
+
+ for k, v in pairs(filetypes.pattern or {}) do
+ pattern[normalize_path(k)] = v
+ end
+
+ if filetypes.pattern then
+ pattern_sorted = sort_by_priority(pattern)
+ end
+end
+
+---@private
+local function dispatch(ft, path, bufnr, ...)
+ if type(ft) == "function" then
+ ft = ft(path, bufnr, ...)
+ end
+
+ if type(ft) == "string" then
+ api.nvim_buf_set_option(bufnr, "filetype", ft)
+ return true
+ 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 matches
+ 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)
+ matches = name:match(fullpat) or path:match(fullpat)
+ else
+ matches = tail:match(fullpat)
+ end
+ return matches
+end
+
+--- Set the filetype for the given buffer from a file name.
+---
+---@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()
+
+ name = normalize_path(name)
+
+ -- 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
+ end
+
+ -- Next check against just the file name
+ local tail = vim.fn.fnamemodify(name, ":t")
+ if dispatch(filename[tail], path, bufnr) then
+ return
+ end
+
+ -- Next, check the file path against available patterns with non-negative priority
+ local j = 1
+ for i, v in ipairs(pattern_sorted) do
+ local k = next(v)
+ local opts = v[k][2]
+ if opts.priority < 0 then
+ j = i
+ break
+ end
+
+ local ft = v[k][1]
+ local matches = match_pattern(name, path, tail, k)
+ if matches then
+ if dispatch(ft, path, bufnr, matches) then
+ return
+ end
+ end
+ end
+
+ -- Next, check file extension
+ local ext = vim.fn.fnamemodify(name, ":e")
+ if dispatch(extension[ext], path, bufnr) then
+ return
+ end
+
+ -- Finally, 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 matches = match_pattern(name, path, tail, k)
+ if matches then
+ if dispatch(ft, path, bufnr, matches) then
+ return
+ end
+ end
+ end
+end
+
+return M
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index 236f3165f2..4105ef0675 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -1,9 +1,16 @@
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)
local options = {}
-- TODO: Add validation
for k, v in pairs(hi_info) do
@@ -13,33 +20,50 @@ function highlight.create(higroup, hi_info, default)
end
---@private
-function highlight.link(higroup, link_to, force)
+function M.link(higroup, link_to, force)
vim.cmd(string.format([[highlight%s link %s %s]], force and "!" or " default", higroup, link_to))
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_ns = api.nvim_create_namespace("hlyank")
--- Highlight the yanked region
---
--- use from init.vim via
@@ -49,26 +73,40 @@ 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 timeout = opts.timeout or 150
@@ -79,19 +117,23 @@ function highlight.on_yank(opts)
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]}
+ 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)
-
- 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 }
)
+
+ 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)
end
-return highlight
+return M
diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua
new file mode 100644
index 0000000000..df49eff4b6
--- /dev/null
+++ b/runtime/lua/vim/keymap.lua
@@ -0,0 +1,135 @@
+local keymap = {}
+
+--- Add a new |mapping|.
+--- Examples:
+--- <pre>
+--- -- Can add mapping to Lua functions
+--- vim.keymap.set('n', 'lhs', function() print("real lua function") end)
+---
+--- -- Can use it to map multiple modes
+--- vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer=true })
+---
+--- -- Can add mapping for specific buffer
+--- vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 })
+---
+--- -- Expr mappings
+--- vim.keymap.set('i', '<Tab>', function()
+--- return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>"
+--- end, { expr = true })
+--- -- <Plug> mappings
+--- vim.keymap.set('n', '[%%', '<Plug>(MatchitNormalMultiBackward)')
+--- </pre>
+---
+--- Note that in a mapping like:
+--- <pre>
+--- 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:
+--- <pre>
+--- vim.keymap.set('n', 'asdf', function() return require('jkl').my_fun() end)
+--- </pre>
+---
+---@param mode string|table Same mode short names as |nvim_set_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.
+--
+---@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:
+--- - 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 `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}
+ }
+
+ opts = vim.deepcopy(opts) or {}
+ 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
+ local user_rhs = rhs
+ rhs = function ()
+ return vim.api.nvim_replace_termcodes(user_rhs(), true, true, true)
+ end
+ end
+ -- clear replace_keycodes from opts table
+ opts.replace_keycodes = nil
+
+ if opts.remap == nil then
+ -- default remap value is false
+ opts.noremap = true
+ else
+ -- remaps behavior is opposite of noremap option.
+ opts.noremap = not opts.remap
+ opts.remap = nil
+ end
+
+ if is_rhs_luaref then
+ opts.callback = rhs
+ rhs = ''
+ end
+
+ if opts.buffer then
+ local bufnr = opts.buffer == true and 0 or opts.buffer
+ opts.buffer = nil
+ for _, m in ipairs(mode) do
+ vim.api.nvim_buf_set_keymap(bufnr, m, lhs, rhs, opts)
+ end
+ else
+ opts.buffer = nil
+ for _, m in ipairs(mode) do
+ vim.api.nvim_set_keymap(m, lhs, rhs, opts)
+ end
+ end
+end
+
+--- Remove an existing mapping.
+--- Examples:
+--- <pre>
+--- vim.keymap.del('n', 'lhs')
+---
+--- vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 })
+--- </pre>
+---@param opts table A table of optional arguments:
+--- - buffer: (number or boolean) Remove a mapping from the given buffer.
+--- When "true" or 0, use the current buffer.
+---@see |vim.keymap.set()|
+---
+function keymap.del(modes, lhs, opts)
+ vim.validate {
+ mode = {modes, {'s', 't'}},
+ lhs = {lhs, 's'},
+ opts = {opts, 't', true}
+ }
+
+ opts = opts or {}
+ 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
+ for _, mode in ipairs(modes) do
+ vim.api.nvim_del_keymap(mode, lhs)
+ end
+ else
+ for _, mode in ipairs(modes) do
+ vim.api.nvim_buf_del_keymap(buffer, mode, lhs)
+ end
+ end
+end
+
+return keymap
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 00839ec181..8d11b4621c 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -256,7 +256,7 @@ local function validate_client_config(config)
(not config.flags
or not config.flags.debounce_text_changes
or type(config.flags.debounce_text_changes) == 'number'),
- "flags.debounce_text_changes must be nil or a number with the debounce time in milliseconds"
+ "flags.debounce_text_changes must be a number with the debounce time in milliseconds"
)
local cmd, cmd_args = lsp._cmd_parts(config.cmd)
@@ -290,7 +290,7 @@ end
--- Memoizes a function. On first run, the function return value is saved and
--- immediately returned on subsequent runs. If the function returns a multival,
--- only the first returned value will be memoized and returned. The function will only be run once,
---- even if it has side-effects.
+--- even if it has side effects.
---
---@param fn (function) Function to run
---@returns (function) Memoized function
@@ -306,70 +306,138 @@ local function once(fn)
end
end
-
local changetracking = {}
do
--@private
--- client_id → state
---
--- state
+ --- use_incremental_sync: bool
+ --- buffers: bufnr -> buffer_state
+ ---
+ --- buffer_state
--- pending_change?: function that the timer starts to trigger didChange
--- pending_changes: table (uri -> list of pending changeset tables));
- -- Only set if incremental_sync is used
- --- use_incremental_sync: bool
- --- buffers?: table (bufnr → lines); for incremental sync only
+ --- Only set if incremental_sync is used
+ ---
--- timer?: uv_timer
+ --- lines: table
local state_by_client = {}
---@private
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
+ )
local state = state_by_client[client.id]
if not state then
state = {
- pending_changes = {};
- use_incremental_sync = (
- if_nil(client.config.flags.allow_incremental_sync, true)
- and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental
- );
+ buffers = {};
+ debounce = client.config.flags.debounce_text_changes or 150,
+ use_incremental_sync = use_incremental_sync;
}
state_by_client[client.id] = state
end
- if not state.use_incremental_sync then
- return
- end
- if not state.buffers then
- state.buffers = {}
+ if not state.buffers[bufnr] then
+ local buf_state = {}
+ 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
- state.buffers[bufnr] = nvim_buf_get_lines(bufnr, 0, -1, true)
end
---@private
function changetracking.reset_buf(client, bufnr)
- changetracking.flush(client)
+ changetracking.flush(client, bufnr)
local state = state_by_client[client.id]
if state and state.buffers then
+ local buf_state = state.buffers[bufnr]
state.buffers[bufnr] = nil
+ if buf_state and buf_state.timer then
+ buf_state.timer:stop()
+ buf_state.timer:close()
+ buf_state.timer = nil
+ end
end
end
---@private
function changetracking.reset(client_id)
local state = state_by_client[client_id]
- if state then
- state_by_client[client_id] = nil
- changetracking._reset_timer(state)
+ if not state then
+ return
end
+ for _, buf_state in pairs(state.buffers) do
+ if buf_state.timer then
+ buf_state.timer:stop()
+ buf_state.timer:close()
+ buf_state.timer = nil
+ end
+ end
+ state.buffers = {}
+ end
+
+ ---@private
+ --
+ -- Adjust debounce time by taking time of last didChange notification into
+ -- consideration. If the last didChange happened more than `debounce` time ago,
+ -- debounce can be skipped and otherwise maybe reduced.
+ --
+ -- This turns the debounce into a kind of client rate limiting
+ local function next_debounce(debounce, buf_state)
+ if debounce == 0 then
+ return 0
+ end
+ local ns_to_ms = 0.000001
+ if not buf_state.last_flush then
+ return debounce
+ end
+ local now = uv.hrtime()
+ local ms_since_last_flush = (now - buf_state.last_flush) * ns_to_ms
+ return math.max(debounce - ms_since_last_flush, 0)
end
---@private
function changetracking.prepare(bufnr, firstline, lastline, new_lastline)
- local incremental_changes = function(client)
- local cached_buffers = state_by_client[client.id].buffers
- local curr_lines = nvim_buf_get_lines(bufnr, 0, -1, true)
+ local incremental_changes = function(client, buf_state)
+
+ 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(
- cached_buffers[bufnr], curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending)
- cached_buffers[bufnr] = curr_lines
+ 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()
@@ -383,75 +451,68 @@ do
return
end
local state = state_by_client[client.id]
- local debounce = client.config.flags.debounce_text_changes
- if not debounce then
- local changes = state.use_incremental_sync and incremental_changes(client) or full_changes()
- client.notify("textDocument/didChange", {
- textDocument = {
- uri = uri;
- version = util.buf_versions[bufnr];
- };
- contentChanges = { changes, }
- })
- return
- end
- changetracking._reset_timer(state)
+ local buf_state = state.buffers[bufnr]
+ changetracking._reset_timer(buf_state)
+ local debounce = next_debounce(state.debounce, buf_state)
if state.use_incremental_sync then
-- This must be done immediately and cannot be delayed
-- The contents would further change and startline/endline may no longer fit
- if not state.pending_changes[uri] then
- state.pending_changes[uri] = {}
- end
- table.insert(state.pending_changes[uri], incremental_changes(client))
+ table.insert(buf_state.pending_changes, incremental_changes(client, buf_state))
end
- state.pending_change = function()
- state.pending_change = nil
+ buf_state.pending_change = function()
+ 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
return
end
- if state.use_incremental_sync then
- for change_uri, content_changes in pairs(state.pending_changes) do
- client.notify("textDocument/didChange", {
- textDocument = {
- uri = change_uri;
- version = util.buf_versions[vim.uri_to_bufnr(change_uri)];
- };
- contentChanges = content_changes,
- })
- end
- state.pending_changes = {}
- else
- client.notify("textDocument/didChange", {
- textDocument = {
- uri = uri;
- version = util.buf_versions[bufnr];
- };
- contentChanges = { full_changes() },
- })
- end
+ 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],
+ },
+ contentChanges = changes,
+ })
+ buf_state.pending_changes = {}
+ end
+ if debounce == 0 then
+ buf_state.pending_change()
+ else
+ local timer = vim.loop.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))
end
- state.timer = vim.loop.new_timer()
- -- Must use schedule_wrap because `full_changes()` calls nvim_buf_get_lines
- state.timer:start(debounce, 0, vim.schedule_wrap(state.pending_change))
end
end
- function changetracking._reset_timer(state)
- if state.timer then
- state.timer:stop()
- state.timer:close()
- state.timer = nil
+ function changetracking._reset_timer(buf_state)
+ if buf_state.timer then
+ buf_state.timer:stop()
+ buf_state.timer:close()
+ buf_state.timer = nil
end
end
--- Flushes any outstanding change notification.
---@private
- function changetracking.flush(client)
+ function changetracking.flush(client, bufnr)
local state = state_by_client[client.id]
- if state then
- changetracking._reset_timer(state)
- if state.pending_change then
- state.pending_change()
+ if not state then
+ return
+ end
+ if bufnr then
+ local buf_state = state.buffers[bufnr] or {}
+ changetracking._reset_timer(buf_state)
+ if buf_state.pending_change then
+ buf_state.pending_change()
+ end
+ else
+ for _, buf_state in pairs(state.buffers) do
+ changetracking._reset_timer(buf_state)
+ if buf_state.pending_change then
+ buf_state.pending_change()
+ end
end
end
end
@@ -645,8 +706,8 @@ end
---@param on_error Callback with parameters (code, ...), invoked
--- when the client operation throws an error. `code` is a number describing
--- the error. Other arguments may be passed depending on the error kind. See
---- |vim.lsp.client_errors| for possible errors.
---- Use `vim.lsp.client_errors[code]` to get human-friendly name.
+--- |vim.lsp.rpc.client_errors| for possible errors.
+--- Use `vim.lsp.rpc.client_errors[code]` to get human-friendly name.
---
---@param before_init Callback with parameters (initialize_params, config)
--- invoked before the LSP "initialize" phase, where `params` contains the
@@ -757,8 +818,8 @@ function lsp.start_client(config)
---
---@param code (number) Error code
---@param err (...) Other arguments may be passed depending on the error kind
- ---@see |vim.lsp.client_errors| for possible errors. Use
- ---`vim.lsp.client_errors[code]` to get a human-friendly name.
+ ---@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 })
err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
@@ -897,7 +958,7 @@ function lsp.start_client(config)
client.initialized = true
uninitialized_clients[client_id] = nil
client.workspace_folders = workspace_folders
- -- TODO(mjlbach): Backwards compatbility, to be removed in 0.7
+ -- 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
@@ -957,7 +1018,7 @@ function lsp.start_client(config)
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)
+ 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 success, request_id = rpc.request(method, params, function(err, result)
@@ -1014,14 +1075,16 @@ function lsp.start_client(config)
---@private
--- Sends a notification to an LSP server.
---
- ---@param method (string) LSP method name.
- ---@param params (optional, table) LSP request params.
- ---@param bufnr (number) Buffer handle, or 0 for current.
+ ---@param method string LSP method name.
+ ---@param params table|nil LSP request params.
---@returns {status} (bool) true if the notification was successful.
---If it is false, then it will always be false
---(the client has shutdown).
- function client.notify(...)
- return rpc.notify(...)
+ function client.notify(method, params)
+ if method ~= 'textDocument/didChange' then
+ changetracking.flush(client)
+ end
+ return rpc.notify(method, params)
end
---@private
@@ -1131,7 +1194,7 @@ function lsp._text_document_did_save_handler(bufnr)
if client.resolved_capabilities.text_document_save then
local included_text
if client.resolved_capabilities.text_document_save_include_text then
- included_text = text()
+ included_text = text(bufnr)
end
client.notify('textDocument/didSave', {
textDocument = {
@@ -1535,7 +1598,7 @@ end
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
@@ -1708,14 +1771,14 @@ end
--
-- Can be used to lookup the number from the name or the
-- name from the number.
--- Levels by name: "trace", "debug", "info", "warn", "error"
--- Level numbers begin with "trace" at 0
+-- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR"
+-- 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"
---- Level numbers begin with "trace" at 0
+--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR"
+--- Level numbers begin with "TRACE" at 0
---
--- Use `lsp.log_levels` for reverse lookup.
---
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 8e3ed9b002..eb7ec579f1 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -184,7 +184,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
@@ -228,7 +228,7 @@ function M.formatting_seq_sync(options, timeout_ms, order)
local params = util.make_formatting_params(options)
local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, vim.api.nvim_get_current_buf())
if result and result.result then
- util.apply_text_edits(result.result, bufnr)
+ 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)
end
@@ -447,13 +447,16 @@ end
---@param query (string, optional)
function M.workspace_symbol(query)
query = query or npcall(vfn.input, "Query: ")
+ if query == nil then
+ return
+ end
local params = {query = query}
request('workspace/symbol', params)
end
--- Send request to the server to resolve document highlights for the current
--- text document position. This request can be triggered by a key mapping or
---- by events such as `CursorHold`, eg:
+--- by events such as `CursorHold`, e.g.:
---
--- <pre>
--- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
@@ -503,7 +506,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
@@ -627,14 +630,19 @@ end
--- Executes an LSP server command.
---
----@param command A valid `ExecuteCommandParams` object
+---@param command_params table A valid `ExecuteCommandParams` object
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
-function M.execute_command(command)
+function M.execute_command(command_params)
validate {
- command = { command.command, 's' },
- arguments = { command.arguments, 't', true }
+ command = { command_params.command, 's' },
+ arguments = { command_params.arguments, 't', true }
+ }
+ command_params = {
+ command=command_params.command,
+ arguments=command_params.arguments,
+ workDoneToken=command_params.workDoneToken,
}
- request('workspace/executeCommand', command)
+ request('workspace/executeCommand', command_params )
end
return M
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index 8850d25233..68942ae11a 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -168,8 +168,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,
@@ -243,7 +243,7 @@ end
---@param client_id number
---@private
function M.save(diagnostics, bufnr, client_id)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.save is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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
@@ -257,7 +257,7 @@ end
--- If nil, diagnostics of all clients are included.
---@return table with diagnostics grouped by bufnr (bufnr: Diagnostic[])
function M.get_all(client_id)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.get_all is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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
@@ -279,7 +279,7 @@ end
--- Else, return just the diagnostics associated with the client_id.
---@param predicate function|nil Optional function for filtering diagnostics
function M.get(bufnr, client_id, predicate)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.get is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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 = {}
@@ -341,7 +341,7 @@ end
---@param severity DiagnosticSeverity
---@param client_id number the client id
function M.get_count(bufnr, severity, client_id)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.get_count is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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
@@ -358,7 +358,7 @@ end
---@param opts table See |vim.lsp.diagnostic.goto_next()|
---@return table Previous diagnostic
function M.get_prev(opts)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.get_prev is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)
@@ -376,7 +376,7 @@ end
---@param opts table See |vim.lsp.diagnostic.goto_next()|
---@return table Previous diagnostic position
function M.get_prev_pos(opts)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.get_prev_pos is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)
@@ -393,7 +393,7 @@ end
---
---@param opts table See |vim.lsp.diagnostic.goto_next()|
function M.goto_prev(opts)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.goto_prev is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)
@@ -411,7 +411,7 @@ end
---@param opts table See |vim.lsp.diagnostic.goto_next()|
---@return table Next diagnostic
function M.get_next(opts)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.get_next is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)
@@ -429,7 +429,7 @@ end
---@param opts table See |vim.lsp.diagnostic.goto_next()|
---@return table Next diagnostic position
function M.get_next_pos(opts)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.get_next_pos is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)
@@ -444,7 +444,7 @@ end
---
---@deprecated Prefer |vim.diagnostic.goto_next()|
function M.goto_next(opts)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.goto_next is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)
@@ -468,7 +468,7 @@ end
--- - severity_limit (DiagnosticSeverity):
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
function M.set_signs(diagnostics, bufnr, client_id, _, opts)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.set_signs is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)}
@@ -489,7 +489,7 @@ end
--- - severity_limit (DiagnosticSeverity):
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
function M.set_underline(diagnostics, bufnr, client_id, _, opts)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.set_underline is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)}
@@ -511,7 +511,7 @@ end
--- - severity_limit (DiagnosticSeverity):
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
function M.set_virtual_text(diagnostics, bufnr, client_id, _, opts)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.set_virtual_text is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)}
@@ -530,7 +530,7 @@ end
---@return an array of [text, hl_group] arrays. This can be passed directly to
--- the {virt_text} option of |nvim_buf_set_extmark()|.
function M.get_virtual_text_chunks_for_line(bufnr, _, line_diags, opts)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.get_virtual_text_chunks_for_line is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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
@@ -548,7 +548,7 @@ end
---@param position table|nil The (0,0)-indexed position
---@return table {popup_bufnr, win_id}
function M.show_position_diagnostics(opts, buf_nr, position)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.show_position_diagnostics is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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
@@ -572,7 +572,7 @@ end
---@param client_id number|nil the client id
---@return table {popup_bufnr, win_id}
function M.show_line_diagnostics(opts, buf_nr, line_nr, client_id)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.show_line_diagnostics is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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
@@ -596,7 +596,7 @@ end
--- client. The default is to redraw diagnostics for all attached
--- clients.
function M.redraw(bufnr, client_id)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.redraw is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)
@@ -624,7 +624,7 @@ end
--- - {workspace}: (boolean, default true)
--- - Set the list with workspace diagnostics
function M.set_qflist(opts)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.set_qflist is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)
@@ -656,7 +656,7 @@ end
--- - {workspace}: (boolean, default false)
--- - Set the list with workspace diagnostics
function M.set_loclist(opts)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.set_loclist is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)
@@ -684,7 +684,7 @@ end
-- send diagnostic information and the client will still process it. The
-- diagnostics are simply not displayed to the user.
function M.disable(bufnr, client_id)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.disable is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)
@@ -705,7 +705,7 @@ end
--- client. The default is to enable diagnostics for all attached
--- clients.
function M.enable(bufnr, client_id)
- vim.api.nvim_echo({{'vim.lsp.diagnostic.enable is deprecated. See :h deprecated', 'WarningMsg'}}, true, {})
+ 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)
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index c974d1a6b4..f5aefd4402 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -111,13 +111,15 @@ M['client/registerCapability'] = function(_, _, ctx)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
-M['workspace/applyEdit'] = function(_, workspace_edit)
+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)
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;
@@ -150,6 +152,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
@@ -159,6 +172,31 @@ M['textDocument/codeLens'] = function(...)
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,23 +207,26 @@ 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';
+ title = title_fn(ctx);
items = map_result(result, ctx.bufnr);
+ context = ctx;
})
api.nvim_command("lopen")
else
vim.fn.setqflist({}, ' ', {
- title = 'Language Server';
+ title = title_fn(ctx);
items = map_result(result, ctx.bufnr);
+ context = ctx;
})
api.nvim_command("botright copen")
end
@@ -194,31 +235,36 @@ local function response_to_list(map_result, entity)
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, _)
+M['textDocument/rename'] = function(_, result, ctx, _)
if not result then return end
- util.apply_workspace_edit(result)
+ 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)
+ 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)
+ 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
@@ -246,7 +292,7 @@ end
---@param config table Configuration table.
--- - border: (default=nil)
--- - Add borders to the floating window
---- - See |vim.api.nvim_open_win()|
+--- - See |nvim_open_win()|
function M.hover(_, result, ctx, config)
config = config or {}
config.focus_id = ctx.method
@@ -277,19 +323,23 @@ local function location_handler(_, result, ctx, _)
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)
-- 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)
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)
end
end
@@ -380,7 +430,7 @@ local make_call_hierarchy_handler = function(direction)
end
end
vim.fn.setqflist({}, ' ', {title = 'LSP call hierarchy', items = items})
- api.nvim_command("copen")
+ api.nvim_command("botright copen")
end
end
@@ -439,14 +489,20 @@ for k, fn in pairs(M) do
})
if err 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)
-- LSP spec:
-- interface ResponseError:
-- code: integer;
-- message: string;
-- data?: string | number | boolean | array | object | null;
- return err_message(client_name .. ': ' .. tostring(err.code) .. ': ' .. err.message)
+
+ -- 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)
+
+ err_message(client_name .. ': ' .. tostring(err.code) .. ': ' .. err.message)
+ end
+ return
end
return fn(err, result, ctx, config)
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index dbc473b52c..e0b5653587 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -8,8 +8,8 @@ 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'
--- Level numbers begin with 'trace' at 0
+-- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR"
+-- Level numbers begin with "TRACE" at 0
log.levels = vim.deepcopy(vim.log.levels)
-- Default log level is warn.
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 1fb75ddeb7..1ecac50df4 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -133,7 +133,8 @@ local function request_parser_loop()
end
end
-local client_errors = vim.tbl_add_reverse_lookup {
+--- Mapping of error codes used by the client
+local client_errors = {
INVALID_SERVER_MESSAGE = 1;
INVALID_SERVER_JSON = 2;
NO_RESULT_CALLBACK_FOUND = 3;
@@ -143,6 +144,8 @@ local client_errors = vim.tbl_add_reverse_lookup {
SERVER_RESULT_CALLBACK_ERROR = 7;
}
+client_errors = vim.tbl_add_reverse_lookup(client_errors)
+
--- Constructs an error message from an LSP error object.
---
---@param err (table) The error object
diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua
index d01f45ad8f..0f4e5b572b 100644
--- a/runtime/lua/vim/lsp/sync.lua
+++ b/runtime/lua/vim/lsp/sync.lua
@@ -298,7 +298,7 @@ end
---@private
-- rangelength depends on the offset encoding
--- bytes for utf-8 (clangd with extenion)
+-- bytes for utf-8 (clangd with extension)
-- codepoints for utf-16
-- codeunits for utf-32
-- Line endings count here as 2 chars for \r\n (dos), 1 char for \n (unix), and 1 char for \r (mac)
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 5921eb5bf0..655c3a4679 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -10,14 +10,6 @@ local uv = vim.loop
local npcall = vim.F.npcall
local split = vim.split
-local _warned = {}
-local warn_once = function(message)
- if not _warned[message] then
- vim.api.nvim_err_writeln(message)
- _warned[message] = true
- end
-end
-
local M = {}
local default_border = {
@@ -201,6 +193,11 @@ end
local function get_lines(bufnr, 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()
+ end
+
---@private
local function buf_lines()
local lines = {}
@@ -280,7 +277,7 @@ 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
@@ -289,7 +286,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
@@ -363,15 +360,14 @@ 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 defaults to encoding of first client of `bufnr`
---@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, 'string', false };
}
- offset_encoding = offset_encoding or M._get_offset_encoding(bufnr)
if not next(text_edits) then return end
if not api.nvim_buf_is_loaded(bufnr) then
vim.fn.bufload(bufnr)
@@ -405,25 +401,6 @@ 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
@@ -443,16 +420,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']),
+ end_col = get_line_byte_from_position(bufnr, text_edit.range['end'], offset_encoding),
text = vim.split(text_edit.newText, '\n', true),
}
+
+ -- 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 = vim.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
vim.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)
@@ -467,10 +466,13 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
end
end
+ local max = vim.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 })
end
@@ -479,7 +481,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
-- 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 get_line(bufnr, max - 1) == ''
if fix_eol then
vim.api.nvim_buf_set_lines(bufnr, -2, -1, false, {})
end
@@ -517,9 +519,12 @@ 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.
@@ -538,7 +543,7 @@ function M.apply_text_document_edit(text_document_edit, index)
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.
@@ -735,9 +740,13 @@ 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
@@ -753,7 +762,7 @@ function M.apply_workspace_edit(workspace_edit)
elseif change.kind then
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
@@ -766,7 +775,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
@@ -842,7 +851,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]
@@ -983,12 +993,16 @@ 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)
---@returns `true` if the jump succeeded
-function M.jump_to_location(location)
+function M.jump_to_location(location, offset_encoding)
-- location may be Location or LocationLink
local uri = location.uri or location.targetUri
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'"
@@ -1000,10 +1014,10 @@ function M.jump_to_location(location)
--- Jump to new location (adjusting for UTF-16 encoding of characters)
api.nvim_set_current_buf(bufnr)
- api.nvim_buf_set_option(0, 'buflisted', true)
+ api.nvim_buf_set_option(bufnr, 'buflisted', true)
local range = location.range or location.targetSelectionRange
local row = range.start.line
- local col = get_line_byte_from_position(0, range.start)
+ 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")
@@ -1505,18 +1519,20 @@ do --[[ References ]]
---@param bufnr number Buffer id
function M.buf_clear_references(bufnr)
validate { bufnr = {bufnr, 'n', true} }
- api.nvim_buf_clear_namespace(bufnr, reference_ns, 0, -1)
+ api.nvim_buf_clear_namespace(bufnr or 0, reference_ns, 0, -1)
end
--- Shows a list of document highlights for a certain buffer.
---
---@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"]
@@ -1534,7 +1550,8 @@ do --[[ References ]]
reference_ns,
document_highlight_kind[kind],
{ start_line, start_idx },
- { end_line, end_idx })
+ { end_line, end_idx },
+ { priority = vim.highlight.priorities.user })
end
end
end
@@ -1549,9 +1566,14 @@ 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)
@@ -1591,7 +1613,7 @@ function M.locations_to_items(locations)
local pos = temp.start
local row = pos.line
local line = lines[row] or ""
- local col = pos.character
+ local col = M._str_byteindex_enc(line, pos.character, offset_encoding)
table.insert(items, {
filename = filename,
lnum = row + 1,
@@ -1677,7 +1699,7 @@ function M.symbols_to_items(symbols, bufnr)
end
return _items
end
- return _symbols_to_items(symbols, {}, bufnr)
+ return _symbols_to_items(symbols, {}, bufnr or 0)
end
--- Removes empty lines from the beginning and end.
@@ -1775,7 +1797,13 @@ function M._get_offset_encoding(bufnr)
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
@@ -1796,7 +1824,7 @@ 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)
+ local buf = vim.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 {
@@ -1822,7 +1850,7 @@ function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding)
end_pos = {end_pos, 't', true};
offset_encoding = {offset_encoding, 's', true};
}
- bufnr = bufnr or 0
+ bufnr = bufnr or vim.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, '>'))
@@ -1904,7 +1932,9 @@ 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)
@@ -1928,7 +1958,6 @@ function M.lookup_section(settings, section)
end
M._get_line_byte_from_position = get_line_byte_from_position
-M._warn_once = warn_once
M.buf_versions = {}
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 1cf618725d..e170befa4c 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -526,13 +526,23 @@ end
--- => error('arg1: expected even number, got 3')
--- </pre>
---
----@param opt Map of parameter names to validations. Each key is a parameter
+--- If multiple types are valid they can be given as a list.
+--- <pre>
+--- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}}
+--- => NOP (success)
+---
+--- vim.validate{arg1={1, {'string', table'}}}
+--- => error('arg1: expected string|table, got number')
+---
+--- </pre>
+---
+---@param opt table of parameter names to validations. 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
---- - type_name: string type name, one of: ("table", "t", "string",
+--- - type_name: string|table type name, one of: ("table", "t", "string",
--- "s", "number", "n", "boolean", "b", "function", "f", "nil",
---- "thread", "userdata")
+--- "thread", "userdata") or list of them.
--- - optional: (optional) boolean, if true, `nil` is valid
--- 2. (arg_value, fn, msg)
--- - arg_value: argument value
@@ -571,31 +581,43 @@ do
end
local val = spec[1] -- Argument value.
- local t = spec[2] -- Type name, or callable.
+ local types = spec[2] -- Type name, or callable.
local optional = (true == spec[3])
- if type(t) == 'string' then
- local t_name = type_names[t]
- if not t_name then
- return false, string.format('invalid type name: %s', t)
- end
+ if type(types) == 'string' then
+ types = {types}
+ end
- if (not optional or val ~= nil) and not _is_type(val, t_name) then
- return false, string.format("%s: expected %s, got %s", param_name, t_name, type(val))
- end
- elseif vim.is_callable(t) then
+ if vim.is_callable(types) then
-- Check user-provided validation function.
- local valid, optional_message = t(val)
+ 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 '?'), 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)
end
return false, error_message
end
+ elseif type(types) == 'table' then
+ local success = false
+ for i, t in ipairs(types) do
+ local t_name = type_names[t]
+ if not t_name then
+ return false, string.format('invalid type name: %s', t)
+ end
+ types[i] = t_name
+
+ if (optional and val == nil) or _is_type(val, t_name) then
+ success = true
+ break
+ end
+ end
+ if not success then
+ 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(t))
+ return false, string.format("invalid type name: %s", tostring(types))
end
end
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 07f6418c0c..f9d539f028 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -11,6 +11,7 @@ local parsers = {}
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)
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 22b528838c..b6f61cfb2e 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -22,7 +22,21 @@ local _link_default_highlight_once = function(from, to)
return from
end
-TSHighlighter.hl_map = {
+-- 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",
-- Miscs
@@ -66,7 +80,7 @@ TSHighlighter.hl_map = {
["type.builtin"] = "Type",
["structure"] = "Structure",
["include"] = "Include",
-}
+}, subcapture_fallback)
---@private
local function is_highlight_name(capture_name)
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index 6f347ff25f..8b106108df 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
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 594765761d..b83df65151 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -76,8 +76,8 @@ function LanguageTree:lang()
end
--- Determines whether this tree is valid.
---- If the tree is invalid, `parse()` must be called
---- to get the an 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 +234,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
@@ -448,14 +450,14 @@ function LanguageTree:_on_detach(...)
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
@@ -493,7 +495,7 @@ local function tree_contains(tree, range)
return false
end
---- Determines whether @param range is contained in this language tree
+--- Determines whether {range} is contained in this language tree
---
--- This goes down the tree to recursively check children.
---
@@ -508,7 +510,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..8383551b5f 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -138,30 +138,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
@@ -186,11 +199,13 @@ 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")
diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua
index 9568b60fd0..9d4b38f08a 100644
--- a/runtime/lua/vim/ui.lua
+++ b/runtime/lua/vim/ui.lua
@@ -18,6 +18,24 @@ local M = {}
--- Called once the user made a choice.
--- `idx` is the 1-based index of `item` within `item`.
--- `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 {
items = { items, 'table', false },
@@ -57,6 +75,13 @@ 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 {
on_confirm = { on_confirm, 'function', false },