local utils = require("utils") vim.api.nvim_create_autocmd("LspAttach", { group = vim.api.nvim_create_augroup("lsp_buf_conf", { clear = true }), callback = function(event_context) local client = vim.lsp.get_client_by_id(event_context.data.client_id) -- vim.print(client.name, client.server_capabilities) if not client then return end local bufnr = event_context.buf -- Mappings. local map = function(mode, l, r, opts) opts = opts or {} opts.silent = true opts.buffer = bufnr vim.keymap.set(mode, l, r, opts) end map("n", "gd", function() vim.lsp.buf.definition { on_list = function(options) -- custom logic to avoid showing multiple definition when you use this style of code: -- `local M.my_fn_name = function() ... end`. -- See also post here: https://www.reddit.com/r/neovim/comments/19cvgtp/any_way_to_remove_redundant_definition_in_lua_file/ -- vim.print(options.items) local unique_defs = {} local def_loc_hash = {} -- each item in options.items contain the location info for a definition provided by LSP server for _, def_location in pairs(options.items) do -- use filename and line number to uniquelly indentify a definition, -- we do not expect/want multiple definition in single line! local hash_key = def_location.filename .. def_location.lnum if not def_loc_hash[hash_key] then def_loc_hash[hash_key] = true table.insert(unique_defs, def_location) end end options.items = unique_defs -- set the location list ---@diagnostic disable-next-line: param-type-mismatch vim.fn.setloclist(0, {}, " ", options) -- open the location list when we have more than 1 definitions found, -- otherwise, jump directly to the definition if #options.items > 1 then vim.cmd.lopen() else vim.cmd([[silent! lfirst]]) end end, } end, { desc = "go to definition" }) map("n", "", vim.lsp.buf.definition) map("n", "K", function() vim.lsp.buf.hover { border = "single", max_height = 20, max_width = 130, close_events = { "CursorMoved", "BufLeave", "WinLeave", "LSPDetach" }, } end) map("n", "", vim.lsp.buf.signature_help) map("n", "rn", vim.lsp.buf.rename, { desc = "varialbe rename" }) map("n", "ca", vim.lsp.buf.code_action, { desc = "LSP code action" }) map("n", "wa", vim.lsp.buf.add_workspace_folder, { desc = "add workspace folder" }) map("n", "wr", vim.lsp.buf.remove_workspace_folder, { desc = "remove workspace folder" }) map("n", "wl", function() vim.print(vim.lsp.buf.list_workspace_folders()) end, { desc = "list workspace folder" }) -- Set some key bindings conditional on server capabilities -- Disable ruff hover feature in favor of Pyright if client.name == "ruff" then client.server_capabilities.hoverProvider = false end end, nested = true, desc = "Configure buffer keymap and behavior based on LSP", }) -- Enable lsp servers when they are available local capabilities = require("lsp_utils").get_default_capabilities() -- `*` will set default config for all lsp vim.lsp.config("*", { capabilities = capabilities, flags = { debounce_text_changes = 500, }, }) vim.lsp.config("basedpyright", { capabilities = capabilities, offset_encoding = "utf-16", settings = { python = { analysis = { typeCheckingMode = "strict", }, }, }, }) vim.lsp.config("ruff", { capabilities = capabilities, offset_encoding = "utf-8", }) -- A mapping from lsp server name to the executable name local enabled_lsp_servers = { basedpyright = "basedpyright-langserver", ruff = "ruff", lua_ls = "lua-language-server", -- clangd = "clangd", vimls = "vim-language-server", bashls = "bash-language-server", yamlls = "yaml-language-server", gopls = "gopls", -- the server can be install via homebrew: brew install golangci-lint-langserver -- golangci-lint also needs to be installed: https://github.com/golangci/golangci-lint golangci_lint_ls = "golangci-lint-langserver", -- to install codebook, run `brew install codebook-lsp` -- codebook = "codebook-lsp" } for server_name, lsp_executable in pairs(enabled_lsp_servers) do if utils.executable(lsp_executable) then vim.lsp.enable(server_name) else local msg = string.format( "Executable '%s' for server '%s' not found! Server will not be enabled", lsp_executable, server_name ) vim.notify(msg, vim.log.levels.WARN, { title = "Nvim-config" }) end end -- LSP related command vim.api.nvim_create_user_command("LspInfo", "checkhealth vim.lsp", { desc = "Show LSP Info", }) vim.api.nvim_create_user_command("LspLog", function(_) local log_path = vim.lsp.log.get_filename() vim.cmd(string.format("edit %s", log_path)) end, { desc = "Show LSP log", }) vim.api.nvim_create_user_command("LspRestart", "lsp restart", { desc = "Restart LSP", }) --- show LSP progress (works on Ghostty) vim.api.nvim_create_autocmd("LspProgress", { callback = function(ev) local value = ev.data.params.value vim.api.nvim_echo({ { value.message or "done" } }, false, { id = "lsp." .. ev.data.client_id, kind = "progress", source = "vim.lsp", title = value.title, status = value.kind ~= "end" and "running" or "success", percent = value.percentage, }) end, }) -- this controls the LSP inlayHints behavior vim.g.lsp_inlay_hint_enabled = false local update_inlayhint = function(enable) -- Some LSP server supports inlay hint, but disable this feature by default, so you may need to -- enable inlay hint in the LSP server config. vim.lsp.inlay_hint.enable(enable) end vim.api.nvim_create_user_command("LspInlayHints", function(context) -- vim.print("context", context) if context["args"] == "enable" then vim.g.lsp_inlay_hint_enabled = true end if context["args"] == "disable" then vim.g.lsp_inlay_hint_enabled = false end update_inlayhint(vim.g.lsp_inlay_hint_enabled) end, { bang = false, nargs = 1, force = true, desc = "Toggle LSP inlayHints", complete = function() return { "enable", "disable" } end, })