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
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -894,7 +894,8 @@ def request_launch(
disableASLR=False,
disableSTDIO=False,
shellExpandArguments=False,
runInTerminal=False,
runInTerminal=False, # deprecated
console: Optional[str] = None,
enableAutoVariableSummaries=False,
displayExtendedBacktrace=False,
enableSyntheticChildDebugging=False,
Expand Down Expand Up @@ -946,6 +947,8 @@ def request_launch(
args_dict["sourceMap"] = sourceMap
if runInTerminal:
args_dict["runInTerminal"] = runInTerminal
if console:
args_dict["console"] = console
if postRunCommands:
args_dict["postRunCommands"] = postRunCommands
if customFrameFormat:
Expand Down
25 changes: 21 additions & 4 deletions lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,39 @@ def test_failing_launch_program(self):
"'{0}' does not exist".format(program), response["body"]["error"]["format"]
)

def test_failing_launch_commands_and_run_in_terminal(self):
def test_failing_launch_commands_and_console(self):
"""
Tests launching with an invalid program.
Tests launching with launch commands in an integrated terminal.
"""
program = self.getBuildArtifact("a.out")
self.create_debug_adapter()
response = self.launch(
program, launchCommands=["a b c"], runInTerminal=True, expectFailure=True
program,
launchCommands=["a b c"],
console="integratedTerminal",
expectFailure=True,
)
self.assertFalse(response["success"])
self.assertTrue(self.get_dict_value(response, ["body", "error", "showUser"]))
self.assertEqual(
"'launchCommands' and 'runInTerminal' are mutually exclusive",
"'launchCommands' and 'console' are mutually exclusive",
self.get_dict_value(response, ["body", "error", "format"]),
)

def test_failing_console(self):
"""
Tests launching in console with an invalid terminal type.
"""
program = self.getBuildArtifact("a.out")
self.create_debug_adapter()
response = self.launch(program, console="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 'internalConsole\', 'integratedTerminal\' or 'externalTerminal\' at arguments.console",
)

@skipIfWindows
def test_termination(self):
"""
Expand Down
5 changes: 3 additions & 2 deletions lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ 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.console != protocol::eInternalConsole)
return make_error<DAPError>(
"'launchCommands' and 'runInTerminal' are mutually exclusive");
"'launchCommands' and 'console' are mutually exclusive");

dap.SetConfiguration(arguments.configuration, /*is_attach=*/false);
dap.last_launch_request = arguments;
Expand Down
5 changes: 3 additions & 2 deletions lldb/tools/lldb-dap/Handler/RequestHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.console == protocol::eExternalTerminal);
dap.SendReverseRequest<LogFailureResponseHandler>("runInTerminal",
std::move(reverse_request));

Expand Down Expand Up @@ -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.console != protocol::eInternalConsole) {
if (llvm::Error err = RunInTerminal(dap, arguments))
return err;
} else if (launchCommands.empty()) {
Expand Down
12 changes: 8 additions & 4 deletions lldb/tools/lldb-dap/JSONUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
6 changes: 5 additions & 1 deletion lldb/tools/lldb-dap/JSONUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -441,13 +441,17 @@ llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit);
/// launcher uses it on Linux tell the kernel that it should allow the
/// debugger process to attach.
///
/// \param[in] external
/// If set to true, the program will run in an external terminal window
/// instead of IDE's integrated terminal.
///
/// \return
/// A "runInTerminal" JSON object that follows the specification outlined by
/// Microsoft.
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing doc comment at the top.


/// Create a "Terminated" JSON object that contains statistics
///
Expand Down
36 changes: 33 additions & 3 deletions lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,37 @@ json::Value toJSON(const BreakpointLocationsResponseBody &BLRB) {
return json::Object{{"breakpoints", BLRB.breakpoints}};
}

bool fromJSON(const json::Value &Params, Console &C, json::Path P) {
auto oldFormatConsole = Params.getAsBoolean();
if (oldFormatConsole) {
if (*oldFormatConsole)
C = eIntegratedTerminal;
else
C = eInternalConsole;
return true;
}
auto newFormatConsole = Params.getAsString();
if (!newFormatConsole) {
P.report("expected a string");
return false;
}

std::optional<Console> console =
StringSwitch<std::optional<Console>>(*newFormatConsole)
.Case("internalConsole", eInternalConsole)
.Case("integratedTerminal", eIntegratedTerminal)
.Case("externalTerminal", eExternalTerminal)
.Default(std::nullopt);
if (!console) {
P.report("unexpected value, expected 'internalConsole', "
"'integratedTerminal' or 'externalTerminal'");
return false;
}

C = *console;
return true;
}

bool fromJSON(const json::Value &Params, LaunchRequestArguments &LRA,
json::Path P) {
json::ObjectMapper O(Params, P);
Expand All @@ -273,9 +304,8 @@ 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) &&
parseEnv(Params, LRA.env, P);
O.mapOptional("runInTerminal", LRA.console) &&
O.mapOptional("console", LRA.console) && parseEnv(Params, LRA.env, P);
}

bool fromJSON(const json::Value &Params, AttachRequestArguments &ARA,
Expand Down
12 changes: 9 additions & 3 deletions lldb/tools/lldb-dap/Protocol/ProtocolRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,12 @@ struct Configuration {
std::string platformName;
};

enum Console : unsigned {
eInternalConsole,
eIntegratedTerminal,
eExternalTerminal
};

/// lldb-dap specific launch arguments.
struct LaunchRequestArguments {
/// Common lldb-dap configuration values for launching/attaching operations.
Expand Down Expand Up @@ -290,9 +296,9 @@ struct LaunchRequestArguments {
/// Set whether to shell expand arguments to the process when launching.
bool shellExpandArguments = false;

/// Launch the program inside an integrated terminal in the IDE. Useful for
/// debugging interactive command line programs.
bool runInTerminal = false;
/// Specify where to launch the program: internal console, integrated
/// terminal or external terminal.
Console console = eInternalConsole;

/// @}
};
Expand Down
3 changes: 2 additions & 1 deletion lldb/tools/lldb-dap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,8 @@ contain the following key/value pairs:
| **cwd** | string | | The program working directory.
| **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** (deprecated) | boolean | | Launch the program inside an integrated terminal in the IDE. Useful for debugging interactive command line programs.
| **console** | string | | Specify where to launch the program: internal console (`internalConsole`), integrated terminal (`integratedTerminal`) or external terminal (`externalTerminal`).
| **launchCommands** | [string] | | LLDB commands executed to launch the program.

For JSON configurations of `"type": "attach"`, the JSON configuration can contain
Expand Down
18 changes: 17 additions & 1 deletion lldb/tools/lldb-dap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,23 @@
"runInTerminal": {
"type": "boolean",
"description": "Launch the program inside an integrated terminal in the IDE. Useful for debugging interactive command line programs",
"default": false
"default": false,
"deprecationMessage": "Attribute 'runInTerminal' is deprecated, use 'console' instead."
},
"console": {
"type": "string",
"enum": [
"internalConsole",
"integratedTerminal",
"externalTerminal"
],
"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)."
],
"description": "Specify where to launch the program: internal console, integrated terminal or external terminal.",
"default": "internalConsole"
},
"timeout": {
"type": "number",
Expand Down
Loading