diff options
Diffstat (limited to 'contrib/llvm/tools/lldb/source/Commands/CommandCompletions.cpp')
-rw-r--r-- | contrib/llvm/tools/lldb/source/Commands/CommandCompletions.cpp | 305 |
1 files changed, 138 insertions, 167 deletions
diff --git a/contrib/llvm/tools/lldb/source/Commands/CommandCompletions.cpp b/contrib/llvm/tools/lldb/source/Commands/CommandCompletions.cpp index 10c1a24..fd84e1c 100644 --- a/contrib/llvm/tools/lldb/source/Commands/CommandCompletions.cpp +++ b/contrib/llvm/tools/lldb/source/Commands/CommandCompletions.cpp @@ -16,12 +16,12 @@ // C++ Includes // Other libraries and framework includes #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringSet.h" // Project includes #include "lldb/Core/FileSpecList.h" #include "lldb/Core/Module.h" #include "lldb/Core/PluginManager.h" -#include "lldb/Host/FileSpec.h" #include "lldb/Host/FileSystem.h" #include "lldb/Interpreter/Args.h" #include "lldb/Interpreter/CommandCompletions.h" @@ -31,8 +31,13 @@ #include "lldb/Symbol/Variable.h" #include "lldb/Target/Target.h" #include "lldb/Utility/CleanUp.h" +#include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/TildeExpressionResolver.h" #include "llvm/ADT/SmallString.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" using namespace lldb_private; @@ -98,181 +103,131 @@ int CommandCompletions::SourceFiles(CommandInterpreter &interpreter, 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; - } +static int DiskFilesOrDirectories(const llvm::Twine &partial_name, + bool only_directories, bool &saw_directory, + StringList &matches, + TildeExpressionResolver &Resolver) { + matches.Clear(); + + llvm::SmallString<256> CompletionBuffer; + llvm::SmallString<256> Storage; + partial_name.toVector(CompletionBuffer); + + if (CompletionBuffer.size() >= PATH_MAX) + return 0; + + namespace fs = llvm::sys::fs; + namespace path = llvm::sys::path; + + llvm::StringRef SearchDir; + llvm::StringRef PartialItem; + + if (CompletionBuffer.startswith("~")) { + 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<PATH_MAX> 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; + saw_directory = Resolver.ResolvePartial(Username, MatchSet); + for (const auto &S : MatchSet) { + Resolved = S.getKey(); + path::append(Resolved, path::get_separator()); + matches.AppendString(Resolved); + } + saw_directory = (matches.GetSize() > 0); + } + return matches.GetSize(); + } - // If we found a directory, we put a "/" at the end of the name. + // 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()); + saw_directory = true; + matches.AppendString(CompletionBuffer); + return 1; + } - if (remainder[0] == '\0' || strstr(name, remainder) == name) { - if (strlen(name) + parameters->baselen >= PATH_MAX) - return FileSpec::eEnumerateDirectoryResultNext; + // 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; + SearchDir = Resolved; + } else { + SearchDir = path::parent_path(CompletionBuffer); + } - strcpy(end_ptr, name); + size_t FullPrefixLen = CompletionBuffer.size(); - bool isa_directory = false; - if (file_type == FileSpec::eFileTypeDirectory) - isa_directory = true; - else if (file_type == FileSpec::eFileTypeSymbolicLink) { - if (FileSpec(partial_name_copy, false).IsDirectory()) - isa_directory = true; - } + PartialItem = path::filename(CompletionBuffer); + if (PartialItem == ".") + PartialItem = llvm::StringRef(); - 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); + if (SearchDir.empty()) { + llvm::sys::fs::current_path(Storage); + SearchDir = Storage; } + assert(!PartialItem.contains(path::get_separator())); - return FileSpec::eEnumerateDirectoryResultNext; -} + // SearchDir now contains the directory to search in, and Prefix contains the + // text we want to match against items in that directory. -static int DiskFilesOrDirectories(llvm::StringRef 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 = partial_file_name.size(); - - 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.data(), 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 - llvm::SmallString<64> containing_part; - - if (end_ptr == nullptr) { - // 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. - llvm::SmallString<64> resolved_username(partial_name_copy); - FileSpec::ResolveUsername(resolved_username); - - // Not sure how this would happen, a username longer than PATH_MAX? - // Still... - if (resolved_username.size() == 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 = "."; - 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 = "/"; - } else { - containing_part.append(partial_name_copy, end_ptr); - } - // Push end_ptr past the final "/" and set remainder. - end_ptr++; - strcpy(remainder, end_ptr); - } + std::error_code EC; + fs::directory_iterator Iter(SearchDir, EC, false); + fs::directory_iterator End; + for (; Iter != End && !EC; Iter.increment(EC)) { + auto &Entry = *Iter; - // 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: + auto Name = path::filename(Entry.path()); - if (*partial_name_copy == '~') { - FileSpec::ResolveUsername(containing_part); - // User name doesn't exist, we're not getting any further... - if (containing_part.empty()) - return matches.GetSize(); - } + // Omit ".", ".." + if (Name == "." || Name == ".." || !Name.startswith(PartialItem)) + continue; - // Okay, containing_part is now the directory we want to open and look for - // files: + // We have a match. - size_t baselen = end_ptr - partial_name_copy; + fs::file_status st; + if ((EC = Entry.status(st))) + continue; - 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; + // If it's a symlink, then we treat it as a directory as long as the target + // is a directory. + bool is_dir = fs::is_directory(st); + if (fs::is_symlink_file(st)) { + fs::file_status target_st; + if (!fs::status(Entry.path(), target_st)) + is_dir = fs::is_directory(target_st); + } + 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) { + saw_directory = true; + path::append(CompletionBuffer, path::get_separator()); + } - FileSpec::EnumerateDirectory(containing_part.c_str(), true, true, true, - DiskFilesOrDirectoriesCallback, ¶meters); + matches.AppendString(CompletionBuffer); + } return matches.GetSize(); } @@ -283,9 +238,17 @@ int CommandCompletions::DiskFiles(CommandInterpreter &interpreter, 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; + word_complete = false; + StandardTildeExpressionResolver Resolver; + return DiskFiles(partial_file_name, matches, Resolver); +} + +int CommandCompletions::DiskFiles(const llvm::Twine &partial_file_name, + StringList &matches, + TildeExpressionResolver &Resolver) { + bool word_complete; + int ret_val = DiskFilesOrDirectories(partial_file_name, false, word_complete, + matches, Resolver); return ret_val; } @@ -293,9 +256,17 @@ int CommandCompletions::DiskDirectories( CommandInterpreter &interpreter, llvm::StringRef 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; + StandardTildeExpressionResolver Resolver; + return DiskDirectories(partial_file_name, matches, Resolver); +} + +int CommandCompletions::DiskDirectories(const llvm::Twine &partial_file_name, + StringList &matches, + TildeExpressionResolver &Resolver) { + bool word_complete; + int ret_val = DiskFilesOrDirectories(partial_file_name, true, word_complete, + matches, Resolver); return ret_val; } |