Repository: microsoft/ptvsd

Issue:
ptvsd.adapter should support debugServer in launch.json
ptvsd.adapter package currently only communicates with the IDE over stdin/out, as is usual for DAP. However, this is inconvenient for testing and debugging, since it requires it to be registered as a debugger extension with VSCode.

To simply this, we can use the `debugServer` option that VSCode provides in launch.json, whereby it connects to an already-running debug adapter over the specified port: https://vscode.readthedocs.io/en/latest/editor/debugging/#launchjson-attributes

This requires the adapter to support an incoming socket connection. It does not need to be a true server - it only has to listen for and accept a single connection, at the very beginning, where it currently does this:

https://github.com/microsoft/ptvsd/blob/1e6d2269dd47b22b5128e9ddc6c1c7be047e9a4f/src/ptvsd/adapter/__main__.py#L13-L21

Since it still needs to work in stdin/out mode by default, this should be configurable via a command line switch that accepts the port number.


Discussion:

Resolving patch:
diff --git a/src/ptvsd/adapter/__main__.py b/src/ptvsd/adapter/__main__.py
--- a/src/ptvsd/adapter/__main__.py
+++ b/src/ptvsd/adapter/__main__.py
@@ -5,20 +5,28 @@
 from __future__ import absolute_import, print_function, unicode_literals
 
 import sys
+import argparse
 
 # WARNING: ptvsd and submodules must not be imported on top level in this module,
 # and should be imported locally inside main() instead.
 
 
+def _get_parsed_args():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--debug-port", nargs="?", default=None, const=6789)
+    return parser.parse_args(sys.argv[1:])
+
+
 def main():
     import ptvsd
     from ptvsd.common import log
     from ptvsd.adapter import channels
 
+    args = _get_parsed_args()
     log.to_file()
 
     chan = channels.Channels()
-    chan.connect_to_ide()
+    chan.connect_to_ide(address=("localhost", int(args.debug_port)))
 
     chan.ide.send_event(
         "output",
diff --git a/src/ptvsd/adapter/channels.py b/src/ptvsd/adapter/channels.py
--- a/src/ptvsd/adapter/channels.py
+++ b/src/ptvsd/adapter/channels.py
@@ -5,6 +5,7 @@
 from __future__ import absolute_import, print_function, unicode_literals
 
 from ptvsd.common import messaging, singleton
+from ptvsd.common.socket import create_server
 
 
 class Channels(singleton.ThreadSafeSingleton):
@@ -28,13 +29,19 @@ class Channels(singleton.ThreadSafeSingleton):
     """
 
     @singleton.autolocked_method
-    def connect_to_ide(self):
+    def connect_to_ide(self, address=None):
         assert self.ide is None
 
         # Import message handlers lazily to avoid circular imports.
         from ptvsd.adapter import messages
 
-        ide_channel = messaging.JsonIOStream.from_stdio("IDE")
+        if address is None:
+            ide_channel = messaging.JsonIOStream.from_stdio("IDE")
+        else:
+            host, port = address
+            sock = create_server(host, port).accept()
+            ide_channel = messaging.JsonIOStream.from_socket(sock, "IDE")
+
         self.ide = messaging.JsonMessageChannel(
             ide_channel, messages.IDEMessages(), ide_channel.name
         )
diff --git a/src/ptvsd/adapter/messages.py b/src/ptvsd/adapter/messages.py
--- a/src/ptvsd/adapter/messages.py
+++ b/src/ptvsd/adapter/messages.py
@@ -105,8 +105,8 @@ class IDEMessages(Messages):
     # and that must be replayed to the server once it is established.
     def _replay_to_server(handler):
         def store_and_handle(self, message):
-            if self.initial_messages is not None:
-                self.initial_messages.append(message)
+            if self._initial_messages is not None:
+                self._initial_messages.append(message)
             return handler(self, message)
 
         return store_and_handle


Test patch: