Mon Jul 21 23:01:38 2008 Google Inc. <opensource@google.com>
[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 <stdlib.h>
53
54 #include <set>
55 #include <string>
56 #include <utility>
57 #include <vector>
58
59 #include "google/gflags.h"
60
61 using std::set;
62 using std::string;
63 using std::vector;
64
65 #ifndef PATH_SEPARATOR
66 #define PATH_SEPARATOR  '/'
67 #endif
68
69 DEFINE_string(tab_completion_word, "",
70               "If non-empty, HandleCommandLineCompletions() will hijack the "
71               "process and attempt to do bash-style command line flag "
72               "completion on this value.");
73 DEFINE_int32(tab_completion_columns, 80,
74              "Number of columns to use in output for tab completion");
75
76 _START_GOOGLE_NAMESPACE_
77
78 namespace {
79
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     int *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 };
183
184 // Notable flags are flags that are special or preferred for some
185 // reason.  For example, flags that are defined in the binary's module
186 // are expected to be much more relevent than flags defined in some
187 // other random location.  These sets are specified roughly in precedence
188 // order.  Once a flag is placed in one of these 'higher' sets, it won't
189 // be placed in any of the 'lower' sets.
190 struct NotableFlags {
191   typedef set<const CommandLineFlagInfo *> FlagSet;
192   FlagSet perfect_match_flag;
193   FlagSet module_flags;       // Found in module file
194   FlagSet package_flags;      // Found in same directory as module file
195   FlagSet most_common_flags;  // One of the XXX most commonly supplied flags
196   FlagSet subpackage_flags;   // Found in subdirectories of package
197 };
198
199
200 //
201 // Tab completion implementation - entry point
202 static void PrintFlagCompletionInfo(void) {
203   string cursor_word = FLAGS_tab_completion_word;
204   string canonical_token;
205   CompletionOptions options;
206   CanonicalizeCursorWordAndSearchOptions(
207       cursor_word,
208       &canonical_token,
209       &options);
210
211   //VLOG(1) << "Identified canonical_token: '" << canonical_token << "'";
212
213   vector<CommandLineFlagInfo> all_flags;
214   set<const CommandLineFlagInfo *> matching_flags;
215   GetAllFlags(&all_flags);
216   //VLOG(2) << "Found " << all_flags.size() << " flags overall";
217
218   string longest_common_prefix;
219   FindMatchingFlags(
220       all_flags,
221       options,
222       canonical_token,
223       &matching_flags,
224       &longest_common_prefix);
225   //VLOG(1) << "Identified " << matching_flags.size() << " matching flags";
226   //VLOG(1) << "Identified " << longest_common_prefix
227   //        << " as longest common prefix.";
228   if (longest_common_prefix.size() > canonical_token.size()) {
229     // There's actually a shared common prefix to all matching flags,
230     // so may as well output that and quit quickly.
231     //VLOG(1) << "The common prefix '" << longest_common_prefix
232     //        << "' was longer than the token '" << canonical_token
233     //        << "'.  Returning just this prefix for completion.";
234     fprintf(stdout, "--%s", longest_common_prefix.c_str());
235     return;
236   }
237   if (matching_flags.empty()) {
238     //VLOG(1) << "There were no matching flags, returning nothing.";
239     return;
240   }
241
242   string module;
243   string package_dir;
244   TryFindModuleAndPackageDir(all_flags, &module, &package_dir);
245   //VLOG(1) << "Identified module: '" << module << "'";
246   //VLOG(1) << "Identified package_dir: '" << package_dir << "'";
247
248   NotableFlags notable_flags;
249   CategorizeAllMatchingFlags(
250       matching_flags,
251       canonical_token,
252       module,
253       package_dir,
254       &notable_flags);
255   //VLOG(2) << "Categorized matching flags:";
256   //VLOG(2) << " perfect_match: " << notable_flags.perfect_match_flag.size();
257   //VLOG(2) << " module: " << notable_flags.module_flags.size();
258   //VLOG(2) << " package: " << notable_flags.package_flags.size();
259   //VLOG(2) << " most common: " << notable_flags.most_common_flags.size();
260   //VLOG(2) << " subpackage: " << notable_flags.subpackage_flags.size();
261
262   vector<string> completions;
263   FinalizeCompletionOutput(
264       matching_flags,
265       &options,
266       &notable_flags,
267       &completions);
268
269   if (options.force_no_update)
270     completions.push_back("~");
271
272   //VLOG(1) << "Finalized with " << completions.size()
273   //        << " chosen completions";
274
275   for (vector<string>::const_iterator it = completions.begin();
276       it != completions.end();
277       ++it) {
278     //VLOG(9) << "  Completion entry: '" << *it << "'";
279     fprintf(stdout, "%s\n", it->c_str());
280   }
281 }
282
283
284 // 1) Examine search word (and helper method)
285 static void CanonicalizeCursorWordAndSearchOptions(
286     const string &cursor_word,
287     string *canonical_search_token,
288     CompletionOptions *options) {
289   *canonical_search_token = cursor_word;
290   if (canonical_search_token->empty()) return;
291
292   // Get rid of leading quotes and dashes in the search term
293   if ((*canonical_search_token)[0] == '"')
294     *canonical_search_token = canonical_search_token->substr(1);
295   while ((*canonical_search_token)[0] == '-')
296     *canonical_search_token = canonical_search_token->substr(1);
297
298   options->flag_name_substring_search = false;
299   options->flag_location_substring_search = false;
300   options->flag_description_substring_search = false;
301   options->return_all_matching_flags = false;
302   options->force_no_update = false;
303
304   // Look for all search options we can deduce now.  Do this by walking
305   // backwards through the term, looking for up to three '?' and up to
306   // one '+' as suffixed characters.  Consume them if found, and remove
307   // them from the canonical search token.
308   int found_question_marks = 0;
309   int found_plusses = 0;
310   while (true) {
311     if (found_question_marks < 3 &&
312         RemoveTrailingChar(canonical_search_token, '?')) {
313       ++found_question_marks;
314       continue;
315     }
316     if (found_plusses < 1 &&
317         RemoveTrailingChar(canonical_search_token, '+')) {
318       ++found_plusses;
319       continue;
320     }
321     break;
322   }
323
324   switch (found_question_marks) {  // all fallthroughs
325     case 3: options->flag_description_substring_search = true;
326     case 2: options->flag_location_substring_search = true;
327     case 1: options->flag_name_substring_search = true;
328   };
329
330   options->return_all_matching_flags = (found_plusses > 0);
331 }
332
333 // Returns true if a char was removed
334 static bool RemoveTrailingChar(string *str, char c) {
335   if (str->empty()) return false;
336   if ((*str)[str->size() - 1] == c) {
337     *str = str->substr(0, str->size() - 1);
338     return true;
339   }
340   return false;
341 }
342
343
344 // 2) Find all matches (and helper methods)
345 static void FindMatchingFlags(
346     const vector<CommandLineFlagInfo> &all_flags,
347     const CompletionOptions &options,
348     const string &match_token,
349     set<const CommandLineFlagInfo *> *all_matches,
350     string *longest_common_prefix) {
351   all_matches->clear();
352   bool first_match = true;
353   for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin();
354       it != all_flags.end();
355       ++it) {
356     if (DoesSingleFlagMatch(*it, options, match_token)) {
357       all_matches->insert(&*it);
358       if (first_match) {
359         first_match = false;
360         *longest_common_prefix = it->name;
361       } else {
362         if (longest_common_prefix->empty() || it->name.empty()) {
363           longest_common_prefix->clear();
364           continue;
365         }
366         string::size_type pos = 0;
367         while (pos < longest_common_prefix->size() &&
368             pos < it->name.size() &&
369             (*longest_common_prefix)[pos] == it->name[pos])
370           ++pos;
371         longest_common_prefix->erase(pos);
372       }
373     }
374   }
375 }
376
377 // Given the set of all flags, the parsed match options, and the
378 // canonical search token, produce the set of all candidate matching
379 // flags for subsequent analysis or filtering.
380 static bool DoesSingleFlagMatch(
381     const CommandLineFlagInfo &flag,
382     const CompletionOptions &options,
383     const string &match_token) {
384   // Is there a prefix match?
385   string::size_type pos = flag.name.find(match_token);
386   if (pos == 0) return true;
387
388   // Is there a substring match if we want it?
389   if (options.flag_name_substring_search &&
390       pos != string::npos)
391     return true;
392
393   // Is there a location match if we want it?
394   if (options.flag_location_substring_search &&
395       flag.filename.find(match_token) != string::npos)
396     return true;
397
398   // TODO(daven): All searches should probably be case-insensitive
399   // (especially this one...)
400   if (options.flag_description_substring_search &&
401       flag.description.find(match_token) != string::npos)
402     return true;
403
404   return false;
405 }
406
407 // 3) Categorize matches (and helper method)
408
409 // Given a set of matching flags, categorize them by
410 // likely relevence to this specific binary
411 static void CategorizeAllMatchingFlags(
412     const set<const CommandLineFlagInfo *> &all_matches,
413     const string &search_token,
414     const string &module,  // empty if we couldn't find any
415     const string &package_dir,  // empty if we couldn't find any
416     NotableFlags *notable_flags) {
417   notable_flags->perfect_match_flag.clear();
418   notable_flags->module_flags.clear();
419   notable_flags->package_flags.clear();
420   notable_flags->most_common_flags.clear();
421   notable_flags->subpackage_flags.clear();
422
423   for (set<const CommandLineFlagInfo *>::const_iterator it =
424         all_matches.begin();
425       it != all_matches.end();
426       ++it) {
427     //VLOG(2) << "Examinging match '" << (*it)->name << "'";
428     //VLOG(7) << "  filename: '" << (*it)->filename << "'";
429     string::size_type pos = string::npos;
430     if (!package_dir.empty())
431       pos = (*it)->filename.find(package_dir);
432     string::size_type slash = string::npos;
433     if (pos != string::npos)  // candidate for package or subpackage match
434       slash = (*it)->filename.find(
435           PATH_SEPARATOR,
436           pos + package_dir.size() + 1);
437
438     if ((*it)->name == search_token) {
439       // Exact match on some flag's name
440       notable_flags->perfect_match_flag.insert(*it);
441       //VLOG(3) << "Result: perfect match";
442     } else if (!module.empty() && (*it)->filename == module) {
443       // Exact match on module filename
444       notable_flags->module_flags.insert(*it);
445       //VLOG(3) << "Result: module match";
446     } else if (!package_dir.empty() &&
447         pos != string::npos && slash == string::npos) {
448       // In the package, since there was no slash after the package portion
449       notable_flags->package_flags.insert(*it);
450       //VLOG(3) << "Result: package match";
451     } else if (false) {
452       // In the list of the XXX most commonly supplied flags overall
453       // TODO(daven): Compile this list.
454       //VLOG(3) << "Result: most-common match";
455     } else if (!package_dir.empty() &&
456         pos != string::npos && slash != string::npos) {
457       // In a subdirectory of the package
458       notable_flags->subpackage_flags.insert(*it);
459       //VLOG(3) << "Result: subpackage match";
460     }
461
462     //VLOG(3) << "Result: not special match";
463   }
464 }
465
466 static void TryFindModuleAndPackageDir(
467     const vector<CommandLineFlagInfo> all_flags,
468     string *module,
469     string *package_dir) {
470   module->clear();
471   package_dir->clear();
472
473   vector<string> suffixes;
474   // TODO(daven): There's some inherant ambiguity here - multiple directories
475   // could share the same trailing folder and file structure (and even worse,
476   // same file names), causing us to be unsure as to which of the two is the
477   // actual package for this binary.  In this case, we'll arbitrarily choose.
478   string progname(ProgramInvocationShortName());
479   suffixes.push_back("/" + progname + ".");
480   suffixes.push_back("/" + progname + "-main.");
481   suffixes.push_back("/" + progname + "_main.");
482   // These four are new but probably merited?
483   suffixes.push_back("/" + progname + "_test.");
484   suffixes.push_back("/" + progname + "-test.");
485   suffixes.push_back("/" + progname + "_unittest.");
486   suffixes.push_back("/" + progname + "-unittest.");
487
488   for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin();
489       it != all_flags.end();
490       ++it) {
491     for (vector<string>::const_iterator suffix = suffixes.begin();
492         suffix != suffixes.end();
493         ++suffix) {
494       // TODO(daven): Make sure the match is near the end of the string
495       if (it->filename.find(*suffix) != string::npos) {
496         *module = it->filename;
497         string::size_type sep = it->filename.rfind(PATH_SEPARATOR);
498         *package_dir = it->filename.substr(0, (sep == string::npos) ? 0 : sep);
499         return;
500       }
501     }
502   }
503 }
504
505 // Can't specialize template type on a locally defined type.  Silly C++...
506 struct DisplayInfoGroup {
507   string header;
508   string footer;
509   set<const CommandLineFlagInfo *> *group;
510 };
511
512 // 4) Finalize and trim output flag set
513 static void FinalizeCompletionOutput(
514     const set<const CommandLineFlagInfo *> &matching_flags,
515     CompletionOptions *options,
516     NotableFlags *notable_flags,
517     vector<string> *completions) {
518
519   // We want to output lines in groups.  Each group needs to be indented
520   // the same to keep its lines together.  Unless otherwise required,
521   // only 99 lines should be output to prevent bash from harassing the
522   // user.
523
524   // First, figure out which output groups we'll actually use.  For each
525   // nonempty group, there will be ~3 lines of header & footer, plus all
526   // output lines themselves.
527   int max_desired_lines =  // "999999 flags should be enough for anyone.  -dave"
528     (options->return_all_matching_flags ? 999999 : 98);
529   int lines_so_far = 0;
530
531   vector<DisplayInfoGroup> output_groups;
532   bool perfect_match_found = false;
533   if (lines_so_far < max_desired_lines &&
534       !notable_flags->perfect_match_flag.empty()) {
535     perfect_match_found = true;
536     lines_so_far += notable_flags->perfect_match_flag.size() + 2;  // no header
537     DisplayInfoGroup group =
538         { "", "==========", &notable_flags->perfect_match_flag };
539     output_groups.push_back(group);
540   }
541   if (lines_so_far < max_desired_lines &&
542       !notable_flags->module_flags.empty()) {
543     lines_so_far += notable_flags->module_flags.size() + 3;
544     DisplayInfoGroup group = {
545         "-* Matching module flags *-",
546         "===========================",
547         &notable_flags->module_flags };
548     output_groups.push_back(group);
549   }
550   if (lines_so_far < max_desired_lines &&
551       !notable_flags->package_flags.empty()) {
552     lines_so_far += notable_flags->package_flags.size() + 3;
553     DisplayInfoGroup group = {
554         "-* Matching package flags *-",
555         "============================",
556         &notable_flags->package_flags };
557     output_groups.push_back(group);
558   }
559   if (lines_so_far < max_desired_lines &&
560       !notable_flags->most_common_flags.empty()) {
561     lines_so_far += notable_flags->most_common_flags.size() + 3;
562     DisplayInfoGroup group = {
563         "-* Commonly used flags *-",
564         "=========================",
565         &notable_flags->most_common_flags };
566     output_groups.push_back(group);
567   }
568   if (lines_so_far < max_desired_lines &&
569       !notable_flags->subpackage_flags.empty()) {
570     lines_so_far += notable_flags->subpackage_flags.size() + 3;
571     DisplayInfoGroup group = {
572         "-* Matching sub-package flags *-",
573         "================================",
574         &notable_flags->subpackage_flags };
575     output_groups.push_back(group);
576   }
577
578   set<const CommandLineFlagInfo *> obscure_flags;  // flags not notable
579   if (lines_so_far < max_desired_lines) {
580     RetrieveUnusedFlags(matching_flags, *notable_flags, &obscure_flags);
581     if (!obscure_flags.empty()) {
582       lines_so_far += obscure_flags.size() + 2;  // no footer
583       DisplayInfoGroup group = {
584           "-* Other flags *-",
585           "",
586           &obscure_flags };
587       output_groups.push_back(group);
588     }
589   }
590
591   // Second, go through each of the chosen output groups and output
592   // as many of those flags as we can, while remaining below our limit
593   int remaining_lines = max_desired_lines;
594   int completions_output = 0;
595   int indent = output_groups.size() - 1;
596   for (vector<DisplayInfoGroup>::const_iterator it =
597         output_groups.begin();
598       it != output_groups.end();
599       ++it, --indent) {
600     OutputSingleGroupWithLimit(
601         *it->group,  // group
602         string(indent, ' '),  // line indentation
603         it->header,  // header
604         it->footer,  // footer
605         perfect_match_found,  // long format
606         &remaining_lines,  // line limit - reduces this by number printed
607         &completions_output,  // completions (not lines) added
608         completions);  // produced completions
609     perfect_match_found = false;
610   }
611
612   if (completions_output != matching_flags.size()) {
613     options->force_no_update = false;
614     completions->push_back("~ (Remaining flags hidden) ~");
615   } else {
616     options->force_no_update = true;
617   }
618 }
619
620 static void RetrieveUnusedFlags(
621     const set<const CommandLineFlagInfo *> &matching_flags,
622     const NotableFlags &notable_flags,
623     set<const CommandLineFlagInfo *> *unused_flags) {
624   // Remove from 'matching_flags' set all members of the sets of
625   // flags we've already printed (specifically, those in notable_flags)
626   for (set<const CommandLineFlagInfo *>::const_iterator it =
627         matching_flags.begin();
628       it != matching_flags.end();
629       ++it) {
630     if (notable_flags.perfect_match_flag.count(*it) ||
631         notable_flags.module_flags.count(*it) ||
632         notable_flags.package_flags.count(*it) ||
633         notable_flags.most_common_flags.count(*it) ||
634         notable_flags.subpackage_flags.count(*it))
635       continue;
636     unused_flags->insert(*it);
637   }
638 }
639
640 // 5) Output matches (and helpfer methods)
641
642 static void OutputSingleGroupWithLimit(
643     const set<const CommandLineFlagInfo *> &group,
644     const string &line_indentation,
645     const string &header,
646     const string &footer,
647     bool long_output_format,
648     int *remaining_line_limit,
649     int *completion_elements_output,
650     vector<string> *completions) {
651   if (group.empty()) return;
652   if (!header.empty()) {
653     if (*remaining_line_limit < 2) return;
654     *remaining_line_limit -= 2;
655     completions->push_back(line_indentation + header);
656     completions->push_back(line_indentation + string(header.size(), '-'));
657   }
658   for (set<const CommandLineFlagInfo *>::const_iterator it = group.begin();
659       it != group.end() && *remaining_line_limit > 0;
660       ++it) {
661     --*remaining_line_limit;
662     ++*completion_elements_output;
663     completions->push_back(
664         (long_output_format
665           ? GetLongFlagLine(line_indentation, **it)
666           : GetShortFlagLine(line_indentation, **it)));
667   }
668   if (!footer.empty()) {
669     if (*remaining_line_limit < 1) return;
670     --*remaining_line_limit;
671     completions->push_back(line_indentation + footer);
672   }
673 }
674
675 static string GetShortFlagLine(
676     const string &line_indentation,
677     const CommandLineFlagInfo &info) {
678   string prefix =
679     line_indentation + "--" + info.name + " [" +
680     (info.type == "string" ?
681        ("'" + info.default_value + "'") :
682        info.default_value)
683     + "] ";
684   int remainder = FLAGS_tab_completion_columns - prefix.size();
685   string suffix = "";
686   if (remainder > 0)
687     suffix =
688         (info.description.size() > remainder ?
689          (info.description.substr(0, remainder - 3) + "...").c_str() :
690          info.description.c_str());
691   return prefix + suffix;
692 }
693
694 static string GetLongFlagLine(
695     const string &line_indentation,
696     const CommandLineFlagInfo &info) {
697
698   string output = DescribeOneFlag(info);
699
700   // Replace '-' with '--', and remove trailing newline before appending
701   // the module definition location.
702   string old_flagname = "-" + info.name;
703   output.replace(
704       output.find(old_flagname),
705       old_flagname.size(),
706       "-" + old_flagname);
707   // Stick a newline and indentation in front of the type and default
708   // portions of DescribeOneFlag()s description
709   static const char kNewlineWithIndent[] = "\n    ";
710   output.replace(output.find(" type:"), 1, string(kNewlineWithIndent));
711   output.replace(output.find(" default:"), 1, string(kNewlineWithIndent));
712   output = line_indentation + " Details for '--" + info.name + "':\n" +
713      output + "    defined: " + info.filename;
714
715   // Eliminate any doubled newlines that crept in.  Specifically, if
716   // DescribeOneFlag() decided to break the line just before "type"
717   // or "default", we don't want to introduce an extra blank line
718   static const string line_of_spaces(FLAGS_tab_completion_columns, ' ');
719   static const char kDoubledNewlines[] = "\n     \n";
720   for (string::size_type newlines = output.find(kDoubledNewlines);
721       newlines != string::npos;
722       newlines = output.find(kDoubledNewlines))
723     // Replace each 'doubled newline' with a single newline
724     output.replace(newlines, sizeof(kDoubledNewlines) - 1, string("\n"));
725
726   for (string::size_type newline = output.find('\n');
727       newline != string::npos;
728       newline = output.find('\n')) {
729     int newline_pos = newline % FLAGS_tab_completion_columns;
730     int missing_spaces = FLAGS_tab_completion_columns - newline_pos;
731     output.replace(newline, 1, line_of_spaces, 1, missing_spaces);
732   }
733   return output;
734 }
735 }  // anonymous
736
737 void HandleCommandLineCompletions(void) {
738   if (FLAGS_tab_completion_word.empty()) return;
739   PrintFlagCompletionInfo();
740   exit(0);
741 }
742
743 _END_GOOGLE_NAMESPACE_