[Lldb-commits] [lldb] Add the ability to define custom completers to the parsed_cmd template. (PR #109062)

via lldb-commits lldb-commits at lists.llvm.org
Mon Sep 23 15:45:57 PDT 2024


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

>From a0b5801ab231670215657ec720fa0c89bc262c04 Mon Sep 17 00:00:00 2001
From: Jim Ingham <jingham at apple.com>
Date: Thu, 29 Aug 2024 18:22:13 -0700
Subject: [PATCH 1/3] Add the ability to define custom completers for option
 values and arguments to the parsed_cmd template.

---
 lldb/bindings/python/python-wrapper.swig      |  73 +++++++
 lldb/docs/use/python-reference.rst            | 144 +++++++++++++-
 lldb/examples/python/cmdtemplate.py           |  15 +-
 lldb/examples/python/templates/parsed_cmd.py  |  84 +++++++-
 .../lldb/Interpreter/ScriptInterpreter.h      |  15 ++
 lldb/include/lldb/Utility/CompletionRequest.h |   2 +
 .../source/Commands/CommandObjectCommands.cpp | 187 ++++++++++++++++++
 lldb/source/Interpreter/Options.cpp           |   5 +-
 .../Python/SWIGPythonBridge.h                 |   8 +
 .../Python/ScriptInterpreterPython.cpp        |  40 ++++
 .../Python/ScriptInterpreterPythonImpl.h      |   8 +
 .../script/add/TestAddParsedCommand.py        | 115 +++++++----
 .../command/script/add/test_commands.py       |  64 ++++--
 .../Python/PythonTestSuite.cpp                |  13 ++
 14 files changed, 706 insertions(+), 67 deletions(-)

diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig
index 810673aaec5d19..1f5012af99a291 100644
--- a/lldb/bindings/python/python-wrapper.swig
+++ b/lldb/bindings/python/python-wrapper.swig
@@ -752,6 +752,79 @@ lldb_private::python::SWIGBridge::LLDBSwigPythonGetRepeatCommandForScriptedComma
   return result.Str().GetString().str();
 }
 
+StructuredData::DictionarySP
+lldb_private::python::SWIGBridge::LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(PyObject *implementor,
+    std::vector<llvm::StringRef> &args_vec, size_t args_pos, size_t pos_in_arg) {
+
+  PyErr_Cleaner py_err_cleaner(true);
+
+  PythonObject self(PyRefType::Borrowed, implementor);
+  auto pfunc = self.ResolveName<PythonCallable>("handle_argument_completion");
+  // If this isn't implemented, return an empty dict to signal falling back to default completion:
+  if (!pfunc.IsAllocated())
+    return {};
+
+  PythonList args_list(PyInitialValue::Empty);
+  for (auto elem : args_vec)
+    args_list.AppendItem(PythonString(elem));
+
+  PythonObject result = pfunc(args_list, PythonInteger(args_pos), PythonInteger(pos_in_arg));
+  // Returning None means do the ordinary completion
+  if (result.IsNone())
+    return {};
+
+  // Convert the return dictionary to a DictionarySP.
+  StructuredData::ObjectSP result_obj_sp = result.CreateStructuredObject();
+  if (!result_obj_sp)
+    return {};
+
+  StructuredData::DictionarySP dict_sp(new StructuredData::Dictionary(result_obj_sp));
+  if (dict_sp->GetType() == lldb::eStructuredDataTypeInvalid)
+    return {};
+  return dict_sp;
+}
+
+StructuredData::DictionarySP
+lldb_private::python::SWIGBridge::LLDBSwigPythonHandleOptionArgumentCompletionForScriptedCommand(PyObject *implementor,
+    llvm::StringRef &long_option, size_t pos_in_arg) {
+
+  PyErr_Cleaner py_err_cleaner(true);
+
+  PythonObject self(PyRefType::Borrowed, implementor);
+  auto pfunc = self.ResolveName<PythonCallable>("handle_option_argument_completion");
+  // If this isn't implemented, return an empty dict to signal falling back to default completion:
+  if (!pfunc.IsAllocated())
+    return {};
+
+  PythonObject result = pfunc(PythonString(long_option), PythonInteger(pos_in_arg));
+  // Returning None means do the ordinary completion
+  if (result.IsNone())
+    return {};
+
+  // Returning a boolean:
+  // True means the completion was handled, but there were no completions
+  // False means that the completion was not handled, again, do the ordinary completion:
+  if (result.GetObjectType() == PyObjectType::Boolean) {
+    if (!result.IsTrue())
+      return {};
+    // Make up a completion dictionary with the right element:
+    StructuredData::DictionarySP dict_sp(new StructuredData::Dictionary());
+    dict_sp->AddBooleanItem("no-completion", true);
+    return dict_sp;
+  }
+    
+
+  // Convert the return dictionary to a DictionarySP.
+  StructuredData::ObjectSP result_obj_sp = result.CreateStructuredObject();
+  if (!result_obj_sp)
+    return {};
+
+  StructuredData::DictionarySP dict_sp(new StructuredData::Dictionary(result_obj_sp));
+  if (dict_sp->GetType() == lldb::eStructuredDataTypeInvalid)
+    return {};
+  return dict_sp;
+}
+
 #include "lldb/Interpreter/CommandReturnObject.h"
 
 bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallParsedCommandObject(
diff --git a/lldb/docs/use/python-reference.rst b/lldb/docs/use/python-reference.rst
index 041e541a96f083..de4f219a13292f 100644
--- a/lldb/docs/use/python-reference.rst
+++ b/lldb/docs/use/python-reference.rst
@@ -587,8 +587,150 @@ say
 SBCommandReturnObject and SBStream both support this file-like behavior by
 providing write() and flush() calls at the Python layer.
 
+The commands that are added using this Class definition are what lldb calls
+"raw" commands.  The command interpreter doesn't attempt to parse the command,
+doesn't handle option values, neither generating help for them, or their
+completion.  Raw commands are useful when the arguments passed to the command
+are unstructured, and having to protect them against lldb command parsing would
+be onerous.  For instance, "expr" is a raw command.
+
+You can also add scripted commands that implement the "parsed command", where
+the options and their types are specified, as well as the argument and argument
+types.  These commands look and act like the majority of lldb commands, and you
+can also add custom completions for the options and/or the arguments if you have
+special needs.
+
+The easiest way to do this is to derive your new command from the lldb.ParsedCommand
+class.  That responds in the same way to the help & repeat command interfaces, and
+provides some convenience methods, and most importantly an LLDBOptionValueParser,
+accessed throught lldb.ParsedCommand.get_parser().  The parser is used to set
+your command definitions, and to retrieve option values in the __call__ method.
+
+To set the command definition, implement the ParsedCommand abstract method:
+
+::
+
+   def setup_command_definition(self):
+
+This is called when your command is added to lldb.  In this method you add the
+options and their types, the option help strings, etc. to the command using the API:
+
+::
+
+    def add_option(self, short_option, long_option, help, default,
+                   dest = None, required=False, groups = None,
+                   value_type=lldb.eArgTypeNone, completion_type=None,
+                   enum_values=None):
+        """
+        short_option: one character, must be unique, not required
+        long_option:  no spaces, must be unique, required
+        help:         a usage string for this option, will print in the command help
+        default:      the initial value for this option (if it has a value)
+        dest:         the name of the property that gives you access to the value for
+                      this value.  Defaults to the long option if not provided.
+        required: if true, this option must be provided or the command will error out
+        groups: Which "option groups" does this option belong to.  This can either be
+                a simple list (e.g. [1, 3, 4, 5]) or you can specify ranges by sublists:
+                so [1, [3,5]] is the same as [1, 3, 4, 5].
+        value_type: one of the lldb.eArgType enum values.  Some of the common arg
+                    types also have default completers, which will be applied automatically.
+        completion_type: currently these are values form the lldb.CompletionType enum.	If
+                         you need custom completions, implement	handle_option_argument_completion.
+        enum_values: An array of duples: ["element_name", "element_help"].  If provided,
+                     only one of the enum elements is allowed.  The value will be the
+                     element_name for the chosen enum element as a string.
+        """
+
+Similarly, you can add argument types to the command:
+
+::
+
+    def make_argument_element(self, arg_type, repeat = "optional", groups = None):
+        """
+      	arg_type: The argument type, one of the	lldb.eArgType enum values.
+      	repeat:	Choose from the	following options:
+      	      	"plain"	- one value
+      	      	"optional" - zero or more values
+      	      	"plus" - one or	more values
+      	groups:	As with	add_option.
+        """
+
+Then implement the body of the command by defining:
+
+::
+
+    def __call__(self, debugger, args_array, exe_ctx, result):
+        """This is the command callback.  The option values are
+        provided by the 'dest' properties on the parser.
+
+        args_array: This is the list of arguments provided.
+        exe_ctx: Gives the SBExecutionContext on which the
+                 command should operate.
+        result:  Any results of the command should be
+                 written into this SBCommandReturnObject.
+        """
+
+This differs from the "raw" command's __call__ in that the arguments are already
+parsed into the args_array, and the option values are set in the parser, and
+can be accessed using their property name.  The LLDBOptionValueParser class has
+a couple of other handy methods:
+
+::
+    def was_set(self, long_option_name):
+
+returns True if the option was specified on the command line.
+
+::
+    def dest_for_option(self, long_option_name):
+    """
+    This will return the value of the dest variable you defined for opt_name.
+    Mostly useful for handle_completion where you get passed the long option.
+    """
+
+lldb will handle completing your option names, and all your enum values
+automatically.  If your option or argument types have associated built-in completers,
+then lldb will also handle that completion for you.  But if you have a need for
+custom completions, either in your arguments or option values, you can handle
+completion by hand as well.  To handle completion of option value arguments,
+your lldb.ParsedCommand subclass should implement:
+
+::
+    def handle_option_argument_completion(self, args, arg_pos, cursor_pos):
+    """
+    args: A list of the arguments to the command
+    arg_pos: An index into the args list of the argument with the cursor
+    cursor_pos: The cursor position in the arg specified by arg_pos
+    """
+
+When this command is called, the command line has been parsed up to the word
+containing the cursor, and any option values set in that part of the command
+string are available from the option value parser.  That's useful for instance
+if you have a --shared-library option that would constrain the completions for,
+say, a symbol name option or argument.
+
+The return value specifies what the completion options are.  You have four
+choices:
+
+True: the completion was handled with no completions.
+
+False: the completion was not handled, forward it to the regular
+completion machinery.
+
+A dictionary with the key: "completion": there is one candidate,
+whose value is the value of the "completion" key.  Optionally you can pass a
+"mode" key whose value is either "partial" or "complete".  Return partial if
+the "completion" string is a prefix for all the completed value, and "complete"
+if it is the full completion. The default is "complete".
+
+A dictionary with the key: "values" whose value is a list of candidate completion
+strings.  The command interpreter will present those strings as the available choices.
+You can optionally include a "descriptions" key, whose value is a parallel array
+of description strings, and the completion will show the description next to
+each completion.
+
+
 One other handy convenience when defining lldb command-line commands is the
-command command script import which will import a module specified by file
+command "command script import" which will import a module specified by file
 path, so you don't have to change your PYTHONPATH for temporary scripts. It
 also has another convenience that if your new script module has a function of
 the form:
diff --git a/lldb/examples/python/cmdtemplate.py b/lldb/examples/python/cmdtemplate.py
index 9a96888508b6f2..bbdc579f7e8f75 100644
--- a/lldb/examples/python/cmdtemplate.py
+++ b/lldb/examples/python/cmdtemplate.py
@@ -26,8 +26,8 @@ def register_lldb_command(cls, debugger, module_name):
         )
 
     def setup_command_definition(self):
-
-        self.ov_parser.add_option(
+        ov_parser = self.get_parser()
+        ov_parser.add_option(
             "i",
             "in-scope",
             help = "in_scope_only = True",
@@ -36,7 +36,7 @@ def setup_command_definition(self):
             default = True,
         )
 
-        self.ov_parser.add_option(
+        ov_parser.add_option(
             "i",
             "in-scope",
             help = "in_scope_only = True",
@@ -45,7 +45,7 @@ def setup_command_definition(self):
             default=True,
         )
         
-        self.ov_parser.add_option(
+        ov_parser.add_option(
             "a",
             "arguments",
             help = "arguments = True",
@@ -54,7 +54,7 @@ def setup_command_definition(self):
             default = True,
         )
 
-        self.ov_parser.add_option(
+        ov_parser.add_option(
             "l",
             "locals",
             help = "locals = True",
@@ -63,7 +63,7 @@ def setup_command_definition(self):
             default = True,
         )
 
-        self.ov_parser.add_option(
+        ov_parser.add_option(
             "s",
             "statics",
             help = "statics = True",
@@ -100,8 +100,9 @@ def __call__(self, debugger, command, exe_ctx, result):
             result.SetError("invalid frame")
             return
 
+        ov_parser = self.get_parser()
         variables_list = frame.GetVariables(
-            self.ov_parser.arguments, self.ov_parser.locals, self.ov_parser.statics, self.ov_parser.inscope
+            ov_parser.arguments, ov_parser.locals, ov_parser.statics, ov_parser.inscope
         )
         variables_count = variables_list.GetSize()
         if variables_count == 0:
diff --git a/lldb/examples/python/templates/parsed_cmd.py b/lldb/examples/python/templates/parsed_cmd.py
index 06124adf43420a..c2be2d8d835f33 100644
--- a/lldb/examples/python/templates/parsed_cmd.py
+++ b/lldb/examples/python/templates/parsed_cmd.py
@@ -43,7 +43,65 @@ def __call__(self, debugger, args_list, exe_ctx, result):
 will return True if the user set this option, and False if it was left at its default
 value.
 
-There are example commands in the lldb testsuite at:
+Custom Completions:
+
+You can also implement custom completers for your custom command, either for the
+arguments to your command or to the option values in your command.  If you use enum
+values or if your option/argument uses is one of the types we have completers for,
+you should not need to do this.  But if you have your own completeable types, or if
+you want completion of one option to be conditioned by other options on the command
+line, you can use this interface to take over the completion.  
+
+You can choose to add a completion for the option values defined for your command,
+or for the arguments, separately.  For the option values, define:
+
+def handle_option_argument_completion(self, long_option, cursor_pos):
+
+The line to be completed will be parsed up to the option containint the cursor position, 
+and the values will be set in the OptionValue parser object.  long_option will be
+the option name containing the cursor, and cursor_pos will be the position of the cursor
+in that option's value.  You can call the OVParser.dest_for_option(long_option) to get the
+value for that option.  The other options that came before the cursor in the command
+line will also be set in the OV Parser when the completion handler is called.
+
+For argument values, define:
+
+def handle_argument_completion(self, args, arg_pos, cursor_pos):
+
+Again, the command line will be parsed up to the cursor position, and all the options
+before the cursor pose will be set in the OVParser.  args is a python list of the
+arguments, arg_pos is the index of the argument with the cursor, and cursor_pos is
+the position of the cursor in the argument.
+
+In both cases, the return value determines the completion.
+
+Return False to mean "Not Handled" - in which case lldb will fall back on the
+standard completion machinery.
+
+Return True to mean "Handled with no completions".
+
+If there is a single unique completion, return a Python dictionary with two elements:
+
+return {"completion" : "completed_value", "mode" : <"partial", "complete">}
+
+If the mode is "partial", then the completion is to a common base, if it is "complete"
+then the argument is considered done - mostly meaning lldb will put a space after the
+completion string.  "complete" is the default if no "mode" is specified.
+
+If there are multiple completion options, then return:
+
+return {"values" : ["option1", "option2"]}
+
+Optionally, you can return a parallel array of "descriptions" which the completer will 
+print alongside the options:
+
+return {"values" : ["option1", "option2"], "descriptions" : ["the first option", "the second option"]}
+
+The cmdtemplate example currently uses the parsed command infrastructure:
+
+llvm-project/lldb/examples/python/cmdtemplate.py
+
+There are also a few example commands in the lldb testsuite at:
 
 llvm-project/lldb/test/API/commands/command/script/add/test_commands.py
 """
@@ -229,7 +287,11 @@ def was_set(self, opt_name):
         """ Call this in the __call__ method of your command to determine
             whether this option was set on the command line.  It is sometimes
             useful to know whether an option has the default value because the
-            user set it explicitly (was_set -> True) or not.  """
+            user set it explicitly (was_set -> True) or not.
+            You can also call this in a handle_completion method, but it will
+            currently only report true values for the options mentioned 
+            BEFORE the cursor point in the command line.
+        """
 
         elem = self.get_option_element(opt_name)
         if not elem:
@@ -239,6 +301,16 @@ def was_set(self, opt_name):
         except AttributeError:
             return False
 
+    def dest_for_option(self, opt_name):
+        """ This will return the value of the dest variable you defined for opt_name.
+            Mostly useful for handle_completion where you get passed the long option.
+        """
+        elem = self.get_option_element(opt_name)
+        if not elem:
+            return None
+        value = self.__dict__[elem["dest"]]
+        return value
+
     def add_option(self, short_option, long_option, help, default,
                    dest = None, required=False, groups = None,
                    value_type=lldb.eArgTypeNone, completion_type=None,
@@ -251,11 +323,13 @@ def add_option(self, short_option, long_option, help, default,
         dest: the name of the property that gives you access to the value for
                  this value.  Defaults to the long option if not provided.
         required: if true, this option must be provided or the command will error out
-        groups: Which "option groups" does this option belong to
+        groups: Which "option groups" does this option belong to.  This can either be
+                a simple list (e.g. [1, 3, 4, 5]) or you can specify ranges by sublists:
+                so [1, [3,5]] is the same as [1, 3, 4, 5].
         value_type: one of the lldb.eArgType enum values.  Some of the common arg
                     types also have default completers, which will be applied automatically.
-        completion_type: currently these are values form the lldb.CompletionType enum, I
-                         haven't done custom completions yet.
+        completion_type: currently these are values form the lldb.CompletionType enum.  If
+                         you need custom completions, implement handle_option_argument_completion.
         enum_values: An array of duples: ["element_name", "element_help"].  If provided,
                      only one of the enum elements is allowed.  The value will be the 
                      element_name for the chosen enum element as a string. 
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index addb1394ab5652..6fc97466892ca9 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -436,6 +436,21 @@ class ScriptInterpreter : public PluginInterface {
                                      Args &args) {
     return std::nullopt;
   }
+  
+  virtual StructuredData::DictionarySP HandleArgumentCompletionForScriptedCommand(
+      StructuredData::GenericSP impl_obj_sp, std::vector<llvm::StringRef> &args, 
+      size_t args_pos, size_t char_in_arg)
+  {
+    return {};
+  }
+
+  virtual StructuredData::DictionarySP 
+  HandleOptionArgumentCompletionForScriptedCommand(
+      StructuredData::GenericSP impl_obj_sp, llvm::StringRef &long_name, 
+      size_t char_in_arg)
+  {
+    return {};
+  }
 
   virtual bool RunScriptFormatKeyword(const char *impl_function,
                                       Process *process, std::string &output,
diff --git a/lldb/include/lldb/Utility/CompletionRequest.h b/lldb/include/lldb/Utility/CompletionRequest.h
index 1a2b1d639950fc..242ff383047410 100644
--- a/lldb/include/lldb/Utility/CompletionRequest.h
+++ b/lldb/include/lldb/Utility/CompletionRequest.h
@@ -138,6 +138,8 @@ class CompletionRequest {
   const Args::ArgEntry &GetParsedArg() {
     return GetParsedLine()[GetCursorIndex()];
   }
+  
+  size_t GetCursorCharPos() const { return m_cursor_char_position; }
 
   /// Drops the first argument from the argument list.
   void ShiftArguments() {
diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp
index e3291640fa9352..ec1c4be3773030 100644
--- a/lldb/source/Commands/CommandObjectCommands.cpp
+++ b/lldb/source/Commands/CommandObjectCommands.cpp
@@ -1636,7 +1636,125 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
     }
 
     size_t GetNumOptions() { return m_num_options; }
+    
+    void PrepareOptionsForCompletion(CompletionRequest &request, 
+                                    OptionElementVector &option_vec,
+                                    ExecutionContext *exe_ctx) {
+      // I'm not sure if we'll get into trouble doing an option parsing start
+      // and end in this context.  If so, then I'll have to directly tell the
+      // scripter to do this.
+      OptionParsingStarting(exe_ctx);
+      auto opt_defs = GetDefinitions();
+      
+      // Iterate through the options we found so far, and push them into
+      // the scripted side.
+      for (auto option_elem : option_vec) {
+        int cur_defs_index = option_elem.opt_defs_index;
+        // If we don't recognize this option we can't set it.
+        if (cur_defs_index == OptionArgElement::eUnrecognizedArg ||
+            cur_defs_index == OptionArgElement::eBareDash ||
+            cur_defs_index == OptionArgElement::eBareDoubleDash)
+          continue;
+        bool option_has_arg = opt_defs[cur_defs_index].option_has_arg;
+        llvm::StringRef cur_arg_value;
+        if (option_has_arg) {
+          int cur_arg_pos = option_elem.opt_arg_pos;
+          if (cur_arg_pos != OptionArgElement::eUnrecognizedArg &&
+              cur_arg_pos != OptionArgElement::eBareDash &&
+              cur_arg_pos != OptionArgElement::eBareDoubleDash) {
+            cur_arg_value = request.GetParsedLine().GetArgumentAtIndex(cur_arg_pos);
+
+          }
+        }
+        SetOptionValue(cur_defs_index, cur_arg_value, exe_ctx);
+      }
+      OptionParsingFinished(exe_ctx);
+    }
+    
+    void ProcessCompletionDict(CompletionRequest &request, StructuredData::DictionarySP &completion_dict_sp) {
+      // We don't know how to process an empty completion dict, our callers have
+      // to do that.
+      assert(completion_dict_sp && "Must have valid completion dict");
+      // First handle the case of a single completion:
+      llvm::StringRef completion;
+      // If the dictionary has one element "no-completion" then we return here
+      if (completion_dict_sp->GetValueForKeyAsString("no-completion", completion))
+        return;
+
+      if (completion_dict_sp->GetValueForKeyAsString("completion", completion)) {
+        llvm::StringRef mode_str;
+        CompletionMode mode = CompletionMode::Normal;
+        if (completion_dict_sp->GetValueForKeyAsString("mode", mode_str)) {
+          if (mode_str == "complete")
+            mode = CompletionMode::Normal;
+          else if (mode_str == "partial")
+            mode = CompletionMode::Partial;
+          else {
+            // FIXME - how do I report errors here?
+            return;
+          }
+        }
+        request.AddCompletion(completion, "", mode);
+        return;
+      }
+      // The completions are required, the descriptions are not:
+      StructuredData::Array *completions;
+      StructuredData::Array *descriptions;
+      if (completion_dict_sp->GetValueForKeyAsArray("values", completions)) {
+        completion_dict_sp->GetValueForKeyAsArray("descriptions", descriptions);
+        size_t num_completions = completions->GetSize();
+        for (size_t idx = 0; idx < num_completions; idx++) {
+          auto val = completions->GetItemAtIndexAsString(idx);
+          if (!val)
+            // FIXME: How do I report this error?
+            return;
+          
+          if (descriptions) {
+            auto desc = descriptions->GetItemAtIndexAsString(idx);
+            request.AddCompletion(*val, desc ? *desc : "");
+          } else
+            request.AddCompletion(*val);
+        }
+      }
+    }
+    
+    void
+    HandleOptionArgumentCompletion(lldb_private::CompletionRequest &request,
+                                   OptionElementVector &option_vec,
+                                   int opt_element_index,
+                                   CommandInterpreter &interpreter) override {
+      ScriptInterpreter *scripter = interpreter.GetDebugger().GetScriptInterpreter();
+      
+      if (!scripter)
+        return;
 
+      ExecutionContext exe_ctx = interpreter.GetExecutionContext();
+      PrepareOptionsForCompletion(request, option_vec, &exe_ctx);
+        
+      auto defs = GetDefinitions();
+      
+      size_t defs_index = option_vec[opt_element_index].opt_defs_index;
+      llvm::StringRef option_name = defs[defs_index].long_option;
+      bool is_enum = defs[defs_index].enum_values.size() != 0;
+      if (option_name.empty())
+        return;
+      // If this is an enum, we don't call the custom completer, just let the
+      // regular option completer handle that:
+      StructuredData::DictionarySP completion_dict_sp;
+      if (!is_enum)
+        completion_dict_sp
+            = scripter->HandleOptionArgumentCompletionForScriptedCommand(m_cmd_obj_sp, 
+                option_name, request.GetCursorCharPos());
+
+      if (!completion_dict_sp) {
+        Options::HandleOptionArgumentCompletion(request, option_vec, 
+                                                opt_element_index, interpreter);
+        return;
+      }
+      
+      ProcessCompletionDict(request, completion_dict_sp);
+    }
+    
   private:
     struct EnumValueStorage {
       EnumValueStorage() {
@@ -1877,6 +1995,75 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
   Status GetOptionsError() { return m_options_error.Clone(); }
   Status GetArgsError() { return m_args_error.Clone(); }
   bool WantsCompletion() override { return true; }
+  
+private:
+  void PrepareOptionsForCompletion(CompletionRequest &request, 
+                                   OptionElementVector &option_vec) {
+    // First, we have to tell the Scripted side to set the values in its
+    // option store, then we call into the handle_completion passing in
+    // an array of the args, the arg index and the cursor position in the arg.
+    // We want the script side to have a chance to clear its state, so tell
+    // it argument parsing has started:
+    Options *options = GetOptions();
+    // If there are not options, this will be nullptr, and in that case we
+    // can just skip setting the options on the scripted side:
+    if (options)
+      m_options.PrepareOptionsForCompletion(request, option_vec, &m_exe_ctx);
+
+  }
+public:  
+  void HandleArgumentCompletion(CompletionRequest &request, 
+                                OptionElementVector &option_vec) override {
+    ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
+        
+    if (!scripter)
+      return;
+
+    // Set up the options values on the scripted side:
+    PrepareOptionsForCompletion(request, option_vec);
+    
+    // Now we have to make up the argument list.
+    // The ParseForCompletion only identifies tokens in the m_parsed_line
+    // it doesn't remove the options leaving only the args as it does for
+    // the regular Parse, so we have to filter out the option ones using the
+    // option_element_vector:
+    
+    Options *options = GetOptions();
+    auto defs = options->GetDefinitions(); 
+    
+    std::unordered_set<size_t> option_slots;
+    for (const auto &elem : option_vec) {
+      if (elem.opt_defs_index == -1)
+        continue;
+      option_slots.insert(elem.opt_pos);
+      if (defs[elem.opt_defs_index].option_has_arg)
+        option_slots.insert(elem.opt_arg_pos);
+    }
+    
+    std::vector<llvm::StringRef> args_vec;
+    Args &args = request.GetParsedLine();
+    size_t num_args = args.GetArgumentCount();
+    size_t cursor_idx = request.GetCursorIndex();
+    size_t args_elem_pos = cursor_idx;
+    
+    for (size_t idx = 0; idx < num_args; idx++) {
+      if (option_slots.count(idx) == 0)
+        args_vec.push_back(args[idx].ref());
+      else
+        if (idx < cursor_idx)
+          args_elem_pos--;
+    }
+    StructuredData::DictionarySP completion_dict_sp
+        = scripter->HandleArgumentCompletionForScriptedCommand(m_cmd_obj_sp, 
+            args_vec, args_elem_pos, request.GetCursorCharPos());
+
+    if (!completion_dict_sp) {
+      CommandObject::HandleArgumentCompletion(request, option_vec);
+      return;
+    }
+    
+    m_options.ProcessCompletionDict(request, completion_dict_sp);
+  }
 
   bool IsRemovable() const override { return true; }
 
diff --git a/lldb/source/Interpreter/Options.cpp b/lldb/source/Interpreter/Options.cpp
index b8a3f68a49b1cf..3888a5812628cd 100644
--- a/lldb/source/Interpreter/Options.cpp
+++ b/lldb/source/Interpreter/Options.cpp
@@ -661,7 +661,9 @@ bool Options::HandleOptionCompletion(CompletionRequest &request,
 
     } else if (opt_arg_pos == request.GetCursorIndex()) {
       // Okay the cursor is on the completion of an argument. See if it has a
-      // completion, otherwise return no matches.
+      // completion, otherwise return no matches.  Note, opt_defs_index == -1
+      // means we're after an option, but that option doesn't exist.  We'll
+      // end up treating that as an argument.  Not sure we can do much better.
       if (opt_defs_index != -1) {
         HandleOptionArgumentCompletion(request, opt_element_vector, i,
                                        interpreter);
@@ -688,7 +690,6 @@ void Options::HandleOptionArgumentCompletion(
   int opt_defs_index = opt_element_vector[opt_element_index].opt_defs_index;
 
   // See if this is an enumeration type option, and if so complete it here:
-
   const auto &enum_values = opt_defs[opt_defs_index].enum_values;
   if (!enum_values.empty())
     for (const auto &enum_value : enum_values)
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
index 97a3837fd7aa62..29c2bb7932d0de 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -210,6 +210,14 @@ class SWIGBridge {
   LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
                                                    std::string &command);
 
+  static StructuredData::DictionarySP 
+  LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(PyObject *implementor,
+    std::vector<llvm::StringRef> &args_impl, size_t args_pos, size_t pos_in_arg);
+  
+  static StructuredData::DictionarySP 
+  LLDBSwigPythonHandleOptionArgumentCompletionForScriptedCommand(PyObject *implementor,
+    llvm::StringRef &long_option, size_t pos_in_arg);
+  
   static bool LLDBSwigPythonCallModuleInit(const char *python_module_name,
                                            const char *session_dictionary_name,
                                            lldb::DebuggerSP debugger);
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index 63691d24f0dadb..94388b3dac8947 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -2766,6 +2766,46 @@ ScriptInterpreterPythonImpl::GetRepeatCommandForScriptedCommand(
   return ret_val;
 }
 
+StructuredData::DictionarySP 
+ScriptInterpreterPythonImpl::HandleArgumentCompletionForScriptedCommand(
+      StructuredData::GenericSP impl_obj_sp, std::vector<llvm::StringRef> &args, 
+      size_t args_pos, size_t char_in_arg) {
+  StructuredData::DictionarySP completion_dict_sp;
+  if (!impl_obj_sp || !impl_obj_sp->IsValid())
+    return completion_dict_sp;
+
+  {
+    Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN,
+                   Locker::FreeLock);
+
+    completion_dict_sp = 
+      SWIGBridge::LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(
+      static_cast<PyObject *>(impl_obj_sp->GetValue()), args, args_pos, 
+                              char_in_arg);
+  }
+  return completion_dict_sp;
+}
+
+StructuredData::DictionarySP 
+ScriptInterpreterPythonImpl::HandleOptionArgumentCompletionForScriptedCommand(
+      StructuredData::GenericSP impl_obj_sp, llvm::StringRef &long_option, 
+      size_t char_in_arg) {
+  StructuredData::DictionarySP completion_dict_sp;
+  if (!impl_obj_sp || !impl_obj_sp->IsValid())
+    return completion_dict_sp;
+
+  {
+    Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN,
+                   Locker::FreeLock);
+
+    completion_dict_sp = 
+      SWIGBridge::LLDBSwigPythonHandleOptionArgumentCompletionForScriptedCommand(
+      static_cast<PyObject *>(impl_obj_sp->GetValue()), long_option, 
+                              char_in_arg);
+  }
+  return completion_dict_sp;
+}
+
 /// In Python, a special attribute __doc__ contains the docstring for an object
 /// (function, method, class, ...) if any is defined Otherwise, the attribute's
 /// value is None.
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
index 85d79955e45efc..12acc841c904bf 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
@@ -173,6 +173,14 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
   GetRepeatCommandForScriptedCommand(StructuredData::GenericSP impl_obj_sp,
                                      Args &args) override;
 
+  StructuredData::DictionarySP HandleArgumentCompletionForScriptedCommand(
+      StructuredData::GenericSP impl_obj_sp, std::vector<llvm::StringRef> &args, 
+      size_t args_pos, size_t char_in_arg) override;
+
+  StructuredData::DictionarySP HandleOptionArgumentCompletionForScriptedCommand(
+      StructuredData::GenericSP impl_obj_sp, llvm::StringRef &long_options, 
+      size_t char_in_arg) 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 c7680e9bb7f418..40a8cb0c2dc6fa 100644
--- a/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
+++ b/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
@@ -68,6 +68,39 @@ def run_one_repeat(self, commands, expected_num_errors):
 
         return results
 
+    def handle_completion(self, cmd_str, exp_num_completions, exp_matches, exp_descriptions, match_description):
+        matches = lldb.SBStringList()
+        descriptions = lldb.SBStringList()
+
+        interp = self.dbg.GetCommandInterpreter()
+        num_completions = interp.HandleCompletionWithDescriptions(
+            cmd_str, len(cmd_str), 0, 1000, matches, descriptions
+        )
+        self.assertEqual(num_completions, exp_num_completions, "Number of completions is right.")
+        num_matches = matches.GetSize()
+        self.assertEqual(num_matches, exp_matches.GetSize(), "matches and expected matches of different lengths")
+        num_descriptions = descriptions.GetSize()
+        if match_description:
+            self.assertEqual(num_descriptions, exp_descriptions.GetSize(), "descriptions and expected of different lengths")
+            
+        self.assertEqual(
+            matches.GetSize(), num_completions + 1, "The first element is the complete additional text"
+        )
+        
+
+        for idx in range(0, num_matches):
+            match = matches.GetStringAtIndex(idx)
+            exp_match = exp_matches.GetStringAtIndex(idx)
+            self.assertEqual(
+                match, exp_match , f"{match} did not match expectation: {exp_match}"
+            )
+        if match_description:
+            desc = descriptions.GetStringAtIndex(idx)
+            exp_desc = exp_descriptions.GetStringAtIndex(idx)
+            self.assertEqual(
+                desc, exp_desc, f"{desc} didn't match expectation: {exp_desc}"
+            )
+        
     def pycmd_tests(self):
         source_dir = self.getSourceDir()
         test_file_path = os.path.join(source_dir, "test_commands.py")
@@ -175,25 +208,14 @@ def cleanup():
         matches = lldb.SBStringList()
         descriptions = lldb.SBStringList()
 
+
         # First try an enum completion:
-        num_completions = interp.HandleCompletionWithDescriptions(
-            "no-args -e f", 12, 0, 1000, matches, descriptions
-        )
-        self.assertEqual(num_completions, 1, "Only one completion for foo")
-        self.assertEqual(
-            matches.GetSize(), 2, "The first element is the complete additional text"
-        )
-        self.assertEqual(
-            matches.GetStringAtIndex(0), "oo ", "And we got the right extra characters"
-        )
-        self.assertEqual(
-            matches.GetStringAtIndex(1), "foo", "And we got the right match"
-        )
-        self.assertEqual(
-            descriptions.GetSize(), 2, "descriptions matche the return length"
+        # Note - this is an enum so all the values are returned:
+        matches.AppendList(["oo ", "foo"], 2)
+         
+        self.handle_completion(
+            "no-args -e f", 1, matches, descriptions, False
         )
-        # FIXME: we don't return descriptions for enum elements
-        # self.assertEqual(descriptions.GetStringAtIndex(1), "does foo things", "And we got the right description")
 
         # Now try an internal completer, the on disk file one is handy:
         partial_name = os.path.join(source_dir, "test_")
@@ -201,24 +223,11 @@ def cleanup():
 
         matches.Clear()
         descriptions.Clear()
-        num_completions = interp.HandleCompletionWithDescriptions(
-            cmd_str, len(cmd_str) - 1, 0, 1000, matches, descriptions
-        )
-        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(
-            matches.GetStringAtIndex(0),
-            "commands.py' ",
-            "And we got the right extra characters",
+        matches.AppendList(["commands.py' ", test_file_path], 2)
+        # We don't have descriptions for the file path completer:
+        self.handle_completion(
+            cmd_str, 1, matches, descriptions, False
         )
-        self.assertEqual(
-            matches.GetStringAtIndex(1), test_file_path, "And we got the right match"
-        )
-        self.assertEqual(
-            descriptions.GetSize(), 2, "descriptions match the return length"
-        )
-        # FIXME: we don't return descriptions for enum elements
-        # self.assertEqual(descriptions.GetStringAtIndex(1), "does foo things", "And we got the right description")
 
         # Try a command with arguments.
         # FIXME: It should be enough to define an argument and it's type to get the completer
@@ -231,6 +240,44 @@ def cleanup():
             substrs=["0: First Argument", "1: Second Argument"],
         )
 
+        # Now test custom completions - two-args has both option and arg completers.  In both
+        # completers we return different values if the -p option is set, so we can test that too:
+        matches.Clear()
+        descriptions.Clear()
+        cmd_str = "two-args -p something -c other_"
+        matches.AppendString("something ")
+        matches.AppendString("other_something")
+        # This is a full match so no descriptions: 
+        self.handle_completion(cmd_str, 1, matches, descriptions, False)
+
+        matches.Clear()
+        descriptions.Clear()
+        cmd_str = "two-args -c other_"
+        matches.AppendList(["", "other_nice", "other_not_nice", "other_mediocre"], 4)
+        # The option doesn't return descriptions either:
+        self.handle_completion(cmd_str, 3, matches, descriptions, False)
+
+        # Now try the argument - it says "no completions" if the proc_name was set:
+        matches.Clear()
+        descriptions.Clear()
+        cmd_str = "two-args -p something arg"
+        matches.AppendString("")
+        self.handle_completion(cmd_str, 0, matches, descriptions, False)
+
+        cmd_str = "two-args arg_"
+        matches.Clear()
+        descriptions.Clear()
+        matches.AppendList(["", "arg_cool", "arg_yuck"], 3)
+        descriptions.AppendList(["", "good idea", "bad idea"], 3)
+        self.handle_completion(cmd_str, 2, matches, descriptions, True)
+
+        # This one gets a single unique match:
+        cmd_str = "two-args correct_"
+        matches.Clear()
+        descriptions.Clear()
+        matches.AppendList(["answer ", "correct_answer"], 2)
+        self.handle_completion(cmd_str, 1, matches, descriptions, False)
+        
         # Now make sure get_repeat_command works properly:
 
         # no-args turns off auto-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 fcde6cd3ef6dc6..aa38e9ac511a8d 100644
--- a/lldb/test/API/commands/command/script/add/test_commands.py
+++ b/lldb/test/API/commands/command/script/add/test_commands.py
@@ -18,7 +18,7 @@ def __call__(self, debugger, args_array, exe_ctx, result):
             for long_option, elem in opt_def.items():
                 dest = elem["dest"]
                 result.AppendMessage(
-                    f"{long_option} (set: {elem['_value_set']}): {object.__getattribute__(self.ov_parser, dest)}\n"
+                    f"{long_option} (set: {elem['_value_set']}): {object.__getattribute__(self.get_parser(), dest)}\n"
                 )
         else:
             result.AppendMessage("No options\n")
@@ -31,7 +31,6 @@ def __call__(self, debugger, args_array, exe_ctx, result):
                 f"{idx}: {args_array.GetItemAtIndex(idx).GetStringValue(10000)}\n"
             )
 
-
 # Use these to make sure that get_repeat_command sends the right
 # command.
 no_args_repeat = None
@@ -49,7 +48,8 @@ def register_lldb_command(cls, debugger, module_name):
         ParsedCommand.do_register_cmd(cls, debugger, module_name)
 
     def setup_command_definition(self):
-        self.ov_parser.add_option(
+        ov_parser = self.get_parser()
+        ov_parser.add_option(
             "b",
             "bool-arg",
             "a boolean arg, defaults to True",
@@ -59,7 +59,7 @@ def setup_command_definition(self):
             default=True,
         )
 
-        self.ov_parser.add_option(
+        ov_parser.add_option(
             "s",
             "shlib-name",
             "A shared library name.",
@@ -69,7 +69,7 @@ def setup_command_definition(self):
             default=None,
         )
 
-        self.ov_parser.add_option(
+        ov_parser.add_option(
             "d",
             "disk-file-name",
             "An on disk filename",
@@ -78,7 +78,7 @@ def setup_command_definition(self):
             default=None,
         )
 
-        self.ov_parser.add_option(
+        ov_parser.add_option(
             "l",
             "line-num",
             "A line number",
@@ -88,7 +88,7 @@ def setup_command_definition(self):
             default=0,
         )
 
-        self.ov_parser.add_option(
+        ov_parser.add_option(
             "e",
             "enum-option",
             "An enum, doesn't actually do anything",
@@ -126,8 +126,9 @@ def register_lldb_command(cls, debugger, module_name):
         ParsedCommand.do_register_cmd(cls, debugger, module_name)
 
     def setup_command_definition(self):
-        self.ov_parser.add_argument_set(
-            [self.ov_parser.make_argument_element(lldb.eArgTypeSourceFile, "plain")]
+        ov_parser = self.get_parser()
+        ov_parser.add_argument_set(
+            [ov_parser.make_argument_element(lldb.eArgTypeSourceFile, "plain")]
         )
 
     def get_repeat_command(self, command):
@@ -154,7 +155,8 @@ def register_lldb_command(cls, debugger, module_name):
         ParsedCommand.do_register_cmd(cls, debugger, module_name)
 
     def setup_command_definition(self):
-        self.ov_parser.add_option(
+        ov_parser = self.get_parser()
+        ov_parser.add_option(
             "l",
             "language",
             "language defaults to None",
@@ -164,7 +166,7 @@ def setup_command_definition(self):
             default=None,
         )
 
-        self.ov_parser.add_option(
+        ov_parser.add_option(
             "c",
             "log-channel",
             "log channel - defaults to lldb",
@@ -174,7 +176,7 @@ def setup_command_definition(self):
             default="lldb",
         )
 
-        self.ov_parser.add_option(
+        ov_parser.add_option(
             "p",
             "process-name",
             "A process name, defaults to None",
@@ -183,23 +185,23 @@ def setup_command_definition(self):
             default=None,
         )
 
-        self.ov_parser.add_argument_set(
+        ov_parser.add_argument_set(
             [
-                self.ov_parser.make_argument_element(
+                ov_parser.make_argument_element(
                     lldb.eArgTypeClassName, "plain", [1, 2]
                 ),
-                self.ov_parser.make_argument_element(
+                ov_parser.make_argument_element(
                     lldb.eArgTypeOffset, "optional", [1, 2]
                 ),
             ]
         )
 
-        self.ov_parser.add_argument_set(
+        ov_parser.add_argument_set(
             [
-                self.ov_parser.make_argument_element(
+                ov_parser.make_argument_element(
                     lldb.eArgTypePythonClass, "plain", [3, 4]
                 ),
-                self.ov_parser.make_argument_element(
+                ov_parser.make_argument_element(
                     lldb.eArgTypePid, "optional", [3, 4]
                 ),
             ]
@@ -210,6 +212,32 @@ def get_repeat_command(self, command):
         two_arg_repeat = command
         return command + " THIRD_ARG"
 
+    def handle_option_argument_completion(self, long_option, cursor_pos):
+        ov_parser = self.get_parser()
+        value = ov_parser.dest_for_option(long_option)[0:cursor_pos+1]
+        proc_value = ov_parser.proc_name
+        if proc_value != None:
+            new_str = value+proc_value
+            ret_arr = {"completion" : new_str, "mode" : "partial"}
+            return ret_arr
+
+        ret_arr = {"values" : [value+"nice", value+"not_nice", value+"mediocre"]}
+        return ret_arr
+        
+    def handle_argument_completion(self, args, arg_pos, cursor_pos):
+        ov_parser = self.get_parser()
+        orig_arg = args[arg_pos][0:cursor_pos]
+        if orig_arg == "correct_":
+            ret_arr = {"completion": "correct_answer"}
+            return ret_arr
+
+        if ov_parser.was_set("process-name"):
+            # No completions if proc_name was set.
+            return True
+
+        ret_arr = {"values" : [orig_arg + "cool", orig_arg+"yuck"], "descriptions" : ["good idea", "bad idea"]}
+        return ret_arr
+
     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 f2746c3e2516fd..d7a4952642e872 100644
--- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
+++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
@@ -206,6 +206,19 @@ LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
   return std::nullopt;
 }
 
+StructuredData::DictionarySP 
+LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(PyObject *implementor,
+    std::vector<llvm::StringRef> &args, size_t args_pos, size_t pos_in_arg) {
+  return {};
+}
+
+StructuredData::DictionarySP 
+LLDBSwigPythonHandleOptionArgumentCompletionForScriptedCommand(
+      PyObject *implementor, llvm::StringRef &long_options, size_t char_in_arg) {
+  return {};
+}
+
+
 bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallModuleInit(
     const char *python_module_name, const char *session_dictionary_name,
     lldb::DebuggerSP debugger) {

>From d9145381d0527908e188928645e2eacee515c57d Mon Sep 17 00:00:00 2001
From: Jim Ingham <jingham at apple.com>
Date: Tue, 17 Sep 2024 17:08:00 -0700
Subject: [PATCH 2/3] formatting

---
 lldb/examples/python/templates/parsed_cmd.py  | 22 ++---
 .../lldb/Interpreter/ScriptInterpreter.h      | 17 ++--
 lldb/include/lldb/Utility/CompletionRequest.h |  2 +-
 .../source/Commands/CommandObjectCommands.cpp | 88 ++++++++++---------
 .../Python/SWIGPythonBridge.h                 | 17 ++--
 .../Python/ScriptInterpreterPython.cpp        | 28 +++---
 .../Python/ScriptInterpreterPythonImpl.h      |  4 +-
 .../script/add/TestAddParsedCommand.py        | 51 +++++++----
 .../command/script/add/test_commands.py       | 19 ++--
 .../Python/PythonTestSuite.cpp                | 12 +--
 10 files changed, 139 insertions(+), 121 deletions(-)

diff --git a/lldb/examples/python/templates/parsed_cmd.py b/lldb/examples/python/templates/parsed_cmd.py
index c2be2d8d835f33..93ca8f833ec856 100644
--- a/lldb/examples/python/templates/parsed_cmd.py
+++ b/lldb/examples/python/templates/parsed_cmd.py
@@ -284,13 +284,13 @@ def set_option_value(self, exe_ctx, opt_name, opt_value):
         return True
 
     def was_set(self, opt_name):
-        """ Call this in the __call__ method of your command to determine
-            whether this option was set on the command line.  It is sometimes
-            useful to know whether an option has the default value because the
-            user set it explicitly (was_set -> True) or not.
-            You can also call this in a handle_completion method, but it will
-            currently only report true values for the options mentioned 
-            BEFORE the cursor point in the command line.
+        """Call this in the __call__ method of your command to determine
+        whether this option was set on the command line.  It is sometimes
+        useful to know whether an option has the default value because the
+        user set it explicitly (was_set -> True) or not.
+        You can also call this in a handle_completion method, but it will
+        currently only report true values for the options mentioned
+        BEFORE the cursor point in the command line.
         """
 
         elem = self.get_option_element(opt_name)
@@ -302,8 +302,8 @@ def was_set(self, opt_name):
             return False
 
     def dest_for_option(self, opt_name):
-        """ This will return the value of the dest variable you defined for opt_name.
-            Mostly useful for handle_completion where you get passed the long option.
+        """This will return the value of the dest variable you defined for opt_name.
+        Mostly useful for handle_completion where you get passed the long option.
         """
         elem = self.get_option_element(opt_name)
         if not elem:
@@ -331,8 +331,8 @@ def add_option(self, short_option, long_option, help, default,
         completion_type: currently these are values form the lldb.CompletionType enum.  If
                          you need custom completions, implement handle_option_argument_completion.
         enum_values: An array of duples: ["element_name", "element_help"].  If provided,
-                     only one of the enum elements is allowed.  The value will be the 
-                     element_name for the chosen enum element as a string. 
+                     only one of the enum elements is allowed.  The value will be the
+                     element_name for the chosen enum element as a string.
         """
         if not dest:
             dest = long_option
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 6fc97466892ca9..7561585d9886a6 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -436,19 +436,18 @@ class ScriptInterpreter : public PluginInterface {
                                      Args &args) {
     return std::nullopt;
   }
-  
-  virtual StructuredData::DictionarySP HandleArgumentCompletionForScriptedCommand(
-      StructuredData::GenericSP impl_obj_sp, std::vector<llvm::StringRef> &args, 
-      size_t args_pos, size_t char_in_arg)
-  {
+
+  virtual StructuredData::DictionarySP
+  HandleArgumentCompletionForScriptedCommand(
+      StructuredData::GenericSP impl_obj_sp, std::vector<llvm::StringRef> &args,
+      size_t args_pos, size_t char_in_arg) {
     return {};
   }
 
-  virtual StructuredData::DictionarySP 
+  virtual StructuredData::DictionarySP
   HandleOptionArgumentCompletionForScriptedCommand(
-      StructuredData::GenericSP impl_obj_sp, llvm::StringRef &long_name, 
-      size_t char_in_arg)
-  {
+      StructuredData::GenericSP impl_obj_sp, llvm::StringRef &long_name,
+      size_t char_in_arg) {
     return {};
   }
 
diff --git a/lldb/include/lldb/Utility/CompletionRequest.h b/lldb/include/lldb/Utility/CompletionRequest.h
index 242ff383047410..650158a197dbd9 100644
--- a/lldb/include/lldb/Utility/CompletionRequest.h
+++ b/lldb/include/lldb/Utility/CompletionRequest.h
@@ -138,7 +138,7 @@ class CompletionRequest {
   const Args::ArgEntry &GetParsedArg() {
     return GetParsedLine()[GetCursorIndex()];
   }
-  
+
   size_t GetCursorCharPos() const { return m_cursor_char_position; }
 
   /// Drops the first argument from the argument list.
diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp
index ec1c4be3773030..845b89a75b7b39 100644
--- a/lldb/source/Commands/CommandObjectCommands.cpp
+++ b/lldb/source/Commands/CommandObjectCommands.cpp
@@ -1636,16 +1636,16 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
     }
 
     size_t GetNumOptions() { return m_num_options; }
-    
-    void PrepareOptionsForCompletion(CompletionRequest &request, 
-                                    OptionElementVector &option_vec,
-                                    ExecutionContext *exe_ctx) {
+
+    void PrepareOptionsForCompletion(CompletionRequest &request,
+                                     OptionElementVector &option_vec,
+                                     ExecutionContext *exe_ctx) {
       // I'm not sure if we'll get into trouble doing an option parsing start
       // and end in this context.  If so, then I'll have to directly tell the
       // scripter to do this.
       OptionParsingStarting(exe_ctx);
       auto opt_defs = GetDefinitions();
-      
+
       // Iterate through the options we found so far, and push them into
       // the scripted side.
       for (auto option_elem : option_vec) {
@@ -1662,26 +1662,30 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
           if (cur_arg_pos != OptionArgElement::eUnrecognizedArg &&
               cur_arg_pos != OptionArgElement::eBareDash &&
               cur_arg_pos != OptionArgElement::eBareDoubleDash) {
-            cur_arg_value = request.GetParsedLine().GetArgumentAtIndex(cur_arg_pos);
-
+            cur_arg_value =
+                request.GetParsedLine().GetArgumentAtIndex(cur_arg_pos);
           }
         }
         SetOptionValue(cur_defs_index, cur_arg_value, exe_ctx);
       }
       OptionParsingFinished(exe_ctx);
     }
-    
-    void ProcessCompletionDict(CompletionRequest &request, StructuredData::DictionarySP &completion_dict_sp) {
+
+    void
+    ProcessCompletionDict(CompletionRequest &request,
+                          StructuredData::DictionarySP &completion_dict_sp) {
       // We don't know how to process an empty completion dict, our callers have
       // to do that.
       assert(completion_dict_sp && "Must have valid completion dict");
       // First handle the case of a single completion:
       llvm::StringRef completion;
       // If the dictionary has one element "no-completion" then we return here
-      if (completion_dict_sp->GetValueForKeyAsString("no-completion", completion))
+      if (completion_dict_sp->GetValueForKeyAsString("no-completion",
+                                                     completion))
         return;
 
-      if (completion_dict_sp->GetValueForKeyAsString("completion", completion)) {
+      if (completion_dict_sp->GetValueForKeyAsString("completion",
+                                                     completion)) {
         llvm::StringRef mode_str;
         CompletionMode mode = CompletionMode::Normal;
         if (completion_dict_sp->GetValueForKeyAsString("mode", mode_str)) {
@@ -1708,7 +1712,7 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
           if (!val)
             // FIXME: How do I report this error?
             return;
-          
+
           if (descriptions) {
             auto desc = descriptions->GetItemAtIndexAsString(idx);
             request.AddCompletion(*val, desc ? *desc : "");
@@ -1717,22 +1721,23 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
         }
       }
     }
-    
+
     void
     HandleOptionArgumentCompletion(lldb_private::CompletionRequest &request,
                                    OptionElementVector &option_vec,
                                    int opt_element_index,
                                    CommandInterpreter &interpreter) override {
-      ScriptInterpreter *scripter = interpreter.GetDebugger().GetScriptInterpreter();
-      
+      ScriptInterpreter *scripter =
+          interpreter.GetDebugger().GetScriptInterpreter();
+
       if (!scripter)
         return;
 
       ExecutionContext exe_ctx = interpreter.GetExecutionContext();
       PrepareOptionsForCompletion(request, option_vec, &exe_ctx);
-        
+
       auto defs = GetDefinitions();
-      
+
       size_t defs_index = option_vec[opt_element_index].opt_defs_index;
       llvm::StringRef option_name = defs[defs_index].long_option;
       bool is_enum = defs[defs_index].enum_values.size() != 0;
@@ -1742,19 +1747,19 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
       // regular option completer handle that:
       StructuredData::DictionarySP completion_dict_sp;
       if (!is_enum)
-        completion_dict_sp
-            = scripter->HandleOptionArgumentCompletionForScriptedCommand(m_cmd_obj_sp, 
-                option_name, request.GetCursorCharPos());
+        completion_dict_sp =
+            scripter->HandleOptionArgumentCompletionForScriptedCommand(
+                m_cmd_obj_sp, option_name, request.GetCursorCharPos());
 
       if (!completion_dict_sp) {
-        Options::HandleOptionArgumentCompletion(request, option_vec, 
+        Options::HandleOptionArgumentCompletion(request, option_vec,
                                                 opt_element_index, interpreter);
         return;
       }
-      
+
       ProcessCompletionDict(request, completion_dict_sp);
     }
-    
+
   private:
     struct EnumValueStorage {
       EnumValueStorage() {
@@ -1995,9 +2000,9 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
   Status GetOptionsError() { return m_options_error.Clone(); }
   Status GetArgsError() { return m_args_error.Clone(); }
   bool WantsCompletion() override { return true; }
-  
+
 private:
-  void PrepareOptionsForCompletion(CompletionRequest &request, 
+  void PrepareOptionsForCompletion(CompletionRequest &request,
                                    OptionElementVector &option_vec) {
     // First, we have to tell the Scripted side to set the values in its
     // option store, then we call into the handle_completion passing in
@@ -2009,28 +2014,28 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
     // can just skip setting the options on the scripted side:
     if (options)
       m_options.PrepareOptionsForCompletion(request, option_vec, &m_exe_ctx);
-
   }
-public:  
-  void HandleArgumentCompletion(CompletionRequest &request, 
+
+public:
+  void HandleArgumentCompletion(CompletionRequest &request,
                                 OptionElementVector &option_vec) override {
     ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
-        
+
     if (!scripter)
       return;
 
     // Set up the options values on the scripted side:
     PrepareOptionsForCompletion(request, option_vec);
-    
+
     // Now we have to make up the argument list.
     // The ParseForCompletion only identifies tokens in the m_parsed_line
     // it doesn't remove the options leaving only the args as it does for
     // the regular Parse, so we have to filter out the option ones using the
     // option_element_vector:
-    
+
     Options *options = GetOptions();
-    auto defs = options->GetDefinitions(); 
-    
+    auto defs = options->GetDefinitions();
+
     std::unordered_set<size_t> option_slots;
     for (const auto &elem : option_vec) {
       if (elem.opt_defs_index == -1)
@@ -2039,29 +2044,28 @@ class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
       if (defs[elem.opt_defs_index].option_has_arg)
         option_slots.insert(elem.opt_arg_pos);
     }
-    
+
     std::vector<llvm::StringRef> args_vec;
     Args &args = request.GetParsedLine();
     size_t num_args = args.GetArgumentCount();
     size_t cursor_idx = request.GetCursorIndex();
     size_t args_elem_pos = cursor_idx;
-    
+
     for (size_t idx = 0; idx < num_args; idx++) {
       if (option_slots.count(idx) == 0)
         args_vec.push_back(args[idx].ref());
-      else
-        if (idx < cursor_idx)
-          args_elem_pos--;
+      else if (idx < cursor_idx)
+        args_elem_pos--;
     }
-    StructuredData::DictionarySP completion_dict_sp
-        = scripter->HandleArgumentCompletionForScriptedCommand(m_cmd_obj_sp, 
-            args_vec, args_elem_pos, request.GetCursorCharPos());
+    StructuredData::DictionarySP completion_dict_sp =
+        scripter->HandleArgumentCompletionForScriptedCommand(
+            m_cmd_obj_sp, args_vec, args_elem_pos, request.GetCursorCharPos());
 
     if (!completion_dict_sp) {
       CommandObject::HandleArgumentCompletion(request, option_vec);
       return;
     }
-    
+
     m_options.ProcessCompletionDict(request, completion_dict_sp);
   }
 
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
index 29c2bb7932d0de..be26064bf25675 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h
@@ -210,14 +210,15 @@ class SWIGBridge {
   LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
                                                    std::string &command);
 
-  static StructuredData::DictionarySP 
-  LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(PyObject *implementor,
-    std::vector<llvm::StringRef> &args_impl, size_t args_pos, size_t pos_in_arg);
-  
-  static StructuredData::DictionarySP 
-  LLDBSwigPythonHandleOptionArgumentCompletionForScriptedCommand(PyObject *implementor,
-    llvm::StringRef &long_option, size_t pos_in_arg);
-  
+  static StructuredData::DictionarySP
+  LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(
+      PyObject *implementor, std::vector<llvm::StringRef> &args_impl,
+      size_t args_pos, size_t pos_in_arg);
+
+  static StructuredData::DictionarySP
+  LLDBSwigPythonHandleOptionArgumentCompletionForScriptedCommand(
+      PyObject *implementor, llvm::StringRef &long_option, size_t pos_in_arg);
+
   static bool LLDBSwigPythonCallModuleInit(const char *python_module_name,
                                            const char *session_dictionary_name,
                                            lldb::DebuggerSP debugger);
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index 94388b3dac8947..9045a79d1c0c1a 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -2766,10 +2766,10 @@ ScriptInterpreterPythonImpl::GetRepeatCommandForScriptedCommand(
   return ret_val;
 }
 
-StructuredData::DictionarySP 
+StructuredData::DictionarySP
 ScriptInterpreterPythonImpl::HandleArgumentCompletionForScriptedCommand(
-      StructuredData::GenericSP impl_obj_sp, std::vector<llvm::StringRef> &args, 
-      size_t args_pos, size_t char_in_arg) {
+    StructuredData::GenericSP impl_obj_sp, std::vector<llvm::StringRef> &args,
+    size_t args_pos, size_t char_in_arg) {
   StructuredData::DictionarySP completion_dict_sp;
   if (!impl_obj_sp || !impl_obj_sp->IsValid())
     return completion_dict_sp;
@@ -2778,18 +2778,18 @@ ScriptInterpreterPythonImpl::HandleArgumentCompletionForScriptedCommand(
     Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN,
                    Locker::FreeLock);
 
-    completion_dict_sp = 
-      SWIGBridge::LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(
-      static_cast<PyObject *>(impl_obj_sp->GetValue()), args, args_pos, 
-                              char_in_arg);
+    completion_dict_sp =
+        SWIGBridge::LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(
+            static_cast<PyObject *>(impl_obj_sp->GetValue()), args, args_pos,
+            char_in_arg);
   }
   return completion_dict_sp;
 }
 
-StructuredData::DictionarySP 
+StructuredData::DictionarySP
 ScriptInterpreterPythonImpl::HandleOptionArgumentCompletionForScriptedCommand(
-      StructuredData::GenericSP impl_obj_sp, llvm::StringRef &long_option, 
-      size_t char_in_arg) {
+    StructuredData::GenericSP impl_obj_sp, llvm::StringRef &long_option,
+    size_t char_in_arg) {
   StructuredData::DictionarySP completion_dict_sp;
   if (!impl_obj_sp || !impl_obj_sp->IsValid())
     return completion_dict_sp;
@@ -2798,10 +2798,10 @@ ScriptInterpreterPythonImpl::HandleOptionArgumentCompletionForScriptedCommand(
     Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN,
                    Locker::FreeLock);
 
-    completion_dict_sp = 
-      SWIGBridge::LLDBSwigPythonHandleOptionArgumentCompletionForScriptedCommand(
-      static_cast<PyObject *>(impl_obj_sp->GetValue()), long_option, 
-                              char_in_arg);
+    completion_dict_sp = SWIGBridge::
+        LLDBSwigPythonHandleOptionArgumentCompletionForScriptedCommand(
+            static_cast<PyObject *>(impl_obj_sp->GetValue()), long_option,
+            char_in_arg);
   }
   return completion_dict_sp;
 }
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
index 12acc841c904bf..04d495fad26a4e 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h
@@ -174,11 +174,11 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
                                      Args &args) override;
 
   StructuredData::DictionarySP HandleArgumentCompletionForScriptedCommand(
-      StructuredData::GenericSP impl_obj_sp, std::vector<llvm::StringRef> &args, 
+      StructuredData::GenericSP impl_obj_sp, std::vector<llvm::StringRef> &args,
       size_t args_pos, size_t char_in_arg) override;
 
   StructuredData::DictionarySP HandleOptionArgumentCompletionForScriptedCommand(
-      StructuredData::GenericSP impl_obj_sp, llvm::StringRef &long_options, 
+      StructuredData::GenericSP impl_obj_sp, llvm::StringRef &long_options,
       size_t char_in_arg) override;
 
   Status GenerateFunction(const char *signature, const StringList &input,
diff --git a/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py b/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
index 40a8cb0c2dc6fa..6fac1eba919bc9 100644
--- a/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
+++ b/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py
@@ -68,7 +68,14 @@ def run_one_repeat(self, commands, expected_num_errors):
 
         return results
 
-    def handle_completion(self, cmd_str, exp_num_completions, exp_matches, exp_descriptions, match_description):
+    def handle_completion(
+        self,
+        cmd_str,
+        exp_num_completions,
+        exp_matches,
+        exp_descriptions,
+        match_description,
+    ):
         matches = lldb.SBStringList()
         descriptions = lldb.SBStringList()
 
@@ -76,23 +83,34 @@ def handle_completion(self, cmd_str, exp_num_completions, exp_matches, exp_descr
         num_completions = interp.HandleCompletionWithDescriptions(
             cmd_str, len(cmd_str), 0, 1000, matches, descriptions
         )
-        self.assertEqual(num_completions, exp_num_completions, "Number of completions is right.")
+        self.assertEqual(
+            num_completions, exp_num_completions, "Number of completions is right."
+        )
         num_matches = matches.GetSize()
-        self.assertEqual(num_matches, exp_matches.GetSize(), "matches and expected matches of different lengths")
+        self.assertEqual(
+            num_matches,
+            exp_matches.GetSize(),
+            "matches and expected matches of different lengths",
+        )
         num_descriptions = descriptions.GetSize()
         if match_description:
-            self.assertEqual(num_descriptions, exp_descriptions.GetSize(), "descriptions and expected of different lengths")
-            
+            self.assertEqual(
+                num_descriptions,
+                exp_descriptions.GetSize(),
+                "descriptions and expected of different lengths",
+            )
+
         self.assertEqual(
-            matches.GetSize(), num_completions + 1, "The first element is the complete additional text"
+            matches.GetSize(),
+            num_completions + 1,
+            "The first element is the complete additional text",
         )
-        
 
         for idx in range(0, num_matches):
             match = matches.GetStringAtIndex(idx)
             exp_match = exp_matches.GetStringAtIndex(idx)
             self.assertEqual(
-                match, exp_match , f"{match} did not match expectation: {exp_match}"
+                match, exp_match, f"{match} did not match expectation: {exp_match}"
             )
         if match_description:
             desc = descriptions.GetStringAtIndex(idx)
@@ -100,7 +118,7 @@ def handle_completion(self, cmd_str, exp_num_completions, exp_matches, exp_descr
             self.assertEqual(
                 desc, exp_desc, f"{desc} didn't match expectation: {exp_desc}"
             )
-        
+
     def pycmd_tests(self):
         source_dir = self.getSourceDir()
         test_file_path = os.path.join(source_dir, "test_commands.py")
@@ -208,14 +226,11 @@ def cleanup():
         matches = lldb.SBStringList()
         descriptions = lldb.SBStringList()
 
-
         # First try an enum completion:
         # Note - this is an enum so all the values are returned:
         matches.AppendList(["oo ", "foo"], 2)
-         
-        self.handle_completion(
-            "no-args -e f", 1, matches, descriptions, False
-        )
+
+        self.handle_completion("no-args -e f", 1, matches, descriptions, False)
 
         # Now try an internal completer, the on disk file one is handy:
         partial_name = os.path.join(source_dir, "test_")
@@ -225,9 +240,7 @@ def cleanup():
         descriptions.Clear()
         matches.AppendList(["commands.py' ", test_file_path], 2)
         # We don't have descriptions for the file path completer:
-        self.handle_completion(
-            cmd_str, 1, matches, descriptions, False
-        )
+        self.handle_completion(cmd_str, 1, matches, descriptions, False)
 
         # Try a command with arguments.
         # FIXME: It should be enough to define an argument and it's type to get the completer
@@ -247,7 +260,7 @@ def cleanup():
         cmd_str = "two-args -p something -c other_"
         matches.AppendString("something ")
         matches.AppendString("other_something")
-        # This is a full match so no descriptions: 
+        # This is a full match so no descriptions:
         self.handle_completion(cmd_str, 1, matches, descriptions, False)
 
         matches.Clear()
@@ -277,7 +290,7 @@ def cleanup():
         descriptions.Clear()
         matches.AppendList(["answer ", "correct_answer"], 2)
         self.handle_completion(cmd_str, 1, matches, descriptions, False)
-        
+
         # Now make sure get_repeat_command works properly:
 
         # no-args turns off auto-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 aa38e9ac511a8d..b15ea935c05867 100644
--- a/lldb/test/API/commands/command/script/add/test_commands.py
+++ b/lldb/test/API/commands/command/script/add/test_commands.py
@@ -201,9 +201,7 @@ def setup_command_definition(self):
                 ov_parser.make_argument_element(
                     lldb.eArgTypePythonClass, "plain", [3, 4]
                 ),
-                ov_parser.make_argument_element(
-                    lldb.eArgTypePid, "optional", [3, 4]
-                ),
+                ov_parser.make_argument_element(lldb.eArgTypePid, "optional", [3, 4]),
             ]
         )
 
@@ -214,16 +212,16 @@ def get_repeat_command(self, command):
 
     def handle_option_argument_completion(self, long_option, cursor_pos):
         ov_parser = self.get_parser()
-        value = ov_parser.dest_for_option(long_option)[0:cursor_pos+1]
+        value = ov_parser.dest_for_option(long_option)[0 : cursor_pos + 1]
         proc_value = ov_parser.proc_name
         if proc_value != None:
-            new_str = value+proc_value
-            ret_arr = {"completion" : new_str, "mode" : "partial"}
+            new_str = value + proc_value
+            ret_arr = {"completion": new_str, "mode": "partial"}
             return ret_arr
 
-        ret_arr = {"values" : [value+"nice", value+"not_nice", value+"mediocre"]}
+        ret_arr = {"values": [value + "nice", value + "not_nice", value + "mediocre"]}
         return ret_arr
-        
+
     def handle_argument_completion(self, args, arg_pos, cursor_pos):
         ov_parser = self.get_parser()
         orig_arg = args[arg_pos][0:cursor_pos]
@@ -235,7 +233,10 @@ def handle_argument_completion(self, args, arg_pos, cursor_pos):
             # No completions if proc_name was set.
             return True
 
-        ret_arr = {"values" : [orig_arg + "cool", orig_arg+"yuck"], "descriptions" : ["good idea", "bad idea"]}
+        ret_arr = {
+            "values": [orig_arg + "cool", orig_arg + "yuck"],
+            "descriptions": ["good idea", "bad idea"],
+        }
         return ret_arr
 
     def get_short_help(self):
diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
index d7a4952642e872..a2318afdf8cd59 100644
--- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
+++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp
@@ -206,19 +206,19 @@ LLDBSwigPythonGetRepeatCommandForScriptedCommand(PyObject *implementor,
   return std::nullopt;
 }
 
-StructuredData::DictionarySP 
-LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(PyObject *implementor,
-    std::vector<llvm::StringRef> &args, size_t args_pos, size_t pos_in_arg) {
+StructuredData::DictionarySP
+LLDBSwigPythonHandleArgumentCompletionForScriptedCommand(
+    PyObject *implementor, std::vector<llvm::StringRef> &args, size_t args_pos,
+    size_t pos_in_arg) {
   return {};
 }
 
-StructuredData::DictionarySP 
+StructuredData::DictionarySP
 LLDBSwigPythonHandleOptionArgumentCompletionForScriptedCommand(
-      PyObject *implementor, llvm::StringRef &long_options, size_t char_in_arg) {
+    PyObject *implementor, llvm::StringRef &long_options, size_t char_in_arg) {
   return {};
 }
 
-
 bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallModuleInit(
     const char *python_module_name, const char *session_dictionary_name,
     lldb::DebuggerSP debugger) {

>From 036ff87e087a631093da0b1fd8bdea8c0323397b Mon Sep 17 00:00:00 2001
From: Jim Ingham <jingham at apple.com>
Date: Mon, 23 Sep 2024 15:45:30 -0700
Subject: [PATCH 3/3] Respond to Ismail's review comments.

---
 lldb/docs/use/python-reference.rst           | 77 ++++++++++++++------
 lldb/examples/python/templates/parsed_cmd.py | 11 +--
 2 files changed, 60 insertions(+), 28 deletions(-)

diff --git a/lldb/docs/use/python-reference.rst b/lldb/docs/use/python-reference.rst
index de4f219a13292f..6c703005b46877 100644
--- a/lldb/docs/use/python-reference.rst
+++ b/lldb/docs/use/python-reference.rst
@@ -551,7 +551,7 @@ command definition form can't do the right thing.
 Since lldb 3.7, Python commands can also be implemented by means of a class
 which should implement the following interface:
 
-::
+.. code-block:: python
 
   class CommandObjectType:
       def __init__(self, debugger, internal_dict):
@@ -580,14 +580,14 @@ which should implement the following interface:
 As a convenience, you can treat the result object as a Python file object, and
 say
 
-::
+.. code-block:: python
 
   print >>result, "my command does lots of cool stuff"
 
 SBCommandReturnObject and SBStream both support this file-like behavior by
 providing write() and flush() calls at the Python layer.
 
-The commands that are added using this Class definition are what lldb calls
+The commands that are added using this class definition are what lldb calls
 "raw" commands.  The command interpreter doesn't attempt to parse the command,
 doesn't handle option values, neither generating help for them, or their
 completion.  Raw commands are useful when the arguments passed to the command
@@ -606,16 +606,16 @@ provides some convenience methods, and most importantly an LLDBOptionValueParser
 accessed throught lldb.ParsedCommand.get_parser().  The parser is used to set
 your command definitions, and to retrieve option values in the __call__ method.
 
-To set the command definition, implement the ParsedCommand abstract method:
+To set up the command definition, implement the ParsedCommand abstract method:
 
-::
+.. code-block:: python
 
    def setup_command_definition(self):
 
 This is called when your command is added to lldb.  In this method you add the
 options and their types, the option help strings, etc. to the command using the API:
 
-::
+.. code-block:: python
 
     def add_option(self, short_option, long_option, help, default,
                    dest = None, required=False, groups = None,
@@ -643,7 +643,7 @@ options and their types, the option help strings, etc. to the command using the
 
 Similarly, you can add argument types to the command:
 
-::
+.. code-block:: python
 
     def make_argument_element(self, arg_type, repeat = "optional", groups = None):
         """
@@ -657,7 +657,7 @@ Similarly, you can add argument types to the command:
 
 Then implement the body of the command by defining:
 
-::
+.. code-block:: python
 
     def __call__(self, debugger, args_array, exe_ctx, result):
         """This is the command callback.  The option values are
@@ -675,12 +675,13 @@ parsed into the args_array, and the option values are set in the parser, and
 can be accessed using their property name.  The LLDBOptionValueParser class has
 a couple of other handy methods:
 
-::
+.. code-block:: python
     def was_set(self, long_option_name):
 
 returns True if the option was specified on the command line.
 
-::
+.. code-block:: python
+
     def dest_for_option(self, long_option_name):
     """
     This will return the value of the dest variable you defined for opt_name.
@@ -694,16 +695,29 @@ custom completions, either in your arguments or option values, you can handle
 completion by hand as well.  To handle completion of option value arguments,
 your lldb.ParsedCommand subclass should implement:
 
-::
-    def handle_option_argument_completion(self, args, arg_pos, cursor_pos):
+.. code-block:: python
+
+    def handle_option_argument_completion(self, long_option, cursor_pos):
+    """
+    long_option: The long option name of the option whose value you are
+                 asked to complete.
+    cursor_pos: The cursor position in the value for that option - which
+    you can get from the option parser.
+    """
+
+And to handle the completion of arguments:
+    
+.. code-block:: python
+
+    def handle_argument_completion(self, args, arg_pos, cursor_pos):
     """
     args: A list of the arguments to the command
     arg_pos: An index into the args list of the argument with the cursor
     cursor_pos: The cursor position in the arg specified by arg_pos
     """
 
-When this command is called, the command line has been parsed up to the word
-containing the cursor, and any option values set in that part of the command
+When either of these API's is called, the command line will have been parsed up to
+the word containing the cursor, and any option values set in that part of the command
 string are available from the option value parser.  That's useful for instance
 if you have a --shared-library option that would constrain the completions for,
 say, a symbol name option or argument.
@@ -711,18 +725,35 @@ say, a symbol name option or argument.
 The return value specifies what the completion options are.  You have four
 choices:
 
-True: the completion was handled with no completions.
+- `True`: the completion was handled with no completions.
 
-False: the completion was not handled, forward it to the regular
+- `False`: the completion was not handled, forward it to the regular
 completion machinery.
 
-A dictionary with the key: "completion": there is one candidate,
+- A dictionary with the key: "completion": there is one candidate,
 whose value is the value of the "completion" key.  Optionally you can pass a
 "mode" key whose value is either "partial" or "complete".  Return partial if
-the "completion" string is a prefix for all the completed value, and "complete"
-if it is the full completion. The default is "complete".
+the "completion" string is a prefix for all the completed value.
+
+For instance, if the string you are completing is "Test" and the available completions are:
+"Test1", "Test11" and "Test111", you should return the dictionary:
+
+.. code-block:: python
+
+   return {"completion": "Test1", "mode" : "partial"}
+
+and then lldb will add the "1" at the curson and advance it after the added string,
+waiting for more completions.  But if "Test1" is the only completion, return:
+
+.. code-block:: python
 
-A dictionary with the key: "values" whose value is a list of candidate completion
+   {"completion": "Test1", "mode": "complete"}
+
+and lldb will add "1 " at the cursor, indicating the command string is complete.
+
+The default is "complete", you don't need to specify a "mode" in that case.
+
+- A dictionary with the key: "values" whose value is a list of candidate completion
 strings.  The command interpreter will present those strings as the available choices.
 You can optionally include a "descriptions" key, whose value is a parallel array
 of description strings, and the completion will show the description next to
@@ -735,7 +766,7 @@ path, so you don't have to change your PYTHONPATH for temporary scripts. It
 also has another convenience that if your new script module has a function of
 the form:
 
-::
+.. code-block python
 
   def __lldb_init_module(debugger, internal_dict):
       # Command Initialization code goes here
@@ -751,7 +782,7 @@ creating scripts that can be run from the command line. However, for command
 line scripts, the debugger instance must be created manually. Sample code would
 look like:
 
-::
+.. code-block:: python
 
   if __name__ == '__main__':
       # Initialize the debugger before making any API calls.
@@ -774,7 +805,7 @@ look like:
 Now we can create a module called ls.py in the file ~/ls.py that will implement
 a function that can be used by LLDB's python command code:
 
-::
+.. code-block:: python
 
   #!/usr/bin/env python
 
diff --git a/lldb/examples/python/templates/parsed_cmd.py b/lldb/examples/python/templates/parsed_cmd.py
index 93ca8f833ec856..13d6eae405c08d 100644
--- a/lldb/examples/python/templates/parsed_cmd.py
+++ b/lldb/examples/python/templates/parsed_cmd.py
@@ -4,7 +4,8 @@
 The way to use it is to make a class for your command that inherits from ParsedCommandBase.
 That will make an LLDBOptionValueParser which you will use for your
 option definition, and to fetch option values for the current invocation
-of your command.  Access to the OV parser is through:
+of your command.  For concision, I'll call this the `OVParser`.  
+Access to the `OVParser` is through:
 
 ParsedCommandBase.get_parser()
 
@@ -60,16 +61,16 @@ def handle_option_argument_completion(self, long_option, cursor_pos):
 The line to be completed will be parsed up to the option containint the cursor position, 
 and the values will be set in the OptionValue parser object.  long_option will be
 the option name containing the cursor, and cursor_pos will be the position of the cursor
-in that option's value.  You can call the OVParser.dest_for_option(long_option) to get the
-value for that option.  The other options that came before the cursor in the command
-line will also be set in the OV Parser when the completion handler is called.
+in that option's value.  You can call the `OVParser` method: `dest_for_option(long_option)` 
+to get the value for that option.  The other options that came before the cursor in the command
+line will also be set in the `OVParser` when the completion handler is called.
 
 For argument values, define:
 
 def handle_argument_completion(self, args, arg_pos, cursor_pos):
 
 Again, the command line will be parsed up to the cursor position, and all the options
-before the cursor pose will be set in the OVParser.  args is a python list of the
+before the cursor pose will be set in the `OVParser`.  args is a python list of the
 arguments, arg_pos is the index of the argument with the cursor, and cursor_pos is
 the position of the cursor in the argument.
 



More information about the lldb-commits mailing list