diff options
| author | 2026-09-16 08:47:13 +0200 | |
|---|---|---|
| committer | 2026-09-16 08:47:13 +0200 | |
| commit | a5e8bbeb9d79d905a193e95a39acd29bc37e6c66 (patch) | |
| tree | 5b42d54e6b91d454bcbac89f7aac42b0863a869d /neovim/.config/nvim/lua/config | |
| parent | 6906a9d55928c6250ff106d041b73fdd1ac8aae6 (diff) | |
| download | dotfiles-a5e8bbeb9d79d905a193e95a39acd29bc37e6c66.tar.gz dotfiles-a5e8bbeb9d79d905a193e95a39acd29bc37e6c66.tar.bz2 dotfiles-a5e8bbeb9d79d905a193e95a39acd29bc37e6c66.zip | |
Updated dotfiles
Diffstat (limited to 'neovim/.config/nvim/lua/config')
| -rw-r--r-- | neovim/.config/nvim/lua/config/autocmds.lua | 43 | ||||
| -rw-r--r-- | neovim/.config/nvim/lua/config/dap_node.lua | 88 | ||||
| -rw-r--r-- | neovim/.config/nvim/lua/config/keymaps.lua | 12 | ||||
| -rw-r--r-- | neovim/.config/nvim/lua/config/options.lua | 6 | ||||
| -rw-r--r-- | neovim/.config/nvim/lua/config/project_run.lua | 151 |
5 files changed, 300 insertions, 0 deletions
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. | ||
| 22 | local sourced_project_configs = {} | ||
| 23 | |||
| 24 | local 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 | ||
| 53 | end | ||
| 54 | |||
| 55 | vim.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. | ||
| 22 | local M = {} | ||
| 23 | |||
| 24 | local 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 | } | ||
| 41 | end | ||
| 42 | |||
| 43 | function 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 | ||
| 86 | end | ||
| 87 | |||
| 88 | return 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]]) | |||
| 14 | map("t", "<C-h>", [[<C-\><C-n><C-w>h]]) | 14 | map("t", "<C-h>", [[<C-\><C-n><C-w>h]]) |
| 15 | map("t", "<C-l>", [[<C-\><C-n><C-w>l]]) | 15 | map("t", "<C-l>", [[<C-\><C-n><C-w>l]]) |
| 16 | 16 | ||
| 17 | -- Copy to clipboard | ||
| 18 | map("v", "<leader>y", '"+y') | ||
| 19 | map("n", "<leader>Y", '"+yg_') | ||
| 20 | map("n", "<leader>y", '"+y') | ||
| 21 | map("n", "<leader>yy", '"+yy') | ||
| 22 | |||
| 23 | -- Paste from clipboard | ||
| 24 | map("n", "<leader>p", '"+p') | ||
| 25 | map("n", "<leader>P", '"+P') | ||
| 26 | map("v", "<leader>p", '"+p') | ||
| 27 | map("v", "<leader>P", '"+P') | ||
| 28 | |||
| 17 | -- Never use the arrow keys | 29 | -- Never use the arrow keys |
| 18 | for _, key in ipairs({ "<Up>", "<Down>", "<Left>", "<Right>" }) do | 30 | for _, 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 | |||
| 34 | opt.incsearch = true | 34 | opt.incsearch = true |
| 35 | opt.ignorecase = true | 35 | opt.ignorecase = true |
| 36 | opt.smartcase = true | 36 | opt.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 | |||
| 43 | local M = {} | ||
| 44 | |||
| 45 | local 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 | ||
| 51 | end | ||
| 52 | |||
| 53 | local function venv_env(venv) | ||
| 54 | return { | ||
| 55 | VIRTUAL_ENV = venv, | ||
| 56 | PATH = venv .. "/bin:" .. vim.env.PATH, | ||
| 57 | } | ||
| 58 | end | ||
| 59 | |||
| 60 | function 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 | }) | ||
| 101 | end | ||
| 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). | ||
| 107 | function 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 | ||
| 149 | end | ||
| 150 | |||
| 151 | return M | ||
