mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 05:13:10 +00:00
Changes to work recognition per https://github.com/fish-shell/fish-shell/issues/384
Word movement should be very similar to fish 1.x backward-kill-word remains more liberal, but now stops at any of {,'"=}
This commit is contained in:
parent
ce15abd577
commit
0b1e371880
7 changed files with 224 additions and 51 deletions
|
@ -2031,7 +2031,7 @@ static int builtin_function(parser_t &parser, wchar_t **argv)
|
||||||
{
|
{
|
||||||
const wchar_t *nxt = names.at(i).c_str();
|
const wchar_t *nxt = names.at(i).c_str();
|
||||||
size_t l = wcslen(nxt + 2);
|
size_t l = wcslen(nxt + 2);
|
||||||
if (chars+l > common_get_width())
|
if (chars+l > (size_t)common_get_width())
|
||||||
{
|
{
|
||||||
chars = 0;
|
chars = 0;
|
||||||
stderr_buffer.push_back(L'\n');
|
stderr_buffer.push_back(L'\n');
|
||||||
|
|
|
@ -674,6 +674,95 @@ static void test_path()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum word_motion_t {
|
||||||
|
word_motion_left,
|
||||||
|
word_motion_right
|
||||||
|
};
|
||||||
|
static void test_1_word_motion(word_motion_t motion, move_word_style_t style, const wcstring &test)
|
||||||
|
{
|
||||||
|
wcstring command;
|
||||||
|
std::set<size_t> stops;
|
||||||
|
|
||||||
|
// Carets represent stops and should be cut out of the command
|
||||||
|
for (size_t i=0; i < test.size(); i++) {
|
||||||
|
wchar_t wc = test.at(i);
|
||||||
|
if (wc == L'^')
|
||||||
|
{
|
||||||
|
stops.insert(command.size());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
command.push_back(wc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t idx, end;
|
||||||
|
if (motion == word_motion_left)
|
||||||
|
{
|
||||||
|
idx = command.size();
|
||||||
|
end = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
idx = 0;
|
||||||
|
end = command.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
move_word_state_machine_t sm(style);
|
||||||
|
while (idx != end)
|
||||||
|
{
|
||||||
|
size_t char_idx = (motion == word_motion_left ? idx - 1 : idx);
|
||||||
|
wchar_t wc = command.at(char_idx);
|
||||||
|
bool will_stop = ! sm.consume_char(wc);
|
||||||
|
//printf("idx %lu, looking at %lu (%c): %d\n", idx, char_idx, (char)wc, will_stop);
|
||||||
|
bool expected_stop = (stops.count(idx) > 0);
|
||||||
|
if (will_stop != expected_stop)
|
||||||
|
{
|
||||||
|
wcstring tmp = command;
|
||||||
|
tmp.insert(idx, L"^");
|
||||||
|
const char *dir = (motion == word_motion_left ? "left" : "right");
|
||||||
|
if (will_stop)
|
||||||
|
{
|
||||||
|
err(L"Word motion: moving %s, unexpected stop at idx %lu: '%ls'", dir, idx, tmp.c_str());
|
||||||
|
}
|
||||||
|
else if (! will_stop && expected_stop)
|
||||||
|
{
|
||||||
|
err(L"Word motion: moving %s, should have stopped at idx %lu: '%ls'", dir, idx, tmp.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We don't expect to stop here next time
|
||||||
|
if (expected_stop)
|
||||||
|
{
|
||||||
|
stops.erase(idx);
|
||||||
|
}
|
||||||
|
if (will_stop)
|
||||||
|
{
|
||||||
|
sm.reset();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
idx += (motion == word_motion_left ? -1 : 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Test word motion (forward-word, etc.). Carets represent cursor stops. */
|
||||||
|
static void test_word_motion()
|
||||||
|
{
|
||||||
|
say(L"Testing word motion");
|
||||||
|
test_1_word_motion(word_motion_left, move_word_style_punctuation, L"^echo ^hello_^world.^txt");
|
||||||
|
test_1_word_motion(word_motion_right, move_word_style_punctuation, L"echo^ hello^_world^.txt^");
|
||||||
|
|
||||||
|
test_1_word_motion(word_motion_left, move_word_style_punctuation, L"echo ^foo_^foo_^foo/^/^/^/^/^ ");
|
||||||
|
test_1_word_motion(word_motion_right, move_word_style_punctuation, L"echo^ foo^_foo^_foo^/^/^/^/^/ ^");
|
||||||
|
|
||||||
|
test_1_word_motion(word_motion_left, move_word_style_path_components, L"^/^foo/^bar/^baz/");
|
||||||
|
test_1_word_motion(word_motion_left, move_word_style_path_components, L"^echo ^--foo ^--bar");
|
||||||
|
test_1_word_motion(word_motion_left, move_word_style_path_components, L"^echo ^hi ^> /^dev/^null");
|
||||||
|
|
||||||
|
test_1_word_motion(word_motion_left, move_word_style_path_components, L"^echo /^foo/^bar{^aaa,^bbb,^ccc}^bak/");
|
||||||
|
}
|
||||||
|
|
||||||
/** Test is_potential_path */
|
/** Test is_potential_path */
|
||||||
static void test_is_potential_path()
|
static void test_is_potential_path()
|
||||||
{
|
{
|
||||||
|
@ -1489,6 +1578,9 @@ int main(int argc, char **argv)
|
||||||
builtin_init();
|
builtin_init();
|
||||||
reader_init();
|
reader_init();
|
||||||
env_init();
|
env_init();
|
||||||
|
|
||||||
|
test_word_motion();
|
||||||
|
return 0;
|
||||||
|
|
||||||
test_format();
|
test_format();
|
||||||
test_escape();
|
test_escape();
|
||||||
|
@ -1501,6 +1593,7 @@ int main(int argc, char **argv)
|
||||||
test_expand();
|
test_expand();
|
||||||
test_test();
|
test_test();
|
||||||
test_path();
|
test_path();
|
||||||
|
test_word_motion();
|
||||||
test_is_potential_path();
|
test_is_potential_path();
|
||||||
test_colors();
|
test_colors();
|
||||||
test_autosuggest_suggest_special();
|
test_autosuggest_suggest_special();
|
||||||
|
|
|
@ -1447,7 +1447,7 @@ static void highlight_universal_internal(const wcstring &buffstr, std::vector<in
|
||||||
*/
|
*/
|
||||||
if ((buffstr.at(pos) == L'\'') || (buffstr.at(pos) == L'\"'))
|
if ((buffstr.at(pos) == L'\'') || (buffstr.at(pos) == L'\"'))
|
||||||
{
|
{
|
||||||
std::vector<long> lst;
|
std::vector<size_t> lst;
|
||||||
|
|
||||||
int level=0;
|
int level=0;
|
||||||
wchar_t prev_q=0;
|
wchar_t prev_q=0;
|
||||||
|
@ -1476,7 +1476,7 @@ static void highlight_universal_internal(const wcstring &buffstr, std::vector<in
|
||||||
{
|
{
|
||||||
if (prev_q == *str)
|
if (prev_q == *str)
|
||||||
{
|
{
|
||||||
long pos1, pos2;
|
size_t pos1, pos2;
|
||||||
|
|
||||||
level--;
|
level--;
|
||||||
pos1 = lst.back();
|
pos1 = lst.back();
|
||||||
|
|
|
@ -1237,7 +1237,7 @@ int parser_t::is_help(const wchar_t *s, int min_match) const
|
||||||
min_match = maxi(min_match, 3);
|
min_match = maxi(min_match, 3);
|
||||||
|
|
||||||
return (wcscmp(L"-h", s) == 0) ||
|
return (wcscmp(L"-h", s) == 0) ||
|
||||||
(len >= min_match && (wcsncmp(L"--help", s, len) == 0));
|
(len >= (size_t)min_match && (wcsncmp(L"--help", s, len) == 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
job_t *parser_t::job_create(void)
|
job_t *parser_t::job_create(void)
|
||||||
|
|
28
reader.cpp
28
reader.cpp
|
@ -1295,7 +1295,7 @@ static void accept_autosuggestion(bool full)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Accept characters up to a word separator */
|
/* Accept characters up to a word separator */
|
||||||
move_word_state_machine_t state;
|
move_word_state_machine_t state(move_word_style_punctuation);
|
||||||
for (size_t idx = data->command_line.size(); idx < data->autosuggestion.size(); idx++)
|
for (size_t idx = data->command_line.size(); idx < data->autosuggestion.size(); idx++)
|
||||||
{
|
{
|
||||||
wchar_t wc = data->autosuggestion.at(idx);
|
wchar_t wc = data->autosuggestion.at(idx);
|
||||||
|
@ -2048,19 +2048,6 @@ static void handle_token_history(int forward, int reset)
|
||||||
\param dir Direction to move/erase. 0 means move left, 1 means move right.
|
\param dir Direction to move/erase. 0 means move left, 1 means move right.
|
||||||
\param erase Whether to erase the characters along the way or only move past them.
|
\param erase Whether to erase the characters along the way or only move past them.
|
||||||
\param new if the new kill item should be appended to the previous kill item or not.
|
\param new if the new kill item should be appended to the previous kill item or not.
|
||||||
|
|
||||||
The regex we implement:
|
|
||||||
|
|
||||||
WHITESPACE*
|
|
||||||
(SEPARATOR+)
|
|
||||||
|
|
|
||||||
(SLASH*
|
|
||||||
TOK_STRING_CHARACTERS_EXCEPT_SLASH*)
|
|
||||||
|
|
||||||
Interesting test case:
|
|
||||||
/foo/bar/baz/ -> /foo/bar/ -> /foo/ -> /
|
|
||||||
echo --foo --bar -> echo --foo -> echo
|
|
||||||
echo hi>/dev/null -> echo hi>/dev/ -> echo hi >/ -> echo hi > -> echo hi -> echo
|
|
||||||
*/
|
*/
|
||||||
enum move_word_dir_t
|
enum move_word_dir_t
|
||||||
{
|
{
|
||||||
|
@ -2068,7 +2055,7 @@ enum move_word_dir_t
|
||||||
MOVE_DIR_RIGHT
|
MOVE_DIR_RIGHT
|
||||||
};
|
};
|
||||||
|
|
||||||
static void move_word(bool move_right, bool erase, bool newv)
|
static void move_word(bool move_right, bool erase, enum move_word_style_t style, bool newv)
|
||||||
{
|
{
|
||||||
/* Return if we are already at the edge */
|
/* Return if we are already at the edge */
|
||||||
const size_t boundary = move_right ? data->command_length() : 0;
|
const size_t boundary = move_right ? data->command_length() : 0;
|
||||||
|
@ -2076,7 +2063,7 @@ static void move_word(bool move_right, bool erase, bool newv)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* When moving left, a value of 1 means the character at index 0. */
|
/* When moving left, a value of 1 means the character at index 0. */
|
||||||
move_word_state_machine_t state;
|
move_word_state_machine_t state(style);
|
||||||
const wchar_t * const command_line = data->command_line.c_str();
|
const wchar_t * const command_line = data->command_line.c_str();
|
||||||
const size_t start_buff_pos = data->buff_pos;
|
const size_t start_buff_pos = data->buff_pos;
|
||||||
|
|
||||||
|
@ -2696,7 +2683,6 @@ static bool is_backslashed(const wchar_t *str, size_t pos)
|
||||||
|
|
||||||
const wchar_t *reader_readline()
|
const wchar_t *reader_readline()
|
||||||
{
|
{
|
||||||
|
|
||||||
wint_t c;
|
wint_t c;
|
||||||
int last_char=0;
|
int last_char=0;
|
||||||
size_t yank_len=0;
|
size_t yank_len=0;
|
||||||
|
@ -3268,21 +3254,21 @@ const wchar_t *reader_readline()
|
||||||
/* kill one word left */
|
/* kill one word left */
|
||||||
case R_BACKWARD_KILL_WORD:
|
case R_BACKWARD_KILL_WORD:
|
||||||
{
|
{
|
||||||
move_word(MOVE_DIR_LEFT, true /* erase */, last_char!=R_BACKWARD_KILL_WORD);
|
move_word(MOVE_DIR_LEFT, true /* erase */, move_word_style_path_components, last_char!=R_BACKWARD_KILL_WORD);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* kill one word right */
|
/* kill one word right */
|
||||||
case R_KILL_WORD:
|
case R_KILL_WORD:
|
||||||
{
|
{
|
||||||
move_word(MOVE_DIR_RIGHT, true /* erase */, last_char!=R_KILL_WORD);
|
move_word(MOVE_DIR_RIGHT, true /* erase */, move_word_style_punctuation, last_char!=R_KILL_WORD);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* move one word left*/
|
/* move one word left*/
|
||||||
case R_BACKWARD_WORD:
|
case R_BACKWARD_WORD:
|
||||||
{
|
{
|
||||||
move_word(MOVE_DIR_LEFT, false /* do not erase */, false);
|
move_word(MOVE_DIR_LEFT, false /* do not erase */, move_word_style_punctuation, false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3291,7 +3277,7 @@ const wchar_t *reader_readline()
|
||||||
{
|
{
|
||||||
if (data->buff_pos < data->command_length())
|
if (data->buff_pos < data->command_length())
|
||||||
{
|
{
|
||||||
move_word(MOVE_DIR_RIGHT, false /* do not erase */, false);
|
move_word(MOVE_DIR_RIGHT, false /* do not erase */, move_word_style_punctuation, false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
121
tokenizer.cpp
121
tokenizer.cpp
|
@ -668,42 +668,111 @@ void tok_set_pos(tokenizer_t *tok, int pos)
|
||||||
tok_next(tok);
|
tok_next(tok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool move_word_state_machine_t::consume_char_punctuation(wchar_t c)
|
||||||
|
|
||||||
move_word_state_machine_t::move_word_state_machine_t() : state(s_whitespace)
|
|
||||||
{
|
{
|
||||||
}
|
enum
|
||||||
|
{
|
||||||
bool move_word_state_machine_t::consume_char(wchar_t c)
|
s_always_one = 0,
|
||||||
{
|
s_whitespace,
|
||||||
//printf("state %d, consume '%lc'\n", state, c);
|
s_alphanumeric,
|
||||||
|
s_end
|
||||||
|
};
|
||||||
|
|
||||||
bool consumed = false;
|
bool consumed = false;
|
||||||
/* Always treat separators as first. All this does is ensure that we treat ^ as a string character instead of as stderr redirection, which I hypothesize is usually what is desired. */
|
|
||||||
bool was_first = true;
|
|
||||||
while (state != s_end && ! consumed)
|
while (state != s_end && ! consumed)
|
||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
|
case s_always_one:
|
||||||
|
/* Always consume the first character */
|
||||||
|
consumed = true;
|
||||||
|
state = s_whitespace;
|
||||||
|
break;
|
||||||
|
|
||||||
case s_whitespace:
|
case s_whitespace:
|
||||||
if (iswspace(c))
|
if (iswspace(c))
|
||||||
{
|
{
|
||||||
/* Consumed whitespace */
|
/* Consumed whitespace */
|
||||||
consumed = true;
|
consumed = true;
|
||||||
}
|
}
|
||||||
else if (tok_is_string_character(c, was_first))
|
else
|
||||||
{
|
{
|
||||||
/* String path */
|
state = s_alphanumeric;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case s_alphanumeric:
|
||||||
|
if (iswalnum(c))
|
||||||
|
{
|
||||||
|
/* Consumed alphanumeric */
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = s_end;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case s_end:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool move_word_state_machine_t::is_path_component_character(wchar_t c)
|
||||||
|
{
|
||||||
|
/* Always treat separators as first. All this does is ensure that we treat ^ as a string character instead of as stderr redirection, which I hypothesize is usually what is desired. */
|
||||||
|
return tok_is_string_character(c, true) && ! wcschr(L"/={,}'\"", c);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool move_word_state_machine_t::consume_char_path_components(wchar_t c)
|
||||||
|
{
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
s_initial_punctuation,
|
||||||
|
s_whitespace,
|
||||||
|
s_separator,
|
||||||
|
s_slash,
|
||||||
|
s_path_component_characters,
|
||||||
|
s_end
|
||||||
|
};
|
||||||
|
|
||||||
|
//printf("state %d, consume '%lc'\n", state, c);
|
||||||
|
bool consumed = false;
|
||||||
|
while (state != s_end && ! consumed)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case s_initial_punctuation:
|
||||||
|
if (! is_path_component_character(c))
|
||||||
|
{
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
state = s_whitespace;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case s_whitespace:
|
||||||
|
if (iswspace(c))
|
||||||
|
{
|
||||||
|
/* Consumed whitespace */
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
else if (c == L'/' || is_path_component_character(c))
|
||||||
|
{
|
||||||
|
/* Path component */
|
||||||
state = s_slash;
|
state = s_slash;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Separator path */
|
/* Path separator */
|
||||||
state = s_separator;
|
state = s_separator;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case s_separator:
|
case s_separator:
|
||||||
if (! iswspace(c) && ! tok_is_string_character(c, was_first))
|
if (! iswspace(c) && ! is_path_component_character(c))
|
||||||
{
|
{
|
||||||
/* Consumed separator */
|
/* Consumed separator */
|
||||||
consumed = true;
|
consumed = true;
|
||||||
|
@ -722,12 +791,12 @@ bool move_word_state_machine_t::consume_char(wchar_t c)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
state = s_nonseparators_except_slash;
|
state = s_path_component_characters;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case s_nonseparators_except_slash:
|
case s_path_component_characters:
|
||||||
if (c != L'/' && tok_is_string_character(c, was_first))
|
if (is_path_component_character(c))
|
||||||
{
|
{
|
||||||
/* Consumed string character except slash */
|
/* Consumed string character except slash */
|
||||||
consumed = true;
|
consumed = true;
|
||||||
|
@ -747,3 +816,21 @@ bool move_word_state_machine_t::consume_char(wchar_t c)
|
||||||
return consumed;
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool move_word_state_machine_t::consume_char(wchar_t c)
|
||||||
|
{
|
||||||
|
switch (style)
|
||||||
|
{
|
||||||
|
case move_word_style_punctuation: return consume_char_punctuation(c);
|
||||||
|
case move_word_style_path_components: return consume_char_path_components(c);
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
move_word_state_machine_t::move_word_state_machine_t(move_word_style_t syl) : state(0), style(syl)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void move_word_state_machine_t::reset()
|
||||||
|
{
|
||||||
|
state = 0;
|
||||||
|
}
|
||||||
|
|
25
tokenizer.h
25
tokenizer.h
|
@ -183,22 +183,29 @@ const wchar_t *tok_get_desc(int type);
|
||||||
*/
|
*/
|
||||||
int tok_get_error(tokenizer_t *tok);
|
int tok_get_error(tokenizer_t *tok);
|
||||||
|
|
||||||
|
enum move_word_style_t
|
||||||
|
{
|
||||||
|
move_word_style_punctuation, //stop at punctuation
|
||||||
|
move_word_style_path_components //stops at path components
|
||||||
|
};
|
||||||
|
|
||||||
/* Our state machine that implements "one word" movement or erasure. */
|
/* Our state machine that implements "one word" movement or erasure. */
|
||||||
class move_word_state_machine_t
|
class move_word_state_machine_t
|
||||||
{
|
{
|
||||||
enum
|
private:
|
||||||
{
|
|
||||||
s_whitespace,
|
bool consume_char_punctuation(wchar_t c);
|
||||||
s_separator,
|
bool consume_char_path_components(wchar_t c);
|
||||||
s_slash,
|
bool is_path_component_character(wchar_t c);
|
||||||
s_nonseparators_except_slash,
|
|
||||||
s_end
|
int state;
|
||||||
} state;
|
move_word_style_t style;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
move_word_state_machine_t();
|
|
||||||
|
move_word_state_machine_t(move_word_style_t st);
|
||||||
bool consume_char(wchar_t c);
|
bool consume_char(wchar_t c);
|
||||||
|
void reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue