Skip to content

Commit fccae85

Browse files
authored
[lldb] Add completions for plugin list/enable/disable (#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 ... ```
1 parent d67d91a commit fccae85

File tree

8 files changed

+130
-5
lines changed

8 files changed

+130
-5
lines changed

lldb/include/lldb/Core/PluginManager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,9 @@ class PluginManager {
787787

788788
static std::vector<RegisteredPluginInfo> GetUnwindAssemblyPluginInfo();
789789
static bool SetUnwindAssemblyPluginEnabled(llvm::StringRef name, bool enable);
790+
791+
static void AutoCompletePluginName(llvm::StringRef partial_name,
792+
CompletionRequest &request);
790793
};
791794

792795
} // namespace lldb_private

lldb/include/lldb/Interpreter/CommandCompletions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ class CommandCompletions {
123123
static void ThreadIDs(CommandInterpreter &interpreter,
124124
CompletionRequest &request, SearchFilter *searcher);
125125

126+
static void ManagedPlugins(CommandInterpreter &interpreter,
127+
CompletionRequest &request,
128+
SearchFilter *searcher);
129+
126130
/// This completer works for commands whose only arguments are a command path.
127131
/// It isn't tied to an argument type because it completes not on a single
128132
/// argument but on the sequence of arguments, so you have to invoke it by

lldb/include/lldb/lldb-enumerations.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1321,10 +1321,11 @@ enum CompletionType {
13211321
eTypeCategoryNameCompletion = (1ul << 24),
13221322
eCustomCompletion = (1ul << 25),
13231323
eThreadIDCompletion = (1ul << 26),
1324+
eManagedPluginCompletion = (1ul << 27),
13241325
// This last enum element is just for input validation.
13251326
// Add new completions before this element,
13261327
// and then increment eTerminatorCompletion's shift value
1327-
eTerminatorCompletion = (1ul << 27)
1328+
eTerminatorCompletion = (1ul << 28)
13281329
};
13291330

13301331
/// Specifies if children need to be re-computed

lldb/packages/Python/lldbsuite/test/lldbtest.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2268,17 +2268,24 @@ def completions_match(self, command, completions, max_completions=-1):
22682268
completions, list(match_strings)[1:], "List of returned completion is wrong"
22692269
)
22702270

2271-
def completions_contain(self, command, completions):
2271+
def completions_contain(self, command, completions, match=True):
22722272
"""Checks that the completions for the given command contain the given
22732273
list of completions."""
22742274
interp = self.dbg.GetCommandInterpreter()
22752275
match_strings = lldb.SBStringList()
22762276
interp.HandleCompletion(command, len(command), 0, -1, match_strings)
22772277
for completion in completions:
22782278
# match_strings is a 1-indexed list, so we have to slice...
2279-
self.assertIn(
2280-
completion, list(match_strings)[1:], "Couldn't find expected completion"
2281-
)
2279+
if match:
2280+
self.assertIn(
2281+
completion,
2282+
list(match_strings)[1:],
2283+
"Couldn't find expected completion",
2284+
)
2285+
else:
2286+
self.assertNotIn(
2287+
completion, list(match_strings)[1:], "Found unexpected completion"
2288+
)
22822289

22832290
def filecheck(
22842291
self, command, check_file, filecheck_options="", expect_cmd_failure=False

lldb/source/Commands/CommandCompletions.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ bool CommandCompletions::InvokeCommonCompletionCallbacks(
8787
{lldb::eTypeCategoryNameCompletion,
8888
CommandCompletions::TypeCategoryNames},
8989
{lldb::eThreadIDCompletion, CommandCompletions::ThreadIDs},
90+
{lldb::eManagedPluginCompletion, CommandCompletions::ManagedPlugins},
9091
{lldb::eTerminatorCompletion,
9192
nullptr} // This one has to be last in the list.
9293
};
@@ -850,6 +851,13 @@ void CommandCompletions::ThreadIDs(CommandInterpreter &interpreter,
850851
}
851852
}
852853

854+
void CommandCompletions::ManagedPlugins(CommandInterpreter &interpreter,
855+
CompletionRequest &request,
856+
SearchFilter *searcher) {
857+
PluginManager::AutoCompletePluginName(request.GetCursorArgumentPrefix(),
858+
request);
859+
}
860+
853861
void CommandCompletions::CompleteModifiableCmdPathArgs(
854862
CommandInterpreter &interpreter, CompletionRequest &request,
855863
OptionElementVector &opt_element_vector) {

lldb/source/Commands/CommandObjectPlugin.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,14 @@ List only the plugin 'foo' matching a fully qualified name exactly
194194

195195
Options *GetOptions() override { return &m_options; }
196196

197+
void
198+
HandleArgumentCompletion(CompletionRequest &request,
199+
OptionElementVector &opt_element_vector) override {
200+
lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
201+
GetCommandInterpreter(), lldb::eManagedPluginCompletion, request,
202+
nullptr);
203+
}
204+
197205
protected:
198206
void DoExecute(Args &command, CommandReturnObject &result) override {
199207
size_t argc = command.GetArgumentCount();
@@ -293,6 +301,14 @@ class CommandObjectPluginEnable : public CommandObjectParsed {
293301
AddSimpleArgumentList(eArgTypeManagedPlugin);
294302
}
295303

304+
void
305+
HandleArgumentCompletion(CompletionRequest &request,
306+
OptionElementVector &opt_element_vector) override {
307+
lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
308+
GetCommandInterpreter(), lldb::eManagedPluginCompletion, request,
309+
nullptr);
310+
}
311+
296312
~CommandObjectPluginEnable() override = default;
297313

298314
protected:
@@ -309,6 +325,14 @@ class CommandObjectPluginDisable : public CommandObjectParsed {
309325
AddSimpleArgumentList(eArgTypeManagedPlugin);
310326
}
311327

328+
void
329+
HandleArgumentCompletion(CompletionRequest &request,
330+
OptionElementVector &opt_element_vector) override {
331+
lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
332+
GetCommandInterpreter(), lldb::eManagedPluginCompletion, request,
333+
nullptr);
334+
}
335+
312336
~CommandObjectPluginDisable() override = default;
313337

314338
protected:

lldb/source/Core/PluginManager.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "lldb/Utility/Status.h"
1919
#include "lldb/Utility/StringList.h"
2020
#include "llvm/ADT/StringRef.h"
21+
#include "llvm/ADT/Twine.h"
2122
#include "llvm/Support/DynamicLibrary.h"
2223
#include "llvm/Support/FileSystem.h"
2324
#include "llvm/Support/raw_ostream.h"
@@ -2473,3 +2474,34 @@ bool PluginManager::SetUnwindAssemblyPluginEnabled(llvm::StringRef name,
24732474
bool enable) {
24742475
return GetUnwindAssemblyInstances().SetInstanceEnabled(name, enable);
24752476
}
2477+
2478+
void PluginManager::AutoCompletePluginName(llvm::StringRef name,
2479+
CompletionRequest &request) {
2480+
// Split the name into the namespace and the plugin name.
2481+
// If there is no dot then the ns_name will be equal to name and
2482+
// plugin_prefix will be empty.
2483+
llvm::StringRef ns_name, plugin_prefix;
2484+
std::tie(ns_name, plugin_prefix) = name.split('.');
2485+
2486+
for (const PluginNamespace &plugin_ns : GetPluginNamespaces()) {
2487+
// If the plugin namespace matches exactly then
2488+
// add all the plugins in this namespace as completions if the
2489+
// plugin names starts with the plugin_prefix. If the plugin_prefix
2490+
// is empty then it will match all the plugins (empty string is a
2491+
// prefix of everything).
2492+
if (plugin_ns.name == ns_name) {
2493+
for (const RegisteredPluginInfo &plugin : plugin_ns.get_info()) {
2494+
llvm::SmallString<128> buf;
2495+
if (plugin.name.starts_with(plugin_prefix))
2496+
request.AddCompletion(
2497+
(plugin_ns.name + "." + plugin.name).toStringRef(buf));
2498+
}
2499+
} else if (plugin_ns.name.starts_with(name) &&
2500+
!plugin_ns.get_info().empty()) {
2501+
// Otherwise check if the namespace is a prefix of the full name.
2502+
// Use a partial completion here so that we can either operate on the full
2503+
// namespace or tab-complete to the next level.
2504+
request.AddCompletion(plugin_ns.name, "", CompletionMode::Partial);
2505+
}
2506+
}
2507+
}

lldb/test/API/commands/plugin/TestPlugin.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,49 @@ def do_list_disable_enable_test(self, plugin_namespace):
6060
self.expect(
6161
f"plugin enable {plugin_namespace}", substrs=[plugin_namespace, "[+]"]
6262
)
63+
64+
def test_completions(self):
65+
# Make sure completions work for the plugin list, enable, and disable commands.
66+
# We just check a few of the expected plugins to make sure the completion works.
67+
self.completions_contain(
68+
"plugin list ", ["abi", "architecture", "disassembler"]
69+
)
70+
self.completions_contain(
71+
"plugin enable ", ["abi", "architecture", "disassembler"]
72+
)
73+
self.completions_contain(
74+
"plugin disable ", ["abi", "architecture", "disassembler"]
75+
)
76+
77+
# A completion for a partial namespace should be the full namespace.
78+
# This allows the user to run the command on the full namespace.
79+
self.completions_match("plugin list ab", ["abi"])
80+
self.completions_contain(
81+
"plugin list object", ["object-container", "object-file"]
82+
)
83+
84+
# A completion for a full namespace should contain the plugins in that namespace.
85+
self.completions_contain("plugin list abi", ["abi.sysv-x86_64"])
86+
self.completions_contain("plugin list abi.", ["abi.sysv-x86_64"])
87+
self.completions_contain("plugin list abi.s", ["abi.sysv-x86_64"])
88+
self.completions_contain("plugin list abi.sysv-x", ["abi.sysv-x86_64"])
89+
90+
# Check for a completion that is a both a complete namespace and a prefix of
91+
# another namespace. It should return the completions for the plugins in the completed
92+
# namespace as well as the completion for the partial namespace.
93+
self.completions_contain(
94+
"plugin list language", ["language.cplusplus", "language-runtime"]
95+
)
96+
97+
# When the namespace is a prefix of another namespace and the user types a dot, the
98+
# completion should not include the match for the partial namespace.
99+
self.completions_contain(
100+
"plugin list language.", ["language.cplusplus"], match=True
101+
)
102+
self.completions_contain(
103+
"plugin list language.", ["language-runtime"], match=False
104+
)
105+
106+
# Check for an empty completion list when the names is invalid.
107+
# See docs for `complete_from_to` for how this checks for an empty list.
108+
self.complete_from_to("plugin list abi.foo", ["plugin list abi.foo"])

0 commit comments

Comments
 (0)