Skip to content

Commit 77d131e

Browse files
authored
Add the ability for Script based commands to specify their "repeat command" (#94823)
Among other things, returning an empty string as the repeat command disables auto-repeat, which can be useful for state-changing commands. There's one remaining refinement to this setup, which is that for parsed script commands, it should be possible to change an option value, or add a new option value that wasn't originally specified, then ask lldb "make this back into a command string". That would make doing fancy things with repeat commands easier. That capability isn't present in the lldb_private side either, however. So that's for a next iteration. I haven't added this to the docs on adding commands yet. I wanted to make sure this was an acceptable approach before I spend the time to do that.
1 parent 94471e6 commit 77d131e

File tree

13 files changed

+202
-13
lines changed

13 files changed

+202
-13
lines changed

lldb/bindings/python/python-wrapper.swig

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,28 @@ bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallCommandObject(
728728
return true;
729729
}
730730

731+
std::optional<std::string>
732+
lldb_private::python::SWIGBridge::LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
733+
std::string &command) {
734+
PyErr_Cleaner py_err_cleaner(true);
735+
736+
PythonObject self(PyRefType::Borrowed, implementor);
737+
auto pfunc = self.ResolveName<PythonCallable>("get_repeat_command");
738+
// If not implemented, repeat the exact command.
739+
if (!pfunc.IsAllocated())
740+
return std::nullopt;
741+
742+
PythonString command_str(command);
743+
PythonObject result = pfunc(command_str);
744+
745+
// A return of None is the equivalent of nullopt - means repeat
746+
// the command as is:
747+
if (result.IsNone())
748+
return std::nullopt;
749+
750+
return result.Str().GetString().str();
751+
}
752+
731753
#include "lldb/Interpreter/CommandReturnObject.h"
732754

733755
bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallParsedCommandObject(

lldb/docs/use/python-reference.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,18 @@ which should implement the following interface:
562562
this call should return the short help text for this command[1]
563563
def get_long_help(self):
564564
this call should return the long help text for this command[1]
565+
def get_repeat_command(self, command):
566+
The auto-repeat command is what will get executed when the user types just
567+
a return at the next prompt after this command is run. Even if your command
568+
was run because it was specified as a repeat command, that invocation will still
569+
get asked for IT'S repeat command, so you can chain a series of repeats, for instance
570+
to implement a pager.
571+
572+
The command argument is the command that is about to be executed.
573+
574+
If this call returns None, then the ordinary repeat mechanism will be used
575+
If this call returns an empty string, then auto-repeat is disabled
576+
If this call returns any other string, that will be the repeat command [1]
565577

566578
[1] This method is optional.
567579

lldb/examples/python/cmdtemplate.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class FrameStatCommand(ParsedCommand):
1919

2020
@classmethod
2121
def register_lldb_command(cls, debugger, module_name):
22-
ParsedCommandBase.do_register_cmd(cls, debugger, module_name)
22+
ParsedCommand.do_register_cmd(cls, debugger, module_name)
2323
print(
2424
'The "{0}" command has been installed, type "help {0}" or "{0} '
2525
'--help" for detailed help.'.format(cls.program)
@@ -72,6 +72,10 @@ def setup_command_definition(self):
7272
default = True,
7373
)
7474

75+
def get_repeat_command(self, args):
76+
"""As an example, make the command not auto-repeat:"""
77+
return ""
78+
7579
def get_short_help(self):
7680
return "Example command for use in debugging"
7781

lldb/include/lldb/Interpreter/CommandObject.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,10 @@ class CommandObject : public std::enable_shared_from_this<CommandObject> {
297297
/// \param[in] current_command_args
298298
/// The command arguments.
299299
///
300+
/// \param[in] index
301+
/// This is for internal use - it is how the completion request is tracked
302+
/// in CommandObjectMultiword, and should otherwise be ignored.
303+
///
300304
/// \return
301305
/// std::nullopt if there is no special repeat command - it will use the
302306
/// current command line.

lldb/include/lldb/Interpreter/ScriptInterpreter.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,12 @@ class ScriptInterpreter : public PluginInterface {
439439
return false;
440440
}
441441

442+
virtual std::optional<std::string>
443+
GetRepeatCommandForScriptedCommand(StructuredData::GenericSP impl_obj_sp,
444+
Args &args) {
445+
return std::nullopt;
446+
}
447+
442448
virtual bool RunScriptFormatKeyword(const char *impl_function,
443449
Process *process, std::string &output,
444450
Status &error) {

lldb/source/Commands/CommandObjectCommands.cpp

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,6 +1142,15 @@ class CommandObjectScriptingObjectRaw : public CommandObjectRaw {
11421142

11431143
ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; }
11441144

1145+
std::optional<std::string> GetRepeatCommand(Args &args,
1146+
uint32_t index) override {
1147+
ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
1148+
if (!scripter)
1149+
return std::nullopt;
1150+
1151+
return scripter->GetRepeatCommandForScriptedCommand(m_cmd_obj_sp, args);
1152+
}
1153+
11451154
llvm::StringRef GetHelp() override {
11461155
if (m_fetched_help_short)
11471156
return CommandObjectRaw::GetHelp();
@@ -1588,7 +1597,9 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
15881597
options.ForEach(add_element);
15891598
return error;
15901599
}
1591-
1600+
1601+
size_t GetNumOptions() { return m_num_options; }
1602+
15921603
private:
15931604
struct EnumValueStorage {
15941605
EnumValueStorage() {
@@ -1827,6 +1838,15 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
18271838

18281839
ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; }
18291840

1841+
std::optional<std::string> GetRepeatCommand(Args &args,
1842+
uint32_t index) override {
1843+
ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
1844+
if (!scripter)
1845+
return std::nullopt;
1846+
1847+
return scripter->GetRepeatCommandForScriptedCommand(m_cmd_obj_sp, args);
1848+
}
1849+
18301850
llvm::StringRef GetHelp() override {
18311851
if (m_fetched_help_short)
18321852
return CommandObjectParsed::GetHelp();
@@ -1857,9 +1877,14 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
18571877
SetHelpLong(docstring);
18581878
return CommandObjectParsed::GetHelpLong();
18591879
}
1860-
1861-
Options *GetOptions() override { return &m_options; }
18621880

1881+
Options *GetOptions() override {
1882+
// CommandObjectParsed requires that a command with no options return
1883+
// nullptr.
1884+
if (m_options.GetNumOptions() == 0)
1885+
return nullptr;
1886+
return &m_options;
1887+
}
18631888

18641889
protected:
18651890
void DoExecute(Args &args,

lldb/source/Commands/CommandObjectThread.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
132132
Options *GetOptions() override { return &m_options; }
133133

134134
std::optional<std::string> GetRepeatCommand(Args &current_args,
135-
uint32_t idx) override {
135+
uint32_t index) override {
136136
llvm::StringRef count_opt("--count");
137137
llvm::StringRef start_opt("--start");
138138

lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ class SWIGBridge {
206206
lldb_private::CommandReturnObject &cmd_retobj,
207207
lldb::ExecutionContextRefSP exe_ctx_ref_sp);
208208

209+
static std::optional<std::string>
210+
LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
211+
std::string &command);
212+
209213
static bool LLDBSwigPythonCallModuleInit(const char *python_module_name,
210214
const char *session_dictionary_name,
211215
lldb::DebuggerSP debugger);

lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2708,6 +2708,33 @@ bool ScriptInterpreterPythonImpl::RunScriptBasedParsedCommand(
27082708
return ret_val;
27092709
}
27102710

2711+
std::optional<std::string>
2712+
ScriptInterpreterPythonImpl::GetRepeatCommandForScriptedCommand(
2713+
StructuredData::GenericSP impl_obj_sp, Args &args) {
2714+
if (!impl_obj_sp || !impl_obj_sp->IsValid())
2715+
return std::nullopt;
2716+
2717+
lldb::DebuggerSP debugger_sp = m_debugger.shared_from_this();
2718+
2719+
if (!debugger_sp.get())
2720+
return std::nullopt;
2721+
2722+
std::optional<std::string> ret_val;
2723+
2724+
{
2725+
Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN,
2726+
Locker::FreeLock);
2727+
2728+
StructuredData::ArraySP args_arr_sp(new StructuredData::Array());
2729+
2730+
// For scripting commands, we send the command string:
2731+
std::string command;
2732+
args.GetQuotedCommandString(command);
2733+
ret_val = SWIGBridge::LLDBSwigPythonGetRepeatCommandForScriptedCommand(
2734+
static_cast<PyObject *>(impl_obj_sp->GetValue()), command);
2735+
}
2736+
return ret_val;
2737+
}
27112738

27122739
/// In Python, a special attribute __doc__ contains the docstring for an object
27132740
/// (function, method, class, ...) if any is defined Otherwise, the attribute's

lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,16 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
160160
lldb_private::CommandReturnObject &cmd_retobj, Status &error,
161161
const lldb_private::ExecutionContext &exe_ctx) override;
162162

163-
virtual bool RunScriptBasedParsedCommand(
164-
StructuredData::GenericSP impl_obj_sp, Args& args,
163+
bool RunScriptBasedParsedCommand(
164+
StructuredData::GenericSP impl_obj_sp, Args &args,
165165
ScriptedCommandSynchronicity synchronicity,
166166
lldb_private::CommandReturnObject &cmd_retobj, Status &error,
167167
const lldb_private::ExecutionContext &exe_ctx) override;
168168

169-
169+
std::optional<std::string>
170+
GetRepeatCommandForScriptedCommand(StructuredData::GenericSP impl_obj_sp,
171+
Args &args) override;
172+
170173
Status GenerateFunction(const char *signature, const StringList &input,
171174
bool is_callback) override;
172175

0 commit comments

Comments
 (0)