1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
-- Single-source-of-truth helper for project-local run/debug configs, meant
-- to be called once per config from a project's `.nvim.lua` (requires
-- `opt.exrc` - see options.lua - and trusting the file once via
-- `:help :trust`). One call registers both an overseer run task and a
-- matching nvim-dap debug config, so a project states its command once
-- instead of repeating it across a separate run-file and debug-file.
--
-- Example .nvim.lua (script invocation):
-- require("config.project_run").python({
-- name = "Run app",
-- program = vim.fn.getcwd() .. "/main.py",
-- args = { "--flag" }, -- optional, shared by both run and debug
-- })
--
-- Example .nvim.lua (module invocation, `python -m <module>`):
-- require("config.project_run").python({
-- name = "Run app",
-- module = "fastapi",
-- args = { "dev", "app" },
-- })
--
-- This shows "Run app" in the <leader>rr picker (plain execution) and in
-- the <leader>dc picker (same script/module, under debugpy, breakpoints
-- included). If <cwd>/.venv exists, both get VIRTUAL_ENV/PATH pointed at
-- it, same as `uv run` (or `source .venv/bin/activate`) would - and the
-- debug config additionally gets pythonPath set to that venv's interpreter,
-- since debugpy runs the script with the literal interpreter it's told
-- rather than resolving `python3` via PATH.
--
-- Example .nvim.lua (Node, script invocation):
-- require("config.project_run").node({
-- name = "Run app",
-- program = vim.fn.getcwd() .. "/index.js",
-- args = { "--flag" }, -- optional, shared by both run and debug
-- })
--
-- Example .nvim.lua (Node, `npm run <script>` invocation):
-- require("config.project_run").node({
-- name = "Run app",
-- npmScript = "dev",
-- })
local M = {}
local function venv_dir()
local venv = vim.fn.getcwd() .. "/.venv"
if vim.fn.isdirectory(venv) == 0 then
return nil
end
return venv
end
local function venv_env(venv)
return {
VIRTUAL_ENV = venv,
PATH = venv .. "/bin:" .. vim.env.PATH,
}
end
function M.python(opts)
assert(opts.name, "project_run.python: opts.name is required")
assert(opts.program or opts.module, "project_run.python: opts.program or opts.module is required")
local venv = venv_dir()
local env = venv and venv_env(venv) or nil
local cmd = opts.module and { "python3", "-m", opts.module } or { "python3", opts.program }
require("overseer").register_template({
name = opts.name,
builder = function()
return {
cmd = cmd,
args = opts.args,
name = opts.name,
env = env,
}
end,
})
local dap = require("dap")
dap.configurations.python = dap.configurations.python or {}
table.insert(dap.configurations.python, {
type = "python",
request = "launch",
name = opts.name,
program = opts.program,
module = opts.module,
args = opts.args,
env = env,
-- nvim-dap-python only auto-detects an interpreter (VIRTUAL_ENV env var,
-- then a venv/.venv/env/.env folder search) when pythonPath/python isn't
-- already set - and debugpy needs the literal interpreter path here
-- regardless (it spawns it directly, not through a shell that'd resolve
-- `python3` via PATH the way env.PATH above does for the overseer task).
-- Set it explicitly from the same .venv we already found, so debugging
-- doesn't depend on that separate heuristic agreeing with us - without
-- this, a missed/wrong guess falls back to debugpy's own adapter venv,
-- which can't see the project's installed packages.
pythonPath = venv and (venv .. "/bin/python") or nil,
})
end
-- Node has no venv-style interpreter juggling to do - node_modules
-- resolution is automatic - so this is a straight translation of opts into
-- an overseer task plus a pwa-node dap config, into all four JS/TS
-- filetypes (dap_node.lua registers the same four for its own defaults).
function M.node(opts)
assert(opts.name, "project_run.node: opts.name is required")
assert(opts.program or opts.npmScript, "project_run.node: opts.program or opts.npmScript is required")
local cmd = opts.npmScript and { "npm", "run", opts.npmScript } or { "node", opts.program }
require("overseer").register_template({
name = opts.name,
builder = function()
return {
cmd = cmd,
args = opts.args,
name = opts.name,
}
end,
})
-- A fresh table per filetype - nvim-dap's session setup can stash
-- bookkeeping fields onto a running config, so the four filetypes
-- shouldn't share one table instance.
local function config()
local c = {
type = "pwa-node",
request = "launch",
name = opts.name,
args = opts.args,
cwd = vim.fn.getcwd(),
}
if opts.npmScript then
c.runtimeExecutable = "npm"
c.runtimeArgs = { "run", opts.npmScript }
else
c.program = opts.program
end
return c
end
local dap = require("dap")
for _, filetype in ipairs({ "javascript", "typescript", "javascriptreact", "typescriptreact" }) do
dap.configurations[filetype] = dap.configurations[filetype] or {}
table.insert(dap.configurations[filetype], config())
end
end
return M
|