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