aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbash/.bashrc2
-rw-r--r--iterm2/.iterm2/com.googlecode.iterm2.plist8
-rw-r--r--neovim/.config/nvim/README.md135
-rw-r--r--neovim/.config/nvim/lazy-lock.json25
-rw-r--r--neovim/.config/nvim/lua/config/autocmds.lua43
-rw-r--r--neovim/.config/nvim/lua/config/dap_node.lua88
-rw-r--r--neovim/.config/nvim/lua/config/keymaps.lua12
-rw-r--r--neovim/.config/nvim/lua/config/options.lua6
-rw-r--r--neovim/.config/nvim/lua/config/project_run.lua151
-rw-r--r--neovim/.config/nvim/lua/plugins/claude-code.lua1
-rw-r--r--neovim/.config/nvim/lua/plugins/colorscheme.lua8
-rw-r--r--neovim/.config/nvim/lua/plugins/dap-python.lua71
-rw-r--r--neovim/.config/nvim/lua/plugins/dap.lua181
-rw-r--r--neovim/.config/nvim/lua/plugins/editing.lua10
-rw-r--r--neovim/.config/nvim/lua/plugins/fuzzyfinder.lua36
-rw-r--r--neovim/.config/nvim/lua/plugins/git.lua5
-rw-r--r--neovim/.config/nvim/lua/plugins/indent-detect.lua8
-rw-r--r--neovim/.config/nvim/lua/plugins/langs.lua14
-rw-r--r--neovim/.config/nvim/lua/plugins/overseer.lua57
-rw-r--r--neovim/.config/nvim/lua/plugins/statusline.lua24
-rw-r--r--neovim/.config/nvim/lua/plugins/toggleterm.lua18
-rw-r--r--neovim/.config/nvim/lua/plugins/which-key.lua17
-rw-r--r--neovim/.config/nvim/nvim.log6
-rw-r--r--nvim.log4
24 files changed, 862 insertions, 68 deletions
diff --git a/bash/.bashrc b/bash/.bashrc
index 9ccd0e3..c923972 100755
--- a/bash/.bashrc
+++ b/bash/.bashrc
@@ -104,10 +104,12 @@ fi
104# Set the default editor 104# Set the default editor
105if exists nvim; then 105if exists nvim; then
106 export EDITOR="nvim" 106 export EDITOR="nvim"
107 alias vim=nvim
107elif exists vim; then 108elif exists vim; then
108 export EDITOR="vim" 109 export EDITOR="vim"
109elif exists vi; then 110elif exists vi; then
110 export EDITOR="vi" 111 export EDITOR="vi"
112 alias vim=nvim
111fi 113fi
112 114
113# vim() { 115# vim() {
diff --git a/iterm2/.iterm2/com.googlecode.iterm2.plist b/iterm2/.iterm2/com.googlecode.iterm2.plist
index f7b56cd..848452e 100644
--- a/iterm2/.iterm2/com.googlecode.iterm2.plist
+++ b/iterm2/.iterm2/com.googlecode.iterm2.plist
@@ -26,6 +26,8 @@
26 <string>https://api.openai.com/v1/responses</string> 26 <string>https://api.openai.com/v1/responses</string>
27 <key>CheckTestRelease</key> 27 <key>CheckTestRelease</key>
28 <true/> 28 <true/>
29 <key>ClaudeCodeWorkgroupUpsellSuppressed</key>
30 <true/>
29 <key>Default Bookmark Guid</key> 31 <key>Default Bookmark Guid</key>
30 <string>F73BDC14-4F5E-4A25-AE4A-2C6DB0A9BFEF</string> 32 <string>F73BDC14-4F5E-4A25-AE4A-2C6DB0A9BFEF</string>
31 <key>DisableFullscreenTransparency</key> 33 <key>DisableFullscreenTransparency</key>
@@ -53,6 +55,8 @@
53 <false/> 55 <false/>
54 <key>Ambiguous Double Width</key> 56 <key>Ambiguous Double Width</key>
55 <false/> 57 <false/>
58 <key>Animate Movement</key>
59 <false/>
56 <key>Ansi 0 Color</key> 60 <key>Ansi 0 Color</key>
57 <dict> 61 <dict>
58 <key>Blue Component</key> 62 <key>Blue Component</key>
@@ -343,7 +347,7 @@
343 <key>Non-ASCII Anti Aliased</key> 347 <key>Non-ASCII Anti Aliased</key>
344 <true/> 348 <true/>
345 <key>Normal Font</key> 349 <key>Normal Font</key>
346 <string>Menlo-Regular 11</string> 350 <string>JetBrainsMonoNLNFM-Regular 11</string>
347 <key>Option Key Sends</key> 351 <key>Option Key Sends</key>
348 <integer>0</integer> 352 <integer>0</integer>
349 <key>Prevent Opening in a Tab</key> 353 <key>Prevent Opening in a Tab</key>
@@ -621,6 +625,8 @@
621 <string>xterm-256color</string> 625 <string>xterm-256color</string>
622 <key>Transparency</key> 626 <key>Transparency</key>
623 <real>0.0</real> 627 <real>0.0</real>
628 <key>Unicode Normalization</key>
629 <integer>0</integer>
624 <key>Unicode Version</key> 630 <key>Unicode Version</key>
625 <integer>9</integer> 631 <integer>9</integer>
626 <key>Unlimited Scrollback</key> 632 <key>Unlimited Scrollback</key>
diff --git a/neovim/.config/nvim/README.md b/neovim/.config/nvim/README.md
index 5092af0..0e50788 100644
--- a/neovim/.config/nvim/README.md
+++ b/neovim/.config/nvim/README.md
@@ -15,6 +15,8 @@ lua/config/ Everything that isn't a plugin declaration
15 options.lua vim.opt settings (search, undo, whitespace display, ...) 15 options.lua vim.opt settings (search, undo, whitespace display, ...)
16 keymaps.lua Global keymaps not owned by a specific plugin 16 keymaps.lua Global keymaps not owned by a specific plugin
17 autocmds.lua Global autocmds not owned by a specific plugin 17 autocmds.lua Global autocmds not owned by a specific plugin
18 project_run.lua Helper for project-local run/debug configs (see below)
19 dap_node.lua Node/js-debug-adapter wiring for nvim-dap (see below)
18 lazy.lua Bootstraps lazy.nvim and points it at lua/plugins/ 20 lazy.lua Bootstraps lazy.nvim and points it at lua/plugins/
19lua/plugins/ One file per plugin (or small plugin group), each 21lua/plugins/ One file per plugin (or small plugin group), each
20 returning a lazy.nvim plugin spec (or list of specs) 22 returning a lazy.nvim plugin spec (or list of specs)
@@ -34,18 +36,23 @@ such tables) and it's included on next launch.
34| File | Plugin(s) | Purpose | 36| File | Plugin(s) | Purpose |
35|---|---|---| 37|---|---|---|
36| `colorscheme.lua` | vim-janah | Color scheme, falls back to `desert` if it fails to load | 38| `colorscheme.lua` | vim-janah | Color scheme, falls back to `desert` if it fails to load |
37| `statusline.lua` | vim-airline, vim-airline-themes, tmuxline.vim | Status/tab line | 39| `statusline.lua` | lualine.nvim | Status line |
38| `filetree.lua` | neo-tree.nvim | File explorer sidebar with git-status colors (`<leader>e` to toggle) | 40| `filetree.lua` | neo-tree.nvim | File explorer sidebar with git-status colors (`<leader>e` to toggle) |
39| `fuzzyfinder.lua` | ctrlp.vim | Fuzzy file finder (ripgrep-backed if `rg` is on PATH) | 41| `fuzzyfinder.lua` | telescope.nvim, telescope-ui-select.nvim | Fuzzy file finder (`<C-p>`), live grep (`<leader>fg`), buffers (`<leader>fb`); also overrides `vim.ui.select` globally so overseer's/dap's pickers are Telescope windows |
40| `editing.lua` | nerdcommenter, quick-scope | Comment toggling, `f`/`F`/`t`/`T` target highlighting | 42| `editing.lua` | quick-scope | `f`/`F`/`t`/`T` target highlighting (comment toggling is Neovim's built-in `gc`/`gcc`) |
41| `git.lua` | vim-fugitive, vim-signify | `:Git` commands, git-diff gutter signs | 43| `git.lua` | vim-fugitive, gitsigns.nvim | `:Git` commands, git-diff gutter signs |
42| `ale.lua` | ALE | Linters/fixers only — `g:ale_disable_lsp = 1` so it doesn't fight the LSP client's diagnostics/hover/go-to-def | 44| `ale.lua` | ALE | Linters/fixers only — `g:ale_disable_lsp = 1` so it doesn't fight the LSP client's diagnostics/hover/go-to-def |
43| `langs.lua` | vim-ansible-yaml, vim-terraform | Only loaded if `ansible`/`terraform` is on PATH (lazy.nvim `cond`) |
44| `claude-code.lua` | claude-code.nvim (+ plenary.nvim) | `:ClaudeCode` and friends | 45| `claude-code.lua` | claude-code.nvim (+ plenary.nvim) | `:ClaudeCode` and friends |
46| `toggleterm.lua` | toggleterm.nvim | Scratch terminal toggle — `<C-.>`, vertical split on the right matching claude-code.nvim's placement/ratio |
45| `treesitter.lua` | nvim-treesitter (`main` branch) | Parser installs + syntax highlighting | 47| `treesitter.lua` | nvim-treesitter (`main` branch) | Parser installs + syntax highlighting |
46| `autopairs.lua` | nvim-autopairs, nvim-ts-autotag | Auto-close brackets/quotes/HTML+JSX tags | 48| `autopairs.lua` | nvim-autopairs, nvim-ts-autotag | Auto-close brackets/quotes/HTML+JSX tags |
47| `completion.lua` | blink.cmp (+ friendly-snippets) | Completion popup, Tab-to-accept (`super-tab` preset) | 49| `completion.lua` | blink.cmp (+ friendly-snippets) | Completion popup, Tab-to-accept (`super-tab` preset) |
48| `lsp.lua` | nvim-lspconfig, mason.nvim, mason-lspconfig.nvim | Installs & enables language servers, wires blink.cmp's capabilities into them | 50| `lsp.lua` | nvim-lspconfig, mason.nvim, mason-lspconfig.nvim | Installs & enables language servers, wires blink.cmp's capabilities into them |
51| `dap.lua` | nvim-dap, nvim-dap-ui, nvim-dap-virtual-text, mason-nvim-dap.nvim | Debugger core + UI, installs debug adapters via Mason (`<leader>d*`) |
52| `dap-python.lua` | nvim-dap-python | Python launch configs, wired to Mason's debugpy install |
53| `overseer.lua` | overseer.nvim | Save/run/rerun arbitrary run configurations for any language (`<leader>r*`) |
54| `which-key.lua` | which-key.nvim | Keymap discoverability popup; labels the `<leader>d`/`<leader>r`/etc. groups |
55| `indent-detect.lua` | guess-indent.nvim | Detects each buffer's real indentation and matches shiftwidth/tabstop to it |
49 56
50## Language servers 57## Language servers
51 58
@@ -59,6 +66,112 @@ hover) come from Neovim's built-in default LSP keymaps (`:help lsp-defaults`)
59once a server attaches — `grn`, `gra`, `grr`, `gri`, `gO`, `K`, `<C-]>`. No 66once a server attaches — `grn`, `gra`, `grr`, `gri`, `gO`, `K`, `<C-]>`. No
60extra plugin or keymap needed for these. 67extra plugin or keymap needed for these.
61 68
69## Debugging
70
71Python and Node.js (JavaScript/TypeScript), via `nvim-dap` + `nvim-dap-ui`.
72The `debugpy` and `js-debug-adapter` adapters are installed and kept up to
73date by Mason (via `mason-nvim-dap.nvim`, the DAP equivalent of
74`mason-lspconfig.nvim`), so `:Mason` shows them alongside the language
75servers. Keymaps live under `<leader>d` — `<leader>db` to set a breakpoint,
76`<leader>dc` to start/continue (picks a registered config from any window,
77not just the source buffer; resumes in place if a session is already stopped
78at a breakpoint), `<leader>do`/`di`/`dO` to step over/into/out, `<leader>dt`
79to terminate, `<leader>du` to reopen the UI if you close it manually (it
80opens/closes itself automatically around a session). Press `<leader>d` and
81wait to see the full list via which-key.
82
83Python's adapter/config setup lives in `lua/plugins/dap-python.lua`, wrapping
84the `nvim-dap-python` plugin. Node's lives in `lua/config/dap_node.lua`
85instead — there's no maintained `nvim-dap-python`-style plugin for it (the
86community option, `nvim-dap-vscode-js`, expects an older, manually-built
87vscode-js-debug layout that doesn't match what Mason's `js-debug-adapter`
88package installs), so that file resolves the Mason install path directly and
89registers the `pwa-node` adapter + default `javascript`/`typescript`/
90`javascriptreact`/`typescriptreact` configs itself. It's called eagerly from
91`dap.lua`'s own `config()` rather than being its own `lua/plugins/*` entry,
92since it isn't an installable plugin — same reasoning as
93`lua/config/project_run.lua`.
94
95Adding another language later (e.g. C/C++ via `codelldb`) means adding its
96adapter to `dap.lua`'s `ensure_installed`, and either writing its own
97`dap-<language>.lua` plugin file (if a wrapper plugin exists, following the
98`dap-python.lua` pattern) or a `lua/config/dap_<language>.lua` module called
99from `dap.lua`'s `config()` (if not, following the `dap_node.lua` pattern) —
100including `lazy = false` on `dap.lua` itself either way, so the adapter is
101registered from startup rather than only after a buffer of that filetype is
102opened (the `<leader>dc` picker works from any window, so it can't rely on
103that to lazy-trigger the adapter).
104
105`overseer.lua` sets `dap = true`, which patches `nvim-dap` so a debug
106configuration can set `preLaunchTask`/`postDebugTask` to the name of an
107overseer task (or `"${defaultBuildTask}"` for whichever task is tagged
108`BUILD`) — useful for "build, then debug" workflows once a compiled
109language is added; not needed for plain Python/Node scripts.
110
111## Run configurations
112
113`overseer.nvim` provides PyCharm-style saved/rerunnable run configurations
114for any language or toolchain (shell commands, npm scripts, make, cargo,
115python scripts, ...) — it isn't tied to LSP/DAP language support. Keymaps
116live under `<leader>r` — `<leader>rr` to run a task (prompts for a template
117or a raw command), `<leader>rl` to rerun the last task, `<leader>ro` to
118toggle the task list/output window, `<leader>rq` for quick actions
119(restart/stop/dispose) on a selected task. Press `<leader>r` and wait to see
120the full list via which-key.
121
122`overseer.lua`'s `component_aliases.default` adds the `open_output`
123component (`on_start = "always"`, `focus = false`) on top of overseer's own
124defaults, so starting *any* task — custom, `.nvim.lua`-declared, or
125auto-detected (npm/make/etc.) — docks its output automatically instead of
126running silently until you manually `<leader>ro`.
127
128## Project-local run/debug configs
129
130A project can drop a `.nvim.lua` anywhere in its directory tree and have it
131auto-sourced (after a one-time `:help :trust` prompt — it runs arbitrary
132Lua, so trust it deliberately, not reflexively). This is the PyCharm-style
133"named configuration" mechanism, without adopting any other editor's file
134format.
135
136This is **not** core Vim/Neovim `'exrc'` (deliberately left off in
137options.lua) — `exrc` only checks the literal cwd once at startup, so it
138misses a project's `.nvim.lua` whenever Neovim didn't happen to start with
139cwd exactly there. Instead, an autocmd in `lua/config/autocmds.lua` searches
140*upward* from cwd on `VimEnter`/`DirChanged` (stopping at `$HOME`, same
141nearest-ancestor pattern LSP root-dir detection uses) and reads the file via
142`vim.secure.read` — the same trust mechanism `exrc` itself uses, so `:trust`
143still manages the allowlist unchanged, it's just found reliably regardless
144of how you opened Neovim.
145
146`lua/config/project_run.lua` gives that file one call per config that
147registers both a `<leader>rr` run task and a matching `<leader>dc` debug
148config from the *same* declaration, instead of repeating the command across
149two separate files:
150
151```lua
152-- <project root>/.nvim.lua
153require("config.project_run").python({
154 name = "Run app",
155 program = vim.fn.getcwd() .. "/main.py",
156 args = { "--flag" }, -- optional, shared by both run and debug
157})
158
159require("config.project_run").node({
160 name = "Run app",
161 program = vim.fn.getcwd() .. "/index.js", -- or npmScript = "dev" for `npm run dev`
162 args = { "--flag" }, -- optional, shared by both run and debug
163})
164```
165
166`dap.lua` and `overseer.lua` are both `lazy = false` specifically so this
167works immediately when the autocmd sources the file, rather than needing
168the plugins already triggered by a keymap first.
169
170(`nvim-dap` and `overseer.nvim` also natively read `.vscode/launch.json` and
171`.vscode/tasks.json` respectively, if you'd rather use that ecosystem's
172format for some reason — but `project_run.lua` is the recommended path here
173since it states each command once.)
174
62## Adding a new plugin 175## Adding a new plugin
63 176
64Drop a new file in `lua/plugins/` (or add to an existing group file if it 177Drop a new file in `lua/plugins/` (or add to an existing group file if it
@@ -74,6 +187,18 @@ return {
74Restart Neovim (or `:Lazy sync`) to install it, then commit the updated 187Restart Neovim (or `:Lazy sync`) to install it, then commit the updated
75`lazy-lock.json` alongside your change. 188`lazy-lock.json` alongside your change.
76 189
190If a plugin backs a keymap or command meant to work from any window — not
191scoped to a particular filetype/buffer, like the run/debug pickers, the
192fuzzy finder, or the file explorer — set `lazy = false` rather than gating
193it behind `ft`/`event`, even though lazy.nvim would otherwise recommend one.
194A trigger tied to "some buffer of type X was opened" silently breaks the
195moment the keymap fires before that's happened (this bit `dap-python.lua`:
196it lazy-loaded on `ft = "python"`, so debugging broke whenever `<leader>dc`
197was pressed before any `.py` buffer had been opened this session). Plain
198per-buffer editing conveniences (`autopairs.lua`'s `InsertEnter`,
199`ale.lua`'s `BufReadPre`, `which-key.lua`'s `VeryLazy`) don't have this
200problem and can stay lazy.
201
77## Relationship to Vim 202## Relationship to Vim
78 203
79Vim and Neovim used to share one `vimrc` (`has('nvim')` branches handled the 204Vim and Neovim used to share one `vimrc` (`has('nvim')` branches handled the
diff --git a/neovim/.config/nvim/lazy-lock.json b/neovim/.config/nvim/lazy-lock.json
index 6ac90ec..fd32868 100644
--- a/neovim/.config/nvim/lazy-lock.json
+++ b/neovim/.config/nvim/lazy-lock.json
@@ -2,26 +2,33 @@
2 "ale": { "branch": "master", "commit": "e1789bc54483d76ac9ddb40b633d1645c8281914" }, 2 "ale": { "branch": "master", "commit": "e1789bc54483d76ac9ddb40b633d1645c8281914" },
3 "blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" }, 3 "blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" },
4 "claude-code.nvim": { "branch": "main", "commit": "55c0cb59828fbc3bec744288286a46f5d5750b83" }, 4 "claude-code.nvim": { "branch": "main", "commit": "55c0cb59828fbc3bec744288286a46f5d5750b83" },
5 "ctrlp.vim": { "branch": "master", "commit": "0065942ef13ce28293e2b26cb622d035da050b44" },
6 "friendly-snippets": { "branch": "main", "commit": "b4d01b0fdf3c9a549961c2f9ffe8dc09be166219" }, 5 "friendly-snippets": { "branch": "main", "commit": "b4d01b0fdf3c9a549961c2f9ffe8dc09be166219" },
6 "gitsigns.nvim": { "branch": "main", "commit": "f2421c550618d257048afa650413d9e542ddbe67" },
7 "guess-indent.nvim": { "branch": "main", "commit": "84a4987ff36798c2fc1169cbaff67960aed9776f" },
7 "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" }, 8 "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" },
8 "mason-lspconfig.nvim": { "branch": "main", "commit": "b5576cd62899a1a478d0e67ef3d378ee55dba82d" }, 9 "lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
10 "mason-lspconfig.nvim": { "branch": "main", "commit": "77e06f7d2fc127e6ef0a720b59acbc50757cff19" },
11 "mason-nvim-dap.nvim": { "branch": "main", "commit": "9a10e096703966335bd5c46c8c875d5b0690dade" },
9 "mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" }, 12 "mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" },
10 "neo-tree.nvim": { "branch": "v3.x", "commit": "1a14083046d96e88e361d1da00761c40d45012f3" }, 13 "neo-tree.nvim": { "branch": "v3.x", "commit": "1a14083046d96e88e361d1da00761c40d45012f3" },
11 "nerdcommenter": { "branch": "master", "commit": "a462bbda1e26f44fb3d3eb9d9d1c6a07aa98e665" },
12 "nui.nvim": { "branch": "main", "commit": "10fc361835c856ba4233ef5ea135b919bf3dce97" }, 14 "nui.nvim": { "branch": "main", "commit": "10fc361835c856ba4233ef5ea135b919bf3dce97" },
13 "nvim-autopairs": { "branch": "master", "commit": "430522f95fe4fb7c511ec64f8c1a90cc6a66c05c" }, 15 "nvim-autopairs": { "branch": "master", "commit": "430522f95fe4fb7c511ec64f8c1a90cc6a66c05c" },
16 "nvim-dap": { "branch": "master", "commit": "cfa2d58f4537aca6ca83e2de1a0d9f1491121264" },
17 "nvim-dap-python": { "branch": "master", "commit": "1808458eba2b18f178f990e01376941a42c7f93b" },
18 "nvim-dap-ui": { "branch": "master", "commit": "cc9dd33aade7f20bae414d0cba163bc60d4d4b43" },
19 "nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" },
14 "nvim-lspconfig": { "branch": "master", "commit": "ac9d2f7c4757db6320cab6697fe73e5e8adb2457" }, 20 "nvim-lspconfig": { "branch": "master", "commit": "ac9d2f7c4757db6320cab6697fe73e5e8adb2457" },
15 "nvim-treesitter": { "branch": "main", "commit": "d4d59cb369da46b95699bd2200efbcffc6dadb3b" }, 21 "nvim-nio": { "branch": "master", "commit": "edcc181a875301dd21840189aa2f2f9ad69fc172" },
22 "nvim-treesitter": { "branch": "main", "commit": "9a168f6357ed21c3a636e1727bc7d382abc451b8" },
16 "nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" }, 23 "nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" },
17 "nvim-web-devicons": { "branch": "master", "commit": "5f032a85be210cd1c6ac98861eb3b187ff3bd5eb" }, 24 "nvim-web-devicons": { "branch": "master", "commit": "5f032a85be210cd1c6ac98861eb3b187ff3bd5eb" },
25 "overseer.nvim": { "branch": "master", "commit": "a93d9f6d6defdac4bcd6d2c8ba988650e42e0a0e" },
18 "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" }, 26 "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
19 "quick-scope": { "branch": "master", "commit": "6cee1d9e0b9ac0fbffeb538d4a5ba9f5628fabbc" }, 27 "quick-scope": { "branch": "master", "commit": "6cee1d9e0b9ac0fbffeb538d4a5ba9f5628fabbc" },
20 "tmuxline.vim": { "branch": "master", "commit": "4119c553923212cc67f4e135e6f946dc3ec0a4d6" }, 28 "telescope-ui-select.nvim": { "branch": "master", "commit": "6e51d7da30bd139a6950adf2a47fda6df9fa06d2" },
21 "vim-airline": { "branch": "master", "commit": "ae24f4aca06731d5d7224df1fc5415975331b214" }, 29 "telescope.nvim": { "branch": "master", "commit": "40aedd8a68c78a656a10a8d62d80c54af59420fb" },
22 "vim-airline-themes": { "branch": "master", "commit": "77aab8c6cf7179ddb8a05741da7e358a86b2c3ab" }, 30 "toggleterm.nvim": { "branch": "main", "commit": "9a88eae817ef395952e08650b3283726786fb5fb" },
23 "vim-ansible-yaml": { "branch": "master", "commit": "a6f92d17ff01b2e63d6c9fdbb7f7e13c7fd41d14" },
24 "vim-fugitive": { "branch": "master", "commit": "3b753cf8c6a4dcde6edee8827d464ba9b8c4a6f0" }, 31 "vim-fugitive": { "branch": "master", "commit": "3b753cf8c6a4dcde6edee8827d464ba9b8c4a6f0" },
25 "vim-janah": { "branch": "master", "commit": "3b8ae976987b6ade2abeac25f0208e8bc90d7138" }, 32 "vim-janah": { "branch": "master", "commit": "3b8ae976987b6ade2abeac25f0208e8bc90d7138" },
26 "vim-signify": { "branch": "master", "commit": "3b5ae37eb2b77c3ae58d60dfdc3fc30258078663" } 33 "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
27} 34}
diff --git a/neovim/.config/nvim/lua/config/autocmds.lua b/neovim/.config/nvim/lua/config/autocmds.lua
index 7bc4f6b..e9ebe04 100644
--- a/neovim/.config/nvim/lua/config/autocmds.lua
+++ b/neovim/.config/nvim/lua/config/autocmds.lua
@@ -13,6 +13,49 @@ vim.api.nvim_create_autocmd("BufWritePre", {
13 callback = strip_trailing_whitespace, 13 callback = strip_trailing_whitespace,
14}) 14})
15 15
16-- Project-local config: search upward from cwd for a .nvim.lua, the same
17-- nearest-ancestor pattern LSP root-dir detection uses, so it's found
18-- regardless of exactly which subdirectory Neovim's cwd is or how you got
19-- there (:cd, opening a file, session restore, ...) - unlike core 'exrc'
20-- (deliberately left off in options.lua), which only ever checks the
21-- literal cwd once at startup.
22local sourced_project_configs = {}
23
24local function source_project_config()
25 local found = vim.fs.find(".nvim.lua", {
26 upward = true,
27 path = vim.fn.getcwd(),
28 stop = vim.env.HOME,
29 })[1]
30 if not found or sourced_project_configs[found] then
31 return
32 end
33
34 -- vim.secure.read is the same trust mechanism 'exrc' itself uses: prompts
35 -- on first encounter, honors ~/.local/state/nvim/trust, :trust still
36 -- manages the allowlist unchanged. Returns nil (no prompt shown again)
37 -- for a file previously denied.
38 local content = vim.secure.read(found)
39 if not content then
40 return
41 end
42 sourced_project_configs[found] = true
43
44 local chunk, load_err = load(content, "@" .. found)
45 if not chunk then
46 vim.notify("Error loading " .. found .. ":\n" .. load_err, vim.log.levels.ERROR)
47 return
48 end
49 local ok, run_err = pcall(chunk)
50 if not ok then
51 vim.notify("Error running " .. found .. ":\n" .. run_err, vim.log.levels.ERROR)
52 end
53end
54
55vim.api.nvim_create_autocmd({ "VimEnter", "DirChanged" }, {
56 callback = source_project_config,
57})
58
16-- Note: the colorscheme (with its "desert" fallback) is applied from 59-- Note: the colorscheme (with its "desert" fallback) is applied from
17-- lua/plugins/colorscheme.lua's own `config` function, not here — under 60-- lua/plugins/colorscheme.lua's own `config` function, not here — under
18-- lazy.nvim that's the natural place to react to "the plugin is now loaded". 61-- lazy.nvim that's the natural place to react to "the plugin is now loaded".
diff --git a/neovim/.config/nvim/lua/config/dap_node.lua b/neovim/.config/nvim/lua/config/dap_node.lua
new file mode 100644
index 0000000..0fb6890
--- /dev/null
+++ b/neovim/.config/nvim/lua/config/dap_node.lua
@@ -0,0 +1,88 @@
1-- JavaScript/TypeScript launch configs, wired to the js-debug-adapter
2-- (Microsoft's vscode-js-debug, bundled by Mason as a standalone
3-- multi-session DAP server) that dap.lua's mason-nvim-dap ensure_installed
4-- installs.
5--
6-- There's no nvim-dap-python equivalent for this: the one community plugin
7-- for it (mxsdev/nvim-dap-vscode-js) hardcodes the older
8-- `out/src/vsDebugServer.js` vscode-js-debug build layout, which doesn't
9-- match what Mason's js-debug-adapter package actually installs
10-- (`js-debug/src/dapDebugServer.js`) - so this resolves the Mason path
11-- directly instead, same approach as dap-python.lua's debugpy_path.
12--
13-- Lives under lua/config/ rather than lua/plugins/ (and is required
14-- eagerly from dap.lua's config()) because it isn't an installable plugin
15-- itself, just adapter/config wiring - same reason project_run.lua lives
16-- here instead of there.
17--
18-- js-debug-adapter is a multi-session server: debugging a script that
19-- spawns a worker thread arrives as a `startDebugging` reverse request
20-- rather than a second top-level session. nvim-dap's core (dap/session.lua)
21-- already handles that natively, so no extra plugin is needed for it either.
22local M = {}
23
24local function configurations()
25 return {
26 {
27 type = "pwa-node",
28 request = "launch",
29 name = "Launch file",
30 program = "${file}",
31 cwd = "${workspaceFolder}",
32 },
33 {
34 type = "pwa-node",
35 request = "attach",
36 name = "Attach to process",
37 processId = require("dap.utils").pick_process,
38 cwd = "${workspaceFolder}",
39 },
40 }
41end
42
43function M.setup()
44 local registry = require("mason-registry")
45 local pkg = registry.get_package("js-debug-adapter")
46
47 local function register()
48 local entrypoint = pkg:get_install_path() .. "/js-debug/src/dapDebugServer.js"
49
50 require("dap").adapters["pwa-node"] = {
51 type = "server",
52 host = "localhost",
53 port = "${port}",
54 executable = {
55 command = "node",
56 args = { entrypoint, "${port}" },
57 },
58 }
59
60 local dap = require("dap")
61 for _, filetype in ipairs({ "javascript", "typescript", "javascriptreact", "typescriptreact" }) do
62 dap.configurations[filetype] = configurations()
63 end
64 end
65
66 if pkg:is_installed() then
67 register()
68 else
69 -- Same reasoning as dap-python.lua: dap.lua's mason-nvim-dap
70 -- ensure_installed triggers the install, but that's async - on a fresh
71 -- machine (or before it finishes) the entrypoint doesn't exist on disk
72 -- yet, so wire up the adapter once the install actually completes
73 -- instead of pointing it at a path that isn't there yet.
74 vim.notify(
75 "js-debug-adapter not installed yet - dap.lua's ensure_installed should be"
76 .. " installing it now (check :Mason); run :MasonInstall js-debug-adapter"
77 .. " if it doesn't show up there. Debugging will work once it's done.",
78 vim.log.levels.WARN
79 )
80 registry:on("package:install:success", function(installed_pkg)
81 if installed_pkg.name == "js-debug-adapter" then
82 register()
83 end
84 end)
85 end
86end
87
88return M
diff --git a/neovim/.config/nvim/lua/config/keymaps.lua b/neovim/.config/nvim/lua/config/keymaps.lua
index 66e97a0..65e3236 100644
--- a/neovim/.config/nvim/lua/config/keymaps.lua
+++ b/neovim/.config/nvim/lua/config/keymaps.lua
@@ -14,6 +14,18 @@ map("t", "<C-k>", [[<C-\><C-n><C-w>k]])
14map("t", "<C-h>", [[<C-\><C-n><C-w>h]]) 14map("t", "<C-h>", [[<C-\><C-n><C-w>h]])
15map("t", "<C-l>", [[<C-\><C-n><C-w>l]]) 15map("t", "<C-l>", [[<C-\><C-n><C-w>l]])
16 16
17-- Copy to clipboard
18map("v", "<leader>y", '"+y')
19map("n", "<leader>Y", '"+yg_')
20map("n", "<leader>y", '"+y')
21map("n", "<leader>yy", '"+yy')
22
23-- Paste from clipboard
24map("n", "<leader>p", '"+p')
25map("n", "<leader>P", '"+P')
26map("v", "<leader>p", '"+p')
27map("v", "<leader>P", '"+P')
28
17-- Never use the arrow keys 29-- Never use the arrow keys
18for _, key in ipairs({ "<Up>", "<Down>", "<Left>", "<Right>" }) do 30for _, key in ipairs({ "<Up>", "<Down>", "<Left>", "<Right>" }) do
19 map({ "n", "i" }, key, "<Nop>") 31 map({ "n", "i" }, key, "<Nop>")
diff --git a/neovim/.config/nvim/lua/config/options.lua b/neovim/.config/nvim/lua/config/options.lua
index be614e1..1282850 100644
--- a/neovim/.config/nvim/lua/config/options.lua
+++ b/neovim/.config/nvim/lua/config/options.lua
@@ -34,3 +34,9 @@ opt.hlsearch = true
34opt.incsearch = true 34opt.incsearch = true
35opt.ignorecase = true 35opt.ignorecase = true
36opt.smartcase = true 36opt.smartcase = true
37
38-- Project-local config (.nvim.lua) is handled by a custom autocmd in
39-- autocmds.lua instead of the core 'exrc' option: 'exrc' only checks the
40-- literal cwd at startup, once, so it misses a project's .nvim.lua whenever
41-- Neovim didn't start with cwd exactly there. Leave 'exrc' off to avoid
42-- double-sourcing the same file via both mechanisms.
diff --git a/neovim/.config/nvim/lua/config/project_run.lua b/neovim/.config/nvim/lua/config/project_run.lua
new file mode 100644
index 0000000..40c1795
--- /dev/null
+++ b/neovim/.config/nvim/lua/config/project_run.lua
@@ -0,0 +1,151 @@
1-- Single-source-of-truth helper for project-local run/debug configs, meant
2-- to be called once per config from a project's `.nvim.lua` (requires
3-- `opt.exrc` - see options.lua - and trusting the file once via
4-- `:help :trust`). One call registers both an overseer run task and a
5-- matching nvim-dap debug config, so a project states its command once
6-- instead of repeating it across a separate run-file and debug-file.
7--
8-- Example .nvim.lua (script invocation):
9-- require("config.project_run").python({
10-- name = "Run app",
11-- program = vim.fn.getcwd() .. "/main.py",
12-- args = { "--flag" }, -- optional, shared by both run and debug
13-- })
14--
15-- Example .nvim.lua (module invocation, `python -m <module>`):
16-- require("config.project_run").python({
17-- name = "Run app",
18-- module = "fastapi",
19-- args = { "dev", "app" },
20-- })
21--
22-- This shows "Run app" in the <leader>rr picker (plain execution) and in
23-- the <leader>dc picker (same script/module, under debugpy, breakpoints
24-- included). If <cwd>/.venv exists, both get VIRTUAL_ENV/PATH pointed at
25-- it, same as `uv run` (or `source .venv/bin/activate`) would - and the
26-- debug config additionally gets pythonPath set to that venv's interpreter,
27-- since debugpy runs the script with the literal interpreter it's told
28-- rather than resolving `python3` via PATH.
29--
30-- Example .nvim.lua (Node, script invocation):
31-- require("config.project_run").node({
32-- name = "Run app",
33-- program = vim.fn.getcwd() .. "/index.js",
34-- args = { "--flag" }, -- optional, shared by both run and debug
35-- })
36--
37-- Example .nvim.lua (Node, `npm run <script>` invocation):
38-- require("config.project_run").node({
39-- name = "Run app",
40-- npmScript = "dev",
41-- })
42
43local M = {}
44
45local function venv_dir()
46 local venv = vim.fn.getcwd() .. "/.venv"
47 if vim.fn.isdirectory(venv) == 0 then
48 return nil
49 end
50 return venv
51end
52
53local function venv_env(venv)
54 return {
55 VIRTUAL_ENV = venv,
56 PATH = venv .. "/bin:" .. vim.env.PATH,
57 }
58end
59
60function M.python(opts)
61 assert(opts.name, "project_run.python: opts.name is required")
62 assert(opts.program or opts.module, "project_run.python: opts.program or opts.module is required")
63
64 local venv = venv_dir()
65 local env = venv and venv_env(venv) or nil
66 local cmd = opts.module and { "python3", "-m", opts.module } or { "python3", opts.program }
67
68 require("overseer").register_template({
69 name = opts.name,
70 builder = function()
71 return {
72 cmd = cmd,
73 args = opts.args,
74 name = opts.name,
75 env = env,
76 }
77 end,
78 })
79
80 local dap = require("dap")
81 dap.configurations.python = dap.configurations.python or {}
82 table.insert(dap.configurations.python, {
83 type = "python",
84 request = "launch",
85 name = opts.name,
86 program = opts.program,
87 module = opts.module,
88 args = opts.args,
89 env = env,
90 -- nvim-dap-python only auto-detects an interpreter (VIRTUAL_ENV env var,
91 -- then a venv/.venv/env/.env folder search) when pythonPath/python isn't
92 -- already set - and debugpy needs the literal interpreter path here
93 -- regardless (it spawns it directly, not through a shell that'd resolve
94 -- `python3` via PATH the way env.PATH above does for the overseer task).
95 -- Set it explicitly from the same .venv we already found, so debugging
96 -- doesn't depend on that separate heuristic agreeing with us - without
97 -- this, a missed/wrong guess falls back to debugpy's own adapter venv,
98 -- which can't see the project's installed packages.
99 pythonPath = venv and (venv .. "/bin/python") or nil,
100 })
101end
102
103-- Node has no venv-style interpreter juggling to do - node_modules
104-- resolution is automatic - so this is a straight translation of opts into
105-- an overseer task plus a pwa-node dap config, into all four JS/TS
106-- filetypes (dap_node.lua registers the same four for its own defaults).
107function M.node(opts)
108 assert(opts.name, "project_run.node: opts.name is required")
109 assert(opts.program or opts.npmScript, "project_run.node: opts.program or opts.npmScript is required")
110
111 local cmd = opts.npmScript and { "npm", "run", opts.npmScript } or { "node", opts.program }
112
113 require("overseer").register_template({
114 name = opts.name,
115 builder = function()
116 return {
117 cmd = cmd,
118 args = opts.args,
119 name = opts.name,
120 }
121 end,
122 })
123
124 -- A fresh table per filetype - nvim-dap's session setup can stash
125 -- bookkeeping fields onto a running config, so the four filetypes
126 -- shouldn't share one table instance.
127 local function config()
128 local c = {
129 type = "pwa-node",
130 request = "launch",
131 name = opts.name,
132 args = opts.args,
133 cwd = vim.fn.getcwd(),
134 }
135 if opts.npmScript then
136 c.runtimeExecutable = "npm"
137 c.runtimeArgs = { "run", opts.npmScript }
138 else
139 c.program = opts.program
140 end
141 return c
142 end
143
144 local dap = require("dap")
145 for _, filetype in ipairs({ "javascript", "typescript", "javascriptreact", "typescriptreact" }) do
146 dap.configurations[filetype] = dap.configurations[filetype] or {}
147 table.insert(dap.configurations[filetype], config())
148 end
149end
150
151return M
diff --git a/neovim/.config/nvim/lua/plugins/claude-code.lua b/neovim/.config/nvim/lua/plugins/claude-code.lua
index 0390852..b2e881f 100644
--- a/neovim/.config/nvim/lua/plugins/claude-code.lua
+++ b/neovim/.config/nvim/lua/plugins/claude-code.lua
@@ -1,5 +1,6 @@
1return { 1return {
2 "greggh/claude-code.nvim", 2 "greggh/claude-code.nvim",
3 lazy = false,
3 dependencies = { "nvim-lua/plenary.nvim" }, 4 dependencies = { "nvim-lua/plenary.nvim" },
4 cmd = { "ClaudeCode", "ClaudeCodeContinue", "ClaudeCodeResume", "ClaudeCodeVerbose" }, 5 cmd = { "ClaudeCode", "ClaudeCodeContinue", "ClaudeCodeResume", "ClaudeCodeVerbose" },
5 config = function() 6 config = function()
diff --git a/neovim/.config/nvim/lua/plugins/colorscheme.lua b/neovim/.config/nvim/lua/plugins/colorscheme.lua
index 457dc71..0349cf9 100644
--- a/neovim/.config/nvim/lua/plugins/colorscheme.lua
+++ b/neovim/.config/nvim/lua/plugins/colorscheme.lua
@@ -6,6 +6,14 @@ return {
6 local ok = pcall(vim.cmd.colorscheme, "janah") 6 local ok = pcall(vim.cmd.colorscheme, "janah")
7 if not ok then 7 if not ok then
8 vim.cmd.colorscheme("desert") 8 vim.cmd.colorscheme("desert")
9 return
9 end 10 end
11
12 -- vim-janah predates floating windows and never defines NormalFloat/
13 -- FloatBorder, so they fall back to Neovim's near-black default and any
14 -- floating UI (:Mason, :Lazy, which-key's popup, ...) renders as a
15 -- mismatched black box. Match them to janah's own Normal/Comment colors.
16 vim.api.nvim_set_hl(0, "NormalFloat", { bg = "#262626", fg = "#dadada" })
17 vim.api.nvim_set_hl(0, "FloatBorder", { bg = "#262626", fg = "#585858" })
10 end, 18 end,
11} 19}
diff --git a/neovim/.config/nvim/lua/plugins/dap-python.lua b/neovim/.config/nvim/lua/plugins/dap-python.lua
new file mode 100644
index 0000000..b61e0f4
--- /dev/null
+++ b/neovim/.config/nvim/lua/plugins/dap-python.lua
@@ -0,0 +1,71 @@
1-- Python launch configs (nvim-dap-python), wired to the debugpy adapter
2-- that mason-nvim-dap.nvim installs (dap.lua). Kept in its own file since
3-- this is the "per-language" half of debugging, parallel to how
4-- ftplugin/python.lua holds Python-only settings.
5return {
6 "mfussenegger/nvim-dap-python",
7 -- Not left to lazy-load off `ft = "python"`: dap.lua's <leader>dc picker
8 -- works from any window (not just a Python buffer) and needs
9 -- dap.adapters.python already registered by the time a config is picked,
10 -- even if no .py buffer has been opened yet this session.
11 lazy = false,
12 -- mason.nvim is listed directly (not just transitively via dap.lua)
13 -- because config() below needs require("mason-registry") to be loadable
14 -- from startup, same reasoning as lazy = false above.
15 dependencies = { "mfussenegger/nvim-dap", "mason-org/mason.nvim" },
16 config = function()
17 local registry = require("mason-registry")
18 local pkg = registry.get_package("debugpy")
19
20 -- debugpy_path is the interpreter that runs the debug *adapter* itself
21 -- (Mason's isolated venv) - it does not control which Python runs your
22 -- script. dap-python defaults that to the active venv/system python3;
23 -- project-specific venvs may need a manual `pythonPath` override in a
24 -- generated launch config later.
25 local debugpy_path = pkg:get_install_path() .. "/venv/bin/python"
26
27 local function setup()
28 require("dap-python").setup(debugpy_path)
29
30 -- nvim-dap-python doesn't expose adapter.options for tuning, and
31 -- nvim-dap's default terminate-request timeout (3s) is tight enough
32 -- that debugpy's real response can arrive just after nvim-dap already
33 -- gave up and moved on ("No callback found" in dap.log) - <leader>dt
34 -- still works, but dap.session() doesn't clear for a few extra
35 -- seconds, so <leader>dc right after can catch it still "active".
36 -- Wrap the adapter dap-python just registered to give it more room.
37 local dap = require("dap")
38 local base_adapter = dap.adapters.python
39 dap.adapters.python = function(cb, config)
40 base_adapter(function(adapter)
41 adapter.options = adapter.options or {}
42 adapter.options.disconnect_timeout_sec = 8
43 cb(adapter)
44 end, config)
45 end
46 end
47
48 if pkg:is_installed() then
49 setup()
50 else
51 -- dap.lua's mason-nvim-dap ensure_installed triggers debugpy's
52 -- install, but that's async - on a fresh machine (or before it
53 -- finishes) debugpy_path doesn't exist on disk yet, and <leader>dc
54 -- would fail with a raw ENOENT from debugpy itself instead of a clear
55 -- message. Wire up the adapter once the install actually completes
56 -- instead of pointing it at a path that isn't there yet.
57 vim.notify(
58 "debugpy not installed yet - dap.lua's ensure_installed should be"
59 .. " installing it now (check :Mason); run :MasonInstall debugpy"
60 .. " if it doesn't show up there. Debugging will work once it's"
61 .. " done.",
62 vim.log.levels.WARN
63 )
64 registry:on("package:install:success", function(installed_pkg)
65 if installed_pkg.name == "debugpy" then
66 setup()
67 end
68 end)
69 end
70 end,
71}
diff --git a/neovim/.config/nvim/lua/plugins/dap.lua b/neovim/.config/nvim/lua/plugins/dap.lua
new file mode 100644
index 0000000..975eaea
--- /dev/null
+++ b/neovim/.config/nvim/lua/plugins/dap.lua
@@ -0,0 +1,181 @@
1-- Debugging via nvim-dap. Adapter installs are bridged through Mason
2-- (mason-nvim-dap.nvim), the same pattern lsp.lua uses for language servers,
3-- so adding a debug adapter for another language later is just adding it to
4-- ensure_installed here - Python specifics live in dap-python.lua, Node
5-- specifics in lua/config/dap_node.lua.
6return {
7 "mfussenegger/nvim-dap",
8 -- Not left to lazy-load off `keys`: a project's .nvim.lua (see
9 -- lua/config/project_run.lua) needs `require("dap")` to work immediately
10 -- when exrc sources it, which requires the plugin already being on
11 -- runtimepath rather than deferred until a <leader>d keymap fires.
12 lazy = false,
13 dependencies = {
14 {
15 "rcarriga/nvim-dap-ui",
16 dependencies = { "nvim-neotest/nvim-nio" },
17 },
18 "theHamsta/nvim-dap-virtual-text",
19 {
20 "jay-babu/mason-nvim-dap.nvim",
21 dependencies = { "mason-org/mason.nvim" },
22 },
23 },
24 keys = {
25 { "<leader>db", function() require("dap").toggle_breakpoint() end, desc = "Toggle breakpoint" },
26 {
27 "<leader>dB",
28 function() require("dap").set_breakpoint(vim.fn.input("Breakpoint condition: ")) end,
29 desc = "Conditional breakpoint",
30 },
31 {
32 "<leader>dc",
33 function()
34 local dap = require("dap")
35
36 if dap.session() then
37 -- Existing session: resume past a breakpoint, or nvim-dap's
38 -- terminate/pause/restart prompt if active but not stopped.
39 -- This branch doesn't consult buffer filetype, so it's fine to
40 -- call from any window.
41 dap.continue()
42 return
43 end
44
45 -- No active session: pick a config to start. dap.continue()'s own
46 -- path for this (select_config_and_run) only offers
47 -- dap.configurations[<current buffer's filetype>], so calling it
48 -- from the task-output dock or some other non-source window finds
49 -- nothing. Flatten across every filetype instead - same as
50 -- <leader>rr does for run tasks - so this works from anywhere and
51 -- you just pick the config you want from the list.
52 local entries = {}
53 for filetype, configs in pairs(dap.configurations) do
54 for _, config in ipairs(configs) do
55 table.insert(entries, { config = config, filetype = filetype })
56 end
57 end
58
59 -- .vscode/launch.json (if present in cwd) is read on-demand by
60 -- nvim-dap's own "dap.launch.json" provider - dap.ext.vscode's
61 -- load_launchjs() is deprecated now that this exists. That provider
62 -- is normally consulted inside dap.continue()'s own
63 -- select_config_and_run, which we're bypassing above, so call it
64 -- directly here to fold its configs into the same picker - the
65 -- debug-side counterpart to overseer's tasks.json support (README's
66 -- Run configurations section). Pair a launch.json entry with a
67 -- same-named tasks.json entry for PyCharm-style "run or debug the
68 -- same thing".
69 local ok, launchjson_configs = pcall(dap.providers.configs["dap.launch.json"])
70 if ok then
71 for _, config in ipairs(launchjson_configs) do
72 table.insert(entries, { config = config, filetype = config.type })
73 end
74 end
75
76 if vim.tbl_isempty(entries) then
77 vim.notify("No debug configurations registered (see project_run.lua)", vim.log.levels.WARN)
78 return
79 end
80
81 vim.ui.select(entries, {
82 prompt = "Configuration: ",
83 format_item = function(e) return e.config.name end,
84 }, function(entry)
85 if entry then
86 -- Pass along the filetype the config was registered under (not
87 -- necessarily the invoking buffer's) so breakpoint-hit window
88 -- jumping still picks a sensible window.
89 dap.run(entry.config, { filetype = entry.filetype })
90 end
91 end)
92 end,
93 desc = "Continue / start debugging",
94 },
95 { "<leader>do", function() require("dap").step_over() end, desc = "Step over" },
96 { "<leader>di", function() require("dap").step_into() end, desc = "Step into" },
97 { "<leader>dO", function() require("dap").step_out() end, desc = "Step out" },
98 { "<leader>dt", function() require("dap").terminate() end, desc = "Terminate session" },
99 { "<leader>dr", function() require("dap").repl.toggle() end, desc = "Toggle REPL" },
100 { "<leader>du", function() require("dapui").toggle() end, desc = "Toggle debug UI" },
101 },
102 config = function()
103 require("dapui").setup()
104 require("nvim-dap-virtual-text").setup()
105
106 -- nvim-dap doesn't define these signs itself - it just places them
107 -- (breakpoints.lua, session.lua's DapStopped placement) and expects the
108 -- config to have already called sign_define, so without this nothing
109 -- renders: no breakpoint marker in the sign column, and no indication
110 -- of the current line when a session stops (the stop itself still
111 -- works, sign_place just errors silently into a pcall).
112 vim.fn.sign_define("DapBreakpoint", { text = "●", texthl = "DiagnosticError" })
113 vim.fn.sign_define("DapBreakpointCondition", { text = "◆", texthl = "DiagnosticWarn" })
114 vim.fn.sign_define("DapBreakpointRejected", { text = "✗", texthl = "DiagnosticError" })
115 vim.fn.sign_define("DapLogPoint", { text = "📝", texthl = "DiagnosticInfo" })
116 vim.fn.sign_define("DapStopped", { text = "▶", texthl = "DiagnosticWarn", linehl = "DapStoppedLine" })
117 -- default = true so a colorscheme (or later config) can override this
118 -- without a load-order fight; Visual is just a visible fallback.
119 vim.api.nvim_set_hl(0, "DapStoppedLine", { default = true, link = "Visual" })
120
121 -- handlers = {} opts out of mason-nvim-dap's automatic adapter
122 -- registration: nvim-dap-python (dap-python.lua) registers its own
123 -- "python" adapter/config, so we don't want a second one clobbering it.
124 -- Adding a language without its own dap-<lang>.lua later should drop
125 -- this override so its adapter gets auto-registered instead.
126 require("mason-nvim-dap").setup({
127 -- ensure_installed wants nvim-dap *adapter/source* names, not Mason
128 -- *package* names - mason-nvim-dap looks each entry up in its own
129 -- name -> package mapping (source.lua: python -> debugpy) and just
130 -- silently skips anything it doesn't recognize, no error/log. Passing
131 -- "debugpy" here (the package name) matched nothing, so it never
132 -- triggered an install at all.
133 ensure_installed = { "python", "js" },
134 automatic_installation = true,
135 handlers = {},
136 })
137
138 -- Node/JS/TS adapter + default configs - not left to mason-nvim-dap's
139 -- own handler since "js" has no default adapter mapping there (only the
140 -- deprecated node2 does), see lua/config/dap_node.lua for why.
141 require("config.dap_node").setup()
142
143 local dap, dapui = require("dap"), require("dapui")
144
145 -- nvim-dap jumps to the stopped line using Neovim's 'switchbuf' option,
146 -- which defaults to "uselast": reuse the current window if it's already
147 -- a plain/source buffer, else fall back to the alternate window - and
148 -- if neither applies (e.g. <leader>dc was started from the task-output
149 -- dock or some other non-source window, which the picker now allows),
150 -- it silently does nothing. No error, no jump - the session *is*
151 -- stopped (stepping/continuing still works), it just never shows where.
152 -- Append "vsplit" so there's always a guaranteed-successful last resort
153 -- that opens the source file if nothing else matched.
154 dap.defaults.fallback.switchbuf = "usevisible,useopen,uselast,vsplit"
155
156 -- Open/close dap-ui automatically around a debug session, so <leader>du
157 -- is only needed to bring it back after manually closing it mid-session.
158 dap.listeners.after.event_initialized["dapui_config"] = function()
159 dapui.open()
160 end
161 dap.listeners.before.event_terminated["dapui_config"] = function()
162 dapui.close()
163 end
164 dap.listeners.before.event_exited["dapui_config"] = function()
165 dapui.close()
166 end
167
168 -- These events arrive asynchronously over debugpy's socket (a libuv
169 -- callback, not a keypress), and nvim-dap updates its state - cursor
170 -- position, signs, dapui panels - without that alone forcing Neovim to
171 -- repaint. So hitting a breakpoint, continuing past one, or a session
172 -- ending can all silently not *look* like anything happened until the
173 -- next keystroke forces a redraw that reveals the already-current
174 -- state (e.g. <leader>dc appearing to need pressing twice to resume).
175 for _, event in ipairs({ "event_stopped", "event_continued", "event_terminated", "event_exited" }) do
176 dap.listeners.after[event]["dap_redraw"] = function()
177 vim.cmd("redraw")
178 end
179 end
180 end,
181}
diff --git a/neovim/.config/nvim/lua/plugins/editing.lua b/neovim/.config/nvim/lua/plugins/editing.lua
index c1d5567..62b1dbf 100644
--- a/neovim/.config/nvim/lua/plugins/editing.lua
+++ b/neovim/.config/nvim/lua/plugins/editing.lua
@@ -1,13 +1,3 @@
1return { 1return {
2 {
3 "preservim/nerdcommenter",
4 init = function()
5 vim.g.NERDSpaceDelims = 1
6 vim.g.NERDCompactSexyComs = 1
7 vim.g.NERDDefaultAlign = "left"
8 vim.g.NERDCommentEmptyLines = 1
9 vim.g.NERDTrimTrailingWhitespace = 1
10 end,
11 },
12 "unblevable/quick-scope", 2 "unblevable/quick-scope",
13} 3}
diff --git a/neovim/.config/nvim/lua/plugins/fuzzyfinder.lua b/neovim/.config/nvim/lua/plugins/fuzzyfinder.lua
index 444d9ff..2ed9970 100644
--- a/neovim/.config/nvim/lua/plugins/fuzzyfinder.lua
+++ b/neovim/.config/nvim/lua/plugins/fuzzyfinder.lua
@@ -1,10 +1,32 @@
1return { 1return {
2 "ctrlpvim/ctrlp.vim", 2 "nvim-telescope/telescope.nvim",
3 init = function() 3 -- Not left to lazy-load off `keys`: telescope-ui-select's vim.ui.select()
4 -- Use ripgrep for listing files (fast, respects .gitignore) 4 -- override (below) needs to be installed from startup, otherwise
5 if vim.fn.executable("rg") == 1 then 5 -- overseer's <leader>rr / nvim-dap-python's <leader>dc pickers fall back
6 vim.g.ctrlp_user_command = 'rg %s --files --color=never --glob ""' 6 -- to Neovim's default list until something (e.g. <C-p>) happens to
7 vim.g.ctrlp_use_caching = 0 7 -- trigger telescope's own lazy-load first.
8 end 8 lazy = false,
9 dependencies = {
10 "nvim-lua/plenary.nvim",
11 "nvim-telescope/telescope-ui-select.nvim",
12 },
13 keys = {
14 { "<C-p>", "<cmd>Telescope find_files<cr>" },
15 { "<leader>fg", "<cmd>Telescope live_grep<cr>" },
16 { "<leader>fb", "<cmd>Telescope buffers<cr>" },
17 },
18 config = function()
19 -- telescope-ui-select makes vim.ui.select() - overseer's <leader>rr
20 -- template picker, nvim-dap-python's launch-config picker, LSP code
21 -- actions, etc. - a scrollable/fuzzy-searchable Telescope window
22 -- instead of Neovim's default numbered-list-in-the-cmdline prompt.
23 require("telescope").setup({
24 extensions = {
25 ["ui-select"] = {
26 require("telescope.themes").get_dropdown({}),
27 },
28 },
29 })
30 require("telescope").load_extension("ui-select")
9 end, 31 end,
10} 32}
diff --git a/neovim/.config/nvim/lua/plugins/git.lua b/neovim/.config/nvim/lua/plugins/git.lua
index 8402905..ef07442 100644
--- a/neovim/.config/nvim/lua/plugins/git.lua
+++ b/neovim/.config/nvim/lua/plugins/git.lua
@@ -1,4 +1,7 @@
1return { 1return {
2 "tpope/vim-fugitive", 2 "tpope/vim-fugitive",
3 "mhinz/vim-signify", 3 {
4 "lewis6991/gitsigns.nvim",
5 opts = {},
6 },
4} 7}
diff --git a/neovim/.config/nvim/lua/plugins/indent-detect.lua b/neovim/.config/nvim/lua/plugins/indent-detect.lua
new file mode 100644
index 0000000..945769c
--- /dev/null
+++ b/neovim/.config/nvim/lua/plugins/indent-detect.lua
@@ -0,0 +1,8 @@
1-- Detects each buffer's actual indentation/spacing on open and sets
2-- shiftwidth/tabstop/expandtab to match, rather than relying solely on
3-- ftplugin defaults (e.g. ftplugin/python.lua) or global options.opt.
4return {
5 "nmac427/guess-indent.nvim",
6 event = { "BufReadPost", "BufNewFile" },
7 opts = {},
8}
diff --git a/neovim/.config/nvim/lua/plugins/langs.lua b/neovim/.config/nvim/lua/plugins/langs.lua
deleted file mode 100644
index 7bf22d4..0000000
--- a/neovim/.config/nvim/lua/plugins/langs.lua
+++ /dev/null
@@ -1,14 +0,0 @@
1return {
2 {
3 "chase/vim-ansible-yaml",
4 cond = function()
5 return vim.fn.executable("ansible") == 1
6 end,
7 },
8 {
9 "hashivim/vim-terraform",
10 cond = function()
11 return vim.fn.executable("terraform") == 1
12 end,
13 },
14}
diff --git a/neovim/.config/nvim/lua/plugins/overseer.lua b/neovim/.config/nvim/lua/plugins/overseer.lua
new file mode 100644
index 0000000..99d0f5f
--- /dev/null
+++ b/neovim/.config/nvim/lua/plugins/overseer.lua
@@ -0,0 +1,57 @@
1-- Run configurations (PyCharm-style "run this again"), language-agnostic:
2-- works for npm scripts, make, cargo, arbitrary shell commands, python
3-- scripts, etc, unlike DAP which is Python-only for now.
4return {
5 "stevearc/overseer.nvim",
6 -- Not left to lazy-load off `cmd`/`keys`: a project's .nvim.lua (see
7 -- lua/config/project_run.lua) needs `require("overseer")` to work
8 -- immediately when exrc sources it, which requires the plugin already
9 -- being on runtimepath rather than deferred until first use.
10 lazy = false,
11 keys = {
12 { "<leader>rr", "<cmd>OverseerRun<cr>", desc = "Run task" },
13 {
14 "<leader>rl",
15 function()
16 -- :OverseerRestartLast exists in current overseer.nvim versions; the
17 -- run_action fallback covers older/newer versions that lack it
18 -- rather than hard-failing the keymap.
19 if vim.fn.exists(":OverseerRestartLast") == 2 then
20 vim.cmd("OverseerRestartLast")
21 return
22 end
23 local overseer = require("overseer")
24 local tasks = overseer.list_tasks({ recent_first = true })
25 if vim.tbl_isempty(tasks) then
26 vim.notify("No previous task to rerun", vim.log.levels.WARN)
27 else
28 overseer.run_action(tasks[1], "restart")
29 end
30 end,
31 desc = "Rerun last task",
32 },
33 { "<leader>ro", "<cmd>OverseerToggle<cr>", desc = "Toggle task list/output" },
34 { "<leader>rq", "<cmd>OverseerQuickAction<cr>", desc = "Quick action on task" },
35 },
36 opts = {
37 -- Patches nvim-dap so a debug config can set preLaunchTask/postDebugTask
38 -- to an overseer task name (or "${defaultBuildTask}" for whatever task
39 -- is tagged BUILD) - lets a dap.configurations entry build before it
40 -- launches the debugger. See README's Debugging section.
41 dap = true,
42 -- overseer's actual default components (on_exit_set_status,
43 -- on_complete_notify, on_complete_dispose) don't open the output panel
44 -- on their own - without open_output added here, starting any task
45 -- (whether from a template, .nvim.lua/project_run.lua, or auto-detected
46 -- npm/make scripts) runs silently until you manually <leader>ro to look.
47 -- Adding it here applies to every task, not just custom ones.
48 component_aliases = {
49 default = {
50 "on_exit_set_status",
51 "on_complete_notify",
52 { "on_complete_dispose", require_view = { "SUCCESS", "FAILURE" } },
53 { "open_output", direction = "dock", focus = false, on_start = "always" },
54 },
55 },
56 },
57}
diff --git a/neovim/.config/nvim/lua/plugins/statusline.lua b/neovim/.config/nvim/lua/plugins/statusline.lua
index b93b34c..17741a6 100644
--- a/neovim/.config/nvim/lua/plugins/statusline.lua
+++ b/neovim/.config/nvim/lua/plugins/statusline.lua
@@ -1,23 +1,5 @@
1return { 1return {
2 "bling/vim-airline", 2 "nvim-lualine/lualine.nvim",
3 lazy = false, 3 dependencies = { "nvim-tree/nvim-web-devicons" },
4 dependencies = { 4 opts = {},
5 "vim-airline/vim-airline-themes",
6 "edkolev/tmuxline.vim",
7 },
8 init = function()
9 -- On arch linux install the following:
10 -- otf-powerline-symbols-git
11 -- powerline-fonts-git
12 vim.g.airline_powerline_fonts = 1
13 vim.g["airline#extensions#tabline#enabled"] = 1
14
15 -- vim.g dict reads return a copy, not a live reference, so the nested
16 -- field has to be set via a local table and reassigned.
17 local symbols = vim.g.airline_symbols or {}
18 symbols.space = "\u{a0}"
19 vim.g.airline_symbols = symbols
20
21 vim.opt.laststatus = 2
22 end,
23} 5}
diff --git a/neovim/.config/nvim/lua/plugins/toggleterm.lua b/neovim/.config/nvim/lua/plugins/toggleterm.lua
new file mode 100644
index 0000000..b8067a3
--- /dev/null
+++ b/neovim/.config/nvim/lua/plugins/toggleterm.lua
@@ -0,0 +1,18 @@
1-- Scratch terminal for running arbitrary shell commands, toggled like
2-- claude-code.nvim's own <C-,> - open/close (not kill) a persistent split.
3return {
4 "akinsho/toggleterm.nvim",
5 lazy = false, -- global <C-.> toggle must work from any window, not just after some trigger event
6 opts = {
7 open_mapping = [[<C-.>]], -- registers n, t (terminal_mappings) and i (insert_mappings) mode maps automatically
8 direction = "vertical",
9 -- toggleterm's vertical direction always opens via "botright vsplit" internally,
10 -- so this already matches claude-code.nvim's right-side placement without
11 -- touching 'splitright'.
12 size = function(term)
13 if term.direction == "vertical" then
14 return math.floor(vim.o.columns * 0.3) -- matches claude-code.nvim's window.position ratio
15 end
16 end,
17 },
18}
diff --git a/neovim/.config/nvim/lua/plugins/which-key.lua b/neovim/.config/nvim/lua/plugins/which-key.lua
new file mode 100644
index 0000000..190d651
--- /dev/null
+++ b/neovim/.config/nvim/lua/plugins/which-key.lua
@@ -0,0 +1,17 @@
1-- Popup showing available keymaps/groups as you type a prefix like
2-- <leader>d or <leader>r, so the debug/run namespaces stay discoverable
3-- without memorizing every letter. Auto-discovers each keymap's own `desc`
4-- field from dap.lua/overseer.lua/etc - only the group headers need
5-- registering here.
6return {
7 "folke/which-key.nvim",
8 event = "VeryLazy",
9 opts = {
10 spec = {
11 { "<leader>d", group = "Debug", icon = "🐞" },
12 { "<leader>r", group = "Run", icon = "▶" },
13 { "<leader>f", group = "Find" },
14 { "<leader>e", group = "Explorer" },
15 },
16 },
17}
diff --git a/neovim/.config/nvim/nvim.log b/neovim/.config/nvim/nvim.log
new file mode 100644
index 0000000..7158332
--- /dev/null
+++ b/neovim/.config/nvim/nvim.log
@@ -0,0 +1,6 @@
1WRN 2026-09-14T16:30:21.846 ?.4836 server_start:197: Failed to start server: operation not permitted: /tmp/claude-501/nvim.tom/StQYRj/nvim.4836.0
2WRN 2026-09-14T16:30:26.261 ?.4845 server_start:197: Failed to start server: operation not permitted: /tmp/claude-501/nvim.tom/K4I50n/nvim.4845.0
3WRN 2026-09-14T16:31:13.471 ?.4921 server_start:197: Failed to start server: operation not permitted: /tmp/claude-501/nvim.tom/OkXebD/nvim.4921.0
4WRN 2026-09-14T16:33:09.911 ?.5030 server_start:197: Failed to start server: operation not permitted: /tmp/claude-501/nvim.tom/e73IV2/nvim.5030.0
5WRN 2026-09-15T10:45:56.983 ?.34498 server_start:197: Failed to start server: operation not permitted: /tmp/claude-501/nvim.tom/R2azoM/nvim.34498.0
6WRN 2026-09-15T11:05:45.401 c/nvim.35169.0 server_start:197: Failed to start server: operation not permitted: /tmp/claude-501/nvim.tom/8xMtRC/nvim.38701.0
diff --git a/nvim.log b/nvim.log
new file mode 100644
index 0000000..54cac1c
--- /dev/null
+++ b/nvim.log
@@ -0,0 +1,4 @@
1WRN 2026-09-15T10:52:53.198 c/nvim.35169.0 server_start:197: Failed to start server: operation not permitted: /tmp/claude-501/nvim.tom/70FEV5/nvim.35600.0
2ERR 2026-09-15T10:52:53.200 c/nvim.35169.0 pty_proc_spawn:190: forkpty failed: Operation not permitted
3WRN 2026-09-15T10:53:00.647 c/nvim.35169.0 server_start:197: Failed to start server: operation not permitted: /tmp/claude-501/nvim.tom/NuetHc/nvim.35627.0
4ERR 2026-09-15T10:53:00.650 c/nvim.35169.0 pty_proc_spawn:190: forkpty failed: Operation not permitted