[Lldb-commits] [lldb] Add the ability for Script based commands to specify their "repeat command" (PR #94823)

via lldb-commits lldb-commits at lists.llvm.org
Tue Jul 2 14:11:17 PDT 2024


https://github.com/jimingham updated https://github.com/llvm/llvm-project/pull/94823

>From c2fea75364a0017be5e59020467d661bd00122ba Mon Sep 17 00:00:00 2001
From: Jim Ingham <jingham at apple.com>
Date: Fri, 7 Jun 2024 17:36:34 -0700
Subject: [PATCH 1/4] Add the ability for Script based commands to specify
 their "repeat command".

Among other things, returning an empty string as the repeat command disables
auto-repeat, which can be useful for state-changing commands.
---
 lldb/bindings/python/python-wrapper.swig      | 23 ++++++++
 lldb/examples/python/cmdtemplate.py           |  6 +-
 lldb/include/lldb/Interpreter/CommandObject.h |  4 ++
 .../lldb/Interpreter/ScriptInterpreter.h      |  5 ++
 .../source/Commands/CommandObjectCommands.cpp | 26 ++++++++-
 lldb/source/Commands/CommandObjectThread.cpp  |  2 +-
 .../Python/SWIGPythonBridge.h                 |  4 ++
 .../Python/ScriptInterpreterPython.cpp        | 27 +++++++++
 .../Python/ScriptInterpreterPythonImpl.h      | 14 +++--
 .../script/add/TestAddParsedCommand.py        | 55 +++++++++++++++++++
 .../command/script/add/test_commands.py       | 25 ++++++++-
 .../Python/PythonTestSuite.cpp                |  6 ++
 12 files changed, 187 insertions(+), 10 deletions(-)

diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index 1370afc885d43..bb3c9433e2032 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -831,6 +831,29 @@ bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallCommandObject(
   return true;
 }
 
+std::optional<std::string>
+lldb_private::python::SWIGBridge::LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
+                                               std::string &command) {
+  PyErr_Cleaner py_err_cleaner(true);
+
+  PythonObject self(PyRefType::Borrowed, implementor);
+  auto pfunc = self.ResolveName<PythonCallable>("get_repeat_command");
+  // If not implemented, repeat the exact command.
+  if (!pfunc.IsAllocated())
+    return std::nullopt;
+
+  PythonObject result;
+  PythonString command_str(command);
+  result = pfunc(command_str);
+
+  // A return of None is the equivalent of nullopt - means repeat
+  // the command as is:
+  if (result.IsNone())
+    return std::nullopt;
+
+  return result.Str().GetString().str();
+}
+
 #include "lldb/Interpreter/CommandReturnObject.h"
 
 bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallParsedCommandObject(
diff --git a/lldb/examples/python/cmdtemplate.py b/lldb/examples/python/cmdtemplate.py
index 49a08365268f8..9a96888508b6f 100644
--- a/lldb/examples/python/cmdtemplate.py
+++ b/lldb/examples/python/cmdtemplate.py
@@ -19,7 +19,7 @@ class FrameStatCommand(ParsedCommand):
 
     @classmethod
     def register_lldb_command(cls, debugger, module_name):
-        ParsedCommandBase.do_register_cmd(cls, debugger, module_name)
+        ParsedCommand.do_register_cmd(cls, debugger, module_name)
         print(
             'The "{0}" command has been installed, type "help {0}" or "{0} '
             '--help" for detailed help.'.format(cls.program)
@@ -72,6 +72,10 @@ def setup_command_definition(self):
             default = True,
         )
 
+    def get_repeat_command(self, args):
+        """As an example, make the command not auto-repeat:"""
+        return ""
+
     def get_short_help(self):
         return "Example command for use in debugging"
 
diff --git a/lldb/include/lldb/Interpreter/CommandObject.h b/lldb/include/lldb/Interpreter/CommandObject.h
index a641a468b49d2..727ea0d963734 100644
--- a/lldb/include/lldb/Interpreter/CommandObject.h
+++ b/lldb/include/lldb/Interpreter/CommandObject.h
@@ -296,6 +296,10 @@ class CommandObject : public std::enable_shared_from_this<CommandObject> {
   ///
   /// \param[in] current_command_args
   ///    The command arguments.
+  ///    
+  /// \param[in] index
+  ///    This is for internal use - it is how the completion request is tracked
+  ///    in CommandObjectMultiword, and should otherwise be ignored.
   ///
   /// \return
   ///     std::nullopt if there is no special repeat command - it will use the
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 932eaa8b8a4a2..934fd1837fcc0 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -480,6 +480,11 @@ class ScriptInterpreter : public PluginInterface {
       const lldb_private::ExecutionContext &exe_ctx) {
     return false;
   }
+  
+  virtual std::optional<std::string> GetRepeatCommandForScriptedCommand(
+      StructuredData::GenericSP impl_obj_sp, Args &args) {
+    return std::nullopt;
+  }
 
   virtual bool RunScriptFormatKeyword(const char *impl_function,
                                       Process *process, std::string &output,
diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp
index f4903e373b086..4144876c0751f 100644
--- a/lldb/source/Commands/CommandObjectCommands.cpp
+++ b/lldb/source/Commands/CommandObjectCommands.cpp
@@ -1142,6 +1142,14 @@ class CommandObjectScriptingObjectRaw : public CommandObjectRaw {
 
   ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; }
 
+  std::optional<std::string> GetRepeatCommand(Args &args, uint32_t index) override {
+    ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
+    if (!scripter)
+      return std::nullopt;
+      
+    return scripter->GetRepeatCommandForScriptedCommand(m_cmd_obj_sp, args);
+  }
+
   llvm::StringRef GetHelp() override {
     if (m_fetched_help_short)
       return CommandObjectRaw::GetHelp();
@@ -1589,6 +1597,8 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
       return error;
     }
     
+    size_t GetNumOptions() { return m_num_options; }
+    
   private:
     struct EnumValueStorage {
       EnumValueStorage() {
@@ -1827,6 +1837,14 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
 
   ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; }
 
+  std::optional<std::string> GetRepeatCommand(Args &args, uint32_t index) override {
+    ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
+    if (!scripter)
+      return std::nullopt;
+      
+    return scripter->GetRepeatCommandForScriptedCommand(m_cmd_obj_sp, args);
+  }
+  
   llvm::StringRef GetHelp() override {
     if (m_fetched_help_short)
       return CommandObjectParsed::GetHelp();
@@ -1858,7 +1876,13 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
     return CommandObjectParsed::GetHelpLong();
   }
   
-  Options *GetOptions() override { return &m_options; }
+  Options *GetOptions() override {
+    // CommandObjectParsed requires that a command with no options return
+    // nullptr.
+    if (m_options.GetNumOptions() == 0)
+      return nullptr;
+    return &m_options; 
+  }
 
 
 protected:
diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp
index db96ee2cec383..772d76e8b148b 100644
--- a/lldb/source/Commands/CommandObjectThread.cpp
+++ b/lldb/source/Commands/CommandObjectThread.cpp
@@ -132,7 +132,7 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads {
   Options *GetOptions() override { return &m_options; }
 
   std::optional<std::string> GetRepeatCommand(Args &current_args,
-                                              uint32_t idx) override {
+                                              uint32_t index) override {
     llvm::StringRef count_opt("--count");
     llvm::StringRef start_opt("--start");
 
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
index c1a11b9134d62..c94fa7d82ab53 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -219,6 +219,10 @@ class SWIGBridge {
                                   StructuredDataImpl &args_impl,
                                   lldb_private::CommandReturnObject &cmd_retobj,
                                   lldb::ExecutionContextRefSP exe_ctx_ref_sp);
+  
+  static std::optional<std::string>
+  LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
+                                                 std::string &command);  
 
   static bool LLDBSwigPythonCallModuleInit(const char *python_module_name,
                                            const char *session_dictionary_name,
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index 6e676de146b3d..c621ee8381ea9 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -2818,6 +2818,33 @@ bool ScriptInterpreterPythonImpl::RunScriptBasedParsedCommand(
   return ret_val;
 }
 
+std::optional<std::string> ScriptInterpreterPythonImpl::GetRepeatCommandForScriptedCommand(
+      StructuredData::GenericSP impl_obj_sp, Args &args) {
+  if (!impl_obj_sp || !impl_obj_sp->IsValid())
+    return std::nullopt;
+
+  lldb::DebuggerSP debugger_sp = m_debugger.shared_from_this();
+
+  if (!debugger_sp.get())
+    return std::nullopt;
+
+  std::optional<std::string> ret_val;
+  
+  {
+    Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN,
+                   Locker::FreeLock);
+
+    StructuredData::ArraySP args_arr_sp(new StructuredData::Array());
+
+    // For scripting commands, we send the command string:
+    std::string command;
+    args.GetQuotedCommandString(command);
+    ret_val = SWIGBridge::LLDBSwigPythonGetRepeatCommandForScriptedCommand(
+        static_cast<PyObject *>(impl_obj_sp->GetValue()), command);
+  }
+  return ret_val;
+      
+}
 
 /// In Python, a special attribute __doc__ contains the docstring for an object
 /// (function, method, class, ...) if any is defined Otherwise, the attribute's
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
index fcd21dff612b1..f8c4873fafb41 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
@@ -182,12 +182,14 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
       lldb_private::CommandReturnObject &cmd_retobj, Status &error,
       const lldb_private::ExecutionContext &exe_ctx) override;
 
-    virtual bool RunScriptBasedParsedCommand(
-      StructuredData::GenericSP impl_obj_sp, Args& args,
-      ScriptedCommandSynchronicity synchronicity,
-      lldb_private::CommandReturnObject &cmd_retobj, Status &error,
-      const lldb_private::ExecutionContext &exe_ctx) override;
-
+  bool RunScriptBasedParsedCommand(
+    StructuredData::GenericSP impl_obj_sp, Args& args,
+    ScriptedCommandSynchronicity synchronicity,
+    lldb_private::CommandReturnObject &cmd_retobj, Status &error,
+    const lldb_private::ExecutionContext &exe_ctx) override;
+
+  std::optional<std::string> GetRepeatCommandForScriptedCommand(
+      StructuredData::GenericSP impl_obj_sp, Args &args) override;
   
   Status GenerateFunction(const char *signature, const StringList &input,
                           bool is_callback) override;
diff --git a/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py b/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
index d30b0b67124ed..10a54e9fed35f 100644
--- a/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
+++ b/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
@@ -16,6 +16,11 @@ class ParsedCommandTestCase(TestBase):
     def test(self):
         self.pycmd_tests()
 
+    def setUp(self):
+        TestBase.setUp(self)
+        self.stdin_path = self.getBuildArtifact("stdin.txt")
+        self.stdout_path = self.getBuildArtifact("stdout.txt")
+
     def check_help_options(self, cmd_name, opt_list, substrs=[]):
         """
         Pass the command name in cmd_name and a vector of the short option, type & long option.
@@ -32,6 +37,38 @@ def check_help_options(self, cmd_name, opt_list, substrs=[]):
         print(f"Opt Vec\n{substrs}")
         self.expect("help " + cmd_name, substrs=substrs)
 
+    def run_one_repeat(self, commands, expected_num_errors):
+        with open(self.stdin_path, "w") as input_handle:
+            input_handle.write(commands)
+            
+        in_fileH = open(self.stdin_path, "r")
+        self.dbg.SetInputFileHandle(in_fileH, False)
+        
+        out_fileH = open(self.stdout_path, "w")
+        self.dbg.SetOutputFileHandle(out_fileH, False)
+        self.dbg.SetErrorFileHandle(out_fileH, False)
+        
+        options = lldb.SBCommandInterpreterRunOptions()
+        options.SetEchoCommands(False)
+        options.SetPrintResults(True)
+        options.SetPrintErrors(True)
+        options.SetAllowRepeats(True)
+        
+        n_errors, quit_requested, has_crashed = self.dbg.RunCommandInterpreter(
+            True, False, options, 0, False, False)
+            
+        in_fileH.close()
+        out_fileH.close()
+                
+        results = None
+        with open(self.stdout_path, "r") as out_fileH:
+            results = out_fileH.read()
+
+        print(f"RESULTS:\n{results}\nDONE")
+        self.assertEqual(n_errors, expected_num_errors)
+                
+        return results
+
     def pycmd_tests(self):
         source_dir = self.getSourceDir()
         test_file_path = os.path.join(source_dir, "test_commands.py")
@@ -197,3 +234,21 @@ def cleanup():
             "two-args 'First Argument' 'Second Argument'",
             substrs=["0: First Argument", "1: Second Argument"],
         )
+
+        # Now make sure get_repeat_command works properly:
+
+        # no-args turns off auto-repeat
+        results = self.run_one_repeat("no-args\n\n", 1)
+        self.assertIn("No auto repeat", results, "Got auto-repeat error")
+
+        # one-args does the normal repeat
+        results = self.run_one_repeat("one-arg-no-opt ONE_ARG\n\n", 0)
+        self.assertEqual(results.count("ONE_ARG"), 2, "We did a normal repeat")
+
+        # two-args adds an argument:
+        results = self.run_one_repeat("two-args FIRST_ARG SECOND_ARG\n\n", 0)
+        self.assertEqual(results.count("FIRST_ARG"), 2, "Passed first arg to both commands")
+        self.assertEqual(results.count("SECOND_ARG"), 2, "Passed second arg to both commands")
+        self.assertEqual(results.count("THIRD_ARG"), 1, "Passed third arg in repeat")
+
+                    
diff --git a/lldb/test/API/commands/command/script/add/test_commands.py b/lldb/test/API/commands/command/script/add/test_commands.py
index 68f5a44556366..94d8bb241b2da 100644
--- a/lldb/test/API/commands/command/script/add/test_commands.py
+++ b/lldb/test/API/commands/command/script/add/test_commands.py
@@ -32,6 +32,12 @@ def __call__(self, debugger, args_array, exe_ctx, result):
             )
 
 
+# Use these to make sure that get_repeat_command sends the right
+# command.
+no_args_repeat = None
+one_arg_repeat = None
+two_arg_repeat = None
+
 class NoArgsCommand(ReportingCmd):
     program = "no-args"
 
@@ -96,6 +102,12 @@ def setup_command_definition(self):
             default="foo",
         )
 
+    def get_repeat_command(self, command):
+        # No auto-repeat
+        global no_args_repeat
+        no_args_repeat = command
+        return ""
+
     def get_short_help(self):
         return "Example command for use in debugging"
 
@@ -118,6 +130,12 @@ def setup_command_definition(self):
             [self.ov_parser.make_argument_element(lldb.eArgTypeSourceFile, "plain")]
         )
 
+    def get_repeat_command(self, command):
+        # Repeat the current command
+        global one_arg_repeat
+        one_arg_repeat = command
+        return None
+
     def get_short_help(self):
         return "Example command for use in debugging"
 
@@ -187,8 +205,13 @@ def setup_command_definition(self):
             ]
         )
 
+    def get_repeat_command(self, command):
+        global two_arg_repeat
+        two_arg_repeat = command
+        return command + " THIRD_ARG"
+        
     def get_short_help(self):
-        return "Example command for use in debugging"
+        return "This is my short help string"
 
     def get_long_help(self):
         return self.help_string
diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
index 23162436d42c9..03eb08af8182f 100644
--- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
+++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
@@ -210,6 +210,12 @@ bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallParsedCommandObject(
   return false;
 }
 
+std::optional<std::string>
+LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
+                                               std::string &command) {
+  return std::nullopt;
+}
+
 bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallModuleInit(
     const char *python_module_name, const char *session_dictionary_name,
     lldb::DebuggerSP debugger) {

>From e3f6b8ca2f05b0721b213f5a2af9e0b96d1229bf Mon Sep 17 00:00:00 2001
From: Jim Ingham <jingham at apple.com>
Date: Fri, 7 Jun 2024 17:54:28 -0700
Subject: [PATCH 2/4] formatters

---
 lldb/include/lldb/Interpreter/CommandObject.h |  2 +-
 .../lldb/Interpreter/ScriptInterpreter.h      |  7 ++---
 .../source/Commands/CommandObjectCommands.cpp | 21 ++++++++-------
 .../Python/SWIGPythonBridge.h                 |  4 +--
 .../Python/ScriptInterpreterPython.cpp        |  8 +++---
 .../Python/ScriptInterpreterPythonImpl.h      | 17 ++++++------
 .../script/add/TestAddParsedCommand.py        | 27 ++++++++++---------
 .../command/script/add/test_commands.py       |  2 +-
 .../Python/PythonTestSuite.cpp                |  2 +-
 9 files changed, 48 insertions(+), 42 deletions(-)

diff --git a/lldb/include/lldb/Interpreter/CommandObject.h b/lldb/include/lldb/Interpreter/CommandObject.h
index 727ea0d963734..d48dbcdd5a5da 100644
--- a/lldb/include/lldb/Interpreter/CommandObject.h
+++ b/lldb/include/lldb/Interpreter/CommandObject.h
@@ -296,7 +296,7 @@ class CommandObject : public std::enable_shared_from_this<CommandObject> {
   ///
   /// \param[in] current_command_args
   ///    The command arguments.
-  ///    
+  ///
   /// \param[in] index
   ///    This is for internal use - it is how the completion request is tracked
   ///    in CommandObjectMultiword, and should otherwise be ignored.
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 934fd1837fcc0..ff3306b853a14 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -480,9 +480,10 @@ class ScriptInterpreter : public PluginInterface {
       const lldb_private::ExecutionContext &exe_ctx) {
     return false;
   }
-  
-  virtual std::optional<std::string> GetRepeatCommandForScriptedCommand(
-      StructuredData::GenericSP impl_obj_sp, Args &args) {
+
+  virtual std::optional<std::string>
+  GetRepeatCommandForScriptedCommand(StructuredData::GenericSP impl_obj_sp,
+                                     Args &args) {
     return std::nullopt;
   }
 
diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp
index 4144876c0751f..c63445b7c8c86 100644
--- a/lldb/source/Commands/CommandObjectCommands.cpp
+++ b/lldb/source/Commands/CommandObjectCommands.cpp
@@ -1142,11 +1142,12 @@ class CommandObjectScriptingObjectRaw : public CommandObjectRaw {
 
   ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; }
 
-  std::optional<std::string> GetRepeatCommand(Args &args, uint32_t index) override {
+  std::optional<std::string> GetRepeatCommand(Args &args,
+                                              uint32_t index) override {
     ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
     if (!scripter)
       return std::nullopt;
-      
+
     return scripter->GetRepeatCommandForScriptedCommand(m_cmd_obj_sp, args);
   }
 
@@ -1596,9 +1597,9 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
       options.ForEach(add_element);
       return error;
     }
-    
+
     size_t GetNumOptions() { return m_num_options; }
-    
+
   private:
     struct EnumValueStorage {
       EnumValueStorage() {
@@ -1837,14 +1838,15 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
 
   ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; }
 
-  std::optional<std::string> GetRepeatCommand(Args &args, uint32_t index) override {
+  std::optional<std::string> GetRepeatCommand(Args &args,
+                                              uint32_t index) override {
     ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
     if (!scripter)
       return std::nullopt;
-      
+
     return scripter->GetRepeatCommandForScriptedCommand(m_cmd_obj_sp, args);
   }
-  
+
   llvm::StringRef GetHelp() override {
     if (m_fetched_help_short)
       return CommandObjectParsed::GetHelp();
@@ -1875,16 +1877,15 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
       SetHelpLong(docstring);
     return CommandObjectParsed::GetHelpLong();
   }
-  
+
   Options *GetOptions() override {
     // CommandObjectParsed requires that a command with no options return
     // nullptr.
     if (m_options.GetNumOptions() == 0)
       return nullptr;
-    return &m_options; 
+    return &m_options;
   }
 
-
 protected:
   void DoExecute(Args &args,
                  CommandReturnObject &result) override {
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
index c94fa7d82ab53..7ee5dc5b0469e 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -219,10 +219,10 @@ class SWIGBridge {
                                   StructuredDataImpl &args_impl,
                                   lldb_private::CommandReturnObject &cmd_retobj,
                                   lldb::ExecutionContextRefSP exe_ctx_ref_sp);
-  
+
   static std::optional<std::string>
   LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
-                                                 std::string &command);  
+                                                   std::string &command);
 
   static bool LLDBSwigPythonCallModuleInit(const char *python_module_name,
                                            const char *session_dictionary_name,
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index c621ee8381ea9..bedb7c4189f2b 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -2818,8 +2818,9 @@ bool ScriptInterpreterPythonImpl::RunScriptBasedParsedCommand(
   return ret_val;
 }
 
-std::optional<std::string> ScriptInterpreterPythonImpl::GetRepeatCommandForScriptedCommand(
-      StructuredData::GenericSP impl_obj_sp, Args &args) {
+std::optional<std::string>
+ScriptInterpreterPythonImpl::GetRepeatCommandForScriptedCommand(
+    StructuredData::GenericSP impl_obj_sp, Args &args) {
   if (!impl_obj_sp || !impl_obj_sp->IsValid())
     return std::nullopt;
 
@@ -2829,7 +2830,7 @@ std::optional<std::string> ScriptInterpreterPythonImpl::GetRepeatCommandForScrip
     return std::nullopt;
 
   std::optional<std::string> ret_val;
-  
+
   {
     Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN,
                    Locker::FreeLock);
@@ -2843,7 +2844,6 @@ std::optional<std::string> ScriptInterpreterPythonImpl::GetRepeatCommandForScrip
         static_cast<PyObject *>(impl_obj_sp->GetValue()), command);
   }
   return ret_val;
-      
 }
 
 /// In Python, a special attribute __doc__ contains the docstring for an object
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
index f8c4873fafb41..0185a1a0422f6 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
@@ -183,14 +183,15 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
       const lldb_private::ExecutionContext &exe_ctx) override;
 
   bool RunScriptBasedParsedCommand(
-    StructuredData::GenericSP impl_obj_sp, Args& args,
-    ScriptedCommandSynchronicity synchronicity,
-    lldb_private::CommandReturnObject &cmd_retobj, Status &error,
-    const lldb_private::ExecutionContext &exe_ctx) override;
-
-  std::optional<std::string> GetRepeatCommandForScriptedCommand(
-      StructuredData::GenericSP impl_obj_sp, Args &args) override;
-  
+      StructuredData::GenericSP impl_obj_sp, Args &args,
+      ScriptedCommandSynchronicity synchronicity,
+      lldb_private::CommandReturnObject &cmd_retobj, Status &error,
+      const lldb_private::ExecutionContext &exe_ctx) override;
+
+  std::optional<std::string>
+  GetRepeatCommandForScriptedCommand(StructuredData::GenericSP impl_obj_sp,
+                                     Args &args) override;
+
   Status GenerateFunction(const char *signature, const StringList &input,
                           bool is_callback) override;
 
diff --git a/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py b/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
index 10a54e9fed35f..381cdf2022f48 100644
--- a/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
+++ b/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
@@ -40,33 +40,34 @@ def check_help_options(self, cmd_name, opt_list, substrs=[]):
     def run_one_repeat(self, commands, expected_num_errors):
         with open(self.stdin_path, "w") as input_handle:
             input_handle.write(commands)
-            
+
         in_fileH = open(self.stdin_path, "r")
         self.dbg.SetInputFileHandle(in_fileH, False)
-        
+
         out_fileH = open(self.stdout_path, "w")
         self.dbg.SetOutputFileHandle(out_fileH, False)
         self.dbg.SetErrorFileHandle(out_fileH, False)
-        
+
         options = lldb.SBCommandInterpreterRunOptions()
         options.SetEchoCommands(False)
         options.SetPrintResults(True)
         options.SetPrintErrors(True)
         options.SetAllowRepeats(True)
-        
+
         n_errors, quit_requested, has_crashed = self.dbg.RunCommandInterpreter(
-            True, False, options, 0, False, False)
-            
+            True, False, options, 0, False, False
+        )
+
         in_fileH.close()
         out_fileH.close()
-                
+
         results = None
         with open(self.stdout_path, "r") as out_fileH:
             results = out_fileH.read()
 
         print(f"RESULTS:\n{results}\nDONE")
         self.assertEqual(n_errors, expected_num_errors)
-                
+
         return results
 
     def pycmd_tests(self):
@@ -247,8 +248,10 @@ def cleanup():
 
         # two-args adds an argument:
         results = self.run_one_repeat("two-args FIRST_ARG SECOND_ARG\n\n", 0)
-        self.assertEqual(results.count("FIRST_ARG"), 2, "Passed first arg to both commands")
-        self.assertEqual(results.count("SECOND_ARG"), 2, "Passed second arg to both commands")
+        self.assertEqual(
+            results.count("FIRST_ARG"), 2, "Passed first arg to both commands"
+        )
+        self.assertEqual(
+            results.count("SECOND_ARG"), 2, "Passed second arg to both commands"
+        )
         self.assertEqual(results.count("THIRD_ARG"), 1, "Passed third arg in repeat")
-
-                    
diff --git a/lldb/test/API/commands/command/script/add/test_commands.py b/lldb/test/API/commands/command/script/add/test_commands.py
index 94d8bb241b2da..fcde6cd3ef6dc 100644
--- a/lldb/test/API/commands/command/script/add/test_commands.py
+++ b/lldb/test/API/commands/command/script/add/test_commands.py
@@ -209,7 +209,7 @@ def get_repeat_command(self, command):
         global two_arg_repeat
         two_arg_repeat = command
         return command + " THIRD_ARG"
-        
+
     def get_short_help(self):
         return "This is my short help string"
 
diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
index 03eb08af8182f..dce86accf9691 100644
--- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
+++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
@@ -212,7 +212,7 @@ bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallParsedCommandObject(
 
 std::optional<std::string>
 LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
-                                               std::string &command) {
+                                                 std::string &command) {
   return std::nullopt;
 }
 

>From dda7d78c51a64e2bf39c082cd2fb414a30be35ba Mon Sep 17 00:00:00 2001
From: Jim Ingham <jingham at apple.com>
Date: Tue, 2 Jul 2024 14:05:28 -0700
Subject: [PATCH 3/4] Add docs for `get_repeat_command`.

---
 lldb/docs/use/python-reference.rst | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/lldb/docs/use/python-reference.rst b/lldb/docs/use/python-reference.rst
index 795e38fab3794..041e541a96f08 100644
--- a/lldb/docs/use/python-reference.rst
+++ b/lldb/docs/use/python-reference.rst
@@ -562,6 +562,18 @@ which should implement the following interface:
           this call should return the short help text for this command[1]
       def get_long_help(self):
           this call should return the long help text for this command[1]
+      def get_repeat_command(self, command):
+          The auto-repeat command is what will get executed when the user types just
+          a return at the next prompt after this command is run.  Even if your command
+          was run because it was specified as a repeat command, that invocation will still
+          get asked for IT'S repeat command, so you can chain a series of repeats, for instance
+          to implement a pager.
+
+          The command argument is the command that is about to be executed.
+
+          If this call returns None, then the ordinary repeat mechanism will be used
+          If this call returns an empty string, then auto-repeat is disabled
+          If this call returns any other string, that will be the repeat command [1]
 
 [1] This method is optional.
 

>From 461007259ecf8850f23aa23b6c9bd505df72e7f5 Mon Sep 17 00:00:00 2001
From: Jim Ingham <jingham at apple.com>
Date: Tue, 2 Jul 2024 14:10:31 -0700
Subject: [PATCH 4/4] Remove some debugging print's, responding to review
 comments.

---
 lldb/bindings/python/python-wrapper.swig                     | 3 +--
 .../API/commands/command/script/add/TestAddParsedCommand.py  | 5 -----
 2 files changed, 1 insertion(+), 7 deletions(-)

diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index bb3c9433e2032..86f8de0b4a8b9 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -842,9 +842,8 @@ lldb_private::python::SWIGBridge::LLDBSwigPythonGetRepeatCommandForScriptedComma
   if (!pfunc.IsAllocated())
     return std::nullopt;
 
-  PythonObject result;
   PythonString command_str(command);
-  result = pfunc(command_str);
+  PythonObject result = pfunc(command_str);
 
   // A return of None is the equivalent of nullopt - means repeat
   // the command as is:
diff --git a/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py b/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
index 381cdf2022f48..c7680e9bb7f41 100644
--- a/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
+++ b/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
@@ -34,7 +34,6 @@ def check_help_options(self, cmd_name, opt_list, substrs=[]):
             else:
                 (short_opt, type, long_opt) = elem
                 substrs.append(f"-{short_opt} <{type}> ( --{long_opt} <{type}> )")
-        print(f"Opt Vec\n{substrs}")
         self.expect("help " + cmd_name, substrs=substrs)
 
     def run_one_repeat(self, commands, expected_num_errors):
@@ -65,7 +64,6 @@ def run_one_repeat(self, commands, expected_num_errors):
         with open(self.stdout_path, "r") as out_fileH:
             results = out_fileH.read()
 
-        print(f"RESULTS:\n{results}\nDONE")
         self.assertEqual(n_errors, expected_num_errors)
 
         return results
@@ -206,9 +204,6 @@ def cleanup():
         num_completions = interp.HandleCompletionWithDescriptions(
             cmd_str, len(cmd_str) - 1, 0, 1000, matches, descriptions
         )
-        print(
-            f"First: {matches.GetStringAtIndex(0)}\nSecond: {matches.GetStringAtIndex(1)}\nThird: {matches.GetStringAtIndex(2)}"
-        )
         self.assertEqual(num_completions, 1, "Only one completion for source file")
         self.assertEqual(matches.GetSize(), 2, "The first element is the complete line")
         self.assertEqual(



More information about the lldb-commits mailing list