Debug test runnables through the Xdebug adapter#130
Open
roxblnfk wants to merge 2 commits into
Open
Conversation
Register a debug locator so the gutter offers "Debug" next to "Run" for
PHPUnit/Pest/Testo runnables, and associate the Xdebug adapter with the PHP
language (debuggers = ["Xdebug"]) so Zed reaches the locator at all.
The locator turns a runnable task into an Xdebug launch scenario: it drops the
`php` wrapper to find the program, strips the shell quotes tasks.json adds for
"Run" (the adapter spawns argv without a shell), and skips inline `php -r` code.
At launch time (get_installed_binary) fill in what the adapter needs but a
generated scenario lacks:
- cwd -> worktree root (a locator scenario carries "cwd": null);
- runtimeExecutable -> absolute php (a PHP_BINARY env override, else
which("php")), since spawn("php") can not resolve it on Windows;
- runtimeArgs -> -dxdebug.mode=debug -dxdebug.start_with_request=yes
-dxdebug.client_port=${port}, because the adapter forwards runtimeArgs
verbatim and never enables Xdebug itself;
- fail with an actionable message when php resolves to a .bat/.cmd shim, which
Node refuses to spawn (CVE-2024-27980) with a cryptic EINVAL.
Also document runtimeExecutable/runtimeArgs in the Xdebug config schema.
An Xdebug launch config without a `program` should listen for incoming Xdebug
connections (debugging a web request) rather than spawn PHP. Gate the CLI-launch
fixups — resolving the PHP binary, injecting the `-dxdebug…` runtime args, and
the `.bat`/`.cmd` guard — on the presence of a non-empty `program`, and drop
`program` from the config schema's required list so a program-less (listen)
config validates.
Without this, injecting `runtimeArgs` flipped a listener into a launch (the
adapter would spawn `php` with no script instead of listening), and the schema
rejected the program-less config.
Listen by adding a program-less scenario to `.zed/debug.json`:
[{ "label": "Listen for Xdebug", "adapter": "Xdebug", "request": "launch" }]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Lets you debug PHP test runnables with the bundled Xdebug adapter: the gutter now offers Debug next to Run for the PHPUnit/Pest runnables (and any
php <script>task), launching the exact test under the cursor with breakpoints. It also enables Xdebug's listen mode for debugging incoming web requests.The extension already ships the Xdebug debug adapter, but nothing connected it to the runnable tasks, and the PHP language wasn't associated with any debugger — so no debug affordance ever appeared. This PR wires that up.
How
languages/php/config.tomlgainsdebuggers = ["Xdebug"]. Without this,code_actions.rs::debug_scenariosbails out early (no default debug adapter for the buffer) and never reaches a locator.[debug_locators.php]anddap_locator_create_scenarioturn a runnable task into an Xdebug launch scenario. It drops thephpwrapper to find the program, strips the double quotestasks.jsonadds for the "Run" shell (the adapter spawns argv directly, without a shell), and skips inlinephp -rcode.get_installed_binary, the single choke point every scenario passes through — gutter,debug.json, F4). These apply only when the config has aprogram(a CLI launch); see listen mode below:cwd→ worktree root (a locator scenario carries"cwd": null, whichentry(..).or_insertwouldn't fill);runtimeExecutable→ absolute php, via aPHP_BINARYenv override thenwhich("php"), becausespawn("php")can't resolve a bare name on Windows;runtimeArgs→-dxdebug.mode=debug -dxdebug.start_with_request=yes -dxdebug.client_port=${port}. The adapter forwardsruntimeArgsto PHP verbatim and never enables Xdebug itself, so without these the launched script never connects back.${port}is replaced by the adapter with the DBGp port it listens on, so it always matches. A user's ownruntimeArgstake precedence..bat/.cmdshim, fail with an actionable message instead of the crypticspawn EINVALNode throws for batch files (CVE-2024-27980).debug_adapter_schemas/Xdebug.jsondocumentsruntimeExecutableandruntimeArgs, so they autocomplete when editing.zed/debug.json.Listen mode (debugging web requests)
The same adapter listens for incoming Xdebug connections when a launch config has no
program— the usual workflow for debugging a request triggered from the browser or an external CLI run. Because the spawn-time fixups above are gated onprogram, a program-less config is left untouched: the adapter listens instead of trying to spawnphpwith no script, and the.bat/.cmdguard doesn't reject a session that never launches PHP.Set it up:
Add a program-less scenario to
.zed/debug.json:[{ "adapter": "Xdebug", "label": "Listen for Xdebug", "request": "launch" }]Open
debugger: start, pick Listen for Xdebug from the list and start it. The session now listens on the DBGp port (9003 by default; set"port"in the config to change it) — no executable to fill in.Set your breakpoints.
Run PHP with Xdebug pointed at the listener, e.g.:
or configure the same via
php.ini/XDEBUG_MODE=debugand just trigger a web request. Xdebug connects back to the listening session and breakpoints bind.Requirements
Breakpoints require Xdebug to be loaded in the PHP that runs (via
zend_extension); the-dxdebug.*overrides only configure an already-installed Xdebug, they don't load it.Tests
Unit tests in
src/xdebug.rscover the locator: the testo/phpunit program split, quote stripping, declining inline code, and declining other adapters.cargo test,cargo fmt --checkandcargo clippy --all-features -- -D warnings(wasm target) are green.