-- my_lsp.lua local ok_cmp, cmp = pcall(require, "cmp") local ok_kind, lspkind = pcall(require, "lspkind") local ok_cmp_caps, cmp_caps = pcall(require, "cmp_nvim_lsp") local has_lspconfig, lspconfig = pcall(require, "lspconfig") -- ── completion (cmp) ─────────────────────────────────────────────────────────── vim.opt.completeopt = { "menu", "menuone", "noselect" } vim.opt.shortmess:append("c") if ok_kind then lspkind.init({ symbol_map = { Text = "󰉿", Method = "󰊕", Function = "λ", Constructor = "", Field = "󰜢", Variable = "󰀫", Class = "󰠱", Interface = "", Module = "󰆧", Property = "󰜢", Unit = "󰑭", Value = "󰎠", Enum = "", Keyword = "󰌋", Snippet = "", Color = "󰏘", File = "󰈙", Reference = "󰈇", Folder = "󰉋", EnumMember = "", Constant = "󰏿", Struct = "󰙅", Event = "", Operator = "󰆕", TypeParameter = "τ", }, }) end local function has_words_before() local unpack = unpack or table.unpack local line, col = unpack(vim.api.nvim_win_get_cursor(0)) if col == 0 then return false end local l = vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1] or "" return l:sub(col, col):match("%s") == nil end local function feedkey(key, mode) vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(key, true, true, true), mode, true) end if ok_cmp then cmp.setup({ mapping = { [""] = cmp.mapping.scroll_docs(-4, { "i", "s" }), [""] = cmp.mapping.scroll_docs(4, { "i", "s" }), [""] = cmp.mapping.close(), [""] = cmp.mapping(cmp.mapping.complete(), { "i", "c" }), [""] = cmp.mapping(function() cmp.select_next_item() pcall(vim.fn["vsnip#deactivate"]) end, { "i", "s" }), [""] = cmp.mapping(function() cmp.select_prev_item() pcall(vim.fn["vsnip#deactivate"]) end, { "i", "s" }), [""] = cmp.mapping(function(fallback) if vim.fn == 1 then feedkey("(vsnip-expand-or-jump)", "") elseif cmp.visible() then cmp.mapping.confirm({ select = true })() elseif has_words_before() then cmp.complete() else fallback() end end, { "i", "s" }), [""] = cmp.mapping(function() if cmp.visible() then cmp.select_prev_item() elseif vim.fn["vsnip#jumpable"](-1) == 1 then feedkey("(vsnip-jump-prev)", "") end end, { "i", "s" }), }, formatting = ok_kind and { format = lspkind.cmp_format({ mode = "symbol_text", maxwidth = 50, ellipsis_char = "...", show_labelDetails = true, before = function(_, item) local function split_args(s) if #s == 0 or not s:find("%(") then return s, "" end local i = s:find("%(") - 1 return s:sub(1, i), s:sub(i + 1) end local verbosity = (vim.bo.filetype == "java") and 2 or 1 if vim.bo.filetype == "java" and item.kind == "Method" then local name, args = split_args(item.abbr) item.abbr = name item.menu = (args or "") .. (item.menu or "") end item.abbr = item.abbr:sub(1, 20 * verbosity) item.menu = (item.menu or ""):sub(1, 20 * verbosity) return item end, }), } or nil, sources = { { name = "nvim_lsp" }, { name = "path" }, { name = "vim_vsnip" }, { name = "buffer", keyword_length = 5 }, }, sorting = { comparators = {} }, -- let LSP decide snippet = { expand = function(args) vim.fn["vsnip#anonymous"](args.body) end, }, experimental = { native_menu = false, ghost_text = true }, }) -- zsh source on zsh buffers vim.api.nvim_create_augroup("CmpZsh", { clear = true }) vim.api.nvim_create_autocmd("FileType", { group = "CmpZsh", pattern = "zsh", callback = function() require("cmp").setup.buffer { sources = { { name = "zsh" } } } end, }) end -- ── LSP shared on_attach & capabilities ─────────────────────────────────────── local M = {} M.on_attach = function(client, bufnr) -- omnifunc, tagfunc, formatexpr vim.bo[bufnr].omnifunc = "v:lua.vim.lsp.omnifunc" if vim.lsp.formatexpr then vim.bo[bufnr].formatexpr = "v:lua.vim.lsp.formatexpr" end if vim.lsp.tagfunc then vim.bo[bufnr].tagfunc = "v:lua.vim.lsp.tagfunc" end local map = function(mode, lhs, rhs) vim.keymap.set(mode, lhs, rhs, { buffer = bufnr, silent = true, noremap = true }) end map("n", "rn", vim.lsp.buf.rename) map("n", "ca", vim.lsp.buf.code_action) map("n", "", vim.lsp.buf.hover) map("n", "g0", vim.lsp.buf.document_symbol) map("n", "gW", vim.lsp.buf.workspace_symbol) map("n", "gd", vim.lsp.buf.definition) map("n", "gD", vim.lsp.buf.declaration) map("n", "gi", vim.lsp.buf.implementation) map("n", "gr", vim.lsp.buf.references) map("n", "", vim.lsp.buf.signature_help) map("n", "g", vim.lsp.buf.type_definition) map("n", "[d", vim.diagnostic.goto_prev) map("n", "]d", vim.diagnostic.goto_next) -- document highlight (only if server supports it) if client.server_capabilities.documentHighlightProvider then local gid = vim.api.nvim_create_augroup("LspDocumentHighlight" .. bufnr, { clear = true }) vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, { group = gid, buffer = bufnr, callback = vim.lsp.buf.document_highlight, }) vim.api.nvim_create_autocmd("CursorMoved", { group = gid, buffer = bufnr, callback = vim.lsp.buf.clear_references, }) end vim.opt_local.signcolumn = "yes" end local capabilities = vim.lsp.protocol.make_client_capabilities() if ok_cmp_caps then capabilities = cmp_caps.default_capabilities(capabilities) end -- nice diagnostics styling vim.cmd [[hi DiagnosticUnderlineError gui=undercurl guisp=salmon]] vim.cmd [[hi DiagnosticUnderlineWarn gui=undercurl guisp=gold]] for type, icon in pairs({ Error = " ", Warn = " ", Hint = " ", Info = " " }) do local hl = "DiagnosticSign" .. type vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" }) end -- ── Servers (one table to rule them all) ────────────────────────────────────── local servers = { bashls = { filetypes = { "bash", "sh" } }, clangd = { filetypes = { "c", "cpp", "objc", "objcpp", "cuda" } }, jqls = { filetypes = { "jq" } }, lua_ls = { filetypes = { "lua" } }, ocamllsp = {}, perlpls = {}, rust_analyzer = { settings = { semantic_tokens = { modifiers = { disabled = { "dead" }, }, }, } }, verible = {}, vimls = {}, zls = {}, texlab = {}, hls = { settings = { haskell = { plugin = { semanticTokens = { globalOn = true } } } } }, pylsp = { settings = { pylsp = { plugins = { pycodestyle = { enabled = false } } } } }, } -- Fallback command guesses for native start (used only if lspconfig is absent) local default_cmd = { bashls = { "bash-language-server", "start" }, clangd = { "clangd" }, jqls = { "jq-lsp" }, lua_ls = { "lua-language-server" }, ocamllsp = { "ocamllsp" }, perlpls = { "perlpls" }, rust_analyzer = { "rust-analyzer" }, verible = { "verible-verilog-ls" }, vimls = { "vim-language-server", "--stdio" }, zls = { "zls" }, texlab = { "texlab" }, hls = { "haskell-language-server-wrapper", "--lsp" }, pylsp = { "pylsp" }, } -- ── Setup: prefer native vim.lsp.config, fallback to lspconfig ─────────────── local has_native = (vim.lsp and vim.lsp.config and vim.lsp.start) local function setup_with_native(name, opts) -- Build a vim.lsp.config() object and start per-filetype local cfg = vim.lsp.config(name, { name = name, cmd = opts.cmd or default_cmd[name], root_dir = opts.root_dir, -- optional; can rely on server defaults filetypes = opts.filetypes, settings = opts.settings, capabilities = capabilities, on_attach = M.on_attach, }) vim.lsp.enable(name); -- if cfg.cmd == nil then -- vim.notify(("LSP %s: no cmd found; skipping native start."):format(name), vim.log.levels.WARN) -- return -- end -- Start when a matching buffer opens -- local pat = opts.filetypes or "*" -- vim.api.nvim_create_autocmd("FileType", { -- pattern = pat, -- callback = function(args) -- -- Only start if ft matches the server's filetypes (or none specified) -- if not cfg.filetypes or vim.tbl_contains(cfg.filetypes, vim.bo[args.buf].filetype) then -- vim.lsp.start(cfg, { bufnr = args.buf }) -- end -- end, -- }) end local function setup_with_lspconfig(name, opts) if not has_lspconfig then vim.notify(("lspconfig not found; cannot setup %s"):format(name), vim.log.levels.WARN) return end local final = vim.tbl_deep_extend("force", { capabilities = capabilities, on_attach = M.on_attach, }, opts or {}) lspconfig[name].setup(final) end for name, opts in pairs(servers) do if has_native then setup_with_native(name, opts) else setup_with_lspconfig(name, opts) end end return M