diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h --- a/lldb/include/lldb/Interpreter/CommandInterpreter.h +++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -352,7 +352,8 @@ CommandReturnObject &result); bool HandleCommand(const char *command_line, LazyBool add_to_history, - CommandReturnObject &result); + CommandReturnObject &result, + bool force_repeat_command = false); bool WasInterrupted() const; diff --git a/lldb/source/Commands/CommandObjectRegexCommand.cpp b/lldb/source/Commands/CommandObjectRegexCommand.cpp --- a/lldb/source/Commands/CommandObjectRegexCommand.cpp +++ b/lldb/source/Commands/CommandObjectRegexCommand.cpp @@ -72,8 +72,9 @@ result.GetOutputStream().Printf("%s\n", new_command->c_str()); // We don't have to pass an override_context here, as the command that // called us should have set up the context appropriately. - return m_interpreter.HandleCommand(new_command->c_str(), - eLazyBoolNo, result); + bool force_repeat_command = true; + return m_interpreter.HandleCommand(new_command->c_str(), eLazyBoolNo, + result, force_repeat_command); } } result.SetStatus(eReturnStatusFailed); diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -1874,8 +1874,8 @@ bool CommandInterpreter::HandleCommand(const char *command_line, LazyBool lazy_add_to_history, - CommandReturnObject &result) { - + CommandReturnObject &result, + bool force_repeat_command) { std::string command_string(command_line); std::string original_command_string(command_line); @@ -2004,17 +2004,26 @@ // arguments. if (cmd_obj != nullptr) { + bool generate_repeat_command = add_to_history; // If we got here when empty_command was true, then this command is a // stored "repeat command" which we should give a chance to produce it's // repeat command, even though we don't add repeat commands to the history. - if (add_to_history || empty_command) { + generate_repeat_command |= empty_command; + // For `command regex`, the regex command (ex `bt`) is added to history, but + // the resolved command (ex `thread backtrace`) is _not_ added to history. + // However, the resolved command must be given the opportunity to provide a + // repeat command. `force_repeat_command` supports this case. + generate_repeat_command |= force_repeat_command; + if (generate_repeat_command) { Args command_args(command_string); std::optional repeat_command = cmd_obj->GetRepeatCommand(command_args, 0); - if (repeat_command) + if (repeat_command) { + LLDB_LOGF(log, "Repeat command: %s", repeat_command->data()); m_repeat_command.assign(*repeat_command); - else + } else { m_repeat_command.assign(original_command_string); + } } if (add_to_history) diff --git a/lldb/test/API/functionalities/alias/TestBtAliasRepeat.py b/lldb/test/API/functionalities/alias/TestBtAliasRepeat.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/alias/TestBtAliasRepeat.py @@ -0,0 +1,19 @@ +import lldb +from lldbsuite.test.lldbtest import TestBase +from lldbsuite.test import lldbutil + + +class TestCase(TestBase): + def test(self): + self.build() + lldbutil.run_to_source_breakpoint(self, "return", lldb.SBFileSpec("main.c")) + + # Expect "frame #0" but not "frame #1". + self.expect("bt 1", inHistory=True, patterns=["frame #0", "^(?!.*frame #1)"]) + + # Run an empty command to run the repeat command for `bt`. + # The repeat command for `bt N` lists the subsequent N frames. + # + # In this case, after printing the frame 0 with `bt 1`, the repeat + # command will print "frame #1" (and won't print "frame #0"). + self.expect("", patterns=["^(?!.*frame #0)", "frame #1"])