require("mason").setup() -- Add mason bin to PATH so vim.lsp.enable can find installed servers vim.env.PATH = vim.fn.stdpath("data") .. "/mason/bin:" .. vim.env.PATH require("mason-lspconfig").setup({ ensure_installed = { "gopls", "bashls", "ansiblels", "yamlls", }, }) vim.lsp.config("gopls", { cmd = { "gopls" }, filetypes = { "go", "gomod", "gowork", "gotmpl" }, root_markers = { "go.mod", "go.work", ".git" }, settings = { gopls = { analyses = { unusedparams = true, shadow = true, nilness = true, unusedwrite = true, useany = true, }, staticcheck = true, gofumpt = true, }, }, }) vim.lsp.config("bashls", { cmd = { "bash-language-server", "start" }, filetypes = { "sh", "bash" }, }) vim.lsp.config("ansiblels", { cmd = { "ansible-language-server", "--stdio" }, filetypes = { "yaml.ansible" }, root_markers = { "ansible.cfg", ".ansible-lint", "playbooks" }, }) vim.lsp.config("yamlls", { cmd = { "yaml-language-server", "--stdio" }, filetypes = { "yaml", "yaml.docker-compose" }, }) vim.lsp.enable({ "gopls", "bashls", "ansiblels", "yamlls" }) -- Diagnostics UI vim.diagnostic.config({ virtual_text = true, signs = true, underline = true, update_in_insert = false, float = { border = "rounded" }, }) -- LSP keymaps (set when an LSP attaches) vim.api.nvim_create_autocmd("LspAttach", { group = vim.api.nvim_create_augroup("lsp_keymaps", { clear = true }), callback = function(ev) local opts = { buffer = ev.buf } -- Jump to definition, reusing an existing split if the file is already open local function goto_definition_reuse() local params = vim.lsp.util.make_position_params(0, "utf-8") vim.lsp.buf_request(0, "textDocument/definition", params, function(err, result) if err or not result or vim.tbl_isempty(result) then return end local target = result[1] or result local uri = target.uri or target.targetUri local range = target.range or target.targetSelectionRange local bufnr = vim.uri_to_bufnr(uri) -- Check if file is already visible in a window for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do if vim.api.nvim_win_get_buf(win) == bufnr then vim.api.nvim_set_current_win(win) local pos = range.start vim.api.nvim_win_set_cursor(win, { pos.line + 1, pos.character }) return end end -- Not open anywhere, use default behavior vim.lsp.util.show_document(target, "utf-8", { focus = true }) end) end -- Navigation vim.keymap.set("n", "gd", goto_definition_reuse, opts) vim.keymap.set("n", "gD", vim.lsp.buf.declaration, opts) vim.keymap.set("n", "gi", vim.lsp.buf.implementation, opts) vim.keymap.set("n", "gr", vim.lsp.buf.references, opts) vim.keymap.set("n", "gt", vim.lsp.buf.type_definition, opts) -- Info vim.keymap.set("n", "K", vim.lsp.buf.hover, opts) vim.keymap.set("n", "", vim.lsp.buf.signature_help, opts) vim.keymap.set("i", "", vim.lsp.buf.signature_help, opts) -- Actions vim.keymap.set("n", "ca", vim.lsp.buf.code_action, opts) vim.keymap.set("n", "rn", vim.lsp.buf.rename, opts) vim.keymap.set("n", "f", function() vim.lsp.buf.format({ async = true }) end, opts) -- Diagnostics vim.keymap.set("n", "[d", vim.diagnostic.goto_prev, opts) vim.keymap.set("n", "]d", vim.diagnostic.goto_next, opts) vim.keymap.set("n", "q", vim.diagnostic.setloclist, opts) vim.keymap.set("n", "gl", vim.diagnostic.open_float, opts) end, }) -- Auto-format and organize imports on save for Go vim.api.nvim_create_autocmd("BufWritePre", { group = vim.api.nvim_create_augroup("go_organize_imports", { clear = true }), pattern = "*.go", callback = function() -- Organize imports via code action local params = vim.lsp.util.make_range_params(0, "utf-8") params.context = { only = { "source.organizeImports" } } local result = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params, 1000) for _, res in pairs(result or {}) do for _, action in pairs(res.result or {}) do if action.edit then vim.lsp.util.apply_workspace_edit(action.edit, "utf-8") elseif action.command then vim.lsp.buf.execute_command(action.command) end end end -- Format vim.lsp.buf.format({ async = false }) end, })