https://github.com/dmpots created https://github.com/llvm/llvm-project/pull/147775
This commit adds completion support for the plugin commands. It will try to complete partial namespaces to the full namespace string. If the completion input is already a full namespace string then it will add all the matching plugins in that namespace as completions. This lets the user complete to the namespace first and then tab-complete to the next level if desired. ``` (lldb) plugin list a<tab> Available completions: abi architecture (lldb) plugin list ab<tab> (lldb) plugin list abi<tab> (lldb) plugin list abi.<tab> Available completions: abi.SysV-arm64 abi.ABIMacOSX_arm64 abi.SysV-arm ... ``` >From c35518e7b221ffbc43b9fb6aa3cbec079bda39b2 Mon Sep 17 00:00:00 2001 From: David Peixotto <p...@meta.com> Date: Thu, 3 Jul 2025 17:08:27 -0700 Subject: [PATCH] [lldb] Add completions for plugin list/enable/disable This commit adds completion support for the plugin commands. It will try to complete partial namespaces to the full namespace string. If the completion input is already a full namespace string then it will add all the matching plugins in that namespace as completions. This lets the user complete to the namespace first and then tab-complete to the next level if desired. ``` (lldb) plugin list a<tab> Available completions: abi architecture (lldb) plugin list ab<tab> (lldb) plugin list abi<tab> (lldb) plugin list abi.<tab> Available completions: abi.SysV-arm64 abi.ABIMacOSX_arm64 abi.SysV-arm ... ``` --- lldb/include/lldb/Core/PluginManager.h | 3 ++ .../lldb/Interpreter/CommandCompletions.h | 4 ++ lldb/include/lldb/lldb-enumerations.h | 3 +- .../Python/lldbsuite/test/lldbtest.py | 15 ++++-- lldb/source/Commands/CommandCompletions.cpp | 8 ++++ lldb/source/Commands/CommandObjectPlugin.cpp | 24 ++++++++++ lldb/source/Core/PluginManager.cpp | 33 +++++++++++++ lldb/test/API/commands/plugin/TestPlugin.py | 46 +++++++++++++++++++ 8 files changed, 131 insertions(+), 5 deletions(-) diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h index 5499e99025d8a..369785ceea5a5 100644 --- a/lldb/include/lldb/Core/PluginManager.h +++ b/lldb/include/lldb/Core/PluginManager.h @@ -787,6 +787,9 @@ class PluginManager { static std::vector<RegisteredPluginInfo> GetUnwindAssemblyPluginInfo(); static bool SetUnwindAssemblyPluginEnabled(llvm::StringRef name, bool enable); + + static void AutoCompletePluginName(llvm::StringRef partial_name, + CompletionRequest &request); }; } // namespace lldb_private diff --git a/lldb/include/lldb/Interpreter/CommandCompletions.h b/lldb/include/lldb/Interpreter/CommandCompletions.h index c7292b3b1471a..0c0424cbac6eb 100644 --- a/lldb/include/lldb/Interpreter/CommandCompletions.h +++ b/lldb/include/lldb/Interpreter/CommandCompletions.h @@ -123,6 +123,10 @@ class CommandCompletions { static void ThreadIDs(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher); + static void ManagedPlugins(CommandInterpreter &interpreter, + CompletionRequest &request, + SearchFilter *searcher); + /// This completer works for commands whose only arguments are a command path. /// It isn't tied to an argument type because it completes not on a single /// argument but on the sequence of arguments, so you have to invoke it by diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index 69e8671b6e21b..e021c7a926cf1 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -1321,10 +1321,11 @@ enum CompletionType { eTypeCategoryNameCompletion = (1ul << 24), eCustomCompletion = (1ul << 25), eThreadIDCompletion = (1ul << 26), + eManagedPluginCompletion = (1ul << 27), // This last enum element is just for input validation. // Add new completions before this element, // and then increment eTerminatorCompletion's shift value - eTerminatorCompletion = (1ul << 27) + eTerminatorCompletion = (1ul << 28) }; /// Specifies if children need to be re-computed diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py index a4ff96e4158ce..63fadb59a82a1 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -2268,7 +2268,7 @@ def completions_match(self, command, completions, max_completions=-1): completions, list(match_strings)[1:], "List of returned completion is wrong" ) - def completions_contain(self, command, completions): + def completions_contain(self, command, completions, match=True): """Checks that the completions for the given command contain the given list of completions.""" interp = self.dbg.GetCommandInterpreter() @@ -2276,9 +2276,16 @@ def completions_contain(self, command, completions): interp.HandleCompletion(command, len(command), 0, -1, match_strings) for completion in completions: # match_strings is a 1-indexed list, so we have to slice... - self.assertIn( - completion, list(match_strings)[1:], "Couldn't find expected completion" - ) + if match: + self.assertIn( + completion, + list(match_strings)[1:], + "Couldn't find expected completion", + ) + else: + self.assertNotIn( + completion, list(match_strings)[1:], "Found unexpected completion" + ) def filecheck( self, command, check_file, filecheck_options="", expect_cmd_failure=False diff --git a/lldb/source/Commands/CommandCompletions.cpp b/lldb/source/Commands/CommandCompletions.cpp index 38231a8e993c7..3e223090c4286 100644 --- a/lldb/source/Commands/CommandCompletions.cpp +++ b/lldb/source/Commands/CommandCompletions.cpp @@ -87,6 +87,7 @@ bool CommandCompletions::InvokeCommonCompletionCallbacks( {lldb::eTypeCategoryNameCompletion, CommandCompletions::TypeCategoryNames}, {lldb::eThreadIDCompletion, CommandCompletions::ThreadIDs}, + {lldb::eManagedPluginCompletion, CommandCompletions::ManagedPlugins}, {lldb::eTerminatorCompletion, nullptr} // This one has to be last in the list. }; @@ -850,6 +851,13 @@ void CommandCompletions::ThreadIDs(CommandInterpreter &interpreter, } } +void CommandCompletions::ManagedPlugins(CommandInterpreter &interpreter, + CompletionRequest &request, + SearchFilter *searcher) { + PluginManager::AutoCompletePluginName(request.GetCursorArgumentPrefix(), + request); +} + void CommandCompletions::CompleteModifiableCmdPathArgs( CommandInterpreter &interpreter, CompletionRequest &request, OptionElementVector &opt_element_vector) { diff --git a/lldb/source/Commands/CommandObjectPlugin.cpp b/lldb/source/Commands/CommandObjectPlugin.cpp index cdc9006bf5261..093ebde5f5f2c 100644 --- a/lldb/source/Commands/CommandObjectPlugin.cpp +++ b/lldb/source/Commands/CommandObjectPlugin.cpp @@ -194,6 +194,14 @@ List only the plugin 'foo' matching a fully qualified name exactly Options *GetOptions() override { return &m_options; } + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), lldb::eManagedPluginCompletion, request, + nullptr); + } + protected: void DoExecute(Args &command, CommandReturnObject &result) override { size_t argc = command.GetArgumentCount(); @@ -293,6 +301,14 @@ class CommandObjectPluginEnable : public CommandObjectParsed { AddSimpleArgumentList(eArgTypeManagedPlugin); } + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), lldb::eManagedPluginCompletion, request, + nullptr); + } + ~CommandObjectPluginEnable() override = default; protected: @@ -309,6 +325,14 @@ class CommandObjectPluginDisable : public CommandObjectParsed { AddSimpleArgumentList(eArgTypeManagedPlugin); } + void + HandleArgumentCompletion(CompletionRequest &request, + OptionElementVector &opt_element_vector) override { + lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks( + GetCommandInterpreter(), lldb::eManagedPluginCompletion, request, + nullptr); + } + ~CommandObjectPluginDisable() override = default; protected: diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp index 3f20a96edc187..59d1dac40a00c 100644 --- a/lldb/source/Core/PluginManager.cpp +++ b/lldb/source/Core/PluginManager.cpp @@ -18,6 +18,7 @@ #include "lldb/Utility/Status.h" #include "lldb/Utility/StringList.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" #include "llvm/Support/DynamicLibrary.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/raw_ostream.h" @@ -2473,3 +2474,35 @@ bool PluginManager::SetUnwindAssemblyPluginEnabled(llvm::StringRef name, bool enable) { return GetUnwindAssemblyInstances().SetInstanceEnabled(name, enable); } + +void PluginManager::AutoCompletePluginName(llvm::StringRef name, + CompletionRequest &request) { + // Split the name into the namespace and the plugin name. + // If there is no dot then the ns_name will be equal to name and + // plugin_prefix will be empty. + llvm::StringRef ns_name, plugin_prefix; + std::tie(ns_name, plugin_prefix) = name.split('.'); + + for (const PluginNamespace &plugin_ns : GetPluginNamespaces()) { + // If the plugin namespace matches exactly then + // add all the plugins in this namespace as completions if the + // plugin names starts with the plugin_prefix. If the plugin_prefix + // is empty then it will match all the plugins (empty string is a + // prefix of everything). + if (plugin_ns.name == ns_name) { + for (const RegisteredPluginInfo &plugin : plugin_ns.get_info()) { + llvm::SmallString<128> buf; + if (plugin.name.starts_with(plugin_prefix)) + request.AddCompletion( + (plugin_ns.name + "." + plugin.name).toStringRef(buf)); + } + } + // Otherwise check if the namespace is a prefix of the full name. + // Use a partial completion here so that we can either operate on the full + // namespace or tab-complete to the next level. + else if (plugin_ns.name.starts_with(name) && + !plugin_ns.get_info().empty()) { + request.AddCompletion(plugin_ns.name, "", CompletionMode::Partial); + } + } +} diff --git a/lldb/test/API/commands/plugin/TestPlugin.py b/lldb/test/API/commands/plugin/TestPlugin.py index fdfb14bfcc24e..e7de7a3f797f1 100644 --- a/lldb/test/API/commands/plugin/TestPlugin.py +++ b/lldb/test/API/commands/plugin/TestPlugin.py @@ -60,3 +60,49 @@ def do_list_disable_enable_test(self, plugin_namespace): self.expect( f"plugin enable {plugin_namespace}", substrs=[plugin_namespace, "[+]"] ) + + def test_completions(self): + # Make sure completions work for the plugin list, enable, and disable commands. + # We just check a few of the expected plugins to make sure the completion works. + self.completions_contain( + "plugin list ", ["abi", "architecture", "disassembler"] + ) + self.completions_contain( + "plugin enable ", ["abi", "architecture", "disassembler"] + ) + self.completions_contain( + "plugin disable ", ["abi", "architecture", "disassembler"] + ) + + # A completion for a partial namespace should be the full namespace. + # This allows the user to run the command on the full namespace. + self.completions_match("plugin list ab", ["abi"]) + self.completions_contain( + "plugin list object", ["object-container", "object-file"] + ) + + # A completion for a full namespace should contain the plugins in that namespace. + self.completions_contain("plugin list abi", ["abi.sysv-x86_64"]) + self.completions_contain("plugin list abi.", ["abi.sysv-x86_64"]) + self.completions_contain("plugin list abi.s", ["abi.sysv-x86_64"]) + self.completions_contain("plugin list abi.sysv-x", ["abi.sysv-x86_64"]) + + # Check for a completion that is a both a complete namespace and a prefix of + # another namespace. It should return the completions for the plugins in the completed + # namespace as well as the completion for the partial namespace. + self.completions_contain( + "plugin list language", ["language.cplusplus", "language-runtime"] + ) + + # When the namespace is a prefix of another namespace and the user types a dot, the + # completion should not include the match for the partial namespace. + self.completions_contain( + "plugin list language.", ["language.cplusplus"], match=True + ) + self.completions_contain( + "plugin list language.", ["language-runtime"], match=False + ) + + # Check for an empty completion list when the names is invalid. + # See docs for `complete_from_to` for how this checks for an empty list. + self.complete_from_to("plugin list abi.foo", ["plugin list abi.foo"]) _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits