aboutsummaryrefslogtreecommitdiff
path: root/lldb/source/Commands/CommandObjectCommands.cpp
diff options
context:
space:
mode:
authorjimingham <jingham@apple.com>2024-09-24 10:00:00 -0700
committerGitHub <noreply@github.com>2024-09-24 10:00:00 -0700
commit04b443e77845cd20ab5acc4356cee509316135dd (patch)
tree72ee14a4a01bba2b99af12c0b7524a931205b4b4 /lldb/source/Commands/CommandObjectCommands.cpp
parentc6bf59f26b2d74474a66182db6ebd576273bfb00 (diff)
downloadllvm-04b443e77845cd20ab5acc4356cee509316135dd.zip
llvm-04b443e77845cd20ab5acc4356cee509316135dd.tar.gz
llvm-04b443e77845cd20ab5acc4356cee509316135dd.tar.bz2
Add the ability to define custom completers to the parsed_cmd template. (#109062)
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.
Diffstat (limited to 'lldb/source/Commands/CommandObjectCommands.cpp')
-rw-r--r--lldb/source/Commands/CommandObjectCommands.cpp191
1 files changed, 191 insertions, 0 deletions
diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp
index e3291640..845b89a 100644
--- a/lldb/source/Commands/CommandObjectCommands.cpp
+++ b/lldb/source/Commands/CommandObjectCommands.cpp
@@ -1637,6 +1637,129 @@ private:
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() {
@@ -1878,6 +2001,74 @@ public:
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; }
ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; }