//===-- CommandCompletions.cpp --------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" #include "lldb/Breakpoint/Watchpoint.h" #include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" #include "lldb/DataFormatters/DataVisualization.h" #include "lldb/Host/FileSystem.h" #include "lldb/Interpreter/CommandCompletions.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandObject.h" #include "lldb/Interpreter/CommandObjectMultiword.h" #include "lldb/Interpreter/OptionValueProperties.h" #include "lldb/Symbol/CompileUnit.h" #include "lldb/Symbol/Variable.h" #include "lldb/Target/Language.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" #include "lldb/Target/Thread.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/FileSpecList.h" #include "lldb/Utility/StreamString.h" #include "lldb/Utility/TildeExpressionResolver.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" using namespace lldb_private; // This is the command completion callback that is used to complete the // argument of the option it is bound to (in the OptionDefinition table // below). typedef void (*CompletionCallback)(CommandInterpreter &interpreter, CompletionRequest &request, // A search filter to limit the search... lldb_private::SearchFilter *searcher); struct CommonCompletionElement { uint64_t type; CompletionCallback callback; }; bool CommandCompletions::InvokeCommonCompletionCallbacks( CommandInterpreter &interpreter, uint32_t completion_mask, CompletionRequest &request, SearchFilter *searcher) { bool handled = false; const CommonCompletionElement common_completions[] = { {lldb::eNoCompletion, nullptr}, {lldb::eSourceFileCompletion, CommandCompletions::SourceFiles}, {lldb::eDiskFileCompletion, CommandCompletions::DiskFiles}, {lldb::eDiskDirectoryCompletion, CommandCompletions::DiskDirectories}, {lldb::eSymbolCompletion, CommandCompletions::Symbols}, {lldb::eModuleCompletion, CommandCompletions::Modules}, {lldb::eModuleUUIDCompletion, CommandCompletions::ModuleUUIDs}, {lldb::eSettingsNameCompletion, CommandCompletions::SettingsNames}, {lldb::ePlatformPluginCompletion, CommandCompletions::PlatformPluginNames}, {lldb::eArchitectureCompletion, CommandCompletions::ArchitectureNames}, {lldb::eVariablePathCompletion, CommandCompletions::VariablePath}, {lldb::eRegisterCompletion, CommandCompletions::Registers}, {lldb::eBreakpointCompletion, CommandCompletions::Breakpoints}, {lldb::eProcessPluginCompletion, CommandCompletions::ProcessPluginNames}, {lldb::eDisassemblyFlavorCompletion, CommandCompletions::DisassemblyFlavors}, {lldb::eTypeLanguageCompletion, CommandCompletions::TypeLanguages}, {lldb::eFrameIndexCompletion, CommandCompletions::FrameIndexes}, {lldb::eStopHookIDCompletion, CommandCompletions::StopHookIDs}, {lldb::eThreadIndexCompletion, CommandCompletions::ThreadIndexes}, {lldb::eWatchpointIDCompletion, CommandCompletions::WatchPointIDs}, {lldb::eBreakpointNameCompletion, CommandCompletions::BreakpointNames}, {lldb::eProcessIDCompletion, CommandCompletions::ProcessIDs}, {lldb::eProcessNameCompletion, CommandCompletions::ProcessNames}, {lldb::eRemoteDiskFileCompletion, CommandCompletions::RemoteDiskFiles}, {lldb::eRemoteDiskDirectoryCompletion, CommandCompletions::RemoteDiskDirectories}, {lldb::eTypeCategoryNameCompletion, CommandCompletions::TypeCategoryNames}, {lldb::eThreadIDCompletion, CommandCompletions::ThreadIDs}, {lldb::eTerminatorCompletion, nullptr} // This one has to be last in the list. }; for (int i = 0;; i++) { if (common_completions[i].type == lldb::eTerminatorCompletion) break; else if ((common_completions[i].type & completion_mask) == common_completions[i].type && common_completions[i].callback != nullptr) { handled = true; common_completions[i].callback(interpreter, request, searcher); } } return handled; } namespace { // The Completer class is a convenient base class for building searchers that // go along with the SearchFilter passed to the standard Completer functions. class Completer : public Searcher { public: Completer(CommandInterpreter &interpreter, CompletionRequest &request) : m_interpreter(interpreter), m_request(request) {} ~Completer() override = default; CallbackReturn SearchCallback(SearchFilter &filter, SymbolContext &context, Address *addr) override = 0; lldb::SearchDepth GetDepth() override = 0; virtual void DoCompletion(SearchFilter *filter) = 0; protected: CommandInterpreter &m_interpreter; CompletionRequest &m_request; private: Completer(const Completer &) = delete; const Completer &operator=(const Completer &) = delete; }; } // namespace // SourceFileCompleter implements the source file completer namespace { class SourceFileCompleter : public Completer { public: SourceFileCompleter(CommandInterpreter &interpreter, CompletionRequest &request) : Completer(interpreter, request) { FileSpec partial_spec(m_request.GetCursorArgumentPrefix()); m_file_name = partial_spec.GetFilename().GetCString(); m_dir_name = partial_spec.GetDirectory().GetCString(); } lldb::SearchDepth GetDepth() override { return lldb::eSearchDepthCompUnit; } Searcher::CallbackReturn SearchCallback(SearchFilter &filter, SymbolContext &context, Address *addr) override { if (context.comp_unit != nullptr) { const char *cur_file_name = context.comp_unit->GetPrimaryFile().GetFilename().GetCString(); const char *cur_dir_name = context.comp_unit->GetPrimaryFile().GetDirectory().GetCString(); bool match = false; if (m_file_name && cur_file_name && strstr(cur_file_name, m_file_name) == cur_file_name) match = true; if (match && m_dir_name && cur_dir_name && strstr(cur_dir_name, m_dir_name) != cur_dir_name) match = false; if (match) { m_matching_files.AppendIfUnique(context.comp_unit->GetPrimaryFile()); } } return Searcher::eCallbackReturnContinue; } void DoCompletion(SearchFilter *filter) override { filter->Search(*this); // Now convert the filelist to completions: for (size_t i = 0; i < m_matching_files.GetSize(); i++) { m_request.AddCompletion( m_matching_files.GetFileSpecAtIndex(i).GetFilename().GetCString()); } } private: FileSpecList m_matching_files; const char *m_file_name; const char *m_dir_name; SourceFileCompleter(const SourceFileCompleter &) = delete; const SourceFileCompleter &operator=(const SourceFileCompleter &) = delete; }; } // namespace static bool regex_chars(const char comp) { return llvm::StringRef("[](){}+.*|^$\\?").contains(comp); } namespace { class SymbolCompleter : public Completer { public: SymbolCompleter(CommandInterpreter &interpreter, CompletionRequest &request) : Completer(interpreter, request) { std::string regex_str; if (!m_request.GetCursorArgumentPrefix().empty()) { regex_str.append("^"); regex_str.append(std::string(m_request.GetCursorArgumentPrefix())); } else { // Match anything since the completion string is empty regex_str.append("."); } std::string::iterator pos = find_if(regex_str.begin() + 1, regex_str.end(), regex_chars); while (pos < regex_str.end()) { pos = regex_str.insert(pos, '\\'); pos = find_if(pos + 2, regex_str.end(), regex_chars); } m_regex = RegularExpression(regex_str); } lldb::SearchDepth GetDepth() override { return lldb::eSearchDepthModule; } Searcher::CallbackReturn SearchCallback(SearchFilter &filter, SymbolContext &context, Address *addr) override { if (context.module_sp) { SymbolContextList sc_list; ModuleFunctionSearchOptions function_options; function_options.include_symbols = true; function_options.include_inlines = true; context.module_sp->FindFunctions(m_regex, function_options, sc_list); // Now add the functions & symbols to the list - only add if unique: for (const SymbolContext &sc : sc_list) { ConstString func_name = sc.GetFunctionName(Mangled::ePreferDemangled); // Ensure that the function name matches the regex. This is more than // a sanity check. It is possible that the demangled function name // does not start with the prefix, for example when it's in an // anonymous namespace. if (!func_name.IsEmpty() && m_regex.Execute(func_name.GetStringRef())) m_match_set.insert(func_name); } } return Searcher::eCallbackReturnContinue; } void DoCompletion(SearchFilter *filter) override { filter->Search(*this); collection::iterator pos = m_match_set.begin(), end = m_match_set.end(); for (pos = m_match_set.begin(); pos != end; pos++) m_request.AddCompletion((*pos).GetCString()); } private: RegularExpression m_regex; typedef std::set collection; collection m_match_set; SymbolCompleter(const SymbolCompleter &) = delete; const SymbolCompleter &operator=(const SymbolCompleter &) = delete; }; } // namespace namespace { class ModuleCompleter : public Completer { public: ModuleCompleter(CommandInterpreter &interpreter, CompletionRequest &request) : Completer(interpreter, request) { llvm::StringRef request_str = m_request.GetCursorArgumentPrefix(); // We can match the full path, or the file name only. The full match will be // attempted always, the file name match only if the request does not // contain a path separator. // Preserve both the path as spelled by the user (used for completion) and // the canonical version (used for matching). m_spelled_path = request_str; m_canonical_path = FileSpec(m_spelled_path).GetPath(); if (!m_spelled_path.empty() && llvm::sys::path::is_separator(m_spelled_path.back()) && !llvm::StringRef(m_canonical_path).ends_with(m_spelled_path.back())) { m_canonical_path += m_spelled_path.back(); } if (llvm::find_if(request_str, [](char c) { return llvm::sys::path::is_separator(c); }) == request_str.end()) m_file_name = request_str; } lldb::SearchDepth GetDepth() override { return lldb::eSearchDepthModule; } Searcher::CallbackReturn SearchCallback(SearchFilter &filter, SymbolContext &context, Address *addr) override { if (context.module_sp) { // Attempt a full path match. std::string cur_path = context.module_sp->GetFileSpec().GetPath(); llvm::StringRef cur_path_view = cur_path; if (cur_path_view.consume_front(m_canonical_path)) m_request.AddCompletion((m_spelled_path + cur_path_view).str()); // And a file name match. if (m_file_name) { llvm::StringRef cur_file_name = context.module_sp->GetFileSpec().GetFilename().GetStringRef(); if (cur_file_name.starts_with(*m_file_name)) m_request.AddCompletion(cur_file_name); } } return Searcher::eCallbackReturnContinue; } void DoCompletion(SearchFilter *filter) override { filter->Search(*this); } private: std::optional m_file_name; llvm::StringRef m_spelled_path; std::string m_canonical_path; ModuleCompleter(const ModuleCompleter &) = delete; const ModuleCompleter &operator=(const ModuleCompleter &) = delete; }; } // namespace void CommandCompletions::SourceFiles(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { SourceFileCompleter completer(interpreter, request); if (searcher == nullptr) { lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); SearchFilterForUnconstrainedSearches null_searcher(target_sp); completer.DoCompletion(&null_searcher); } else { completer.DoCompletion(searcher); } } static void DiskFilesOrDirectories(const llvm::Twine &partial_name, bool only_directories, CompletionRequest &request, TildeExpressionResolver &Resolver) { llvm::SmallString<256> CompletionBuffer; llvm::SmallString<256> Storage; partial_name.toVector(CompletionBuffer); if (CompletionBuffer.size() >= PATH_MAX) return; namespace path = llvm::sys::path; llvm::StringRef SearchDir; llvm::StringRef PartialItem; if (CompletionBuffer.starts_with("~")) { llvm::StringRef Buffer = CompletionBuffer; size_t FirstSep = Buffer.find_if([](char c) { return path::is_separator(c); }); llvm::StringRef Username = Buffer.take_front(FirstSep); llvm::StringRef Remainder; if (FirstSep != llvm::StringRef::npos) Remainder = Buffer.drop_front(FirstSep + 1); llvm::SmallString<256> Resolved; if (!Resolver.ResolveExact(Username, Resolved)) { // We couldn't resolve it as a full username. If there were no slashes // then this might be a partial username. We try to resolve it as such // but after that, we're done regardless of any matches. if (FirstSep == llvm::StringRef::npos) { llvm::StringSet<> MatchSet; Resolver.ResolvePartial(Username, MatchSet); for (const auto &S : MatchSet) { Resolved = S.getKey(); path::append(Resolved, path::get_separator()); request.AddCompletion(Resolved, "", CompletionMode::Partial); } } return; } // If there was no trailing slash, then we're done as soon as we resolve // the expression to the correct directory. Otherwise we need to continue // looking for matches within that directory. if (FirstSep == llvm::StringRef::npos) { // Make sure it ends with a separator. path::append(CompletionBuffer, path::get_separator()); request.AddCompletion(CompletionBuffer, "", CompletionMode::Partial); return; } // We want to keep the form the user typed, so we special case this to // search in the fully resolved directory, but CompletionBuffer keeps the // unmodified form that the user typed. Storage = Resolved; llvm::StringRef RemainderDir = path::parent_path(Remainder); if (!RemainderDir.empty()) { // Append the remaining path to the resolved directory. Storage.append(path::get_separator()); Storage.append(RemainderDir); } SearchDir = Storage; } else if (CompletionBuffer == path::root_directory(CompletionBuffer)) { SearchDir = CompletionBuffer; } else { SearchDir = path::parent_path(CompletionBuffer); } size_t FullPrefixLen = CompletionBuffer.size(); PartialItem = path::filename(CompletionBuffer); // path::filename() will return "." when the passed path ends with a // directory separator or the separator when passed the disk root directory. // We have to filter those out, but only when the "." doesn't come from the // completion request itself. if ((PartialItem == "." || PartialItem == path::get_separator()) && path::is_separator(CompletionBuffer.back())) PartialItem = llvm::StringRef(); if (SearchDir.empty()) { llvm::sys::fs::current_path(Storage); SearchDir = Storage; } assert(!PartialItem.contains(path::get_separator())); // SearchDir now contains the directory to search in, and Prefix contains the // text we want to match against items in that directory. FileSystem &fs = FileSystem::Instance(); std::error_code EC; llvm::vfs::directory_iterator Iter = fs.DirBegin(SearchDir, EC); llvm::vfs::directory_iterator End; for (; Iter != End && !EC; Iter.increment(EC)) { auto &Entry = *Iter; llvm::ErrorOr Status = fs.GetStatus(Entry.path()); if (!Status) continue; auto Name = path::filename(Entry.path()); // Omit ".", ".." if (Name == "." || Name == ".." || !Name.starts_with(PartialItem)) continue; bool is_dir = Status->isDirectory(); // If it's a symlink, then we treat it as a directory as long as the target // is a directory. if (Status->isSymlink()) { FileSpec symlink_filespec(Entry.path()); FileSpec resolved_filespec; auto error = fs.ResolveSymbolicLink(symlink_filespec, resolved_filespec); if (error.Success()) is_dir = fs.IsDirectory(symlink_filespec); } if (only_directories && !is_dir) continue; // Shrink it back down so that it just has the original prefix the user // typed and remove the part of the name which is common to the located // item and what the user typed. CompletionBuffer.resize(FullPrefixLen); Name = Name.drop_front(PartialItem.size()); CompletionBuffer.append(Name); if (is_dir) { path::append(CompletionBuffer, path::get_separator()); } CompletionMode mode = is_dir ? CompletionMode::Partial : CompletionMode::Normal; request.AddCompletion(CompletionBuffer, "", mode); } } static void DiskFilesOrDirectories(const llvm::Twine &partial_name, bool only_directories, StringList &matches, TildeExpressionResolver &Resolver) { CompletionResult result; std::string partial_name_str = partial_name.str(); CompletionRequest request(partial_name_str, partial_name_str.size(), result); DiskFilesOrDirectories(partial_name, only_directories, request, Resolver); result.GetMatches(matches); } static void DiskFilesOrDirectories(CompletionRequest &request, bool only_directories) { StandardTildeExpressionResolver resolver; DiskFilesOrDirectories(request.GetCursorArgumentPrefix(), only_directories, request, resolver); } void CommandCompletions::DiskFiles(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { DiskFilesOrDirectories(request, /*only_dirs*/ false); } void CommandCompletions::DiskFiles(const llvm::Twine &partial_file_name, StringList &matches, TildeExpressionResolver &Resolver) { DiskFilesOrDirectories(partial_file_name, false, matches, Resolver); } void CommandCompletions::DiskDirectories(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { DiskFilesOrDirectories(request, /*only_dirs*/ true); } void CommandCompletions::DiskDirectories(const llvm::Twine &partial_file_name, StringList &matches, TildeExpressionResolver &Resolver) { DiskFilesOrDirectories(partial_file_name, true, matches, Resolver); } void CommandCompletions::RemoteDiskFiles(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { lldb::PlatformSP platform_sp = interpreter.GetDebugger().GetPlatformList().GetSelectedPlatform(); if (platform_sp) platform_sp->AutoCompleteDiskFileOrDirectory(request, false); } void CommandCompletions::RemoteDiskDirectories(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { lldb::PlatformSP platform_sp = interpreter.GetDebugger().GetPlatformList().GetSelectedPlatform(); if (platform_sp) platform_sp->AutoCompleteDiskFileOrDirectory(request, true); } void CommandCompletions::Modules(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { ModuleCompleter completer(interpreter, request); if (searcher == nullptr) { lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); SearchFilterForUnconstrainedSearches null_searcher(target_sp); completer.DoCompletion(&null_searcher); } else { completer.DoCompletion(searcher); } } void CommandCompletions::ModuleUUIDs(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { const ExecutionContext &exe_ctx = interpreter.GetExecutionContext(); if (!exe_ctx.HasTargetScope()) return; exe_ctx.GetTargetPtr()->GetImages().ForEach( [&request](const lldb::ModuleSP &module) { StreamString strm; module->GetDescription(strm.AsRawOstream(), lldb::eDescriptionLevelInitial); request.TryCompleteCurrentArg(module->GetUUID().GetAsString(), strm.GetString()); return true; }); } void CommandCompletions::Symbols(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { SymbolCompleter completer(interpreter, request); if (searcher == nullptr) { lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); SearchFilterForUnconstrainedSearches null_searcher(target_sp); completer.DoCompletion(&null_searcher); } else { completer.DoCompletion(searcher); } } void CommandCompletions::SettingsNames(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { // Cache the full setting name list static StringList g_property_names; if (g_property_names.GetSize() == 0) { // Generate the full setting name list on demand lldb::OptionValuePropertiesSP properties_sp( interpreter.GetDebugger().GetValueProperties()); if (properties_sp) { StreamString strm; properties_sp->DumpValue(nullptr, strm, OptionValue::eDumpOptionName); const std::string &str = std::string(strm.GetString()); g_property_names.SplitIntoLines(str.c_str(), str.size()); } } for (const std::string &s : g_property_names) request.TryCompleteCurrentArg(s); } void CommandCompletions::PlatformPluginNames(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { PluginManager::AutoCompletePlatformName(request.GetCursorArgumentPrefix(), request); } void CommandCompletions::ArchitectureNames(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { ArchSpec::AutoComplete(request); } void CommandCompletions::VariablePath(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { Variable::AutoComplete(interpreter.GetExecutionContext(), request); } void CommandCompletions::Registers(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { std::string reg_prefix; if (request.GetCursorArgumentPrefix().starts_with("$")) reg_prefix = "$"; RegisterContext *reg_ctx = interpreter.GetExecutionContext().GetRegisterContext(); if (!reg_ctx) return; const size_t reg_num = reg_ctx->GetRegisterCount(); for (size_t reg_idx = 0; reg_idx < reg_num; ++reg_idx) { const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoAtIndex(reg_idx); request.TryCompleteCurrentArg(reg_prefix + reg_info->name, reg_info->alt_name); } } void CommandCompletions::Breakpoints(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { lldb::TargetSP target = interpreter.GetDebugger().GetSelectedTarget(); if (!target) return; const BreakpointList &breakpoints = target->GetBreakpointList(); std::unique_lock lock; target->GetBreakpointList().GetListMutex(lock); size_t num_breakpoints = breakpoints.GetSize(); if (num_breakpoints == 0) return; for (size_t i = 0; i < num_breakpoints; ++i) { lldb::BreakpointSP bp = breakpoints.GetBreakpointAtIndex(i); StreamString s; bp->GetDescription(&s, lldb::eDescriptionLevelBrief); llvm::StringRef bp_info = s.GetString(); const size_t colon_pos = bp_info.find_first_of(':'); if (colon_pos != llvm::StringRef::npos) bp_info = bp_info.drop_front(colon_pos + 2); request.TryCompleteCurrentArg(std::to_string(bp->GetID()), bp_info); } } void CommandCompletions::BreakpointNames(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { lldb::TargetSP target = interpreter.GetDebugger().GetSelectedTarget(); if (!target) return; std::vector name_list; target->GetBreakpointNames(name_list); for (const std::string &name : name_list) request.TryCompleteCurrentArg(name); } void CommandCompletions::ProcessPluginNames(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { PluginManager::AutoCompleteProcessName(request.GetCursorArgumentPrefix(), request); } void CommandCompletions::DisassemblyFlavors(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { // Currently the only valid options for disassemble -F are default, and for // Intel architectures, att and intel. static const char *flavors[] = {"default", "att", "intel"}; for (const char *flavor : flavors) { request.TryCompleteCurrentArg(flavor); } } void CommandCompletions::ProcessIDs(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { lldb::PlatformSP platform_sp(interpreter.GetPlatform(true)); if (!platform_sp) return; ProcessInstanceInfoList process_infos; ProcessInstanceInfoMatch match_info; platform_sp->FindProcesses(match_info, process_infos); for (const ProcessInstanceInfo &info : process_infos) request.TryCompleteCurrentArg(std::to_string(info.GetProcessID()), info.GetNameAsStringRef()); } void CommandCompletions::ProcessNames(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { lldb::PlatformSP platform_sp(interpreter.GetPlatform(true)); if (!platform_sp) return; ProcessInstanceInfoList process_infos; ProcessInstanceInfoMatch match_info; platform_sp->FindProcesses(match_info, process_infos); for (const ProcessInstanceInfo &info : process_infos) request.TryCompleteCurrentArg(info.GetNameAsStringRef()); } void CommandCompletions::TypeLanguages(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { for (int bit : Language::GetLanguagesSupportingTypeSystems().bitvector.set_bits()) { request.TryCompleteCurrentArg( Language::GetNameForLanguageType(static_cast(bit))); } } void CommandCompletions::FrameIndexes(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { const ExecutionContext &exe_ctx = interpreter.GetExecutionContext(); if (!exe_ctx.HasProcessScope()) return; lldb::ThreadSP thread_sp = exe_ctx.GetThreadSP(); Debugger &dbg = interpreter.GetDebugger(); const uint32_t frame_num = thread_sp->GetStackFrameCount(); for (uint32_t i = 0; i < frame_num; ++i) { lldb::StackFrameSP frame_sp = thread_sp->GetStackFrameAtIndex(i); StreamString strm; // Dumping frames can be slow, allow interruption. if (INTERRUPT_REQUESTED(dbg, "Interrupted in frame completion")) break; frame_sp->Dump(&strm, false, true); request.TryCompleteCurrentArg(std::to_string(i), strm.GetString()); } } void CommandCompletions::StopHookIDs(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { const lldb::TargetSP target_sp = interpreter.GetExecutionContext().GetTargetSP(); if (!target_sp) return; const size_t num = target_sp->GetNumStopHooks(); for (size_t idx = 0; idx < num; ++idx) { StreamString strm; // The value 11 is an offset to make the completion description looks // neater. strm.SetIndentLevel(11); const Target::StopHookSP stophook_sp = target_sp->GetStopHookAtIndex(idx); stophook_sp->GetDescription(strm, lldb::eDescriptionLevelInitial); request.TryCompleteCurrentArg(std::to_string(stophook_sp->GetID()), strm.GetString()); } } void CommandCompletions::ThreadIndexes(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { const ExecutionContext &exe_ctx = interpreter.GetExecutionContext(); if (!exe_ctx.HasProcessScope()) return; ThreadList &threads = exe_ctx.GetProcessPtr()->GetThreadList(); lldb::ThreadSP thread_sp; for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) { StreamString strm; thread_sp->GetStatus(strm, 0, 1, 1, true); request.TryCompleteCurrentArg(std::to_string(thread_sp->GetIndexID()), strm.GetString()); } } void CommandCompletions::WatchPointIDs(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { const ExecutionContext &exe_ctx = interpreter.GetExecutionContext(); if (!exe_ctx.HasTargetScope()) return; const WatchpointList &wp_list = exe_ctx.GetTargetPtr()->GetWatchpointList(); for (lldb::WatchpointSP wp_sp : wp_list.Watchpoints()) { StreamString strm; wp_sp->Dump(&strm); request.TryCompleteCurrentArg(std::to_string(wp_sp->GetID()), strm.GetString()); } } void CommandCompletions::TypeCategoryNames(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { DataVisualization::Categories::ForEach( [&request](const lldb::TypeCategoryImplSP &category_sp) { request.TryCompleteCurrentArg(category_sp->GetName(), category_sp->GetDescription()); return true; }); } void CommandCompletions::ThreadIDs(CommandInterpreter &interpreter, CompletionRequest &request, SearchFilter *searcher) { const ExecutionContext &exe_ctx = interpreter.GetExecutionContext(); if (!exe_ctx.HasProcessScope()) return; ThreadList &threads = exe_ctx.GetProcessPtr()->GetThreadList(); lldb::ThreadSP thread_sp; for (uint32_t idx = 0; (thread_sp = threads.GetThreadAtIndex(idx)); ++idx) { StreamString strm; thread_sp->GetStatus(strm, 0, 1, 1, true); request.TryCompleteCurrentArg(std::to_string(thread_sp->GetID()), strm.GetString()); } } void CommandCompletions::CompleteModifiableCmdPathArgs( CommandInterpreter &interpreter, CompletionRequest &request, OptionElementVector &opt_element_vector) { // The only arguments constitute a command path, however, there might be // options interspersed among the arguments, and we need to skip those. Do that // by copying the args vector, and just dropping all the option bits: Args args = request.GetParsedLine(); std::vector to_delete; for (auto &elem : opt_element_vector) { to_delete.push_back(elem.opt_pos); if (elem.opt_arg_pos != 0) to_delete.push_back(elem.opt_arg_pos); } sort(to_delete.begin(), to_delete.end(), std::greater()); for (size_t idx : to_delete) args.DeleteArgumentAtIndex(idx); // At this point, we should only have args, so now lookup the command up to // the cursor element. // There's nothing here but options. It doesn't seem very useful here to // dump all the commands, so just return. size_t num_args = args.GetArgumentCount(); if (num_args == 0) return; // There's just one argument, so we should complete its name: StringList matches; if (num_args == 1) { interpreter.GetUserCommandObject(args.GetArgumentAtIndex(0), &matches, nullptr); request.AddCompletions(matches); return; } // There was more than one path element, lets find the containing command: Status error; CommandObjectMultiword *mwc = interpreter.VerifyUserMultiwordCmdPath(args, true, error); // Something was wrong somewhere along the path, but I don't think there's // a good way to go back and fill in the missing elements: if (error.Fail()) return; // This should never happen. We already handled the case of one argument // above, and we can only get Success & nullptr back if there's a one-word // leaf. assert(mwc != nullptr); mwc->GetSubcommandObject(args.GetArgumentAtIndex(num_args - 1), &matches); if (matches.GetSize() == 0) return; request.AddCompletions(matches); }