vspdb 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vspdb-0.1.0/LICENSE +21 -0
- vspdb-0.1.0/PKG-INFO +161 -0
- vspdb-0.1.0/README.md +133 -0
- vspdb-0.1.0/pyproject.toml +44 -0
- vspdb-0.1.0/setup.cfg +4 -0
- vspdb-0.1.0/vspdb/__init__.py +11 -0
- vspdb-0.1.0/vspdb/__main__.py +8 -0
- vspdb-0.1.0/vspdb/cli.py +469 -0
- vspdb-0.1.0/vspdb/vscode_extension/README.md +32 -0
- vspdb-0.1.0/vspdb/vscode_extension/extension.js +128 -0
- vspdb-0.1.0/vspdb/vscode_extension/package.json +63 -0
- vspdb-0.1.0/vspdb.egg-info/PKG-INFO +161 -0
- vspdb-0.1.0/vspdb.egg-info/SOURCES.txt +15 -0
- vspdb-0.1.0/vspdb.egg-info/dependency_links.txt +1 -0
- vspdb-0.1.0/vspdb.egg-info/entry_points.txt +2 -0
- vspdb-0.1.0/vspdb.egg-info/requires.txt +1 -0
- vspdb-0.1.0/vspdb.egg-info/top_level.txt +1 -0
vspdb-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kaustubh Mhatre (^_^)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
vspdb-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vspdb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Run a Python script under the VS Code debugger from the terminal, halting on the first line.
|
|
5
|
+
Author-email: Kaustubh <kmhatre14@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kmhatre14/vspdb
|
|
8
|
+
Project-URL: Repository, https://github.com/kmhatre14/vspdb
|
|
9
|
+
Project-URL: Issues, https://github.com/kmhatre14/vspdb/issues
|
|
10
|
+
Keywords: debugpy,vscode,debugger,pdb,debugging,cli,stopOnEntry
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: debugpy>=1.6
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# vspdb — VS Code pdb for the terminal
|
|
30
|
+
|
|
31
|
+
Debug any Python script (with arguments) in the VS Code debugger straight from
|
|
32
|
+
the terminal — no hand-written `launch.json`, no `debug.py` wrapper.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python -m vspdb my_script.py arg1 arg2
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
vspdb starts a `debugpy` debug server, gets VS Code to attach, and **halts on the
|
|
39
|
+
first line of your script** — just like `stopOnEntry`. It ships a small VS Code
|
|
40
|
+
helper (installed automatically) so this works with **zero clicks**.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Install — one package, that's it
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install vspdb # (or: pip install -e . from this folder)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
That's the only thing you install. vspdb ships a tiny VS Code helper extension
|
|
51
|
+
**inside** the package and installs it into VS Code for you on first use. So:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python -m vspdb my_script.py 10 # explicit (or: vspdb my_script.py 10)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The **first** run prints a one-time note that it installed the helper — **reload
|
|
58
|
+
the VS Code window once** (`Ctrl+Shift+P` → "Developer: Reload Window"). After
|
|
59
|
+
that it's fully automatic.
|
|
60
|
+
|
|
61
|
+
> Tip: install into the same interpreter VS Code uses for the workspace
|
|
62
|
+
> (`Ctrl+Shift+P` → "Python: Select Interpreter") so breakpoints line up. You can
|
|
63
|
+
> also pre-install the helper without running a script: `python -m vspdb --setup`.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## How you use it: zero clicks
|
|
68
|
+
|
|
69
|
+
Once the helper is active (after that one reload), just run scripts — each one
|
|
70
|
+
attaches and halts on its first line, **no debug panel, no F5**:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python -m vspdb my_script.py 10
|
|
74
|
+
python -m vspdb another.py --flag
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The helper keeps a `debugpy` listener ready in VS Code (one per window, on a
|
|
78
|
+
per-project port) and vspdb connects to the right window automatically.
|
|
79
|
+
|
|
80
|
+
### Don't want the helper? (no-extension fallback)
|
|
81
|
+
|
|
82
|
+
Run with `--no-ext` (or set `VSPDB_NO_AUTO_EXTENSION=1`). Then vspdb won't touch
|
|
83
|
+
VS Code; instead it writes two configs to `.vscode/launch.json` and you attach
|
|
84
|
+
manually:
|
|
85
|
+
|
|
86
|
+
- **vspdb: Listen** — start it once per window (F5), then runs are zero-click.
|
|
87
|
+
- **vspdb: Attach** — vspdb waits; press F5 once per run.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Options
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
python -m vspdb [options] <script.py> [script args...]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Everything **after** the script name is passed to your script untouched (so your
|
|
98
|
+
script can use `--port`, `--host`, etc. without clashing with vspdb).
|
|
99
|
+
|
|
100
|
+
| Option | Description |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `--port, -p PORT` | Debug port (default: `5678`). |
|
|
103
|
+
| `--host HOST` | Debug host/interface (default: `127.0.0.1`). |
|
|
104
|
+
| `--listen` | Force listen mode (open the port, wait for VS Code). |
|
|
105
|
+
| `--connect` | Force connect mode (attach to a VS Code that is already listening). |
|
|
106
|
+
| `--no-wait` | Don't wait for VS Code; run immediately (disables stop-on-entry). |
|
|
107
|
+
| `--no-stop` | Attach, but don't halt on the first line. |
|
|
108
|
+
| `--no-launch-json` | Don't create/update `.vscode/launch.json`. |
|
|
109
|
+
| `--setup` | (Re)install the bundled VS Code helper extension and exit. |
|
|
110
|
+
| `--no-ext` | Don't auto-install the VS Code helper on this run. |
|
|
111
|
+
|
|
112
|
+
The `--port` default is read from `.vscode/vspdb.json` (written by the helper) if
|
|
113
|
+
present, else `5678`. Disable auto-install globally with `VSPDB_NO_AUTO_EXTENSION=1`.
|
|
114
|
+
|
|
115
|
+
Examples:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Different port
|
|
119
|
+
python -m vspdb --port 5690 my_script.py 10
|
|
120
|
+
|
|
121
|
+
# Your script gets its own --port; vspdb does not consume it
|
|
122
|
+
python -m vspdb server.py --port 8080 --debug
|
|
123
|
+
|
|
124
|
+
# Run under the debugger but don't stop on entry (rely on your own breakpoints)
|
|
125
|
+
python -m vspdb --no-stop my_script.py 10
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## How it works
|
|
131
|
+
|
|
132
|
+
1. Parses `vspdb` options, then treats the first positional as the script and the
|
|
133
|
+
rest as the script's `argv`.
|
|
134
|
+
2. Installs the bundled VS Code helper into VS Code if it isn't there yet (unless
|
|
135
|
+
`--no-ext`), and ensures `.vscode/launch.json` has the manual-fallback attach
|
|
136
|
+
configs (merging, never clobbering, any existing file).
|
|
137
|
+
3. Resolves the port: if `--port` isn't given, it walks up from the script's
|
|
138
|
+
directory for `.vscode/vspdb.json` (written by the auto-listen extension) so it
|
|
139
|
+
attaches to the window that owns the script's project — important when several
|
|
140
|
+
VS Code windows are open. Then it **connects** if that port is being listened
|
|
141
|
+
on, otherwise **listens** and waits for VS Code.
|
|
142
|
+
4. Once the client is attached, it runs your script as `__main__` with the right
|
|
143
|
+
`sys.argv`, having injected a `debugpy.breakpoint()` at the first executable
|
|
144
|
+
line so the debugger halts there. Original line numbers are preserved, so
|
|
145
|
+
tracebacks stay accurate.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Requirements
|
|
150
|
+
|
|
151
|
+
- Python 3.8+
|
|
152
|
+
- `debugpy` (installed automatically)
|
|
153
|
+
- VS Code with the Python / Python Debugger extension
|
|
154
|
+
|
|
155
|
+
## Author
|
|
156
|
+
|
|
157
|
+
Kaustubh — [kmhatre14@gmail.com](mailto:kmhatre14@gmail.com) · [github.com/kmhatre14](https://github.com/kmhatre14)
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
[MIT](LICENSE). Built with assistance from Claude Opus 4.8.
|
vspdb-0.1.0/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# vspdb — VS Code pdb for the terminal
|
|
2
|
+
|
|
3
|
+
Debug any Python script (with arguments) in the VS Code debugger straight from
|
|
4
|
+
the terminal — no hand-written `launch.json`, no `debug.py` wrapper.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
python -m vspdb my_script.py arg1 arg2
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
vspdb starts a `debugpy` debug server, gets VS Code to attach, and **halts on the
|
|
11
|
+
first line of your script** — just like `stopOnEntry`. It ships a small VS Code
|
|
12
|
+
helper (installed automatically) so this works with **zero clicks**.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Install — one package, that's it
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install vspdb # (or: pip install -e . from this folder)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
That's the only thing you install. vspdb ships a tiny VS Code helper extension
|
|
23
|
+
**inside** the package and installs it into VS Code for you on first use. So:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python -m vspdb my_script.py 10 # explicit (or: vspdb my_script.py 10)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The **first** run prints a one-time note that it installed the helper — **reload
|
|
30
|
+
the VS Code window once** (`Ctrl+Shift+P` → "Developer: Reload Window"). After
|
|
31
|
+
that it's fully automatic.
|
|
32
|
+
|
|
33
|
+
> Tip: install into the same interpreter VS Code uses for the workspace
|
|
34
|
+
> (`Ctrl+Shift+P` → "Python: Select Interpreter") so breakpoints line up. You can
|
|
35
|
+
> also pre-install the helper without running a script: `python -m vspdb --setup`.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## How you use it: zero clicks
|
|
40
|
+
|
|
41
|
+
Once the helper is active (after that one reload), just run scripts — each one
|
|
42
|
+
attaches and halts on its first line, **no debug panel, no F5**:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python -m vspdb my_script.py 10
|
|
46
|
+
python -m vspdb another.py --flag
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The helper keeps a `debugpy` listener ready in VS Code (one per window, on a
|
|
50
|
+
per-project port) and vspdb connects to the right window automatically.
|
|
51
|
+
|
|
52
|
+
### Don't want the helper? (no-extension fallback)
|
|
53
|
+
|
|
54
|
+
Run with `--no-ext` (or set `VSPDB_NO_AUTO_EXTENSION=1`). Then vspdb won't touch
|
|
55
|
+
VS Code; instead it writes two configs to `.vscode/launch.json` and you attach
|
|
56
|
+
manually:
|
|
57
|
+
|
|
58
|
+
- **vspdb: Listen** — start it once per window (F5), then runs are zero-click.
|
|
59
|
+
- **vspdb: Attach** — vspdb waits; press F5 once per run.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Options
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
python -m vspdb [options] <script.py> [script args...]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Everything **after** the script name is passed to your script untouched (so your
|
|
70
|
+
script can use `--port`, `--host`, etc. without clashing with vspdb).
|
|
71
|
+
|
|
72
|
+
| Option | Description |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `--port, -p PORT` | Debug port (default: `5678`). |
|
|
75
|
+
| `--host HOST` | Debug host/interface (default: `127.0.0.1`). |
|
|
76
|
+
| `--listen` | Force listen mode (open the port, wait for VS Code). |
|
|
77
|
+
| `--connect` | Force connect mode (attach to a VS Code that is already listening). |
|
|
78
|
+
| `--no-wait` | Don't wait for VS Code; run immediately (disables stop-on-entry). |
|
|
79
|
+
| `--no-stop` | Attach, but don't halt on the first line. |
|
|
80
|
+
| `--no-launch-json` | Don't create/update `.vscode/launch.json`. |
|
|
81
|
+
| `--setup` | (Re)install the bundled VS Code helper extension and exit. |
|
|
82
|
+
| `--no-ext` | Don't auto-install the VS Code helper on this run. |
|
|
83
|
+
|
|
84
|
+
The `--port` default is read from `.vscode/vspdb.json` (written by the helper) if
|
|
85
|
+
present, else `5678`. Disable auto-install globally with `VSPDB_NO_AUTO_EXTENSION=1`.
|
|
86
|
+
|
|
87
|
+
Examples:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Different port
|
|
91
|
+
python -m vspdb --port 5690 my_script.py 10
|
|
92
|
+
|
|
93
|
+
# Your script gets its own --port; vspdb does not consume it
|
|
94
|
+
python -m vspdb server.py --port 8080 --debug
|
|
95
|
+
|
|
96
|
+
# Run under the debugger but don't stop on entry (rely on your own breakpoints)
|
|
97
|
+
python -m vspdb --no-stop my_script.py 10
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## How it works
|
|
103
|
+
|
|
104
|
+
1. Parses `vspdb` options, then treats the first positional as the script and the
|
|
105
|
+
rest as the script's `argv`.
|
|
106
|
+
2. Installs the bundled VS Code helper into VS Code if it isn't there yet (unless
|
|
107
|
+
`--no-ext`), and ensures `.vscode/launch.json` has the manual-fallback attach
|
|
108
|
+
configs (merging, never clobbering, any existing file).
|
|
109
|
+
3. Resolves the port: if `--port` isn't given, it walks up from the script's
|
|
110
|
+
directory for `.vscode/vspdb.json` (written by the auto-listen extension) so it
|
|
111
|
+
attaches to the window that owns the script's project — important when several
|
|
112
|
+
VS Code windows are open. Then it **connects** if that port is being listened
|
|
113
|
+
on, otherwise **listens** and waits for VS Code.
|
|
114
|
+
4. Once the client is attached, it runs your script as `__main__` with the right
|
|
115
|
+
`sys.argv`, having injected a `debugpy.breakpoint()` at the first executable
|
|
116
|
+
line so the debugger halts there. Original line numbers are preserved, so
|
|
117
|
+
tracebacks stay accurate.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Requirements
|
|
122
|
+
|
|
123
|
+
- Python 3.8+
|
|
124
|
+
- `debugpy` (installed automatically)
|
|
125
|
+
- VS Code with the Python / Python Debugger extension
|
|
126
|
+
|
|
127
|
+
## Author
|
|
128
|
+
|
|
129
|
+
Kaustubh — [kmhatre14@gmail.com](mailto:kmhatre14@gmail.com) · [github.com/kmhatre14](https://github.com/kmhatre14)
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
[MIT](LICENSE). Built with assistance from Claude Opus 4.8.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "vspdb"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Run a Python script under the VS Code debugger from the terminal, halting on the first line."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Kaustubh", email = "kmhatre14@gmail.com" }]
|
|
13
|
+
keywords = ["debugpy", "vscode", "debugger", "pdb", "debugging", "cli", "stopOnEntry"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Topic :: Software Development :: Debuggers",
|
|
27
|
+
]
|
|
28
|
+
dependencies = ["debugpy>=1.6"]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/kmhatre14/vspdb"
|
|
32
|
+
Repository = "https://github.com/kmhatre14/vspdb"
|
|
33
|
+
Issues = "https://github.com/kmhatre14/vspdb/issues"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
vspdb = "vspdb.cli:main"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools]
|
|
39
|
+
packages = ["vspdb"]
|
|
40
|
+
|
|
41
|
+
# Ship the bundled VS Code helper extension inside the wheel so `python -m vspdb`
|
|
42
|
+
# can install it into VS Code automatically.
|
|
43
|
+
[tool.setuptools.package-data]
|
|
44
|
+
vspdb = ["vscode_extension/*"]
|
vspdb-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""vspdb — run a Python script under the VS Code debugger straight from the terminal.
|
|
2
|
+
|
|
3
|
+
python -m vspdb my_script.py arg1 arg2
|
|
4
|
+
|
|
5
|
+
See :mod:`vspdb.cli` for the implementation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from vspdb.cli import main
|
|
9
|
+
|
|
10
|
+
__all__ = ["main"]
|
|
11
|
+
__version__ = "0.1.0"
|
vspdb-0.1.0/vspdb/cli.py
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"""vspdb — run a Python script under the VS Code debugger from the terminal.
|
|
2
|
+
|
|
3
|
+
The goal: instead of hand-editing ``.vscode/launch.json`` every time you want to
|
|
4
|
+
debug a script that takes arguments, you just run::
|
|
5
|
+
|
|
6
|
+
python -m vspdb my_script.py arg1 arg2
|
|
7
|
+
|
|
8
|
+
vspdb starts a ``debugpy`` debug server, makes VS Code attach to it, and halts on
|
|
9
|
+
the first line of *your* script — exactly as if you had set ``stopOnEntry``.
|
|
10
|
+
|
|
11
|
+
It supports two flows (and picks the right one automatically):
|
|
12
|
+
|
|
13
|
+
* **Listen flow (zero clicks per run).** Start the auto-generated
|
|
14
|
+
"vspdb: Listen" configuration in VS Code once. VS Code then listens on the
|
|
15
|
+
port; every ``python -m vspdb ...`` connects straight to it.
|
|
16
|
+
* **Attach flow (one F5 per run).** If nothing is listening, vspdb opens the
|
|
17
|
+
port itself and waits. Press F5 and pick "vspdb: Attach" to attach.
|
|
18
|
+
|
|
19
|
+
Either way the ``.vscode/launch.json`` entries are created for you on first run.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import ast
|
|
24
|
+
import json
|
|
25
|
+
import os
|
|
26
|
+
import re
|
|
27
|
+
import shutil
|
|
28
|
+
import socket
|
|
29
|
+
import sys
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
DEFAULT_HOST = "127.0.0.1"
|
|
33
|
+
DEFAULT_PORT = 5678
|
|
34
|
+
|
|
35
|
+
ATTACH_CONFIG_NAME = "vspdb: Attach (connect to running script)"
|
|
36
|
+
LISTEN_CONFIG_NAME = "vspdb: Listen (zero-click, start me first)"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# --------------------------------------------------------------------------- #
|
|
40
|
+
# Argument handling
|
|
41
|
+
# --------------------------------------------------------------------------- #
|
|
42
|
+
def _split_argv(argv):
|
|
43
|
+
"""Split our own options from the target script and its arguments.
|
|
44
|
+
|
|
45
|
+
Everything up to the first positional token is treated as a vspdb option;
|
|
46
|
+
that first positional is the script, and everything after it belongs to the
|
|
47
|
+
script (so the script can freely use ``--port`` etc. without clashing).
|
|
48
|
+
"""
|
|
49
|
+
value_flags = {"--port", "-p", "--host"}
|
|
50
|
+
opts = []
|
|
51
|
+
i = 0
|
|
52
|
+
while i < len(argv):
|
|
53
|
+
tok = argv[i]
|
|
54
|
+
if not tok.startswith("-"):
|
|
55
|
+
return opts, tok, argv[i + 1:]
|
|
56
|
+
opts.append(tok)
|
|
57
|
+
# consume the value of "--port 5678" style flags (but not "--port=5678")
|
|
58
|
+
if tok in value_flags and "=" not in tok:
|
|
59
|
+
if i + 1 < len(argv):
|
|
60
|
+
opts.append(argv[i + 1])
|
|
61
|
+
i += 2
|
|
62
|
+
continue
|
|
63
|
+
i += 1
|
|
64
|
+
return opts, None, []
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _build_parser():
|
|
68
|
+
parser = argparse.ArgumentParser(
|
|
69
|
+
prog="python -m vspdb",
|
|
70
|
+
description="Run a Python script under the VS Code debugger, halting on the first line.",
|
|
71
|
+
usage="python -m vspdb [options] <script.py> [script args...]",
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument("--port", "-p", type=int, default=None,
|
|
74
|
+
help="Debug port. Default: read from .vscode/vspdb.json if the "
|
|
75
|
+
f"auto-listen extension wrote one, else {DEFAULT_PORT}.")
|
|
76
|
+
parser.add_argument("--host", default=None,
|
|
77
|
+
help=f"Debug host/interface (default: {DEFAULT_HOST}).")
|
|
78
|
+
mode = parser.add_mutually_exclusive_group()
|
|
79
|
+
mode.add_argument("--listen", action="store_true",
|
|
80
|
+
help="Force listen mode: open the port and wait for VS Code to attach.")
|
|
81
|
+
mode.add_argument("--connect", action="store_true",
|
|
82
|
+
help="Force connect mode: attach to a VS Code that is already listening.")
|
|
83
|
+
parser.add_argument("--no-wait", action="store_true",
|
|
84
|
+
help="Don't block waiting for VS Code; run immediately (no stop-on-entry).")
|
|
85
|
+
parser.add_argument("--no-stop", action="store_true",
|
|
86
|
+
help="Attach but do not halt on the first line.")
|
|
87
|
+
parser.add_argument("--no-launch-json", action="store_true",
|
|
88
|
+
help="Do not create/update .vscode/launch.json.")
|
|
89
|
+
parser.add_argument("--setup", action="store_true",
|
|
90
|
+
help="(Re)install the bundled VS Code helper extension and exit.")
|
|
91
|
+
parser.add_argument("--no-ext", action="store_true",
|
|
92
|
+
help="Don't auto-install the VS Code helper extension on this run.")
|
|
93
|
+
return parser
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# --------------------------------------------------------------------------- #
|
|
97
|
+
# .vscode/launch.json management
|
|
98
|
+
# --------------------------------------------------------------------------- #
|
|
99
|
+
def _vspdb_configs(host, port):
|
|
100
|
+
"""The two attach configurations vspdb keeps in launch.json."""
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
"name": ATTACH_CONFIG_NAME,
|
|
104
|
+
"type": "debugpy",
|
|
105
|
+
"request": "attach",
|
|
106
|
+
"connect": {"host": host, "port": port},
|
|
107
|
+
"justMyCode": False,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": LISTEN_CONFIG_NAME,
|
|
111
|
+
"type": "debugpy",
|
|
112
|
+
"request": "attach",
|
|
113
|
+
"listen": {"host": host, "port": port},
|
|
114
|
+
"justMyCode": False,
|
|
115
|
+
},
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _strip_jsonc(text):
|
|
120
|
+
"""Best-effort strip of // and /* */ comments and trailing commas (JSONC)."""
|
|
121
|
+
out = []
|
|
122
|
+
i, n = 0, len(text)
|
|
123
|
+
in_str = False
|
|
124
|
+
quote = ""
|
|
125
|
+
while i < n:
|
|
126
|
+
c = text[i]
|
|
127
|
+
if in_str:
|
|
128
|
+
out.append(c)
|
|
129
|
+
if c == "\\" and i + 1 < n:
|
|
130
|
+
out.append(text[i + 1])
|
|
131
|
+
i += 2
|
|
132
|
+
continue
|
|
133
|
+
if c == quote:
|
|
134
|
+
in_str = False
|
|
135
|
+
i += 1
|
|
136
|
+
continue
|
|
137
|
+
if c in ('"', "'"):
|
|
138
|
+
in_str = True
|
|
139
|
+
quote = c
|
|
140
|
+
out.append(c)
|
|
141
|
+
i += 1
|
|
142
|
+
continue
|
|
143
|
+
if c == "/" and i + 1 < n and text[i + 1] == "/":
|
|
144
|
+
while i < n and text[i] != "\n":
|
|
145
|
+
i += 1
|
|
146
|
+
continue
|
|
147
|
+
if c == "/" and i + 1 < n and text[i + 1] == "*":
|
|
148
|
+
i += 2
|
|
149
|
+
while i + 1 < n and not (text[i] == "*" and text[i + 1] == "/"):
|
|
150
|
+
i += 1
|
|
151
|
+
i += 2
|
|
152
|
+
continue
|
|
153
|
+
out.append(c)
|
|
154
|
+
i += 1
|
|
155
|
+
result = "".join(out)
|
|
156
|
+
return re.sub(r",(\s*[}\]])", r"\1", result)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _load_launch_json(path):
|
|
160
|
+
raw = path.read_text()
|
|
161
|
+
try:
|
|
162
|
+
return json.loads(raw)
|
|
163
|
+
except json.JSONDecodeError:
|
|
164
|
+
return json.loads(_strip_jsonc(raw))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def ensure_launch_json(root, host, port):
|
|
168
|
+
"""Create/merge vspdb's attach configs into ``<root>/.vscode/launch.json``.
|
|
169
|
+
|
|
170
|
+
Existing user configurations are preserved; we only add ours if a config of
|
|
171
|
+
the same name isn't present. If the file exists but can't be parsed, we leave
|
|
172
|
+
it untouched and print the configs so the user can add them manually.
|
|
173
|
+
"""
|
|
174
|
+
vscode_dir = root / ".vscode"
|
|
175
|
+
path = vscode_dir / "launch.json"
|
|
176
|
+
data = {"version": "0.2.0", "configurations": []}
|
|
177
|
+
|
|
178
|
+
if path.exists():
|
|
179
|
+
try:
|
|
180
|
+
parsed = _load_launch_json(path)
|
|
181
|
+
except Exception:
|
|
182
|
+
print(f"vspdb: could not parse {path}; not modifying it. "
|
|
183
|
+
"Add these configurations manually if needed:", file=sys.stderr)
|
|
184
|
+
print(json.dumps(_vspdb_configs(host, port), indent=4), file=sys.stderr)
|
|
185
|
+
return
|
|
186
|
+
if isinstance(parsed, dict):
|
|
187
|
+
data = parsed
|
|
188
|
+
data.setdefault("version", "0.2.0")
|
|
189
|
+
data.setdefault("configurations", [])
|
|
190
|
+
|
|
191
|
+
existing_names = {c.get("name") for c in data["configurations"] if isinstance(c, dict)}
|
|
192
|
+
added = False
|
|
193
|
+
for cfg in _vspdb_configs(host, port):
|
|
194
|
+
if cfg["name"] not in existing_names:
|
|
195
|
+
data["configurations"].append(cfg)
|
|
196
|
+
added = True
|
|
197
|
+
|
|
198
|
+
if added:
|
|
199
|
+
vscode_dir.mkdir(exist_ok=True)
|
|
200
|
+
path.write_text(json.dumps(data, indent=4) + "\n")
|
|
201
|
+
print(f"vspdb: wrote attach configs to {path}", file=sys.stderr)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# --------------------------------------------------------------------------- #
|
|
205
|
+
# Connection handling
|
|
206
|
+
# --------------------------------------------------------------------------- #
|
|
207
|
+
def _extension_source():
|
|
208
|
+
"""Path to the bundled VS Code extension shipped inside the package."""
|
|
209
|
+
return Path(__file__).resolve().parent / "vscode_extension"
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _vscode_extension_bases():
|
|
213
|
+
"""Existing VS Code extensions directories (local + Remote/SSH server)."""
|
|
214
|
+
home = Path.home()
|
|
215
|
+
bases = []
|
|
216
|
+
for rel in (".vscode/extensions", ".vscode-server/extensions", ".vscode-remote/extensions"):
|
|
217
|
+
p = home / rel
|
|
218
|
+
if p.is_dir():
|
|
219
|
+
bases.append(p)
|
|
220
|
+
return bases
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def ensure_extension_installed(force=False):
|
|
224
|
+
"""Install the bundled auto-listen extension into VS Code if not already there.
|
|
225
|
+
|
|
226
|
+
Returns (newly_installed, folder_name). Best-effort: never raises. Skips
|
|
227
|
+
cleanly when no VS Code extensions directory exists (e.g. VS Code not used).
|
|
228
|
+
"""
|
|
229
|
+
src = _extension_source()
|
|
230
|
+
try:
|
|
231
|
+
pkg = json.loads((src / "package.json").read_text())
|
|
232
|
+
except Exception:
|
|
233
|
+
return False, None
|
|
234
|
+
|
|
235
|
+
folder = "{}.{}-{}".format(
|
|
236
|
+
pkg.get("publisher", "vspdb"), pkg.get("name", "vspdb-autolisten"),
|
|
237
|
+
pkg.get("version", "0.0.0"))
|
|
238
|
+
files = ["package.json", "extension.js", "README.md"]
|
|
239
|
+
newly = False
|
|
240
|
+
|
|
241
|
+
bases = _vscode_extension_bases()
|
|
242
|
+
for base in bases:
|
|
243
|
+
dest = base / folder
|
|
244
|
+
# Remove stale/older copies of this extension so VS Code doesn't load two
|
|
245
|
+
# listeners (e.g. an earlier manual install under a different publisher).
|
|
246
|
+
for old in base.glob("*vspdb-autolisten*"):
|
|
247
|
+
if old.resolve() != dest.resolve():
|
|
248
|
+
shutil.rmtree(old, ignore_errors=True)
|
|
249
|
+
if force or not dest.exists():
|
|
250
|
+
try:
|
|
251
|
+
shutil.rmtree(dest, ignore_errors=True)
|
|
252
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
253
|
+
for name in files:
|
|
254
|
+
s = src / name
|
|
255
|
+
if s.exists():
|
|
256
|
+
shutil.copy2(s, dest / name)
|
|
257
|
+
newly = True
|
|
258
|
+
except Exception:
|
|
259
|
+
pass
|
|
260
|
+
|
|
261
|
+
return newly, (folder if bases else None)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def discover_endpoint(start_dir, explicit_host, explicit_port):
|
|
265
|
+
"""Resolve (host, port, source) for the debug connection.
|
|
266
|
+
|
|
267
|
+
An explicit ``--port`` always wins. Otherwise we walk up from ``start_dir``
|
|
268
|
+
(the script's directory) looking for ``.vscode/vspdb.json`` — written by the
|
|
269
|
+
auto-listen VS Code extension — so the run attaches to the window that owns
|
|
270
|
+
the script's project, not some other open window. Falls back to the defaults.
|
|
271
|
+
"""
|
|
272
|
+
host, port, source = explicit_host, explicit_port, "default"
|
|
273
|
+
|
|
274
|
+
if explicit_port is not None:
|
|
275
|
+
source = "--port"
|
|
276
|
+
else:
|
|
277
|
+
d = start_dir
|
|
278
|
+
while True:
|
|
279
|
+
candidate = d / ".vscode" / "vspdb.json"
|
|
280
|
+
if candidate.is_file():
|
|
281
|
+
try:
|
|
282
|
+
data = json.loads(candidate.read_text())
|
|
283
|
+
except Exception:
|
|
284
|
+
data = {}
|
|
285
|
+
if data.get("port"):
|
|
286
|
+
port = int(data["port"])
|
|
287
|
+
if host is None:
|
|
288
|
+
host = data.get("host")
|
|
289
|
+
source = str(candidate)
|
|
290
|
+
break
|
|
291
|
+
if d.parent == d:
|
|
292
|
+
break
|
|
293
|
+
d = d.parent
|
|
294
|
+
|
|
295
|
+
return host or DEFAULT_HOST, port if port is not None else DEFAULT_PORT, source
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _is_listening(host, port):
|
|
299
|
+
"""True if something is already accepting connections on host:port."""
|
|
300
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
301
|
+
sock.settimeout(0.3)
|
|
302
|
+
return sock.connect_ex((host, port)) == 0
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _decide_mode(ns, host, port):
|
|
306
|
+
if ns.listen:
|
|
307
|
+
return "listen"
|
|
308
|
+
if ns.connect:
|
|
309
|
+
return "connect"
|
|
310
|
+
# Auto: if VS Code is already listening (Listen config running), connect to it.
|
|
311
|
+
return "connect" if _is_listening(host, port) else "listen"
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# --------------------------------------------------------------------------- #
|
|
315
|
+
# Running the target with stop-on-entry
|
|
316
|
+
# --------------------------------------------------------------------------- #
|
|
317
|
+
def _set_location(node, lineno, col):
|
|
318
|
+
for child in ast.walk(node):
|
|
319
|
+
child.lineno = lineno
|
|
320
|
+
child.col_offset = col
|
|
321
|
+
if hasattr(child, "end_lineno"):
|
|
322
|
+
child.end_lineno = lineno
|
|
323
|
+
if hasattr(child, "end_col_offset"):
|
|
324
|
+
child.end_col_offset = col + 1
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _compile_with_entry_breakpoint(source, filename):
|
|
328
|
+
"""Compile ``source`` with a ``debugpy.breakpoint()`` injected at the first
|
|
329
|
+
real statement, so the debugger halts on the first line of the user's code.
|
|
330
|
+
|
|
331
|
+
The injected call carries the line number of the first executable statement,
|
|
332
|
+
so VS Code shows the stop on that line. Original line numbers are preserved,
|
|
333
|
+
so tracebacks remain accurate.
|
|
334
|
+
"""
|
|
335
|
+
tree = ast.parse(source, filename)
|
|
336
|
+
body = tree.body
|
|
337
|
+
insert_at = 0
|
|
338
|
+
|
|
339
|
+
# Skip a leading module docstring.
|
|
340
|
+
if (body and isinstance(body[0], ast.Expr)
|
|
341
|
+
and isinstance(getattr(body[0], "value", None), ast.Constant)
|
|
342
|
+
and isinstance(body[0].value.value, str)):
|
|
343
|
+
insert_at = 1
|
|
344
|
+
# Skip __future__ imports (which must remain first).
|
|
345
|
+
while (insert_at < len(body) and isinstance(body[insert_at], ast.ImportFrom)
|
|
346
|
+
and body[insert_at].module == "__future__"):
|
|
347
|
+
insert_at += 1
|
|
348
|
+
|
|
349
|
+
if insert_at >= len(body):
|
|
350
|
+
return compile(tree, filename, "exec") # nothing to stop on
|
|
351
|
+
|
|
352
|
+
anchor = body[insert_at]
|
|
353
|
+
injected = ast.parse("__import__('debugpy').breakpoint()").body
|
|
354
|
+
for stmt in injected:
|
|
355
|
+
_set_location(stmt, anchor.lineno, anchor.col_offset)
|
|
356
|
+
body[insert_at:insert_at] = injected
|
|
357
|
+
ast.fix_missing_locations(tree)
|
|
358
|
+
return compile(tree, filename, "exec")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def run_script(script_path, script_args, stop_on_entry):
|
|
362
|
+
"""Execute ``script_path`` as ``__main__`` with ``script_args`` as argv."""
|
|
363
|
+
source = script_path.read_text()
|
|
364
|
+
if stop_on_entry:
|
|
365
|
+
try:
|
|
366
|
+
code = _compile_with_entry_breakpoint(source, str(script_path))
|
|
367
|
+
except SyntaxError:
|
|
368
|
+
code = compile(source, str(script_path), "exec") # surface real error on exec
|
|
369
|
+
else:
|
|
370
|
+
code = compile(source, str(script_path), "exec")
|
|
371
|
+
|
|
372
|
+
sys.argv = [str(script_path)] + list(script_args)
|
|
373
|
+
sys.path.insert(0, str(script_path.parent))
|
|
374
|
+
|
|
375
|
+
namespace = {
|
|
376
|
+
"__name__": "__main__",
|
|
377
|
+
"__file__": str(script_path),
|
|
378
|
+
"__package__": None,
|
|
379
|
+
"__doc__": None,
|
|
380
|
+
"__loader__": None,
|
|
381
|
+
"__spec__": None,
|
|
382
|
+
}
|
|
383
|
+
exec(code, namespace)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
# --------------------------------------------------------------------------- #
|
|
387
|
+
# Entry point
|
|
388
|
+
# --------------------------------------------------------------------------- #
|
|
389
|
+
def main(argv=None):
|
|
390
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
391
|
+
opts, script, script_args = _split_argv(argv)
|
|
392
|
+
|
|
393
|
+
parser = _build_parser()
|
|
394
|
+
ns = parser.parse_args(opts)
|
|
395
|
+
|
|
396
|
+
# `--setup`: just (re)install the VS Code helper and exit. No script needed.
|
|
397
|
+
if ns.setup:
|
|
398
|
+
_, folder = ensure_extension_installed(force=True)
|
|
399
|
+
if folder:
|
|
400
|
+
print(f"vspdb: installed VS Code helper '{folder}'.", file=sys.stderr)
|
|
401
|
+
print("vspdb: reload VS Code once (Ctrl+Shift+P -> 'Developer: Reload "
|
|
402
|
+
"Window') to activate it.", file=sys.stderr)
|
|
403
|
+
else:
|
|
404
|
+
print("vspdb: no VS Code extensions directory found; nothing to install.",
|
|
405
|
+
file=sys.stderr)
|
|
406
|
+
return 0
|
|
407
|
+
|
|
408
|
+
if script is None:
|
|
409
|
+
parser.print_help()
|
|
410
|
+
return 2
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
import debugpy
|
|
414
|
+
except ImportError:
|
|
415
|
+
print("vspdb requires 'debugpy'. Install it with:\n pip install debugpy",
|
|
416
|
+
file=sys.stderr)
|
|
417
|
+
return 1
|
|
418
|
+
|
|
419
|
+
script_path = Path(script).resolve()
|
|
420
|
+
if not script_path.exists():
|
|
421
|
+
print(f"vspdb: script not found: {script}", file=sys.stderr)
|
|
422
|
+
return 1
|
|
423
|
+
|
|
424
|
+
# Auto-install the bundled VS Code helper so a single `pip install vspdb` is
|
|
425
|
+
# all the user needs. Opt out with --no-ext or VSPDB_NO_AUTO_EXTENSION=1.
|
|
426
|
+
if not ns.no_ext and not os.environ.get("VSPDB_NO_AUTO_EXTENSION"):
|
|
427
|
+
newly, folder = ensure_extension_installed()
|
|
428
|
+
if newly and folder:
|
|
429
|
+
print(f"vspdb: installed its VS Code helper '{folder}'. Reload VS Code once "
|
|
430
|
+
"(Ctrl+Shift+P -> 'Developer: Reload Window') to enable zero-click "
|
|
431
|
+
"attach.", file=sys.stderr)
|
|
432
|
+
|
|
433
|
+
host, port, source = discover_endpoint(script_path.parent, ns.host, ns.port)
|
|
434
|
+
if source not in ("default", "--port"):
|
|
435
|
+
print(f"vspdb: using {host}:{port} from {source}", file=sys.stderr)
|
|
436
|
+
|
|
437
|
+
if not ns.no_launch_json:
|
|
438
|
+
ensure_launch_json(Path.cwd(), host, port)
|
|
439
|
+
|
|
440
|
+
mode = _decide_mode(ns, host, port)
|
|
441
|
+
client_attached = False
|
|
442
|
+
|
|
443
|
+
if mode == "connect":
|
|
444
|
+
print(f"vspdb: connecting to VS Code listener at {host}:{port} ...", file=sys.stderr)
|
|
445
|
+
try:
|
|
446
|
+
debugpy.connect((host, port))
|
|
447
|
+
client_attached = True
|
|
448
|
+
print("vspdb: attached to VS Code.", file=sys.stderr)
|
|
449
|
+
except Exception as exc: # noqa: BLE001 - report and fall back to listen
|
|
450
|
+
print(f"vspdb: could not connect ({exc}); falling back to listen mode.",
|
|
451
|
+
file=sys.stderr)
|
|
452
|
+
mode = "listen"
|
|
453
|
+
|
|
454
|
+
if mode == "listen":
|
|
455
|
+
debugpy.listen((host, port))
|
|
456
|
+
print(f"vspdb: debug server listening on {host}:{port}", file=sys.stderr)
|
|
457
|
+
if ns.no_wait:
|
|
458
|
+
print("vspdb: --no-wait set; running without waiting for VS Code.", file=sys.stderr)
|
|
459
|
+
else:
|
|
460
|
+
print(f"vspdb: in VS Code, press F5 and choose '{ATTACH_CONFIG_NAME}'.",
|
|
461
|
+
file=sys.stderr)
|
|
462
|
+
print("vspdb: waiting for the debugger to attach ...", file=sys.stderr)
|
|
463
|
+
debugpy.wait_for_client()
|
|
464
|
+
client_attached = True
|
|
465
|
+
print("vspdb: attached. Running script.", file=sys.stderr)
|
|
466
|
+
|
|
467
|
+
stop_on_entry = client_attached and not ns.no_stop
|
|
468
|
+
run_script(script_path, script_args, stop_on_entry)
|
|
469
|
+
return 0
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# vspdb auto-listen (VS Code extension)
|
|
2
|
+
|
|
3
|
+
The in-editor helper for [vspdb](https://pypi.org/project/vspdb/). It keeps a
|
|
4
|
+
`debugpy` listener running so:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
python -m vspdb my_script.py 10
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
attaches the debugger and halts on the first line **with zero clicks**.
|
|
11
|
+
|
|
12
|
+
> You normally don't install this yourself. The `vspdb` Python package bundles it
|
|
13
|
+
> and installs it into VS Code automatically the first time you run
|
|
14
|
+
> `python -m vspdb` (or `python -m vspdb --setup`). After that, reload the VS Code
|
|
15
|
+
> window once to activate it.
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
- On window open, starts a `debugpy` **attach + listen** session on a port unique
|
|
20
|
+
to that workspace, and records it in `<workspace>/.vscode/vspdb.json`.
|
|
21
|
+
- Restarts the listener after each session ends, so every run attaches.
|
|
22
|
+
- `python -m vspdb` reads `vspdb.json` and connects to the right window.
|
|
23
|
+
|
|
24
|
+
## Settings
|
|
25
|
+
|
|
26
|
+
| Setting | Default | Meaning |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| `vspdb.host` | `127.0.0.1` | Must match `python -m vspdb --host`. |
|
|
29
|
+
| `vspdb.port` | `5678` | Base port (each window offsets from here). |
|
|
30
|
+
| `vspdb.autoStart` | `true` | Auto-start/restart the listener. |
|
|
31
|
+
|
|
32
|
+
Command: `Ctrl+Shift+P` → **"vspdb: Start debug listener now"**.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// vspdb auto-listen — keeps a debugpy listener alive so `python -m vspdb`
|
|
2
|
+
// attaches and stops on entry with zero clicks.
|
|
3
|
+
//
|
|
4
|
+
// This extension is installed automatically by the `vspdb` Python package
|
|
5
|
+
// (`python -m vspdb` copies it into VS Code on first run). You normally never
|
|
6
|
+
// install it by hand.
|
|
7
|
+
//
|
|
8
|
+
// Multi-window safe: each VS Code window picks a port derived from its workspace
|
|
9
|
+
// folder (so two projects don't fight over one port) and records the chosen
|
|
10
|
+
// host/port in <workspace>/.vscode/vspdb.json. `python -m vspdb` reads that file
|
|
11
|
+
// to connect to THIS project's window, not some other open window.
|
|
12
|
+
|
|
13
|
+
const vscode = require("vscode");
|
|
14
|
+
const net = require("net");
|
|
15
|
+
const fs = require("fs");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
|
|
18
|
+
const CONFIG_NAME = "vspdb auto-listen";
|
|
19
|
+
let restarting = false;
|
|
20
|
+
let currentPort = null;
|
|
21
|
+
|
|
22
|
+
function settings() {
|
|
23
|
+
const cfg = vscode.workspace.getConfiguration("vspdb");
|
|
24
|
+
return {
|
|
25
|
+
host: cfg.get("host", "127.0.0.1"),
|
|
26
|
+
basePort: cfg.get("port", 5678),
|
|
27
|
+
autoStart: cfg.get("autoStart", true),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function workspaceFolder() {
|
|
32
|
+
const folders = vscode.workspace.workspaceFolders;
|
|
33
|
+
return folders && folders.length ? folders[0] : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Stable per-workspace starting port so different windows don't all start at the
|
|
37
|
+
// same number (which would race for one port).
|
|
38
|
+
function startingPort(basePort, key) {
|
|
39
|
+
let h = 0;
|
|
40
|
+
for (let i = 0; i < key.length; i++) h = (h * 31 + key.charCodeAt(i)) >>> 0;
|
|
41
|
+
return basePort + (h % 200); // basePort .. basePort+199
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function findFreePort(host, from) {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
let port = from;
|
|
47
|
+
const tryPort = () => {
|
|
48
|
+
const srv = net.createServer();
|
|
49
|
+
srv.once("error", () => {
|
|
50
|
+
port += 1;
|
|
51
|
+
if (port > from + 250) return resolve(from);
|
|
52
|
+
tryPort();
|
|
53
|
+
});
|
|
54
|
+
srv.once("listening", () => srv.close(() => resolve(port)));
|
|
55
|
+
srv.listen(port, host);
|
|
56
|
+
};
|
|
57
|
+
tryPort();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function writePortFile(folder, host, port) {
|
|
62
|
+
try {
|
|
63
|
+
const dir = path.join(folder.uri.fsPath, ".vscode");
|
|
64
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
65
|
+
fs.writeFileSync(
|
|
66
|
+
path.join(dir, "vspdb.json"),
|
|
67
|
+
JSON.stringify({ host, port }, null, 2) + "\n"
|
|
68
|
+
);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
/* ignore */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function startListener(reusePort) {
|
|
75
|
+
const { host, basePort } = settings();
|
|
76
|
+
const folder = workspaceFolder();
|
|
77
|
+
const from = reusePort || startingPort(basePort, folder ? folder.uri.fsPath : "default");
|
|
78
|
+
const port = await findFreePort(host, from);
|
|
79
|
+
currentPort = port;
|
|
80
|
+
if (folder) writePortFile(folder, host, port);
|
|
81
|
+
|
|
82
|
+
const config = {
|
|
83
|
+
name: CONFIG_NAME,
|
|
84
|
+
type: "debugpy",
|
|
85
|
+
request: "attach",
|
|
86
|
+
listen: { host, port },
|
|
87
|
+
justMyCode: false,
|
|
88
|
+
};
|
|
89
|
+
vscode.debug.startDebugging(folder, config).then(
|
|
90
|
+
(ok) => {
|
|
91
|
+
if (ok) {
|
|
92
|
+
vscode.window.setStatusBarMessage(`vspdb: listening on ${host}:${port}`, 4000);
|
|
93
|
+
} else {
|
|
94
|
+
vscode.window.setStatusBarMessage(
|
|
95
|
+
`vspdb: could not start listener on ${host}:${port}`,
|
|
96
|
+
4000
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
() => {}
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function activate(context) {
|
|
105
|
+
if (settings().autoStart) startListener();
|
|
106
|
+
|
|
107
|
+
context.subscriptions.push(
|
|
108
|
+
vscode.commands.registerCommand("vspdb.startListener", () => startListener())
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// After a vspdb session ends (debuggee disconnected), bring the listener back
|
|
112
|
+
// on the same port so the next terminal run attaches with no clicks.
|
|
113
|
+
context.subscriptions.push(
|
|
114
|
+
vscode.debug.onDidTerminateDebugSession((session) => {
|
|
115
|
+
if (session.name === CONFIG_NAME && settings().autoStart && !restarting) {
|
|
116
|
+
restarting = true;
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
restarting = false;
|
|
119
|
+
startListener(currentPort);
|
|
120
|
+
}, 300);
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function deactivate() {}
|
|
127
|
+
|
|
128
|
+
module.exports = { activate, deactivate };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vspdb-autolisten",
|
|
3
|
+
"displayName": "vspdb auto-listen",
|
|
4
|
+
"description": "Keeps a debugpy listener running so `python -m vspdb` attaches and stops on entry with zero clicks.",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"publisher": "vspdb",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"vscode": "^1.60.0"
|
|
10
|
+
},
|
|
11
|
+
"categories": [
|
|
12
|
+
"Debuggers",
|
|
13
|
+
"Other"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"vspdb",
|
|
17
|
+
"debugpy",
|
|
18
|
+
"debugger",
|
|
19
|
+
"attach",
|
|
20
|
+
"stopOnEntry"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/kmhatre14/vspdb.git",
|
|
25
|
+
"directory": "vspdb/vscode_extension"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/kmhatre14/vspdb",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/kmhatre14/vspdb/issues"
|
|
30
|
+
},
|
|
31
|
+
"activationEvents": [
|
|
32
|
+
"onStartupFinished"
|
|
33
|
+
],
|
|
34
|
+
"main": "./extension.js",
|
|
35
|
+
"contributes": {
|
|
36
|
+
"commands": [
|
|
37
|
+
{
|
|
38
|
+
"command": "vspdb.startListener",
|
|
39
|
+
"title": "vspdb: Start debug listener now"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"configuration": {
|
|
43
|
+
"title": "vspdb",
|
|
44
|
+
"properties": {
|
|
45
|
+
"vspdb.host": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"default": "127.0.0.1",
|
|
48
|
+
"description": "Host/interface the vspdb listener binds to (must match `python -m vspdb --host`)."
|
|
49
|
+
},
|
|
50
|
+
"vspdb.port": {
|
|
51
|
+
"type": "number",
|
|
52
|
+
"default": 5678,
|
|
53
|
+
"description": "Base port the vspdb listener uses (must match `python -m vspdb --port`)."
|
|
54
|
+
},
|
|
55
|
+
"vspdb.autoStart": {
|
|
56
|
+
"type": "boolean",
|
|
57
|
+
"default": true,
|
|
58
|
+
"description": "Automatically start the listener when a window opens, and restart it after each debug session ends."
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vspdb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Run a Python script under the VS Code debugger from the terminal, halting on the first line.
|
|
5
|
+
Author-email: Kaustubh <kmhatre14@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kmhatre14/vspdb
|
|
8
|
+
Project-URL: Repository, https://github.com/kmhatre14/vspdb
|
|
9
|
+
Project-URL: Issues, https://github.com/kmhatre14/vspdb/issues
|
|
10
|
+
Keywords: debugpy,vscode,debugger,pdb,debugging,cli,stopOnEntry
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: debugpy>=1.6
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# vspdb — VS Code pdb for the terminal
|
|
30
|
+
|
|
31
|
+
Debug any Python script (with arguments) in the VS Code debugger straight from
|
|
32
|
+
the terminal — no hand-written `launch.json`, no `debug.py` wrapper.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python -m vspdb my_script.py arg1 arg2
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
vspdb starts a `debugpy` debug server, gets VS Code to attach, and **halts on the
|
|
39
|
+
first line of your script** — just like `stopOnEntry`. It ships a small VS Code
|
|
40
|
+
helper (installed automatically) so this works with **zero clicks**.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Install — one package, that's it
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install vspdb # (or: pip install -e . from this folder)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
That's the only thing you install. vspdb ships a tiny VS Code helper extension
|
|
51
|
+
**inside** the package and installs it into VS Code for you on first use. So:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python -m vspdb my_script.py 10 # explicit (or: vspdb my_script.py 10)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The **first** run prints a one-time note that it installed the helper — **reload
|
|
58
|
+
the VS Code window once** (`Ctrl+Shift+P` → "Developer: Reload Window"). After
|
|
59
|
+
that it's fully automatic.
|
|
60
|
+
|
|
61
|
+
> Tip: install into the same interpreter VS Code uses for the workspace
|
|
62
|
+
> (`Ctrl+Shift+P` → "Python: Select Interpreter") so breakpoints line up. You can
|
|
63
|
+
> also pre-install the helper without running a script: `python -m vspdb --setup`.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## How you use it: zero clicks
|
|
68
|
+
|
|
69
|
+
Once the helper is active (after that one reload), just run scripts — each one
|
|
70
|
+
attaches and halts on its first line, **no debug panel, no F5**:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python -m vspdb my_script.py 10
|
|
74
|
+
python -m vspdb another.py --flag
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The helper keeps a `debugpy` listener ready in VS Code (one per window, on a
|
|
78
|
+
per-project port) and vspdb connects to the right window automatically.
|
|
79
|
+
|
|
80
|
+
### Don't want the helper? (no-extension fallback)
|
|
81
|
+
|
|
82
|
+
Run with `--no-ext` (or set `VSPDB_NO_AUTO_EXTENSION=1`). Then vspdb won't touch
|
|
83
|
+
VS Code; instead it writes two configs to `.vscode/launch.json` and you attach
|
|
84
|
+
manually:
|
|
85
|
+
|
|
86
|
+
- **vspdb: Listen** — start it once per window (F5), then runs are zero-click.
|
|
87
|
+
- **vspdb: Attach** — vspdb waits; press F5 once per run.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Options
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
python -m vspdb [options] <script.py> [script args...]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Everything **after** the script name is passed to your script untouched (so your
|
|
98
|
+
script can use `--port`, `--host`, etc. without clashing with vspdb).
|
|
99
|
+
|
|
100
|
+
| Option | Description |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `--port, -p PORT` | Debug port (default: `5678`). |
|
|
103
|
+
| `--host HOST` | Debug host/interface (default: `127.0.0.1`). |
|
|
104
|
+
| `--listen` | Force listen mode (open the port, wait for VS Code). |
|
|
105
|
+
| `--connect` | Force connect mode (attach to a VS Code that is already listening). |
|
|
106
|
+
| `--no-wait` | Don't wait for VS Code; run immediately (disables stop-on-entry). |
|
|
107
|
+
| `--no-stop` | Attach, but don't halt on the first line. |
|
|
108
|
+
| `--no-launch-json` | Don't create/update `.vscode/launch.json`. |
|
|
109
|
+
| `--setup` | (Re)install the bundled VS Code helper extension and exit. |
|
|
110
|
+
| `--no-ext` | Don't auto-install the VS Code helper on this run. |
|
|
111
|
+
|
|
112
|
+
The `--port` default is read from `.vscode/vspdb.json` (written by the helper) if
|
|
113
|
+
present, else `5678`. Disable auto-install globally with `VSPDB_NO_AUTO_EXTENSION=1`.
|
|
114
|
+
|
|
115
|
+
Examples:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Different port
|
|
119
|
+
python -m vspdb --port 5690 my_script.py 10
|
|
120
|
+
|
|
121
|
+
# Your script gets its own --port; vspdb does not consume it
|
|
122
|
+
python -m vspdb server.py --port 8080 --debug
|
|
123
|
+
|
|
124
|
+
# Run under the debugger but don't stop on entry (rely on your own breakpoints)
|
|
125
|
+
python -m vspdb --no-stop my_script.py 10
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## How it works
|
|
131
|
+
|
|
132
|
+
1. Parses `vspdb` options, then treats the first positional as the script and the
|
|
133
|
+
rest as the script's `argv`.
|
|
134
|
+
2. Installs the bundled VS Code helper into VS Code if it isn't there yet (unless
|
|
135
|
+
`--no-ext`), and ensures `.vscode/launch.json` has the manual-fallback attach
|
|
136
|
+
configs (merging, never clobbering, any existing file).
|
|
137
|
+
3. Resolves the port: if `--port` isn't given, it walks up from the script's
|
|
138
|
+
directory for `.vscode/vspdb.json` (written by the auto-listen extension) so it
|
|
139
|
+
attaches to the window that owns the script's project — important when several
|
|
140
|
+
VS Code windows are open. Then it **connects** if that port is being listened
|
|
141
|
+
on, otherwise **listens** and waits for VS Code.
|
|
142
|
+
4. Once the client is attached, it runs your script as `__main__` with the right
|
|
143
|
+
`sys.argv`, having injected a `debugpy.breakpoint()` at the first executable
|
|
144
|
+
line so the debugger halts there. Original line numbers are preserved, so
|
|
145
|
+
tracebacks stay accurate.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Requirements
|
|
150
|
+
|
|
151
|
+
- Python 3.8+
|
|
152
|
+
- `debugpy` (installed automatically)
|
|
153
|
+
- VS Code with the Python / Python Debugger extension
|
|
154
|
+
|
|
155
|
+
## Author
|
|
156
|
+
|
|
157
|
+
Kaustubh — [kmhatre14@gmail.com](mailto:kmhatre14@gmail.com) · [github.com/kmhatre14](https://github.com/kmhatre14)
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
[MIT](LICENSE). Built with assistance from Claude Opus 4.8.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
vspdb/__init__.py
|
|
5
|
+
vspdb/__main__.py
|
|
6
|
+
vspdb/cli.py
|
|
7
|
+
vspdb.egg-info/PKG-INFO
|
|
8
|
+
vspdb.egg-info/SOURCES.txt
|
|
9
|
+
vspdb.egg-info/dependency_links.txt
|
|
10
|
+
vspdb.egg-info/entry_points.txt
|
|
11
|
+
vspdb.egg-info/requires.txt
|
|
12
|
+
vspdb.egg-info/top_level.txt
|
|
13
|
+
vspdb/vscode_extension/README.md
|
|
14
|
+
vspdb/vscode_extension/extension.js
|
|
15
|
+
vspdb/vscode_extension/package.json
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
debugpy>=1.6
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
vspdb
|