[Lldb-commits] [lldb] 16317f1 - [lldb-vscode] Creating a new flag for adjusting the behavior of evaluation repl expressions to allow users to more easily invoke lldb commands.
David Goldman via lldb-commits
lldb-commits at lists.llvm.org
Tue Jul 11 13:38:00 PDT 2023
Author: John Harrison
Date: 2023-07-11T16:37:25-04:00
New Revision: 16317f1ced77e1d8711188f2fcc6c86a783d9c56
URL: https://github.com/llvm/llvm-project/commit/16317f1ced77e1d8711188f2fcc6c86a783d9c56
DIFF: https://github.com/llvm/llvm-project/commit/16317f1ced77e1d8711188f2fcc6c86a783d9c56.diff
LOG: [lldb-vscode] Creating a new flag for adjusting the behavior of evaluation repl expressions to allow users to more easily invoke lldb commands.
This adds a new flag and lldb runtime command to allow users to manage the behavior of the lldb-vscode evaluate repl request.
When evaluating a repl context this now has runtime managed flag for control how the repl behaviors with the follow values:
* `variable` - the existing behavior, with this mode requests are evaluted in the current frame context as variable expressions. To trigger a lldb command prefix an expression with ` and it will be evaluted as an lldb command.
* `command` - all expressions are evaluated as lldb commands.
* `auto` - An alternative mode that will attempt to determine if the expression is an lldb command or a variable expression. Based off the intepreted results the expression will be evaluted either as a command or an expression.
Additionally, I enabled completions and ensured they work with the new repl expression behavior to provide auto-completes.
RFC Discussion: https://discourse.llvm.org/t/rfc-lldb-vscode-evaluate-repl-behavior-and-improvements/71667
Reviewed By: wallace
Differential Revision: https://reviews.llvm.org/D154030
Added:
Modified:
lldb/tools/lldb-vscode/Options.td
lldb/tools/lldb-vscode/README.md
lldb/tools/lldb-vscode/VSCode.cpp
lldb/tools/lldb-vscode/VSCode.h
lldb/tools/lldb-vscode/lldb-vscode.cpp
Removed:
################################################################################
diff --git a/lldb/tools/lldb-vscode/Options.td b/lldb/tools/lldb-vscode/Options.td
index a73671ad749c47..8c51a994ebc270 100644
--- a/lldb/tools/lldb-vscode/Options.td
+++ b/lldb/tools/lldb-vscode/Options.td
@@ -39,3 +39,7 @@ def debugger_pid: Separate<["--", "-"], "debugger-pid">,
MetaVarName<"<pid>">,
HelpText<"The PID of the lldb-vscode instance that sent the launchInTerminal "
"request when using --launch-target.">;
+
+def repl_mode: S<"repl-mode">,
+ MetaVarName<"<mode>">,
+ HelpText<"The mode for handling repl evaluation requests, supported modes: variable, command, auto.">;
diff --git a/lldb/tools/lldb-vscode/README.md b/lldb/tools/lldb-vscode/README.md
index cd249ed2fac7c6..154ccefc5f5979 100644
--- a/lldb/tools/lldb-vscode/README.md
+++ b/lldb/tools/lldb-vscode/README.md
@@ -236,3 +236,22 @@ This will launch a server and then request a child debug session for a client.
]
}
```
+
+## repl-mode
+
+Inspect or adjust the behavior of lldb-vscode repl evaluation requests. The
+supported modes are `variable`, `command` and `auto`.
+
+* `variable` - Variable mode expressions are evaluated in the context of the
+ current frame. Use a `\`` prefix on the command to run an lldb command.
+* `command` - Command mode expressions are evaluated as lldb commands, as a
+ result, values printed by lldb are always stringified representations of the
+ expression output.
+* `auto` - Auto mode will attempt to infer if the expression represents an lldb
+ command or a variable expression. A heuristic is used to infer if the input
+ represents a variable or a command. Use a `\`` prefix to ensure an expression
+ is evaluated as a command.
+
+The initial repl-mode can be configured with the cli flag `--repl-mode=<mode>`
+and may also be adjusted at runtime using the lldb command
+`lldb-vscode repl-mode <mode>`.
diff --git a/lldb/tools/lldb-vscode/VSCode.cpp b/lldb/tools/lldb-vscode/VSCode.cpp
index b1c6817c2128b9..f007e09eee1e1d 100644
--- a/lldb/tools/lldb-vscode/VSCode.cpp
+++ b/lldb/tools/lldb-vscode/VSCode.cpp
@@ -44,7 +44,8 @@ VSCode::VSCode()
configuration_done_sent(false), waiting_for_run_in_terminal(false),
progress_event_reporter(
[&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }),
- reverse_request_seq(0) {
+ reverse_request_seq(0), repl_mode(ReplMode::Auto),
+ auto_repl_mode_collision_warning(false) {
const char *log_file_path = getenv("LLDBVSCODE_LOG");
#if defined(_WIN32)
// Windows opens stdout and stdin in text mode which converts \n to 13,10
@@ -392,6 +393,50 @@ llvm::json::Value VSCode::CreateTopLevelScopes() {
return llvm::json::Value(std::move(scopes));
}
+ExpressionContext VSCode::DetectExpressionContext(lldb::SBFrame &frame,
+ std::string &text) {
+ // Include ` as an escape hatch.
+ if (!text.empty() && text[0] == '`') {
+ text = text.substr(1);
+ return ExpressionContext::Command;
+ }
+
+ switch (repl_mode) {
+ case ReplMode::Variable:
+ return ExpressionContext::Variable;
+ case ReplMode::Command:
+ return ExpressionContext::Command;
+ case ReplMode::Auto:
+ if (!frame.IsValid()) {
+ return ExpressionContext::Command;
+ }
+
+ lldb::SBValue value =
+ frame.GetValueForVariablePath(text.data(), lldb::eDynamicDontRunTarget);
+
+ lldb::SBCommandReturnObject result;
+ debugger.GetCommandInterpreter().ResolveCommand(text.data(), result);
+
+ if (value.GetError().Success()) {
+ // Check if the expression is both a local variable and an lldb command.
+ if (result.Succeeded() && !auto_repl_mode_collision_warning) {
+ llvm::errs() << "Variable expression '" << text
+ << "' is hiding an lldb command, prefix an expression "
+ "with ` to ensure it runs as a lldb command.\n";
+ auto_repl_mode_collision_warning = true;
+ }
+
+ return ExpressionContext::Variable;
+ }
+
+ if (result.Succeeded()) {
+ return ExpressionContext::Command;
+ }
+
+ return ExpressionContext::Variable;
+ }
+}
+
void VSCode::RunLLDBCommands(llvm::StringRef prefix,
const std::vector<std::string> &commands) {
SendOutput(OutputType::Console,
@@ -501,7 +546,8 @@ bool VSCode::HandleObject(const llvm::json::Object &object) {
return true; // Success
} else {
if (log)
- *log << "error: unhandled command \"" << command.data() << std::endl;
+ *log << "error: unhandled command \"" << command.data() << "\""
+ << std::endl;
return false; // Fail
}
}
@@ -636,7 +682,7 @@ void Variables::Clear() {
expandable_variables.clear();
}
-int64_t Variables::GetNewVariableRefence(bool is_permanent) {
+int64_t Variables::GetNewVariableReference(bool is_permanent) {
if (is_permanent)
return next_permanent_var_ref++;
return next_temporary_var_ref++;
@@ -661,7 +707,7 @@ lldb::SBValue Variables::GetVariable(int64_t var_ref) const {
int64_t Variables::InsertExpandableVariable(lldb::SBValue variable,
bool is_permanent) {
- int64_t var_ref = GetNewVariableRefence(is_permanent);
+ int64_t var_ref = GetNewVariableReference(is_permanent);
if (is_permanent)
expandable_permanent_variables.insert(std::make_pair(var_ref, variable));
else
@@ -728,4 +774,51 @@ bool StartDebuggingRequestHandler::DoExecute(
return true;
}
+bool ReplModeRequestHandler::DoExecute(lldb::SBDebugger debugger,
+ char **command,
+ lldb::SBCommandReturnObject &result) {
+ // Command format like: `repl-mode <variable|command|auto>?`
+ // If a new mode is not specified report the current mode.
+ if (!command || llvm::StringRef(command[0]).empty()) {
+ std::string mode;
+ switch (g_vsc.repl_mode) {
+ case ReplMode::Variable:
+ mode = "variable";
+ break;
+ case ReplMode::Command:
+ mode = "command";
+ break;
+ case ReplMode::Auto:
+ mode = "auto";
+ break;
+ }
+
+ result.Printf("lldb-vscode repl-mode %s.\n", mode.c_str());
+ result.SetStatus(lldb::eReturnStatusSuccessFinishResult);
+
+ return true;
+ }
+
+ llvm::StringRef new_mode{command[0]};
+
+ if (new_mode == "variable") {
+ g_vsc.repl_mode = ReplMode::Variable;
+ } else if (new_mode == "command") {
+ g_vsc.repl_mode = ReplMode::Command;
+ } else if (new_mode == "auto") {
+ g_vsc.repl_mode = ReplMode::Auto;
+ } else {
+ lldb::SBStream error_message;
+ error_message.Printf("Invalid repl-mode '%s'. Expected one of 'variable', "
+ "'command' or 'auto'.\n",
+ new_mode.data());
+ result.SetError(error_message.GetData());
+ return false;
+ }
+
+ result.Printf("lldb-vscode repl-mode %s set.\n", new_mode.data());
+ result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult);
+ return true;
+}
+
} // namespace lldb_vscode
diff --git a/lldb/tools/lldb-vscode/VSCode.h b/lldb/tools/lldb-vscode/VSCode.h
index 2d67da2bbc092e..4a2779fa4972a8 100644
--- a/lldb/tools/lldb-vscode/VSCode.h
+++ b/lldb/tools/lldb-vscode/VSCode.h
@@ -84,6 +84,14 @@ enum class PacketStatus {
JSONNotObject
};
+enum class ReplMode { Variable = 0, Command, Auto };
+
+/// The detected context of an expression based off the current repl mode.
+enum class ExpressionContext {
+ Variable = 0,
+ Command,
+};
+
struct Variables {
/// Variable_reference start index of permanent expandable variable.
static constexpr int64_t PermanentVariableStartIndex = (1ll << 32);
@@ -109,7 +117,7 @@ struct Variables {
/// \return a new variableReference.
/// Specify is_permanent as true for variable that should persist entire
/// debug session.
- int64_t GetNewVariableRefence(bool is_permanent);
+ int64_t GetNewVariableReference(bool is_permanent);
/// \return the expandable variable corresponding with variableReference
/// value of \p value.
@@ -129,6 +137,11 @@ struct StartDebuggingRequestHandler : public lldb::SBCommandPluginInterface {
lldb::SBCommandReturnObject &result) override;
};
+struct ReplModeRequestHandler : public lldb::SBCommandPluginInterface {
+ bool DoExecute(lldb::SBDebugger debugger, char **command,
+ lldb::SBCommandReturnObject &result) override;
+};
+
struct VSCode {
std::string debug_adaptor_path;
InputStream input;
@@ -173,6 +186,9 @@ struct VSCode {
std::map<int /* request_seq */, ResponseCallback /* reply handler */>
inflight_reverse_requests;
StartDebuggingRequestHandler start_debugging_request_handler;
+ ReplModeRequestHandler repl_mode_request_handler;
+ ReplMode repl_mode;
+ bool auto_repl_mode_collision_warning;
VSCode();
~VSCode();
@@ -206,6 +222,9 @@ struct VSCode {
llvm::json::Value CreateTopLevelScopes();
+ ExpressionContext DetectExpressionContext(lldb::SBFrame &frame,
+ std::string &text);
+
void RunLLDBCommands(llvm::StringRef prefix,
const std::vector<std::string> &commands);
diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp
index 5815b0d22506b3..8f82ab8e2e3e16 100644
--- a/lldb/tools/lldb-vscode/lldb-vscode.cpp
+++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp
@@ -1065,50 +1065,63 @@ void request_completions(const llvm::json::Object &request) {
FillResponse(request, response);
llvm::json::Object body;
auto arguments = request.getObject("arguments");
+
+ // If we have a frame, try to set the context for variable completions.
+ lldb::SBFrame frame = g_vsc.GetLLDBFrame(*arguments);
+ if (frame.IsValid()) {
+ frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
+ frame.GetThread().SetSelectedFrame(frame.GetFrameID());
+ }
+
std::string text = std::string(GetString(arguments, "text"));
auto original_column = GetSigned(arguments, "column", text.size());
- auto actual_column = original_column - 1;
+ auto original_line = GetSigned(arguments, "line", 1);
+ auto offset = original_column - 1;
+ if (original_line > 1) {
+ llvm::StringRef text_ref{text};
+ ::llvm::SmallVector<::llvm::StringRef, 2> lines;
+ text_ref.split(lines, '\n');
+ for (int i = 0; i < original_line - 1; i++) {
+ offset += lines[i].size();
+ }
+ }
llvm::json::Array targets;
- // NOTE: the 'line' argument is not needed, as multiline expressions
- // work well already
- // TODO: support frameID. Currently
- // g_vsc.debugger.GetCommandInterpreter().HandleCompletionWithDescriptions
- // is frame-unaware.
-
- if (!text.empty() && text[0] == '`') {
- text = text.substr(1);
- actual_column--;
- } else {
- char command[] = "expression -- ";
+
+ if (g_vsc.DetectExpressionContext(frame, text) ==
+ ExpressionContext::Variable) {
+ char command[] = "frame variable ";
text = command + text;
- actual_column += strlen(command);
+ offset += strlen(command);
}
lldb::SBStringList matches;
lldb::SBStringList descriptions;
- g_vsc.debugger.GetCommandInterpreter().HandleCompletionWithDescriptions(
- text.c_str(), actual_column, 0, -1, matches, descriptions);
- size_t count = std::min((uint32_t)100, matches.GetSize());
- targets.reserve(count);
- for (size_t i = 0; i < count; i++) {
- std::string match = matches.GetStringAtIndex(i);
- std::string description = descriptions.GetStringAtIndex(i);
-
- llvm::json::Object item;
-
- llvm::StringRef match_ref = match;
- for (llvm::StringRef commit_point : {".", "->"}) {
- if (match_ref.contains(commit_point)) {
- match_ref = match_ref.rsplit(commit_point).second;
+
+ if (g_vsc.debugger.GetCommandInterpreter().HandleCompletionWithDescriptions(
+ text.c_str(), offset, 0, 100, matches, descriptions)) {
+ // The first element is the common substring after the cursor position for
+ // all the matches. The rest of the elements are the matches.
+ targets.reserve(matches.GetSize() - 1);
+ std::string common_pattern = matches.GetStringAtIndex(0);
+ for (size_t i = 1; i < matches.GetSize(); i++) {
+ std::string match = matches.GetStringAtIndex(i);
+ std::string description = descriptions.GetStringAtIndex(i);
+
+ llvm::json::Object item;
+ llvm::StringRef match_ref = match;
+ for (llvm::StringRef commit_point : {".", "->"}) {
+ if (match_ref.contains(commit_point)) {
+ match_ref = match_ref.rsplit(commit_point).second;
+ }
}
- }
- EmplaceSafeString(item, "text", match_ref);
+ EmplaceSafeString(item, "text", match_ref);
- if (description.empty())
- EmplaceSafeString(item, "label", match);
- else
- EmplaceSafeString(item, "label", match + " -- " + description);
+ if (description.empty())
+ EmplaceSafeString(item, "label", match);
+ else
+ EmplaceSafeString(item, "label", match + " -- " + description);
- targets.emplace_back(std::move(item));
+ targets.emplace_back(std::move(item));
+ }
}
body.try_emplace("targets", std::move(targets));
@@ -1223,12 +1236,17 @@ void request_evaluate(const llvm::json::Object &request) {
llvm::json::Object body;
auto arguments = request.getObject("arguments");
lldb::SBFrame frame = g_vsc.GetLLDBFrame(*arguments);
- const auto expression = GetString(arguments, "expression");
+ std::string expression = GetString(arguments, "expression").str();
llvm::StringRef context = GetString(arguments, "context");
- if (!expression.empty() && expression[0] == '`') {
- auto result =
- RunLLDBCommands(llvm::StringRef(), {std::string(expression.substr(1))});
+ if (context == "repl" && g_vsc.DetectExpressionContext(frame, expression) ==
+ ExpressionContext::Command) {
+ // If we're evaluating a command relative to the current frame, set the
+ // focus_tid to the current frame for any thread related events.
+ if (frame.IsValid()) {
+ g_vsc.focus_tid = frame.GetThread().GetThreadID();
+ }
+ auto result = RunLLDBCommands(llvm::StringRef(), {std::string(expression)});
EmplaceSafeString(body, "result", result);
body.try_emplace("variablesReference", (int64_t)0);
} else {
@@ -1472,11 +1490,17 @@ void request_initialize(const llvm::json::Object &request) {
g_vsc.debugger =
lldb::SBDebugger::Create(source_init_file, log_cb, nullptr);
auto cmd = g_vsc.debugger.GetCommandInterpreter().AddMultiwordCommand(
- "lldb-vscode", nullptr);
+ "lldb-vscode", "Commands for managing lldb-vscode.");
+ if (GetBoolean(arguments, "supportsStartDebuggingRequest", false)) {
+ cmd.AddCommand(
+ "startDebugging", &g_vsc.start_debugging_request_handler,
+ "Sends a startDebugging request from the debug adapter to the client "
+ "to "
+ "start a child debug session of the same type as the caller.");
+ }
cmd.AddCommand(
- "startDebugging", &g_vsc.start_debugging_request_handler,
- "Sends a startDebugging request from the debug adapter to the client to "
- "start a child debug session of the same type as the caller.");
+ "repl-mode", &g_vsc.repl_mode_request_handler,
+ "Get or set the repl behavior of vscode-lldb evaluation requests.");
g_vsc.progress_event_thread = std::thread(ProgressEventThreadFunction);
@@ -1519,22 +1543,16 @@ void request_initialize(const llvm::json::Object &request) {
body.try_emplace("supportsGotoTargetsRequest", false);
// The debug adapter supports the stepInTargetsRequest.
body.try_emplace("supportsStepInTargetsRequest", false);
- // We need to improve the current implementation of completions in order to
- // enable it again. For some context, this is how VSCode works:
- // - VSCode sends a completion request whenever chars are added, the user
- // triggers completion manually via CTRL-space or similar mechanisms, but
- // not when there's a deletion. Besides, VSCode doesn't let us know which
- // of these events we are handling. What is more, the use can paste or cut
- // sections of the text arbitrarily.
- // https://github.com/microsoft/vscode/issues/89531 tracks part of the
- // issue just mentioned.
- // This behavior causes many problems with the current way completion is
- // implemented in lldb-vscode, as these requests could be really expensive,
- // blocking the debugger, and there could be many concurrent requests unless
- // the user types very slowly... We need to address this specific issue, or
- // at least trigger completion only when the user explicitly wants it, which
- // is the behavior of LLDB CLI, that expects a TAB.
- body.try_emplace("supportsCompletionsRequest", false);
+ // The debug adapter supports the completions request.
+ body.try_emplace("supportsCompletionsRequest", true);
+
+ llvm::json::Array completion_characters;
+ completion_characters.emplace_back(".");
+ completion_characters.emplace_back(" ");
+ completion_characters.emplace_back("\t");
+ body.try_emplace("completionTriggerCharacters",
+ std::move(completion_characters));
+
// The debug adapter supports the modules request.
body.try_emplace("supportsModulesRequest", true);
// The set of additional module information exposed by the debug adapter.
@@ -2093,6 +2111,7 @@ void request_scopes(const llvm::json::Object &request) {
frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread());
frame.GetThread().SetSelectedFrame(frame.GetFrameID());
}
+
g_vsc.variables.locals = frame.GetVariables(/*arguments=*/true,
/*locals=*/true,
/*statics=*/false,
@@ -3406,6 +3425,23 @@ int main(int argc, char *argv[]) {
return EXIT_SUCCESS;
}
+ if (input_args.hasArg(OPT_repl_mode)) {
+ llvm::opt::Arg *repl_mode = input_args.getLastArg(OPT_repl_mode);
+ llvm::StringRef repl_mode_value = repl_mode->getValue();
+ if (repl_mode_value == "auto") {
+ g_vsc.repl_mode = ReplMode::Auto;
+ } else if (repl_mode_value == "variable") {
+ g_vsc.repl_mode = ReplMode::Variable;
+ } else if (repl_mode_value == "command") {
+ g_vsc.repl_mode = ReplMode::Command;
+ } else {
+ llvm::errs()
+ << "'" << repl_mode_value
+ << "' is not a valid option, use 'variable', 'command' or 'auto'.\n";
+ return EXIT_FAILURE;
+ }
+ }
+
if (llvm::opt::Arg *target_arg = input_args.getLastArg(OPT_launch_target)) {
if (llvm::opt::Arg *comm_file = input_args.getLastArg(OPT_comm_file)) {
lldb::pid_t pid = LLDB_INVALID_PROCESS_ID;
More information about the lldb-commits
mailing list