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