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 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 #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 58 #include "config.h" 59 #include "gflags/gflags.h" 60 #include "gflags/gflags_completions.h" 61 #include "util.h" 62 63 using std::set; 64 using std::string; 65 using std::vector; 66 67 68 DEFINE_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."); 72 DEFINE_int32(tab_completion_columns, 80, 73 "Number of columns to use in output for tab completion"); 74 75 76 namespace GFLAGS_NAMESPACE { 77 78 79 namespace { 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 ¬able_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 size_t *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 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) { } 187 }; 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. 195 struct 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 207 static void PrintFlagCompletionInfo(void) { 208 string cursor_word = FLAGS_tab_completion_word; 209 string canonical_token; 210 CompletionOptions options = CompletionOptions(); 211 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 ¬able_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 ¬able_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) 290 static 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 330 case 3: options->flag_description_substring_search = true; 331 case 2: options->flag_location_substring_search = true; 332 case 1: options->flag_name_substring_search = true; 333 }; 334 335 options->return_all_matching_flags = (found_plusses > 0); 336 } 337 338 // Returns true if a char was removed 339 static bool RemoveTrailingChar(string *str, char c) { 340 if (str->empty()) return false; 341 if ((*str)[str->size() - 1] == c) { 342 *str = str->substr(0, str->size() - 1); 343 return true; 344 } 345 return false; 346 } 347 348 349 // 2) Find all matches (and helper methods) 350 static void FindMatchingFlags( 351 const vector<CommandLineFlagInfo> &all_flags, 352 const CompletionOptions &options, 353 const string &match_token, 354 set<const CommandLineFlagInfo *> *all_matches, 355 string *longest_common_prefix) { 356 all_matches->clear(); 357 bool first_match = true; 358 for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin(); 359 it != all_flags.end(); 360 ++it) { 361 if (DoesSingleFlagMatch(*it, options, match_token)) { 362 all_matches->insert(&*it); 363 if (first_match) { 364 first_match = false; 365 *longest_common_prefix = it->name; 366 } else { 367 if (longest_common_prefix->empty() || it->name.empty()) { 368 longest_common_prefix->clear(); 369 continue; 370 } 371 string::size_type pos = 0; 372 while (pos < longest_common_prefix->size() && 373 pos < it->name.size() && 374 (*longest_common_prefix)[pos] == it->name[pos]) 375 ++pos; 376 longest_common_prefix->erase(pos); 377 } 378 } 379 } 380 } 381 382 // Given the set of all flags, the parsed match options, and the 383 // canonical search token, produce the set of all candidate matching 384 // flags for subsequent analysis or filtering. 385 static bool DoesSingleFlagMatch( 386 const CommandLineFlagInfo &flag, 387 const CompletionOptions &options, 388 const string &match_token) { 389 // Is there a prefix match? 390 string::size_type pos = flag.name.find(match_token); 391 if (pos == 0) return true; 392 393 // Is there a substring match if we want it? 394 if (options.flag_name_substring_search && 395 pos != string::npos) 396 return true; 397 398 // Is there a location match if we want it? 399 if (options.flag_location_substring_search && 400 flag.filename.find(match_token) != string::npos) 401 return true; 402 403 // TODO(user): All searches should probably be case-insensitive 404 // (especially this one...) 405 if (options.flag_description_substring_search && 406 flag.description.find(match_token) != string::npos) 407 return true; 408 409 return false; 410 } 411 412 // 3) Categorize matches (and helper method) 413 414 // Given a set of matching flags, categorize them by 415 // likely relevence to this specific binary 416 static void CategorizeAllMatchingFlags( 417 const set<const CommandLineFlagInfo *> &all_matches, 418 const string &search_token, 419 const string &module, // empty if we couldn't find any 420 const string &package_dir, // empty if we couldn't find any 421 NotableFlags *notable_flags) { 422 notable_flags->perfect_match_flag.clear(); 423 notable_flags->module_flags.clear(); 424 notable_flags->package_flags.clear(); 425 notable_flags->most_common_flags.clear(); 426 notable_flags->subpackage_flags.clear(); 427 428 for (set<const CommandLineFlagInfo *>::const_iterator it = 429 all_matches.begin(); 430 it != all_matches.end(); 431 ++it) { 432 DVLOG(2) << "Examining match '" << (*it)->name << "'"; 433 DVLOG(7) << " filename: '" << (*it)->filename << "'"; 434 string::size_type pos = string::npos; 435 if (!package_dir.empty()) 436 pos = (*it)->filename.find(package_dir); 437 string::size_type slash = string::npos; 438 if (pos != string::npos) // candidate for package or subpackage match 439 slash = (*it)->filename.find( 440 PATH_SEPARATOR, 441 pos + package_dir.size() + 1); 442 443 if ((*it)->name == search_token) { 444 // Exact match on some flag's name 445 notable_flags->perfect_match_flag.insert(*it); 446 DVLOG(3) << "Result: perfect match"; 447 } else if (!module.empty() && (*it)->filename == module) { 448 // Exact match on module filename 449 notable_flags->module_flags.insert(*it); 450 DVLOG(3) << "Result: module match"; 451 } else if (!package_dir.empty() && 452 pos != string::npos && slash == string::npos) { 453 // In the package, since there was no slash after the package portion 454 notable_flags->package_flags.insert(*it); 455 DVLOG(3) << "Result: package match"; 456 } else if (false) { 457 // In the list of the XXX most commonly supplied flags overall 458 // TODO(user): Compile this list. 459 DVLOG(3) << "Result: most-common match"; 460 } else if (!package_dir.empty() && 461 pos != string::npos && slash != string::npos) { 462 // In a subdirectory of the package 463 notable_flags->subpackage_flags.insert(*it); 464 DVLOG(3) << "Result: subpackage match"; 465 } 466 467 DVLOG(3) << "Result: not special match"; 468 } 469 } 470 471 static void PushNameWithSuffix(vector<string>* suffixes, const char* suffix) { 472 suffixes->push_back( 473 StringPrintf("/%s%s", ProgramInvocationShortName(), suffix)); 474 } 475 476 static void TryFindModuleAndPackageDir( 477 const vector<CommandLineFlagInfo> &all_flags, 478 string *module, 479 string *package_dir) { 480 module->clear(); 481 package_dir->clear(); 482 483 vector<string> suffixes; 484 // TODO(user): There's some inherant ambiguity here - multiple directories 485 // could share the same trailing folder and file structure (and even worse, 486 // same file names), causing us to be unsure as to which of the two is the 487 // actual package for this binary. In this case, we'll arbitrarily choose. 488 PushNameWithSuffix(&suffixes, "."); 489 PushNameWithSuffix(&suffixes, "-main."); 490 PushNameWithSuffix(&suffixes, "_main."); 491 // These four are new but probably merited? 492 PushNameWithSuffix(&suffixes, "-test."); 493 PushNameWithSuffix(&suffixes, "_test."); 494 PushNameWithSuffix(&suffixes, "-unittest."); 495 PushNameWithSuffix(&suffixes, "_unittest."); 496 497 for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin(); 498 it != all_flags.end(); 499 ++it) { 500 for (vector<string>::const_iterator suffix = suffixes.begin(); 501 suffix != suffixes.end(); 502 ++suffix) { 503 // TODO(user): Make sure the match is near the end of the string 504 if (it->filename.find(*suffix) != string::npos) { 505 *module = it->filename; 506 string::size_type sep = it->filename.rfind(PATH_SEPARATOR); 507 *package_dir = it->filename.substr(0, (sep == string::npos) ? 0 : sep); 508 return; 509 } 510 } 511 } 512 } 513 514 // Can't specialize template type on a locally defined type. Silly C++... 515 struct DisplayInfoGroup { 516 const char* header; 517 const char* footer; 518 set<const CommandLineFlagInfo *> *group; 519 520 int SizeInLines() const { 521 int size_in_lines = static_cast<int>(group->size()) + 1; 522 if (strlen(header) > 0) { 523 size_in_lines++; 524 } 525 if (strlen(footer) > 0) { 526 size_in_lines++; 527 } 528 return size_in_lines; 529 } 530 }; 531 532 // 4) Finalize and trim output flag set 533 static void FinalizeCompletionOutput( 534 const set<const CommandLineFlagInfo *> &matching_flags, 535 CompletionOptions *options, 536 NotableFlags *notable_flags, 537 vector<string> *completions) { 538 539 // We want to output lines in groups. Each group needs to be indented 540 // the same to keep its lines together. Unless otherwise required, 541 // only 99 lines should be output to prevent bash from harassing the 542 // user. 543 544 // First, figure out which output groups we'll actually use. For each 545 // nonempty group, there will be ~3 lines of header & footer, plus all 546 // output lines themselves. 547 int max_desired_lines = // "999999 flags should be enough for anyone. -dave" 548 (options->return_all_matching_flags ? 999999 : 98); 549 int lines_so_far = 0; 550 551 vector<DisplayInfoGroup> output_groups; 552 bool perfect_match_found = false; 553 if (!notable_flags->perfect_match_flag.empty()) { 554 perfect_match_found = true; 555 DisplayInfoGroup group = 556 { "", 557 "==========", 558 ¬able_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 ¬able_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 ¬able_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 ¬able_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 ¬able_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 ¬able_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 bool is_string = (info.type == "string"); 701 SStringPrintf(&prefix, "%s--%s [%s%s%s] ", 702 line_indentation.c_str(), 703 info.name.c_str(), 704 (is_string ? "'" : ""), 705 info.default_value.c_str(), 706 (is_string ? "'" : "")); 707 int remainder = 708 FLAGS_tab_completion_columns - static_cast<int>(prefix.size()); 709 string suffix; 710 if (remainder > 0) 711 suffix = 712 (static_cast<int>(info.description.size()) > remainder ? 713 (info.description.substr(0, remainder - 3) + "...").c_str() : 714 info.description.c_str()); 715 return prefix + suffix; 716 } 717 718 static string GetLongFlagLine( 719 const string &line_indentation, 720 const CommandLineFlagInfo &info) { 721 722 string output = DescribeOneFlag(info); 723 724 // Replace '-' with '--', and remove trailing newline before appending 725 // the module definition location. 726 string old_flagname = "-" + info.name; 727 output.replace( 728 output.find(old_flagname), 729 old_flagname.size(), 730 "-" + old_flagname); 731 // Stick a newline and indentation in front of the type and default 732 // portions of DescribeOneFlag()s description 733 static const char kNewlineWithIndent[] = "\n "; 734 output.replace(output.find(" type:"), 1, string(kNewlineWithIndent)); 735 output.replace(output.find(" default:"), 1, string(kNewlineWithIndent)); 736 output = StringPrintf("%s Details for '--%s':\n" 737 "%s defined: %s", 738 line_indentation.c_str(), 739 info.name.c_str(), 740 output.c_str(), 741 info.filename.c_str()); 742 743 // Eliminate any doubled newlines that crept in. Specifically, if 744 // DescribeOneFlag() decided to break the line just before "type" 745 // or "default", we don't want to introduce an extra blank line 746 static const string line_of_spaces(FLAGS_tab_completion_columns, ' '); 747 static const char kDoubledNewlines[] = "\n \n"; 748 for (string::size_type newlines = output.find(kDoubledNewlines); 749 newlines != string::npos; 750 newlines = output.find(kDoubledNewlines)) 751 // Replace each 'doubled newline' with a single newline 752 output.replace(newlines, sizeof(kDoubledNewlines) - 1, string("\n")); 753 754 for (string::size_type newline = output.find('\n'); 755 newline != string::npos; 756 newline = output.find('\n')) { 757 int newline_pos = static_cast<int>(newline) % FLAGS_tab_completion_columns; 758 int missing_spaces = FLAGS_tab_completion_columns - newline_pos; 759 output.replace(newline, 1, line_of_spaces, 1, missing_spaces); 760 } 761 return output; 762 } 763 } // anonymous 764 765 void HandleCommandLineCompletions(void) { 766 if (FLAGS_tab_completion_word.empty()) return; 767 PrintFlagCompletionInfo(); 768 gflags_exitfunc(0); 769 } 770 771 772 } // namespace GFLAGS_NAMESPACE 773