aboutsummaryrefslogtreecommitdiffstats
path: root/neovim/.config/nvim/lua/config/project_run.lua
diff options
context:
space:
mode:
Diffstat (limited to 'neovim/.config/nvim/lua/config/project_run.lua')
-rw-r--r--neovim/.config/nvim/lua/config/project_run.lua151
1 files changed, 151 insertions, 0 deletions
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