1 | //===-- CommandCompletions.cpp --------------------------------------------===// |
---|---|
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "llvm/ADT/STLExtras.h" |
10 | #include "llvm/ADT/SmallString.h" |
11 | #include "llvm/ADT/StringRef.h" |
12 | #include "llvm/ADT/StringSet.h" |
13 | |
14 | #include "lldb/Breakpoint/Watchpoint.h" |
15 | #include "lldb/Core/Module.h" |
16 | #include "lldb/Core/PluginManager.h" |
17 | #include "lldb/DataFormatters/DataVisualization.h" |
18 | #include "lldb/Host/FileSystem.h" |
19 | #include "lldb/Interpreter/CommandCompletions.h" |
20 | #include "lldb/Interpreter/CommandInterpreter.h" |
21 | #include "lldb/Interpreter/CommandObject.h" |
22 | #include "lldb/Interpreter/CommandObjectMultiword.h" |
23 | #include "lldb/Interpreter/OptionValueProperties.h" |
24 | #include "lldb/Symbol/CompileUnit.h" |
25 | #include "lldb/Symbol/Variable.h" |
26 | #include "lldb/Target/Language.h" |
27 | #include "lldb/Target/Process.h" |
28 | #include "lldb/Target/RegisterContext.h" |
29 | #include "lldb/Target/Thread.h" |
30 | #include "lldb/Utility/FileSpec.h" |
31 | #include "lldb/Utility/FileSpecList.h" |
32 | #include "lldb/Utility/StreamString.h" |
33 | #include "lldb/Utility/TildeExpressionResolver.h" |
34 | |
35 | #include "llvm/Support/FileSystem.h" |
36 | #include "llvm/Support/Path.h" |
37 | |
38 | using namespace lldb_private; |
39 | |
40 | // This is the command completion callback that is used to complete the |
41 | // argument of the option it is bound to (in the OptionDefinition table |
42 | // below). |
43 | typedef void (*CompletionCallback)(CommandInterpreter &interpreter, |
44 | CompletionRequest &request, |
45 | // A search filter to limit the search... |
46 | lldb_private::SearchFilter *searcher); |
47 | |
48 | struct CommonCompletionElement { |
49 | uint64_t type; |
50 | CompletionCallback callback; |
51 | }; |
52 | |
53 | bool CommandCompletions::InvokeCommonCompletionCallbacks( |
54 | CommandInterpreter &interpreter, uint32_t completion_mask, |
55 | CompletionRequest &request, SearchFilter *searcher) { |
56 | bool handled = false; |
57 | |
58 | const CommonCompletionElement common_completions[] = { |
59 | {.type: lldb::eNoCompletion, .callback: nullptr}, |
60 | {.type: lldb::eSourceFileCompletion, .callback: CommandCompletions::SourceFiles}, |
61 | {.type: lldb::eDiskFileCompletion, .callback: CommandCompletions::DiskFiles}, |
62 | {.type: lldb::eDiskDirectoryCompletion, .callback: CommandCompletions::DiskDirectories}, |
63 | {.type: lldb::eSymbolCompletion, .callback: CommandCompletions::Symbols}, |
64 | {.type: lldb::eModuleCompletion, .callback: CommandCompletions::Modules}, |
65 | {.type: lldb::eModuleUUIDCompletion, .callback: CommandCompletions::ModuleUUIDs}, |
66 | {.type: lldb::eSettingsNameCompletion, .callback: CommandCompletions::SettingsNames}, |
67 | {.type: lldb::ePlatformPluginCompletion, |
68 | .callback: CommandCompletions::PlatformPluginNames}, |
69 | {.type: lldb::eArchitectureCompletion, .callback: CommandCompletions::ArchitectureNames}, |
70 | {.type: lldb::eVariablePathCompletion, .callback: CommandCompletions::VariablePath}, |
71 | {.type: lldb::eRegisterCompletion, .callback: CommandCompletions::Registers}, |
72 | {.type: lldb::eBreakpointCompletion, .callback: CommandCompletions::Breakpoints}, |
73 | {.type: lldb::eProcessPluginCompletion, .callback: CommandCompletions::ProcessPluginNames}, |
74 | {.type: lldb::eDisassemblyFlavorCompletion, |
75 | .callback: CommandCompletions::DisassemblyFlavors}, |
76 | {.type: lldb::eTypeLanguageCompletion, .callback: CommandCompletions::TypeLanguages}, |
77 | {.type: lldb::eFrameIndexCompletion, .callback: CommandCompletions::FrameIndexes}, |
78 | {.type: lldb::eStopHookIDCompletion, .callback: CommandCompletions::StopHookIDs}, |
79 | {.type: lldb::eThreadIndexCompletion, .callback: CommandCompletions::ThreadIndexes}, |
80 | {.type: lldb::eWatchpointIDCompletion, .callback: CommandCompletions::WatchPointIDs}, |
81 | {.type: lldb::eBreakpointNameCompletion, .callback: CommandCompletions::BreakpointNames}, |
82 | {.type: lldb::eProcessIDCompletion, .callback: CommandCompletions::ProcessIDs}, |
83 | {.type: lldb::eProcessNameCompletion, .callback: CommandCompletions::ProcessNames}, |
84 | {.type: lldb::eRemoteDiskFileCompletion, .callback: CommandCompletions::RemoteDiskFiles}, |
85 | {.type: lldb::eRemoteDiskDirectoryCompletion, |
86 | .callback: CommandCompletions::RemoteDiskDirectories}, |
87 | {.type: lldb::eTypeCategoryNameCompletion, |
88 | .callback: CommandCompletions::TypeCategoryNames}, |
89 | {.type: lldb::eThreadIDCompletion, .callback: CommandCompletions::ThreadIDs}, |
90 | {.type: lldb::eTerminatorCompletion, |
91 | .callback: nullptr} // This one has to be last in the list. |
92 | }; |
93 | |
94 | for (int i = 0; request.ShouldAddCompletions(); i++) { |
95 | if (common_completions[i].type == lldb::eTerminatorCompletion) |
96 | break; |
97 | else if ((common_completions[i].type & completion_mask) == |
98 | common_completions[i].type && |
99 | common_completions[i].callback != nullptr) { |
100 | handled = true; |
101 | common_completions[i].callback(interpreter, request, searcher); |
102 | } |
103 | } |
104 | return handled; |
105 | } |
106 | |
107 | namespace { |
108 | // The Completer class is a convenient base class for building searchers that |
109 | // go along with the SearchFilter passed to the standard Completer functions. |
110 | class Completer : public Searcher { |
111 | public: |
112 | Completer(CommandInterpreter &interpreter, CompletionRequest &request) |
113 | : m_interpreter(interpreter), m_request(request) {} |
114 | |
115 | ~Completer() override = default; |
116 | |
117 | CallbackReturn SearchCallback(SearchFilter &filter, SymbolContext &context, |
118 | Address *addr) override = 0; |
119 | |
120 | lldb::SearchDepth GetDepth() override = 0; |
121 | |
122 | virtual void DoCompletion(SearchFilter *filter) = 0; |
123 | |
124 | protected: |
125 | CommandInterpreter &m_interpreter; |
126 | CompletionRequest &m_request; |
127 | |
128 | private: |
129 | Completer(const Completer &) = delete; |
130 | const Completer &operator=(const Completer &) = delete; |
131 | }; |
132 | } // namespace |
133 | |
134 | // SourceFileCompleter implements the source file completer |
135 | namespace { |
136 | class SourceFileCompleter : public Completer { |
137 | public: |
138 | SourceFileCompleter(CommandInterpreter &interpreter, |
139 | CompletionRequest &request) |
140 | : Completer(interpreter, request) { |
141 | FileSpec partial_spec(m_request.GetCursorArgumentPrefix()); |
142 | m_file_name = partial_spec.GetFilename().GetCString(); |
143 | m_dir_name = partial_spec.GetDirectory().GetCString(); |
144 | } |
145 | |
146 | lldb::SearchDepth GetDepth() override { return lldb::eSearchDepthCompUnit; } |
147 | |
148 | Searcher::CallbackReturn SearchCallback(SearchFilter &filter, |
149 | SymbolContext &context, |
150 | Address *addr) override { |
151 | if (context.comp_unit != nullptr) { |
152 | const char *cur_file_name = |
153 | context.comp_unit->GetPrimaryFile().GetFilename().GetCString(); |
154 | const char *cur_dir_name = |
155 | context.comp_unit->GetPrimaryFile().GetDirectory().GetCString(); |
156 | |
157 | bool match = false; |
158 | if (m_file_name && cur_file_name && |
159 | strstr(haystack: cur_file_name, needle: m_file_name) == cur_file_name) |
160 | match = true; |
161 | |
162 | if (match && m_dir_name && cur_dir_name && |
163 | strstr(haystack: cur_dir_name, needle: m_dir_name) != cur_dir_name) |
164 | match = false; |
165 | |
166 | if (match) { |
167 | m_matching_files.AppendIfUnique(file: context.comp_unit->GetPrimaryFile()); |
168 | } |
169 | } |
170 | return m_matching_files.GetSize() >= m_request.GetMaxNumberOfCompletionsToAdd() |
171 | ? Searcher::eCallbackReturnStop |
172 | : Searcher::eCallbackReturnContinue; |
173 | } |
174 | |
175 | void DoCompletion(SearchFilter *filter) override { |
176 | filter->Search(searcher&: *this); |
177 | // Now convert the filelist to completions: |
178 | for (size_t i = 0; i < m_matching_files.GetSize(); i++) { |
179 | m_request.AddCompletion( |
180 | completion: m_matching_files.GetFileSpecAtIndex(idx: i).GetFilename().GetCString()); |
181 | } |
182 | } |
183 | |
184 | private: |
185 | FileSpecList m_matching_files; |
186 | const char *m_file_name; |
187 | const char *m_dir_name; |
188 | |
189 | SourceFileCompleter(const SourceFileCompleter &) = delete; |
190 | const SourceFileCompleter &operator=(const SourceFileCompleter &) = delete; |
191 | }; |
192 | } // namespace |
193 | |
194 | static bool regex_chars(const char comp) { |
195 | return llvm::StringRef("[](){}+.*|^$\\?").contains(C: comp); |
196 | } |
197 | |
198 | namespace { |
199 | class SymbolCompleter : public Completer { |
200 | |
201 | public: |
202 | SymbolCompleter(CommandInterpreter &interpreter, CompletionRequest &request) |
203 | : Completer(interpreter, request) { |
204 | std::string regex_str; |
205 | if (!m_request.GetCursorArgumentPrefix().empty()) { |
206 | regex_str.append(s: "^"); |
207 | regex_str.append(str: std::string(m_request.GetCursorArgumentPrefix())); |
208 | } else { |
209 | // Match anything since the completion string is empty |
210 | regex_str.append(s: "."); |
211 | } |
212 | std::string::iterator pos = |
213 | find_if(first: regex_str.begin() + 1, last: regex_str.end(), pred: regex_chars); |
214 | while (pos < regex_str.end()) { |
215 | pos = regex_str.insert(p: pos, c: '\\'); |
216 | pos = find_if(first: pos + 2, last: regex_str.end(), pred: regex_chars); |
217 | } |
218 | m_regex = RegularExpression(regex_str); |
219 | } |
220 | |
221 | lldb::SearchDepth GetDepth() override { return lldb::eSearchDepthModule; } |
222 | |
223 | Searcher::CallbackReturn SearchCallback(SearchFilter &filter, |
224 | SymbolContext &context, |
225 | Address *addr) override { |
226 | if (context.module_sp) { |
227 | SymbolContextList sc_list; |
228 | ModuleFunctionSearchOptions function_options; |
229 | function_options.include_symbols = true; |
230 | function_options.include_inlines = true; |
231 | context.module_sp->FindFunctions(regex: m_regex, options: function_options, sc_list); |
232 | |
233 | // Now add the functions & symbols to the list - only add if unique: |
234 | for (const SymbolContext &sc : sc_list) { |
235 | if (m_match_set.size() >= m_request.GetMaxNumberOfCompletionsToAdd()) |
236 | break; |
237 | |
238 | ConstString func_name = sc.GetFunctionName(preference: Mangled::ePreferDemangled); |
239 | // Ensure that the function name matches the regex. This is more than |
240 | // a sanity check. It is possible that the demangled function name |
241 | // does not start with the prefix, for example when it's in an |
242 | // anonymous namespace. |
243 | if (!func_name.IsEmpty() && m_regex.Execute(string: func_name.GetStringRef())) |
244 | m_match_set.insert(x: func_name); |
245 | } |
246 | } |
247 | return m_match_set.size() >= m_request.GetMaxNumberOfCompletionsToAdd() |
248 | ? Searcher::eCallbackReturnStop |
249 | : Searcher::eCallbackReturnContinue; |
250 | } |
251 | |
252 | void DoCompletion(SearchFilter *filter) override { |
253 | filter->Search(searcher&: *this); |
254 | collection::iterator pos = m_match_set.begin(), end = m_match_set.end(); |
255 | for (pos = m_match_set.begin(); pos != end; pos++) |
256 | m_request.AddCompletion(completion: (*pos).GetCString()); |
257 | } |
258 | |
259 | private: |
260 | RegularExpression m_regex; |
261 | typedef std::set<ConstString> collection; |
262 | collection m_match_set; |
263 | |
264 | SymbolCompleter(const SymbolCompleter &) = delete; |
265 | const SymbolCompleter &operator=(const SymbolCompleter &) = delete; |
266 | }; |
267 | } // namespace |
268 | |
269 | namespace { |
270 | class ModuleCompleter : public Completer { |
271 | public: |
272 | ModuleCompleter(CommandInterpreter &interpreter, CompletionRequest &request) |
273 | : Completer(interpreter, request) { |
274 | llvm::StringRef request_str = m_request.GetCursorArgumentPrefix(); |
275 | // We can match the full path, or the file name only. The full match will be |
276 | // attempted always, the file name match only if the request does not |
277 | // contain a path separator. |
278 | |
279 | // Preserve both the path as spelled by the user (used for completion) and |
280 | // the canonical version (used for matching). |
281 | m_spelled_path = request_str; |
282 | m_canonical_path = FileSpec(m_spelled_path).GetPath(); |
283 | if (!m_spelled_path.empty() && |
284 | llvm::sys::path::is_separator(value: m_spelled_path.back()) && |
285 | !llvm::StringRef(m_canonical_path).ends_with(Suffix: m_spelled_path.back())) { |
286 | m_canonical_path += m_spelled_path.back(); |
287 | } |
288 | |
289 | if (llvm::find_if(Range&: request_str, P: [](char c) { |
290 | return llvm::sys::path::is_separator(value: c); |
291 | }) == request_str.end()) |
292 | m_file_name = request_str; |
293 | } |
294 | |
295 | lldb::SearchDepth GetDepth() override { return lldb::eSearchDepthModule; } |
296 | |
297 | Searcher::CallbackReturn SearchCallback(SearchFilter &filter, |
298 | SymbolContext &context, |
299 | Address *addr) override { |
300 | if (context.module_sp) { |
301 | // Attempt a full path match. |
302 | std::string cur_path = context.module_sp->GetFileSpec().GetPath(); |
303 | llvm::StringRef cur_path_view = cur_path; |
304 | if (cur_path_view.consume_front(Prefix: m_canonical_path)) |
305 | m_request.AddCompletion(completion: (m_spelled_path + cur_path_view).str()); |
306 | |
307 | // And a file name match. |
308 | if (m_file_name) { |
309 | llvm::StringRef cur_file_name = |
310 | context.module_sp->GetFileSpec().GetFilename().GetStringRef(); |
311 | if (cur_file_name.starts_with(Prefix: *m_file_name)) |
312 | m_request.AddCompletion(completion: cur_file_name); |
313 | } |
314 | } |
315 | return m_request.ShouldAddCompletions() ? Searcher::eCallbackReturnContinue |
316 | : Searcher::eCallbackReturnStop; |
317 | } |
318 | |
319 | void DoCompletion(SearchFilter *filter) override { filter->Search(searcher&: *this); } |
320 | |
321 | private: |
322 | std::optional<llvm::StringRef> m_file_name; |
323 | llvm::StringRef m_spelled_path; |
324 | std::string m_canonical_path; |
325 | |
326 | ModuleCompleter(const ModuleCompleter &) = delete; |
327 | const ModuleCompleter &operator=(const ModuleCompleter &) = delete; |
328 | }; |
329 | } // namespace |
330 | |
331 | void CommandCompletions::SourceFiles(CommandInterpreter &interpreter, |
332 | CompletionRequest &request, |
333 | SearchFilter *searcher) { |
334 | SourceFileCompleter completer(interpreter, request); |
335 | |
336 | if (searcher == nullptr) { |
337 | lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); |
338 | SearchFilterForUnconstrainedSearches null_searcher(target_sp); |
339 | completer.DoCompletion(filter: &null_searcher); |
340 | } else { |
341 | completer.DoCompletion(filter: searcher); |
342 | } |
343 | } |
344 | |
345 | static void DiskFilesOrDirectories(const llvm::Twine &partial_name, |
346 | bool only_directories, |
347 | CompletionRequest &request, |
348 | TildeExpressionResolver &Resolver) { |
349 | llvm::SmallString<256> CompletionBuffer; |
350 | llvm::SmallString<256> Storage; |
351 | partial_name.toVector(Out&: CompletionBuffer); |
352 | |
353 | if (CompletionBuffer.size() >= PATH_MAX) |
354 | return; |
355 | |
356 | namespace path = llvm::sys::path; |
357 | |
358 | llvm::StringRef SearchDir; |
359 | llvm::StringRef PartialItem; |
360 | |
361 | if (CompletionBuffer.starts_with(Prefix: "~")) { |
362 | llvm::StringRef Buffer = CompletionBuffer; |
363 | size_t FirstSep = |
364 | Buffer.find_if(F: [](char c) { return path::is_separator(value: c); }); |
365 | |
366 | llvm::StringRef Username = Buffer.take_front(N: FirstSep); |
367 | llvm::StringRef Remainder; |
368 | if (FirstSep != llvm::StringRef::npos) |
369 | Remainder = Buffer.drop_front(N: FirstSep + 1); |
370 | |
371 | llvm::SmallString<256> Resolved; |
372 | if (!Resolver.ResolveExact(Expr: Username, Output&: Resolved)) { |
373 | // We couldn't resolve it as a full username. If there were no slashes |
374 | // then this might be a partial username. We try to resolve it as such |
375 | // but after that, we're done regardless of any matches. |
376 | if (FirstSep == llvm::StringRef::npos) { |
377 | llvm::StringSet<> MatchSet; |
378 | Resolver.ResolvePartial(Expr: Username, Output&: MatchSet); |
379 | for (const auto &S : MatchSet) { |
380 | Resolved = S.getKey(); |
381 | path::append(path&: Resolved, a: path::get_separator()); |
382 | request.AddCompletion(completion: Resolved, description: "", mode: CompletionMode::Partial); |
383 | } |
384 | } |
385 | return; |
386 | } |
387 | |
388 | // If there was no trailing slash, then we're done as soon as we resolve |
389 | // the expression to the correct directory. Otherwise we need to continue |
390 | // looking for matches within that directory. |
391 | if (FirstSep == llvm::StringRef::npos) { |
392 | // Make sure it ends with a separator. |
393 | path::append(path&: CompletionBuffer, a: path::get_separator()); |
394 | request.AddCompletion(completion: CompletionBuffer, description: "", mode: CompletionMode::Partial); |
395 | return; |
396 | } |
397 | |
398 | // We want to keep the form the user typed, so we special case this to |
399 | // search in the fully resolved directory, but CompletionBuffer keeps the |
400 | // unmodified form that the user typed. |
401 | Storage = Resolved; |
402 | llvm::StringRef RemainderDir = path::parent_path(path: Remainder); |
403 | if (!RemainderDir.empty()) { |
404 | // Append the remaining path to the resolved directory. |
405 | Storage.append(RHS: path::get_separator()); |
406 | Storage.append(RHS: RemainderDir); |
407 | } |
408 | SearchDir = Storage; |
409 | } else if (CompletionBuffer == path::root_directory(path: CompletionBuffer)) { |
410 | SearchDir = CompletionBuffer; |
411 | } else { |
412 | SearchDir = path::parent_path(path: CompletionBuffer); |
413 | } |
414 | |
415 | size_t FullPrefixLen = CompletionBuffer.size(); |
416 | |
417 | PartialItem = path::filename(path: CompletionBuffer); |
418 | |
419 | // path::filename() will return "." when the passed path ends with a |
420 | // directory separator or the separator when passed the disk root directory. |
421 | // We have to filter those out, but only when the "." doesn't come from the |
422 | // completion request itself. |
423 | if ((PartialItem == "."|| PartialItem == path::get_separator()) && |
424 | path::is_separator(value: CompletionBuffer.back())) |
425 | PartialItem = llvm::StringRef(); |
426 | |
427 | if (SearchDir.empty()) { |
428 | llvm::sys::fs::current_path(result&: Storage); |
429 | SearchDir = Storage; |
430 | } |
431 | assert(!PartialItem.contains(path::get_separator())); |
432 | |
433 | // SearchDir now contains the directory to search in, and Prefix contains the |
434 | // text we want to match against items in that directory. |
435 | |
436 | FileSystem &fs = FileSystem::Instance(); |
437 | std::error_code EC; |
438 | llvm::vfs::directory_iterator Iter = fs.DirBegin(dir: SearchDir, ec&: EC); |
439 | llvm::vfs::directory_iterator End; |
440 | for (; Iter != End && !EC && request.ShouldAddCompletions(); |
441 | Iter.increment(EC)) { |
442 | auto &Entry = *Iter; |
443 | llvm::ErrorOr<llvm::vfs::Status> Status = fs.GetStatus(path: Entry.path()); |
444 | |
445 | if (!Status) |
446 | continue; |
447 | |
448 | auto Name = path::filename(path: Entry.path()); |
449 | |
450 | // Omit ".", ".." |
451 | if (Name == "."|| Name == ".."|| !Name.starts_with(Prefix: PartialItem)) |
452 | continue; |
453 | |
454 | bool is_dir = Status->isDirectory(); |
455 | |
456 | // If it's a symlink, then we treat it as a directory as long as the target |
457 | // is a directory. |
458 | if (Status->isSymlink()) { |
459 | FileSpec symlink_filespec(Entry.path()); |
460 | FileSpec resolved_filespec; |
461 | auto error = fs.ResolveSymbolicLink(src: symlink_filespec, dst&: resolved_filespec); |
462 | if (error.Success()) |
463 | is_dir = fs.IsDirectory(file_spec: symlink_filespec); |
464 | } |
465 | |
466 | if (only_directories && !is_dir) |
467 | continue; |
468 | |
469 | // Shrink it back down so that it just has the original prefix the user |
470 | // typed and remove the part of the name which is common to the located |
471 | // item and what the user typed. |
472 | CompletionBuffer.resize(N: FullPrefixLen); |
473 | Name = Name.drop_front(N: PartialItem.size()); |
474 | CompletionBuffer.append(RHS: Name); |
475 | |
476 | if (is_dir) { |
477 | path::append(path&: CompletionBuffer, a: path::get_separator()); |
478 | } |
479 | |
480 | CompletionMode mode = |
481 | is_dir ? CompletionMode::Partial : CompletionMode::Normal; |
482 | request.AddCompletion(completion: CompletionBuffer, description: "", mode); |
483 | } |
484 | } |
485 | |
486 | static void DiskFilesOrDirectories(const llvm::Twine &partial_name, |
487 | bool only_directories, StringList &matches, |
488 | TildeExpressionResolver &Resolver) { |
489 | CompletionResult result; |
490 | std::string partial_name_str = partial_name.str(); |
491 | CompletionRequest request(partial_name_str, partial_name_str.size(), result); |
492 | DiskFilesOrDirectories(partial_name, only_directories, request, Resolver); |
493 | result.GetMatches(matches); |
494 | } |
495 | |
496 | static void DiskFilesOrDirectories(CompletionRequest &request, |
497 | bool only_directories) { |
498 | StandardTildeExpressionResolver resolver; |
499 | DiskFilesOrDirectories(partial_name: request.GetCursorArgumentPrefix(), only_directories, |
500 | request, Resolver&: resolver); |
501 | } |
502 | |
503 | void CommandCompletions::DiskFiles(CommandInterpreter &interpreter, |
504 | CompletionRequest &request, |
505 | SearchFilter *searcher) { |
506 | DiskFilesOrDirectories(request, /*only_dirs*/ only_directories: false); |
507 | } |
508 | |
509 | void CommandCompletions::DiskFiles(const llvm::Twine &partial_file_name, |
510 | StringList &matches, |
511 | TildeExpressionResolver &Resolver) { |
512 | DiskFilesOrDirectories(partial_name: partial_file_name, only_directories: false, matches, Resolver); |
513 | } |
514 | |
515 | void CommandCompletions::DiskDirectories(CommandInterpreter &interpreter, |
516 | CompletionRequest &request, |
517 | SearchFilter *searcher) { |
518 | DiskFilesOrDirectories(request, /*only_dirs*/ only_directories: true); |
519 | } |
520 | |
521 | void CommandCompletions::DiskDirectories(const llvm::Twine &partial_file_name, |
522 | StringList &matches, |
523 | TildeExpressionResolver &Resolver) { |
524 | DiskFilesOrDirectories(partial_name: partial_file_name, only_directories: true, matches, Resolver); |
525 | } |
526 | |
527 | void CommandCompletions::RemoteDiskFiles(CommandInterpreter &interpreter, |
528 | CompletionRequest &request, |
529 | SearchFilter *searcher) { |
530 | lldb::PlatformSP platform_sp = |
531 | interpreter.GetDebugger().GetPlatformList().GetSelectedPlatform(); |
532 | if (platform_sp) |
533 | platform_sp->AutoCompleteDiskFileOrDirectory(request, only_dir: false); |
534 | } |
535 | |
536 | void CommandCompletions::RemoteDiskDirectories(CommandInterpreter &interpreter, |
537 | CompletionRequest &request, |
538 | SearchFilter *searcher) { |
539 | lldb::PlatformSP platform_sp = |
540 | interpreter.GetDebugger().GetPlatformList().GetSelectedPlatform(); |
541 | if (platform_sp) |
542 | platform_sp->AutoCompleteDiskFileOrDirectory(request, only_dir: true); |
543 | } |
544 | |
545 | void CommandCompletions::Modules(CommandInterpreter &interpreter, |
546 | CompletionRequest &request, |
547 | SearchFilter *searcher) { |
548 | ModuleCompleter completer(interpreter, request); |
549 | |
550 | if (searcher == nullptr) { |
551 | lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); |
552 | SearchFilterForUnconstrainedSearches null_searcher(target_sp); |
553 | completer.DoCompletion(filter: &null_searcher); |
554 | } else { |
555 | completer.DoCompletion(filter: searcher); |
556 | } |
557 | } |
558 | |
559 | void CommandCompletions::ModuleUUIDs(CommandInterpreter &interpreter, |
560 | CompletionRequest &request, |
561 | SearchFilter *searcher) { |
562 | const ExecutionContext &exe_ctx = interpreter.GetExecutionContext(); |
563 | if (!exe_ctx.HasTargetScope()) |
564 | return; |
565 | |
566 | exe_ctx.GetTargetPtr()->GetImages().ForEach( |
567 | callback: [&request](const lldb::ModuleSP &module) { |
568 | StreamString strm; |
569 | module->GetDescription(s&: strm.AsRawOstream(), |
570 | level: lldb::eDescriptionLevelInitial); |
571 | request.TryCompleteCurrentArg(completion: module->GetUUID().GetAsString(), |
572 | description: strm.GetString()); |
573 | return true; |
574 | }); |
575 | } |
576 | |
577 | void CommandCompletions::Symbols(CommandInterpreter &interpreter, |
578 | CompletionRequest &request, |
579 | SearchFilter *searcher) { |
580 | SymbolCompleter completer(interpreter, request); |
581 | |
582 | if (searcher == nullptr) { |
583 | lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); |
584 | SearchFilterForUnconstrainedSearches null_searcher(target_sp); |
585 | completer.DoCompletion(filter: &null_searcher); |
586 | } else { |
587 | completer.DoCompletion(filter: searcher); |
588 | } |
589 | } |
590 | |
591 | void CommandCompletions::SettingsNames(CommandInterpreter &interpreter, |
592 | CompletionRequest &request, |
593 | SearchFilter *searcher) { |
594 | // Cache the full setting name list |
595 | static StringList g_property_names; |
596 | if (g_property_names.GetSize() == 0) { |
597 | // Generate the full setting name list on demand |
598 | lldb::OptionValuePropertiesSP properties_sp( |
599 | interpreter.GetDebugger().GetValueProperties()); |
600 | if (properties_sp) { |
601 | StreamString strm; |
602 | properties_sp->DumpValue(exe_ctx: nullptr, strm, dump_mask: OptionValue::eDumpOptionName); |
603 | const std::string &str = std::string(strm.GetString()); |
604 | g_property_names.SplitIntoLines(lines: str.c_str(), len: str.size()); |
605 | } |
606 | } |
607 | |
608 | for (const std::string &s : g_property_names) |
609 | request.TryCompleteCurrentArg(completion: s); |
610 | } |
611 | |
612 | void CommandCompletions::PlatformPluginNames(CommandInterpreter &interpreter, |
613 | CompletionRequest &request, |
614 | SearchFilter *searcher) { |
615 | PluginManager::AutoCompletePlatformName(partial_name: request.GetCursorArgumentPrefix(), |
616 | request); |
617 | } |
618 | |
619 | void CommandCompletions::ArchitectureNames(CommandInterpreter &interpreter, |
620 | CompletionRequest &request, |
621 | SearchFilter *searcher) { |
622 | ArchSpec::AutoComplete(request); |
623 | } |
624 | |
625 | void CommandCompletions::VariablePath(CommandInterpreter &interpreter, |
626 | CompletionRequest &request, |
627 | SearchFilter *searcher) { |
628 | Variable::AutoComplete(exe_ctx: interpreter.GetExecutionContext(), request); |
629 | } |
630 | |
631 | void CommandCompletions::Registers(CommandInterpreter &interpreter, |
632 | CompletionRequest &request, |
633 | SearchFilter *searcher) { |
634 | std::string reg_prefix; |
635 | if (request.GetCursorArgumentPrefix().starts_with(Prefix: "$")) |
636 | reg_prefix = "$"; |
637 | |
638 | RegisterContext *reg_ctx = |
639 | interpreter.GetExecutionContext().GetRegisterContext(); |
640 | if (!reg_ctx) |
641 | return; |
642 | |
643 | const size_t reg_num = reg_ctx->GetRegisterCount(); |
644 | for (size_t reg_idx = 0; reg_idx < reg_num; ++reg_idx) { |
645 | const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoAtIndex(reg: reg_idx); |
646 | request.TryCompleteCurrentArg(completion: reg_prefix + reg_info->name, |
647 | description: reg_info->alt_name); |
648 | } |
649 | } |
650 | |
651 | void CommandCompletions::Breakpoints(CommandInterpreter &interpreter, |
652 | CompletionRequest &request, |
653 | SearchFilter *searcher) { |
654 | lldb::TargetSP target = interpreter.GetDebugger().GetSelectedTarget(); |
655 | if (!target) |
656 | return; |
657 | |
658 | const BreakpointList &breakpoints = target->GetBreakpointList(); |
659 | |
660 | std::unique_lock<std::recursive_mutex> lock; |
661 | target->GetBreakpointList().GetListMutex(lock); |
662 | |
663 | size_t num_breakpoints = breakpoints.GetSize(); |
664 | if (num_breakpoints == 0) |
665 | return; |
666 | |
667 | for (size_t i = 0; i < num_breakpoints; ++i) { |
668 | lldb::BreakpointSP bp = breakpoints.GetBreakpointAtIndex(i); |
669 | |
670 | StreamString s; |
671 | bp->GetDescription(s: &s, level: lldb::eDescriptionLevelBrief); |
672 | llvm::StringRef bp_info = s.GetString(); |
673 | |
674 | const size_t colon_pos = bp_info.find_first_of(C: ':'); |
675 | if (colon_pos != llvm::StringRef::npos) |
676 | bp_info = bp_info.drop_front(N: colon_pos + 2); |
677 | |
678 | request.TryCompleteCurrentArg(completion: std::to_string(val: bp->GetID()), description: bp_info); |
679 | } |
680 | } |
681 | |
682 | void CommandCompletions::BreakpointNames(CommandInterpreter &interpreter, |
683 | CompletionRequest &request, |
684 | SearchFilter *searcher) { |
685 | lldb::TargetSP target = interpreter.GetDebugger().GetSelectedTarget(); |
686 | if (!target) |
687 | return; |
688 | |
689 | std::vector<std::string> name_list; |
690 | target->GetBreakpointNames(names&: name_list); |
691 | |
692 | for (const std::string &name : name_list) |
693 | request.TryCompleteCurrentArg(completion: name); |
694 | } |
695 | |
696 | void CommandCompletions::ProcessPluginNames(CommandInterpreter &interpreter, |
697 | CompletionRequest &request, |
698 | SearchFilter *searcher) { |
699 | PluginManager::AutoCompleteProcessName(partial_name: request.GetCursorArgumentPrefix(), |
700 | request); |
701 | } |
702 | void CommandCompletions::DisassemblyFlavors(CommandInterpreter &interpreter, |
703 | CompletionRequest &request, |
704 | SearchFilter *searcher) { |
705 | // Currently the only valid options for disassemble -F are default, and for |
706 | // Intel architectures, att and intel. |
707 | static const char *flavors[] = {"default", "att", "intel"}; |
708 | for (const char *flavor : flavors) { |
709 | request.TryCompleteCurrentArg(completion: flavor); |
710 | } |
711 | } |
712 | |
713 | void CommandCompletions::ProcessIDs(CommandInterpreter &interpreter, |
714 | CompletionRequest &request, |
715 | SearchFilter *searcher) { |
716 | lldb::PlatformSP platform_sp(interpreter.GetPlatform(prefer_target_platform: true)); |
717 | if (!platform_sp) |
718 | return; |
719 | ProcessInstanceInfoList process_infos; |
720 | ProcessInstanceInfoMatch match_info; |
721 | platform_sp->FindProcesses(match_info, proc_infos&: process_infos); |
722 | for (const ProcessInstanceInfo &info : process_infos) |
723 | request.TryCompleteCurrentArg(completion: std::to_string(val: info.GetProcessID()), |
724 | description: info.GetNameAsStringRef()); |
725 | } |
726 | |
727 | void CommandCompletions::ProcessNames(CommandInterpreter &interpreter, |
728 | CompletionRequest &request, |
729 | SearchFilter *searcher) { |
730 | lldb::PlatformSP platform_sp(interpreter.GetPlatform(prefer_target_platform: true)); |
731 | if (!platform_sp) |
732 | return; |
733 | ProcessInstanceInfoList process_infos; |
734 | ProcessInstanceInfoMatch match_info; |
735 | platform_sp->FindProcesses(match_info, proc_infos&: process_infos); |
736 | for (const ProcessInstanceInfo &info : process_infos) |
737 | request.TryCompleteCurrentArg(completion: info.GetNameAsStringRef()); |
738 | } |
739 | |
740 | void CommandCompletions::TypeLanguages(CommandInterpreter &interpreter, |
741 | CompletionRequest &request, |
742 | SearchFilter *searcher) { |
743 | for (int bit : |
744 | Language::GetLanguagesSupportingTypeSystems().bitvector.set_bits()) { |
745 | request.TryCompleteCurrentArg( |
746 | completion: Language::GetNameForLanguageType(language: static_cast<lldb::LanguageType>(bit))); |
747 | } |
748 | } |
749 | |
750 | void CommandCompletions::FrameIndexes(CommandInterpreter &interpreter, |
751 | CompletionRequest &request, |
752 | SearchFilter *searcher) { |
753 | const ExecutionContext &exe_ctx = interpreter.GetExecutionContext(); |
754 | if (!exe_ctx.HasProcessScope()) |
755 | return; |
756 | |
757 | lldb::ThreadSP thread_sp = exe_ctx.GetThreadSP(); |
758 | Debugger &dbg = interpreter.GetDebugger(); |
759 | const uint32_t frame_num = thread_sp->GetStackFrameCount(); |
760 | for (uint32_t i = 0; i < frame_num; ++i) { |
761 | lldb::StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(idx: i); |
762 | StreamString strm; |
763 | // Dumping frames can be slow, allow interruption. |
764 | if (INTERRUPT_REQUESTED(dbg, "Interrupted in frame completion")) |
765 | break; |
766 | frame_sp->Dump(strm: &strm, show_frame_index: false, show_fullpaths: true); |
767 | request.TryCompleteCurrentArg(completion: std::to_string(val: i), description: strm.GetString()); |
768 | } |
769 | } |
770 | |
771 | void CommandCompletions::StopHookIDs(CommandInterpreter &interpreter, |
772 | CompletionRequest &request, |
773 | SearchFilter *searcher) { |
774 | const lldb::TargetSP target_sp = |
775 | interpreter.GetExecutionContext().GetTargetSP(); |
776 | if (!target_sp) |
777 | return; |
778 | |
779 | const size_t num = target_sp->GetNumStopHooks(); |
780 | for (size_t idx = 0; idx < num; ++idx) { |
781 | StreamString strm; |
782 | // The value 11 is an offset to make the completion description looks |
783 | // neater. |
784 | strm.SetIndentLevel(11); |
785 | const Target::StopHookSP stophook_sp = target_sp->GetStopHookAtIndex(index: idx); |
786 | stophook_sp->GetDescription(s&: strm, level: lldb::eDescriptionLevelInitial); |
787 | request.TryCompleteCurrentArg(completion: std::to_string(val: stophook_sp->GetID()), |
788 | description: strm.GetString()); |
789 | } |
790 | } |
791 | |
792 | void CommandCompletions::ThreadIndexes(CommandInterpreter &interpreter, |
793 | CompletionRequest &request, |
794 | SearchFilter *searcher) { |
795 | const ExecutionContext &exe_ctx = interpreter.GetExecutionContext(); |
796 | if (!exe_ctx.HasProcessScope()) |
797 | return; |
798 | |
799 | ThreadList &threads = exe_ctx.GetProcessPtr()->GetThreadList(); |
800 | lldb::ThreadSP thread_sp; |
801 | for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) { |
802 | StreamString strm; |
803 | thread_sp->GetStatus(strm, start_frame: 0, num_frames: 1, num_frames_with_source: 1, stop_format: true, /*show_hidden*/ true); |
804 | request.TryCompleteCurrentArg(completion: std::to_string(val: thread_sp->GetIndexID()), |
805 | description: strm.GetString()); |
806 | } |
807 | } |
808 | |
809 | void CommandCompletions::WatchPointIDs(CommandInterpreter &interpreter, |
810 | CompletionRequest &request, |
811 | SearchFilter *searcher) { |
812 | const ExecutionContext &exe_ctx = interpreter.GetExecutionContext(); |
813 | if (!exe_ctx.HasTargetScope()) |
814 | return; |
815 | |
816 | const WatchpointList &wp_list = exe_ctx.GetTargetPtr()->GetWatchpointList(); |
817 | for (lldb::WatchpointSP wp_sp : wp_list.Watchpoints()) { |
818 | StreamString strm; |
819 | wp_sp->Dump(s: &strm); |
820 | request.TryCompleteCurrentArg(completion: std::to_string(val: wp_sp->GetID()), |
821 | description: strm.GetString()); |
822 | } |
823 | } |
824 | |
825 | void CommandCompletions::TypeCategoryNames(CommandInterpreter &interpreter, |
826 | CompletionRequest &request, |
827 | SearchFilter *searcher) { |
828 | DataVisualization::Categories::ForEach( |
829 | callback: [&request](const lldb::TypeCategoryImplSP &category_sp) { |
830 | request.TryCompleteCurrentArg(completion: category_sp->GetName(), |
831 | description: category_sp->GetDescription()); |
832 | return true; |
833 | }); |
834 | } |
835 | |
836 | void CommandCompletions::ThreadIDs(CommandInterpreter &interpreter, |
837 | CompletionRequest &request, |
838 | SearchFilter *searcher) { |
839 | const ExecutionContext &exe_ctx = interpreter.GetExecutionContext(); |
840 | if (!exe_ctx.HasProcessScope()) |
841 | return; |
842 | |
843 | ThreadList &threads = exe_ctx.GetProcessPtr()->GetThreadList(); |
844 | lldb::ThreadSP thread_sp; |
845 | for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) { |
846 | StreamString strm; |
847 | thread_sp->GetStatus(strm, start_frame: 0, num_frames: 1, num_frames_with_source: 1, stop_format: true, /*show_hidden*/ true); |
848 | request.TryCompleteCurrentArg(completion: std::to_string(val: thread_sp->GetID()), |
849 | description: strm.GetString()); |
850 | } |
851 | } |
852 | |
853 | void CommandCompletions::CompleteModifiableCmdPathArgs( |
854 | CommandInterpreter &interpreter, CompletionRequest &request, |
855 | OptionElementVector &opt_element_vector) { |
856 | // The only arguments constitute a command path, however, there might be |
857 | // options interspersed among the arguments, and we need to skip those. Do that |
858 | // by copying the args vector, and just dropping all the option bits: |
859 | Args args = request.GetParsedLine(); |
860 | std::vector<size_t> to_delete; |
861 | for (auto &elem : opt_element_vector) { |
862 | to_delete.push_back(x: elem.opt_pos); |
863 | if (elem.opt_arg_pos != 0) |
864 | to_delete.push_back(x: elem.opt_arg_pos); |
865 | } |
866 | sort(first: to_delete.begin(), last: to_delete.end(), comp: std::greater<size_t>()); |
867 | for (size_t idx : to_delete) |
868 | args.DeleteArgumentAtIndex(idx); |
869 | |
870 | // At this point, we should only have args, so now lookup the command up to |
871 | // the cursor element. |
872 | |
873 | // There's nothing here but options. It doesn't seem very useful here to |
874 | // dump all the commands, so just return. |
875 | size_t num_args = args.GetArgumentCount(); |
876 | if (num_args == 0) |
877 | return; |
878 | |
879 | // There's just one argument, so we should complete its name: |
880 | StringList matches; |
881 | if (num_args == 1) { |
882 | interpreter.GetUserCommandObject(cmd: args.GetArgumentAtIndex(idx: 0), matches: &matches, |
883 | descriptions: nullptr); |
884 | request.AddCompletions(completions: matches); |
885 | return; |
886 | } |
887 | |
888 | // There was more than one path element, lets find the containing command: |
889 | Status error; |
890 | CommandObjectMultiword *mwc = |
891 | interpreter.VerifyUserMultiwordCmdPath(path&: args, leaf_is_command: true, result&: error); |
892 | |
893 | // Something was wrong somewhere along the path, but I don't think there's |
894 | // a good way to go back and fill in the missing elements: |
895 | if (error.Fail()) |
896 | return; |
897 | |
898 | // This should never happen. We already handled the case of one argument |
899 | // above, and we can only get Success & nullptr back if there's a one-word |
900 | // leaf. |
901 | assert(mwc != nullptr); |
902 | |
903 | mwc->GetSubcommandObject(sub_cmd: args.GetArgumentAtIndex(idx: num_args - 1), matches: &matches); |
904 | if (matches.GetSize() == 0) |
905 | return; |
906 | |
907 | request.AddCompletions(completions: matches); |
908 | } |
909 |
Definitions
- CommonCompletionElement
- InvokeCommonCompletionCallbacks
- Completer
- Completer
- ~Completer
- Completer
- operator=
- SourceFileCompleter
- SourceFileCompleter
- GetDepth
- SearchCallback
- DoCompletion
- SourceFileCompleter
- operator=
- regex_chars
- SymbolCompleter
- SymbolCompleter
- GetDepth
- SearchCallback
- DoCompletion
- SymbolCompleter
- operator=
- ModuleCompleter
- ModuleCompleter
- GetDepth
- SearchCallback
- DoCompletion
- ModuleCompleter
- operator=
- SourceFiles
- DiskFilesOrDirectories
- DiskFilesOrDirectories
- DiskFilesOrDirectories
- DiskFiles
- DiskFiles
- DiskDirectories
- DiskDirectories
- RemoteDiskFiles
- RemoteDiskDirectories
- Modules
- ModuleUUIDs
- Symbols
- SettingsNames
- PlatformPluginNames
- ArchitectureNames
- VariablePath
- Registers
- Breakpoints
- BreakpointNames
- ProcessPluginNames
- DisassemblyFlavors
- ProcessIDs
- ProcessNames
- TypeLanguages
- FrameIndexes
- StopHookIDs
- ThreadIndexes
- WatchPointIDs
- TypeCategoryNames
- ThreadIDs
Learn to use CMake with our Intro Training
Find out more