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 // Author: Dave Nicponski
     32 //
     33 // Bash-style command line flag completion for C++ binaries
     34 //
     35 // This module implements bash-style completions.  It achieves this
     36 // goal in the following broad chunks:
     37 //
     38 //  1) Take a to-be-completed word, and examine it for search hints
     39 //  2) Identify all potentially matching flags
     40 //     2a) If there are no matching flags, do nothing.
     41 //     2b) If all matching flags share a common prefix longer than the
     42 //         completion word, output just that matching prefix
     43 //  3) Categorize those flags to produce a rough ordering of relevence.
     44 //  4) Potentially trim the set of flags returned to a smaller number
     45 //     that bash is happier with
     46 //  5) Output the matching flags in groups ordered by relevence.
     47 //     5a) Force bash to place most-relevent groups at the top of the list
     48 //     5b) Trim most flag's descriptions to fit on a single terminal line
     49 
     50 
     51 #include "config.h"
     52 #include <stdio.h>
     53 #include <stdlib.h>
     54 #include <string.h>   // for strlen
     55 
     56 #include <set>
     57 #include <string>
     58 #include <utility>
     59 #include <vector>
     60 
     61 #include <gflags/gflags.h>
     62 
     63 #ifndef PATH_SEPARATOR
     64 #define PATH_SEPARATOR  '/'
     65 #endif
     66 
     67 DEFINE_string(tab_completion_word, "",
     68               "If non-empty, HandleCommandLineCompletions() will hijack the "
     69               "process and attempt to do bash-style command line flag "
     70               "completion on this value.");
     71 DEFINE_int32(tab_completion_columns, 80,
     72              "Number of columns to use in output for tab completion");
     73 
     74 _START_GOOGLE_NAMESPACE_
     75 
     76 namespace {
     77 
     78 using std::set;
     79 using std::string;
     80 using std::vector;
     81 
     82 // Function prototypes and Type forward declarations.  Code may be
     83 // more easily understood if it is roughly ordered according to
     84 // control flow, rather than by C's "declare before use" ordering
     85 struct CompletionOptions;
     86 struct NotableFlags;
     87 
     88 // The entry point if flag completion is to be used.
     89 static void PrintFlagCompletionInfo(void);
     90 
     91 
     92 // 1) Examine search word
     93 static void CanonicalizeCursorWordAndSearchOptions(
     94     const string &cursor_word,
     95     string *canonical_search_token,
     96     CompletionOptions *options);
     97 
     98 static bool RemoveTrailingChar(string *str, char c);
     99 
    100 
    101 // 2) Find all matches
    102 static void FindMatchingFlags(
    103     const vector<CommandLineFlagInfo> &all_flags,
    104     const CompletionOptions &options,
    105     const string &match_token,
    106     set<const CommandLineFlagInfo *> *all_matches,
    107     string *longest_common_prefix);
    108 
    109 static bool DoesSingleFlagMatch(
    110     const CommandLineFlagInfo &flag,
    111     const CompletionOptions &options,
    112     const string &match_token);
    113 
    114 
    115 // 3) Categorize matches
    116 static void CategorizeAllMatchingFlags(
    117     const set<const CommandLineFlagInfo *> &all_matches,
    118     const string &search_token,
    119     const string &module,
    120     const string &package_dir,
    121     NotableFlags *notable_flags);
    122 
    123 static void TryFindModuleAndPackageDir(
    124     const vector<CommandLineFlagInfo> all_flags,
    125     string *module,
    126     string *package_dir);
    127 
    128 
    129 // 4) Decide which flags to use
    130 static void FinalizeCompletionOutput(
    131     const set<const CommandLineFlagInfo *> &matching_flags,
    132     CompletionOptions *options,
    133     NotableFlags *notable_flags,
    134     vector<string> *completions);
    135 
    136 static void RetrieveUnusedFlags(
    137     const set<const CommandLineFlagInfo *> &matching_flags,
    138     const NotableFlags &notable_flags,
    139     set<const CommandLineFlagInfo *> *unused_flags);
    140 
    141 
    142 // 5) Output matches
    143 static void OutputSingleGroupWithLimit(
    144     const set<const CommandLineFlagInfo *> &group,
    145     const string &line_indentation,
    146     const string &header,
    147     const string &footer,
    148     bool long_output_format,
    149     int *remaining_line_limit,
    150     size_t *completion_elements_added,
    151     vector<string> *completions);
    152 
    153 // (helpers for #5)
    154 static string GetShortFlagLine(
    155     const string &line_indentation,
    156     const CommandLineFlagInfo &info);
    157 
    158 static string GetLongFlagLine(
    159     const string &line_indentation,
    160     const CommandLineFlagInfo &info);
    161 
    162 
    163 //
    164 // Useful types
    165 
    166 // Try to deduce the intentions behind this completion attempt.  Return the
    167 // canonical search term in 'canonical_search_token'.  Binary search options
    168 // are returned in the various booleans, which should all have intuitive
    169 // semantics, possibly except:
    170 //  - return_all_matching_flags: Generally, we'll trim the number of
    171 //    returned candidates to some small number, showing those that are
    172 //    most likely to be useful first.  If this is set, however, the user
    173 //    really does want us to return every single flag as an option.
    174 //  - force_no_update: Any time we output lines, all of which share a
    175 //    common prefix, bash will 'helpfully' not even bother to show the
    176 //    output, instead changing the current word to be that common prefix.
    177 //    If it's clear this shouldn't happen, we'll set this boolean
    178 struct CompletionOptions {
    179   bool flag_name_substring_search;
    180   bool flag_location_substring_search;
    181   bool flag_description_substring_search;
    182   bool return_all_matching_flags;
    183   bool force_no_update;
    184 };
    185 
    186 // Notable flags are flags that are special or preferred for some
    187 // reason.  For example, flags that are defined in the binary's module
    188 // are expected to be much more relevent than flags defined in some
    189 // other random location.  These sets are specified roughly in precedence
    190 // order.  Once a flag is placed in one of these 'higher' sets, it won't
    191 // be placed in any of the 'lower' sets.
    192 struct NotableFlags {
    193   typedef set<const CommandLineFlagInfo *> FlagSet;
    194   FlagSet perfect_match_flag;
    195   FlagSet module_flags;       // Found in module file
    196   FlagSet package_flags;      // Found in same directory as module file
    197   FlagSet most_common_flags;  // One of the XXX most commonly supplied flags
    198   FlagSet subpackage_flags;   // Found in subdirectories of package
    199 };
    200 
    201 
    202 //
    203 // Tab completion implementation - entry point
    204 static void PrintFlagCompletionInfo(void) {
    205   string cursor_word = FLAGS_tab_completion_word;
    206   string canonical_token;
    207   CompletionOptions options = { };
    208   CanonicalizeCursorWordAndSearchOptions(
    209       cursor_word,
    210       &canonical_token,
    211       &options);
    212 
    213   //VLOG(1) << "Identified canonical_token: '" << canonical_token << "'";
    214 
    215   vector<CommandLineFlagInfo> all_flags;
    216   set<const CommandLineFlagInfo *> matching_flags;
    217   GetAllFlags(&all_flags);
    218   //VLOG(2) << "Found " << all_flags.size() << " flags overall";
    219 
    220   string longest_common_prefix;
    221   FindMatchingFlags(
    222       all_flags,
    223       options,
    224       canonical_token,
    225       &matching_flags,
    226       &longest_common_prefix);
    227   //VLOG(1) << "Identified " << matching_flags.size() << " matching flags";
    228   //VLOG(1) << "Identified " << longest_common_prefix
    229   //        << " as longest common prefix.";
    230   if (longest_common_prefix.size() > canonical_token.size()) {
    231     // There's actually a shared common prefix to all matching flags,
    232     // so may as well output that and quit quickly.
    233     //VLOG(1) << "The common prefix '" << longest_common_prefix
    234     //        << "' was longer than the token '" << canonical_token
    235     //        << "'.  Returning just this prefix for completion.";
    236     fprintf(stdout, "--%s", longest_common_prefix.c_str());
    237     return;
    238   }
    239   if (matching_flags.empty()) {
    240     //VLOG(1) << "There were no matching flags, returning nothing.";
    241     return;
    242   }
    243 
    244   string module;
    245   string package_dir;
    246   TryFindModuleAndPackageDir(all_flags, &module, &package_dir);
    247   //VLOG(1) << "Identified module: '" << module << "'";
    248   //VLOG(1) << "Identified package_dir: '" << package_dir << "'";
    249 
    250   NotableFlags notable_flags;
    251   CategorizeAllMatchingFlags(
    252       matching_flags,
    253       canonical_token,
    254       module,
    255       package_dir,
    256       &notable_flags);
    257   //VLOG(2) << "Categorized matching flags:";
    258   //VLOG(2) << " perfect_match: " << notable_flags.perfect_match_flag.size();
    259   //VLOG(2) << " module: " << notable_flags.module_flags.size();
    260   //VLOG(2) << " package: " << notable_flags.package_flags.size();
    261   //VLOG(2) << " most common: " << notable_flags.most_common_flags.size();
    262   //VLOG(2) << " subpackage: " << notable_flags.subpackage_flags.size();
    263 
    264   vector<string> completions;
    265   FinalizeCompletionOutput(
    266       matching_flags,
    267       &options,
    268       &notable_flags,
    269       &completions);
    270 
    271   if (options.force_no_update)
    272     completions.push_back("~");
    273 
    274   //VLOG(1) << "Finalized with " << completions.size()
    275   //        << " chosen completions";
    276 
    277   for (vector<string>::const_iterator it = completions.begin();
    278       it != completions.end();
    279       ++it) {
    280     //VLOG(9) << "  Completion entry: '" << *it << "'";
    281     fprintf(stdout, "%s\n", it->c_str());
    282   }
    283 }
    284 
    285 
    286 // 1) Examine search word (and helper method)
    287 static void CanonicalizeCursorWordAndSearchOptions(
    288     const string &cursor_word,
    289     string *canonical_search_token,
    290     CompletionOptions *options) {
    291   *canonical_search_token = cursor_word;
    292   if (canonical_search_token->empty()) return;
    293 
    294   // Get rid of leading quotes and dashes in the search term
    295   if ((*canonical_search_token)[0] == '"')
    296     *canonical_search_token = canonical_search_token->substr(1);
    297   while ((*canonical_search_token)[0] == '-')
    298     *canonical_search_token = canonical_search_token->substr(1);
    299 
    300   options->flag_name_substring_search = false;
    301   options->flag_location_substring_search = false;
    302   options->flag_description_substring_search = false;
    303   options->return_all_matching_flags = false;
    304   options->force_no_update = false;
    305 
    306   // Look for all search options we can deduce now.  Do this by walking
    307   // backwards through the term, looking for up to three '?' and up to
    308   // one '+' as suffixed characters.  Consume them if found, and remove
    309   // them from the canonical search token.
    310   int found_question_marks = 0;
    311   int found_plusses = 0;
    312   while (true) {
    313     if (found_question_marks < 3 &&
    314         RemoveTrailingChar(canonical_search_token, '?')) {
    315       ++found_question_marks;
    316       continue;
    317     }
    318     if (found_plusses < 1 &&
    319         RemoveTrailingChar(canonical_search_token, '+')) {
    320       ++found_plusses;
    321       continue;
    322     }
    323     break;
    324   }
    325 
    326   switch (found_question_marks) {  // all fallthroughs
    327     case 3: options->flag_description_substring_search = true;
    328     case 2: options->flag_location_substring_search = true;
    329     case 1: options->flag_name_substring_search = true;
    330   };
    331 
    332   options->return_all_matching_flags = (found_plusses > 0);
    333 }
    334 
    335 // Returns true if a char was removed
    336 static bool RemoveTrailingChar(string *str, char c) {
    337   if (str->empty()) return false;
    338   if ((*str)[str->size() - 1] == c) {
    339     *str = str->substr(0, str->size() - 1);
    340     return true;
    341   }
    342   return false;
    343 }
    344 
    345 
    346 // 2) Find all matches (and helper methods)
    347 static void FindMatchingFlags(
    348     const vector<CommandLineFlagInfo> &all_flags,
    349     const CompletionOptions &options,
    350     const string &match_token,
    351     set<const CommandLineFlagInfo *> *all_matches,
    352     string *longest_common_prefix) {
    353   all_matches->clear();
    354   bool first_match = true;
    355   for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin();
    356       it != all_flags.end();
    357       ++it) {
    358     if (DoesSingleFlagMatch(*it, options, match_token)) {
    359       all_matches->insert(&*it);
    360       if (first_match) {
    361         first_match = false;
    362         *longest_common_prefix = it->name;
    363       } else {
    364         if (longest_common_prefix->empty() || it->name.empty()) {
    365           longest_common_prefix->clear();
    366           continue;
    367         }
    368         string::size_type pos = 0;
    369         while (pos < longest_common_prefix->size() &&
    370             pos < it->name.size() &&
    371             (*longest_common_prefix)[pos] == it->name[pos])
    372           ++pos;
    373         longest_common_prefix->erase(pos);
    374       }
    375     }
    376   }
    377 }
    378 
    379 // Given the set of all flags, the parsed match options, and the
    380 // canonical search token, produce the set of all candidate matching
    381 // flags for subsequent analysis or filtering.
    382 static bool DoesSingleFlagMatch(
    383     const CommandLineFlagInfo &flag,
    384     const CompletionOptions &options,
    385     const string &match_token) {
    386   // Is there a prefix match?
    387   string::size_type pos = flag.name.find(match_token);
    388   if (pos == 0) return true;
    389 
    390   // Is there a substring match if we want it?
    391   if (options.flag_name_substring_search &&
    392       pos != string::npos)
    393     return true;
    394 
    395   // Is there a location match if we want it?
    396   if (options.flag_location_substring_search &&
    397       flag.filename.find(match_token) != string::npos)
    398     return true;
    399 
    400   // TODO(daven): All searches should probably be case-insensitive
    401   // (especially this one...)
    402   if (options.flag_description_substring_search &&
    403       flag.description.find(match_token) != string::npos)
    404     return true;
    405 
    406   return false;
    407 }
    408 
    409 // 3) Categorize matches (and helper method)
    410 
    411 // Given a set of matching flags, categorize them by
    412 // likely relevence to this specific binary
    413 static void CategorizeAllMatchingFlags(
    414     const set<const CommandLineFlagInfo *> &all_matches,
    415     const string &search_token,
    416     const string &module,  // empty if we couldn't find any
    417     const string &package_dir,  // empty if we couldn't find any
    418     NotableFlags *notable_flags) {
    419   notable_flags->perfect_match_flag.clear();
    420   notable_flags->module_flags.clear();
    421   notable_flags->package_flags.clear();
    422   notable_flags->most_common_flags.clear();
    423   notable_flags->subpackage_flags.clear();
    424 
    425   for (set<const CommandLineFlagInfo *>::const_iterator it =
    426         all_matches.begin();
    427       it != all_matches.end();
    428       ++it) {
    429     //VLOG(2) << "Examining match '" << (*it)->name << "'";
    430     //VLOG(7) << "  filename: '" << (*it)->filename << "'";
    431     string::size_type pos = string::npos;
    432     if (!package_dir.empty())
    433       pos = (*it)->filename.find(package_dir);
    434     string::size_type slash = string::npos;
    435     if (pos != string::npos)  // candidate for package or subpackage match
    436       slash = (*it)->filename.find(
    437           PATH_SEPARATOR,
    438           pos + package_dir.size() + 1);
    439 
    440     if ((*it)->name == search_token) {
    441       // Exact match on some flag's name
    442       notable_flags->perfect_match_flag.insert(*it);
    443       //VLOG(3) << "Result: perfect match";
    444     } else if (!module.empty() && (*it)->filename == module) {
    445       // Exact match on module filename
    446       notable_flags->module_flags.insert(*it);
    447       //VLOG(3) << "Result: module match";
    448     } else if (!package_dir.empty() &&
    449         pos != string::npos && slash == string::npos) {
    450       // In the package, since there was no slash after the package portion
    451       notable_flags->package_flags.insert(*it);
    452       //VLOG(3) << "Result: package match";
    453     } else if (false) {
    454       // In the list of the XXX most commonly supplied flags overall
    455       // TODO(daven): Compile this list.
    456       //VLOG(3) << "Result: most-common match";
    457     } else if (!package_dir.empty() &&
    458         pos != string::npos && slash != string::npos) {
    459       // In a subdirectory of the package
    460       notable_flags->subpackage_flags.insert(*it);
    461       //VLOG(3) << "Result: subpackage match";
    462     }
    463 
    464     //VLOG(3) << "Result: not special match";
    465   }
    466 }
    467 
    468 static void PushNameWithSuffix(vector<string>* suffixes, const char* suffix) {
    469   string s("/");
    470   s += ProgramInvocationShortName();
    471   s += suffix;
    472   suffixes->push_back(s);
    473 }
    474 
    475 static void TryFindModuleAndPackageDir(
    476     const vector<CommandLineFlagInfo> all_flags,
    477     string *module,
    478     string *package_dir) {
    479   module->clear();
    480   package_dir->clear();
    481 
    482   vector<string> suffixes;
    483   // TODO(daven): There's some inherant ambiguity here - multiple directories
    484   // could share the same trailing folder and file structure (and even worse,
    485   // same file names), causing us to be unsure as to which of the two is the
    486   // actual package for this binary.  In this case, we'll arbitrarily choose.
    487   PushNameWithSuffix(&suffixes, ".");
    488   PushNameWithSuffix(&suffixes, "-main.");
    489   PushNameWithSuffix(&suffixes, "_main.");
    490   // These four are new but probably merited?
    491   PushNameWithSuffix(&suffixes, "-test.");
    492   PushNameWithSuffix(&suffixes, "_test.");
    493   PushNameWithSuffix(&suffixes, "-unittest.");
    494   PushNameWithSuffix(&suffixes, "_unittest.");
    495 
    496   for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin();
    497       it != all_flags.end();
    498       ++it) {
    499     for (vector<string>::const_iterator suffix = suffixes.begin();
    500         suffix != suffixes.end();
    501         ++suffix) {
    502       // TODO(daven): Make sure the match is near the end of the string
    503       if (it->filename.find(*suffix) != string::npos) {
    504         *module = it->filename;
    505         string::size_type sep = it->filename.rfind(PATH_SEPARATOR);
    506         *package_dir = it->filename.substr(0, (sep == string::npos) ? 0 : sep);
    507         return;
    508       }
    509     }
    510   }
    511 }
    512 
    513 // Can't specialize template type on a locally defined type.  Silly C++...
    514 struct DisplayInfoGroup {
    515   const char* header;
    516   const char* footer;
    517   set<const CommandLineFlagInfo *> *group;
    518 
    519   int SizeInLines() const {
    520     int size_in_lines = static_cast<int>(group->size()) + 1;
    521     if (strlen(header) > 0) {
    522       size_in_lines++;
    523     }
    524     if (strlen(footer) > 0) {
    525       size_in_lines++;
    526     }
    527     return size_in_lines;
    528   }
    529 };
    530 
    531 // 4) Finalize and trim output flag set
    532 static void FinalizeCompletionOutput(
    533     const set<const CommandLineFlagInfo *> &matching_flags,
    534     CompletionOptions *options,
    535     NotableFlags *notable_flags,
    536     vector<string> *completions) {
    537 
    538   // We want to output lines in groups.  Each group needs to be indented
    539   // the same to keep its lines together.  Unless otherwise required,
    540   // only 99 lines should be output to prevent bash from harassing the
    541   // user.
    542 
    543   // First, figure out which output groups we'll actually use.  For each
    544   // nonempty group, there will be ~3 lines of header & footer, plus all
    545   // output lines themselves.
    546   int max_desired_lines =  // "999999 flags should be enough for anyone.  -dave"
    547     (options->return_all_matching_flags ? 999999 : 98);
    548   int lines_so_far = 0;
    549 
    550   vector<DisplayInfoGroup> output_groups;
    551   bool perfect_match_found = false;
    552   if (lines_so_far < max_desired_lines &&
    553       !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     line_indentation + "--" + info.name + " [" +
    701     (info.type == "string" ?
    702        ("'" + info.default_value + "'") :
    703        info.default_value)
    704     + "] ";
    705   int remainder =
    706       FLAGS_tab_completion_columns - static_cast<int>(prefix.size());
    707   string suffix;
    708   if (remainder > 0)
    709     suffix =
    710         (static_cast<int>(info.description.size()) > remainder ?
    711          (info.description.substr(0, remainder - 3) + "...").c_str() :
    712          info.description.c_str());
    713   return prefix + suffix;
    714 }
    715 
    716 static string GetLongFlagLine(
    717     const string &line_indentation,
    718     const CommandLineFlagInfo &info) {
    719 
    720   string output = DescribeOneFlag(info);
    721 
    722   // Replace '-' with '--', and remove trailing newline before appending
    723   // the module definition location.
    724   string old_flagname = "-" + info.name;
    725   output.replace(
    726       output.find(old_flagname),
    727       old_flagname.size(),
    728       "-" + old_flagname);
    729   // Stick a newline and indentation in front of the type and default
    730   // portions of DescribeOneFlag()s description
    731   static const char kNewlineWithIndent[] = "\n    ";
    732   output.replace(output.find(" type:"), 1, string(kNewlineWithIndent));
    733   output.replace(output.find(" default:"), 1, string(kNewlineWithIndent));
    734   output = line_indentation + " Details for '--" + info.name + "':\n" +
    735      output + "    defined: " + info.filename;
    736 
    737   // Eliminate any doubled newlines that crept in.  Specifically, if
    738   // DescribeOneFlag() decided to break the line just before "type"
    739   // or "default", we don't want to introduce an extra blank line
    740   static const string line_of_spaces(FLAGS_tab_completion_columns, ' ');
    741   static const char kDoubledNewlines[] = "\n     \n";
    742   for (string::size_type newlines = output.find(kDoubledNewlines);
    743       newlines != string::npos;
    744       newlines = output.find(kDoubledNewlines))
    745     // Replace each 'doubled newline' with a single newline
    746     output.replace(newlines, sizeof(kDoubledNewlines) - 1, string("\n"));
    747 
    748   for (string::size_type newline = output.find('\n');
    749       newline != string::npos;
    750       newline = output.find('\n')) {
    751     int newline_pos = static_cast<int>(newline) % FLAGS_tab_completion_columns;
    752     int missing_spaces = FLAGS_tab_completion_columns - newline_pos;
    753     output.replace(newline, 1, line_of_spaces, 1, missing_spaces);
    754   }
    755   return output;
    756 }
    757 }  // anonymous
    758 
    759 void HandleCommandLineCompletions(void) {
    760   if (FLAGS_tab_completion_word.empty()) return;
    761   PrintFlagCompletionInfo();
    762   exit(0);
    763 }
    764 
    765 _END_GOOGLE_NAMESPACE_
    766