Skip to content

Commit 6f5b98b

Browse files
authored
[lldb] returning command completions up to a maximum (#135565)
- Adding `max_return_elements` field to `CompletionRequest`. - adding maximum checks to `SymbolCompleter` and `SourceFileCompleter`. Fixes #135553
1 parent cef9ed5 commit 6f5b98b

File tree

5 files changed

+85
-8
lines changed

5 files changed

+85
-8
lines changed

lldb/include/lldb/Utility/CompletionRequest.h

+24
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ class CompletionRequest {
115115
CompletionRequest(llvm::StringRef command_line, unsigned raw_cursor_pos,
116116
CompletionResult &result);
117117

118+
/// Sets the maximum number of completions that should be returned.
119+
void SetMaxReturnElements(size_t max_return_elements) {
120+
m_max_return_elements = max_return_elements;
121+
}
122+
118123
/// Returns the raw user input used to create this CompletionRequest cut off
119124
/// at the cursor position. The cursor will be at the end of the raw line.
120125
llvm::StringRef GetRawLine() const {
@@ -157,6 +162,23 @@ class CompletionRequest {
157162

158163
size_t GetCursorIndex() const { return m_cursor_index; }
159164

165+
size_t GetMaxReturnElements() const { return m_max_return_elements; }
166+
167+
/// Returns true if the maximum number of completions has not been reached
168+
/// yet, hence we should keep adding completions.
169+
bool ShouldAddCompletions() const {
170+
return GetMaxNumberOfCompletionsToAdd() > 0;
171+
}
172+
173+
/// Returns the maximum number of completions that need to be added
174+
/// until reaching the maximum
175+
size_t GetMaxNumberOfCompletionsToAdd() const {
176+
const size_t number_of_results = m_result.GetNumberOfResults();
177+
if (number_of_results >= m_max_return_elements)
178+
return 0;
179+
return m_max_return_elements - number_of_results;
180+
}
181+
160182
/// Adds a possible completion string. If the completion was already
161183
/// suggested before, it will not be added to the list of results. A copy of
162184
/// the suggested completion is stored, so the given string can be free'd
@@ -231,6 +253,8 @@ class CompletionRequest {
231253
size_t m_cursor_index;
232254
/// The cursor position in the argument indexed by m_cursor_index.
233255
size_t m_cursor_char_position;
256+
/// The maximum number of completions that should be returned.
257+
size_t m_max_return_elements = std::numeric_limits<size_t>::max();
234258

235259
/// The result this request is supposed to fill out.
236260
/// We keep this object private to ensure that no backend can in any way

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -2257,12 +2257,12 @@ def complete_from_to(self, str_input, patterns):
22572257
substrs=[p],
22582258
)
22592259

2260-
def completions_match(self, command, completions):
2260+
def completions_match(self, command, completions, max_completions=-1):
22612261
"""Checks that the completions for the given command are equal to the
22622262
given list of completions"""
22632263
interp = self.dbg.GetCommandInterpreter()
22642264
match_strings = lldb.SBStringList()
2265-
interp.HandleCompletion(command, len(command), 0, -1, match_strings)
2265+
interp.HandleCompletion(command, len(command), 0, max_completions, match_strings)
22662266
# match_strings is a 1-indexed list, so we have to slice...
22672267
self.assertCountEqual(
22682268
completions, list(match_strings)[1:], "List of returned completion is wrong"

lldb/source/API/SBCommandInterpreter.cpp

+14-1
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,26 @@ int SBCommandInterpreter::HandleCompletionWithDescriptions(
263263
if (!IsValid())
264264
return 0;
265265

266+
if (max_return_elements == 0)
267+
return 0;
268+
266269
lldb_private::StringList lldb_matches, lldb_descriptions;
267270
CompletionResult result;
268271
CompletionRequest request(current_line, cursor - current_line, result);
272+
if (max_return_elements > 0)
273+
request.SetMaxReturnElements(max_return_elements);
269274
m_opaque_ptr->HandleCompletion(request);
270275
result.GetMatches(lldb_matches);
271276
result.GetDescriptions(lldb_descriptions);
272277

278+
// limit the matches to the max_return_elements if necessary
279+
if (max_return_elements > 0 &&
280+
lldb_matches.GetSize() > static_cast<size_t>(max_return_elements)) {
281+
lldb_matches.SetSize(max_return_elements);
282+
lldb_descriptions.SetSize(max_return_elements);
283+
}
284+
int number_of_matches = lldb_matches.GetSize();
285+
273286
// Make the result array indexed from 1 again by adding the 'common prefix'
274287
// of all completions as element 0. This is done to emulate the old API.
275288
if (request.GetParsedLine().GetArgumentCount() == 0) {
@@ -303,7 +316,7 @@ int SBCommandInterpreter::HandleCompletionWithDescriptions(
303316
matches.AppendList(temp_matches_list);
304317
SBStringList temp_descriptions_list(&lldb_descriptions);
305318
descriptions.AppendList(temp_descriptions_list);
306-
return result.GetNumberOfResults();
319+
return number_of_matches;
307320
}
308321

309322
int SBCommandInterpreter::HandleCompletionWithDescriptions(

lldb/source/Commands/CommandCompletions.cpp

+14-5
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ bool CommandCompletions::InvokeCommonCompletionCallbacks(
9191
nullptr} // This one has to be last in the list.
9292
};
9393

94-
for (int i = 0;; i++) {
94+
for (int i = 0; request.ShouldAddCompletions(); i++) {
9595
if (common_completions[i].type == lldb::eTerminatorCompletion)
9696
break;
9797
else if ((common_completions[i].type & completion_mask) ==
@@ -167,7 +167,9 @@ class SourceFileCompleter : public Completer {
167167
m_matching_files.AppendIfUnique(context.comp_unit->GetPrimaryFile());
168168
}
169169
}
170-
return Searcher::eCallbackReturnContinue;
170+
return m_matching_files.GetSize() >= m_request.GetMaxNumberOfCompletionsToAdd()
171+
? Searcher::eCallbackReturnStop
172+
: Searcher::eCallbackReturnContinue;
171173
}
172174

173175
void DoCompletion(SearchFilter *filter) override {
@@ -230,6 +232,9 @@ class SymbolCompleter : public Completer {
230232

231233
// Now add the functions & symbols to the list - only add if unique:
232234
for (const SymbolContext &sc : sc_list) {
235+
if (m_match_set.size() >= m_request.GetMaxNumberOfCompletionsToAdd())
236+
break;
237+
233238
ConstString func_name = sc.GetFunctionName(Mangled::ePreferDemangled);
234239
// Ensure that the function name matches the regex. This is more than
235240
// a sanity check. It is possible that the demangled function name
@@ -239,7 +244,9 @@ class SymbolCompleter : public Completer {
239244
m_match_set.insert(func_name);
240245
}
241246
}
242-
return Searcher::eCallbackReturnContinue;
247+
return m_match_set.size() >= m_request.GetMaxNumberOfCompletionsToAdd()
248+
? Searcher::eCallbackReturnStop
249+
: Searcher::eCallbackReturnContinue;
243250
}
244251

245252
void DoCompletion(SearchFilter *filter) override {
@@ -305,7 +312,8 @@ class ModuleCompleter : public Completer {
305312
m_request.AddCompletion(cur_file_name);
306313
}
307314
}
308-
return Searcher::eCallbackReturnContinue;
315+
return m_request.ShouldAddCompletions() ? Searcher::eCallbackReturnContinue
316+
: Searcher::eCallbackReturnStop;
309317
}
310318

311319
void DoCompletion(SearchFilter *filter) override { filter->Search(*this); }
@@ -429,7 +437,8 @@ static void DiskFilesOrDirectories(const llvm::Twine &partial_name,
429437
std::error_code EC;
430438
llvm::vfs::directory_iterator Iter = fs.DirBegin(SearchDir, EC);
431439
llvm::vfs::directory_iterator End;
432-
for (; Iter != End && !EC; Iter.increment(EC)) {
440+
for (; Iter != End && !EC && request.ShouldAddCompletions();
441+
Iter.increment(EC)) {
433442
auto &Entry = *Iter;
434443
llvm::ErrorOr<llvm::vfs::Status> Status = fs.GetStatus(Entry.path());
435444

lldb/test/API/commands/expression/completion/TestExprCompletion.py

+31
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,37 @@ def test_expr_completion_with_descriptions(self):
297297
enforce_order=True,
298298
)
299299

300+
def test_expr_completion_max_results(self):
301+
self.build()
302+
self.main_source = "main.cpp"
303+
self.main_source_spec = lldb.SBFileSpec(self.main_source)
304+
self.createTestTarget()
305+
306+
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
307+
self, "// Break here", self.main_source_spec
308+
)
309+
310+
expected_completions = [
311+
"some_expr.~Expr()",
312+
"some_expr.operator=(", # Copy operator
313+
"some_expr.operator=(", # Move operator
314+
"some_expr.MemberVariableBar",
315+
"some_expr.StaticMemberMethodBar()",
316+
"some_expr.Self()",
317+
"some_expr.FooNoArgsBar()",
318+
"some_expr.FooWithArgsBar(",
319+
"some_expr.FooNumbersBar1()",
320+
"some_expr.FooUnderscoreBar_()",
321+
"some_expr.FooWithMultipleArgsBar(",
322+
]
323+
324+
for i in range(1, len(expected_completions)):
325+
self.completions_match(
326+
"expr some_expr.",
327+
expected_completions[:i],
328+
max_completions=i,
329+
)
330+
300331
def assume_no_completions(self, str_input, cursor_pos=None):
301332
interp = self.dbg.GetCommandInterpreter()
302333
match_strings = lldb.SBStringList()

0 commit comments

Comments
 (0)