1--- ### LSP utils.
2--
3-- DESCRIPTION:
4-- Functions we use to configure the plugin `mason-lspconfig.nvim`.
5-- You can specify your own lsp settings inside `M.apply_user_lsp_settings()`.
6--
7-- Most options we use in `M.apply_default_lsp_settings()`
8-- can be tweaked on the file `../1-options.lua`.
9-- Take this into consideration to minimize the risk of breaking stuff.
10
11-- Functions:
12-- -> M.apply_default_lsp_settings → Apply our default lsp settings.
13-- -> M.apply_user_lsp_mappings → Apply the user lsp keymappings.
14-- -> M.apply_user_lsp_settings → Apply the user lsp settings.
15-- -> M.setup → It passes the user lsp settings to lspconfig.
16
17local M = {}
18local utils = require "base.utils"
19local stored_handlers = {}
20
21--- Apply default settings for diagnostics, formatting, and lsp capabilities.
22--- It only need to be executed once, normally on mason-lspconfig.
23---@return nil
24M.apply_default_lsp_settings = function()
25 -- Icons
26 -- Apply the icons defined in ../icons/nerd_font.lua
27 local get_icon = utils.get_icon
28 local signs = {
29 { name = "DiagnosticSignError", text = get_icon("DiagnosticError"), texthl = "DiagnosticSignError" },
30 { name = "DiagnosticSignWarn", text = get_icon("DiagnosticWarn"), texthl = "DiagnosticSignWarn" },
31 { name = "DiagnosticSignHint", text = get_icon("DiagnosticHint"), texthl = "DiagnosticSignHint" },
32 { name = "DiagnosticSignInfo", text = get_icon("DiagnosticInfo"), texthl = "DiagnosticSignInfo" },
33 { name = "DapStopped", text = get_icon("DapStopped"), texthl = "DiagnosticWarn" },
34 { name = "DapBreakpoint", text = get_icon("DapBreakpoint"), texthl = "DiagnosticInfo" },
35 { name = "DapBreakpointRejected", text = get_icon("DapBreakpointRejected"), texthl = "DiagnosticError" },
36 { name = "DapBreakpointCondition", text = get_icon("DapBreakpointCondition"), texthl = "DiagnosticInfo" },
37 { name = "DapLogPoint", text = get_icon("DapLogPoint"), texthl = "DiagnosticInfo" }
38 }
39 for _, sign in ipairs(signs) do
40 vim.fn.sign_define(sign.name, sign)
41 end
42
43 -- Borders
44 -- Apply the option lsp_round_borders_enabled from ../1-options.lua
45 if vim.g.lsp_round_borders_enabled then
46 vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, { border = "rounded", silent = true })
47 vim.lsp.handlers["textDocument/signatureHelp"] =
48 vim.lsp.with(vim.lsp.handlers.signature_help, { border = "rounded", silent = true })
49 end
50
51 -- Set default diagnostics
52 local default_diagnostics = {
53 virtual_text = true,
54 signs = {
55 text = {
56 [vim.diagnostic.severity.ERROR] = utils.get_icon("DiagnosticError"),
57 [vim.diagnostic.severity.HINT] = utils.get_icon("DiagnosticHint"),
58 [vim.diagnostic.severity.WARN] = utils.get_icon("DiagnosticWarn"),
59 [vim.diagnostic.severity.INFO] = utils.get_icon("DiagnosticInfo"),
60 },
61 active = signs,
62 },
63 update_in_insert = true,
64 underline = true,
65 severity_sort = true,
66 float = {
67 focused = false,
68 style = "minimal",
69 border = "rounded",
70 source = "always",
71 header = "",
72 prefix = "",
73 },
74 }
75
76 -- Apply default diagnostics
77 -- Applies the option diagnostics_mode from ../1-options.lua
78 M.diagnostics = {
79 -- diagnostics off
80 [0] = vim.tbl_deep_extend(
81 "force",
82 default_diagnostics,
83 { underline = false, virtual_text = false, signs = false, update_in_insert = false }
84 ),
85 -- status only
86 vim.tbl_deep_extend("force", default_diagnostics, { virtual_text = false, signs = false }),
87 -- virtual text off, signs on
88 vim.tbl_deep_extend("force", default_diagnostics, { virtual_text = false }),
89 -- all diagnostics on
90 default_diagnostics,
91 }
92 vim.diagnostic.config(M.diagnostics[vim.g.diagnostics_mode])
93
94 -- Apply formatting settings
95 M.formatting = { format_on_save = { enabled = true }, disabled = {} }
96 if type(M.formatting.format_on_save) == "boolean" then
97 M.formatting.format_on_save = { enabled = M.formatting.format_on_save }
98 end
99 M.format_opts = vim.deepcopy(M.formatting)
100 M.format_opts.disabled = nil
101 M.format_opts.format_on_save = nil
102 M.format_opts.filter = function(client)
103 local filter = M.formatting.filter
104 local disabled = M.formatting.disabled or {}
105 -- check if client is fully disabled or filtered by function
106 return not (vim.tbl_contains(disabled, client.name) or (type(filter) == "function" and not filter(client)))
107 end
108end
109
110--- This function has the sole purpose of passing the lsp keymappings to lsp.
111--- We have this function, because we use it on none-ls.
112---@param client string The client where the lsp mappings will load.
113---@param bufnr string The bufnr where the lsp mappings will load.
114function M.apply_user_lsp_mappings(client, bufnr)
115 local lsp_mappings = require("base.4-mappings").lsp_mappings(client, bufnr)
116 if not vim.tbl_isempty(lsp_mappings.v) then
117 lsp_mappings.v["<leader>l"] = { desc = utils.get_icon("ActiveLSP", 1, true) .. "LSP" }
118 end
119 utils.set_mappings(lsp_mappings, { buffer = bufnr })
120end
121
122--- Here you can specify custom settings for the lsp servers you install.
123--- This is not normally necessary. But you can.
124---@param server_name string The name of the server
125---@return table # The table of LSP options used when setting up the given language server
126function M.apply_user_lsp_settings(server_name)
127 local server = require("lspconfig")[server_name]
128
129 -- Define user server capabilities.
130 M.capabilities = vim.lsp.protocol.make_client_capabilities()
131 M.capabilities.textDocument.completion.completionItem.documentationFormat = { "markdown", "plaintext" }
132 M.capabilities.textDocument.completion.completionItem.snippetSupport = true
133 M.capabilities.textDocument.completion.completionItem.preselectSupport = true
134 M.capabilities.textDocument.completion.completionItem.insertReplaceSupport = true
135 M.capabilities.textDocument.completion.completionItem.labelDetailsSupport = true
136 M.capabilities.textDocument.completion.completionItem.deprecatedSupport = true
137 M.capabilities.textDocument.completion.completionItem.commitCharactersSupport = true
138 M.capabilities.textDocument.completion.completionItem.tagSupport = { valueSet = { 1 } }
139 M.capabilities.textDocument.completion.completionItem.resolveSupport =
140 { properties = { "documentation", "detail", "additionalTextEdits" } }
141 M.capabilities.textDocument.foldingRange = { dynamicRegistration = false, lineFoldingOnly = true }
142 M.flags = {}
143 local opts = vim.tbl_deep_extend("force", server, { capabilities = M.capabilities, flags = M.flags })
144
145 -- Define user server rules.
146 if server_name == "jsonls" then -- Add schemastore schemas
147 local is_schemastore_loaded, schemastore = pcall(require, "schemastore")
148 if is_schemastore_loaded then
149 opts.settings = { json = { schemas = schemastore.json.schemas(), validate = { enable = true } } }
150 end
151 end
152 if server_name == "yamlls" then -- Add schemastore schemas
153 local is_schemastore_loaded, schemastore = pcall(require, "schemastore")
154 if is_schemastore_loaded then opts.settings = { yaml = { schemas = schemastore.yaml.schemas() } } end
155 end
156 if server_name == "lua_ls" then -- Disable third party checking
157 pcall(require, "neodev")
158 opts.settings = { Lua = { workspace = { checkThirdParty = false } } }
159 end
160
161 -- Apply them
162 local old_on_attach = server.on_attach
163 opts.on_attach = function(client, bufnr)
164 -- If the server on_attach function exist → server.on_attach(client, bufnr)
165 if type(old_on_attach) == "function" then old_on_attach(client, bufnr) end
166 -- Also, apply mappings to the buffer.
167 M.apply_user_lsp_mappings(client, bufnr)
168 end
169 return opts
170end
171
172--- This function passes the `user lsp settings` to lspconfig,
173--- which is the responsible of configuring everything for us.
174---
175--- You are meant to call this function from the plugin `mason-lspconfig.nvim`.
176---@param server string A lsp server name.
177---@return nil
178M.setup = function(server)
179 -- Get the user settings.
180 local opts = M.apply_user_lsp_settings(server)
181
182 -- Get a handler from lspconfig.
183 local setup_handler = stored_handlers[server] or require("lspconfig")[server].setup(opts)
184
185 -- Apply our user settings to the lspconfig handler.
186 if setup_handler then setup_handler(server, opts) end
187end
188
189return M