#include "completion_generator.h" #include #include #include #include "last_getopt_parse_result.h" using NLastGetopt::NEscaping::Q; using NLastGetopt::NEscaping::QQ; using NLastGetopt::NEscaping::C; using NLastGetopt::NEscaping::CC; using NLastGetopt::NEscaping::S; using NLastGetopt::NEscaping::SS; using NLastGetopt::NEscaping::B; using NLastGetopt::NEscaping::BB; namespace NLastGetopt { #define L out.Line() #define I auto Y_GENERATE_UNIQUE_ID(indent) = out.Indent() TCompletionGenerator::TCompletionGenerator(const TModChooser* modChooser) : Options_(modChooser) { Y_ABORT_UNLESS(modChooser != nullptr); } TCompletionGenerator::TCompletionGenerator(const TOpts* opts) : Options_(opts) { Y_ABORT_UNLESS(opts != nullptr); } void TZshCompletionGenerator::Generate(TStringBuf command, IOutputStream& stream) { TFormattedOutput out; NComp::TCompleterManager manager{command}; L << "#compdef " << command; L; L << "_" << command << "() {"; { I; L << "local state line desc modes context curcontext=\"$curcontext\" ret=1"; L << "local words_orig=(\"${words[@]}\")"; L << "local current_orig=\"$((CURRENT - 1))\""; L << "local prefix_orig=\"$PREFIX\""; L << "local suffix_orig=\"$SUFFIX\""; L; std::visit(TOverloaded{ [&out, &manager](const TModChooser* modChooser) { GenerateModesCompletion(out, *modChooser, manager); }, [&out, &manager](const TOpts* opts) { GenerateOptsCompletion(out, *opts, manager); } }, Options_); L; L << "return ret"; } L << "}"; L; manager.GenerateZsh(out); out.Print(stream); } void TZshCompletionGenerator::GenerateModesCompletion(TFormattedOutput& out, const TModChooser& chooser, NComp::TCompleterManager& manager) { auto modes = chooser.GetUnsortedModes(); L << "_arguments -C \\"; L << " '(- : *)'{-h,--help}'[show help information]' \\"; if (chooser.GetVersionHandler() != nullptr) { L << " '(- : *)'{-v,--version}'[display version information]' \\"; } if (!chooser.IsSvnRevisionOptionDisabled()) { L << " '(- : *)--svnrevision[show build information]' \\"; } L << " '(-v --version -h --help --svnrevision)1: :->modes' \\"; L << " '(-v --version -h --help --svnrevision)*:: :->args' \\"; L << " && ret=0"; L; L << "case $state in"; { I; L << "modes)"; { I; size_t tag = 0; bool empty = true; L << "desc='modes'"; L << "modes=("; for (auto& mode : modes) { if (mode->Hidden) { continue; } if (!mode->Name.empty()) { I; if (!mode->Description.empty()) { L << QQ(mode->Name) << ":" << QQ(mode->Description); } else { L << QQ(mode->Name); } empty = false; } else { L << ")"; if (!empty) { L << "_describe -t 'mode-group-" << tag << "' $desc modes"; } L; if (mode->Description.empty()) { L << "desc='modes'"; } else { L << "desc=" << SS(mode->Description); } L << "modes=("; empty = true; ++tag; } } L << ")"; if (!empty) { L << "_describe -t 'mode-group-" << tag << "' $desc modes"; } L; L << ";;"; } L << "args)"; { I; L << "case $line[1] in"; { I; for (auto& mode : modes) { if (mode->Name.empty() || mode->Hidden) { continue; } auto& line = L << SS(mode->Name); for (auto& alias : mode->Aliases) { line << "|" << SS(alias); } line << ")"; { I; if (auto mainArgs = dynamic_cast(mode->Main)) { GenerateOptsCompletion(out, mainArgs->GetOptions(), manager); } else if (auto mainModes = dynamic_cast(mode->Main)) { GenerateModesCompletion(out, mainModes->GetSubModes(), manager); } else { GenerateDefaultOptsCompletion(out, manager); } L << ";;"; } } } L << "esac"; L << ";;"; } } L << "esac"; } void TZshCompletionGenerator::GenerateOptsCompletion(TFormattedOutput& out, const TOpts& opts, NComp::TCompleterManager& manager) { L << "_arguments -s \\"; { I; if (opts.ArgPermutation_ == EArgPermutation::REQUIRE_ORDER) { L << "-S \\"; } for (auto opt: opts.GetOpts()) { if (!opt->Hidden_) { GenerateOptCompletion(out, opts, *opt, manager); } } auto argSpecs = opts.GetFreeArgSpecs(); size_t numFreeArgs = opts.GetFreeArgsMax(); bool unlimitedArgs = false; if (numFreeArgs == TOpts::UNLIMITED_ARGS) { numFreeArgs = argSpecs.empty() ? 0 : (argSpecs.rbegin()->first + 1); unlimitedArgs = true; } for (size_t i = 0; i < numFreeArgs; ++i) { auto& spec = argSpecs[i]; auto& line = L << "'" << (i + 1) << ":"; if (spec.IsOptional()) { line << ":"; } auto argHelp = spec.GetCompletionArgHelp(opts.GetDefaultFreeArgTitle()); if (argHelp) { line << Q(argHelp); } else { line << " "; } line << ":"; if (spec.Completer_) { line << spec.Completer_->GenerateZshAction(manager); } else { line << "_default"; } line << "' \\"; } if (unlimitedArgs) { auto& spec = opts.GetTrailingArgSpec(); auto& line = L << "'*:"; auto argHelp = spec.GetCompletionArgHelp(opts.GetDefaultFreeArgTitle()); if (argHelp) { line << Q(argHelp); } else { line << " "; } line << ":"; if (spec.Completer_) { line << spec.Completer_->GenerateZshAction(manager); } else { line << "_default"; } line << "' \\"; } L << "&& ret=0"; } } void TZshCompletionGenerator::GenerateDefaultOptsCompletion(TFormattedOutput& out, NComp::TCompleterManager&) { L << "_arguments \\"; L << " '(- *)'{-h,--help}'[show help information]' \\"; L << " '(- *)--svnrevision[show build information]' \\"; L << " '(-h --help --svnrevision)*: :_files' \\"; L << " && ret=0"; } void TZshCompletionGenerator::GenerateOptCompletion(TFormattedOutput& out, const TOpts& opts, const TOpt& opt, NComp::TCompleterManager& manager) { auto& line = L; THashSet disableOptions; if (opt.DisableCompletionForOptions_) { disableOptions.insert("-"); } else { if (!opt.AllowMultipleCompletion_) { for (auto shortName: opt.GetShortNames()) { disableOptions.insert(TString("-") + shortName); } for (auto& longName: opt.GetLongNames()) { disableOptions.insert("--" + longName); } } for (auto disabledShortName : opt.DisableCompletionForChar_) { auto disabledOpt = opts.FindCharOption(disabledShortName); if (disabledOpt) { for (auto shortName: disabledOpt->GetShortNames()) { disableOptions.insert(TString("-") + shortName); } for (auto& longName: disabledOpt->GetLongNames()) { disableOptions.insert("--" + longName); } } else { disableOptions.insert(TString("-") + disabledShortName); } } for (auto& disabledLongName : opt.DisableCompletionForLongName_) { auto disabledOpt = opts.FindLongOption(disabledLongName); if (disabledOpt) { for (auto shortName: disabledOpt->GetShortNames()) { disableOptions.insert(TString("-") + shortName); } for (auto& longName: disabledOpt->GetLongNames()) { disableOptions.insert("--" + longName); } } else { disableOptions.insert("--" + disabledLongName); } } } if (opt.DisableCompletionForFreeArgs_) { disableOptions.insert(":"); disableOptions.insert("*"); } else { for (auto i : opt.DisableCompletionForFreeArg_) { disableOptions.insert(ToString(i + 1)); } } TStringBuf sep = ""; if (!disableOptions.empty()) { line << "'("; for (auto& disableOption : disableOptions) { line << sep << disableOption; sep = " "; } line << ")"; } sep = ""; TStringBuf mul = ""; TStringBuf quot = ""; if (opt.GetShortNames().size() + opt.GetLongNames().size() > 1) { if (!disableOptions.empty()) { line << "'"; } line << "{"; quot = "'"; } else { if (disableOptions.empty()) { line << "'"; } } if (opt.AllowMultipleCompletion_) { mul = "*"; } for (auto& flag : opt.GetShortNames()) { line << sep << quot << mul << "-" << Q(TStringBuf(&flag, 1)) << quot; sep = ","; } for (auto& flag : opt.GetLongNames()) { line << sep << quot << mul << "--" << Q(flag) << quot; sep = ","; } if (opt.GetShortNames().size() + opt.GetLongNames().size() > 1) { line << "}'"; } if (opt.GetCompletionHelp()) { line << "["; line << Q(opt.GetCompletionHelp()); line << "]"; } if (opt.HasArg_ != EHasArg::NO_ARGUMENT) { if (opt.HasArg_ == EHasArg::OPTIONAL_ARGUMENT) { line << ":"; } line << ":"; if (opt.GetCompletionArgHelp()) { line << C(opt.GetCompletionArgHelp()); } else { line << " "; } line << ":"; if (opt.Completer_) { line << C(opt.Completer_->GenerateZshAction(manager)); } else { line << "_default"; } } line << "' \\"; } void TBashCompletionGenerator::Generate(TStringBuf command, IOutputStream& stream) { TFormattedOutput out; NComp::TCompleterManager manager{command}; L << "_" << command << "() {"; { I; L << "COMPREPLY=()"; L; L << "local i args opts items candidates"; L; L << "local cur prev words cword"; L << "_get_comp_words_by_ref -n \"\\\"'><=;|&(:\" cur prev words cword"; L; L << "local need_space=\"1\""; L << "local IFS=$' \\t\\n'"; L; std::visit(TOverloaded{ [&out, &manager](const TModChooser* modChooser) { GenerateModesCompletion(out, *modChooser, manager, 1); }, [&out, &manager](const TOpts* opts) { GenerateOptsCompletion(out, *opts, manager, 1); } }, Options_); L; L; L << "__ltrim_colon_completions \"$cur\""; L; L << "IFS=$'\\n'"; L << "if [ ${#COMPREPLY[@]} -ne 0 ]; then"; { I; L << "if [[ -z $need_space ]]; then"; { I; L << "COMPREPLY=( $(printf \"%q\\n\" \"${COMPREPLY[@]}\") )"; } L << "else"; { I; L << "COMPREPLY=( $(printf \"%q \\n\" \"${COMPREPLY[@]}\") )"; } L << "fi"; } L << "fi"; L; L << "return 0"; } L << "}"; L; L << "complete -o nospace -o default -F _" << command << " " << command; out.Print(stream); } void TBashCompletionGenerator::GenerateModesCompletion(TFormattedOutput& out, const TModChooser& chooser, NComp::TCompleterManager& manager, size_t level) { auto modes = chooser.GetUnsortedModes(); L << "if [[ ${cword} == " << level << " ]] ; then"; { I; L << "if [[ ${cur} == -* ]] ; then"; { I; auto& line = L << "COMPREPLY+=( $(compgen -W '-h --help"; if (chooser.GetVersionHandler() != nullptr) { line << " -v --version"; } if (!chooser.IsSvnRevisionOptionDisabled()) { line << " --svnrevision"; } line << "' -- ${cur}) )"; } L << "else"; { I; auto& line = L << "COMPREPLY+=( $(compgen -W '"; TStringBuf sep = ""; for (auto& mode : modes) { if (!mode->Hidden && !mode->NoCompletion) { line << sep << B(mode->Name); sep = " "; } } line << "' -- ${cur}) )"; } L << "fi"; } L << "else"; { I; L << "case \"${words[" << level << "]}\" in"; { I; for (auto& mode : modes) { if (mode->Name.empty() || mode->Hidden || mode->NoCompletion) { continue; } auto& line = L << BB(mode->Name); for (auto& alias : mode->Aliases) { line << "|" << BB(alias); } line << ")"; { I; if (auto mainArgs = dynamic_cast(mode->Main)) { GenerateOptsCompletion(out, mainArgs->GetOptions(), manager, level + 1); } else if (auto mainModes = dynamic_cast(mode->Main)) { GenerateModesCompletion(out, mainModes->GetSubModes(), manager, level + 1); } else { GenerateDefaultOptsCompletion(out, manager); } L << ";;"; } } } L << "esac"; } L << "fi"; } void TBashCompletionGenerator::GenerateOptsCompletion(TFormattedOutput& out, const TOpts& opts, NComp::TCompleterManager&, size_t level) { auto unorderedOpts = opts.GetOpts(); L << "if [[ ${cur} == -* ]] ; then"; { I; auto& line = L << "COMPREPLY+=( $(compgen -W '"; TStringBuf sep = ""; for (auto& opt : unorderedOpts) { if (opt->IsHidden()) { continue; } for (auto& shortName : opt->GetShortNames()) { line << sep << "-" << B(TStringBuf(&shortName, 1)); sep = " "; } for (auto& longName: opt->GetLongNames()) { line << sep << "--" << B(longName); sep = " "; } } line << "' -- ${cur}) )"; } L << "else"; { I; L << "case ${prev} in"; { I; for (auto& opt : unorderedOpts) { if (opt->HasArg_ == EHasArg::NO_ARGUMENT || opt->IsHidden()) { continue; } auto& line = L; TStringBuf sep = ""; for (auto& shortName : opt->GetShortNames()) { line << sep << "'-" << B(TStringBuf(&shortName, 1)) << "'"; sep = "|"; } for (auto& longName: opt->GetLongNames()) { line << sep << "'--" << B(longName) << "'"; sep = "|"; } line << ")"; { I; if (opt->Completer_ != nullptr) { opt->Completer_->GenerateBash(out); } L << ";;"; } } L << "*)"; { I; L << "args=0"; auto& line = L << "opts='@("; TStringBuf sep = ""; for (auto& opt : unorderedOpts) { if (opt->HasArg_ == EHasArg::NO_ARGUMENT || opt->IsHidden()) { continue; } for (auto& shortName : opt->GetShortNames()) { line << sep << "-" << B(TStringBuf(&shortName, 1)); sep = "|"; } for (auto& longName: opt->GetLongNames()) { line << sep << "--" << B(longName); sep = "|"; } } line << ")'"; L << "for (( i=" << level << "; i < cword; i++ )); do"; { I; L << "if [[ ${words[i]} != -* && ${words[i-1]} != $opts ]]; then"; { I; L << "(( args++ ))"; } L << "fi"; } L << "done"; L; auto argSpecs = opts.GetFreeArgSpecs(); size_t numFreeArgs = opts.GetFreeArgsMax(); bool unlimitedArgs = false; if (numFreeArgs == TOpts::UNLIMITED_ARGS) { numFreeArgs = argSpecs.empty() ? 0 : (argSpecs.rbegin()->first + 1); unlimitedArgs = true; } L << "case ${args} in"; { I; for (size_t i = 0; i < numFreeArgs; ++i) { L << i << ")"; { I; auto& spec = argSpecs[i]; if (spec.Completer_ != nullptr) { spec.Completer_->GenerateBash(out); } L << ";;"; } } if (unlimitedArgs) { L << "*)"; { I; auto& spec = opts.GetTrailingArgSpec(); if (spec.Completer_ != nullptr) { spec.Completer_->GenerateBash(out); } L << ";;"; } } } L << "esac"; L << ";;"; } } L << "esac"; } L << "fi"; } void TBashCompletionGenerator::GenerateDefaultOptsCompletion(TFormattedOutput& out, NComp::TCompleterManager&) { L << "if [[ ${cur} == -* ]] ; then"; { I; L << "COMPREPLY+=( $(compgen -W '-h --help --svnrevision' -- ${cur}) )"; } L << "fi"; } #undef I #undef L TString NEscaping::Q(TStringBuf string) { TStringBuilder out; out.reserve(string.size()); for (auto c: string) { switch (c) { case '\a': case '\b': case '\f': case '\n': case '\r': case '\t': case '\v': out << " "; break; case '\\': out << "\\\\"; break; case '\'': out << "''"; break; case '\"': out << "\\\""; break; case '[': out << "\\["; break; case ']': out << "\\]"; break; case ':': out << "\\:"; break; case '+': out << "\\+"; break; case '=': out << "\\="; break; default: out << c; break; } } return out; } TString NEscaping::QQ(TStringBuf string) { auto q = Q(string); return "'" + q + "'"; } TString NEscaping::C(TStringBuf string) { TStringBuilder out; out.reserve(string.size() + 1); for (auto c: string) { switch (c) { case '\a': case '\b': case '\f': case '\n': case '\r': case '\t': case '\v': out << " "; break; case '\'': out << "''"; break; case ':': out << "\\:"; break; default: out << c; break; } } return out; } TString NEscaping::CC(TStringBuf string) { auto c = C(string); return "'" + c + "'"; } TString NEscaping::S(TStringBuf string) { TStringBuilder out; out.reserve(string.size() + 1); for (auto c: string) { switch (c) { case '\a': case '\b': case '\f': case '\n': case '\r': case '\t': case '\v': out << " "; break; case '\'': out << "''"; break; default: out << c; break; } } return out; } TString NEscaping::SS(TStringBuf string) { auto s = S(string); return "'" + s + "'"; } TString NEscaping::B(TStringBuf string) { TStringBuilder out; out.reserve(string.size() + 1); for (auto c: string) { switch (c) { case '\a': case '\b': case '\f': case '\n': case '\r': case '\t': case '\v': out << " "; break; case '\'': out << "'\"'\"'"; break; default: out << c; break; } } return out; } TString NEscaping::BB(TStringBuf string) { auto b = B(string); return "'" + b + "'"; } }