diff --git a/home-manager/modules/nvim.nix b/home-manager/modules/nvim.nix index fd42d81e..bf70bbae 100644 --- a/home-manager/modules/nvim.nix +++ b/home-manager/modules/nvim.nix @@ -76,7 +76,6 @@ prettierd # LSP servers - efm-langserver nil rust-analyzer taplo diff --git a/nvim/lua/my/configure/lspconfig.lua b/nvim/lua/my/configure/lspconfig.lua index da9b8283..cc886035 100644 --- a/nvim/lua/my/configure/lspconfig.lua +++ b/nvim/lua/my/configure/lspconfig.lua @@ -1,14 +1,35 @@ local nvim_navbuddy_telescope +local formatters_by_ft = require('my.lsp.filetypes').formatters_by_ft return { 'neovim/nvim-lspconfig', dependencies = { 'hrsh7th/cmp-nvim-lsp', - { 'folke/neodev.nvim', event = 'BufReadPre' }, + { 'folke/neodev.nvim' }, + { 'folke/neoconf.nvim' }, { - 'creativenull/efmls-configs-nvim', - dev = true, - -- neoconf must be loaded before any LSP - dependencies = { 'folke/neoconf.nvim' }, + 'stevearc/conform.nvim', + opts = { + formatters_by_ft = formatters_by_ft, + format_after_save = function() + if not require('my.utils.lsp').is_formatting_enabled() then + return + end + return { lsp_fallback = true } + end, + }, + }, + { + 'mfussenegger/nvim-lint', + init = function() + vim.api.nvim_create_autocmd('BufWritePost', { + callback = function() + require('lint').try_lint() + end, + }) + end, + config = function() + require('lint').linters_by_ft = require('my.lsp.filetypes').linters_by_ft + end, }, { 'folke/neoconf.nvim', @@ -109,19 +130,11 @@ return { require('my.utils.lsp').on_attach(require('my.utils.lsp').on_attach_default) end, config = function() - local efm_setup_done = false - local function setup_lsp_for_filetype(filetype, server_name) local has_config, config = pcall(require, 'my.lsp.' .. filetype) config = has_config and config or {} config.capabilities = require('cmp_nvim_lsp').default_capabilities(vim.lsp.protocol.make_client_capabilities()) - -- setup efmls if not done already - if not efm_setup_done and require('my.lsp.filetypes').uses_efm(filetype) then - efm_setup_done = true - require('lspconfig').efm.setup(require('my.lsp.filetypes').efmls_config(config.capabilities)) - end - require('lspconfig')[server_name].setup(config) local snippets = require('my.lsp.snippets')[filetype] if snippets then @@ -137,7 +150,7 @@ return { end end - return require('my.lsp.filetypes').uses_efm(vim.bo.ft) + return false end local function setup_server(filetype, file_patterns, server_name) diff --git a/nvim/lua/my/configure/treesitter.lua b/nvim/lua/my/configure/treesitter.lua index 88f9d621..93a95156 100644 --- a/nvim/lua/my/configure/treesitter.lua +++ b/nvim/lua/my/configure/treesitter.lua @@ -80,7 +80,7 @@ return { -- highlight mdx with markdown -- it's close enough. We also do JSX injection via ./after/queries/markdown/injections.scm vim.treesitter.language.register('markdown', 'mdx') require('nvim-treesitter.configs').setup({ ---@diagnostic disable-line:missing-fields - ensure_installed = require('my.lsp.filetypes').treesitter_parsers(), + ensure_installed = require('my.lsp.filetypes').treesitter_parsers, highlight = { enable = true, }, diff --git a/nvim/lua/my/legendary/autocmds.lua b/nvim/lua/my/legendary/autocmds.lua index 42906290..ff1343cb 100644 --- a/nvim/lua/my/legendary/autocmds.lua +++ b/nvim/lua/my/legendary/autocmds.lua @@ -47,7 +47,7 @@ function M.default_autocmds() } end -function M.lsp_autocmds(bufnr, server_name) +function M.lsp_autocmds(bufnr) local _, autocmds = pcall(vim.api.nvim_get_autocmds, { group = 'LspOnAttachAutocmds' }) autocmds = (type(autocmds) == 'table' and autocmds) or {} local augroup = { @@ -71,18 +71,6 @@ function M.lsp_autocmds(bufnr, server_name) }) end - if - not vim.iter(autocmds):find(function(autocmd) - return autocmd.buflocal == true and autocmd.buffer == bufnr and autocmd.event == 'BufWritePost' - end) - then - table.insert(augroup, { - 'BufWritePost', - require('my.utils.lsp').format_document, - opts = { buffer = bufnr }, - }) - end - if #augroup == 0 then return {} end diff --git a/nvim/lua/my/legendary/commands.lua b/nvim/lua/my/legendary/commands.lua index 596ae28c..1d73fcc3 100644 --- a/nvim/lua/my/legendary/commands.lua +++ b/nvim/lua/my/legendary/commands.lua @@ -108,7 +108,9 @@ function M.lsp_commands(bufnr) vim.list_extend(commands, { { ':Format', - require('my.utils.lsp').format_document, + function() + require('conform').format({ async = true, lsp_fallback = true }) + end, description = 'Format the current document with LSP', opts = { buffer = bufnr }, }, diff --git a/nvim/lua/my/lsp/filetypes.lua b/nvim/lua/my/lsp/filetypes.lua index b39296e6..ae30a439 100644 --- a/nvim/lua/my/lsp/filetypes.lua +++ b/nvim/lua/my/lsp/filetypes.lua @@ -39,7 +39,7 @@ M.config = { lspconfig = 'marksman', formatter = { 'prettier_d', - 'cbfmt', + 'injected', -- see :h conform-formatters, formats injected code blocks }, treesitter = { 'markdown', 'markdown_inline' }, }, @@ -89,77 +89,20 @@ M.config['javascript'] = M.config['typescript'] M.config['typescriptreact'] = M.config['typescript'] M.config['javascriptreact'] = M.config['typescript'] -local efm_customizations = { - ['cbfmt'] = function() - local cbfmt = require('efmls-configs.formatters.cbfmt') - cbfmt.formatCommand = - string.format('%s --config %s', cbfmt.formatCommand, string.format('%s/.config/cbfmt.toml', vim.env.HOME)) - return cbfmt - end, -} - -local function load_efm_modules(mods, mod_type) - if not mods then - return nil - end - - -- normalize type to string[] - mods = type(mods) == 'string' and { mods } or mods - return vim - .iter(mods) - :map(function(mod) - if efm_customizations[mod] then - return efm_customizations[mod]() - end - - local ok, module = pcall(require, string.format('efmls-configs.%s.%s', mod_type, mod)) - if not ok then - vim.notify(string.format('Module efmls-configs.%s.%s not found', mod_type, mod)) - return nil - end - return module - end) - :totable() -end - -local function load_linters(linters) - return load_efm_modules(linters, 'linters') or {} -end - -local function load_formatters(formatters) - return load_efm_modules(formatters, 'formatters') or {} -end - -function M.efmls_config(capabilities) - local languages = {} - for filetype, config in pairs(M.config) do - if config.linter or config.formatter then - languages[filetype] = vim.list_extend(load_formatters(config.formatter), load_linters(config.linter)) +local function cfg_by_ft(cfg) + local result = {} + for ft, ft_cfg in pairs(M.config) do + if ft_cfg[cfg] then + result[ft] = type(ft_cfg[cfg]) == 'table' and ft_cfg[cfg] or { ft_cfg[cfg] } end end - - return { - filetypes = vim.tbl_keys(languages), - settings = { languages = languages }, - init_options = { - documentFormatting = true, - documentRangeFormatting = true, - }, - capabilities = capabilities, - } -end - -function M.uses_efm(ft) - ft = ft or vim.bo.ft - return vim.tbl_get(M.config, ft, 'formatter') ~= nil or vim.tbl_get(M.config, ft, 'linter') ~= nil + return result end -function M.formats_with_efm(ft) - ft = ft or vim.bo.ft - return vim.tbl_get(M.config, ft, 'formatter') ~= nil -end +M.formatters_by_ft = cfg_by_ft('formatter') +M.linters_by_ft = cfg_by_ft('linter') -function M.treesitter_parsers() +M.treesitter_parsers = (function() local result = {} for ft, config in pairs(M.config) do if config.treesitter ~= false then @@ -173,6 +116,6 @@ function M.treesitter_parsers() table.insert(result, 'regex') return result -end +end)() return M diff --git a/nvim/lua/my/utils/lsp.lua b/nvim/lua/my/utils/lsp.lua index 3380e1b8..c10701c5 100644 --- a/nvim/lua/my/utils/lsp.lua +++ b/nvim/lua/my/utils/lsp.lua @@ -2,8 +2,6 @@ local Methods = vim.lsp.protocol.Methods local M = {} -local init_done = false - local formatting_enabled = true ---Set up a callback to run on LSP attach @@ -21,11 +19,6 @@ function M.on_attach(callback) end function M.on_attach_default(client, bufnr) - if not init_done then - init_done = true - M.setup_async_formatting() - end - -- if current nvim version supports inlay hints, enable them if vim.lsp['inlay_hint'] ~= nil and client.supports_method(Methods.textDocument_inlayHint) then vim.lsp.inlay_hint.enable(true) @@ -40,43 +33,6 @@ function M.on_attach_default(client, bufnr) end end -function M.setup_async_formatting() - -- format on save asynchronously, see M.format_document - vim.lsp.handlers[Methods.textDocument_formatting] = function(err, result, ctx) - if err ~= nil then - -- efm uses table messages - if type(err) == 'table' then - if err.message then - err = err.message - else - err = vim.inspect(err) - end - end - vim.api.nvim_err_write(err --[[@as string]]) - return - end - - if result == nil then - return - end - - if - vim.api.nvim_buf_get_var(ctx.bufnr, 'format_changedtick') == vim.api.nvim_buf_get_var(ctx.bufnr, 'changedtick') - then - local view = vim.fn.winsaveview() - vim.lsp.util.apply_text_edits(result, ctx.bufnr, 'utf-16') - if view then - vim.fn.winrestview(view) - end - if ctx.bufnr == vim.api.nvim_get_current_buf() then - vim.b.format_saving = true - vim.cmd.update() - vim.b.format_saving = false - end - end - end -end - function M.apply_ui_tweaks() -- customize LSP icons local icons = { @@ -132,19 +88,19 @@ function M.toggle_formatting_enabled(enable) end end +function M.is_formatting_enabled() + return formatting_enabled +end + ---@param buf number|nil defaults to 0 (current buffer) ---@return string|nil function M.get_formatter_name(buf) buf = buf or tonumber(vim.g.actual_curbuf or vim.api.nvim_get_current_buf()) - -- if it uses efm-langserver, grab the formatter name - local ft_efm_cfg = require('my.lsp.filetypes').config[vim.bo[buf].filetype] - if ft_efm_cfg and ft_efm_cfg.formatter then - if type(ft_efm_cfg.formatter) == 'table' then - return ft_efm_cfg.formatter[1] - else - return tostring(ft_efm_cfg.formatter) - end + -- if it uses conform.nvim, grab the formatter name + local formatter = require('my.lsp.filetypes').formatters_by_ft[vim.bo[buf].ft] + if formatter then + return type(formatter) == 'table' and table.concat(formatter, ', ') or formatter end -- otherwise just return the LSP server name @@ -168,25 +124,4 @@ function M.is_formatting_supported(buf) return #clients > 0 end -function M.format_document() - if not M.is_formatting_supported() then - return - end - - if not vim.b.format_saving then - vim.b.format_changedtick = vim.b.changedtick - local formats_with_efm = require('my.lsp.filetypes').formats_with_efm() - vim.lsp.buf.format({ - async = true, - filter = function(client) - if formats_with_efm then - return client.name == 'efm' - else - return client.name ~= 'efm' - end - end, - }) - end -end - return M