Refactor reader to work on selectable 'editable_line_t' so that most

commands can operate on pager search field
This commit is contained in:
ridiculousfish 2014-01-26 14:47:15 -08:00
parent f58dff62c7
commit b9372944f5
2 changed files with 208 additions and 167 deletions

View file

@ -181,7 +181,13 @@ static volatile unsigned int s_generation_count;
/* This pthreads generation count is set when an autosuggestion background thread starts up, so it can easily check if the work it is doing is no longer useful. */ /* This pthreads generation count is set when an autosuggestion background thread starts up, so it can easily check if the work it is doing is no longer useful. */
static pthread_key_t generation_count_key; static pthread_key_t generation_count_key;
static void set_command_line_and_position(const wcstring &new_str, size_t pos); static void set_command_line_and_position(editable_line_t *el, const wcstring &new_str, size_t pos);
void editable_line_t::insert_string(const wcstring &str)
{
this->text.insert(this->position, str);
this->position += str.size();
}
/** /**
A struct describing the state of the interactive reader. These A struct describing the state of the interactive reader. These
@ -254,15 +260,10 @@ public:
return command_line.size(); return command_line.size();
} }
const wcstring &command_text() const /* The line that is currently being edited. Typically the command line, but may be the search field */
editable_line_t *active_edit_line()
{ {
return command_line.get_text(); return &command_line;
}
/** The current position of the cursor in buff. */
size_t buff_pos() const
{
return command_line.position;
} }
/** Do what we need to do whenever our command line changes */ /** Do what we need to do whenever our command line changes */
@ -418,7 +419,7 @@ static struct termios terminal_mode_on_startup;
static struct termios terminal_mode_for_executing_programs; static struct termios terminal_mode_for_executing_programs;
static void reader_super_highlight_me_plenty(size_t pos); static void reader_super_highlight_me_plenty(int highlight_pos_adjust = 0);
/** /**
Variable to keep track of forced exits - see \c reader_exit_forced(); Variable to keep track of forced exits - see \c reader_exit_forced();
@ -536,11 +537,12 @@ wcstring combine_command_and_autosuggestion(const wcstring &cmdline, const wcstr
static void reader_repaint() static void reader_repaint()
{ {
editable_line_t *el = &data->command_line;
// Update the indentation // Update the indentation
data->indents = parse_util_compute_indents(data->command_text()); data->indents = parse_util_compute_indents(el->text);
// Combine the command and autosuggestion into one string // Combine the command and autosuggestion into one string
wcstring full_line = combine_command_and_autosuggestion(data->command_text(), data->autosuggestion); wcstring full_line = combine_command_and_autosuggestion(el->text, data->autosuggestion);
size_t len = full_line.size(); size_t len = full_line.size();
if (len < 1) if (len < 1)
@ -564,7 +566,7 @@ static void reader_repaint()
data->command_length(), data->command_length(),
&colors[0], &colors[0],
&indents[0], &indents[0],
data->buff_pos(), el->position,
data->current_page_rendering); data->current_page_rendering);
data->repaint_needed = false; data->repaint_needed = false;
@ -582,7 +584,8 @@ static void reader_repaint_without_autosuggestion()
/** Internal helper function for handling killing parts of text. */ /** Internal helper function for handling killing parts of text. */
static void reader_kill(size_t begin_idx, size_t length, int mode, int newv) static void reader_kill(size_t begin_idx, size_t length, int mode, int newv)
{ {
const wchar_t *begin = data->command_text().c_str() + begin_idx; editable_line_t *el = data->active_edit_line();
const wchar_t *begin = el->text.c_str() + begin_idx;
if (newv) if (newv)
{ {
data->kill_item = wcstring(begin, length); data->kill_item = wcstring(begin, length);
@ -605,17 +608,17 @@ static void reader_kill(size_t begin_idx, size_t length, int mode, int newv)
kill_replace(old, data->kill_item); kill_replace(old, data->kill_item);
} }
if (data->buff_pos() > begin_idx) if (el->position > begin_idx)
{ {
/* Move the buff position back by the number of characters we deleted, but don't go past buff_pos */ /* Move the buff position back by the number of characters we deleted, but don't go past buff_pos */
size_t backtrack = mini(data->command_line.position - begin_idx, length); size_t backtrack = mini(el->position - begin_idx, length);
data->command_line.position -= backtrack; el->position -= backtrack;
} }
data->command_line.text.erase(begin_idx, length); el->text.erase(begin_idx, length);
data->command_line_changed(); data->command_line_changed();
reader_super_highlight_me_plenty(data->buff_pos()); reader_super_highlight_me_plenty();
reader_repaint(); reader_repaint();
} }
@ -743,18 +746,19 @@ bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t curso
bool reader_data_t::expand_abbreviation_as_necessary(size_t cursor_backtrack) bool reader_data_t::expand_abbreviation_as_necessary(size_t cursor_backtrack)
{ {
bool result = false; bool result = false;
if (this->expand_abbreviations) editable_line_t *el = data->active_edit_line();
if (this->expand_abbreviations && el == &data->command_line)
{ {
/* Try expanding abbreviations */ /* Try expanding abbreviations */
wcstring new_cmdline; wcstring new_cmdline;
size_t cursor_pos = this->buff_pos() - mini(this->buff_pos(), cursor_backtrack); size_t cursor_pos = el->position - mini(el->position, cursor_backtrack);
if (reader_expand_abbreviation_in_command(this->command_text(), cursor_pos, &new_cmdline)) if (reader_expand_abbreviation_in_command(el->text, cursor_pos, &new_cmdline))
{ {
/* We expanded an abbreviation! The cursor moves by the difference in the command line lengths. */ /* 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(); size_t new_buff_pos = el->position + new_cmdline.size() - el->text.size();
this->command_line.text.swap(new_cmdline); el->text.swap(new_cmdline);
this->command_line.position = new_buff_pos; el->position = new_buff_pos;
data->command_line_changed(); data->command_line_changed();
result = true; result = true;
} }
@ -1033,23 +1037,24 @@ void reader_react_to_color_change()
*/ */
static void remove_backward() static void remove_backward()
{ {
editable_line_t *el = data->active_edit_line();
if (data->buff_pos() <= 0) if (el->position <= 0)
return; return;
/* Fake composed character sequences by continuing to delete until we delete a character of width at least 1. */ /* Fake composed character sequences by continuing to delete until we delete a character of width at least 1. */
int width; int width;
do do
{ {
data->command_line.position -= 1; el->position -= 1;
width = fish_wcwidth(data->command_line.text.at(data->command_line.position)); width = fish_wcwidth(el->text.at(el->position));
data->command_line.text.erase(data->command_line.position, 1); el->text.erase(el->position, 1);
} }
while (width == 0 && data->command_line.position > 0); while (width == 0 && el->position > 0);
data->command_line_changed(); data->command_line_changed();
data->suppress_autosuggestion = true; data->suppress_autosuggestion = true;
reader_super_highlight_me_plenty(data->buff_pos()); reader_super_highlight_me_plenty();
reader_repaint(); reader_repaint();
@ -1068,17 +1073,22 @@ static bool insert_string(const wcstring &str, bool should_expand_abbreviations
if (len == 0) if (len == 0)
return false; return false;
data->command_line.text.insert(data->buff_pos(), str); editable_line_t *el = data->active_edit_line();
data->command_line.position += len; el->insert_string(str);
data->command_line_changed(); data->command_line_changed();
data->suppress_autosuggestion = false;
if (should_expand_abbreviations) if (el == &data->command_line)
data->expand_abbreviation_as_necessary(1); {
data->suppress_autosuggestion = false;
/* Syntax highlight. Note we must have that buff_pos > 0 because we just added something nonzero to its length */ if (should_expand_abbreviations)
assert(data->buff_pos() > 0); data->expand_abbreviation_as_necessary(1);
reader_super_highlight_me_plenty(data->buff_pos()-1);
/* Syntax highlight. Note we must have that buff_pos > 0 because we just added something nonzero to its length */
assert(el->position > 0);
reader_super_highlight_me_plenty(-1);
}
reader_repaint(); reader_repaint();
return true; return true;
@ -1219,8 +1229,9 @@ wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flag
*/ */
static void completion_insert(const wchar_t *val, complete_flags_t flags) static void completion_insert(const wchar_t *val, complete_flags_t flags)
{ {
size_t cursor = data->buff_pos(); editable_line_t *el = data->active_edit_line();
wcstring new_command_line = completion_apply_to_command_line(val, flags, data->command_text(), &cursor, false /* not append only */); size_t cursor = el->position;
wcstring new_command_line = completion_apply_to_command_line(val, flags, el->text, &cursor, false /* not append only */);
reader_set_buffer_maintaining_pager(new_command_line, cursor); reader_set_buffer_maintaining_pager(new_command_line, cursor);
/* Since we just inserted a completion, don't immediately do a new autosuggestion */ /* Since we just inserted a completion, don't immediately do a new autosuggestion */
@ -1287,37 +1298,39 @@ static void run_pager(const wcstring &prefix, int is_quoted, const std::vector<c
escaped_separator = escape(COMPLETE_SEP_STR, 1); escaped_separator = escape(COMPLETE_SEP_STR, 1);
editable_line_t *el = &data->command_line;
for (size_t i=0; i< comp.size(); i++) for (size_t i=0; i< comp.size(); i++)
{ {
long base_len=-1; long base_len=-1;
const completion_t &el = comp.at(i); const completion_t &cmpl = comp.at(i);
wcstring completion_text; wcstring completion_text;
wcstring description_text; wcstring description_text;
// Note that an empty completion is perfectly sensible here, e.g. tab-completing 'foo' with a file called 'foo' and another called 'foobar' // Note that an empty completion is perfectly sensible here, e.g. tab-completing 'foo' with a file called 'foo' and another called 'foobar'
if ((el.flags & COMPLETE_REPLACES_TOKEN) && match_type_shares_prefix(el.match.type)) if ((cmpl.flags & COMPLETE_REPLACES_TOKEN) && match_type_shares_prefix(cmpl.match.type))
{ {
// Compute base_len if we have not yet // Compute base_len if we have not yet
if (base_len == -1) if (base_len == -1)
{ {
const wchar_t *begin, *buff = data->command_text().c_str(); const wchar_t *begin, *buff = el->text.c_str();
parse_util_token_extent(buff, data->buff_pos(), &begin, 0, 0, 0); parse_util_token_extent(buff, el->position, &begin, 0, 0, 0);
base_len = data->buff_pos() - (begin-buff); base_len = el->position - (begin-buff);
} }
completion_text = escape_string(el.completion.c_str() + base_len, ESCAPE_ALL | ESCAPE_NO_QUOTED); completion_text = escape_string(cmpl.completion.c_str() + base_len, ESCAPE_ALL | ESCAPE_NO_QUOTED);
} }
else else
{ {
completion_text = escape_string(el.completion, ESCAPE_ALL | ESCAPE_NO_QUOTED); completion_text = escape_string(cmpl.completion, ESCAPE_ALL | ESCAPE_NO_QUOTED);
} }
if (! el.description.empty()) if (! cmpl.description.empty())
{ {
description_text = escape_string(el.description, true); description_text = escape_string(cmpl.description, true);
} }
/* It's possible (even common) to have an empty completion with no description. An example would be completing 'foo' with extant files 'foo' and 'foobar'. But fish_pager ignores blank lines. So if our completion text is empty, always include a description, even if it's empty. /* It's possible (even common) to have an empty completion with no description. An example would be completing 'foo' with extant files 'foo' and 'foobar'. But fish_pager ignores blank lines. So if our completion text is empty, always include a description, even if it's empty.
@ -1472,17 +1485,19 @@ static int threaded_autosuggest(autosuggestion_context_t *ctx)
static bool can_autosuggest(void) static bool can_autosuggest(void)
{ {
/* We autosuggest if suppress_autosuggestion is not set, if we're not doing a history search, and our command line contains a non-whitespace character. */ /* We autosuggest if suppress_autosuggestion is not set, if we're not doing a history search, and our command line contains a non-whitespace character. */
const editable_line_t *el = data->active_edit_line();
const wchar_t *whitespace = L" \t\r\n\v"; const wchar_t *whitespace = L" \t\r\n\v";
return ! data->suppress_autosuggestion && return ! data->suppress_autosuggestion &&
data->history_search.is_at_end() && data->history_search.is_at_end() &&
data->command_text().find_first_not_of(whitespace) != wcstring::npos; el == &data->command_line &&
el->text.find_first_not_of(whitespace) != wcstring::npos;
} }
static void autosuggest_completed(autosuggestion_context_t *ctx, int result) static void autosuggest_completed(autosuggestion_context_t *ctx, int result)
{ {
if (result && if (result &&
can_autosuggest() && can_autosuggest() &&
ctx->search_string == data->command_text() && ctx->search_string == data->command_line.text &&
string_prefixes_string_case_insensitive(ctx->search_string, ctx->autosuggestion)) string_prefixes_string_case_insensitive(ctx->search_string, ctx->autosuggestion))
{ {
/* Autosuggestion is active and the search term has not changed, so we're good to go */ /* Autosuggestion is active and the search term has not changed, so we're good to go */
@ -1500,7 +1515,8 @@ static void update_autosuggestion(void)
data->autosuggestion.clear(); data->autosuggestion.clear();
if (data->allow_autosuggestion && ! data->suppress_autosuggestion && ! data->command_line.empty() && data->history_search.is_at_end()) if (data->allow_autosuggestion && ! data->suppress_autosuggestion && ! data->command_line.empty() && data->history_search.is_at_end())
{ {
autosuggestion_context_t *ctx = new autosuggestion_context_t(data->history, data->command_text(), data->buff_pos()); const editable_line_t *el = data->active_edit_line();
autosuggestion_context_t *ctx = new autosuggestion_context_t(data->history, el->text, el->position);
iothread_perform(threaded_autosuggest, autosuggest_completed, ctx); iothread_perform(threaded_autosuggest, autosuggest_completed, ctx);
} }
} }
@ -1530,7 +1546,7 @@ static void accept_autosuggestion(bool full)
} }
data->command_line.position = data->command_line.size(); data->command_line.position = 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();
reader_repaint(); reader_repaint();
} }
} }
@ -1587,7 +1603,8 @@ static void reader_flash()
{ {
struct timespec pollint; struct timespec pollint;
for (size_t i=0; i<data->buff_pos(); i++) editable_line_t *el = &data->command_line;
for (size_t i=0; i<el->position; i++)
{ {
data->colors.at(i) = highlight_spec_search_match<<16; data->colors.at(i) = highlight_spec_search_match<<16;
} }
@ -1598,7 +1615,7 @@ static void reader_flash()
pollint.tv_nsec = 100 * 1000000; pollint.tv_nsec = 100 * 1000000;
nanosleep(&pollint, NULL); nanosleep(&pollint, NULL);
reader_super_highlight_me_plenty(data->buff_pos()); reader_super_highlight_me_plenty();
reader_repaint(); reader_repaint();
} }
@ -1720,10 +1737,11 @@ static bool handle_completions(const std::vector<completion_t> &comp)
{ {
bool done = false; bool done = false;
bool success = false; bool success = false;
const wchar_t *begin, *end, *buff = data->command_text().c_str(); const editable_line_t *el = &data->command_line;
const wchar_t *begin, *end, *buff = el->text.c_str();
parse_util_token_extent(buff, data->buff_pos(), &begin, 0, 0, 0); parse_util_token_extent(buff, el->position, &begin, 0, 0, 0);
end = buff+data->buff_pos(); end = buff+el->position;
const wcstring tok(begin, end - begin); const wcstring tok(begin, end - begin);
@ -1857,10 +1875,10 @@ static bool handle_completions(const std::vector<completion_t> &comp)
/* We didn't get a common prefix. Print the list. */ /* We didn't get a common prefix. Print the list. */
size_t len, prefix_start = 0; size_t len, prefix_start = 0;
wcstring prefix; wcstring prefix;
parse_util_get_parameter_info(data->command_text(), data->buff_pos(), NULL, &prefix_start, NULL); parse_util_get_parameter_info(el->text, el->position, NULL, &prefix_start, NULL);
assert(data->buff_pos() >= prefix_start); assert(el->position >= prefix_start);
len = data->buff_pos() - prefix_start; len = el->position - prefix_start;
if (match_type_requires_full_replacement(best_match_type)) if (match_type_requires_full_replacement(best_match_type))
{ {
@ -1869,17 +1887,17 @@ static bool handle_completions(const std::vector<completion_t> &comp)
} }
else if (len <= PREFIX_MAX_LEN) else if (len <= PREFIX_MAX_LEN)
{ {
prefix.append(data->command_text(), prefix_start, len); prefix.append(el->text, prefix_start, len);
} }
else else
{ {
// append just the end of the string // append just the end of the string
prefix = wcstring(&ellipsis_char, 1); prefix = wcstring(&ellipsis_char, 1);
prefix.append(data->command_text(), prefix_start + len - PREFIX_MAX_LEN, PREFIX_MAX_LEN); prefix.append(el->text, prefix_start + len - PREFIX_MAX_LEN, PREFIX_MAX_LEN);
} }
wchar_t quote; wchar_t quote;
parse_util_get_parameter_info(data->command_text(), data->buff_pos(), &quote, NULL, NULL); parse_util_get_parameter_info(el->text, el->position, &quote, NULL, NULL);
bool is_quoted = (quote != L'\0'); bool is_quoted = (quote != L'\0');
if (pager_use_inline()) if (pager_use_inline())
@ -2113,7 +2131,7 @@ void reader_sanity_check()
/* Note: 'data' is non-null if we are interactive, except in the testing environment */ /* Note: 'data' is non-null if we are interactive, except in the testing environment */
if (get_is_interactive() && data != NULL) if (get_is_interactive() && data != NULL)
{ {
if (!(data->buff_pos() <= data->command_length())) if (data->command_line.position > data->command_line.size())
sanity_lose(); sanity_lose();
if (data->colors.size() != data->command_length()) if (data->colors.size() != data->command_length())
@ -2128,12 +2146,12 @@ void reader_sanity_check()
/** /**
Set the specified string as the current buffer. Set the specified string as the current buffer.
*/ */
static void set_command_line_and_position(const wcstring &new_str, size_t pos) static void set_command_line_and_position(editable_line_t *el, const wcstring &new_str, size_t pos)
{ {
data->command_line.text = new_str; el->text = new_str;
data->command_line.position = pos; el->position = pos;
data->command_line_changed(); data->command_line_changed();
reader_super_highlight_me_plenty(data->buff_pos()); reader_super_highlight_me_plenty();
reader_repaint(); reader_repaint();
} }
@ -2144,8 +2162,9 @@ static void reader_replace_current_token(const wchar_t *new_token)
size_t new_pos; size_t new_pos;
/* Find current token */ /* Find current token */
const wchar_t *buff = data->command_text().c_str(); editable_line_t *el = data->active_edit_line();
parse_util_token_extent(buff, data->buff_pos(), &begin, &end, 0, 0); const wchar_t *buff = el->text.c_str();
parse_util_token_extent(buff, el->position, &begin, &end, 0, 0);
if (!begin || !end) if (!begin || !end)
return; return;
@ -2156,7 +2175,7 @@ static void reader_replace_current_token(const wchar_t *new_token)
new_buff.append(end); new_buff.append(end);
new_pos = (begin-buff) + wcslen(new_token); new_pos = (begin-buff) + wcslen(new_token);
set_command_line_and_position(new_buff, new_pos); set_command_line_and_position(el, new_buff, new_pos);
} }
@ -2165,9 +2184,10 @@ static void reader_replace_current_token(const wchar_t *new_token)
*/ */
static void reset_token_history() static void reset_token_history()
{ {
const editable_line_t *el = data->active_edit_line();
const wchar_t *begin, *end; const wchar_t *begin, *end;
const wchar_t *buff = data->command_text().c_str(); const wchar_t *buff = el->text.c_str();
parse_util_token_extent((wchar_t *)buff, data->buff_pos(), &begin, &end, 0, 0); parse_util_token_extent((wchar_t *)buff, el->position, &begin, &end, 0, 0);
data->search_buff.clear(); data->search_buff.clear();
if (begin) if (begin)
@ -2228,7 +2248,7 @@ static void handle_token_history(int forward, int reset)
} }
reader_replace_current_token(str); reader_replace_current_token(str);
reader_super_highlight_me_plenty(data->buff_pos()); reader_super_highlight_me_plenty();
reader_repaint(); reader_repaint();
} }
else else
@ -2309,7 +2329,7 @@ static void handle_token_history(int forward, int reset)
if (str) if (str)
{ {
reader_replace_current_token(str); reader_replace_current_token(str);
reader_super_highlight_me_plenty(data->buff_pos()); reader_super_highlight_me_plenty();
reader_repaint(); reader_repaint();
data->search_pos = data->search_prev.size(); data->search_pos = data->search_prev.size();
data->search_prev.push_back(str); data->search_prev.push_back(str);
@ -2337,19 +2357,19 @@ enum move_word_dir_t
MOVE_DIR_RIGHT MOVE_DIR_RIGHT
}; };
static void move_word(bool move_right, bool erase, enum move_word_style_t style, bool newv) static void move_word(editable_line_t *el, 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;
if (data->buff_pos() == boundary) if (el->position == boundary)
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(style); move_word_state_machine_t state(style);
const wchar_t * const command_line = data->command_text().c_str(); const wchar_t * const command_line = el->text.c_str();
const size_t start_buff_pos = data->buff_pos(); const size_t start_buff_pos = el->position;
size_t buff_pos = data->buff_pos(); size_t buff_pos = el->position;
while (buff_pos != boundary) while (buff_pos != boundary)
{ {
size_t idx = (move_right ? buff_pos : buff_pos - 1); size_t idx = (move_right ? buff_pos : buff_pos - 1);
@ -2389,7 +2409,7 @@ static void move_word(bool move_right, bool erase, enum move_word_style_t style,
const wchar_t *reader_get_buffer(void) const wchar_t *reader_get_buffer(void)
{ {
ASSERT_IS_MAIN_THREAD(); ASSERT_IS_MAIN_THREAD();
return data ? data->command_text().c_str():NULL; return data ? data->command_line.text.c_str() : NULL;
} }
history_t *reader_get_history(void) history_t *reader_get_history(void)
@ -2417,7 +2437,7 @@ static void reader_set_buffer_maintaining_pager(const wcstring &b, size_t pos)
data->search_buff.clear(); data->search_buff.clear();
data->history_search.go_to_end(); data->history_search.go_to_end();
reader_super_highlight_me_plenty(data->buff_pos()); reader_super_highlight_me_plenty();
reader_repaint_needed(); reader_repaint_needed();
} }
@ -2437,7 +2457,7 @@ size_t reader_get_cursor_pos()
if (!data) if (!data)
return (size_t)(-1); return (size_t)(-1);
return data->buff_pos(); return data->command_line.position;
} }
#define ENV_CMD_DURATION L"CMD_DURATION" #define ENV_CMD_DURATION L"CMD_DURATION"
@ -2720,8 +2740,9 @@ static void highlight_search(void)
{ {
if (! data->search_buff.empty() && ! data->history_search.is_at_end()) if (! data->search_buff.empty() && ! data->history_search.is_at_end())
{ {
const editable_line_t *el = &data->command_line;
const wcstring &needle = data->search_buff; const wcstring &needle = data->search_buff;
size_t match_pos = data->command_text().find(needle); size_t match_pos = el->text.find(needle);
if (match_pos != wcstring::npos) if (match_pos != wcstring::npos)
{ {
size_t end = match_pos + needle.size(); size_t end = match_pos + needle.size();
@ -2765,19 +2786,23 @@ static int threaded_highlight(background_highlight_context_t *ctx)
to avoid repaint issues on terminals where e.g. syntax highlighting to avoid repaint issues on terminals where e.g. syntax highlighting
maykes characters under the sursor unreadable. maykes characters under the sursor unreadable.
\param match_highlight_pos the position to use for bracket matching. This need not be the same as the surrent cursor position \param match_highlight_pos_adjust the adjustment to the position to use for bracket matching. This is added to the current cursor position and may be negative.
\param error if non-null, any possible errors in the buffer are further descibed by the strings inserted into the specified arraylist \param error if non-null, any possible errors in the buffer are further descibed by the strings inserted into the specified arraylist
*/ */
static void reader_super_highlight_me_plenty(size_t match_highlight_pos) static void reader_super_highlight_me_plenty(int match_highlight_pos_adjust)
{ {
const editable_line_t *el = &data->command_line;
long match_highlight_pos = (long)el->position + match_highlight_pos_adjust;
assert(match_highlight_pos >= 0);
reader_sanity_check(); reader_sanity_check();
background_highlight_context_t *ctx = new background_highlight_context_t(data->command_text(), match_highlight_pos, data->highlight_function); background_highlight_context_t *ctx = new background_highlight_context_t(el->text, match_highlight_pos, data->highlight_function);
iothread_perform(threaded_highlight, highlight_complete, ctx); iothread_perform(threaded_highlight, highlight_complete, ctx);
highlight_search(); highlight_search();
/* Here's a hack. Check to see if our autosuggestion still applies; if so, don't recompute it. Since the autosuggestion computation is asynchronous, this avoids "flashing" as you type into the autosuggestion. */ /* Here's a hack. Check to see if our autosuggestion still applies; if so, don't recompute it. Since the autosuggestion computation is asynchronous, this avoids "flashing" as you type into the autosuggestion. */
const wcstring &cmd = data->command_text(), &suggest = data->autosuggestion; const wcstring &cmd = el->text, &suggest = data->autosuggestion;
if (can_autosuggest() && ! suggest.empty() && string_prefixes_string_case_insensitive(cmd, suggest)) if (can_autosuggest() && ! suggest.empty() && string_prefixes_string_case_insensitive(cmd, suggest))
{ {
/* The autosuggestion is still reasonable, so do nothing */ /* The autosuggestion is still reasonable, so do nothing */
@ -3014,7 +3039,7 @@ const wchar_t *reader_readline(void)
exec_prompt(); exec_prompt();
reader_super_highlight_me_plenty(data->buff_pos()); reader_super_highlight_me_plenty();
s_reset(&data->screen, screen_reset_abandon_line); s_reset(&data->screen, screen_reset_abandon_line);
reader_repaint(); reader_repaint();
@ -3110,17 +3135,16 @@ const wchar_t *reader_readline(void)
//fprintf(stderr, "\n\nchar: %ls\n\n", describe_char(c).c_str()); //fprintf(stderr, "\n\nchar: %ls\n\n", describe_char(c).c_str());
const wchar_t * const buff = data->command_text().c_str();
switch (c) switch (c)
{ {
/* go to beginning of line*/ /* go to beginning of line*/
case R_BEGINNING_OF_LINE: case R_BEGINNING_OF_LINE:
{ {
while ((data->buff_pos()>0) && editable_line_t *el = data->active_edit_line();
(buff[data->buff_pos()-1] != L'\n')) while (el->position > 0 && el->text.at(el->position - 1) != L'\n')
{ {
data->command_line.position--; el->position--;
} }
reader_repaint_needed(); reader_repaint_needed();
@ -3129,12 +3153,14 @@ const wchar_t *reader_readline(void)
case R_END_OF_LINE: case R_END_OF_LINE:
{ {
if (data->buff_pos() < data->command_length()) editable_line_t *el = data->active_edit_line();
if (el->position < el->size())
{ {
while (buff[data->buff_pos()] && const wchar_t *buff = el->text.c_str();
buff[data->buff_pos()] != L'\n') while (buff[el->position] &&
buff[el->position] != L'\n')
{ {
data->command_line.position++; el->position++;
} }
} }
else else
@ -3196,6 +3222,8 @@ const wchar_t *reader_readline(void)
if (!data->complete_func) if (!data->complete_func)
break; break;
/* Use the command line only; it doesn't make sense to complete in any other line */
editable_line_t *el = &data->command_line;
if (is_navigating_pager_contents() || (! comp_empty && last_char == R_COMPLETE)) if (is_navigating_pager_contents() || (! comp_empty && last_char == R_COMPLETE))
{ {
/* The user typed R_COMPLETE more than once in a row. Cycle through our available completions. */ /* The user typed R_COMPLETE more than once in a row. Cycle through our available completions. */
@ -3206,24 +3234,24 @@ const wchar_t *reader_readline(void)
/* Either the user hit tab only once, or we had no visible completion list. */ /* Either the user hit tab only once, or we had no visible completion list. */
/* Remove a trailing backslash. This may trigger an extra repaint, but this is rare. */ /* Remove a trailing backslash. This may trigger an extra repaint, but this is rare. */
if (is_backslashed(data->command_text(), data->buff_pos())) if (is_backslashed(el->text, el->position))
{ {
remove_backward(); remove_backward();
} }
/* Get the string; we have to do this after removing any trailing backslash */ /* Get the string; we have to do this after removing any trailing backslash */
const wchar_t * const buff = data->command_text().c_str(); const wchar_t * const buff = el->text.c_str();
/* Clear the completion list */ /* Clear the completion list */
comp.clear(); comp.clear();
/* Figure out the extent of the command substitution surrounding the cursor. This is because we only look at the current command substitution to form completions - stuff happening outside of it is not interesting. */ /* Figure out the extent of the command substitution surrounding the cursor. This is because we only look at the current command substitution to form completions - stuff happening outside of it is not interesting. */
const wchar_t *cmdsub_begin, *cmdsub_end; const wchar_t *cmdsub_begin, *cmdsub_end;
parse_util_cmdsubst_extent(buff, data->buff_pos(), &cmdsub_begin, &cmdsub_end); parse_util_cmdsubst_extent(buff, el->position, &cmdsub_begin, &cmdsub_end);
/* Figure out the extent of the token within the command substitution. Note we pass cmdsub_begin here, not buff */ /* Figure out the extent of the token within the command substitution. Note we pass cmdsub_begin here, not buff */
const wchar_t *token_begin, *token_end; const wchar_t *token_begin, *token_end;
parse_util_token_extent(cmdsub_begin, data->buff_pos() - (cmdsub_begin-buff), &token_begin, &token_end, 0, 0); parse_util_token_extent(cmdsub_begin, el->position - (cmdsub_begin-buff), &token_begin, &token_end, 0, 0);
/* Hack: the token may extend past the end of the command substitution, e.g. in (echo foo) the last token is 'foo)'. Don't let that happen. */ /* Hack: the token may extend past the end of the command substitution, e.g. in (echo foo) the last token is 'foo)'. Don't let that happen. */
if (token_end > cmdsub_end) token_end = cmdsub_end; if (token_end > cmdsub_end) token_end = cmdsub_end;
@ -3232,9 +3260,9 @@ const wchar_t *reader_readline(void)
size_t end_of_token_offset = token_end - buff; size_t end_of_token_offset = token_end - buff;
/* Move the cursor to the end */ /* Move the cursor to the end */
if (data->buff_pos() != end_of_token_offset) if (el->position != end_of_token_offset)
{ {
data->command_line.position = end_of_token_offset; el->position = end_of_token_offset;
reader_repaint(); reader_repaint();
} }
@ -3249,8 +3277,8 @@ const wchar_t *reader_readline(void)
prioritize_completions(comp); prioritize_completions(comp);
/* Record our cycle_command_line */ /* Record our cycle_command_line */
cycle_command_line = data->command_text(); cycle_command_line = el->text;
cycle_cursor_pos = data->buff_pos(); cycle_cursor_pos = el->position;
comp_empty = handle_completions(comp); comp_empty = handle_completions(comp);
@ -3264,8 +3292,9 @@ const wchar_t *reader_readline(void)
/* kill */ /* kill */
case R_KILL_LINE: case R_KILL_LINE:
{ {
const wchar_t *buff = data->command_text().c_str(); const editable_line_t *el = data->active_edit_line();
const wchar_t *begin = &buff[data->buff_pos()]; const wchar_t *buff = el->text.c_str();
const wchar_t *begin = &buff[el->position];
const wchar_t *end = begin; const wchar_t *end = begin;
while (*end && *end != L'\n') while (*end && *end != L'\n')
@ -3285,10 +3314,11 @@ const wchar_t *reader_readline(void)
case R_BACKWARD_KILL_LINE: case R_BACKWARD_KILL_LINE:
{ {
if (data->buff_pos() > 0) const editable_line_t *el = data->active_edit_line();
if (el->position > 0)
{ {
const wchar_t *buff = data->command_text().c_str(); const wchar_t *buff = el->text.c_str();
const wchar_t *end = &buff[data->buff_pos()]; const wchar_t *end = &buff[el->position];
const wchar_t *begin = end; const wchar_t *begin = end;
/* Make sure we delete at least one character (see #580) */ /* Make sure we delete at least one character (see #580) */
@ -3314,8 +3344,9 @@ const wchar_t *reader_readline(void)
case R_KILL_WHOLE_LINE: case R_KILL_WHOLE_LINE:
{ {
const wchar_t *buff = data->command_text().c_str(); const editable_line_t *el = data->active_edit_line();
const wchar_t *end = &buff[data->buff_pos()]; const wchar_t *buff = el->text.c_str();
const wchar_t *end = &buff[el->position];
const wchar_t *begin = end; const wchar_t *begin = end;
size_t len; size_t len;
@ -3386,7 +3417,7 @@ const wchar_t *reader_readline(void)
reader_replace_current_token(data->search_buff.c_str()); reader_replace_current_token(data->search_buff.c_str());
} }
data->search_buff.clear(); data->search_buff.clear();
reader_super_highlight_me_plenty(data->buff_pos()); reader_super_highlight_me_plenty();
reader_repaint_needed(); reader_repaint_needed();
} }
@ -3407,9 +3438,10 @@ const wchar_t *reader_readline(void)
Remove the current character in the character buffer and on the Remove the current character in the character buffer and on the
screen using syntax highlighting, etc. screen using syntax highlighting, etc.
*/ */
if (data->buff_pos() < data->command_length()) editable_line_t *el = data->active_edit_line();
if (el->position < el->size())
{ {
data->command_line.position++; el->position++;
remove_backward(); remove_backward();
} }
break; break;
@ -3425,10 +3457,13 @@ const wchar_t *reader_readline(void)
/* Delete any autosuggestion */ /* Delete any autosuggestion */
data->autosuggestion.clear(); data->autosuggestion.clear();
/* We only execute the command line */
const editable_line_t *el = &data->command_line;
/* Allow backslash-escaped newlines, but only if the following character is whitespace, or we're at the end of the text (see issue #163) */ /* Allow backslash-escaped newlines, but only if the following character is whitespace, or we're at the end of the text (see issue #163) */
if (is_backslashed(data->command_text(), data->buff_pos())) if (is_backslashed(el->text, el->position))
{ {
if (data->buff_pos() >= data->command_length() || iswspace(data->command_text().at(data->buff_pos()))) if (el->position >= el->size() || iswspace(el->text.at(el->position)))
{ {
insert_char('\n'); insert_char('\n');
break; break;
@ -3436,7 +3471,7 @@ const wchar_t *reader_readline(void)
} }
/* See if this command is valid */ /* See if this command is valid */
int command_test_result = data->test_func(data->command_text().c_str()); int command_test_result = data->test_func(el->text.c_str());
if (command_test_result == 0 || command_test_result == PARSER_TEST_INCOMPLETE) if (command_test_result == 0 || command_test_result == PARSER_TEST_INCOMPLETE)
{ {
/* This command is valid, but an abbreviation may make it invalid. If so, we will have to test again. */ /* This command is valid, but an abbreviation may make it invalid. If so, we will have to test again. */
@ -3444,8 +3479,8 @@ const wchar_t *reader_readline(void)
if (abbreviation_expanded) if (abbreviation_expanded)
{ {
/* It's our reponsibility to rehighlight and repaint. But everything we do below triggers a repaint. */ /* It's our reponsibility to rehighlight and repaint. But everything we do below triggers a repaint. */
reader_super_highlight_me_plenty(data->buff_pos()); reader_super_highlight_me_plenty();
command_test_result = data->test_func(data->command_text().c_str()); command_test_result = data->test_func(el->text.c_str());
} }
} }
@ -3455,9 +3490,10 @@ const wchar_t *reader_readline(void)
case 0: case 0:
{ {
/* Finished command, execute it. Don't add items that start with a leading space. */ /* Finished command, execute it. Don't add items that start with a leading space. */
if (data->history != NULL && ! data->command_line.empty() && data->command_text().at(0) != L' ') const editable_line_t *el = &data->command_line;
if (data->history != NULL && ! el->empty() && el->text.at(0) != L' ')
{ {
data->history->add_with_file_detection(data->command_text()); data->history->add_with_file_detection(el->text);
} }
finished=1; finished=1;
data->command_line.position = data->command_length(); data->command_line.position = data->command_length();
@ -3498,7 +3534,6 @@ const wchar_t *reader_readline(void)
case R_HISTORY_TOKEN_SEARCH_FORWARD: case R_HISTORY_TOKEN_SEARCH_FORWARD:
{ {
int reset = 0; int reset = 0;
if (data->search_mode == NO_SEARCH) if (data->search_mode == NO_SEARCH)
{ {
reset = 1; reset = 1;
@ -3512,7 +3547,8 @@ const wchar_t *reader_readline(void)
data->search_mode = TOKEN_SEARCH; data->search_mode = TOKEN_SEARCH;
} }
data->search_buff.append(data->command_text()); const editable_line_t *el = &data->command_line;
data->search_buff.append(el->text);
data->history_search = history_search_t(*data->history, data->search_buff, HISTORY_SEARCH_TYPE_CONTAINS); data->history_search = history_search_t(*data->history, data->search_buff, HISTORY_SEARCH_TYPE_CONTAINS);
/* Skip the autosuggestion as history unless it was truncated */ /* Skip the autosuggestion as history unless it was truncated */
@ -3551,7 +3587,7 @@ const wchar_t *reader_readline(void)
{ {
new_text = data->history_search.current_string(); new_text = data->history_search.current_string();
} }
set_command_line_and_position(new_text, new_text.size()); set_command_line_and_position(&data->command_line, new_text, new_text.size());
break; break;
} }
@ -3616,30 +3652,31 @@ const wchar_t *reader_readline(void)
{ {
move_word_style_t style = (c == R_BACKWARD_KILL_PATH_COMPONENT ? move_word_style_path_components : move_word_style_punctuation); move_word_style_t style = (c == R_BACKWARD_KILL_PATH_COMPONENT ? move_word_style_path_components : move_word_style_punctuation);
bool newv = (last_char != R_BACKWARD_KILL_WORD && last_char != R_BACKWARD_KILL_PATH_COMPONENT); bool newv = (last_char != R_BACKWARD_KILL_WORD && last_char != R_BACKWARD_KILL_PATH_COMPONENT);
move_word(MOVE_DIR_LEFT, true /* erase */, style, newv); move_word(data->active_edit_line(), MOVE_DIR_LEFT, true /* erase */, style, newv);
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 */, move_word_style_punctuation, last_char!=R_KILL_WORD); move_word(data->active_edit_line(), 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 */, move_word_style_punctuation, false); move_word(data->active_edit_line(), MOVE_DIR_LEFT, false /* do not erase */, move_word_style_punctuation, false);
break; break;
} }
/* move one word right*/ /* move one word right*/
case R_FORWARD_WORD: case R_FORWARD_WORD:
{ {
if (data->buff_pos() < data->command_length()) editable_line_t *el = data->active_edit_line();
if (el->position < el->size())
{ {
move_word(MOVE_DIR_RIGHT, false /* do not erase */, move_word_style_punctuation, false); move_word(el, MOVE_DIR_RIGHT, false /* do not erase */, move_word_style_punctuation, false);
} }
else else
{ {
@ -3650,12 +3687,13 @@ const wchar_t *reader_readline(void)
case R_BEGINNING_OF_HISTORY: case R_BEGINNING_OF_HISTORY:
{ {
data->history_search = history_search_t(*data->history, data->command_text(), HISTORY_SEARCH_TYPE_PREFIX); const editable_line_t *el = &data->command_line;
data->history_search = history_search_t(*data->history, el->text, HISTORY_SEARCH_TYPE_PREFIX);
data->history_search.go_to_beginning(); data->history_search.go_to_beginning();
if (! data->history_search.is_at_end()) if (! data->history_search.is_at_end())
{ {
wcstring new_text = data->history_search.current_string(); wcstring new_text = data->history_search.current_string();
set_command_line_and_position(new_text, new_text.size()); set_command_line_and_position(&data->command_line, new_text, new_text.size());
} }
break; break;
@ -3701,7 +3739,8 @@ const wchar_t *reader_readline(void)
else else
{ {
/* Not navigating the pager contents */ /* Not navigating the pager contents */
int line_old = parse_util_get_line_from_offset(data->command_text(), data->buff_pos()); editable_line_t *el = data->active_edit_line();
int line_old = parse_util_get_line_from_offset(el->text, el->position);
int line_new; int line_new;
if (c == R_UP_LINE) if (c == R_UP_LINE)
@ -3709,7 +3748,7 @@ const wchar_t *reader_readline(void)
else else
line_new = line_old+1; line_new = line_old+1;
int line_count = parse_util_lineno(data->command_text().c_str(), data->command_length())-1; int line_count = parse_util_lineno(el->text.c_str(), el->size())-1;
if (line_new >= 0 && line_new <= line_count) if (line_new >= 0 && line_new <= line_count)
{ {
@ -3721,17 +3760,17 @@ const wchar_t *reader_readline(void)
size_t line_offset_old; size_t line_offset_old;
size_t total_offset_new; size_t total_offset_new;
base_pos_new = parse_util_get_offset_from_line(data->command_text(), line_new); base_pos_new = parse_util_get_offset_from_line(el->text, line_new);
base_pos_old = parse_util_get_offset_from_line(data->command_text(), line_old); base_pos_old = parse_util_get_offset_from_line(el->text, line_old);
assert(base_pos_new != (size_t)(-1) && base_pos_old != (size_t)(-1)); assert(base_pos_new != (size_t)(-1) && base_pos_old != (size_t)(-1));
indent_old = data->indents.at(base_pos_old); indent_old = data->indents.at(base_pos_old);
indent_new = data->indents.at(base_pos_new); indent_new = data->indents.at(base_pos_new);
line_offset_old = data->buff_pos() - parse_util_get_offset_from_line(data->command_text(), line_old); line_offset_old = el->position - parse_util_get_offset_from_line(el->text, line_old);
total_offset_new = parse_util_get_offset(data->command_text(), line_new, line_offset_old - 4*(indent_new-indent_old)); total_offset_new = parse_util_get_offset(el->text, line_new, line_offset_old - 4*(indent_new-indent_old));
data->command_line.position = total_offset_new; el->position = total_offset_new;
reader_repaint_needed(); reader_repaint_needed();
} }
} }
@ -3755,41 +3794,43 @@ const wchar_t *reader_readline(void)
case R_TRANSPOSE_CHARS: case R_TRANSPOSE_CHARS:
{ {
if (data->command_length() < 2) editable_line_t *el = data->active_edit_line();
if (el->size() < 2)
{ {
break; break;
} }
/* If the cursor is at the end, transpose the last two characters of the line */ /* If the cursor is at the end, transpose the last two characters of the line */
if (data->command_line.position == data->command_length()) if (el->position == el->size())
{ {
data->command_line.position--; el->position--;
} }
/* /*
Drag the character before the cursor forward over the character at the cursor, moving Drag the character before the cursor forward over the character at the cursor, moving
the cursor forward as well. the cursor forward as well.
*/ */
if (data->command_line.position > 0) if (el->position > 0)
{ {
wcstring local_cmd = data->command_text(); wcstring local_cmd = el->text;
std::swap(local_cmd.at(data->buff_pos()), local_cmd.at(data->buff_pos()-1)); std::swap(local_cmd.at(el->position), local_cmd.at(el->position-1));
set_command_line_and_position(local_cmd, data->buff_pos() + 1); set_command_line_and_position(el, local_cmd, el->position + 1);
} }
break; break;
} }
case R_TRANSPOSE_WORDS: case R_TRANSPOSE_WORDS:
{ {
size_t len = data->command_length(); editable_line_t *el = data->active_edit_line();
const wchar_t *buff = data->command_text().c_str(); size_t len = el->size();
const wchar_t *buff = el->text.c_str();
const wchar_t *tok_begin, *tok_end, *prev_begin, *prev_end; const wchar_t *tok_begin, *tok_end, *prev_begin, *prev_end;
/* If we are not in a token, look for one ahead */ /* If we are not in a token, look for one ahead */
while (data->buff_pos() != len && !iswalnum(buff[data->buff_pos()])) while (el->position != len && !iswalnum(buff[el->position]))
data->command_line.position++; el->position++;
parse_util_token_extent(buff, data->buff_pos(), &tok_begin, &tok_end, &prev_begin, &prev_end); parse_util_token_extent(buff, el->position, &tok_begin, &tok_end, &prev_begin, &prev_end);
/* In case we didn't find a token at or after the cursor... */ /* In case we didn't find a token at or after the cursor... */
if (tok_begin == &buff[len]) if (tok_begin == &buff[len])
@ -3814,7 +3855,7 @@ const wchar_t *reader_readline(void)
new_buff.append(prev); new_buff.append(prev);
new_buff.append(trail); new_buff.append(trail);
/* Put cursor right after the second token */ /* Put cursor right after the second token */
set_command_line_and_position(new_buff, tok_end - buff); set_command_line_and_position(el, new_buff, tok_end - buff);
} }
break; break;
} }
@ -3823,15 +3864,16 @@ const wchar_t *reader_readline(void)
case R_DOWNCASE_WORD: case R_DOWNCASE_WORD:
case R_CAPITALIZE_WORD: case R_CAPITALIZE_WORD:
{ {
editable_line_t *el = data->active_edit_line();
// For capitalize_word, whether we've capitalized a character so far // For capitalize_word, whether we've capitalized a character so far
bool capitalized_first = false; bool capitalized_first = false;
// We apply the operation from the current location to the end of the word // We apply the operation from the current location to the end of the word
size_t pos = data->buff_pos(); size_t pos = el->position;
move_word(MOVE_DIR_RIGHT, false, move_word_style_punctuation, false); move_word(el, MOVE_DIR_RIGHT, false, move_word_style_punctuation, false);
for (; pos < data->buff_pos(); pos++) for (; pos < el->position; pos++)
{ {
wchar_t chr = data->command_text().at(pos); wchar_t chr = el->text.at(pos);
// We always change the case; this decides whether we go uppercase (true) or lowercase (false) // We always change the case; this decides whether we go uppercase (true) or lowercase (false)
bool make_uppercase; bool make_uppercase;
@ -3850,7 +3892,7 @@ const wchar_t *reader_readline(void)
capitalized_first = capitalized_first || make_uppercase; capitalized_first = capitalized_first || make_uppercase;
} }
data->command_line_changed(); data->command_line_changed();
reader_super_highlight_me_plenty(data->buff_pos()); reader_super_highlight_me_plenty();
reader_repaint_needed(); reader_repaint_needed();
break; break;
} }
@ -3925,7 +3967,7 @@ const wchar_t *reader_readline(void)
set_color(rgb_color_t::reset(), rgb_color_t::reset()); set_color(rgb_color_t::reset(), rgb_color_t::reset());
} }
return finished ? data->command_text().c_str() : NULL; return finished ? data->command_line.text.c_str() : NULL;
} }
int reader_search_mode() int reader_search_mode()

View file

@ -53,9 +53,8 @@ class editable_line_t
{ {
} }
/* Internal */ /* Inserts the string at the cursor position */
void kill(size_t begin_idx, size_t length, int mode, int newv); void insert_string(const wcstring &str);
}; };
/** /**