Initial abbreviation work. Tests currently fail.

This commit is contained in:
ridiculousfish 2013-07-17 00:38:04 -07:00
parent 58ad04b61c
commit 92099c7af2
6 changed files with 285 additions and 5 deletions

View file

@ -2370,8 +2370,9 @@ static int builtin_read(parser_t &parser, wchar_t **argv)
reader_set_highlight_function(&highlight_shell); reader_set_highlight_function(&highlight_shell);
reader_set_test_function(&reader_shell_test); 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_allow_autosuggesting(false);
reader_set_expand_abbreviations(false);
reader_set_exit_on_interrupt(true); reader_set_exit_on_interrupt(true);
reader_set_buffer(commandline, wcslen(commandline)); reader_set_buffer(commandline, wcslen(commandline));

View file

@ -1933,3 +1933,42 @@ bool fish_openSUSE_dbus_hack_hack_hack_hack(std::vector<completion_t> *args)
} }
return result; 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;
}

View file

@ -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); 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 */ /* Terrible hacks */
bool fish_xdm_login_hack_hack_hack_hack(std::vector<std::string> *cmds, int argc, const char * const *argv); 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); bool fish_openSUSE_dbus_hack_hack_hack_hack(std::vector<completion_t> *args);

View file

@ -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__); 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 */ /** Test path functions */
static void test_path() static void test_path()
{ {
@ -1776,6 +1837,7 @@ int main(int argc, char **argv)
test_lru(); test_lru();
test_expand(); test_expand();
test_fuzzy_match(); test_fuzzy_match();
test_abbreviations();
test_test(); test_test();
test_path(); test_path();
test_word_motion(); test_word_motion();

View file

@ -182,6 +182,8 @@ static pthread_key_t generation_count_key;
/* A color is an int */ /* A color is an int */
typedef int color_t; 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 A struct describing the state of the interactive reader. These
states can be stacked, in case reader_readline() calls are states can be stacked, in case reader_readline() calls are
@ -203,6 +205,9 @@ public:
/** When backspacing, we temporarily suppress autosuggestions */ /** When backspacing, we temporarily suppress autosuggestions */
bool suppress_autosuggestion; bool suppress_autosuggestion;
/** Whether abbreviations are expanded */
bool expand_abbreviations;
/** The representation of the current screen contents */ /** The representation of the current screen contents */
screen_t screen; screen_t screen;
@ -244,6 +249,9 @@ public:
/** Do what we need to do whenever our command line changes */ /** Do what we need to do whenever our command line changes */
void command_line_changed(void); 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. */ /** The current position of the cursor in buff. */
size_t buff_pos; size_t buff_pos;
@ -326,6 +334,7 @@ public:
reader_data_t() : reader_data_t() :
allow_autosuggestion(0), allow_autosuggestion(0),
suppress_autosuggestion(0), suppress_autosuggestion(0),
expand_abbreviations(0),
history(0), history(0),
token_history_pos(0), token_history_pos(0),
search_pos(0), search_pos(0),
@ -635,6 +644,148 @@ void reader_data_t::command_line_changed()
s_generation_count++; 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. */ /** Sorts and remove any duplicate completions in the list. */
static void sort_and_make_unique(std::vector<completion_t> &l) 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 Set the specified string as the current buffer.
not modify prefix_width.
*/ */
static void set_command_line_and_position(const wcstring &new_str, size_t pos) 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; data->allow_autosuggestion = flag;
} }
void reader_set_expand_abbreviations(bool flag)
{
data->expand_abbreviations = flag;
}
void reader_set_complete_function(complete_function_t f) void reader_set_complete_function(complete_function_t f)
{ {
data->complete_func = f; data->complete_func = f;
@ -2712,6 +2867,7 @@ static int read_i(void)
reader_set_highlight_function(&highlight_shell); reader_set_highlight_function(&highlight_shell);
reader_set_test_function(&reader_shell_test); reader_set_test_function(&reader_shell_test);
reader_set_allow_autosuggesting(true); reader_set_allow_autosuggesting(true);
reader_set_expand_abbreviations(true);
reader_import_history_if_necessary(); reader_import_history_if_necessary();
parser_t &parser = parser_t::principal_parser(); parser_t &parser = parser_t::principal_parser();
@ -2851,7 +3007,6 @@ const wchar_t *reader_readline(void)
data->search_buff.clear(); data->search_buff.clear();
data->search_mode = NO_SEARCH; data->search_mode = NO_SEARCH;
exec_prompt(); exec_prompt();
reader_super_highlight_me_plenty(data->buff_pos); 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: case 0:

View file

@ -204,6 +204,10 @@ void reader_set_right_prompt(const wcstring &prompt);
/** Sets whether autosuggesting is allowed. */ /** Sets whether autosuggesting is allowed. */
void reader_set_allow_autosuggesting(bool flag); 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. */ /** Sets whether the reader should exit on ^C. */
void reader_set_exit_on_interrupt(bool flag); 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. */ /* 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); 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. */ /* 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); 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);