Home | History | Annotate | Download | only in src
      1 // Copyright (c) 2008, Google Inc.
      2 // All rights reserved.
      3 //
      4 // Redistribution and use in source and binary forms, with or without
      5 // modification, are permitted provided that the following conditions are
      6 // met:
      7 //
      8 //     * Redistributions of source code must retain the above copyright
      9 // notice, this list of conditions and the following disclaimer.
     10 //     * Redistributions in binary form must reproduce the above
     11 // copyright notice, this list of conditions and the following disclaimer
     12 // in the documentation and/or other materials provided with the
     13 // distribution.
     14 //     * Neither the name of Google Inc. nor the names of its
     15 // contributors may be used to endorse or promote products derived from
     16 // this software without specific prior written permission.
     17 //
     18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29 //
     30 // ---
     31 
     32 // Bash-style command line flag completion for C++ binaries
     33 //
     34 // This module implements bash-style completions.  It achieves this
     35 // goal in the following broad chunks:
     36 //
     37 //  1) Take a to-be-completed word, and examine it for search hints
     38 //  2) Identify all potentially matching flags
     39 //     2a) If there are no matching flags, do nothing.
     40 //     2b) If all matching flags share a common prefix longer than the
     41 //         completion word, output just that matching prefix
     42 //  3) Categorize those flags to produce a rough ordering of relevence.
     43 //  4) Potentially trim the set of flags returned to a smaller number
     44 //     that bash is happier with
     45 //  5) Output the matching flags in groups ordered by relevence.
     46 //     5a) Force bash to place most-relevent groups at the top of the list
     47 //     5b) Trim most flag's descriptions to fit on a single terminal line
     48 
     49 #include <stdio.h>
     50 #include <stdlib.h>
     51 #include <string.h>   // for strlen
     52 
     53 #include <set>
     54 #include <string>
     55 #include <utility>
     56 #include <vector>
     57 
     58 #include "config.h"
     59 #include "gflags/gflags.h"
     60 #include "gflags/gflags_completions.h"
     61 #include "util.h"
     62 
     63 using std::set;
     64 using std::string;
     65 using std::vector;
     66 
     67 
     68 DEFINE_string(tab_completion_word, "",
     69               "If non-empty, HandleCommandLineCompletions() will hijack the "
     70               "process and attempt to do bash-style command line flag "
     71               "completion on this value.");
     72 DEFINE_int32(tab_completion_columns, 80,
     73              "Number of columns to use in output for tab completion");
     74 
     75 
     76 namespace GFLAGS_NAMESPACE {
     77 
     78 
     79 namespace {
     80 // Function prototypes and Type forward declarations.  Code may be
     81 // more easily understood if it is roughly ordered according to
     82 // control flow, rather than by C's "declare before use" ordering
     83 struct CompletionOptions;
     84 struct NotableFlags;
     85 
     86 // The entry point if flag completion is to be used.
     87 static void PrintFlagCompletionInfo(void);
     88 
     89 
     90 // 1) Examine search word
     91 static void CanonicalizeCursorWordAndSearchOptions(
     92     const string &cursor_word,
     93     string *canonical_search_token,
     94     CompletionOptions *options);
     95 
     96 static bool RemoveTrailingChar(string *str, char c);
     97 
     98 
     99 // 2) Find all matches
    100 static void FindMatchingFlags(
    101     const vector<CommandLineFlagInfo> &all_flags,
    102     const CompletionOptions &options,
    103     const string &match_token,
    104     set<const CommandLineFlagInfo *> *all_matches,
    105     string *longest_common_prefix);
    106 
    107 static bool DoesSingleFlagMatch(
    108     const CommandLineFlagInfo &flag,
    109     const CompletionOptions &options,
    110     const string &match_token);
    111 
    112 
    113 // 3) Categorize matches
    114 static void CategorizeAllMatchingFlags(
    115     const set<const CommandLineFlagInfo *> &all_matches,
    116     const string &search_token,
    117     const string &module,
    118     const string &package_dir,
    119     NotableFlags *notable_flags);
    120 
    121 static void TryFindModuleAndPackageDir(
    122     const vector<CommandLineFlagInfo> &all_flags,
    123     string *module,
    124     string *package_dir);
    125 
    126 
    127 // 4) Decide which flags to use
    128 static void FinalizeCompletionOutput(
    129     const set<const CommandLineFlagInfo *> &matching_flags,
    130     CompletionOptions *options,
    131     NotableFlags *notable_flags,
    132     vector<string> *completions);
    133 
    134 static void RetrieveUnusedFlags(
    135     const set<const CommandLineFlagInfo *> &matching_flags,
    136     const NotableFlags &notable_flags,
    137     set<const CommandLineFlagInfo *> *unused_flags);
    138 
    139 
    140 // 5) Output matches
    141 static void OutputSingleGroupWithLimit(
    142     const set<const CommandLineFlagInfo *> &group,
    143     const string &line_indentation,
    144     const string &header,
    145     const string &footer,
    146     bool long_output_format,
    147     int *remaining_line_limit,
    148     size_t *completion_elements_added,
    149     vector<string> *completions);
    150 
    151 // (helpers for #5)
    152 static string GetShortFlagLine(
    153     const string &line_indentation,
    154     const CommandLineFlagInfo &info);
    155 
    156 static string GetLongFlagLine(
    157     const string &line_indentation,
    158     const CommandLineFlagInfo &info);
    159 
    160 
    161 //
    162 // Useful types
    163 
    164 // Try to deduce the intentions behind this completion attempt.  Return the
    165 // canonical search term in 'canonical_search_token'.  Binary search options
    166 // are returned in the various booleans, which should all have intuitive
    167 // semantics, possibly except:
    168 //  - return_all_matching_flags: Generally, we'll trim the number of
    169 //    returned candidates to some small number, showing those that are
    170 //    most likely to be useful first.  If this is set, however, the user
    171 //    really does want us to return every single flag as an option.
    172 //  - force_no_update: Any time we output lines, all of which share a
    173 //    common prefix, bash will 'helpfully' not even bother to show the
    174 //    output, instead changing the current word to be that common prefix.
    175 //    If it's clear this shouldn't happen, we'll set this boolean
    176 struct CompletionOptions {
    177   bool flag_name_substring_search;
    178   bool flag_location_substring_search;
    179   bool flag_description_substring_search;
    180   bool return_all_matching_flags;
    181   bool force_no_update;
    182   CompletionOptions(): flag_name_substring_search(false),
    183                        flag_location_substring_search(false),
    184                        flag_description_substring_search(false),
    185                        return_all_matching_flags(false),
    186                        force_no_update(false) { }
    187 };
    188 
    189 // Notable flags are flags that are special or preferred for some
    190 // reason.  For example, flags that are defined in the binary's module
    191 // are expected to be much more relevent than flags defined in some
    192 // other random location.  These sets are specified roughly in precedence
    193 // order.  Once a flag is placed in one of these 'higher' sets, it won't
    194 // be placed in any of the 'lower' sets.
    195 struct NotableFlags {
    196   typedef set<const CommandLineFlagInfo *> FlagSet;
    197   FlagSet perfect_match_flag;
    198   FlagSet module_flags;       // Found in module file
    199   FlagSet package_flags;      // Found in same directory as module file
    200   FlagSet most_common_flags;  // One of the XXX most commonly supplied flags
    201   FlagSet subpackage_flags;   // Found in subdirectories of package
    202 };
    203 
    204 
    205 //
    206 // Tab completion implementation - entry point
    207 static void PrintFlagCompletionInfo(void) {
    208   string cursor_word = FLAGS_tab_completion_word;
    209   string canonical_token;
    210   CompletionOptions options = CompletionOptions();
    211   CanonicalizeCursorWordAndSearchOptions(
    212       cursor_word,
    213       &canonical_token,
    214       &options);
    215 
    216   DVLOG(1) << "Identified canonical_token: '" << canonical_token << "'";
    217 
    218   vector<CommandLineFlagInfo> all_flags;
    219   set<const CommandLineFlagInfo *> matching_flags;
    220   GetAllFlags(&all_flags);
    221   DVLOG(2) << "Found " << all_flags.size() << " flags overall";
    222 
    223   string longest_common_prefix;
    224   FindMatchingFlags(
    225       all_flags,
    226       options,
    227       canonical_token,
    228       &matching_flags,
    229       &longest_common_prefix);
    230   DVLOG(1) << "Identified " << matching_flags.size() << " matching flags";
    231   DVLOG(1) << "Identified " << longest_common_prefix
    232           << " as longest common prefix.";
    233   if (longest_common_prefix.size() > canonical_token.size()) {
    234     // There's actually a shared common prefix to all matching flags,
    235     // so may as well output that and quit quickly.
    236     DVLOG(1) << "The common prefix '" << longest_common_prefix
    237             << "' was longer than the token '" << canonical_token
    238             << "'.  Returning just this prefix for completion.";
    239     fprintf(stdout, "--%s", longest_common_prefix.c_str());
    240     return;
    241   }
    242   if (matching_flags.empty()) {
    243     VLOG(1) << "There were no matching flags, returning nothing.";
    244     return;
    245   }
    246 
    247   string module;
    248   string package_dir;
    249   TryFindModuleAndPackageDir(all_flags, &module, &package_dir);
    250   DVLOG(1) << "Identified module: '" << module << "'";
    251   DVLOG(1) << "Identified package_dir: '" << package_dir << "'";
    252 
    253   NotableFlags notable_flags;
    254   CategorizeAllMatchingFlags(
    255       matching_flags,
    256       canonical_token,
    257       module,
    258       package_dir,
    259       &notable_flags);
    260   DVLOG(2) << "Categorized matching flags:";
    261   DVLOG(2) << " perfect_match: " << notable_flags.perfect_match_flag.size();
    262   DVLOG(2) << " module: " << notable_flags.module_flags.size();
    263   DVLOG(2) << " package: " << notable_flags.package_flags.size();
    264   DVLOG(2) << " most common: " << notable_flags.most_common_flags.size();
    265   DVLOG(2) << " subpackage: " << notable_flags.subpackage_flags.size();
    266 
    267   vector<string> completions;
    268   FinalizeCompletionOutput(
    269       matching_flags,
    270       &options,
    271       &notable_flags,
    272       &completions);
    273 
    274   if (options.force_no_update)
    275     completions.push_back("~");
    276 
    277   DVLOG(1) << "Finalized with " << completions.size()
    278           << " chosen completions";
    279 
    280   for (vector<string>::const_iterator it = completions.begin();
    281       it != completions.end();
    282       ++it) {
    283     DVLOG(9) << "  Completion entry: '" << *it << "'";
    284     fprintf(stdout, "%s\n", it->c_str());
    285   }
    286 }
    287 
    288 
    289 // 1) Examine search word (and helper method)
    290 static void CanonicalizeCursorWordAndSearchOptions(
    291     const string &cursor_word,
    292     string *canonical_search_token,
    293     CompletionOptions *options) {
    294   *canonical_search_token = cursor_word;
    295   if (canonical_search_token->empty()) return;
    296 
    297   // Get rid of leading quotes and dashes in the search term
    298   if ((*canonical_search_token)[0] == '"')
    299     *canonical_search_token = canonical_search_token->substr(1);
    300   while ((*canonical_search_token)[0] == '-')
    301     *canonical_search_token = canonical_search_token->substr(1);
    302 
    303   options->flag_name_substring_search = false;
    304   options->flag_location_substring_search = false;
    305   options->flag_description_substring_search = false;
    306   options->return_all_matching_flags = false;
    307   options->force_no_update = false;
    308 
    309   // Look for all search options we can deduce now.  Do this by walking
    310   // backwards through the term, looking for up to three '?' and up to
    311   // one '+' as suffixed characters.  Consume them if found, and remove
    312   // them from the canonical search token.
    313   int found_question_marks = 0;
    314   int found_plusses = 0;
    315   while (true) {
    316     if (found_question_marks < 3 &&
    317         RemoveTrailingChar(canonical_search_token, '?')) {
    318       ++found_question_marks;
    319       continue;
    320     }
    321     if (found_plusses < 1 &&
    322         RemoveTrailingChar(canonical_search_token, '+')) {
    323       ++found_plusses;
    324       continue;
    325     }
    326     break;
    327   }
    328 
    329   switch (found_question_marks) {  // all fallthroughs
    330     case 3: options->flag_description_substring_search = true;
    331     case 2: options->flag_location_substring_search = true;
    332     case 1: options->flag_name_substring_search = true;
    333   };
    334 
    335   options->return_all_matching_flags = (found_plusses > 0);
    336 }
    337 
    338 // Returns true if a char was removed
    339 static bool RemoveTrailingChar(string *str, char c) {
    340   if (str->empty()) return false;
    341   if ((*str)[str->size() - 1] == c) {
    342     *str = str->substr(0, str->size() - 1);
    343     return true;
    344   }
    345   return false;
    346 }
    347 
    348 
    349 // 2) Find all matches (and helper methods)
    350 static void FindMatchingFlags(
    351     const vector<CommandLineFlagInfo> &all_flags,
    352     const CompletionOptions &options,
    353     const string &match_token,
    354     set<const CommandLineFlagInfo *> *all_matches,
    355     string *longest_common_prefix) {
    356   all_matches->clear();
    357   bool first_match = true;
    358   for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin();
    359       it != all_flags.end();
    360       ++it) {
    361     if (DoesSingleFlagMatch(*it, options, match_token)) {
    362       all_matches->insert(&*it);
    363       if (first_match) {
    364         first_match = false;
    365         *longest_common_prefix = it->name;
    366       } else {
    367         if (longest_common_prefix->empty() || it->name.empty()) {
    368           longest_common_prefix->clear();
    369           continue;
    370         }
    371         string::size_type pos = 0;
    372         while (pos < longest_common_prefix->size() &&
    373             pos < it->name.size() &&
    374             (*longest_common_prefix)[pos] == it->name[pos])
    375           ++pos;
    376         longest_common_prefix->erase(pos);
    377       }
    378     }
    379   }
    380 }
    381 
    382 // Given the set of all flags, the parsed match options, and the
    383 // canonical search token, produce the set of all candidate matching
    384 // flags for subsequent analysis or filtering.
    385 static bool DoesSingleFlagMatch(
    386     const CommandLineFlagInfo &flag,
    387     const CompletionOptions &options,
    388     const string &match_token) {
    389   // Is there a prefix match?
    390   string::size_type pos = flag.name.find(match_token);
    391   if (pos == 0) return true;
    392 
    393   // Is there a substring match if we want it?
    394   if (options.flag_name_substring_search &&
    395       pos != string::npos)
    396     return true;
    397 
    398   // Is there a location match if we want it?
    399   if (options.flag_location_substring_search &&
    400       flag.filename.find(match_token) != string::npos)
    401     return true;
    402 
    403   // TODO(user): All searches should probably be case-insensitive
    404   // (especially this one...)
    405   if (options.flag_description_substring_search &&
    406       flag.description.find(match_token) != string::npos)
    407     return true;
    408 
    409   return false;
    410 }
    411 
    412 // 3) Categorize matches (and helper method)
    413 
    414 // Given a set of matching flags, categorize them by
    415 // likely relevence to this specific binary
    416 static void CategorizeAllMatchingFlags(
    417     const set<const CommandLineFlagInfo *> &all_matches,
    418     const string &search_token,
    419     const string &module,  // empty if we couldn't find any
    420     const string &package_dir,  // empty if we couldn't find any
    421     NotableFlags *notable_flags) {
    422   notable_flags->perfect_match_flag.clear();
    423   notable_flags->module_flags.clear();
    424   notable_flags->package_flags.clear();
    425   notable_flags->most_common_flags.clear();
    426   notable_flags->subpackage_flags.clear();
    427 
    428   for (set<const CommandLineFlagInfo *>::const_iterator it =
    429         all_matches.begin();
    430       it != all_matches.end();
    431       ++it) {
    432     DVLOG(2) << "Examining match '" << (*it)->name << "'";
    433     DVLOG(7) << "  filename: '" << (*it)->filename << "'";
    434     string::size_type pos = string::npos;
    435     if (!package_dir.empty())
    436       pos = (*it)->filename.find(package_dir);
    437     string::size_type slash = string::npos;
    438     if (pos != string::npos)  // candidate for package or subpackage match
    439       slash = (*it)->filename.find(
    440           PATH_SEPARATOR,
    441           pos + package_dir.size() + 1);
    442 
    443     if ((*it)->name == search_token) {
    444       // Exact match on some flag's name
    445       notable_flags->perfect_match_flag.insert(*it);
    446       DVLOG(3) << "Result: perfect match";
    447     } else if (!module.empty() && (*it)->filename == module) {
    448       // Exact match on module filename
    449       notable_flags->module_flags.insert(*it);
    450       DVLOG(3) << "Result: module match";
    451     } else if (!package_dir.empty() &&
    452         pos != string::npos && slash == string::npos) {
    453       // In the package, since there was no slash after the package portion
    454       notable_flags->package_flags.insert(*it);
    455       DVLOG(3) << "Result: package match";
    456     } else if (false) {
    457       // In the list of the XXX most commonly supplied flags overall
    458       // TODO(user): Compile this list.
    459       DVLOG(3) << "Result: most-common match";
    460     } else if (!package_dir.empty() &&
    461         pos != string::npos && slash != string::npos) {
    462       // In a subdirectory of the package
    463       notable_flags->subpackage_flags.insert(*it);
    464       DVLOG(3) << "Result: subpackage match";
    465     }
    466 
    467     DVLOG(3) << "Result: not special match";
    468   }
    469 }
    470 
    471 static void PushNameWithSuffix(vector<string>* suffixes, const char* suffix) {
    472   suffixes->push_back(
    473       StringPrintf("/%s%s", ProgramInvocationShortName(), suffix));
    474 }
    475 
    476 static void TryFindModuleAndPackageDir(
    477     const vector<CommandLineFlagInfo> &all_flags,
    478     string *module,
    479     string *package_dir) {
    480   module->clear();
    481   package_dir->clear();
    482 
    483   vector<string> suffixes;
    484   // TODO(user): There's some inherant ambiguity here - multiple directories
    485   // could share the same trailing folder and file structure (and even worse,
    486   // same file names), causing us to be unsure as to which of the two is the
    487   // actual package for this binary.  In this case, we'll arbitrarily choose.
    488   PushNameWithSuffix(&suffixes, ".");
    489   PushNameWithSuffix(&suffixes, "-main.");
    490   PushNameWithSuffix(&suffixes, "_main.");
    491   // These four are new but probably merited?
    492   PushNameWithSuffix(&suffixes, "-test.");
    493   PushNameWithSuffix(&suffixes, "_test.");
    494   PushNameWithSuffix(&suffixes, "-unittest.");
    495   PushNameWithSuffix(&suffixes, "_unittest.");
    496 
    497   for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin();
    498       it != all_flags.end();
    499       ++it) {
    500     for (vector<string>::const_iterator suffix = suffixes.begin();
    501         suffix != suffixes.end();
    502         ++suffix) {
    503       // TODO(user): Make sure the match is near the end of the string
    504       if (it->filename.find(*suffix) != string::npos) {
    505         *module = it->filename;
    506         string::size_type sep = it->filename.rfind(PATH_SEPARATOR);
    507         *package_dir = it->filename.substr(0, (sep == string::npos) ? 0 : sep);
    508         return;
    509       }
    510     }
    511   }
    512 }
    513 
    514 // Can't specialize template type on a locally defined type.  Silly C++...
    515 struct DisplayInfoGroup {
    516   const char* header;
    517   const char* footer;
    518   set<const CommandLineFlagInfo *> *group;
    519 
    520   int SizeInLines() const {
    521     int size_in_lines = static_cast<int>(group->size()) + 1;
    522     if (strlen(header) > 0) {
    523       size_in_lines++;
    524     }
    525     if (strlen(footer) > 0) {
    526       size_in_lines++;
    527     }
    528     return size_in_lines;
    529   }
    530 };
    531 
    532 // 4) Finalize and trim output flag set
    533 static void FinalizeCompletionOutput(
    534     const set<const CommandLineFlagInfo *> &matching_flags,
    535     CompletionOptions *options,
    536     NotableFlags *notable_flags,
    537     vector<string> *completions) {
    538 
    539   // We want to output lines in groups.  Each group needs to be indented
    540   // the same to keep its lines together.  Unless otherwise required,
    541   // only 99 lines should be output to prevent bash from harassing the
    542   // user.
    543 
    544   // First, figure out which output groups we'll actually use.  For each
    545   // nonempty group, there will be ~3 lines of header & footer, plus all
    546   // output lines themselves.
    547   int max_desired_lines =  // "999999 flags should be enough for anyone.  -dave"
    548     (options->return_all_matching_flags ? 999999 : 98);
    549   int lines_so_far = 0;
    550 
    551   vector<DisplayInfoGroup> output_groups;
    552   bool perfect_match_found = false;
    553   if (!notable_flags->perfect_match_flag.empty()) {
    554     perfect_match_found = true;
    555     DisplayInfoGroup group =
    556         { "",
    557           "==========",
    558           &notable_flags->perfect_match_flag };
    559     lines_so_far += group.SizeInLines();
    560     output_groups.push_back(group);
    561   }
    562   if (lines_so_far < max_desired_lines &&
    563       !notable_flags->module_flags.empty()) {
    564     DisplayInfoGroup group = {
    565         "-* Matching module flags *-",
    566         "===========================",
    567         &notable_flags->module_flags };
    568     lines_so_far += group.SizeInLines();
    569     output_groups.push_back(group);
    570   }
    571   if (lines_so_far < max_desired_lines &&
    572       !notable_flags->package_flags.empty()) {
    573     DisplayInfoGroup group = {
    574         "-* Matching package flags *-",
    575         "============================",
    576         &notable_flags->package_flags };
    577     lines_so_far += group.SizeInLines();
    578     output_groups.push_back(group);
    579   }
    580   if (lines_so_far < max_desired_lines &&
    581       !notable_flags->most_common_flags.empty()) {
    582     DisplayInfoGroup group = {
    583         "-* Commonly used flags *-",
    584         "=========================",
    585         &notable_flags->most_common_flags };
    586     lines_so_far += group.SizeInLines();
    587     output_groups.push_back(group);
    588   }
    589   if (lines_so_far < max_desired_lines &&
    590       !notable_flags->subpackage_flags.empty()) {
    591     DisplayInfoGroup group = {
    592         "-* Matching sub-package flags *-",
    593         "================================",
    594         &notable_flags->subpackage_flags };
    595     lines_so_far += group.SizeInLines();
    596     output_groups.push_back(group);
    597   }
    598 
    599   set<const CommandLineFlagInfo *> obscure_flags;  // flags not notable
    600   if (lines_so_far < max_desired_lines) {
    601     RetrieveUnusedFlags(matching_flags, *notable_flags, &obscure_flags);
    602     if (!obscure_flags.empty()) {
    603       DisplayInfoGroup group = {
    604           "-* Other flags *-",
    605           "",
    606           &obscure_flags };
    607       lines_so_far += group.SizeInLines();
    608       output_groups.push_back(group);
    609     }
    610   }
    611 
    612   // Second, go through each of the chosen output groups and output
    613   // as many of those flags as we can, while remaining below our limit
    614   int remaining_lines = max_desired_lines;
    615   size_t completions_output = 0;
    616   int indent = static_cast<int>(output_groups.size()) - 1;
    617   for (vector<DisplayInfoGroup>::const_iterator it =
    618         output_groups.begin();
    619       it != output_groups.end();
    620       ++it, --indent) {
    621     OutputSingleGroupWithLimit(
    622         *it->group,  // group
    623         string(indent, ' '),  // line indentation
    624         string(it->header),  // header
    625         string(it->footer),  // footer
    626         perfect_match_found,  // long format
    627         &remaining_lines,  // line limit - reduces this by number printed
    628         &completions_output,  // completions (not lines) added
    629         completions);  // produced completions
    630     perfect_match_found = false;
    631   }
    632 
    633   if (completions_output != matching_flags.size()) {
    634     options->force_no_update = false;
    635     completions->push_back("~ (Remaining flags hidden) ~");
    636   } else {
    637     options->force_no_update = true;
    638   }
    639 }
    640 
    641 static void RetrieveUnusedFlags(
    642     const set<const CommandLineFlagInfo *> &matching_flags,
    643     const NotableFlags &notable_flags,
    644     set<const CommandLineFlagInfo *> *unused_flags) {
    645   // Remove from 'matching_flags' set all members of the sets of
    646   // flags we've already printed (specifically, those in notable_flags)
    647   for (set<const CommandLineFlagInfo *>::const_iterator it =
    648         matching_flags.begin();
    649       it != matching_flags.end();
    650       ++it) {
    651     if (notable_flags.perfect_match_flag.count(*it) ||
    652         notable_flags.module_flags.count(*it) ||
    653         notable_flags.package_flags.count(*it) ||
    654         notable_flags.most_common_flags.count(*it) ||
    655         notable_flags.subpackage_flags.count(*it))
    656       continue;
    657     unused_flags->insert(*it);
    658   }
    659 }
    660 
    661 // 5) Output matches (and helper methods)
    662 
    663 static void OutputSingleGroupWithLimit(
    664     const set<const CommandLineFlagInfo *> &group,
    665     const string &line_indentation,
    666     const string &header,
    667     const string &footer,
    668     bool long_output_format,
    669     int *remaining_line_limit,
    670     size_t *completion_elements_output,
    671     vector<string> *completions) {
    672   if (group.empty()) return;
    673   if (!header.empty()) {
    674     if (*remaining_line_limit < 2) return;
    675     *remaining_line_limit -= 2;
    676     completions->push_back(line_indentation + header);
    677     completions->push_back(line_indentation + string(header.size(), '-'));
    678   }
    679   for (set<const CommandLineFlagInfo *>::const_iterator it = group.begin();
    680       it != group.end() && *remaining_line_limit > 0;
    681       ++it) {
    682     --*remaining_line_limit;
    683     ++*completion_elements_output;
    684     completions->push_back(
    685         (long_output_format
    686           ? GetLongFlagLine(line_indentation, **it)
    687           : GetShortFlagLine(line_indentation, **it)));
    688   }
    689   if (!footer.empty()) {
    690     if (*remaining_line_limit < 1) return;
    691     --*remaining_line_limit;
    692     completions->push_back(line_indentation + footer);
    693   }
    694 }
    695 
    696 static string GetShortFlagLine(
    697     const string &line_indentation,
    698     const CommandLineFlagInfo &info) {
    699   string prefix;
    700   bool is_string = (info.type == "string");
    701   SStringPrintf(&prefix, "%s--%s [%s%s%s] ",
    702                 line_indentation.c_str(),
    703                 info.name.c_str(),
    704                 (is_string ? "'" : ""),
    705                 info.default_value.c_str(),
    706                 (is_string ? "'" : ""));
    707   int remainder =
    708       FLAGS_tab_completion_columns - static_cast<int>(prefix.size());
    709   string suffix;
    710   if (remainder > 0)
    711     suffix =
    712         (static_cast<int>(info.description.size()) > remainder ?
    713          (info.description.substr(0, remainder - 3) + "...").c_str() :
    714          info.description.c_str());
    715   return prefix + suffix;
    716 }
    717 
    718 static string GetLongFlagLine(
    719     const string &line_indentation,
    720     const CommandLineFlagInfo &info) {
    721 
    722   string output = DescribeOneFlag(info);
    723 
    724   // Replace '-' with '--', and remove trailing newline before appending
    725   // the module definition location.
    726   string old_flagname = "-" + info.name;
    727   output.replace(
    728       output.find(old_flagname),
    729       old_flagname.size(),
    730       "-" + old_flagname);
    731   // Stick a newline and indentation in front of the type and default
    732   // portions of DescribeOneFlag()s description
    733   static const char kNewlineWithIndent[] = "\n    ";
    734   output.replace(output.find(" type:"), 1, string(kNewlineWithIndent));
    735   output.replace(output.find(" default:"), 1, string(kNewlineWithIndent));
    736   output = StringPrintf("%s Details for '--%s':\n"
    737                         "%s    defined: %s",
    738                         line_indentation.c_str(),
    739                         info.name.c_str(),
    740                         output.c_str(),
    741                         info.filename.c_str());
    742 
    743   // Eliminate any doubled newlines that crept in.  Specifically, if
    744   // DescribeOneFlag() decided to break the line just before "type"
    745   // or "default", we don't want to introduce an extra blank line
    746   static const string line_of_spaces(FLAGS_tab_completion_columns, ' ');
    747   static const char kDoubledNewlines[] = "\n     \n";
    748   for (string::size_type newlines = output.find(kDoubledNewlines);
    749       newlines != string::npos;
    750       newlines = output.find(kDoubledNewlines))
    751     // Replace each 'doubled newline' with a single newline
    752     output.replace(newlines, sizeof(kDoubledNewlines) - 1, string("\n"));
    753 
    754   for (string::size_type newline = output.find('\n');
    755       newline != string::npos;
    756       newline = output.find('\n')) {
    757     int newline_pos = static_cast<int>(newline) % FLAGS_tab_completion_columns;
    758     int missing_spaces = FLAGS_tab_completion_columns - newline_pos;
    759     output.replace(newline, 1, line_of_spaces, 1, missing_spaces);
    760   }
    761   return output;
    762 }
    763 }  // anonymous
    764 
    765 void HandleCommandLineCompletions(void) {
    766   if (FLAGS_tab_completion_word.empty()) return;
    767   PrintFlagCompletionInfo();
    768   gflags_exitfunc(0);
    769 }
    770 
    771 
    772 } // namespace GFLAGS_NAMESPACE
    773