#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_FAIL("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_VERIFY(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
}