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