debug: implement new prototype thin DA for delve

Updates golang/vscode-go#23

This DA serves as a DAP proxy between the editor and a DAP-capable Delve. Right now all it does is launch Delve (in DAP mode) when LaunchRequest is received from the editor, and then relays all messages between the editor and Delve back and forth.

package.json section is copied from the current DA as-is. Some of its options will be removed and cleaned up in the subsequent PRs to make the diffs more obvious (see golang/vscode-go#271).

Change-Id: I3493c5ee1d9e80071a17ea32c04a15eb021c8006
GitHub-Last-Rev: ddd68dcb3d19cd9e6ec7cc9adc3e9a5c132cc4d4
GitHub-Pull-Request: golang/vscode-go#267
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/240417
Reviewed-by: Polina Sokolova <polina@google.com>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 94310ec..b4b1ca6 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -35,6 +35,21 @@
 			"preLaunchTask": "npm"
 		},
 		{
+			"name": "Launch as server (dlv dap)",
+			"type": "node",
+			"protocol": "inspector",
+			"request": "launch",
+			"program": "${workspaceFolder}/out/src/debugAdapter2/goDlvDebugMain.js",
+			"args": [
+				"--server=4711"
+			],
+			"sourceMaps": true,
+			"outFiles": [
+				"${workspaceFolder}/out/**/*.js"
+			],
+			"preLaunchTask": "npm"
+		},
+		{
 			"name": "Launch Extension Tests",
 			"type": "extensionHost",
 			"request": "launch",
diff --git a/package.json b/package.json
index 357b2ae..8b37645 100644
--- a/package.json
+++ b/package.json
@@ -750,6 +750,401 @@
             }
           }
         }
+      },
+      {
+        "type": "godlvdap",
+        "label": "Go Dlv Dap (Experimental)",
+        "program": "./out/src/debugAdapter2/goDlvDebugMain.js",
+        "runtime": "node",
+        "languages": [
+          "go"
+        ],
+        "configurationSnippets": [
+          {
+            "label": "Go: Launch package",
+            "description": "Debug the package in the program attribute",
+            "body": {
+              "name": "${2:Launch Package}",
+              "type": "godlvdap",
+              "request": "launch",
+              "mode": "debug",
+              "program": "^\"\\${workspaceFolder}${1:}\""
+            }
+          },
+          {
+            "label": "Go: Launch file",
+            "description": "Debug the file in the program attribute",
+            "body": {
+              "name": "${2:Launch file}",
+              "type": "godlvdap",
+              "request": "launch",
+              "mode": "debug",
+              "program": "^\"${1:\\${file\\}}\""
+            }
+          },
+          {
+            "label": "Go: Launch test package",
+            "description": "Debug the test package in the program attribute",
+            "body": {
+              "name": "${2:Launch test package}",
+              "type": "godlvdap",
+              "request": "launch",
+              "mode": "test",
+              "program": "^\"\\${workspaceFolder}${1:}\""
+            }
+          },
+          {
+            "label": "Go: Launch test function",
+            "description": "Debug the test function in the args, ensure program attributes points to right package",
+            "body": {
+              "name": "${3:Launch test function}",
+              "type": "godlvdap",
+              "request": "launch",
+              "mode": "test",
+              "program": "^\"\\${workspaceFolder}${1:}\"",
+              "args": [
+                "-test.run",
+                "${2:MyTestFunction}"
+              ]
+            }
+          },
+          {
+            "label": "Go: Attach to local process",
+            "description": "Attach to an existing process by process ID",
+            "body": {
+              "name": "${1:Attach to Process}",
+              "type": "godlvdap",
+              "request": "attach",
+              "mode": "local",
+              "processId": 0
+            }
+          },
+          {
+            "label": "Go: Connect to server",
+            "description": "Connect to a remote headless debug server",
+            "body": {
+              "name": "${1:Connect to server}",
+              "type": "godlvdap",
+              "request": "attach",
+              "mode": "remote",
+              "remotePath": "^\"\\${workspaceFolder}\"",
+              "port": 2345,
+              "host": "127.0.0.1"
+            }
+          }
+        ],
+        "configurationAttributes": {
+          "launch": {
+            "required": [],
+            "properties": {
+              "program": {
+                "type": "string",
+                "description": "Path to the program folder (or any file within that folder) when in 'debug' or 'test' mode, and to the pre-built binary file to debug in 'exec' mode.",
+                "default": "${workspaceFolder}"
+              },
+              "mode": {
+                "enum": [
+                  "auto",
+                  "debug",
+                  "remote",
+                  "test",
+                  "exec"
+                ],
+                "description": "One of 'auto', 'debug', 'remote', 'test', 'exec'.",
+                "default": "auto"
+              },
+              "stopOnEntry": {
+                "type": "boolean",
+                "description": "Automatically stop program after launch.",
+                "default": false
+              },
+              "args": {
+                "type": "array",
+                "description": "Command line arguments passed to the program.",
+                "items": {
+                  "type": "string"
+                },
+                "default": []
+              },
+              "showLog": {
+                "type": "boolean",
+                "description": "Show log output from the delve debugger.",
+                "default": false
+              },
+              "cwd": {
+                "type": "string",
+                "description": "Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace.",
+                "default": "."
+              },
+              "env": {
+                "type": "object",
+                "description": "Environment variables passed to the program.",
+                "default": {}
+              },
+              "buildFlags": {
+                "type": "string",
+                "description": "Build flags, to be passed to the Go compiler.",
+                "default": ""
+              },
+              "init": {
+                "type": "string",
+                "description": "Init file, executed by the terminal client.",
+                "default": ""
+              },
+              "remotePath": {
+                "type": "string",
+                "description": "Absolute path to the file being debugged on the remote machine in case of remote debugging.",
+                "default": ""
+              },
+              "port": {
+                "type": "number",
+                "description": "The port that the delve debugger will be listening on.",
+                "default": 2345
+              },
+              "host": {
+                "type": "string",
+                "description": "The host name of the machine the delve debugger will be listening on.",
+                "default": "127.0.0.1"
+              },
+              "trace": {
+                "type": "string",
+                "enum": [
+                  "log",
+                  "verbose",
+                  "error"
+                ],
+                "default": "error",
+                "description": "Various levels of logging shown in the debug console. When set to 'log' or 'verbose', the logs will also be written to a file."
+              },
+              "envFile": {
+                "type": [
+                  "string",
+                  "array"
+                ],
+                "items": {
+                  "type": "string"
+                },
+                "description": "Absolute path to a file containing environment variable definitions. Multiple files can be specified by provided an array of absolute paths",
+                "default": "${workspaceFolder}/.env"
+              },
+              "backend": {
+                "type": "string",
+                "enum": [
+                  "default",
+                  "native",
+                  "lldb"
+                ],
+                "description": "Backend used by delve. Only available in delve version 0.12.2 and above."
+              },
+              "output": {
+                "type": "string",
+                "description": "Output path for the binary of delve",
+                "default": "debug"
+              },
+              "logOutput": {
+                "type": "string",
+                "enum": [
+                  "debugger",
+                  "gdbwire",
+                  "lldbout",
+                  "debuglineerr",
+                  "dap",
+                  "rpc"
+                ],
+                "description": "Comma separated list of components that should produce debug output.",
+                "default": "debugger"
+              },
+              "dlvLoadConfig": {
+                "type": "object",
+                "properties": {
+                  "followPointers": {
+                    "type": "boolean",
+                    "description": "FollowPointers requests pointers to be automatically dereferenced",
+                    "default": true
+                  },
+                  "maxVariableRecurse": {
+                    "type": "number",
+                    "description": "MaxVariableRecurse is how far to recurse when evaluating nested types",
+                    "default": 1
+                  },
+                  "maxStringLen": {
+                    "type": "number",
+                    "description": "MaxStringLen is the maximum number of bytes read from a string",
+                    "default": 64
+                  },
+                  "maxArrayValues": {
+                    "type": "number",
+                    "description": "MaxArrayValues is the maximum number of elements read from an array, a slice or a map",
+                    "default": 64
+                  },
+                  "maxStructFields": {
+                    "type": "number",
+                    "description": "MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields",
+                    "default": -1
+                  }
+                },
+                "description": "LoadConfig describes to delve, how to load values from target's memory",
+                "default": {
+                  "followPointers": true,
+                  "maxVariableRecurse": 1,
+                  "maxStringLen": 64,
+                  "maxArrayValues": 64,
+                  "maxStructFields": -1
+                }
+              },
+              "apiVersion": {
+                "type": "number",
+                "enum": [
+                  1,
+                  2
+                ],
+                "description": "Delve Api Version to use. Default value is 2.",
+                "default": 2
+              },
+              "stackTraceDepth": {
+                "type": "number",
+                "description": "Maximum depth of stack trace collected from Delve",
+                "default": 50
+              },
+              "showGlobalVariables": {
+                "type": "boolean",
+                "default": true,
+                "description": "Boolean value to indicate whether global package variables should be shown in the variables pane or not."
+              }
+            }
+          },
+          "attach": {
+            "required": [],
+            "properties": {
+              "processId": {
+                "type": "number",
+                "description": "The ID of the process to be debugged."
+              },
+              "mode": {
+                "enum": [
+                  "local",
+                  "remote"
+                ],
+                "description": "Indicates local or remote debugging.  Local maps to the dlv 'attach' command, remote maps to 'connect'.",
+                "default": "local"
+              },
+              "showLog": {
+                "type": "boolean",
+                "description": "Show log output from the delve debugger.",
+                "default": false
+              },
+              "cwd": {
+                "type": "string",
+                "description": "Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace.",
+                "default": "${workspaceFolder}"
+              },
+              "remotePath": {
+                "type": "string",
+                "description": "If remote debugging, the path to the source code on the remote machine, if different from the local machine.",
+                "default": ""
+              },
+              "port": {
+                "type": "number",
+                "description": "The port that the delve debugger will be listening on.",
+                "default": 2345
+              },
+              "host": {
+                "type": "string",
+                "description": "The host name of the machine the delve debugger will be listening on.",
+                "default": "127.0.0.1"
+              },
+              "trace": {
+                "type": "string",
+                "enum": [
+                  "log",
+                  "verbose",
+                  "error"
+                ],
+                "default": "error",
+                "description": "Various levels of logging shown in the debug console. When set to 'log' or 'verbose', the logs will also be written to a file."
+              },
+              "backend": {
+                "type": "string",
+                "enum": [
+                  "default",
+                  "native",
+                  "lldb"
+                ],
+                "description": "Backend used by delve. Only available in delve version 0.12.2 and above."
+              },
+              "logOutput": {
+                "type": "string",
+                "enum": [
+                  "debugger",
+                  "gdbwire",
+                  "lldbout",
+                  "debuglineerr",
+                  "dap",
+                  "rpc"
+                ],
+                "description": "Comma separated list of components that should produce debug output.",
+                "default": "debugger"
+              },
+              "dlvLoadConfig": {
+                "type": "object",
+                "properties": {
+                  "followPointers": {
+                    "type": "boolean",
+                    "description": "FollowPointers requests pointers to be automatically dereferenced",
+                    "default": true
+                  },
+                  "maxVariableRecurse": {
+                    "type": "number",
+                    "description": "MaxVariableRecurse is how far to recurse when evaluating nested types",
+                    "default": 1
+                  },
+                  "maxStringLen": {
+                    "type": "number",
+                    "description": "MaxStringLen is the maximum number of bytes read from a string",
+                    "default": 64
+                  },
+                  "maxArrayValues": {
+                    "type": "number",
+                    "description": "MaxArrayValues is the maximum number of elements read from an array, a slice or a map",
+                    "default": 64
+                  },
+                  "maxStructFields": {
+                    "type": "number",
+                    "description": "MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields",
+                    "default": -1
+                  }
+                },
+                "description": "LoadConfig describes to delve, how to load values from target's memory",
+                "default": {
+                  "followPointers": true,
+                  "maxVariableRecurse": 1,
+                  "maxStringLen": 64,
+                  "maxArrayValues": 64,
+                  "maxStructFields": -1
+                }
+              },
+              "apiVersion": {
+                "type": "number",
+                "enum": [
+                  1,
+                  2
+                ],
+                "description": "Delve Api Version to use. Default value is 2.",
+                "default": 2
+              },
+              "stackTraceDepth": {
+                "type": "number",
+                "description": "Maximum depth of stack trace collected from Delve",
+                "default": 50
+              },
+              "showGlobalVariables": {
+                "type": "boolean",
+                "default": true,
+                "description": "Boolean value to indicate whether global package variables should be shown in the variables pane or not."
+              }
+            }
+          }
+        }
       }
     ],
     "configuration": {
diff --git a/src/debugAdapter2/README.md b/src/debugAdapter2/README.md
new file mode 100644
index 0000000..f0ca4fb
--- /dev/null
+++ b/src/debugAdapter2/README.md
@@ -0,0 +1,6 @@
+# Debug Adapter
+
+This debug adapter is experimental, in-development code. If you
+actually need to debug Go code, please use the default adapter ("type": "go").
+
+See the [contribution documentation](../../docs/contributing.md) to learn how to develop the debug adapter.
diff --git a/src/debugAdapter2/dapClient.ts b/src/debugAdapter2/dapClient.ts
new file mode 100644
index 0000000..bcceba9
--- /dev/null
+++ b/src/debugAdapter2/dapClient.ts
@@ -0,0 +1,97 @@
+/*---------------------------------------------------------
+ * Copyright 2020 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+import { EventEmitter } from 'events';
+import stream = require('stream');
+
+import { DebugProtocol } from 'vscode-debugprotocol';
+
+// DapClient implements a simple client for the DAP protocol.
+// It's initialized with a pair of streams that the caller creats and enables
+// sending and receiving DAP messages over these streams.
+// After calling connect():
+//
+//  - For sending messages call send().
+//  - For receiving messages, subscibe to events this class emits.
+//      - 'event', 'respones', 'request' - each carrying an appropriate
+//        DebugProtocol type as an argument.
+export class DAPClient extends EventEmitter {
+	private static readonly TWO_CRLF = '\r\n\r\n';
+
+	private outputStream: stream.Writable;
+
+	private rawData = Buffer.alloc(0);
+	private contentLength: number = -1;
+
+	constructor() {
+		super();
+	}
+
+	public send(req: any): void {
+		const json = JSON.stringify(req);
+		this.outputStream.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`, 'utf8');
+	}
+
+	protected connect(readable: stream.Readable, writable: stream.Writable): void {
+		this.outputStream = writable;
+
+		readable.on('data', (data: Buffer) => {
+			this.handleData(data);
+		});
+	}
+
+	// Implements parsing of the DAP protocol. We cannot use ProtocolClient
+	// from the vscode-debugadapter package, because it's not exported and
+	// is not meant for external usage.
+	// See https://github.com/microsoft/vscode-debugadapter-node/issues/232
+	private handleData(data: Buffer): void {
+		this.rawData = Buffer.concat([this.rawData, data]);
+
+		while (true) {
+			if (this.contentLength >= 0) {
+				if (this.rawData.length >= this.contentLength) {
+					const message = this.rawData.toString('utf8', 0, this.contentLength);
+					this.rawData = this.rawData.slice(this.contentLength);
+					this.contentLength = -1;
+					if (message.length > 0) {
+						this.dispatch(message);
+					}
+					continue;	// there may be more complete messages to process
+				}
+			} else {
+				const idx = this.rawData.indexOf(DAPClient.TWO_CRLF);
+				if (idx !== -1) {
+					const header = this.rawData.toString('utf8', 0, idx);
+					const lines = header.split('\r\n');
+					for (const line of lines) {
+						const pair = line.split(/: +/);
+						if (pair[0] === 'Content-Length') {
+							this.contentLength = +pair[1];
+						}
+					}
+					this.rawData = this.rawData.slice(idx + DAPClient.TWO_CRLF.length);
+					continue;
+				}
+			}
+			break;
+		}
+	}
+
+	private dispatch(body: string): void {
+		const rawData = JSON.parse(body);
+
+		if (rawData.type === 'event') {
+			const event = <DebugProtocol.Event>rawData;
+			this.emit('event', event);
+		} else if (rawData.type === 'response') {
+			const response = <DebugProtocol.Response>rawData;
+			this.emit('response', response);
+		} else if (rawData.type === 'request') {
+			const request = <DebugProtocol.Request>rawData;
+			this.emit('request', request);
+		} else {
+			throw new Error(`unknown message ${JSON.stringify(rawData)}`);
+		}
+	}
+}
diff --git a/src/debugAdapter2/goDlvDebug.ts b/src/debugAdapter2/goDlvDebug.ts
new file mode 100644
index 0000000..e895945
--- /dev/null
+++ b/src/debugAdapter2/goDlvDebug.ts
@@ -0,0 +1,628 @@
+/*---------------------------------------------------------
+ * Copyright 2020 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+// NOTE: This debug adapter is experimental, in-development code. If you
+// actually need to debug Go code, please use the default adapter.
+
+import { ChildProcess, spawn } from 'child_process';
+import * as fs from 'fs';
+import net = require('net');
+import * as os from 'os';
+import * as path from 'path';
+
+import {
+	logger,
+	Logger,
+	LoggingDebugSession,
+	TerminatedEvent
+} from 'vscode-debugadapter';
+import { DebugProtocol } from 'vscode-debugprotocol';
+
+import { envPath } from '../goPath';
+import { DAPClient } from './dapClient';
+
+interface LoadConfig {
+	// FollowPointers requests pointers to be automatically dereferenced.
+	followPointers: boolean;
+	// MaxVariableRecurse is how far to recurse when evaluating nested types.
+	maxVariableRecurse: number;
+	// MaxStringLen is the maximum number of bytes read from a string
+	maxStringLen: number;
+	// MaxArrayValues is the maximum number of elements read from an array, a slice or a map.
+	maxArrayValues: number;
+	// MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields.
+	maxStructFields: number;
+}
+
+// This interface should always match the schema found in `package.json`.
+interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
+	request: 'launch';
+	[key: string]: any;
+	program: string;
+	stopOnEntry?: boolean;
+	args?: string[];
+	showLog?: boolean;
+	logOutput?: string;
+	cwd?: string;
+	env?: { [key: string]: string };
+	mode?: 'auto' | 'debug' | 'remote' | 'test' | 'exec';
+	remotePath?: string;
+	port?: number;
+	host?: string;
+	buildFlags?: string;
+	init?: string;
+	trace?: 'verbose' | 'log' | 'error';
+	/** Optional path to .env file. */
+	envFile?: string | string[];
+	backend?: string;
+	output?: string;
+	/** Delve LoadConfig parameters */
+	dlvLoadConfig?: LoadConfig;
+	dlvToolPath: string;
+	/** Delve Version */
+	apiVersion: number;
+	/** Delve maximum stack trace depth */
+	stackTraceDepth: number;
+
+	showGlobalVariables?: boolean;
+	packagePathToGoModPathMap: { [key: string]: string };
+}
+
+interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments {
+	request: 'attach';
+	processId?: number;
+	stopOnEntry?: boolean;
+	showLog?: boolean;
+	logOutput?: string;
+	cwd?: string;
+	mode?: 'local' | 'remote';
+	remotePath?: string;
+	port?: number;
+	host?: string;
+	trace?: 'verbose' | 'log' | 'error';
+	backend?: string;
+	/** Delve LoadConfig parameters */
+	dlvLoadConfig?: LoadConfig;
+	dlvToolPath: string;
+	/** Delve Version */
+	apiVersion: number;
+	/** Delve maximum stack trace depth */
+	stackTraceDepth: number;
+
+	showGlobalVariables?: boolean;
+}
+
+process.on('uncaughtException', (err: any) => {
+	const errMessage = err && (err.stack || err.message);
+	logger.error(`Unhandled error in debug adapter: ${errMessage}`);
+	throw err;
+});
+
+function logArgsToString(args: any[]): string {
+	return args
+		.map((arg) => {
+			return typeof arg === 'string' ? arg : JSON.stringify(arg);
+		})
+		.join(' ');
+}
+
+function log(...args: any[]) {
+	logger.warn(logArgsToString(args));
+}
+
+function logError(...args: any[]) {
+	logger.error(logArgsToString(args));
+}
+
+// GoDlvDapDebugSession implements a DAP debug adapter to talk to the editor.
+//
+// This adapter serves as a DAP proxy between the editor and the DAP server
+// inside Delve. It relies on functionality inherited from DebugSession to
+// implement the server side interfacing the editor, and on DapClient to
+// implement the client side interfacing Delve:
+//
+//      Editor                GoDlvDapDebugSession                 Delve
+//  +------------+        +--------------+-----------+         +------------+
+//  | DAP Client | <====> | DebugSession | DAPClient |  <====> | DAP Server |
+//  +------------+        +--------------+-----------+         +------------+
+export class GoDlvDapDebugSession extends LoggingDebugSession {
+	private readonly DEFAULT_DELVE_HOST = '127.0.0.1';
+	private readonly DEFAULT_DELVE_PORT = 42042;
+
+	private logLevel: Logger.LogLevel = Logger.LogLevel.Error;
+
+	private dlvClient: DelveClient;
+
+	public constructor() {
+		super();
+
+		// Invoke logger.init here because we want logging to work in 'inline'
+		// DA mode. It's typically called in the start() method of our parent
+		// class, but this method isn't called in 'inline' mode.
+		logger.init((e) => this.sendEvent(e));
+
+		// this debugger uses zero-based lines and columns
+		this.setDebuggerLinesStartAt1(false);
+		this.setDebuggerColumnsStartAt1(false);
+	}
+
+	protected initializeRequest(
+		response: DebugProtocol.InitializeResponse,
+		args: DebugProtocol.InitializeRequestArguments,
+		request?: DebugProtocol.Request
+	): void {
+		log('InitializeRequest');
+		response.body.supportsConfigurationDoneRequest = true;
+
+		// We respond to InitializeRequest here, because Delve hasn't been
+		// launched yet. Delve will start responding to DAP requests after
+		// LaunchRequest is received, which tell us how to start it.
+
+		// TODO: we could send an InitializeRequest to Delve when
+		// it launches, wait for its response and sanity check the capabilities
+		// it reports. Once DAP support in Delve is complete, this can be part
+		// of making sure that the "dlv" binary we find is sufficiently
+		// up-to-date to talk DAP with us.
+		this.sendResponse(response);
+		log('InitializeResponse');
+	}
+
+	protected launchRequest(
+		response: DebugProtocol.LaunchResponse,
+		args: LaunchRequestArguments,
+		request: DebugProtocol.Request
+	): void {
+		// Setup logger now that we have the 'trace' level passed in from
+		// LaunchRequestArguments.
+		this.logLevel =
+			args.trace === 'verbose'
+				? Logger.LogLevel.Verbose
+				: args.trace === 'log'
+					? Logger.LogLevel.Log
+					: Logger.LogLevel.Error;
+		const logPath =
+			this.logLevel !== Logger.LogLevel.Error ? path.join(os.tmpdir(), 'vscode-godlvdapdebug.txt') : undefined;
+		logger.setup(this.logLevel, logPath);
+
+		log('launchRequest');
+
+		if (!args.port) {
+			args.port = this.DEFAULT_DELVE_PORT;
+		}
+		if (!args.host) {
+			args.host = this.DEFAULT_DELVE_HOST;
+		}
+
+		// TODO: if this is a noDebug launch request, don't launch Delve;
+		// instead, run the program directly.
+
+		this.dlvClient = new DelveClient(args);
+
+		this.dlvClient.on('stdout', (str) => {
+			log('dlv stdout:', str);
+		});
+
+		this.dlvClient.on('stderr', (str) => {
+			log('dlv stderr:', str);
+		});
+
+		this.dlvClient.on('connected', () => {
+			this.dlvClient.send(request);
+		});
+
+		this.dlvClient.on('close', (rc) => {
+			if (rc !== 0) {
+				this.sendErrorResponse(
+					response,
+					3000,
+					'Failed to continue: Check the debug console for details.');
+			}
+			log('Sending TerminatedEvent as delve is closed');
+			this.sendEvent(new TerminatedEvent());
+		});
+
+		// Relay events and responses back to vscode. In the future we will
+		// add middleware here to intercept specific kinds of responses/events
+		// for special handling.
+		this.dlvClient.on('event', (event) => {
+			this.sendEvent(event);
+		});
+
+		this.dlvClient.on('response', (resp) => {
+			this.sendResponse(resp);
+		});
+	}
+
+	protected attachRequest(
+		response: DebugProtocol.AttachResponse,
+		args: AttachRequestArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected terminateRequest(
+		response: DebugProtocol.TerminateResponse,
+		args: DebugProtocol.TerminateArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected restartRequest(
+		response: DebugProtocol.RestartResponse,
+		args: DebugProtocol.RestartArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected setBreakPointsRequest(
+		response: DebugProtocol.SetBreakpointsResponse,
+		args: DebugProtocol.SetBreakpointsArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected setFunctionBreakPointsRequest(
+		response: DebugProtocol.SetFunctionBreakpointsResponse,
+		args: DebugProtocol.SetFunctionBreakpointsArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected setExceptionBreakPointsRequest(
+		response: DebugProtocol.SetExceptionBreakpointsResponse,
+		args: DebugProtocol.SetExceptionBreakpointsArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected configurationDoneRequest(
+		response: DebugProtocol.ConfigurationDoneResponse,
+		args: DebugProtocol.ConfigurationDoneArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected continueRequest(
+		response: DebugProtocol.ContinueResponse,
+		args: DebugProtocol.ContinueArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected nextRequest(
+		response: DebugProtocol.NextResponse,
+		args: DebugProtocol.NextArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected stepInRequest(
+		response: DebugProtocol.StepInResponse,
+		args: DebugProtocol.StepInArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected stepOutRequest(
+		response: DebugProtocol.StepOutResponse,
+		args: DebugProtocol.StepOutArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected stepBackRequest(
+		response: DebugProtocol.StepBackResponse,
+		args: DebugProtocol.StepBackArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected reverseContinueRequest(
+		response: DebugProtocol.ReverseContinueResponse,
+		args: DebugProtocol.ReverseContinueArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected restartFrameRequest(
+		response: DebugProtocol.RestartFrameResponse,
+		args: DebugProtocol.RestartFrameArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected gotoRequest(
+		response: DebugProtocol.GotoResponse,
+		args: DebugProtocol.GotoArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected pauseRequest(
+		response: DebugProtocol.PauseResponse,
+		args: DebugProtocol.PauseArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected sourceRequest(
+		response: DebugProtocol.SourceResponse,
+		args: DebugProtocol.SourceArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected threadsRequest(
+		response: DebugProtocol.ThreadsResponse,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected terminateThreadsRequest(
+		response: DebugProtocol.TerminateThreadsResponse,
+		args: DebugProtocol.TerminateThreadsArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected stackTraceRequest(
+		response: DebugProtocol.StackTraceResponse,
+		args: DebugProtocol.StackTraceArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected scopesRequest(
+		response: DebugProtocol.ScopesResponse,
+		args: DebugProtocol.ScopesArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected variablesRequest(
+		response: DebugProtocol.VariablesResponse,
+		args: DebugProtocol.VariablesArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected setVariableRequest(
+		response: DebugProtocol.SetVariableResponse,
+		args: DebugProtocol.SetVariableArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected setExpressionRequest(
+		response: DebugProtocol.SetExpressionResponse,
+		args: DebugProtocol.SetExpressionArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected evaluateRequest(
+		response: DebugProtocol.EvaluateResponse,
+		args: DebugProtocol.EvaluateArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected stepInTargetsRequest(
+		response: DebugProtocol.StepInTargetsResponse,
+		args: DebugProtocol.StepInTargetsArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected gotoTargetsRequest(
+		response: DebugProtocol.GotoTargetsResponse,
+		args: DebugProtocol.GotoTargetsArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected completionsRequest(
+		response: DebugProtocol.CompletionsResponse,
+		args: DebugProtocol.CompletionsArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected exceptionInfoRequest(
+		response: DebugProtocol.ExceptionInfoResponse,
+		args: DebugProtocol.ExceptionInfoArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected loadedSourcesRequest(
+		response: DebugProtocol.LoadedSourcesResponse,
+		args: DebugProtocol.LoadedSourcesArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected dataBreakpointInfoRequest(
+		response: DebugProtocol.DataBreakpointInfoResponse,
+		args: DebugProtocol.DataBreakpointInfoArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected setDataBreakpointsRequest(
+		response: DebugProtocol.SetDataBreakpointsResponse,
+		args: DebugProtocol.SetDataBreakpointsArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected readMemoryRequest(
+		response: DebugProtocol.ReadMemoryResponse,
+		args: DebugProtocol.ReadMemoryArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected disassembleRequest(
+		response: DebugProtocol.DisassembleResponse,
+		args: DebugProtocol.DisassembleArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected cancelRequest(
+		response: DebugProtocol.CancelResponse,
+		args: DebugProtocol.CancelArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected breakpointLocationsRequest(
+		response: DebugProtocol.BreakpointLocationsResponse,
+		args: DebugProtocol.BreakpointLocationsArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+
+	protected setInstructionBreakpointsRequest(
+		response: DebugProtocol.SetInstructionBreakpointsResponse,
+		args: DebugProtocol.SetInstructionBreakpointsArguments,
+		request?: DebugProtocol.Request
+	): void {
+		this.dlvClient.send(request);
+	}
+}
+
+// DelveClient provides a DAP client to talk to a DAP server in Delve.
+//
+// After creation, it emits the following events:
+//
+//    'connected':            delve is connected to delve
+//    'request (request)':    delve sent request
+//    'response (response)':  delve sent response
+//    'event (event)':        delve sent event
+//    'stdout' (str):         delve emitted str to stdout
+//    'stderr' (str):         delve emitted str to stderr
+//    'close' (rc):           delve exited with return code rc
+class DelveClient extends DAPClient {
+	private debugProcess: ChildProcess;
+
+	constructor(launchArgs: LaunchRequestArguments) {
+		super();
+
+		const launchArgsEnv = launchArgs.env || {};
+		const env = Object.assign({}, process.env, launchArgsEnv);
+
+		// Let users override direct path to delve by setting it in the env
+		// map in launch.json; if unspecified, fall back to dlvToolPath.
+		let dlvPath = launchArgsEnv['dlvPath'];
+		if (!dlvPath) {
+			dlvPath = launchArgs.dlvToolPath;
+		}
+
+		if (!fs.existsSync(dlvPath)) {
+			log(
+				`Couldn't find dlv at the Go tools path, ${process.env['GOPATH']}${
+				env['GOPATH'] ? ', ' + env['GOPATH'] : ''
+				} or ${envPath}`
+			);
+			throw new Error(
+				`Cannot find Delve debugger. Install from https://github.com/go-delve/delve/ & ensure it is in your Go tools path, "GOPATH/bin" or "PATH".`
+			);
+		}
+
+		const dlvArgs = new Array<string>();
+		dlvArgs.push('dap');
+		dlvArgs.push(`--listen=${launchArgs.host}:${launchArgs.port}`);
+		if (launchArgs.showLog) {
+			dlvArgs.push('--log=' + launchArgs.showLog.toString());
+		}
+		if (launchArgs.logOutput) {
+			dlvArgs.push('--log-output=' + launchArgs.logOutput);
+		}
+
+		log(`Running: ${dlvPath} ${dlvArgs.join(' ')}`);
+
+		this.debugProcess = spawn(dlvPath, dlvArgs, {
+			cwd: path.dirname(launchArgs.program),
+			env
+		});
+
+		this.debugProcess.stderr.on('data', (chunk) => {
+			const str = chunk.toString();
+			this.emit('stderr', str);
+		});
+
+		this.debugProcess.stdout.on('data', (chunk) => {
+			const str = chunk.toString();
+			this.emit('stdout', str);
+		});
+
+		this.debugProcess.on('close', (rc) => {
+			if (rc) {
+				logError(`Process exiting with code: ${rc} signal: ${this.debugProcess.killed}`);
+			} else {
+				log(`Process exiting normally ${this.debugProcess.killed}`);
+			}
+			this.emit('close', rc);
+		});
+
+		this.debugProcess.on('error', (err) => {
+			throw err;
+		});
+
+		// Give the Delve DAP server some time to start up before connecting.
+		// TODO: do this in a more robust way.
+		setTimeout(() => {
+			const socket = net.createConnection(
+				launchArgs.port,
+				launchArgs.host,
+				() => {
+					this.connect(socket, socket);
+					this.emit('connected');
+				});
+
+			socket.on('error', (err) => {
+				throw err;
+			});
+		}, 100);
+	}
+}
diff --git a/src/debugAdapter2/goDlvDebugMain.ts b/src/debugAdapter2/goDlvDebugMain.ts
new file mode 100644
index 0000000..2d4cdb8
--- /dev/null
+++ b/src/debugAdapter2/goDlvDebugMain.ts
@@ -0,0 +1,10 @@
+/*---------------------------------------------------------
+ * Copyright 2020 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+// This file is for running the godlvdap debug adapter as a standalone program
+// in a separate process (e.g. when working in --server mode).
+import { GoDlvDapDebugSession } from './goDlvDebug';
+
+GoDlvDapDebugSession.run(GoDlvDapDebugSession);
diff --git a/src/goMain.ts b/src/goMain.ts
index e10fa8d..39ec4a9 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -8,6 +8,7 @@
 
 import * as path from 'path';
 import vscode = require('vscode');
+import { GoDlvDapDebugSession } from './debugAdapter2/goDlvDebug';
 import { browsePackages } from './goBrowsePackage';
 import { buildCode } from './goBuild';
 import { check, notifyIfGeneratedFile, removeTestStatus } from './goCheck';
@@ -173,6 +174,16 @@
 	ctx.subscriptions.push(vscode.languages.registerCodeLensProvider(GO_MODE, testCodeLensProvider));
 	ctx.subscriptions.push(vscode.languages.registerCodeLensProvider(GO_MODE, referencesCodeLensProvider));
 	ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('go', new GoDebugConfigurationProvider()));
+	ctx.subscriptions.push(
+		vscode.debug.registerDebugConfigurationProvider('godlvdap', new GoDebugConfigurationProvider()));
+
+	// Use an InlineDebugAdapterFactory to create a new debug adapter for
+	// the 'godlvdap' command in inline mode, without launching a subprocess.
+	const factory = new InlineDebugAdapterFactory();
+	ctx.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('godlvdap', factory));
+	if ('dispose' in factory) {
+		ctx.subscriptions.push(factory);
+	}
 
 	buildDiagnosticCollection = vscode.languages.createDiagnosticCollection('go');
 	ctx.subscriptions.push(buildDiagnosticCollection);
@@ -408,7 +419,7 @@
 				e.affectsConfiguration('go.gopath') ||
 				e.affectsConfiguration('go.toolsEnvVars') ||
 				e.affectsConfiguration('go.testEnvFile')) {
-					updateGoVarsFromConfig();
+				updateGoVarsFromConfig();
 			}
 			// If there was a change in "toolsGopath" setting, then clear cache for go tools
 			if (getToolsGopath() !== getToolsGopath(false)) {
@@ -645,3 +656,10 @@
 		promptForMissingTool(tool);
 	}
 }
+
+class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory {
+	public createDebugAdapterDescriptor(session: vscode.DebugSession
+	): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
+		return new vscode.DebugAdapterInlineImplementation(new GoDlvDapDebugSession());
+	}
+}