1 /// Functions related to tab-completion.
2 ///
3 /// These functions are used for storing and retrieving tab-completion data, as well as for
4 /// performing tab-completion.
5 ///
6 #include "config.h" // IWYU pragma: keep
7
8 #include "complete.h"
9
10 #include <pthread.h>
11 #include <pwd.h>
12 #include <stddef.h>
13 #include <wctype.h>
14
15 #include <algorithm>
16 #include <atomic>
17 #include <cstddef>
18 #include <cwchar>
19 #include <functional>
20 #include <iterator>
21 #include <list>
22 #include <memory>
23 #include <numeric>
24 #include <set>
25 #include <string>
26 #include <type_traits>
27 #include <unordered_map>
28 #include <unordered_set>
29 #include <utility>
30
31 #include "autoload.h"
32 #include "builtin.h"
33 #include "common.h"
34 #include "env.h"
35 #include "exec.h"
36 #include "expand.h"
37 #include "fallback.h" // IWYU pragma: keep
38 #include "function.h"
39 #include "history.h"
40 #include "iothread.h"
41 #include "parse_constants.h"
42 #include "parse_util.h"
43 #include "parser.h"
44 #include "parser_keywords.h"
45 #include "path.h"
46 #include "proc.h"
47 #include "reader.h"
48 #include "util.h"
49 #include "wcstringutil.h"
50 #include "wildcard.h"
51 #include "wutil.h" // IWYU pragma: keep
52
53 // Completion description strings, mostly for different types of files, such as sockets, block
54 // devices, etc.
55 //
56 // There are a few more completion description strings defined in expand.c. Maybe all completion
57 // description strings should be defined in the same file?
58
59 /// Description for ~USER completion.
60 #define COMPLETE_USER_DESC _(L"Home for %ls")
61
62 /// Description for short variables. The value is concatenated to this description.
63 #define COMPLETE_VAR_DESC_VAL _(L"Variable: %ls")
64
65 /// Description for abbreviations.
66 #define ABBR_DESC _(L"Abbreviation: %ls")
67
68 /// The special cased translation macro for completions. The empty string needs to be special cased,
69 /// since it can occur, and should not be translated. (Gettext returns the version information as
70 /// the response).
71 #ifdef HAVE_GETTEXT
C_(const wcstring & s)72 static const wchar_t *C_(const wcstring &s) {
73 return s.empty() ? L"" : wgettext(s.c_str()).c_str();
74 }
75 #else
C_(const wcstring & s)76 static const wcstring &C_(const wcstring &s) { return s; }
77 #endif
78
79 /// Struct describing a completion option entry.
80 ///
81 /// If option is empty, the comp field must not be empty and contains a list of arguments to the
82 /// command.
83 ///
84 /// The type field determines how the option is to be interpreted: either empty (args_only) or
85 /// short, single-long ("old") or double-long ("GNU"). An invariant is that the option is empty if
86 /// and only if the type is args_only.
87 ///
88 /// If option is non-empty, it specifies a switch for the command. If \c comp is also not empty, it
89 /// contains a list of non-switch arguments that may only follow directly after the specified
90 /// switch.
91 using complete_entry_opt_t = struct complete_entry_opt {
92 // Text of the option (like 'foo').
93 wcstring option;
94 // Type of the option: args-oly, short, single_long, or double_long.
95 complete_option_type_t type;
96 // Arguments to the option.
97 wcstring comp;
98 // Description of the completion.
99 wcstring desc;
100 // Condition under which to use the option.
101 wcstring condition;
102 // Determines how completions should be performed on the argument after the switch.
103 completion_mode_t result_mode;
104 // Completion flags.
105 complete_flags_t flags;
106
107 wcstring localized_desc() const { return C_(desc); }
108
109 size_t expected_dash_count() const {
110 switch (this->type) {
111 case option_type_args_only:
112 return 0;
113 case option_type_short:
114 case option_type_single_long:
115 return 1;
116 case option_type_double_long:
117 return 2;
118 }
119 DIE("unreachable");
120 }
121 };
122
123 /// Last value used in the order field of completion_entry_t.
124 static std::atomic<unsigned int> k_complete_order{0};
125
126 /// Struct describing a command completion.
127 using option_list_t = std::list<complete_entry_opt_t>;
128 class completion_entry_t {
129 public:
130 /// List of all options.
131 option_list_t options;
132
133 /// Command string.
134 const wcstring cmd;
135 /// True if command is a path.
136 const bool cmd_is_path;
137 /// Order for when this completion was created. This aids in outputting completions sorted by
138 /// time.
139 const unsigned int order;
140
141 /// Getters for option list.
142 const option_list_t &get_options() const;
143
144 /// Adds or removes an option.
145 void add_option(const complete_entry_opt_t &opt);
146 bool remove_option(const wcstring &option, complete_option_type_t type);
147
completion_entry_t(wcstring c,bool type)148 completion_entry_t(wcstring c, bool type)
149 : cmd(std::move(c)), cmd_is_path(type), order(++k_complete_order) {}
150 };
151
152 /// Set of all completion entries.
153 namespace std {
154 template <>
155 struct hash<completion_entry_t> {
operator ()std::hash156 size_t operator()(const completion_entry_t &c) const {
157 std::hash<wcstring> hasher;
158 return hasher(wcstring(c.cmd));
159 }
160 };
161 template <>
162 struct equal_to<completion_entry_t> {
operator ()std::equal_to163 bool operator()(const completion_entry_t &c1, const completion_entry_t &c2) const {
164 return c1.cmd == c2.cmd;
165 }
166 };
167 } // namespace std
168 using completion_entry_set_t = std::unordered_set<completion_entry_t>;
169 static owning_lock<completion_entry_set_t> s_completion_set;
170
171 /// Completion "wrapper" support. The map goes from wrapping-command to wrapped-command-list.
172 using wrapper_map_t = std::unordered_map<wcstring, wcstring_list_t>;
173 static owning_lock<wrapper_map_t> wrapper_map;
174
175 /// Comparison function to sort completions by their order field.
compare_completions_by_order(const completion_entry_t & p1,const completion_entry_t & p2)176 static bool compare_completions_by_order(const completion_entry_t &p1,
177 const completion_entry_t &p2) {
178 return p1.order < p2.order;
179 }
180
add_option(const complete_entry_opt_t & opt)181 void completion_entry_t::add_option(const complete_entry_opt_t &opt) { options.push_front(opt); }
182
get_options() const183 const option_list_t &completion_entry_t::get_options() const { return options; }
184
const_desc(const wcstring & s)185 description_func_t const_desc(const wcstring &s) {
186 return [=](const wcstring &ignored) {
187 UNUSED(ignored);
188 return s;
189 };
190 }
191
192 /// Clear the COMPLETE_AUTO_SPACE flag, and set COMPLETE_NO_SPACE appropriately depending on the
193 /// suffix of the string.
resolve_auto_space(const wcstring & comp,complete_flags_t flags)194 static complete_flags_t resolve_auto_space(const wcstring &comp, complete_flags_t flags) {
195 complete_flags_t new_flags = flags;
196 if (flags & COMPLETE_AUTO_SPACE) {
197 new_flags &= ~COMPLETE_AUTO_SPACE;
198 size_t len = comp.size();
199 if (len > 0 && (std::wcschr(L"/=@:.,-", comp.at(len - 1)) != nullptr))
200 new_flags |= COMPLETE_NO_SPACE;
201 }
202 return new_flags;
203 }
204
205 /// completion_t functions. Note that the constructor resolves flags!
completion_t(wcstring comp,wcstring desc,string_fuzzy_match_t mat,complete_flags_t flags_val)206 completion_t::completion_t(wcstring comp, wcstring desc, string_fuzzy_match_t mat,
207 complete_flags_t flags_val)
208 : completion(std::move(comp)),
209 description(std::move(desc)),
210 match(mat),
211 flags(resolve_auto_space(completion, flags_val)) {}
212
213 completion_t::completion_t(const completion_t &) = default;
214 completion_t::completion_t(completion_t &&) = default;
215 completion_t &completion_t::operator=(const completion_t &) = default;
216 completion_t &completion_t::operator=(completion_t &&) = default;
217 completion_t::~completion_t() = default;
218
natural_compare_completions(const completion_t & a,const completion_t & b)219 __attribute__((always_inline)) static inline bool natural_compare_completions(
220 const completion_t &a, const completion_t &b) {
221 // For this to work, stable_sort must be used because results aren't interchangeable.
222 if (a.flags & b.flags & COMPLETE_DONT_SORT) {
223 // Both completions are from a source with the --keep-order flag.
224 return false;
225 }
226 return wcsfilecmp(a.completion.c_str(), b.completion.c_str()) < 0;
227 }
228
is_naturally_less_than(const completion_t & a,const completion_t & b)229 bool completion_t::is_naturally_less_than(const completion_t &a, const completion_t &b) {
230 return natural_compare_completions(a, b);
231 }
232
prepend_token_prefix(const wcstring & prefix)233 void completion_t::prepend_token_prefix(const wcstring &prefix) {
234 if (this->flags & COMPLETE_REPLACES_TOKEN) {
235 this->completion.insert(0, prefix);
236 }
237 }
238
add(completion_t && comp)239 bool completion_receiver_t::add(completion_t &&comp) {
240 if (this->completions_.size() >= limit_) {
241 return false;
242 }
243 this->completions_.push_back(std::move(comp));
244 return true;
245 }
246
add(wcstring && comp)247 bool completion_receiver_t::add(wcstring &&comp) { return this->add(std::move(comp), wcstring{}); }
248
add(wcstring && comp,wcstring desc,complete_flags_t flags,string_fuzzy_match_t match)249 bool completion_receiver_t::add(wcstring &&comp, wcstring desc, complete_flags_t flags,
250 string_fuzzy_match_t match) {
251 return this->add(completion_t(std::move(comp), std::move(desc), match, flags));
252 }
253
add_list(completion_list_t && lst)254 bool completion_receiver_t::add_list(completion_list_t &&lst) {
255 size_t total_size = lst.size() + this->size();
256 if (total_size < this->size() || total_size > limit_) {
257 return false;
258 }
259
260 if (completions_.empty()) {
261 completions_ = std::move(lst);
262 } else {
263 completions_.reserve(completions_.size() + lst.size());
264 std::move(lst.begin(), lst.end(), std::back_inserter(completions_));
265 }
266 return true;
267 }
268
take()269 completion_list_t completion_receiver_t::take() {
270 completion_list_t res{};
271 std::swap(res, this->completions_);
272 return res;
273 }
274
subreceiver() const275 completion_receiver_t completion_receiver_t::subreceiver() const {
276 size_t remaining_capacity = limit_ < size() ? 0 : limit_ - size();
277 return completion_receiver_t(remaining_capacity);
278 }
279
280 // If these functions aren't force inlined, it is actually faster to call
281 // stable_sort twice rather than to iterate once performing all comparisons in one go!
compare_completions_by_duplicate_arguments(const completion_t & a,const completion_t & b)282 __attribute__((always_inline)) static inline bool compare_completions_by_duplicate_arguments(
283 const completion_t &a, const completion_t &b) {
284 bool ad = a.flags & COMPLETE_DUPLICATES_ARGUMENT;
285 bool bd = b.flags & COMPLETE_DUPLICATES_ARGUMENT;
286 return ad < bd;
287 }
288
compare_completions_by_tilde(const completion_t & a,const completion_t & b)289 __attribute__((always_inline)) static inline bool compare_completions_by_tilde(
290 const completion_t &a, const completion_t &b) {
291 if (a.completion.empty() || b.completion.empty()) {
292 return false;
293 }
294 return ((a.completion.back() == L'~') < (b.completion.back() == L'~'));
295 }
296
297 /// Unique the list of completions, without perturbing their order.
unique_completions_retaining_order(completion_list_t * comps)298 static void unique_completions_retaining_order(completion_list_t *comps) {
299 std::unordered_set<wcstring> seen;
300 seen.reserve(comps->size());
301 auto pred = [&seen](const completion_t &c) {
302 // Remove (return true) if insertion fails.
303 bool inserted = seen.insert(c.completion).second;
304 return !inserted;
305 };
306 comps->erase(std::remove_if(comps->begin(), comps->end(), pred), comps->end());
307 }
308
completions_sort_and_prioritize(completion_list_t * comps,completion_request_flags_t flags)309 void completions_sort_and_prioritize(completion_list_t *comps, completion_request_flags_t flags) {
310 if (comps->empty()) return;
311
312 // Find the best rank.
313 uint32_t best_rank = UINT32_MAX;
314 for (const auto &comp : *comps) {
315 best_rank = std::min(best_rank, comp.rank());
316 }
317
318 // Throw out completions of worse ranks.
319 comps->erase(std::remove_if(comps->begin(), comps->end(),
320 [=](const completion_t &comp) { return comp.rank() > best_rank; }),
321 comps->end());
322
323 // Deduplicate both sorted and unsorted results.
324 unique_completions_retaining_order(comps);
325
326 // Sort, provided COMPLETE_DONT_SORT isn't set.
327 // Here we do not pass suppress_exact, so that exact matches appear first.
328 stable_sort(comps->begin(), comps->end(), [&](const completion_t &a, const completion_t &b) {
329 return a.rank() < b.rank() || natural_compare_completions(a, b);
330 });
331
332 // Lastly, if this is for an autosuggestion, prefer to avoid completions that duplicate
333 // arguments, and penalize files that end in tilde - they're frequently autosave files from e.g.
334 // emacs. Also prefer samecase to smartcase.
335 if (flags & completion_request_t::autosuggestion) {
336 stable_sort(comps->begin(), comps->end(), [](const completion_t &a, const completion_t &b) {
337 if (a.match.case_fold != b.match.case_fold) {
338 return a.match.case_fold < b.match.case_fold;
339 }
340 return compare_completions_by_duplicate_arguments(a, b) ||
341 compare_completions_by_tilde(a, b);
342 });
343 }
344 }
345
346 /// Class representing an attempt to compute completions.
347 class completer_t {
348 /// The operation context for this completion.
349 const operation_context_t &ctx;
350
351 /// Flags associated with the completion request.
352 const completion_request_flags_t flags;
353
354 /// The output completions.
355 completion_receiver_t completions;
356
357 /// Table of completions conditions that have already been tested and the corresponding test
358 /// results.
359 using condition_cache_t = std::unordered_map<wcstring, bool>;
360 condition_cache_t condition_cache;
361
362 enum complete_type_t { COMPLETE_DEFAULT, COMPLETE_AUTOSUGGEST };
363
type() const364 complete_type_t type() const {
365 return (flags & completion_request_t::autosuggestion) ? COMPLETE_AUTOSUGGEST
366 : COMPLETE_DEFAULT;
367 }
368
wants_descriptions() const369 bool wants_descriptions() const { return flags & completion_request_t::descriptions; }
370
fuzzy() const371 bool fuzzy() const { return flags & completion_request_t::fuzzy_match; }
372
373 bool try_complete_variable(const wcstring &str);
374 bool try_complete_user(const wcstring &str);
375
376 bool complete_param_for_command(const wcstring &cmd_orig, const wcstring &popt,
377 const wcstring &str, bool use_switches, bool *out_do_file);
378
379 void complete_param_expand(const wcstring &str, bool do_file,
380 bool handle_as_special_cd = false);
381
382 void complete_cmd(const wcstring &str);
383
384 /// Attempt to complete an abbreviation for the given string.
385 void complete_abbr(const wcstring &cmd);
386
387 void complete_from_args(const wcstring &str, const wcstring &args, const wcstring &desc,
388 complete_flags_t flags);
389
390 void complete_cmd_desc(const wcstring &str);
391
392 bool complete_variable(const wcstring &str, size_t start_offset);
393
394 bool condition_test(const wcstring &condition);
395
396 void complete_strings(const wcstring &wc_escaped, const description_func_t &desc_func,
397 const completion_list_t &possible_comp, complete_flags_t flags);
398
expand_flags() const399 expand_flags_t expand_flags() const {
400 expand_flags_t result{};
401 if (this->type() == COMPLETE_AUTOSUGGEST) result |= expand_flag::skip_cmdsubst;
402 if (this->fuzzy()) result |= expand_flag::fuzzy_match;
403 if (this->wants_descriptions()) result |= expand_flag::gen_descriptions;
404 return result;
405 }
406
407 // Bag of data to support expanding a command's arguments using custom completions, including
408 // the wrap chain.
409 struct custom_arg_data_t {
custom_arg_data_tcompleter_t::custom_arg_data_t410 explicit custom_arg_data_t(wcstring_list_t *vars) : var_assignments(vars) { assert(vars); }
411
412 // The unescaped argument before the argument which is being completed, or empty if none.
413 wcstring previous_argument{};
414
415 // The unescaped argument which is being completed, or empty if none.
416 wcstring current_argument{};
417
418 // Whether a -- has been encountered, which suppresses options.
419 bool had_ddash{false};
420
421 // Whether to perform file completions.
422 // This is an "out" parameter of the wrap chain walk: if any wrapped command suppresses file
423 // completions this gets set to false.
424 bool do_file{true};
425
426 // Depth in the wrap chain.
427 size_t wrap_depth{0};
428
429 // The list of variable assignments: escaped strings of the form VAR=VAL.
430 // This may be temporarily appended to as we explore the wrap chain.
431 // When completing, variable assignments are really set in a local scope.
432 wcstring_list_t *var_assignments;
433
434 // The set of wrapped commands which we have visited, and so should not be explored again.
435 std::set<wcstring> visited_wrapped_commands{};
436 };
437
438 void complete_custom(const wcstring &cmd, const wcstring &cmdline, custom_arg_data_t *ad);
439
440 void walk_wrap_chain(const wcstring &cmd, const wcstring &cmdline, source_range_t cmdrange,
441 custom_arg_data_t *ad);
442
443 cleanup_t apply_var_assignments(const wcstring_list_t &var_assignments);
444
empty() const445 bool empty() const { return completions.empty(); }
446
447 void escape_opening_brackets(const wcstring &argument);
448
449 void mark_completions_duplicating_arguments(const wcstring &cmd, const wcstring &prefix,
450 const std::vector<tok_t> &args);
451
452 public:
completer_t(const operation_context_t & ctx,completion_request_flags_t f)453 completer_t(const operation_context_t &ctx, completion_request_flags_t f)
454 : ctx(ctx), flags(f), completions(ctx.expansion_limit) {}
455
456 void perform_for_commandline(wcstring cmdline);
457
acquire_completions()458 completion_list_t acquire_completions() { return completions.take(); }
459 };
460
461 // Autoloader for completions.
462 static owning_lock<autoload_t> completion_autoloader{autoload_t(L"fish_complete_path")};
463
464 /// Create a new completion entry.
append_completion(completion_list_t * completions,wcstring comp,wcstring desc,complete_flags_t flags,string_fuzzy_match_t match)465 void append_completion(completion_list_t *completions, wcstring comp, wcstring desc,
466 complete_flags_t flags, string_fuzzy_match_t match) {
467 completions->emplace_back(std::move(comp), std::move(desc), match, flags);
468 }
469
470 /// Test if the specified script returns zero. The result is cached, so that if multiple completions
471 /// use the same condition, it needs only be evaluated once. condition_cache_clear must be called
472 /// after a completion run to make sure that there are no stale completions.
condition_test(const wcstring & condition)473 bool completer_t::condition_test(const wcstring &condition) {
474 if (condition.empty()) {
475 // std::fwprintf( stderr, L"No condition specified\n" );
476 return true;
477 }
478 if (!ctx.parser) {
479 return false;
480 }
481
482 ASSERT_IS_MAIN_THREAD();
483 bool test_res;
484 auto cached_entry = condition_cache.find(condition);
485 if (cached_entry == condition_cache.end()) {
486 // Compute new value and reinsert it.
487 test_res =
488 (0 == exec_subshell(condition, *ctx.parser, false /* don't apply exit status */));
489 condition_cache[condition] = test_res;
490 } else {
491 // Use the old value.
492 test_res = cached_entry->second;
493 }
494 return test_res;
495 }
496
497 /// Locate the specified entry. Create it if it doesn't exist. Must be called while locked.
complete_get_exact_entry(completion_entry_set_t & completion_set,const wcstring & cmd,bool cmd_is_path)498 static completion_entry_t &complete_get_exact_entry(completion_entry_set_t &completion_set,
499 const wcstring &cmd, bool cmd_is_path) {
500 auto ins = completion_set.emplace(cmd, cmd_is_path);
501
502 // NOTE SET_ELEMENTS_ARE_IMMUTABLE: Exposing mutable access here is only okay as long as callers
503 // do not change any field that matters to ordering - affecting order without telling std::set
504 // invalidates its internal state.
505 return const_cast<completion_entry_t &>(*ins.first);
506 }
507
complete_add(const wchar_t * cmd,bool cmd_is_path,const wcstring & option,complete_option_type_t option_type,completion_mode_t result_mode,const wchar_t * condition,const wchar_t * comp,const wchar_t * desc,complete_flags_t flags)508 void complete_add(const wchar_t *cmd, bool cmd_is_path, const wcstring &option,
509 complete_option_type_t option_type, completion_mode_t result_mode,
510 const wchar_t *condition, const wchar_t *comp, const wchar_t *desc,
511 complete_flags_t flags) {
512 assert(cmd && "Null command");
513 // option should be empty iff the option type is arguments only.
514 assert(option.empty() == (option_type == option_type_args_only));
515
516 // Lock the lock that allows us to edit the completion entry list.
517 auto completion_set = s_completion_set.acquire();
518 completion_entry_t &c = complete_get_exact_entry(*completion_set, cmd, cmd_is_path);
519
520 // Create our new option.
521 complete_entry_opt_t opt;
522 opt.option = option;
523 opt.type = option_type;
524 opt.result_mode = result_mode;
525
526 if (comp) opt.comp = comp;
527 if (condition) opt.condition = condition;
528 if (desc) opt.desc = desc;
529 opt.flags = flags;
530
531 c.add_option(opt);
532 }
533
534 /// Remove all completion options in the specified entry that match the specified short / long
535 /// option strings. Returns true if it is now empty and should be deleted, false if it's not empty.
536 /// Must be called while locked.
remove_option(const wcstring & option,complete_option_type_t type)537 bool completion_entry_t::remove_option(const wcstring &option, complete_option_type_t type) {
538 auto iter = this->options.begin();
539 while (iter != this->options.end()) {
540 if (iter->option == option && iter->type == type) {
541 iter = this->options.erase(iter);
542 } else {
543 // Just go to the next one.
544 ++iter;
545 }
546 }
547 return this->options.empty();
548 }
549
complete_remove(const wcstring & cmd,bool cmd_is_path,const wcstring & option,complete_option_type_t type)550 void complete_remove(const wcstring &cmd, bool cmd_is_path, const wcstring &option,
551 complete_option_type_t type) {
552 auto completion_set = s_completion_set.acquire();
553
554 completion_entry_t tmp_entry(cmd, cmd_is_path);
555 auto iter = completion_set->find(tmp_entry);
556 if (iter != completion_set->end()) {
557 // const_cast: See SET_ELEMENTS_ARE_IMMUTABLE.
558 auto &entry = const_cast<completion_entry_t &>(*iter);
559
560 bool delete_it = entry.remove_option(option, type);
561 if (delete_it) {
562 completion_set->erase(iter);
563 }
564 }
565 }
566
complete_remove_all(const wcstring & cmd,bool cmd_is_path)567 void complete_remove_all(const wcstring &cmd, bool cmd_is_path) {
568 auto completion_set = s_completion_set.acquire();
569 completion_entry_t tmp_entry(cmd, cmd_is_path);
570 completion_set->erase(tmp_entry);
571 }
572
573 /// Find the full path and commandname from a command string 'str'.
parse_cmd_string(const wcstring & str,wcstring * path,wcstring * cmd,const environment_t & vars)574 static void parse_cmd_string(const wcstring &str, wcstring *path, wcstring *cmd,
575 const environment_t &vars) {
576 bool found = path_get_path(str, path, vars);
577 // If the command was not found, 'path' is the empty string.
578 // Resolve commands that use relative paths because we compare full paths with "complete -p".
579 if (found && !str.empty() && str.at(0) != L'/') {
580 if (auto full_path = wrealpath(*path)) {
581 path->assign(full_path.acquire());
582 }
583 }
584
585 // Make sure the path is not included in the command.
586 size_t last_slash = str.find_last_of(L'/');
587 if (last_slash != wcstring::npos) {
588 *cmd = str.substr(last_slash + 1);
589 } else {
590 *cmd = str;
591 }
592 }
593
594 /// Copy any strings in possible_comp which have the specified prefix to the
595 /// completer's completion array. The prefix may contain wildcards. The output
596 /// will consist of completion_t structs.
597 ///
598 /// There are three ways to specify descriptions for each completion. Firstly,
599 /// if a description has already been added to the completion, it is _not_
600 /// replaced. Secondly, if the desc_func function is specified, use it to
601 /// determine a dynamic completion. Thirdly, if none of the above are available,
602 /// the desc string is used as a description.
603 ///
604 /// @param wc_escaped
605 /// the prefix, possibly containing wildcards. The wildcard should not have
606 /// been unescaped, i.e. '*' should be used for any string, not the
607 /// ANY_STRING character.
608 /// @param desc_func
609 /// the function that generates a description for those completions without an
610 /// embedded description
611 /// @param possible_comp
612 /// the list of possible completions to iterate over
613 /// @param flags
614 /// The flags
complete_strings(const wcstring & wc_escaped,const description_func_t & desc_func,const completion_list_t & possible_comp,complete_flags_t flags)615 void completer_t::complete_strings(const wcstring &wc_escaped, const description_func_t &desc_func,
616 const completion_list_t &possible_comp, complete_flags_t flags) {
617 wcstring tmp = wc_escaped;
618 if (!expand_one(tmp,
619 this->expand_flags() | expand_flag::skip_cmdsubst | expand_flag::skip_wildcards,
620 ctx))
621 return;
622
623 const wcstring wc = parse_util_unescape_wildcards(tmp);
624
625 for (const auto &comp : possible_comp) {
626 const wcstring &comp_str = comp.completion;
627 if (!comp_str.empty()) {
628 wildcard_complete(comp_str, wc.c_str(), desc_func, &this->completions,
629 this->expand_flags(), flags);
630 }
631 }
632 }
633
634 /// If command to complete is short enough, substitute the description with the whatis information
635 /// for the executable.
complete_cmd_desc(const wcstring & str)636 void completer_t::complete_cmd_desc(const wcstring &str) {
637 ASSERT_IS_MAIN_THREAD();
638 if (!ctx.parser) return;
639
640 wcstring cmd;
641 size_t pos = str.find_last_of(L'/');
642 if (pos != std::string::npos) {
643 if (pos + 1 > str.length()) return;
644 cmd = wcstring(str, pos + 1);
645 } else {
646 cmd = str;
647 }
648
649 // Using apropos with a single-character search term produces far to many results - require at
650 // least two characters if we don't know the location of the whatis-database.
651 if (cmd.length() < 2) return;
652
653 if (wildcard_has(cmd, false)) {
654 return;
655 }
656
657 bool skip = true;
658 for (const auto &c : completions.get_list()) {
659 if (c.completion.empty() || (c.completion.back() != L'/')) {
660 skip = false;
661 break;
662 }
663 }
664
665 if (skip) {
666 return;
667 }
668
669 wcstring lookup_cmd(L"__fish_describe_command ");
670 lookup_cmd.append(escape_string(cmd, ESCAPE_ALL));
671
672 // First locate a list of possible descriptions using a single call to apropos or a direct
673 // search if we know the location of the whatis database. This can take some time on slower
674 // systems with a large set of manuals, but it should be ok since apropos is only called once.
675 wcstring_list_t list;
676 (void)exec_subshell(lookup_cmd, *ctx.parser, list, false /* don't apply exit status */);
677
678 // Then discard anything that is not a possible completion and put the result into a
679 // hashtable with the completion as key and the description as value.
680 std::unordered_map<wcstring, wcstring> lookup;
681 // A typical entry is the command name, followed by a tab, followed by a description.
682 for (const wcstring &elstr : list) {
683 // Skip keys that are too short.
684 if (elstr.size() < cmd.size()) continue;
685
686 // Skip cases without a tab, or without a description, or bizarre cases where the tab is
687 // part of the command.
688 size_t tab_idx = elstr.find(L'\t');
689 if (tab_idx == wcstring::npos || tab_idx + 1 >= elstr.size() || tab_idx < cmd.size())
690 continue;
691
692 // Make the key. This is the stuff after the command.
693 // For example:
694 // elstr = lsmod
695 // cmd = ls
696 // key = mod
697 // Note an empty key is common and natural, if 'cmd' were already valid.
698 wcstring key(elstr, cmd.size(), tab_idx - cmd.size());
699 wcstring val(elstr, tab_idx + 1);
700 assert(!val.empty() && "tab index should not have been at the end.");
701
702 // And once again I make sure the first character is uppercased because I like it that
703 // way, and I get to decide these things.
704 val.at(0) = towupper(val.at(0));
705 lookup.insert(std::make_pair(std::move(key), std::move(val)));
706 }
707
708 // Then do a lookup on every completion and if a match is found, change to the new
709 // description.
710 for (auto &completion : completions.get_list()) {
711 const wcstring &el = completion.completion;
712 auto new_desc_iter = lookup.find(el);
713 if (new_desc_iter != lookup.end()) completion.description = new_desc_iter->second;
714 }
715 }
716
717 /// Returns a description for the specified function, or an empty string if none.
complete_function_desc(const wcstring & fn)718 static wcstring complete_function_desc(const wcstring &fn) {
719 wcstring result;
720 function_get_desc(fn, result);
721 return result;
722 }
723
724 /// Complete the specified command name. Search for executables in the path, executables defined
725 /// using an absolute path, functions, builtins and directories for implicit cd commands.
726 ///
727 /// \param str_cmd the command string to find completions for
complete_cmd(const wcstring & str_cmd)728 void completer_t::complete_cmd(const wcstring &str_cmd) {
729 completion_list_t possible_comp;
730
731 // Append all possible executables
732 expand_result_t result =
733 expand_string(str_cmd, &this->completions,
734 this->expand_flags() | expand_flag::special_for_command |
735 expand_flag::for_completions | expand_flag::executables_only,
736 ctx);
737 if (result == expand_result_t::cancel) {
738 return;
739 }
740 if (result == expand_result_t::ok && this->wants_descriptions()) {
741 this->complete_cmd_desc(str_cmd);
742 }
743
744 // We don't really care if this succeeds or fails. If it succeeds this->completions will be
745 // updated with choices for the user.
746 expand_result_t ignore =
747 // Append all matching directories
748 expand_string(
749 str_cmd, &this->completions,
750 this->expand_flags() | expand_flag::for_completions | expand_flag::directories_only,
751 ctx);
752 UNUSED(ignore);
753
754 if (str_cmd.empty() || (str_cmd.find(L'/') == wcstring::npos && str_cmd.at(0) != L'~')) {
755 bool include_hidden = !str_cmd.empty() && str_cmd.at(0) == L'_';
756 wcstring_list_t names = function_get_names(include_hidden);
757 for (wcstring &name : names) {
758 // Append all known matching functions
759 append_completion(&possible_comp, std::move(name));
760 }
761
762 this->complete_strings(str_cmd, complete_function_desc, possible_comp, 0);
763
764 possible_comp.clear();
765
766 // Append all matching builtins
767 builtin_get_names(&possible_comp);
768 this->complete_strings(str_cmd, builtin_get_desc, possible_comp, 0);
769 }
770 }
771
complete_abbr(const wcstring & cmd)772 void completer_t::complete_abbr(const wcstring &cmd) {
773 std::map<wcstring, wcstring> abbrs = get_abbreviations(ctx.vars);
774 completion_list_t possible_comp;
775 possible_comp.reserve(abbrs.size());
776 for (const auto &kv : abbrs) {
777 possible_comp.emplace_back(kv.first);
778 }
779
780 auto desc_func = [&](const wcstring &key) {
781 auto iter = abbrs.find(key);
782 assert(iter != abbrs.end() && "Abbreviation not found");
783 return format_string(ABBR_DESC, iter->second.c_str());
784 };
785 this->complete_strings(cmd, desc_func, possible_comp, COMPLETE_NO_SPACE);
786 }
787
788 /// Evaluate the argument list (as supplied by complete -a) and insert any
789 /// return matching completions. Matching is done using @c
790 /// copy_strings_with_prefix, meaning the completion may contain wildcards.
791 /// Logically, this is not always the right thing to do, but I have yet to come
792 /// up with a case where this matters.
793 ///
794 /// @param str
795 /// The string to complete.
796 /// @param args
797 /// The list of option arguments to be evaluated.
798 /// @param desc
799 /// Description of the completion
800 /// @param flags
801 /// The flags
802 ///
complete_from_args(const wcstring & str,const wcstring & args,const wcstring & desc,complete_flags_t flags)803 void completer_t::complete_from_args(const wcstring &str, const wcstring &args,
804 const wcstring &desc, complete_flags_t flags) {
805 bool is_autosuggest = (this->type() == COMPLETE_AUTOSUGGEST);
806
807 bool saved_interactive = false;
808 statuses_t status;
809 if (ctx.parser) {
810 saved_interactive = ctx.parser->libdata().is_interactive;
811 ctx.parser->libdata().is_interactive = false;
812 status = ctx.parser->get_last_statuses();
813 }
814
815 expand_flags_t eflags{};
816 if (is_autosuggest) {
817 eflags |= expand_flag::skip_cmdsubst;
818 }
819
820 completion_list_t possible_comp = parser_t::expand_argument_list(args, eflags, ctx);
821
822 if (ctx.parser) {
823 ctx.parser->libdata().is_interactive = saved_interactive;
824 ctx.parser->set_last_statuses(status);
825 }
826
827 this->complete_strings(escape_string(str, ESCAPE_ALL), const_desc(desc), possible_comp, flags);
828 }
829
leading_dash_count(const wchar_t * str)830 static size_t leading_dash_count(const wchar_t *str) {
831 size_t cursor = 0;
832 while (str[cursor] == L'-') {
833 cursor++;
834 }
835 return cursor;
836 }
837
838 /// Match a parameter.
param_match(const complete_entry_opt_t * e,const wchar_t * optstr)839 static bool param_match(const complete_entry_opt_t *e, const wchar_t *optstr) {
840 bool result = false;
841 if (e->type != option_type_args_only) {
842 size_t dashes = leading_dash_count(optstr);
843 result = (dashes == e->expected_dash_count() && e->option == &optstr[dashes]);
844 }
845 return result;
846 }
847
848 /// Test if a string is an option with an argument, like --color=auto or -I/usr/include.
param_match2(const complete_entry_opt_t * e,const wchar_t * optstr)849 static const wchar_t *param_match2(const complete_entry_opt_t *e, const wchar_t *optstr) {
850 // We may get a complete_entry_opt_t with no options if it's just arguments.
851 if (e->option.empty()) {
852 return nullptr;
853 }
854
855 // Verify leading dashes.
856 size_t cursor = leading_dash_count(optstr);
857 if (cursor != e->expected_dash_count()) {
858 return nullptr;
859 }
860
861 // Verify options match.
862 if (!string_prefixes_string(e->option, &optstr[cursor])) {
863 return nullptr;
864 }
865 cursor += e->option.length();
866
867 // Short options are like -DNDEBUG. Long options are like --color=auto. So check for an equal
868 // sign for long options.
869 assert(e->type != option_type_short);
870 if (optstr[cursor] != L'=') {
871 return nullptr;
872 }
873 cursor += 1;
874 return &optstr[cursor];
875 }
876
877 /// Parses a token of short options plus one optional parameter like
878 /// '-xzPARAM', where x and z are short options.
879 ///
880 /// Returns the position of the last option character (e.g. the position of z which is 2).
881 /// Everything after that is assumed to be part of the parameter.
882 /// Returns wcstring::npos if there is no valid short option.
short_option_pos(const wcstring & arg,const option_list_t & options)883 static size_t short_option_pos(const wcstring &arg, const option_list_t &options) {
884 if (arg.size() <= 1 || leading_dash_count(arg.c_str()) != 1) {
885 return wcstring::npos;
886 }
887 for (size_t pos = 1; pos < arg.size(); pos++) {
888 wchar_t arg_char = arg.at(pos);
889 const complete_entry_opt_t *match = nullptr;
890 for (const complete_entry_opt_t &o : options) {
891 if (o.type == option_type_short && o.option.at(0) == arg_char) {
892 match = &o;
893 break;
894 }
895 }
896 if (match == nullptr) {
897 // The first character after the dash is not a valid option.
898 if (pos == 1) return wcstring::npos;
899 return pos - 1;
900 }
901 if (match->result_mode.requires_param) {
902 return pos;
903 }
904 }
905 return arg.size() - 1;
906 }
907
908 /// Load command-specific completions for the specified command.
complete_load(const wcstring & name)909 static void complete_load(const wcstring &name) {
910 // We have to load this as a function, since it may define a --wraps or signature.
911 // See issue #2466.
912 auto &parser = parser_t::principal_parser();
913 function_load(name, parser);
914
915 // It's important to NOT hold the lock around completion loading.
916 // We need to take the lock to decide what to load, drop it to perform the load, then reacquire
917 // it.
918 // Note we only look at the global fish_function_path and fish_complete_path.
919 maybe_t<wcstring> path_to_load =
920 completion_autoloader.acquire()->resolve_command(name, env_stack_t::globals());
921 if (path_to_load) {
922 autoload_t::perform_autoload(*path_to_load, parser);
923 completion_autoloader.acquire()->mark_autoload_finished(name);
924 }
925 }
926
927 /// complete_param: Given a command, find completions for the argument str of command cmd_orig with
928 /// previous option popt. If file completions should be disabled, then mark *out_do_file as false.
929 ///
930 /// \return true if successful, false if there's an error.
931 ///
932 /// Examples in format (cmd, popt, str):
933 ///
934 /// echo hello world <tab> -> ("echo", "world", "")
935 /// echo hello world<tab> -> ("echo", "hello", "world")
936 ///
complete_param_for_command(const wcstring & cmd_orig,const wcstring & popt,const wcstring & str,bool use_switches,bool * out_do_file)937 bool completer_t::complete_param_for_command(const wcstring &cmd_orig, const wcstring &popt,
938 const wcstring &str, bool use_switches,
939 bool *out_do_file) {
940 bool use_common = true, use_files = true, has_force = false;
941
942 wcstring cmd, path;
943 parse_cmd_string(cmd_orig, &path, &cmd, ctx.vars);
944
945 // FLOGF(error, L"\nThinking about looking up completions for %ls\n", cmd.c_str());
946 bool head_exists = builtin_exists(cmd);
947 // Only reload environment variables if builtin_exists returned false, as an optimization
948 if (!head_exists) {
949 head_exists = function_exists_no_autoload(cmd);
950 // While it may seem like first testing `path_get_path` before resorting to an env lookup
951 // may be faster, path_get_path can potentially do a lot of FS/IO access, so env.get() +
952 // function_exists() should still be faster.
953 // Use cmd_orig here as it is potentially pathed.
954 head_exists = head_exists || path_get_path(cmd_orig, nullptr, ctx.vars);
955 }
956
957 if (!head_exists) {
958 // Do not load custom completions if the head does not exist
959 // This prevents errors caused during the execution of completion providers for
960 // tools that do not exist. Applies to both manual completions ("cm<TAB>", "cmd <TAB>")
961 // and automatic completions ("gi" autosuggestion provider -> git)
962 FLOG(complete, "Skipping completions for non-existent head");
963 } else {
964 iothread_perform_on_main([&]() { complete_load(cmd); });
965 }
966
967 // Make a list of lists of all options that we care about.
968 std::vector<option_list_t> all_options;
969 {
970 auto completion_set = s_completion_set.acquire();
971 for (const completion_entry_t &i : *completion_set) {
972 const wcstring &match = i.cmd_is_path ? path : cmd;
973 if (wildcard_match(match, i.cmd)) {
974 // Copy all of their options into our list.
975 all_options.push_back(i.get_options()); // Oof, this is a lot of copying
976 }
977 }
978 }
979
980 // Now release the lock and test each option that we captured above. We have to do this outside
981 // the lock because callouts (like the condition) may add or remove completions. See issue 2.
982 for (const option_list_t &options : all_options) {
983 size_t short_opt_pos = short_option_pos(str, options);
984 bool last_option_requires_param = false;
985 use_common = true;
986 if (use_switches) {
987 if (str[0] == L'-') {
988 // Check if we are entering a combined option and argument (like --color=auto or
989 // -I/usr/include).
990 for (const complete_entry_opt_t &o : options) {
991 const wchar_t *arg;
992 if (o.type == option_type_short) {
993 if (short_opt_pos == wcstring::npos) continue;
994 if (o.option.at(0) != str.at(short_opt_pos)) continue;
995 last_option_requires_param = o.result_mode.requires_param;
996 arg = str.c_str() + short_opt_pos + 1;
997 } else {
998 arg = param_match2(&o, str.c_str());
999 }
1000 if (arg != nullptr && this->condition_test(o.condition)) {
1001 if (o.result_mode.requires_param) use_common = false;
1002 if (o.result_mode.no_files) use_files = false;
1003 if (o.result_mode.force_files) has_force = true;
1004 complete_from_args(arg, o.comp, o.localized_desc(), o.flags);
1005 }
1006 }
1007 } else if (popt[0] == L'-') {
1008 // Set to true if we found a matching old-style switch.
1009 // Here we are testing the previous argument,
1010 // to see how we should complete the current argument
1011 bool old_style_match = false;
1012
1013 // If we are using old style long options, check for them first.
1014 for (const complete_entry_opt_t &o : options) {
1015 if (o.type == option_type_single_long && param_match(&o, popt.c_str()) &&
1016 this->condition_test(o.condition)) {
1017 old_style_match = true;
1018 if (o.result_mode.requires_param) use_common = false;
1019 if (o.result_mode.no_files) use_files = false;
1020 if (o.result_mode.force_files) has_force = true;
1021 complete_from_args(str, o.comp, o.localized_desc(), o.flags);
1022 }
1023 }
1024
1025 // No old style option matched, or we are not using old style options. We check if
1026 // any short (or gnu style) options do.
1027 if (!old_style_match) {
1028 size_t prev_short_opt_pos = short_option_pos(popt, options);
1029 for (const complete_entry_opt_t &o : options) {
1030 // Gnu-style options with _optional_ arguments must be specified as a single
1031 // token, so that it can be differed from a regular argument.
1032 // Here we are testing the previous argument for a GNU-style match,
1033 // to see how we should complete the current argument
1034 if (!o.result_mode.requires_param) continue;
1035
1036 bool match = false;
1037 if (o.type == option_type_short) {
1038 match = prev_short_opt_pos != wcstring::npos &&
1039 // Only if the option was the last char in the token,
1040 // i.e. there is no parameter yet.
1041 prev_short_opt_pos + 1 == popt.size() &&
1042 o.option.at(0) == popt.at(prev_short_opt_pos);
1043 } else if (o.type == option_type_double_long) {
1044 match = param_match(&o, popt.c_str());
1045 }
1046 if (match && this->condition_test(o.condition)) {
1047 if (o.result_mode.requires_param) use_common = false;
1048 if (o.result_mode.no_files) use_files = false;
1049 if (o.result_mode.force_files) has_force = true;
1050 complete_from_args(str, o.comp, o.localized_desc(), o.flags);
1051 }
1052 }
1053 }
1054 }
1055 }
1056
1057 if (!use_common) {
1058 continue;
1059 }
1060
1061 // Now we try to complete an option itself
1062 for (const complete_entry_opt_t &o : options) {
1063 // If this entry is for the base command, check if any of the arguments match.
1064 if (!this->condition_test(o.condition)) continue;
1065 if (o.option.empty()) {
1066 use_files = use_files && (!(o.result_mode.no_files));
1067 has_force = has_force || o.result_mode.force_files;
1068 complete_from_args(str, o.comp, o.localized_desc(), o.flags);
1069 }
1070
1071 if (!use_switches || str.empty()) {
1072 continue;
1073 }
1074
1075 // Check if the short style option matches.
1076 if (o.type == option_type_short) {
1077 wchar_t optchar = o.option.at(0);
1078 if (short_opt_pos == wcstring::npos) {
1079 // str has no short option at all (but perhaps it is the
1080 // prefix of a single long option).
1081 // Only complete short options if there is no character after the dash.
1082 if (str != L"-") continue;
1083 } else {
1084 // Only complete when the last short option has no parameter yet..
1085 if (short_opt_pos + 1 != str.size()) continue;
1086 // .. and it does not require one ..
1087 if (last_option_requires_param) continue;
1088 // .. and the option is not already there.
1089 if (str.find(optchar) != wcstring::npos) continue;
1090 }
1091 // It's a match.
1092 wcstring desc = o.localized_desc();
1093 // Append a short-style option
1094 if (!this->completions.add(wcstring{o.option}, std::move(desc), 0)) {
1095 return false;
1096 }
1097 }
1098
1099 // Check if the long style option matches.
1100 if (o.type != option_type_single_long && o.type != option_type_double_long) {
1101 continue;
1102 }
1103
1104 wcstring whole_opt(o.expected_dash_count(), L'-');
1105 whole_opt.append(o.option);
1106
1107 if (whole_opt.length() < str.length()) {
1108 continue;
1109 }
1110 int match = string_prefixes_string(str, whole_opt);
1111 if (!match) {
1112 bool match_no_case = wcsncasecmp(str.c_str(), whole_opt.c_str(), str.length()) == 0;
1113 if (!match_no_case) {
1114 continue;
1115 }
1116 }
1117
1118 int has_arg = 0; // does this switch have any known arguments
1119 int req_arg = 0; // does this switch _require_ an argument
1120 size_t offset = 0;
1121 complete_flags_t flags = 0;
1122
1123 if (match) {
1124 offset = str.length();
1125 } else {
1126 flags = COMPLETE_REPLACES_TOKEN;
1127 }
1128
1129 has_arg = !o.comp.empty();
1130 req_arg = o.result_mode.requires_param;
1131
1132 if (o.type == option_type_double_long && (has_arg && !req_arg)) {
1133 // Optional arguments to a switch can only be handled using the '=', so we add it as
1134 // a completion. By default we avoid using '=' and instead rely on '--switch
1135 // switch-arg', since it is more commonly supported by homebrew getopt-like
1136 // functions.
1137 wcstring completion = format_string(L"%ls=", whole_opt.c_str() + offset);
1138 // Append a long-style option with a mandatory trailing equal sign
1139 if (!this->completions.add(std::move(completion), C_(o.desc),
1140 flags | COMPLETE_NO_SPACE)) {
1141 return false;
1142 }
1143 }
1144
1145 // Append a long-style option
1146 if (!this->completions.add(whole_opt.substr(offset), C_(o.desc), flags)) {
1147 return false;
1148 }
1149 }
1150 }
1151
1152 if (has_force) {
1153 *out_do_file = true;
1154 } else if (!use_files) {
1155 *out_do_file = false;
1156 }
1157 return true;
1158 }
1159
1160 /// Perform generic (not command-specific) expansions on the specified string.
complete_param_expand(const wcstring & str,bool do_file,bool handle_as_special_cd)1161 void completer_t::complete_param_expand(const wcstring &str, bool do_file,
1162 bool handle_as_special_cd) {
1163 if (ctx.check_cancel()) return;
1164 expand_flags_t flags =
1165 this->expand_flags() | expand_flag::skip_cmdsubst | expand_flag::for_completions;
1166
1167 if (!do_file) flags |= expand_flag::skip_wildcards;
1168
1169 if (handle_as_special_cd && do_file) {
1170 if (this->type() == COMPLETE_AUTOSUGGEST) {
1171 flags |= expand_flag::special_for_cd_autosuggestion;
1172 }
1173 flags |= expand_flag::directories_only;
1174 flags |= expand_flag::special_for_cd;
1175 }
1176
1177 // Squelch file descriptions per issue #254.
1178 if (this->type() == COMPLETE_AUTOSUGGEST || do_file) flags.clear(expand_flag::gen_descriptions);
1179
1180 // We have the following cases:
1181 //
1182 // --foo=bar => expand just bar
1183 // -foo=bar => expand just bar
1184 // foo=bar => expand the whole thing, and also just bar
1185 //
1186 // We also support colon separator (#2178). If there's more than one, prefer the last one.
1187 size_t sep_index = str.find_last_of(L"=:");
1188 bool complete_from_separator = (sep_index != wcstring::npos);
1189 bool complete_from_start = !complete_from_separator || !string_prefixes_string(L"-", str);
1190
1191 if (complete_from_separator) {
1192 // FIXME: This just cuts the token,
1193 // so any quoting or braces gets lost.
1194 // See #4954.
1195 const wcstring sep_string = wcstring(str, sep_index + 1);
1196 completion_list_t local_completions;
1197 if (expand_string(sep_string, &local_completions, flags, ctx) == expand_result_t::error) {
1198 FLOGF(complete, L"Error while expanding string '%ls'", sep_string.c_str());
1199 }
1200
1201 // Any COMPLETE_REPLACES_TOKEN will also stomp the separator. We need to "repair" them by
1202 // inserting our separator and prefix.
1203 const wcstring prefix_with_sep = wcstring(str, 0, sep_index + 1);
1204 for (completion_t &comp : local_completions) {
1205 comp.prepend_token_prefix(prefix_with_sep);
1206 }
1207 if (!this->completions.add_list(std::move(local_completions))) {
1208 return;
1209 }
1210 }
1211
1212 if (complete_from_start) {
1213 // Don't do fuzzy matching for files if the string begins with a dash (issue #568). We could
1214 // consider relaxing this if there was a preceding double-dash argument.
1215 if (string_prefixes_string(L"-", str)) flags.clear(expand_flag::fuzzy_match);
1216
1217 if (expand_string(str, &this->completions, flags, ctx) == expand_result_t::error) {
1218 FLOGF(complete, L"Error while expanding string '%ls'", str.c_str());
1219 }
1220 }
1221 }
1222
1223 /// Complete the specified string as an environment variable.
1224 /// \return true if this was a variable, so we should stop completion.
complete_variable(const wcstring & str,size_t start_offset)1225 bool completer_t::complete_variable(const wcstring &str, size_t start_offset) {
1226 const wchar_t *const whole_var = str.c_str();
1227 const wchar_t *var = &whole_var[start_offset];
1228 size_t varlen = str.length() - start_offset;
1229 bool res = false;
1230
1231 for (const wcstring &env_name : ctx.vars.get_names(0)) {
1232 bool anchor_start = !fuzzy();
1233 maybe_t<string_fuzzy_match_t> match =
1234 string_fuzzy_match_string(var, env_name, anchor_start);
1235 if (!match) continue;
1236
1237 wcstring comp;
1238 complete_flags_t flags = 0;
1239
1240 if (!match->requires_full_replacement()) {
1241 // Take only the suffix.
1242 comp.append(env_name.c_str() + varlen);
1243 } else {
1244 comp.append(whole_var, start_offset);
1245 comp.append(env_name);
1246 flags = COMPLETE_REPLACES_TOKEN | COMPLETE_DONT_ESCAPE;
1247 }
1248
1249 wcstring desc;
1250 if (this->wants_descriptions()) {
1251 if (this->type() != COMPLETE_AUTOSUGGEST) {
1252 // $history can be huge, don't put all of it in the completion description; see
1253 // #6288.
1254 if (env_name == L"history") {
1255 std::shared_ptr<history_t> history =
1256 history_t::with_name(history_session_id(ctx.vars));
1257 for (size_t i = 1; i < history->size() && desc.size() < 64; i++) {
1258 if (i > 1) desc += L' ';
1259 desc += expand_escape_string(history->item_at_index(i).str());
1260 }
1261 } else {
1262 // Can't use ctx.vars here, it could be any variable.
1263 auto var = ctx.vars.get(env_name);
1264 if (!var) continue;
1265
1266 wcstring value = expand_escape_variable(*var);
1267 desc = format_string(COMPLETE_VAR_DESC_VAL, value.c_str());
1268 }
1269 }
1270 }
1271
1272 // Append matching environment variables
1273 // TODO: need to propagate overflow here.
1274 ignore_result(this->completions.add(std::move(comp), std::move(desc), flags, *match));
1275
1276 res = true;
1277 }
1278
1279 return res;
1280 }
1281
try_complete_variable(const wcstring & str)1282 bool completer_t::try_complete_variable(const wcstring &str) {
1283 enum { e_unquoted, e_single_quoted, e_double_quoted } mode = e_unquoted;
1284 const size_t len = str.size();
1285
1286 // Get the position of the dollar heading a (possibly empty) run of valid variable characters.
1287 // npos means none.
1288 size_t variable_start = wcstring::npos;
1289
1290 for (size_t in_pos = 0; in_pos < len; in_pos++) {
1291 wchar_t c = str.at(in_pos);
1292 if (!valid_var_name_char(c)) {
1293 // This character cannot be in a variable, reset the dollar.
1294 variable_start = -1;
1295 }
1296
1297 switch (c) {
1298 case L'\\': {
1299 in_pos++;
1300 break;
1301 }
1302 case L'$': {
1303 if (mode == e_unquoted || mode == e_double_quoted) {
1304 variable_start = in_pos;
1305 }
1306 break;
1307 }
1308 case L'\'': {
1309 if (mode == e_single_quoted) {
1310 mode = e_unquoted;
1311 } else if (mode == e_unquoted) {
1312 mode = e_single_quoted;
1313 }
1314 break;
1315 }
1316 case L'"': {
1317 if (mode == e_double_quoted) {
1318 mode = e_unquoted;
1319 } else if (mode == e_unquoted) {
1320 mode = e_double_quoted;
1321 }
1322 break;
1323 }
1324 default: {
1325 break; // all other chars ignored here
1326 }
1327 }
1328 }
1329
1330 // Now complete if we have a variable start. Note the variable text may be empty; in that case
1331 // don't generate an autosuggestion, but do allow tab completion.
1332 bool allow_empty = !(this->flags & completion_request_t::autosuggestion);
1333 bool text_is_empty = (variable_start == len);
1334 bool result = false;
1335 if (variable_start != wcstring::npos && (allow_empty || !text_is_empty)) {
1336 result = this->complete_variable(str, variable_start + 1);
1337 }
1338 return result;
1339 }
1340
1341 /// Try to complete the specified string as a username. This is used by ~USER type expansion.
1342 ///
1343 /// \return false if unable to complete, true otherwise
try_complete_user(const wcstring & str)1344 bool completer_t::try_complete_user(const wcstring &str) {
1345 #ifndef HAVE_GETPWENT
1346 // The getpwent() function does not exist on Android. A Linux user on Android isn't
1347 // really a user - each installed app gets an UID assigned. Listing all UID:s is not
1348 // possible without root access, and doing a ~USER type expansion does not make sense
1349 // since every app is sandboxed and can't access eachother.
1350 return false;
1351 #else
1352 const wchar_t *cmd = str.c_str();
1353 const wchar_t *first_char = cmd;
1354
1355 if (*first_char != L'~' || std::wcschr(first_char, L'/')) return false;
1356
1357 const wchar_t *user_name = first_char + 1;
1358 const wchar_t *name_end = std::wcschr(user_name, L'~');
1359 if (name_end) return false;
1360
1361 double start_time = timef();
1362 bool result = false;
1363 size_t name_len = str.length() - 1;
1364
1365 static std::mutex s_setpwent_lock;
1366 scoped_lock locker(s_setpwent_lock);
1367 setpwent();
1368 // cppcheck-suppress getpwentCalled
1369 while (struct passwd *pw = getpwent()) {
1370 if (ctx.check_cancel()) {
1371 break;
1372 }
1373 const wcstring pw_name_str = str2wcstring(pw->pw_name);
1374 const wchar_t *pw_name = pw_name_str.c_str();
1375 if (std::wcsncmp(user_name, pw_name, name_len) == 0) {
1376 wcstring desc = format_string(COMPLETE_USER_DESC, pw_name);
1377 // Append a user name.
1378 // TODO: propagate overflow?
1379 ignore_result(
1380 this->completions.add(&pw_name[name_len], std::move(desc), COMPLETE_NO_SPACE));
1381 result = true;
1382 } else if (wcsncasecmp(user_name, pw_name, name_len) == 0) {
1383 wcstring name = format_string(L"~%ls", pw_name);
1384 wcstring desc = format_string(COMPLETE_USER_DESC, pw_name);
1385 // Append a user name
1386 ignore_result(this->completions.add(
1387 std::move(name), std::move(desc),
1388 COMPLETE_REPLACES_TOKEN | COMPLETE_DONT_ESCAPE | COMPLETE_NO_SPACE));
1389 result = true;
1390 }
1391
1392 // If we've spent too much time (more than 200 ms) doing this give up.
1393 if (timef() - start_time > 0.2) break;
1394 }
1395
1396 endpwent();
1397 return result;
1398 #endif
1399 }
1400
1401 // If we have variable assignments, attempt to apply them in our parser. As soon as the return
1402 // value goes out of scope, the variables will be removed from the parser.
apply_var_assignments(const wcstring_list_t & var_assignments)1403 cleanup_t completer_t::apply_var_assignments(const wcstring_list_t &var_assignments) {
1404 if (!ctx.parser || var_assignments.empty()) return cleanup_t{[] {}};
1405 env_stack_t &vars = ctx.parser->vars();
1406 assert(&vars == &ctx.vars &&
1407 "Don't know how to tab complete with a parser but a different variable set");
1408
1409 // clone of parse_execution_context_t::apply_variable_assignments.
1410 // Crucially do NOT expand subcommands:
1411 // VAR=(launch_missiles) cmd<tab>
1412 // should not launch missiles.
1413 // Note we also do NOT send --on-variable events.
1414 const expand_flags_t expand_flags = expand_flag::skip_cmdsubst;
1415 const block_t *block = ctx.parser->push_block(block_t::variable_assignment_block());
1416 for (const wcstring &var_assign : var_assignments) {
1417 maybe_t<size_t> equals_pos = variable_assignment_equals_pos(var_assign);
1418 assert(equals_pos && "All variable assignments should have equals position");
1419 const wcstring variable_name = var_assign.substr(0, *equals_pos);
1420 const wcstring expression = var_assign.substr(*equals_pos + 1);
1421
1422 completion_list_t expression_expanded;
1423 auto expand_ret = expand_string(expression, &expression_expanded, expand_flags, ctx);
1424 // If expansion succeeds, set the value; if it fails (e.g. it has a cmdsub) set an empty
1425 // value anyways.
1426 wcstring_list_t vals;
1427 if (expand_ret == expand_result_t::ok) {
1428 for (auto &completion : expression_expanded) {
1429 vals.emplace_back(std::move(completion.completion));
1430 }
1431 }
1432 ctx.parser->vars().set(variable_name, ENV_LOCAL | ENV_EXPORT, std::move(vals));
1433 if (ctx.check_cancel()) break;
1434 }
1435 return cleanup_t([=] { ctx.parser->pop_block(block); });
1436 }
1437
1438 // Complete a command by invoking user-specified completions.
complete_custom(const wcstring & cmd,const wcstring & cmdline,custom_arg_data_t * ad)1439 void completer_t::complete_custom(const wcstring &cmd, const wcstring &cmdline,
1440 custom_arg_data_t *ad) {
1441 if (ctx.check_cancel()) return;
1442
1443 bool is_autosuggest = this->type() == COMPLETE_AUTOSUGGEST;
1444 // Perhaps set a transient commandline so that custom completions
1445 // buitin_commandline will refer to the wrapped command. But not if
1446 // we're doing autosuggestions.
1447 maybe_t<cleanup_t> remove_transient{};
1448 bool wants_transient = ad->wrap_depth > 0 && !is_autosuggest;
1449 if (wants_transient) {
1450 ctx.parser->libdata().transient_commandlines.push_back(cmdline);
1451 remove_transient.emplace([=] { ctx.parser->libdata().transient_commandlines.pop_back(); });
1452 }
1453
1454 // Maybe apply variable assignments.
1455 cleanup_t restore_vars{apply_var_assignments(*ad->var_assignments)};
1456 if (ctx.check_cancel()) return;
1457
1458 if (!complete_param_for_command(
1459 cmd, ad->previous_argument, ad->current_argument, !ad->had_ddash,
1460 &ad->do_file)) { // Invoke any custom completions for this command.
1461 }
1462 }
1463
1464 // Invoke command-specific completions given by \p arg_data.
1465 // Then, for each target wrapped by the given command, update the command
1466 // line with that target and invoke this recursively.
1467 // The command whose completions to use is given by \p cmd. The full command line is given by \p
1468 // cmdline and the command's range in it is given by \p cmdrange. Note: the command range
1469 // may have a different length than the command itself, because the command is unescaped (i.e.
1470 // quotes removed).
walk_wrap_chain(const wcstring & cmd,const wcstring & cmdline,source_range_t cmdrange,custom_arg_data_t * ad)1471 void completer_t::walk_wrap_chain(const wcstring &cmd, const wcstring &cmdline,
1472 source_range_t cmdrange, custom_arg_data_t *ad) {
1473 // Limit our recursion depth. This prevents cycles in the wrap chain graph from overflowing.
1474 if (ad->wrap_depth > 24) return;
1475 if (ctx.cancel_checker()) return;
1476
1477 // Extract command from the command line and invoke the receiver with it.
1478 complete_custom(cmd, cmdline, ad);
1479
1480 wcstring_list_t targets = complete_get_wrap_targets(cmd);
1481 scoped_push<size_t> saved_depth(&ad->wrap_depth, ad->wrap_depth + 1);
1482
1483 for (const wcstring &wt : targets) {
1484 // We may append to the variable assignment list; ensure we restore it.
1485 const size_t saved_var_count = ad->var_assignments->size();
1486 cleanup_t restore_vars([=] {
1487 assert(ad->var_assignments->size() >= saved_var_count &&
1488 "Should not delete var assignments");
1489 ad->var_assignments->resize(saved_var_count);
1490 });
1491
1492 // Separate the wrap target into any variable assignments VAR=... and the command itself.
1493 wcstring wrapped_command;
1494 tokenizer_t tokenizer(wt.c_str(), 0);
1495 size_t wrapped_command_offset_in_wt = wcstring::npos;
1496 while (auto tok = tokenizer.next()) {
1497 wcstring tok_src = tok->get_source(wt);
1498 if (variable_assignment_equals_pos(tok_src)) {
1499 ad->var_assignments->push_back(std::move(tok_src));
1500 } else {
1501 wrapped_command_offset_in_wt = tok->offset;
1502 wrapped_command = std::move(tok_src);
1503 break;
1504 }
1505 }
1506
1507 // Skip this wrapped command if empty, or if we've seen it before.
1508 if (wrapped_command.empty() ||
1509 !ad->visited_wrapped_commands.insert(wrapped_command).second) {
1510 continue;
1511 }
1512
1513 // Construct a fake command line containing the wrap target.
1514 wcstring faux_commandline = cmdline;
1515 faux_commandline.replace(cmdrange.start, cmdrange.length, wt);
1516
1517 // Recurse with our new command and command line.
1518 source_range_t faux_source_range{uint32_t(cmdrange.start + wrapped_command_offset_in_wt),
1519 uint32_t(wrapped_command.size())};
1520 walk_wrap_chain(wrapped_command, faux_commandline, faux_source_range, ad);
1521 }
1522 }
1523
1524 /// If the argument contains a '[' typed by the user, completion by appending to the argument might
1525 /// produce an invalid token (#5831).
1526 ///
1527 /// Check if there is any unescaped, unquoted '['; if yes, make the completions replace the entire
1528 /// argument instead of appending, so '[' will be escaped.
escape_opening_brackets(const wcstring & argument)1529 void completer_t::escape_opening_brackets(const wcstring &argument) {
1530 bool have_unquoted_unescaped_bracket = false;
1531 wchar_t quote = L'\0';
1532 bool escaped = false;
1533 for (wchar_t c : argument) {
1534 have_unquoted_unescaped_bracket |= (c == L'[') && !quote && !escaped;
1535 if (escaped) {
1536 escaped = false;
1537 } else if (c == L'\\') {
1538 escaped = true;
1539 } else if (c == L'\'' || c == L'"') {
1540 if (quote == c) {
1541 // Closing a quote.
1542 quote = L'\0';
1543 } else if (quote == L'\0') {
1544 // Opening a quote.
1545 quote = c;
1546 }
1547 }
1548 }
1549 if (!have_unquoted_unescaped_bracket) return;
1550 // Since completion_apply_to_command_line will escape the completion, we need to provide an
1551 // unescaped version.
1552 wcstring unescaped_argument;
1553 if (!unescape_string(argument, &unescaped_argument, UNESCAPE_INCOMPLETE)) return;
1554 for (completion_t &comp : completions.get_list()) {
1555 if (comp.flags & COMPLETE_REPLACES_TOKEN) continue;
1556 comp.flags |= COMPLETE_REPLACES_TOKEN;
1557 if (comp.flags & COMPLETE_DONT_ESCAPE) {
1558 // If the completion won't be escaped, we need to do it here.
1559 // Currently, this will probably never happen since COMPLETE_DONT_ESCAPE
1560 // is only set for user or variable names which cannot contain '['.
1561 unescaped_argument = escape_string(unescaped_argument, ESCAPE_ALL);
1562 }
1563 comp.completion = unescaped_argument + comp.completion;
1564 }
1565 }
1566
1567 /// Set the DUPLICATES_ARG flag in any completion that duplicates an argument.
mark_completions_duplicating_arguments(const wcstring & cmd,const wcstring & prefix,const std::vector<tok_t> & args)1568 void completer_t::mark_completions_duplicating_arguments(const wcstring &cmd,
1569 const wcstring &prefix,
1570 const std::vector<tok_t> &args) {
1571 // Get all the arguments, unescaped, into an array that we're going to bsearch.
1572 wcstring_list_t arg_strs;
1573 for (const auto &arg : args) {
1574 wcstring argstr = arg.get_source(cmd);
1575 wcstring argstr_unesc;
1576 if (unescape_string(argstr, &argstr_unesc, UNESCAPE_DEFAULT)) {
1577 arg_strs.push_back(std::move(argstr_unesc));
1578 }
1579 }
1580 std::sort(arg_strs.begin(), arg_strs.end());
1581
1582 wcstring comp_str;
1583 for (completion_t &comp : completions.get_list()) {
1584 comp_str = comp.completion;
1585 if (!(comp.flags & COMPLETE_REPLACES_TOKEN)) {
1586 comp_str.insert(0, prefix);
1587 }
1588 if (std::binary_search(arg_strs.begin(), arg_strs.end(), comp_str)) {
1589 comp.flags |= COMPLETE_DUPLICATES_ARGUMENT;
1590 }
1591 }
1592 }
1593
perform_for_commandline(wcstring cmdline)1594 void completer_t::perform_for_commandline(wcstring cmdline) {
1595 // Limit recursion, in case a user-defined completion has cycles, or the completion for "x"
1596 // wraps "A=B x" (#3474, #7344). No need to do that when there is no parser: this happens only
1597 // for autosuggestions where we don't evaluate command substitutions or variable assignments.
1598 if (ctx.parser) {
1599 if (ctx.parser->libdata().complete_recursion_level >= 24) {
1600 FLOGF(error, _(L"completion reached maximum recursion depth, possible cycle?"),
1601 cmdline.c_str());
1602 return;
1603 }
1604 ++ctx.parser->libdata().complete_recursion_level;
1605 };
1606 cleanup_t decrement{[this]() {
1607 if (ctx.parser) --ctx.parser->libdata().complete_recursion_level;
1608 }};
1609
1610 const size_t cursor_pos = cmdline.size();
1611 const bool is_autosuggest = (flags & completion_request_t::autosuggestion);
1612
1613 // Find the process to operate on. The cursor may be past it (#1261), so backtrack
1614 // until we know we're no longer in a space. But the space may actually be part of the
1615 // argument (#2477).
1616 size_t position_in_statement = cursor_pos;
1617 while (position_in_statement > 0 && cmdline.at(position_in_statement - 1) == L' ') {
1618 position_in_statement--;
1619 }
1620
1621 // Get all the arguments.
1622 std::vector<tok_t> tokens;
1623 parse_util_process_extent(cmdline.c_str(), position_in_statement, nullptr, nullptr, &tokens);
1624
1625 // Hack: fix autosuggestion by removing prefixing "and"s #6249.
1626 if (is_autosuggest) {
1627 while (!tokens.empty() && parser_keywords_is_subcommand(tokens.front().get_source(cmdline)))
1628 tokens.erase(tokens.begin());
1629 }
1630
1631 // Consume variable assignments in tokens strictly before the cursor.
1632 // This is a list of (escaped) strings of the form VAR=VAL.
1633 wcstring_list_t var_assignments;
1634 for (const tok_t &tok : tokens) {
1635 if (tok.location_in_or_at_end_of_source_range(cursor_pos)) break;
1636 wcstring tok_src = tok.get_source(cmdline);
1637 if (!variable_assignment_equals_pos(tok_src)) break;
1638 var_assignments.push_back(std::move(tok_src));
1639 }
1640 tokens.erase(tokens.begin(), tokens.begin() + var_assignments.size());
1641
1642 // Empty process (cursor is after one of ;, &, |, \n, &&, || modulo whitespace).
1643 if (tokens.empty()) {
1644 // Don't autosuggest anything based on the empty string (generalizes #1631).
1645 if (is_autosuggest) return;
1646
1647 complete_cmd(L"");
1648 complete_abbr(L"");
1649 return;
1650 }
1651
1652 const tok_t &cmd_tok = tokens.front();
1653 const tok_t &cur_tok = tokens.back();
1654
1655 // Since fish does not currently support redirect in command position, we return here.
1656 if (cmd_tok.type != token_type_t::string) return;
1657 if (cur_tok.type == token_type_t::error) return;
1658 for (const auto &tok : tokens) { // If there was an error, it was in the last token.
1659 assert(tok.type == token_type_t::string || tok.type == token_type_t::redirect);
1660 }
1661 // If we are completing a variable name or a tilde expansion user name, we do that and
1662 // return. No need for any other completions.
1663 const wcstring current_token = cur_tok.get_source(cmdline);
1664 if (cur_tok.location_in_or_at_end_of_source_range(cursor_pos)) {
1665 if (try_complete_variable(current_token) || try_complete_user(current_token)) {
1666 return;
1667 }
1668 }
1669
1670 if (cmd_tok.location_in_or_at_end_of_source_range(cursor_pos)) {
1671 maybe_t<size_t> equal_sign_pos = variable_assignment_equals_pos(current_token);
1672 if (equal_sign_pos) {
1673 complete_param_expand(current_token, true /* do_file */);
1674 return;
1675 }
1676 // Complete command filename.
1677 complete_cmd(current_token);
1678 complete_abbr(current_token);
1679 return;
1680 }
1681 // See whether we are in an argument, in a redirection or in the whitespace in between.
1682 bool in_redirection = cur_tok.type == token_type_t::redirect;
1683
1684 bool had_ddash = false;
1685 wcstring current_argument, previous_argument;
1686 if (cur_tok.type == token_type_t::string &&
1687 cur_tok.location_in_or_at_end_of_source_range(position_in_statement)) {
1688 // If the cursor is in whitespace, then the "current" argument is empty and the
1689 // previous argument is the matching one. But if the cursor was in or at the end
1690 // of the argument, then the current argument is the matching one, and the
1691 // previous argument is the one before it.
1692 bool cursor_in_whitespace = !cur_tok.location_in_or_at_end_of_source_range(cursor_pos);
1693 if (cursor_in_whitespace) {
1694 current_argument.clear();
1695 previous_argument = current_token;
1696 } else {
1697 current_argument = current_token;
1698 if (tokens.size() >= 2) {
1699 tok_t prev_tok = tokens.at(tokens.size() - 2);
1700 if (prev_tok.type == token_type_t::string)
1701 previous_argument = prev_tok.get_source(cmdline);
1702 in_redirection = prev_tok.type == token_type_t::redirect;
1703 }
1704 }
1705
1706 // Check to see if we have a preceding double-dash.
1707 for (size_t i = 0; i < tokens.size() - 1; i++) {
1708 if (tokens.at(i).get_source(cmdline) == L"--") {
1709 had_ddash = true;
1710 break;
1711 }
1712 }
1713 }
1714
1715 bool do_file = false, handle_as_special_cd = false;
1716 if (in_redirection) {
1717 do_file = true;
1718 } else {
1719 // Try completing as an argument.
1720 custom_arg_data_t arg_data{&var_assignments};
1721 arg_data.had_ddash = had_ddash;
1722
1723 assert(cmd_tok.offset < std::numeric_limits<uint32_t>::max());
1724 assert(cmd_tok.length < std::numeric_limits<uint32_t>::max());
1725 source_range_t command_range = {static_cast<uint32_t>(cmd_tok.offset),
1726 static_cast<uint32_t>(cmd_tok.length)};
1727
1728 wcstring unesc_command;
1729 bool unescaped =
1730 unescape_string(cmd_tok.get_source(cmdline), &unesc_command, UNESCAPE_DEFAULT) &&
1731 unescape_string(previous_argument, &arg_data.previous_argument, UNESCAPE_DEFAULT) &&
1732 unescape_string(current_argument, &arg_data.current_argument, UNESCAPE_INCOMPLETE);
1733 if (unescaped) {
1734 // Have to walk over the command and its entire wrap chain. If any command
1735 // disables do_file, then they all do.
1736 walk_wrap_chain(unesc_command, cmdline, command_range, &arg_data);
1737 do_file = arg_data.do_file;
1738
1739 // If we're autosuggesting, and the token is empty, don't do file suggestions.
1740 if (is_autosuggest && arg_data.current_argument.empty()) {
1741 do_file = false;
1742 }
1743 }
1744
1745 // Hack. If we're cd, handle it specially (issue #1059, others).
1746 handle_as_special_cd =
1747 (unesc_command == L"cd") || arg_data.visited_wrapped_commands.count(L"cd");
1748 }
1749
1750 // Maybe apply variable assignments.
1751 cleanup_t restore_vars{apply_var_assignments(var_assignments)};
1752 if (ctx.check_cancel()) return;
1753
1754 // This function wants the unescaped string.
1755 complete_param_expand(current_argument, do_file, handle_as_special_cd);
1756
1757 // Escape '[' in the argument before completing it.
1758 escape_opening_brackets(current_argument);
1759
1760 // Lastly mark any completions that appear to already be present in arguments.
1761 mark_completions_duplicating_arguments(cmdline, current_token, tokens);
1762 }
1763
complete(const wcstring & cmd_with_subcmds,completion_request_flags_t flags,const operation_context_t & ctx)1764 completion_list_t complete(const wcstring &cmd_with_subcmds, completion_request_flags_t flags,
1765 const operation_context_t &ctx) {
1766 // Determine the innermost subcommand.
1767 const wchar_t *cmdsubst_begin, *cmdsubst_end;
1768 parse_util_cmdsubst_extent(cmd_with_subcmds.c_str(), cmd_with_subcmds.size(), &cmdsubst_begin,
1769 &cmdsubst_end);
1770 assert(cmdsubst_begin != nullptr && cmdsubst_end != nullptr && cmdsubst_end >= cmdsubst_begin);
1771 wcstring cmd = wcstring(cmdsubst_begin, cmdsubst_end - cmdsubst_begin);
1772 completer_t completer(ctx, flags);
1773 completer.perform_for_commandline(std::move(cmd));
1774 return completer.acquire_completions();
1775 }
1776
1777 /// Print the short switch \c opt, and the argument \c arg to the specified
1778 /// wcstring, but only if \c argument isn't an empty string.
append_switch(wcstring & out,wchar_t opt,const wcstring & arg)1779 static void append_switch(wcstring &out, wchar_t opt, const wcstring &arg) {
1780 if (arg.empty()) return;
1781 append_format(out, L" -%lc %ls", opt, escape_string(arg, ESCAPE_ALL).c_str());
1782 }
append_switch(wcstring & out,const wcstring & opt,const wcstring & arg)1783 static void append_switch(wcstring &out, const wcstring &opt, const wcstring &arg) {
1784 if (arg.empty()) return;
1785 append_format(out, L" --%ls %ls", opt.c_str(), escape_string(arg, ESCAPE_ALL).c_str());
1786 }
append_switch(wcstring & out,wchar_t opt)1787 static void append_switch(wcstring &out, wchar_t opt) { append_format(out, L" -%lc", opt); }
append_switch(wcstring & out,const wcstring & opt)1788 static void append_switch(wcstring &out, const wcstring &opt) {
1789 append_format(out, L" --%ls", opt.c_str());
1790 }
1791
completion2string(const complete_entry_opt_t & o,const wcstring & cmd,bool is_path)1792 static wcstring completion2string(const complete_entry_opt_t &o, const wcstring &cmd,
1793 bool is_path) {
1794 wcstring out;
1795 out.append(L"complete");
1796
1797 if (o.flags & COMPLETE_DONT_SORT) append_switch(out, L'k');
1798
1799 if (o.result_mode.no_files && o.result_mode.requires_param) {
1800 append_switch(out, L"exclusive");
1801 } else if (o.result_mode.no_files) {
1802 append_switch(out, L"no-files");
1803 } else if (o.result_mode.force_files) {
1804 append_switch(out, L"force-files");
1805 } else if (o.result_mode.requires_param) {
1806 append_switch(out, L"requires-param");
1807 }
1808
1809 if (is_path)
1810 append_switch(out, L'p', cmd);
1811 else {
1812 out.append(L" ");
1813 out.append(escape_string(cmd, ESCAPE_ALL));
1814 }
1815
1816 switch (o.type) {
1817 case option_type_args_only: {
1818 break;
1819 }
1820 case option_type_short: {
1821 append_switch(out, L's', wcstring(1, o.option.at(0)));
1822 break;
1823 }
1824 case option_type_single_long:
1825 case option_type_double_long: {
1826 append_switch(out, o.type == option_type_single_long ? L'o' : L'l', o.option);
1827 break;
1828 }
1829 }
1830
1831 append_switch(out, L'd', C_(o.desc));
1832 append_switch(out, L'a', o.comp);
1833 append_switch(out, L'n', o.condition);
1834 out.append(L"\n");
1835 return out;
1836 }
1837
1838 /// Use by the bare `complete`, loaded completions are printed out as commands
complete_print(const wcstring & cmd)1839 wcstring complete_print(const wcstring &cmd) {
1840 wcstring out;
1841 out.reserve(40); // just a guess
1842 auto completion_set = s_completion_set.acquire();
1843
1844 // Get a list of all completions in a vector, then sort it by order.
1845 std::vector<std::reference_wrapper<const completion_entry_t>> all_completions;
1846 // These should be "c"begin/end, but then gcc from ~~the dark ages~~ RHEL 7 would complain.
1847 all_completions.insert(all_completions.begin(), completion_set->begin(), completion_set->end());
1848 sort(all_completions.begin(), all_completions.end(), compare_completions_by_order);
1849
1850 for (const completion_entry_t &e : all_completions) {
1851 if (!cmd.empty() && e.cmd != cmd) continue;
1852 const option_list_t &options = e.get_options();
1853 for (const complete_entry_opt_t &o : options) {
1854 out.append(completion2string(o, e.cmd, e.cmd_is_path));
1855 }
1856 }
1857
1858 // Append wraps.
1859 auto locked_wrappers = wrapper_map.acquire();
1860 for (const auto &entry : *locked_wrappers) {
1861 const wcstring &src = entry.first;
1862 if (!cmd.empty() && src != cmd) continue;
1863 for (const wcstring &target : entry.second) {
1864 out.append(L"complete ");
1865 out.append(escape_string(src, ESCAPE_ALL));
1866 append_switch(out, L"wraps", target);
1867 out.append(L"\n");
1868 }
1869 }
1870 return out;
1871 }
1872
complete_invalidate_path()1873 void complete_invalidate_path() {
1874 // TODO: here we unload all completions for commands that are loaded by the autoloader. We also
1875 // unload any completions that the user may specified on the command line. We should in
1876 // principle track those completions loaded by the autoloader alone.
1877 wcstring_list_t cmds = completion_autoloader.acquire()->get_autoloaded_commands();
1878 for (const wcstring &cmd : cmds) {
1879 complete_remove_all(cmd, false /* not a path */);
1880 }
1881 }
1882
1883 /// Add a new target that wraps a command. Example: __fish_XYZ (function) wraps XYZ (target).
complete_add_wrapper(const wcstring & command,const wcstring & new_target)1884 bool complete_add_wrapper(const wcstring &command, const wcstring &new_target) {
1885 if (command.empty() || new_target.empty()) {
1886 return false;
1887 }
1888
1889 // If the command and the target are the same,
1890 // there's no point in following the wrap-chain because we'd only complete the same thing.
1891 // TODO: This should maybe include full cycle detection.
1892 if (command == new_target) return false;
1893
1894 auto locked_map = wrapper_map.acquire();
1895 wrapper_map_t &wraps = *locked_map;
1896 wcstring_list_t *targets = &wraps[command];
1897 // If it's already present, we do nothing.
1898 if (!contains(*targets, new_target)) {
1899 targets->push_back(new_target);
1900 }
1901 return true;
1902 }
1903
complete_remove_wrapper(const wcstring & command,const wcstring & target_to_remove)1904 bool complete_remove_wrapper(const wcstring &command, const wcstring &target_to_remove) {
1905 if (command.empty() || target_to_remove.empty()) {
1906 return false;
1907 }
1908
1909 auto locked_map = wrapper_map.acquire();
1910 wrapper_map_t &wraps = *locked_map;
1911 bool result = false;
1912 auto current_targets_iter = wraps.find(command);
1913 if (current_targets_iter != wraps.end()) {
1914 wcstring_list_t *targets = ¤t_targets_iter->second;
1915 auto where = std::find(targets->begin(), targets->end(), target_to_remove);
1916 if (where != targets->end()) {
1917 targets->erase(where);
1918 result = true;
1919 }
1920 }
1921 return result;
1922 }
1923
complete_get_wrap_targets(const wcstring & command)1924 wcstring_list_t complete_get_wrap_targets(const wcstring &command) {
1925 if (command.empty()) {
1926 return {};
1927 }
1928 auto locked_map = wrapper_map.acquire();
1929 wrapper_map_t &wraps = *locked_map;
1930 auto iter = wraps.find(command);
1931 if (iter == wraps.end()) return {};
1932 return iter->second;
1933 }
1934