Skip to content

[lldb-dap] Add external terminal support #146950

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

DrSergei
Copy link
Contributor

@DrSergei DrSergei commented Jul 3, 2025

Popular debugger extensions, such as cpptools (externalConsole option) and codelldb (terminal option), support launching program in external console. This patch change behavior of runInTerminal option similar to codellb, but save old behavior if boolean value was set.

@llvmbot
Copy link
Member

llvmbot commented Jul 3, 2025

@llvm/pr-subscribers-lldb

Author: None (DrSergei)

Changes

Popular debugger extensions, such as cpptools (externalConsole option) and codelldb (terminal option), support launching program in external console. This patch change behavior of runInTerminal option similar to codellb, but save old behavior if boolean value was set.


Full diff: https://github.com/llvm/llvm-project/pull/146950.diff

10 Files Affected:

  • (modified) lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py (+1-1)
  • (modified) lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py (+17-1)
  • (modified) lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp (+1-1)
  • (modified) lldb/tools/lldb-dap/Handler/RequestHandler.cpp (+3-2)
  • (modified) lldb/tools/lldb-dap/JSONUtils.cpp (+8-4)
  • (modified) lldb/tools/lldb-dap/JSONUtils.h (+1-1)
  • (modified) lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp (+32-2)
  • (modified) lldb/tools/lldb-dap/Protocol/ProtocolRequests.h (+3-1)
  • (modified) lldb/tools/lldb-dap/README.md (+1)
  • (modified) lldb/tools/lldb-dap/package.json (+21-3)
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
index 0fe36cd4bc71f..d8c1ffa6c00f0 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
@@ -890,7 +890,7 @@ def request_launch(
         args: Optional[list[str]] = None,
         cwd: Optional[str] = None,
         env: Optional[dict[str, str]] = None,
-        stopOnEntry=False,
+        stopOnEntry: Union[bool, str] = False,
         disableASLR=False,
         disableSTDIO=False,
         shellExpandArguments=False,
diff --git a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
index ae8142ae4f484..e8cb64dc9cf4e 100644
--- a/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
+++ b/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
@@ -46,7 +46,7 @@ def test_failing_launch_program(self):
 
     def test_failing_launch_commands_and_run_in_terminal(self):
         """
-        Tests launching with an invalid program.
+        Tests launching with a launch commands in integrated terminal.
         """
         program = self.getBuildArtifact("a.out")
         self.create_debug_adapter()
@@ -60,6 +60,22 @@ def test_failing_launch_commands_and_run_in_terminal(self):
             self.get_dict_value(response, ["body", "error", "format"]),
         )
 
+    def test_failing_run_in_terminal(self):
+        """
+        Tests launching in terminal.
+        """
+        program = self.getBuildArtifact("a.out")
+        self.create_debug_adapter()
+        response = self.launch(
+            program, runInTerminal="invalid", expectFailure=True
+        )
+        self.assertFalse(response["success"])
+        self.assertTrue(self.get_dict_value(response, ["body", "error", "showUser"]))
+        self.assertRegex(
+            response["body"]["error"]["format"],
+            r"unexpected value, expected 'console', 'integrated' or 'external' at arguments.runInTerminal",
+        )
+
     @skipIfWindows
     def test_termination(self):
         """
diff --git a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp
index 1d7b4b7009462..907d93021cd76 100644
--- a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp
@@ -23,7 +23,7 @@ namespace lldb_dap {
 /// Launch request; value of command field is 'launch'.
 Error LaunchRequestHandler::Run(const LaunchRequestArguments &arguments) const {
   // Validate that we have a well formed launch request.
-  if (!arguments.launchCommands.empty() && arguments.runInTerminal)
+  if (!arguments.launchCommands.empty() && arguments.terminal)
     return make_error<DAPError>(
         "'launchCommands' and 'runInTerminal' are mutually exclusive");
 
diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
index 93bc80a38e29d..d2227cfffa18a 100644
--- a/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/RequestHandler.cpp
@@ -80,7 +80,8 @@ RunInTerminal(DAP &dap, const protocol::LaunchRequestArguments &arguments) {
 
   llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest(
       arguments.configuration.program, arguments.args, arguments.env,
-      arguments.cwd, comm_file.m_path, debugger_pid);
+      arguments.cwd, comm_file.m_path, debugger_pid,
+      arguments.terminal == protocol::eExternal);
   dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal",
                                                     std::move(reverse_request));
 
@@ -192,7 +193,7 @@ llvm::Error BaseRequestHandler::LaunchProcess(
     // about process state changes during the launch.
     ScopeSyncMode scope_sync_mode(dap.debugger);
 
-    if (arguments.runInTerminal) {
+    if (arguments.terminal) {
       if (llvm::Error err = RunInTerminal(dap, arguments))
         return err;
     } else if (launchCommands.empty()) {
diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp
index 08e65ab835a57..08e3e504689e7 100644
--- a/lldb/tools/lldb-dap/JSONUtils.cpp
+++ b/lldb/tools/lldb-dap/JSONUtils.cpp
@@ -1168,11 +1168,15 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit) {
 llvm::json::Object CreateRunInTerminalReverseRequest(
     llvm::StringRef program, const std::vector<std::string> &args,
     const llvm::StringMap<std::string> &env, llvm::StringRef cwd,
-    llvm::StringRef comm_file, lldb::pid_t debugger_pid) {
+    llvm::StringRef comm_file, lldb::pid_t debugger_pid, bool external) {
   llvm::json::Object run_in_terminal_args;
-  // This indicates the IDE to open an embedded terminal, instead of opening
-  // the terminal in a new window.
-  run_in_terminal_args.try_emplace("kind", "integrated");
+  if (external)
+    // This indicates the IDE to open an external terminal window.
+    run_in_terminal_args.try_emplace("kind", "external");
+  else
+    // This indicates the IDE to open an embedded terminal, instead of opening
+    // the terminal in a new window.
+    run_in_terminal_args.try_emplace("kind", "integrated");
 
   // The program path must be the first entry in the "args" field
   std::vector<std::string> req_args = {DAP::debug_adapter_path.str(),
diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h
index fd9a06931ebff..4a8feab741e4b 100644
--- a/lldb/tools/lldb-dap/JSONUtils.h
+++ b/lldb/tools/lldb-dap/JSONUtils.h
@@ -447,7 +447,7 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit);
 llvm::json::Object CreateRunInTerminalReverseRequest(
     llvm::StringRef program, const std::vector<std::string> &args,
     const llvm::StringMap<std::string> &env, llvm::StringRef cwd,
-    llvm::StringRef comm_file, lldb::pid_t debugger_pid);
+    llvm::StringRef comm_file, lldb::pid_t debugger_pid, bool external);
 
 /// Create a "Terminated" JSON object that contains statistics
 ///
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
index 9bd84a6c898f9..f55efd185f9f6 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
@@ -262,6 +262,37 @@ json::Value toJSON(const BreakpointLocationsResponseBody &BLRB) {
   return json::Object{{"breakpoints", BLRB.breakpoints}};
 }
 
+bool fromJSON(const json::Value &Params, Terminal &T, json::Path P) {
+  auto oldFormatTerminal = Params.getAsBoolean();
+  if (oldFormatTerminal) {
+    if (*oldFormatTerminal)
+      T = eIntegrated;
+    else
+      T = eConsole;
+    return true;
+  }
+  auto newFormatTerminal = Params.getAsString();
+  if (!newFormatTerminal) {
+    P.report("expected a string");
+    return false;
+  }
+
+  std::optional<Terminal> terminal =
+      StringSwitch<std::optional<Terminal>>(*newFormatTerminal)
+          .Case("console", eConsole)
+          .Case("integrated", eIntegrated)
+          .Case("external", eExternal)
+          .Default(std::nullopt);
+  if (!terminal) {
+    P.report(
+        "unexpected value, expected 'console', 'integrated' or 'external'");
+    return false;
+  }
+
+  T = *terminal;
+  return true;
+}
+
 bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA,
               json::Path P) {
   json::ObjectMapper O(Params, P);
@@ -273,8 +304,7 @@ bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA,
          O.mapOptional("disableASLR", LRA.disableASLR) &&
          O.mapOptional("disableSTDIO", LRA.disableSTDIO) &&
          O.mapOptional("shellExpandArguments", LRA.shellExpandArguments) &&
-
-         O.mapOptional("runInTerminal", LRA.runInTerminal) &&
+         O.mapOptional("runInTerminal", LRA.terminal) &&
          parseEnv(Params, LRA.env, P);
 }
 
diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
index d4b816c72679b..2e72f50c63afe 100644
--- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
+++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
@@ -242,6 +242,8 @@ struct Configuration {
   std::string platformName;
 };
 
+enum Terminal : unsigned { eConsole, eIntegrated, eExternal };
+
 /// lldb-dap specific launch arguments.
 struct LaunchRequestArguments {
   /// Common lldb-dap configuration values for launching/attaching operations.
@@ -292,7 +294,7 @@ struct LaunchRequestArguments {
 
   /// Launch the program inside an integrated terminal in the IDE. Useful for
   /// debugging interactive command line programs.
-  bool runInTerminal = false;
+  Terminal terminal = eConsole;
 
   /// @}
 };
diff --git a/lldb/tools/lldb-dap/README.md b/lldb/tools/lldb-dap/README.md
index 18bfa9d518b98..61d2cc06f99eb 100644
--- a/lldb/tools/lldb-dap/README.md
+++ b/lldb/tools/lldb-dap/README.md
@@ -236,6 +236,7 @@ contain the following key/value pairs:
 | **env**                           | dictionary  |     | Environment variables to set when launching the program. The format of each environment variable string is "VAR=VALUE" for environment variables with values or just "VAR" for environment variables with no values.
 | **stopOnEntry**                   | boolean     |     | Whether to stop program immediately after launching.
 | **runInTerminal**                 | boolean     |     | Launch the program inside an integrated terminal in the IDE. Useful for debugging interactive command line programs.
+| **runInTerminal**                 | string      |     | Specifies where program should be launch: `integrated` for an integrated terminal in the IDE, `external` for external terminal window or `console` (default) for IDE debug console.
 | **launchCommands**                | [string]    |     | LLDB commands executed to launch the program.
 
 For JSON configurations of `"type": "attach"`, the JSON configuration can contain
diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json
index b150dee792c34..cf5712261d051 100644
--- a/lldb/tools/lldb-dap/package.json
+++ b/lldb/tools/lldb-dap/package.json
@@ -526,9 +526,27 @@
                 "default": []
               },
               "runInTerminal": {
-                "type": "boolean",
-                "description": "Launch the program inside an integrated terminal in the IDE. Useful for debugging interactive command line programs",
-                "default": false
+                "anyOf": [
+                  {
+                    "type": "boolean",
+                    "description": "Launch the program inside an integrated terminal in the IDE. Useful for debugging interactive command line programs",
+                    "default": false
+                  },
+                  {
+                    "type": "string",
+                    "enum": [
+                      "console",
+                      "integrated",
+                      "external"
+                    ],
+                    "enumDescriptions": [
+                      "Launch the program inside an integrated terminal in the IDE.",
+                      "Launch the program inside an external terminal window.",
+                      "Use Debug Console for output (input is not supported)."
+                    ],
+                    "default": "console"
+                  }
+                ]
               },
               "timeout": {
                 "type": "number",

Copy link

github-actions bot commented Jul 3, 2025

✅ With the latest revision this PR passed the Python code formatter.

@DrSergei DrSergei force-pushed the add-external-terminal branch from 5e5f3b4 to 112050c Compare July 3, 2025 19:39
@JDevlieghere JDevlieghere requested review from ashgti and da-viper July 3, 2025 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants