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 }; 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 DVLOG(1) << "Identified canonical_token: '" << canonical_token << "'"; 212 213 vector<CommandLineFlagInfo> all_flags; 214 set<const CommandLineFlagInfo *> matching_flags; 215 GetAllFlags(&all_flags); 216 DVLOG(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 DVLOG(1) << "Identified " << matching_flags.size() << " matching flags"; 226 DVLOG(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 DVLOG(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 DVLOG(1) << "Identified module: '" << module << "'"; 246 DVLOG(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 ¬able_flags); 255 DVLOG(2) << "Categorized matching flags:"; 256 DVLOG(2) << " perfect_match: " << notable_flags.perfect_match_flag.size(); 257 DVLOG(2) << " module: " << notable_flags.module_flags.size(); 258 DVLOG(2) << " package: " << notable_flags.package_flags.size(); 259 DVLOG(2) << " most common: " << notable_flags.most_common_flags.size(); 260 DVLOG(2) << " subpackage: " << notable_flags.subpackage_flags.size(); 261 262 vector<string> completions; 263 FinalizeCompletionOutput( 264 matching_flags, 265 &options, 266 ¬able_flags, 267 &completions); 268 269 if (options.force_no_update) 270 completions.push_back("~"); 271 272 DVLOG(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 DVLOG(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(user): 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 DVLOG(2) << "Examining match '" << (*it)->name << "'"; 428 DVLOG(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 DVLOG(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 DVLOG(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 DVLOG(3) << "Result: package match"; 451 } else if (false) { 452 // In the list of the XXX most commonly supplied flags overall 453 // TODO(user): Compile this list. 454 DVLOG(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 DVLOG(3) << "Result: subpackage match"; 460 } 461 462 DVLOG(3) << "Result: not special match"; 463 } 464 } 465 466 static void PushNameWithSuffix(vector<string>* suffixes, const char* suffix) { 467 suffixes->push_back( 468 StringPrintf("/%s%s", ProgramInvocationShortName(), suffix)); 469 } 470 471 static void TryFindModuleAndPackageDir( 472 const vector<CommandLineFlagInfo> &all_flags, 473 string *module, 474 string *package_dir) { 475 module->clear(); 476 package_dir->clear(); 477 478 vector<string> suffixes; 479 // TODO(user): There's some inherant ambiguity here - multiple directories 480 // could share the same trailing folder and file structure (and even worse, 481 // same file names), causing us to be unsure as to which of the two is the 482 // actual package for this binary. In this case, we'll arbitrarily choose. 483 PushNameWithSuffix(&suffixes, "."); 484 PushNameWithSuffix(&suffixes, "-main."); 485 PushNameWithSuffix(&suffixes, "_main."); 486 // These four are new but probably merited? 487 PushNameWithSuffix(&suffixes, "-test."); 488 PushNameWithSuffix(&suffixes, "_test."); 489 PushNameWithSuffix(&suffixes, "-unittest."); 490 PushNameWithSuffix(&suffixes, "_unittest."); 491 492 for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin(); 493 it != all_flags.end(); 494 ++it) { 495 for (vector<string>::const_iterator suffix = suffixes.begin(); 496 suffix != suffixes.end(); 497 ++suffix) { 498 // TODO(user): Make sure the match is near the end of the string 499 if (it->filename.find(*suffix) != string::npos) { 500 *module = it->filename; 501 string::size_type sep = it->filename.rfind(PATH_SEPARATOR); 502 *package_dir = it->filename.substr(0, (sep == string::npos) ? 0 : sep); 503 return; 504 } 505 } 506 } 507 } 508 509 // Can't specialize template type on a locally defined type. Silly C++... 510 struct DisplayInfoGroup { 511 const char* header; 512 const char* footer; 513 set<const CommandLineFlagInfo *> *group; 514 515 int SizeInLines() const { 516 int size_in_lines = static_cast<int>(group->size()) + 1; 517 if (strlen(header) > 0) { 518 size_in_lines++; 519 } 520 if (strlen(footer) > 0) { 521 size_in_lines++; 522 } 523 return size_in_lines; 524 } 525 }; 526 527 // 4) Finalize and trim output flag set 528 static void FinalizeCompletionOutput( 529 const set<const CommandLineFlagInfo *> &matching_flags, 530 CompletionOptions *options, 531 NotableFlags *notable_flags, 532 vector<string> *completions) { 533 534 // We want to output lines in groups. Each group needs to be indented 535 // the same to keep its lines together. Unless otherwise required, 536 // only 99 lines should be output to prevent bash from harassing the 537 // user. 538 539 // First, figure out which output groups we'll actually use. For each 540 // nonempty group, there will be ~3 lines of header & footer, plus all 541 // output lines themselves. 542 int max_desired_lines = // "999999 flags should be enough for anyone. -dave" 543 (options->return_all_matching_flags ? 999999 : 98); 544 int lines_so_far = 0; 545 546 vector<DisplayInfoGroup> output_groups; 547 bool perfect_match_found = false; 548 if (lines_so_far < max_desired_lines && 549 !notable_flags->perfect_match_flag.empty()) { 550 perfect_match_found = true; 551 DisplayInfoGroup group = 552 { "", 553 "==========", 554 ¬able_flags->perfect_match_flag }; 555 lines_so_far += group.SizeInLines(); 556 output_groups.push_back(group); 557 } 558 if (lines_so_far < max_desired_lines && 559 !notable_flags->module_flags.empty()) { 560 DisplayInfoGroup group = { 561 "-* Matching module flags *-", 562 "===========================", 563 ¬able_flags->module_flags }; 564 lines_so_far += group.SizeInLines(); 565 output_groups.push_back(group); 566 } 567 if (lines_so_far < max_desired_lines && 568 !notable_flags->package_flags.empty()) { 569 DisplayInfoGroup group = { 570 "-* Matching package flags *-", 571 "============================", 572 ¬able_flags->package_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->most_common_flags.empty()) { 578 DisplayInfoGroup group = { 579 "-* Commonly used flags *-", 580 "=========================", 581 ¬able_flags->most_common_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->subpackage_flags.empty()) { 587 DisplayInfoGroup group = { 588 "-* Matching sub-package flags *-", 589 "================================", 590 ¬able_flags->subpackage_flags }; 591 lines_so_far += group.SizeInLines(); 592 output_groups.push_back(group); 593 } 594 595 set<const CommandLineFlagInfo *> obscure_flags; // flags not notable 596 if (lines_so_far < max_desired_lines) { 597 RetrieveUnusedFlags(matching_flags, *notable_flags, &obscure_flags); 598 if (!obscure_flags.empty()) { 599 DisplayInfoGroup group = { 600 "-* Other flags *-", 601 "", 602 &obscure_flags }; 603 lines_so_far += group.SizeInLines(); 604 output_groups.push_back(group); 605 } 606 } 607 608 // Second, go through each of the chosen output groups and output 609 // as many of those flags as we can, while remaining below our limit 610 int remaining_lines = max_desired_lines; 611 size_t completions_output = 0; 612 int indent = static_cast<int>(output_groups.size()) - 1; 613 for (vector<DisplayInfoGroup>::const_iterator it = 614 output_groups.begin(); 615 it != output_groups.end(); 616 ++it, --indent) { 617 OutputSingleGroupWithLimit( 618 *it->group, // group 619 string(indent, ' '), // line indentation 620 string(it->header), // header 621 string(it->footer), // footer 622 perfect_match_found, // long format 623 &remaining_lines, // line limit - reduces this by number printed 624 &completions_output, // completions (not lines) added 625 completions); // produced completions 626 perfect_match_found = false; 627 } 628 629 if (completions_output != matching_flags.size()) { 630 options->force_no_update = false; 631 completions->push_back("~ (Remaining flags hidden) ~"); 632 } else { 633 options->force_no_update = true; 634 } 635 } 636 637 static void RetrieveUnusedFlags( 638 const set<const CommandLineFlagInfo *> &matching_flags, 639 const NotableFlags ¬able_flags, 640 set<const CommandLineFlagInfo *> *unused_flags) { 641 // Remove from 'matching_flags' set all members of the sets of 642 // flags we've already printed (specifically, those in notable_flags) 643 for (set<const CommandLineFlagInfo *>::const_iterator it = 644 matching_flags.begin(); 645 it != matching_flags.end(); 646 ++it) { 647 if (notable_flags.perfect_match_flag.count(*it) || 648 notable_flags.module_flags.count(*it) || 649 notable_flags.package_flags.count(*it) || 650 notable_flags.most_common_flags.count(*it) || 651 notable_flags.subpackage_flags.count(*it)) 652 continue; 653 unused_flags->insert(*it); 654 } 655 } 656 657 // 5) Output matches (and helper methods) 658 659 static void OutputSingleGroupWithLimit( 660 const set<const CommandLineFlagInfo *> &group, 661 const string &line_indentation, 662 const string &header, 663 const string &footer, 664 bool long_output_format, 665 int *remaining_line_limit, 666 size_t *completion_elements_output, 667 vector<string> *completions) { 668 if (group.empty()) return; 669 if (!header.empty()) { 670 if (*remaining_line_limit < 2) return; 671 *remaining_line_limit -= 2; 672 completions->push_back(line_indentation + header); 673 completions->push_back(line_indentation + string(header.size(), '-')); 674 } 675 for (set<const CommandLineFlagInfo *>::const_iterator it = group.begin(); 676 it != group.end() && *remaining_line_limit > 0; 677 ++it) { 678 --*remaining_line_limit; 679 ++*completion_elements_output; 680 completions->push_back( 681 (long_output_format 682 ? GetLongFlagLine(line_indentation, **it) 683 : GetShortFlagLine(line_indentation, **it))); 684 } 685 if (!footer.empty()) { 686 if (*remaining_line_limit < 1) return; 687 --*remaining_line_limit; 688 completions->push_back(line_indentation + footer); 689 } 690 } 691 692 static string GetShortFlagLine( 693 const string &line_indentation, 694 const CommandLineFlagInfo &info) { 695 string prefix; 696 bool is_string = (info.type == "string"); 697 SStringPrintf(&prefix, "%s--%s [%s%s%s] ", 698 line_indentation.c_str(), 699 info.name.c_str(), 700 (is_string ? "'" : ""), 701 info.default_value.c_str(), 702 (is_string ? "'" : "")); 703 int remainder = 704 FLAGS_tab_completion_columns - static_cast<int>(prefix.size()); 705 string suffix; 706 if (remainder > 0) 707 suffix = 708 (static_cast<int>(info.description.size()) > remainder ? 709 (info.description.substr(0, remainder - 3) + "...").c_str() : 710 info.description.c_str()); 711 return prefix + suffix; 712 } 713 714 static string GetLongFlagLine( 715 const string &line_indentation, 716 const CommandLineFlagInfo &info) { 717 718 string output = DescribeOneFlag(info); 719 720 // Replace '-' with '--', and remove trailing newline before appending 721 // the module definition location. 722 string old_flagname = "-" + info.name; 723 output.replace( 724 output.find(old_flagname), 725 old_flagname.size(), 726 "-" + old_flagname); 727 // Stick a newline and indentation in front of the type and default 728 // portions of DescribeOneFlag()s description 729 static const char kNewlineWithIndent[] = "\n "; 730 output.replace(output.find(" type:"), 1, string(kNewlineWithIndent)); 731 output.replace(output.find(" default:"), 1, string(kNewlineWithIndent)); 732 output = StringPrintf("%s Details for '--%s':\n" 733 "%s defined: %s", 734 line_indentation.c_str(), 735 info.name.c_str(), 736 output.c_str(), 737 info.filename.c_str()); 738 739 // Eliminate any doubled newlines that crept in. Specifically, if 740 // DescribeOneFlag() decided to break the line just before "type" 741 // or "default", we don't want to introduce an extra blank line 742 static const string line_of_spaces(FLAGS_tab_completion_columns, ' '); 743 static const char kDoubledNewlines[] = "\n \n"; 744 for (string::size_type newlines = output.find(kDoubledNewlines); 745 newlines != string::npos; 746 newlines = output.find(kDoubledNewlines)) 747 // Replace each 'doubled newline' with a single newline 748 output.replace(newlines, sizeof(kDoubledNewlines) - 1, string("\n")); 749 750 for (string::size_type newline = output.find('\n'); 751 newline != string::npos; 752 newline = output.find('\n')) { 753 int newline_pos = static_cast<int>(newline) % FLAGS_tab_completion_columns; 754 int missing_spaces = FLAGS_tab_completion_columns - newline_pos; 755 output.replace(newline, 1, line_of_spaces, 1, missing_spaces); 756 } 757 return output; 758 } 759 } // anonymous 760 761 void HandleCommandLineCompletions(void) { 762 if (FLAGS_tab_completion_word.empty()) return; 763 PrintFlagCompletionInfo(); 764 gflags_exitfunc(0); 765 } 766 767 768 } // namespace GFLAGS_NAMESPACE 769