src/goDebugConfiguration.ts: add substitutePath to go.delveConfig

Allow users to configure substitutePath from settings.json so that
the setting can be applied to codelenses.

Updates golang/vscode-go#1467

Change-Id: I504d91e60f5b1a8c79369ad933b79247caa371ac
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/318589
Trust: Suzy Mueller <suzmue@golang.org>
Run-TryBot: Suzy Mueller <suzmue@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/docs/debugging.md b/docs/debugging.md
index 5e81b51..4b55ced 100644
--- a/docs/debugging.md
+++ b/docs/debugging.md
@@ -69,7 +69,8 @@
     * `maxStructFields`: Maximum number of fields read from a struct. A setting of `-1` indicates that all fields should be read (default: `-1`).
     * `maxVariableRecurse`: How far to recurse when evaluating nested types (default: `1`).
     * `followPointers`: Automatically dereference pointers (default: `true`).
-  * `showGlobalVariables`: Show global variables in the Debug view (default: `false`).
+  * `debugAdapter`: Controls which debug adapter to use (default: `legacy`).
+  * `substitutePath`: Path mappings to apply to get from a path in the editor to a path in the compiled program (default: `[]`).
 
 There are some common cases when you might want to tweak the Delve configurations.
 
diff --git a/docs/settings.md b/docs/settings.md
index 69f7768..65ec09f 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -146,6 +146,7 @@
 | `debugAdapter` | Select which debug adapter to use by default. This is also used for choosing which debug adapter to use when no launch.json is present and with codelenses. <br/> Allowed Options: `legacy`, `dlv-dap` <br/> Default: `"legacy"` |
 | `dlvLoadConfig` | LoadConfig describes to delve, how to load values from target's memory. Ignored by 'dlv-dap'. <br/> Default: ``` { <pre>"followPointers" :	true,<br/>"maxArrayValues" :	64,<br/>"maxStringLen" :	64,<br/>"maxStructFields" :	-1,<br/>"maxVariableRecurse" :	1,</pre>} ``` |
 | `showGlobalVariables` | Boolean value to indicate whether global package variables should be shown in the variables pane or not. <br/> Default: `false` |
+| `substitutePath` | An array of mappings from a local path to the remote path that is used by the debuggee. The debug adapter will replace the local path with the remote path in all of the calls. Overriden by remotePath. |
 
 Default:
 ```
@@ -160,6 +161,7 @@
 		"maxVariableRecurse" :	1,
 	},
 	"showGlobalVariables" :	false,
+	"substitutePath" :	[],
 }
 ```
 ### `go.disableConcurrentTests`
diff --git a/package.json b/package.json
index e9df8a0..265d957 100644
--- a/package.json
+++ b/package.json
@@ -1737,6 +1737,26 @@
               ],
               "description": "Select which debug adapter to use by default. This is also used for choosing which debug adapter to use when no launch.json is present and with codelenses.",
               "default": "legacy"
+            },
+            "substitutePath": {
+              "type": "array",
+              "items": {
+                "type": "object",
+                "properties": {
+                  "from": {
+                    "type": "string",
+                    "description": "The absolute local path to be replaced when passing paths to the debugger",
+                    "default": ""
+                  },
+                  "to": {
+                    "type": "string",
+                    "description": "The absolute remote path to be replaced when passing paths back to the client",
+                    "default": ""
+                  }
+                }
+              },
+              "description": "An array of mappings from a local path to the remote path that is used by the debuggee. The debug adapter will replace the local path with the remote path in all of the calls. Overriden by remotePath.",
+              "default": []
             }
           },
           "default": {
@@ -1749,7 +1769,8 @@
             },
             "apiVersion": 2,
             "showGlobalVariables": false,
-            "debugAdapter": "legacy"
+            "debugAdapter": "legacy",
+            "substitutePath": []
           },
           "description": "Delve settings that applies to all debugging sessions. Debug configuration in the launch.json file will override these values.",
           "scope": "resource"
diff --git a/src/goDebugConfiguration.ts b/src/goDebugConfiguration.ts
index dad70f3..2923793 100644
--- a/src/goDebugConfiguration.ts
+++ b/src/goDebugConfiguration.ts
@@ -183,6 +183,9 @@
 		) {
 			debugConfiguration['showGlobalVariables'] = dlvConfig['showGlobalVariables'];
 		}
+		if (!debugConfiguration.hasOwnProperty('substitutePath') && dlvConfig.hasOwnProperty('substitutePath')) {
+			debugConfiguration['substitutePath'] = dlvConfig['substitutePath'];
+		}
 		if (debugConfiguration.request === 'attach' && !debugConfiguration['cwd']) {
 			debugConfiguration['cwd'] = '${workspaceFolder}';
 		}
diff --git a/test/integration/goDebugConfiguration.test.ts b/test/integration/goDebugConfiguration.test.ts
index d2c112d..08e5397 100644
--- a/test/integration/goDebugConfiguration.test.ts
+++ b/test/integration/goDebugConfiguration.test.ts
@@ -200,6 +200,8 @@
 			assert.strictEqual(filledResult.dlvToolPath, emptyResult.dlvToolPath);
 			assert.strictEqual(filledResult.apiVersion, emptyResult.apiVersion);
 			assert.strictEqual(filledResult.showGlobalVariables, emptyResult.showGlobalVariables);
+			assert.strictEqual(filledResult.debugAdapter, emptyResult.debugAdapter);
+			assert.strictEqual(filledResult.substitutePath, emptyResult.substitutePath);
 		});
 
 		test('delve config in settings.json is added to debug config', async () => {
@@ -219,7 +221,9 @@
 							maxStructFields: 5
 						},
 						apiVersion: 1,
-						showGlobalVariables: true
+						showGlobalVariables: true,
+						debugAdapter: 'dlv-dap',
+						substitutePath: [{ from: 'hello', to: 'goodbye' }]
 					}
 				}
 			}) as vscode.WorkspaceConfiguration;
@@ -236,6 +240,10 @@
 			const result = await debugConfigProvider.resolveDebugConfiguration(undefined, cfg);
 			assert.strictEqual(result.apiVersion, 1);
 			assert.strictEqual(result.showGlobalVariables, true);
+			assert.strictEqual(result.debugAdapter, 'dlv-dap');
+			assert.strictEqual(result.substitutePath.length, 1);
+			assert.strictEqual(result.substitutePath[0].from, 'hello');
+			assert.strictEqual(result.substitutePath[0].to, 'goodbye');
 			const dlvLoadConfig = result.dlvLoadConfig;
 			assert.strictEqual(dlvLoadConfig.followPointers, false);
 			assert.strictEqual(dlvLoadConfig.maxVariableRecurse, 3);
@@ -261,13 +269,15 @@
 							maxStructFields: 5
 						},
 						apiVersion: 1,
-						showGlobalVariables: true
+						showGlobalVariables: true,
+						debugAdapter: 'dlv-dap',
+						substitutePath: [{ from: 'hello', to: 'goodbye' }]
 					}
 				}
 			}) as vscode.WorkspaceConfiguration;
 			sinon.stub(config, 'getGoConfig').returns(goConfig);
 
-			const cfg = {
+			const cfg: vscode.DebugConfiguration = {
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
@@ -281,12 +291,16 @@
 					maxStringLen: 128,
 					maxArrayValues: 128,
 					maxStructFields: -1
-				}
+				},
+				debugAdapter: 'legacy',
+				substitutePath: []
 			};
 
 			const result = await debugConfigProvider.resolveDebugConfiguration(undefined, cfg);
 			assert.strictEqual(result.apiVersion, 2);
 			assert.strictEqual(result.showGlobalVariables, false);
+			assert.strictEqual(result.debugAdapter, 'legacy');
+			assert.strictEqual(result.substitutePath.length, 0);
 			const dlvLoadConfig = result.dlvLoadConfig;
 			assert.strictEqual(dlvLoadConfig.followPointers, true);
 			assert.strictEqual(dlvLoadConfig.maxVariableRecurse, 6);