diff options
Diffstat (limited to 'config/nvim')
| -rw-r--r-- | config/nvim/colors/dark.lua | 12 | ||||
| -rw-r--r-- | config/nvim/colors/default.lua | 11 | ||||
| -rw-r--r-- | config/nvim/colors/light.lua | 20 | ||||
| -rw-r--r-- | config/nvim/colors/molokai.lua | 5 | ||||
| -rw-r--r-- | config/nvim/colors/onehalfdark.lua | 3 | ||||
| -rw-r--r-- | config/nvim/init.lua | 702 | ||||
| -rw-r--r-- | config/nvim/local-plugins/cfg-watch/lua/cfg_watch/init.lua | 58 | ||||
| -rw-r--r-- | config/nvim/lua/.gitignore | 1 | ||||
| -rw-r--r-- | config/nvim/lua/config/lazy.lua | 37 | ||||
| -rw-r--r-- | config/nvim/lua/plugins/colorschemes.lua | 23 | ||||
| -rw-r--r-- | config/nvim/lua/plugins/completion.lua | 159 | ||||
| -rw-r--r-- | config/nvim/lua/plugins/editing.lua | 127 | ||||
| -rw-r--r-- | config/nvim/lua/plugins/editor.lua | 29 | ||||
| -rw-r--r-- | config/nvim/lua/plugins/fzf.lua | 148 | ||||
| -rw-r--r-- | config/nvim/lua/plugins/git.lua | 63 | ||||
| -rw-r--r-- | config/nvim/lua/plugins/lsp.lua | 281 | ||||
| -rw-r--r-- | config/nvim/lua/plugins/reload.lua | 28 | ||||
| -rw-r--r-- | config/nvim/lua/plugins/testing.lua | 41 | ||||
| -rw-r--r-- | config/nvim/lua/plugins/ui.lua | 335 | ||||
| -rw-r--r-- | config/nvim/lua/plugins/which-key.lua | 30 | ||||
| -rw-r--r-- | config/nvim/stylua.toml | 18 |
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" |
