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 = &current_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