This commit is contained in:
Walter Jenkins 2025-10-12 16:31:14 -05:00
parent bc15b5bca3
commit 4564d7093e
8 changed files with 278 additions and 210 deletions

View file

@ -1,19 +0,0 @@
return {
{
"mason-org/mason.nvim",
version = "^1.0.0",
config = function()
require("mason").setup()
end,
},
{
"mason-org/mason-lspconfig.nvim",
version = "^1.0.0",
config = function()
require("mason-lspconfig").setup({
ensure_installed = { "ts_ls", "gopls", "templ" },
automatic_installation = true,
})
end,
},
}

View file

@ -0,0 +1,72 @@
-- lua/plugins/autoformat.lua
-- Automatically format Go code on save and when idle after changes
return {
"neovim/nvim-lspconfig",
event = { "BufReadPre", "BufNewFile" },
config = function()
---------------------------------------------------------------------------
-- 🧹 Format on save
---------------------------------------------------------------------------
vim.api.nvim_create_autocmd("BufWritePre", {
pattern = "*.go",
callback = function()
-- Runs both gopls and none-ls formatters in order
vim.lsp.buf.format({ async = false })
end,
})
---------------------------------------------------------------------------
-- ⚡ Auto-format when idle (after you stop typing)
---------------------------------------------------------------------------
local format_timer = vim.loop.new_timer()
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
pattern = "*.go",
callback = function()
-- Cancel previous pending format
format_timer:stop()
-- Wait 1.5 seconds after the last change before formatting
format_timer:start(1500, 0, vim.schedule_wrap(function()
-- Only format if the buffer still exists and is listed
local bufnr = vim.api.nvim_get_current_buf()
if vim.api.nvim_buf_is_valid(bufnr) and vim.bo[bufnr].modifiable then
vim.lsp.buf.format({ async = true })
end
end))
end,
})
---------------------------------------------------------------------------
-- 🧪 Optional: run `goimports` and quick test on save
---------------------------------------------------------------------------
vim.api.nvim_create_autocmd("BufWritePost", {
pattern = "*.go",
callback = function()
-- Automatically fix imports using goimports if available
vim.fn.jobstart({ "goimports", "-w", vim.fn.expand("%:p") }, {
on_exit = function()
-- Optionally, trigger a quick test run for feedback
vim.fn.jobstart({ "go", "test", "./..." }, {
cwd = vim.fn.getcwd(),
stdout_buffered = true,
stderr_buffered = true,
on_stdout = function(_, data)
if data then
vim.notify(table.concat(data, "\n"), vim.log.levels.INFO, { title = "go test" })
end
end,
on_stderr = function(_, data)
if data then
vim.notify(table.concat(data, "\n"), vim.log.levels.ERROR, { title = "go test" })
end
end,
})
end,
})
end,
})
end,
}

View file

@ -2,27 +2,27 @@
return {
{
"neovim/nvim-lspconfig",
lazy = false,
config = function()
local lspconfig = require("lspconfig")
local util = require("lspconfig.util")
local configs = require("lspconfig.configs")
-- Ensure *.templ is recognized as 'templ'
vim.filetype.add({
extension = { templ = "templ" },
})
-- Recognize .templ files
vim.filetype.add({ extension = { templ = "templ" } })
-- Go LSP with import organization
-- ==============================
-- gopls
-- ==============================
lspconfig.gopls.setup({
root_dir = function(fname)
return util.root_pattern("go.work", "go.mod", ".git")(fname)
or util.path.dirname(fname)
end,
handlers = {
-- Suppress signature help errors that are common with incomplete Go code
["textDocument/signatureHelp"] = function(err, result, ctx, config)
if err and string.find(err.message, "cannot get type") then
-- Silently ignore "cannot get type" errors for signature help
return nil
if err and err.message and err.message:find("cannot get type") then
return
end
return vim.lsp.handlers["textDocument/signatureHelp"](err, result, ctx, config)
end,
@ -59,114 +59,121 @@ return {
usePlaceholders = true,
completeUnimported = true,
staticcheck = true,
directoryFilters = { "-.git", "-.vscode", "-.idea", "-.vscode-test", "-node_modules", "-dist", "-build", "-out", "-coverage", "-tmp", "-.cache" },
directoryFilters = {
"-.git","-.vscode","-.idea","-.vscode-test","-node_modules",
"-dist","-build","-out","-coverage","-tmp","-.cache",
},
semanticTokens = true,
-- Performance optimizations for large repositories
memoryMode = "DegradeClosed",
symbolMatcher = "FastFuzzy",
-- Reduce signature help noise
["ui.completion.experimentalPostfixCompletions"] = false,
},
},
})
-- TypeScript (make sure you don't also set this up elsewhere to avoid duplicates)
lspconfig.ts_ls.setup({})
-- ==============================
-- TypeScript / JavaScript (ts_ls OR tsserver fallback)
-- ==============================
local ts_server = lspconfig.ts_ls or lspconfig.tsserver
if ts_server then
ts_server.setup({})
end
-- ✅ Templ LSP: auto-start when in a repo with go.mod or .git
lspconfig.templ.setup({
cmd = { "templ", "lsp" }, -- or absolute path if needed
filetypes = { "templ" },
root_dir = util.root_pattern("go.mod", ".git"),
single_file_support = true,
})
-- ==============================
-- Astro (guard if missing)
-- ==============================
if lspconfig.astro then
local function get_typescript_lib()
local mason_ts = vim.fs.normalize(
"~/.local/share/nvim/mason/packages/typescript-language-server/node_modules/typescript/lib"
)
if vim.fn.isdirectory(mason_ts) == 1 then return mason_ts end
-- LSP client monitoring helper
vim.api.nvim_create_user_command('LspClients', function()
local global_ts = (vim.fn.system("npm root -g"):gsub("\n", "")) .. "/typescript/lib"
if vim.fn.isdirectory(global_ts) == 1 then return global_ts end
return vim.fs.normalize(
"~/.local/share/nvim/mason/packages/astro-language-server/node_modules/typescript/lib"
)
end
lspconfig.astro.setup({
init_options = { typescript = { tsdk = get_typescript_lib() } },
})
end
-- ==============================
-- templ (register config if missing)
-- ==============================
if not configs.templ then
configs.templ = {
default_config = {
cmd = { "templ", "lsp" },
filetypes = { "templ" },
root_dir = util.root_pattern("go.mod", ".git"),
single_file_support = true,
},
}
end
lspconfig.templ.setup({})
-- ==============================
-- Utilities
-- ==============================
vim.api.nvim_create_user_command("LspClients", function()
local clients = vim.lsp.get_clients()
local client_counts = {}
for _, client in ipairs(clients) do
client_counts[client.name] = (client_counts[client.name] or 0) + 1
local counts = {}
for _, c in ipairs(clients) do
counts[c.name] = (counts[c.name] or 0) + 1
end
print("=== Active LSP Clients ===")
for name, count in pairs(client_counts) do
local status = count > 1 and " ⚠️ DUPLICATE" or ""
print(string.format("%s: %d client(s)%s", name, count, status))
for name, n in pairs(counts) do
local dup = n > 1 and " ⚠️ DUPLICATE" or ""
print(string.format("%s: %d client(s)%s", name, n, dup))
end
if next(client_counts) == nil then
print("No active LSP clients")
end
end, { desc = "Show active LSP clients and detect duplicates" })
if next(counts) == nil then print("No active LSP clients") end
end, {})
-- Command to kill duplicate gopls clients (keep only the one with settings)
vim.api.nvim_create_user_command('LspKillDuplicates', function()
vim.api.nvim_create_user_command("LspKillDuplicates", function()
local gopls_clients = vim.lsp.get_clients({ name = "gopls" })
if #gopls_clients <= 1 then
print("No duplicate gopls clients found")
return
end
local client_to_keep = nil
local clients_to_kill = {}
-- Find the client with the most settings (should be our configured one)
for _, client in ipairs(gopls_clients) do
local settings_count = 0
if client.config.settings and client.config.settings.gopls then
for _ in pairs(client.config.settings.gopls) do
settings_count = settings_count + 1
end
end
if settings_count > 0 and not client_to_keep then
client_to_keep = client
else
table.insert(clients_to_kill, client)
local keep, kill = nil, {}
for _, c in ipairs(gopls_clients) do
local cnt = 0
if c.config.settings and c.config.settings.gopls then
for _ in pairs(c.config.settings.gopls) do cnt = cnt + 1 end
end
if cnt > 0 and not keep then keep = c else table.insert(kill, c) end
end
-- Kill the duplicates
for _, client in ipairs(clients_to_kill) do
print(string.format("Killing duplicate gopls client (id: %d)", client.id))
client.stop(true)
for _, c in ipairs(kill) do
print(("Killing duplicate gopls client (id: %d)"):format(c.id))
c.stop(true)
end
if client_to_keep then
print(string.format("Kept gopls client (id: %d) with settings", client_to_keep.id))
end
end, { desc = "Kill duplicate gopls clients" })
if keep then print(("Kept gopls client (id: %d) with settings"):format(keep.id)) end
end, {})
-- Safe hover helper
local function has_hover(bufnr)
for _, c in pairs(vim.lsp.get_active_clients({ bufnr = bufnr })) do
if c.server_capabilities and c.server_capabilities.hoverProvider then
return true
end
if c.server_capabilities and c.server_capabilities.hoverProvider then return true end
end
return false
end
-- LSP keymaps are handled in lsp-keymaps.lua
vim.api.nvim_create_autocmd("LspAttach", {
callback = function(args)
local bufnr = args.buf
-- Use the centralized keymap system
local lsp_keymaps = require('plugins.lsp-keymaps')
local lsp_keymaps = require("plugins.lsp-keymaps")
lsp_keymaps.on_attach(nil, bufnr)
-- Safe hover (keeping this custom logic)
local function buf_map(mode, lhs, rhs, desc)
vim.keymap.set(mode, lhs, rhs, { buffer = bufnr, desc = desc })
end
buf_map("n", "K", function()
if not has_hover(bufnr) then
return
end
if not has_hover(bufnr) then return end
local ok, saga_hover = pcall(require, "lspsaga.hover")
if ok and saga_hover and saga_hover.render_hover_doc then
pcall(function() saga_hover:render_hover_doc() end)

21
lua/plugins/mason.lua Normal file
View file

@ -0,0 +1,21 @@
-- lua/plugins/mason.lua
return {
{
"williamboman/mason.nvim",
lazy = false,
config = function()
require("mason").setup()
end,
},
{
"williamboman/mason-lspconfig.nvim",
lazy = false,
dependencies = { "williamboman/mason.nvim" },
opts = {
ensure_installed = { "gopls", "ts_ls", "templ", "astro" },
automatic_installation = true,
automatic_setup = false, -- IMPORTANT: don't auto-setup servers
},
},
}

View file

@ -1,52 +1,38 @@
-- Format on save and linters
-- lua/plugins/none-ls.lua
return {
'nvimtools/none-ls.nvim',
"nvimtools/none-ls.nvim",
event = { "BufReadPre", "BufNewFile" }, -- load early so it can attach
dependencies = {
'nvimtools/none-ls-extras.nvim',
'gbprod/none-ls-shellcheck.nvim',
"williamboman/mason.nvim",
"jay-babu/mason-null-ls.nvim",
"nvimtools/none-ls-extras.nvim", -- optional
},
config = function()
local null_ls = require 'null-ls'
local formatting = null_ls.builtins.formatting -- to setup formatters
local diagnostics = null_ls.builtins.diagnostics -- to setup linters
local null_ls = require("null-ls")
null_ls.setup({
sources = {
-- Go
null_ls.builtins.formatting.gofumpt,
null_ls.builtins.formatting.golines, -- optional
null_ls.builtins.diagnostics.golangci_lint, -- if installed
-- Note: Use Mason to manually install tools:
-- :MasonInstall checkmake prettier stylua eslint_d shfmt ruff goimports
-- Web (keep only what you use)
null_ls.builtins.formatting.prettierd,
null_ls.builtins.diagnostics.eslint_d,
},
})
local sources = {
diagnostics.checkmake,
formatting.prettier.with { filetypes = { 'html', 'json', 'yaml', 'markdown' } }, -- removed 'templ' for debugging
formatting.stylua,
formatting.shfmt.with { args = { '-i', '4' } },
require('none-ls.formatting.ruff').with { extra_args = { '--extend-select', 'I' } },
require 'none-ls.formatting.ruff_format',
formatting.goimports, -- Add goimports for Go files
}
require("mason-null-ls").setup({
ensure_installed = { "gofumpt", "golines", "golangci-lint", "prettierd", "eslint_d" },
automatic_installation = true,
})
local augroup = vim.api.nvim_create_augroup('LspFormatting', {})
null_ls.setup {
debug = false, -- Disable debug mode to reduce log spam
sources = sources,
-- you can reuse a shared lspconfig on_attach callback here
on_attach = function(client, bufnr)
if client.supports_method 'textDocument/formatting' then
-- Skip formatting for .templ files during debugging
local filetype = vim.api.nvim_buf_get_option(bufnr, 'filetype')
if filetype == 'templ' then
return
end
vim.api.nvim_clear_autocmds { group = augroup, buffer = bufnr }
vim.api.nvim_create_autocmd('BufWritePre', {
group = augroup,
buffer = bufnr,
callback = function()
vim.lsp.buf.format { async = false }
end,
})
end
end,
}
-- (optional) format on save for Go
vim.api.nvim_create_autocmd("BufWritePre", {
pattern = "*.go",
callback = function() vim.lsp.buf.format({ async = false }) end,
})
end,
}

View file

@ -1,4 +1,4 @@
-- Highlight, edit, and navigate code
return {
'nvim-treesitter/nvim-treesitter',
build = ':TSUpdate',
@ -98,7 +98,6 @@ return {
},
},
}
-- Register additional file extensions
vim.filetype.add { extension = { tf = 'terraform' } }
vim.filetype.add { extension = { tfvars = 'terraform' } }

18
lua/plugins/tscontext.lua Normal file
View file

@ -0,0 +1,18 @@
return {
'treesitter-context',
setup{
enable = true, -- Enable this plugin (Can be enabled/disabled later via commands)
multiwindow = false, -- Enable multiwindow support.
max_lines = 0, -- How many lines the window should span. Values <= 0 mean no limit.
min_window_height = 0, -- Minimum editor window height to enable context. Values <= 0 mean no limit.
line_numbers = true,
multiline_threshold = 20, -- Maximum number of lines to show for a single context
trim_scope = 'outer', -- Which context lines to discard if `max_lines` is exceeded. Choices: 'inner', 'outer'
mode = 'cursor', -- Line used to calculate context. Choices: 'cursor', 'topline'
-- Separator between context and content. Should be a single character string, like '-'.
-- When separator is set, the context will only show up when there are at least 2 lines above cursorline.
separator = nil,
zindex = 20, -- The Z-index of the context window
on_attach = nil, -- (fun(buf: integer): boolean) return false to disable attaching
}
}