123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- #include "completer.h"
- #include "completion_generator.h"
- #include <util/string/cast.h>
- #include <util/generic/fwd.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::NComp {
- #define L out.Line()
- #define I auto Y_GENERATE_UNIQUE_ID(indent) = out.Indent()
- TCompleterManager::TCompleterManager(TStringBuf command)
- : Command_(command)
- , Id_(0)
- {
- }
- TStringBuf TCompleterManager::GetCompleterID(const ICompleter* completer) {
- return Queue_.emplace_back(TStringBuilder() << "_" << Command_ << "__completer_" << ++Id_, completer).first;
- }
- void TCompleterManager::GenerateZsh(TFormattedOutput& out) {
- while (!Queue_.empty()) {
- auto[name, completer] = Queue_.back();
- Queue_.pop_back();
- L << "(( $+functions[" << name << "] )) ||";
- L << name << "() {";
- {
- I;
- completer->GenerateZsh(out, *this);
- }
- L << "}";
- L;
- }
- }
- class TAlternativeCompleter: public ICompleter {
- public:
- TAlternativeCompleter(TVector<TAlternative> alternatives)
- : Alternatives_(std::move(alternatives))
- {
- }
- void GenerateBash(TFormattedOutput& out) const override {
- for (auto& alternative: Alternatives_) {
- if (alternative.Completer != nullptr) {
- alternative.Completer->GenerateBash(out);
- }
- }
- }
- TStringBuf GenerateZshAction(TCompleterManager& manager) const override {
- return manager.GetCompleterID(this);
- }
- void GenerateZsh(TFormattedOutput& out, TCompleterManager& manager) const override {
- // We should use '_alternative' here, but it doesn't process escape codes in group descriptions,
- // so we dispatch alternatives ourselves.
- L << "local expl action";
- size_t i = 0;
- for (auto& alternative: Alternatives_) {
- auto tag = "alt-" + ToString(++i);
- auto action = alternative.Completer ? alternative.Completer->GenerateZshAction(manager) : TStringBuf();
- L;
- if (action.empty()) {
- L << "_message -e " << SS(tag) << " " << SS(alternative.Description);
- } else if (action.StartsWith("((") && action.EndsWith("))")) {
- L << "action=" << action.substr(1, action.size() - 2);
- L << "_describe -t " << SS(tag) << " " << SS(alternative.Description) << " action -M 'r:|[_-]=* r:|=*'";
- } else if (action.StartsWith("(") && action.EndsWith(")")) {
- L << "action=" << action << "";
- L << "_describe -t " << SS(tag) << " " << SS(alternative.Description) << " action -M 'r:|[_-]=* r:|=*'";
- } else if (action.StartsWith(' ')) {
- L << action.substr(1);
- } else {
- L << "_description " << SS(tag) << " expl " << SS(alternative.Description);
- TStringBuf word, args;
- action.Split(' ', word, args);
- L << word << " \"${expl[@]}\" " << args;
- }
- }
- }
- private:
- TVector<TAlternative> Alternatives_;
- };
- ICompleterPtr Alternative(TVector<TAlternative> alternatives) {
- return MakeSimpleShared<TAlternativeCompleter>(std::move(alternatives));
- }
- class TSimpleCompleter: public ICompleter {
- public:
- TSimpleCompleter(TString bashCode, TString action)
- : BashCode(std::move(bashCode))
- , Action(std::move(action))
- {
- }
- void GenerateBash(TFormattedOutput& out) const override {
- if (BashCode) {
- L << BashCode;
- }
- }
- TStringBuf GenerateZshAction(TCompleterManager&) const override {
- return Action;
- }
- void GenerateZsh(TFormattedOutput&, TCompleterManager&) const override {
- Y_ABORT("unreachable");
- }
- private:
- TString BashCode;
- TString Action;
- };
- ICompleterPtr Choice(TVector<TChoice> choices) {
- auto bash = TStringBuilder() << "COMPREPLY+=( $(compgen -W '";
- TStringBuf sep = "";
- for (auto& choice : choices) {
- bash << sep << B(choice.Choice);
- sep = " ";
- }
- bash << "' -- ${cur}) )";
- auto action = TStringBuilder();
- action << "((";
- for (auto& choice: choices) {
- action << " " << SS(choice.Choice);
- if (choice.Description) {{
- action << ":" << SS(choice.Description);
- }}
- }
- action << "))";
- return MakeSimpleShared<TSimpleCompleter>(bash, action);
- }
- TString Compgen(TStringBuf flags) {
- return TStringBuilder() << "COMPREPLY+=( $(compgen " << flags << " -- ${cur}) )";
- }
- ICompleterPtr Default() {
- return MakeSimpleShared<TSimpleCompleter>("", "_default");
- }
- ICompleterPtr File(TString pattern) {
- if (pattern) {
- pattern = " -g " + SS(pattern);
- }
- return MakeSimpleShared<TSimpleCompleter>("", "_files" + pattern);
- }
- ICompleterPtr Directory() {
- return MakeSimpleShared<TSimpleCompleter>("", "_files -/");
- }
- ICompleterPtr Host() {
- return MakeSimpleShared<TSimpleCompleter>(Compgen("-A hostname"), "_hosts");
- }
- ICompleterPtr Pid() {
- return MakeSimpleShared<TSimpleCompleter>("", "_pids");
- }
- ICompleterPtr User() {
- return MakeSimpleShared<TSimpleCompleter>(Compgen("-A user"), "_users");
- }
- ICompleterPtr Group() {
- // For some reason, OSX freezes when trying to perform completion for groups.
- // You can try removing this ifdef and debugging it, but be prepared to force-shutdown your machine
- // (and possibly reinstall OSX if force-shutdown breaks anything).
- #ifdef _darwin_
- return MakeSimpleShared<TSimpleCompleter>("", "");
- #else
- return MakeSimpleShared<TSimpleCompleter>(Compgen("-A group"), "_groups");
- #endif
- }
- ICompleterPtr Url() {
- return MakeSimpleShared<TSimpleCompleter>("", "_urls");
- }
- ICompleterPtr Tty() {
- return MakeSimpleShared<TSimpleCompleter>("", "_ttys");
- }
- ICompleterPtr NetInterface() {
- return MakeSimpleShared<TSimpleCompleter>("", "_net_interfaces");
- }
- ICompleterPtr TimeZone() {
- return MakeSimpleShared<TSimpleCompleter>("", "_time_zone");
- }
- ICompleterPtr Signal() {
- return MakeSimpleShared<TSimpleCompleter>(Compgen("-A signal"), "_signals");
- }
- ICompleterPtr Domain() {
- return MakeSimpleShared<TSimpleCompleter>("", "_domains");
- }
- namespace {
- TCustomCompleter* Head = nullptr;
- TStringBuf SpecialFlag = "---CUSTOM-COMPLETION---";
- }
- void TCustomCompleter::FireCustomCompleter(int argc, const char** argv) {
- if (!argc) {
- return;
- }
- for (int i = 1; i < argc - 4; ++i) {
- if (SpecialFlag == argv[i]) {
- auto name = TStringBuf(argv[i + 1]);
- auto curIdx = FromString<int>(argv[i + 2]);
- auto prefix = TStringBuf(argv[i + 3]);
- auto suffix = TStringBuf(argv[i + 4]);
- auto cur = TStringBuf();
- if (0 <= curIdx && curIdx < i) {
- cur = TStringBuf(argv[curIdx]);
- }
- if (cur && !prefix && !suffix) {
- prefix = cur; // bash does not send prefix and suffix
- }
- auto head = Head;
- while (head) {
- if (head->GetUniqueName() == name) {
- head->GenerateCompletions(i, argv, curIdx, cur, prefix, suffix);
- }
- head = head->Next_;
- }
- exit(0);
- }
- }
- }
- void TCustomCompleter::RegisterCustomCompleter(TCustomCompleter* completer) noexcept {
- Y_ABORT_UNLESS(completer);
- completer->Next_ = Head;
- Head = completer;
- }
- void TCustomCompleter::AddCompletion(TStringBuf completion) {
- Cout << completion << Endl; // this was easy =)
- // TODO: support option descriptions and messages
- }
- void TMultipartCustomCompleter::GenerateCompletions(int argc, const char** argv, int curIdx, TStringBuf cur, TStringBuf prefix, TStringBuf suffix) {
- auto root = TStringBuf();
- if (prefix.Contains(Sep_)) {
- auto tmp = TStringBuf();
- prefix.RSplit(Sep_, root, tmp);
- }
- if (root) {
- Cout << root << Sep_ << Endl;
- } else {
- Cout << Endl;
- }
- Cout << Sep_ << Endl;
- GenerateCompletionParts(argc, argv, curIdx, cur, prefix, suffix, root);
- }
- class TLaunchSelf: public ICompleter {
- public:
- TLaunchSelf(TCustomCompleter* completer)
- : Completer_(completer)
- {
- }
- void GenerateBash(TFormattedOutput& out) const override {
- L << "IFS=$'\\n'";
- L << "COMPREPLY+=( $(compgen -W \"$(${words[@]} " << SpecialFlag << " " << Completer_->GetUniqueName() << " \"${cword}\" \"\" \"\" 2> /dev/null)\" -- ${cur}) )";
- L << "IFS=$' \\t\\n'";
- }
- TStringBuf GenerateZshAction(TCompleterManager& manager) const override {
- return manager.GetCompleterID(this);
- }
- void GenerateZsh(TFormattedOutput& out, TCompleterManager&) const override {
- L << "compadd ${@} ${expl[@]} -- \"${(@f)$(${words_orig[@]} " << SpecialFlag << " " << Completer_->GetUniqueName() << " \"${current_orig}\" \"${prefix_orig}\" \"${suffix_orig}\" 2> /dev/null)}\"";
- }
- private:
- TCustomCompleter* Completer_;
- };
- ICompleterPtr LaunchSelf(TCustomCompleter& completer) {
- return MakeSimpleShared<TLaunchSelf>(&completer);
- }
- class TLaunchSelfMultiPart: public ICompleter {
- public:
- TLaunchSelfMultiPart(TCustomCompleter* completer)
- : Completer_(completer)
- {
- }
- void GenerateBash(TFormattedOutput& out) const override {
- L << "IFS=$'\\n'";
- L << "items=( $(${words[@]} " << SpecialFlag << " " << Completer_->GetUniqueName() << " \"${cword}\" \"\" \"\" 2> /dev/null) )";
- L << "candidates=$(compgen -W \"${items[*]:1}\" -- \"$cur\")";
- L << "COMPREPLY+=( $candidates )";
- L << "[[ $candidates == *\"${items[1]}\" ]] && need_space=\"\"";
- L << "IFS=$' \\t\\n'";
- }
- TStringBuf GenerateZshAction(TCompleterManager& manager) const override {
- return manager.GetCompleterID(this);
- }
- void GenerateZsh(TFormattedOutput& out, TCompleterManager&) const override {
- L << "local items=( \"${(@f)$(${words_orig[@]} " << SpecialFlag << " " << Completer_->GetUniqueName() << " \"${current_orig}\" \"${prefix_orig}\" \"${suffix_orig}\" 2> /dev/null)}\" )";
- L;
- L << "local rempat=${items[1]}";
- L << "shift items";
- L;
- L << "local sep=${items[1]}";
- L << "shift items";
- L;
- L << "local files=( ${items:#*\"${sep}\"} )";
- L << "local filenames=( ${files#\"${rempat}\"} )";
- L << "local dirs=( ${(M)items:#*\"${sep}\"} )";
- L << "local dirnames=( ${dirs#\"${rempat}\"} )";
- L;
- L << "local need_suf";
- L << "compset -S \"${sep}*\" || need_suf=\"1\"";
- L;
- L << "compadd ${@} ${expl[@]} -d filenames -- ${(q)files}";
- L << "compadd ${@} ${expl[@]} ${need_suf:+-S\"${sep}\"} -q -d dirnames -- ${(q)dirs%\"${sep}\"}";
- }
- private:
- TCustomCompleter* Completer_;
- };
- ICompleterPtr LaunchSelfMultiPart(TCustomCompleter& completer) {
- return MakeSimpleShared<TLaunchSelfMultiPart>(&completer);
- }
- #undef I
- #undef L
- }
|