Skip to content

Commit 881f26a

Browse files
cmticeJDevlieghere
authored andcommitted
[lldb-dap] Add feature to remember last non-empty expression. (llvm#107485)
Update lldb-dap so if the user just presses return, which sends an empty expression, it re-evaluates the most recent non-empty expression/command. Also udpated test to test this case. (cherry picked from commit 2011cbc)
1 parent 7abfcd0 commit 881f26a

File tree

5 files changed

+49
-5
lines changed

5 files changed

+49
-5
lines changed

lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,22 @@ def run_test_evaluate_expressions(
5454
line_number(source, "// breakpoint 5"),
5555
line_number(source, "// breakpoint 6"),
5656
line_number(source, "// breakpoint 7"),
57+
line_number(source, "// breakpoint 8"),
5758
],
5859
)
5960
self.continue_to_next_stop()
6061

6162
# Expressions at breakpoint 1, which is in main
6263
self.assertEvaluate("var1", "20")
64+
# Empty expression should equate to the previous expression.
65+
if context == "repl":
66+
self.assertEvaluate("", "20")
67+
else:
68+
self.assertEvaluateFailure("")
6369
self.assertEvaluate("var2", "21")
70+
if context == "repl":
71+
self.assertEvaluate("", "21")
72+
self.assertEvaluate("", "21")
6473
self.assertEvaluate("static_int", "42")
6574
self.assertEvaluate("non_static_int", "43")
6675
self.assertEvaluate("struct1.foo", "15")
@@ -191,6 +200,15 @@ def run_test_evaluate_expressions(
191200
self.continue_to_next_stop()
192201
self.assertEvaluate("my_bool_vec", "size=2")
193202

203+
# Test memory read, especially with 'empty' repeat commands.
204+
if context == "repl":
205+
self.continue_to_next_stop()
206+
self.assertEvaluate("memory read -c 1 &my_ints", ".* 05 .*\n")
207+
self.assertEvaluate("", ".* 0a .*\n")
208+
self.assertEvaluate("", ".* 0f .*\n")
209+
self.assertEvaluate("", ".* 14 .*\n")
210+
self.assertEvaluate("", ".* 19 .*\n")
211+
194212
@skipIfWindows
195213
def test_generic_evaluate_expressions(self):
196214
# Tests context-less expression evaluations

lldb/test/API/tools/lldb-dap/evaluate/main.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "foo.h"
22

3+
#include <cstdint>
34
#include <map>
45
#include <vector>
56

@@ -45,5 +46,6 @@ int main(int argc, char const *argv[]) {
4546
my_bool_vec.push_back(false); // breakpoint 6
4647
my_bool_vec.push_back(true); // breakpoint 7
4748

48-
return 0;
49+
uint8_t my_ints[] = {5, 10, 15, 20, 25, 30};
50+
return 0; // breakpoint 8
4951
}

lldb/tools/lldb-dap/DAP.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,12 @@ struct DAP {
205205
std::string command_escape_prefix = "`";
206206
lldb::SBFormat frame_format;
207207
lldb::SBFormat thread_format;
208+
// This is used to allow request_evaluate to handle empty expressions
209+
// (ie the user pressed 'return' and expects the previous expression to
210+
// repeat). If the previous expression was a command, this string will be
211+
// empty; if the previous expression was a variable expression, this string
212+
// will contain that expression.
213+
std::string last_nonempty_var_expression;
208214

209215
DAP();
210216
~DAP();

lldb/tools/lldb-dap/LLDBUtils.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ bool RunLLDBCommands(llvm::StringRef prefix,
4545
// RunTerminateCommands.
4646
static std::mutex handle_command_mutex;
4747
std::lock_guard<std::mutex> locker(handle_command_mutex);
48-
interp.HandleCommand(command.str().c_str(), result);
48+
interp.HandleCommand(command.str().c_str(), result,
49+
/*add_to_history=*/true);
4950
}
5051

5152
const bool got_error = !result.Succeeded();

lldb/tools/lldb-dap/lldb-dap.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,9 +1569,16 @@ void request_evaluate(const llvm::json::Object &request) {
15691569
lldb::SBFrame frame = g_dap.GetLLDBFrame(*arguments);
15701570
std::string expression = GetString(arguments, "expression").str();
15711571
llvm::StringRef context = GetString(arguments, "context");
1572-
1573-
if (context == "repl" && g_dap.DetectExpressionContext(frame, expression) ==
1574-
ExpressionContext::Command) {
1572+
bool repeat_last_command =
1573+
expression.empty() && g_dap.last_nonempty_var_expression.empty();
1574+
1575+
if (context == "repl" && (repeat_last_command ||
1576+
(!expression.empty() &&
1577+
g_dap.DetectExpressionContext(frame, expression) ==
1578+
ExpressionContext::Command))) {
1579+
// Since the current expression is not for a variable, clear the
1580+
// last_nonempty_var_expression field.
1581+
g_dap.last_nonempty_var_expression.clear();
15751582
// If we're evaluating a command relative to the current frame, set the
15761583
// focus_tid to the current frame for any thread related events.
15771584
if (frame.IsValid()) {
@@ -1582,6 +1589,16 @@ void request_evaluate(const llvm::json::Object &request) {
15821589
EmplaceSafeString(body, "result", result);
15831590
body.try_emplace("variablesReference", (int64_t)0);
15841591
} else {
1592+
if (context == "repl") {
1593+
// If the expression is empty and the last expression was for a
1594+
// variable, set the expression to the previous expression (repeat the
1595+
// evaluation); otherwise save the current non-empty expression for the
1596+
// next (possibly empty) variable expression.
1597+
if (expression.empty())
1598+
expression = g_dap.last_nonempty_var_expression;
1599+
else
1600+
g_dap.last_nonempty_var_expression = expression;
1601+
}
15851602
// Always try to get the answer from the local variables if possible. If
15861603
// this fails, then if the context is not "hover", actually evaluate an
15871604
// expression using the expression parser.

0 commit comments

Comments
 (0)