From a2e5555c64d61c8ffa173298f582e8cfe32d9ea6 Mon Sep 17 00:00:00 2001 From: Simon Lasbrugnas Date: Mon, 2 Feb 2026 16:11:23 +0100 Subject: [PATCH] fix(lsp)!: actually use the root lsp/ folder and remove lua/custom/lsp/** configs This commit removes the need for nvim-lspconfig which is replaced by the vim.lsp API. The lsp/ folder is a copy of nvim-lspconfig's lsp/ folder as it makes it easy to modify LSP configurations on the fly. Conform: biome has been replaced with oxfmt --- lazy-lock.json | 23 +- lsp/angularls.lua | 142 ++++-- lsp/apex_ls.lua | 2 + lsp/arduino_language_server.lua | 2 + lsp/ast_grep.lua | 27 +- lsp/autohotkey_lsp.lua | 1 + lsp/basedpyright.lua | 9 +- lsp/biome.lua | 13 +- lsp/bitbake_language_server.lua | 9 +- lsp/brioche.lua | 12 + lsp/buf_ls.lua | 10 +- lsp/bufls.lua | 17 - lsp/codebook.lua | 2 + lsp/copilot.lua | 29 +- lsp/csharp_ls.lua | 16 +- lsp/css_variables.lua | 14 +- lsp/denols.lua | 20 +- lsp/djls.lua | 12 + lsp/ds_pinyin_lsp.lua | 2 + lsp/elixirls.lua | 1 + lsp/emmet_language_server.lua | 2 - lsp/erlangls.lua | 21 - lsp/eslint.lua | 14 +- lsp/expert.lua | 2 +- lsp/flow.lua | 11 +- lsp/gitlab_duo.lua | 448 ++++++++++++++++++ lsp/glint.lua | 1 + lsp/gn_language_server.lua | 13 + lsp/golangci_lint_ls.lua | 17 +- lsp/harper_ls.lua | 3 +- lsp/hls.lua | 2 +- lsp/home_assistant.lua | 18 + lsp/html.lua | 2 +- lsp/hylo_ls.lua | 13 + lsp/intelephense.lua | 7 + lsp/java_language_server.lua | 1 + lsp/jdtls.lua | 89 ++-- lsp/julials.lua | 9 +- lsp/kotlin_language_server.lua | 2 +- lsp/lexical.lua | 7 +- lsp/ltex_plus.lua | 2 + lsp/lua_ls.lua | 71 +-- lsp/neocmake.lua | 4 +- lsp/nextls.lua | 3 +- lsp/ocamlls.lua | 20 - lsp/ocamllsp.lua | 66 ++- lsp/omnisharp.lua | 5 +- lsp/oso.lua | 32 ++ lsp/oxfmt.lua | 52 ++ lsp/oxlint.lua | 65 ++- lsp/pony_language_server.lua | 12 + lsp/postgres_lsp.lua | 7 +- lsp/pyright.lua | 6 +- lsp/roslyn_ls.lua | 29 +- lsp/ruby_lsp.lua | 12 +- lsp/rumdl.lua | 12 + lsp/rust_analyzer.lua | 42 ++ lsp/slangd.lua | 8 +- lsp/smarty_ls.lua | 2 +- lsp/snyk_ls.lua | 84 +++- lsp/sourcekit.lua | 2 +- lsp/stylua.lua | 2 +- lsp/svelte.lua | 1 + lsp/systemd_ls.lua | 22 +- lsp/systemd_lsp.lua | 20 + lsp/tailwindcss.lua | 7 + lsp/tblgen_lsp_server.lua | 7 +- lsp/tclsp.lua | 22 + lsp/termux_language_server.lua | 25 +- lsp/texlab.lua | 32 +- lsp/tinymist.lua | 15 +- lsp/tofu_ls.lua | 2 +- lsp/ts_ls.lua | 43 +- lsp/tsgo.lua | 17 +- lsp/ty.lua | 2 +- lsp/vale_ls.lua | 2 +- lsp/volar.lua | 12 +- lsp/vscoqtop.lua | 15 +- lsp/vsrocq.lua | 10 + lsp/vtsls.lua | 6 + lsp/wc_language_server.lua | 85 ++++ lua/custom/lsp/config/biome.lua | 11 - .../docker_compose_language_service.lua | 1 - lua/custom/lsp/config/dockerls.lua | 1 - lua/custom/lsp/config/eslint.lua | 11 - lua/custom/lsp/config/gopls.lua | 48 -- lua/custom/lsp/config/html.lua | 11 - lua/custom/lsp/config/jsonls.lua | 17 - lua/custom/lsp/config/lua_ls.lua | 10 - lua/custom/lsp/config/prismals.lua | 1 - lua/custom/lsp/config/tailwindcss.lua | 1 - lua/custom/lsp/config/ts_ls.lua | 6 - lua/custom/lsp/config/yamlls.lua | 11 - lua/custom/lspconfig.lua | 9 + lua/custom/plugins/conform.lua | 47 +- lua/custom/plugins/mason-lspconfig.lua | 7 +- lua/custom/plugins/nvim-lint.lua | 27 -- 97 files changed, 1616 insertions(+), 531 deletions(-) create mode 100644 lsp/brioche.lua delete mode 100644 lsp/bufls.lua create mode 100644 lsp/djls.lua delete mode 100644 lsp/erlangls.lua create mode 100644 lsp/gitlab_duo.lua create mode 100644 lsp/gn_language_server.lua create mode 100644 lsp/home_assistant.lua create mode 100644 lsp/hylo_ls.lua delete mode 100644 lsp/ocamlls.lua create mode 100644 lsp/oso.lua create mode 100644 lsp/oxfmt.lua create mode 100644 lsp/pony_language_server.lua create mode 100644 lsp/rumdl.lua create mode 100644 lsp/systemd_lsp.lua create mode 100644 lsp/tclsp.lua create mode 100644 lsp/vsrocq.lua create mode 100644 lsp/wc_language_server.lua delete mode 100644 lua/custom/lsp/config/biome.lua delete mode 100644 lua/custom/lsp/config/docker_compose_language_service.lua delete mode 100644 lua/custom/lsp/config/dockerls.lua delete mode 100644 lua/custom/lsp/config/eslint.lua delete mode 100644 lua/custom/lsp/config/gopls.lua delete mode 100644 lua/custom/lsp/config/html.lua delete mode 100644 lua/custom/lsp/config/jsonls.lua delete mode 100644 lua/custom/lsp/config/lua_ls.lua delete mode 100644 lua/custom/lsp/config/prismals.lua delete mode 100644 lua/custom/lsp/config/tailwindcss.lua delete mode 100644 lua/custom/lsp/config/ts_ls.lua delete mode 100644 lua/custom/lsp/config/yamlls.lua create mode 100644 lua/custom/lspconfig.lua delete mode 100644 lua/custom/plugins/nvim-lint.lua diff --git a/lazy-lock.json b/lazy-lock.json index a4656b4..7813f88 100644 --- a/lazy-lock.json +++ b/lazy-lock.json @@ -11,24 +11,24 @@ "conform.nvim": { "branch": "master", "commit": "c2526f1cde528a66e086ab1668e996d162c75f4f" }, "dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" }, "format-ts-errors.nvim": { "branch": "main", "commit": "4b7418d6689bc0fd3c1db0500c67133422522384" }, - "fzf-lua": { "branch": "main", "commit": "32f34220c8e9f1111dd082f9790e72683ca8531a" }, + "fzf-lua": { "branch": "main", "commit": "e5804f4924cf74ad03834c25988998a273ae0d7b" }, "github-nvim-theme": { "branch": "main", "commit": "c106c9472154d6b2c74b74565616b877ae8ed31d" }, "gitsigns.nvim": { "branch": "main", "commit": "abf82a65f185bd54adc0679f74b7d6e1ada690c9" }, - "go.nvim": { "branch": "master", "commit": "41a18f0c05534c375bafec7ed05cdb409c4abcc6" }, + "go.nvim": { "branch": "master", "commit": "d89ea7aba93220383ded5bb22b1e82841980cecf" }, "gruvbox": { "branch": "master", "commit": "697c00291db857ca0af00ec154e5bd514a79191f" }, "hybrid.nvim": { "branch": "master", "commit": "74dfee0d5084a3db5e2ad0a78a67ee45e93a64bf" }, "img-clip.nvim": { "branch": "main", "commit": "b6ddfb97b5600d99afe3452d707444afda658aca" }, "indent-blankline.nvim": { "branch": "master", "commit": "005b56001b2cb30bfa61b7986bc50657816ba4ba" }, - "kanagawa-paper.nvim": { "branch": "master", "commit": "c85d672cc9a6d968771dc6f2203c1dc2bade6ff2" }, + "kanagawa-paper.nvim": { "branch": "master", "commit": "ccd5f727919bfe93af76561c31f43b53cd2871db" }, "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" }, "lazydev.nvim": { "branch": "main", "commit": "5231c62aa83c2f8dc8e7ba957aa77098cda1257d" }, "lsp-zero.nvim": { "branch": "v4.x", "commit": "d388e2b71834c826e61a3eba48caec53d7602510" }, - "lsp_signature.nvim": { "branch": "master", "commit": "7d3bb0a641f516f1c7fd2e47852580dadbd7a430" }, - "lspkind.nvim": { "branch": "master", "commit": "dbac5149fb5fb1b642266ff268b1e0f4ebac9293" }, + "lsp_signature.nvim": { "branch": "master", "commit": "0efb088dce050d38a3608ee69f80f2a62cf9849c" }, + "lspkind.nvim": { "branch": "master", "commit": "c7274c48137396526b59d86232eabcdc7fed8a32" }, "lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" }, "lush.nvim": { "branch": "main", "commit": "9c60ec2279d62487d942ce095e49006af28eed6e" }, "luvit-meta": { "branch": "main", "commit": "0ea4ff636c5bb559ffa78108561d0976f4de9682" }, - "mason-lspconfig.nvim": { "branch": "main", "commit": "4823a251e7578a835bb979c37df390fca692ba39" }, + "mason-lspconfig.nvim": { "branch": "main", "commit": "c953789db7fd28eafe5eb5659846d34b5024b3cc" }, "mason-nvim-dap.nvim": { "branch": "main", "commit": "9a10e096703966335bd5c46c8c875d5b0690dade" }, "mason.nvim": { "branch": "main", "commit": "44d1e90e1f66e077268191e3ee9d2ac97cc18e65" }, "mellow.nvim": { "branch": "main", "commit": "5cd188489bcc7eb512f0a30581ad972070f8e5cd" }, @@ -39,25 +39,24 @@ "nordic.nvim": { "branch": "main", "commit": "962c717820a9d7201ef7622cf1e78bd57806bb7c" }, "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" }, "nvim-cmp": { "branch": "main", "commit": "da88697d7f45d16852c6b2769dc52387d1ddc45f" }, - "nvim-dap": { "branch": "master", "commit": "085386b9359ddf8d76ad89b98973b8e332dc5ba3" }, + "nvim-dap": { "branch": "master", "commit": "e47878dcf1ccc30136b30d19ab19fe76946d61cd" }, "nvim-dap-ui": { "branch": "master", "commit": "cf91d5e2d07c72903d052f5207511bf7ecdb7122" }, "nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" }, - "nvim-lint": { "branch": "master", "commit": "ca6ea12daf0a4d92dc24c5c9ae22a1f0418ade37" }, - "nvim-lspconfig": { "branch": "master", "commit": "419b082102fa813739588dd82e19a8b6b2442855" }, + "nvim-lspconfig": { "branch": "master", "commit": "3f58aeca0c6ece8a9fb8782ea3fcb6024f285be3" }, "nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" }, - "nvim-treesitter": { "branch": "main", "commit": "568ede7e79172a0fe7c9d631454a97ad968deaf2" }, + "nvim-treesitter": { "branch": "main", "commit": "4967fa48b0fe7a7f92cee546c76bb4bb61bb14d5" }, "nvim-treesitter-context": { "branch": "master", "commit": "64dd4cf3f6fd0ab17622c5ce15c91fc539c3f24a" }, "nvim-ts-autotag": { "branch": "main", "commit": "db15f2e0df2f5db916e511e3fffb682ef2f6354f" }, "nvim-web-devicons": { "branch": "master", "commit": "803353450c374192393f5387b6a0176d0972b848" }, "oil.nvim": { "branch": "master", "commit": "f55b25e493a7df76371cfadd0ded5004cb9cd48a" }, "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, - "render-markdown.nvim": { "branch": "main", "commit": "c54380dd4d8d1738b9691a7c349ecad7967ac12e" }, + "render-markdown.nvim": { "branch": "main", "commit": "48934b49a2363b49ae1d698ed4cb30fb79d7efe8" }, "supermaven-nvim": { "branch": "main", "commit": "07d20fce48a5629686aefb0a7cd4b25e33947d50" }, "telescope-fzf-native.nvim": { "branch": "main", "commit": "6fea601bd2b694c6f2ae08a6c6fab14930c60e2c" }, "telescope.nvim": { "branch": "master", "commit": "ad7d9580338354ccc136e5b8f0aa4f880434dcdc" }, "transparent.nvim": { "branch": "main", "commit": "8ac59883de84e9cd1850ea25cf087031c5ba7d54" }, "typescript-tools.nvim": { "branch": "master", "commit": "c2f5910074103705661e9651aa841e0d7eea9932" }, - "vague.nvim": { "branch": "main", "commit": "1e3ae7fbfa952371a477348fe8df4e33dc7e7480" }, + "vague.nvim": { "branch": "main", "commit": "30d2239ecf8adab9bc0d07d42e7a07283879dab6" }, "vim-bujo": { "branch": "master", "commit": "ee3fecb5b3206b8073ac3fe4153391f320c99197" }, "vim-fugitive": { "branch": "master", "commit": "61b51c09b7c9ce04e821f6cf76ea4f6f903e3cf4" }, "zenbones.nvim": { "branch": "main", "commit": "4635a3f46d1066975d1074cd9f61f93cb1e32f64" } diff --git a/lsp/angularls.lua b/lsp/angularls.lua index 80b9d11..af82b87 100644 --- a/lsp/angularls.lua +++ b/lsp/angularls.lua @@ -14,72 +14,110 @@ -- Angular requires a node_modules directory to probe for @angular/language-service and typescript -- in order to use your projects configured versions. -local root_dir = vim.fn.getcwd() -local node_modules_dir = vim.fs.find('node_modules', { path = root_dir, upward = true })[1] -local project_root = node_modules_dir and vim.fs.dirname(node_modules_dir) or '?' +local fs, fn, uv = vim.fs, vim.fn, vim.uv -local function get_probe_dir() - return project_root and (project_root .. '/node_modules') or '' +--- Recursively solve for the original ngserver path on Windows +-- For a given ngserver path: +-- - If it is not a CMD wrapper, return the path; +-- - Or else, extract the path from the CMD wrapper. +-- +-- @param cmd_path (string) path for the ngserver executable or its CMD wrapper. +-- @return (string) the original executable path for ngserver +-- @usage +-- -- Base case: cmd_path already points to ngserver (expected behavior on Linux) +-- resolve_cmd_shim('/home/user/project/node_modules/@angular/language-server/bin/ngserver') +-- => '/home/user/project/node_modules/@angular/language-server/bin/ngserver' +-- +-- -- Recursive case: cmd_path points to a CMD wrapper (Windows) +-- resolve_cmd_shim('C:/Users/user/project/node_modules/.bin/ngserver.cmd') +-- => 'C:/Users/user/project/node_modules/@angular/language-server/bin/ngserver' +local function resolve_cmd_shim(cmd_path) + if not cmd_path:lower():match('%ngserver.cmd$') then + return cmd_path + end + + local ok, content = pcall(fn.readblob, cmd_path) + if not ok or not content then + return cmd_path + end + + local target = content:match('%s%"%%dp0%%\\([^\r\n]-ngserver[^\r\n]-)%"') + if not target then + return cmd_path + end + + local full = fs.normalize(fs.joinpath(fs.dirname(cmd_path), target)) + + return resolve_cmd_shim(full) end -local function get_angular_core_version() - if not project_root then - return '' +local function collect_node_modules(root_dir) + local results = {} + + local project_node = fs.joinpath(root_dir, 'node_modules') + if uv.fs_stat(project_node) then + table.insert(results, project_node) end - local package_json = project_root .. '/package.json' - if not vim.uv.fs_stat(package_json) then - return '' + local ngserver_exe = fn.exepath('ngserver') + if ngserver_exe and #ngserver_exe > 0 then + local realpath = uv.fs_realpath(ngserver_exe) or ngserver_exe + realpath = resolve_cmd_shim(realpath) + local candidate = fs.normalize(fs.joinpath(fs.dirname(realpath), '../../..')) + if uv.fs_stat(candidate) then + table.insert(results, candidate) + end end - local contents = io.open(package_json):read '*a' - local json = vim.json.decode(contents) - if not json.dependencies then - return '' - end - - local angular_core_version = json.dependencies['@angular/core'] - - angular_core_version = angular_core_version and angular_core_version:match('%d+%.%d+%.%d+') - - return angular_core_version + return results end -local default_probe_dir = get_probe_dir() -local default_angular_core_version = get_angular_core_version() +local function get_angular_core_version(root_dir) + local package_json = fs.joinpath(root_dir, 'package.json') + if not uv.fs_stat(package_json) then + return '' + end --- structure should be like --- - $EXTENSION_PATH --- - @angular --- - language-server --- - bin --- - ngserver --- - typescript -local ngserver_exe = vim.fn.exepath('ngserver') -local ngserver_path = #(ngserver_exe or '') > 0 and vim.fs.dirname(vim.uv.fs_realpath(ngserver_exe)) or '?' -local extension_path = vim.fs.normalize(vim.fs.joinpath(ngserver_path, '../../../')) + local ok, content = pcall(fn.readblob, package_json) + if not ok or not content then + return '' + end --- angularls will get module by `require.resolve(PROBE_PATH, MODULE_NAME)` of nodejs -local ts_probe_dirs = vim.iter({ extension_path, default_probe_dir }):join(',') -local ng_probe_dirs = vim - .iter({ extension_path, default_probe_dir }) - :map(function(p) - return vim.fs.joinpath(p, '/@angular/language-server/node_modules') - end) - :join(',') + local json = vim.json.decode(content) or {} + + local version = (json.dependencies or {})['@angular/core'] or '' + return version:match('%d+%.%d+%.%d+') or '' +end ---@type vim.lsp.Config return { - cmd = { - 'ngserver', - '--stdio', - '--tsProbeLocations', - ts_probe_dirs, - '--ngProbeLocations', - ng_probe_dirs, - '--angularCoreVersion', - default_angular_core_version, - }, + cmd = function(dispatchers, config) + local root_dir = (config and config.root_dir) or fn.getcwd() + local node_paths = collect_node_modules(root_dir) + + local ts_probe = table.concat(node_paths, ',') + local ng_probe = table.concat( + vim + .iter(node_paths) + :map(function(p) + return fs.joinpath(p, '@angular/language-server/node_modules') + end) + :totable(), + ',' + ) + local cmd = { + 'ngserver', + '--stdio', + '--tsProbeLocations', + ts_probe, + '--ngProbeLocations', + ng_probe, + '--angularCoreVersion', + get_angular_core_version(root_dir), + } + return vim.lsp.rpc.start(cmd, dispatchers) + end, + filetypes = { 'typescript', 'html', 'typescriptreact', 'typescript.tsx', 'htmlangular' }, root_markers = { 'angular.json', 'nx.json' }, } diff --git a/lsp/apex_ls.lua b/lsp/apex_ls.lua index 01616c9..7052c19 100644 --- a/lsp/apex_ls.lua +++ b/lsp/apex_ls.lua @@ -36,6 +36,7 @@ ---@type vim.lsp.Config return { cmd = function(dispatchers, config) + ---@diagnostic disable: undefined-field local local_cmd = { vim.env.JAVA_HOME and (vim.env.JAVA_HOME .. '/bin/java') or 'java', '-cp', @@ -48,6 +49,7 @@ return { if config.apex_jvm_max_heap then table.insert(local_cmd, '-Xmx' .. config.apex_jvm_max_heap) end + ---@diagnostic enable: undefined-field table.insert(local_cmd, 'apex.jorje.lsp.ApexLanguageServerLauncher') return vim.lsp.rpc.start(local_cmd, dispatchers) diff --git a/lsp/arduino_language_server.lua b/lsp/arduino_language_server.lua index 0feccd5..769a917 100644 --- a/lsp/arduino_language_server.lua +++ b/lsp/arduino_language_server.lua @@ -82,9 +82,11 @@ return { }, capabilities = { textDocument = { + ---@diagnostic disable-next-line: assign-type-mismatch semanticTokens = vim.NIL, }, workspace = { + ---@diagnostic disable-next-line: assign-type-mismatch semanticTokens = vim.NIL, }, }, diff --git a/lsp/ast_grep.lua b/lsp/ast_grep.lua index 47388a9..5ac225f 100644 --- a/lsp/ast_grep.lua +++ b/lsp/ast_grep.lua @@ -17,23 +17,32 @@ return { return client.config.cmd_cwd == config.cmd_cwd end, filetypes = { -- https://ast-grep.github.io/reference/languages.html + 'bash', 'c', 'cpp', - 'rust', + 'cs', + 'css', + 'elixir', 'go', + 'haskell', + 'html', 'java', - 'python', 'javascript', 'javascriptreact', - 'javascript.jsx', + 'json', + 'kotlin', + 'lua', + 'nix', + 'php', + 'python', + 'ruby', + 'rust', + 'scala', + 'solidity', + 'swift', 'typescript', 'typescriptreact', - 'typescript.tsx', - 'html', - 'css', - 'kotlin', - 'dart', - 'lua', + 'yaml', }, root_markers = { 'sgconfig.yaml', 'sgconfig.yml' }, } diff --git a/lsp/autohotkey_lsp.lua b/lsp/autohotkey_lsp.lua index cb92330..fe11d15 100644 --- a/lsp/autohotkey_lsp.lua +++ b/lsp/autohotkey_lsp.lua @@ -16,6 +16,7 @@ return { cmd = { 'autohotkey_lsp', '--stdio' }, filetypes = { 'autohotkey' }, root_markers = { 'package.json' }, + ---@diagnostic disable-next-line: missing-fields flags = { debounce_text_changes = 500 }, --capabilities = capabilities, --on_attach = custom_attach, diff --git a/lsp/basedpyright.lua b/lsp/basedpyright.lua index 5f54c3d..fee784e 100644 --- a/lsp/basedpyright.lua +++ b/lsp/basedpyright.lua @@ -12,6 +12,7 @@ local function set_python_path(command) } for _, client in ipairs(clients) do if client.settings then + ---@diagnostic disable-next-line: param-type-mismatch client.settings.python = vim.tbl_deep_extend('force', client.settings.python or {}, { pythonPath = path }) else client.config.settings = vim.tbl_deep_extend('force', client.config.settings, { python = { pythonPath = path } }) @@ -25,20 +26,23 @@ return { cmd = { 'basedpyright-langserver', '--stdio' }, filetypes = { 'python' }, root_markers = { + 'pyrightconfig.json', 'pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt', 'Pipfile', - 'pyrightconfig.json', '.git', }, settings = { basedpyright = { analysis = { autoSearchPaths = true, - useLibraryCodeForTypes = true, diagnosticMode = 'openFilesOnly', + -- https://docs.basedpyright.com/latest/configuration/language-server-settings/ + -- Explicitly setting `basedpyright.analysis.useLibraryCodeForTypes` is **discouraged** by the official docs. + -- Because it will override per-project configurations like `pyproject.toml`. + -- If left unset, its default value is `true`, and it can be correctly overridden by project config files. }, }, }, @@ -52,6 +56,7 @@ return { -- Using client.request() directly because "basedpyright.organizeimports" is private -- (not advertised via capabilities), which client:exec_cmd() refuses to call. -- https://github.com/neovim/neovim/blob/c333d64663d3b6e0dd9aa440e433d346af4a3d81/runtime/lua/vim/lsp/client.lua#L1024-L1030 + ---@diagnostic disable-next-line: param-type-mismatch client.request('workspace/executeCommand', params, nil, bufnr) end, { desc = 'Organize Imports', diff --git a/lsp/biome.lua b/lsp/biome.lua index a134347..8dc91cf 100644 --- a/lsp/biome.lua +++ b/lsp/biome.lua @@ -34,7 +34,6 @@ return { 'jsonc', 'svelte', 'typescript', - 'typescript.tsx', 'typescriptreact', 'vue', }, @@ -44,10 +43,18 @@ return { -- As stated in the documentation above, this LSP supports monorepos and simple projects. -- We select then from the project root, which is identified by the presence of a package -- manager lock file. - local root_markers = { 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb', 'bun.lock' } + local root_markers = { + 'package-lock.json', + 'yarn.lock', + 'pnpm-lock.yaml', + 'bun.lockb', + 'bun.lock', + 'deno.lock', + } -- Give the root markers equal priority by wrapping them in a table root_markers = vim.fn.has('nvim-0.11.3') == 1 and { root_markers, { '.git' } } or vim.list_extend(root_markers, { '.git' }) + -- We fallback to the current working directory if no project root is found local project_root = vim.fs.root(bufnr, root_markers) or vim.fn.getcwd() @@ -55,7 +62,7 @@ return { -- in its directory tree. local filename = vim.api.nvim_buf_get_name(bufnr) local biome_config_files = { 'biome.json', 'biome.jsonc' } - biome_config_files = util.insert_package_json(biome_config_files, 'biome', filename) + biome_config_files = util.insert_package_json(biome_config_files, 'biomejs', filename) local is_buffer_using_biome = vim.fs.find(biome_config_files, { path = filename, type = 'file', diff --git a/lsp/bitbake_language_server.lua b/lsp/bitbake_language_server.lua index 36e08b3..846db07 100644 --- a/lsp/bitbake_language_server.lua +++ b/lsp/bitbake_language_server.lua @@ -1,6 +1,13 @@ ---@brief --- ---- 🛠️ bitbake language server +--- https://github.com/Freed-Wu/bitbake-language-server +--- +--- `bitbake-language-server` can be installed via `pip`: +--- ```sh +--- pip install bitbake-language-server +--- ``` +--- +--- Language server for bitbake. ---@type vim.lsp.Config return { diff --git a/lsp/brioche.lua b/lsp/brioche.lua new file mode 100644 index 0000000..24bf7e1 --- /dev/null +++ b/lsp/brioche.lua @@ -0,0 +1,12 @@ +---@brief +--- +--- https://github.com/brioche-dev/brioche +--- +--- `Brioche Language Server`. + +---@type vim.lsp.Config +return { + cmd = { 'brioche', 'lsp' }, + filetypes = { 'brioche' }, + root_markers = { 'project.bri' }, +} diff --git a/lsp/buf_ls.lua b/lsp/buf_ls.lua index 75c0b44..2d2f5a7 100644 --- a/lsp/buf_ls.lua +++ b/lsp/buf_ls.lua @@ -1,13 +1,17 @@ --- @brief --- https://github.com/bufbuild/buf --- ---- buf beta lsp included in the cli itself +--- buf lsp included in the cli itself --- ---- buf beta lsp is a Protobuf language server compatible with Buf modules and workspaces +--- buf lsp is a Protobuf language server compatible with Buf modules and workspaces ---@type vim.lsp.Config return { - cmd = { 'buf', 'beta', 'lsp', '--timeout=0', '--log-format=text' }, + cmd = { 'buf', 'lsp', 'serve', '--log-format=text' }, filetypes = { 'proto' }, root_markers = { 'buf.yaml', '.git' }, + reuse_client = function(client, config) + -- `buf lsp serve` is meant to be used with multiple workspaces. + return client.name == config.name + end, } diff --git a/lsp/bufls.lua b/lsp/bufls.lua deleted file mode 100644 index e7dc52f..0000000 --- a/lsp/bufls.lua +++ /dev/null @@ -1,17 +0,0 @@ ----@brief ---- ---- https://github.com/bufbuild/buf-language-server ---- ---- `buf-language-server` can be installed via `go install`: ---- ```sh ---- go install github.com/bufbuild/buf-language-server/cmd/bufls@latest ---- ``` ---- ---- bufls is a Protobuf language server compatible with Buf modules and workspaces - ----@type vim.lsp.Config -return { - cmd = { 'bufls', 'serve' }, - filetypes = { 'proto' }, - root_markers = { 'buf.work.yaml', '.git' }, -} diff --git a/lsp/codebook.lua b/lsp/codebook.lua index dea265c..ec8931f 100644 --- a/lsp/codebook.lua +++ b/lsp/codebook.lua @@ -28,10 +28,12 @@ return { 'python', 'ruby', 'rust', + 'swift', 'toml', 'text', 'typescript', 'typescriptreact', + 'zig', }, root_markers = { '.git', 'codebook.toml', '.codebook.toml' }, } diff --git a/lsp/copilot.lua b/lsp/copilot.lua index 5f216d5..4aa37f4 100644 --- a/lsp/copilot.lua +++ b/lsp/copilot.lua @@ -13,6 +13,33 @@ --- --- Please see [terms of use for GitHub Copilot](https://docs.github.com/en/site-policy/github-terms/github-terms-for-additional-products-and-features#github-copilot) --- +--- You need to enable `:help lsp-inline-completion` to receive suggestions. For example, you can enable it in the LspAttach event: +--- +--- ```lua +--- vim.api.nvim_create_autocmd('LspAttach', { +--- callback = function(args) +--- local bufnr = args.buf +--- local client = assert(vim.lsp.get_client_by_id(args.data.client_id)) +--- +--- if client:supports_method(vim.lsp.protocol.Methods.textDocument_inlineCompletion, bufnr) then +--- vim.lsp.inline_completion.enable(true, { bufnr = bufnr }) +--- +--- vim.keymap.set( +--- 'i', +--- '', +--- vim.lsp.inline_completion.get, +--- { desc = 'LSP: accept inline completion', buffer = bufnr } +--- ) +--- vim.keymap.set( +--- 'i', +--- '', +--- vim.lsp.inline_completion.select, +--- { desc = 'LSP: switch inline completion', buffer = bufnr } +--- ) +--- end +--- end +--- }) +--- ``` ---@param bufnr integer, ---@param client vim.lsp.Client @@ -38,7 +65,7 @@ local function sign_in(bufnr, client) if continue == 1 then client:exec_cmd(command, { bufnr = bufnr }, function(cmd_err, cmd_result) if cmd_err then - vim.notify(err.message, vim.log.levels.ERROR) + vim.notify(cmd_err.message, vim.log.levels.ERROR) return end if cmd_result.status == 'OK' then diff --git a/lsp/csharp_ls.lua b/lsp/csharp_ls.lua index 7764592..e284090 100644 --- a/lsp/csharp_ls.lua +++ b/lsp/csharp_ls.lua @@ -12,7 +12,15 @@ local util = require 'lspconfig.util' ---@type vim.lsp.Config return { - cmd = { 'csharp-ls' }, + cmd = function(dispatchers, config) + return vim.lsp.rpc.start({ 'csharp-ls' }, dispatchers, { + -- csharp-ls attempt to locate sln, slnx or csproj files from cwd, so set cwd to root directory. + -- If cmd_cwd is provided, use it instead. + cwd = config.cmd_cwd or config.root_dir, + env = config.cmd_env, + detached = config.detached, + }) + end, root_dir = function(bufnr, on_dir) local fname = vim.api.nvim_buf_get_name(bufnr) on_dir(util.root_pattern '*.sln'(fname) or util.root_pattern '*.slnx'(fname) or util.root_pattern '*.csproj'(fname)) @@ -21,4 +29,10 @@ return { init_options = { AutomaticWorkspaceInit = true, }, + get_language_id = function(_, ft) + if ft == 'cs' then + return 'csharp' + end + return ft + end, } diff --git a/lsp/css_variables.lua b/lsp/css_variables.lua index f9cdbcd..ce63617 100644 --- a/lsp/css_variables.lua +++ b/lsp/css_variables.lua @@ -14,7 +14,19 @@ return { cmd = { 'css-variables-language-server', '--stdio' }, filetypes = { 'css', 'scss', 'less' }, - root_markers = { 'package.json', '.git' }, + + -- Taken from lsp/ts_ls.lua to handle simple projects and monorepos. + root_dir = function(bufnr, on_dir) + local root_markers = { 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb', 'bun.lock' } + -- Give the root markers equal priority by wrapping them in a table + root_markers = vim.fn.has 'nvim-0.11.3' == 1 and { root_markers, { '.git' } } + or vim.list_extend(root_markers, { '.git' }) + -- We fallback to the current working directory if no project root is found + local project_root = vim.fs.root(bufnr, root_markers) or vim.fn.getcwd() + + on_dir(project_root) + end, + -- Same as inlined defaults that don't seem to work without hardcoding them in the lua config -- https://github.com/vunguyentuan/vscode-css-variables/blob/763a564df763f17aceb5f3d6070e0b444a2f47ff/packages/css-variables-language-server/src/CSSVariableManager.ts#L31-L50 settings = { diff --git a/lsp/denols.lua b/lsp/denols.lua index 8c051fb..b5d9ffe 100644 --- a/lsp/denols.lua +++ b/lsp/denols.lua @@ -75,7 +75,24 @@ return { 'typescriptreact', 'typescript.tsx', }, - root_markers = { 'deno.json', 'deno.jsonc', '.git' }, + root_dir = function(bufnr, on_dir) + -- The project root is where the LSP can be started from + local root_markers = { 'deno.lock' } + -- Give the root markers equal priority by wrapping them in a table + root_markers = vim.fn.has('nvim-0.11.3') == 1 and { root_markers, { '.git' } } + or vim.list_extend(root_markers, { '.git' }) + -- exclude non-deno projects (npm, yarn, pnpm, bun) + local non_deno_path = vim.fs.root( + bufnr, + { 'package.json', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb', 'bun.lock' } + ) + local project_root = vim.fs.root(bufnr, root_markers) + if non_deno_path and (not project_root or #non_deno_path >= #project_root) then + return + end + -- We fallback to the current working directory if no project root is found + on_dir(project_root or vim.fn.getcwd()) + end, settings = { deno = { enable = true, @@ -96,6 +113,7 @@ return { on_attach = function(client, bufnr) vim.api.nvim_buf_create_user_command(bufnr, 'LspDenolsCache', function() client:exec_cmd({ + title = 'DenolsCache', command = 'deno.cache', arguments = { {}, vim.uri_from_bufnr(bufnr) }, }, { bufnr = bufnr }, function(err, _, ctx) diff --git a/lsp/djls.lua b/lsp/djls.lua new file mode 100644 index 0000000..7ef8d5f --- /dev/null +++ b/lsp/djls.lua @@ -0,0 +1,12 @@ +---@brief +--- +--- https://github.com/joshuadavidthomas/django-language-server +--- +--- `djls`, a language server for the Django web framework. + +---@type vim.lsp.Config +return { + cmd = { 'djls', 'serve' }, + filetypes = { 'htmldjango', 'html', 'python' }, + root_markers = { 'manage.py', 'pyproject.toml', '.git' }, +} diff --git a/lsp/ds_pinyin_lsp.lua b/lsp/ds_pinyin_lsp.lua index 68a99fa..84bf67e 100644 --- a/lsp/ds_pinyin_lsp.lua +++ b/lsp/ds_pinyin_lsp.lua @@ -23,6 +23,7 @@ end local function ds_pinyin_lsp_off(bufnr) local ds_pinyin_lsp_client = vim.lsp.get_clients({ bufnr = bufnr, name = 'ds_pinyin_lsp' })[1] if ds_pinyin_lsp_client then + ---@diagnostic disable-next-line: param-type-mismatch ds_pinyin_lsp_client:notify('$/turn/completion', { ['completion_on'] = false, }) @@ -34,6 +35,7 @@ end local function ds_pinyin_lsp_on(bufnr) local ds_pinyin_lsp_client = vim.lsp.get_clients({ bufnr = bufnr, name = 'ds_pinyin_lsp' })[1] if ds_pinyin_lsp_client then + ---@diagnostic disable-next-line: param-type-mismatch ds_pinyin_lsp_client:notify('$/turn/completion', { ['completion_on'] = true, }) diff --git a/lsp/elixirls.lua b/lsp/elixirls.lua index da7b7b3..c5f21c0 100644 --- a/lsp/elixirls.lua +++ b/lsp/elixirls.lua @@ -32,6 +32,7 @@ ---@type vim.lsp.Config return { + cmd = { 'elixir-ls' }, filetypes = { 'elixir', 'eelixir', 'heex', 'surface' }, root_dir = function(bufnr, on_dir) local fname = vim.api.nvim_buf_get_name(bufnr) diff --git a/lsp/emmet_language_server.lua b/lsp/emmet_language_server.lua index 89c5f4b..d508a24 100644 --- a/lsp/emmet_language_server.lua +++ b/lsp/emmet_language_server.lua @@ -19,11 +19,9 @@ return { 'htmldjango', 'javascriptreact', 'less', - 'pug', 'sass', 'scss', 'svelte', - 'templ', 'typescriptreact', 'vue', }, diff --git a/lsp/erlangls.lua b/lsp/erlangls.lua deleted file mode 100644 index d209d83..0000000 --- a/lsp/erlangls.lua +++ /dev/null @@ -1,21 +0,0 @@ ----@brief ---- ---- https://erlang-ls.github.io ---- ---- Language Server for Erlang. ---- ---- Clone [erlang_ls](https://github.com/erlang-ls/erlang_ls) ---- Compile the project with `make` and copy resulting binaries somewhere in your $PATH eg. `cp _build/*/bin/* ~/local/bin` ---- ---- Installation instruction can be found [here](https://github.com/erlang-ls/erlang_ls). ---- ---- Installation requirements: ---- - [Erlang OTP 21+](https://github.com/erlang/otp) ---- - [rebar3 3.9.1+](https://github.com/erlang/rebar3) - ----@type vim.lsp.Config -return { - cmd = { 'erlang_ls' }, - filetypes = { 'erlang' }, - root_markers = { 'rebar.config', 'erlang.mk', '.git' }, -} diff --git a/lsp/eslint.lua b/lsp/eslint.lua index 24fac5c..a30c349 100644 --- a/lsp/eslint.lua +++ b/lsp/eslint.lua @@ -74,7 +74,7 @@ return { }, workspace_required = true, on_attach = function(client, bufnr) - vim.api.nvim_buf_create_user_command(0, 'LspEslintFixAll', function() + vim.api.nvim_buf_create_user_command(bufnr, 'LspEslintFixAll', function() client:request_sync('workspace/executeCommand', { command = 'eslint.applyAllFixes', arguments = { @@ -95,6 +95,12 @@ return { -- Give the root markers equal priority by wrapping them in a table root_markers = vim.fn.has('nvim-0.11.3') == 1 and { root_markers, { '.git' } } or vim.list_extend(root_markers, { '.git' }) + + -- exclude deno + if vim.fs.root(bufnr, { 'deno.json', 'deno.jsonc', 'deno.lock' }) then + return + end + -- We fallback to the current working directory if no project root is found local project_root = vim.fs.root(bufnr, root_markers) or vim.fn.getcwd() @@ -122,6 +128,7 @@ return { -- Refer to https://github.com/Microsoft/vscode-eslint#settings-options for documentation. settings = { validate = 'on', + ---@diagnostic disable-next-line: assign-type-mismatch packageManager = nil, useESLintClass = false, experimental = { @@ -194,9 +201,8 @@ return { -- Support Yarn2 (PnP) projects local pnp_cjs = root_dir .. '/.pnp.cjs' local pnp_js = root_dir .. '/.pnp.js' - if vim.uv.fs_stat(pnp_cjs) or vim.uv.fs_stat(pnp_js) then - local cmd = config.cmd - config.cmd = vim.list_extend({ 'yarn', 'exec' }, cmd) + if type(config.cmd) == 'table' and (vim.uv.fs_stat(pnp_cjs) or vim.uv.fs_stat(pnp_js)) then + config.cmd = vim.list_extend({ 'yarn', 'exec' }, config.cmd --[[@as table]]) end end end, diff --git a/lsp/expert.lua b/lsp/expert.lua index f7146a6..83141ed 100644 --- a/lsp/expert.lua +++ b/lsp/expert.lua @@ -12,7 +12,7 @@ ---@type vim.lsp.Config return { filetypes = { 'elixir', 'eelixir', 'heex', 'surface' }, - cmd = { 'expert' }, + cmd = { 'expert', '--stdio' }, root_dir = function(bufnr, on_dir) local fname = vim.api.nvim_buf_get_name(bufnr) --- Elixir workspaces may have multiple `mix.exs` files, for an "umbrella" layout or monorepo. diff --git a/lsp/flow.lua b/lsp/flow.lua index 5056831..9274381 100644 --- a/lsp/flow.lua +++ b/lsp/flow.lua @@ -14,7 +14,16 @@ ---@type vim.lsp.Config return { - cmd = { 'npx', '--no-install', 'flow', 'lsp' }, + cmd = function(dispatchers) + local cmd = nil + if vim.fn.executable('flow') then + cmd = { 'flow', 'lsp' } + else + cmd = { 'npx', '--no-install', 'flow', 'lsp' } + end + + return vim.lsp.rpc.start(cmd, dispatchers) + end, filetypes = { 'javascript', 'javascriptreact', 'javascript.jsx' }, root_markers = { '.flowconfig' }, } diff --git a/lsp/gitlab_duo.lua b/lsp/gitlab_duo.lua new file mode 100644 index 0000000..08439f6 --- /dev/null +++ b/lsp/gitlab_duo.lua @@ -0,0 +1,448 @@ +---@brief +--- +--- GitLab Duo Language Server Configuration for Neovim +--- +--- https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp +--- +--- The GitLab LSP enables any editor or IDE to integrate with GitLab Duo +--- for AI-powered code suggestions via the Language Server Protocol. +--- +--- Prerequisites: +--- - Node.js and npm installed +--- - GitLab account with Duo Pro license +--- - Internet connection for OAuth device flow +--- +--- Setup: +--- 1. Run :LspGitLabDuoSignIn to start OAuth authentication +--- 2. Follow the browser prompts to authorize +--- 3. Enable inline completion in LspAttach event (see example below) +--- +--- Inline Completion Example: +--- ```lua +--- vim.api.nvim_create_autocmd('LspAttach', { +--- callback = function(args) +--- local bufnr = args.buf +--- local client = assert(vim.lsp.get_client_by_id(args.data.client_id)) +--- +--- if vim.lsp.inline_completion and +--- client:supports_method(vim.lsp.protocol.Methods.textDocument_inlineCompletion, bufnr) then +--- vim.lsp.inline_completion.enable(true, { bufnr = bufnr }) +--- +--- -- Tab to accept suggestion +--- vim.keymap.set('i', '', function() +--- if vim.lsp.inline_completion.is_visible() then +--- return vim.lsp.inline_completion.accept() +--- else +--- return '' +--- end +--- end, { expr = true, buffer = bufnr, desc = 'GitLab Duo: Accept suggestion' }) +--- +--- -- Alt/Option+[ for previous suggestion +--- vim.keymap.set('i', '', vim.lsp.inline_completion.select_prev, +--- { buffer = bufnr, desc = 'GitLab Duo: Previous suggestion' }) +--- +--- -- Alt/Option+] for next suggestion +--- vim.keymap.set('i', '', vim.lsp.inline_completion.select_next, +--- { buffer = bufnr, desc = 'GitLab Duo: Next suggestion' }) +--- end +--- end +--- }) +--- ``` + +-- Configuration +local config = { + gitlab_url = 'https://gitlab.com', + -- This is a oauth application created from tachyons-gitlab account with `api` scope + client_id = '00bb391f527d2e77b3467b0b6b900151cc6a28dcfb18fa1249871e43bc3e5832', + scopes = 'api', + token_file = vim.fn.stdpath('data') .. '/gitlab_duo_oauth.json', +} + +-- Helper function to make POST requests with curl via vim.system +local function curl_post(url, data, headers) + local curl_args = { + 'curl', + '-s', + '-w', + '\n%{http_code}', + '-X', + 'POST', + url, + } + + -- Add headers + for key, value in pairs(headers or {}) do + table.insert(curl_args, '-H') + table.insert(curl_args, key .. ': ' .. value) + end + + -- Add data + if data then + table.insert(curl_args, '-d') + table.insert(curl_args, data) + end + + local result = vim.system(curl_args, { text = true }):wait() + + -- Split body and status code + local output = result.stdout or '' + local body_end = output:match('.*\n()%d+$') + + local body = '' + local status = 0 + + if body_end then + body = output:sub(1, body_end - 2) -- -2 to remove trailing newline + status = tonumber(output:match('\n(%d+)$')) or 0 + else + body = output + end + + return { + status = status, + body = body, + } +end + +-- Token management +local function save_token(token_data) + token_data.saved_at = os.time() + local file = io.open(config.token_file, 'w') + if file then + file:write(vim.json.encode(token_data)) + file:close() + return true + end + return false +end + +local function load_token() + if vim.fn.filereadable(config.token_file) == 0 then + return nil + end + + local blob = vim.fn.readblob(config.token_file) + return vim.json.decode(blob) +end + +local function is_token_expired(token_data) + if not token_data or not token_data.saved_at or not token_data.expires_in then + return true + end + local token_age = os.time() - token_data.saved_at + return token_age >= (token_data.expires_in - 60) -- 60 second buffer +end + +local function refresh_access_token(refresh_token) + vim.notify('Refreshing GitLab OAuth token...', vim.log.levels.INFO) + + local response = curl_post( + config.gitlab_url .. '/oauth/token', + string.format('client_id=%s&refresh_token=%s&grant_type=refresh_token', config.client_id, refresh_token), + { ['Content-Type'] = 'application/x-www-form-urlencoded' } + ) + + if response.status ~= 200 then + vim.notify('Failed to refresh token: ' .. (response.body or 'Unknown error'), vim.log.levels.ERROR) + return nil + end + + local ok, body = pcall(vim.json.decode, response.body) + if not ok or not body.access_token then + vim.notify('Invalid refresh response', vim.log.levels.ERROR) + return nil + end + + save_token(body) + vim.notify('Token refreshed successfully', vim.log.levels.INFO) + return body +end + +local function get_valid_token() + local token_data = load_token() + + if not token_data then + return nil, 'no_token' + end + + if is_token_expired(token_data) then + if token_data.refresh_token then + local new_token_data = refresh_access_token(token_data.refresh_token) + if new_token_data then + return new_token_data.access_token, 'refreshed' + end + return nil, 'refresh_failed' + end + return nil, 'expired' + end + + return token_data.access_token, 'valid' +end + +-- OAuth Device Flow +local function device_authorization() + local response = curl_post( + config.gitlab_url .. '/oauth/authorize_device', + string.format('client_id=%s&scope=%s', config.client_id, config.scopes), + { ['Content-Type'] = 'application/x-www-form-urlencoded' } + ) + + if response.status ~= 200 then + vim.notify('Device authorization failed: ' .. response.status, vim.log.levels.ERROR) + return nil + end + + local data = vim.json.decode(response.body) + + return data +end + +local function poll_for_token(device_code, interval, client) + local max_attempts = 60 + local attempts = 0 + + local function poll() + attempts = attempts + 1 + + local response = curl_post( + config.gitlab_url .. '/oauth/token', + string.format( + 'client_id=%s&device_code=%s&grant_type=urn:ietf:params:oauth:grant-type:device_code', + config.client_id, + device_code + ), + { ['Content-Type'] = 'application/x-www-form-urlencoded' } + ) + + local ok, body = pcall(vim.json.decode, response.body) + if not ok then + vim.notify('Failed to parse token response', vim.log.levels.ERROR) + return + end + + if response.status == 200 and body.access_token then + save_token(body) + vim.notify('GitLab Duo authentication successful!', vim.log.levels.INFO) + + -- Update LSP with new token + vim.schedule(function() + client:notify('workspace/didChangeConfiguration', { + settings = { + token = body.access_token, + baseUrl = config.gitlab_url, + }, + }) + end) + return + end + + if body.error == 'authorization_pending' then + if attempts < max_attempts then + vim.defer_fn(poll, interval * 1000) + else + vim.notify('Authorization timed out', vim.log.levels.ERROR) + end + elseif body.error == 'slow_down' then + vim.defer_fn(poll, (interval + 5) * 1000) + elseif body.error == 'access_denied' then + vim.notify('Authorization denied', vim.log.levels.ERROR) + elseif body.error == 'expired_token' then + vim.notify('Device code expired. Please run :LspGitLabDuoSignIn again', vim.log.levels.ERROR) + else + vim.notify('OAuth error: ' .. (body.error or 'unknown'), vim.log.levels.ERROR) + end + end + + poll() +end + +---@param client vim.lsp.Client +local function sign_in(client) + vim.notify('Starting GitLab device authorization...', vim.log.levels.INFO) + + local auth_data = device_authorization() + if not auth_data then + return + end + + vim.ui.open(auth_data.verification_uri .. '?user_code=' .. auth_data.user_code) + + poll_for_token(auth_data.device_code, auth_data.interval or 5, client) +end + +---@param client vim.lsp.Client +local function sign_out(client) + local ok = os.remove(config.token_file) + if ok then + vim.notify('Signed out. Token removed.', vim.log.levels.INFO) + client:notify('workspace/didChangeConfiguration', { + settings = { token = '' }, + }) + else + vim.notify('Failed to remove token file', vim.log.levels.ERROR) + end +end + +local function show_status() + local token_data = load_token() + + if not token_data then + vim.notify('Not signed in. Run :LspGitLabDuoSignIn to authenticate.', vim.log.levels.INFO) + return + end + + local info = { + 'GitLab Duo Status:', + '', + 'Instance: ' .. config.gitlab_url, + 'Signed in: Yes', + 'Has refresh token: ' .. (token_data.refresh_token and 'Yes' or 'No'), + } + + if token_data.saved_at and token_data.expires_in then + local time_left = token_data.expires_in - (os.time() - token_data.saved_at) + if time_left > 0 then + local hours = math.floor(time_left / 3600) + local minutes = math.floor((time_left % 3600) / 60) + table.insert(info, string.format('Token expires in: %dh %dm', hours, minutes)) + else + table.insert(info, 'Token status: EXPIRED') + end + end + + vim.notify(table.concat(info, '\n'), vim.log.levels.INFO) +end + +---@type vim.lsp.Config +return { + cmd = { + 'npx', + '--registry=https://gitlab.com/api/v4/packages/npm/', + '@gitlab-org/gitlab-lsp', + '--stdio', + }, + root_markers = { '.git' }, + filetypes = { + 'ruby', + 'go', + 'javascript', + 'typescript', + 'typescriptreact', + 'javascriptreact', + 'rust', + 'lua', + 'python', + 'java', + 'cpp', + 'c', + 'php', + 'cs', + 'kotlin', + 'swift', + 'scala', + 'vue', + 'svelte', + 'html', + 'css', + 'scss', + 'json', + 'yaml', + }, + init_options = { + editorInfo = { + name = 'Neovim', + version = tostring(vim.version()), + }, + editorPluginInfo = { + name = 'Neovim LSP', + version = tostring(vim.version()), + }, + ide = { + name = 'Neovim', + version = tostring(vim.version()), + vendor = 'Neovim', + }, + extension = { + name = 'Neovim LSP Client', + version = tostring(vim.version()), + }, + }, + settings = { + baseUrl = config.gitlab_url, + logLevel = 'info', + codeCompletion = { + enableSecretRedaction = true, + }, + telemetry = { + enabled = false, + }, + featureFlags = { + streamCodeGenerations = false, + }, + }, + on_init = function(client) + -- Handle token validation errors + client.handlers['$/gitlab/token/check'] = function(_, result) + if result and result.reason then + vim.notify(string.format('GitLab Duo: %s - %s', result.reason, result.message or ''), vim.log.levels.ERROR) + + -- Try to refresh if possible + local token_data = load_token() + if token_data and token_data.refresh_token then + vim.schedule(function() + local new_token_data = refresh_access_token(token_data.refresh_token) + if new_token_data then + client:notify('workspace/didChangeConfiguration', { + settings = { token = new_token_data.access_token, baseUrl = config.gitlab_url }, + }) + else + vim.notify('Run :LspGitLabDuoSignIn to re-authenticate', vim.log.levels.WARN) + end + end) + else + vim.notify('Run :LspGitLabDuoSignIn to authenticate', vim.log.levels.WARN) + end + end + end + + -- Handle feature state changes + client.handlers['$/gitlab/featureStateChange'] = function(_, result) + if result and result.state == 'disabled' and result.checks then + for _, check in ipairs(result.checks) do + vim.notify(string.format('GitLab Duo: %s', check.message or check.id), vim.log.levels.WARN) + end + end + end + + -- Check authentication status + local token, status = get_valid_token() + + if token then + client:notify('workspace/didChangeConfiguration', { + settings = { + token = token, + baseUrl = config.gitlab_url, + }, + }) + end + + if not token then + vim.notify('GitLab Duo: Not authenticated. Run :LspGitLabDuoSignIn to sign in.', vim.log.levels.WARN) + elseif status == 'refreshed' then + vim.notify('GitLab Duo: Token refreshed automatically', vim.log.levels.INFO) + end + end, + on_attach = function(client, bufnr) + vim.api.nvim_buf_create_user_command(bufnr, 'LspGitLabDuoSignIn', function() + sign_in(client) + end, { desc = 'Sign in to GitLab Duo with OAuth' }) + + vim.api.nvim_buf_create_user_command(bufnr, 'LspGitLabDuoSignOut', function() + sign_out(client) + end, { desc = 'Sign out from GitLab Duo' }) + + vim.api.nvim_buf_create_user_command(bufnr, 'LspGitLabDuoStatus', function() + show_status() + end, { desc = 'Show GitLab Duo authentication status' }) + end, +} diff --git a/lsp/glint.lua b/lsp/glint.lua index 7da9414..4485a85 100644 --- a/lsp/glint.lua +++ b/lsp/glint.lua @@ -25,6 +25,7 @@ ---@type vim.lsp.Config return { cmd = function(dispatchers, config) + ---@diagnostic disable-next-line: undefined-field local cmd = (config.init_options.glint.useGlobal or not config.root_dir) and { 'glint-language-server' } or { config.root_dir .. '/node_modules/.bin/glint-language-server' } return vim.lsp.rpc.start(cmd, dispatchers) diff --git a/lsp/gn_language_server.lua b/lsp/gn_language_server.lua new file mode 100644 index 0000000..2bf80a0 --- /dev/null +++ b/lsp/gn_language_server.lua @@ -0,0 +1,13 @@ +---@brief +--- +--- https://github.com/google/gn-language-server +--- +--- A language server for GN, the build configuration language used in Chromium, +--- Fuchsia, and other projects. + +---@type vim.lsp.Config +return { + cmd = { 'gn-language-server', '--stdio' }, + filetypes = { 'gn' }, + root_markers = { '.gn', '.git' }, +} diff --git a/lsp/golangci_lint_ls.lua b/lsp/golangci_lint_ls.lua index 8a95035..84f9e49 100644 --- a/lsp/golangci_lint_ls.lua +++ b/lsp/golangci_lint_ls.lua @@ -18,7 +18,22 @@ return { cmd = { 'golangci-lint-langserver' }, filetypes = { 'go', 'gomod' }, init_options = { - command = { 'golangci-lint', 'run', '--output.json.path=stdout', '--show-stats=false' }, + command = { + 'golangci-lint', + 'run', + -- disable all output formats that might be enabled by the users .golangci.yml + '--output.text.path=', + '--output.tab.path=', + '--output.html.path=', + '--output.checkstyle.path=', + '--output.junit-xml.path=', + '--output.teamcity.path=', + '--output.sarif.path=', + -- disable stats output + '--show-stats=false', + -- enable JSON output to be used by the language server + '--output.json.path=stdout', + }, }, root_markers = { '.golangci.yml', diff --git a/lsp/harper_ls.lua b/lsp/harper_ls.lua index ca157fb..42bdf94 100644 --- a/lsp/harper_ls.lua +++ b/lsp/harper_ls.lua @@ -21,6 +21,7 @@ return { cmd = { 'harper-ls', '--stdio' }, filetypes = { + 'asciidoc', 'c', 'cpp', 'cs', @@ -47,5 +48,5 @@ return { 'clojure', 'sh', }, - root_markers = { '.git' }, + root_markers = { '.harper-dictionary.txt', '.git' }, } diff --git a/lsp/hls.lua b/lsp/hls.lua index 705a97d..5b2ec86 100644 --- a/lsp/hls.lua +++ b/lsp/hls.lua @@ -25,7 +25,7 @@ return { settings = { haskell = { formattingProvider = 'ormolu', - cabalFormattingProvider = 'cabalfmt', + cabalFormattingProvider = 'cabal-fmt', }, }, } diff --git a/lsp/home_assistant.lua b/lsp/home_assistant.lua new file mode 100644 index 0000000..80a9354 --- /dev/null +++ b/lsp/home_assistant.lua @@ -0,0 +1,18 @@ +---@brief +--- +--- https://github.com/keesschollaart81/vscode-home-assistant +--- +--- `vscode-home-assistant` can be installed via from source or by downloading +--- and extracting the VSCode "Home Assistant Config Helper" extension +--- +--- `vscode-home-assistant` is a language server for Home Assistant ported from the VSCode "Home Assistant Config Helper" extension. + +---@type vim.lsp.Config +return { + cmd = { 'vscode-home-assistant', '--stdio' }, + filetypes = { 'yaml' }, + root_markers = { + 'configuration.yaml', + 'configuration.yml', + }, +} diff --git a/lsp/html.lua b/lsp/html.lua index 2b53d32..2ef030b 100644 --- a/lsp/html.lua +++ b/lsp/html.lua @@ -25,7 +25,7 @@ ---@type vim.lsp.Config return { cmd = { 'vscode-html-language-server', '--stdio' }, - filetypes = { 'html', 'templ' }, + filetypes = { 'html' }, root_markers = { 'package.json', '.git' }, settings = {}, init_options = { diff --git a/lsp/hylo_ls.lua b/lsp/hylo_ls.lua new file mode 100644 index 0000000..682cddb --- /dev/null +++ b/lsp/hylo_ls.lua @@ -0,0 +1,13 @@ +---@brief +--- +--- https://github.com/hylo-lang/hylo-language-server +--- +--- A language server for the Hylo programming language. + +---@type vim.lsp.Config +return { + cmd = { 'hylo-language-server', '--stdio' }, + filetypes = { 'hylo' }, + root_markers = { '.git' }, + settings = {}, +} diff --git a/lsp/intelephense.lua b/lsp/intelephense.lua index 8a91c40..04fbb95 100644 --- a/lsp/intelephense.lua +++ b/lsp/intelephense.lua @@ -30,4 +30,11 @@ return { cmd = { 'intelephense', '--stdio' }, filetypes = { 'php' }, root_markers = { '.git', 'composer.json' }, + settings = { + intelephense = { + telemetry = { + enabled = false, + }, + }, + }, } diff --git a/lsp/java_language_server.lua b/lsp/java_language_server.lua index 1458254..06f08d0 100644 --- a/lsp/java_language_server.lua +++ b/lsp/java_language_server.lua @@ -8,6 +8,7 @@ ---@type vim.lsp.Config return { + cmd = { 'java-language-server' }, filetypes = { 'java' }, root_markers = { 'build.gradle', 'build.gradle.kts', 'pom.xml', '.git' }, settings = {}, diff --git a/lsp/jdtls.lua b/lsp/jdtls.lua index 4ea904f..ed20613 100644 --- a/lsp/jdtls.lua +++ b/lsp/jdtls.lua @@ -32,22 +32,8 @@ --- vim.lsp.config('jdtls', { cmd = { 'jdtls' } }) --- ``` -local env = { - HOME = vim.uv.os_homedir(), - XDG_CACHE_HOME = os.getenv 'XDG_CACHE_HOME', - JDTLS_JVM_ARGS = os.getenv 'JDTLS_JVM_ARGS', -} - -local function get_cache_dir() - return env.XDG_CACHE_HOME and env.XDG_CACHE_HOME or env.HOME .. '/.cache' -end - local function get_jdtls_cache_dir() - return get_cache_dir() .. '/jdtls' -end - -local function get_jdtls_config_dir() - return get_jdtls_cache_dir() .. '/config' + return vim.fn.stdpath('cache') .. '/jdtls' end local function get_jdtls_workspace_dir() @@ -55,39 +41,62 @@ local function get_jdtls_workspace_dir() end local function get_jdtls_jvm_args() + local env = os.getenv('JDTLS_JVM_ARGS') local args = {} - for a in string.gmatch((env.JDTLS_JVM_ARGS or ''), '%S+') do + for a in string.gmatch((env or ''), '%S+') do local arg = string.format('--jvm-arg=%s', a) table.insert(args, arg) end return unpack(args) end +local root_markers1 = { + -- Multi-module projects + 'mvnw', -- Maven + 'gradlew', -- Gradle + 'settings.gradle', -- Gradle + 'settings.gradle.kts', -- Gradle + -- Use git directory as last resort for multi-module maven projects + -- In multi-module maven projects it is not really possible to determine what is the parent directory + -- and what is submodule directory. And jdtls does not break if the parent directory is at higher level than + -- actual parent pom.xml so propagating all the way to root git directory is fine + '.git', +} +local root_markers2 = { + -- Single-module projects + 'build.xml', -- Ant + 'pom.xml', -- Maven + 'build.gradle', -- Gradle + 'build.gradle.kts', -- Gradle +} + ---@type vim.lsp.Config return { - cmd = { - 'jdtls', - '-configuration', - get_jdtls_config_dir(), - '-data', - get_jdtls_workspace_dir(), - get_jdtls_jvm_args(), - }, + ---@param dispatchers? vim.lsp.rpc.Dispatchers + ---@param config vim.lsp.ClientConfig + cmd = function(dispatchers, config) + local workspace_dir = get_jdtls_workspace_dir() + local data_dir = workspace_dir + + if config.root_dir then + data_dir = data_dir .. '/' .. vim.fn.fnamemodify(config.root_dir, ':p:h:t') + end + + local config_cmd = { + 'jdtls', + '-data', + data_dir, + get_jdtls_jvm_args(), + } + + return vim.lsp.rpc.start(config_cmd, dispatchers, { + cwd = config.cmd_cwd, + env = config.cmd_env, + detached = config.detached, + }) + end, filetypes = { 'java' }, - root_markers = { - -- Multi-module projects - '.git', - 'build.gradle', - 'build.gradle.kts', - -- Single-module projects - 'build.xml', -- Ant - 'pom.xml', -- Maven - 'settings.gradle', -- Gradle - 'settings.gradle.kts', -- Gradle - }, - init_options = { - workspace = get_jdtls_workspace_dir(), - jvm_args = {}, - os_config = nil, - }, + root_markers = vim.fn.has('nvim-0.11.3') == 1 and { root_markers1, root_markers2 } + or vim.list_extend(root_markers1, root_markers2), + init_options = {}, } diff --git a/lsp/julials.lua b/lsp/julials.lua index 949a7e8..a181bfb 100644 --- a/lsp/julials.lua +++ b/lsp/julials.lua @@ -2,12 +2,12 @@ --- --- https://github.com/julia-vscode/julia-vscode --- ---- LanguageServer.jl can be installed with `julia` and `Pkg`: +--- LanguageServer.jl, SymbolServer.jl and StaticLint.jl can be installed with `julia` and `Pkg`: --- ```sh ---- julia --project=~/.julia/environments/nvim-lspconfig -e 'using Pkg; Pkg.add("LanguageServer")' +--- julia --project=~/.julia/environments/nvim-lspconfig -e 'using Pkg; Pkg.add("LanguageServer"); Pkg.add("SymbolServer"); Pkg.add("StaticLint")' --- ``` --- where `~/.julia/environments/nvim-lspconfig` is the location where ---- the default configuration expects LanguageServer.jl to be installed. +--- the default configuration expects LanguageServer.jl, SymbolServer.jl and StaticLint.jl to be installed. --- --- To update an existing install, use the following command: --- ```sh @@ -36,6 +36,7 @@ local function activate_env(path) local function _activate_env(environment) if environment then for _, julials_client in ipairs(julials_clients) do + ---@diagnostic disable-next-line: param-type-mismatch julials_client:notify('julia/activateenvironment', { envPath = environment }) end vim.notify('Julia environment activated: \n`' .. environment .. '`', vim.log.levels.INFO) @@ -89,7 +90,7 @@ local cmd = { "environments", "nvim-lspconfig" ) pushfirst!(LOAD_PATH, ls_install_path) - using LanguageServer + using LanguageServer, SymbolServer, StaticLint popfirst!(LOAD_PATH) depot_path = get(ENV, "JULIA_DEPOT_PATH", "") project_path = let diff --git a/lsp/kotlin_language_server.lua b/lsp/kotlin_language_server.lua index b5446e2..ee18f32 100644 --- a/lsp/kotlin_language_server.lua +++ b/lsp/kotlin_language_server.lua @@ -37,6 +37,6 @@ return { cmd = { 'kotlin-language-server' }, init_options = { -- Enables caching and use project root to store cache data. - storagePath = vim.fs.root(vim.fn.expand '%:p:h', root_files), + storagePath = vim.fs.root(vim.fn.expand '%:p:h', root_files) --[[@as string]], }, } diff --git a/lsp/lexical.lua b/lsp/lexical.lua index ee6d595..6968a5e 100644 --- a/lsp/lexical.lua +++ b/lsp/lexical.lua @@ -4,13 +4,12 @@ --- --- Lexical is a next-generation language server for the Elixir programming language. --- ---- Follow the [Detailed Installation Instructions](https://github.com/lexical-lsp/lexical/blob/main/pages/installation.md) ---- ---- **By default, `lexical` doesn't have a `cmd` set.** ---- This is because nvim-lspconfig does not make assumptions about your path. +--- To install from source, follow the [Detailed Installation Instructions](https://github.com/lexical-lsp/lexical/blob/main/pages/installation.md). +--- Ensure to point `cmd` to the generated `_build/dev/package/lexical/start_lexical.sh` executable. ---@type vim.lsp.Config return { + cmd = { 'lexical' }, filetypes = { 'elixir', 'eelixir', 'heex', 'surface' }, root_markers = { 'mix.exs', '.git' }, } diff --git a/lsp/ltex_plus.lua b/lsp/ltex_plus.lua index c241cb7..d9b9941 100644 --- a/lsp/ltex_plus.lua +++ b/lsp/ltex_plus.lua @@ -36,6 +36,7 @@ local language_id_mapping = { return { cmd = { 'ltex-ls-plus' }, filetypes = { + 'asciidoc', 'bib', 'context', 'gitcommit', @@ -62,6 +63,7 @@ return { settings = { ltex = { enabled = { + 'asciidoc', 'bib', 'context', 'gitcommit', diff --git a/lsp/lua_ls.lua b/lsp/lua_ls.lua index 560f1d4..1be2332 100644 --- a/lsp/lua_ls.lua +++ b/lsp/lua_ls.lua @@ -12,28 +12,29 @@ --- analysis, and location handling for plugins on runtime path, you can use the following --- settings. --- -vim.lsp.config('lua_ls', { +--- ```lua +vim.lsp.config("lua_ls", { on_init = function(client) if client.workspace_folders then local path = client.workspace_folders[1].name if - path ~= vim.fn.stdpath('config') - and (vim.uv.fs_stat(path .. '/.luarc.json') or vim.uv.fs_stat(path .. '/.luarc.jsonc')) + path ~= vim.fn.stdpath("config") + and (vim.uv.fs_stat(path .. "/.luarc.json") or vim.uv.fs_stat(path .. "/.luarc.jsonc")) then return end end - client.config.settings.Lua = vim.tbl_deep_extend('force', client.config.settings.Lua, { + client.config.settings.Lua = vim.tbl_deep_extend("force", client.config.settings.Lua, { runtime = { -- Tell the language server which version of Lua you're using (most -- likely LuaJIT in the case of Neovim) - version = 'LuaJIT', + version = "LuaJIT", -- Tell the language server how to find Lua modules same way as Neovim -- (see `:h lua-module-load`) path = { - 'lua/?.lua', - 'lua/?/init.lua', + "lua/?.lua", + "lua/?/init.lua", }, }, -- Make the server aware of Neovim runtime files @@ -41,39 +42,53 @@ vim.lsp.config('lua_ls', { checkThirdParty = false, library = { vim.env.VIMRUNTIME, - -- vim.fn.expand("~/.local/share/love2d/library"), -- Depending on the usage, you might want to add additional paths -- here. - -- '${3rd}/love2d/library' - -- '${3rd}/busted/library' - } + -- '${3rd}/luv/library', + -- '${3rd}/busted/library', + }, -- Or pull in all of 'runtimepath'. -- NOTE: this is a lot slower and will cause issues when working on -- your own configuration. -- See https://github.com/neovim/nvim-lspconfig/issues/3189 - -- library = { - -- vim.api.nvim_get_runtime_file('', true), - -- } - } + -- library = vim.api.nvim_get_runtime_file('', true), + }, }) end, settings = { - Lua = {} - } + Lua = {}, + }, }) +--- ``` +--- +--- See `lua-language-server`'s [documentation](https://luals.github.io/wiki/settings/) for an explanation of the above fields: +--- * [Lua.runtime.path](https://luals.github.io/wiki/settings/#runtimepath) +--- * [Lua.workspace.library](https://luals.github.io/wiki/settings/#workspacelibrary) +--- + +local root_markers1 = { + ".emmyrc.json", + ".luarc.json", + ".luarc.jsonc", +} +local root_markers2 = { + ".luacheckrc", + ".stylua.toml", + "stylua.toml", + "selene.toml", + "selene.yml", +} ---@type vim.lsp.Config return { - cmd = { 'lua-language-server' }, - filetypes = { 'lua' }, - root_markers = { - '.luarc.json', - '.luarc.jsonc', - '.luacheckrc', - '.stylua.toml', - 'stylua.toml', - 'selene.toml', - 'selene.yml', - '.git', + cmd = { "lua-language-server" }, + filetypes = { "lua" }, + root_markers = vim.fn.has("nvim-0.11.3") == 1 and { root_markers1, root_markers2, { ".git" } } + or vim.list_extend(vim.list_extend(root_markers1, root_markers2), { ".git" }), + settings = { + Lua = { + codeLens = { enable = true }, + hint = { enable = true, semicolon = "Disable" }, + }, }, } diff --git a/lsp/neocmake.lua b/lsp/neocmake.lua index b7e288b..34f8dac 100644 --- a/lsp/neocmake.lua +++ b/lsp/neocmake.lua @@ -1,6 +1,6 @@ ---@brief --- ---- https://github.com/Decodetalkers/neocmakelsp +--- https://github.com/neocmakelsp/neocmakelsp --- --- CMake LSP Implementation --- @@ -18,7 +18,7 @@ ---@type vim.lsp.Config return { - cmd = { 'neocmakelsp', '--stdio' }, + cmd = { 'neocmakelsp', 'stdio' }, filetypes = { 'cmake' }, root_markers = { '.git', 'build', 'cmake' }, } diff --git a/lsp/nextls.lua b/lsp/nextls.lua index 26f6001..c243dfb 100644 --- a/lsp/nextls.lua +++ b/lsp/nextls.lua @@ -2,10 +2,11 @@ --- --- https://github.com/elixir-tools/next-ls --- ---- **By default, next-ls does not set its `cmd`. Please see the following [detailed instructions](https://www.elixir-tools.dev/docs/next-ls/installation/) for possible installation methods.** +--- **Please see the following [detailed instructions](https://www.elixir-tools.dev/docs/next-ls/installation/) for possible installation methods.** ---@type vim.lsp.Config return { + cmd = { 'nextls', '--stdio' }, filetypes = { 'elixir', 'eelixir', 'heex', 'surface' }, root_markers = { 'mix.exs', '.git' }, } diff --git a/lsp/ocamlls.lua b/lsp/ocamlls.lua deleted file mode 100644 index 90e9193..0000000 --- a/lsp/ocamlls.lua +++ /dev/null @@ -1,20 +0,0 @@ ----@brief ---- ---- https://github.com/ocaml-lsp/ocaml-language-server ---- ---- `ocaml-language-server` can be installed via `npm` ---- ```sh ---- npm install -g ocaml-language-server ---- ``` - -local util = require 'lspconfig.util' - ----@type vim.lsp.Config -return { - cmd = { 'ocaml-language-server', '--stdio' }, - filetypes = { 'ocaml', 'reason' }, - root_dir = function(bufnr, on_dir) - local fname = vim.api.nvim_buf_get_name(bufnr) - on_dir(util.root_pattern('*.opam', 'esy.json', 'package.json')(fname)) - end, -} diff --git a/lsp/ocamllsp.lua b/lsp/ocamllsp.lua index 505e982..d7a2726 100644 --- a/lsp/ocamllsp.lua +++ b/lsp/ocamllsp.lua @@ -9,7 +9,40 @@ --- opam install ocaml-lsp-server --- ``` -local util = require 'lspconfig.util' +-- https://github.com/ocaml/ocaml-lsp/blob/master/ocaml-lsp-server/docs/ocamllsp/switchImplIntf-spec.md +local function switch_impl_intf(bufnr, client) + local method_name = 'ocamllsp/switchImplIntf' + ---@diagnostic disable-next-line:param-type-mismatch + if not client or not client:supports_method(method_name) then + return vim.notify(('method %s is not supported by any servers active on the current buffer'):format(method_name)) + end + local uri = vim.lsp.util.make_given_range_params(nil, nil, bufnr, client.offset_encoding).textDocument.uri + if not uri then + return vim.notify('could not get URI for current buffer') + end + local params = { uri } + ---@diagnostic disable-next-line:param-type-mismatch + client:request(method_name, params, function(err, result) + if err then + error(tostring(err)) + end + if not result or #result == 0 then + vim.notify('corresponding file cannot be determined') + elseif #result == 1 then + vim.cmd.edit(vim.uri_to_fname(result[1])) + else + vim.ui.select( + result, + { prompt = 'Select an implementation/interface:', format_item = vim.uri_to_fname }, + function(choice) + if choice then + vim.cmd.edit(vim.uri_to_fname(choice)) + end + end + ) + end + end, bufnr) +end local language_id_of = { menhir = 'ocaml.menhir', @@ -20,17 +53,36 @@ local language_id_of = { dune = 'dune', } -local get_language_id = function(_, ftype) - return language_id_of[ftype] +local language_id_of_ext = { + mll = language_id_of.ocamllex, + mly = language_id_of.menhir, + mli = language_id_of.ocamlinterface, +} + +local get_language_id = function(bufnr, ftype) + if ftype == 'ocaml' then + local path = vim.api.nvim_buf_get_name(bufnr) + local ext = vim.fn.fnamemodify(path, ':e') + return language_id_of_ext[ext] or language_id_of.ocaml + else + return language_id_of[ftype] + end end +local root_markers1 = { 'dune-project', 'dune-workspace' } +local root_markers2 = { '*.opam', 'opam', 'esy.json', 'package.json' } +local root_markers3 = { '.git' } + ---@type vim.lsp.Config return { cmd = { 'ocamllsp' }, filetypes = { 'ocaml', 'menhir', 'ocamlinterface', 'ocamllex', 'reason', 'dune' }, - root_dir = function(bufnr, on_dir) - local fname = vim.api.nvim_buf_get_name(bufnr) - on_dir(util.root_pattern('*.opam', 'esy.json', 'package.json', '.git', 'dune-project', 'dune-workspace')(fname)) - end, + root_markers = vim.fn.has('nvim-0.11.3') == 1 and { root_markers1, root_markers2, root_markers3 } + or vim.list_extend(vim.list_extend(root_markers1, root_markers2), root_markers3), get_language_id = get_language_id, + on_attach = function(client, bufnr) + vim.api.nvim_buf_create_user_command(bufnr, 'LspOcamllspSwitchImplIntf', function() + switch_impl_intf(bufnr, client) + end, { desc = 'Switch between implementation/interface' }) + end, } diff --git a/lsp/omnisharp.lua b/lsp/omnisharp.lua index cfc9ba1..adaf4d7 100644 --- a/lsp/omnisharp.lua +++ b/lsp/omnisharp.lua @@ -8,8 +8,6 @@ --- --- OmniSharp requires the [dotnet-sdk](https://dotnet.microsoft.com/download) to be installed. --- ---- **By default, omnisharp-roslyn doesn't have a `cmd` set.** This is because nvim-lspconfig does not make assumptions about your path. You must add the following to your init.vim or init.lua to set `cmd` to the absolute path ($HOME and ~ are not expanded) of the unzipped run script or binary. ---- --- For `go_to_definition` to work fully, extended `textDocument/definition` handler is needed, for example see [omnisharp-extended-lsp.nvim](https://github.com/Hoffs/omnisharp-extended-lsp.nvim) --- --- @@ -32,7 +30,8 @@ return { root_dir = function(bufnr, on_dir) local fname = vim.api.nvim_buf_get_name(bufnr) on_dir( - util.root_pattern '*.sln'(fname) + util.root_pattern '*.slnx'(fname) + or util.root_pattern '*.sln'(fname) or util.root_pattern '*.csproj'(fname) or util.root_pattern 'omnisharp.json'(fname) or util.root_pattern 'function.json'(fname) diff --git a/lsp/oso.lua b/lsp/oso.lua new file mode 100644 index 0000000..390dbd8 --- /dev/null +++ b/lsp/oso.lua @@ -0,0 +1,32 @@ +---@brief +--- https://www.osohq.com/docs/develop/local-dev/env-setup +--- +--- Oso Polar language server. +--- +--- `oso-cloud` can be installed by following the instructions +--- [here](https://www.osohq.com/docs/develop/local-dev/env-setup). +--- +--- The default `cmd` assumes that the `oso-cloud` binary can be found in the `$PATH`. +--- +--- You may need to configure the filetype for Polar (*.polar) files: +--- +--- ``` +--- autocmd BufNewFile,BufRead *.polar set filetype=polar +--- ``` +--- +--- or +--- +--- ```lua +--- vim.filetype.add({ +--- pattern = { +--- ['.*/*.polar'] = 'polar', +--- }, +--- }) +--- +--- Alternatively, you may use a syntax plugin like https://github.com/osohq/polar.vim + +---@type vim.lsp.Config +return { + cmd = { 'oso-cloud', 'lsp' }, + filetypes = { 'polar' }, +} diff --git a/lsp/oxfmt.lua b/lsp/oxfmt.lua new file mode 100644 index 0000000..c0b3519 --- /dev/null +++ b/lsp/oxfmt.lua @@ -0,0 +1,52 @@ +--- @brief +--- +--- https://github.com/oxc-project/oxc +--- https://oxc.rs/docs/guide/usage/formatter.html +--- +--- `oxfmt` is a Prettier-compatible code formatter that supports multiple languages +--- including JavaScript, TypeScript, JSON, YAML, HTML, CSS, Markdown, and more. +--- It can be installed via `npm`: +--- +--- ```sh +--- npm i -g oxfmt +--- ``` + +local util = require 'lspconfig.util' + +---@type vim.lsp.Config +return { + cmd = { 'oxfmt', '--lsp' }, + filetypes = { + 'javascript', + 'javascriptreact', + 'javascript.jsx', + 'typescript', + 'typescriptreact', + 'typescript.tsx', + 'toml', + 'json', + 'jsonc', + 'json5', + 'yaml', + 'html', + 'vue', + 'handlebars', + 'hbs', + 'css', + 'scss', + 'less', + 'graphql', + 'markdown', + 'mdx', + }, + workspace_required = true, + root_dir = function(bufnr, on_dir) + local fname = vim.api.nvim_buf_get_name(bufnr) + + -- Oxfmt resolves configuration by walking upward and using the nearest config file + -- to the file being processed. We therefore compute the root directory by locating + -- the closest `.oxfmtrc.json` (or `package.json` fallback) above the buffer. + local root_markers = util.insert_package_json({ '.oxfmtrc.json' }, 'oxfmt', fname)[1] + on_dir(vim.fs.dirname(vim.fs.find(root_markers, { path = fname, upward = true })[1])) + end, +} diff --git a/lsp/oxlint.lua b/lsp/oxlint.lua index 4585a98..436f342 100644 --- a/lsp/oxlint.lua +++ b/lsp/oxlint.lua @@ -1,31 +1,62 @@ --- @brief --- --- https://github.com/oxc-project/oxc +--- https://oxc.rs/docs/guide/usage/linter.html --- ---- `oxc` is a linter / formatter for JavaScript / Typescript supporting over 500 rules from ESLint and its popular plugins +--- `oxlint` is a linter for JavaScript / TypeScript supporting over 500 rules from ESLint and its popular plugins. +--- It also supports linting framework files (Vue, Svelte, Astro) by analyzing their