[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
Tue Sep 17 16:30:58 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: None (jimingham)
<details>
<summary>Changes</summary>
If your arguments or option values are of a type that naturally uses one of our common completion mechanisms, you will get completion for free. But if you have your own custom values or if you want to do fancy things like have `break set -s foo.dylib -n ba<TAB>` only complete on symbols in foo.dylib, you can use this new mechanism to achieve that.
---
Patch is 48.25 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/109062.diff
14 Files Affected:
- (modified) lldb/bindings/python/python-wrapper.swig (+73)
- (modified) lldb/docs/use/python-reference.rst (+143-1)
- (modified) lldb/examples/python/cmdtemplate.py (+8-7)
- (modified) lldb/examples/python/templates/parsed_cmd.py (+79-5)
- (modified) lldb/include/lldb/Interpreter/ScriptInterpreter.h (+15)
- (modified) lldb/include/lldb/Utility/CompletionRequest.h (+2)
- (modified) lldb/source/Commands/CommandObjectCommands.cpp (+187)
- (modified) lldb/source/Interpreter/Options.cpp (+3-2)
- (modified) lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h (+8)
- (modified) lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp (+40)
- (modified) lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h (+8)
- (modified) lldb/test/API/commands/command/script/add/TestAddParsedCommand.py (+81-34)
- (modified) lldb/test/API/commands/command/script/add/test_commands.py (+46-18)
- (modified) lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp (+13)
``````````diff
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/...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/109062
More information about the lldb-commits
mailing list