2011-11-07 12:55:18 +00:00
|
|
|
// Copyright (c) 2008, Google Inc.
|
|
|
|
// All rights reserved.
|
|
|
|
//
|
|
|
|
// Redistribution and use in source and binary forms, with or without
|
|
|
|
// modification, are permitted provided that the following conditions are
|
|
|
|
// met:
|
|
|
|
//
|
|
|
|
// * Redistributions of source code must retain the above copyright
|
|
|
|
// notice, this list of conditions and the following disclaimer.
|
|
|
|
// * Redistributions in binary form must reproduce the above
|
|
|
|
// copyright notice, this list of conditions and the following disclaimer
|
|
|
|
// in the documentation and/or other materials provided with the
|
|
|
|
// distribution.
|
|
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
|
|
// contributors may be used to endorse or promote products derived from
|
|
|
|
// this software without specific prior written permission.
|
|
|
|
//
|
|
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
//
|
|
|
|
// ---
|
2012-03-11 19:52:25 +00:00
|
|
|
|
2011-11-07 12:55:18 +00:00
|
|
|
// Bash-style command line flag completion for C++ binaries
|
|
|
|
//
|
|
|
|
// This module implements bash-style completions. It achieves this
|
|
|
|
// goal in the following broad chunks:
|
|
|
|
//
|
|
|
|
// 1) Take a to-be-completed word, and examine it for search hints
|
|
|
|
// 2) Identify all potentially matching flags
|
|
|
|
// 2a) If there are no matching flags, do nothing.
|
|
|
|
// 2b) If all matching flags share a common prefix longer than the
|
|
|
|
// completion word, output just that matching prefix
|
|
|
|
// 3) Categorize those flags to produce a rough ordering of relevence.
|
|
|
|
// 4) Potentially trim the set of flags returned to a smaller number
|
|
|
|
// that bash is happier with
|
|
|
|
// 5) Output the matching flags in groups ordered by relevence.
|
|
|
|
// 5a) Force bash to place most-relevent groups at the top of the list
|
|
|
|
// 5b) Trim most flag's descriptions to fit on a single terminal line
|
|
|
|
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h> // for strlen
|
|
|
|
|
|
|
|
#include <set>
|
|
|
|
#include <string>
|
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
|
|
|
|
2012-03-11 19:52:25 +00:00
|
|
|
#include "gflags/gflags.h"
|
|
|
|
#include "util.h"
|
|
|
|
|
|
|
|
using std::set;
|
|
|
|
using std::string;
|
|
|
|
using std::vector;
|
2011-11-07 12:55:18 +00:00
|
|
|
|
|
|
|
#ifndef PATH_SEPARATOR
|
|
|
|
#define PATH_SEPARATOR '/'
|
|
|
|
#endif
|
|
|
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
_START_GOOGLE_NAMESPACE_
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
// Function prototypes and Type forward declarations. Code may be
|
|
|
|
// more easily understood if it is roughly ordered according to
|
|
|
|
// control flow, rather than by C's "declare before use" ordering
|
|
|
|
struct CompletionOptions;
|
|
|
|
struct NotableFlags;
|
|
|
|
|
|
|
|
// The entry point if flag completion is to be used.
|
|
|
|
static void PrintFlagCompletionInfo(void);
|
|
|
|
|
|
|
|
|
|
|
|
// 1) Examine search word
|
|
|
|
static void CanonicalizeCursorWordAndSearchOptions(
|
|
|
|
const string &cursor_word,
|
|
|
|
string *canonical_search_token,
|
|
|
|
CompletionOptions *options);
|
|
|
|
|
|
|
|
static bool RemoveTrailingChar(string *str, char c);
|
|
|
|
|
|
|
|
|
|
|
|
// 2) Find all matches
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
|
|
// 3) Categorize matches
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
|
|
// 4) Decide which flags to use
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
|
|
// 5) Output matches
|
|
|
|
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);
|
|
|
|
|
|
|
|
// (helpers for #5)
|
|
|
|
static string GetShortFlagLine(
|
|
|
|
const string &line_indentation,
|
|
|
|
const CommandLineFlagInfo &info);
|
|
|
|
|
|
|
|
static string GetLongFlagLine(
|
|
|
|
const string &line_indentation,
|
|
|
|
const CommandLineFlagInfo &info);
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Useful types
|
|
|
|
|
|
|
|
// Try to deduce the intentions behind this completion attempt. Return the
|
|
|
|
// canonical search term in 'canonical_search_token'. Binary search options
|
|
|
|
// are returned in the various booleans, which should all have intuitive
|
|
|
|
// semantics, possibly except:
|
|
|
|
// - return_all_matching_flags: Generally, we'll trim the number of
|
|
|
|
// returned candidates to some small number, showing those that are
|
|
|
|
// most likely to be useful first. If this is set, however, the user
|
|
|
|
// really does want us to return every single flag as an option.
|
|
|
|
// - force_no_update: Any time we output lines, all of which share a
|
|
|
|
// common prefix, bash will 'helpfully' not even bother to show the
|
|
|
|
// output, instead changing the current word to be that common prefix.
|
|
|
|
// If it's clear this shouldn't happen, we'll set this boolean
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Notable flags are flags that are special or preferred for some
|
|
|
|
// reason. For example, flags that are defined in the binary's module
|
|
|
|
// are expected to be much more relevent than flags defined in some
|
|
|
|
// other random location. These sets are specified roughly in precedence
|
|
|
|
// order. Once a flag is placed in one of these 'higher' sets, it won't
|
|
|
|
// be placed in any of the 'lower' sets.
|
|
|
|
struct NotableFlags {
|
|
|
|
typedef set<const CommandLineFlagInfo *> FlagSet;
|
|
|
|
FlagSet perfect_match_flag;
|
|
|
|
FlagSet module_flags; // Found in module file
|
|
|
|
FlagSet package_flags; // Found in same directory as module file
|
|
|
|
FlagSet most_common_flags; // One of the XXX most commonly supplied flags
|
|
|
|
FlagSet subpackage_flags; // Found in subdirectories of package
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Tab completion implementation - entry point
|
|
|
|
static void PrintFlagCompletionInfo(void) {
|
|
|
|
string cursor_word = FLAGS_tab_completion_word;
|
|
|
|
string canonical_token;
|
|
|
|
CompletionOptions options = { };
|
|
|
|
CanonicalizeCursorWordAndSearchOptions(
|
|
|
|
cursor_word,
|
|
|
|
&canonical_token,
|
|
|
|
&options);
|
|
|
|
|
2012-03-11 19:52:25 +00:00
|
|
|
DVLOG(1) << "Identified canonical_token: '" << canonical_token << "'";
|
2011-11-07 12:55:18 +00:00
|
|
|
|
|
|
|
vector<CommandLineFlagInfo> all_flags;
|
|
|
|
set<const CommandLineFlagInfo *> matching_flags;
|
|
|
|
GetAllFlags(&all_flags);
|
2012-03-11 19:52:25 +00:00
|
|
|
DVLOG(2) << "Found " << all_flags.size() << " flags overall";
|
2011-11-07 12:55:18 +00:00
|
|
|
|
|
|
|
string longest_common_prefix;
|
|
|
|
FindMatchingFlags(
|
|
|
|
all_flags,
|
|
|
|
options,
|
|
|
|
canonical_token,
|
|
|
|
&matching_flags,
|
|
|
|
&longest_common_prefix);
|
2012-03-11 19:52:25 +00:00
|
|
|
DVLOG(1) << "Identified " << matching_flags.size() << " matching flags";
|
|
|
|
DVLOG(1) << "Identified " << longest_common_prefix
|
|
|
|
<< " as longest common prefix.";
|
2011-11-07 12:55:18 +00:00
|
|
|
if (longest_common_prefix.size() > canonical_token.size()) {
|
|
|
|
// There's actually a shared common prefix to all matching flags,
|
|
|
|
// so may as well output that and quit quickly.
|
2012-03-11 19:52:25 +00:00
|
|
|
DVLOG(1) << "The common prefix '" << longest_common_prefix
|
|
|
|
<< "' was longer than the token '" << canonical_token
|
|
|
|
<< "'. Returning just this prefix for completion.";
|
2011-11-07 12:55:18 +00:00
|
|
|
fprintf(stdout, "--%s", longest_common_prefix.c_str());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (matching_flags.empty()) {
|
2012-03-11 19:52:25 +00:00
|
|
|
VLOG(1) << "There were no matching flags, returning nothing.";
|
2011-11-07 12:55:18 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
string module;
|
|
|
|
string package_dir;
|
|
|
|
TryFindModuleAndPackageDir(all_flags, &module, &package_dir);
|
2012-03-11 19:52:25 +00:00
|
|
|
DVLOG(1) << "Identified module: '" << module << "'";
|
|
|
|
DVLOG(1) << "Identified package_dir: '" << package_dir << "'";
|
2011-11-07 12:55:18 +00:00
|
|
|
|
|
|
|
NotableFlags notable_flags;
|
|
|
|
CategorizeAllMatchingFlags(
|
|
|
|
matching_flags,
|
|
|
|
canonical_token,
|
|
|
|
module,
|
|
|
|
package_dir,
|
|
|
|
¬able_flags);
|
2012-03-11 19:52:25 +00:00
|
|
|
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();
|
2011-11-07 12:55:18 +00:00
|
|
|
|
|
|
|
vector<string> completions;
|
|
|
|
FinalizeCompletionOutput(
|
|
|
|
matching_flags,
|
|
|
|
&options,
|
|
|
|
¬able_flags,
|
|
|
|
&completions);
|
|
|
|
|
|
|
|
if (options.force_no_update)
|
|
|
|
completions.push_back("~");
|
|
|
|
|
2012-03-11 19:52:25 +00:00
|
|
|
DVLOG(1) << "Finalized with " << completions.size()
|
|
|
|
<< " chosen completions";
|
2011-11-07 12:55:18 +00:00
|
|
|
|
|
|
|
for (vector<string>::const_iterator it = completions.begin();
|
|
|
|
it != completions.end();
|
|
|
|
++it) {
|
2012-03-11 19:52:25 +00:00
|
|
|
DVLOG(9) << " Completion entry: '" << *it << "'";
|
2011-11-07 12:55:18 +00:00
|
|
|
fprintf(stdout, "%s\n", it->c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1) Examine search word (and helper method)
|
|
|
|
static void CanonicalizeCursorWordAndSearchOptions(
|
|
|
|
const string &cursor_word,
|
|
|
|
string *canonical_search_token,
|
|
|
|
CompletionOptions *options) {
|
|
|
|
*canonical_search_token = cursor_word;
|
|
|
|
if (canonical_search_token->empty()) return;
|
|
|
|
|
|
|
|
// Get rid of leading quotes and dashes in the search term
|
|
|
|
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;
|
|
|
|
|
|
|
|
// Look for all search options we can deduce now. Do this by walking
|
|
|
|
// backwards through the term, looking for up to three '?' and up to
|
|
|
|
// one '+' as suffixed characters. Consume them if found, and remove
|
|
|
|
// them from the canonical search token.
|
|
|
|
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) { // all fallthroughs
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true if a char was removed
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2) Find all matches (and helper methods)
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Given the set of all flags, the parsed match options, and the
|
|
|
|
// canonical search token, produce the set of all candidate matching
|
|
|
|
// flags for subsequent analysis or filtering.
|
|
|
|
static bool DoesSingleFlagMatch(
|
|
|
|
const CommandLineFlagInfo &flag,
|
|
|
|
const CompletionOptions &options,
|
|
|
|
const string &match_token) {
|
|
|
|
// Is there a prefix match?
|
|
|
|
string::size_type pos = flag.name.find(match_token);
|
|
|
|
if (pos == 0) return true;
|
|
|
|
|
|
|
|
// Is there a substring match if we want it?
|
|
|
|
if (options.flag_name_substring_search &&
|
|
|
|
pos != string::npos)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Is there a location match if we want it?
|
|
|
|
if (options.flag_location_substring_search &&
|
|
|
|
flag.filename.find(match_token) != string::npos)
|
|
|
|
return true;
|
|
|
|
|
2012-03-11 19:52:25 +00:00
|
|
|
// TODO(user): All searches should probably be case-insensitive
|
2011-11-07 12:55:18 +00:00
|
|
|
// (especially this one...)
|
|
|
|
if (options.flag_description_substring_search &&
|
|
|
|
flag.description.find(match_token) != string::npos)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3) Categorize matches (and helper method)
|
|
|
|
|
|
|
|
// Given a set of matching flags, categorize them by
|
|
|
|
// likely relevence to this specific binary
|
|
|
|
static void CategorizeAllMatchingFlags(
|
|
|
|
const set<const CommandLineFlagInfo *> &all_matches,
|
|
|
|
const string &search_token,
|
|
|
|
const string &module, // empty if we couldn't find any
|
|
|
|
const string &package_dir, // empty if we couldn't find any
|
|
|
|
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) {
|
2012-03-11 19:52:25 +00:00
|
|
|
DVLOG(2) << "Examining match '" << (*it)->name << "'";
|
|
|
|
DVLOG(7) << " filename: '" << (*it)->filename << "'";
|
2011-11-07 12:55:18 +00:00
|
|
|
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) // candidate for package or subpackage match
|
|
|
|
slash = (*it)->filename.find(
|
|
|
|
PATH_SEPARATOR,
|
|
|
|
pos + package_dir.size() + 1);
|
|
|
|
|
|
|
|
if ((*it)->name == search_token) {
|
|
|
|
// Exact match on some flag's name
|
|
|
|
notable_flags->perfect_match_flag.insert(*it);
|
2012-03-11 19:52:25 +00:00
|
|
|
DVLOG(3) << "Result: perfect match";
|
2011-11-07 12:55:18 +00:00
|
|
|
} else if (!module.empty() && (*it)->filename == module) {
|
|
|
|
// Exact match on module filename
|
|
|
|
notable_flags->module_flags.insert(*it);
|
2012-03-11 19:52:25 +00:00
|
|
|
DVLOG(3) << "Result: module match";
|
2011-11-07 12:55:18 +00:00
|
|
|
} else if (!package_dir.empty() &&
|
|
|
|
pos != string::npos && slash == string::npos) {
|
|
|
|
// In the package, since there was no slash after the package portion
|
|
|
|
notable_flags->package_flags.insert(*it);
|
2012-03-11 19:52:25 +00:00
|
|
|
DVLOG(3) << "Result: package match";
|
2011-11-07 12:55:18 +00:00
|
|
|
} else if (false) {
|
|
|
|
// In the list of the XXX most commonly supplied flags overall
|
2012-03-11 19:52:25 +00:00
|
|
|
// TODO(user): Compile this list.
|
|
|
|
DVLOG(3) << "Result: most-common match";
|
2011-11-07 12:55:18 +00:00
|
|
|
} else if (!package_dir.empty() &&
|
|
|
|
pos != string::npos && slash != string::npos) {
|
|
|
|
// In a subdirectory of the package
|
|
|
|
notable_flags->subpackage_flags.insert(*it);
|
2012-03-11 19:52:25 +00:00
|
|
|
DVLOG(3) << "Result: subpackage match";
|
2011-11-07 12:55:18 +00:00
|
|
|
}
|
|
|
|
|
2012-03-11 19:52:25 +00:00
|
|
|
DVLOG(3) << "Result: not special match";
|
2011-11-07 12:55:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void PushNameWithSuffix(vector<string>* suffixes, const char* suffix) {
|
2012-03-11 19:52:25 +00:00
|
|
|
suffixes->push_back(
|
|
|
|
StringPrintf("/%s%s", ProgramInvocationShortName(), suffix));
|
2011-11-07 12:55:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void TryFindModuleAndPackageDir(
|
|
|
|
const vector<CommandLineFlagInfo> all_flags,
|
|
|
|
string *module,
|
|
|
|
string *package_dir) {
|
|
|
|
module->clear();
|
|
|
|
package_dir->clear();
|
|
|
|
|
|
|
|
vector<string> suffixes;
|
2012-03-11 19:52:25 +00:00
|
|
|
// TODO(user): There's some inherant ambiguity here - multiple directories
|
2011-11-07 12:55:18 +00:00
|
|
|
// could share the same trailing folder and file structure (and even worse,
|
|
|
|
// same file names), causing us to be unsure as to which of the two is the
|
|
|
|
// actual package for this binary. In this case, we'll arbitrarily choose.
|
|
|
|
PushNameWithSuffix(&suffixes, ".");
|
|
|
|
PushNameWithSuffix(&suffixes, "-main.");
|
|
|
|
PushNameWithSuffix(&suffixes, "_main.");
|
|
|
|
// These four are new but probably merited?
|
|
|
|
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) {
|
2012-03-11 19:52:25 +00:00
|
|
|
// TODO(user): Make sure the match is near the end of the string
|
2011-11-07 12:55:18 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Can't specialize template type on a locally defined type. Silly C++...
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// 4) Finalize and trim output flag set
|
|
|
|
static void FinalizeCompletionOutput(
|
|
|
|
const set<const CommandLineFlagInfo *> &matching_flags,
|
|
|
|
CompletionOptions *options,
|
|
|
|
NotableFlags *notable_flags,
|
|
|
|
vector<string> *completions) {
|
|
|
|
|
|
|
|
// We want to output lines in groups. Each group needs to be indented
|
|
|
|
// the same to keep its lines together. Unless otherwise required,
|
|
|
|
// only 99 lines should be output to prevent bash from harassing the
|
|
|
|
// user.
|
|
|
|
|
|
|
|
// First, figure out which output groups we'll actually use. For each
|
|
|
|
// nonempty group, there will be ~3 lines of header & footer, plus all
|
|
|
|
// output lines themselves.
|
|
|
|
int max_desired_lines = // "999999 flags should be enough for anyone. -dave"
|
|
|
|
(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; // flags not notable
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Second, go through each of the chosen output groups and output
|
|
|
|
// as many of those flags as we can, while remaining below our limit
|
|
|
|
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, // group
|
|
|
|
string(indent, ' '), // line indentation
|
|
|
|
string(it->header), // header
|
|
|
|
string(it->footer), // footer
|
|
|
|
perfect_match_found, // long format
|
|
|
|
&remaining_lines, // line limit - reduces this by number printed
|
|
|
|
&completions_output, // completions (not lines) added
|
|
|
|
completions); // produced 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) {
|
|
|
|
// Remove from 'matching_flags' set all members of the sets of
|
|
|
|
// flags we've already printed (specifically, those in notable_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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 5) Output matches (and helper methods)
|
|
|
|
|
|
|
|
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) {
|
2012-03-11 19:52:25 +00:00
|
|
|
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 ? "'" : ""));
|
2011-11-07 12:55:18 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
// Replace '-' with '--', and remove trailing newline before appending
|
|
|
|
// the module definition location.
|
|
|
|
string old_flagname = "-" + info.name;
|
|
|
|
output.replace(
|
|
|
|
output.find(old_flagname),
|
|
|
|
old_flagname.size(),
|
|
|
|
"-" + old_flagname);
|
|
|
|
// Stick a newline and indentation in front of the type and default
|
|
|
|
// portions of DescribeOneFlag()s description
|
|
|
|
static const char kNewlineWithIndent[] = "\n ";
|
|
|
|
output.replace(output.find(" type:"), 1, string(kNewlineWithIndent));
|
|
|
|
output.replace(output.find(" default:"), 1, string(kNewlineWithIndent));
|
2012-03-11 19:52:25 +00:00
|
|
|
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());
|
2011-11-07 12:55:18 +00:00
|
|
|
|
|
|
|
// Eliminate any doubled newlines that crept in. Specifically, if
|
|
|
|
// DescribeOneFlag() decided to break the line just before "type"
|
|
|
|
// or "default", we don't want to introduce an extra blank line
|
|
|
|
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))
|
|
|
|
// Replace each 'doubled newline' with a single newline
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
} // anonymous
|
|
|
|
|
|
|
|
void HandleCommandLineCompletions(void) {
|
|
|
|
if (FLAGS_tab_completion_word.empty()) return;
|
|
|
|
PrintFlagCompletionInfo();
|
2012-03-11 19:52:25 +00:00
|
|
|
gflags_exitfunc(0);
|
2011-11-07 12:55:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_END_GOOGLE_NAMESPACE_
|