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

120
init.lua
View File

@ -1,108 +1,92 @@
-- ~/.config/nvim/init.lua
require 'core.options' -- Load general options require 'core.options' -- Load general options
require 'core.keymaps' -- Load general keymaps require 'core.keymaps' -- Load general keymaps
require 'core.snippets' -- Custom code snippets require 'core.snippets' -- Custom code snippets
-- Install package manager -- Install package manager (lazy.nvim)
local lazypath = vim.fn.stdpath 'data' .. '/lazy/lazy.nvim' local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim'
if not vim.loop.fs_stat(lazypath) then if not vim.loop.fs_stat(lazypath) then
vim.fn.system { vim.fn.system({
'git', 'git', 'clone', '--filter=blob:none',
'clone',
'--filter=blob:none',
'https://github.com/folke/lazy.nvim.git', 'https://github.com/folke/lazy.nvim.git',
'--branch=stable', -- latest stable release '--branch=stable', lazypath,
lazypath, })
}
end end
vim.opt.rtp:prepend(lazypath) vim.opt.rtp:prepend(lazypath)
-- Filetypes
vim.filetype.add({ vim.filetype.add({
extension = { extension = { templ = 'templ' },
templ = "templ",
}
}) })
-- Import color theme based on environment variable NVIM_THEME -- Theme selection (robust against unknown NVIM_THEME)
local default_color_scheme = 'quantum' local default_color_scheme = 'quantum'
local env_var_nvim_theme = os.getenv 'NVIM_THEME' or default_color_scheme local env_var_nvim_theme = os.getenv('NVIM_THEME') or default_color_scheme
-- Define a table of theme modules
local themes = { local themes = {
quantum = 'plugins.themes.quantum', quantum = 'plugins.themes.quantum',
nord = 'plugins.themes.nord', nord = 'plugins.themes.nord',
onedark = 'plugins.themes.onedark', onedark = 'plugins.themes.onedark',
} }
local theme_module = themes[env_var_nvim_theme] or themes[default_color_scheme]
-- Setup plugins -- Plugins
require('lazy').setup({ require('lazy').setup({
require(themes[env_var_nvim_theme]), require(theme_module),
require 'core.ui', require 'core.ui',
require 'plugins.aaa', -- Mason setup -- Load mason early so tools are ready for LSP configs
require 'plugins.aerial', require 'plugins.mason',
require 'plugins.flash',
require 'plugins.autocompletion', -- Core dev UX
require 'plugins.bufferline', require 'plugins.treesitter',
require 'plugins.comment', require 'plugins.telescope',
require 'plugins.conform',
require 'plugins.database',
require 'plugins.debug',
require 'plugins.gitsigns',
require 'plugins.harpoon',
require 'plugins.lazygit',
require 'plugins.lsp',
require 'plugins.lualine', require 'plugins.lualine',
require 'plugins.none-ls', require 'plugins.bufferline',
require 'plugins.indent-blankline', require 'plugins.indent-blankline',
require 'plugins.neo-tree', require 'plugins.neo-tree',
require 'plugins.misc',
require 'plugins.snack',
require 'plugins.telescope',
require 'plugins.toggleterm', require 'plugins.toggleterm',
require 'plugins.treesitter',
require 'plugins.vim-tmux-navigator', require 'plugins.vim-tmux-navigator',
require 'plugins.zellij', require 'plugins.zellij',
require 'plugins.flash',
require 'plugins.comment',
require 'plugins.harpoon',
require 'plugins.gitsigns',
require 'plugins.lazygit',
require 'plugins.aerial',
require 'plugins.misc',
-- LSP & companions
require 'plugins.autocompletion',
require 'plugins.lsp',
require 'plugins.none-ls', -- none-ls/null-ls sources & setup
require 'plugins.autoformat', -- your autoformat-on-save/idle logic
-- Optional: pick one formatter stack. If you keep Conform,
-- ensure it doesn't also format Go on save to avoid double-format.
require 'plugins.conform',
-- Debugging / DB (as you had)
require 'plugins.debug',
require 'plugins.database',
}, { }, {
ui = { ui = {
-- If you have a Nerd Font, set icons to an empty table which will use the
-- default lazy.nvim defined Nerd Font icons otherwise define a unicode icons table
icons = vim.g.have_nerd_font and {} or { icons = vim.g.have_nerd_font and {} or {
cmd = '', cmd = '', config = '🛠', event = '📅', ft = '📂', init = '',
config = '🛠', keys = '🗝', plugin = '🔌', runtime = '💻', require = '🌙',
event = '📅', source = '📄', start = '🚀', task = '📌', lazy = '💤 ',
ft = '📂',
init = '',
keys = '🗝',
plugin = '🔌',
runtime = '💻',
require = '🌙',
source = '📄',
start = '🚀',
task = '📌',
lazy = '💤 ',
}, },
}, },
}) })
-- Function to check if a file exists -- (Optional) tiny helper if you ever want to source a session file
local function file_exists(file) local function file_exists(file)
local f = io.open(file, 'r') local f = io.open(file, 'r')
if f then if f then f:close(); return true end
f:close() return false
return true
else
return false
end
end end
-- Path to the session file -- local session_file = '.session.vim'
local session_file = '.session.vim' -- if file_exists(session_file) then vim.cmd('source ' .. session_file) end
-- Check if the session file exists in the current directory
-- if file_exists(session_file) then
-- -- Source the session file
-- vim.cmd('source ' .. session_file)
-- end
-- The line beneath this is called `modeline`. See `:help modeline`
-- vim: ts=2 sts=2 sw=2 et -- vim: ts=2 sts=2 sw=2 et

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 { return {
{ {
"neovim/nvim-lspconfig", "neovim/nvim-lspconfig",
lazy = false,
config = function() config = function()
local lspconfig = require("lspconfig") local lspconfig = require("lspconfig")
local util = require("lspconfig.util") local util = require("lspconfig.util")
local configs = require("lspconfig.configs")
-- Ensure *.templ is recognized as 'templ' -- Recognize .templ files
vim.filetype.add({ vim.filetype.add({ extension = { templ = "templ" } })
extension = { templ = "templ" },
})
-- Go LSP with import organization -- ==============================
-- gopls
-- ==============================
lspconfig.gopls.setup({ lspconfig.gopls.setup({
root_dir = function(fname) root_dir = function(fname)
return util.root_pattern("go.work", "go.mod", ".git")(fname) return util.root_pattern("go.work", "go.mod", ".git")(fname)
or util.path.dirname(fname) or util.path.dirname(fname)
end, end,
handlers = { handlers = {
-- Suppress signature help errors that are common with incomplete Go code
["textDocument/signatureHelp"] = function(err, result, ctx, config) ["textDocument/signatureHelp"] = function(err, result, ctx, config)
if err and string.find(err.message, "cannot get type") then if err and err.message and err.message:find("cannot get type") then
-- Silently ignore "cannot get type" errors for signature help return
return nil
end end
return vim.lsp.handlers["textDocument/signatureHelp"](err, result, ctx, config) return vim.lsp.handlers["textDocument/signatureHelp"](err, result, ctx, config)
end, end,
@ -59,114 +59,121 @@ return {
usePlaceholders = true, usePlaceholders = true,
completeUnimported = true, completeUnimported = true,
staticcheck = 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, semanticTokens = true,
-- Performance optimizations for large repositories
memoryMode = "DegradeClosed", memoryMode = "DegradeClosed",
symbolMatcher = "FastFuzzy", symbolMatcher = "FastFuzzy",
-- Reduce signature help noise
["ui.completion.experimentalPostfixCompletions"] = false, ["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({ -- Astro (guard if missing)
cmd = { "templ", "lsp" }, -- or absolute path if needed -- ==============================
filetypes = { "templ" }, if lspconfig.astro then
root_dir = util.root_pattern("go.mod", ".git"), local function get_typescript_lib()
single_file_support = true, 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 local global_ts = (vim.fn.system("npm root -g"):gsub("\n", "")) .. "/typescript/lib"
vim.api.nvim_create_user_command('LspClients', function() 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 clients = vim.lsp.get_clients()
local client_counts = {} local counts = {}
for _, c in ipairs(clients) do
for _, client in ipairs(clients) do counts[c.name] = (counts[c.name] or 0) + 1
client_counts[client.name] = (client_counts[client.name] or 0) + 1
end end
print("=== Active LSP Clients ===") print("=== Active LSP Clients ===")
for name, count in pairs(client_counts) do for name, n in pairs(counts) do
local status = count > 1 and " ⚠️ DUPLICATE" or "" local dup = n > 1 and " ⚠️ DUPLICATE" or ""
print(string.format("%s: %d client(s)%s", name, count, status)) print(string.format("%s: %d client(s)%s", name, n, dup))
end end
if next(counts) == nil then print("No active LSP clients") end
end, {})
if next(client_counts) == nil then vim.api.nvim_create_user_command("LspKillDuplicates", function()
print("No active LSP clients")
end
end, { desc = "Show active LSP clients and detect duplicates" })
-- Command to kill duplicate gopls clients (keep only the one with settings)
vim.api.nvim_create_user_command('LspKillDuplicates', function()
local gopls_clients = vim.lsp.get_clients({ name = "gopls" }) local gopls_clients = vim.lsp.get_clients({ name = "gopls" })
if #gopls_clients <= 1 then if #gopls_clients <= 1 then
print("No duplicate gopls clients found") print("No duplicate gopls clients found")
return return
end end
local keep, kill = nil, {}
local client_to_keep = nil for _, c in ipairs(gopls_clients) do
local clients_to_kill = {} local cnt = 0
if c.config.settings and c.config.settings.gopls then
-- Find the client with the most settings (should be our configured one) for _ in pairs(c.config.settings.gopls) do cnt = cnt + 1 end
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)
end end
if cnt > 0 and not keep then keep = c else table.insert(kill, c) end
end end
for _, c in ipairs(kill) do
-- Kill the duplicates print(("Killing duplicate gopls client (id: %d)"):format(c.id))
for _, client in ipairs(clients_to_kill) do c.stop(true)
print(string.format("Killing duplicate gopls client (id: %d)", client.id))
client.stop(true)
end end
if keep then print(("Kept gopls client (id: %d) with settings"):format(keep.id)) end
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" })
-- Safe hover helper
local function has_hover(bufnr) local function has_hover(bufnr)
for _, c in pairs(vim.lsp.get_active_clients({ bufnr = bufnr })) do for _, c in pairs(vim.lsp.get_active_clients({ bufnr = bufnr })) do
if c.server_capabilities and c.server_capabilities.hoverProvider then if c.server_capabilities and c.server_capabilities.hoverProvider then return true end
return true
end
end end
return false return false
end end
-- LSP keymaps are handled in lsp-keymaps.lua
vim.api.nvim_create_autocmd("LspAttach", { vim.api.nvim_create_autocmd("LspAttach", {
callback = function(args) callback = function(args)
local bufnr = args.buf local bufnr = args.buf
local lsp_keymaps = require("plugins.lsp-keymaps")
-- Use the centralized keymap system
local lsp_keymaps = require('plugins.lsp-keymaps')
lsp_keymaps.on_attach(nil, bufnr) lsp_keymaps.on_attach(nil, bufnr)
-- Safe hover (keeping this custom logic)
local function buf_map(mode, lhs, rhs, desc) local function buf_map(mode, lhs, rhs, desc)
vim.keymap.set(mode, lhs, rhs, { buffer = bufnr, desc = desc }) vim.keymap.set(mode, lhs, rhs, { buffer = bufnr, desc = desc })
end end
buf_map("n", "K", function() buf_map("n", "K", function()
if not has_hover(bufnr) then if not has_hover(bufnr) then return end
return
end
local ok, saga_hover = pcall(require, "lspsaga.hover") local ok, saga_hover = pcall(require, "lspsaga.hover")
if ok and saga_hover and saga_hover.render_hover_doc then if ok and saga_hover and saga_hover.render_hover_doc then
pcall(function() saga_hover:render_hover_doc() end) 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 { return {
'nvimtools/none-ls.nvim', "nvimtools/none-ls.nvim",
event = { "BufReadPre", "BufNewFile" }, -- load early so it can attach
dependencies = { dependencies = {
'nvimtools/none-ls-extras.nvim', "williamboman/mason.nvim",
'gbprod/none-ls-shellcheck.nvim', "jay-babu/mason-null-ls.nvim",
"nvimtools/none-ls-extras.nvim", -- optional
}, },
config = function() config = function()
local null_ls = require 'null-ls' local null_ls = require("null-ls")
local formatting = null_ls.builtins.formatting -- to setup formatters
local diagnostics = null_ls.builtins.diagnostics -- to setup linters
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: -- Web (keep only what you use)
-- :MasonInstall checkmake prettier stylua eslint_d shfmt ruff goimports null_ls.builtins.formatting.prettierd,
null_ls.builtins.diagnostics.eslint_d,
},
})
local sources = { require("mason-null-ls").setup({
diagnostics.checkmake, ensure_installed = { "gofumpt", "golines", "golangci-lint", "prettierd", "eslint_d" },
formatting.prettier.with { filetypes = { 'html', 'json', 'yaml', 'markdown' } }, -- removed 'templ' for debugging automatic_installation = true,
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
}
local augroup = vim.api.nvim_create_augroup('LspFormatting', {}) -- (optional) format on save for Go
null_ls.setup { vim.api.nvim_create_autocmd("BufWritePre", {
debug = false, -- Disable debug mode to reduce log spam pattern = "*.go",
sources = sources, callback = function() vim.lsp.buf.format({ async = false }) end,
-- 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,
}
end, end,
} }

View File

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