| 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 | |