summaryrefslogtreecommitdiff
path: root/config/nvim
diff options
context:
space:
mode:
Diffstat (limited to 'config/nvim')
-rw-r--r--config/nvim/colors/dark.lua12
-rw-r--r--config/nvim/colors/default.lua11
-rw-r--r--config/nvim/colors/light.lua20
-rw-r--r--config/nvim/colors/molokai.lua5
-rw-r--r--config/nvim/colors/onehalfdark.lua3
-rw-r--r--config/nvim/init.lua702
-rw-r--r--config/nvim/local-plugins/cfg-watch/lua/cfg_watch/init.lua58
-rw-r--r--config/nvim/lua/.gitignore1
-rw-r--r--config/nvim/lua/config/lazy.lua37
-rw-r--r--config/nvim/lua/plugins/colorschemes.lua23
-rw-r--r--config/nvim/lua/plugins/completion.lua159
-rw-r--r--config/nvim/lua/plugins/editing.lua127
-rw-r--r--config/nvim/lua/plugins/editor.lua29
-rw-r--r--config/nvim/lua/plugins/fzf.lua148
-rw-r--r--config/nvim/lua/plugins/git.lua63
-rw-r--r--config/nvim/lua/plugins/lsp.lua281
-rw-r--r--config/nvim/lua/plugins/reload.lua28
-rw-r--r--config/nvim/lua/plugins/testing.lua41
-rw-r--r--config/nvim/lua/plugins/ui.lua335
-rw-r--r--config/nvim/lua/plugins/which-key.lua30
-rw-r--r--config/nvim/stylua.toml18
21 files changed, 2131 insertions, 0 deletions
diff --git a/config/nvim/colors/dark.lua b/config/nvim/colors/dark.lua
new file mode 100644
index 0000000..1b59858
--- /dev/null
+++ b/config/nvim/colors/dark.lua
@@ -0,0 +1,12 @@
+vim.g.ayucolor = "dark"
+vim.opt.background = "dark"
+vim.cmd("colorscheme ayu")
+-- vim.cmd("colorscheme github") -- Alternative color scheme (commented out)
+
+-- vim.g.airline_theme = "github"
+-- vim.g.lightline = { colorscheme = "github" }
+
+-- GitHub color theme settings
+-- vim.g.github_colors_soft = 1 -- Slightly darker background like GitHub inline code blocks
+-- vim.g.github_colors_block_diffmark = 0 -- More blocky diff markers in the sign column (e.g., GitGutter)
+
diff --git a/config/nvim/colors/default.lua b/config/nvim/colors/default.lua
new file mode 100644
index 0000000..44f59dd
--- /dev/null
+++ b/config/nvim/colors/default.lua
@@ -0,0 +1,11 @@
+-- This file should only be used by the theme script with variables injected
+-- If variables are not set, use fallback values
+---@diagnostic disable-next-line: undefined-global
+local mode = theme_mode or "dark"
+---@diagnostic disable-next-line: undefined-global
+local colors = theme_colors or "ayu"
+
+vim.g.ayucolor = mode
+vim.opt.background = mode
+vim.cmd("colorscheme " .. colors)
+
diff --git a/config/nvim/colors/light.lua b/config/nvim/colors/light.lua
new file mode 100644
index 0000000..ee2056c
--- /dev/null
+++ b/config/nvim/colors/light.lua
@@ -0,0 +1,20 @@
+vim.g.ayucolor = "light"
+vim.opt.background = "light"
+vim.cmd("colorscheme onehalflight")
+
+-- Alternative color schemes
+-- vim.cmd("colorscheme github")
+-- vim.g.airline_theme = "github"
+-- vim.g.lightline = { colorscheme = "github" }
+
+-- GitHub theme customizations
+-- vim.g.github_colors_soft = 1 -- Use a slightly darker background like GitHub inline code blocks
+-- vim.g.github_colors_block_diffmark = 0 -- Use blocky diff markers in the sign column (e.g., GitGutter)
+
+-- OneDark theme configuration
+-- vim.g.onedark_config = { style = "light" }
+-- vim.cmd("colorscheme onedark")
+
+-- Another alternative colorscheme
+-- vim.cmd("colorscheme PaperColor")
+
diff --git a/config/nvim/colors/molokai.lua b/config/nvim/colors/molokai.lua
new file mode 100644
index 0000000..9c63e5d
--- /dev/null
+++ b/config/nvim/colors/molokai.lua
@@ -0,0 +1,5 @@
+vim.g.ayucolor = "dark"
+vim.opt.background = "dark"
+-- vim.cmd("colorscheme monokai")
+-- vim.cmd("colorscheme deep-space")
+vim.cmd("colorscheme happy_hacking")
diff --git a/config/nvim/colors/onehalfdark.lua b/config/nvim/colors/onehalfdark.lua
new file mode 100644
index 0000000..3656341
--- /dev/null
+++ b/config/nvim/colors/onehalfdark.lua
@@ -0,0 +1,3 @@
+vim.g.ayucolor = "dark"
+vim.opt.background = "dark"
+vim.cmd("colorscheme onehalfdark")
diff --git a/config/nvim/init.lua b/config/nvim/init.lua
new file mode 100644
index 0000000..3f094e8
--- /dev/null
+++ b/config/nvim/init.lua
@@ -0,0 +1,702 @@
+------------------------------------------------------
+---- Runtime -----------------------------------------
+------------------------------------------------------
+
+-- Ensure XDG environment variables are set
+vim.env.XDG_CONFIG_HOME = vim.env.XDG_CONFIG_HOME or vim.env.HOME .. "/.config"
+vim.env.XDG_CACHE_HOME = vim.env.XDG_CACHE_HOME or vim.env.HOME .. "/.cache"
+vim.env.XDG_DATA_HOME = vim.env.XDG_DATA_HOME or vim.env.HOME .. "/.local/share"
+
+-- Ensure the Neovim cache directory exists
+local nvim_cache_dir = vim.env.XDG_CACHE_HOME .. "/nvim"
+if vim.fn.isdirectory(nvim_cache_dir) == 0 then
+ vim.fn.mkdir(nvim_cache_dir, "p")
+end
+
+-- Ensure necessary paths are in $PATH
+local paths = { "/usr/local/bin", "/opt/brew/bin", "/opt/homebrew/bin" }
+for _, path in ipairs(paths) do
+ if not string.match(vim.env.PATH, path) then
+ vim.env.PATH = path .. ":" .. vim.env.PATH
+ end
+end
+
+-- Add node_modules/.bin/ to PATH for Node.js projects
+if not string.match(vim.env.PATH, "node_modules/%.bin") then
+ vim.env.PATH = vim.env.PATH .. ":node_modules/.bin/"
+end
+
+-- Speed up startup by explicitly setting the Python interpreter
+local python_paths = {
+ "/opt/brew/bin/python3",
+ "/opt/homebrew/bin/python3",
+ "/usr/local/bin/python3",
+ "/usr/bin/python3",
+}
+for _, path in ipairs(python_paths) do
+ if vim.fn.executable(path) == 1 then
+ vim.g.python3_host_prog = path
+ break
+ end
+end
+
+-- Disable unused language providers (no Ruby/Perl remote plugins here).
+-- Skips provider probing at startup and quiets :checkhealth.
+vim.g.loaded_ruby_provider = 0
+vim.g.loaded_perl_provider = 0
+
+------------------------------------------------------
+---- Basics ------------------------------------------
+------------------------------------------------------
+
+-- colorscheme is set from
+-- ~/.local/share/nvim/colors.lua
+
+-- Set leader key
+vim.g.mapleader = " "
+
+-- Sensible defaults
+vim.opt.encoding = "utf-8"
+vim.opt.mouse = "a" -- Use mouse in terminal
+vim.opt.showcmd = true
+vim.opt.ruler = true -- Show cursor position
+vim.opt.number = true -- Show line numbers
+-- vim.opt.cursorline = true -- Highlight current line
+vim.opt.showmatch = true -- Highlight matching brackets/braces
+vim.opt.matchtime = 5 -- Duration to show matching brace (1/10 sec)
+-- vim.opt.shortmess:append("I") -- Disable intro message / splash screen
+vim.opt.splitbelow = true -- New horizontal splits go below
+vim.opt.splitright = true -- New vertical splits go right
+vim.opt.backspace = { "indent", "eol", "start" } -- Backspace works across lines
+vim.opt.hidden = true -- Allow changing buffers without saving
+vim.opt.errorbells = false
+vim.opt.autoread = true -- Auto read when file changes elsewhere
+vim.opt.autowrite = true -- Auto write before :next, :make, etc.
+vim.opt.cmdheight = 2 -- More space for messages
+vim.opt.scrolloff = 5 -- Start scrolling when within 5 lines near top/bottom
+vim.opt.laststatus = 2 -- Always display the status line
+vim.opt.termguicolors = true -- Use 24-bit colors in the terminal
+vim.opt.updatetime = 200 -- Snappier CursorHold + gitsigns (default 4000)
+vim.opt.timeoutlen = 300 -- Faster which-key / mapped-sequence timeout (default 1000)
+vim.o.winborder = "rounded" -- Consistent border on all floating windows (0.11+)
+vim.opt.splitkeep = "screen" -- Keep text visually stable when opening/closing splits
+-- Unified sign/number/fold/git gutter (snacks.statuscolumn, enabled in ui.lua)
+vim.o.statuscolumn = [[%!v:lua.require'snacks.statuscolumn'.get()]]
+
+-- Bash-like autocomplete for commands
+vim.opt.wildmode = { "list:longest", "list:full" } -- First tab completes longest, second cycles
+vim.opt.wildmenu = true
+
+-- Indentation
+vim.opt.expandtab = true
+vim.opt.tabstop = 4
+vim.opt.softtabstop = 4
+vim.opt.shiftwidth = 4
+vim.opt.autoindent = true
+
+-- Invisibles
+vim.opt.listchars = { tab = "▸ ", eol = "¬", nbsp = "☠", trail = "⍁" }
+vim.opt.list = false
+
+-- Wrapping
+vim.opt.wrap = true
+vim.opt.linebreak = true
+vim.opt.showbreak = "…"
+vim.opt.formatoptions:remove("t")
+-- vim.opt.textwidth = 80
+-- vim.opt.colorcolumn = "80"
+
+-- Autocomplete
+vim.opt.completeopt = { "longest", "preview", "menu" }
+vim.opt.dictionary:remove("/usr/share/dict/words")
+vim.opt.dictionary:append("/usr/share/dict/words")
+vim.opt.thesaurus:remove("/usr/share/dict/mthesaur")
+vim.opt.thesaurus:append("/usr/share/dict/mthesaur")
+
+-- Searching
+vim.opt.ignorecase = true
+vim.opt.smartcase = true
+vim.opt.incsearch = true
+vim.opt.hlsearch = true
+
+-- Folding
+vim.opt.foldmethod = "expr"
+vim.opt.foldexpr = "v:lua.vim.treesitter.foldexpr()"
+vim.opt.foldtext = "v:lua.vim.treesitter.foldtext()"
+vim.opt.foldnestmax = 4
+vim.opt.foldminlines = 3
+vim.opt.foldlevel = 5
+vim.opt.foldtext =
+ [[substitute(getline(v:foldstart),'\\t',repeat('\ ',&tabstop),'g').'…'.trim(getline(v:foldend)) . ' (#' . (v:foldend - v:foldstart + 1) . ' lines)']]
+vim.opt.fillchars = "fold: "
+
+-- Swap and Undo Directories
+local swapdir = os.getenv("XDG_CACHE_HOME") .. "/nvim/swap//"
+if vim.fn.isdirectory(swapdir) == 0 then
+ vim.fn.mkdir(swapdir, "p")
+end
+vim.opt.swapfile = true
+vim.opt.directory = swapdir
+
+local undodir = os.getenv("XDG_CACHE_HOME") .. "/nvim/undo//"
+if vim.fn.isdirectory(undodir) == 0 then
+ vim.fn.mkdir(undodir, "p")
+end
+vim.opt.undofile = true
+vim.opt.undodir = undodir
+
+-- File Search Path
+vim.opt.path = ".,,**"
+
+-- Disable netrw, we're using nvim-tree instead
+vim.g.loaded_netrw = 1
+vim.g.loaded_netrwPlugin = 1
+
+-- Set SHELL command
+if vim.fn.executable("/usr/local/bin/zsh") == 1 then
+ vim.opt.shell = "/usr/local/bin/zsh -l"
+elseif vim.fn.executable("/usr/bin/zsh") == 1 then
+ vim.opt.shell = "/usr/bin/zsh -l"
+end
+
+-- Terminal settings
+vim.g.terminal_scrollback_buffer_size = 10000 -- Store reasonable amount of terminal history
+
+-- Built-in :grep command
+if vim.fn.executable("rg") == 1 then
+ vim.opt.grepprg = "rg --vimgrep --smart-case --no-heading"
+ vim.opt.grepformat = "%f:%l:%c:%m,%f:%l:%m"
+elseif vim.fn.executable("ag") == 1 then
+ vim.opt.grepprg = "ag --vimgrep --smart-case --noheading"
+ vim.opt.grepformat = "%f:%l:%c:%m,%f:%l:%m" -- this has not been tested
+end
+
+------------------------------------------------------
+---- Neovide (GUI) -----------------------------------
+------------------------------------------------------
+
+if vim.g.neovide then
+ -- Put anything you want to happen only in Neovide here
+ -- vim.o.guifont = "LiterationMono Nerd Font Mono:14"
+ -- vim.o.guifont = "SauceCodePro NF:h16"
+ vim.o.guifont = "Agave Nerd Font Mono:h16"
+ -- vim.o.guifont = "SFMono Nerd Font:h16"
+ -- vim.o.guifont = "FiraCode Nerd Font:h16"
+
+ vim.g.neovide_opacity = 0.8
+ vim.g.transparency = 1
+
+ vim.g.neovide_scale_factor = 1.0
+ local change_scale_factor = function(delta)
+ vim.g.neovide_scale_factor = vim.g.neovide_scale_factor * delta
+ end
+ vim.keymap.set("n", "<C-=>", function()
+ change_scale_factor(1.05)
+ end)
+ vim.keymap.set("n", "<C-->", function()
+ change_scale_factor(1 / 1.05)
+ end)
+ vim.keymap.set("n", "<C-0>", function()
+ change_scale_factor(1)
+ end)
+
+ vim.g.neovide_window_blurred = true
+ vim.g.neovide_show_border = false
+ vim.g.neovide_input_macos_option_key_is_meta = "only_left"
+
+ -- vim.g.neovide_cursor_animation_length = 0.150
+ vim.g.neovide_cursor_animation_length = 0.025
+ vim.g.neovide_cursor_short_animation_length = 0.5
+ vim.g.neovide_cursor_trail_size = 0.2
+ vim.g.neovide_cursor_vfx_mode = "pixiedust"
+
+ vim.keymap.set("n", "<D-n>", function()
+ vim.fn.jobstart("neovide --fork", { detach = true })
+ end) -- Open new instance
+ vim.keymap.set("n", "<D-s>", ":w<CR>") -- Save
+ vim.keymap.set("v", "<D-c>", '"+y') -- Copy
+ vim.keymap.set("n", "<D-v>", '"+P') -- Paste normal mode
+ vim.keymap.set("v", "<D-v>", '"+P') -- Paste visual mode
+ vim.keymap.set("c", "<D-v>", "<C-R>+") -- Paste command mode
+ vim.keymap.set("i", "<D-v>", "<C-R>+") -- Paste insert mode
+
+ local dirs = { "/Volumes/Sense/src", "~/src" }
+ for _, dir in ipairs(dirs) do
+ if vim.fn.isdirectory(dir) == 1 then
+ vim.cmd("cd " .. dir)
+ break
+ end
+ end
+end
+
+------------------------------------------------------
+---- lazy.nvim ---------------------------------------
+------------------------------------------------------
+
+-- this will load ~/.config/nvim/lua/config/lazy.lua
+-- Guarded so re-sourcing init.lua (via <Leader><) does not call lazy.setup()
+-- again; only the options/keymaps/autocmds below get re-applied.
+if not vim.g.lazy_bootstrapped then
+ vim.g.lazy_bootstrapped = true
+ require("config.lazy")
+end
+
+------------------------------------------------------
+---- Terminal ----------------------------------------
+------------------------------------------------------
+
+-- Terminal lives under <Leader>; so <Leader>t is free for tests (neotest)
+vim.keymap.set("n", "<Leader>;v", ":vsplit term://$SHELL<CR>i", { desc = "Terminal in vertical split" })
+vim.keymap.set("n", "<Leader>;s", ":belowright split term://$SHELL<CR>i", { desc = "Terminal in horizontal split" })
+-- Toggleable, reusable terminal (snacks) — hide/show without re-spawning
+vim.keymap.set("n", "<Leader>;t", function()
+ Snacks.terminal.toggle()
+end, { desc = "Toggle terminal" })
+vim.keymap.set("t", "<Esc>", "<C-\\><C-n>", { desc = "Exit terminal mode" })
+
+------------------------------------------------------
+---- Running the current file ------------------------
+------------------------------------------------------
+
+-- Run a command in a terminal, optionally in a split: split_mode = "v" | "h" | nil
+local function run_cur(cmd, split_mode)
+ if split_mode == "v" then
+ vim.cmd("vsplit")
+ elseif split_mode == "h" then
+ vim.cmd("split")
+ end
+ vim.cmd("terminal " .. cmd)
+end
+
+-- Determine the interpreter and run the current file
+local function run_cur_self(split_mode)
+ vim.cmd("write") -- Save the file
+
+ local filetype = vim.bo.filetype
+ local interpreter
+
+ if vim.fn.filereadable("run.sh") == 1 then
+ interpreter = "bash run.sh"
+ elseif vim.fn.filereadable("sh/run.sh") == 1 then
+ interpreter = "bash sh/run.sh"
+ elseif filetype == "sh" then
+ interpreter = "bash"
+ elseif filetype:match("javascript") then
+ interpreter = "node"
+ elseif filetype:match("scala") then
+ interpreter = "scala"
+ elseif filetype == "python" then
+ interpreter = "pyexec"
+ elseif filetype:match("lua") then
+ interpreter = "lua"
+ elseif filetype == "make" then
+ run_cur("make", split_mode)
+ -- or, if the file is executable, run it directly
+ elseif vim.fn.executable(vim.fn.expand("%")) == 1 then
+ interpreter = ""
+ else
+ print("No interpreter set for filetype=" .. filetype)
+ return
+ end
+
+ -- Use absolute path and shell-escape the filename
+ local file = vim.fn.shellescape(vim.fn.expand("%:p"))
+ run_cur(interpreter .. " " .. file, split_mode)
+end
+
+vim.keymap.set("n", "<Leader>nn", function()
+ run_cur_self(nil)
+end, { desc = "Run current file" })
+vim.keymap.set("n", "<Leader>nv", function()
+ run_cur_self("v")
+end, { desc = "Run current file (vertical split)" })
+vim.keymap.set("n", "<Leader>ns", function()
+ run_cur_self("h")
+end, { desc = "Run current file (horizontal split)" })
+
+------------------------------------------------------
+---- Diagnostics -------------------------------------
+------------------------------------------------------
+
+vim.diagnostic.config({
+ virtual_lines = { current_line = true }, -- full diagnostic inline, cursor line only
+ virtual_text = false,
+ signs = false,
+ underline = true,
+ update_in_insert = false,
+ severity_sort = true,
+ float = {
+ border = "rounded",
+ source = true, -- always show the source (was the deprecated "always")
+ header = "",
+ prefix = "",
+ },
+})
+local function mapDiagnosticsToggle(key, diagnostic_option)
+ vim.keymap.set("n", key, function()
+ local current_config = vim.diagnostic.config() or {}
+ local new_config = not current_config[diagnostic_option]
+ vim.diagnostic.config({ [diagnostic_option] = new_config })
+ end, { desc = "Toggle diagnostic " .. diagnostic_option })
+end
+mapDiagnosticsToggle("<Leader>dl", "virtual_lines")
+mapDiagnosticsToggle("<Leader>dt", "virtual_text")
+mapDiagnosticsToggle("<Leader>ds", "signs")
+mapDiagnosticsToggle("<Leader>du", "underline")
+vim.keymap.set("n", "<Leader>dd", function()
+ vim.diagnostic.enable(not vim.diagnostic.is_enabled())
+end, { desc = "Toggle diagnostics" })
+vim.keymap.set("n", "<Leader>dq", function()
+ vim.diagnostic.setqflist()
+end, { desc = "Show all diagnostics in quickfix" })
+vim.keymap.set("n", "<Leader>dh", function()
+ vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled({}))
+end, { desc = "Toggle inlay hints" })
+
+------------------------------------------------------
+---- Productivity ------------------------------------
+------------------------------------------------------
+
+vim.keymap.set("n", "<Leader>,", ":e $MYVIMRC<CR>", { noremap = true, silent = true })
+vim.keymap.set("n", "<Leader><", function()
+ -- Re-applies options/keymaps/autocmds. The lazy bootstrap is guarded above,
+ -- so this does not re-run lazy.setup(). For plugin changes use :Lazy reload
+ -- <plugin> or restart.
+ dofile(vim.env.MYVIMRC)
+ vim.notify("Core config reloaded", vim.log.levels.INFO)
+end, { desc = "Reload core config (options/keymaps/autocmds)" })
+vim.keymap.set("n", "<Leader>w", ":update<CR>", { desc = "Save file if modified" })
+vim.keymap.set("n", "<Leader>W", ":write<CR>", { desc = "Save file" })
+vim.keymap.set("n", "<Leader>\\", ":nohl<CR>", { desc = "Clear search highlights" })
+
+-- Quickfix navigation
+vim.keymap.set("n", "]q", ":cnext<CR>", { desc = "Quickfix next" })
+vim.keymap.set("n", "[q", ":cprevious<CR>", { desc = "Quickfix previous" })
+vim.keymap.set("n", "[[", function()
+ local qf_exists = false
+ for _, win in pairs(vim.fn.getwininfo()) do
+ if win["quickfix"] == 1 then
+ qf_exists = true
+ break
+ end
+ end
+ if qf_exists then
+ vim.cmd("cclose")
+ else
+ vim.cmd("copen")
+ end
+end, { desc = "Quickfix toggle" })
+vim.keymap.set("n", "[]", ":copen<CR>", { desc = "Quickfix open" })
+vim.keymap.set("n", "][", ":cclose<CR>", { desc = "Quickfix close" })
+
+-- Location list navigation
+vim.keymap.set("n", "]l", ":lnext<CR>", { noremap = true, silent = true, desc = "Location list next" })
+vim.keymap.set("n", "[l", ":lprevious<CR>", { noremap = true, silent = true, desc = "Location list previous" })
+
+-- Common typing mistakes
+vim.cmd([[cnoreabbrev E e]])
+vim.cmd([[inoreabbrev requrie require]])
+vim.cmd([[inoreabbrev lenght length]])
+
+-- Copy current filename with line number
+vim.keymap.set(
+ "n",
+ "<Leader><Leader>l",
+ ":let @+ = expand('%') . ':' . line('.')<CR>",
+ { noremap = true, silent = true }
+)
+
+-- %% expands to dirname of current file
+vim.cmd([[cnoremap <expr> %% getcmdtype() == ':' ? expand('%:p:h').'/' : '%%']])
+
+-- Redraw screen
+vim.keymap.set("n", "<C-n>", "<C-l>", { noremap = true, silent = true })
+
+-- Disable distracting features in current buffer
+vim.keymap.set("n", "<Leader>C", function()
+ -- Disable gitsigns
+ require("gitsigns").setup({ signcolumn = false })
+
+ -- Disable snacks.indent for current buffer
+ vim.b.snacks_indent = false
+
+ -- Disable line numbers and listchars
+ vim.opt_local.number = false
+ vim.opt_local.list = false
+
+ vim.opt_local.showtabline = 0 -- Hide bufferline
+ -- disable diagnostics
+
+ vim.cmd("nohlsearch") -- cclear search highlight
+ vim.diagnostic.enable(false)
+
+ vim.opt_local.cursorline = false -- Disable cursorline
+
+ require("lualine").hide()
+
+ vim.notify("Disabled distractions in current buffer", vim.log.levels.INFO)
+end, { desc = "Disable distractions" })
+
+------------------------------------------------------
+---- Autocommands ------------------------------------
+------------------------------------------------------
+
+local api = vim.api
+
+-- Primary autocmd group. clear=true means re-sourcing the config replaces these
+-- handlers instead of stacking duplicates.
+local szAutoGroup = api.nvim_create_augroup("szAutoGroup", { clear = true })
+
+-- Trigger autoread when files change on disk
+api.nvim_create_autocmd({ "FocusGained", "BufEnter", "CursorHold", "CursorHoldI" }, {
+ group = szAutoGroup,
+ pattern = "*",
+ command = "if mode() !~ '\\v(c|r.?|!|t)' && getcmdwintype() == '' | checktime | endif",
+})
+-- Notification after file change
+api.nvim_create_autocmd("FileChangedShellPost", {
+ group = szAutoGroup,
+ pattern = "*",
+ callback = function()
+ vim.notify("File changed on disk. Buffer reloaded.", vim.log.levels.WARN)
+ end,
+})
+
+-- Terminal buffers: no line numbers, soft wrap
+api.nvim_create_autocmd("TermOpen", {
+ group = szAutoGroup,
+ pattern = "*",
+ callback = function()
+ vim.opt_local.number = false
+ vim.opt_local.relativenumber = false
+ vim.opt_local.wrap = true
+ end,
+})
+
+-- Project-specific keybindings
+local function project_has_file(filename)
+ return vim.fn.filereadable(vim.fn.getcwd() .. "/" .. filename) == 1
+end
+
+local function setup_project_keybindings()
+ -- Clear any existing project keybindings
+ pcall(vim.keymap.del, "n", "<Leader>cT")
+
+ -- TypeScript compilation: <Leader>cT (requires tsconfig.json)
+ if project_has_file("tsconfig.json") then
+ vim.keymap.set("n", "<Leader>cT", function()
+ vim.opt.makeprg = "npx tsc --noEmit"
+ vim.opt.errorformat = "%f(%l,%c): %trror TS%n: %m,%f(%l,%c): %tarning TS%n: %m"
+ vim.cmd("make!")
+ vim.cmd("copen")
+ end, { desc = "TypeScript compile to quickfix" })
+ end
+end
+
+api.nvim_create_autocmd("VimEnter", {
+ group = szAutoGroup,
+ callback = setup_project_keybindings,
+})
+api.nvim_create_autocmd("DirChanged", {
+ group = szAutoGroup,
+ callback = setup_project_keybindings,
+})
+
+-- Filetype detection & settings
+api.nvim_create_autocmd({ "BufRead", "BufNewFile", "BufEnter" }, {
+ group = szAutoGroup,
+ pattern = "*/.kube/config",
+ command = "setlocal nowrap filetype=yaml",
+})
+api.nvim_create_autocmd({ "BufRead", "BufNewFile", "BufEnter" }, {
+ group = szAutoGroup,
+ pattern = "*gitconfig*,*git/config*",
+ command = "setlocal filetype=gitconfig",
+})
+api.nvim_create_autocmd("FileType", {
+ group = szAutoGroup,
+ pattern = "matlab",
+ command = "setlocal commentstring=%\\ %s",
+})
+api.nvim_create_autocmd("FileType", {
+ group = szAutoGroup,
+ pattern = "tf",
+ command = "setlocal commentstring=#\\ %s",
+})
+api.nvim_create_autocmd("FileType", {
+ group = szAutoGroup,
+ pattern = "tla",
+ command = "setlocal commentstring=\\\\*\\ %s",
+})
+api.nvim_create_autocmd("FileType", {
+ group = szAutoGroup,
+ pattern = "sql",
+ command = "setlocal commentstring=--\\ %s",
+})
+api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ group = szAutoGroup,
+ pattern = "*Dockerfile*",
+ command = "set filetype=Dockerfile",
+})
+api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ group = szAutoGroup,
+ pattern = "*Jenkinsfile*",
+ command = "set filetype=groovy",
+})
+api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ group = szAutoGroup,
+ pattern = "*.keymap",
+ command = "set filetype=dts | setlocal commentstring=//\\ %s",
+})
+api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
+ group = szAutoGroup,
+ pattern = "*.mjml",
+ command = "set filetype=html",
+})
+
+-- Allow comments in JSON files
+api.nvim_create_autocmd("FileType", {
+ group = szAutoGroup,
+ pattern = "json",
+ command = [[syntax match Comment +\/\/.\+$+]],
+})
+
+-- Insert console.log() in JS/TS files
+api.nvim_create_autocmd("FileType", {
+ group = szAutoGroup,
+ pattern = { "javascript", "typescript", "javascriptreact", "typescriptreact" },
+ command = "inoremap <C-l> console.log()<Esc>i",
+})
+
+-- Restore cursor to file position from the previous editing session
+api.nvim_create_autocmd("BufReadPost", {
+ group = szAutoGroup,
+ callback = function(args)
+ local mark = api.nvim_buf_get_mark(args.buf, '"')
+ local line_count = api.nvim_buf_line_count(args.buf)
+ if mark[1] > 0 and mark[1] <= line_count then
+ api.nvim_win_set_cursor(0, mark)
+ -- defer centering slightly so it's applied after render
+ vim.schedule(function()
+ vim.cmd("normal! zz")
+ end)
+ end
+ end,
+})
+
+-- QuickFix window behavior
+api.nvim_create_autocmd("QuickFixCmdPost", {
+ group = szAutoGroup,
+ pattern = "*grep*",
+ command = "cwindow",
+})
+
+-- Only allow a single tab when starting Neovim
+api.nvim_create_autocmd("VimEnter", {
+ group = szAutoGroup,
+ pattern = "*",
+ command = "silent! tabonly",
+})
+
+-- Open help in a vertical split
+api.nvim_create_autocmd("FileType", {
+ group = szAutoGroup,
+ pattern = "help",
+ command = "wincmd L",
+})
+
+-- Auto resize splits when the terminal window is resized
+api.nvim_create_autocmd("VimResized", {
+ group = szAutoGroup,
+ command = "wincmd =",
+})
+
+-- Highlight yanked text
+api.nvim_create_autocmd("TextYankPost", {
+ group = api.nvim_create_augroup("highlight_yank", { clear = true }),
+ pattern = "*",
+ desc = "highlight selection on yank",
+ callback = function()
+ vim.hl.on_yank({ timeout = 200, visual = true })
+ end,
+})
+
+-- Swapfile recovery
+api.nvim_create_autocmd("SwapExists", {
+ group = szAutoGroup,
+ pattern = "*",
+ command = "let b:swapchoice = '?' | let b:swapname = v:swapname",
+})
+api.nvim_create_autocmd("BufReadPost", {
+ group = szAutoGroup,
+ pattern = "*",
+ command = "let b:swapchoice_likely = (&l:ro ? 'o' : 'r')",
+})
+api.nvim_create_autocmd("BufEnter", {
+ group = szAutoGroup,
+ pattern = "*",
+ command = "let b:swapchoice_likely = (&l:ro ? 'o' : 'e')",
+})
+api.nvim_create_autocmd("BufWinEnter", {
+ group = szAutoGroup,
+ pattern = "*",
+ command = [[
+ if exists('b:swapchoice') && exists('b:swapchoice_likely') |
+ let b:swapchoice = b:swapchoice_likely |
+ unlet b:swapchoice_likely |
+ endif
+ ]],
+})
+
+-- Open a diff against the original file version
+local function diff_orig()
+ vim.cmd("vert new")
+ vim.opt_local.buftype = "nofile"
+ vim.cmd("r #")
+ vim.cmd("0d_")
+ vim.cmd("diffthis")
+ vim.cmd("wincmd p")
+ vim.cmd("diffthis")
+end
+-- Handle swap file recovery
+local function handle_recover()
+ vim.fn.system(
+ "diff - " .. vim.fn.shellescape(vim.fn.expand("%:p")),
+ table.concat(vim.fn.getline(1, "$") --[[@as string[] ]], "\n") .. "\n"
+ )
+ if vim.v.shell_error ~= 0 then
+ diff_orig()
+ else
+ vim.fn.delete(vim.b.swapname)
+ vim.notify("No differences; deleting the old swap file.", vim.log.levels.WARN)
+ api.nvim_feedkeys(" ", "n", false)
+ end
+end
+api.nvim_create_autocmd("BufWinEnter", {
+ group = szAutoGroup,
+ pattern = "*",
+ callback = function()
+ if vim.b.swapchoice == "r" then
+ handle_recover()
+ end
+ end,
+})
+
+-- Show cursorline only in the active window
+local active_cursorline = api.nvim_create_augroup("active_cursorline", { clear = true })
+api.nvim_create_autocmd({ "WinEnter", "BufEnter" }, {
+ group = active_cursorline,
+ callback = function()
+ vim.opt_local.cursorline = true
+ end,
+})
+api.nvim_create_autocmd({ "WinLeave", "BufLeave" }, {
+ group = active_cursorline,
+ callback = function()
+ vim.opt_local.cursorline = false
+ end,
+})
+
+-- LSP reference highlighting under the cursor is handled by snacks.words
+-- (configured in lua/plugins/ui.lua), which uses CursorHold rather than firing
+-- an LSP request on every cursor move.
diff --git a/config/nvim/local-plugins/cfg-watch/lua/cfg_watch/init.lua b/config/nvim/local-plugins/cfg-watch/lua/cfg_watch/init.lua
new file mode 100644
index 0000000..3b5c9d6
--- /dev/null
+++ b/config/nvim/local-plugins/cfg-watch/lua/cfg_watch/init.lua
@@ -0,0 +1,58 @@
+local M = {}
+
+local function load_config_path(filepath)
+ local uv = vim.uv or vim.loop
+
+ if uv.fs_stat(filepath) == nil then
+ local dir = filepath:match("(.*/)")
+ if dir and uv.fs_stat(dir) == nil then
+ assert(uv.fs_mkdir(dir, 448)) -- 0o700
+ end
+ local file, err = io.open(filepath, "w")
+ if not file then
+ vim.schedule(function()
+ vim.notify("Error creating file: " .. filepath .. "\n" .. err, vim.log.levels.ERROR)
+ end)
+ return
+ end
+ file:write("-- Created by cfg-watch\n")
+ file:close()
+ else
+ vim.schedule(function()
+ local ok, err = pcall(dofile, filepath)
+ if not ok then
+ vim.schedule(function()
+ vim.notify("Error loading config: " .. filepath .. "\n" .. err, vim.log.levels.ERROR)
+ end)
+ end
+ end)
+ end
+end
+
+function M.load_colors()
+ local filepath = vim.fn.stdpath("data") .. "/colors.lua" -- ~/.local/share/nvim/colors.lua
+ load_config_path(filepath)
+
+ local ok, fwatch = pcall(require, "fwatch")
+ if ok and fwatch and fwatch.once then
+ fwatch.once(filepath, {
+ on_event = function() M.load_colors() end,
+ on_error = function(err)
+ vim.notify("fwatch error: " .. err, vim.log.levels.ERROR)
+ end,
+ })
+ else
+ vim.notify("cfg-watch: fwatch not available", vim.log.levels.ERROR)
+ end
+
+ vim.schedule(function()
+ vim.cmd("hi Normal guibg=NONE ctermbg=NONE")
+ end)
+end
+
+function M.load_config(name)
+ local filepath = vim.fn.stdpath("config") .. "/lua/" .. name .. ".lua"
+ load_config_path(filepath)
+end
+
+return M
diff --git a/config/nvim/lua/.gitignore b/config/nvim/lua/.gitignore
new file mode 100644
index 0000000..8ad8db1
--- /dev/null
+++ b/config/nvim/lua/.gitignore
@@ -0,0 +1 @@
+local.lua
diff --git a/config/nvim/lua/config/lazy.lua b/config/nvim/lua/config/lazy.lua
new file mode 100644
index 0000000..58636f9
--- /dev/null
+++ b/config/nvim/lua/config/lazy.lua
@@ -0,0 +1,37 @@
+-- Check if Lazy.nvim is installed, if not, install it
+local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" -- Lazy.nvim install path
+if not (vim.uv or vim.loop).fs_stat(lazypath) then
+ local lazyrepo = "https://github.com/folke/lazy.nvim.git"
+ local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
+ if vim.v.shell_error ~= 0 then
+ vim.api.nvim_echo({
+ { "Failed to clone lazy.nvim:\n", "ErrorMsg" },
+ { out, "WarningMsg" },
+ { "\nPress any key to exit..." },
+ }, true, {})
+ vim.fn.getchar()
+ os.exit(1)
+ end
+end
+vim.opt.rtp:prepend(lazypath)
+
+-- Load Lazy.nvim and install plugins if necessary
+require("lazy").setup({
+ spec = {
+ -- import plugins from ~/.config/nvim/lua/plugins/
+ -- eg ~/.config/nvim/lua/plugins/completion.lua
+ { import = "plugins" },
+ },
+ install = {
+ -- install missing plugins on startup. This doesn't increase startup time.
+ missing = true,
+ },
+ -- automatically check for plugin updates
+ checker = {
+ enabled = true, -- automatically check for plugin updates
+ concurrency = nil, -- set to 1 to check for updates very slowly
+ notify = false, -- get a notification when new updates are found
+ frequency = 6 * 30 * 24 * 3600, -- check for updates every 6 months
+ check_pinned = false, -- check for pinned packages that can't be updated
+ },
+})
diff --git a/config/nvim/lua/plugins/colorschemes.lua b/config/nvim/lua/plugins/colorschemes.lua
new file mode 100644
index 0000000..005403d
--- /dev/null
+++ b/config/nvim/lua/plugins/colorschemes.lua
@@ -0,0 +1,23 @@
+local plugins = {
+ { "sonph/onehalf", rtp = "vim/" },
+ { "altercation/vim-colors-solarized" },
+ { "nlknguyen/papercolor-theme" },
+ { "tanvirtin/monokai.nvim" },
+ { "tomasr/molokai" },
+ { "cohlin/vim-colorschemes" },
+ { "reedes/vim-colors-pencil" },
+ { "ayu-theme/ayu-vim" },
+ { "overcache/NeoSolarized" },
+ { "cormacrelf/vim-colors-github" },
+ { "rafi/awesome-vim-colorschemes" },
+}
+
+-- These are lazy-loaded: cfg-watch (lua/plugins/reload.lua) lists them as
+-- dependencies, so they are loaded right before ~/.local/share/nvim/colors.lua
+-- runs `:colorscheme`. Keeping them lazy keeps them off the startup path while
+-- still letting the dynamic light/dark theme switcher reach any of them.
+for _, plugin in ipairs(plugins) do
+ plugin.lazy = true
+end
+
+return plugins
diff --git a/config/nvim/lua/plugins/completion.lua b/config/nvim/lua/plugins/completion.lua
new file mode 100644
index 0000000..fa037cd
--- /dev/null
+++ b/config/nvim/lua/plugins/completion.lua
@@ -0,0 +1,159 @@
+local plugins = {
+ -- Autocompletion and Snippets
+ {
+ "Saghen/blink.cmp",
+ version = "1.*", -- Use a stable release version
+ main = "blink.cmp",
+ opts = {
+ keymap = { -- https://cmp.saghen.dev/configuration/keymap.html
+ preset = "default",
+ ["<Tab>"] = { "accept", "fallback" },
+ },
+ sources = {
+ -- Enable completion from lazydev, LSP, filesystem paths, snippets, buffer
+ -- (and optionally supermaven)
+ -- default = { "lazydev", "lsp", "path", "snippets", "buffer", "supermaven" },
+ default = { "lazydev", "lsp", "path", "snippets", "buffer" },
+ providers = {
+ -- Neovim Lua API / plugin spec completions (folke/lazydev.nvim)
+ lazydev = {
+ name = "LazyDev",
+ module = "lazydev.integrations.blink",
+ score_offset = 100, -- show lazydev items above LSP
+ },
+ buffer = {
+ -- Optional: Configure buffer-specific settings
+ -- max_async_buffer_size = 200000, -- 200KB default
+ -- use_cache = true, -- Cache words for better performance
+ },
+ -- lsp = { fallbacks = {} }, -- always show the buffer source
+ supermaven = {
+ enabled = false,
+ name = "supermaven",
+ module = "blink-cmp-supermaven",
+ async = true,
+
+ },
+ },
+ },
+ completion = {
+ -- Enable ghost text to show completions inline
+ ghost_text = {
+ -- enabled = true,
+ enabled = false,
+ },
+ -- Configure the completion menu
+ menu = {
+ auto_show = true, -- Automatically show the menu
+ },
+ list = {
+ selection = {
+ preselect = true,
+ auto_insert = false,
+ },
+ },
+ -- Avoid unnecessary requests for performance
+ trigger = {
+ -- prefetch_on_insert = true, -- When true, will prefetch the completion items when entering insert mode
+ show_in_snippet = false, -- When false, will not show the completion window automatically when in a snippet
+ show_on_backspace = false, -- When true, will show completion window after backspacing
+ show_on_backspace_in_keyword = false, -- When true, will show completion window after backspacing into a keyword
+ show_on_backspace_after_accept = false, -- When true, will show the completion window after accepting a completion and then backspacing into a keyword
+ show_on_backspace_after_insert_enter = false, -- When true, will show the completion window after entering insert mode and backspacing into keyword
+ show_on_keyword = false, -- When true, will show the completion window after typing any of alphanumerics, `-` or `_`
+ show_on_trigger_character = false, -- When true, will show the completion window after typing a trigger character
+ show_on_insert = false, -- When true, will show the completion window after entering insert mode
+ -- LSPs can indicate when to show the completion window via trigger characters
+ -- however, some LSPs (i.e. tsserver) return characters that would essentially
+ -- always show the window. We block these by default.
+ -- show_on_blocked_trigger_characters = { " ", "\n", "\t" },
+ -- You can also block per filetype with a function:
+ -- show_on_blocked_trigger_characters = function(ctx)
+ -- if vim.bo.filetype == 'markdown' then return { ' ', '\n', '\t', '.', '/', '(', '[' } end
+ -- return { ' ', '\n', '\t' }
+ -- end,
+ -- When both this and show_on_trigger_character are true, will show the completion window
+ -- when the cursor comes after a trigger character after accepting an item
+ show_on_accept_on_trigger_character = false,
+ -- When both this and show_on_trigger_character are true, will show the completion window
+ -- when the cursor comes after a trigger character when entering insert mode
+ show_on_insert_on_trigger_character = false,
+ -- List of trigger characters (on top of `show_on_blocked_trigger_characters`) that won't trigger
+ -- the completion window when the cursor comes after a trigger character when
+ -- entering insert mode/accepting an item
+ -- show_on_x_blocked_trigger_characters = { "'", '"', "(" },
+ -- or a function, similar to show_on_blocked_trigger_character
+ },
+ },
+ signature = {
+ enabled = true,
+ window = {
+ show_documentation = false,
+ },
+ },
+ -- Disable blink.cmp for command-line mode to let native wildmenu work
+ cmdline = {
+ enabled = false,
+ },
+ },
+ },
+
+ -- Supermaven was acquired (and killed) by Cursor
+ {
+ "supermaven-inc/supermaven-nvim",
+ event = "InsertEnter",
+ opts = {
+ keymaps = {
+ accept_suggestion = "<Tab>",
+ clear_suggestion = "<C-]>",
+ accept_word = "<C-j>",
+ },
+ ignore_filetypes = {},
+ disable_inline_completion = false,
+ disable_keymaps = false,
+ condition = function()
+ return false
+ end,
+ log_level = "info",
+ },
+ config = function(_, opts)
+ require("supermaven-nvim").setup(opts)
+ -- Clear supermaven ghost text when entering command-line mode
+ -- to prevent interference with wildmenu completion
+ vim.api.nvim_create_autocmd("CmdlineEnter", {
+ callback = function()
+ local ok, preview = pcall(require, "supermaven-nvim.completion_preview")
+ if ok and preview.on_dispose_inlay then
+ preview.on_dispose_inlay()
+ end
+ end,
+ })
+ end,
+ },
+ {
+ "huijiro/blink-cmp-supermaven",
+ },
+
+ -- Lua LSP completion + types for the Neovim API and lazy plugin specs.
+ -- Replaces the old lua_ls `globals` hack for editing this config.
+ {
+ "folke/lazydev.nvim",
+ ft = "lua",
+ opts = {
+ library = {
+ -- Load luvit types when the `vim.uv` word is found
+ { path = "${3rd}/luv/library", words = { "vim%.uv" } },
+ -- Types for snacks.nvim (used throughout this config)
+ { path = "snacks.nvim", words = { "Snacks" } },
+ },
+ },
+ },
+}
+
+for _, plugin in ipairs(plugins) do
+ if not plugin.priority then
+ plugin.priority = 2000
+ end
+end
+
+return plugins
diff --git a/config/nvim/lua/plugins/editing.lua b/config/nvim/lua/plugins/editing.lua
new file mode 100644
index 0000000..af7a867
--- /dev/null
+++ b/config/nvim/lua/plugins/editing.lua
@@ -0,0 +1,127 @@
+local plugins = {
+ { "godlygeek/tabular" }, -- text filtering and alignment
+ {
+ "Wansmer/treesj", -- splitting/joining blocks of code
+ dependencies = { "nvim-treesitter/nvim-treesitter" },
+ config = function()
+ require("treesj").setup({
+ -- use_default_keymaps = false,
+ })
+ vim.keymap.set("n", "<leader>jj", require("treesj").join)
+ vim.keymap.set("n", "<leader>js", require("treesj").split)
+ vim.keymap.set("n", "<leader>jm", require("treesj").toggle)
+ vim.keymap.set("n", "<leader>jM", function()
+ require("treesj").toggle({ split = { recursive = true } })
+ end)
+ end,
+ },
+ { "tpope/vim-repeat" }, -- enable plugins to repeat actions
+ { "tpope/vim-sleuth" }, -- heuristically set buffer options
+ -- Add/delete/replace surrounding pairs: sa (add), sd (delete), sr (replace).
+ -- Replaces tpope/vim-surround; consolidates onto mini.* with built-in
+ -- dot-repeat. NOTE: the `s` prefix shadows native `s` (substitute) — use `cl`.
+ {
+ "nvim-mini/mini.surround",
+ event = "VeryLazy",
+ opts = {},
+ },
+ -- Auto close/rename HTML/JSX/TSX/Astro tags
+ {
+ "windwp/nvim-ts-autotag",
+ ft = {
+ "html", "xml", "markdown", "svelte", "vue",
+ "javascript", "javascriptreact", "typescript", "typescriptreact", "astro",
+ },
+ opts = {},
+ },
+ -- Context-aware commentstring (e.g. {/* */} inside JSX, not //)
+ {
+ "folke/ts-comments.nvim",
+ event = "VeryLazy",
+ opts = {},
+ },
+ -- Extended text objects: af/if (function), ac/ic (class),
+ -- ao/io (block/conditional/loop), plus args/quotes/brackets.
+ -- Takes over the old fold if/af mappings.
+ {
+ "nvim-mini/mini.ai",
+ event = "VeryLazy",
+ dependencies = {
+ "nvim-treesitter/nvim-treesitter-textobjects",
+ },
+ opts = function()
+ local ai = require("mini.ai")
+ return {
+ n_lines = 500,
+ custom_textobjects = {
+ f = ai.gen_spec.treesitter({ a = "@function.outer", i = "@function.inner" }),
+ c = ai.gen_spec.treesitter({ a = "@class.outer", i = "@class.inner" }),
+ o = ai.gen_spec.treesitter({
+ a = { "@block.outer", "@conditional.outer", "@loop.outer" },
+ i = { "@block.inner", "@conditional.inner", "@loop.inner" },
+ }),
+ },
+ }
+ end,
+ },
+ -- Treesitter textobjects (main): move between and swap functions/classes/args,
+ -- and make ; , (and f F t T) repeat the last move.
+ {
+ "nvim-treesitter/nvim-treesitter-textobjects",
+ branch = "main",
+ event = "VeryLazy",
+ config = function()
+ require("nvim-treesitter-textobjects").setup({
+ move = { set_jumps = true }, -- record moves in the jumplist
+ })
+
+ local move = require("nvim-treesitter-textobjects.move")
+ local swap = require("nvim-treesitter-textobjects.swap")
+ local rep = require("nvim-treesitter-textobjects.repeatable_move")
+
+ -- Repeat the last textobject move with ; and , ...
+ vim.keymap.set({ "n", "x", "o" }, ";", rep.repeat_last_move)
+ vim.keymap.set({ "n", "x", "o" }, ",", rep.repeat_last_move_opposite)
+ -- ... and upgrade f/F/t/T to repeat with the same ; ,
+ vim.keymap.set({ "n", "x", "o" }, "f", rep.builtin_f_expr, { expr = true })
+ vim.keymap.set({ "n", "x", "o" }, "F", rep.builtin_F_expr, { expr = true })
+ vim.keymap.set({ "n", "x", "o" }, "t", rep.builtin_t_expr, { expr = true })
+ vim.keymap.set({ "n", "x", "o" }, "T", rep.builtin_T_expr, { expr = true })
+
+ -- Jump between functions / classes ( ]] [[ are taken by quickfix ).
+ -- Note: ]c / [c shadow diff-mode change navigation; swap to ]C/[C if
+ -- you'd rather keep that.
+ local jumps = {
+ ["]f"] = { move.goto_next_start, "@function.outer", "Next function start" },
+ ["[f"] = { move.goto_previous_start, "@function.outer", "Prev function start" },
+ ["]F"] = { move.goto_next_end, "@function.outer", "Next function end" },
+ ["[F"] = { move.goto_previous_end, "@function.outer", "Prev function end" },
+ ["]c"] = { move.goto_next_start, "@class.outer", "Next class start" },
+ ["[c"] = { move.goto_previous_start, "@class.outer", "Prev class start" },
+ }
+ for lhs, m in pairs(jumps) do
+ vim.keymap.set({ "n", "x", "o" }, lhs, function()
+ m[1](m[2], "textobjects")
+ end, { desc = m[3] })
+ end
+
+ -- Swap the argument under the cursor with the next / previous one
+ vim.keymap.set("n", "<leader>sa", function()
+ swap.swap_next("@parameter.inner")
+ end, { desc = "Swap argument with next" })
+ vim.keymap.set("n", "<leader>sA", function()
+ swap.swap_previous("@parameter.inner")
+ end, { desc = "Swap argument with previous" })
+ end,
+ },
+ -- Comment.nvim and nvim-ts-context-commentstring removed
+ -- Neovim 0.10+ has built-in gc/gcc commenting with native treesitter commentstring support
+}
+
+for _, plugin in ipairs(plugins) do
+ if not plugin.priority then
+ plugin.priority = 2000
+ end
+end
+
+return plugins
diff --git a/config/nvim/lua/plugins/editor.lua b/config/nvim/lua/plugins/editor.lua
new file mode 100644
index 0000000..4e1cb4d
--- /dev/null
+++ b/config/nvim/lua/plugins/editor.lua
@@ -0,0 +1,29 @@
+return {
+ -- Highlight and navigate TODO / FIXME / HACK / BUG comments
+ {
+ "folke/todo-comments.nvim",
+ event = "VeryLazy",
+ dependencies = { "nvim-lua/plenary.nvim" },
+ opts = {},
+ keys = {
+ { "]t", function() require("todo-comments").jump_next() end, desc = "Next todo comment" },
+ { "[t", function() require("todo-comments").jump_prev() end, desc = "Previous todo comment" },
+ { "<leader>qt", "<cmd>TodoTrouble<cr>", desc = "Todo (Trouble)" },
+ },
+ },
+ -- Pretty, navigable lists for diagnostics, references, symbols, quickfix.
+ -- Lives under <leader>q ("lists") to avoid the <leader>x buffer-close prefix.
+ {
+ "folke/trouble.nvim",
+ cmd = "Trouble",
+ dependencies = { "nvim-tree/nvim-web-devicons" },
+ opts = {},
+ keys = {
+ { "<leader>qd", "<cmd>Trouble diagnostics toggle<cr>", desc = "Diagnostics (Trouble)" },
+ { "<leader>qb", "<cmd>Trouble diagnostics toggle filter.buf=0<cr>", desc = "Buffer diagnostics (Trouble)" },
+ { "<leader>qs", "<cmd>Trouble symbols toggle<cr>", desc = "Symbols (Trouble)" },
+ { "<leader>ql", "<cmd>Trouble lsp toggle<cr>", desc = "LSP defs/refs (Trouble)" },
+ { "<leader>qq", "<cmd>Trouble qflist toggle<cr>", desc = "Quickfix list (Trouble)" },
+ },
+ },
+}
diff --git a/config/nvim/lua/plugins/fzf.lua b/config/nvim/lua/plugins/fzf.lua
new file mode 100644
index 0000000..8ef0104
--- /dev/null
+++ b/config/nvim/lua/plugins/fzf.lua
@@ -0,0 +1,148 @@
+return {
+ {
+ "ibhagwan/fzf-lua",
+ dependencies = { "nvim-tree/nvim-web-devicons" },
+ config = function()
+ local fzf = require("fzf-lua")
+ fzf.setup({
+ defaults = {
+ file_icons = true,
+ },
+ grep = {
+ rg_opts = table.concat({
+ "--color=never",
+ "--no-heading",
+ "--with-filename",
+ "--line-number",
+ "--column",
+ "--smart-case",
+ "--glob=!.git/**",
+ "--glob=!package-lock.json",
+ "--glob=!lazy-lock.json",
+ "--glob=!node_modules/**",
+ }, " "),
+ },
+ keymap = {
+ builtin = {
+ ["<C-h>"] = "toggle-help",
+ },
+ fzf = {
+ ["ctrl-q"] = "select-all+accept",
+ },
+ },
+ })
+
+ -- General pickers
+ vim.keymap.set("n", "<Leader>f", fzf.resume, { desc = "Resume search" })
+ vim.keymap.set("n", "<C-p>", fzf.files, { desc = "Find file" })
+ vim.keymap.set("n", "<Leader>a", fzf.live_grep, { desc = "Find text" })
+ vim.keymap.set("n", "\\b", fzf.buffers, { desc = "Find buffer" })
+ vim.keymap.set("n", "\\h", fzf.help_tags, { desc = "Find help" })
+ vim.keymap.set("n", "\\k", fzf.keymaps, { desc = "Find keymaps" })
+ vim.keymap.set("n", "<Leader><Leader>t", fzf.colorschemes, { desc = "Select colorscheme" })
+
+ -- Git
+ vim.keymap.set("n", "\\g", fzf.git_status, { desc = "Find changed file" })
+
+ -- LSP pickers
+ vim.keymap.set("n", "\\r", function()
+ fzf.lsp_references({ includeDeclaration = false })
+ end, { desc = "Find references" })
+ vim.keymap.set("n", "\\c", fzf.lsp_incoming_calls, { desc = "Find incoming calls" })
+ vim.keymap.set("n", "\\o", fzf.lsp_outgoing_calls, { desc = "Find outgoing calls" })
+ vim.keymap.set("n", "\\d", fzf.lsp_definitions, { desc = "Find definitions" })
+ vim.keymap.set("n", "\\t", fzf.lsp_typedefs, { desc = "Find type definitions" })
+ vim.keymap.set("n", "\\i", fzf.lsp_implementations, { desc = "Find implementations" })
+ vim.keymap.set("n", "\\s", fzf.lsp_document_symbols, { desc = "Find document symbols" })
+ vim.keymap.set("n", "\\S", fzf.lsp_live_workspace_symbols, { desc = "Find workspace symbols" })
+
+ -- Search nvim config
+ vim.keymap.set("n", "\\C", function()
+ fzf.live_grep({
+ cwd = vim.fn.expand("~/.config/nvim"),
+ rg_opts = "--follow " .. table.concat({
+ "--color=never",
+ "--no-heading",
+ "--with-filename",
+ "--line-number",
+ "--column",
+ "--smart-case",
+ }, " "),
+ })
+ end, { desc = "Search config" })
+
+ -- Custom directory picker
+ local function reset_lsp_workspace_folders()
+ local clients = vim.lsp.get_clients()
+ if #clients == 0 then
+ vim.notify("No active LSP clients found", vim.log.levels.WARN)
+ return
+ end
+ for _, client in ipairs(clients) do
+ local current_folders = client.workspace_folders or {}
+ for _, folder in ipairs(current_folders) do
+ vim.lsp.buf.remove_workspace_folder(folder.name)
+ end
+ vim.lsp.buf.add_workspace_folder(vim.fn.getcwd())
+ vim.notify(
+ string.format("Reset workspace folders for %s to: %s", client.name, vim.fn.getcwd()),
+ vim.log.levels.INFO
+ )
+ end
+ end
+
+ local function build_find_dir_cmd()
+ local home = vim.fn.expand("~")
+ local homeSrc = vim.fn.expand("~/src")
+ local sense = "/Volumes/Sense"
+ local cmd = "find"
+ local search_paths = {}
+ local excluded_dirs = {
+ "*/cloud-copy*",
+ "*/node_modules*",
+ "*/.venv*",
+ "*/venv*",
+ }
+ if vim.fn.isdirectory(sense) == 1 then
+ table.insert(search_paths, sense)
+ table.insert(excluded_dirs, homeSrc)
+ else
+ table.insert(search_paths, homeSrc)
+ end
+ table.insert(search_paths, vim.fn.expand("~/.local/share/nvim"))
+
+ local args = { cmd }
+ for _, path in ipairs(search_paths) do
+ table.insert(args, path)
+ end
+ table.insert(args, "-type")
+ table.insert(args, "d")
+ table.insert(args, "-maxdepth")
+ table.insert(args, "4")
+ for _, dir in ipairs(excluded_dirs) do
+ table.insert(args, "-not")
+ table.insert(args, "-path")
+ table.insert(args, dir)
+ end
+ return table.concat(args, " ")
+ end
+
+ vim.keymap.set("n", "<Leader>p", function()
+ fzf.fzf_exec(build_find_dir_cmd(), {
+ prompt = "Change Directory> ",
+ actions = {
+ ["default"] = function(selected)
+ local dir = selected[1]
+ vim.cmd("cd " .. vim.fn.fnameescape(dir))
+ vim.opt.titlestring = vim.fn.fnamemodify(dir, ":t")
+ vim.opt.title = true
+ reset_lsp_workspace_folders()
+ vim.cmd("LspRestart")
+ print("Changed directory to: " .. dir)
+ end,
+ },
+ })
+ end, { desc = "Change working directory" })
+ end,
+ },
+}
diff --git a/config/nvim/lua/plugins/git.lua b/config/nvim/lua/plugins/git.lua
new file mode 100644
index 0000000..ad120f4
--- /dev/null
+++ b/config/nvim/lua/plugins/git.lua
@@ -0,0 +1,63 @@
+return {
+ {
+ "lewis6991/gitsigns.nvim",
+ config = function()
+ -- Git hunk navigation
+ local gs = require("gitsigns")
+ gs.setup({
+ signs_staged_enable = true,
+ signcolumn = true, -- Toggle with `:Gitsigns toggle_signs`
+ numhl = true, -- Toggle with `:Gitsigns toggle_numhl`
+ linehl = false, -- Toggle with `:Gitsigns toggle_linehl`
+ word_diff = false, -- Toggle with `:Gitsigns toggle_word_diff`
+ current_line_blame_opts = {
+ virt_text_pos = 'right_align', -- 'eol' | 'overlay' | 'right_align'
+ },
+ -- current_line_blame = true,
+ -- current_line_blame_formatter = '<author>, <author_time:%R> - <summary>',
+ })
+ vim.keymap.set("n", "[g", gs.prev_hunk, { desc = "Goto previous git hunk" })
+ vim.keymap.set("n", "]g", gs.next_hunk, { desc = "Goto next git hunk" })
+
+ -- vim.keymap.set("n", "<Leader>cg", gitsigns.preview_hunk, { desc = "Preview git hunk" })
+ vim.keymap.set("n", "<Leader>gd", function() gs.preview_hunk_inline() end, { desc = "Preview hunk inline" })
+ vim.keymap.set("n", "<Leader>gD", function() gs.preview_hunk() end, { desc = "Preview hunk popup" })
+ vim.keymap.set("n", "<Leader>gf", function() gs.diffthis() end, { desc = "Diff file" })
+ vim.keymap.set("n", "<Leader>gr", function() gs.reset_hunk() end, { desc = "Revert hunk" })
+ vim.keymap.set("n", "<Leader>gs", function() gs.stage_hunk() end, { desc = "Stage hunk" })
+ vim.keymap.set("n", "<Leader>ga", function() gs.stage_buffer() end, { desc = "Stage buffer" })
+ vim.keymap.set("n", "<Leader>gB", function() gs.toggle_current_line_blame() end, { desc = "Toggle current line blame (virtual text)" })
+ vim.keymap.set("n", "<Leader>gb", function() gs.blame() end, { desc = "Open blame" })
+
+ -- Select hunks as a text object.
+ vim.keymap.set({'o', 'x'}, 'ih', '<Cmd>Gitsigns select_hunk<CR>')
+
+ -- Open current file/line (or visual selection) on the git remote
+ vim.keymap.set({ "n", "x" }, "<Leader>go", function() Snacks.gitbrowse() end, { desc = "Open in remote (browser)" })
+ end,
+ },
+ -- Lazy-loaded: kept only for its `:Git <arbitrary>` escape hatch and :Gblame,
+ -- since neogit + diffview + gitsigns cover day-to-day staging/diff/blame.
+ {
+ "tpope/vim-fugitive",
+ cmd = { "Git", "G", "Gdiffsplit", "Gread", "Gwrite", "Gedit", "Gblame", "Gclog" },
+ },
+ {
+ "NeogitOrg/neogit",
+ lazy = true,
+ dependencies = {
+ "nvim-lua/plenary.nvim", -- required
+ "sindrets/diffview.nvim", -- optional - Diff integration
+
+ -- Only one of these is needed.
+ "ibhagwan/fzf-lua", -- optional
+ -- "nvim-telescope/telescope.nvim", -- optional
+ -- "nvim-mini/mini.pick", -- optional
+ -- "folke/snacks.nvim", -- optional
+ },
+ cmd = "Neogit",
+ keys = {
+ { "<leader>gg", "<cmd>Neogit<cr>", desc = "Show Neogit UI" }
+ }
+ },
+}
diff --git a/config/nvim/lua/plugins/lsp.lua b/config/nvim/lua/plugins/lsp.lua
new file mode 100644
index 0000000..c3853b3
--- /dev/null
+++ b/config/nvim/lua/plugins/lsp.lua
@@ -0,0 +1,281 @@
+-- LSP attach handler using LspAttach autocmd (NeoVim 0.11+ recommended pattern)
+vim.api.nvim_create_autocmd('LspAttach', {
+ callback = function(args)
+ local client = vim.lsp.get_client_by_id(args.data.client_id)
+ local bufnr = args.buf
+
+ -- Enable completion triggered by <c-x><c-o>
+ vim.bo[bufnr].omnifunc = "v:lua.vim.lsp.omnifunc"
+
+ vim.keymap.set("n", "K", function() vim.lsp.buf.hover() end, { buffer = bufnr, desc = "Hover info" })
+ vim.keymap.set("n", "<c-k>", function() vim.lsp.buf.signature_help() end, { buffer = bufnr, desc = "Signature help" })
+ vim.keymap.set("n", "<Leader>L", function() vim.lsp.buf.format({ async = true }) end, { buffer = bufnr, desc = "LSP format" })
+
+ -- TypeScript/JavaScript specific: organize imports
+ if client and client.name == "ts_ls" then
+ vim.keymap.set("n", "gro", function()
+ client:exec_cmd({
+ command = "_typescript.organizeImports",
+ arguments = { vim.api.nvim_buf_get_name(0) },
+ title = "",
+ })
+ end, { buffer = bufnr, desc = "Organize imports" })
+ end
+ end,
+})
+
+local function configure_lsp() -- Setup LSP configurations using vim.lsp.config (NeoVim 0.11+)
+ -- Completion capabilities from blink.cmp (extends Neovim's defaults with the
+ -- snippet/resolve/tag support that the hand-rolled table used to set).
+ local capabilities = require("blink.cmp").get_lsp_capabilities()
+
+ -- Global config for all LSP servers
+ vim.lsp.config('*', {
+ capabilities = capabilities,
+ })
+
+ -- Configure lua-language-server
+ vim.lsp.config('lua_ls', {
+ settings = {
+ Lua = {
+ diagnostics = {
+ -- `vim`/`require` are covered by lazydev.nvim + LuaLS defaults.
+ -- `hs` is the Hammerspoon global, which lazydev doesn't know about.
+ globals = {
+ "hs",
+ },
+ disable = {
+ "unused-local",
+ },
+ },
+ },
+ },
+ })
+
+ -- Configure bash-language-server
+ vim.lsp.config('bashls', {
+ settings = {
+ bashIde = {
+ enableExplainshellEndpoint = false,
+ explainshellEndpoint = "",
+ globPattern = "*@(.sh|.bash|.zsh|bash*|zsh*)",
+ shfmt = {
+ path = "shfmt",
+ args = { "-i", "2", "-ci", "-sr" },
+ },
+ },
+ },
+ })
+
+ -- Configure typescript-language-server
+ vim.lsp.config('ts_ls', {})
+
+ -- Configure astro-language-server
+ vim.lsp.config('astro', {})
+
+ -- Configure gopls (Go language server)
+ vim.lsp.config('gopls', {
+ settings = {
+ gopls = {
+ analyses = {
+ unusedparams = true,
+ },
+ staticcheck = true,
+ gofumpt = true,
+ },
+ },
+ })
+
+ -- Configure pyright (Python language server)
+ vim.lsp.config('pyright', {
+ settings = {
+ python = {
+ analysis = {
+ autoSearchPaths = true,
+ useLibraryCodeForTypes = true,
+ diagnosticMode = "workspace",
+ },
+ },
+ },
+ })
+
+ -- Configure jdtls (Java language server)
+ vim.lsp.config('jdtls', {})
+
+ -- Enable all configured servers
+ -- Note: This doesn't force installation, it just enables them if installed
+ vim.lsp.enable({ 'lua_ls', 'bashls', 'ts_ls', 'astro', 'gopls', 'pyright', 'jdtls' })
+
+ -- ESLint LSP is temporarily disabled (causes errors with call hierarchy)
+ -- To re-enable, remove the line below and add 'eslint' to vim.lsp.enable()
+ vim.lsp.enable('eslint', false)
+end
+
+return {
+ {
+ "nvim-treesitter/nvim-treesitter",
+ branch = "main", -- Use main branch for Neovim 0.12+ compatibility
+ build = ":TSUpdate",
+ config = function()
+ -- Add nvim-treesitter's runtime directory to runtimepath for queries (folds, indents, etc.)
+ local ts_runtime = vim.fn.stdpath("data") .. "/lazy/nvim-treesitter/runtime"
+ if vim.fn.isdirectory(ts_runtime) == 1 then
+ vim.opt.runtimepath:prepend(ts_runtime)
+ end
+
+ -- The `main` branch has no `ensure_installed` option, so we install any
+ -- missing parsers ourselves. Add/remove languages here as needed.
+ local TS = require("nvim-treesitter")
+ local ensure_installed = {
+ "astro", "go", "html", "javascript", "lua", "python", "tsx", "typescript",
+ }
+ local installed = TS.get_installed("parsers")
+ local to_install = vim.tbl_filter(function(lang)
+ return not vim.tbl_contains(installed, lang)
+ end, ensure_installed)
+ if #to_install > 0 then
+ TS.install(to_install, { summary = true })
+ end
+
+ -- On the `main` branch, treesitter highlighting must be started per
+ -- buffer (it is NOT automatic). Start it whenever a parser is available.
+ vim.api.nvim_create_autocmd("FileType", {
+ group = vim.api.nvim_create_augroup("sz_treesitter_start", { clear = true }),
+ callback = function(args)
+ pcall(vim.treesitter.start, args.buf)
+ end,
+ })
+
+ -- Show all available code actions (including organize imports)
+ vim.keymap.set(
+ "n",
+ "<leader>ca",
+ vim.lsp.buf.code_action,
+ { noremap = true, silent = true, desc = "Show code actions" }
+ )
+ vim.keymap.set("v", "<C-k>", vim.lsp.buf.code_action,
+ { noremap = true, silent = true, desc = "Show code actions" })
+ end,
+ },
+ {
+ "williamboman/mason.nvim",
+ main = "mason",
+ opts = {
+ registries = {
+ "github:mason-org/mason-registry",
+ "github:Crashdummyy/mason-registry", -- Roslyn LSP
+ },
+ },
+ },
+ {
+ "williamboman/mason-lspconfig.nvim",
+ main = "mason-lspconfig",
+ opts = {
+ -- Only ensure core language servers are installed on ALL machines
+ -- Other servers will be auto-installed when you open a file of that type
+ ensure_installed = { "lua_ls", "bashls" },
+ },
+ },
+ {
+ "WhoIsSethDaniel/mason-tool-installer.nvim",
+ dependencies = { "williamboman/mason.nvim" },
+ config = function()
+ require("mason-tool-installer").setup({
+ -- Core tools - always installed
+ ensure_installed = {
+ "stylua", -- Lua formatting
+ "shfmt", -- Bash formatting
+ "prettier", -- JS/TS/Astro formatting
+ },
+ -- Optional tools - install with: :MasonToolsInstall
+ -- Or add them here when working on those projects
+ -- "black", -- Python formatting
+ -- "isort", -- Python imports sorting
+ -- "gofumpt", -- Go formatting (stricter than gofmt)
+ -- "goimports", -- Go imports formatting
+ -- "csharpier", -- C# formatting
+ -- "netcoredbg", -- C#/.NET debugging
+ -- "google-java-format", -- Java formatting
+ })
+ end,
+ },
+ {
+ "neovim/nvim-lspconfig",
+ config = configure_lsp,
+ },
+ {
+ "seblj/roslyn.nvim",
+ ft = "cs",
+ dependencies = {
+ "nvim-lua/plenary.nvim",
+ },
+ opts = {
+ filewatching = "roslyn",
+ -- Disable Razor extension (only needed for Blazor)
+ razor = {
+ enabled = false,
+ },
+ config = {
+ settings = {
+ ["csharp|inlay_hints"] = {
+ csharp_enable_inlay_hints_for_implicit_object_creation = true,
+ csharp_enable_inlay_hints_for_implicit_variable_types = true,
+ csharp_enable_inlay_hints_for_lambda_parameter_types = true,
+ csharp_enable_inlay_hints_for_types = true,
+ dotnet_enable_inlay_hints_for_indexer_parameters = true,
+ dotnet_enable_inlay_hints_for_literal_parameters = true,
+ dotnet_enable_inlay_hints_for_object_creation_parameters = true,
+ dotnet_enable_inlay_hints_for_other_parameters = true,
+ dotnet_enable_inlay_hints_for_parameters = true,
+ dotnet_suppress_inlay_hints_for_parameters_that_differ_only_by_suffix = true,
+ dotnet_suppress_inlay_hints_for_parameters_that_match_argument_name = true,
+ dotnet_suppress_inlay_hints_for_parameters_that_match_method_intent = true,
+ },
+ ["csharp|completion"] = {
+ dotnet_show_completion_items_from_unimported_namespaces = true,
+ dotnet_show_name_completion_suggestions = true,
+ },
+ ["csharp|code_lens"] = {
+ dotnet_enable_references_code_lens = true,
+ dotnet_enable_tests_code_lens = true,
+ },
+ },
+ },
+ },
+ },
+ {
+ "stevearc/conform.nvim",
+ event = { "BufReadPre", "BufNewFile" },
+ config = function()
+ local conform = require("conform")
+ conform.setup({
+ formatters_by_ft = {
+ astro = { "prettier" },
+ bash = { "shfmt" },
+ cs = { "csharpier" },
+ css = { "prettier" },
+ go = { "goimports", "gofumpt" }, -- Run goimports then gofumpt
+ html = { "prettier" },
+ java = { "google-java-format" },
+ javascript = { "prettier" },
+ javascriptreact = { "prettier" },
+ json = { "prettier" },
+ lua = { "stylua" },
+ markdown = { "prettier" },
+ python = { "isort", "black" }, -- Run isort then black
+ typescript = { "prettier" },
+ typescriptreact = { "prettier" },
+ },
+ -- Set to `true` to format on save, or provide a function for conditional formatting
+ -- format_on_save = {
+ -- timeout_ms = 500,
+ -- lsp_fallback = true, -- Use LSP formatting as a fallback if no external formatter is found
+ -- },
+ })
+
+ vim.keymap.set({ "n", "v" }, "<leader>l", function()
+ conform.format({ async = true, lsp_format = "fallback" })
+ end, { desc = "Format file or range" })
+ end,
+ },
+}
diff --git a/config/nvim/lua/plugins/reload.lua b/config/nvim/lua/plugins/reload.lua
new file mode 100644
index 0000000..6bd1366
--- /dev/null
+++ b/config/nvim/lua/plugins/reload.lua
@@ -0,0 +1,28 @@
+return {
+ {
+ dir = vim.fn.stdpath("config") .. "/local-plugins/cfg-watch",
+ name = "cfg-watch",
+ event = "VeryLazy", -- fires after init.lua finishes and UI is ready
+ -- Colorscheme plugins (defined in lua/plugins/colorschemes.lua as lazy=true)
+ -- are listed here so they load before load_colors() runs `:colorscheme`.
+ dependencies = {
+ { "rktjmp/fwatch.nvim" },
+ "sonph/onehalf",
+ "altercation/vim-colors-solarized",
+ "nlknguyen/papercolor-theme",
+ "tanvirtin/monokai.nvim",
+ "tomasr/molokai",
+ "cohlin/vim-colorschemes",
+ "reedes/vim-colors-pencil",
+ "ayu-theme/ayu-vim",
+ "overcache/NeoSolarized",
+ "cormacrelf/vim-colors-github",
+ "rafi/awesome-vim-colorschemes",
+ },
+ config = function()
+ local lc = require("cfg_watch")
+ lc.load_colors()
+ lc.load_config("local")
+ end,
+ },
+}
diff --git a/config/nvim/lua/plugins/testing.lua b/config/nvim/lua/plugins/testing.lua
new file mode 100644
index 0000000..c402bb1
--- /dev/null
+++ b/config/nvim/lua/plugins/testing.lua
@@ -0,0 +1,41 @@
+return {
+ {
+ "nvim-neotest/neotest",
+ dependencies = {
+ "nvim-neotest/nvim-nio",
+ "nvim-lua/plenary.nvim",
+ "nvim-treesitter/nvim-treesitter",
+ "nvim-neotest/neotest-jest",
+ -- "marilari88/neotest-vitest",
+ },
+ cmd = { "Neotest" },
+ keys = {
+ { "<Leader>ta", function() require("neotest").run.run(vim.fn.getcwd()) end, desc = "Run all tests" },
+ { "<Leader>tf", function() require("neotest").run.run(vim.fn.expand("%")) end, desc = "Run tests in file" },
+ { "<Leader>tn", function() require("neotest").run.run() end, desc = "Run nearest test" },
+ { "<Leader>to", function() require("neotest").output.open({ enter = true }) end, desc = "Show test output" },
+ { "<Leader>tS", function() require("neotest").summary.toggle() end, desc = "Toggle test summary" },
+ },
+ config = function()
+ require("neotest").setup({
+ adapters = {
+ require("neotest-jest")({
+ jestCommand = "npm test --",
+ jestArguments = function(defaultArguments, context)
+ return defaultArguments
+ end,
+ -- jestConfigFile = "custom.jest.config.ts",
+ -- env = { CI = true },
+ cwd = function(path)
+ return vim.fn.getcwd()
+ end,
+ filter_dir = function(name, rel_path, root)
+ return name ~= "node_modules" and name ~= "dist" and name ~= "build"
+ end,
+ }),
+ -- require("neotest-vitest"),
+ },
+ })
+ end,
+ },
+}
diff --git a/config/nvim/lua/plugins/ui.lua b/config/nvim/lua/plugins/ui.lua
new file mode 100644
index 0000000..d035789
--- /dev/null
+++ b/config/nvim/lua/plugins/ui.lua
@@ -0,0 +1,335 @@
+return {
+ {
+ "folke/snacks.nvim",
+ priority = 1000,
+ lazy = false,
+ ---@type snacks.Config
+ opts = {
+ indent = {
+ enabled = true,
+ animate = {
+ enabled = false,
+ },
+ },
+ -- Disable expensive features on very large / minified files
+ bigfile = { enabled = true },
+ -- Render a file before plugins fully load, for faster opening
+ quickfile = { enabled = true },
+ -- LSP reference highlighting under the cursor (CursorHold-based);
+ -- replaces the hand-rolled CursorMoved autocmd in init.lua
+ words = { enabled = true },
+ -- Treesitter scope detection: ii/ai indent text-objects + [i/]i jumps
+ scope = { enabled = true },
+ -- Unified gutter (signs+number+fold+git). The statuscolumn option
+ -- itself is set in init.lua.
+ statuscolumn = { enabled = true },
+ },
+ keys = {
+ { "<Leader>it", function()
+ if Snacks.indent.enabled then
+ Snacks.indent.disable()
+ else
+ Snacks.indent.enable()
+ end
+ end, desc = "Toggle indent guides" },
+ },
+ },
+ {
+ "nvim-lualine/lualine.nvim",
+ config = function()
+ local enable_web_dev_icons = vim.g.enable_web_dev_icons ~= 0
+ local lualine_custom_theme = require("lualine.themes.auto")
+ lualine_custom_theme.normal.c.bg = "none"
+ require("lualine").setup({
+ options = {
+ icons_enabled = enable_web_dev_icons,
+ theme = lualine_custom_theme,
+ component_separators = "|",
+ section_separators = { left = "", right = "" },
+ disabled_filetypes = {},
+ always_divide_middle = true,
+ globalstatus = true,
+ },
+ sections = {
+ lualine_a = { "mode" },
+ lualine_b = { "branch", "diff", "diagnostics" },
+ lualine_c = { "filename" },
+ lualine_x = { "encoding", "fileformat", "filetype" },
+ lualine_y = {
+ function()
+ return vim.fn.fnamemodify(vim.fn.getcwd(), ":t")
+ end,
+ },
+ lualine_z = { "progress", "location" },
+ },
+ inactive_sections = {
+ lualine_a = {},
+ lualine_b = {},
+ lualine_c = { "filename" },
+ lualine_x = { "location" },
+ lualine_y = {},
+ lualine_z = {},
+ },
+ tabline = {},
+ extensions = {},
+ })
+ end,
+ },
+ { "nvim-tree/nvim-web-devicons" },
+ { "nvim-mini/mini.icons", version = false },
+ {
+ "j-hui/fidget.nvim",
+ main = "fidget",
+ opts = {},
+ },
+ {
+ "romgrk/barbar.nvim",
+ version = "^1.0.0",
+ dependencies = {
+ "nvim-tree/nvim-web-devicons", -- for file icons
+ "lewis6991/gitsigns.nvim", -- optional for git status
+ },
+ init = function()
+ vim.g.barbar_auto_setup = false
+ end,
+ config = function()
+ require("barbar").setup({
+ -- Enable/disable animations
+ animation = true,
+ -- Enable/disable auto-hiding the tab bar when there is a single buffer
+ auto_hide = true,
+ -- Enable/disable current/total tabpages indicator (top right corner)
+ tabpages = true,
+ -- Enables/disable clickable tabs
+ clickable = true,
+ -- Configure icons on the bufferline
+ icons = {
+ -- Configure the base icons on the bufferline
+ buffer_index = false,
+ buffer_number = false,
+ button = "", -- Empty string disables close button
+ -- Enables / disables diagnostic symbols
+ diagnostics = {
+ [vim.diagnostic.severity.ERROR] = { enabled = true },
+ [vim.diagnostic.severity.WARN] = { enabled = true },
+ [vim.diagnostic.severity.INFO] = { enabled = false },
+ [vim.diagnostic.severity.HINT] = { enabled = false },
+ },
+ gitsigns = {
+ added = { enabled = true, icon = "+" },
+ changed = { enabled = true, icon = "~" },
+ deleted = { enabled = true, icon = "-" },
+ },
+ filetype = {
+ -- Sets the icon's highlight group.
+ -- If false, will use nvim-web-devicons colors
+ custom_colors = false,
+ -- Requires `nvim-web-devicons` if `true`
+ enabled = false,
+ },
+ separator = { left = "▎", right = "" },
+ -- Configure the icons on the bufferline when modified
+ modified = { button = "" },
+ -- Configure the icons on the bufferline based on the visibility of a buffer
+ pinned = { button = "📌" },
+ -- Configure the icons on the bufferline for various kinds of tabs
+ preset = "default",
+ -- Configure the icons on the bufferline for alternate layout
+ alternate = { filetype = { enabled = false } },
+ -- Configure the icons on the bufferline when the buffer is currently active
+ current = { buffer_index = false },
+ -- Configure the icons on the bufferline for inactive tabs
+ -- inactive = { button = "×" },
+ inactive = {},
+ -- Configure the icons on the bufferline for visible tabs
+ visible = { modified = { buffer_number = false } },
+ },
+ -- If true, new buffers will be inserted at the start/end of the list.
+ -- Default is to insert after current buffer.
+ insert_at_end = false,
+ insert_at_start = false,
+ -- Sets the maximum padding width with which to surround each tab
+ maximum_padding = 1,
+ -- Sets the minimum padding width with which to surround each tab
+ minimum_padding = 1,
+ -- Sets the maximum buffer name length
+ maximum_length = 30,
+ -- Sets the minimum buffer name length
+ minimum_length = 0,
+ -- If set, the letters for each buffer in buffer-pick mode will be
+ -- assigned based on their name. Otherwise or in case all letters are
+ -- already assigned, the behavior is to assign letters in order of
+ -- usability (see order below)
+ semantic_letters = true,
+ -- Sets the name of unnamed buffers. By default format is "[Buffer X]"
+ -- where X is the buffer number. But only a static string is accepted here.
+ no_name_title = nil,
+ -- Set the filetypes which barbar will offset itself for
+ sidebar_filetypes = {
+ NvimTree = { text = "Files", align = "left" },
+ },
+ })
+
+ -- Buffer navigation with <Leader> + number
+ for i = 1, 8 do
+ vim.keymap.set("n", "<Leader>" .. i, ":BufferGoto " .. i .. "<CR>")
+ end
+ vim.keymap.set("n", "<Leader>9", ":BufferLast<CR>", { desc = "Go to last buffer" })
+ vim.keymap.set("n", "<Leader>]", ":BufferNext<CR>")
+ vim.keymap.set("n", "<Leader>[", ":BufferPrevious<CR>")
+ -- vim.keymap.set("n", "<A-<>", ":BufferMovePrevious<CR>")
+ -- vim.keymap.set("n", "<A->>", ":BufferMoveNext<CR>")
+ -- vim.keymap.set("n", "<A-p>", ":BufferPin<CR>")
+ vim.keymap.set("n", "<Leader>x", ":BufferClose<CR>", { desc = "Close buffer" })
+ vim.keymap.set("n", "<Leader>X", ":BufferCloseAllButCurrent<CR>", { desc = "Close other buffers" })
+ end,
+ },
+ { "fweep/vim-zsh-path-completion" },
+ {
+ "nvim-tree/nvim-tree.lua",
+ cmd = { "NvimTreeToggle", "NvimTreeFindFile" },
+ keys = {
+ { "<Leader>/", ":NvimTreeToggle .<CR>", desc = "Toggle file tree" },
+ { "<Leader>?", ":NvimTreeFindFile<CR>", desc = "Find current file in tree" },
+ },
+ config = function()
+ vim.g.enable_web_dev_icons = true
+ local enable_web_dev_icons = vim.g.enable_web_dev_icons ~= 0
+ require("nvim-tree").setup({
+ actions = {
+ open_file = {
+ window_picker = {
+ enable = false,
+ },
+ },
+ },
+ renderer = {
+ highlight_git = true,
+ root_folder_label = false, -- Set to false to hide the root folder name, or a function to customize display
+ icons = {
+ show = {
+ file = enable_web_dev_icons,
+ folder = enable_web_dev_icons,
+ folder_arrow = enable_web_dev_icons,
+ git = false,
+ },
+ },
+ -- special_files = {},
+ },
+ view = {
+ -- signcolumn = 'no',
+ },
+ })
+ end,
+ },
+ {
+ "folke/noice.nvim",
+ event = "VeryLazy",
+ dependencies = {
+ -- if you lazy-load any plugin below, make sure to add proper `module="..."` entries
+ "MunifTanjim/nui.nvim",
+ -- OPTIONAL:
+ -- `nvim-notify` is only needed, if you want to use the notification view.
+ -- If not available, we use `mini` as the fallback
+ "rcarriga/nvim-notify",
+ },
+ config = function()
+ require("notify").setup({
+ background_colour = "#000000",
+ })
+
+ local noice = require("noice")
+ noice.setup({
+ messages = {
+ view = "mini",
+ view_error = "mini", -- view for errors
+ view_warn = "mini", -- view for warnings
+ view_search = false,
+ },
+ lsp = {
+ -- override markdown rendering so that **cmp** and other plugins use **Treesitter**
+ override = {
+ ["vim.lsp.util.convert_input_to_markdown_lines"] = true,
+ ["vim.lsp.util.stylize_markdown"] = true,
+ ["cmp.entry.get_documentation"] = true, -- requires hrsh7th/nvim-cmp
+ },
+ },
+ -- you can enable a preset for easier configuration
+ presets = {
+ -- bottom_search = true, -- use a classic bottom cmdline for search
+ command_palette = true, -- position the cmdline and popupmenu together
+ long_message_to_split = true, -- long messages will be sent to a split
+ inc_rename = false, -- enables an input dialog for inc-rename.nvim
+ lsp_doc_border = false, -- add a border to hover docs and signature help
+ },
+ routes = {
+ -- <C-g> buffer path → mini view, kept visible for 10s.
+ -- Must come first so it wins (and stops) before the generic
+ -- notify route below; route opts.timeout sets autohide.
+ {
+ view = "cmdline_popup",
+ filter = {
+ event = "notify",
+ cond = function(m)
+ return m.opts ~= nil and m.opts.buffer_path == true
+ end,
+ },
+ opts = { timeout = 10000 },
+ },
+
+ { view = "mini", filter = { event = "notify", find = "^Register" } },
+
+ -- 1) all normal Vim messages → mini
+ -- { filter = { event = "msg_show" }, view = "mini" },
+ -- 2) mode/recording messages → mini
+ -- { filter = { event = "msg_showmode" }, view = "mini" },
+ -- 3) anything sent via vim.notify() → mini
+ { filter = { event = "notify" }, view = "mini" },
+ -- 4) LSP progress to mini (leave hover/signature in their own views)
+ -- { filter = { event = "lsp", kind = "progress" }, view = "mini" },
+ },
+ })
+
+ vim.api.nvim_create_autocmd("RecordingEnter", {
+ group = vim.api.nvim_create_augroup("NoiceMacroNotification", { clear = true }),
+ callback = function()
+ _MACRO_RECORDING_STATUS = true
+ noice.notify(string.format("Register: %s", vim.fn.reg_recording()), vim.log.levels.INFO, {
+ title = "Recording macro",
+ view = "mini",
+ keep = function()
+ return _MACRO_RECORDING_STATUS
+ end,
+ })
+ end,
+ })
+ vim.api.nvim_create_autocmd("RecordingLeave", {
+ group = vim.api.nvim_create_augroup("NoiceMacroNotificationDismiss", { clear = true }),
+ callback = function()
+ _MACRO_RECORDING_STATUS = false
+ noice.notify(string.format("Register: %s", vim.fn.reg_recording()), vim.log.levels.INFO, {
+ title = "Saved macro",
+ view = "mini",
+ timeout = 2000,
+ })
+ end,
+ })
+
+ -- setup :Noice dismiss
+ vim.keymap.set("n", "<Leader>nd", ":Noice dismiss<CR>", { desc = "Dismiss notifications" })
+
+ -- <C-g>: show full buffer path. Emit a notify (instead of the native
+ -- msg_show) so noice's msg_show dedup can't drop repeated presses, and
+ -- tag it so the route below sends it to the mini view with a long timeout.
+ vim.keymap.set("n", "<C-g>", function()
+ local path = vim.fn.expand("%:p")
+ if path == "" then
+ path = "[No Name]"
+ end
+ vim.notify(path, vim.log.levels.INFO, { buffer_path = true })
+ end, { desc = "Show full buffer path" })
+ end,
+ },
+ -- Inlay hints are toggled natively via <Leader>dh (see init.lua); the
+ -- MysticalDevil/inlay-hints.nvim plugin is no longer needed on 0.10+.
+}
diff --git a/config/nvim/lua/plugins/which-key.lua b/config/nvim/lua/plugins/which-key.lua
new file mode 100644
index 0000000..8462cbf
--- /dev/null
+++ b/config/nvim/lua/plugins/which-key.lua
@@ -0,0 +1,30 @@
+return {
+ {
+ "folke/which-key.nvim",
+ event = "VeryLazy",
+ keys = {
+ {
+ "<leader>@",
+ function()
+ local which_key = require("which-key")
+ which_key.show({ global = false })
+ end,
+ desc = "Buffer Local Keymaps (which-key)",
+ },
+ },
+ config = function()
+ local wk = require("which-key")
+ wk.add({
+ { "<leader>c", group = "Code actions" },
+ { "<leader>g", group = "Git actions" },
+ { "<leader>d", group = "Diagnostics" },
+ { "<leader>t", group = "Tests" },
+ { "<leader>;", group = "Terminal" },
+ { "<leader>n", group = "Run" },
+ { "<leader>j", group = "Split/Join" },
+ { "<leader>q", group = "Lists (Trouble)" },
+ { "<leader>s", group = "Swap args" },
+ })
+ end,
+ },
+}
diff --git a/config/nvim/stylua.toml b/config/nvim/stylua.toml
new file mode 100644
index 0000000..2717ca2
--- /dev/null
+++ b/config/nvim/stylua.toml
@@ -0,0 +1,18 @@
+# StyLua configuration for Neovim Lua files
+# See: https://github.com/JohnnyMorganz/StyLua
+
+# Maximum line length before wrapping
+column_width = 120
+
+# Indentation settings
+indent_type = "Spaces"
+indent_width = 4
+
+# Quote style for strings
+quote_style = "AutoPreferDouble"
+
+# Call parentheses style
+call_parentheses = "Always"
+
+# Collapse simple statements onto a single line
+collapse_simple_statement = "Never"