Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions lldb/tools/lldb-dap/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS Support)
add_lldb_library(lldbDAP
Breakpoint.cpp
BreakpointBase.cpp
ClientLauncher.cpp
CommandPlugins.cpp
DAP.cpp
DAPError.cpp
Expand Down
57 changes: 57 additions & 0 deletions lldb/tools/lldb-dap/ClientLauncher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "ClientLauncher.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/FormatVariadic.h"

using namespace lldb_dap;

std::optional<ClientLauncher::Client>
ClientLauncher::GetClientFrom(llvm::StringRef str) {
return llvm::StringSwitch<std::optional<ClientLauncher::Client>>(str.lower())
.Case("vscode", ClientLauncher::VSCode)
Copy link
Member

@vogelsgesang vogelsgesang Nov 3, 2025

Choose a reason for hiding this comment

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

would be great to also have a "url" / "terminal" / "stdout" client which simply prints the URL to stdout. That would allow me to also use it with custom scripts

Copy link
Member

Choose a reason for hiding this comment

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

although 🤔 I guess that even that would already be VSCode specific... So I guess it would have to be "vscode-url" instead of just "url"

Copy link
Member Author

Choose a reason for hiding this comment

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

Done. This also made a good candidate for a Shell test.

.Default(std::nullopt);
}

std::unique_ptr<ClientLauncher>
ClientLauncher::GetLauncher(ClientLauncher::Client client) {
switch (client) {
case ClientLauncher::VSCode:
return std::make_unique<VSCodeLauncher>();
}
return nullptr;
}

std::string VSCodeLauncher::URLEncode(llvm::StringRef str) {
std::string out;
llvm::raw_string_ostream os(out);
for (char c : str) {
if (std::isalnum(c) || llvm::StringRef("-_.~").contains(c))
os << c;
else
os << '%' << llvm::utohexstr(c, false, 2);
}
return os.str();
}

llvm::Error VSCodeLauncher::Launch(const std::vector<llvm::StringRef> args) {
std::vector<std::string> encoded_launch_args;
for (llvm::StringRef arg : args)
encoded_launch_args.push_back(URLEncode(arg));

const std::string args_str = llvm::join(args, "&args=");
const std::string launch_url = llvm::formatv(
"vscode://llvm-vs-code-extensions.lldb-dap/start?program={0}", args_str);
const std::string command =
llvm::formatv("code --open-url {0}", launch_url).str();

std::system(command.c_str());
return llvm::Error::success();
}
41 changes: 41 additions & 0 deletions lldb/tools/lldb-dap/ClientLauncher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLDB_TOOLS_LLDB_DAP_CLIENTLAUNCHER_H
#define LLDB_TOOLS_LLDB_DAP_CLIENTLAUNCHER_H

#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include <vector>

namespace lldb_dap {

class ClientLauncher {
public:
enum Client {
VSCode,
};

virtual ~ClientLauncher() = default;
virtual llvm::Error Launch(const std::vector<llvm::StringRef> args) = 0;

static std::optional<Client> GetClientFrom(llvm::StringRef str);
static std::unique_ptr<ClientLauncher> GetLauncher(Client client);
};

class VSCodeLauncher final : public ClientLauncher {
public:
using ClientLauncher::ClientLauncher;

llvm::Error Launch(const std::vector<llvm::StringRef> args) override;
static std::string URLEncode(llvm::StringRef str);
};

} // namespace lldb_dap

#endif
8 changes: 8 additions & 0 deletions lldb/tools/lldb-dap/tool/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,11 @@ def connection_timeout: S<"connection-timeout">,
"timeout is reached, the server will be closed and the process will exit. "
"Not specifying this argument or specifying non-positive values will "
"cause the server to wait for new connections indefinitely.">;

def client
: S<"client">,
MetaVarName<"<client>">,
HelpText<
"Use lldb-dap as a launcher for a curated number of DAP client.">;

def REM : R<["--"], "">;
38 changes: 38 additions & 0 deletions lldb/tools/lldb-dap/tool/lldb-dap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//

#include "ClientLauncher.h"
#include "DAP.h"
#include "DAPLog.h"
#include "EventHelper.h"
Expand Down Expand Up @@ -141,6 +142,12 @@ static void PrintHelp(LLDBDAPOptTable &table, llvm::StringRef tool_name) {
debugger to attach to the process.

lldb-dap -g

You can also use lldb-dap to launch a supported client, for example the
LLDB-DAP Visual Studio Code extension.

lldb-dap --client vscode -- /path/to/binary <args>

)___";
}

Expand All @@ -150,6 +157,29 @@ static void PrintVersion() {
llvm::outs() << "liblldb: " << lldb::SBDebugger::GetVersionString() << '\n';
}

static llvm::Error LaunchClient(const llvm::opt::InputArgList &args) {
auto *client_arg = args.getLastArg(OPT_client);
assert(client_arg && "must have client arg");

std::optional<ClientLauncher::Client> client =
ClientLauncher::GetClientFrom(client_arg->getValue());
if (!client)
return llvm::createStringError(
llvm::formatv("unsupported client: {0}", client_arg->getValue()));

std::vector<llvm::StringRef> launch_args;
if (auto *arg = args.getLastArgNoClaim(OPT_REM)) {
for (auto *value : arg->getValues()) {
launch_args.push_back(value);
}
}

if (launch_args.empty())
return llvm::createStringError("no launch arguments provided");

return ClientLauncher::GetLauncher(*client)->Launch(launch_args);
}

#if not defined(_WIN32)
struct FDGroup {
int GetFlags() const {
Expand Down Expand Up @@ -541,6 +571,14 @@ int main(int argc, char *argv[]) {
return EXIT_SUCCESS;
}

if (input_args.hasArg(OPT_client)) {
if (llvm::Error error = LaunchClient(input_args)) {
llvm::WithColor::error() << llvm::toString(std::move(error)) << '\n';
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

ReplMode default_repl_mode = ReplMode::Auto;
if (input_args.hasArg(OPT_repl_mode)) {
llvm::opt::Arg *repl_mode = input_args.getLastArg(OPT_repl_mode);
Expand Down
1 change: 1 addition & 0 deletions lldb/unittests/DAP/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
add_lldb_unittest(DAPTests
ClientLauncherTest.cpp
DAPErrorTest.cpp
DAPTest.cpp
DAPTypesTest.cpp
Expand Down
71 changes: 71 additions & 0 deletions lldb/unittests/DAP/ClientLauncherTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "ClientLauncher.h"
#include "llvm/ADT/StringRef.h"
#include "gtest/gtest.h"
#include <optional>

using namespace lldb_dap;
using namespace llvm;

TEST(ClientLauncherTest, GetClientFromVSCode) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we also include a URLEncode test? Either directly or by checking with calls to VSCodeLauncher::Launch?

std::optional<ClientLauncher::Client> result =
ClientLauncher::GetClientFrom("vscode");
ASSERT_TRUE(result.has_value());
EXPECT_EQ(ClientLauncher::VSCode, result.value());
}

TEST(ClientLauncherTest, GetClientFromVSCodeUpperCase) {
std::optional<ClientLauncher::Client> result =
ClientLauncher::GetClientFrom("VSCODE");
ASSERT_TRUE(result.has_value());
EXPECT_EQ(ClientLauncher::VSCode, result.value());
}

TEST(ClientLauncherTest, GetClientFromVSCodeMixedCase) {
std::optional<ClientLauncher::Client> result =
ClientLauncher::GetClientFrom("VSCode");
ASSERT_TRUE(result.has_value());
EXPECT_EQ(ClientLauncher::VSCode, result.value());
}

TEST(ClientLauncherTest, GetClientFromInvalidString) {
std::optional<ClientLauncher::Client> result =
ClientLauncher::GetClientFrom("invalid");
EXPECT_FALSE(result.has_value());
}

TEST(ClientLauncherTest, GetClientFromEmptyString) {
std::optional<ClientLauncher::Client> result =
ClientLauncher::GetClientFrom("");
EXPECT_FALSE(result.has_value());
}

TEST(ClientLauncherTest, URLEncode) {
EXPECT_EQ("", VSCodeLauncher::URLEncode(""));
EXPECT_EQ(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~",
VSCodeLauncher::URLEncode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST"
"UVWXYZ0123456789-_.~"));
EXPECT_EQ("hello%20world", VSCodeLauncher::URLEncode("hello world"));
EXPECT_EQ("hello%21%40%23%24", VSCodeLauncher::URLEncode("hello!@#$"));
EXPECT_EQ("%2Fpath%2Fto%2Ffile", VSCodeLauncher::URLEncode("/path/to/file"));
EXPECT_EQ("key%3Dvalue%26key2%3Dvalue2",
VSCodeLauncher::URLEncode("key=value&key2=value2"));
EXPECT_EQ("100%25complete", VSCodeLauncher::URLEncode("100%complete"));
EXPECT_EQ("file_name%20with%20spaces%20%26%20special%21.txt",
VSCodeLauncher::URLEncode("file_name with spaces & special!.txt"));
EXPECT_EQ("%00%01%02",
VSCodeLauncher::URLEncode(llvm::StringRef("\x00\x01\x02", 3)));
EXPECT_EQ("test-file_name.txt~",
VSCodeLauncher::URLEncode("test-file_name.txt~"));

// UTF-8 encoded characters should be percent-encoded byte by byte.
EXPECT_EQ("%C3%A9", VSCodeLauncher::URLEncode("é"));
}
Loading