diff options
| author | Dimitry Andric <dim@FreeBSD.org> | 2017-04-16 16:04:10 +0000 | 
|---|---|---|
| committer | Dimitry Andric <dim@FreeBSD.org> | 2017-04-16 16:04:10 +0000 | 
| commit | 74a628f776edb588bff8f8f5cc16eac947c9d631 (patch) | |
| tree | dc32e010ac4902621e5a279bfeb48628f7f0e166 /source/Commands/CommandCompletions.cpp | |
| parent | afed7be32164a598f8172282c249af7266c48b46 (diff) | |
Diffstat (limited to 'source/Commands/CommandCompletions.cpp')
| -rw-r--r-- | source/Commands/CommandCompletions.cpp | 305 | 
1 files changed, 138 insertions, 167 deletions
diff --git a/source/Commands/CommandCompletions.cpp b/source/Commands/CommandCompletions.cpp index 10c1a2429bbe..fd84e1c4f857 100644 --- a/source/Commands/CommandCompletions.cpp +++ b/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;  }  | 
