// Copyright (c) 2008, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// ---

// Bash-style command line flag completion for C++ binaries
//
// This module implements bash-style completions.  It achieves this
// goal in the following broad chunks:
//
//  1) Take a to-be-completed word, and examine it for search hints
//  2) Identify all potentially matching flags
//     2a) If there are no matching flags, do nothing.
//     2b) If all matching flags share a common prefix longer than the
//         completion word, output just that matching prefix
//  3) Categorize those flags to produce a rough ordering of relevence.
//  4) Potentially trim the set of flags returned to a smaller number
//     that bash is happier with
//  5) Output the matching flags in groups ordered by relevence.
//     5a) Force bash to place most-relevent groups at the top of the list
//     5b) Trim most flag's descriptions to fit on a single terminal line


#include <gflags_config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>   // for strlen

#include <set>
#include <string>
#include <utility>
#include <vector>

#include <gflags/gflags.h>
#include "util.h"

using std::set;
using std::string;
using std::vector;

#ifndef PATH_SEPARATOR
#define PATH_SEPARATOR  '/'
#endif

DEFINE_string(tab_completion_word, "",
              "If non-empty, HandleCommandLineCompletions() will hijack the "
              "process and attempt to do bash-style command line flag "
              "completion on this value.");
DEFINE_int32(tab_completion_columns, 80,
             "Number of columns to use in output for tab completion");

_START_GOOGLE_NAMESPACE_

namespace {
// Function prototypes and Type forward declarations.  Code may be
// more easily understood if it is roughly ordered according to
// control flow, rather than by C's "declare before use" ordering
struct CompletionOptions;
struct NotableFlags;

// The entry point if flag completion is to be used.
static void PrintFlagCompletionInfo(void);


// 1) Examine search word
static void CanonicalizeCursorWordAndSearchOptions(
    const string& cursor_word,
    string* canonical_search_token,
    CompletionOptions* options);

static bool RemoveTrailingChar(string* str, char c);


// 2) Find all matches
static void FindMatchingFlags(
    const vector<CommandLineFlagInfo>& all_flags,
    const CompletionOptions& options,
    const string& match_token,
    set<const CommandLineFlagInfo*>* all_matches,
    string* longest_common_prefix);

static bool DoesSingleFlagMatch(
    const CommandLineFlagInfo& flag,
    const CompletionOptions& options,
    const string& match_token);


// 3) Categorize matches
static void CategorizeAllMatchingFlags(
    const set<const CommandLineFlagInfo*>& all_matches,
    const string& search_token,
    const string& module,
    const string& package_dir,
    NotableFlags* notable_flags);

static void TryFindModuleAndPackageDir(
    const vector<CommandLineFlagInfo> all_flags,
    string* module,
    string* package_dir);


// 4) Decide which flags to use
static void FinalizeCompletionOutput(
    const set<const CommandLineFlagInfo*>& matching_flags,
    CompletionOptions* options,
    NotableFlags* notable_flags,
    vector<string>* completions);

static void RetrieveUnusedFlags(
    const set<const CommandLineFlagInfo*>& matching_flags,
    const NotableFlags& notable_flags,
    set<const CommandLineFlagInfo*>* unused_flags);


// 5) Output matches
static void OutputSingleGroupWithLimit(
    const set<const CommandLineFlagInfo*>& group,
    const string& line_indentation,
    const string& header,
    const string& footer,
    bool long_output_format,
    int* remaining_line_limit,
    size_t* completion_elements_added,
    vector<string>* completions);

// (helpers for #5)
static string GetShortFlagLine(
    const string& line_indentation,
    const CommandLineFlagInfo& info);

static string GetLongFlagLine(
    const string& line_indentation,
    const CommandLineFlagInfo& info);


//
// Useful types

// Try to deduce the intentions behind this completion attempt.  Return the
// canonical search term in 'canonical_search_token'.  Binary search options
// are returned in the various booleans, which should all have intuitive
// semantics, possibly except:
//  - return_all_matching_flags: Generally, we'll trim the number of
//    returned candidates to some small number, showing those that are
//    most likely to be useful first.  If this is set, however, the user
//    really does want us to return every single flag as an option.
//  - force_no_update: Any time we output lines, all of which share a
//    common prefix, bash will 'helpfully' not even bother to show the
//    output, instead changing the current word to be that common prefix.
//    If it's clear this shouldn't happen, we'll set this boolean
struct CompletionOptions {
    bool flag_name_substring_search;
    bool flag_location_substring_search;
    bool flag_description_substring_search;
    bool return_all_matching_flags;
    bool force_no_update;
};

// Notable flags are flags that are special or preferred for some
// reason.  For example, flags that are defined in the binary's module
// are expected to be much more relevent than flags defined in some
// other random location.  These sets are specified roughly in precedence
// order.  Once a flag is placed in one of these 'higher' sets, it won't
// be placed in any of the 'lower' sets.
struct NotableFlags {
    typedef set<const CommandLineFlagInfo*> FlagSet;
    FlagSet perfect_match_flag;
    FlagSet module_flags;       // Found in module file
    FlagSet package_flags;      // Found in same directory as module file
    FlagSet most_common_flags;  // One of the XXX most commonly supplied flags
    FlagSet subpackage_flags;   // Found in subdirectories of package
};


//
// Tab completion implementation - entry point
static void PrintFlagCompletionInfo(void) {
    string cursor_word = FLAGS_tab_completion_word;
    string canonical_token;
    CompletionOptions options = { };
    CanonicalizeCursorWordAndSearchOptions(
        cursor_word,
        &canonical_token,
        &options);

    DVLOG(1) << "Identified canonical_token: '" << canonical_token << "'";

    vector<CommandLineFlagInfo> all_flags;
    set<const CommandLineFlagInfo*> matching_flags;
    GetAllFlags(&all_flags);
    DVLOG(2) << "Found " << all_flags.size() << " flags overall";

    string longest_common_prefix;
    FindMatchingFlags(
        all_flags,
        options,
        canonical_token,
        &matching_flags,
        &longest_common_prefix);
    DVLOG(1) << "Identified " << matching_flags.size() << " matching flags";
    DVLOG(1) << "Identified " << longest_common_prefix
             << " as longest common prefix.";

    if (longest_common_prefix.size() > canonical_token.size()) {
        // There's actually a shared common prefix to all matching flags,
        // so may as well output that and quit quickly.
        DVLOG(1) << "The common prefix '" << longest_common_prefix
                 << "' was longer than the token '" << canonical_token
                 << "'.  Returning just this prefix for completion.";
        fprintf(stdout, "--%s", longest_common_prefix.c_str());
        return;
    }

    if (matching_flags.empty()) {
        VLOG(1) << "There were no matching flags, returning nothing.";
        return;
    }

    string module;
    string package_dir;
    TryFindModuleAndPackageDir(all_flags, &module, &package_dir);
    DVLOG(1) << "Identified module: '" << module << "'";
    DVLOG(1) << "Identified package_dir: '" << package_dir << "'";

    NotableFlags notable_flags;
    CategorizeAllMatchingFlags(
        matching_flags,
        canonical_token,
        module,
        package_dir,
        &notable_flags);
    DVLOG(2) << "Categorized matching flags:";
    DVLOG(2) << " perfect_match: " << notable_flags.perfect_match_flag.size();
    DVLOG(2) << " module: " << notable_flags.module_flags.size();
    DVLOG(2) << " package: " << notable_flags.package_flags.size();
    DVLOG(2) << " most common: " << notable_flags.most_common_flags.size();
    DVLOG(2) << " subpackage: " << notable_flags.subpackage_flags.size();

    vector<string> completions;
    FinalizeCompletionOutput(
        matching_flags,
        &options,
        &notable_flags,
        &completions);

    if (options.force_no_update) {
        completions.push_back("~");
    }

    DVLOG(1) << "Finalized with " << completions.size()
             << " chosen completions";

    for (vector<string>::const_iterator it = completions.begin();
            it != completions.end();
            ++it) {
        DVLOG(9) << "  Completion entry: '" << *it << "'";
        fprintf(stdout, "%s\n", it->c_str());
    }
}


// 1) Examine search word (and helper method)
static void CanonicalizeCursorWordAndSearchOptions(
    const string& cursor_word,
    string* canonical_search_token,
    CompletionOptions* options) {
    *canonical_search_token = cursor_word;

    if (canonical_search_token->empty()) {
        return;
    }

    // Get rid of leading quotes and dashes in the search term
    if ((*canonical_search_token)[0] == '"') {
        *canonical_search_token = canonical_search_token->substr(1);
    }

    while ((*canonical_search_token)[0] == '-') {
        *canonical_search_token = canonical_search_token->substr(1);
    }

    options->flag_name_substring_search = false;
    options->flag_location_substring_search = false;
    options->flag_description_substring_search = false;
    options->return_all_matching_flags = false;
    options->force_no_update = false;

    // Look for all search options we can deduce now.  Do this by walking
    // backwards through the term, looking for up to three '?' and up to
    // one '+' as suffixed characters.  Consume them if found, and remove
    // them from the canonical search token.
    int found_question_marks = 0;
    int found_plusses = 0;

    while (true) {
        if (found_question_marks < 3 &&
                RemoveTrailingChar(canonical_search_token, '?')) {
            ++found_question_marks;
            continue;
        }

        if (found_plusses < 1 &&
                RemoveTrailingChar(canonical_search_token, '+')) {
            ++found_plusses;
            continue;
        }

        break;
    }

    switch (found_question_marks) {  // all fallthroughs
    case 3:
        options->flag_description_substring_search = true;

    case 2:
        options->flag_location_substring_search = true;

    case 1:
        options->flag_name_substring_search = true;
    };

    options->return_all_matching_flags = (found_plusses > 0);
}

// Returns true if a char was removed
static bool RemoveTrailingChar(string* str, char c) {
    if (str->empty()) {
        return false;
    }

    if ((*str)[str->size() - 1] == c) {
        *str = str->substr(0, str->size() - 1);
        return true;
    }

    return false;
}


// 2) Find all matches (and helper methods)
static void FindMatchingFlags(
    const vector<CommandLineFlagInfo>& all_flags,
    const CompletionOptions& options,
    const string& match_token,
    set<const CommandLineFlagInfo*>* all_matches,
    string* longest_common_prefix) {
    all_matches->clear();
    bool first_match = true;

    for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin();
            it != all_flags.end();
            ++it) {
        if (DoesSingleFlagMatch(*it, options, match_token)) {
            all_matches->insert(&*it);

            if (first_match) {
                first_match = false;
                *longest_common_prefix = it->name;
            }
            else {
                if (longest_common_prefix->empty() || it->name.empty()) {
                    longest_common_prefix->clear();
                    continue;
                }

                string::size_type pos = 0;

                while (pos < longest_common_prefix->size() &&
                        pos < it->name.size() &&
                        (*longest_common_prefix)[pos] == it->name[pos]) {
                    ++pos;
                }

                longest_common_prefix->erase(pos);
            }
        }
    }
}

// Given the set of all flags, the parsed match options, and the
// canonical search token, produce the set of all candidate matching
// flags for subsequent analysis or filtering.
static bool DoesSingleFlagMatch(
    const CommandLineFlagInfo& flag,
    const CompletionOptions& options,
    const string& match_token) {
    // Is there a prefix match?
    string::size_type pos = flag.name.find(match_token);

    if (pos == 0) {
        return true;
    }

    // Is there a substring match if we want it?
    if (options.flag_name_substring_search &&
            pos != string::npos) {
        return true;
    }

    // Is there a location match if we want it?
    if (options.flag_location_substring_search &&
            flag.filename.find(match_token) != string::npos) {
        return true;
    }

    // TODO(user): All searches should probably be case-insensitive
    // (especially this one...)
    if (options.flag_description_substring_search &&
            flag.description.find(match_token) != string::npos) {
        return true;
    }

    return false;
}

// 3) Categorize matches (and helper method)

// Given a set of matching flags, categorize them by
// likely relevence to this specific binary
static void CategorizeAllMatchingFlags(
    const set<const CommandLineFlagInfo*>& all_matches,
    const string& search_token,
    const string& module,  // empty if we couldn't find any
    const string& package_dir,  // empty if we couldn't find any
    NotableFlags* notable_flags) {
    notable_flags->perfect_match_flag.clear();
    notable_flags->module_flags.clear();
    notable_flags->package_flags.clear();
    notable_flags->most_common_flags.clear();
    notable_flags->subpackage_flags.clear();

    for (set<const CommandLineFlagInfo*>::const_iterator it =
                all_matches.begin();
            it != all_matches.end();
            ++it) {
        DVLOG(2) << "Examining match '" << (*it)->name << "'";
        DVLOG(7) << "  filename: '" << (*it)->filename << "'";
        string::size_type pos = string::npos;

        if (!package_dir.empty()) {
            pos = (*it)->filename.find(package_dir);
        }

        string::size_type slash = string::npos;

        if (pos != string::npos)  // candidate for package or subpackage match
            slash = (*it)->filename.find(
                        PATH_SEPARATOR,
                        pos + package_dir.size() + 1);

        if ((*it)->name == search_token) {
            // Exact match on some flag's name
            notable_flags->perfect_match_flag.insert(*it);
            DVLOG(3) << "Result: perfect match";
        }
        else if (!module.empty() && (*it)->filename == module) {
            // Exact match on module filename
            notable_flags->module_flags.insert(*it);
            DVLOG(3) << "Result: module match";
        }
        else if (!package_dir.empty() &&
                 pos != string::npos && slash == string::npos) {
            // In the package, since there was no slash after the package portion
            notable_flags->package_flags.insert(*it);
            DVLOG(3) << "Result: package match";
        }
        else if (false) {
            // In the list of the XXX most commonly supplied flags overall
            // TODO(user): Compile this list.
            DVLOG(3) << "Result: most-common match";
        }
        else if (!package_dir.empty() &&
                 pos != string::npos && slash != string::npos) {
            // In a subdirectory of the package
            notable_flags->subpackage_flags.insert(*it);
            DVLOG(3) << "Result: subpackage match";
        }

        DVLOG(3) << "Result: not special match";
    }
}

static void PushNameWithSuffix(vector<string>* suffixes, const char* suffix) {
    suffixes->push_back(
        StringPrintf("/%s%s", ProgramInvocationShortName(), suffix));
}

static void TryFindModuleAndPackageDir(
    const vector<CommandLineFlagInfo> all_flags,
    string* module,
    string* package_dir) {
    module->clear();
    package_dir->clear();

    vector<string> suffixes;
    // TODO(user): There's some inherant ambiguity here - multiple directories
    // could share the same trailing folder and file structure (and even worse,
    // same file names), causing us to be unsure as to which of the two is the
    // actual package for this binary.  In this case, we'll arbitrarily choose.
    PushNameWithSuffix(&suffixes, ".");
    PushNameWithSuffix(&suffixes, "-main.");
    PushNameWithSuffix(&suffixes, "_main.");
    // These four are new but probably merited?
    PushNameWithSuffix(&suffixes, "-test.");
    PushNameWithSuffix(&suffixes, "_test.");
    PushNameWithSuffix(&suffixes, "-unittest.");
    PushNameWithSuffix(&suffixes, "_unittest.");

    for (vector<CommandLineFlagInfo>::const_iterator it = all_flags.begin();
            it != all_flags.end();
            ++it) {
        for (vector<string>::const_iterator suffix = suffixes.begin();
                suffix != suffixes.end();
                ++suffix) {
            // TODO(user): Make sure the match is near the end of the string
            if (it->filename.find(*suffix) != string::npos) {
                *module = it->filename;
                string::size_type sep = it->filename.rfind(PATH_SEPARATOR);
                *package_dir = it->filename.substr(0, (sep == string::npos) ? 0 : sep);
                return;
            }
        }
    }
}

// Can't specialize template type on a locally defined type.  Silly C++...
struct DisplayInfoGroup {
    const char* header;
    const char* footer;
    set<const CommandLineFlagInfo*>* group;

    int SizeInLines() const {
        int size_in_lines = static_cast<int>(group->size()) + 1;

        if (strlen(header) > 0) {
            size_in_lines++;
        }

        if (strlen(footer) > 0) {
            size_in_lines++;
        }

        return size_in_lines;
    }
};

// 4) Finalize and trim output flag set
static void FinalizeCompletionOutput(
    const set<const CommandLineFlagInfo*>& matching_flags,
    CompletionOptions* options,
    NotableFlags* notable_flags,
    vector<string>* completions) {

    // We want to output lines in groups.  Each group needs to be indented
    // the same to keep its lines together.  Unless otherwise required,
    // only 99 lines should be output to prevent bash from harassing the
    // user.

    // First, figure out which output groups we'll actually use.  For each
    // nonempty group, there will be ~3 lines of header & footer, plus all
    // output lines themselves.
    int max_desired_lines =  // "999999 flags should be enough for anyone.  -dave"
        (options->return_all_matching_flags ? 999999 : 98);
    int lines_so_far = 0;

    vector<DisplayInfoGroup> output_groups;
    bool perfect_match_found = false;

    if (lines_so_far < max_desired_lines &&
            !notable_flags->perfect_match_flag.empty()) {
        perfect_match_found = true;
        DisplayInfoGroup group = {
            "",
            "==========",
            &notable_flags->perfect_match_flag
        };
        lines_so_far += group.SizeInLines();
        output_groups.push_back(group);
    }

    if (lines_so_far < max_desired_lines &&
            !notable_flags->module_flags.empty()) {
        DisplayInfoGroup group = {
            "-* Matching module flags *-",
            "===========================",
            &notable_flags->module_flags
        };
        lines_so_far += group.SizeInLines();
        output_groups.push_back(group);
    }

    if (lines_so_far < max_desired_lines &&
            !notable_flags->package_flags.empty()) {
        DisplayInfoGroup group = {
            "-* Matching package flags *-",
            "============================",
            &notable_flags->package_flags
        };
        lines_so_far += group.SizeInLines();
        output_groups.push_back(group);
    }

    if (lines_so_far < max_desired_lines &&
            !notable_flags->most_common_flags.empty()) {
        DisplayInfoGroup group = {
            "-* Commonly used flags *-",
            "=========================",
            &notable_flags->most_common_flags
        };
        lines_so_far += group.SizeInLines();
        output_groups.push_back(group);
    }

    if (lines_so_far < max_desired_lines &&
            !notable_flags->subpackage_flags.empty()) {
        DisplayInfoGroup group = {
            "-* Matching sub-package flags *-",
            "================================",
            &notable_flags->subpackage_flags
        };
        lines_so_far += group.SizeInLines();
        output_groups.push_back(group);
    }

    set<const CommandLineFlagInfo*> obscure_flags;   // flags not notable

    if (lines_so_far < max_desired_lines) {
        RetrieveUnusedFlags(matching_flags, *notable_flags, &obscure_flags);

        if (!obscure_flags.empty()) {
            DisplayInfoGroup group = {
                "-* Other flags *-",
                "",
                &obscure_flags
            };
            lines_so_far += group.SizeInLines();
            output_groups.push_back(group);
        }
    }

    // Second, go through each of the chosen output groups and output
    // as many of those flags as we can, while remaining below our limit
    int remaining_lines = max_desired_lines;
    size_t completions_output = 0;
    int indent = static_cast<int>(output_groups.size()) - 1;

    for (vector<DisplayInfoGroup>::const_iterator it =
                output_groups.begin();
            it != output_groups.end();
            ++it, --indent) {
        OutputSingleGroupWithLimit(
            *it->group,  // group
            string(indent, ' '),  // line indentation
            string(it->header),  // header
            string(it->footer),  // footer
            perfect_match_found,  // long format
            &remaining_lines,  // line limit - reduces this by number printed
            &completions_output,  // completions (not lines) added
            completions);  // produced completions
        perfect_match_found = false;
    }

    if (completions_output != matching_flags.size()) {
        options->force_no_update = false;
        completions->push_back("~ (Remaining flags hidden) ~");
    }
    else {
        options->force_no_update = true;
    }
}

static void RetrieveUnusedFlags(
    const set<const CommandLineFlagInfo*>& matching_flags,
    const NotableFlags& notable_flags,
    set<const CommandLineFlagInfo*>* unused_flags) {
    // Remove from 'matching_flags' set all members of the sets of
    // flags we've already printed (specifically, those in notable_flags)
    for (set<const CommandLineFlagInfo*>::const_iterator it =
                matching_flags.begin();
            it != matching_flags.end();
            ++it) {
        if (notable_flags.perfect_match_flag.count(*it) ||
                notable_flags.module_flags.count(*it) ||
                notable_flags.package_flags.count(*it) ||
                notable_flags.most_common_flags.count(*it) ||
                notable_flags.subpackage_flags.count(*it)) {
            continue;
        }

        unused_flags->insert(*it);
    }
}

// 5) Output matches (and helper methods)

static void OutputSingleGroupWithLimit(
    const set<const CommandLineFlagInfo*>& group,
    const string& line_indentation,
    const string& header,
    const string& footer,
    bool long_output_format,
    int* remaining_line_limit,
    size_t* completion_elements_output,
    vector<string>* completions) {
    if (group.empty()) {
        return;
    }

    if (!header.empty()) {
        if (*remaining_line_limit < 2) {
            return;
        }

        *remaining_line_limit -= 2;
        completions->push_back(line_indentation + header);
        completions->push_back(line_indentation + string(header.size(), '-'));
    }

    for (set<const CommandLineFlagInfo*>::const_iterator it = group.begin();
            it != group.end() && *remaining_line_limit > 0;
            ++it) {
        --*remaining_line_limit;
        ++*completion_elements_output;
        completions->push_back(
            (long_output_format
             ? GetLongFlagLine(line_indentation, **it)
             : GetShortFlagLine(line_indentation, **it)));
    }

    if (!footer.empty()) {
        if (*remaining_line_limit < 1) {
            return;
        }

        --*remaining_line_limit;
        completions->push_back(line_indentation + footer);
    }
}

static string GetShortFlagLine(
    const string& line_indentation,
    const CommandLineFlagInfo& info) {
    string prefix;
    bool is_string = (info.type == "string");
    SStringPrintf(&prefix, "%s--%s [%s%s%s] ",
                  line_indentation.c_str(),
                  info.name.c_str(),
                  (is_string ? "'" : ""),
                  info.default_value.c_str(),
                  (is_string ? "'" : ""));
    int remainder =
        FLAGS_tab_completion_columns - static_cast<int>(prefix.size());
    string suffix;

    if (remainder > 0)
        suffix =
            (static_cast<int>(info.description.size()) > remainder ?
             (info.description.substr(0, remainder - 3) + "...").c_str() :
             info.description.c_str());

    return prefix + suffix;
}

static string GetLongFlagLine(
    const string& line_indentation,
    const CommandLineFlagInfo& info) {

    string output = DescribeOneFlag(info);

    // Replace '-' with '--', and remove trailing newline before appending
    // the module definition location.
    string old_flagname = "-" + info.name;
    output.replace(
        output.find(old_flagname),
        old_flagname.size(),
        "-" + old_flagname);
    // Stick a newline and indentation in front of the type and default
    // portions of DescribeOneFlag()s description
    static const char kNewlineWithIndent[] = "\n    ";
    output.replace(output.find(" type:"), 1, string(kNewlineWithIndent));
    output.replace(output.find(" default:"), 1, string(kNewlineWithIndent));
    output = StringPrintf("%s Details for '--%s':\n"
                          "%s    defined: %s",
                          line_indentation.c_str(),
                          info.name.c_str(),
                          output.c_str(),
                          info.filename.c_str());

    // Eliminate any doubled newlines that crept in.  Specifically, if
    // DescribeOneFlag() decided to break the line just before "type"
    // or "default", we don't want to introduce an extra blank line
    static const string line_of_spaces(FLAGS_tab_completion_columns, ' ');
    static const char kDoubledNewlines[] = "\n     \n";

    for (string::size_type newlines = output.find(kDoubledNewlines);
            newlines != string::npos;
            newlines = output.find(kDoubledNewlines))
        // Replace each 'doubled newline' with a single newline
    {
        output.replace(newlines, sizeof(kDoubledNewlines) - 1, string("\n"));
    }

    for (string::size_type newline = output.find('\n');
            newline != string::npos;
            newline = output.find('\n')) {
        int newline_pos = static_cast<int>(newline) % FLAGS_tab_completion_columns;
        int missing_spaces = FLAGS_tab_completion_columns - newline_pos;
        output.replace(newline, 1, line_of_spaces, 1, missing_spaces);
    }

    return output;
}
}  // anonymous

void HandleCommandLineCompletions(void) {
    if (FLAGS_tab_completion_word.empty()) {
        return;
    }

    PrintFlagCompletionInfo();
    gflags_exitfunc(0);
}

_END_GOOGLE_NAMESPACE_
