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