<div dir="ltr">Thanks for the heads up, I'll try to look at this tomorrow.</div><br><div class="gmail_quote"><div dir="ltr">On Tue, Apr 11, 2017 at 5:55 PM Jason Molenda <<a href="mailto:jmolenda@apple.com">jmolenda@apple.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">I noticed that the <a href="http://llvm.org" rel="noreferrer" target="_blank">llvm.org</a> sources crash when you do filename completion with a file in the current working directory:<br>
<br>
 % build/Debug/lldb<br>
 (lldb) file aaa[TAB]Assertion failed: (!SearchDir.empty()), function DiskFilesOrDirectories, file /Volumes/newwork/svn/lldb/source/Commands/CommandCompletions.cpp, line 179.<br>
 Abort<br>
<br>
<br>
Or through the SB API:<br>
<br>
 % build/Debug/lldb<br>
 (lldb) scri<br>
 >>> print lldb.debugger.GetCommandInterpreter().HandleCompletion("file /tmp/ll", 11, 0, -1, lldb.SBStringList())<br>
 0<br>
 >>> print lldb.debugger.GetCommandInterpreter().HandleCompletion("file ll", 7, 0, -1, lldb.SBStringList())<br>
 Assertion failed: (!SearchDir.empty()), function DiskFilesOrDirectories, file /Volumes/newwork/svn/lldb/source/Commands/CommandCompletions.cpp, line 179.<br>
 Abort<br>
<br>
<br>
maybe it's related from r297585?  There have been a few overlapping changes to CommandCompletions.cpp by you and Pavel recently, not sure exactly which it might be.<br>
<br>
If this can't be tested through the unit test framework, it would be easy to add an SB API test case, see above.<br>
<br>
Thanks<br>
<br>
<br>
<br>
> On Mar 12, 2017, at 11:18 AM, Zachary Turner via lldb-commits <<a href="mailto:lldb-commits@lists.llvm.org" target="_blank">lldb-commits@lists.llvm.org</a>> wrote:<br>
><br>
> Author: zturner<br>
> Date: Sun Mar 12 13:18:50 2017<br>
> New Revision: 297585<br>
><br>
> URL: <a href="http://llvm.org/viewvc/llvm-project?rev=297585&view=rev" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project?rev=297585&view=rev</a><br>
> Log:<br>
> Make file / directory completion work properly on Windows.<br>
><br>
> There were a couple of problems with this function on Windows. Different<br>
> separators and differences in how tilde expressions are resolved for<br>
> starters, but in addition there was no clear indication of what the<br>
> function's inputs or outputs were supposed to be, and there were no tests<br>
> to demonstrate its use.<br>
><br>
> To more easily paper over the differences between Windows paths,<br>
> non-Windows paths, and tilde expressions, I've ported this function to use<br>
> LLVM-based directory iteration (in fact, I would like to eliminate all of<br>
> LLDB's directory iteration code entirely since LLVM's is cleaner / more<br>
> efficient (i.e. it invokes fewer stat calls)). and llvm's portable path<br>
> manipulation library.<br>
><br>
> Since file and directory completion assumes you are referring to files and<br>
> directories on your local machine, it's safe to assume the path syntax<br>
> properties of the host in doing so, so LLVM's APIs are perfect for this.<br>
><br>
> I've also added a fairly robust set of unit tests. Since you can't really<br>
> predict what users will be on your machine, or what their home directories<br>
> will be, I added an interface called TildeExpressionResolver, and in the<br>
> unit test I've mocked up a fake implementation that acts like a unix<br>
> password database. This allows us to configure some fake users and home<br>
> directories in the test, so we can exercise all of those hard-to-test<br>
> codepaths that normally otherwise depend on the host.<br>
><br>
> Differential Revision: <a href="https://reviews.llvm.org/D30789" rel="noreferrer" target="_blank">https://reviews.llvm.org/D30789</a><br>
><br>
> Added:<br>
>    lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h<br>
>    lldb/trunk/source/Utility/TildeExpressionResolver.cpp<br>
>    lldb/trunk/unittests/Interpreter/TestCompletion.cpp<br>
> Modified:<br>
>    lldb/trunk/include/lldb/Interpreter/CommandCompletions.h<br>
>    lldb/trunk/source/Commands/CommandCompletions.cpp<br>
>    lldb/trunk/source/Utility/CMakeLists.txt<br>
>    lldb/trunk/unittests/Interpreter/CMakeLists.txt<br>
><br>
> Modified: lldb/trunk/include/lldb/Interpreter/CommandCompletions.h<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Interpreter/CommandCompletions.h?rev=297585&r1=297584&r2=297585&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Interpreter/CommandCompletions.h?rev=297585&r1=297584&r2=297585&view=diff</a><br>
> ==============================================================================<br>
> --- lldb/trunk/include/lldb/Interpreter/CommandCompletions.h (original)<br>
> +++ lldb/trunk/include/lldb/Interpreter/CommandCompletions.h Sun Mar 12 13:18:50 2017<br>
> @@ -21,7 +21,10 @@<br>
> #include "lldb/Utility/RegularExpression.h"<br>
> #include "lldb/lldb-private.h"<br>
><br>
> +#include "llvm/ADT/Twine.h"<br>
> +<br>
> namespace lldb_private {<br>
> +struct TildeExpressionResolver;<br>
> class CommandCompletions {<br>
> public:<br>
>   //----------------------------------------------------------------------<br>
> @@ -76,12 +79,19 @@ public:<br>
>                        int max_return_elements, SearchFilter *searcher,<br>
>                        bool &word_complete, StringList &matches);<br>
><br>
> +  static int DiskFiles(const llvm::Twine &partial_file_name,<br>
> +                       StringList &matches, TildeExpressionResolver &Resolver);<br>
> +<br>
>   static int DiskDirectories(CommandInterpreter &interpreter,<br>
>                              llvm::StringRef partial_file_name,<br>
>                              int match_start_point, int max_return_elements,<br>
>                              SearchFilter *searcher, bool &word_complete,<br>
>                              StringList &matches);<br>
><br>
> +  static int DiskDirectories(const llvm::Twine &partial_file_name,<br>
> +                             StringList &matches,<br>
> +                             TildeExpressionResolver &Resolver);<br>
> +<br>
>   static int SourceFiles(CommandInterpreter &interpreter,<br>
>                          llvm::StringRef partial_file_name,<br>
>                          int match_start_point, int max_return_elements,<br>
><br>
> Added: lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h?rev=297585&view=auto" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h?rev=297585&view=auto</a><br>
> ==============================================================================<br>
> --- lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h (added)<br>
> +++ lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h Sun Mar 12 13:18:50 2017<br>
> @@ -0,0 +1,57 @@<br>
> +//===--------------------- TildeExpressionResolver.h ------------*- C++ -*-===//<br>
> +//<br>
> +//                     The LLVM Compiler Infrastructure<br>
> +//<br>
> +// This file is distributed under the University of Illinois Open Source<br>
> +// License. See LICENSE.TXT for details.<br>
> +//<br>
> +//===----------------------------------------------------------------------===//<br>
> +<br>
> +#ifndef LLDB_UTILITY_TILDE_EXPRESSION_RESOLVER_H<br>
> +#define LLDB_UTILITY_TILDE_EXPRESSION_RESOLVER_H<br>
> +<br>
> +#include "llvm/ADT/SmallVector.h"<br>
> +#include "llvm/ADT/StringRef.h"<br>
> +#include "llvm/ADT/StringSet.h"<br>
> +<br>
> +namespace lldb_private {<br>
> +class TildeExpressionResolver {<br>
> +public:<br>
> +  virtual ~TildeExpressionResolver();<br>
> +<br>
> +  /// \brief Resolve a Tilde Expression contained according to bash rules.<br>
> +  ///<br>
> +  /// \param Expr Contains the tilde expression to resolve.  A valid tilde<br>
> +  ///             expression must begin with a tilde and contain only non<br>
> +  ///             separator characters.<br>
> +  ///<br>
> +  /// \param Output Contains the resolved tilde expression, or the original<br>
> +  ///               input if the tilde expression could not be resolved.<br>
> +  ///<br>
> +  /// \returns true if \p Expr was successfully resolved, false otherwise.<br>
> +  virtual bool ResolveExact(llvm::StringRef Expr,<br>
> +                            llvm::SmallVectorImpl<char> &Output) = 0;<br>
> +<br>
> +  /// \brief Auto-complete a tilde expression with all matching values.<br>
> +  ///<br>
> +  /// \param Expr Contains the tilde expression prefix to resolve.  See<br>
> +  ///             ResolveExact() for validity rules.<br>
> +  ///<br>
> +  /// \param Output Contains all matching home directories, each one<br>
> +  ///               itself unresolved (i.e. you need to call ResolveExact<br>
> +  ///               on each item to turn it into a real path).<br>
> +  ///<br>
> +  /// \returns true if there were any matches, false otherwise.<br>
> +  virtual bool ResolvePartial(llvm::StringRef Expr,<br>
> +                              llvm::StringSet<> &Output) = 0;<br>
> +};<br>
> +<br>
> +class StandardTildeExpressionResolver : public TildeExpressionResolver {<br>
> +public:<br>
> +  bool ResolveExact(llvm::StringRef Expr,<br>
> +                    llvm::SmallVectorImpl<char> &Output) override;<br>
> +  bool ResolvePartial(llvm::StringRef Expr, llvm::StringSet<> &Output) override;<br>
> +};<br>
> +}<br>
> +<br>
> +#endif // #ifndef LLDB_UTILITY_TILDE_EXPRESSION_RESOLVER_H<br>
><br>
> Modified: lldb/trunk/source/Commands/CommandCompletions.cpp<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Commands/CommandCompletions.cpp?rev=297585&r1=297584&r2=297585&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Commands/CommandCompletions.cpp?rev=297585&r1=297584&r2=297585&view=diff</a><br>
> ==============================================================================<br>
> --- lldb/trunk/source/Commands/CommandCompletions.cpp (original)<br>
> +++ lldb/trunk/source/Commands/CommandCompletions.cpp Sun Mar 12 13:18:50 2017<br>
> @@ -16,6 +16,7 @@<br>
> // C++ Includes<br>
> // Other libraries and framework includes<br>
> #include "llvm/ADT/SmallString.h"<br>
> +#include "llvm/ADT/StringSet.h"<br>
><br>
> // Project includes<br>
> #include "lldb/Core/FileSpecList.h"<br>
> @@ -31,9 +32,11 @@<br>
> #include "lldb/Symbol/Variable.h"<br>
> #include "lldb/Target/Target.h"<br>
> #include "lldb/Utility/CleanUp.h"<br>
> +#include "lldb/Utility/TildeExpressionResolver.h"<br>
><br>
> #include "llvm/ADT/SmallString.h"<br>
> #include "llvm/Support/FileSystem.h"<br>
> +#include "llvm/Support/Path.h"<br>
><br>
> using namespace lldb_private;<br>
><br>
> @@ -99,180 +102,127 @@ int CommandCompletions::SourceFiles(Comm<br>
>   return matches.GetSize();<br>
> }<br>
><br>
> -typedef struct DiskFilesOrDirectoriesBaton {<br>
> -  const char *remainder;<br>
> -  char *partial_name_copy;<br>
> -  bool only_directories;<br>
> -  bool *saw_directory;<br>
> -  StringList *matches;<br>
> -  char *end_ptr;<br>
> -  size_t baselen;<br>
> -} DiskFilesOrDirectoriesBaton;<br>
> -<br>
> -FileSpec::EnumerateDirectoryResult<br>
> -DiskFilesOrDirectoriesCallback(void *baton, llvm::sys::fs::file_type file_type,<br>
> -                               const FileSpec &spec) {<br>
> -  const char *name = spec.GetFilename().AsCString();<br>
> -<br>
> -  const DiskFilesOrDirectoriesBaton *parameters =<br>
> -      (DiskFilesOrDirectoriesBaton *)baton;<br>
> -  char *end_ptr = parameters->end_ptr;<br>
> -  char *partial_name_copy = parameters->partial_name_copy;<br>
> -  const char *remainder = parameters->remainder;<br>
> -<br>
> -  // Omit ".", ".." and any . files if the match string doesn't start with .<br>
> -  if (name[0] == '.') {<br>
> -    if (name[1] == '\0')<br>
> -      return FileSpec::eEnumerateDirectoryResultNext;<br>
> -    else if (name[1] == '.' && name[2] == '\0')<br>
> -      return FileSpec::eEnumerateDirectoryResultNext;<br>
> -    else if (remainder[0] != '.')<br>
> -      return FileSpec::eEnumerateDirectoryResultNext;<br>
> -  }<br>
> -<br>
> -  // If we found a directory, we put a "/" at the end of the name.<br>
> -<br>
> -  if (remainder[0] == '\0' || strstr(name, remainder) == name) {<br>
> -    if (strlen(name) + parameters->baselen >= PATH_MAX)<br>
> -      return FileSpec::eEnumerateDirectoryResultNext;<br>
> -<br>
> -    strcpy(end_ptr, name);<br>
> -<br>
> -    namespace fs = llvm::sys::fs;<br>
> -    bool isa_directory = false;<br>
> -    if (file_type == fs::file_type::directory_file)<br>
> -      isa_directory = true;<br>
> -    else if (file_type == fs::file_type::symlink_file)<br>
> -      isa_directory = fs::is_directory(partial_name_copy);<br>
> -<br>
> -    if (isa_directory) {<br>
> -      *parameters->saw_directory = true;<br>
> -      size_t len = strlen(parameters->partial_name_copy);<br>
> -      partial_name_copy[len] = '/';<br>
> -      partial_name_copy[len + 1] = '\0';<br>
> -    }<br>
> -    if (parameters->only_directories && !isa_directory)<br>
> -      return FileSpec::eEnumerateDirectoryResultNext;<br>
> -    parameters->matches->AppendString(partial_name_copy);<br>
> -  }<br>
> -<br>
> -  return FileSpec::eEnumerateDirectoryResultNext;<br>
> -}<br>
> -<br>
> -static int DiskFilesOrDirectories(llvm::StringRef partial_file_name,<br>
> +static int DiskFilesOrDirectories(const llvm::Twine &partial_name,<br>
>                                   bool only_directories, bool &saw_directory,<br>
> -                                  StringList &matches) {<br>
> -  // I'm going to  use the "glob" function with GLOB_TILDE for user directory<br>
> -  // expansion.<br>
> -  // If it is not defined on your host system, you'll need to implement it<br>
> -  // yourself...<br>
> -<br>
> -  size_t partial_name_len = partial_file_name.size();<br>
> -<br>
> -  if (partial_name_len >= PATH_MAX)<br>
> -    return matches.GetSize();<br>
> -<br>
> -  // This copy of the string will be cut up into the directory part, and the<br>
> -  // remainder.  end_ptr below will point to the place of the remainder in this<br>
> -  // string.  Then when we've resolved the containing directory, and opened it,<br>
> -  // we'll read the directory contents and overwrite the partial_name_copy<br>
> -  // starting from end_ptr with each of the matches.  Thus we will preserve the<br>
> -  // form the user originally typed.<br>
> -<br>
> -  char partial_name_copy[PATH_MAX];<br>
> -  memcpy(partial_name_copy, partial_file_name.data(), partial_name_len);<br>
> -  partial_name_copy[partial_name_len] = '\0';<br>
> -<br>
> -  // We'll need to save a copy of the remainder for comparison, which we do<br>
> -  // here.<br>
> -  char remainder[PATH_MAX];<br>
> -<br>
> -  // end_ptr will point past the last / in partial_name_copy, or if there is no<br>
> -  // slash to the beginning of the string.<br>
> -  char *end_ptr;<br>
> -<br>
> -  end_ptr = strrchr(partial_name_copy, '/');<br>
> -<br>
> -  // This will store the resolved form of the containing directory<br>
> -  llvm::SmallString<64> containing_part;<br>
> -<br>
> -  if (end_ptr == nullptr) {<br>
> -    // There's no directory.  If the thing begins with a "~" then this is a bare<br>
> -    // user name.<br>
> -    if (*partial_name_copy == '~') {<br>
> -      // Nothing here but the user name.  We could just put a slash on the end,<br>
> -      // but for completeness sake we'll resolve the user name and only put a<br>
> -      // slash<br>
> -      // on the end if it exists.<br>
> -      llvm::SmallString<64> resolved_username(partial_name_copy);<br>
> -      FileSpec::ResolveUsername(resolved_username);<br>
> -<br>
> -      // Not sure how this would happen, a username longer than PATH_MAX?<br>
> -      // Still...<br>
> -      if (resolved_username.size() == 0) {<br>
> -        // The user name didn't resolve, let's look in the password database for<br>
> -        // matches.<br>
> -        // The user name database contains duplicates, and is not in<br>
> -        // alphabetical order, so<br>
> -        // we'll use a set to manage that for us.<br>
> -        FileSpec::ResolvePartialUsername(partial_name_copy, matches);<br>
> -        if (matches.GetSize() > 0)<br>
> -          saw_directory = true;<br>
> -        return matches.GetSize();<br>
> -      } else {<br>
> -        // The thing exists, put a '/' on the end, and return it...<br>
> -        // FIXME: complete user names here:<br>
> -        partial_name_copy[partial_name_len] = '/';<br>
> -        partial_name_copy[partial_name_len + 1] = '\0';<br>
> -        matches.AppendString(partial_name_copy);<br>
> -        saw_directory = true;<br>
> -        return matches.GetSize();<br>
> +                                  StringList &matches,<br>
> +                                  TildeExpressionResolver &Resolver) {<br>
> +  matches.Clear();<br>
> +<br>
> +  llvm::SmallString<256> CompletionBuffer;<br>
> +  llvm::SmallString<256> Storage;<br>
> +  partial_name.toVector(CompletionBuffer);<br>
> +<br>
> +  if (CompletionBuffer.size() >= PATH_MAX)<br>
> +    return 0;<br>
> +<br>
> +  namespace fs = llvm::sys::fs;<br>
> +  namespace path = llvm::sys::path;<br>
> +<br>
> +  llvm::StringRef SearchDir;<br>
> +  llvm::StringRef PartialItem;<br>
> +<br>
> +  if (CompletionBuffer.startswith("~")) {<br>
> +    llvm::StringRef Buffer(CompletionBuffer);<br>
> +    size_t FirstSep = Buffer.find_if(path::is_separator);<br>
> +<br>
> +    llvm::StringRef Username = Buffer.take_front(FirstSep);<br>
> +    llvm::StringRef Remainder;<br>
> +    if (FirstSep != llvm::StringRef::npos)<br>
> +      Remainder = Buffer.drop_front(FirstSep + 1);<br>
> +<br>
> +    llvm::SmallString<PATH_MAX> Resolved;<br>
> +    if (!Resolver->ResolveExact(Username, Resolved)) {<br>
> +      // We couldn't resolve it as a full username.  If there were no slashes<br>
> +      // then this might be a partial username.   We try to resolve it as such<br>
> +      // but after that, we're done regardless of any matches.<br>
> +      if (FirstSep == llvm::StringRef::npos) {<br>
> +        llvm::StringSet<> MatchSet;<br>
> +        saw_directory = Resolver->ResolvePartial(Username, MatchSet);<br>
> +        for (const auto &S : MatchSet) {<br>
> +          Resolved = S.getKey();<br>
> +          path::append(Resolved, path::get_separator());<br>
> +          matches.AppendString(Resolved);<br>
> +        }<br>
> +        saw_directory = (matches.GetSize() > 0);<br>
>       }<br>
> -    } else {<br>
> -      // The containing part is the CWD, and the whole string is the remainder.<br>
> -      containing_part = ".";<br>
> -      strcpy(remainder, partial_name_copy);<br>
> -      end_ptr = partial_name_copy;<br>
> +      return matches.GetSize();<br>
>     }<br>
> -  } else {<br>
> -    if (end_ptr == partial_name_copy) {<br>
> -      // We're completing a file or directory in the root volume.<br>
> -      containing_part = "/";<br>
> -    } else {<br>
> -      containing_part.append(partial_name_copy, end_ptr);<br>
> +<br>
> +    // If there was no trailing slash, then we're done as soon as we resolve the<br>
> +    // expression to the correct directory.  Otherwise we need to continue<br>
> +    // looking for matches within that directory.<br>
> +    if (FirstSep == llvm::StringRef::npos) {<br>
> +      // Make sure it ends with a separator.<br>
> +      path::append(CompletionBuffer, path::get_separator());<br>
> +      saw_directory = true;<br>
> +      matches.AppendString(CompletionBuffer);<br>
> +      return 1;<br>
>     }<br>
> -    // Push end_ptr past the final "/" and set remainder.<br>
> -    end_ptr++;<br>
> -    strcpy(remainder, end_ptr);<br>
> -  }<br>
><br>
> -  // Look for a user name in the containing part, and if it's there, resolve it<br>
> -  // and stick the<br>
> -  // result back into the containing_part:<br>
> -<br>
> -  if (*partial_name_copy == '~') {<br>
> -    FileSpec::ResolveUsername(containing_part);<br>
> -    // User name doesn't exist, we're not getting any further...<br>
> -    if (containing_part.empty())<br>
> -      return matches.GetSize();<br>
> +    // We want to keep the form the user typed, so we special case this to<br>
> +    // search in the fully resolved directory, but CompletionBuffer keeps the<br>
> +    // unmodified form that the user typed.<br>
> +    Storage = Resolved;<br>
> +    SearchDir = Resolved;<br>
> +  } else {<br>
> +    SearchDir = path::parent_path(CompletionBuffer);<br>
>   }<br>
><br>
> -  // Okay, containing_part is now the directory we want to open and look for<br>
> -  // files:<br>
> +  size_t FullPrefixLen = CompletionBuffer.size();<br>
> +<br>
> +  PartialItem = path::filename(CompletionBuffer);<br>
> +  if (PartialItem == ".")<br>
> +    PartialItem = llvm::StringRef();<br>
><br>
> -  size_t baselen = end_ptr - partial_name_copy;<br>
> +  assert(!SearchDir.empty());<br>
> +  assert(!PartialItem.contains(path::get_separator()));<br>
><br>
> -  DiskFilesOrDirectoriesBaton parameters;<br>
> -  parameters.remainder = remainder;<br>
> -  parameters.partial_name_copy = partial_name_copy;<br>
> -  parameters.only_directories = only_directories;<br>
> -  parameters.saw_directory = &saw_directory;<br>
> -  parameters.matches = &matches;<br>
> -  parameters.end_ptr = end_ptr;<br>
> -  parameters.baselen = baselen;<br>
> +  // SearchDir now contains the directory to search in, and Prefix contains the<br>
> +  // text we want to match against items in that directory.<br>
><br>
> -  FileSpec::EnumerateDirectory(containing_part.c_str(), true, true, true,<br>
> -                               DiskFilesOrDirectoriesCallback, &parameters);<br>
> +  std::error_code EC;<br>
> +  fs::directory_iterator Iter(SearchDir, EC, false);<br>
> +  fs::directory_iterator End;<br>
> +  for (; Iter != End && !EC; Iter.increment(EC)) {<br>
> +    auto &Entry = *Iter;<br>
> +<br>
> +    auto Name = path::filename(Entry.path());<br>
> +<br>
> +    // Omit ".", ".."<br>
> +    if (Name == "." || Name == ".." || !Name.startswith(PartialItem))<br>
> +      continue;<br>
> +<br>
> +    // We have a match.<br>
> +<br>
> +    fs::file_status st;<br>
> +    if (EC = Entry.status(st))<br>
> +      continue;<br>
> +<br>
> +    // If it's a symlink, then we treat it as a directory as long as the target<br>
> +    // is a directory.<br>
> +    bool is_dir = fs::is_directory(st);<br>
> +    if (fs::is_symlink_file(st)) {<br>
> +      fs::file_status target_st;<br>
> +      if (!fs::status(Entry.path(), target_st))<br>
> +        is_dir = fs::is_directory(target_st);<br>
> +    }<br>
> +    if (only_directories && !is_dir)<br>
> +      continue;<br>
> +<br>
> +    // Shrink it back down so that it just has the original prefix the user<br>
> +    // typed and remove the part of the name which is common to the located<br>
> +    // item and what the user typed.<br>
> +    CompletionBuffer.resize(FullPrefixLen);<br>
> +    Name = Name.drop_front(PartialItem.size());<br>
> +    CompletionBuffer.append(Name);<br>
> +<br>
> +    if (is_dir) {<br>
> +      saw_directory = true;<br>
> +      path::append(CompletionBuffer, path::get_separator());<br>
> +    }<br>
> +<br>
> +    matches.AppendString(CompletionBuffer);<br>
> +  }<br>
><br>
>   return matches.GetSize();<br>
> }<br>
> @@ -283,9 +233,17 @@ int CommandCompletions::DiskFiles(Comman<br>
>                                   int max_return_elements,<br>
>                                   SearchFilter *searcher, bool &word_complete,<br>
>                                   StringList &matches) {<br>
> -  int ret_val =<br>
> -      DiskFilesOrDirectories(partial_file_name, false, word_complete, matches);<br>
> -  word_complete = !word_complete;<br>
> +  word_complete = false;<br>
> +  StandardTildeExpressionResolver Resolver;<br>
> +  return DiskFiles(partial_file_name, matches, Resolver);<br>
> +}<br>
> +<br>
> +int CommandCompletions::DiskFiles(const llvm::Twine &partial_file_name,<br>
> +                                  StringList &matches,<br>
> +                                  TildeExpressionResolver &Resolver) {<br>
> +  bool word_complete;<br>
> +  int ret_val = DiskFilesOrDirectories(partial_file_name, false, word_complete,<br>
> +                                       matches, Resolver);<br>
>   return ret_val;<br>
> }<br>
><br>
> @@ -293,9 +251,17 @@ int CommandCompletions::DiskDirectories(<br>
>     CommandInterpreter &interpreter, llvm::StringRef partial_file_name,<br>
>     int match_start_point, int max_return_elements, SearchFilter *searcher,<br>
>     bool &word_complete, StringList &matches) {<br>
> -  int ret_val =<br>
> -      DiskFilesOrDirectories(partial_file_name, true, word_complete, matches);<br>
>   word_complete = false;<br>
> +  StandardTildeExpressionResolver Resolver;<br>
> +  return DiskDirectories(partial_file_name, matches, Resolver);<br>
> +}<br>
> +<br>
> +int CommandCompletions::DiskDirectories(const llvm::Twine &partial_file_name,<br>
> +                                        StringList &matches,<br>
> +                                        TildeExpressionResolver &Resolver) {<br>
> +  bool word_complete;<br>
> +  int ret_val = DiskFilesOrDirectories(partial_file_name, true, word_complete,<br>
> +                                       matches, Resolver);<br>
>   return ret_val;<br>
> }<br>
><br>
><br>
> Modified: lldb/trunk/source/Utility/CMakeLists.txt<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Utility/CMakeLists.txt?rev=297585&r1=297584&r2=297585&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Utility/CMakeLists.txt?rev=297585&r1=297584&r2=297585&view=diff</a><br>
> ==============================================================================<br>
> --- lldb/trunk/source/Utility/CMakeLists.txt (original)<br>
> +++ lldb/trunk/source/Utility/CMakeLists.txt Sun Mar 12 13:18:50 2017<br>
> @@ -25,6 +25,7 @@ add_lldb_library(lldbUtility<br>
>   StringExtractorGDBRemote.cpp<br>
>   StringLexer.cpp<br>
>   TaskPool.cpp<br>
> +  TildeExpressionResolver.cpp<br>
>   UserID.cpp<br>
>   UriParser.cpp<br>
>   UUID.cpp<br>
><br>
> Added: lldb/trunk/source/Utility/TildeExpressionResolver.cpp<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Utility/TildeExpressionResolver.cpp?rev=297585&view=auto" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Utility/TildeExpressionResolver.cpp?rev=297585&view=auto</a><br>
> ==============================================================================<br>
> --- lldb/trunk/source/Utility/TildeExpressionResolver.cpp (added)<br>
> +++ lldb/trunk/source/Utility/TildeExpressionResolver.cpp Sun Mar 12 13:18:50 2017<br>
> @@ -0,0 +1,66 @@<br>
> +//===--------------------- TildeExpressionResolver.cpp ----------*- C++ -*-===//<br>
> +//<br>
> +//                     The LLVM Compiler Infrastructure<br>
> +//<br>
> +// This file is distributed under the University of Illinois Open Source<br>
> +// License. See LICENSE.TXT for details.<br>
> +//<br>
> +//===----------------------------------------------------------------------===//<br>
> +<br>
> +#include "lldb/Utility/TildeExpressionResolver.h"<br>
> +<br>
> +#include "llvm/ADT/SmallString.h"<br>
> +#include "llvm/Support/FileSystem.h"<br>
> +#include "llvm/Support/Path.h"<br>
> +<br>
> +using namespace lldb_private;<br>
> +using namespace llvm;<br>
> +<br>
> +namespace fs = llvm::sys::fs;<br>
> +namespace path = llvm::sys::path;<br>
> +<br>
> +TildeExpressionResolver::~TildeExpressionResolver() {}<br>
> +<br>
> +bool StandardTildeExpressionResolver::ResolveExact(<br>
> +    StringRef Expr, SmallVectorImpl<char> &Output) {<br>
> +  // We expect the tilde expression to be ONLY the expression itself, and<br>
> +  // contain<br>
> +  // no separators.<br>
> +  assert(!llvm::any_of(Expr, path::is_separator));<br>
> +  assert(Expr.empty() || Expr[0] == '~');<br>
> +<br>
> +  return !fs::real_path(Expr, Output, true);<br>
> +}<br>
> +<br>
> +bool StandardTildeExpressionResolver::ResolvePartial(StringRef Expr,<br>
> +                                                     StringSet<> &Output) {<br>
> +  // We expect the tilde expression to be ONLY the expression itself, and<br>
> +  // contain no separators.<br>
> +  assert(!llvm::any_of(Expr, path::is_separator));<br>
> +  assert(Expr.empty() || Expr[0] == '~');<br>
> +<br>
> +  Output.clear();<br>
> +#if defined(LLVM_ON_WIN32)<br>
> +  return false;<br>
> +#else<br>
> +  if (Expr.empty())<br>
> +    return false;<br>
> +<br>
> +  SmallString<32> Buffer = "~";<br>
> +  setpwent();<br>
> +  struct passwd *user_entry;<br>
> +  Expr = Expr.drop_front();<br>
> +<br>
> +  while ((user_entry = getpwent()) != NULL) {<br>
> +    StringRef ThisName(user_entry->pw_name);<br>
> +    if (!ThisName.startswith(Expr))<br>
> +      continue;<br>
> +<br>
> +    Buffer.resize(1);<br>
> +    Buffer.append(ThisName);<br>
> +    Buffer.append(path::get_separator()) Output.insert(Buffer);<br>
> +  }<br>
> +<br>
> +  return true;<br>
> +#endif<br>
> +}<br>
><br>
> Modified: lldb/trunk/unittests/Interpreter/CMakeLists.txt<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/lldb/trunk/unittests/Interpreter/CMakeLists.txt?rev=297585&r1=297584&r2=297585&view=diff" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/lldb/trunk/unittests/Interpreter/CMakeLists.txt?rev=297585&r1=297584&r2=297585&view=diff</a><br>
> ==============================================================================<br>
> --- lldb/trunk/unittests/Interpreter/CMakeLists.txt (original)<br>
> +++ lldb/trunk/unittests/Interpreter/CMakeLists.txt Sun Mar 12 13:18:50 2017<br>
> @@ -1,5 +1,6 @@<br>
> add_lldb_unittest(InterpreterTests<br>
>   TestArgs.cpp<br>
> +  TestCompletion.cpp<br>
><br>
>   LINK_LIBS<br>
>     lldbInterpreter<br>
><br>
> Added: lldb/trunk/unittests/Interpreter/TestCompletion.cpp<br>
> URL: <a href="http://llvm.org/viewvc/llvm-project/lldb/trunk/unittests/Interpreter/TestCompletion.cpp?rev=297585&view=auto" rel="noreferrer" target="_blank">http://llvm.org/viewvc/llvm-project/lldb/trunk/unittests/Interpreter/TestCompletion.cpp?rev=297585&view=auto</a><br>
> ==============================================================================<br>
> --- lldb/trunk/unittests/Interpreter/TestCompletion.cpp (added)<br>
> +++ lldb/trunk/unittests/Interpreter/TestCompletion.cpp Sun Mar 12 13:18:50 2017<br>
> @@ -0,0 +1,346 @@<br>
> +//===-- TestCompletion.cpp --------------------------------------*- C++ -*-===//<br>
> +//<br>
> +//                     The LLVM Compiler Infrastructure<br>
> +//<br>
> +// This file is distributed under the University of Illinois Open Source<br>
> +// License. See LICENSE.TXT for details.<br>
> +//<br>
> +//===----------------------------------------------------------------------===//<br>
> +<br>
> +#include "gtest/gtest.h"<br>
> +<br>
> +#include "lldb/Core/StringList.h"<br>
> +#include "lldb/Interpreter/CommandCompletions.h"<br>
> +#include "lldb/Utility/TildeExpressionResolver.h"<br>
> +<br>
> +#include "llvm/ADT/SmallString.h"<br>
> +#include "llvm/Support/FileSystem.h"<br>
> +#include "llvm/Support/Path.h"<br>
> +#include "llvm/Support/raw_ostream.h"<br>
> +<br>
> +namespace fs = llvm::sys::fs;<br>
> +namespace path = llvm::sys::path;<br>
> +using namespace llvm;<br>
> +using namespace lldb_private;<br>
> +<br>
> +#define ASSERT_NO_ERROR(x)                                                     \<br>
> +  if (std::error_code ASSERT_NO_ERROR_ec = x) {                                \<br>
> +    SmallString<128> MessageStorage;                                           \<br>
> +    raw_svector_ostream Message(MessageStorage);                               \<br>
> +    Message << #x ": did not return errc::success.\n"                          \<br>
> +            << "error number: " << ASSERT_NO_ERROR_ec.value() << "\n"          \<br>
> +            << "error message: " << ASSERT_NO_ERROR_ec.message() << "\n";      \<br>
> +    GTEST_FATAL_FAILURE_(MessageStorage.c_str());                              \<br>
> +  } else {                                                                     \<br>
> +  }<br>
> +<br>
> +namespace {<br>
> +<br>
> +class MockTildeExpressionResolver : public TildeExpressionResolver {<br>
> +  StringRef CurrentUser;<br>
> +  StringMap<StringRef> UserDirectories;<br>
> +<br>
> +public:<br>
> +  explicit MockTildeExpressionResolver(StringRef CurrentUser, StringRef HomeDir)<br>
> +      : CurrentUser(CurrentUser) {<br>
> +    UserDirectories.insert(std::make_pair(CurrentUser, HomeDir));<br>
> +  }<br>
> +<br>
> +  void AddKnownUser(StringRef User, StringRef HomeDir) {<br>
> +    assert(UserDirectories.find(User) == UserDirectories.end());<br>
> +    UserDirectories.insert(std::make_pair(User, HomeDir));<br>
> +  }<br>
> +<br>
> +  void Clear() {<br>
> +    CurrentUser = StringRef();<br>
> +    UserDirectories.clear();<br>
> +  }<br>
> +<br>
> +  void SetCurrentUser(StringRef User) {<br>
> +    assert(UserDirectories.find(User) != UserDirectories.end());<br>
> +    CurrentUser = User;<br>
> +  }<br>
> +<br>
> +  bool ResolveExact(StringRef Expr, SmallVectorImpl<char> &Output) override {<br>
> +    Output.clear();<br>
> +<br>
> +    assert(!llvm::any_of(Expr, llvm::sys::path::is_separator));<br>
> +    assert(Expr.empty() || Expr[0] == '~');<br>
> +    Expr = Expr.drop_front();<br>
> +    if (Expr.empty()) {<br>
> +      auto Dir = UserDirectories[CurrentUser];<br>
> +      Output.append(Dir.begin(), Dir.end());<br>
> +      return true;<br>
> +    }<br>
> +<br>
> +    for (const auto &User : UserDirectories) {<br>
> +      if (User.getKey() != Expr)<br>
> +        continue;<br>
> +      Output.append(User.getValue().begin(), User.getValue().end());<br>
> +      return true;<br>
> +    }<br>
> +    return false;<br>
> +  }<br>
> +<br>
> +  bool ResolvePartial(StringRef Expr, StringSet<> &Output) override {<br>
> +    Output.clear();<br>
> +<br>
> +    assert(!llvm::any_of(Expr, llvm::sys::path::is_separator));<br>
> +    assert(Expr.empty() || Expr[0] == '~');<br>
> +    Expr = Expr.drop_front();<br>
> +<br>
> +    SmallString<16> QualifiedName = "~";<br>
> +    for (const auto &User : UserDirectories) {<br>
> +      if (!User.getKey().startswith(Expr))<br>
> +        continue;<br>
> +      QualifiedName.resize(1);<br>
> +      QualifiedName.append(User.getKey().begin(), User.getKey().end());<br>
> +      Output.insert(QualifiedName);<br>
> +    }<br>
> +<br>
> +    return !Output.empty();<br>
> +  }<br>
> +};<br>
> +<br>
> +class CompletionTest : public testing::Test {<br>
> +protected:<br>
> +  /// Unique temporary directory in which all created filesystem entities must<br>
> +  /// be placed. It is removed at the end of the test suite.<br>
> +  static SmallString<128> BaseDir;<br>
> +<br>
> +  static SmallString<128> DirFoo;<br>
> +  static SmallString<128> DirFooA;<br>
> +  static SmallString<128> DirFooB;<br>
> +  static SmallString<128> DirFooC;<br>
> +  static SmallString<128> DirBar;<br>
> +  static SmallString<128> DirBaz;<br>
> +  static SmallString<128> DirTestFolder;<br>
> +<br>
> +  static SmallString<128> FileAA;<br>
> +  static SmallString<128> FileAB;<br>
> +  static SmallString<128> FileAC;<br>
> +  static SmallString<128> FileFoo;<br>
> +  static SmallString<128> FileBar;<br>
> +  static SmallString<128> FileBaz;<br>
> +<br>
> +  static void SetUpTestCase() {<br>
> +    ASSERT_NO_ERROR(fs::createUniqueDirectory("FsCompletion", BaseDir));<br>
> +    const char *DirNames[] = {"foo", "fooa", "foob",       "fooc",<br>
> +                              "bar", "baz",  "test_folder"};<br>
> +    const char *FileNames[] = {"aa%%%%.tmp",  "ab%%%%.tmp",  "ac%%%%.tmp",<br>
> +                               "foo%%%%.tmp", "bar%%%%.tmp", "baz%%%%.tmp"};<br>
> +    SmallString<128> *Dirs[] = {&DirFoo, &DirFooA, &DirFooB,      &DirFooC,<br>
> +                                &DirBar, &DirBaz,  &DirTestFolder};<br>
> +    for (auto Dir : llvm::zip(DirNames, Dirs)) {<br>
> +      auto &Path = *std::get<1>(Dir);<br>
> +      Path = BaseDir;<br>
> +      path::append(Path, std::get<0>(Dir));<br>
> +      ASSERT_NO_ERROR(fs::create_directory(Path));<br>
> +    }<br>
> +<br>
> +    SmallString<128> *Files[] = {&FileAA,  &FileAB,  &FileAC,<br>
> +                                 &FileFoo, &FileBar, &FileBaz};<br>
> +    for (auto File : llvm::zip(FileNames, Files)) {<br>
> +      auto &Path = *std::get<1>(File);<br>
> +      Path = BaseDir;<br>
> +      path::append(Path, std::get<0>(File));<br>
> +      int FD;<br>
> +      ASSERT_NO_ERROR(fs::createUniqueFile(Path, FD, Path));<br>
> +      ::close(FD);<br>
> +    }<br>
> +  }<br>
> +<br>
> +  static void TearDownTestCase() {<br>
> +    ASSERT_NO_ERROR(fs::remove_directories(BaseDir));<br>
> +  }<br>
> +<br>
> +  static bool HasEquivalentFile(const Twine &Path, const StringList &Paths) {<br>
> +    for (int I = 0; I < Paths.GetSize(); ++I) {<br>
> +      if (fs::equivalent(Path, Paths[I]))<br>
> +        return true;<br>
> +    }<br>
> +    return false;<br>
> +  }<br>
> +<br>
> +  static bool ContainsExactString(const Twine &Str, const StringList &Paths) {<br>
> +    SmallString<16> Storage;<br>
> +    StringRef Rendered = Str.toStringRef(Storage);<br>
> +    for (int I = 0; I < Paths.GetSize(); ++I) {<br>
> +      if (Paths[I] == Rendered)<br>
> +        return true;<br>
> +    }<br>
> +    return false;<br>
> +  }<br>
> +};<br>
> +<br>
> +SmallString<128> CompletionTest::BaseDir;<br>
> +<br>
> +SmallString<128> CompletionTest::DirFoo;<br>
> +SmallString<128> CompletionTest::DirFooA;<br>
> +SmallString<128> CompletionTest::DirFooB;<br>
> +SmallString<128> CompletionTest::DirFooC;<br>
> +SmallString<128> CompletionTest::DirBar;<br>
> +SmallString<128> CompletionTest::DirBaz;<br>
> +SmallString<128> CompletionTest::DirTestFolder;<br>
> +<br>
> +SmallString<128> CompletionTest::FileAA;<br>
> +SmallString<128> CompletionTest::FileAB;<br>
> +SmallString<128> CompletionTest::FileAC;<br>
> +SmallString<128> CompletionTest::FileFoo;<br>
> +SmallString<128> CompletionTest::FileBar;<br>
> +SmallString<128> CompletionTest::FileBaz;<br>
> +}<br>
> +<br>
> +TEST_F(CompletionTest, DirCompletionAbsolute) {<br>
> +  // All calls to DiskDirectories() return only directories, even when<br>
> +  // there are files which also match.  The tests below all check this<br>
> +  // by asserting an exact result count, and verifying against known<br>
> +  // folders.<br>
> +<br>
> +  StringList Results;<br>
> +  // When a directory is specified that doesn't end in a slash, it searches<br>
> +  // for that directory, not items under it.<br>
> +  int Count = CommandCompletions::DiskDirectories2(BaseDir, Results);<br>
> +  ASSERT_EQ(1, Count);<br>
> +  ASSERT_EQ(Count, Results.GetSize());<br>
> +  EXPECT_TRUE(HasEquivalentFile(BaseDir, Results));<br>
> +<br>
> +  // When the same directory ends with a slash, it finds all children.<br>
> +  Count = CommandCompletions::DiskDirectories2(Twine(BaseDir) + "/", Results);<br>
> +  ASSERT_EQ(7, Count);<br>
> +  ASSERT_EQ(Count, Results.GetSize());<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFoo, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFooB, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFooC, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirBar, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirBaz, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirTestFolder, Results));<br>
> +<br>
> +  // When a partial name matches, it returns all matches.  If it matches both<br>
> +  // a full name AND some partial names, it returns all of them.<br>
> +  Count =<br>
> +      CommandCompletions::DiskDirectories2(Twine(BaseDir) + "/foo", Results);<br>
> +  ASSERT_EQ(4, Count);<br>
> +  ASSERT_EQ(Count, Results.GetSize());<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFoo, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFooB, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFooC, Results));<br>
> +<br>
> +  // If it matches only partial names, it still works as expected.<br>
> +  Count = CommandCompletions::DiskDirectories2(Twine(BaseDir) + "/b", Results);<br>
> +  ASSERT_EQ(2, Count);<br>
> +  ASSERT_EQ(Count, Results.GetSize());<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirBar, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirBaz, Results));<br>
> +}<br>
> +<br>
> +TEST_F(CompletionTest, FileCompletionAbsolute) {<br>
> +  // All calls to DiskFiles() return both files and directories  The tests below<br>
> +  // all check this by asserting an exact result count, and verifying against<br>
> +  // known folders.<br>
> +<br>
> +  StringList Results;<br>
> +  // When an item is specified that doesn't end in a slash but exactly matches<br>
> +  // one item, it returns that item.<br>
> +  int Count = CommandCompletions::DiskFiles2(Twine(BaseDir) + "/fooa", Results);<br>
> +  ASSERT_EQ(1, Count);<br>
> +  ASSERT_EQ(Count, Results.GetSize());<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));<br>
> +<br>
> +  // The previous check verified a directory match.  But it should work for<br>
> +  // files too.<br>
> +  Count = CommandCompletions::DiskFiles2(Twine(BaseDir) + "/aa", Results);<br>
> +  ASSERT_EQ(1, Count);<br>
> +  ASSERT_EQ(Count, Results.GetSize());<br>
> +  EXPECT_TRUE(HasEquivalentFile(FileAA, Results));<br>
> +<br>
> +  // When it ends with a slash, it should find all files and directories.<br>
> +  Count = CommandCompletions::DiskFiles2(Twine(BaseDir) + "/", Results);<br>
> +  ASSERT_EQ(13, Count);<br>
> +  ASSERT_EQ(Count, Results.GetSize());<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFoo, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFooB, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFooC, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirBar, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirBaz, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirTestFolder, Results));<br>
> +<br>
> +  EXPECT_TRUE(HasEquivalentFile(FileAA, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(FileAB, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(FileAC, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(FileFoo, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(FileBar, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(FileBaz, Results));<br>
> +<br>
> +  // When a partial name matches, it returns all file & directory matches.<br>
> +  Count = CommandCompletions::DiskFiles2(Twine(BaseDir) + "/foo", Results);<br>
> +  ASSERT_EQ(5, Count);<br>
> +  ASSERT_EQ(Count, Results.GetSize());<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFoo, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFooB, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(DirFooC, Results));<br>
> +  EXPECT_TRUE(HasEquivalentFile(FileFoo, Results));<br>
> +}<br>
> +<br>
> +TEST_F(CompletionTest, DirCompletionUsername) {<br>
> +  MockTildeExpressionResolver Resolver("James", BaseDir);<br>
> +  Resolver.AddKnownUser("Kirk", DirFooB);<br>
> +  Resolver.AddKnownUser("Lars", DirFooC);<br>
> +  Resolver.AddKnownUser("Jason", DirFoo);<br>
> +  Resolver.AddKnownUser("Larry", DirFooA);<br>
> +<br>
> +  // Just resolving current user's home directory by itself should return the<br>
> +  // directory.<br>
> +  StringList Results;<br>
> +  int Count = CommandCompletions::DiskDirectories2("~", Results, &Resolver);<br>
> +  ASSERT_EQ(1, Count);<br>
> +  ASSERT_EQ(Count, Results.GetSize());<br>
> +  EXPECT_TRUE(ContainsExactString(Twine("~") + path::get_separator(), Results));<br>
> +<br>
> +  // With a slash appended, it should return all items in the directory.<br>
> +  Count = CommandCompletions::DiskDirectories2("~/", Results, &Resolver);<br>
> +  ASSERT_EQ(7, Count);<br>
> +  ASSERT_EQ(Count, Results.GetSize());<br>
> +  EXPECT_TRUE(<br>
> +      ContainsExactString(Twine("~/foo") + path::get_separator(), Results));<br>
> +  EXPECT_TRUE(<br>
> +      ContainsExactString(Twine("~/fooa") + path::get_separator(), Results));<br>
> +  EXPECT_TRUE(<br>
> +      ContainsExactString(Twine("~/foob") + path::get_separator(), Results));<br>
> +  EXPECT_TRUE(<br>
> +      ContainsExactString(Twine("~/fooc") + path::get_separator(), Results));<br>
> +  EXPECT_TRUE(<br>
> +      ContainsExactString(Twine("~/bar") + path::get_separator(), Results));<br>
> +  EXPECT_TRUE(<br>
> +      ContainsExactString(Twine("~/baz") + path::get_separator(), Results));<br>
> +  EXPECT_TRUE(ContainsExactString(<br>
> +      Twine("~/test_folder") + path::get_separator(), Results));<br>
> +<br>
> +  // With ~username syntax it should return one match if there is an exact<br>
> +  // match.<br>
> +  // It shouldn't translate to the actual directory, it should keep the form the<br>
> +  // user typed.<br>
> +  Count = CommandCompletions::DiskDirectories2("~Lars", Results, &Resolver);<br>
> +  ASSERT_EQ(1, Count);<br>
> +  ASSERT_EQ(Count, Results.GetSize());<br>
> +  EXPECT_TRUE(<br>
> +      ContainsExactString(Twine("~Lars") + path::get_separator(), Results));<br>
> +<br>
> +  // But with a username that is not found, no results are returned.<br>
> +  Count = CommandCompletions::DiskDirectories2("~Dave", Results, &Resolver);<br>
> +  ASSERT_EQ(0, Count);<br>
> +  ASSERT_EQ(Count, Results.GetSize());<br>
> +<br>
> +  // And if there are multiple matches, it should return all of them.<br>
> +  Count = CommandCompletions::DiskDirectories2("~La", Results, &Resolver);<br>
> +  ASSERT_EQ(2, Count);<br>
> +  ASSERT_EQ(Count, Results.GetSize());<br>
> +  EXPECT_TRUE(<br>
> +      ContainsExactString(Twine("~Lars") + path::get_separator(), Results));<br>
> +  EXPECT_TRUE(<br>
> +      ContainsExactString(Twine("~Larry") + path::get_separator(), Results));<br>
> +}<br>
><br>
><br>
> _______________________________________________<br>
> lldb-commits mailing list<br>
> <a href="mailto:lldb-commits@lists.llvm.org" target="_blank">lldb-commits@lists.llvm.org</a><br>
> <a href="http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits" rel="noreferrer" target="_blank">http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits</a><br>
<br>
</blockquote></div>