* PORTING: Add windows (MSVC) support (csilvers)
[platform/upstream/gflags.git] / src / gflags_completions.cc
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 using std::set;
64 using std::string;
65 using std::vector;
66
67 #ifndef PATH_SEPARATOR
68 #define PATH_SEPARATOR  '/'
69 #endif
70
71 DEFINE_string(tab_completion_word, "",
72               "If non-empty, HandleCommandLineCompletions() will hijack the "
73               "process and attempt to do bash-style command line flag "
74               "completion on this value.");
75 DEFINE_int32(tab_completion_columns, 80,
76              "Number of columns to use in output for tab completion");
77
78 _START_GOOGLE_NAMESPACE_
79
80 namespace {
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_