//===-- CommandCompletions.cpp ----------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "lldb/lldb-python.h" // C Includes #include #if defined(__APPLE__) || defined(__linux__) #include #endif // C++ Includes // Other libraries and framework includes // Project includes #include "lldb/Host/FileSpec.h" #include "lldb/Core/FileSpecList.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/Module.h" #include "lldb/Interpreter/Args.h" #include "lldb/Interpreter/CommandCompletions.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Symbol/CompileUnit.h" #include "lldb/Symbol/Variable.h" #include "lldb/Target/Target.h" #include "lldb/Utility/CleanUp.h" using namespace lldb_private; CommandCompletions::CommonCompletionElement CommandCompletions::g_common_completions[] = { {eCustomCompletion, NULL}, {eSourceFileCompletion, CommandCompletions::SourceFiles}, {eDiskFileCompletion, CommandCompletions::DiskFiles}, {eDiskDirectoryCompletion, CommandCompletions::DiskDirectories}, {eSymbolCompletion, CommandCompletions::Symbols}, {eModuleCompletion, CommandCompletions::Modules}, {eSettingsNameCompletion, CommandCompletions::SettingsNames}, {ePlatformPluginCompletion, CommandCompletions::PlatformPluginNames}, {eArchitectureCompletion, CommandCompletions::ArchitectureNames}, {eVariablePathCompletion, CommandCompletions::VariablePath}, {eNoCompletion, NULL} // This one has to be last in the list. }; bool CommandCompletions::InvokeCommonCompletionCallbacks ( CommandInterpreter &interpreter, uint32_t completion_mask, const char *completion_str, int match_start_point, int max_return_elements, SearchFilter *searcher, bool &word_complete, StringList &matches ) { bool handled = false; if (completion_mask & eCustomCompletion) return false; for (int i = 0; ; i++) { if (g_common_completions[i].type == eNoCompletion) break; else if ((g_common_completions[i].type & completion_mask) == g_common_completions[i].type && g_common_completions[i].callback != NULL) { handled = true; g_common_completions[i].callback (interpreter, completion_str, match_start_point, max_return_elements, searcher, word_complete, matches); } } return handled; } int CommandCompletions::SourceFiles ( CommandInterpreter &interpreter, const char *partial_file_name, int match_start_point, int max_return_elements, SearchFilter *searcher, bool &word_complete, StringList &matches ) { word_complete = true; // Find some way to switch "include support files..." SourceFileCompleter completer (interpreter, false, partial_file_name, match_start_point, max_return_elements, matches); if (searcher == NULL) { lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); SearchFilter null_searcher (target_sp); completer.DoCompletion (&null_searcher); } else { completer.DoCompletion (searcher); } return matches.GetSize(); } typedef struct DiskFilesOrDirectoriesBaton { const char *remainder; char *partial_name_copy; bool only_directories; bool *saw_directory; StringList *matches; char *end_ptr; size_t baselen; } DiskFilesOrDirectoriesBaton; FileSpec::EnumerateDirectoryResult DiskFilesOrDirectoriesCallback(void *baton, FileSpec::FileType file_type, const FileSpec &spec) { const char *name = spec.GetFilename().AsCString(); const DiskFilesOrDirectoriesBaton *parameters = (DiskFilesOrDirectoriesBaton*)baton; char *end_ptr = parameters->end_ptr; char *partial_name_copy = parameters->partial_name_copy; const char *remainder = parameters->remainder; // Omit ".", ".." and any . files if the match string doesn't start with . if (name[0] == '.') { if (name[1] == '\0') return FileSpec::eEnumerateDirectoryResultNext; else if (name[1] == '.' && name[2] == '\0') return FileSpec::eEnumerateDirectoryResultNext; else if (remainder[0] != '.') return FileSpec::eEnumerateDirectoryResultNext; } // If we found a directory, we put a "/" at the end of the name. if (remainder[0] == '\0' || strstr(name, remainder) == name) { if (strlen(name) + parameters->baselen >= PATH_MAX) return FileSpec::eEnumerateDirectoryResultNext; strcpy(end_ptr, name); bool isa_directory = false; if (file_type == FileSpec::eFileTypeDirectory) isa_directory = true; else if (file_type == FileSpec::eFileTypeSymbolicLink) { struct stat stat_buf; if ((stat(partial_name_copy, &stat_buf) == 0) && S_ISDIR(stat_buf.st_mode)) isa_directory = true; } if (isa_directory) { *parameters->saw_directory = true; size_t len = strlen(parameters->partial_name_copy); partial_name_copy[len] = '/'; partial_name_copy[len + 1] = '\0'; } if (parameters->only_directories && !isa_directory) return FileSpec::eEnumerateDirectoryResultNext; parameters->matches->AppendString(partial_name_copy); } return FileSpec::eEnumerateDirectoryResultNext; } static int DiskFilesOrDirectories ( const char *partial_file_name, bool only_directories, bool &saw_directory, StringList &matches ) { // I'm going to use the "glob" function with GLOB_TILDE for user directory expansion. // If it is not defined on your host system, you'll need to implement it yourself... size_t partial_name_len = strlen(partial_file_name); if (partial_name_len >= PATH_MAX) return matches.GetSize(); // This copy of the string will be cut up into the directory part, and the remainder. end_ptr // below will point to the place of the remainder in this string. Then when we've resolved the // containing directory, and opened it, we'll read the directory contents and overwrite the // partial_name_copy starting from end_ptr with each of the matches. Thus we will preserve // the form the user originally typed. char partial_name_copy[PATH_MAX]; memcpy(partial_name_copy, partial_file_name, partial_name_len); partial_name_copy[partial_name_len] = '\0'; // We'll need to save a copy of the remainder for comparison, which we do here. char remainder[PATH_MAX]; // end_ptr will point past the last / in partial_name_copy, or if there is no slash to the beginning of the string. char *end_ptr; end_ptr = strrchr(partial_name_copy, '/'); // This will store the resolved form of the containing directory char containing_part[PATH_MAX]; if (end_ptr == NULL) { // There's no directory. If the thing begins with a "~" then this is a bare // user name. if (*partial_name_copy == '~') { // Nothing here but the user name. We could just put a slash on the end, // but for completeness sake we'll resolve the user name and only put a slash // on the end if it exists. char resolved_username[PATH_MAX]; size_t resolved_username_len = FileSpec::ResolveUsername (partial_name_copy, resolved_username, sizeof (resolved_username)); // Not sure how this would happen, a username longer than PATH_MAX? Still... if (resolved_username_len >= sizeof (resolved_username)) return matches.GetSize(); else if (resolved_username_len == 0) { // The user name didn't resolve, let's look in the password database for matches. // The user name database contains duplicates, and is not in alphabetical order, so // we'll use a set to manage that for us. FileSpec::ResolvePartialUsername (partial_name_copy, matches); if (matches.GetSize() > 0) saw_directory = true; return matches.GetSize(); } else { //The thing exists, put a '/' on the end, and return it... // FIXME: complete user names here: partial_name_copy[partial_name_len] = '/'; partial_name_copy[partial_name_len+1] = '\0'; matches.AppendString(partial_name_copy); saw_directory = true; return matches.GetSize(); } } else { // The containing part is the CWD, and the whole string is the remainder. containing_part[0] = '.'; containing_part[1] = '\0'; strcpy(remainder, partial_name_copy); end_ptr = partial_name_copy; } } else { if (end_ptr == partial_name_copy) { // We're completing a file or directory in the root volume. containing_part[0] = '/'; containing_part[1] = '\0'; } else { size_t len = end_ptr - partial_name_copy; memcpy(containing_part, partial_name_copy, len); containing_part[len] = '\0'; } // Push end_ptr past the final "/" and set remainder. end_ptr++; strcpy(remainder, end_ptr); } // Look for a user name in the containing part, and if it's there, resolve it and stick the // result back into the containing_part: if (*partial_name_copy == '~') { size_t resolved_username_len = FileSpec::ResolveUsername(containing_part, containing_part, sizeof (containing_part)); // User name doesn't exist, we're not getting any further... if (resolved_username_len == 0 || resolved_username_len >= sizeof (containing_part)) return matches.GetSize(); } // Okay, containing_part is now the directory we want to open and look for files: size_t baselen = end_ptr - partial_name_copy; DiskFilesOrDirectoriesBaton parameters; parameters.remainder = remainder; parameters.partial_name_copy = partial_name_copy; parameters.only_directories = only_directories; parameters.saw_directory = &saw_directory; parameters.matches = &matches; parameters.end_ptr = end_ptr; parameters.baselen = baselen; FileSpec::EnumerateDirectory(containing_part, true, true, true, DiskFilesOrDirectoriesCallback, ¶meters); return matches.GetSize(); } int CommandCompletions::DiskFiles ( CommandInterpreter &interpreter, const char *partial_file_name, int match_start_point, int max_return_elements, SearchFilter *searcher, bool &word_complete, StringList &matches ) { int ret_val = DiskFilesOrDirectories (partial_file_name, false, word_complete, matches); word_complete = !word_complete; return ret_val; } int CommandCompletions::DiskDirectories ( CommandInterpreter &interpreter, const char *partial_file_name, int match_start_point, int max_return_elements, SearchFilter *searcher, bool &word_complete, StringList &matches ) { int ret_val = DiskFilesOrDirectories (partial_file_name, true, word_complete, matches); word_complete = false; return ret_val; } int CommandCompletions::Modules ( CommandInterpreter &interpreter, const char *partial_file_name, int match_start_point, int max_return_elements, SearchFilter *searcher, bool &word_complete, StringList &matches ) { word_complete = true; ModuleCompleter completer (interpreter, partial_file_name, match_start_point, max_return_elements, matches); if (searcher == NULL) { lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); SearchFilter null_searcher (target_sp); completer.DoCompletion (&null_searcher); } else { completer.DoCompletion (searcher); } return matches.GetSize(); } int CommandCompletions::Symbols ( CommandInterpreter &interpreter, const char *partial_file_name, int match_start_point, int max_return_elements, SearchFilter *searcher, bool &word_complete, StringList &matches) { word_complete = true; SymbolCompleter completer (interpreter, partial_file_name, match_start_point, max_return_elements, matches); if (searcher == NULL) { lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget(); SearchFilter null_searcher (target_sp); completer.DoCompletion (&null_searcher); } else { completer.DoCompletion (searcher); } return matches.GetSize(); } int CommandCompletions::SettingsNames (CommandInterpreter &interpreter, const char *partial_setting_name, int match_start_point, int max_return_elements, SearchFilter *searcher, bool &word_complete, StringList &matches) { // 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(NULL, strm, OptionValue::eDumpOptionName); const std::string &str = strm.GetString(); g_property_names.SplitIntoLines(str.c_str(), str.size()); } } size_t exact_matches_idx = SIZE_MAX; const size_t num_matches = g_property_names.AutoComplete (partial_setting_name, matches, exact_matches_idx); word_complete = exact_matches_idx != SIZE_MAX; return num_matches; } int CommandCompletions::PlatformPluginNames (CommandInterpreter &interpreter, const char *partial_name, int match_start_point, int max_return_elements, SearchFilter *searcher, bool &word_complete, lldb_private::StringList &matches) { const uint32_t num_matches = PluginManager::AutoCompletePlatformName(partial_name, matches); word_complete = num_matches == 1; return num_matches; } int CommandCompletions::ArchitectureNames (CommandInterpreter &interpreter, const char *partial_name, int match_start_point, int max_return_elements, SearchFilter *searcher, bool &word_complete, lldb_private::StringList &matches) { const uint32_t num_matches = ArchSpec::AutoComplete (partial_name, matches); word_complete = num_matches == 1; return num_matches; } int CommandCompletions::VariablePath (CommandInterpreter &interpreter, const char *partial_name, int match_start_point, int max_return_elements, SearchFilter *searcher, bool &word_complete, lldb_private::StringList &matches) { return Variable::AutoComplete (interpreter.GetExecutionContext(), partial_name, matches, word_complete); } CommandCompletions::Completer::Completer ( CommandInterpreter &interpreter, const char *completion_str, int match_start_point, int max_return_elements, StringList &matches ) : m_interpreter (interpreter), m_completion_str (completion_str), m_match_start_point (match_start_point), m_max_return_elements (max_return_elements), m_matches (matches) { } CommandCompletions::Completer::~Completer () { } //---------------------------------------------------------------------- // SourceFileCompleter //---------------------------------------------------------------------- CommandCompletions::SourceFileCompleter::SourceFileCompleter ( CommandInterpreter &interpreter, bool include_support_files, const char *completion_str, int match_start_point, int max_return_elements, StringList &matches ) : CommandCompletions::Completer (interpreter, completion_str, match_start_point, max_return_elements, matches), m_include_support_files (include_support_files), m_matching_files() { FileSpec partial_spec (m_completion_str.c_str(), false); m_file_name = partial_spec.GetFilename().GetCString(); m_dir_name = partial_spec.GetDirectory().GetCString(); } Searcher::Depth CommandCompletions::SourceFileCompleter::GetDepth() { return eDepthCompUnit; } Searcher::CallbackReturn CommandCompletions::SourceFileCompleter::SearchCallback ( SearchFilter &filter, SymbolContext &context, Address *addr, bool complete ) { if (context.comp_unit != NULL) { if (m_include_support_files) { FileSpecList supporting_files = context.comp_unit->GetSupportFiles(); for (size_t sfiles = 0; sfiles < supporting_files.GetSize(); sfiles++) { const FileSpec &sfile_spec = supporting_files.GetFileSpecAtIndex(sfiles); const char *sfile_file_name = sfile_spec.GetFilename().GetCString(); const char *sfile_dir_name = sfile_spec.GetFilename().GetCString(); bool match = false; if (m_file_name && sfile_file_name && strstr (sfile_file_name, m_file_name) == sfile_file_name) match = true; if (match && m_dir_name && sfile_dir_name && strstr (sfile_dir_name, m_dir_name) != sfile_dir_name) match = false; if (match) { m_matching_files.AppendIfUnique(sfile_spec); } } } else { const char *cur_file_name = context.comp_unit->GetFilename().GetCString(); const char *cur_dir_name = context.comp_unit->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); } } } return Searcher::eCallbackReturnContinue; } size_t CommandCompletions::SourceFileCompleter::DoCompletion (SearchFilter *filter) { filter->Search (*this); // Now convert the filelist to completions: for (size_t i = 0; i < m_matching_files.GetSize(); i++) { m_matches.AppendString (m_matching_files.GetFileSpecAtIndex(i).GetFilename().GetCString()); } return m_matches.GetSize(); } //---------------------------------------------------------------------- // SymbolCompleter //---------------------------------------------------------------------- static bool regex_chars (const char comp) { if (comp == '[' || comp == ']' || comp == '(' || comp == ')' || comp == '{' || comp == '}' || comp == '+' || comp == '.' || comp == '*' || comp == '|' || comp == '^' || comp == '$' || comp == '\\' || comp == '?') return true; else return false; } CommandCompletions::SymbolCompleter::SymbolCompleter ( CommandInterpreter &interpreter, const char *completion_str, int match_start_point, int max_return_elements, StringList &matches ) : CommandCompletions::Completer (interpreter, completion_str, match_start_point, max_return_elements, matches) { std::string regex_str; if (completion_str && completion_str[0]) { regex_str.append("^"); regex_str.append(completion_str); } 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.Compile(regex_str.c_str()); } Searcher::Depth CommandCompletions::SymbolCompleter::GetDepth() { return eDepthModule; } Searcher::CallbackReturn CommandCompletions::SymbolCompleter::SearchCallback ( SearchFilter &filter, SymbolContext &context, Address *addr, bool complete ) { if (context.module_sp) { SymbolContextList sc_list; const bool include_symbols = true; const bool include_inlines = true; const bool append = true; context.module_sp->FindFunctions (m_regex, include_symbols, include_inlines, append, sc_list); SymbolContext sc; // Now add the functions & symbols to the list - only add if unique: for (uint32_t i = 0; i < sc_list.GetSize(); i++) { if (sc_list.GetContextAtIndex(i, sc)) { ConstString func_name = sc.GetFunctionName(Mangled::ePreferDemangled); if (!func_name.IsEmpty()) m_match_set.insert (func_name); } } } return Searcher::eCallbackReturnContinue; } size_t CommandCompletions::SymbolCompleter::DoCompletion (SearchFilter *filter) { 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_matches.AppendString((*pos).GetCString()); return m_matches.GetSize(); } //---------------------------------------------------------------------- // ModuleCompleter //---------------------------------------------------------------------- CommandCompletions::ModuleCompleter::ModuleCompleter ( CommandInterpreter &interpreter, const char *completion_str, int match_start_point, int max_return_elements, StringList &matches ) : CommandCompletions::Completer (interpreter, completion_str, match_start_point, max_return_elements, matches) { FileSpec partial_spec (m_completion_str.c_str(), false); m_file_name = partial_spec.GetFilename().GetCString(); m_dir_name = partial_spec.GetDirectory().GetCString(); } Searcher::Depth CommandCompletions::ModuleCompleter::GetDepth() { return eDepthModule; } Searcher::CallbackReturn CommandCompletions::ModuleCompleter::SearchCallback ( SearchFilter &filter, SymbolContext &context, Address *addr, bool complete ) { if (context.module_sp) { const char *cur_file_name = context.module_sp->GetFileSpec().GetFilename().GetCString(); const char *cur_dir_name = context.module_sp->GetFileSpec().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_matches.AppendString (cur_file_name); } } return Searcher::eCallbackReturnContinue; } size_t CommandCompletions::ModuleCompleter::DoCompletion (SearchFilter *filter) { filter->Search (*this); return m_matches.GetSize(); }