2016-04-29 11:10:27 +08:00
|
|
|
// String expansion functions. These functions perform several kinds of parameter expansion.
|
2016-04-21 14:00:54 +08:00
|
|
|
// IWYU pragma: no_include <cstddef>
|
|
|
|
#include "config.h"
|
2006-08-11 09:18:35 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
#include <errno.h>
|
|
|
|
#include <pwd.h>
|
2015-07-25 23:14:25 +08:00
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stddef.h>
|
2016-04-29 11:10:27 +08:00
|
|
|
#include <stdlib.h>
|
2005-09-20 21:26:39 +08:00
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
2016-04-29 11:10:27 +08:00
|
|
|
#include <wchar.h>
|
|
|
|
#include <wctype.h>
|
2017-02-13 12:24:22 +08:00
|
|
|
|
2015-09-16 02:40:20 +08:00
|
|
|
#ifdef HAVE_SYS_SYSCTL_H
|
2016-04-21 14:00:54 +08:00
|
|
|
#include <sys/sysctl.h> // IWYU pragma: keep
|
2015-09-16 02:40:20 +08:00
|
|
|
#endif
|
2005-09-20 21:26:39 +08:00
|
|
|
#ifdef SunOS
|
|
|
|
#include <procfs.h>
|
|
|
|
#endif
|
2016-04-21 14:00:54 +08:00
|
|
|
#if __APPLE__
|
|
|
|
#include <sys/proc.h>
|
|
|
|
#else
|
|
|
|
#include <dirent.h>
|
2016-04-29 11:10:27 +08:00
|
|
|
#include <sys/stat.h>
|
2016-04-21 14:00:54 +08:00
|
|
|
#endif
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2017-02-11 10:47:02 +08:00
|
|
|
#include <algorithm>
|
|
|
|
#include <functional>
|
|
|
|
#include <memory> // IWYU pragma: keep
|
|
|
|
#include <type_traits>
|
2017-08-20 00:55:06 +08:00
|
|
|
#include <unordered_map>
|
2017-08-10 02:11:58 +08:00
|
|
|
#include <utility>
|
2017-02-11 10:47:02 +08:00
|
|
|
#include <vector>
|
|
|
|
|
2005-09-20 21:26:39 +08:00
|
|
|
#include "common.h"
|
2016-04-29 11:10:27 +08:00
|
|
|
#include "complete.h"
|
2005-09-20 21:26:39 +08:00
|
|
|
#include "env.h"
|
|
|
|
#include "exec.h"
|
2016-04-29 11:10:27 +08:00
|
|
|
#include "expand.h"
|
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
2018-01-31 10:30:30 +08:00
|
|
|
#include "history.h"
|
2013-11-30 05:31:18 +08:00
|
|
|
#include "iothread.h"
|
2016-04-21 14:00:54 +08:00
|
|
|
#include "parse_constants.h"
|
2016-04-29 11:10:27 +08:00
|
|
|
#include "parse_util.h"
|
|
|
|
#include "path.h"
|
|
|
|
#include "proc.h"
|
2018-01-31 10:30:30 +08:00
|
|
|
#include "reader.h"
|
2016-04-29 11:10:27 +08:00
|
|
|
#include "wildcard.h"
|
2018-03-12 08:36:10 +08:00
|
|
|
#include "wcstringutil.h"
|
2016-04-29 11:10:27 +08:00
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
2016-04-21 14:00:54 +08:00
|
|
|
#ifdef KERN_PROCARGS2
|
|
|
|
#else
|
|
|
|
#include "tokenizer.h"
|
|
|
|
#endif
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
/// Characters which make a string unclean if they are the first character of the string. See \c
|
|
|
|
/// expand_is_clean().
|
2018-03-24 16:34:58 +08:00
|
|
|
#define UNCLEAN_FIRST L"~"
|
2016-04-29 11:10:27 +08:00
|
|
|
/// Unclean characters. See \c expand_is_clean().
|
2005-10-26 18:51:02 +08:00
|
|
|
#define UNCLEAN L"$*?\\\"'({})"
|
|
|
|
|
2016-02-05 17:22:46 +08:00
|
|
|
static void remove_internal_separator(wcstring *s, bool conv);
|
2012-07-17 03:05:36 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
/// Test if the specified argument is clean, i.e. it does not contain any tokens which need to be
|
|
|
|
/// expanded or otherwise altered. Clean strings can be passed through expand_string and expand_one
|
|
|
|
/// without changing them. About two thirds of all strings are clean, so skipping expansion on them
|
|
|
|
/// actually does save a small amount of time, since it avoids multiple memory allocations during
|
|
|
|
/// the expansion process.
|
|
|
|
///
|
|
|
|
/// \param in the string to test
|
|
|
|
static bool expand_is_clean(const wcstring &in) {
|
|
|
|
if (in.empty()) return true;
|
|
|
|
|
|
|
|
// Test characters that have a special meaning in the first character position.
|
|
|
|
if (wcschr(UNCLEAN_FIRST, in.at(0)) != NULL) return false;
|
2006-01-31 03:53:10 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// Test characters that have a special meaning in any character position.
|
2016-02-05 15:57:21 +08:00
|
|
|
return in.find_first_of(UNCLEAN) == wcstring::npos;
|
2005-10-26 18:51:02 +08:00
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
/// Append a syntax error to the given error list.
|
|
|
|
static void append_syntax_error(parse_error_list_t *errors, size_t source_start, const wchar_t *fmt,
|
|
|
|
...) {
|
2017-07-27 11:17:04 +08:00
|
|
|
if (!errors) return;
|
|
|
|
|
|
|
|
parse_error_t error;
|
|
|
|
error.source_start = source_start;
|
|
|
|
error.source_length = 0;
|
|
|
|
error.code = parse_error_syntax;
|
|
|
|
|
|
|
|
va_list va;
|
|
|
|
va_start(va, fmt);
|
|
|
|
error.text = vformat_string(fmt, va);
|
|
|
|
va_end(va);
|
|
|
|
|
|
|
|
errors->push_back(error);
|
2014-03-22 08:13:33 +08:00
|
|
|
}
|
|
|
|
|
2017-07-27 11:17:04 +08:00
|
|
|
/// Append a cmdsub error to the given error list. But only do so if the error hasn't already been
|
|
|
|
/// recorded. This is needed because command substitution is a recursive process and some errors
|
|
|
|
/// could consequently be recorded more than once.
|
2016-04-29 11:10:27 +08:00
|
|
|
static void append_cmdsub_error(parse_error_list_t *errors, size_t source_start, const wchar_t *fmt,
|
|
|
|
...) {
|
2017-07-27 11:17:04 +08:00
|
|
|
if (!errors) return;
|
|
|
|
|
|
|
|
parse_error_t error;
|
|
|
|
error.source_start = source_start;
|
|
|
|
error.source_length = 0;
|
|
|
|
error.code = parse_error_cmdsubst;
|
|
|
|
|
|
|
|
va_list va;
|
|
|
|
va_start(va, fmt);
|
|
|
|
error.text = vformat_string(fmt, va);
|
|
|
|
va_end(va);
|
|
|
|
|
|
|
|
for (auto it : *errors) {
|
|
|
|
if (error.text == it.text) return;
|
2014-03-22 08:13:33 +08:00
|
|
|
}
|
2017-07-27 11:17:04 +08:00
|
|
|
|
|
|
|
errors->push_back(error);
|
2014-03-22 08:13:33 +08:00
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
/// Test if the specified string does not contain character which can not be used inside a quoted
|
|
|
|
/// string.
|
|
|
|
static int is_quotable(const wchar_t *str) {
|
|
|
|
switch (*str) {
|
|
|
|
case 0: {
|
2012-11-19 16:31:03 +08:00
|
|
|
return 1;
|
2016-04-29 11:10:27 +08:00
|
|
|
}
|
2012-11-19 16:31:03 +08:00
|
|
|
case L'\n':
|
|
|
|
case L'\t':
|
|
|
|
case L'\r':
|
|
|
|
case L'\b':
|
2016-12-24 08:37:00 +08:00
|
|
|
case L'\e': {
|
2012-11-19 16:31:03 +08:00
|
|
|
return 0;
|
2016-04-29 11:10:27 +08:00
|
|
|
}
|
|
|
|
default: { return is_quotable(str + 1); }
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
return 0;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
static int is_quotable(const wcstring &str) { return is_quotable(str.c_str()); }
|
2006-01-31 03:53:10 +08:00
|
|
|
|
2017-08-06 12:40:27 +08:00
|
|
|
wcstring expand_escape_variable(const env_var_t &var) {
|
2012-11-19 08:30:30 +08:00
|
|
|
wcstring buff;
|
2017-08-10 02:11:58 +08:00
|
|
|
wcstring_list_t lst;
|
2006-06-21 08:48:36 +08:00
|
|
|
|
2017-08-06 12:40:27 +08:00
|
|
|
var.to_list(lst);
|
2017-08-10 02:11:58 +08:00
|
|
|
if (lst.size() == 0) {
|
|
|
|
; // empty list expands to nothing
|
|
|
|
} else if (lst.size() == 1) {
|
2016-10-23 11:32:25 +08:00
|
|
|
const wcstring &el = lst.at(0);
|
|
|
|
|
|
|
|
if (el.find(L' ') != wcstring::npos && is_quotable(el)) {
|
|
|
|
buff.append(L"'");
|
|
|
|
buff.append(el);
|
|
|
|
buff.append(L"'");
|
|
|
|
} else {
|
|
|
|
buff.append(escape_string(el, 1));
|
2016-04-29 11:10:27 +08:00
|
|
|
}
|
2016-10-23 11:32:25 +08:00
|
|
|
} else {
|
|
|
|
for (size_t j = 0; j < lst.size(); j++) {
|
|
|
|
const wcstring &el = lst.at(j);
|
|
|
|
if (j) buff.append(L" ");
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-10-23 11:32:25 +08:00
|
|
|
if (is_quotable(el)) {
|
2012-11-19 08:30:30 +08:00
|
|
|
buff.append(L"'");
|
|
|
|
buff.append(el);
|
|
|
|
buff.append(L"'");
|
2016-04-29 11:10:27 +08:00
|
|
|
} else {
|
2012-11-19 08:30:30 +08:00
|
|
|
buff.append(escape_string(el, 1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-08-10 02:11:58 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
return buff;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
/// Parse an array slicing specification Returns 0 on success. If a parse error occurs, returns the
|
|
|
|
/// index of the bad token. Note that 0 can never be a bad index because the string always starts
|
|
|
|
/// with [.
|
|
|
|
static size_t parse_slice(const wchar_t *in, wchar_t **end_ptr, std::vector<long> &idx,
|
|
|
|
std::vector<size_t> &source_positions, size_t array_size) {
|
2012-08-05 04:02:44 +08:00
|
|
|
const long size = (long)array_size;
|
2018-03-11 03:16:07 +08:00
|
|
|
size_t pos = 1; // skip past the opening square brace
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
while (1) {
|
|
|
|
while (iswspace(in[pos]) || (in[pos] == INTERNAL_SEPARATOR)) pos++;
|
|
|
|
if (in[pos] == L']') {
|
2012-11-19 08:30:30 +08:00
|
|
|
pos++;
|
|
|
|
break;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2014-03-22 08:13:33 +08:00
|
|
|
const size_t i1_src_pos = pos;
|
2016-11-23 12:24:03 +08:00
|
|
|
const wchar_t *end;
|
|
|
|
long tmp = fish_wcstol(&in[pos], &end);
|
|
|
|
// We don't test `*end` as is typically done because we expect it to not be the null char.
|
|
|
|
// Ignore the case of errno==-1 because it means the end char wasn't the null char.
|
|
|
|
if (errno > 0) {
|
2014-08-21 15:26:14 +08:00
|
|
|
return pos;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-04-29 11:10:27 +08:00
|
|
|
// debug( 0, L"Push idx %d", tmp );
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2017-06-16 20:17:17 +08:00
|
|
|
long i1 = tmp > -1 ? tmp : size + tmp + 1;
|
2016-04-29 11:10:27 +08:00
|
|
|
pos = end - in;
|
|
|
|
while (in[pos] == INTERNAL_SEPARATOR) pos++;
|
|
|
|
if (in[pos] == L'.' && in[pos + 1] == L'.') {
|
|
|
|
pos += 2;
|
|
|
|
while (in[pos] == INTERNAL_SEPARATOR) pos++;
|
2014-04-01 01:01:39 +08:00
|
|
|
|
2014-03-22 08:13:33 +08:00
|
|
|
const size_t number_start = pos;
|
2016-11-23 12:24:03 +08:00
|
|
|
long tmp1 = fish_wcstol(&in[pos], &end);
|
|
|
|
// Ignore the case of errno==-1 because it means the end char wasn't the null char.
|
|
|
|
if (errno > 0) {
|
2014-08-21 15:26:14 +08:00
|
|
|
return pos;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-04-29 11:10:27 +08:00
|
|
|
pos = end - in;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
|
|
|
// debug( 0, L"Push range %d %d", tmp, tmp1 );
|
2016-04-29 11:10:27 +08:00
|
|
|
long i2 = tmp1 > -1 ? tmp1 : size + tmp1 + 1;
|
2017-06-16 20:17:17 +08:00
|
|
|
// Clamp to array size, but only when doing a range,
|
|
|
|
// and only when just one is too high.
|
|
|
|
if (i1 > size && i2 > size) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
i1 = i1 < size ? i1 : size;
|
|
|
|
i2 = i2 < size ? i2 : size;
|
2012-11-19 08:30:30 +08:00
|
|
|
// debug( 0, L"Push range idx %d %d", i1, i2 );
|
2016-04-29 11:10:27 +08:00
|
|
|
short direction = i2 < i1 ? -1 : 1;
|
|
|
|
for (long jjj = i1; jjj * direction <= i2 * direction; jjj += direction) {
|
2012-11-19 08:30:30 +08:00
|
|
|
// debug(0, L"Expand range [subst]: %i\n", jjj);
|
|
|
|
idx.push_back(jjj);
|
2014-03-22 08:13:33 +08:00
|
|
|
source_positions.push_back(number_start);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
// debug( 0, L"Push idx %d", tmp );
|
|
|
|
idx.push_back(i1);
|
2014-03-22 08:13:33 +08:00
|
|
|
source_positions.push_back(i1_src_pos);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
if (end_ptr) {
|
|
|
|
*end_ptr = (wchar_t *)(in + pos);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
return 0;
|
2006-08-24 21:39:04 +08:00
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
/// Expand all environment variables in the string *ptr.
|
|
|
|
///
|
|
|
|
/// This function is slow, fragile and complicated. There are lots of little corner cases, like
|
|
|
|
/// $$foo should do a double expansion, $foo$bar should not double expand bar, etc. Also, it's easy
|
|
|
|
/// to accidentally leak memory on array out of bounds errors an various other situations. All in
|
|
|
|
/// all, this function should be rewritten, split out into multiple logical units and carefully
|
|
|
|
/// tested. After that, it can probably be optimized to do fewer memory allocations, fewer string
|
|
|
|
/// scans and overall just less work. But until that happens, don't edit it unless you know exactly
|
|
|
|
/// what you are doing, and do proper testing afterwards.
|
|
|
|
///
|
|
|
|
/// This function operates on strings backwards, starting at last_idx.
|
|
|
|
///
|
|
|
|
/// 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()
|
|
|
|
/// as last_idx instead of string.size()-1.
|
2018-01-31 09:39:25 +08:00
|
|
|
static bool expand_variables(const wcstring &instr, std::vector<completion_t> *out, size_t last_idx,
|
|
|
|
parse_error_list_t *errors) {
|
2014-08-29 04:19:38 +08:00
|
|
|
const size_t insize = instr.size();
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// last_idx may be 1 past the end of the string, but no further.
|
2018-01-31 09:39:25 +08:00
|
|
|
assert(last_idx <= insize && "Invalid last_idx");
|
2016-04-29 11:10:27 +08:00
|
|
|
if (last_idx == 0) {
|
2014-08-25 05:28:31 +08:00
|
|
|
append_completion(out, instr);
|
|
|
|
return true;
|
|
|
|
}
|
2014-08-29 04:19:38 +08:00
|
|
|
|
2018-01-31 09:39:25 +08:00
|
|
|
// Locate the last VARIABLE_EXPAND or VARIABLE_EXPAND_SINGLE
|
|
|
|
bool is_single = false;
|
|
|
|
size_t varexp_char_idx = last_idx;
|
|
|
|
while (varexp_char_idx--) {
|
|
|
|
const wchar_t c = instr.at(varexp_char_idx);
|
|
|
|
if (c == VARIABLE_EXPAND || c == VARIABLE_EXPAND_SINGLE) {
|
|
|
|
is_single = (c == VARIABLE_EXPAND_SINGLE);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (varexp_char_idx >= instr.size()) {
|
|
|
|
// No variable expand char, we're done.
|
|
|
|
append_completion(out, instr);
|
|
|
|
return true;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-01-31 09:39:25 +08:00
|
|
|
// Get the variable name.
|
|
|
|
const size_t var_name_start = varexp_char_idx + 1;
|
|
|
|
size_t var_name_stop = var_name_start;
|
|
|
|
while (var_name_stop < insize) {
|
|
|
|
const wchar_t nc = instr.at(var_name_stop);
|
|
|
|
if (nc == VARIABLE_EXPAND_EMPTY) {
|
|
|
|
var_name_stop++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!valid_var_name_char(nc)) break;
|
|
|
|
var_name_stop++;
|
|
|
|
}
|
|
|
|
assert(var_name_stop >= var_name_start && "Bogus variable name indexes");
|
|
|
|
const size_t var_name_len = var_name_stop - var_name_start;
|
|
|
|
|
|
|
|
// It's an error if the name is empty.
|
|
|
|
if (var_name_len == 0) {
|
|
|
|
if (errors) {
|
|
|
|
parse_util_expand_variable_error(instr, 0 /* global_token_pos */, varexp_char_idx,
|
|
|
|
errors);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2014-04-01 01:01:39 +08:00
|
|
|
|
2018-01-31 09:39:25 +08:00
|
|
|
// Get the variable name as a string, then try to get the variable from env.
|
|
|
|
const wcstring var_name(instr, var_name_start, var_name_len);
|
2018-01-31 10:30:30 +08:00
|
|
|
// Do a dirty hack to make sliced history fast (#4650). We expand from either a variable, or a
|
|
|
|
// history_t. Note that "history" is read only in env.cpp so it's safe to special-case it in
|
|
|
|
// this way (it cannot be shadowed, etc).
|
|
|
|
history_t *history = nullptr;
|
|
|
|
maybe_t<env_var_t> var{};
|
|
|
|
if (var_name == L"history") {
|
|
|
|
// We do this only on the main thread, matching env.cpp.
|
|
|
|
if (is_main_thread()) {
|
|
|
|
history = reader_get_history();
|
|
|
|
}
|
|
|
|
} else if (var_name != wcstring{VARIABLE_EXPAND_EMPTY}) {
|
|
|
|
var = env_get(var_name);
|
|
|
|
}
|
2018-01-31 09:39:25 +08:00
|
|
|
|
|
|
|
// Parse out any following slice.
|
|
|
|
// Record the end of the variable name and any following slice.
|
|
|
|
size_t var_name_and_slice_stop = var_name_stop;
|
2018-01-31 10:30:30 +08:00
|
|
|
bool all_values = true;
|
2018-01-31 09:39:25 +08:00
|
|
|
const size_t slice_start = var_name_stop;
|
|
|
|
// List of indexes, and parallel array of source positions of each index in the variable list.
|
2012-01-31 13:33:15 +08:00
|
|
|
std::vector<long> var_idx_list;
|
2014-03-22 08:13:33 +08:00
|
|
|
std::vector<size_t> var_pos_list;
|
2018-01-31 09:39:25 +08:00
|
|
|
if (slice_start < insize && instr.at(slice_start) == L'[') {
|
2018-01-31 10:30:30 +08:00
|
|
|
all_values = false;
|
2018-01-31 09:39:25 +08:00
|
|
|
const wchar_t *in = instr.c_str();
|
|
|
|
wchar_t *slice_end;
|
|
|
|
// If a variable is missing, behave as though we have one value, so that $var[1] always
|
|
|
|
// works.
|
2018-01-31 10:30:30 +08:00
|
|
|
size_t effective_val_count = 1;
|
|
|
|
if (var) {
|
|
|
|
effective_val_count = var->as_list().size();
|
|
|
|
} else if (history) {
|
|
|
|
effective_val_count = history->size();
|
|
|
|
}
|
2018-01-31 09:39:25 +08:00
|
|
|
size_t bad_pos = parse_slice(in + slice_start, &slice_end, var_idx_list, var_pos_list,
|
|
|
|
effective_val_count);
|
|
|
|
if (bad_pos != 0) {
|
|
|
|
append_syntax_error(errors, slice_start + bad_pos, L"Invalid index value");
|
|
|
|
return false;
|
2016-10-31 06:37:16 +08:00
|
|
|
}
|
2018-01-31 09:39:25 +08:00
|
|
|
var_name_and_slice_stop = (slice_end - in);
|
|
|
|
}
|
2016-10-31 06:37:16 +08:00
|
|
|
|
2018-01-31 10:30:30 +08:00
|
|
|
if (!var && !history) {
|
2018-01-31 09:39:25 +08:00
|
|
|
// Expanding a non-existent variable.
|
|
|
|
if (!is_single) {
|
|
|
|
// Normal expansions of missing variables successfully expand to nothing.
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
// Expansion to single argument.
|
|
|
|
// Replace the variable name and slice with VARIABLE_EXPAND_EMPTY.
|
|
|
|
wcstring res(instr, 0, varexp_char_idx);
|
|
|
|
if (!res.empty() && res.back() == VARIABLE_EXPAND_SINGLE) {
|
|
|
|
res.push_back(VARIABLE_EXPAND_EMPTY);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2018-01-31 09:39:25 +08:00
|
|
|
res.append(instr, var_name_and_slice_stop, wcstring::npos);
|
|
|
|
return expand_variables(res, out, varexp_char_idx, errors);
|
2016-10-31 06:37:16 +08:00
|
|
|
}
|
2018-01-31 09:39:25 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-01-31 10:30:30 +08:00
|
|
|
// Ok, we have a variable or a history. Let's expand it.
|
2018-01-31 09:39:25 +08:00
|
|
|
// Start by respecting the sliced elements.
|
2018-01-31 10:30:30 +08:00
|
|
|
assert((var || history) && "Should have variable or history here");
|
|
|
|
wcstring_list_t var_item_list;
|
|
|
|
if (all_values) {
|
|
|
|
if (history) {
|
|
|
|
history->get_history(var_item_list);
|
|
|
|
} else {
|
|
|
|
var->to_list(var_item_list);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// We have to respect the slice.
|
|
|
|
if (history) {
|
|
|
|
// Ask history to map indexes to item strings.
|
|
|
|
// Note this may have missing entries for out-of-bounds.
|
|
|
|
auto item_map = history->items_at_indexes(var_idx_list);
|
|
|
|
for (long item_index : var_idx_list) {
|
|
|
|
auto iter = item_map.find(item_index);
|
|
|
|
if (iter != item_map.end()) {
|
|
|
|
var_item_list.push_back(iter->second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const wcstring_list_t &all_var_items = var->as_list();
|
|
|
|
for (long item_index : var_idx_list) {
|
|
|
|
// Check that we are within array bounds. If not, skip the element. Note:
|
|
|
|
// Negative indices (`echo $foo[-1]`) are already converted to positive ones
|
|
|
|
// here, So tmp < 1 means it's definitely not in.
|
|
|
|
// Note we are 1-based.
|
|
|
|
if (item_index >= 1 && size_t(item_index) <= all_var_items.size()) {
|
|
|
|
var_item_list.push_back(all_var_items.at(item_index - 1));
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-10-31 06:37:16 +08:00
|
|
|
}
|
2018-01-31 09:39:25 +08:00
|
|
|
}
|
2016-10-31 06:37:16 +08:00
|
|
|
|
2018-01-31 09:39:25 +08:00
|
|
|
if (is_single) {
|
|
|
|
wcstring res(instr, 0, varexp_char_idx);
|
|
|
|
if (!res.empty()) {
|
|
|
|
if (res.back() != VARIABLE_EXPAND_SINGLE) {
|
|
|
|
res.push_back(INTERNAL_SEPARATOR);
|
|
|
|
} else if (var_item_list.empty() || var_item_list.front().empty()) {
|
|
|
|
// First expansion is empty, but we need to recursively expand.
|
|
|
|
res.push_back(VARIABLE_EXPAND_EMPTY);
|
2016-10-31 06:37:16 +08:00
|
|
|
}
|
2018-01-31 09:39:25 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-01-31 09:39:25 +08:00
|
|
|
// Append all entries in var_item_list, separated by spaces.
|
|
|
|
// Remove the last space.
|
|
|
|
if (!var_item_list.empty()) {
|
|
|
|
for (const wcstring &item : var_item_list) {
|
|
|
|
res.append(item);
|
|
|
|
res.push_back(L' ');
|
2016-10-31 06:37:16 +08:00
|
|
|
}
|
2018-01-31 09:39:25 +08:00
|
|
|
res.pop_back();
|
|
|
|
}
|
|
|
|
res.append(instr, var_name_and_slice_stop, wcstring::npos);
|
|
|
|
return expand_variables(res, out, varexp_char_idx, errors);
|
|
|
|
} else {
|
|
|
|
// Normal cartesian-product expansion.
|
|
|
|
for (const wcstring &item : var_item_list) {
|
|
|
|
if (varexp_char_idx == 0 && var_name_and_slice_stop == insize) {
|
|
|
|
append_completion(out, item);
|
2016-10-31 06:37:16 +08:00
|
|
|
} else {
|
2018-01-31 09:39:25 +08:00
|
|
|
wcstring new_in(instr, 0, varexp_char_idx);
|
|
|
|
if (!new_in.empty()) {
|
|
|
|
if (new_in.back() != VARIABLE_EXPAND) {
|
|
|
|
new_in.push_back(INTERNAL_SEPARATOR);
|
|
|
|
} else if (item.empty()) {
|
|
|
|
new_in.push_back(VARIABLE_EXPAND_EMPTY);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2018-01-31 09:39:25 +08:00
|
|
|
new_in.append(item);
|
|
|
|
new_in.append(instr, var_name_and_slice_stop, wcstring::npos);
|
|
|
|
if (!expand_variables(new_in, out, varexp_char_idx, errors)) {
|
|
|
|
return false;
|
|
|
|
}
|
2016-10-31 06:37:16 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2018-01-31 09:39:25 +08:00
|
|
|
return true;
|
2011-12-27 11:18:46 +08:00
|
|
|
}
|
|
|
|
|
2018-03-11 03:16:07 +08:00
|
|
|
/// Perform brace expansion.
|
|
|
|
static expand_error_t expand_braces(const wcstring &instr, expand_flags_t flags,
|
2016-04-29 11:10:27 +08:00
|
|
|
std::vector<completion_t> *out, parse_error_list_t *errors) {
|
2012-11-19 08:30:30 +08:00
|
|
|
bool syntax_error = false;
|
2018-03-11 03:16:07 +08:00
|
|
|
int brace_count = 0;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-03-11 03:16:07 +08:00
|
|
|
const wchar_t *brace_begin = NULL, *brace_end = NULL;
|
2012-11-21 05:52:53 +08:00
|
|
|
const wchar_t *last_sep = NULL;
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
const wchar_t *item_begin;
|
2018-03-11 03:16:07 +08:00
|
|
|
size_t length_preceding_braces, length_following_braces, tot_len;
|
2006-01-31 03:53:10 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
const wchar_t *const in = instr.c_str();
|
|
|
|
|
2018-03-11 03:16:07 +08:00
|
|
|
// Locate the first non-nested brace pair.
|
2016-04-29 11:10:27 +08:00
|
|
|
for (const wchar_t *pos = in; (*pos) && !syntax_error; pos++) {
|
|
|
|
switch (*pos) {
|
2018-03-11 03:16:07 +08:00
|
|
|
case BRACE_BEGIN: {
|
|
|
|
if (brace_count == 0) brace_begin = pos;
|
|
|
|
brace_count++;
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2018-03-11 03:16:07 +08:00
|
|
|
case BRACE_END: {
|
|
|
|
brace_count--;
|
|
|
|
if (brace_count < 0) {
|
2012-11-19 16:31:03 +08:00
|
|
|
syntax_error = true;
|
2018-03-11 03:16:07 +08:00
|
|
|
} else if (brace_count == 0) {
|
|
|
|
brace_end = pos;
|
2012-11-21 05:52:53 +08:00
|
|
|
}
|
2017-07-05 05:50:01 +08:00
|
|
|
break;
|
2012-11-19 16:31:03 +08:00
|
|
|
}
|
2018-03-11 03:16:07 +08:00
|
|
|
case BRACE_SEP: {
|
|
|
|
if (brace_count == 1) last_sep = pos;
|
2016-11-02 13:36:30 +08:00
|
|
|
break;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-11-03 09:29:14 +08:00
|
|
|
default: {
|
|
|
|
break; // we ignore all other characters here
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-11 03:16:07 +08:00
|
|
|
if (brace_count > 0) {
|
2016-04-29 11:10:27 +08:00
|
|
|
if (!(flags & EXPAND_FOR_COMPLETIONS)) {
|
2012-11-19 08:30:30 +08:00
|
|
|
syntax_error = true;
|
2016-04-29 11:10:27 +08:00
|
|
|
} else {
|
2018-03-11 03:16:07 +08:00
|
|
|
// The user hasn't typed an end brace yet; make one up and append it, then expand
|
2016-04-29 11:10:27 +08:00
|
|
|
// that.
|
2012-02-23 04:00:02 +08:00
|
|
|
wcstring mod;
|
2016-04-29 11:10:27 +08:00
|
|
|
if (last_sep) {
|
2018-03-11 03:16:07 +08:00
|
|
|
mod.append(in, brace_begin - in + 1);
|
2016-04-29 11:10:27 +08:00
|
|
|
mod.append(last_sep + 1);
|
2018-03-11 03:16:07 +08:00
|
|
|
mod.push_back(BRACE_END);
|
2016-04-29 11:10:27 +08:00
|
|
|
} else {
|
2012-02-23 04:00:02 +08:00
|
|
|
mod.append(in);
|
2018-03-11 03:16:07 +08:00
|
|
|
mod.push_back(BRACE_END);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// Note: this code looks very fishy, apparently it has never worked.
|
2018-03-11 03:16:07 +08:00
|
|
|
return expand_braces(mod, 1, out, errors);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
|
|
|
|
2018-01-02 22:20:19 +08:00
|
|
|
// Expand a literal "{}" to itself because it is useless otherwise,
|
|
|
|
// and this eases e.g. `find -exec {}`. See #1109.
|
2018-03-11 03:16:07 +08:00
|
|
|
if (brace_begin + 1 == brace_end) {
|
2018-01-02 22:20:19 +08:00
|
|
|
wcstring newstr = instr;
|
2018-03-11 03:16:07 +08:00
|
|
|
newstr.at(brace_begin - in) = L'{';
|
|
|
|
newstr.at(brace_end - in) = L'}';
|
|
|
|
return expand_braces(newstr, flags, out, errors);
|
2018-01-02 22:20:19 +08:00
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
if (syntax_error) {
|
2018-03-11 03:16:07 +08:00
|
|
|
append_syntax_error(errors, SOURCE_LOCATION_UNKNOWN, _(L"Mismatched braces"));
|
2016-02-05 17:22:46 +08:00
|
|
|
return EXPAND_ERROR;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-03-11 03:16:07 +08:00
|
|
|
if (brace_begin == NULL) {
|
2012-11-21 05:52:53 +08:00
|
|
|
append_completion(out, instr);
|
2016-02-05 17:22:46 +08:00
|
|
|
return EXPAND_OK;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
|
2018-03-11 03:16:07 +08:00
|
|
|
length_preceding_braces = (brace_begin - in);
|
|
|
|
length_following_braces = wcslen(brace_end) - 1;
|
|
|
|
tot_len = length_preceding_braces + length_following_braces;
|
|
|
|
item_begin = brace_begin + 1;
|
|
|
|
for (const wchar_t *pos = (brace_begin + 1); true; pos++) {
|
|
|
|
if (brace_count == 0 && ((*pos == BRACE_SEP) || (pos == brace_end))) {
|
2016-10-23 02:21:13 +08:00
|
|
|
assert(pos >= item_begin);
|
|
|
|
size_t item_len = pos - item_begin;
|
2018-03-12 11:02:43 +08:00
|
|
|
wcstring item = wcstring(item_begin, item_len);
|
2018-03-12 20:31:28 +08:00
|
|
|
item = trim(item, (const wchar_t[]) { BRACE_SPACE, L'\0' });
|
2018-03-12 11:02:43 +08:00
|
|
|
for (auto &c : item) {
|
|
|
|
if (c == BRACE_SPACE) {
|
|
|
|
c = ' ';
|
|
|
|
}
|
|
|
|
}
|
2016-10-23 02:21:13 +08:00
|
|
|
|
|
|
|
wcstring whole_item;
|
|
|
|
whole_item.reserve(tot_len + item_len + 2);
|
2018-03-11 03:16:07 +08:00
|
|
|
whole_item.append(in, length_preceding_braces);
|
2018-03-12 11:02:43 +08:00
|
|
|
whole_item.append(item.begin(), item.end());
|
2018-03-11 03:16:07 +08:00
|
|
|
whole_item.append(brace_end + 1);
|
|
|
|
expand_braces(whole_item, flags, out, errors);
|
2016-10-23 02:21:13 +08:00
|
|
|
|
|
|
|
item_begin = pos + 1;
|
2018-03-11 03:16:07 +08:00
|
|
|
if (pos == brace_end) break;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-03-11 03:16:07 +08:00
|
|
|
if (*pos == BRACE_BEGIN) {
|
|
|
|
brace_count++;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-03-11 03:16:07 +08:00
|
|
|
if (*pos == BRACE_END) {
|
|
|
|
brace_count--;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2016-02-05 17:22:46 +08:00
|
|
|
return EXPAND_OK;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
/// Perform cmdsubst expansion.
|
2017-07-27 11:17:04 +08:00
|
|
|
static bool expand_cmdsubst(const wcstring &input, std::vector<completion_t> *out_list,
|
|
|
|
parse_error_list_t *errors) {
|
|
|
|
wchar_t *paren_begin = nullptr, *paren_end = nullptr;
|
|
|
|
wchar_t *tail_begin = nullptr;
|
2012-11-19 08:30:30 +08:00
|
|
|
size_t i, j;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
const wchar_t *const in = input.c_str();
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-02-19 12:31:35 +08:00
|
|
|
switch (parse_util_locate_cmdsubst(in, &paren_begin, &paren_end, false)) {
|
2016-04-29 11:10:27 +08:00
|
|
|
case -1: {
|
|
|
|
append_syntax_error(errors, SOURCE_LOCATION_UNKNOWN, L"Mismatched parenthesis");
|
2017-07-27 11:17:04 +08:00
|
|
|
return false;
|
2016-04-29 11:10:27 +08:00
|
|
|
}
|
|
|
|
case 0: {
|
2014-01-08 06:57:58 +08:00
|
|
|
append_completion(out_list, input);
|
2017-07-27 11:17:04 +08:00
|
|
|
return true;
|
2016-04-29 11:10:27 +08:00
|
|
|
}
|
|
|
|
case 1: {
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2016-04-29 11:10:27 +08:00
|
|
|
}
|
2016-11-03 09:29:14 +08:00
|
|
|
default: {
|
|
|
|
DIE("unhandled parse_ret value");
|
|
|
|
break;
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2017-07-27 11:17:04 +08:00
|
|
|
wcstring_list_t sub_res;
|
|
|
|
const wcstring subcmd(paren_begin + 1, paren_end - paren_begin - 1);
|
|
|
|
if (exec_subshell(subcmd, sub_res, true /* apply_exit_status */, true /* is_subcmd */) == -1) {
|
2016-04-29 11:10:27 +08:00
|
|
|
append_cmdsub_error(errors, SOURCE_LOCATION_UNKNOWN,
|
|
|
|
L"Unknown error while evaulating command substitution");
|
2017-07-27 11:17:04 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (proc_get_last_status() == STATUS_READ_TOO_MUCH) {
|
|
|
|
append_cmdsub_error(
|
|
|
|
errors, in - paren_begin,
|
|
|
|
_(L"Too much data emitted by command substitution so it was discarded\n"));
|
|
|
|
return false;
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2017-07-27 11:17:04 +08:00
|
|
|
tail_begin = paren_end + 1;
|
2016-04-29 11:10:27 +08:00
|
|
|
if (*tail_begin == L'[') {
|
2012-11-19 08:30:30 +08:00
|
|
|
std::vector<long> slice_idx;
|
2014-03-22 08:13:33 +08:00
|
|
|
std::vector<size_t> slice_source_positions;
|
2016-04-29 11:10:27 +08:00
|
|
|
const wchar_t *const slice_begin = tail_begin;
|
2012-11-19 08:30:30 +08:00
|
|
|
wchar_t *slice_end;
|
2014-08-21 15:26:14 +08:00
|
|
|
size_t bad_pos;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
bad_pos =
|
|
|
|
parse_slice(slice_begin, &slice_end, slice_idx, slice_source_positions, sub_res.size());
|
|
|
|
if (bad_pos != 0) {
|
2014-08-21 15:26:14 +08:00
|
|
|
append_syntax_error(errors, slice_begin - in + bad_pos, L"Invalid index value");
|
2017-07-27 11:17:04 +08:00
|
|
|
return false;
|
2016-05-05 06:19:47 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-05-05 06:19:47 +08:00
|
|
|
wcstring_list_t sub_res2;
|
|
|
|
tail_begin = slice_end;
|
|
|
|
for (i = 0; i < slice_idx.size(); i++) {
|
|
|
|
long idx = slice_idx.at(i);
|
2017-04-08 03:48:44 +08:00
|
|
|
if ((size_t)idx > sub_res.size() || idx < 1) {
|
|
|
|
continue;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-05-05 06:19:47 +08:00
|
|
|
idx = idx - 1;
|
|
|
|
|
|
|
|
sub_res2.push_back(sub_res.at(idx));
|
|
|
|
// debug( 0, L"Pushing item '%ls' with index %d onto sliced result", al_get(
|
|
|
|
// sub_res, idx ), idx );
|
|
|
|
// sub_res[idx] = 0; // ??
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-05-05 06:19:47 +08:00
|
|
|
sub_res = sub_res2;
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// 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
|
2012-01-17 00:56:47 +08:00
|
|
|
std::vector<completion_t> tail_expand;
|
2016-04-29 11:10:27 +08:00
|
|
|
expand_cmdsubst(tail_begin, &tail_expand, errors); // TODO: offset error locations
|
|
|
|
|
|
|
|
// Combine the result of the current command substitution with the result of the recursive tail
|
|
|
|
// expansion.
|
|
|
|
for (i = 0; i < sub_res.size(); i++) {
|
2014-01-08 06:57:58 +08:00
|
|
|
const wcstring &sub_item = sub_res.at(i);
|
|
|
|
const wcstring sub_item2 = escape_string(sub_item, 1);
|
|
|
|
|
|
|
|
wcstring whole_item;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
for (j = 0; j < tail_expand.size(); j++) {
|
2014-01-08 06:57:58 +08:00
|
|
|
whole_item.clear();
|
|
|
|
const wcstring &tail_item = tail_expand.at(j).completion;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// sb_append_substring( &whole_item, in, len1 );
|
2017-07-27 11:17:04 +08:00
|
|
|
whole_item.append(in, paren_begin - in);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// sb_append_char( &whole_item, INTERNAL_SEPARATOR );
|
2011-12-27 11:18:46 +08:00
|
|
|
whole_item.push_back(INTERNAL_SEPARATOR);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// sb_append_substring( &whole_item, sub_item2, item_len );
|
2012-11-19 08:30:30 +08:00
|
|
|
whole_item.append(sub_item2);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// sb_append_char( &whole_item, INTERNAL_SEPARATOR );
|
2011-12-27 11:18:46 +08:00
|
|
|
whole_item.push_back(INTERNAL_SEPARATOR);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// sb_append( &whole_item, tail_item );
|
2011-12-27 11:18:46 +08:00
|
|
|
whole_item.append(tail_item);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// al_push( out, whole_item.buff );
|
2014-01-08 06:57:58 +08:00
|
|
|
append_completion(out_list, whole_item);
|
2011-12-27 11:18:46 +08:00
|
|
|
}
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2017-07-27 11:17:04 +08:00
|
|
|
if (proc_get_last_status() == STATUS_READ_TOO_MUCH) return false;
|
|
|
|
return true;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// Given that input[0] is HOME_DIRECTORY or tilde (ugh), return the user's name. Return the empty
|
|
|
|
// string if it is just a tilde. Also return by reference the index of the first character of the
|
|
|
|
// remaining part of the string (e.g. the subsequent slash).
|
|
|
|
static wcstring get_home_directory_name(const wcstring &input, size_t *out_tail_idx) {
|
|
|
|
const wchar_t *const in = input.c_str();
|
2013-04-08 14:54:43 +08:00
|
|
|
assert(in[0] == HOME_DIRECTORY || in[0] == L'~');
|
|
|
|
size_t tail_idx;
|
|
|
|
|
|
|
|
const wchar_t *name_end = wcschr(in, L'/');
|
2016-04-29 11:10:27 +08:00
|
|
|
if (name_end) {
|
2013-04-08 14:54:43 +08:00
|
|
|
tail_idx = name_end - in;
|
2016-04-29 11:10:27 +08:00
|
|
|
} else {
|
2013-04-08 14:54:43 +08:00
|
|
|
tail_idx = wcslen(in);
|
|
|
|
}
|
|
|
|
*out_tail_idx = tail_idx;
|
|
|
|
return input.substr(1, tail_idx - 1);
|
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
/// Attempts tilde expansion of the string specified, modifying it in place.
|
|
|
|
static void expand_home_directory(wcstring &input) {
|
|
|
|
if (!input.empty() && input.at(0) == HOME_DIRECTORY) {
|
2011-12-27 11:18:46 +08:00
|
|
|
size_t tail_idx;
|
2013-04-08 14:54:43 +08:00
|
|
|
wcstring username = get_home_directory_name(input, &tail_idx);
|
2016-04-29 11:10:27 +08:00
|
|
|
|
2017-08-28 15:25:41 +08:00
|
|
|
maybe_t<env_var_t> home;
|
2016-04-29 11:10:27 +08:00
|
|
|
if (username.empty()) {
|
|
|
|
// Current users home directory.
|
2017-08-06 09:22:49 +08:00
|
|
|
home = env_get(L"HOME");
|
2017-07-20 22:06:01 +08:00
|
|
|
if (home.missing_or_empty()) {
|
2017-08-11 06:20:53 +08:00
|
|
|
input.clear();
|
2017-07-20 22:06:01 +08:00
|
|
|
return;
|
|
|
|
}
|
2011-12-27 11:18:46 +08:00
|
|
|
tail_idx = 1;
|
2016-04-29 11:10:27 +08:00
|
|
|
} else {
|
|
|
|
// Some other users home directory.
|
2013-04-08 14:54:43 +08:00
|
|
|
std::string name_cstr = wcs2string(username);
|
2017-05-10 13:23:32 +08:00
|
|
|
struct passwd userinfo;
|
|
|
|
struct passwd *result;
|
|
|
|
char buf[8192];
|
|
|
|
int retval = getpwnam_r(name_cstr.c_str(), &userinfo, buf, sizeof(buf), &result);
|
2017-08-28 15:25:41 +08:00
|
|
|
if (!retval && result) {
|
2017-08-06 06:08:39 +08:00
|
|
|
home = env_var_t(L"HOME", str2wcstring(userinfo.pw_dir));
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
|
|
|
}
|
2016-04-29 11:10:27 +08:00
|
|
|
|
2017-10-11 15:08:26 +08:00
|
|
|
maybe_t<wcstring> realhome;
|
|
|
|
if (home)
|
|
|
|
realhome = wrealpath(home->as_string());
|
|
|
|
|
2017-08-28 15:25:41 +08:00
|
|
|
if (realhome) {
|
2017-10-11 15:08:26 +08:00
|
|
|
input.replace(input.begin(), input.begin() + tail_idx, *realhome);
|
2016-04-29 11:10:27 +08:00
|
|
|
} else {
|
2014-10-25 13:58:52 +08:00
|
|
|
input[0] = L'~';
|
2015-01-20 17:04:01 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2006-01-31 03:53:10 +08:00
|
|
|
}
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
void expand_tilde(wcstring &input) {
|
|
|
|
// Avoid needless COW behavior by ensuring we use const at.
|
2013-04-08 14:54:43 +08:00
|
|
|
const wcstring &tmp = input;
|
2016-04-29 11:10:27 +08:00
|
|
|
if (!tmp.empty() && tmp.at(0) == L'~') {
|
2012-11-19 08:30:30 +08:00
|
|
|
input.at(0) = HOME_DIRECTORY;
|
|
|
|
expand_home_directory(input);
|
|
|
|
}
|
2011-12-27 11:18:46 +08:00
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
static void unexpand_tildes(const wcstring &input, std::vector<completion_t> *completions) {
|
|
|
|
// If input begins with tilde, then try to replace the corresponding string in each completion
|
|
|
|
// with the tilde. If it does not, there's nothing to do.
|
|
|
|
if (input.empty() || input.at(0) != L'~') return;
|
2013-05-05 17:33:17 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// We only operate on completions that replace their contents. If we don't have any, we're done.
|
2013-04-08 14:54:43 +08:00
|
|
|
// In particular, empty vectors are common.
|
|
|
|
bool has_candidate_completion = false;
|
2016-04-29 11:10:27 +08:00
|
|
|
for (size_t i = 0; i < completions->size(); i++) {
|
|
|
|
if (completions->at(i).flags & COMPLETE_REPLACES_TOKEN) {
|
2013-04-08 14:54:43 +08:00
|
|
|
has_candidate_completion = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-04-29 11:10:27 +08:00
|
|
|
if (!has_candidate_completion) return;
|
2013-05-05 17:33:17 +08:00
|
|
|
|
2013-04-08 14:54:43 +08:00
|
|
|
size_t tail_idx;
|
|
|
|
wcstring username_with_tilde = L"~";
|
|
|
|
username_with_tilde.append(get_home_directory_name(input, &tail_idx));
|
2013-05-05 17:33:17 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// Expand username_with_tilde.
|
2013-04-08 14:54:43 +08:00
|
|
|
wcstring home = username_with_tilde;
|
|
|
|
expand_tilde(home);
|
2013-05-05 17:33:17 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// Now for each completion that starts with home, replace it with the username_with_tilde.
|
|
|
|
for (size_t i = 0; i < completions->size(); i++) {
|
2013-04-08 14:54:43 +08:00
|
|
|
completion_t &comp = completions->at(i);
|
2016-04-29 11:10:27 +08:00
|
|
|
if ((comp.flags & COMPLETE_REPLACES_TOKEN) &&
|
|
|
|
string_prefixes_string(home, comp.completion)) {
|
2013-04-08 14:54:43 +08:00
|
|
|
comp.completion.replace(0, home.size(), username_with_tilde);
|
2013-05-05 17:33:17 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// And mark that our tilde is literal, so it doesn't try to escape it.
|
2013-04-08 14:54:43 +08:00
|
|
|
comp.flags |= COMPLETE_DONT_ESCAPE_TILDES;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// If the given path contains the user's home directory, replace that with a tilde. We don't try to
|
|
|
|
// be smart about case insensitivity, etc.
|
|
|
|
wcstring replace_home_directory_with_tilde(const wcstring &str) {
|
|
|
|
// Only absolute paths get this treatment.
|
2013-12-17 07:33:20 +08:00
|
|
|
wcstring result = str;
|
2016-04-29 11:10:27 +08:00
|
|
|
if (string_prefixes_string(L"/", result)) {
|
2013-12-17 07:33:20 +08:00
|
|
|
wcstring home_directory = L"~";
|
|
|
|
expand_tilde(home_directory);
|
2016-04-29 11:10:27 +08:00
|
|
|
if (!string_suffixes_string(L"/", home_directory)) {
|
2013-12-17 07:33:20 +08:00
|
|
|
home_directory.push_back(L'/');
|
|
|
|
}
|
2014-01-15 17:40:40 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// Now check if the home_directory prefixes the string.
|
|
|
|
if (string_prefixes_string(home_directory, result)) {
|
2013-12-17 07:33:20 +08:00
|
|
|
// Success
|
|
|
|
result.replace(0, home_directory.size(), L"~/");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
/// Remove any internal separators. Also optionally convert wildcard characters to regular
|
|
|
|
/// equivalents. This is done to support EXPAND_SKIP_WILDCARDS.
|
|
|
|
static void remove_internal_separator(wcstring *str, bool conv) {
|
|
|
|
// Remove all instances of INTERNAL_SEPARATOR.
|
2016-02-05 17:22:46 +08:00
|
|
|
str->erase(std::remove(str->begin(), str->end(), (wchar_t)INTERNAL_SEPARATOR), str->end());
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// If conv is true, replace all instances of ANY_CHAR with '?', ANY_STRING with '*',
|
|
|
|
// ANY_STRING_RECURSIVE with '*'.
|
|
|
|
if (conv) {
|
|
|
|
for (size_t idx = 0; idx < str->size(); idx++) {
|
|
|
|
switch (str->at(idx)) {
|
|
|
|
case ANY_CHAR: {
|
2016-02-05 17:22:46 +08:00
|
|
|
str->at(idx) = L'?';
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2016-04-29 11:10:27 +08:00
|
|
|
}
|
2012-11-19 16:31:03 +08:00
|
|
|
case ANY_STRING:
|
2016-04-29 11:10:27 +08:00
|
|
|
case ANY_STRING_RECURSIVE: {
|
2016-02-05 17:22:46 +08:00
|
|
|
str->at(idx) = L'*';
|
2012-11-19 16:31:03 +08:00
|
|
|
break;
|
2016-04-29 11:10:27 +08:00
|
|
|
}
|
2016-11-03 09:29:14 +08:00
|
|
|
default: {
|
|
|
|
break; // we ignore all other characters
|
|
|
|
}
|
2012-07-17 03:05:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-12-27 11:18:46 +08:00
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
/// A stage in string expansion is represented as a function that takes an input and returns a list
|
|
|
|
/// of output (by reference). We get flags and errors. It may return an error; if so expansion
|
|
|
|
/// halts.
|
2016-11-02 10:12:14 +08:00
|
|
|
typedef expand_error_t (*expand_stage_t)(const wcstring &input, //!OCLINT(unused param)
|
|
|
|
std::vector<completion_t> *out, //!OCLINT(unused param)
|
|
|
|
expand_flags_t flags, //!OCLINT(unused param)
|
|
|
|
parse_error_list_t *errors); //!OCLINT(unused param)
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
static expand_error_t expand_stage_cmdsubst(const wcstring &input, std::vector<completion_t> *out,
|
|
|
|
expand_flags_t flags, parse_error_list_t *errors) {
|
|
|
|
if (EXPAND_SKIP_CMDSUBST & flags) {
|
2012-11-19 08:30:30 +08:00
|
|
|
wchar_t *begin, *end;
|
2016-04-29 11:10:27 +08:00
|
|
|
if (parse_util_locate_cmdsubst(input.c_str(), &begin, &end, true) == 0) {
|
2016-02-05 17:22:46 +08:00
|
|
|
append_completion(out, input);
|
2016-04-29 11:10:27 +08:00
|
|
|
} else {
|
|
|
|
append_cmdsub_error(errors, SOURCE_LOCATION_UNKNOWN,
|
|
|
|
L"Command substitutions not allowed");
|
2017-07-27 11:17:04 +08:00
|
|
|
return EXPAND_ERROR;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2016-04-29 11:10:27 +08:00
|
|
|
} else {
|
2017-07-27 11:17:04 +08:00
|
|
|
bool cmdsubst_ok = expand_cmdsubst(input, out, errors);
|
|
|
|
if (!cmdsubst_ok) return EXPAND_ERROR;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2017-07-27 11:17:04 +08:00
|
|
|
|
|
|
|
return EXPAND_OK;
|
2016-02-05 17:22:46 +08:00
|
|
|
}
|
2013-05-05 17:33:17 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
static expand_error_t expand_stage_variables(const wcstring &input, std::vector<completion_t> *out,
|
|
|
|
expand_flags_t flags, parse_error_list_t *errors) {
|
|
|
|
// We accept incomplete strings here, since complete uses expand_string to expand incomplete
|
|
|
|
// strings from the commandline.
|
2016-02-05 17:22:46 +08:00
|
|
|
wcstring next;
|
|
|
|
unescape_string(input, &next, UNESCAPE_SPECIAL | UNESCAPE_INCOMPLETE);
|
2016-04-29 11:10:27 +08:00
|
|
|
|
|
|
|
if (EXPAND_SKIP_VARIABLES & flags) {
|
|
|
|
for (size_t i = 0; i < next.size(); i++) {
|
|
|
|
if (next.at(i) == VARIABLE_EXPAND) {
|
2016-02-05 17:22:46 +08:00
|
|
|
next[i] = L'$';
|
2012-01-31 13:33:15 +08:00
|
|
|
}
|
|
|
|
}
|
2016-02-05 17:22:46 +08:00
|
|
|
append_completion(out, next);
|
2016-04-29 11:10:27 +08:00
|
|
|
} else {
|
|
|
|
if (!expand_variables(next, out, next.size(), errors)) {
|
2012-01-31 13:33:15 +08:00
|
|
|
return EXPAND_ERROR;
|
|
|
|
}
|
|
|
|
}
|
2016-02-05 17:22:46 +08:00
|
|
|
return EXPAND_OK;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-03-11 03:16:07 +08:00
|
|
|
static expand_error_t expand_stage_braces(const wcstring &input, std::vector<completion_t> *out,
|
2016-04-29 11:10:27 +08:00
|
|
|
expand_flags_t flags, parse_error_list_t *errors) {
|
2018-03-11 03:16:07 +08:00
|
|
|
return expand_braces(input, flags, out, errors);
|
2016-02-05 17:22:46 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-03-09 17:39:53 +08:00
|
|
|
static expand_error_t expand_stage_home(const wcstring &input,
|
2016-04-29 11:10:27 +08:00
|
|
|
std::vector<completion_t> *out,
|
|
|
|
expand_flags_t flags, parse_error_list_t *errors) {
|
2016-02-05 17:22:46 +08:00
|
|
|
wcstring next = input;
|
2016-04-29 11:10:27 +08:00
|
|
|
|
|
|
|
if (!(EXPAND_SKIP_HOME_DIRECTORIES & flags)) {
|
2016-02-05 17:22:46 +08:00
|
|
|
expand_home_directory(next);
|
|
|
|
}
|
2018-03-09 17:39:53 +08:00
|
|
|
append_completion(out, next);
|
2016-02-05 17:22:46 +08:00
|
|
|
return EXPAND_OK;
|
|
|
|
}
|
2013-10-27 06:27:39 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector<completion_t> *out,
|
|
|
|
expand_flags_t flags, parse_error_list_t *errors) {
|
2016-10-10 05:38:26 +08:00
|
|
|
UNUSED(errors);
|
2016-02-05 17:22:46 +08:00
|
|
|
expand_error_t result = EXPAND_OK;
|
2016-02-08 17:29:23 +08:00
|
|
|
wcstring path_to_expand = input;
|
2016-04-29 11:10:27 +08:00
|
|
|
|
2016-05-05 06:19:47 +08:00
|
|
|
remove_internal_separator(&path_to_expand, flags & EXPAND_SKIP_WILDCARDS);
|
2016-02-08 17:29:23 +08:00
|
|
|
const bool has_wildcard = wildcard_has(path_to_expand, true /* internal, i.e. ANY_CHAR */);
|
2016-04-29 11:10:27 +08:00
|
|
|
|
|
|
|
if (has_wildcard && (flags & EXECUTABLES_ONLY)) {
|
2016-11-02 11:42:02 +08:00
|
|
|
; // don't do wildcard expansion for executables, see issue #785
|
2016-04-29 11:10:27 +08:00
|
|
|
} else if (((flags & EXPAND_FOR_COMPLETIONS) && (!(flags & EXPAND_SKIP_WILDCARDS))) ||
|
|
|
|
has_wildcard) {
|
|
|
|
// We either have a wildcard, or we don't have a wildcard but we're doing completion
|
|
|
|
// expansion (so we want to get the completion of a file path). Note that if
|
|
|
|
// EXPAND_SKIP_WILDCARDS is set, we stomped wildcards in remove_internal_separator above, so
|
|
|
|
// there actually aren't any.
|
|
|
|
//
|
|
|
|
// So we're going to treat this input as a file path. Compute the "working directories",
|
|
|
|
// which may be CDPATH if the special flag is set.
|
2016-02-08 17:29:23 +08:00
|
|
|
const wcstring working_dir = env_get_pwd_slash();
|
|
|
|
wcstring_list_t effective_working_dirs;
|
2016-10-21 12:14:40 +08:00
|
|
|
bool for_cd = static_cast<bool>(flags & EXPAND_SPECIAL_FOR_CD);
|
|
|
|
bool for_command = static_cast<bool>(flags & EXPAND_SPECIAL_FOR_COMMAND);
|
2016-04-29 11:10:27 +08:00
|
|
|
if (!for_cd && !for_command) {
|
|
|
|
// Common case.
|
2016-02-08 17:29:23 +08:00
|
|
|
effective_working_dirs.push_back(working_dir);
|
2016-04-29 11:10:27 +08:00
|
|
|
} else {
|
|
|
|
// Either EXPAND_SPECIAL_FOR_COMMAND or EXPAND_SPECIAL_FOR_CD. We can handle these
|
|
|
|
// mostly the same. There's the following differences:
|
|
|
|
//
|
|
|
|
// 1. An empty CDPATH should be treated as '.', but an empty PATH should be left empty
|
2017-04-04 14:16:11 +08:00
|
|
|
// (no commands can be found). Also, an empty element in either is treated as '.' for
|
|
|
|
// consistency with POSIX shells. Note that we rely on the latter by having called
|
|
|
|
// `munge_colon_delimited_array()` for these special env vars. Thus we do not
|
|
|
|
// special-case them here.
|
2016-04-29 11:10:27 +08:00
|
|
|
//
|
|
|
|
// 2. PATH is only "one level," while CDPATH is multiple levels. That is, input like
|
|
|
|
// 'foo/bar' should resolve against CDPATH, but not PATH.
|
|
|
|
//
|
|
|
|
// In either case, we ignore the path if we start with ./ or /. Also ignore it if we are
|
|
|
|
// doing command completion and we contain a slash, per IEEE 1003.1, chapter 8 under
|
|
|
|
// PATH.
|
2016-04-09 06:52:10 +08:00
|
|
|
if (string_prefixes_string(L"/", path_to_expand) ||
|
|
|
|
string_prefixes_string(L"./", path_to_expand) ||
|
|
|
|
string_prefixes_string(L"../", path_to_expand) ||
|
2016-04-29 11:10:27 +08:00
|
|
|
(for_command && path_to_expand.find(L'/') != wcstring::npos)) {
|
2016-02-08 17:29:23 +08:00
|
|
|
effective_working_dirs.push_back(working_dir);
|
2016-04-29 11:10:27 +08:00
|
|
|
} else {
|
2017-08-06 06:08:39 +08:00
|
|
|
// Get the PATH/CDPATH and CWD. Perhaps these should be passed in. An empty CDPATH
|
2016-04-29 11:10:27 +08:00
|
|
|
// implies just the current directory, while an empty PATH is left empty.
|
2017-08-28 15:25:41 +08:00
|
|
|
const wchar_t *name = for_cd ? L"CDPATH" : L"PATH";
|
|
|
|
auto paths = env_get(name);
|
2017-08-06 06:08:39 +08:00
|
|
|
if (paths.missing_or_empty()) {
|
2017-08-28 15:25:41 +08:00
|
|
|
paths = env_var_t(name, for_cd ? L"." : L"");
|
2017-08-06 06:08:39 +08:00
|
|
|
}
|
2016-04-29 11:10:27 +08:00
|
|
|
|
2018-02-18 05:18:00 +08:00
|
|
|
for (const wcstring &next_path : paths->as_list()) {
|
2016-04-29 11:10:27 +08:00
|
|
|
effective_working_dirs.push_back(
|
|
|
|
path_apply_working_directory(next_path, working_dir));
|
2016-02-07 06:39:47 +08:00
|
|
|
}
|
|
|
|
}
|
2016-02-05 17:22:46 +08:00
|
|
|
}
|
2016-04-29 11:10:27 +08:00
|
|
|
|
2016-02-07 06:39:47 +08:00
|
|
|
result = EXPAND_WILDCARD_NO_MATCH;
|
2016-02-05 17:22:46 +08:00
|
|
|
std::vector<completion_t> expanded;
|
2016-04-29 11:10:27 +08:00
|
|
|
for (size_t wd_idx = 0; wd_idx < effective_working_dirs.size(); wd_idx++) {
|
|
|
|
int local_wc_res = wildcard_expand_string(
|
|
|
|
path_to_expand, effective_working_dirs.at(wd_idx), flags, &expanded);
|
|
|
|
if (local_wc_res > 0) {
|
|
|
|
// Something matched,so overall we matched.
|
2016-02-07 06:39:47 +08:00
|
|
|
result = EXPAND_WILDCARD_MATCH;
|
2016-04-29 11:10:27 +08:00
|
|
|
} else if (local_wc_res < 0) {
|
2016-02-07 06:39:47 +08:00
|
|
|
// Cancellation
|
|
|
|
result = EXPAND_ERROR;
|
|
|
|
break;
|
2012-01-31 13:33:15 +08:00
|
|
|
}
|
|
|
|
}
|
2016-04-29 11:10:27 +08:00
|
|
|
|
2016-02-22 19:37:39 +08:00
|
|
|
std::sort(expanded.begin(), expanded.end(), completion_t::is_naturally_less_than);
|
2018-02-18 05:18:00 +08:00
|
|
|
std::move(expanded.begin(), expanded.end(), std::back_inserter(*out));
|
2016-04-29 11:10:27 +08:00
|
|
|
} else {
|
|
|
|
// 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
|
|
|
|
// completion on the floor.
|
|
|
|
if (!(flags & EXPAND_FOR_COMPLETIONS)) {
|
2016-02-08 17:29:23 +08:00
|
|
|
append_completion(out, path_to_expand);
|
2016-02-05 17:22:46 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
expand_error_t expand_string(const wcstring &input, std::vector<completion_t> *out_completions,
|
|
|
|
expand_flags_t flags, parse_error_list_t *errors) {
|
|
|
|
// Early out. If we're not completing, and there's no magic in the input, we're done.
|
|
|
|
if (!(flags & EXPAND_FOR_COMPLETIONS) && expand_is_clean(input)) {
|
2016-02-05 17:22:46 +08:00
|
|
|
append_completion(out_completions, input);
|
|
|
|
return EXPAND_OK;
|
|
|
|
}
|
2016-04-29 11:10:27 +08:00
|
|
|
|
|
|
|
// Our expansion stages.
|
|
|
|
const expand_stage_t stages[] = {expand_stage_cmdsubst, expand_stage_variables,
|
2018-03-11 03:16:07 +08:00
|
|
|
expand_stage_braces, expand_stage_home,
|
2016-04-29 11:10:27 +08:00
|
|
|
expand_stage_wildcards};
|
|
|
|
|
|
|
|
// Load up our single initial completion.
|
2016-02-05 17:22:46 +08:00
|
|
|
std::vector<completion_t> completions, output_storage;
|
|
|
|
append_completion(&completions, input);
|
2016-04-29 11:10:27 +08:00
|
|
|
|
2016-02-05 17:22:46 +08:00
|
|
|
expand_error_t total_result = EXPAND_OK;
|
2016-04-29 11:10:27 +08:00
|
|
|
for (size_t stage_idx = 0;
|
|
|
|
total_result != EXPAND_ERROR && stage_idx < sizeof stages / sizeof *stages; stage_idx++) {
|
|
|
|
for (size_t i = 0; total_result != EXPAND_ERROR && i < completions.size(); i++) {
|
2016-02-05 17:22:46 +08:00
|
|
|
const wcstring &next = completions.at(i).completion;
|
|
|
|
expand_error_t this_result = stages[stage_idx](next, &output_storage, flags, errors);
|
2016-04-29 11:10:27 +08:00
|
|
|
// If this_result was no match, but total_result is that we have a match, then don't
|
|
|
|
// change it.
|
|
|
|
if (!(this_result == EXPAND_WILDCARD_NO_MATCH &&
|
|
|
|
total_result == EXPAND_WILDCARD_MATCH)) {
|
2016-02-05 17:22:46 +08:00
|
|
|
total_result = this_result;
|
2012-01-31 13:33:15 +08:00
|
|
|
}
|
|
|
|
}
|
2016-04-29 11:10:27 +08:00
|
|
|
|
|
|
|
// Output becomes our next stage's input.
|
2016-02-05 17:22:46 +08:00
|
|
|
completions.swap(output_storage);
|
|
|
|
output_storage.clear();
|
2012-01-31 13:33:15 +08:00
|
|
|
}
|
2016-04-29 11:10:27 +08:00
|
|
|
|
|
|
|
if (total_result != EXPAND_ERROR) {
|
|
|
|
// Hack to un-expand tildes (see #647).
|
|
|
|
if (!(flags & EXPAND_SKIP_HOME_DIRECTORIES)) {
|
2016-03-06 13:50:05 +08:00
|
|
|
unexpand_tildes(input, &completions);
|
|
|
|
}
|
|
|
|
out_completions->insert(out_completions->end(), completions.begin(), completions.end());
|
2013-04-08 14:54:43 +08:00
|
|
|
}
|
2016-02-05 17:22:46 +08:00
|
|
|
return total_result;
|
2011-12-27 11:18:46 +08:00
|
|
|
}
|
2007-02-19 07:25:20 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
bool expand_one(wcstring &string, expand_flags_t flags, parse_error_list_t *errors) {
|
2012-11-19 08:30:30 +08:00
|
|
|
std::vector<completion_t> completions;
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-10-23 02:21:13 +08:00
|
|
|
if (!(flags & EXPAND_FOR_COMPLETIONS) && expand_is_clean(string)) {
|
2012-01-30 18:45:55 +08:00
|
|
|
return true;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2016-10-23 02:21:13 +08:00
|
|
|
if (expand_string(string, &completions, flags | EXPAND_NO_DESCRIPTIONS, errors) &&
|
|
|
|
completions.size() == 1) {
|
|
|
|
string = completions.at(0).completion;
|
|
|
|
return true;
|
2012-01-30 18:23:58 +08:00
|
|
|
}
|
2016-10-23 02:21:13 +08:00
|
|
|
return false;
|
2012-01-30 18:23:58 +08:00
|
|
|
}
|
2013-01-05 05:09:01 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
// https://github.com/fish-shell/fish-shell/issues/367
|
|
|
|
//
|
|
|
|
// With them the Seed of Wisdom did I sow,
|
|
|
|
// And with my own hand labour'd it to grow:
|
|
|
|
// And this was all the Harvest that I reap'd---
|
|
|
|
// "I came like Water, and like Wind I go."
|
2013-01-05 05:09:01 +08:00
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
static std::string escape_single_quoted_hack_hack_hack_hack(const char *str) {
|
2013-01-05 05:09:01 +08:00
|
|
|
std::string result;
|
|
|
|
size_t len = strlen(str);
|
|
|
|
result.reserve(len + 2);
|
|
|
|
result.push_back('\'');
|
2016-04-29 11:10:27 +08:00
|
|
|
for (size_t i = 0; i < len; i++) {
|
2013-01-05 05:09:01 +08:00
|
|
|
char c = str[i];
|
2016-04-29 11:10:27 +08:00
|
|
|
// Escape backslashes and single quotes only.
|
|
|
|
if (c == '\\' || c == '\'') result.push_back('\\');
|
2013-01-05 05:09:01 +08:00
|
|
|
result.push_back(c);
|
|
|
|
}
|
|
|
|
result.push_back('\'');
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
bool fish_xdm_login_hack_hack_hack_hack(std::vector<std::string> *cmds, int argc,
|
|
|
|
const char *const *argv) {
|
2016-10-31 06:37:16 +08:00
|
|
|
if (!cmds || cmds->size() != 1) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-01-05 05:09:01 +08:00
|
|
|
bool result = false;
|
2016-10-31 06:37:16 +08:00
|
|
|
const std::string &cmd = cmds->at(0);
|
|
|
|
if (cmd == "exec \"${@}\"" || cmd == "exec \"$@\"") {
|
|
|
|
// We're going to construct a new command that starts with exec, and then has the
|
|
|
|
// remaining arguments escaped.
|
|
|
|
std::string new_cmd = "exec";
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
|
|
const char *arg = argv[i];
|
|
|
|
if (arg) {
|
|
|
|
new_cmd.push_back(' ');
|
|
|
|
new_cmd.append(escape_single_quoted_hack_hack_hack_hack(arg));
|
2013-01-05 05:09:01 +08:00
|
|
|
}
|
|
|
|
}
|
2016-10-31 06:37:16 +08:00
|
|
|
|
|
|
|
cmds->at(0) = new_cmd;
|
|
|
|
result = true;
|
2013-01-05 05:09:01 +08:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-08-20 00:55:06 +08:00
|
|
|
std::unordered_map<const wcstring, const wcstring> abbreviations;
|
2017-08-10 02:11:58 +08:00
|
|
|
void update_abbr_cache(const wchar_t *op, const wcstring &varname) {
|
2017-05-22 13:22:55 +08:00
|
|
|
wcstring abbr;
|
|
|
|
if (!unescape_string(varname.substr(wcslen(L"_fish_abbr_")), &abbr, 0, STRING_STYLE_VAR)) {
|
|
|
|
debug(1, L"Abbreviation var '%ls' is not correctly encoded, ignoring it.", varname.c_str());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
abbreviations.erase(abbr);
|
|
|
|
if (wcscmp(op, L"ERASE") != 0) {
|
2017-08-28 15:25:41 +08:00
|
|
|
const auto expansion = env_get(varname);
|
2017-05-22 13:22:55 +08:00
|
|
|
if (!expansion.missing_or_empty()) {
|
2017-08-28 15:25:41 +08:00
|
|
|
abbreviations.emplace(std::make_pair(abbr, expansion->as_string()));
|
2017-05-22 13:22:55 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-29 11:10:27 +08:00
|
|
|
bool expand_abbreviation(const wcstring &src, wcstring *output) {
|
|
|
|
if (src.empty()) return false;
|
2013-07-17 15:38:04 +08:00
|
|
|
|
2017-05-22 13:22:55 +08:00
|
|
|
auto abbr = abbreviations.find(src);
|
|
|
|
if (abbr == abbreviations.end()) return false;
|
|
|
|
if (output != NULL) output->assign(abbr->second);
|
|
|
|
return true;
|
2013-07-17 15:38:04 +08:00
|
|
|
|
2017-05-22 13:22:55 +08:00
|
|
|
#if 0
|
|
|
|
for (auto abbr : abbreviations) {
|
|
|
|
if (src == abbr.first) {
|
|
|
|
// We found a matching abbreviation. Set output to the expansion.
|
|
|
|
if (output != NULL) output->assign(abbr.second);
|
|
|
|
return true;
|
2013-07-17 15:38:04 +08:00
|
|
|
}
|
|
|
|
}
|
2017-05-22 13:22:55 +08:00
|
|
|
|
|
|
|
return false;
|
|
|
|
#endif
|
2013-07-17 15:38:04 +08:00
|
|
|
}
|