return { { 'williamboman/mason.nvim', lazy = false, opts = {} }, -- Autocompletion { 'hrsh7th/nvim-cmp', event = 'InsertEnter', config = function() local cmp = require('cmp') cmp.setup({ sources = { { name = 'nvim_lsp' } }, mapping = cmp.mapping.preset.insert({ [''] = cmp.mapping.complete(), [''] = cmp.mapping.scroll_docs(-4), [''] = cmp.mapping.scroll_docs(4), -- Use enter to confirm completion [''] = cmp.mapping.confirm({ select = false }), -- Navigate completions with and [""] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Insert }), [""] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Insert }), [''] = cmp.mapping(function(fallback) if cmp.visible() then cmp.confirm({ select = true }) elseif vim.snippet and vim.snippet.active() then vim.snippet.jump(1) else fallback() end end, { 'i', 's' }), }), snippet = { expand = function(args) vim.snippet.expand(args.body) end } }) end }, -- LSP { 'neovim/nvim-lspconfig', cmd = { 'LspInfo', 'LspInstall', 'LspStart' }, event = { 'BufReadPre', 'BufNewFile' }, dependencies = { { 'hrsh7th/cmp-nvim-lsp' }, { 'williamboman/mason.nvim' }, { 'williamboman/mason-lspconfig.nvim' } }, init = function() -- Reserve a space in the gutter -- This will avoid an annoying layout shift in the screen vim.opt.signcolumn = 'yes' end, config = function() vim.diagnostic.config({ update_in_insert = true }) -- Add cmp_nvim_lsp capabilities settings to lspconfig -- This should be executed before you configure any language server local capabilities = vim.lsp.protocol.make_client_capabilities() capabilities = require('cmp_nvim_lsp').default_capabilities(capabilities) -- Define the autoformat function locally local buffer_autoformat = function(bufnr) local group = 'lsp_autoformat' vim.api.nvim_create_augroup(group, { clear = false }) vim.api.nvim_clear_autocmds({ group = group, buffer = bufnr }) --[[ Disable for now the format on save so it does not fuck up other shit vim.api.nvim_create_autocmd('BufWritePre', { buffer = bufnr, group = group, desc = 'LSP format on save', callback = function() -- note: do not enable async formatting vim.lsp.buf.format({ async = false, timeout_ms = 10000 }) end }) --]] end -- LspAttach is where you enable features that only work -- if there is a language server active in the file vim.api.nvim_create_autocmd('LspAttach', { desc = 'LSP actions', callback = function(event) vim.lsp.inlay_hint.enable(true, { bufnr = event.buf }) vim.lsp.inline_completion.enable(true, { bufnr = event.buf }) -- Autoformat on save local id = vim.tbl_get(event, 'data', 'client_id') local client = id and vim.lsp.get_client_by_id(id) if client == nil then return end if client:supports_method('textDocument/formatting') then buffer_autoformat(event.buf) end -- vim.keymap.set('n', 'K', 'lua vim.lsp.buf.hover()', -- { buffer = event.buf, desc = "Hover over selection" }) vim.keymap.set('n', 'gd', 'lua vim.lsp.buf.definition()', { buffer = event.buf, desc = "Go to definition" }) -- Go to definition in split window: Split window, move to left and open definition vim.keymap.set('n', 'gD', 'vsplit | wincmd L | lua vim.lsp.buf.declaration()', { noremap = true, silent = true, desc = "Go to definition in split window" }) vim.keymap.set('n', 'gi', 'lua vim.lsp.buf.implementation()', { buffer = event.buf, desc = "Go to implementation" }) vim.keymap.set('n', 'go', 'lua vim.lsp.buf.type_definition()', { buffer = event.buf, desc = "Go to type definition" }) vim.keymap.set('n', 'gr', 'lua vim.lsp.buf.references()', { buffer = event.buf, desc = "Show references" }) vim.keymap.set('n', 'gs', 'lua vim.lsp.buf.signature_help()', { buffer = event.buf, desc = "Show signature help" }) vim.keymap.set('n', 'gR', 'lua vim.lsp.buf.rename()', { buffer = event.buf, desc = "Rename" }) vim.keymap.set({ 'n', 'x' }, 'f', 'lua vim.lsp.buf.format({async = true})', { buffer = event.buf, desc = "Format selection" }) vim.keymap.set('n', '', 'lua vim.lsp.buf.code_action()', { buffer = event.buf, desc = "Code action" }) end }) vim.lsp.config('eslint', { capabilities = capabilities, settings = { eslint = { enable = true, packageManager = "yarn", configFiles = ".eslint.config.mjs", validate = "on" } } }) require('mason').setup() require('mason-lspconfig').setup({ ensure_installed = { "eslint", "clangd" }, handlers = { -- this first function is the "default handler" -- it applies to every language server without a "custom handler" function(server_name) vim.lsp.config(server_name, { capabilities = capabilities }) vim.lsp.enable(server_name) end, lua_ls = function() vim.lsp.config('lua_ls', { capabilities = capabilities, settings = { Lua = { diagnostics = { globals = { 'vim' }, }, workspace = { library = vim.api.nvim_get_runtime_file("", true), checkThirdParty = false, }, }, }, }) vim.lsp.enable('lua_ls') end, tinymist = function() vim.lsp.config('tinymist', { capabilities = capabilities, settings = { formatterMode = "typstyle", exportPdf = "onSave", }, }) vim.lsp.enable('tinymist') end, } }) -- sourcekit is NOT managed by mason, set up separately vim.lsp.config('sourcekit', { capabilities = capabilities, cmd = { "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/sourcekit-lsp", }, root_dir = function(bufnr, on_dir) local fname = vim.api.nvim_buf_get_name(bufnr) local util = require("lspconfig.util") on_dir( util.root_pattern("buildServer.json")(fname) or util.root_pattern("*.xcodeproj", "*.xcworkspace")(fname) or util.root_pattern("Package.swift")(fname) or vim.fs.root(fname, ".git") ) end, }) vim.lsp.enable('sourcekit') -- kotlin-lsp (JetBrains) — not auto-handled by mason-lspconfig -- The pre-alpha EAP build has an expiry baked in, so we launch it under -- `faketime` pinned to a date within its valid window. -- Requires: `brew install libfaketime` (provides the `faketime` binary). vim.lsp.config('kotlin_lsp', { capabilities = capabilities, cmd = { 'faketime', '2026-06-04', 'kotlin-lsp', '--stdio' }, }) vim.lsp.enable('kotlin_lsp') -- Ruby LSP — uses the GLOBAL `ruby-lsp` gem, NOT a Gemfile entry. When -- launched in a project it builds its own "composed bundle" under -- .ruby-lsp/ that layers ruby-lsp on top of the app's gems, so your -- Gemfile stays untouched (this is what the ruby-lsp maintainers -- recommend). We inherit lspconfig's default cmd, which runs `ruby-lsp` -- (resolved from rbenv's PATH) with cwd = root_dir. -- Install once per Ruby you use, e.g.: gem install ruby-lsp (Ruby >= 3) vim.lsp.config('ruby_lsp', { capabilities = capabilities, }) vim.lsp.enable('ruby_lsp') -- Standalone RuboCop LSP, run from the project's bundle (rubocop is -- normally already in the Gemfile, so no change needed there). -- NOTE: ruby-lsp's composed bundle also auto-activates a RuboCop addon -- when rubocop is present, so running this too can produce duplicate -- diagnostics. Comment out the two lines below if you'd rather get -- RuboCop only through ruby-lsp. vim.lsp.config('rubocop', { capabilities = capabilities, cmd = function(dispatchers, config) return vim.lsp.rpc.start( { 'bundle', 'exec', 'rubocop', '--lsp' }, dispatchers, config and config.root_dir and { cwd = config.cmd_cwd or config.root_dir } ) end, }) vim.lsp.enable('rubocop') end } }