completer.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. #include "completer.h"
  2. #include "completion_generator.h"
  3. #include <util/string/cast.h>
  4. #include <util/generic/fwd.h>
  5. using NLastGetopt::NEscaping::Q;
  6. using NLastGetopt::NEscaping::QQ;
  7. using NLastGetopt::NEscaping::C;
  8. using NLastGetopt::NEscaping::CC;
  9. using NLastGetopt::NEscaping::S;
  10. using NLastGetopt::NEscaping::SS;
  11. using NLastGetopt::NEscaping::B;
  12. using NLastGetopt::NEscaping::BB;
  13. namespace NLastGetopt::NComp {
  14. #define L out.Line()
  15. #define I auto Y_GENERATE_UNIQUE_ID(indent) = out.Indent()
  16. TCompleterManager::TCompleterManager(TStringBuf command)
  17. : Command_(command)
  18. , Id_(0)
  19. {
  20. }
  21. TStringBuf TCompleterManager::GetCompleterID(const ICompleter* completer) {
  22. return Queue_.emplace_back(TStringBuilder() << "_" << Command_ << "__completer_" << ++Id_, completer).first;
  23. }
  24. void TCompleterManager::GenerateZsh(TFormattedOutput& out) {
  25. while (!Queue_.empty()) {
  26. auto[name, completer] = Queue_.back();
  27. Queue_.pop_back();
  28. L << "(( $+functions[" << name << "] )) ||";
  29. L << name << "() {";
  30. {
  31. I;
  32. completer->GenerateZsh(out, *this);
  33. }
  34. L << "}";
  35. L;
  36. }
  37. }
  38. class TAlternativeCompleter: public ICompleter {
  39. public:
  40. TAlternativeCompleter(TVector<TAlternative> alternatives)
  41. : Alternatives_(std::move(alternatives))
  42. {
  43. }
  44. void GenerateBash(TFormattedOutput& out) const override {
  45. for (auto& alternative: Alternatives_) {
  46. if (alternative.Completer != nullptr) {
  47. alternative.Completer->GenerateBash(out);
  48. }
  49. }
  50. }
  51. TStringBuf GenerateZshAction(TCompleterManager& manager) const override {
  52. return manager.GetCompleterID(this);
  53. }
  54. void GenerateZsh(TFormattedOutput& out, TCompleterManager& manager) const override {
  55. // We should use '_alternative' here, but it doesn't process escape codes in group descriptions,
  56. // so we dispatch alternatives ourselves.
  57. L << "local expl action";
  58. size_t i = 0;
  59. for (auto& alternative: Alternatives_) {
  60. auto tag = "alt-" + ToString(++i);
  61. auto action = alternative.Completer ? alternative.Completer->GenerateZshAction(manager) : TStringBuf();
  62. L;
  63. if (action.empty()) {
  64. L << "_message -e " << SS(tag) << " " << SS(alternative.Description);
  65. } else if (action.StartsWith("((") && action.EndsWith("))")) {
  66. L << "action=" << action.substr(1, action.size() - 2);
  67. L << "_describe -t " << SS(tag) << " " << SS(alternative.Description) << " action -M 'r:|[_-]=* r:|=*'";
  68. } else if (action.StartsWith("(") && action.EndsWith(")")) {
  69. L << "action=" << action << "";
  70. L << "_describe -t " << SS(tag) << " " << SS(alternative.Description) << " action -M 'r:|[_-]=* r:|=*'";
  71. } else if (action.StartsWith(' ')) {
  72. L << action.substr(1);
  73. } else {
  74. L << "_description " << SS(tag) << " expl " << SS(alternative.Description);
  75. TStringBuf word, args;
  76. action.Split(' ', word, args);
  77. L << word << " \"${expl[@]}\" " << args;
  78. }
  79. }
  80. }
  81. private:
  82. TVector<TAlternative> Alternatives_;
  83. };
  84. ICompleterPtr Alternative(TVector<TAlternative> alternatives) {
  85. return MakeSimpleShared<TAlternativeCompleter>(std::move(alternatives));
  86. }
  87. class TSimpleCompleter: public ICompleter {
  88. public:
  89. TSimpleCompleter(TString bashCode, TString action)
  90. : BashCode(std::move(bashCode))
  91. , Action(std::move(action))
  92. {
  93. }
  94. void GenerateBash(TFormattedOutput& out) const override {
  95. if (BashCode) {
  96. L << BashCode;
  97. }
  98. }
  99. TStringBuf GenerateZshAction(TCompleterManager&) const override {
  100. return Action;
  101. }
  102. void GenerateZsh(TFormattedOutput&, TCompleterManager&) const override {
  103. Y_ABORT("unreachable");
  104. }
  105. private:
  106. TString BashCode;
  107. TString Action;
  108. };
  109. ICompleterPtr Choice(TVector<TChoice> choices) {
  110. auto bash = TStringBuilder() << "COMPREPLY+=( $(compgen -W '";
  111. TStringBuf sep = "";
  112. for (auto& choice : choices) {
  113. bash << sep << B(choice.Choice);
  114. sep = " ";
  115. }
  116. bash << "' -- ${cur}) )";
  117. auto action = TStringBuilder();
  118. action << "((";
  119. for (auto& choice: choices) {
  120. action << " " << SS(choice.Choice);
  121. if (choice.Description) {{
  122. action << ":" << SS(choice.Description);
  123. }}
  124. }
  125. action << "))";
  126. return MakeSimpleShared<TSimpleCompleter>(bash, action);
  127. }
  128. TString Compgen(TStringBuf flags) {
  129. return TStringBuilder() << "COMPREPLY+=( $(compgen " << flags << " -- ${cur}) )";
  130. }
  131. ICompleterPtr Default() {
  132. return MakeSimpleShared<TSimpleCompleter>("", "_default");
  133. }
  134. ICompleterPtr File(TString pattern) {
  135. if (pattern) {
  136. pattern = " -g " + SS(pattern);
  137. }
  138. return MakeSimpleShared<TSimpleCompleter>("", "_files" + pattern);
  139. }
  140. ICompleterPtr Directory() {
  141. return MakeSimpleShared<TSimpleCompleter>("", "_files -/");
  142. }
  143. ICompleterPtr Host() {
  144. return MakeSimpleShared<TSimpleCompleter>(Compgen("-A hostname"), "_hosts");
  145. }
  146. ICompleterPtr Pid() {
  147. return MakeSimpleShared<TSimpleCompleter>("", "_pids");
  148. }
  149. ICompleterPtr User() {
  150. return MakeSimpleShared<TSimpleCompleter>(Compgen("-A user"), "_users");
  151. }
  152. ICompleterPtr Group() {
  153. // For some reason, OSX freezes when trying to perform completion for groups.
  154. // You can try removing this ifdef and debugging it, but be prepared to force-shutdown your machine
  155. // (and possibly reinstall OSX if force-shutdown breaks anything).
  156. #ifdef _darwin_
  157. return MakeSimpleShared<TSimpleCompleter>("", "");
  158. #else
  159. return MakeSimpleShared<TSimpleCompleter>(Compgen("-A group"), "_groups");
  160. #endif
  161. }
  162. ICompleterPtr Url() {
  163. return MakeSimpleShared<TSimpleCompleter>("", "_urls");
  164. }
  165. ICompleterPtr Tty() {
  166. return MakeSimpleShared<TSimpleCompleter>("", "_ttys");
  167. }
  168. ICompleterPtr NetInterface() {
  169. return MakeSimpleShared<TSimpleCompleter>("", "_net_interfaces");
  170. }
  171. ICompleterPtr TimeZone() {
  172. return MakeSimpleShared<TSimpleCompleter>("", "_time_zone");
  173. }
  174. ICompleterPtr Signal() {
  175. return MakeSimpleShared<TSimpleCompleter>(Compgen("-A signal"), "_signals");
  176. }
  177. ICompleterPtr Domain() {
  178. return MakeSimpleShared<TSimpleCompleter>("", "_domains");
  179. }
  180. namespace {
  181. TCustomCompleter* Head = nullptr;
  182. TStringBuf SpecialFlag = "---CUSTOM-COMPLETION---";
  183. }
  184. void TCustomCompleter::FireCustomCompleter(int argc, const char** argv) {
  185. if (!argc) {
  186. return;
  187. }
  188. for (int i = 1; i < argc - 4; ++i) {
  189. if (SpecialFlag == argv[i]) {
  190. auto name = TStringBuf(argv[i + 1]);
  191. auto curIdx = FromString<int>(argv[i + 2]);
  192. auto prefix = TStringBuf(argv[i + 3]);
  193. auto suffix = TStringBuf(argv[i + 4]);
  194. auto cur = TStringBuf();
  195. if (0 <= curIdx && curIdx < i) {
  196. cur = TStringBuf(argv[curIdx]);
  197. }
  198. if (cur && !prefix && !suffix) {
  199. prefix = cur; // bash does not send prefix and suffix
  200. }
  201. auto head = Head;
  202. while (head) {
  203. if (head->GetUniqueName() == name) {
  204. head->GenerateCompletions(i, argv, curIdx, cur, prefix, suffix);
  205. }
  206. head = head->Next_;
  207. }
  208. exit(0);
  209. }
  210. }
  211. }
  212. void TCustomCompleter::RegisterCustomCompleter(TCustomCompleter* completer) noexcept {
  213. Y_ABORT_UNLESS(completer);
  214. completer->Next_ = Head;
  215. Head = completer;
  216. }
  217. void TCustomCompleter::AddCompletion(TStringBuf completion) {
  218. Cout << completion << Endl; // this was easy =)
  219. // TODO: support option descriptions and messages
  220. }
  221. void TMultipartCustomCompleter::GenerateCompletions(int argc, const char** argv, int curIdx, TStringBuf cur, TStringBuf prefix, TStringBuf suffix) {
  222. auto root = TStringBuf();
  223. if (prefix.Contains(Sep_)) {
  224. auto tmp = TStringBuf();
  225. prefix.RSplit(Sep_, root, tmp);
  226. }
  227. if (root) {
  228. Cout << root << Sep_ << Endl;
  229. } else {
  230. Cout << Endl;
  231. }
  232. Cout << Sep_ << Endl;
  233. GenerateCompletionParts(argc, argv, curIdx, cur, prefix, suffix, root);
  234. }
  235. class TLaunchSelf: public ICompleter {
  236. public:
  237. TLaunchSelf(TCustomCompleter* completer)
  238. : Completer_(completer)
  239. {
  240. }
  241. void GenerateBash(TFormattedOutput& out) const override {
  242. L << "IFS=$'\\n'";
  243. L << "COMPREPLY+=( $(compgen -W \"$(${words[@]} " << SpecialFlag << " " << Completer_->GetUniqueName() << " \"${cword}\" \"\" \"\" 2> /dev/null)\" -- ${cur}) )";
  244. L << "IFS=$' \\t\\n'";
  245. }
  246. TStringBuf GenerateZshAction(TCompleterManager& manager) const override {
  247. return manager.GetCompleterID(this);
  248. }
  249. void GenerateZsh(TFormattedOutput& out, TCompleterManager&) const override {
  250. L << "compadd ${@} ${expl[@]} -- \"${(@f)$(${words_orig[@]} " << SpecialFlag << " " << Completer_->GetUniqueName() << " \"${current_orig}\" \"${prefix_orig}\" \"${suffix_orig}\" 2> /dev/null)}\"";
  251. }
  252. private:
  253. TCustomCompleter* Completer_;
  254. };
  255. ICompleterPtr LaunchSelf(TCustomCompleter& completer) {
  256. return MakeSimpleShared<TLaunchSelf>(&completer);
  257. }
  258. class TLaunchSelfMultiPart: public ICompleter {
  259. public:
  260. TLaunchSelfMultiPart(TCustomCompleter* completer)
  261. : Completer_(completer)
  262. {
  263. }
  264. void GenerateBash(TFormattedOutput& out) const override {
  265. L << "IFS=$'\\n'";
  266. L << "items=( $(${words[@]} " << SpecialFlag << " " << Completer_->GetUniqueName() << " \"${cword}\" \"\" \"\" 2> /dev/null) )";
  267. L << "candidates=$(compgen -W \"${items[*]:1}\" -- \"$cur\")";
  268. L << "COMPREPLY+=( $candidates )";
  269. L << "[[ $candidates == *\"${items[1]}\" ]] && need_space=\"\"";
  270. L << "IFS=$' \\t\\n'";
  271. }
  272. TStringBuf GenerateZshAction(TCompleterManager& manager) const override {
  273. return manager.GetCompleterID(this);
  274. }
  275. void GenerateZsh(TFormattedOutput& out, TCompleterManager&) const override {
  276. L << "local items=( \"${(@f)$(${words_orig[@]} " << SpecialFlag << " " << Completer_->GetUniqueName() << " \"${current_orig}\" \"${prefix_orig}\" \"${suffix_orig}\" 2> /dev/null)}\" )";
  277. L;
  278. L << "local rempat=${items[1]}";
  279. L << "shift items";
  280. L;
  281. L << "local sep=${items[1]}";
  282. L << "shift items";
  283. L;
  284. L << "local files=( ${items:#*\"${sep}\"} )";
  285. L << "local filenames=( ${files#\"${rempat}\"} )";
  286. L << "local dirs=( ${(M)items:#*\"${sep}\"} )";
  287. L << "local dirnames=( ${dirs#\"${rempat}\"} )";
  288. L;
  289. L << "local need_suf";
  290. L << "compset -S \"${sep}*\" || need_suf=\"1\"";
  291. L;
  292. L << "compadd ${@} ${expl[@]} -d filenames -- ${(q)files}";
  293. L << "compadd ${@} ${expl[@]} ${need_suf:+-S\"${sep}\"} -q -d dirnames -- ${(q)dirs%\"${sep}\"}";
  294. }
  295. private:
  296. TCustomCompleter* Completer_;
  297. };
  298. ICompleterPtr LaunchSelfMultiPart(TCustomCompleter& completer) {
  299. return MakeSimpleShared<TLaunchSelfMultiPart>(&completer);
  300. }
  301. #undef I
  302. #undef L
  303. }