From e8ff3fed7c834327b421fca02ccdfec0b89e2165 Mon Sep 17 00:00:00 2001 From: Oscar Wallberg Date: Thu, 5 Jun 2025 11:30:14 +0200 Subject: [PATCH] refactor(LSP): use the new vim.lsp.* functions to set up LSP servers --- init.lua | 21 +- lazy-lock.json | 32 +- lua/ow/bootstrap.lua | 16 +- lua/ow/log.lua | 44 ++ lua/ow/lsp.lua | 517 ++++++++++++++++----- lua/ow/lsp/config/bashls.lua | 36 -- lua/ow/lsp/config/clangd.lua | 28 -- lua/ow/lsp/config/cmake.lua | 20 - lua/ow/lsp/config/gopls.lua | 36 -- lua/ow/lsp/config/groovyls.lua | 30 -- lua/ow/lsp/config/hls.lua | 18 - lua/ow/lsp/config/intelephense.lua | 76 --- lua/ow/lsp/config/jedi_language_server.lua | 20 - lua/ow/lsp/config/lemminx.lua | 40 -- lua/ow/lsp/config/lua_ls.lua | 87 ---- lua/ow/lsp/config/mesonlsp.lua | 21 - lua/ow/lsp/config/pyrefly.lua | 20 - lua/ow/lsp/config/pyright.lua | 27 -- lua/ow/lsp/config/ruff.lua | 27 -- lua/ow/lsp/config/rust_analyzer.lua | 191 -------- lua/ow/lsp/config/zls.lua | 31 -- lua/ow/lsp/keymap.lua | 74 +-- lua/ow/lsp/linter.lua | 113 +++-- lua/ow/lsp/package.lua | 341 -------------- lua/ow/lsp/server.lua | 392 ---------------- lua/ow/plugins/cmp.lua | 3 +- lua/ow/plugins/lspconfig.lua | 5 + lua/ow/plugins/nvim-tree.lua | 6 +- lua/ow/{utils.lua => util.lua} | 28 -- 29 files changed, 556 insertions(+), 1744 deletions(-) create mode 100644 lua/ow/log.lua delete mode 100644 lua/ow/lsp/config/bashls.lua delete mode 100644 lua/ow/lsp/config/clangd.lua delete mode 100644 lua/ow/lsp/config/cmake.lua delete mode 100644 lua/ow/lsp/config/gopls.lua delete mode 100644 lua/ow/lsp/config/groovyls.lua delete mode 100644 lua/ow/lsp/config/hls.lua delete mode 100644 lua/ow/lsp/config/intelephense.lua delete mode 100644 lua/ow/lsp/config/jedi_language_server.lua delete mode 100644 lua/ow/lsp/config/lemminx.lua delete mode 100644 lua/ow/lsp/config/lua_ls.lua delete mode 100644 lua/ow/lsp/config/mesonlsp.lua delete mode 100644 lua/ow/lsp/config/pyrefly.lua delete mode 100644 lua/ow/lsp/config/pyright.lua delete mode 100644 lua/ow/lsp/config/ruff.lua delete mode 100644 lua/ow/lsp/config/rust_analyzer.lua delete mode 100644 lua/ow/lsp/config/zls.lua delete mode 100644 lua/ow/lsp/package.lua delete mode 100644 lua/ow/lsp/server.lua create mode 100644 lua/ow/plugins/lspconfig.lua rename lua/ow/{utils.lua => util.lua} (93%) diff --git a/init.lua b/init.lua index d9fed65..dfed48e 100644 --- a/init.lua +++ b/init.lua @@ -1,6 +1,6 @@ vim.loader.enable() -local utils = require("ow.utils") +local log = require("ow.log") local files = { "globals", @@ -13,28 +13,19 @@ for _, file in ipairs(files) do local pkg = "ow.core." .. file local ok, err = pcall(require, pkg) if not ok then - utils.err("Error while loading package " .. pkg) - utils.err(err) + log.error("Error while loading package " .. pkg) + log.error(err) return end end local ok, err = pcall(require, "ow.bootstrap") if not ok then - utils.err("Error during bootstrap") - utils.err(err:gsub("\t", " ")) + log.error("Error during bootstrap") + log.error(err:gsub("\t", " ")) return end ----@type LazyPluginSpec[] -local plugins = { - { - "neovim/nvim-lspconfig", - config = require("ow.lsp").setup, - }, - { import = "ow.plugins" }, -} - ---@type LazyConfig local opts = { install = { @@ -69,4 +60,4 @@ local opts = { }, } -require("lazy").setup(plugins, opts) +require("lazy").setup("ow.plugins", opts) diff --git a/lazy-lock.json b/lazy-lock.json index 7bb39be..506a073 100644 --- a/lazy-lock.json +++ b/lazy-lock.json @@ -1,34 +1,36 @@ { "Comment.nvim": { "branch": "master", "commit": "e30b7f2008e52442154b66f7c519bfd2f1e32acb" }, - "LuaSnip": { "branch": "master", "commit": "03c8e67eb7293c404845b3982db895d59c0d1538" }, - "aerial.nvim": { "branch": "master", "commit": "2e00d1d4248f08dddfceacb8d2996e51e13e00f6" }, + "LuaSnip": { "branch": "master", "commit": "458560534a73f7f8d7a11a146c801db00b081df0" }, + "aerial.nvim": { "branch": "master", "commit": "5c0df1679bf7c814c924dc6646cc5291daca8363" }, "catppuccin": { "branch": "main", "commit": "5b5e3aef9ad7af84f463d17b5479f06b87d5c429" }, - "cmp-cmdline": { "branch": "main", "commit": "d250c63aa13ead745e3a40f61fdd3470efde3923" }, + "cmp-cmdline": { "branch": "main", "commit": "d126061b624e0af6c3a556428712dd4d4194ec6d" }, "cmp-nvim-lsp": { "branch": "main", "commit": "a8912b88ce488f411177fc8aed358b04dc246d7b" }, "cmp-path": { "branch": "main", "commit": "c6635aae33a50d6010bf1aa756ac2398a2d54c32" }, "cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" }, "fidget.nvim": { "branch": "main", "commit": "0ba1e16d07627532b6cae915cc992ecac249fb97" }, "friendly-snippets": { "branch": "main", "commit": "572f5660cf05f8cd8834e096d7b4c921ba18e175" }, - "gitsigns.nvim": { "branch": "main", "commit": "ee28ba3e70ecea811b8f6d7b51d81976e94b121c" }, + "gitsigns.nvim": { "branch": "main", "commit": "d0f90ef51d4be86b824b012ec52ed715b5622e51" }, "lazy.nvim": { "branch": "main", "commit": "6c3bda4aca61a13a9c63f1c1d1b16b9d3be90d7a" }, "lspkind.nvim": { "branch": "master", "commit": "d79a1c3299ad0ef94e255d045bed9fa26025dab6" }, - "mason.nvim": { "branch": "main", "commit": "fc98833b6da5de5a9c5b1446ac541577059555be" }, + "mason.nvim": { "branch": "main", "commit": "8024d64e1330b86044fed4c8494ef3dcd483a67c" }, "nvim-cmp": { "branch": "main", "commit": "b5311ab3ed9c846b585c0c15b7559be131ec4be9" }, "nvim-colorizer.lua": { "branch": "master", "commit": "517df88cf2afb36652830df2c655df2da416a0ae" }, - "nvim-dap": { "branch": "master", "commit": "8df427aeba0a06c6577dc3ab82de3076964e3b8d" }, - "nvim-lspconfig": { "branch": "master", "commit": "94d0fec9135719e046903bbbbf8f39e3d3436d4e" }, + "nvim-dap": { "branch": "master", "commit": "ea82027c3447dc1a022be9a9884de276c05cd33a" }, + "nvim-lspconfig": { "branch": "master", "commit": "036885e8e5456d3907626b634693234f628afef6" }, "nvim-notify": { "branch": "master", "commit": "b5825cf9ee881dd8e43309c93374ed5b87b7a896" }, - "nvim-tree.lua": { "branch": "master", "commit": "582ae48c9e43d2bcd55dfcc8e2e7a1f29065d924" }, - "nvim-treesitter": { "branch": "master", "commit": "94ea4f436d2b59c80f02e293466c374584f03b8c" }, - "nvim-treesitter-context": { "branch": "master", "commit": "6daca3ad780f045550b820f262002f35175a6c04" }, - "nvim-web-devicons": { "branch": "master", "commit": "50b5b06bff13a9b4eab946de7c7033649a6618a1" }, - "oil.nvim": { "branch": "master", "commit": "685cdb4ffa74473d75a1b97451f8654ceeab0f4a" }, - "onedark.nvim": { "branch": "master", "commit": "0e5512d1bebd1f08954710086f87a5caa173a924" }, - "orgmode": { "branch": "master", "commit": "15d66ead1285d99f8a21c4ef4874ac62e9320fe6" }, + "nvim-tree.lua": { "branch": "master", "commit": "1c733e8c1957dc67f47580fe9c458a13b5612d5b" }, + "nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" }, + "nvim-treesitter-context": { "branch": "master", "commit": "464a443b5a6657f39772b20baa95d02ffe97b268" }, + "nvim-web-devicons": { "branch": "master", "commit": "1fb58cca9aebbc4fd32b086cb413548ce132c127" }, + "oil.nvim": { "branch": "master", "commit": "08c2bce8b00fd780fb7999dbffdf7cd174e896fb" }, + "onedark.nvim": { "branch": "master", "commit": "11de4da47f3e69cb70c3ae9816bd8af166cbe121" }, + "orgmode": { "branch": "master", "commit": "32ef9e95f43a6e951fb931b438372546a4f0c524" }, "plenary.nvim": { "branch": "master", "commit": "857c5ac632080dba10aae49dba902ce3abf91b35" }, "telescope-fzf-native.nvim": { "branch": "main", "commit": "1f08ed60cafc8f6168b72b80be2b2ea149813e55" }, - "telescope.nvim": { "branch": "master", "commit": "a4ed82509cecc56df1c7138920a1aeaf246c0ac5" }, + "telescope-undo.nvim": { "branch": "main", "commit": "928d0c2dc9606e01e2cc547196f48d2eaecf58e5" }, + "telescope.nvim": { "branch": "master", "commit": "b4da76be54691e854d3e0e02c36b0245f945c2c7" }, "tokyonight.nvim": { "branch": "main", "commit": "057ef5d260c1931f1dffd0f052c685dcd14100a3" }, + "undotree": { "branch": "master", "commit": "b951b87b46c34356d44aa71886aecf9dd7f5788a" }, "vim-flog": { "branch": "master", "commit": "665b16ac8915f746bc43c9572b4581a5e9047216" }, "vim-fugitive": { "branch": "master", "commit": "4a745ea72fa93bb15dd077109afbb3d1809383f2" }, "vim-ucl": { "branch": "main", "commit": "efbcab5f98b663dbc3cac92801a90b11869c23d0" } diff --git a/lua/ow/bootstrap.lua b/lua/ow/bootstrap.lua index 9822f84..1bd8a04 100644 --- a/lua/ow/bootstrap.lua +++ b/lua/ow/bootstrap.lua @@ -6,20 +6,20 @@ if version.major == 0 then end end -local utils = require("ow.utils") +local util = require("ow.util") -- Install lazy.nvim local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" if not vim.uv.fs_stat(lazypath) then - utils.assert_installed("git") + util.assert_installed("git") if not os.getenv("CC") then - utils.assert_installed("cc") + util.assert_installed("cc") end - utils.assert_installed("make") - utils.assert_any_installed({ "curl", "wget", }) - utils.assert_installed("unzip") - utils.assert_installed("tar") - utils.assert_installed("gzip") + util.assert_installed("make") + util.assert_any_installed({ "curl", "wget", }) + util.assert_installed("unzip") + util.assert_installed("tar") + util.assert_installed("gzip") local resp = vim.system({ "git", diff --git a/lua/ow/log.lua b/lua/ow/log.lua new file mode 100644 index 0000000..e41d012 --- /dev/null +++ b/lua/ow/log.lua @@ -0,0 +1,44 @@ +local M = {} + +--- Log error message +---@param fmt string +---@param ... any +function M.error(fmt, ...) + vim.notify( + fmt:format(...), + vim.log.levels.ERROR + ) +end + +--- Log warning message +---@param fmt string +---@param ... any +function M.warning(fmt, ...) + vim.notify( + fmt:format(...), + vim.log.levels.WARN + ) +end + +--- Log info message +---@param fmt string +---@param ... any +function M.info(fmt, ...) + vim.notify( + fmt:format(...), + vim.log.levels.INFO + ) +end + +--- Log debug message +---@param fmt string +---@param ... any +function M.debug(fmt, ...) + vim.notify( + fmt:format(...), + vim.log.levels.DEBUG + ) +end + +return M + diff --git a/lua/ow/lsp.lua b/lua/ow/lsp.lua index 4f5b0a0..88733e9 100644 --- a/lua/ow/lsp.lua +++ b/lua/ow/lsp.lua @@ -1,12 +1,11 @@ -local utils = require("ow.utils") +---@type fun(name: string, cfg: vim.lsp.Config) +vim.lsp.config = vim.lsp.config -local CONFIG_DIR = vim.fn.stdpath("config") .. "/lua/ow/lsp/config" +local keymap = require("ow.lsp.keymap") +local linter = require("ow.lsp.linter") +local log = require("ow.log") +local util = require("ow.util") ----@class Server -local Server = require("ow.lsp.server") - ----@class LSP ----@field diagnostic_signs vim.diagnostic.Opts.Signs local M = {} M.diagnostic_signs = { @@ -18,122 +17,54 @@ M.diagnostic_signs = { }, } ----@type table -local servers = {} +---@param fn? fun(client: vim.lsp.Client, bufnr: integer) +---@return fun(client: vim.lsp.Client, bufnr: integer) +function M.with_defaults(fn) + return function(client, bufnr) + keymap.set_defaults(bufnr) -function M.get_servers() - return servers -end + -- For document highlight + vim.cmd.highlight({ "link LspReferenceRead Visual", bang = true }) + vim.cmd.highlight({ "link LspReferenceText Visual", bang = true }) + vim.cmd.highlight({ "link LspReferenceWrite Visual", bang = true }) -local function get_module_name(filepath) - return filepath:match("([^/\\]+)%.lua$") -end + client.settings = M.with_file( + string.format(".%s.json", client.name), + client.settings + ) or client.settings -local function get_server_config(name) - local module = "ow.lsp.config." .. name - package.loaded[module] = nil - return utils.try_require("ow.lsp.config." .. name) -end - -local reload_server_config = utils.debounce_with_id(function(name, events) - utils.info(("Reloading server with new config"):format(name), name) - ---@type Server|nil - local server = servers[name] - - if server and server.config.enable then - server:deinit() - servers[name] = nil - end - - if events.rename then - local _, _, err_name = - vim.uv.fs_stat(("%s/%s.lua"):format(CONFIG_DIR, name)) - if err_name == "ENOENT" then - return + if fn then + fn(client, bufnr) end end +end - local config = get_server_config(name) - if not config then +--- Load a JSON file and return a parsed table merged with settings +---@param path string +---@param settings? table +---@return table? +function M.with_file(path, settings) + local file = io.open(path, "r") + if not file then return end - server = Server.new(name, config) - if not server or not server.config.enable then - return - end - - local on_done = function(success) - if success then - utils.info(("%s reloaded"):format(name)) - end - end - - if #server:get_ft_buffers() ~= 0 then - server:setup(on_done) - else - server:init(on_done) - end - - servers[name] = server -end, 1000) - -local function process_change(error, filename, events) - if error then - utils.err( - ("Error on change for %s:\n%s"):format(filename, error), - "ow.lsp.on_config_change" - ) - return - end - - local name = get_module_name(filename) - if not name or name == "init" then - return - end - - reload_server_config(name, name, events) -end - -local function load_configs() - local handle = vim.uv.fs_scandir(CONFIG_DIR) - while handle do - local filepath = vim.uv.fs_scandir_next(handle) - - if not filepath then - break - end - - local name = get_module_name(filepath) - - if name == "init" then - goto continue - end - - local config = get_server_config(name) - - if not config then - goto continue - end - - local server = Server.new(name, config) - if server then - servers[name] = server - end - - ::continue:: - end - - vim.uv.fs_event_start( - vim.uv.new_fs_event(), - CONFIG_DIR, - {}, - vim.schedule_wrap(process_change) + local json = file:read("*all") + file:close() + local ok, resp = pcall( + vim.json.decode, + json, + { luanil = { object = true, array = true } } ) + if not ok then + log.warning("Failed to parse json file %s: %s", path, resp) + return + end + + return vim.tbl_deep_extend("force", settings or {}, resp) end ---- Setup diagnostics UI -local function setup_diagnostics() +function M.setup() vim.diagnostic.config({ underline = true, signs = M.diagnostic_signs, @@ -155,17 +86,365 @@ local function setup_diagnostics() wrap = false, }, }) -end -function M.setup() - load_configs() - setup_diagnostics() + vim.lsp.enable({ + "bashls", + "clangd", + "cmake", + "gopls", + "intelephense", + -- "jedi_language_server", + "lemminx", + "lua_ls", + "mesonlsp", + -- "phpactor", + -- "pyrefly", + "pyright", + "ruff", + "rust_analyzer", + "zls", + }) - for _, server in pairs(servers) do - if server.config.enable then - server:init() - end + local capabilities = vim.lsp.protocol.make_client_capabilities() + local cmp_nvim_lsp = util.try_require("cmp_nvim_lsp") + if cmp_nvim_lsp then + capabilities = vim.tbl_deep_extend( + "force", + capabilities, + cmp_nvim_lsp.default_capabilities() + ) end + + vim.lsp.config("*", { + on_attach = M.with_defaults(), + capabilities = capabilities, + }) + + vim.lsp.config("bashls", { + filetypes = { + "sh", + "bash", + "zsh", + }, + on_attach = M.with_defaults(function(_, bufnr) + keymap.set(bufnr, { + { + mode = "n", + lhs = "lf", + rhs = function() + util.format({ + cmd = { "shfmt", "-s", "-i", "4", "-" }, + output = "stdout", + }) + end, + }, + }) + end), + }) + + vim.lsp.config("clangd", { + filetypes = { + "c", + "cpp", + }, + cmd = { + "clangd", + "--clang-tidy", + "--enable-config", + -- Fix for errors in files outside of project + -- https://clangd.llvm.org/faq#how-do-i-fix-errors-i-get-when-opening-headers-outside-of-my-project-directory + "--compile-commands-dir=build", + }, + single_file_support = true, + on_attach = M.with_defaults(function(_, bufnr) + keymap.set(bufnr, { + { + mode = "n", + lhs = "gs", + rhs = vim.cmd.ClangdSwitchSourceHeader, + }, + }) + end), + }) + + vim.lsp.config("cmake", { + init_options = { + buildDirectory = "build", + }, + }) + + vim.lsp.config("gopls", { + settings = { + gopls = { + staticcheck = true, + semanticTokens = true, + }, + }, + on_attach = M.with_defaults(function(_, bufnr) + keymap.set(bufnr, { + { + mode = "n", + lhs = "lf", + rhs = function() + util.format({ + cmd = { + "golines", + "-m", + "80", + "--shorten-comments", + }, + output = "stdout", + }) + vim.lsp.buf.format({ async = true }) + end, + }, + }) + end), + }) + + vim.lsp.config("intelephense", { + settings = { + intelephense = { + environment = { + phpVersion = "8.4", + }, + format = { + enable = true, + braces = "psr12", + }, + }, + }, + on_attach = M.with_defaults(function(_, bufnr) + linter.add(bufnr, { + cmd = { + "phpcs", + "--standard=PSR12", + "--report=emacs", + "-s", + "-q", + "-", + }, + stdin = true, + stdout = true, + pattern = "^.+:(%d+):(%d+): (%w+) %- (.*) %((.*)%)$", + groups = { "lnum", "col", "severity", "message", "source" }, + source = "phpcs", + severity_map = { + error = vim.diagnostic.severity.ERROR, + warning = vim.diagnostic.severity.WARN, + }, + zero_idx_col = true, + zero_idx_lnum = true, + }) + + keymap.set(bufnr, { + { + mode = "n", + lhs = "lf", + rhs = function() + vim.lsp.buf.format() + util.format({ + cmd = { + "php-cs-fixer", + "fix", + "%file%", + "--quiet", + }, + output = "in_place", + ignore_stderr = true, + env = { PHP_CS_FIXER_IGNORE_ENV = "1" }, + }) + end, + }, + }) + end), + }) + + vim.lsp.config("jedi_language_server", { + init_options = { + completion = { + disableSnippets = true, + }, + diagnostics = { + enable = true, + }, + }, + }) + + vim.lsp.config("lemminx", { + init_options = { + settings = { + xml = { + format = { + enabled = true, -- is able to format document + splitAttributes = true, -- each attribute is formatted onto new line + joinCDATALines = false, -- normalize content inside CDATA + joinCommentLines = false, -- normalize content inside comments + formatComments = true, -- keep comment in relative position + joinContentLines = false, -- normalize content inside elements + spaceBeforeEmptyCloseLine = true, -- insert whitespace before self closing tag end bracket + }, + validation = { + noGrammar = "ignore", + enabled = true, + schema = true, + }, + }, + }, + }, + }) + + local lua_library_paths = { + vim.env.VIMRUNTIME, + } + for _, plugin in ipairs(require("lazy").plugins()) do + table.insert(lua_library_paths, plugin.dir) + end + + vim.lsp.config("lua_ls", { + settings = { + Lua = { + completion = { showWord = "Disable" }, + runtime = { + version = "LuaJIT", + path = { + "lua/?.lua", + "lua/?/init.lua", + }, + pathStrict = true, + }, + workspace = { + library = lua_library_paths, + checkThirdParty = false, + }, + hint = { + enable = false, + arrayIndex = "Disable", + await = true, + paramName = "All", + paramType = true, + semicolon = "Disable", + setType = true, + }, + telemetry = { enable = false }, + }, + }, + on_attach = M.with_defaults(function(_, bufnr) + keymap.set(bufnr, { + { + mode = "n", + lhs = "lf", + rhs = function() + util.format({ + cmd = { "stylua", "-" }, + output = "stdout", + }) + end, + }, + { + mode = "x", + lhs = "lf", + rhs = function() + util.format({ + cmd = { + "stylua", + "-", + "--range-start", + "%byte_start%", + "--range-end", + "%byte_end%", + }, + output = "stdout", + }) + end, + }, + }) + end), + }) + + vim.lsp.config("mesonlsp", { + settings = { + others = { + disableInlayHints = true, + }, + }, + }) + + vim.lsp.config("pyright", { + settings = { + python = { + analysis = { + autoSearchPaths = true, + diagnosticMode = "openFilesOnly", + useLibraryCodeForTypes = true, + typeCheckingMode = "strict", + stubPath = "stubs", + }, + }, + pyright = { + disableLanguageServices = false, + }, + }, + }) + + vim.lsp.config("ruff", { + on_attach = M.with_defaults(function(_, bufnr) + keymap.set(bufnr, { + { + mode = "n", + lhs = "lf", + rhs = function() + vim.lsp.buf.format() + util.format({ + cmd = { + "ruff", + "check", + "--stdin-filename=%file%", + "--select=I", + "--fix", + "--quiet", + "-", + }, + output = "stdout", + }) + end, + }, + }) + end), + }) + + vim.lsp.config("rust_analyzer", { + settings = { + ["rust-analyzer"] = { + inlayHints = { + chainingHints = { + enable = false, + }, + parameterHints = { + enable = false, + }, + typeHints = { + enable = false, + }, + }, + }, + }, + }) + + vim.lsp.config("zls", { + settings = { + zls = { + warn_style = true, + highlight_global_var_declarations = true, + inlay_hints_show_variable_type_hints = false, + inlay_hints_show_struct_literal_field_type = false, + inlay_hints_show_parameter_name = false, + inlay_hints_show_builtin = false, + }, + }, + }) + + vim.lsp.config("pyrefly", {}) end return M diff --git a/lua/ow/lsp/config/bashls.lua b/lua/ow/lsp/config/bashls.lua deleted file mode 100644 index b79c848..0000000 --- a/lua/ow/lsp/config/bashls.lua +++ /dev/null @@ -1,36 +0,0 @@ -local utils = require("ow.utils") - -return { - enable = true, - dependencies = { - "npm", - }, - mason = { - "bash-language-server", - dependencies = { - { "shellcheck" }, - { "shfmt" }, - }, - }, - keymaps = { - { - mode = "n", - lhs = "lf", - rhs = function() - utils.format({ - cmd = { "shfmt", "-s", "-i", "4", "-" }, - output = "stdout", - }) - end, - }, - }, - lspconfig = { - filetypes = { - "sh", - "bash", - "zsh", - }, - cmd = { "bash-language-server", "start" }, - single_file_support = true, - }, -} diff --git a/lua/ow/lsp/config/clangd.lua b/lua/ow/lsp/config/clangd.lua deleted file mode 100644 index 3f8cf52..0000000 --- a/lua/ow/lsp/config/clangd.lua +++ /dev/null @@ -1,28 +0,0 @@ ----@type ServerConfig -return { - enable = true, - mason = { "clangd" }, - keymaps = { - { - mode = "n", - lhs = "gs", - rhs = vim.cmd.ClangdSwitchSourceHeader, - }, - }, - lspconfig = { - filetypes = { - "c", - "cpp", - }, - cmd = { - "clangd", - "--clang-tidy", - "--enable-config", - - -- Fix for errors in files outside of project - -- https://clangd.llvm.org/faq#how-do-i-fix-errors-i-get-when-opening-headers-outside-of-my-project-directory - "--compile-commands-dir=build", - }, - single_file_support = true, - }, -} diff --git a/lua/ow/lsp/config/cmake.lua b/lua/ow/lsp/config/cmake.lua deleted file mode 100644 index 43ede00..0000000 --- a/lua/ow/lsp/config/cmake.lua +++ /dev/null @@ -1,20 +0,0 @@ -return { - enable = true, - dependencies = { - "python3", - }, - mason = { - name = "cmake-language-server", - -- version = "", - }, - lspconfig = { - filetypes = { - "cmake", - }, - cmd = { "cmake-language-server", }, - single_file_support = true, - init_options = { - buildDirectory = "build", - }, - }, -} diff --git a/lua/ow/lsp/config/gopls.lua b/lua/ow/lsp/config/gopls.lua deleted file mode 100644 index dda75b5..0000000 --- a/lua/ow/lsp/config/gopls.lua +++ /dev/null @@ -1,36 +0,0 @@ -local utils = require("ow.utils") - ----@type ServerConfig -return { - enable = true, - mason = { "gopls", dependencies = { "golines" } }, - keymaps = { - { - mode = "n", - lhs = "lf", - rhs = function() - utils.format({ - cmd = { "golines", "-m", "80", "--shorten-comments" }, - output = "stdout", - }) - vim.lsp.buf.format({ async = true }) - end, - }, - }, - lspconfig = { - filetypes = { - "go", - "gomod", - "gowork", - "gotmpl", - }, - cmd = { "gopls" }, - single_file_support = true, - settings = { - gopls = { - staticcheck = true, - semanticTokens = true, - }, - }, - }, -} diff --git a/lua/ow/lsp/config/groovyls.lua b/lua/ow/lsp/config/groovyls.lua deleted file mode 100644 index f5bfc99..0000000 --- a/lua/ow/lsp/config/groovyls.lua +++ /dev/null @@ -1,30 +0,0 @@ ---[[ - Copyright 2023 Oscar Wallberg - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -]] - -return { - enable = false, - dependencies = { - "java", - }, - mason = { - "groovy-language-server", - }, - lspconfig = { - cmd = { - "groovy-language-server", - }, - }, -} diff --git a/lua/ow/lsp/config/hls.lua b/lua/ow/lsp/config/hls.lua deleted file mode 100644 index d9994bc..0000000 --- a/lua/ow/lsp/config/hls.lua +++ /dev/null @@ -1,18 +0,0 @@ -return { - enable = true, - mason = { - name = "haskell-language-server", - -- version = "", - }, - lspconfig = { - filetypes = { "haskell", "lhaskell", "cabal", }, - cmd = { "haskell-language-server-wrapper", "--lsp", }, - single_file_support = true, - settings = { - haskell = { - cabalFormattingProvider = "cabalfmt", - formattingProvider = "ormolu", - }, - }, - }, -} diff --git a/lua/ow/lsp/config/intelephense.lua b/lua/ow/lsp/config/intelephense.lua deleted file mode 100644 index 971f195..0000000 --- a/lua/ow/lsp/config/intelephense.lua +++ /dev/null @@ -1,76 +0,0 @@ --- spec: --- https://github.com/bmewburn/intelephense-docs/blob/master/installation.md --- https://github.com/bmewburn/vscode-intelephense/blob/master/package.json - -local utils = require("ow.utils") - ----@type ServerConfig -return { - enable = true, - dependencies = { - "npm", - }, - mason = { "intelephense", dependencies = { "phpcs" } }, - linters = { - { - cmd = { - "phpcs", - "--standard=PSR12", - "--report=emacs", - "-s", - "-q", - "-", - }, - stdin = true, - stdout = true, - pattern = "^.+:(%d+):(%d+): (%w+) %- (.*) %((.*)%)$", - groups = { "lnum", "col", "severity", "message", "source" }, - source = "phpcs", - severity_map = { - error = vim.diagnostic.severity.ERROR, - warning = vim.diagnostic.severity.WARN, - }, - zero_idx_col = true, - zero_idx_lnum = true, - }, - }, - keymaps = { - { - mode = "n", - lhs = "lf", - rhs = function() - vim.lsp.buf.format() - utils.format({ - cmd = { - "php-cs-fixer", - "fix", - "%file%", - "--quiet", - }, - output = "in_place", - ignore_stderr = true, - env = { PHP_CS_FIXER_IGNORE_ENV = "1" }, - }) - end, - }, - }, - settings_file = ".intelephense.json", - lspconfig = { - filetypes = { - "php", - }, - cmd = { "intelephense", "--stdio" }, - single_file_support = true, - settings = { - intelephense = { - environment = { - phpVersion = "8.4", - }, - format = { - enable = true, - braces = "psr12", - }, - }, - }, - }, -} diff --git a/lua/ow/lsp/config/jedi_language_server.lua b/lua/ow/lsp/config/jedi_language_server.lua deleted file mode 100644 index 865798a..0000000 --- a/lua/ow/lsp/config/jedi_language_server.lua +++ /dev/null @@ -1,20 +0,0 @@ ----@type ServerConfig -return { - mason = { "jedi-language-server" }, - lspconfig = { - filetypes = { - "python", - }, - cmd = { "jedi-language-server" }, - single_file_support = true, - init_options = { - completion = { - disableSnippets = true, - }, - diagnostics = { - enable = true, - }, - }, - }, -} - diff --git a/lua/ow/lsp/config/lemminx.lua b/lua/ow/lsp/config/lemminx.lua deleted file mode 100644 index 444d2d6..0000000 --- a/lua/ow/lsp/config/lemminx.lua +++ /dev/null @@ -1,40 +0,0 @@ --- spec: https://github.com/eclipse/lemminx/blob/main/docs/Configuration.md - -return { - enable = true, - mason = { - name = "lemminx", - dependencies = { "xmlformatter" }, - }, - lspconfig = { - filetypes = { - "xml", - "xsd", - "xsl", - "xslt", - "svg", - }, - cmd = { "lemminx", }, - single_file_support = true, - init_options = { - settings = { - xml = { - format = { - enabled = true, -- is able to format document - splitAttributes = true, -- each attribute is formatted onto new line - joinCDATALines = false, -- normalize content inside CDATA - joinCommentLines = false, -- normalize content inside comments - formatComments = true, -- keep comment in relative position - joinContentLines = false, -- normalize content inside elements - spaceBeforeEmptyCloseLine = true, -- insert whitespace before self closing tag end bracket - }, - validation = { - noGrammar = "ignore", - enabled = true, - schema = true, - } - }, - }, - }, - }, -} diff --git a/lua/ow/lsp/config/lua_ls.lua b/lua/ow/lsp/config/lua_ls.lua deleted file mode 100644 index 94415a4..0000000 --- a/lua/ow/lsp/config/lua_ls.lua +++ /dev/null @@ -1,87 +0,0 @@ --- spec: https://luals.github.io/wiki/settings/ -local utils = require("ow.utils") - ----@type ServerConfig -return { - enable = true, - dependencies = { "cargo" }, - mason = { - "lua-language-server", - post_install = { { cmd = { "cargo", "install", "stylua", "--features", "lua54" } } }, - }, - keymaps = { - { - mode = "n", - lhs = "lf", - rhs = function() - utils.format({ - cmd = { "stylua", "-" }, - output = "stdout", - }) - end, - }, - { - mode = "x", - lhs = "lf", - rhs = function() - utils.format({ - cmd = { - "stylua", - "-", - "--range-start", - "%byte_start%", - "--range-end", - "%byte_end%", - }, - output = "stdout", - }) - end, - }, - }, - lspconfig = { - filetypes = { "lua" }, - cmd = { "lua-language-server" }, - single_file_support = true, - settings = { - Lua = { - completion = { showWord = "Disable" }, - diagnostics = { - -- disable = { "missing-fields" }, - }, - runtime = { - version = "LuaJIT", - path = { - -- "?.lua", - "?.lua", - "?/init.lua", - -- "?.lua", - -- "lua/?.lua", - -- "lua/?/init.lua", - -- "?/lua/?.lua", - -- "?/lua/?/init.lua", - }, - -- pathStrict = true, - }, - workspace = { - library = { - vim.env.VIMRUNTIME, - "~/repos/awesome-code-doc", - "/usr/share/awesome/lib", - vim.fn.stdpath("data") .. "/lazy", - }, - checkThirdParty = false, - }, - hint = { - enable = false, - arrayIndex = "Disable", - await = true, - paramName = "All", - paramType = true, - semicolon = "Disable", - setType = true, - }, - telemetry = { enable = false }, - }, - }, - }, -} diff --git a/lua/ow/lsp/config/mesonlsp.lua b/lua/ow/lsp/config/mesonlsp.lua deleted file mode 100644 index 5699f7d..0000000 --- a/lua/ow/lsp/config/mesonlsp.lua +++ /dev/null @@ -1,21 +0,0 @@ ----@type ServerConfig -return { - enable = true, - mason = { "mesonlsp" }, - lspconfig = { - ---@type fun(filename: string, bufnr: number): string? - root_dir = function(filename, bufnr) - local parent = require("lspconfig").util.find_git_ancestor(filename) - if not parent then - local win = vim.fn.bufwinid(bufnr) - parent = vim.fn.getcwd(win) - end - return parent - end, - settings = { - others = { - disableInlayHints = true, - }, - }, - }, -} diff --git a/lua/ow/lsp/config/pyrefly.lua b/lua/ow/lsp/config/pyrefly.lua deleted file mode 100644 index dd16caf..0000000 --- a/lua/ow/lsp/config/pyrefly.lua +++ /dev/null @@ -1,20 +0,0 @@ -local util = require("lspconfig.util") - ----@type ServerConfig -return { - enable = false, - dependencies = { "pyrefly" }, - lspconfig = { - cmd = { "pyrefly", "lsp" }, - filetypes = { "python" }, - root_dir = util.root_pattern( - "pyrefly.toml", - "pyproject.toml", - "setup.py", - "setup.cfg", - "requirements.txt", - "Pipfile", - ".git" - ), - }, -} diff --git a/lua/ow/lsp/config/pyright.lua b/lua/ow/lsp/config/pyright.lua deleted file mode 100644 index 3397abb..0000000 --- a/lua/ow/lsp/config/pyright.lua +++ /dev/null @@ -1,27 +0,0 @@ ----@type ServerConfig -return { - mason = { "pyright" }, - lspconfig = { - filetypes = { - "python", - }, - cmd = { "pyright-langserver", "--stdio" }, - single_file_support = true, - -- https://microsoft.github.io/pyright/#/settings - settings = { - python = { - analysis = { - autoSearchPaths = true, - diagnosticMode = "openFilesOnly", - useLibraryCodeForTypes = true, - typeCheckingMode = "strict", - stubPath = "stubs", - }, - }, - pyright = { - disableLanguageServices = true, - }, - }, - }, -} - diff --git a/lua/ow/lsp/config/ruff.lua b/lua/ow/lsp/config/ruff.lua deleted file mode 100644 index 3d19e1e..0000000 --- a/lua/ow/lsp/config/ruff.lua +++ /dev/null @@ -1,27 +0,0 @@ -local utils = require("ow.utils") - ----@type ServerConfig -return { - mason = "ruff", - keymaps = { - { - mode = "n", - lhs = "lf", - rhs = function() - vim.lsp.buf.format() - utils.format({ - cmd = { - "ruff", - "check", - "--stdin-filename=%file%", - "--select=I", - "--fix", - "--quiet", - "-", - }, - output = "stdout", - }) - end, - }, - }, -} diff --git a/lua/ow/lsp/config/rust_analyzer.lua b/lua/ow/lsp/config/rust_analyzer.lua deleted file mode 100644 index a47a7a5..0000000 --- a/lua/ow/lsp/config/rust_analyzer.lua +++ /dev/null @@ -1,191 +0,0 @@ --- spec: https://rust-analyzer.github.io/manual.html#configuration - -return { - enable = true, - mason = { - name = "rust-analyzer", - -- version = "", - }, - lspconfig = { - filetypes = { - "rust", - }, - cmd = { "rust-analyzer", }, - single_file_support = true, - settings = { - ["rust-analyzer"] = { - inlayHints = { - chainingHints = { - enable = false, - }, - parameterHints = { - enable = false, - }, - typeHints = { - enable = false, - }, - }, - --[[ assist = { - emitMustUse = false, - expressionFillDefault = false, - }, - cachePriming = { - enable = false, - }, - cargo = { - autoreload = false, - buildScripts = { - enable = false, - }, - }, - checkOnSave = false, - completion = { - autoimport = { - enable = false, - }, - autoself = { - enable = false, - }, - callable = { - snippets = false, - }, - fullFunctionSignatures = { - enable = false, - }, - postfix = { - enable = false, - }, - privateEditable = { - enable = false, - }, - }, - diagnostics = { - enable = false, - }, - highlightRelated = { - breakPoints = { - enable = false, - }, - closureCaptures = { - enable = false, - }, - exitPoints = { - enable = false, - }, - references = { - enable = false, - }, - yieldPoints = { - enable = false, - }, - }, - hover = { - actions = { - enable = false, - }, - documentation = { - enable = true, - }, - links = { - enable = false, - }, - memoryLayout = { - enable = false, - }, - }, - imports = { - group = { - enable = false, - }, - }, - inlayHints = { - bindingModeHints = { - enable = false, - }, - chainingHints = { - enable = false, - }, - closingBraceHints = { - enable = false, - }, - closureCaptureHints = { - enable = false, - }, - closureReturnTypeHints = { - enable = false, - }, - discriminantHints = { - enable = false, - }, - expressionAdjustmentHints = { - enable = false, - }, - lifetimeElisionHints = { - enable = false, - }, - parameterHints = { - enable = false, - }, - reborrowHints = { - enable = false, - }, - typeHints = { - enable = false, - }, - }, - joinLines = { - joinAssignments = false, - joinElseIf = false, - removeTrailingComma = false, - unwrapTrivialBlock = false, - }, - lens = { - enable = false, - }, - notifications = { - cargoTomlNotFound = false, - }, - procMacro = { - enable = false, - }, - references = { - excludeImports = false, - }, - rustfmt = { - rangeFormatting = { - enable = false, - }, - }, - semanticHighlighting = { - doc = { - comment = { - inject = { - enable = false, - }, - }, - }, - nonStandardTokens = false, - operator = { - enable = false, - }, - punctuation = { - enable = false, - }, - strings = { - enable = false, - }, - }, - signatureInfo = { - documentation = { - enable = true, - }, - }, - typing = { - autoClosingAngleBrackets = { - enable = false, - }, - }, ]] - }, - }, - }, -} diff --git a/lua/ow/lsp/config/zls.lua b/lua/ow/lsp/config/zls.lua deleted file mode 100644 index 461112b..0000000 --- a/lua/ow/lsp/config/zls.lua +++ /dev/null @@ -1,31 +0,0 @@ --- spec: https://github.com/zigtools/zls#configuration-options - ----@type ServerConfig -return { - enable = true, - dependencies = { - "zig", - }, - -- mason = { - -- name = "zls", - -- -- version = "", - -- }, - lspconfig = { - filetypes = { - "zig", - "zir", - }, - cmd = { "zls", }, - single_file_support = true, - settings = { - zls = { - warn_style = true, - highlight_global_var_declarations = true, - inlay_hints_show_variable_type_hints = false, - inlay_hints_show_struct_literal_field_type = false, - inlay_hints_show_parameter_name = false, - inlay_hints_show_builtin = false, - }, - }, - }, -} diff --git a/lua/ow/lsp/keymap.lua b/lua/ow/lsp/keymap.lua index 29ee260..388b334 100644 --- a/lua/ow/lsp/keymap.lua +++ b/lua/ow/lsp/keymap.lua @@ -1,7 +1,4 @@ --- Mappings. --- See `:help vim.lsp.*` for documentation on any of the below functions - -local utils = require("ow.utils") +local util = require("ow.util") ---@class Keymap ---@field mode string|string[] @@ -9,29 +6,25 @@ local utils = require("ow.utils") ---@field rhs string|function ---@field opts? vim.keymap.set.Opts -local MODE_TYPES = { "n", "v", "s", "x", "o", "i", "l", "c" } - local M = {} ----@type table -M.old = {} - ----@type table -M.new = {} - ---- Load LSP keybinds ----@param server Server -function M:init(server, bufnr) - self.old[bufnr] = {} - for _, mode in ipairs(MODE_TYPES) do - vim.tbl_extend( - "error", - self.old[bufnr], - vim.api.nvim_buf_get_keymap(bufnr, mode) +---@param bufnr integer +---@param keymaps Keymap[] +function M.set(bufnr, keymaps) + for _, keymap in ipairs(keymaps) do + keymap.opts = vim.tbl_extend( + "force", + keymap.opts or {}, + { buffer = bufnr, remap = true } ) + vim.keymap.set(keymap.mode, keymap.lhs, keymap.rhs, keymap.opts) end +end - self.new[bufnr] = { +---@param bufnr integer +function M.set_defaults(bufnr) + ---@type Keymap[] + local keymaps = { { mode = { "n" }, lhs = "df", rhs = vim.diagnostic.open_float }, { mode = { "n" }, @@ -88,10 +81,10 @@ function M:init(server, bufnr) }, } - local telescope = utils.try_require("telescope.builtin") + local telescope = util.try_require("telescope.builtin") if telescope then - vim.list_extend(self.new[bufnr], { + vim.list_extend(keymaps, { { mode = "n", lhs = "dl", rhs = telescope.diagnostics }, { mode = "n", lhs = "grt", rhs = telescope.lsp_type_definitions, }, { mode = "n", lhs = "gd", rhs = telescope.lsp_definitions }, @@ -99,43 +92,14 @@ function M:init(server, bufnr) { mode = "n", lhs = "grr", rhs = telescope.lsp_references }, }) else - vim.list_extend(self.new[bufnr], { + vim.list_extend(keymaps, { { mode = "n", lhs = "dl", rhs = vim.diagnostic.setloclist }, { mode = "n", lhs = "grt", rhs = vim.lsp.buf.type_definition, }, { mode = "n", lhs = "gd", rhs = vim.lsp.buf.definition }, }) end - if server.config.keymaps then - vim.list_extend(self.new[bufnr], server.config.keymaps) - end - - for _, keymap in ipairs(self.new[bufnr]) do - keymap.opts = vim.tbl_extend( - "force", - keymap.opts or {}, - { buffer = bufnr, remap = true } - ) - vim.keymap.set(keymap.mode, keymap.lhs, keymap.rhs, keymap.opts) - end -end - -function M:deinit(bufnr) - if self.new[bufnr] then - for _, keymap in ipairs(self.new[bufnr]) do - -- pcall to avoid error if keymap was already removed, - -- for example if server.config.keymaps overrides a default LSP keymap - pcall(vim.keymap.del, keymap.mode, keymap.lhs, { buffer = bufnr }) - end - self.new[bufnr] = nil - end - - if self.old[bufnr] then - for _, keymap in ipairs(self.old[bufnr]) do - vim.cmd.mapset(keymap) - end - self.old[bufnr] = nil - end + M.set(bufnr, keymaps) end return M diff --git a/lua/ow/lsp/linter.lua b/lua/ow/lsp/linter.lua index 455a90e..e8d0927 100644 --- a/lua/ow/lsp/linter.lua +++ b/lua/ow/lsp/linter.lua @@ -1,10 +1,10 @@ -local utils = require("ow.utils") +local util = require("ow.util") +local log = require("ow.log") ---@class Linter ----@field name string ---@field namespace number ---@field augroup number ----@field buffers number[] +---@field bufnr number ---@field config LinterConfig M = {} M.__index = M @@ -89,10 +89,9 @@ end --- Clamp column to line length ---@param diag vim.Diagnostic ----@param bufnr number -function M:clamp_col(diag, bufnr) +function M:clamp_col(diag) local lines = - vim.api.nvim_buf_get_lines(bufnr, diag.lnum, diag.lnum + 1, false) + vim.api.nvim_buf_get_lines(self.bufnr, diag.lnum, diag.lnum + 1, false) if #lines == 0 then return end @@ -160,7 +159,7 @@ function M:fix_indexing(diag) end end -function M:process_json_output(json, bufnr) +function M:process_json_output(json) ---@type vim.Diagnostic[] local diagnostics = {} @@ -170,7 +169,7 @@ function M:process_json_output(json, bufnr) end if type(items) ~= "table" then - utils.err("diagnostics root is not an array or object") + log.error("diagnostics root is not an array or object") return end @@ -198,7 +197,7 @@ function M:process_json_output(json, bufnr) end self:fix_indexing(diag) - self:clamp_col(diag, bufnr) + self:clamp_col(diag) self:add_tags(diag) if type(self.config.json.callback) == "function" then @@ -212,12 +211,10 @@ function M:process_json_output(json, bufnr) end --- Validate input ----@param name string ---@param config LinterConfig ---@return boolean -function M.validate(name, config) +function M.validate(config) local ok, resp = pcall(vim.validate, { - name = { name, "string" }, config = { config, "table" }, }) @@ -226,7 +223,7 @@ function M.validate(name, config) cmd = { config.cmd, function(t) - return utils.is_list(t, "string") + return util.is_list(t, "string") end, "list of strings", }, @@ -237,7 +234,7 @@ function M.validate(name, config) groups = { config.groups, function(t) - return utils.is_list(t, "string") + return util.is_list(t, "string") end, true, "list of strings", @@ -245,7 +242,7 @@ function M.validate(name, config) severity_map = { config.severity_map, function(t) - return utils.is_map(t, "string", "number") + return util.is_map(t, "string", "number") end, true, "map of string and number", @@ -258,23 +255,24 @@ function M.validate(name, config) end if not ok then - utils.err(("Invalid config for linter:\n%s"):format(resp)) + log.error("Invalid config for linter: %s", resp) return false end if not config.json and (not config.pattern or not config.groups) then - utils.err("Either json or pattern and groups must be provided") + log.error("Either json or pattern and groups must be provided") return false end return true end -function M:run(bufnr) +---@return boolean success +function M:run() local input if self.config.stdin then - input = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + input = vim.api.nvim_buf_get_lines(self.bufnr, 0, -1, false) end local cmd = vim.fn.copy(self.config.cmd) @@ -286,6 +284,7 @@ function M:run(bufnr) cmd[i] = arg end + local success = true local ok, resp = pcall( vim.system, cmd, @@ -301,7 +300,7 @@ function M:run(bufnr) if self.config.stderr then output = out.stderr or "" elseif out.stderr and out.stderr ~= "" then - utils.err(out.stderr) + log.error(out.stderr) end if self.config.json then @@ -311,13 +310,14 @@ function M:run(bufnr) { luanil = { object = true, array = true } } ) if not ok then - utils.err("Failed to parse JSON: " .. json) + log.error("Failed to parse JSON: " .. json) + success = false return end - local diagnostics = self:process_json_output(json, bufnr) + local diagnostics = self:process_json_output(json) if diagnostics then - vim.diagnostic.set(self.namespace, bufnr, diagnostics) + vim.diagnostic.set(self.namespace, self.bufnr, diagnostics) end return @@ -335,67 +335,64 @@ function M:run(bufnr) ) if not ok then - utils.err(tostring(resp)) + log.error(tostring(resp)) + success = false return elseif resp then resp.source = resp.source or self.config.source - self:clamp_col(resp, bufnr) + self:clamp_col(resp) self:add_tags(resp) self:fix_indexing(resp) table.insert(diagnostics, resp) end end - vim.diagnostic.set(self.namespace, bufnr, diagnostics) + vim.diagnostic.set(self.namespace, self.bufnr, diagnostics) end) ) if not ok then - utils.err(("Failed to run %s:\n%s"):format(self.config.cmd[1], resp)) + log.error("Failed to run %s: %s", self.config.cmd[1], resp) + success = false end + + return success end -function M:init(bufnr) - table.insert(self.buffers, bufnr) - vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, { - buffer = bufnr, - callback = utils.debounce(function() - self:run(bufnr) - end, self.config.debounce), - group = self.augroup, - }) - self:run(bufnr) -end - -function M:deinit() - for _, bufnr in ipairs(self.buffers) do - vim.api.nvim_buf_clear_namespace(bufnr, self.namespace, 0, -1) - end - self.buffers = {} - - vim.api.nvim_clear_autocmds({ group = self.augroup }) -end - ---- Create a new instance ----@param name string +---@param bufnr integer ---@param config LinterConfig ----@return Linter|nil -function M.new(name, config) - if not M.validate(name, config) then +function M.add(bufnr, config) + if not M.validate(config) then return end config.debounce = config.debounce or 100 local linter = { - name = name, - namespace = vim.api.nvim_create_namespace(name), - augroup = vim.api.nvim_create_augroup(name, {}), - buffers = {}, + namespace = vim.api.nvim_create_namespace("ow.lsp.linter"), + augroup = vim.api.nvim_create_augroup( + "ow.lsp.linter", + { clear = false } + ), + bufnr = bufnr, config = config, } - return setmetatable(linter, M) + linter = setmetatable(linter, M) + + local success = linter:run() + if not success then + log.error("Not adding linter because of previous errors") + return + end + + vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, { + buffer = linter.bufnr, + callback = util.debounce(function() + linter:run() + end, linter.config.debounce), + group = linter.augroup, + }) end return M diff --git a/lua/ow/lsp/package.lua b/lua/ow/lsp/package.lua deleted file mode 100644 index 1cbd008..0000000 --- a/lua/ow/lsp/package.lua +++ /dev/null @@ -1,341 +0,0 @@ -local utils = require("ow.utils") - ----@class PostInstallStep ----@field cmd string[] - ----@class MasonPackage ----@field dependencies? MasonPackage[] ----@field config MasonPackageConfig -local M = {} -M.__index = M - ----@class MasonPackageConfig ----@field [1]? string ----@field name? string ----@field version? string ----@field dependencies? string[]|MasonPackageConfig[] ----@field post_install? PostInstallStep[] -M.config = {} - ---- Validate MasonPackageConfig ----@param config string|MasonPackageConfig ----@return boolean -function M.validate(config) - local ok, resp = pcall(vim.validate, { config = { config, { "table" } } }) - if ok then - ok, resp = pcall(vim.validate, { - name = { config.name or config[1], { "string" }, true }, - version = { config.version, { "string" }, true }, - dependencies = { - config.dependencies, - function(f) - if not f then - return true - end - - for _, dep in ipairs(f) do - local t = type(dep) - if - (t ~= "string" and t ~= "table") - or (type(dep) == "table" and not M.validate(dep)) - then - return false - end - end - - return true - end, - "list of string|MasonPackageConfig", - }, - post_install = { - config.post_install, - function(field) - if not field then - return true - end - - if not utils.is_list(field) then - return false - end - - for _, step in ipairs(field) do - local o, r = pcall(vim.validate, { step = { step, { "table" } } }) - if o then - o, r = pcall(vim.validate, { - cmd = { - step.cmd, - function(f) - return utils.is_list(f, "string") - end, - "list of strings or nil", - }, - }) - end - - if not o then - utils.err(("Invalid config for post_install step: %s"):format(r), "LSP") - return false - end - - return true - end - - return true - end, - "list of steps", - }, - }) - end - - if not ok then - utils.err( - ("Invalid config for %s:\n%s"):format(config.name or config[1] or config, resp), - "LSP" - ) - return false - end - - return true -end - ---- Run post installation steps ----@param pkg Package -function M:post_install(pkg) - ---@param step PostInstallStep - ---@param msg string - local function log_err(step, msg) - local cmd = table.concat(step.cmd, " ") - - utils.err( - ("Post installation step for %s:\n`%s`\nfailed with:\n%s"):format( - self.config.name, - cmd, - msg - ), - "LSP" - ) - end - - if self.config.post_install then - for _, step in ipairs(self.config.post_install) do - utils.info("running post install step", "LSP") - local cwd = pkg:get_install_path() - local args = step.cmd - local prog = table.remove(args, 1) - - if prog:find("[/\\]") then - prog = vim.fn.resolve(("%s/%s"):format(cwd, prog)) - end - - if not utils.is_executable(prog) then - log_err(step, "command not executable") - return - end - - local job = require("plenary.job"):new({ - command = prog, - args = args, - cwd = pkg:get_install_path(), - enabled_recording = true, - on_exit = vim.schedule_wrap(function(job, code, signal) - if code ~= 0 or signal ~= 0 then - log_err(step, table.concat(job:stderr_result(), "\n")) - return - end - utils.info("post install step done", "LSP") - end), - }) - job:start() - end - end -end - ---- Handle installation ----@param pkg Package ----@param old_version? string ----@param new_version? string ----@param on_done? fun(success: boolean) -function M:handle_install(pkg, old_version, new_version, on_done) - local version_str = "" - if old_version and new_version then - version_str = (" %s -> %s"):format(old_version, new_version) - elseif new_version then - version_str = " " .. new_version - end - - utils.info(("Installing %s%s"):format(self.config.name, version_str), "LSP") - local handle = pkg:install({ version = new_version }) - local err - handle:on( - "stderr", - vim.schedule_wrap(function(msg) - err = (err or "") .. msg - end) - ) - - handle:once( - "closed", - vim.schedule_wrap(function() - local is_installed = pkg:is_installed() - - if is_installed then - utils.info(("Successfully installed %s"):format(self.config.name), "LSP") - self:post_install(pkg) - else - if err then - err = ":\n" .. err - else - err = "" - end - - utils.err(("Failed to install %s%s"):format(self.config.name, err), "LSP") - end - - if on_done then - on_done(is_installed) - end - end) - ) -end - ---- Perform installation ----@param on_done fun(success: boolean)? -function M:install(on_done) - local registry = require("mason-registry") - local success, result = pcall(registry.get_package, self.config.name) - - if not success then - utils.err(("Failed to get package %s:\n%s"):format(self.config.name, result), "LSP") - - if on_done then - on_done(false) - end - - return - end - - local pkg = result - - if not pkg:is_installed() then - self:handle_install(pkg, nil, self.config.version, on_done) - elseif self.config.version then - pkg:get_installed_version(function(ok, version_or_err) - if not ok then - utils.err( - ("Failed to check currently installed version for %s:\n%s"):format( - self.config.name, - version_or_err - ), - "LSP" - ) - elseif self.config.version ~= version_or_err then - self:handle_install(pkg, version_or_err, self.config.version, on_done) - return - end - - if on_done then - on_done(ok) - end - end) - else - pkg:check_new_version(function(update_available, res) - local ok = type(res) == "table" or res == "Package is not outdated." - - if not ok then - utils.err( - ("Failed to check for update for %s:\n%s"):format(self.config.name, res), - "LSP" - ) - elseif update_available then - self:handle_install(pkg, res.current_version, res.latest_version, on_done) - return - end - - if on_done then - on_done(ok) - end - end) - - return - end -end - ---- Install package dependencies ----@param on_done fun(success: boolean)? -function M:install_dependencies(on_done) - local total = #self.dependencies - local completed = 0 - local all_successful = true - - --- Handle install result - ---@param success boolean - local function handle_result(success) - completed = completed + 1 - - if not success then - all_successful = false - end - - if completed == total and on_done then - on_done(all_successful) - end - end - - for _, dep in ipairs(self.dependencies) do - dep:install_with_dependencies(handle_result) - end -end - ---- Install package and any defined dependencies ----@param on_done fun(success: boolean)? -function M:install_with_dependencies(on_done) - --- Handle install result - ---@param success boolean - local function handle_result(success) - if success then - self:install(on_done) - elseif on_done then - on_done(success) - end - end - - require("mason-registry").refresh(function() - if self.dependencies and #self.dependencies ~= 0 then - self:install_dependencies(handle_result) - else - self:install(on_done) - end - end) -end - ---- Create a new instance ----@param config MasonPackageConfig|string ----@return MasonPackage|nil -function M.new(config) - if type(config) == "string" then - config = { config } - end - - if not M.validate(config) then - return - end - - config.name = config.name or config[1] - - local pkg = { config = config } - - if pkg.config.dependencies then - pkg.dependencies = {} - - for _, dep_cfg in ipairs(config.dependencies) do - local dep = M.new(dep_cfg) - if dep then - table.insert(pkg.dependencies, dep) - end - end - end - - return setmetatable(pkg, M) -end - -return M diff --git a/lua/ow/lsp/server.lua b/lua/ow/lsp/server.lua deleted file mode 100644 index e7484f1..0000000 --- a/lua/ow/lsp/server.lua +++ /dev/null @@ -1,392 +0,0 @@ -local keymap = require("ow.lsp.keymap") -local utils = require("ow.utils") - ----@class Linter -local Linter = require("ow.lsp.linter") - ----@class MasonPackage -local MasonPackage = require("ow.lsp.package") - ----@class Server ----@field name? string ----@field mason? MasonPackage ----@field client? vim.lsp.Client ----@field attached_buffers? number[] ----@field manager lspconfig.Manager ----@field linters? Linter[] ----@field config ServerConfig -local M = {} - -M.__index = M - ----@class ServerConfig ----@field enable? boolean ----@field dependencies? string[] ----@field mason? string|MasonPackageConfig ----@field keymaps? Keymap[] ----@field linters? LinterConfig[] ----@field settings_file? string ----@field lspconfig? lspconfig.Config -M.config = {} - ---- Validate ServerConfig ----@param config ServerConfig ----@return boolean -function M.validate(name, config) - local ok, resp = pcall(vim.validate, { config = { config, { "table" } } }) - - if ok then - ok, resp = pcall(vim.validate, { - enable = { config.enable, { "boolean" }, true }, - dependencies = { - config.dependencies, - function(f) - return utils.is_list_or_nil(f, "string") - end, - "list of strings or nil", - }, - mason = { config.mason, { "string", "table" }, true }, - keymaps = { - config.keymaps, - function(f) - if not f then - return true - end - - if not utils.is_list(f, "table") then - return false - end - - for _, key in ipairs(f) do - local o, r = pcall(vim.validate, { - mode = { key.mode, { "string", "table" } }, - lhs = { key.lhs, "string" }, - rhs = { key.rhs, { "string", "function" } }, - opts = { key.opts, "table", true }, - }) - - if not o then - utils.err(("Invalid keymap:\n%s"):format(r)) - return false - end - end - - return true - end, - "list of keymaps", - }, - lspconfig = { config.lspconfig, { "table" }, true }, - }) - end - - if not ok then - utils.err(("Invalid config for %s:\n%s"):format(name, resp)) - return false - end - - return true -end - ---- Called when language server attaches ----@param client vim.lsp.Client ----@param bufnr integer -function M:on_attach(client, bufnr) - if self.client and self.client.id ~= client.id then - self.client:stop(true) - end - self.client = client - self.attached_buffers = self.attached_buffers or {} - table.insert(self.attached_buffers, bufnr) - - keymap:init(self, bufnr) - if self.linters then - for _, linter in ipairs(self.linters) do - local bin = linter.config.cmd[1] - if utils.is_executable(bin) then - linter:init(bufnr) - else - utils.warn( - ("Not adding %s because it is not installed"):format(bin) - ) - end - end - end - - -- For document highlight - vim.cmd.highlight({ "link LspReferenceRead Visual", bang = true }) - vim.cmd.highlight({ "link LspReferenceText Visual", bang = true }) - vim.cmd.highlight({ "link LspReferenceWrite Visual", bang = true }) - - ---@alias lsp.Client vim.lsp.Client - -- require("lsp_compl").attach(client, bufnr, { - -- server_side_fuzzy_completion = true, - -- }) -end - ---- Configure the LSP client -function M:configure_client() - local lspconfig = require("lspconfig") - - local capabilities = vim.lsp.protocol.make_client_capabilities() - local cmp_nvim_lsp = utils.try_require("cmp_nvim_lsp") - if cmp_nvim_lsp then - capabilities = vim.tbl_deep_extend( - "force", - capabilities, - cmp_nvim_lsp.default_capabilities() - ) - end - - -- local epo = utils.try_require("epo") - -- if epo then - -- capabilities = vim.tbl_deep_extend( - -- "force", - -- capabilities, - -- epo.register_cap() - -- ) - -- end - - -- local lsp_compl = utils.try_require("lsp_compl") - -- if lsp_compl then - -- capabilities = vim.tbl_deep_extend("force", capabilities, lsp_compl.capabilities()) - -- end - -- - - self.config.lspconfig.capabilities = capabilities - self.config.lspconfig.on_attach = function(client, bufnr) - local ok, ret = pcall(self.on_attach, self, client, bufnr) - if not ok then - utils.err( - ("Failed to load on_attach for %s:\n%s"):format(self.name, ret), - "lsp.server:configure_client" - ) - end - end - - if self.config.settings_file then - local file = io.open(self.config.settings_file, "r") - if not file then - utils.warn( - ("Failed to open file for reading: %s"):format( - self.config.settings_file - ) - ) - else - local json = file:read("*all") - file:close() - local ok, resp = pcall( - vim.json.decode, - json, - { luanil = { object = true, array = true } } - ) - if not ok then - utils.warn( - ("Failed to parse json file %s:%s"):format( - self.config.settings_file, - resp - ) - ) - else - self.config.lspconfig.settings = vim.tbl_deep_extend( - "force", - self.config.lspconfig.settings or {}, - resp - ) - end - end - end - - local ok, ret = pcall(lspconfig[self.name].setup, self.config.lspconfig) - if not ok then - utils.err( - ("Failed to setup LSP server %s with lspconfig: %s"):format( - self.name, - ret - ) - ) - return - end - - self.manager = lspconfig[self.name].manager - for _, bufnr in ipairs(self:get_ft_buffers()) do - self.manager:try_add_wrapper(bufnr) - end - - if self.config.linters then - self.linters = {} - for i, config in ipairs(self.config.linters) do - local linter = - Linter.new(("%s_linter%d"):format(self.name, i), config) - - if linter then - table.insert(self.linters, linter) - end - end - end -end - -function M:get_ft_buffers() - local filetypes = self.config.lspconfig.filetypes or {} - if not vim.list_contains(filetypes, self.config.lspconfig.filetype) then - table.insert(filetypes, self.config.lspconfig.filetype) - end - if #filetypes == 0 then - return {} - end - return vim.tbl_filter(function(bufnr) - return vim.list_contains(filetypes, vim.bo[bufnr].filetype) - end, vim.api.nvim_list_bufs()) -end - ---- Check for and return missing dependencies ----@return table -function M:get_missing_unmanaged_deps() - local missing_deps = {} - if self.config.dependencies ~= nil then - for _, dep in ipairs(self.config.dependencies) do - if not utils.is_executable(dep) then - table.insert(missing_deps, dep) - end - end - end - return missing_deps -end - ---- Install LSP server ----@param on_done fun(success: boolean)? -function M:install(on_done) - --- Handle install result - ---@param success boolean - local function handle_result(success) - if not success then - self.config.enable = false - end - if on_done then - on_done(success) - end - end - - self.mason:install_with_dependencies(handle_result) -end - ---- Setup LSP server -function M:setup(on_done) - local missing_deps = self:get_missing_unmanaged_deps() - - if #missing_deps > 0 then - utils.warn( - ( - "Disabling %s because the following package(s)" - .. "are not installed: %s" - ):format(self.name, table.concat(missing_deps, ", ")) - ) - self.config.enable = false - return - end - - if self.mason then - self:install(function(success) - if success then - self:configure_client() - end - - if on_done then - on_done(success) - end - end) - elseif vim.fn.executable(self.config.lspconfig.cmd[1]) == 1 then - self:configure_client() - else - utils.warn(self.name .. " not installed, disabling") - self.config.enable = false - end -end - ---- Load autocmd for setting up LSP server upon entering a buffer of related ---- filetype -function M:init(on_done) - local group = vim.api.nvim_create_augroup("lsp_bootstrap_" .. self.name, {}) - vim.api.nvim_create_autocmd("FileType", { - once = true, - pattern = self.config.lspconfig.filetypes or {}, - callback = function() - self:setup(on_done) - end, - group = group, - }) -end - -function M:deinit() - if self.attached_buffers then - for _, bufnr in ipairs(self.attached_buffers) do - keymap:deinit(bufnr) - end - end - - if self.client then - self.client:stop(true) - self.client = nil - end - - vim.api.nvim_clear_autocmds({ group = "lsp_bootstrap_" .. self.name }) - - if self.linters then - for _, linter in ipairs(self.linters) do - linter:deinit() - end - - self.linters = nil - end - - require("lspconfig.configs")[self.name] = nil - require("lspconfig")[self.name] = nil -end - ---- Create a new instance ----@param name string ----@param config? ServerConfig ----@return Server|nil -function M.new(name, config) - config = config or {} - - if config.enable == nil then - config.enable = true - end - - if not M.validate(name, config) then - return - end - - local ok, resp = pcall(require, "lspconfig.configs." .. name) - if not ok and config.lspconfig then - require("lspconfig.configs")[name] = vim.tbl_deep_extend( - "keep", - { default_config = {} }, - config.lspconfig - ) - elseif ok then - config.lspconfig = vim.tbl_deep_extend( - "keep", - config.lspconfig or {}, - resp.default_config - ) - else - utils.err( - ("Server with name %s does not exist in lspconfig"):format(name) - ) - return - end - - local server = { name = name, config = config } - - if server.config.mason then - local pkg = MasonPackage.new(server.config.mason) - if pkg then - server.mason = pkg - end - end - - return setmetatable(server, M) -end - -return M diff --git a/lua/ow/plugins/cmp.lua b/lua/ow/plugins/cmp.lua index 29706de..8a901c9 100644 --- a/lua/ow/plugins/cmp.lua +++ b/lua/ow/plugins/cmp.lua @@ -38,7 +38,7 @@ return { require("luasnip.loaders.from_vscode").lazy_load() end, build = ( - require("ow.utils").os_name ~= "Windows_NT" + require("ow.util").os_name ~= "Windows_NT" and "make install_jsregexp" or nil ), @@ -55,7 +55,6 @@ return { config = function() local cmp = require("cmp") local luasnip = require("luasnip") - local utils = require("ow.utils") local lspkind = require("lspkind") ---@type cmp.ConfigSchema diff --git a/lua/ow/plugins/lspconfig.lua b/lua/ow/plugins/lspconfig.lua new file mode 100644 index 0000000..b3f273f --- /dev/null +++ b/lua/ow/plugins/lspconfig.lua @@ -0,0 +1,5 @@ +---@type LazyPluginSpec +return { + "neovim/nvim-lspconfig", + config = require("ow.lsp").setup, +} diff --git a/lua/ow/plugins/nvim-tree.lua b/lua/ow/plugins/nvim-tree.lua index 0da8f6d..0f98952 100644 --- a/lua/ow/plugins/nvim-tree.lua +++ b/lua/ow/plugins/nvim-tree.lua @@ -1,12 +1,12 @@ -local utils = require("ow.utils") +local util = require("ow.util") local function override_highlights() -- File Icon - local hl = utils.get_hl_source("NvimTreeFileIcon") + local hl = util.get_hl_source("NvimTreeFileIcon") vim.api.nvim_set_hl(0, "NvimTreeFileIcon", { fg = hl.fg, bg = nil }) -- Symlink Icon - hl = utils.get_hl_source("NvimTreeSymlinkIcon") + hl = util.get_hl_source("NvimTreeSymlinkIcon") vim.api.nvim_set_hl(0, "NvimTreeSymlinkIcon", { fg = hl.fg, bg = nil }) end diff --git a/lua/ow/utils.lua b/lua/ow/util.lua similarity index 93% rename from lua/ow/utils.lua rename to lua/ow/util.lua index c07100a..b5ee405 100644 --- a/lua/ow/utils.lua +++ b/lua/ow/util.lua @@ -97,34 +97,6 @@ function M.assert_python3_module_installed(mod) end end ---- Send a debug notification ----@param msg string Message to send ----@param title? string Title of notification -function M.debug(msg, title) - notify(msg, title, vim.log.levels.DEBUG) -end - ---- Send an info notification ----@param msg string Message to send ----@param title? string Title of notification -function M.info(msg, title) - notify(msg, title, vim.log.levels.INFO) -end - ---- Send a warning notification ----@param msg string Message to send ----@param title? string Title of notification -function M.warn(msg, title) - notify(msg, title, vim.log.levels.WARN) -end - ---- Send an error notification ----@param msg string Message to send ----@param title? string Title of notification -function M.err(msg, title) - notify(msg, title, vim.log.levels.ERROR) -end - --- Attempts to load a module and logs errors on failure. ---@param module string The module to attempt to load. ---@return any module The loaded module if successful, otherwise nil.