mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-25 12:23:09 +00:00
forward-word should accept a word of an autosuggestion
https://github.com/fish-shell/fish-shell/issues/435
This commit is contained in:
parent
983bc5cecc
commit
eec6db0a23
6 changed files with 136 additions and 105 deletions
|
@ -34,9 +34,10 @@ struct autoload_function_t : public lru_node_t
|
||||||
bool is_internalized; /** Whether this function came from a builtin "internalized" script */
|
bool is_internalized; /** Whether this function came from a builtin "internalized" script */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct builtin_script_t {
|
struct builtin_script_t
|
||||||
const wchar_t *name;
|
{
|
||||||
const char *def;
|
const wchar_t *name;
|
||||||
|
const char *def;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct builtin_script_t;
|
struct builtin_script_t;
|
||||||
|
|
|
@ -461,7 +461,7 @@ static void builtin_bind_function_names()
|
||||||
/**
|
/**
|
||||||
Add specified key binding.
|
Add specified key binding.
|
||||||
*/
|
*/
|
||||||
static int builtin_bind_add(wchar_t *seq, wchar_t *cmd, int terminfo)
|
static int builtin_bind_add(const wchar_t *seq, const wchar_t *cmd, int terminfo)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (terminfo)
|
if (terminfo)
|
||||||
|
|
127
reader.cpp
127
reader.cpp
|
@ -1283,13 +1283,29 @@ static void update_autosuggestion(void)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void accept_autosuggestion(void)
|
/* Accept any autosuggestion by replacing the command line with it. If full is true, take the whole thing; if it's false, then take only the first "word" */
|
||||||
|
static void accept_autosuggestion(bool full)
|
||||||
{
|
{
|
||||||
/* Accept any autosuggestion by replacing the command line with it. */
|
|
||||||
if (! data->autosuggestion.empty())
|
if (! data->autosuggestion.empty())
|
||||||
{
|
{
|
||||||
/* Accept the autosuggestion */
|
/* Accept the autosuggestion */
|
||||||
data->command_line = data->autosuggestion;
|
if (full)
|
||||||
|
{
|
||||||
|
/* Just take the whole thing */
|
||||||
|
data->command_line = data->autosuggestion;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Accept characters up to a word separator */
|
||||||
|
move_word_state_machine_t state;
|
||||||
|
for (size_t idx = data->command_line.size(); idx < data->autosuggestion.size(); idx++)
|
||||||
|
{
|
||||||
|
wchar_t wc = data->autosuggestion.at(idx);
|
||||||
|
if (! state.consume_char(wc))
|
||||||
|
break;
|
||||||
|
data->command_line.push_back(wc);
|
||||||
|
}
|
||||||
|
}
|
||||||
data->buff_pos = data->command_line.size();
|
data->buff_pos = data->command_line.size();
|
||||||
data->command_line_changed();
|
data->command_line_changed();
|
||||||
reader_super_highlight_me_plenty(data->buff_pos);
|
reader_super_highlight_me_plenty(data->buff_pos);
|
||||||
|
@ -2026,98 +2042,6 @@ static void handle_token_history(int forward, int reset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Our state machine that implements "one word" movement or erasure. */
|
|
||||||
class move_word_state_machine_t
|
|
||||||
{
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
s_whitespace,
|
|
||||||
s_separator,
|
|
||||||
s_slash,
|
|
||||||
s_nonseparators_except_slash,
|
|
||||||
s_end
|
|
||||||
} state;
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
move_word_state_machine_t() : state(s_whitespace)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool consume_char(wchar_t c)
|
|
||||||
{
|
|
||||||
//printf("state %d, consume '%lc'\n", state, c);
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
switch (state)
|
|
||||||
{
|
|
||||||
case s_whitespace:
|
|
||||||
if (iswspace(c))
|
|
||||||
{
|
|
||||||
/* Consumed whitespace */
|
|
||||||
consumed = true;
|
|
||||||
}
|
|
||||||
else if (tok_is_string_character(c, was_first))
|
|
||||||
{
|
|
||||||
/* String path */
|
|
||||||
state = s_slash;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Separator path */
|
|
||||||
state = s_separator;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case s_separator:
|
|
||||||
if (! iswspace(c) && ! tok_is_string_character(c, was_first))
|
|
||||||
{
|
|
||||||
/* Consumed separator */
|
|
||||||
consumed = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state = s_end;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case s_slash:
|
|
||||||
if (c == L'/')
|
|
||||||
{
|
|
||||||
/* Consumed slash */
|
|
||||||
consumed = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state = s_nonseparators_except_slash;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case s_nonseparators_except_slash:
|
|
||||||
if (c != L'/' && tok_is_string_character(c, was_first))
|
|
||||||
{
|
|
||||||
/* Consumed string character except slash */
|
|
||||||
consumed = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
state = s_end;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
/* We won't get here, but keep the compiler happy */
|
|
||||||
case s_end:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return consumed;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Move buffer position one word or erase one word. This function
|
Move buffer position one word or erase one word. This function
|
||||||
updates both the internal buffer and the screen. It is used by
|
updates both the internal buffer and the screen. It is used by
|
||||||
|
@ -3338,7 +3262,7 @@ const wchar_t *reader_readline()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
accept_autosuggestion();
|
accept_autosuggestion(true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -3367,7 +3291,14 @@ const wchar_t *reader_readline()
|
||||||
/* move one word right*/
|
/* move one word right*/
|
||||||
case R_FORWARD_WORD:
|
case R_FORWARD_WORD:
|
||||||
{
|
{
|
||||||
move_word(MOVE_DIR_RIGHT, false /* do not erase */, false);
|
if (data->buff_pos < data->command_length())
|
||||||
|
{
|
||||||
|
move_word(MOVE_DIR_RIGHT, false /* do not erase */, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
accept_autosuggestion(false /* accept only one word */);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3440,7 +3371,7 @@ const wchar_t *reader_readline()
|
||||||
|
|
||||||
case R_ACCEPT_AUTOSUGGESTION:
|
case R_ACCEPT_AUTOSUGGESTION:
|
||||||
{
|
{
|
||||||
accept_autosuggestion();
|
accept_autosuggestion(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -566,9 +566,10 @@ static void s_move(screen_t *s, data_buffer_t *b, int new_x, int new_y)
|
||||||
{
|
{
|
||||||
if (s->actual.cursor.x == new_x && s->actual.cursor.y == new_y)
|
if (s->actual.cursor.x == new_x && s->actual.cursor.y == new_y)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// If we are at the end of our window, then either the cursor stuck to the edge or it didn't. We don't know! We can fix it up though.
|
// If we are at the end of our window, then either the cursor stuck to the edge or it didn't. We don't know! We can fix it up though.
|
||||||
if (s->actual.cursor.x == common_get_width()) {
|
if (s->actual.cursor.x == common_get_width())
|
||||||
|
{
|
||||||
// Either issue a cr to go back to the beginning of this line, or a nl to go to the beginning of the next one, depending on what we think is more efficient
|
// Either issue a cr to go back to the beginning of this line, or a nl to go to the beginning of the next one, depending on what we think is more efficient
|
||||||
if (new_y <= s->actual.cursor.y)
|
if (new_y <= s->actual.cursor.y)
|
||||||
{
|
{
|
||||||
|
@ -887,7 +888,8 @@ static void s_update(screen_t *scr, const wchar_t *left_prompt, const wchar_t *r
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we're soft wrapped, and if we're going to change the first character of the next line, don't skip over the last two characters so that we maintain soft-wrapping */
|
/* If we're soft wrapped, and if we're going to change the first character of the next line, don't skip over the last two characters so that we maintain soft-wrapping */
|
||||||
if (o_line.is_soft_wrapped && i + 1 < scr->desired.line_count()) {
|
if (o_line.is_soft_wrapped && i + 1 < scr->desired.line_count())
|
||||||
|
{
|
||||||
bool first_character_of_next_line_will_change = true;
|
bool first_character_of_next_line_will_change = true;
|
||||||
if (i + 1 < scr->actual.line_count())
|
if (i + 1 < scr->actual.line_count())
|
||||||
{
|
{
|
||||||
|
|
|
@ -669,6 +669,85 @@ void tok_set_pos(tokenizer_t *tok, int pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
move_word_state_machine_t::move_word_state_machine_t() : state(s_whitespace)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool move_word_state_machine_t::consume_char(wchar_t c)
|
||||||
|
{
|
||||||
|
//printf("state %d, consume '%lc'\n", state, c);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case s_whitespace:
|
||||||
|
if (iswspace(c))
|
||||||
|
{
|
||||||
|
/* Consumed whitespace */
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
else if (tok_is_string_character(c, was_first))
|
||||||
|
{
|
||||||
|
/* String path */
|
||||||
|
state = s_slash;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Separator path */
|
||||||
|
state = s_separator;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case s_separator:
|
||||||
|
if (! iswspace(c) && ! tok_is_string_character(c, was_first))
|
||||||
|
{
|
||||||
|
/* Consumed separator */
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = s_end;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case s_slash:
|
||||||
|
if (c == L'/')
|
||||||
|
{
|
||||||
|
/* Consumed slash */
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = s_nonseparators_except_slash;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case s_nonseparators_except_slash:
|
||||||
|
if (c != L'/' && tok_is_string_character(c, was_first))
|
||||||
|
{
|
||||||
|
/* Consumed string character except slash */
|
||||||
|
consumed = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = s_end;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* We won't get here, but keep the compiler happy */
|
||||||
|
case s_end:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef TOKENIZER_TEST
|
#ifdef TOKENIZER_TEST
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
18
tokenizer.h
18
tokenizer.h
|
@ -184,4 +184,22 @@ const wchar_t *tok_get_desc(int type);
|
||||||
int tok_get_error(tokenizer_t *tok);
|
int tok_get_error(tokenizer_t *tok);
|
||||||
|
|
||||||
|
|
||||||
|
/* Our state machine that implements "one word" movement or erasure. */
|
||||||
|
class move_word_state_machine_t
|
||||||
|
{
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
s_whitespace,
|
||||||
|
s_separator,
|
||||||
|
s_slash,
|
||||||
|
s_nonseparators_except_slash,
|
||||||
|
s_end
|
||||||
|
} state;
|
||||||
|
|
||||||
|
public:
|
||||||
|
move_word_state_machine_t();
|
||||||
|
bool consume_char(wchar_t c);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue