
There were a couple of problems with this function on Windows. Different separators and differences in how tilde expressions are resolved for starters, but in addition there was no clear indication of what the function's inputs or outputs were supposed to be, and there were no tests to demonstrate its use. To more easily paper over the differences between Windows paths, non-Windows paths, and tilde expressions, I've ported this function to use LLVM-based directory iteration (in fact, I would like to eliminate all of LLDB's directory iteration code entirely since LLVM's is cleaner / more efficient (i.e. it invokes fewer stat calls)). and llvm's portable path manipulation library. Since file and directory completion assumes you are referring to files and directories on your local machine, it's safe to assume the path syntax properties of the host in doing so, so LLVM's APIs are perfect for this. I've also added a fairly robust set of unit tests. Since you can't really predict what users will be on your machine, or what their home directories will be, I added an interface called TildeExpressionResolver, and in the unit test I've mocked up a fake implementation that acts like a unix password database. This allows us to configure some fake users and home directories in the test, so we can exercise all of those hard-to-test codepaths that normally otherwise depend on the host. Differential Revision: https://reviews.llvm.org/D30789 llvm-svn: 297585
565 lines
21 KiB
C++
565 lines
21 KiB
C++
//===-- CommandCompletions.cpp ----------------------------------*- C++ -*-===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// C Includes
|
|
#include <sys/stat.h>
|
|
#if defined(__APPLE__) || defined(__linux__)
|
|
#include <pwd.h>
|
|
#endif
|
|
|
|
// 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"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Interpreter/OptionValueProperties.h"
|
|
#include "lldb/Symbol/CompileUnit.h"
|
|
#include "lldb/Symbol/Variable.h"
|
|
#include "lldb/Target/Target.h"
|
|
#include "lldb/Utility/CleanUp.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;
|
|
|
|
CommandCompletions::CommonCompletionElement
|
|
CommandCompletions::g_common_completions[] = {
|
|
{eCustomCompletion, nullptr},
|
|
{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, nullptr} // This one has to be last in the list.
|
|
};
|
|
|
|
bool CommandCompletions::InvokeCommonCompletionCallbacks(
|
|
CommandInterpreter &interpreter, uint32_t completion_mask,
|
|
llvm::StringRef 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 != nullptr) {
|
|
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,
|
|
llvm::StringRef 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 == nullptr) {
|
|
lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget();
|
|
SearchFilterForUnconstrainedSearches null_searcher(target_sp);
|
|
completer.DoCompletion(&null_searcher);
|
|
} else {
|
|
completer.DoCompletion(searcher);
|
|
}
|
|
return matches.GetSize();
|
|
}
|
|
|
|
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(path::is_separator);
|
|
|
|
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 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;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
size_t FullPrefixLen = CompletionBuffer.size();
|
|
|
|
PartialItem = path::filename(CompletionBuffer);
|
|
if (PartialItem == ".")
|
|
PartialItem = llvm::StringRef();
|
|
|
|
assert(!SearchDir.empty());
|
|
assert(!PartialItem.contains(path::get_separator()));
|
|
|
|
// SearchDir now contains the directory to search in, and Prefix contains the
|
|
// text we want to match against items in that directory.
|
|
|
|
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;
|
|
|
|
auto Name = path::filename(Entry.path());
|
|
|
|
// Omit ".", ".."
|
|
if (Name == "." || Name == ".." || !Name.startswith(PartialItem))
|
|
continue;
|
|
|
|
// We have a match.
|
|
|
|
fs::file_status st;
|
|
if (EC = Entry.status(st))
|
|
continue;
|
|
|
|
// 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());
|
|
}
|
|
|
|
matches.AppendString(CompletionBuffer);
|
|
}
|
|
|
|
return matches.GetSize();
|
|
}
|
|
|
|
int CommandCompletions::DiskFiles(CommandInterpreter &interpreter,
|
|
llvm::StringRef partial_file_name,
|
|
int match_start_point,
|
|
int max_return_elements,
|
|
SearchFilter *searcher, bool &word_complete,
|
|
StringList &matches) {
|
|
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;
|
|
}
|
|
|
|
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) {
|
|
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;
|
|
}
|
|
|
|
int CommandCompletions::Modules(CommandInterpreter &interpreter,
|
|
llvm::StringRef 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 == nullptr) {
|
|
lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget();
|
|
SearchFilterForUnconstrainedSearches null_searcher(target_sp);
|
|
completer.DoCompletion(&null_searcher);
|
|
} else {
|
|
completer.DoCompletion(searcher);
|
|
}
|
|
return matches.GetSize();
|
|
}
|
|
|
|
int CommandCompletions::Symbols(CommandInterpreter &interpreter,
|
|
llvm::StringRef 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 == nullptr) {
|
|
lldb::TargetSP target_sp = interpreter.GetDebugger().GetSelectedTarget();
|
|
SearchFilterForUnconstrainedSearches null_searcher(target_sp);
|
|
completer.DoCompletion(&null_searcher);
|
|
} else {
|
|
completer.DoCompletion(searcher);
|
|
}
|
|
return matches.GetSize();
|
|
}
|
|
|
|
int CommandCompletions::SettingsNames(
|
|
CommandInterpreter &interpreter, llvm::StringRef 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(nullptr, 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, llvm::StringRef 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, llvm::StringRef 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, llvm::StringRef 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,
|
|
llvm::StringRef 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() = default;
|
|
|
|
//----------------------------------------------------------------------
|
|
// SourceFileCompleter
|
|
//----------------------------------------------------------------------
|
|
|
|
CommandCompletions::SourceFileCompleter::SourceFileCompleter(
|
|
CommandInterpreter &interpreter, bool include_support_files,
|
|
llvm::StringRef 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, 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 != nullptr) {
|
|
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) {
|
|
return (comp == '[' || comp == ']' || comp == '(' || comp == ')' ||
|
|
comp == '{' || comp == '}' || comp == '+' || comp == '.' ||
|
|
comp == '*' || comp == '|' || comp == '^' || comp == '$' ||
|
|
comp == '\\' || comp == '?');
|
|
}
|
|
|
|
CommandCompletions::SymbolCompleter::SymbolCompleter(
|
|
CommandInterpreter &interpreter, llvm::StringRef 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.empty()) {
|
|
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);
|
|
}
|
|
|
|
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, llvm::StringRef 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, 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();
|
|
}
|