mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-31 23:28:45 +00:00
Introduce completion_receiver_t
completion_receiver_t wraps a completion list; it will centralize logic around adding completions and most importantly it will enforce that we do not exceed our expansion limit.
This commit is contained in:
parent
a614a19b07
commit
af3383e727
4 changed files with 97 additions and 37 deletions
|
@ -255,8 +255,12 @@ bool contains(const Col &col, const T2 &val) {
|
||||||
/// Append a vector \p donator to the vector \p receiver.
|
/// Append a vector \p donator to the vector \p receiver.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void vec_append(std::vector<T> &receiver, std::vector<T> &&donator) {
|
void vec_append(std::vector<T> &receiver, std::vector<T> &&donator) {
|
||||||
|
if (receiver.empty()) {
|
||||||
|
receiver = std::move(donator);
|
||||||
|
} else {
|
||||||
receiver.insert(receiver.end(), std::make_move_iterator(donator.begin()),
|
receiver.insert(receiver.end(), std::make_move_iterator(donator.begin()),
|
||||||
std::make_move_iterator(donator.end()));
|
std::make_move_iterator(donator.end()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move an object into a shared_ptr.
|
/// Move an object into a shared_ptr.
|
||||||
|
|
|
@ -236,6 +236,32 @@ void completion_t::prepend_token_prefix(const wcstring &prefix) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void completion_receiver_t::add(completion_t &&comp) {
|
||||||
|
this->completions_.push_back(std::move(comp));
|
||||||
|
}
|
||||||
|
|
||||||
|
void completion_receiver_t::add(wcstring &&comp) { this->add(std::move(comp), wcstring{}); }
|
||||||
|
|
||||||
|
void completion_receiver_t::add(wcstring &&comp, wcstring &&desc, complete_flags_t flags,
|
||||||
|
string_fuzzy_match_t match) {
|
||||||
|
this->completions_.emplace_back(std::move(comp), std::move(desc), match, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void completion_receiver_t::add_list(completion_list_t &&lst) {
|
||||||
|
if (completions_.empty()) {
|
||||||
|
completions_ = std::move(lst);
|
||||||
|
} else {
|
||||||
|
completions_.reserve(completions_.size() + lst.size());
|
||||||
|
std::move(lst.begin(), lst.end(), std::back_inserter(completions_));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completion_list_t completion_receiver_t::take() {
|
||||||
|
completion_list_t res{};
|
||||||
|
std::swap(res, this->completions_);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
// If these functions aren't force inlined, it is actually faster to call
|
// If these functions aren't force inlined, it is actually faster to call
|
||||||
// stable_sort twice rather than to iterate once performing all comparisons in one go!
|
// stable_sort twice rather than to iterate once performing all comparisons in one go!
|
||||||
__attribute__((always_inline)) static inline bool compare_completions_by_duplicate_arguments(
|
__attribute__((always_inline)) static inline bool compare_completions_by_duplicate_arguments(
|
||||||
|
|
|
@ -120,6 +120,37 @@ using completion_request_flags_t = enum_set_t<completion_request_t>;
|
||||||
class completion_t;
|
class completion_t;
|
||||||
using completion_list_t = std::vector<completion_t>;
|
using completion_list_t = std::vector<completion_t>;
|
||||||
|
|
||||||
|
/// A completion receiver accepts completions. It is essentially a wrapper around std::vector with
|
||||||
|
/// some conveniences.
|
||||||
|
class completion_receiver_t {
|
||||||
|
public:
|
||||||
|
/// Add a completion.
|
||||||
|
void add(completion_t &&comp);
|
||||||
|
|
||||||
|
/// Add a completion with the given string, and default other properties.
|
||||||
|
void add(wcstring &&comp);
|
||||||
|
|
||||||
|
/// Add a completion with the given string, description, flags, and fuzzy match.
|
||||||
|
void add(wcstring &&comp, wcstring &&desc, complete_flags_t flags = 0,
|
||||||
|
string_fuzzy_match_t match = string_fuzzy_match_t::exact_match());
|
||||||
|
|
||||||
|
/// Add a list of completions.
|
||||||
|
void add_list(completion_list_t &&lst);
|
||||||
|
|
||||||
|
/// Swap our completions with a new list.
|
||||||
|
void swap(completion_list_t &lst) { std::swap(completions_, lst); }
|
||||||
|
|
||||||
|
/// Clear the list of completions. This retains the storage inside completions_ which can be
|
||||||
|
/// useful to prevent allocations.
|
||||||
|
void clear() { completions_.clear(); }
|
||||||
|
|
||||||
|
/// \return the list of completions, clearing them.
|
||||||
|
completion_list_t take();
|
||||||
|
|
||||||
|
private:
|
||||||
|
completion_list_t completions_;
|
||||||
|
};
|
||||||
|
|
||||||
enum complete_option_type_t {
|
enum complete_option_type_t {
|
||||||
option_type_args_only, // no option
|
option_type_args_only, // no option
|
||||||
option_type_short, // -x
|
option_type_short, // -x
|
||||||
|
|
|
@ -285,14 +285,14 @@ static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long
|
||||||
/// Note: last_idx is considered to be where it previously finished procesisng. This means it
|
/// Note: last_idx is considered to be where it previously finished procesisng. This means it
|
||||||
/// actually starts operating on last_idx-1. As such, to process a string fully, pass string.size()
|
/// actually starts operating on last_idx-1. As such, to process a string fully, pass string.size()
|
||||||
/// as last_idx instead of string.size()-1.
|
/// as last_idx instead of string.size()-1.
|
||||||
static bool expand_variables(wcstring instr, completion_list_t *out, size_t last_idx,
|
static bool expand_variables(wcstring instr, completion_receiver_t *out, size_t last_idx,
|
||||||
const environment_t &vars, parse_error_list_t *errors) {
|
const environment_t &vars, parse_error_list_t *errors) {
|
||||||
const size_t insize = instr.size();
|
const size_t insize = instr.size();
|
||||||
|
|
||||||
// last_idx may be 1 past the end of the string, but no further.
|
// last_idx may be 1 past the end of the string, but no further.
|
||||||
assert(last_idx <= insize && "Invalid last_idx");
|
assert(last_idx <= insize && "Invalid last_idx");
|
||||||
if (last_idx == 0) {
|
if (last_idx == 0) {
|
||||||
append_completion(out, std::move(instr));
|
out->add(std::move(instr));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,7 +308,7 @@ static bool expand_variables(wcstring instr, completion_list_t *out, size_t last
|
||||||
}
|
}
|
||||||
if (varexp_char_idx >= instr.size()) {
|
if (varexp_char_idx >= instr.size()) {
|
||||||
// No variable expand char, we're done.
|
// No variable expand char, we're done.
|
||||||
append_completion(out, std::move(instr));
|
out->add(std::move(instr));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,9 +458,9 @@ static bool expand_variables(wcstring instr, completion_list_t *out, size_t last
|
||||||
return expand_variables(std::move(res), out, varexp_char_idx, vars, errors);
|
return expand_variables(std::move(res), out, varexp_char_idx, vars, errors);
|
||||||
} else {
|
} else {
|
||||||
// Normal cartesian-product expansion.
|
// Normal cartesian-product expansion.
|
||||||
for (const wcstring &item : var_item_list) {
|
for (wcstring &item : var_item_list) {
|
||||||
if (varexp_char_idx == 0 && var_name_and_slice_stop == insize) {
|
if (varexp_char_idx == 0 && var_name_and_slice_stop == insize) {
|
||||||
append_completion(out, item);
|
out->add(std::move(item));
|
||||||
} else {
|
} else {
|
||||||
wcstring new_in(instr, 0, varexp_char_idx);
|
wcstring new_in(instr, 0, varexp_char_idx);
|
||||||
if (!new_in.empty()) {
|
if (!new_in.empty()) {
|
||||||
|
@ -482,8 +482,8 @@ static bool expand_variables(wcstring instr, completion_list_t *out, size_t last
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform brace expansion, placing the expanded strings into \p out.
|
/// Perform brace expansion, placing the expanded strings into \p out.
|
||||||
static expand_result_t expand_braces(wcstring &&instr, expand_flags_t flags, completion_list_t *out,
|
static expand_result_t expand_braces(wcstring &&instr, expand_flags_t flags,
|
||||||
parse_error_list_t *errors) {
|
completion_receiver_t *out, parse_error_list_t *errors) {
|
||||||
bool syntax_error = false;
|
bool syntax_error = false;
|
||||||
int brace_count = 0;
|
int brace_count = 0;
|
||||||
|
|
||||||
|
@ -549,7 +549,7 @@ static expand_result_t expand_braces(wcstring &&instr, expand_flags_t flags, com
|
||||||
}
|
}
|
||||||
|
|
||||||
if (brace_begin == nullptr) {
|
if (brace_begin == nullptr) {
|
||||||
append_completion(out, std::move(instr));
|
out->add(std::move(instr));
|
||||||
return expand_result_t::ok;
|
return expand_result_t::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -594,7 +594,7 @@ static expand_result_t expand_braces(wcstring &&instr, expand_flags_t flags, com
|
||||||
/// Expand a command substitution \p input, executing on \p ctx, and inserting the results into
|
/// Expand a command substitution \p input, executing on \p ctx, and inserting the results into
|
||||||
/// \p out_list, or any errors into \p errors. \return an expand result.
|
/// \p out_list, or any errors into \p errors. \return an expand result.
|
||||||
static expand_result_t expand_cmdsubst(wcstring input, const operation_context_t &ctx,
|
static expand_result_t expand_cmdsubst(wcstring input, const operation_context_t &ctx,
|
||||||
completion_list_t *out_list, parse_error_list_t *errors) {
|
completion_receiver_t *out, parse_error_list_t *errors) {
|
||||||
assert(ctx.parser && "Cannot expand without a parser");
|
assert(ctx.parser && "Cannot expand without a parser");
|
||||||
size_t cursor = 0;
|
size_t cursor = 0;
|
||||||
size_t paren_begin = 0;
|
size_t paren_begin = 0;
|
||||||
|
@ -608,7 +608,7 @@ static expand_result_t expand_cmdsubst(wcstring input, const operation_context_t
|
||||||
return expand_result_t::make_error(STATUS_EXPAND_ERROR);
|
return expand_result_t::make_error(STATUS_EXPAND_ERROR);
|
||||||
}
|
}
|
||||||
case 0: {
|
case 0: {
|
||||||
append_completion(out_list, std::move(input));
|
out->add(std::move(input));
|
||||||
return expand_result_t::ok;
|
return expand_result_t::ok;
|
||||||
}
|
}
|
||||||
case 1: {
|
case 1: {
|
||||||
|
@ -671,9 +671,10 @@ static expand_result_t expand_cmdsubst(wcstring input, const operation_context_t
|
||||||
|
|
||||||
// Recursively call ourselves to expand any remaining command substitutions. The result of this
|
// Recursively call ourselves to expand any remaining command substitutions. The result of this
|
||||||
// recursive call using the tail of the string is inserted into the tail_expand array list
|
// recursive call using the tail of the string is inserted into the tail_expand array list
|
||||||
completion_list_t tail_expand;
|
completion_receiver_t tail_expand_recv;
|
||||||
expand_cmdsubst(input.substr(tail_begin), ctx, &tail_expand,
|
expand_cmdsubst(input.substr(tail_begin), ctx, &tail_expand_recv,
|
||||||
errors); // TODO: offset error locations
|
errors); // TODO: offset error locations
|
||||||
|
completion_list_t tail_expand = tail_expand_recv.take();
|
||||||
|
|
||||||
// Combine the result of the current command substitution with the result of the recursive tail
|
// Combine the result of the current command substitution with the result of the recursive tail
|
||||||
// expansion.
|
// expansion.
|
||||||
|
@ -688,7 +689,7 @@ static expand_result_t expand_cmdsubst(wcstring input, const operation_context_t
|
||||||
whole_item.append(sub_item2);
|
whole_item.append(sub_item2);
|
||||||
whole_item.push_back(INTERNAL_SEPARATOR);
|
whole_item.push_back(INTERNAL_SEPARATOR);
|
||||||
whole_item.append(tail_item.completion);
|
whole_item.append(tail_item.completion);
|
||||||
append_completion(out_list, std::move(whole_item));
|
out->add(std::move(whole_item));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -870,13 +871,13 @@ class expander_t {
|
||||||
/// An expansion stage is a member function pointer.
|
/// An expansion stage is a member function pointer.
|
||||||
/// It accepts the input string (transferring ownership) and returns the list of output
|
/// It accepts the input string (transferring ownership) and returns the list of output
|
||||||
/// completions by reference. It may return an error, which halts expansion.
|
/// completions by reference. It may return an error, which halts expansion.
|
||||||
using stage_t = expand_result_t (expander_t::*)(wcstring, completion_list_t *);
|
using stage_t = expand_result_t (expander_t::*)(wcstring, completion_receiver_t *);
|
||||||
|
|
||||||
expand_result_t stage_cmdsubst(wcstring input, completion_list_t *out);
|
expand_result_t stage_cmdsubst(wcstring input, completion_receiver_t *out);
|
||||||
expand_result_t stage_variables(wcstring input, completion_list_t *out);
|
expand_result_t stage_variables(wcstring input, completion_receiver_t *out);
|
||||||
expand_result_t stage_braces(wcstring input, completion_list_t *out);
|
expand_result_t stage_braces(wcstring input, completion_receiver_t *out);
|
||||||
expand_result_t stage_home_and_self(wcstring input, completion_list_t *out);
|
expand_result_t stage_home_and_self(wcstring input, completion_receiver_t *out);
|
||||||
expand_result_t stage_wildcards(wcstring path_to_expand, completion_list_t *out);
|
expand_result_t stage_wildcards(wcstring path_to_expand, completion_receiver_t *out);
|
||||||
|
|
||||||
expander_t(const operation_context_t &ctx, expand_flags_t flags, parse_error_list_t *errors)
|
expander_t(const operation_context_t &ctx, expand_flags_t flags, parse_error_list_t *errors)
|
||||||
: ctx(ctx), flags(flags), errors(errors) {}
|
: ctx(ctx), flags(flags), errors(errors) {}
|
||||||
|
@ -887,12 +888,12 @@ class expander_t {
|
||||||
parse_error_list_t *errors);
|
parse_error_list_t *errors);
|
||||||
};
|
};
|
||||||
|
|
||||||
expand_result_t expander_t::stage_cmdsubst(wcstring input, completion_list_t *out) {
|
expand_result_t expander_t::stage_cmdsubst(wcstring input, completion_receiver_t *out) {
|
||||||
if (flags & expand_flag::skip_cmdsubst) {
|
if (flags & expand_flag::skip_cmdsubst) {
|
||||||
size_t cur = 0, start = 0, end;
|
size_t cur = 0, start = 0, end;
|
||||||
switch (parse_util_locate_cmdsubst_range(input, &cur, nullptr, &start, &end, true)) {
|
switch (parse_util_locate_cmdsubst_range(input, &cur, nullptr, &start, &end, true)) {
|
||||||
case 0:
|
case 0:
|
||||||
append_completion(out, std::move(input));
|
out->add(std::move(input));
|
||||||
return expand_result_t::ok;
|
return expand_result_t::ok;
|
||||||
case 1:
|
case 1:
|
||||||
append_cmdsub_error(errors, start, L"Command substitutions not allowed");
|
append_cmdsub_error(errors, start, L"Command substitutions not allowed");
|
||||||
|
@ -907,7 +908,7 @@ expand_result_t expander_t::stage_cmdsubst(wcstring input, completion_list_t *ou
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expand_result_t expander_t::stage_variables(wcstring input, completion_list_t *out) {
|
expand_result_t expander_t::stage_variables(wcstring input, completion_receiver_t *out) {
|
||||||
// We accept incomplete strings here, since complete uses expand_string to expand incomplete
|
// We accept incomplete strings here, since complete uses expand_string to expand incomplete
|
||||||
// strings from the commandline.
|
// strings from the commandline.
|
||||||
wcstring next;
|
wcstring next;
|
||||||
|
@ -919,7 +920,7 @@ expand_result_t expander_t::stage_variables(wcstring input, completion_list_t *o
|
||||||
i = L'$';
|
i = L'$';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
append_completion(out, std::move(next));
|
out->add(std::move(next));
|
||||||
} else {
|
} else {
|
||||||
size_t size = next.size();
|
size_t size = next.size();
|
||||||
if (!expand_variables(std::move(next), out, size, ctx.vars, errors)) {
|
if (!expand_variables(std::move(next), out, size, ctx.vars, errors)) {
|
||||||
|
@ -929,20 +930,20 @@ expand_result_t expander_t::stage_variables(wcstring input, completion_list_t *o
|
||||||
return expand_result_t::ok;
|
return expand_result_t::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
expand_result_t expander_t::stage_braces(wcstring input, completion_list_t *out) {
|
expand_result_t expander_t::stage_braces(wcstring input, completion_receiver_t *out) {
|
||||||
return expand_braces(std::move(input), flags, out, errors);
|
return expand_braces(std::move(input), flags, out, errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
expand_result_t expander_t::stage_home_and_self(wcstring input, completion_list_t *out) {
|
expand_result_t expander_t::stage_home_and_self(wcstring input, completion_receiver_t *out) {
|
||||||
if (!(flags & expand_flag::skip_home_directories)) {
|
if (!(flags & expand_flag::skip_home_directories)) {
|
||||||
expand_home_directory(input, ctx.vars);
|
expand_home_directory(input, ctx.vars);
|
||||||
}
|
}
|
||||||
expand_percent_self(input);
|
expand_percent_self(input);
|
||||||
append_completion(out, std::move(input));
|
out->add(std::move(input));
|
||||||
return expand_result_t::ok;
|
return expand_result_t::ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_list_t *out) {
|
expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_receiver_t *out) {
|
||||||
expand_result_t result = expand_result_t::ok;
|
expand_result_t result = expand_result_t::ok;
|
||||||
|
|
||||||
remove_internal_separator(&path_to_expand, flags & expand_flag::skip_wildcards);
|
remove_internal_separator(&path_to_expand, flags & expand_flag::skip_wildcards);
|
||||||
|
@ -1026,14 +1027,13 @@ expand_result_t expander_t::stage_wildcards(wcstring path_to_expand, completion_
|
||||||
[&](const completion_t &a, const completion_t &b) {
|
[&](const completion_t &a, const completion_t &b) {
|
||||||
return wcsfilecmp_glob(a.completion.c_str(), b.completion.c_str()) < 0;
|
return wcsfilecmp_glob(a.completion.c_str(), b.completion.c_str()) < 0;
|
||||||
});
|
});
|
||||||
|
out->add_list(std::move(expanded));
|
||||||
std::move(expanded.begin(), expanded.end(), std::back_inserter(*out));
|
|
||||||
} else {
|
} else {
|
||||||
// Can't fully justify this check. I think it's that SKIP_WILDCARDS is used when completing
|
// Can't fully justify this check. I think it's that SKIP_WILDCARDS is used when completing
|
||||||
// to mean don't do file expansions, so if we're not doing file expansions, just drop this
|
// to mean don't do file expansions, so if we're not doing file expansions, just drop this
|
||||||
// completion on the floor.
|
// completion on the floor.
|
||||||
if (!(flags & expand_flag::for_completions)) {
|
if (!(flags & expand_flag::for_completions)) {
|
||||||
append_completion(out, std::move(path_to_expand));
|
out->add(std::move(path_to_expand));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -1058,9 +1058,10 @@ expand_result_t expander_t::expand_string(wcstring input, completion_list_t *out
|
||||||
&expander_t::stage_wildcards};
|
&expander_t::stage_wildcards};
|
||||||
|
|
||||||
// Load up our single initial completion.
|
// Load up our single initial completion.
|
||||||
completion_list_t completions, output_storage;
|
completion_list_t completions;
|
||||||
append_completion(&completions, input);
|
append_completion(&completions, input);
|
||||||
|
|
||||||
|
completion_receiver_t output_storage;
|
||||||
expand_result_t total_result = expand_result_t::ok;
|
expand_result_t total_result = expand_result_t::ok;
|
||||||
for (stage_t stage : stages) {
|
for (stage_t stage : stages) {
|
||||||
for (completion_t &comp : completions) {
|
for (completion_t &comp : completions) {
|
||||||
|
@ -1077,7 +1078,7 @@ expand_result_t expander_t::expand_string(wcstring input, completion_list_t *out
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output becomes our next stage's input.
|
// Output becomes our next stage's input.
|
||||||
completions.swap(output_storage);
|
output_storage.swap(completions);
|
||||||
output_storage.clear();
|
output_storage.clear();
|
||||||
if (total_result == expand_result_t::error) {
|
if (total_result == expand_result_t::error) {
|
||||||
break;
|
break;
|
||||||
|
@ -1099,9 +1100,7 @@ expand_result_t expander_t::expand_string(wcstring input, completion_list_t *out
|
||||||
if (!(flags & expand_flag::skip_home_directories)) {
|
if (!(flags & expand_flag::skip_home_directories)) {
|
||||||
unexpand_tildes(input, ctx.vars, &completions);
|
unexpand_tildes(input, ctx.vars, &completions);
|
||||||
}
|
}
|
||||||
out_completions->insert(out_completions->end(),
|
vec_append(*out_completions, std::move(completions));
|
||||||
std::make_move_iterator(completions.begin()),
|
|
||||||
std::make_move_iterator(completions.end()));
|
|
||||||
}
|
}
|
||||||
return total_result;
|
return total_result;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue