mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-27 11:43:37 +08:00
Merge branch 'master' of github.com:fish-shell/fish-shell
This commit is contained in:
commit
28fd1a4c5d
19
Makefile.in
19
Makefile.in
|
@ -54,11 +54,10 @@ localedir = @localedir@
|
|||
|
||||
MACROS = -DLOCALEDIR=\"$(localedir)\" -DPREFIX=L\"$(prefix)\" -DDATADIR=L\"$(datadir)\" -DSYSCONFDIR=L\"$(sysconfdir)\" -DBINDIR=L\"$(bindir)\" -DDOCDIR=L\"$(docdir)\"
|
||||
CXXFLAGS = @CXXFLAGS@ $(MACROS) $(EXTRA_CXXFLAGS)
|
||||
CPPFLAGS = @CPPFLAGS@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LDFLAGS_FISH = ${LDFLAGS} @LIBS_FISH@ @LDFLAGS_FISH@
|
||||
LDFLAGS_FISH_INDENT = ${LDFLAGS} @LIBS_FISH_INDENT@
|
||||
LDFLAGS_FISHD = ${LDFLAGS} @LIBS_FISHD@
|
||||
LDFLAGS_MIMEDB = ${LDFLAGS} @LIBS_MIMEDB@
|
||||
LIBS = @LIBS@
|
||||
LDFLAGS_FISH = ${LDFLAGS} @LDFLAGS_FISH@
|
||||
|
||||
#
|
||||
# Set to 1 if we have gettext
|
||||
|
@ -686,7 +685,7 @@ uninstall-translations:
|
|||
#
|
||||
|
||||
fish: $(FISH_OBJS) fish.o
|
||||
$(CXX) $(CXXFLAGS) $(FISH_OBJS) fish.o $(LDFLAGS_FISH) -o $@
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS_FISH) $(FISH_OBJS) fish.o $(LIBS) -o $@
|
||||
|
||||
|
||||
#
|
||||
|
@ -694,7 +693,7 @@ fish: $(FISH_OBJS) fish.o
|
|||
#
|
||||
|
||||
fishd: $(FISHD_OBJS)
|
||||
$(CXX) $(CXXFLAGS) $(FISHD_OBJS) $(LDFLAGS_FISHD) -o $@
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(FISHD_OBJS) $(LIBS) -o $@
|
||||
|
||||
|
||||
#
|
||||
|
@ -702,7 +701,7 @@ fishd: $(FISHD_OBJS)
|
|||
#
|
||||
|
||||
fish_tests: $(FISH_TESTS_OBJS)
|
||||
$(CXX) $(CXXFLAGS) $(FISH_TESTS_OBJS) $(LDFLAGS_FISH) -o $@
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS_FISH) $(FISH_TESTS_OBJS) $(LIBS) -o $@
|
||||
|
||||
|
||||
#
|
||||
|
@ -710,7 +709,7 @@ fish_tests: $(FISH_TESTS_OBJS)
|
|||
#
|
||||
|
||||
mimedb: $(MIME_OBJS)
|
||||
$(CXX) $(CXXFLAGS) $(MIME_OBJS) $(LDFLAGS_MIMEDB) -o $@
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(MIME_OBJS) $(LIBS) -o $@
|
||||
|
||||
|
||||
#
|
||||
|
@ -718,7 +717,7 @@ mimedb: $(MIME_OBJS)
|
|||
#
|
||||
|
||||
fish_indent: $(FISH_INDENT_OBJS)
|
||||
$(CXX) $(CXXFLAGS) $(FISH_INDENT_OBJS) $(LDFLAGS_FISH_INDENT) -o $@
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(FISH_INDENT_OBJS) $(LIBS) -o $@
|
||||
|
||||
|
||||
#
|
||||
|
@ -726,7 +725,7 @@ fish_indent: $(FISH_INDENT_OBJS)
|
|||
#
|
||||
|
||||
key_reader: key_reader.o input_common.o common.o env_universal.o env_universal_common.o wutil.o iothread.o
|
||||
$(CXX) $(CXXFLAGS) key_reader.o input_common.o common.o env_universal.o env_universal_common.o wutil.o iothread.o $(LDFLAGS_FISH) -o $@
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS_FISH) key_reader.o input_common.o common.o env_universal.o env_universal_common.o wutil.o iothread.o $(LIBS) -o $@
|
||||
|
||||
|
||||
#
|
||||
|
|
|
@ -81,6 +81,12 @@ static int builtin_set_color(parser_t &parser, wchar_t **argv)
|
|||
|
||||
int argc = builtin_count_args(argv);
|
||||
|
||||
/* Some code passes variables to set_color that don't exist, like $fish_user_whatever. As a hack, quietly return failure. */
|
||||
if (argc <= 1)
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const wchar_t *bgcolor = NULL;
|
||||
bool bold = false, underline=false;
|
||||
int errret;
|
||||
|
|
72
configure.ac
72
configure.ac
|
@ -24,10 +24,6 @@ conf_arg=$@
|
|||
AC_SUBST(HAVE_GETTEXT)
|
||||
AC_SUBST(HAVE_DOXYGEN)
|
||||
AC_SUBST(LDFLAGS_FISH)
|
||||
AC_SUBST(LIBS_FISH)
|
||||
AC_SUBST(LIBS_FISH_INDENT)
|
||||
AC_SUBST(LIBS_FISHD)
|
||||
AC_SUBST(LIBS_MIMEDB)
|
||||
|
||||
|
||||
#
|
||||
|
@ -98,43 +94,6 @@ AC_LANG(C++)
|
|||
|
||||
echo "CXXFLAGS: $CXXFLAGS"
|
||||
|
||||
#
|
||||
# Detect directories which may contain additional headers, libraries
|
||||
# and commands. This needs to be done early - before Autoconf starts
|
||||
# to mess with CXXFLAGS and all the other environemnt variables.
|
||||
#
|
||||
# This mostly helps OS X users, since fink usually installs out of
|
||||
# tree and doesn't update CXXFLAGS.
|
||||
|
||||
for i in /usr/pkg /sw /opt /opt/local /usr/local; do
|
||||
|
||||
AC_MSG_CHECKING([for $i/include include directory])
|
||||
if test -d $i/include; then
|
||||
AC_MSG_RESULT(yes)
|
||||
CXXFLAGS="$CXXFLAGS -I$i/include/"
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
|
||||
AC_MSG_CHECKING([for $i/lib library directory])
|
||||
if test -d $i/lib; then
|
||||
AC_MSG_RESULT(yes)
|
||||
LDFLAGS="$LDFLAGS -L$i/lib/"
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
|
||||
AC_MSG_CHECKING([for $i/bin command directory])
|
||||
if test -d $i/bin; then
|
||||
AC_MSG_RESULT(yes)
|
||||
optbindirs="$optbindirs $i/bin"
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
|
||||
#
|
||||
# Tell autoconf to create config.h header
|
||||
#
|
||||
|
@ -414,37 +373,6 @@ if test x$local_gettext != xno; then
|
|||
AC_SEARCH_LIBS( gettext, intl,,)
|
||||
fi
|
||||
|
||||
LIBS_SHARED=$LIBS
|
||||
|
||||
#
|
||||
# Check for libraries needed by fish.
|
||||
#
|
||||
|
||||
LIBS="$LIBS_SHARED"
|
||||
LIBS_FISH=$LIBS
|
||||
|
||||
#
|
||||
# Check for libraries needed by fish_indent.
|
||||
#
|
||||
|
||||
LIBS="$LIBS_SHARED"
|
||||
LIBS_FISH_INDENT=$LIBS
|
||||
|
||||
#
|
||||
# Check for libraries needed by fishd.
|
||||
#
|
||||
|
||||
LIBS="$LIBS_SHARED"
|
||||
LIBS_FISHD=$LIBS
|
||||
|
||||
#
|
||||
# Check for libraries needed by mimedb.
|
||||
#
|
||||
|
||||
LIBS="$LIBS_SHARED"
|
||||
LIBS_MIMEDB=$LIBS
|
||||
|
||||
|
||||
#
|
||||
# Check presense of various header files
|
||||
#
|
||||
|
|
|
@ -2203,7 +2203,7 @@ void history_tests_t::test_history(void)
|
|||
test_history_matches(search3, 0);
|
||||
|
||||
/* Test history escaping and unescaping, yaml, etc. */
|
||||
std::vector<history_item_t> before, after;
|
||||
history_item_list_t before, after;
|
||||
history.clear();
|
||||
size_t i, max = 100;
|
||||
for (i=1; i <= max; i++)
|
||||
|
@ -2225,7 +2225,8 @@ void history_tests_t::test_history(void)
|
|||
}
|
||||
|
||||
/* Record this item */
|
||||
history_item_t item(value, time(NULL), paths);
|
||||
history_item_t item(value, time(NULL));
|
||||
item.required_paths = paths;
|
||||
before.push_back(item);
|
||||
history.add(item);
|
||||
}
|
||||
|
|
|
@ -961,7 +961,7 @@ public:
|
|||
void highlighter_t::color_node(const parse_node_t &node, highlight_spec_t color)
|
||||
{
|
||||
// Can only color nodes with valid source ranges
|
||||
if (! node.has_source())
|
||||
if (! node.has_source() || node.source_length == 0)
|
||||
return;
|
||||
|
||||
// Fill the color array with our color in the corresponding range
|
||||
|
@ -1332,7 +1332,6 @@ const highlighter_t::color_array_t & highlighter_t::highlight()
|
|||
case symbol_if_clause:
|
||||
case symbol_else_clause:
|
||||
case symbol_case_item:
|
||||
case symbol_switch_statement:
|
||||
case symbol_boolean_statement:
|
||||
case symbol_decorated_statement:
|
||||
case symbol_if_statement:
|
||||
|
@ -1341,6 +1340,15 @@ const highlighter_t::color_array_t & highlighter_t::highlight()
|
|||
}
|
||||
break;
|
||||
|
||||
case symbol_switch_statement:
|
||||
{
|
||||
const parse_node_t *literal_switch = this->parse_tree.get_child(node, 0, parse_token_type_string);
|
||||
const parse_node_t *switch_arg = this->parse_tree.get_child(node, 1, symbol_argument);
|
||||
this->color_node(*literal_switch, highlight_spec_command);
|
||||
this->color_node(*switch_arg, highlight_spec_param);
|
||||
}
|
||||
break;
|
||||
|
||||
case symbol_for_header:
|
||||
{
|
||||
// Color the 'for' and 'in' as commands
|
||||
|
@ -1357,6 +1365,7 @@ const highlighter_t::color_array_t & highlighter_t::highlight()
|
|||
|
||||
case parse_token_type_background:
|
||||
case parse_token_type_end:
|
||||
case symbol_optional_background:
|
||||
{
|
||||
this->color_node(node, highlight_spec_statement_terminator);
|
||||
}
|
||||
|
|
177
history.cpp
177
history.cpp
|
@ -22,6 +22,7 @@
|
|||
#include "sanity.h"
|
||||
#include "tokenizer.h"
|
||||
#include "reader.h"
|
||||
#include "parse_tree.h"
|
||||
|
||||
#include "wutil.h"
|
||||
#include "history.h"
|
||||
|
@ -235,7 +236,7 @@ static void escape_yaml(std::string &str);
|
|||
/** Undoes escape_yaml */
|
||||
static void unescape_yaml(std::string &str);
|
||||
|
||||
/* We can merge two items if they are the same command. We use the more recent timestamp and the longer list of required paths. */
|
||||
/* We can merge two items if they are the same command. We use the more recent timestamp, more recent identifier, and the longer list of required paths. */
|
||||
bool history_item_t::merge(const history_item_t &item)
|
||||
{
|
||||
bool result = false;
|
||||
|
@ -246,16 +247,20 @@ bool history_item_t::merge(const history_item_t &item)
|
|||
{
|
||||
this->required_paths = item.required_paths;
|
||||
}
|
||||
if (this->identifier < item.identifier)
|
||||
{
|
||||
this->identifier = item.identifier;
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
history_item_t::history_item_t(const wcstring &str) : contents(str), creation_timestamp(time(NULL))
|
||||
history_item_t::history_item_t(const wcstring &str) : contents(str), creation_timestamp(time(NULL)), identifier(0)
|
||||
{
|
||||
}
|
||||
|
||||
history_item_t::history_item_t(const wcstring &str, time_t when, const path_list_t &paths) : contents(str), creation_timestamp(when), required_paths(paths)
|
||||
history_item_t::history_item_t(const wcstring &str, time_t when, history_identifier_t ident) : contents(str), creation_timestamp(when), identifier(ident)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -543,6 +548,7 @@ history_t & history_t::history_with_name(const wcstring &name)
|
|||
history_t::history_t(const wcstring &pname) :
|
||||
name(pname),
|
||||
first_unwritten_new_item_index(0),
|
||||
disable_automatic_save_counter(0),
|
||||
mmap_start(NULL),
|
||||
mmap_length(0),
|
||||
mmap_file_id(kInvalidFileID),
|
||||
|
@ -572,6 +578,19 @@ void history_t::add(const history_item_t &item)
|
|||
{
|
||||
/* We have to add a new item */
|
||||
new_items.push_back(item);
|
||||
save_internal_unless_disabled();
|
||||
}
|
||||
}
|
||||
|
||||
void history_t::save_internal_unless_disabled()
|
||||
{
|
||||
/* This must be called while locked */
|
||||
ASSERT_IS_LOCKED(lock);
|
||||
|
||||
/* Respect disable_automatic_save_counter */
|
||||
if (disable_automatic_save_counter > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* We may or may not vacuum. We try to vacuum every kVacuumFrequency items, but start the countdown at a random number so that even if the user never runs more than 25 commands, we'll eventually vacuum. If countdown_to_vacuum is -1, it means we haven't yet picked a value for the counter. */
|
||||
|
@ -598,10 +617,9 @@ void history_t::add(const history_item_t &item)
|
|||
/* Update our countdown */
|
||||
assert(countdown_to_vacuum > 0);
|
||||
countdown_to_vacuum--;
|
||||
|
||||
}
|
||||
|
||||
void history_t::add(const wcstring &str, const path_list_t &valid_paths)
|
||||
void history_t::add(const wcstring &str, history_identifier_t ident)
|
||||
{
|
||||
time_t when = time(NULL);
|
||||
/* Big hack: do not allow timestamps equal to our birthdate. This is because we include items whose timestamps are equal to our birthdate when reading old history, so we can catch "just closed" items. But this means that we may interpret our own items, that we just wrote, as old items, if we wrote them in the same second as our birthdate.
|
||||
|
@ -609,7 +627,7 @@ void history_t::add(const wcstring &str, const path_list_t &valid_paths)
|
|||
if (when == this->birth_timestamp)
|
||||
when++;
|
||||
|
||||
this->add(history_item_t(str, when, valid_paths));
|
||||
this->add(history_item_t(str, when, ident));
|
||||
}
|
||||
|
||||
void history_t::remove(const wcstring &str)
|
||||
|
@ -621,7 +639,7 @@ void history_t::remove(const wcstring &str)
|
|||
size_t idx = new_items.size();
|
||||
while (idx--)
|
||||
{
|
||||
if (new_items[idx].str() == str)
|
||||
if (new_items.at(idx).str() == str)
|
||||
{
|
||||
new_items.erase(new_items.begin() + idx);
|
||||
|
||||
|
@ -635,6 +653,28 @@ void history_t::remove(const wcstring &str)
|
|||
assert(first_unwritten_new_item_index <= new_items.size());
|
||||
}
|
||||
|
||||
void history_t::set_valid_file_paths(const wcstring_list_t &valid_file_paths, history_identifier_t ident)
|
||||
{
|
||||
/* 0 identifier is used to mean "not necessary" */
|
||||
if (ident == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scoped_lock locker(lock);
|
||||
|
||||
/* Look for an item with the given identifier. It is likely to be at the end of new_items */
|
||||
for (history_item_list_t::reverse_iterator iter = new_items.rbegin(); iter != new_items.rend(); iter++)
|
||||
{
|
||||
if (iter->identifier == ident)
|
||||
{
|
||||
/* Found it */
|
||||
iter->required_paths = valid_file_paths;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void history_t::get_string_representation(wcstring &result, const wcstring &separator)
|
||||
{
|
||||
scoped_lock locker(lock);
|
||||
|
@ -644,7 +684,7 @@ void history_t::get_string_representation(wcstring &result, const wcstring &sepa
|
|||
std::set<wcstring> seen;
|
||||
|
||||
/* Append new items. Note that in principle we could use const_reverse_iterator, but we do not because reverse_iterator is not convertible to const_reverse_iterator ( http://github.com/fish-shell/fish-shell/issues/431 ) */
|
||||
for (std::vector<history_item_t>::reverse_iterator iter=new_items.rbegin(); iter < new_items.rend(); ++iter)
|
||||
for (history_item_list_t::reverse_iterator iter=new_items.rbegin(); iter < new_items.rend(); ++iter)
|
||||
{
|
||||
/* Skip duplicates */
|
||||
if (! seen.insert(iter->str()).second)
|
||||
|
@ -658,7 +698,7 @@ void history_t::get_string_representation(wcstring &result, const wcstring &sepa
|
|||
|
||||
/* Append old items */
|
||||
load_old_if_needed();
|
||||
for (std::vector<size_t>::reverse_iterator iter = old_item_offsets.rbegin(); iter != old_item_offsets.rend(); ++iter)
|
||||
for (std::deque<size_t>::reverse_iterator iter = old_item_offsets.rbegin(); iter != old_item_offsets.rend(); ++iter)
|
||||
{
|
||||
size_t offset = *iter;
|
||||
const history_item_t item = history_t::decode_item(mmap_start + offset, mmap_length - offset, mmap_type);
|
||||
|
@ -825,7 +865,9 @@ history_item_t history_t::decode_item_fish_2_0(const char *base, size_t len)
|
|||
}
|
||||
}
|
||||
done:
|
||||
return history_item_t(cmd, when, paths);
|
||||
history_item_t result(cmd, when);
|
||||
result.required_paths.swap(paths);
|
||||
return result;
|
||||
}
|
||||
|
||||
history_item_t history_t::decode_item(const char *base, size_t len, history_file_type_t type)
|
||||
|
@ -1280,7 +1322,7 @@ bool history_t::save_internal_via_rewrite()
|
|||
history_lru_cache_t lru(HISTORY_SAVE_MAX);
|
||||
|
||||
/* Insert old items in, from old to new. Merge them with our new items, inserting items with earlier timestamps first. */
|
||||
std::vector<history_item_t>::const_iterator new_item_iter = new_items.begin();
|
||||
history_item_list_t::const_iterator new_item_iter = new_items.begin();
|
||||
|
||||
/* Map in existing items (which may have changed out from underneath us, so don't trust our old mmap'd data) */
|
||||
const char *local_mmap_start = NULL;
|
||||
|
@ -1526,6 +1568,22 @@ void history_t::save_and_vacuum(void)
|
|||
this->save_internal(true);
|
||||
}
|
||||
|
||||
void history_t::disable_automatic_saving()
|
||||
{
|
||||
scoped_lock locker(lock);
|
||||
disable_automatic_save_counter++;
|
||||
assert(disable_automatic_save_counter != 0); // overflow!
|
||||
}
|
||||
|
||||
void history_t::enable_automatic_saving()
|
||||
{
|
||||
scoped_lock locker(lock);
|
||||
assert(disable_automatic_save_counter > 0); //underflow
|
||||
disable_automatic_save_counter--;
|
||||
save_internal_unless_disabled();
|
||||
}
|
||||
|
||||
|
||||
void history_t::clear(void)
|
||||
{
|
||||
scoped_lock locker(lock);
|
||||
|
@ -1693,11 +1751,10 @@ bool file_detection_context_t::paths_are_valid(const path_list_t &paths)
|
|||
return perform_file_detection(false) > 0;
|
||||
}
|
||||
|
||||
file_detection_context_t::file_detection_context_t(history_t *hist, const wcstring &cmd) :
|
||||
file_detection_context_t::file_detection_context_t(history_t *hist, history_identifier_t ident) :
|
||||
history(hist),
|
||||
command(cmd),
|
||||
when(time(NULL)),
|
||||
working_directory(env_get_pwd_slash())
|
||||
working_directory(env_get_pwd_slash()),
|
||||
history_item_identifier(ident)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1710,8 +1767,13 @@ static int threaded_perform_file_detection(file_detection_context_t *ctx)
|
|||
|
||||
static void perform_file_detection_done(file_detection_context_t *ctx, int success)
|
||||
{
|
||||
/* Now that file detection is done, create the history item */
|
||||
ctx->history->add(ctx->command, ctx->valid_paths);
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
|
||||
/* Now that file detection is done, update the history item with the valid file paths */
|
||||
ctx->history->set_valid_file_paths(ctx->valid_paths, ctx->history_item_identifier);
|
||||
|
||||
/* Allow saving again */
|
||||
ctx->history->enable_automatic_saving();
|
||||
|
||||
/* Done with the context. */
|
||||
delete ctx;
|
||||
|
@ -1721,7 +1783,9 @@ static bool string_could_be_path(const wcstring &potential_path)
|
|||
{
|
||||
// Assume that things with leading dashes aren't paths
|
||||
if (potential_path.empty() || potential_path.at(0) == L'-')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1730,43 +1794,72 @@ void history_t::add_with_file_detection(const wcstring &str)
|
|||
ASSERT_IS_MAIN_THREAD();
|
||||
path_list_t potential_paths;
|
||||
|
||||
/* Hack hack hack - if the command is likely to trigger an exit, then don't do background file detection, because we won't be able to write it to our history file before we exit. */
|
||||
/* Find all arguments that look like they could be file paths */
|
||||
bool impending_exit = false;
|
||||
parse_node_tree_t tree;
|
||||
parse_tree_from_string(str, parse_flag_none, &tree, NULL);
|
||||
size_t count = tree.size();
|
||||
|
||||
tokenizer_t tokenizer(str.c_str(), TOK_SQUASH_ERRORS);
|
||||
for (; tok_has_next(&tokenizer); tok_next(&tokenizer))
|
||||
for (size_t i=0; i < count; i++)
|
||||
{
|
||||
int type = tok_last_type(&tokenizer);
|
||||
if (type == TOK_STRING)
|
||||
const parse_node_t &node = tree.at(i);
|
||||
if (! node.has_source())
|
||||
{
|
||||
const wchar_t *token_cstr = tok_last(&tokenizer);
|
||||
if (token_cstr)
|
||||
{
|
||||
wcstring potential_path;
|
||||
bool unescaped = unescape_string(token_cstr, &potential_path, UNESCAPE_DEFAULT);
|
||||
if (unescaped && string_could_be_path(potential_path))
|
||||
{
|
||||
potential_paths.push_back(potential_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* What a hack! */
|
||||
impending_exit = impending_exit || contains(potential_path, L"exec", L"exit", L"reboot");
|
||||
}
|
||||
if (node.type == symbol_argument)
|
||||
{
|
||||
wcstring potential_path = node.get_source(str);
|
||||
bool unescaped = unescape_string_in_place(&potential_path, UNESCAPE_DEFAULT);
|
||||
if (unescaped && string_could_be_path(potential_path))
|
||||
{
|
||||
potential_paths.push_back(potential_path);
|
||||
}
|
||||
}
|
||||
else if (node.type == symbol_plain_statement)
|
||||
{
|
||||
/* Hack hack hack - if the command is likely to trigger an exit, then don't do background file detection, because we won't be able to write it to our history file before we exit. */
|
||||
if (tree.decoration_for_plain_statement(node) == parse_statement_decoration_exec)
|
||||
{
|
||||
impending_exit = true;
|
||||
}
|
||||
|
||||
wcstring command;
|
||||
tree.command_for_plain_statement(node, str, &command);
|
||||
unescape_string_in_place(&command, UNESCAPE_DEFAULT);
|
||||
if (contains(command, L"exit", L"reboot"))
|
||||
{
|
||||
impending_exit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (potential_paths.empty() || impending_exit)
|
||||
/* If we got a path, we'll perform file detection for autosuggestion hinting */
|
||||
history_identifier_t identifier = 0;
|
||||
if (! potential_paths.empty() && ! impending_exit)
|
||||
{
|
||||
this->add(str);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We have some paths. Make a context. */
|
||||
file_detection_context_t *context = new file_detection_context_t(this, str);
|
||||
/* Grab the next identifier */
|
||||
static history_identifier_t sLastIdentifier = 0;
|
||||
identifier = ++sLastIdentifier;
|
||||
|
||||
/* Store the potential paths. Reverse them to put them in the same order as in the command. */
|
||||
/* Create a new detection context */
|
||||
file_detection_context_t *context = new file_detection_context_t(this, identifier);
|
||||
context->potential_paths.swap(potential_paths);
|
||||
|
||||
/* Prevent saving until we're done, so we have time to get the paths */
|
||||
this->disable_automatic_saving();
|
||||
|
||||
/* Kick it off. Even though we haven't added the item yet, it updates the item on the main thread, so we can't race */
|
||||
iothread_perform(threaded_perform_file_detection, perform_file_detection_done, context);
|
||||
}
|
||||
}
|
||||
|
||||
/* Actually add the item to the history */
|
||||
this->add(str, identifier);
|
||||
|
||||
/* If we think we're about to exit, save immediately, regardless of any disabling. This may cause us to lose file hinting for some commands, but it beats losing history items */
|
||||
if (impending_exit)
|
||||
{
|
||||
this->save();
|
||||
}
|
||||
}
|
||||
|
|
47
history.h
47
history.h
|
@ -9,6 +9,7 @@
|
|||
#include "common.h"
|
||||
#include "pthread.h"
|
||||
#include "wutil.h"
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <list>
|
||||
|
@ -34,6 +35,8 @@ enum history_search_type_t
|
|||
HISTORY_SEARCH_TYPE_PREFIX
|
||||
};
|
||||
|
||||
typedef uint32_t history_identifier_t;
|
||||
|
||||
class history_item_t
|
||||
{
|
||||
friend class history_t;
|
||||
|
@ -41,8 +44,8 @@ class history_item_t
|
|||
friend class history_tests_t;
|
||||
|
||||
private:
|
||||
explicit history_item_t(const wcstring &);
|
||||
explicit history_item_t(const wcstring &, time_t, const path_list_t &paths = path_list_t());
|
||||
explicit history_item_t(const wcstring &str);
|
||||
explicit history_item_t(const wcstring &, time_t, history_identifier_t ident = 0);
|
||||
|
||||
/** Attempts to merge two compatible history items together */
|
||||
bool merge(const history_item_t &item);
|
||||
|
@ -53,6 +56,9 @@ private:
|
|||
/** Original creation time for the entry */
|
||||
time_t creation_timestamp;
|
||||
|
||||
/** Sometimes unique identifier used for hinting */
|
||||
history_identifier_t identifier;
|
||||
|
||||
/** Paths that we require to be valid for this item to be autosuggested */
|
||||
path_list_t required_paths;
|
||||
|
||||
|
@ -88,6 +94,8 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
typedef std::deque<history_item_t> history_item_list_t;
|
||||
|
||||
/* The type of file that we mmap'd */
|
||||
enum history_file_type_t
|
||||
{
|
||||
|
@ -123,11 +131,14 @@ private:
|
|||
const wcstring name;
|
||||
|
||||
/** New items. Note that these are NOT discarded on save. We need to keep these around so we can distinguish between items in our history and items in the history of other shells that were started after we were started. */
|
||||
std::vector<history_item_t> new_items;
|
||||
history_item_list_t new_items;
|
||||
|
||||
/** The index of the first new item that we have not yet written. */
|
||||
size_t first_unwritten_new_item_index;
|
||||
|
||||
/** Whether we should disable saving to the file for a time */
|
||||
uint32_t disable_automatic_save_counter;
|
||||
|
||||
/** Deleted item contents. */
|
||||
std::set<wcstring> deleted_items;
|
||||
|
||||
|
@ -153,7 +164,7 @@ private:
|
|||
void populate_from_mmap(void);
|
||||
|
||||
/** List of old items, as offsets into out mmap data */
|
||||
std::vector<size_t> old_item_offsets;
|
||||
std::deque<size_t> old_item_offsets;
|
||||
|
||||
/** Whether we've loaded old items */
|
||||
bool loaded_old;
|
||||
|
@ -176,6 +187,9 @@ private:
|
|||
/** Saves history */
|
||||
void save_internal(bool vacuum);
|
||||
|
||||
/** Saves history, maybe */
|
||||
void save_internal_unless_disabled();
|
||||
|
||||
/* Do a private, read-only map of the entirety of a history file with the given name. Returns true if successful. Returns the mapped memory region by reference. */
|
||||
bool map_file(const wcstring &name, const char **out_map_start, size_t *out_map_len, file_id_t *file_id);
|
||||
|
||||
|
@ -195,7 +209,7 @@ public:
|
|||
bool is_empty(void);
|
||||
|
||||
/** Add a new history item to the end */
|
||||
void add(const wcstring &str, const path_list_t &valid_paths = path_list_t());
|
||||
void add(const wcstring &str, history_identifier_t ident = 0);
|
||||
|
||||
/** Remove a history item */
|
||||
void remove(const wcstring &str);
|
||||
|
@ -209,6 +223,10 @@ public:
|
|||
/** Performs a full (non-incremental) save */
|
||||
void save_and_vacuum();
|
||||
|
||||
/** Enable / disable automatic saving. Main thread only! */
|
||||
void disable_automatic_saving();
|
||||
void enable_automatic_saving();
|
||||
|
||||
/** Irreversibly clears history */
|
||||
void clear();
|
||||
|
||||
|
@ -218,6 +236,9 @@ public:
|
|||
/* Gets all the history into a string with ARRAY_SEP_STR. This is intended for the $history environment variable. This may be long! */
|
||||
void get_string_representation(wcstring &str, const wcstring &separator);
|
||||
|
||||
/** Sets the valid file paths for the history item with the given identifier */
|
||||
void set_valid_file_paths(const wcstring_list_t &valid_file_paths, history_identifier_t ident);
|
||||
|
||||
/** Return the specified history at the specified index. 0 is the index of the current commandline. (So the most recent item is at index 1.) */
|
||||
history_item_t item_at_index(size_t idx);
|
||||
};
|
||||
|
@ -295,8 +316,6 @@ public:
|
|||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
Init history library. The history file won't actually be loaded
|
||||
until the first time a history search is performed.
|
||||
|
@ -316,21 +335,14 @@ void history_sanity_check();
|
|||
/* A helper class for threaded detection of paths */
|
||||
struct file_detection_context_t
|
||||
{
|
||||
|
||||
/* Constructor */
|
||||
file_detection_context_t(history_t *hist, const wcstring &cmd);
|
||||
file_detection_context_t(history_t *hist, history_identifier_t ident = 0);
|
||||
|
||||
/* Determine which of potential_paths are valid, and put them in valid_paths */
|
||||
int perform_file_detection();
|
||||
|
||||
/* The history associated with this context */
|
||||
history_t *history;
|
||||
|
||||
/* The command */
|
||||
wcstring command;
|
||||
|
||||
/* When the command was issued */
|
||||
time_t when;
|
||||
history_t * const history;
|
||||
|
||||
/* The working directory at the time the command was issued */
|
||||
wcstring working_directory;
|
||||
|
@ -341,6 +353,9 @@ struct file_detection_context_t
|
|||
/* Paths that were found to be valid */
|
||||
path_list_t valid_paths;
|
||||
|
||||
/* Identifier of the history item to which we are associated */
|
||||
const history_identifier_t history_item_identifier;
|
||||
|
||||
/* Performs file detection. Returns 1 if every path in potential_paths is valid, 0 otherwise. If test_all is true, tests every path; otherwise stops as soon as it reaches an invalid path. */
|
||||
int perform_file_detection(bool test_all);
|
||||
|
||||
|
|
|
@ -44,6 +44,10 @@ enum parse_token_type_t
|
|||
|
||||
symbol_argument_list,
|
||||
|
||||
// "freestanding" argument lists are parsed from the argument list supplied to 'complete -a'
|
||||
// They are not generated by parse trees rooted in symbol_job_list
|
||||
symbol_freestanding_argument_list,
|
||||
|
||||
symbol_argument,
|
||||
symbol_redirection,
|
||||
|
||||
|
|
|
@ -509,7 +509,7 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement(const p
|
|||
parse_execution_result_t result = parse_execution_success;
|
||||
|
||||
/* Get the switch variable */
|
||||
const parse_node_t &switch_value_node = *get_child(statement, 1, parse_token_type_string);
|
||||
const parse_node_t &switch_value_node = *get_child(statement, 1, symbol_argument);
|
||||
const wcstring switch_value = get_source(switch_value_node);
|
||||
|
||||
/* Expand it. We need to offset any errors by the position of the string */
|
||||
|
@ -547,13 +547,15 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement(const p
|
|||
_(L"switch: Expected exactly one argument, got %lu\n"),
|
||||
switch_values_expanded.size());
|
||||
}
|
||||
const wcstring &switch_value_expanded = switch_values_expanded.at(0).completion;
|
||||
|
||||
switch_block_t *sb = new switch_block_t();
|
||||
parser->push_block(sb);
|
||||
|
||||
if (result == parse_execution_success)
|
||||
{
|
||||
const wcstring &switch_value_expanded = switch_values_expanded.at(0).completion;
|
||||
|
||||
switch_block_t *sb = new switch_block_t();
|
||||
parser->push_block(sb);
|
||||
|
||||
|
||||
/* Expand case statements */
|
||||
const parse_node_t *case_item_list = get_child(statement, 3, symbol_case_item_list);
|
||||
|
||||
|
@ -597,16 +599,16 @@ parse_execution_result_t parse_execution_context_t::run_switch_statement(const p
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result == parse_execution_success && matching_case_item != NULL)
|
||||
{
|
||||
/* Success, evaluate the job list */
|
||||
const parse_node_t *job_list = get_child(*matching_case_item, 3, symbol_job_list);
|
||||
result = this->run_job_list(*job_list, sb);
|
||||
}
|
||||
if (result == parse_execution_success && matching_case_item != NULL)
|
||||
{
|
||||
/* Success, evaluate the job list */
|
||||
const parse_node_t *job_list = get_child(*matching_case_item, 3, symbol_job_list);
|
||||
result = this->run_job_list(*job_list, sb);
|
||||
}
|
||||
|
||||
parser->pop_block(sb);
|
||||
parser->pop_block(sb);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -1371,7 +1373,7 @@ parse_execution_result_t parse_execution_context_t::run_1_job(const parse_node_t
|
|||
(job_control_mode==JOB_CONTROL_ALL) ||
|
||||
((job_control_mode == JOB_CONTROL_INTERACTIVE) && (get_is_interactive())));
|
||||
|
||||
job_set_flag(j, JOB_FOREGROUND, 1);
|
||||
job_set_flag(j, JOB_FOREGROUND, ! tree.job_should_be_backgrounded(job_node));
|
||||
|
||||
job_set_flag(j, JOB_TERMINAL, job_get_flag(j, JOB_CONTROL) \
|
||||
&& (!is_subshell && !is_event));
|
||||
|
|
|
@ -82,7 +82,7 @@ RESOLVE(job_list)
|
|||
|
||||
PRODUCTIONS(job) =
|
||||
{
|
||||
{symbol_statement, symbol_job_continuation}
|
||||
{symbol_statement, symbol_job_continuation, symbol_optional_background}
|
||||
};
|
||||
RESOLVE_ONLY(job)
|
||||
|
||||
|
@ -237,7 +237,7 @@ RESOLVE(else_continuation)
|
|||
|
||||
PRODUCTIONS(switch_statement) =
|
||||
{
|
||||
{ KEYWORD(parse_keyword_switch), parse_token_type_string, parse_token_type_end, symbol_case_item_list, symbol_end_command, symbol_arguments_or_redirections_list}
|
||||
{ KEYWORD(parse_keyword_switch), symbol_argument, parse_token_type_end, symbol_case_item_list, symbol_end_command, symbol_arguments_or_redirections_list}
|
||||
};
|
||||
RESOLVE_ONLY(switch_statement)
|
||||
|
||||
|
@ -276,6 +276,26 @@ RESOLVE(argument_list)
|
|||
}
|
||||
}
|
||||
|
||||
PRODUCTIONS(freestanding_argument_list) =
|
||||
{
|
||||
{},
|
||||
{symbol_argument, symbol_freestanding_argument_list},
|
||||
{parse_token_type_end, symbol_freestanding_argument_list},
|
||||
};
|
||||
RESOLVE(freestanding_argument_list)
|
||||
{
|
||||
switch (token1.type)
|
||||
{
|
||||
case parse_token_type_string:
|
||||
return 1;
|
||||
case parse_token_type_end:
|
||||
return 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PRODUCTIONS(block_statement) =
|
||||
{
|
||||
{symbol_block_header, parse_token_type_end, symbol_job_list, symbol_end_command, symbol_arguments_or_redirections_list}
|
||||
|
@ -382,7 +402,7 @@ RESOLVE(decorated_statement)
|
|||
|
||||
PRODUCTIONS(plain_statement) =
|
||||
{
|
||||
{parse_token_type_string, symbol_arguments_or_redirections_list, symbol_optional_background}
|
||||
{parse_token_type_string, symbol_arguments_or_redirections_list}
|
||||
};
|
||||
RESOLVE_ONLY(plain_statement)
|
||||
|
||||
|
@ -485,6 +505,7 @@ const production_t *parse_productions::production_for_token(parse_token_type_t n
|
|||
TEST(case_item_list)
|
||||
TEST(case_item)
|
||||
TEST(argument_list)
|
||||
TEST(freestanding_argument_list)
|
||||
TEST(block_header)
|
||||
TEST(for_header)
|
||||
TEST(while_header)
|
||||
|
|
|
@ -186,6 +186,8 @@ wcstring token_type_description(parse_token_type_t type)
|
|||
|
||||
case symbol_argument_list:
|
||||
return L"argument_list";
|
||||
case symbol_freestanding_argument_list:
|
||||
return L"freestanding_argument_list";
|
||||
|
||||
case symbol_boolean_statement:
|
||||
return L"boolean_statement";
|
||||
|
@ -272,7 +274,7 @@ wcstring keyword_description(parse_keyword_t k)
|
|||
}
|
||||
}
|
||||
|
||||
static wcstring token_type_user_presentable_description(parse_token_type_t type, parse_keyword_t keyword)
|
||||
static wcstring token_type_user_presentable_description(parse_token_type_t type, parse_keyword_t keyword = parse_keyword_none)
|
||||
{
|
||||
if (keyword != parse_keyword_none)
|
||||
{
|
||||
|
@ -285,6 +287,9 @@ static wcstring token_type_user_presentable_description(parse_token_type_t type,
|
|||
case symbol_statement:
|
||||
return L"a command";
|
||||
|
||||
case symbol_argument:
|
||||
return L"an argument";
|
||||
|
||||
case parse_token_type_string:
|
||||
return L"a string";
|
||||
|
||||
|
@ -752,8 +757,6 @@ void parse_ll_t::parse_error_unbalancing_token(parse_token_t token)
|
|||
this->fatal_errored = true;
|
||||
if (this->should_generate_error_messages)
|
||||
{
|
||||
assert(token.type == parse_token_type_string);
|
||||
assert(token.keyword == parse_keyword_end || token.keyword == parse_keyword_else || token.keyword == parse_keyword_case);
|
||||
switch (token.keyword)
|
||||
{
|
||||
case parse_keyword_end:
|
||||
|
@ -769,8 +772,17 @@ void parse_ll_t::parse_error_unbalancing_token(parse_token_t token)
|
|||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unexpected token %ls passed to %s\n", token.describe().c_str(), __FUNCTION__);
|
||||
PARSER_DIE();
|
||||
// At the moment, this case should only be hit if you parse a freestanding_argument_list
|
||||
// For example, 'complete -c foo -a 'one & three'
|
||||
// Hackish error message for that case
|
||||
if (! symbol_stack.empty() && symbol_stack.back().type == symbol_freestanding_argument_list)
|
||||
{
|
||||
this->parse_error(token, parse_error_generic, L"Expected %ls, but found %ls", token_type_user_presentable_description(symbol_argument).c_str(), token.user_presentable_description().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
this->parse_error(token, parse_error_generic, L"Did not expect %ls", token.user_presentable_description().c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1484,6 +1496,19 @@ parse_node_tree_t::parse_node_list_t parse_node_tree_t::specific_statements_for_
|
|||
return result;
|
||||
}
|
||||
|
||||
bool parse_node_tree_t::job_should_be_backgrounded(const parse_node_t &job) const
|
||||
{
|
||||
assert(job.type == symbol_job);
|
||||
assert(job.production_idx == 0);
|
||||
bool result = false;
|
||||
const parse_node_t *opt_background = get_child(job, 2, symbol_optional_background);
|
||||
if (opt_background != NULL) {
|
||||
assert(opt_background->production_idx <= 1);
|
||||
result = (opt_background->production_idx == 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const parse_node_t *parse_node_tree_t::next_node_in_node_list(const parse_node_t &node_list, parse_token_type_t entry_type, const parse_node_t **out_list_tail) const
|
||||
{
|
||||
parse_token_type_t list_type = node_list.type;
|
||||
|
|
16
parse_tree.h
16
parse_tree.h
|
@ -184,6 +184,9 @@ public:
|
|||
|
||||
/* Given a job, return all of its statements. These are 'specific statements' (e.g. symbol_decorated_statement, not symbol_statement) */
|
||||
parse_node_list_t specific_statements_for_job(const parse_node_t &job) const;
|
||||
|
||||
/* Given a job, return whether it should be backgrounded, because it has a & specifier */
|
||||
bool job_should_be_backgrounded(const parse_node_t &job) const;
|
||||
};
|
||||
|
||||
|
||||
|
@ -198,9 +201,9 @@ bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t flags, parse
|
|||
job job_list
|
||||
<TOK_END> job_list
|
||||
|
||||
# A job is a non-empty list of statements, separated by pipes. (Non-empty is useful for cases like if statements, where we require a command). To represent "non-empty", we require a statement, followed by a possibly empty job_continuation
|
||||
# A job is a non-empty list of statements, separated by pipes. (Non-empty is useful for cases like if statements, where we require a command). To represent "non-empty", we require a statement, followed by a possibly empty job_continuation, and then optionally a background specifier '&'
|
||||
|
||||
job = statement job_continuation
|
||||
job = statement job_continuation optional_background
|
||||
job_continuation = <empty> |
|
||||
<TOK_PIPE> statement job_continuation
|
||||
|
||||
|
@ -217,7 +220,7 @@ bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t flags, parse
|
|||
else_continuation = if_clause else_clause |
|
||||
<TOK_END> job_list
|
||||
|
||||
switch_statement = SWITCH <TOK_STRING> <TOK_END> case_item_list end_command arguments_or_redirections_list
|
||||
switch_statement = SWITCH argument <TOK_END> case_item_list end_command arguments_or_redirections_list
|
||||
case_item_list = <empty> |
|
||||
case_item case_item_list |
|
||||
<TOK_END> case_item_list
|
||||
|
@ -240,7 +243,7 @@ bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t flags, parse
|
|||
# A decorated_statement is a command with a list of arguments_or_redirections, possibly with "builtin" or "command" or "exec"
|
||||
|
||||
decorated_statement = plain_statement | COMMAND plain_statement | BUILTIN plain_statement | EXEC plain_statement
|
||||
plain_statement = <TOK_STRING> arguments_or_redirections_list optional_background
|
||||
plain_statement = <TOK_STRING> arguments_or_redirections_list
|
||||
|
||||
argument_list = <empty> | argument argument_list
|
||||
|
||||
|
@ -255,6 +258,11 @@ bool parse_tree_from_string(const wcstring &str, parse_tree_flags_t flags, parse
|
|||
|
||||
end_command = END
|
||||
|
||||
# A freestanding_argument_list is equivalent to a normal argument list, except it may contain TOK_END (newlines, and even semicolons, for historical reasons
|
||||
|
||||
freestanding_argument_list = <empty> |
|
||||
argument freestanding_argument_list |
|
||||
<TOK_END> freestanding_argument_list
|
||||
*/
|
||||
|
||||
#endif
|
||||
|
|
|
@ -493,7 +493,7 @@ void parser_t::expand_argument_list(const wcstring &arg_list_src, std::vector<co
|
|||
|
||||
/* Parse the string as an argument list */
|
||||
parse_node_tree_t tree;
|
||||
if (! parse_tree_from_string(arg_list_src, parse_flag_none, &tree, NULL /* errors */, symbol_argument_list))
|
||||
if (! parse_tree_from_string(arg_list_src, parse_flag_none, &tree, NULL /* errors */, symbol_freestanding_argument_list))
|
||||
{
|
||||
/* Failed to parse. Here we expect to have reported any errors in test_args */
|
||||
return;
|
||||
|
@ -502,7 +502,7 @@ void parser_t::expand_argument_list(const wcstring &arg_list_src, std::vector<co
|
|||
/* Get the root argument list */
|
||||
assert(! tree.empty());
|
||||
const parse_node_t *arg_list = &tree.at(0);
|
||||
assert(arg_list->type == symbol_argument_list);
|
||||
assert(arg_list->type == symbol_freestanding_argument_list);
|
||||
|
||||
/* Extract arguments from it */
|
||||
while (arg_list != NULL)
|
||||
|
@ -968,7 +968,7 @@ bool parser_t::detect_errors_in_argument_list(const wcstring &arg_list_src, wcst
|
|||
|
||||
/* Parse the string as an argument list */
|
||||
parse_node_tree_t tree;
|
||||
if (! parse_tree_from_string(arg_list_src, parse_flag_none, &tree, &errors, symbol_argument_list))
|
||||
if (! parse_tree_from_string(arg_list_src, parse_flag_none, &tree, &errors, symbol_freestanding_argument_list))
|
||||
{
|
||||
/* Failed to parse. */
|
||||
errored = true;
|
||||
|
@ -979,7 +979,7 @@ bool parser_t::detect_errors_in_argument_list(const wcstring &arg_list_src, wcst
|
|||
/* Get the root argument list */
|
||||
assert(! tree.empty());
|
||||
const parse_node_t *arg_list = &tree.at(0);
|
||||
assert(arg_list->type == symbol_argument_list);
|
||||
assert(arg_list->type == symbol_freestanding_argument_list);
|
||||
|
||||
/* Extract arguments from it */
|
||||
while (arg_list != NULL && ! errored)
|
||||
|
|
|
@ -1403,7 +1403,7 @@ struct autosuggestion_context_t
|
|||
search_string(term),
|
||||
cursor_pos(pos),
|
||||
searcher(*history, term, HISTORY_SEARCH_TYPE_PREFIX),
|
||||
detector(history, term),
|
||||
detector(history),
|
||||
working_directory(env_get_pwd_slash()),
|
||||
vars(env_vars_snapshot_t::highlighting_keys),
|
||||
generation_count(s_generation_count)
|
||||
|
|
|
@ -14,7 +14,7 @@ function __fish_print_hostnames -d "Print a list of known hostnames"
|
|||
|
||||
# Print hosts with known ssh keys
|
||||
# Does not match hostnames with @directives specified
|
||||
sgrep -Eoh '^[^#@|, ]*' ~/.ssh/known_hosts{,2} ^/dev/null
|
||||
sgrep -Eoh '^[^#@|, ]*' ~/.ssh/known_hosts{,2} ^/dev/null | sed -E 's/^\[([^]]+)\]:([0-9]+)$/\1/'
|
||||
|
||||
# Print hosts from system wide ssh configuration file
|
||||
if [ -e /etc/ssh/ssh_config ]
|
||||
|
|
Loading…
Reference in New Issue
Block a user