mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 00:07:50 +00:00
feat(debugger): add PHP Remote Debug (Xdebug) run configuration (#1635)
A debug-only PHP Remote Debug (Xdebug) configuration: php-debug listens on the port (9003 by default) with optional path mappings, and the form shows how to start the remote side. php-debug is also offered as a debug adapter in the configuration form. Co-authored-by: Inf1mum <8135225+Inf1mum@users.noreply.github.com>
This commit is contained in:
@@ -30,7 +30,9 @@ const {
|
||||
goRemoteType,
|
||||
pythonRemoteType,
|
||||
nodeRemoteType,
|
||||
phpRemoteType,
|
||||
jdwpAgentArgs,
|
||||
xdebugCommand,
|
||||
remoteAttachConfig,
|
||||
remoteJvmTarget,
|
||||
} = remote;
|
||||
@@ -67,7 +69,9 @@ describe("remote debug configuration types", () => {
|
||||
port: "Port must be a number from 1 to 65535",
|
||||
});
|
||||
expect(missingParams(configOf(type, { port: "abc" }), type).port).toBeDefined();
|
||||
expect(missingParams(configOf(type, { host: "" }), type)).toEqual({ host: "Host is required" });
|
||||
// PHP Remote Debug has no host: php-debug listens itself, Xdebug connects to it.
|
||||
if (type.fields.some((f) => f.key === "host"))
|
||||
expect(missingParams(configOf(type, { host: "" }), type)).toEqual({ host: "Host is required" });
|
||||
});
|
||||
|
||||
it("Remote JVM Debug defaults to attaching to localhost:5005", () => {
|
||||
@@ -157,6 +161,25 @@ describe("remote attach requests", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("PHP Remote Debug defaults to port 9003 and shows the xdebug command line", () => {
|
||||
expect(configOf(phpRemoteType).params).toEqual({ port: "9003" });
|
||||
const field = phpRemoteType.fields.find((f) => f.key === "hint")!;
|
||||
expect(field.control).toBe("output");
|
||||
expect(field.value!({ port: "9004" })).toContain("-dxdebug.client_port=9004");
|
||||
expect(xdebugCommand({})).toBe(
|
||||
"php -dxdebug.mode=debug -dxdebug.start_with_request=yes -dxdebug.client_port=9003 script.php",
|
||||
);
|
||||
});
|
||||
|
||||
it("PHP Remote Debug launches php-debug without a program and maps the roots", () => {
|
||||
const l = remoteAttachConfig(configOf(phpRemoteType, { localRoot: ".", remoteRoot: "/var/www/html" }), "/p")!;
|
||||
expect(l).toMatchObject({ adapter: "php-debug", request: "launch", cwd: "/p" });
|
||||
expect(l.program).toBeNull();
|
||||
expect(JSON.parse(l.extraJson!)).toEqual({ port: 9003, pathMappings: { "/var/www/html": "/p" } });
|
||||
const plain = JSON.parse(remoteAttachConfig(configOf(phpRemoteType), "/p")!.extraJson!);
|
||||
expect(plain).toEqual({ port: 9003 });
|
||||
});
|
||||
|
||||
it("start-the-remote-side hints follow the port", () => {
|
||||
expect(remote.delveServerCommand({ port: "4000" })).toBe(
|
||||
"dlv debug --headless --listen=:4000 --api-version=2 --accept-multiclient",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Remote debug run configurations: the debugger attaches to a process started
|
||||
* elsewhere (another machine, a container) over host:port, like IDEA's Remote JVM
|
||||
* Debug, GoLand's Go Remote, PyCharm's remote debugpy and IDEA's Attach to Node.js.
|
||||
* PHP Remote Debug is the exception: its php-debug listens and Xdebug connects to it.
|
||||
* Only Debug applies to them (`debugOnly`); they launch no process.
|
||||
*/
|
||||
import type { LaunchConfig, RunConfig } from "@/platform/ipc";
|
||||
@@ -13,6 +14,7 @@ export const GO_REMOTE = "go-remote";
|
||||
export const PYTHON_REMOTE = "python-remote";
|
||||
export const NODE_REMOTE = "node-remote";
|
||||
export const GODOT_ATTACH = "godot-attach";
|
||||
export const PHP_REMOTE = "php-remote";
|
||||
|
||||
type Params = Readonly<Record<string, string>>;
|
||||
|
||||
@@ -221,6 +223,38 @@ export const nodeRemoteType: RunConfigTypeContribution = {
|
||||
commandLine: () => null,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PHP Remote Debug (Xdebug): php-debug listens, the remote PHP connects to it
|
||||
|
||||
/** How to start the remote side, so Xdebug connects back to the listening php-debug. */
|
||||
export function xdebugCommand(p: Params): string {
|
||||
return `php -dxdebug.mode=debug -dxdebug.start_with_request=yes -dxdebug.client_port=${portOf(p, 9003)} script.php`;
|
||||
}
|
||||
|
||||
export const phpRemoteType: RunConfigTypeContribution = {
|
||||
id: PHP_REMOTE,
|
||||
label: "PHP Remote Debug (Xdebug)",
|
||||
icon: "file-php",
|
||||
description: "Listen for Xdebug connections: debug PHP requests served elsewhere (php-fpm, a container)",
|
||||
defaults: { port: "9003" },
|
||||
debugOnly: true,
|
||||
debugAdapter: "php-debug",
|
||||
fields: [
|
||||
portField,
|
||||
{
|
||||
key: "hint",
|
||||
label: "Start the remote side with",
|
||||
control: "output",
|
||||
mono: true,
|
||||
value: xdebugCommand,
|
||||
hint: "For web requests: the XDEBUG_SESSION cookie or query parameter; point xdebug.client_host at this machine when PHP runs in a container",
|
||||
},
|
||||
localRootField,
|
||||
remoteRootField("e.g. /var/www/html"),
|
||||
],
|
||||
commandLine: () => null,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Attach to Godot (the editor's debug adapter, Editor Settings › Network › Debug Adapter)
|
||||
|
||||
@@ -250,13 +284,14 @@ export const REMOTE_TYPES: readonly RunConfigTypeContribution[] = [
|
||||
goRemoteType,
|
||||
pythonRemoteType,
|
||||
nodeRemoteType,
|
||||
phpRemoteType,
|
||||
godotAttachType,
|
||||
];
|
||||
|
||||
export const isRemoteConfig = (c: Pick<RunConfig, "kind">) => REMOTE_TYPES.some((t) => t.id === c.kind);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Attach requests
|
||||
// Attach / listen requests
|
||||
|
||||
function isAbsolute(p: string) {
|
||||
return p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p);
|
||||
@@ -281,11 +316,17 @@ export function serverAddress(host: string, port: number): string {
|
||||
return `${host.includes(":") && !host.startsWith("[") ? `[${host}]` : host}:${port}`;
|
||||
}
|
||||
|
||||
function attachBase(rc: RunConfig, adapter: string, root: string, extra: Record<string, unknown>): LaunchConfig {
|
||||
function requestBase(
|
||||
rc: RunConfig,
|
||||
adapter: string,
|
||||
root: string,
|
||||
request: "attach" | "launch",
|
||||
extra: Record<string, unknown>,
|
||||
): LaunchConfig {
|
||||
return {
|
||||
name: rc.name,
|
||||
adapter,
|
||||
request: "attach",
|
||||
request,
|
||||
program: null,
|
||||
args: [],
|
||||
cwd: root,
|
||||
@@ -295,9 +336,11 @@ function attachBase(rc: RunConfig, adapter: string, root: string, extra: Record<
|
||||
}
|
||||
|
||||
/**
|
||||
* The attach request of a Go / Python / Node.js remote configuration. Delve's headless
|
||||
* The request of a Go / Python / Node.js / PHP remote configuration. Delve's headless
|
||||
* server and `debugpy --listen` speak DAP themselves (`debugServer` connects there);
|
||||
* js-debug runs here and connects to the inspector.
|
||||
* js-debug runs here and connects to the inspector. PHP Remote Debug is the one
|
||||
* `launch`: php-debug is the side that listens, and each Xdebug connection becomes
|
||||
* a thread, so nothing attaches and no program is named.
|
||||
*/
|
||||
export function remoteAttachConfig(rc: RunConfig, root: string): LaunchConfig | null {
|
||||
const p = rc.params ?? {};
|
||||
@@ -306,27 +349,35 @@ export function remoteAttachConfig(rc: RunConfig, root: string): LaunchConfig |
|
||||
const skip = workflowSettings().debugger.skipLibraryCode;
|
||||
switch (rc.kind) {
|
||||
case GO_REMOTE:
|
||||
return attachBase(rc, "delve", root, {
|
||||
return requestBase(rc, "delve", root, "attach", {
|
||||
debugServer: serverAddress(host, portOf(p, 2345)),
|
||||
mode: "remote",
|
||||
...(map ? { substitutePath: [{ from: map.local, to: map.remote }] } : {}),
|
||||
});
|
||||
case PYTHON_REMOTE:
|
||||
return attachBase(rc, "debugpy", root, {
|
||||
return requestBase(rc, "debugpy", root, "attach", {
|
||||
debugServer: serverAddress(host, portOf(p, 5678)),
|
||||
justMyCode: skip,
|
||||
...(map ? { pathMappings: [{ localRoot: map.local, remoteRoot: map.remote }] } : {}),
|
||||
});
|
||||
case GODOT_ATTACH:
|
||||
return attachBase(rc, "godot", root, { debugServer: serverAddress(host, portOf(p, 6006)), project: root });
|
||||
return requestBase(rc, "godot", root, "attach", {
|
||||
debugServer: serverAddress(host, portOf(p, 6006)),
|
||||
project: root,
|
||||
});
|
||||
case NODE_REMOTE:
|
||||
return attachBase(rc, "js-debug", root, {
|
||||
return requestBase(rc, "js-debug", root, "attach", {
|
||||
type: "pwa-node",
|
||||
address: host,
|
||||
port: portOf(p, 9229),
|
||||
...(map ? { localRoot: map.local, remoteRoot: map.remote } : {}),
|
||||
skipFiles: skip ? ["<node_internals>/**", "**/node_modules/**"] : [],
|
||||
});
|
||||
case PHP_REMOTE:
|
||||
return requestBase(rc, "php-debug", root, "launch", {
|
||||
port: portOf(p, 9003),
|
||||
...(map ? { pathMappings: { [map.remote]: map.local } } : {}),
|
||||
});
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ import { TomcatSettings } from "./TomcatSettings";
|
||||
import styles from "./Run.module.css";
|
||||
|
||||
/**
|
||||
* Debug adapters with a real launch template (`debugger/launch.ts`, `debugger/java.ts`),
|
||||
* verified against small programs. Others (dart, php-debug, …) only got
|
||||
* Debug adapters with a real launch template (`debugger/launch.ts`, `debugger/java.ts`,
|
||||
* `debugger/remote.ts`), verified against small programs. Others (dart, …) only got
|
||||
* generic launch arguments and are left out until they have one; a configuration that
|
||||
* already names one keeps it.
|
||||
*/
|
||||
@@ -63,6 +63,7 @@ const ADAPTERS: ReadonlyArray<{ id: string; label: string }> = [
|
||||
{ id: "delve", label: "Delve (Go)" },
|
||||
{ id: "codelldb", label: "CodeLLDB (Rust, C, C++)" },
|
||||
{ id: "lldb-dap", label: "lldb-dap (C, C++, Swift)" },
|
||||
{ id: "php-debug", label: "php-debug (PHP)" },
|
||||
];
|
||||
|
||||
/** Options "Modify options" adds to the form: value fields (shown once added) and flags (chips). */
|
||||
|
||||
@@ -30,5 +30,8 @@
|
||||
"{added} line added": "{added} line added",
|
||||
"{added} lines added": "{added} lines added",
|
||||
"{removed} line deleted": "{removed} line deleted",
|
||||
"{removed} lines deleted": "{removed} lines deleted"
|
||||
"{removed} lines deleted": "{removed} lines deleted",
|
||||
"PHP Remote Debug (Xdebug)": "PHP Remote Debug (Xdebug)",
|
||||
"Listen for Xdebug connections: debug PHP requests served elsewhere (php-fpm, a container)": "Listen for Xdebug connections: debug PHP requests served elsewhere (php-fpm, a container)",
|
||||
"For web requests: the XDEBUG_SESSION cookie or query parameter; point xdebug.client_host at this machine when PHP runs in a container": "For web requests: the XDEBUG_SESSION cookie or query parameter; point xdebug.client_host at this machine when PHP runs in a container"
|
||||
}
|
||||
|
||||
@@ -3653,6 +3653,9 @@
|
||||
"pip install debugpy on the remote machine": "在远程机器上执行 pip install debugpy",
|
||||
"Attach to Node.js": "附加到 Node.js",
|
||||
"Attach to a Node.js process started with --inspect here or on another machine": "附加到以 --inspect 启动的 Node.js 进程(本机或其他机器)",
|
||||
"PHP Remote Debug (Xdebug)": "PHP 远程调试 (Xdebug)",
|
||||
"Listen for Xdebug connections: debug PHP requests served elsewhere (php-fpm, a container)": "监听 Xdebug 连接:调试在其他地方(php-fpm、容器中)提供服务的 PHP 请求",
|
||||
"For web requests: the XDEBUG_SESSION cookie or query parameter; point xdebug.client_host at this machine when PHP runs in a container": "对于 Web 请求:设置 XDEBUG_SESSION cookie 或查询参数;当 PHP 运行在容器中时,将 xdebug.client_host 指向本机",
|
||||
"Copied": "已复制",
|
||||
"'{name}' can only be debugged": "'{name}' 只能调试",
|
||||
"It attaches the debugger to a process started elsewhere.": "它将调试器附加到在别处启动的进程。",
|
||||
|
||||
@@ -86,6 +86,7 @@ const BUILT_AT_RUNTIME = new Set<string>([]);
|
||||
const UNTRANSLATED = new Set<string>([
|
||||
"dotnet test",
|
||||
"netcoredbg (.NET)",
|
||||
"php-debug (PHP)",
|
||||
// Product names
|
||||
"Cargo",
|
||||
"CMake",
|
||||
|
||||
Reference in New Issue
Block a user