mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-13 13:39:02 +00:00
Initial abbreviation work. Tests currently fail.
This commit is contained in:
parent
58ad04b61c
commit
92099c7af2
6 changed files with 285 additions and 5 deletions
|
@ -2370,8 +2370,9 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
|
|||
reader_set_highlight_function(&highlight_shell);
|
||||
reader_set_test_function(&reader_shell_test);
|
||||
}
|
||||
/* No autosuggestions in builtin_read */
|
||||
/* No autosuggestions or abbreviations in builtin_read */
|
||||
reader_set_allow_autosuggesting(false);
|
||||
reader_set_expand_abbreviations(false);
|
||||
reader_set_exit_on_interrupt(true);
|
||||
|
||||
reader_set_buffer(commandline, wcslen(commandline));
|
||||
|
|
39
expand.cpp
39
expand.cpp
|
@ -1933,3 +1933,42 @@ bool fish_openSUSE_dbus_hack_hack_hack_hack(std::vector<completion_t> *args)
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool expand_abbreviation(const wcstring &src, wcstring *output)
|
||||
{
|
||||
if (src.empty())
|
||||
return false;
|
||||
|
||||
/* Get the abbreviations. Return false if we have none */
|
||||
env_var_t var = env_get_string(USER_ABBREVIATIONS_VARIABLE_NAME);
|
||||
if (var.missing_or_empty())
|
||||
return false;
|
||||
|
||||
bool result = false;
|
||||
wcstring line;
|
||||
wcstokenizer tokenizer(var, ARRAY_SEP_STR);
|
||||
while (tokenizer.next(line))
|
||||
{
|
||||
/* Line is expected to be of the form 'foo=bar'. Parse out the first =. Be forgiving about spaces, but silently skip on failure (no equals, or equals at the end or beginning). Try to avoid copying any strings until we are sure this is a match. */
|
||||
size_t equals = line.find(L'=');
|
||||
if (equals == wcstring::npos || equals == 0 || equals + 1 == line.size())
|
||||
continue;
|
||||
|
||||
/* Find the character just past the end of the command. Walk backwards, skipping spaces. */
|
||||
size_t cmd_end = equals;
|
||||
while (cmd_end > 0 && iswspace(line.at(cmd_end - 1)))
|
||||
cmd_end--;
|
||||
|
||||
/* See if this command matches */
|
||||
if (line.compare(0, cmd_end, src) == 0)
|
||||
{
|
||||
/* Success. Set output to everythign past the end of the string. */
|
||||
if (output != NULL)
|
||||
output->assign(line, equals + 1, wcstring::npos);
|
||||
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
4
expand.h
4
expand.h
|
@ -206,6 +206,10 @@ void expand_variable_error(parser_t &parser, const wchar_t *token, size_t token_
|
|||
*/
|
||||
std::vector<wcstring> expand_get_all_process_names(void);
|
||||
|
||||
/** Abbreviation support. Expand src as an abbreviation, returning true if one was found, false if not. If result is not-null, returns the abbreviation by reference. */
|
||||
#define USER_ABBREVIATIONS_VARIABLE_NAME L"fish_user_abbreviations"
|
||||
bool expand_abbreviation(const wcstring &src, wcstring *output);
|
||||
|
||||
/* Terrible hacks */
|
||||
bool fish_xdm_login_hack_hack_hack_hack(std::vector<std::string> *cmds, int argc, const char * const *argv);
|
||||
bool fish_openSUSE_dbus_hack_hack_hack_hack(std::vector<completion_t> *args);
|
||||
|
|
|
@ -703,6 +703,67 @@ static void test_fuzzy_match(void)
|
|||
if (string_fuzzy_match_string(L"BB", L"ALPHA!").type != fuzzy_match_none) err(L"test_fuzzy_match failed on line %ld", __LINE__);
|
||||
}
|
||||
|
||||
static void test_abbreviations(void)
|
||||
{
|
||||
say(L"Testing abbreviations");
|
||||
|
||||
const wchar_t *buff = L"echo (echo (echo (gc";
|
||||
size_t cursor_pos = wcslen(buff) - 2;
|
||||
const wchar_t *cmdsub_begin = NULL, *cmdsub_end = NULL;
|
||||
parse_util_cmdsubst_extent(buff, cursor_pos, &cmdsub_begin, &cmdsub_end);
|
||||
assert(cmdsub_begin != NULL && cmdsub_begin >= buff);
|
||||
assert(cmdsub_end != NULL && cmdsub_end >= cmdsub_begin);
|
||||
fprintf(stderr, "cmdsub of '%ls' at %lu is '%ls'\n", buff, cursor_pos, wcstring(cmdsub_begin, cmdsub_end).c_str());
|
||||
exit(0);
|
||||
|
||||
const wchar_t *abbreviations =
|
||||
L"gc=git checkout" ARRAY_SEP_STR
|
||||
L"foo=" ARRAY_SEP_STR
|
||||
L"gc=something else" ARRAY_SEP_STR
|
||||
L"=" ARRAY_SEP_STR
|
||||
L"=foo" ARRAY_SEP_STR
|
||||
L"foo" ARRAY_SEP_STR
|
||||
L"foo=bar";
|
||||
|
||||
env_push(true);
|
||||
|
||||
int ret = env_set(USER_ABBREVIATIONS_VARIABLE_NAME, abbreviations, ENV_LOCAL);
|
||||
if (ret != 0) err(L"Unable to set abbreviation variable");
|
||||
|
||||
wcstring result;
|
||||
if (expand_abbreviation(L"", &result)) err(L"Unexpected success with empty abbreviation");
|
||||
if (expand_abbreviation(L"nothing", &result)) err(L"Unexpected success with missing abbreviation");
|
||||
|
||||
if (! expand_abbreviation(L"gc", &result)) err(L"Unexpected failure with gc abbreviation");
|
||||
if (result != L"git checkout") err(L"Wrong abbreviation result for gc");
|
||||
result.clear();
|
||||
|
||||
if (! expand_abbreviation(L"foo", &result)) err(L"Unexpected failure with foo abbreviation");
|
||||
if (result != L"bar") err(L"Wrong abbreviation result for foo");
|
||||
|
||||
bool expanded;
|
||||
expanded = reader_expand_abbreviation_in_command(L"just a command", wcslen(L"just "), &result);
|
||||
if (expanded) err(L"Command wrongly expanded on line %ld", (long)__LINE__);
|
||||
expanded = reader_expand_abbreviation_in_command(L"gc somebranch", 0, &result);
|
||||
if (expanded) err(L"Command wrongly expanded on line %ld", (long)__LINE__);
|
||||
expanded = reader_expand_abbreviation_in_command(L"gc somebranch", 1, &result);
|
||||
if (expanded) err(L"Command wrongly expanded on line %ld", (long)__LINE__);
|
||||
|
||||
expanded = reader_expand_abbreviation_in_command(L"gc somebranch", wcslen(L"gc "), &result);
|
||||
if (! expanded) err(L"gc not expanded");
|
||||
if (result != L"git checkout somebranch") err(L"gc incorrectly expanded on line %ld to '%ls'", (long)__LINE__, result.c_str());
|
||||
|
||||
expanded = reader_expand_abbreviation_in_command(L"echo hi ; gc somebranch", wcslen(L"echo hi ; gc "), &result);
|
||||
if (! expanded) err(L"gc not expanded on line %ld", (long)__LINE__);
|
||||
if (result != L"echo hi ; git checkout somebranch") err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
|
||||
|
||||
expanded = reader_expand_abbreviation_in_command(L"echo (echo (echo (echo (gc ", wcslen(L"echo (echo (echo (echo (gc "), &result);
|
||||
if (! expanded) err(L"gc not expanded on line %ld", (long)__LINE__);
|
||||
if (result != L"echo (echo (git checkout ") err(L"gc incorrectly expanded on line %ld", (long)__LINE__);
|
||||
|
||||
env_pop();
|
||||
}
|
||||
|
||||
/** Test path functions */
|
||||
static void test_path()
|
||||
{
|
||||
|
@ -1776,6 +1837,7 @@ int main(int argc, char **argv)
|
|||
test_lru();
|
||||
test_expand();
|
||||
test_fuzzy_match();
|
||||
test_abbreviations();
|
||||
test_test();
|
||||
test_path();
|
||||
test_word_motion();
|
||||
|
|
175
reader.cpp
175
reader.cpp
|
@ -182,6 +182,8 @@ static pthread_key_t generation_count_key;
|
|||
/* A color is an int */
|
||||
typedef int color_t;
|
||||
|
||||
static void set_command_line_and_position(const wcstring &new_str, size_t pos);
|
||||
|
||||
/**
|
||||
A struct describing the state of the interactive reader. These
|
||||
states can be stacked, in case reader_readline() calls are
|
||||
|
@ -203,6 +205,9 @@ public:
|
|||
/** When backspacing, we temporarily suppress autosuggestions */
|
||||
bool suppress_autosuggestion;
|
||||
|
||||
/** Whether abbreviations are expanded */
|
||||
bool expand_abbreviations;
|
||||
|
||||
/** The representation of the current screen contents */
|
||||
screen_t screen;
|
||||
|
||||
|
@ -244,6 +249,9 @@ public:
|
|||
/** Do what we need to do whenever our command line changes */
|
||||
void command_line_changed(void);
|
||||
|
||||
/** Expand abbreviations at the current cursor position. Returns true if the command line changed. */
|
||||
bool expand_abbreviation_as_necessary(void);
|
||||
|
||||
/** The current position of the cursor in buff. */
|
||||
size_t buff_pos;
|
||||
|
||||
|
@ -326,6 +334,7 @@ public:
|
|||
reader_data_t() :
|
||||
allow_autosuggestion(0),
|
||||
suppress_autosuggestion(0),
|
||||
expand_abbreviations(0),
|
||||
history(0),
|
||||
token_history_pos(0),
|
||||
search_pos(0),
|
||||
|
@ -635,6 +644,148 @@ void reader_data_t::command_line_changed()
|
|||
s_generation_count++;
|
||||
}
|
||||
|
||||
/* Expand abbreviations at the given cursor position. Does NOT inspect 'data'. */
|
||||
bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t cursor_pos, wcstring *output)
|
||||
{
|
||||
/* Can't have the cursor at the beginning */
|
||||
if (cursor_pos == 0)
|
||||
return false;
|
||||
|
||||
/* See if we are at "command position". Get the surrounding command substitution, and get the extent of the first token. */
|
||||
const wchar_t * const buff = cmdline.c_str();
|
||||
const wchar_t *cmdsub_begin = NULL, *cmdsub_end = NULL;
|
||||
parse_util_cmdsubst_extent(buff, cursor_pos, &cmdsub_begin, &cmdsub_end);
|
||||
assert(cmdsub_begin != NULL && cmdsub_begin >= buff);
|
||||
assert(cmdsub_end != NULL && cmdsub_end >= cmdsub_begin);
|
||||
fprintf(stderr, "cmdsub of '%ls' at %lu is '%ls'\n", cmdline.c_str(), cursor_pos, wcstring(cmdsub_begin, cmdsub_end).c_str());
|
||||
|
||||
/* Determine the offset of this command substitution */
|
||||
const size_t subcmd_offset = cmdsub_begin - buff;
|
||||
|
||||
const wcstring subcmd = wcstring(cmdsub_begin, cmdsub_end - cmdsub_begin);
|
||||
const wchar_t *subcmd_cstr = subcmd.c_str();
|
||||
|
||||
/* Get the token before the cursor */
|
||||
const wchar_t *subcmd_tok_begin = NULL, *subcmd_tok_end = NULL;
|
||||
size_t subcmd_cursor_pos = cursor_pos + subcmd_offset;
|
||||
parse_util_token_extent(subcmd_cstr, subcmd_cursor_pos, NULL, NULL, &subcmd_tok_begin, &subcmd_tok_end);
|
||||
|
||||
/* Compute the offset of the token before the cursor within the subcmd */
|
||||
assert(subcmd_tok_begin >= subcmd_cstr);
|
||||
assert(subcmd_tok_end >= subcmd_tok_begin);
|
||||
const size_t subcmd_tok_begin_offset = subcmd_tok_begin - subcmd_cstr;
|
||||
const size_t subcmd_tok_length = subcmd_tok_end - subcmd_tok_begin;
|
||||
|
||||
/* Now parse the subcmd, looking for commands */
|
||||
bool had_cmd = false, previous_token_is_cmd = false;
|
||||
tokenizer_t tok(subcmd_cstr, TOK_ACCEPT_UNFINISHED | TOK_SQUASH_ERRORS);
|
||||
for (; tok_has_next(&tok); tok_next(&tok))
|
||||
{
|
||||
size_t tok_pos = static_cast<size_t>(tok_get_pos(&tok));
|
||||
if (tok_pos > subcmd_tok_begin_offset)
|
||||
{
|
||||
/* We've passed the token we're interested in */
|
||||
break;
|
||||
}
|
||||
|
||||
int last_type = tok_last_type(&tok);
|
||||
|
||||
switch (last_type)
|
||||
{
|
||||
case TOK_STRING:
|
||||
{
|
||||
if (had_cmd)
|
||||
{
|
||||
/* Parameter to the command. */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Command. */
|
||||
had_cmd = true;
|
||||
if (tok_pos == subcmd_tok_begin_offset)
|
||||
{
|
||||
/* This is the token we care about! */
|
||||
previous_token_is_cmd = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TOK_REDIRECT_NOCLOB:
|
||||
case TOK_REDIRECT_OUT:
|
||||
case TOK_REDIRECT_IN:
|
||||
case TOK_REDIRECT_APPEND:
|
||||
case TOK_REDIRECT_FD:
|
||||
{
|
||||
if (!had_cmd)
|
||||
{
|
||||
break;
|
||||
}
|
||||
tok_next(&tok);
|
||||
break;
|
||||
}
|
||||
|
||||
case TOK_PIPE:
|
||||
case TOK_BACKGROUND:
|
||||
case TOK_END:
|
||||
{
|
||||
had_cmd = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case TOK_COMMENT:
|
||||
case TOK_ERROR:
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
if (previous_token_is_cmd)
|
||||
{
|
||||
/* The token is a command. Try expanding it as an abbreviation. */
|
||||
const wcstring token = wcstring(subcmd, subcmd_tok_begin_offset, subcmd_tok_length);
|
||||
wcstring abbreviation;
|
||||
if (expand_abbreviation(token, &abbreviation))
|
||||
{
|
||||
/* There was an abbreviation! Replace the token in the full command. Maintain the relative position of the cursor. */
|
||||
if (output != NULL)
|
||||
{
|
||||
size_t cmd_tok_begin_offset = subcmd_tok_begin_offset + subcmd_offset;
|
||||
output->assign(cmdline);
|
||||
output->replace(cmd_tok_begin_offset, subcmd_tok_length, abbreviation);
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Expand abbreviations */
|
||||
bool reader_data_t::expand_abbreviation_as_necessary(void)
|
||||
{
|
||||
bool result = false;
|
||||
if (this->expand_abbreviations)
|
||||
{
|
||||
wcstring new_cmdline;
|
||||
if (reader_expand_abbreviation_in_command(this->command_line, this->buff_pos, &new_cmdline))
|
||||
{
|
||||
/* We expanded an abbreviation! The cursor moves by the difference in the command line lengths. */
|
||||
size_t new_buff_pos = this->buff_pos + new_cmdline.size() - this->command_line.size();
|
||||
|
||||
this->command_line.swap(new_cmdline);
|
||||
data->command_line_changed();
|
||||
data->buff_pos = new_buff_pos;
|
||||
reader_super_highlight_me_plenty(data->buff_pos);
|
||||
reader_repaint();
|
||||
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Sorts and remove any duplicate completions in the list. */
|
||||
static void sort_and_make_unique(std::vector<completion_t> &l)
|
||||
|
@ -1980,8 +2131,7 @@ void reader_sanity_check()
|
|||
}
|
||||
|
||||
/**
|
||||
Set the specified string from the history as the current buffer. Do
|
||||
not modify prefix_width.
|
||||
Set the specified string as the current buffer.
|
||||
*/
|
||||
static void set_command_line_and_position(const wcstring &new_str, size_t pos)
|
||||
{
|
||||
|
@ -2462,6 +2612,11 @@ void reader_set_allow_autosuggesting(bool flag)
|
|||
data->allow_autosuggestion = flag;
|
||||
}
|
||||
|
||||
void reader_set_expand_abbreviations(bool flag)
|
||||
{
|
||||
data->expand_abbreviations = flag;
|
||||
}
|
||||
|
||||
void reader_set_complete_function(complete_function_t f)
|
||||
{
|
||||
data->complete_func = f;
|
||||
|
@ -2712,6 +2867,7 @@ static int read_i(void)
|
|||
reader_set_highlight_function(&highlight_shell);
|
||||
reader_set_test_function(&reader_shell_test);
|
||||
reader_set_allow_autosuggesting(true);
|
||||
reader_set_expand_abbreviations(true);
|
||||
reader_import_history_if_necessary();
|
||||
|
||||
parser_t &parser = parser_t::principal_parser();
|
||||
|
@ -2851,7 +3007,6 @@ const wchar_t *reader_readline(void)
|
|||
data->search_buff.clear();
|
||||
data->search_mode = NO_SEARCH;
|
||||
|
||||
|
||||
exec_prompt();
|
||||
|
||||
reader_super_highlight_me_plenty(data->buff_pos);
|
||||
|
@ -3261,7 +3416,19 @@ const wchar_t *reader_readline(void)
|
|||
}
|
||||
}
|
||||
|
||||
switch (data->test_func(data->command_line.c_str()))
|
||||
/* See if this command is valid */
|
||||
int command_test_result = data->test_func(data->command_line.c_str());
|
||||
if (command_test_result == 0)
|
||||
{
|
||||
/* This command is valid, but an abbreviation may make it invalid. If so, we will have to test again. */
|
||||
bool abbreviation_expanded = data->expand_abbreviation_as_necessary();
|
||||
if (abbreviation_expanded)
|
||||
{
|
||||
command_test_result = data->test_func(data->command_line.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
switch (command_test_result)
|
||||
{
|
||||
|
||||
case 0:
|
||||
|
|
7
reader.h
7
reader.h
|
@ -204,6 +204,10 @@ void reader_set_right_prompt(const wcstring &prompt);
|
|||
/** Sets whether autosuggesting is allowed. */
|
||||
void reader_set_allow_autosuggesting(bool flag);
|
||||
|
||||
/** Sets whether abbreviation expansion is performed. */
|
||||
void reader_set_expand_abbreviations(bool flag);
|
||||
|
||||
|
||||
/** Sets whether the reader should exit on ^C. */
|
||||
void reader_set_exit_on_interrupt(bool flag);
|
||||
|
||||
|
@ -243,6 +247,9 @@ int reader_search_mode();
|
|||
/* Given a command line and an autosuggestion, return the string that gets shown to the user. Exposed for testing purposes only. */
|
||||
wcstring combine_command_and_autosuggestion(const wcstring &cmdline, const wcstring &autosuggestion);
|
||||
|
||||
/* Expand abbreviations at the given cursor position. Exposed for testing purposes only. */
|
||||
bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t cursor_pos, wcstring *output);
|
||||
|
||||
/* Apply a completion string. Exposed for testing only. */
|
||||
wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flags_t flags, const wcstring &command_line, size_t *inout_cursor_pos, bool append_only);
|
||||
|
||||
|
|
Loading…
Reference in a new issue