#include "gflags_completions.h"
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "gflags.h"
#include "util.h"
using std::set;
using std::string;
using std::vector;
DEFINE_string(tab_completion_word, "",
"If non-empty, HandleCommandLineCompletions() will hijack the "
"process and attempt to do bash-style command line flag "
"completion on this value.");
DEFINE_int32(tab_completion_columns, 80,
"Number of columns to use in output for tab completion");
namespace GFLAGS_NAMESPACE {
namespace {
struct CompletionOptions;
struct NotableFlags;
static void PrintFlagCompletionInfo(void);
static void CanonicalizeCursorWordAndSearchOptions(
const string &cursor_word,
string *canonical_search_token,
CompletionOptions *options);
static bool RemoveTrailingChar(string *str, char c);
static void FindMatchingFlags(
const vector<CommandLineFlagInfo> &all_flags,
const CompletionOptions &options,
const string &match_token,
set<const CommandLineFlagInfo *> *all_matches,
string *longest_common_prefix);
static bool DoesSingleFlagMatch(
const CommandLineFlagInfo &flag,
const CompletionOptions &options,
const string &match_token);
static void CategorizeAllMatchingFlags(
const set<const CommandLineFlagInfo *> &all_matches,
const string &search_token,
const string &module,
const string &package_dir,
NotableFlags *notable_flags);
static void TryFindModuleAndPackageDir(
const vector<CommandLineFlagInfo> all_flags,
string *module,
string *package_dir);
static void FinalizeCompletionOutput(
const set<const CommandLineFlagInfo *> &matching_flags,
CompletionOptions *options,
NotableFlags *notable_flags,
vector<string> *completions);
static void RetrieveUnusedFlags(
const set<const CommandLineFlagInfo *> &matching_flags,
const NotableFlags ¬able_flags,
set<const CommandLineFlagInfo *> *unused_flags);
static void OutputSingleGroupWithLimit(
const set<const CommandLineFlagInfo *> &group,
const string &line_indentation,
const string &header,
const string &footer,
bool long_output_format,
int *remaining_line_limit,
size_t *completion_elements_added,
vector<string> *completions);
static string GetShortFlagLine(
const string &line_indentation,
const CommandLineFlagInfo &info);
static string GetLongFlagLine(
const string &line_indentation,
const CommandLineFlagInfo &info);
struct CompletionOptions {
bool flag_name_substring_search;
bool flag_location_substring_search;
bool flag_description_substring_search;
bool return_all_matching_flags;
bool force_no_update;
};
struct NotableFlags {
typedef set<const CommandLineFlagInfo *> FlagSet;
FlagSet perfect_match_flag;
FlagSet module_flags; FlagSet package_flags; FlagSet most_common_flags; FlagSet subpackage_flags; };
static void PrintFlagCompletionInfo(void) {
string cursor_word = FLAGS_tab_completion_word;
string canonical_token;
CompletionOptions options = { };
CanonicalizeCursorWordAndSearchOptions(
cursor_word,
&canonical_token,
&options);
DVLOG(1) << "Identified canonical_token: '" << canonical_token << "'";
vector<CommandLineFlagInfo> all_flags;
set<const CommandLineFlagInfo *> matching_flags;
GetAllFlags(&all_flags);
DVLOG(2) << "Found " << all_flags.size() << " flags overall";
string longest_common_prefix;
FindMatchingFlags(
all_flags,
options,
canonical_token,
&matching_flags,
&longest_common_prefix);
DVLOG(1) << "Identified " << matching_flags.size() << " matching flags";
DVLOG(1) << "Identified " << longest_common_prefix
<< " as longest common prefix.";
if (longest_common_prefix.size() > canonical_token.size()) {
DVLOG(1) << "The common prefix '" << longest_common_prefix
<< "' was longer than the token '" << canonical_token
<< "'. Returning just this prefix for completion.";
fprintf(stdout, "--%s", longest_common_prefix.c_str());
return;
}
if (matching_flags.empty()) {
VLOG(1) << "There were no matching flags, returning nothing.";
return;
}
string module;
string package_dir;
TryFindModuleAndPackageDir(all_flags, &module, &package_dir);
DVLOG(1) << "Identified module: '" << module << "'";
DVLOG(1) << "Identified package_dir: '" << package_dir << "'";
NotableFlags notable_flags;
CategorizeAllMatchingFlags(
matching_flags,
canonical_token,
module,
package_dir,
¬able_flags);
DVLOG(2) << "Categorized matching flags:";
DVLOG(2) << " perfect_match: " << notable_flags.perfect_match_flag.size();
DVLOG(2) << " module: " << notable_flags.module_flags.size();
DVLOG(2) << " package: " << notable_flags.package_flags.size();
DVLOG(2) << " most common: " << notable_flags.most_common_flags.size();
DVLOG(2) << " subpackage: " << notable_flags.subpackage_flags.size();
vector<string> completions;
FinalizeCompletionOutput(
matching_flags,
&options,
¬able_flags,
&completions);
if (options.force_no_update)
completions.push_back("~");
DVLOG(1) << "Finalized with " << completions.size()
<< " chosen completions";
for (vector<string>::const_iterator it = completions.begin();
it != completions.end();
++it) {
DVLOG(9) << " Completion entry: '" << *it << "'";
fprintf(stdout, "%s\n", it->c_str());
}
}
static void CanonicalizeCursorWordAndSearchOptions(
const string &cursor_word,
string *canonical_search_token,
CompletionOptions *options) {
*canonical_search_token = cursor_word;
if (canonical_search_token->empty()) return;
if ((*canonical_search_token)[0] == '"')
*canonical_search_token = canonical_search_token->substr(1);
while ((*canonical_search_token)[0] == '-')
*canonical_search_token = canonical_search_token->substr(1);
options->flag_name_substring_search = false;
options->flag_location_substring_search = false;
options->flag_description_substring_search = false;
options->return_all_matching_flags = false;
options->force_no_update = false;
int found_question_marks = 0;
int found_plusses = 0;
while (true) {
if (found_question_marks < 3 &&
RemoveTrailingChar(canonical_search_token, '?')) {
++found_question_marks;
continue;
}
if (found_plusses < 1 &&
RemoveTrailingChar(canonical_search_token, '+')) {
++found_plusses;
continue;
}
break;
}
switch (found_question_marks) { case 3: options->flag_description_substring_search = true;
case 2: options->flag_location_substring_search = true;
case 1: options->flag_name_substring_search = true;
};
options->return_all_matching_flags = (found_plusses > 0);
}
static bool RemoveTrailingChar(string *str, char c) {
if (str->empty()) return false;
if ((*str)[str->size() - 1] == c) {
*str = str->substr(0, str->size() - 1);
return true;
}
return false;
}
static void FindMatchingFlags(
const vector<CommandLineFlagInfo> &all_flags,
const CompletionOptions &options,
const string &match_token,
set<const CommandLineFlagInfo *> *all_matches,
string *longest_common_prefix) {
all_matches->clear();
bool first_match = true;
for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin();
it != all_flags.end();
++it) {
if (DoesSingleFlagMatch(*it, options, match_token)) {
all_matches->insert(&*it);
if (first_match) {
first_match = false;
*longest_common_prefix = it->name;
} else {
if (longest_common_prefix->empty() || it->name.empty()) {
longest_common_prefix->clear();
continue;
}
string::size_type pos = 0;
while (pos < longest_common_prefix->size() &&
pos < it->name.size() &&
(*longest_common_prefix)[pos] == it->name[pos])
++pos;
longest_common_prefix->erase(pos);
}
}
}
}
static bool DoesSingleFlagMatch(
const CommandLineFlagInfo &flag,
const CompletionOptions &options,
const string &match_token) {
string::size_type pos = flag.name.find(match_token);
if (pos == 0) return true;
if (options.flag_name_substring_search &&
pos != string::npos)
return true;
if (options.flag_location_substring_search &&
flag.filename.find(match_token) != string::npos)
return true;
if (options.flag_description_substring_search &&
flag.description.find(match_token) != string::npos)
return true;
return false;
}
static void CategorizeAllMatchingFlags(
const set<const CommandLineFlagInfo *> &all_matches,
const string &search_token,
const string &module, const string &package_dir, NotableFlags *notable_flags) {
notable_flags->perfect_match_flag.clear();
notable_flags->module_flags.clear();
notable_flags->package_flags.clear();
notable_flags->most_common_flags.clear();
notable_flags->subpackage_flags.clear();
for (set<const CommandLineFlagInfo *>::const_iterator it =
all_matches.begin();
it != all_matches.end();
++it) {
DVLOG(2) << "Examining match '" << (*it)->name << "'";
DVLOG(7) << " filename: '" << (*it)->filename << "'";
string::size_type pos = string::npos;
if (!package_dir.empty())
pos = (*it)->filename.find(package_dir);
string::size_type slash = string::npos;
if (pos != string::npos) slash = (*it)->filename.find(
PATH_SEPARATOR,
pos + package_dir.size() + 1);
if ((*it)->name == search_token) {
notable_flags->perfect_match_flag.insert(*it);
DVLOG(3) << "Result: perfect match";
} else if (!module.empty() && (*it)->filename == module) {
notable_flags->module_flags.insert(*it);
DVLOG(3) << "Result: module match";
} else if (!package_dir.empty() &&
pos != string::npos && slash == string::npos) {
notable_flags->package_flags.insert(*it);
DVLOG(3) << "Result: package match";
} else if (false) {
DVLOG(3) << "Result: most-common match";
} else if (!package_dir.empty() &&
pos != string::npos && slash != string::npos) {
notable_flags->subpackage_flags.insert(*it);
DVLOG(3) << "Result: subpackage match";
}
DVLOG(3) << "Result: not special match";
}
}
static void PushNameWithSuffix(vector<string>* suffixes, const char* suffix) {
suffixes->push_back(
StringPrintf("/%s%s", ProgramInvocationShortName(), suffix));
}
static void TryFindModuleAndPackageDir(
const vector<CommandLineFlagInfo> all_flags,
string *module,
string *package_dir) {
module->clear();
package_dir->clear();
vector<string> suffixes;
PushNameWithSuffix(&suffixes, ".");
PushNameWithSuffix(&suffixes, "-main.");
PushNameWithSuffix(&suffixes, "_main.");
PushNameWithSuffix(&suffixes, "-test.");
PushNameWithSuffix(&suffixes, "_test.");
PushNameWithSuffix(&suffixes, "-unittest.");
PushNameWithSuffix(&suffixes, "_unittest.");
for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin();
it != all_flags.end();
++it) {
for (vector<string>::const_iterator suffix = suffixes.begin();
suffix != suffixes.end();
++suffix) {
if (it->filename.find(*suffix) != string::npos) {
*module = it->filename;
string::size_type sep = it->filename.rfind(PATH_SEPARATOR);
*package_dir = it->filename.substr(0, (sep == string::npos) ? 0 : sep);
return;
}
}
}
}
struct DisplayInfoGroup {
const char* header;
const char* footer;
set<const CommandLineFlagInfo *> *group;
int SizeInLines() const {
int size_in_lines = static_cast<int>(group->size()) + 1;
if (strlen(header) > 0) {
size_in_lines++;
}
if (strlen(footer) > 0) {
size_in_lines++;
}
return size_in_lines;
}
};
static void FinalizeCompletionOutput(
const set<const CommandLineFlagInfo *> &matching_flags,
CompletionOptions *options,
NotableFlags *notable_flags,
vector<string> *completions) {
int max_desired_lines = (options->return_all_matching_flags ? 999999 : 98);
int lines_so_far = 0;
vector<DisplayInfoGroup> output_groups;
bool perfect_match_found = false;
if (lines_so_far < max_desired_lines &&
!notable_flags->perfect_match_flag.empty()) {
perfect_match_found = true;
DisplayInfoGroup group =
{ "",
"==========",
¬able_flags->perfect_match_flag };
lines_so_far += group.SizeInLines();
output_groups.push_back(group);
}
if (lines_so_far < max_desired_lines &&
!notable_flags->module_flags.empty()) {
DisplayInfoGroup group = {
"-* Matching module flags *-",
"===========================",
¬able_flags->module_flags };
lines_so_far += group.SizeInLines();
output_groups.push_back(group);
}
if (lines_so_far < max_desired_lines &&
!notable_flags->package_flags.empty()) {
DisplayInfoGroup group = {
"-* Matching package flags *-",
"============================",
¬able_flags->package_flags };
lines_so_far += group.SizeInLines();
output_groups.push_back(group);
}
if (lines_so_far < max_desired_lines &&
!notable_flags->most_common_flags.empty()) {
DisplayInfoGroup group = {
"-* Commonly used flags *-",
"=========================",
¬able_flags->most_common_flags };
lines_so_far += group.SizeInLines();
output_groups.push_back(group);
}
if (lines_so_far < max_desired_lines &&
!notable_flags->subpackage_flags.empty()) {
DisplayInfoGroup group = {
"-* Matching sub-package flags *-",
"================================",
¬able_flags->subpackage_flags };
lines_so_far += group.SizeInLines();
output_groups.push_back(group);
}
set<const CommandLineFlagInfo *> obscure_flags; if (lines_so_far < max_desired_lines) {
RetrieveUnusedFlags(matching_flags, *notable_flags, &obscure_flags);
if (!obscure_flags.empty()) {
DisplayInfoGroup group = {
"-* Other flags *-",
"",
&obscure_flags };
lines_so_far += group.SizeInLines();
output_groups.push_back(group);
}
}
int remaining_lines = max_desired_lines;
size_t completions_output = 0;
int indent = static_cast<int>(output_groups.size()) - 1;
for (vector<DisplayInfoGroup>::const_iterator it =
output_groups.begin();
it != output_groups.end();
++it, --indent) {
OutputSingleGroupWithLimit(
*it->group, string(indent, ' '), string(it->header), string(it->footer), perfect_match_found, &remaining_lines, &completions_output, completions); perfect_match_found = false;
}
if (completions_output != matching_flags.size()) {
options->force_no_update = false;
completions->push_back("~ (Remaining flags hidden) ~");
} else {
options->force_no_update = true;
}
}
static void RetrieveUnusedFlags(
const set<const CommandLineFlagInfo *> &matching_flags,
const NotableFlags ¬able_flags,
set<const CommandLineFlagInfo *> *unused_flags) {
for (set<const CommandLineFlagInfo *>::const_iterator it =
matching_flags.begin();
it != matching_flags.end();
++it) {
if (notable_flags.perfect_match_flag.count(*it) ||
notable_flags.module_flags.count(*it) ||
notable_flags.package_flags.count(*it) ||
notable_flags.most_common_flags.count(*it) ||
notable_flags.subpackage_flags.count(*it))
continue;
unused_flags->insert(*it);
}
}
static void OutputSingleGroupWithLimit(
const set<const CommandLineFlagInfo *> &group,
const string &line_indentation,
const string &header,
const string &footer,
bool long_output_format,
int *remaining_line_limit,
size_t *completion_elements_output,
vector<string> *completions) {
if (group.empty()) return;
if (!header.empty()) {
if (*remaining_line_limit < 2) return;
*remaining_line_limit -= 2;
completions->push_back(line_indentation + header);
completions->push_back(line_indentation + string(header.size(), '-'));
}
for (set<const CommandLineFlagInfo *>::const_iterator it = group.begin();
it != group.end() && *remaining_line_limit > 0;
++it) {
--*remaining_line_limit;
++*completion_elements_output;
completions->push_back(
(long_output_format
? GetLongFlagLine(line_indentation, **it)
: GetShortFlagLine(line_indentation, **it)));
}
if (!footer.empty()) {
if (*remaining_line_limit < 1) return;
--*remaining_line_limit;
completions->push_back(line_indentation + footer);
}
}
static string GetShortFlagLine(
const string &line_indentation,
const CommandLineFlagInfo &info) {
string prefix;
bool is_string = (info.type == "string");
SStringPrintf(&prefix, "%s--%s [%s%s%s] ",
line_indentation.c_str(),
info.name.c_str(),
(is_string ? "'" : ""),
info.default_value.c_str(),
(is_string ? "'" : ""));
int remainder =
FLAGS_tab_completion_columns - static_cast<int>(prefix.size());
string suffix;
if (remainder > 0)
suffix =
(static_cast<int>(info.description.size()) > remainder ?
(info.description.substr(0, remainder - 3) + "...").c_str() :
info.description.c_str());
return prefix + suffix;
}
static string GetLongFlagLine(
const string &line_indentation,
const CommandLineFlagInfo &info) {
string output = DescribeOneFlag(info);
string old_flagname = "-" + info.name;
output.replace(
output.find(old_flagname),
old_flagname.size(),
"-" + old_flagname);
static const char kNewlineWithIndent[] = "\n ";
output.replace(output.find(" type:"), 1, string(kNewlineWithIndent));
output.replace(output.find(" default:"), 1, string(kNewlineWithIndent));
output = StringPrintf("%s Details for '--%s':\n"
"%s defined: %s",
line_indentation.c_str(),
info.name.c_str(),
output.c_str(),
info.filename.c_str());
static const string line_of_spaces(FLAGS_tab_completion_columns, ' ');
static const char kDoubledNewlines[] = "\n \n";
for (string::size_type newlines = output.find(kDoubledNewlines);
newlines != string::npos;
newlines = output.find(kDoubledNewlines))
output.replace(newlines, sizeof(kDoubledNewlines) - 1, string("\n"));
for (string::size_type newline = output.find('\n');
newline != string::npos;
newline = output.find('\n')) {
int newline_pos = static_cast<int>(newline) % FLAGS_tab_completion_columns;
int missing_spaces = FLAGS_tab_completion_columns - newline_pos;
output.replace(newline, 1, line_of_spaces, 1, missing_spaces);
}
return output;
}
}
void HandleCommandLineCompletions(void) {
if (FLAGS_tab_completion_word.empty()) return;
PrintFlagCompletionInfo();
gflags_exitfunc(0);
}
}