refactor(LSP): use the new vim.lsp.* functions to set up LSP servers

This commit is contained in:
2025-06-05 11:30:14 +02:00
parent c569c12ae7
commit e8ff3fed7c
29 changed files with 556 additions and 1744 deletions
+6 -15
View File
@@ -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)
+17 -15
View File
@@ -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" }
+8 -8
View File
@@ -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",
+44
View File
@@ -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
+398 -119
View File
@@ -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<string, Server>
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 = "<leader>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 = "<leader>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 = "<leader>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 = "<leader>lf",
rhs = function()
util.format({
cmd = { "stylua", "-" },
output = "stdout",
})
end,
},
{
mode = "x",
lhs = "<leader>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 = "<leader>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
-36
View File
@@ -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 = "<leader>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,
},
}
-28
View File
@@ -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,
},
}
-20
View File
@@ -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",
},
},
}
-36
View File
@@ -1,36 +0,0 @@
local utils = require("ow.utils")
---@type ServerConfig
return {
enable = true,
mason = { "gopls", dependencies = { "golines" } },
keymaps = {
{
mode = "n",
lhs = "<leader>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,
},
},
},
}
-30
View File
@@ -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",
},
},
}
-18
View File
@@ -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",
},
},
},
}
-76
View File
@@ -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 = "<leader>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",
},
},
},
},
}
@@ -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,
},
},
},
}
-40
View File
@@ -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,
}
},
},
},
},
}
-87
View File
@@ -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 = "<leader>lf",
rhs = function()
utils.format({
cmd = { "stylua", "-" },
output = "stdout",
})
end,
},
{
mode = "x",
lhs = "<leader>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 },
},
},
},
}
-21
View File
@@ -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,
},
},
},
}
-20
View File
@@ -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"
),
},
}
-27
View File
@@ -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,
},
},
},
}
-27
View File
@@ -1,27 +0,0 @@
local utils = require("ow.utils")
---@type ServerConfig
return {
mason = "ruff",
keymaps = {
{
mode = "n",
lhs = "<leader>lf",
rhs = function()
vim.lsp.buf.format()
utils.format({
cmd = {
"ruff",
"check",
"--stdin-filename=%file%",
"--select=I",
"--fix",
"--quiet",
"-",
},
output = "stdout",
})
end,
},
},
}
-191
View File
@@ -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,
},
}, ]]
},
},
},
}
-31
View File
@@ -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,
},
},
},
}
+19 -55
View File
@@ -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<number, vim.api.keyset.keymap[]>
M.old = {}
---@type table<number, Keymap[]>
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 = "<leader>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 = "<leader>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 = "<leader>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
+55 -58
View File
@@ -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
-341
View File
@@ -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
-392
View File
@@ -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<string>
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
+1 -2
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
---@type LazyPluginSpec
return {
"neovim/nvim-lspconfig",
config = require("ow.lsp").setup,
}
+3 -3
View File
@@ -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
-28
View File
@@ -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.