refactor(LSP): use the new vim.lsp.* functions to set up LSP servers
This commit is contained in:
+398
-119
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user