diff --git a/nvim/.config/nvim/ftplugin/gdscript.lua b/nvim/.config/nvim/ftplugin/gdscript.lua index a10eb19..07c2489 100644 --- a/nvim/.config/nvim/ftplugin/gdscript.lua +++ b/nvim/.config/nvim/ftplugin/gdscript.lua @@ -1 +1,2 @@ vim.keymap.set("n", "rp", "GodotRunProject", { buffer = true, desc = "Godot: run project" }) +vim.keymap.set("n", "rk", "GodotKillRun", { buffer = true, desc = "Godot: kill running instance" }) diff --git a/nvim/.config/nvim/lua/autocmds.lua b/nvim/.config/nvim/lua/autocmds.lua index 2b88049..3768d85 100644 --- a/nvim/.config/nvim/lua/autocmds.lua +++ b/nvim/.config/nvim/lua/autocmds.lua @@ -32,53 +32,47 @@ autocmd("TermOpen", { command = "startinsert" }) --- godotdev.nvim manages the gdscript LSP (client "godot_editor") with its own on_attach. +-- godotdev.nvim manages the gdscript LSP with its own on_attach. -- Registered here (startup) so it's ready before the LSP attaches -- when opening -- `nvim foo.gd` directly, FileType fires before UIEnter, meaning the LSP can attach -- before lspconfig.lua ever loads via "User FilePost". +-- +-- NOTE: godotdev.nvim's lsp.lua sets `name = "godot_editor"` in its client config, +-- but Neovim 0.11+ always overwrites client.name with the vim.lsp.config() +-- registration key (see runtime lua/vim/lsp.lua: `resolved_config.name = name`), +-- so the real attached client is actually named "gdscript", never "godot_editor". +-- Gating on that name meant this block silently never ran -- none of gd/gr/ +-- rename/etc. keymaps were ever set for the Godot LSP. Gate on filetype instead, +-- which doesn't depend on Neovim's internal naming behavior. autocmd("LspAttach", { callback = function(args) + if vim.bo[args.buf].filetype ~= "gdscript" then return end local client = vim.lsp.get_client_by_id(args.data.client_id) - if not client or client.name ~= "godot_editor" then return end - - local ok, navic = pcall(require, "nvim-navic") - if ok and client.server_capabilities.documentSymbolProvider then - navic.attach(client, args.buf) - end - - local map = vim.keymap.set - local bufnr = args.buf - local function opts(desc) return { buffer = bufnr, desc = "LSP " .. desc } end - map("n", "gD", vim.lsp.buf.declaration, opts "Go to declaration") - map("n", "gd", vim.lsp.buf.definition, opts "Go to definition") - map("n", "gi", vim.lsp.buf.implementation, opts "Go to implementation") - map("n", "gr", vim.lsp.buf.references, opts "Show references") - map("n", "sh", vim.lsp.buf.signature_help, opts "Show signature help") - map("n", "D", vim.lsp.buf.type_definition, opts "Go to type definition") - map("n", "ra", vim.lsp.buf.rename, opts "Rename symbol") - map({ "n", "v" }, "ca", vim.lsp.buf.code_action, opts "Code action") - map("n", "[d", vim.diagnostic.goto_prev, opts "Go to previous diagnostic") - map("n", "]d", vim.diagnostic.goto_next, opts "Go to next diagnostic") - map("n", "q", vim.diagnostic.setloclist, opts "Set loclist") + if not client then return end + require("configs.lsp_keymaps").attach(client, args.buf) end, }) --- The godotdev "godot_editor" LSP connects over TCP to the running Godot editor, --- so its socket dies on every project reload. Re-attach on insert mode transitions --- (and a delayed retry while still typing) so a reload never needs a Neovim restart. +-- The godotdev gdscript LSP connects over TCP to the running Godot editor, so +-- its socket dies on every project reload. godotdev only ships a manual fix +-- for this (:GodotReconnectLSP); nothing in the plugin re-runs it for you. Do +-- that automatically on BufEnter/FocusGained -- rare, one-shot events (not a +-- keystroke-driven event like InsertEnter, so no debounce/guard is needed) -- +-- via `:edit`, which is what :GodotReconnectLSP itself does under the hood +-- (safe: refuses instead of discarding if there are unsaved changes). local godot_reconnect_group = vim.api.nvim_create_augroup("godot_lsp_reconnect", { clear = true }) local function reconnect_godot_lsp(bufnr) if not vim.api.nvim_buf_is_valid(bufnr) then return end if vim.bo[bufnr].filetype ~= "gdscript" then return end - if next(vim.lsp.get_clients({ name = "godot_editor", bufnr = bufnr })) then return end - local config = vim.lsp.config["gdscript"] - if config then - vim.lsp.start(config, { bufnr = bufnr }) - end + if next(vim.lsp.get_clients({ bufnr = bufnr })) then return end + + vim.api.nvim_buf_call(bufnr, function() + pcall(vim.cmd, "edit") + end) end -autocmd({ "InsertEnter", "InsertLeave" }, { +autocmd({ "BufEnter", "FocusGained" }, { group = godot_reconnect_group, pattern = "*.gd", callback = function(args) @@ -86,19 +80,6 @@ autocmd({ "InsertEnter", "InsertLeave" }, { end, }) -autocmd("InsertEnter", { - group = godot_reconnect_group, - pattern = "*.gd", - callback = function(args) - local bufnr = args.buf - vim.defer_fn(function() - if vim.api.nvim_get_mode().mode:sub(1, 1) == "i" then - reconnect_godot_lsp(bufnr) - end - end, 1500) - end, -}) - -- godotdev's run console appends output without scrolling. Attach to the console -- buffer to tail new output, but only while the cursor sits at the bottom so -- scrolling up to read earlier output is not interrupted. diff --git a/nvim/.config/nvim/lua/configs/lsp_keymaps.lua b/nvim/.config/nvim/lua/configs/lsp_keymaps.lua new file mode 100644 index 0000000..93966bb --- /dev/null +++ b/nvim/.config/nvim/lua/configs/lsp_keymaps.lua @@ -0,0 +1,48 @@ +-- Shared LSP on_attach keymaps. Used by every server in configs/lspconfig.lua +-- and by the gdscript LspAttach handler in autocmds.lua (godotdev.nvim's own +-- on_attach only suppresses messages / wires inline hints, no keymaps -- it +-- deliberately leaves keybindings to the user, same as every other server +-- here, so this is the single place they're all defined). + +local M = {} + +function M.attach(client, bufnr) + -- Attach navic for breadcrumb tracking + if client.server_capabilities.documentSymbolProvider then + local ok, navic = pcall(require, "nvim-navic") + if ok then + navic.attach(client, bufnr) + end + end + + local map = vim.keymap.set + local function opts(desc) + return { buffer = bufnr, desc = "LSP " .. desc } + end + + -- Navigation + map("n", "gD", vim.lsp.buf.declaration, opts "Go to declaration") + map("n", "gd", vim.lsp.buf.definition, opts "Go to definition") + map("n", "gi", vim.lsp.buf.implementation, opts "Go to implementation") + map("n", "gr", vim.lsp.buf.references, opts "Show references") + map("n", "sh", vim.lsp.buf.signature_help, opts "Show signature help") + map("n", "D", vim.lsp.buf.type_definition, opts "Go to type definition") + + -- Workspace + map("n", "wa", vim.lsp.buf.add_workspace_folder, opts "Add workspace folder") + map("n", "wr", vim.lsp.buf.remove_workspace_folder, opts "Remove workspace folder") + map("n", "wl", function() + print(vim.inspect(vim.lsp.buf.list_workspace_folders())) + end, opts "List workspace folders") + + -- Actions + map("n", "ra", vim.lsp.buf.rename, opts "Rename symbol") + map({ "n", "v" }, "ca", vim.lsp.buf.code_action, opts "Code action") + + -- Diagnostics (already mapped in mappings.lua, but keeping for consistency) + map("n", "[d", vim.diagnostic.goto_prev, opts "Go to previous diagnostic") + map("n", "]d", vim.diagnostic.goto_next, opts "Go to next diagnostic") + map("n", "q", vim.diagnostic.setloclist, opts "Set loclist") +end + +return M diff --git a/nvim/.config/nvim/lua/configs/lspconfig.lua b/nvim/.config/nvim/lua/configs/lspconfig.lua index e886fea..2138973 100644 --- a/nvim/.config/nvim/lua/configs/lspconfig.lua +++ b/nvim/.config/nvim/lua/configs/lspconfig.lua @@ -1,42 +1,10 @@ -- LSP capabilities (blink.cmp augments these automatically) local capabilities = vim.lsp.protocol.make_client_capabilities() --- on_attach function with LSP keybindings +-- on_attach function with LSP keybindings (shared with the gdscript +-- LspAttach handler in autocmds.lua -- see lua/configs/lsp_keymaps.lua) local on_attach = function(client, bufnr) - -- Attach navic for breadcrumb tracking - if client.server_capabilities.documentSymbolProvider then - require("nvim-navic").attach(client, bufnr) - end - - local function opts(desc) - return { buffer = bufnr, desc = "LSP " .. desc } - end - - local map = vim.keymap.set - - -- Navigation - map("n", "gD", vim.lsp.buf.declaration, opts "Go to declaration") - map("n", "gd", vim.lsp.buf.definition, opts "Go to definition") - map("n", "gi", vim.lsp.buf.implementation, opts "Go to implementation") - map("n", "gr", vim.lsp.buf.references, opts "Show references") - map("n", "sh", vim.lsp.buf.signature_help, opts "Show signature help") - map("n", "D", vim.lsp.buf.type_definition, opts "Go to type definition") - - -- Workspace - map("n", "wa", vim.lsp.buf.add_workspace_folder, opts "Add workspace folder") - map("n", "wr", vim.lsp.buf.remove_workspace_folder, opts "Remove workspace folder") - map("n", "wl", function() - print(vim.inspect(vim.lsp.buf.list_workspace_folders())) - end, opts "List workspace folders") - - -- Actions - map("n", "ra", vim.lsp.buf.rename, opts "Rename symbol") - map({ "n", "v" }, "ca", vim.lsp.buf.code_action, opts "Code action") - - -- Diagnostics (already mapped in mappings.lua, but keeping for consistency) - map("n", "[d", vim.diagnostic.goto_prev, opts "Go to previous diagnostic") - map("n", "]d", vim.diagnostic.goto_next, opts "Go to next diagnostic") - map("n", "q", vim.diagnostic.setloclist, opts "Set loclist") + require("configs.lsp_keymaps").attach(client, bufnr) end local servers = { diff --git a/nvim/.config/nvim/lua/plugins/tools.lua b/nvim/.config/nvim/lua/plugins/tools.lua index 033d419..89d1685 100644 --- a/nvim/.config/nvim/lua/plugins/tools.lua +++ b/nvim/.config/nvim/lua/plugins/tools.lua @@ -13,6 +13,18 @@ return { changedelete = { text = "󱕖" }, untracked = { text = "┆" }, }, + -- Inline blame on the current line. Right-aligned + lower priority than + -- tiny-inline-diagnostic (2048) so error messages keep the spot next to + -- the code and the blame never paints over them. + current_line_blame = true, + current_line_blame_opts = { + virt_text = true, + virt_text_pos = "right_align", + delay = 250, + ignore_whitespace = false, + virt_text_priority = 100, + }, + current_line_blame_formatter = " , ", }, }, @@ -25,6 +37,8 @@ return { require("vgit").setup({ settings = { live_blame = { + -- Disabled: vgit hard-codes blame at EOL with no priority, so it + -- paints over tiny-inline-diagnostic. Using gitsigns blame instead. enabled = false, }, },