Skip to content

Commit 24eafe9

Browse files
johnromfelixfbecker
authored andcommitted
feat: path mappings (#175)
Adds support for mapping multiple server source roots to local roots. Closes #54
1 parent 6253d4f commit 24eafe9

5 files changed

Lines changed: 152 additions & 66 deletions

File tree

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ In your project, go to the debugger and hit the little gear icon and choose _PHP
4242
- `request`: Always `"launch"`
4343
- `port`: The port on which to listen for XDebug (default: `9000`)
4444
- `stopOnEntry`: Wether to break at the beginning of the script (default: `false`)
45-
- `localSourceRoot`: The path to the folder that is being served by your webserver and maps to `serverSourceRoot` (for example `"${workspaceRoot}/public"`)
46-
- `serverSourceRoot`: The path on the remote host where your webroot is located (for example `"/var/www"`)
45+
- `pathMappings`: A list of server paths mapping to the local source paths on your machine, see "Remote Host Debugging" below
4746
- `log`: Wether to log all communication between VS Code and the adapter to the debug console. See _Troubleshooting_ further down.
4847
- `ignore`: An optional array of glob patterns that errors should be ignored from (for example `**/vendor/**/*.php`)
4948
- `xdebugSettings`: Allows you to override XDebug's remote debugging settings to fine tuning XDebug to your needs. For example, you can play with `max_children` and `max_depth` to change the max number of array and object children that are retrieved and the max depth in structures like arrays and objects. This can speed up the debugger on slow machines.
@@ -82,10 +81,13 @@ Remote Host Debugging
8281
---------------------
8382
To debug a running application on a remote host, you need to tell XDebug to connect to a different IP than `localhost`. This can either be done by setting [`xdebug.remote_host`](https://xdebug.org/docs/remote#remote_host) to your IP or by setting [`xdebug.remote_connect_back = 1`](https://xdebug.org/docs/remote#remote_connect_back) to make XDebug always connect back to the machine who did the web request. The latter is the only setting that supports multiple users debugging the same server and "just works" for web projects. Again, please see the [XDebug documentation](https://xdebug.org/docs/remote#communcation) on the subject for more information.
8483

85-
To make VS Code map the files on the server to the right files on your local machine, you have to set the `localSourceRoot` and `serverSourceRoot` settings in your launch.json. Example:
84+
To make VS Code map the files on the server to the right files on your local machine, you have to set the `pathMappings` settings in your launch.json. Example:
8685
```json
87-
"serverSourceRoot": "/var/www/myproject",
88-
"localSourceRoot": "${workspaceRoot}/public"
86+
// server -> local
87+
"pathMappings": {
88+
"/var/www/html": "{workspaceRoot}/www",
89+
"/app": "{workspaceRoot}/app"
90+
}
8991
```
9092
Please also note that setting any of the CLI debugging options will not work with remote host debugging, because the script is always launched locally. If you want to debug a CLI script on a remote host, you need to launch it manually from the command line.
9193

package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,18 @@
164164
},
165165
"serverSourceRoot": {
166166
"type": "string",
167-
"description": "The source root when debugging a remote host"
167+
"description": "Deprecated: The source root when debugging a remote host",
168+
"deprecationMessage": "Property serverSourceRoot is deprecated, please use pathMappings to define a server root."
168169
},
169170
"localSourceRoot": {
170171
"type": "string",
171-
"description": "The source root on this machine that is the equivalent to the serverSourceRoot on the server."
172+
"description": "Deprecated: The source root on this machine that is the equivalent to the serverSourceRoot on the server.",
173+
"deprecationMessage": "Property localSourceRoot is deprecated, please use pathMappings to define a local root."
174+
},
175+
"pathMappings": {
176+
"type": "object",
177+
"default": {},
178+
"description": "A mapping of server paths to local paths."
172179
},
173180
"ignore": {
174181
"type": "array",

src/paths.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import * as path from 'path';
66
import {decode} from 'urlencode';
77

88
/** converts a server-side XDebug file URI to a local path for VS Code with respect to source root settings */
9-
export function convertDebuggerPathToClient(fileUri: string|url.Url, localSourceRoot?: string, serverSourceRoot?: string): string {
9+
export function convertDebuggerPathToClient(fileUri: string|url.Url, pathMapping?: { [index: string]: string; }): string {
10+
let localSourceRoot: string | undefined;
11+
let serverSourceRoot: string | undefined;
1012
if (typeof fileUri === 'string') {
1113
fileUri = url.parse(fileUri);
1214
}
@@ -17,6 +19,18 @@ export function convertDebuggerPathToClient(fileUri: string|url.Url, localSource
1719
if (serverIsWindows) {
1820
serverPath = serverPath.substr(1);
1921
}
22+
if (pathMapping) {
23+
for (const mappedServerPath of Object.keys(pathMapping) ) {
24+
const mappedLocalSource = pathMapping[mappedServerPath];
25+
// normalize slashes for windows-to-unix
26+
const serverRelative = (serverIsWindows ? path.win32 : path.posix).relative(mappedServerPath, serverPath);
27+
if (serverRelative.indexOf('..') !== 0) {
28+
serverSourceRoot = mappedServerPath;
29+
localSourceRoot = mappedLocalSource;
30+
break;
31+
}
32+
}
33+
}
2034
let localPath: string;
2135
if (serverSourceRoot && localSourceRoot) {
2236
// get the part of the path that is relative to the source root
@@ -30,9 +44,22 @@ export function convertDebuggerPathToClient(fileUri: string|url.Url, localSource
3044
}
3145

3246
/** converts a local path from VS Code to a server-side XDebug file URI with respect to source root settings */
33-
export function convertClientPathToDebugger(localPath: string, localSourceRoot?: string, serverSourceRoot?: string): string {
47+
export function convertClientPathToDebugger(localPath: string, pathMapping?: { [index: string]: string; }): string {
48+
let localSourceRoot: string | undefined;
49+
let serverSourceRoot: string | undefined;
3450
let localFileUri = fileUrl(localPath, {resolve: false});
3551
let serverFileUri: string;
52+
if (pathMapping) {
53+
for (const mappedServerPath of Object.keys(pathMapping) ) {
54+
const mappedLocalSource = pathMapping[mappedServerPath];
55+
const localRelative = path.relative(mappedLocalSource, localPath);
56+
if (localRelative.indexOf('..') !== 0) {
57+
serverSourceRoot = mappedServerPath;
58+
localSourceRoot = mappedLocalSource;
59+
break;
60+
}
61+
}
62+
}
3663
if (serverSourceRoot && localSourceRoot) {
3764
let localSourceRootUrl = fileUrl(localSourceRoot, {resolve: false});
3865
if (!localSourceRootUrl.endsWith('/')) {

src/phpDebug.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ interface LaunchRequestArguments extends VSCodeDebugProtocol.LaunchRequestArgume
5858
serverSourceRoot?: string;
5959
/** The path to the source root on this machine that is the equivalent to the serverSourceRoot on the server. */
6060
localSourceRoot?: string;
61+
/** The path to the source root on this machine that is the equivalent to the serverSourceRoot on the server. */
62+
pathMappings?: { [index: string]: string };
6163
/** If true, will log all communication between VS Code and the adapter to the console */
6264
log?: boolean;
6365
/** Array of glob patterns that errors should be ignored from */
@@ -183,6 +185,14 @@ class PhpDebugSession extends vscode.DebugSession {
183185
}
184186

185187
protected async launchRequest(response: VSCodeDebugProtocol.LaunchResponse, args: LaunchRequestArguments) {
188+
if (args.localSourceRoot && args.serverSourceRoot) {
189+
let pathMappings: {[index: string]: string} = {};
190+
if (args.pathMappings) {
191+
pathMappings = args.pathMappings;
192+
}
193+
pathMappings[args.serverSourceRoot] = args.localSourceRoot;
194+
args.pathMappings = pathMappings;
195+
}
186196
this._args = args;
187197
/** launches the script as CLI */
188198
const launchScript = async () => {
@@ -373,7 +383,7 @@ class PhpDebugSession extends vscode.DebugSession {
373383
/** This is called for each source file that has breakpoints with all the breakpoints in that file and whenever these change. */
374384
protected async setBreakPointsRequest(response: VSCodeDebugProtocol.SetBreakpointsResponse, args: VSCodeDebugProtocol.SetBreakpointsArguments) {
375385
try {
376-
const fileUri = convertClientPathToDebugger(args.source.path!, this._args.localSourceRoot, this._args.serverSourceRoot);
386+
const fileUri = convertClientPathToDebugger(args.source.path!, this._args.pathMappings);
377387
const connections = Array.from(this._connections.values());
378388
let xdebugBreakpoints: Array<xdebug.ConditionalBreakpoint|xdebug.LineBreakpoint>;
379389
response.body = {breakpoints: []};
@@ -559,7 +569,7 @@ class PhpDebugSession extends vscode.DebugSession {
559569
line++;
560570
} else {
561571
// XDebug paths are URIs, VS Code file paths
562-
const filePath = convertDebuggerPathToClient(urlObject, this._args.localSourceRoot, this._args.serverSourceRoot);
572+
const filePath = convertDebuggerPathToClient(urlObject, this._args.pathMappings);
563573
// "Name" of the source and the actual file path
564574
source = {name: path.basename(filePath), path: filePath};
565575
}
@@ -580,7 +590,7 @@ class PhpDebugSession extends vscode.DebugSession {
580590
line++;
581591
} else {
582592
// XDebug paths are URIs, VS Code file paths
583-
const filePath = convertDebuggerPathToClient(urlObject, this._args.localSourceRoot, this._args.serverSourceRoot);
593+
const filePath = convertDebuggerPathToClient(urlObject, this._args.pathMappings);
584594
// "Name" of the source and the actual file path
585595
source = {name: path.basename(filePath), path: filePath};
586596
}

src/test/paths.ts

Lines changed: 94 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -23,37 +23,57 @@ describe('paths', () => {
2323
});
2424
});
2525
describe('with source mapping', () => {
26+
// unix to unix
2627
it('should convert a unix path to a unix URI', () => {
27-
const localSourceRoot = '/home/felix/myproject';
28-
const serverSourceRoot = '/var/www';
29-
assert.equal(
30-
convertClientPathToDebugger('/home/felix/myproject/test.php', localSourceRoot, serverSourceRoot),
31-
'file:///var/www/test.php'
32-
);
28+
// site
29+
assert.equal(convertClientPathToDebugger('/home/felix/mysite/site.php', {
30+
'/var/www': '/home/felix/mysite',
31+
'/app': '/home/felix/mysource'
32+
}), 'file:///var/www/site.php');
33+
// source
34+
assert.equal(convertClientPathToDebugger('/home/felix/mysource/source.php', {
35+
'/var/www': '/home/felix/mysite',
36+
'/app': '/home/felix/mysource'
37+
}), 'file:///app/source.php');
3338
});
39+
// unix to windows
3440
it('should convert a unix path to a windows URI', () => {
35-
const localSourceRoot = '/home/felix/myproject';
36-
const serverSourceRoot = 'C:\\Program Files\\Apache\\2.4\\htdocs';
37-
assert.equal(
38-
convertClientPathToDebugger('/home/felix/myproject/test.php', localSourceRoot, serverSourceRoot),
39-
'file:///C:/Program%20Files/Apache/2.4/htdocs/test.php'
40-
);
41+
// site
42+
assert.equal(convertClientPathToDebugger('/home/felix/mysite/site.php', {
43+
'C:\\Program Files\\Apache\\2.4\\htdocs': '/home/felix/mysite',
44+
'C:\\Program Files\\MySource': '/home/felix/mysource'
45+
}), 'file:///C:/Program%20Files/Apache/2.4/htdocs/site.php');
46+
// source
47+
assert.equal(convertClientPathToDebugger('/home/felix/mysource/source.php', {
48+
'C:\\Program Files\\Apache\\2.4\\htdocs': '/home/felix/mysite',
49+
'C:\\Program Files\\MySource': '/home/felix/mysource'
50+
}), 'file:///C:/Program%20Files/MySource/source.php');
4151
});
42-
it('should convert a windows path to a unix URI', () => {
43-
const localSourceRoot = 'C:\\Users\\felix\\myproject';
44-
const serverSourceRoot = '/var/www';
45-
assert.equal(
46-
convertClientPathToDebugger('C:\\Users\\felix\\myproject\\test.php', localSourceRoot, serverSourceRoot),
47-
'file:///var/www/test.php'
48-
);
52+
// windows to unix
53+
(process.platform === 'win32' ? it : it.skip)('should convert a windows path to a unix URI', () => {
54+
// site
55+
assert.equal(convertClientPathToDebugger('C:\\Users\\felix\\mysite\\site.php', {
56+
'/var/www': 'C:\\Users\\felix\\mysite',
57+
'/app': 'C:\\Users\\felix\\mysource'
58+
}), 'file:///var/www/site.php');
59+
// source
60+
assert.equal(convertClientPathToDebugger('C:\\Users\\felix\\mysource\\source.php', {
61+
'/var/www': 'C:\\Users\\felix\\mysite',
62+
'/app': 'C:\\Users\\felix\\mysource'
63+
}), 'file:///app/source.php');
4964
});
50-
it('should convert a windows path to a windows URI', () => {
51-
const localSourceRoot = 'C:\\Users\\felix\\myproject';
52-
const serverSourceRoot = 'C:\\Program Files\\Apache\\2.4\\htdocs';
53-
assert.equal(
54-
convertClientPathToDebugger('C:\\Users\\felix\\myproject\\test.php', localSourceRoot, serverSourceRoot),
55-
'file:///C:/Program%20Files/Apache/2.4/htdocs/test.php'
56-
);
65+
// windows to windows
66+
(process.platform === 'win32' ? it : it.skip)('should convert a windows path to a windows URI', () => {
67+
// site
68+
assert.equal(convertClientPathToDebugger('C:\\Users\\felix\\mysite\\site.php', {
69+
'C:\\Program Files\\Apache\\2.4\\htdocs': 'C:\\Users\\felix\\mysite',
70+
'C:\\Program Files\\MySource': 'C:\\Users\\felix\\mysource'
71+
}), 'file:///C:/Program%20Files/Apache/2.4/htdocs/site.php');
72+
// source
73+
assert.equal(convertClientPathToDebugger('C:\\Users\\felix\\mysource\\source.php', {
74+
'C:\\Program Files\\Apache\\2.4\\htdocs': 'C:\\Users\\felix\\mysite',
75+
'C:\\Program Files\\MySource': 'C:\\Users\\felix\\mysource'
76+
}), 'file:///C:/Program%20Files/MySource/source.php');
5777
});
5878
});
5979
});
@@ -73,37 +93,57 @@ describe('paths', () => {
7393
});
7494
});
7595
describe('with source mapping', () => {
76-
(process.platform !== 'win32' ? it : it.skip)('should convert a unix URI to a unix path', () => {
77-
const localSourceRoot = '/home/felix/myproject';
78-
const serverSourceRoot = '/var/www';
79-
assert.equal(
80-
convertDebuggerPathToClient('file:///var/www/test.php', localSourceRoot, serverSourceRoot),
81-
'/home/felix/myproject/test.php'
82-
);
96+
// unix to unix
97+
(process.platform !== 'win32' ? it : it.skip)('should map unix uris to unix paths', () => {
98+
// site
99+
assert.equal(convertDebuggerPathToClient('file:///var/www/site.php', {
100+
'/var/www': '/home/felix/mysite',
101+
'/app': '/home/felix/mysource'
102+
}), '/home/felix/mysite/site.php');
103+
// source
104+
assert.equal(convertDebuggerPathToClient('file:///app/source.php', {
105+
'/var/www': '/home/felix/mysite',
106+
'/app': '/home/felix/mysource'
107+
}), '/home/felix/mysource/source.php');
83108
});
84-
(process.platform !== 'win32' ? it : it.skip)('should convert a windows URI to a unix path', () => {
85-
const localSourceRoot = '/home/felix/myproject';
86-
const serverSourceRoot = 'C:\\Program Files\\Apache\\2.4\\htdocs';
87-
assert.equal(
88-
convertDebuggerPathToClient('file:///C:/Program%20Files/Apache/2.4/htdocs/test.php', localSourceRoot, serverSourceRoot),
89-
'/home/felix/myproject/test.php'
90-
);
109+
// unix to windows
110+
(process.platform === 'win32' ? it : it.skip)('should map unix uris to windows paths', () => {
111+
// site
112+
assert.equal(convertDebuggerPathToClient('file:///var/www/site.php', {
113+
'/var/www': 'C:\\Users\\felix\\mysite',
114+
'/app': 'C:\\Users\\felix\\mysource'
115+
}), 'C:\\Users\\felix\\mysite\\site.php');
116+
// source
117+
assert.equal(convertDebuggerPathToClient('file:///app/source.php', {
118+
'/var/www': 'C:\\Users\\felix\\mysite',
119+
'/app': 'C:\\Users\\felix\\mysource'
120+
}), 'C:\\Users\\felix\\mysource\\source.php');
91121
});
92-
(process.platform === 'win32' ? it : it.skip)('should convert a unix URI to a windows path', () => {
93-
const localSourceRoot = 'C:\\Users\\felix\\myproject';
94-
const serverSourceRoot = '/var/www';
95-
assert.equal(
96-
convertDebuggerPathToClient('file:///var/www/test.php', localSourceRoot, serverSourceRoot),
97-
'C:\\Users\\felix\\myproject\\test.php'
98-
);
122+
// windows to unix
123+
(process.platform !== 'win32' ? it : it.skip)('should map windows uris to unix paths', () => {
124+
// site
125+
assert.equal(convertDebuggerPathToClient('file:///C:/Program%20Files/Apache/2.4/htdocs/site.php', {
126+
'C:\\Program Files\\Apache\\2.4\\htdocs': '/home/felix/mysite',
127+
'C:\\Program Files\\MySource': '/home/felix/mysource'
128+
}), '/home/felix/mysite/site.php');
129+
// source
130+
assert.equal(convertDebuggerPathToClient('file:///C:/Program%20Files/MySource/source.php', {
131+
'C:\\Program Files\\Apache\\2.4\\htdocs': '/home/felix/mysite',
132+
'C:\\Program Files\\MySource': '/home/felix/mysource'
133+
}), '/home/felix/mysource/source.php');
99134
});
100-
(process.platform === 'win32' ? it : it.skip)('should convert a windows URI to a windows path', () => {
101-
const localSourceRoot = 'C:\\Users\\felix\\myproject';
102-
const serverSourceRoot = 'C:\\Program Files\\Apache\\2.4\\htdocs';
103-
assert.equal(
104-
convertDebuggerPathToClient('file:///C:/Program%20Files/Apache/2.4/htdocs/test.php', localSourceRoot, serverSourceRoot),
105-
'C:\\Users\\felix\\myproject\\test.php'
106-
);
135+
// windows to windows
136+
(process.platform === 'win32' ? it : it.skip)('should map windows uris to windows paths', () => {
137+
// site
138+
assert.equal(convertDebuggerPathToClient('file:///C:/Program%20Files/Apache/2.4/htdocs/site.php', {
139+
'C:\\Program Files\\Apache\\2.4\\htdocs': 'C:\\Users\\felix\\mysite',
140+
'C:\\Program Files\\MySource': 'C:\\Users\\felix\\mysource'
141+
}), 'C:\\Users\\felix\\mysite\\site.php');
142+
// source
143+
assert.equal(convertDebuggerPathToClient('file:///C:/Program%20Files/MySource/source.php', {
144+
'C:\\Program Files\\Apache\\2.4\\htdocs': 'C:\\Users\\felix\\mysite',
145+
'C:\\Program Files\\MySource': 'C:\\Users\\felix\\mysource'
146+
}), 'C:\\Users\\felix\\mysource\\source.php');
107147
});
108148
});
109149
});

0 commit comments

Comments
 (0)