diff options
Diffstat (limited to 'runtime/lua/vim')
| -rw-r--r-- | runtime/lua/vim/diagnostic.lua | 21 | ||||
| -rw-r--r-- | runtime/lua/vim/filetype.lua | 1620 | ||||
| -rw-r--r-- | runtime/lua/vim/highlight.lua | 19 | ||||
| -rw-r--r-- | runtime/lua/vim/keymap.lua | 135 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp.lua | 233 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 26 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/diagnostic.lua | 46 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/handlers.lua | 100 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/sync.lua | 2 | ||||
| -rw-r--r-- | runtime/lua/vim/lsp/util.lua | 147 | ||||
| -rw-r--r-- | runtime/lua/vim/treesitter.lua | 1 | ||||
| -rw-r--r-- | runtime/lua/vim/treesitter/language.lua | 2 | ||||
| -rw-r--r-- | runtime/lua/vim/treesitter/languagetree.lua | 2 | ||||
| -rw-r--r-- | runtime/lua/vim/treesitter/query.lua | 39 | ||||
| -rw-r--r-- | runtime/lua/vim/ui.lua | 25 | 
15 files changed, 2196 insertions, 222 deletions
| diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 417b661155..b4537c2882 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, @@ -552,7 +552,8 @@ end  ---         - `table`: Enable this feature with overrides. Use an empty table to use default values.  ---         - `function`: Function with signature (namespace, bufnr) that returns any of the above.  --- ----@param opts table Configuration table with the following keys: +---@param opts table|nil When omitted or "nil", retrieve the current configuration. Otherwise, a +---                      configuration table with the following keys:  ---       - underline: (default true) Use underline for diagnostics. Options:  ---                    * severity: Only underline diagnostics matching the given severity  ---                    |diagnostic-severity| @@ -599,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 },    } @@ -611,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 @@ -823,6 +827,7 @@ M.handlers.signs = {      }      bufnr = get_bufnr(bufnr) +    opts = opts or {}      if opts.signs and opts.signs.severity then        diagnostics = filter_by_severity(opts.signs.severity, diagnostics) @@ -890,6 +895,7 @@ M.handlers.underline = {      }      bufnr = get_bufnr(bufnr) +    opts = opts or {}      if opts.underline and opts.underline.severity then        diagnostics = filter_by_severity(opts.underline.severity, diagnostics) @@ -942,6 +948,7 @@ M.handlers.virtual_text = {      }      bufnr = get_bufnr(bufnr) +    opts = opts or {}      local severity      if opts.virtual_text then diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua new file mode 100644 index 0000000000..2fe4aa3d32 --- /dev/null +++ b/runtime/lua/vim/filetype.lua @@ -0,0 +1,1620 @@ +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", +  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", +  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", +  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", +  [".*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..12faa0a6e1 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -25,16 +25,29 @@ end  ---@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) +---@param priority number indicating priority of highlight (default 50) +function highlight.range(bufnr, ns, higroup, start, finish, rtype, inclusive, priority)    rtype = rtype or 'v'    inclusive = inclusive or false +  priority = priority or 50    -- sanity check    if start[2] < 0 or finish[1] < start[1] then return end    local region = vim.region(bufnr, start, finish, rtype, 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 @@ -82,7 +95,7 @@ function highlight.on_yank(opts)    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) +  highlight.range(bufnr, yank_ns, higroup, pos1, pos2, event.regtype, event.inclusive, 200)    vim.defer_fn(      function() diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua new file mode 100644 index 0000000000..d53b790746 --- /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 `true` if `lhs` is a string starting with `<plug>` (case-insensitive), `false` otherwise. +---@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 +    -- remap by default on <plug> mappings and don't otherwise. +    opts.noremap = is_rhs_luaref or rhs:lower():match("^<plug>") == nil +  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 cfbabb12a6..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) @@ -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 @@ -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 @@ -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 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 a1d3b2aa94..a997b887d9 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 @@ -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)}) +      vim.fn.setqflist({}, ' ', { +        title = 'LSP locations', +        items = util.locations_to_items(result, client.offset_encoding) +      })        api.nvim_command("copen")      end    else -    util.jump_to_location(result) +    util.jump_to_location(result, client.offset_encoding)    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/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 dcd68a3433..d22c00ae76 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'" @@ -1003,7 +1017,7 @@ function M.jump_to_location(location)    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(bufnr, 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") @@ -1512,11 +1526,13 @@ do --[[ References ]]    ---    ---@param bufnr number Buffer id    ---@param references table List of `DocumentHighlight` objects to highlight -  ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32", or nil. Defaults to `offset_encoding` of first client of `bufnr` +  ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32".    ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#documentHighlight    function M.buf_highlight_references(bufnr, references, offset_encoding) -    validate { bufnr = {bufnr, 'n', true} } -    offset_encoding = offset_encoding or M._get_offset_encoding(bufnr) +    validate { +      bufnr = {bufnr, 'n', true}, +      offset_encoding = { offset_encoding, 'string', false }; +    }      for _, reference in ipairs(references) do        local start_line, start_char = reference["range"]["start"]["line"], reference["range"]["start"]["character"]        local end_line, end_char = reference["range"]["end"]["line"], reference["range"]["end"]["character"] @@ -1534,7 +1550,10 @@ do --[[ References ]]                        reference_ns,                        document_highlight_kind[kind],                        { start_line, start_idx }, -                      { end_line, end_idx }) +                      { end_line, end_idx }, +                      nil, +                      false, +                      40)      end    end  end @@ -1549,9 +1568,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 +1615,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, @@ -1775,7 +1799,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 @@ -1822,7 +1852,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 +1934,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 +1960,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/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/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..85fd5cd8e0 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -77,7 +77,7 @@ end  --- Determines whether this tree is valid.  --- If the tree is invalid, `parse()` must be called ---- to get the an updated tree. +--- to get the updated tree.  function LanguageTree:is_valid()    return self._valid  end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index ebed502c92..14940332d6 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -138,6 +138,13 @@ 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).  --- @@ -151,17 +158,23 @@ end  ---   -` 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 }, | 
