diff options
author | ridiculousfish <corydoras@ridiculousfish.com> | 2014-03-29 14:19:45 -0700 |
---|---|---|
committer | ridiculousfish <corydoras@ridiculousfish.com> | 2014-03-29 14:19:45 -0700 |
commit | d4fafeb6d66e415e85c67700e5a370765c09bb93 (patch) | |
tree | 7e208d860649e8651180b281ec715a96169a9dbe /reader.cpp | |
parent | 844b01cb6be2ab3a3f7070112c94604b43710835 (diff) | |
parent | 31bf50b2d495222925371556169f61c1c5a81ed7 (diff) |
Merge branch 'master' into 1218_rebase
Conflicts:
builtin.cpp
builtin_commandline.cpp
highlight.cpp
input.cpp
input.h
reader.cpp
screen.cpp
screen.h
Diffstat (limited to 'reader.cpp')
-rw-r--r-- | reader.cpp | 1215 |
1 files changed, 706 insertions, 509 deletions
@@ -100,6 +100,7 @@ commence. #include "parse_util.h" #include "parser_keywords.h" #include "parse_tree.h" +#include "pager.h" /** Maximum length of prefix string when printing completion @@ -180,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. */ 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 @@ -192,11 +199,17 @@ class reader_data_t public: /** String containing the whole current commandline */ - wcstring command_line; + editable_line_t command_line; /** String containing the autosuggestion */ wcstring autosuggestion; + /** Current pager */ + pager_t pager; + + /** Current page rendering */ + page_rendering_t current_page_rendering; + /** Whether autosuggesting is allowed at all */ bool allow_autosuggestion; @@ -238,21 +251,33 @@ public: /** The current position in search_prev */ size_t search_pos; - /** Length of the command */ - size_t command_length() const + bool is_navigating_pager_contents() const + { + return this->pager.is_navigating_contents(); + } + + /* 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.size(); + if (this->is_navigating_pager_contents() && this->pager.is_search_field_shown()) + { + return &this->pager.search_field_line; + } + else + { + return &this->command_line; + } } /** Do what we need to do whenever our command line changes */ - void command_line_changed(void); + void command_line_changed(const editable_line_t *el); + + /** Do what we need to do whenever our pager selection */ + void pager_selection_changed(); /** Expand abbreviations at the current cursor position, minus backtrack_amt. */ bool expand_abbreviation_as_necessary(size_t cursor_backtrack); - /** The current position of the cursor in buff. */ - size_t buff_pos; - /** Indicates whether a selection is currently active */ bool sel_active; @@ -278,6 +303,10 @@ public: /** The output of the last evaluation of the right prompt command */ wcstring right_prompt_buff; + /* Completion support */ + wcstring cycle_command_line; + size_t cycle_cursor_pos; + /** Color is the syntax highlighting for buff. The format is that color[i] is the classification (according to the enum in @@ -348,11 +377,11 @@ public: history(0), token_history_pos(0), search_pos(0), - buff_pos(0), sel_active(0), sel_begin_pos(0), sel_start_pos(0), sel_stop_pos(0), + cycle_cursor_pos(0), complete_func(0), highlight_function(0), test_func(0), @@ -367,6 +396,9 @@ public: } }; +/* Sets the command line contents, without clearing the pager */ +static void reader_set_buffer_maintaining_pager(const wcstring &b, size_t pos); + /** The current interactive reading context */ @@ -413,7 +445,7 @@ static struct termios terminal_mode_on_startup; 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, bool no_io = false); /** Variable to keep track of forced exits - see \c reader_exit_forced(); @@ -450,10 +482,10 @@ static void term_donate() /** Update the cursor position */ -static void update_buff_pos(size_t buff_pos) +static void update_buff_pos(editable_line_t *el, size_t buff_pos) { - data->buff_pos = buff_pos; - if(data->sel_active) + el->position = buff_pos; + if (el == &data->command_line && data->sel_active) { if(data->sel_begin_pos <= buff_pos) { @@ -553,11 +585,12 @@ wcstring combine_command_and_autosuggestion(const wcstring &cmdline, const wcstr */ static void reader_repaint() { + editable_line_t *cmd_line = &data->command_line; // Update the indentation - data->indents = parse_util_compute_indents(data->command_line); + data->indents = parse_util_compute_indents(cmd_line->text); // Combine the command and autosuggestion into one string - wcstring full_line = combine_command_and_autosuggestion(data->command_line, data->autosuggestion); + wcstring full_line = combine_command_and_autosuggestion(cmd_line->text, data->autosuggestion); size_t len = full_line.size(); if (len < 1) @@ -566,46 +599,46 @@ static void reader_repaint() std::vector<highlight_spec_t> colors = data->colors; colors.resize(len, highlight_spec_autosuggestion); - if(data->sel_active) + if (data->sel_active) { - highlight_spec_t selection_color = highlight_make_background(highlight_spec_selection); - for(int i = data->sel_start_pos; i <= std::min(len - 1, data->sel_stop_pos); i++) - { - colors[i] = selection_color; - } + highlight_spec_t selection_color = highlight_make_background(highlight_spec_selection); + for(size_t i = data->sel_start_pos; i <= std::min(len - 1, data->sel_stop_pos); i++) + { + colors[i] = selection_color; + } } std::vector<int> indents = data->indents; indents.resize(len); + // Re-render our completions page if necessary + // We set the term size to 1 less than the true term height. This means we will always show the (bottom) line of the prompt. + data->pager.set_term_size(maxi(1, common_get_width()), maxi(1, common_get_height() - 1)); + data->pager.update_rendering(&data->current_page_rendering); + + bool focused_on_pager = data->active_edit_line() == &data->pager.search_field_line; + size_t cursor_position = focused_on_pager ? data->pager.cursor_position() : cmd_line->position; + s_write(&data->screen, data->left_prompt_buff, data->right_prompt_buff, full_line, - data->command_length(), + cmd_line->size(), &colors[0], &indents[0], - data->buff_pos, data->sel_start_pos, - data->sel_stop_pos); + data->sel_stop_pos, + cursor_position, + data->current_page_rendering, + focused_on_pager); data->repaint_needed = false; } - -static void reader_repaint_without_autosuggestion() -{ - // Swap in an empty autosuggestion, repaint, then swap it out - wcstring saved_autosuggestion; - data->autosuggestion.swap(saved_autosuggestion); - reader_repaint(); - data->autosuggestion.swap(saved_autosuggestion); -} - /** 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(editable_line_t *el, size_t begin_idx, size_t length, int mode, int newv) { - const wchar_t *begin = data->command_line.c_str() + begin_idx; + const wchar_t *begin = el->text.c_str() + begin_idx; if (newv) { data->kill_item = wcstring(begin, length); @@ -628,19 +661,18 @@ static void reader_kill(size_t begin_idx, size_t length, int mode, int newv) 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 */ - size_t backtrack = mini(data->buff_pos - begin_idx, length); - update_buff_pos(data->buff_pos - backtrack); + size_t backtrack = mini(el->position - begin_idx, length); + update_buff_pos(el, el->position - backtrack); } - data->command_line.erase(begin_idx, length); - data->command_line_changed(); + el->text.erase(begin_idx, length); + data->command_line_changed(el); - reader_super_highlight_me_plenty(data->buff_pos); + reader_super_highlight_me_plenty(); reader_repaint(); - } @@ -678,10 +710,12 @@ void reader_pop_current_filename() /** Make sure buffers are large enough to hold the current string length */ -void reader_data_t::command_line_changed() +void reader_data_t::command_line_changed(const editable_line_t *el) { ASSERT_IS_MAIN_THREAD(); - size_t len = command_length(); + if (el == &this->command_line) + { + size_t len = this->command_line.size(); /* When we grow colors, propagate the last color (if any), under the assumption that usually it will be correct. If it is, it avoids a repaint. */ highlight_spec_t last_color = colors.empty() ? highlight_spec_t() : colors.back(); @@ -692,6 +726,39 @@ void reader_data_t::command_line_changed() /* Update the gen count */ s_generation_count++; } + else if (el == &this->pager.search_field_line) + { + this->pager.refilter_completions(); + this->pager_selection_changed(); + } +} + +void reader_data_t::pager_selection_changed() +{ + ASSERT_IS_MAIN_THREAD(); + + const completion_t *completion = this->pager.selected_completion(this->current_page_rendering); + + /* Update the cursor and command line */ + size_t cursor_pos = this->cycle_cursor_pos; + wcstring new_cmd_line; + + if (completion == NULL) + { + new_cmd_line = this->cycle_command_line; + } + else + { + new_cmd_line = completion_apply_to_command_line(completion->completion, completion->flags, this->cycle_command_line, &cursor_pos, false); + } + reader_set_buffer_maintaining_pager(new_cmd_line, cursor_pos); + + /* Since we just inserted a completion, don't immediately do a new autosuggestion */ + this->suppress_autosuggestion = true; + + /* Trigger repaint (see #765) */ + reader_repaint_needed(); +} /* 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) @@ -767,19 +834,21 @@ 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 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 */ wcstring new_cmdline; - size_t cursor_pos = this->buff_pos - mini(this->buff_pos, cursor_backtrack); - if (reader_expand_abbreviation_in_command(this->command_line, cursor_pos, &new_cmdline)) + size_t cursor_pos = el->position - mini(el->position, cursor_backtrack); + 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. */ - 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.swap(new_cmdline); - update_buff_pos(new_buff_pos); - data->command_line_changed(); + el->text.swap(new_cmdline); + update_buff_pos(el, new_buff_pos); + data->command_line_changed(el); result = true; } } @@ -1051,29 +1120,92 @@ void reader_react_to_color_change() } +/* Indicates if the given command char ends paging */ +static bool command_ends_paging(wchar_t c, bool focused_on_search_field) +{ + switch (c) + { + /* These commands always end paging */ + case R_HISTORY_SEARCH_BACKWARD: + case R_HISTORY_SEARCH_FORWARD: + case R_BEGINNING_OF_HISTORY: + case R_END_OF_HISTORY: + case R_HISTORY_TOKEN_SEARCH_BACKWARD: + case R_HISTORY_TOKEN_SEARCH_FORWARD: + case R_ACCEPT_AUTOSUGGESTION: + case R_CANCEL: + return true; + + /* These commands never do */ + case R_COMPLETE: + case R_COMPLETE_AND_SEARCH: + case R_BACKWARD_CHAR: + case R_FORWARD_CHAR: + case R_UP_LINE: + case R_DOWN_LINE: + case R_NULL: + case R_REPAINT: + case R_SUPPRESS_AUTOSUGGESTION: + default: + return false; + + /* R_EXECUTE does end paging, but only executes if it was not paging. So it's handled specially */ + case R_EXECUTE: + return false; + + /* These commands operate on the search field if that's where the focus is */ + case R_BEGINNING_OF_LINE: + case R_END_OF_LINE: + case R_FORWARD_WORD: + case R_BACKWARD_WORD: + case R_DELETE_CHAR: + case R_BACKWARD_DELETE_CHAR: + case R_KILL_LINE: + case R_YANK: + case R_YANK_POP: + case R_BACKWARD_KILL_LINE: + case R_KILL_WHOLE_LINE: + case R_KILL_WORD: + case R_BACKWARD_KILL_WORD: + case R_BACKWARD_KILL_PATH_COMPONENT: + case R_SELF_INSERT: + case R_TRANSPOSE_CHARS: + case R_TRANSPOSE_WORDS: + case R_UPCASE_WORD: + case R_DOWNCASE_WORD: + case R_CAPITALIZE_WORD: + case R_VI_ARG_DIGIT: + case R_VI_DELETE_TO: + case R_BEGINNING_OF_BUFFER: + case R_END_OF_BUFFER: + return ! focused_on_search_field; + } +} + /** Remove the previous character in the character buffer and on the screen using syntax highlighting, etc. */ static void remove_backward() { + editable_line_t *el = data->active_edit_line(); - if (data->buff_pos <= 0) + if (el->position <= 0) return; /* Fake composed character sequences by continuing to delete until we delete a character of width at least 1. */ int width; do { - update_buff_pos(data->buff_pos - 1); - width = fish_wcwidth(data->command_line.at(data->buff_pos)); - data->command_line.erase(data->buff_pos, 1); + update_buff_pos(el, el->position - 1); + width = fish_wcwidth(el->text.at(el->position)); + el->text.erase(el->position, 1); } - while (width == 0 && data->buff_pos > 0); - data->command_line_changed(); + while (width == 0 && el->position > 0); + data->command_line_changed(el); data->suppress_autosuggestion = true; - reader_super_highlight_me_plenty(data->buff_pos); + reader_super_highlight_me_plenty(); reader_repaint(); @@ -1086,23 +1218,27 @@ static void remove_backward() Optionally also expand abbreviations. Returns true if the string changed. */ -static bool insert_string(const wcstring &str, bool should_expand_abbreviations = false) +static bool insert_string(editable_line_t *el, const wcstring &str, bool should_expand_abbreviations = false) { size_t len = str.size(); if (len == 0) return false; - data->command_line.insert(data->buff_pos, str); - update_buff_pos(data->buff_pos + len); - data->command_line_changed(); - data->suppress_autosuggestion = false; - - if (should_expand_abbreviations) - data->expand_abbreviation_as_necessary(1); - - /* Syntax highlight. Note we must have that buff_pos > 0 because we just added something nonzero to its length */ - assert(data->buff_pos > 0); - reader_super_highlight_me_plenty(data->buff_pos-1); + el->insert_string(str); + update_buff_pos(el, el->position); + data->command_line_changed(el); + + if (el == &data->command_line) + { + data->suppress_autosuggestion = false; + + if (should_expand_abbreviations) + data->expand_abbreviation_as_necessary(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(); return true; @@ -1112,9 +1248,9 @@ static bool insert_string(const wcstring &str, bool should_expand_abbreviations Insert the character into the command line buffer and print it to the screen using syntax highlighting, etc. */ -static bool insert_char(wchar_t c, bool should_expand_abbreviations = false) +static bool insert_char(editable_line_t *el, wchar_t c, bool should_expand_abbreviations = false) { - return insert_string(wcstring(1, c), should_expand_abbreviations); + return insert_string(el, wcstring(1, c), should_expand_abbreviations); } @@ -1243,150 +1379,15 @@ wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flag */ static void completion_insert(const wchar_t *val, complete_flags_t flags) { - size_t cursor = data->buff_pos; - wcstring new_command_line = completion_apply_to_command_line(val, flags, data->command_line, &cursor, false /* not append only */); - reader_set_buffer(new_command_line, cursor); + editable_line_t *el = data->active_edit_line(); + 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); /* Since we just inserted a completion, don't immediately do a new autosuggestion */ data->suppress_autosuggestion = true; } -/* Return an escaped path to fish_pager */ -static wcstring escaped_fish_pager_path(void) -{ - wcstring result; - const env_var_t bin_dir = env_get_string(L"__fish_bin_dir"); - if (bin_dir.missing_or_empty()) - { - /* This isn't good, hope our normal command stuff can find it */ - result = L"fish_pager"; - } - else - { - result = escape_string(bin_dir + L"/fish_pager", ESCAPE_ALL); - } - return result; -} - -/** - Run the fish_pager command to display the completion list. If the - fish_pager outputs any text, it is inserted into the input - backbuffer. - - \param prefix the string to display before every completion. - \param is_quoted should be set if the argument is quoted. This will change the display style. - \param comp the list of completions to display -*/ -static void run_pager(const wcstring &prefix, int is_quoted, const std::vector<completion_t> &comp) -{ - wcstring msg; - wcstring prefix_esc; - char *foo; - - shared_ptr<io_buffer_t> in_buff(io_buffer_t::create(true, 3)); - shared_ptr<io_buffer_t> out_buff(io_buffer_t::create(false, 4)); - - // The above may fail e.g. if we have too many open fds - if (in_buff.get() == NULL || out_buff.get() == NULL) - return; - - wchar_t *escaped_separator; - - if (prefix.empty()) - { - prefix_esc = L"\"\""; - } - else - { - prefix_esc = escape_string(prefix, 1); - } - - - const wcstring pager_path = escaped_fish_pager_path(); - const wcstring cmd = format_string(L"%ls -c 3 -r 4 %ls -p %ls", - // L"valgrind --track-fds=yes --log-file=pager.txt --leak-check=full ./%ls %d %ls", - pager_path.c_str(), - is_quoted?L"-q":L"", - prefix_esc.c_str()); - - escaped_separator = escape(COMPLETE_SEP_STR, 1); - - for (size_t i=0; i< comp.size(); i++) - { - long base_len=-1; - const completion_t &el = comp.at(i); - - wcstring completion_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' - if ((el.flags & COMPLETE_REPLACES_TOKEN) && match_type_shares_prefix(el.match.type)) - { - // Compute base_len if we have not yet - if (base_len == -1) - { - const wchar_t *begin, *buff = data->command_line.c_str(); - - parse_util_token_extent(buff, data->buff_pos, &begin, 0, 0, 0); - base_len = data->buff_pos - (begin-buff); - } - - completion_text = escape_string(el.completion.c_str() + base_len, ESCAPE_ALL | ESCAPE_NO_QUOTED); - } - else - { - completion_text = escape_string(el.completion, ESCAPE_ALL | ESCAPE_NO_QUOTED); - } - - - if (! el.description.empty()) - { - description_text = escape_string(el.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. - */ - msg.reserve(msg.size() + completion_text.size() + description_text.size() + 2); - msg.append(completion_text); - if (! description_text.empty() || completion_text.empty()) - { - msg.append(escaped_separator); - msg.append(description_text); - } - msg.push_back(L'\n'); - } - - free(escaped_separator); - - foo = wcs2str(msg.c_str()); - in_buff->out_buffer_append(foo, strlen(foo)); - free(foo); - - term_donate(); - parser_t &parser = parser_t::principal_parser(); - io_chain_t io_chain; - io_chain.push_back(out_buff); - io_chain.push_back(in_buff); - parser.eval(cmd, io_chain, TOP); - term_steal(); - - out_buff->read(); - - const char zero = 0; - out_buff->out_buffer_append(&zero, 1); - - const char *out_data = out_buff->out_buffer_ptr(); - if (out_data) - { - const wcstring str = str2wcstring(out_data); - size_t idx = str.size(); - while (idx--) - { - input_unreadch(str.at(idx)); - } - } -} - struct autosuggestion_context_t { wcstring search_string; @@ -1496,17 +1497,19 @@ static int threaded_autosuggest(autosuggestion_context_t *ctx) 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. */ + const editable_line_t *el = data->active_edit_line(); const wchar_t *whitespace = L" \t\r\n\v"; return ! data->suppress_autosuggestion && data->history_search.is_at_end() && - data->command_line.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) { if (result && can_autosuggest() && - ctx->search_string == data->command_line && + ctx->search_string == data->command_line.text && 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 */ @@ -1524,7 +1527,8 @@ static void update_autosuggestion(void) data->autosuggestion.clear(); 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_line, 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); } } @@ -1538,7 +1542,7 @@ static void accept_autosuggestion(bool full) if (full) { /* Just take the whole thing */ - data->command_line = data->autosuggestion; + data->command_line.text = data->autosuggestion; } else { @@ -1549,16 +1553,38 @@ static void accept_autosuggestion(bool full) wchar_t wc = data->autosuggestion.at(idx); if (! state.consume_char(wc)) break; - data->command_line.push_back(wc); + data->command_line.text.push_back(wc); } } - update_buff_pos(data->command_line.size()); - data->command_line_changed(); - reader_super_highlight_me_plenty(data->buff_pos); + update_buff_pos(&data->command_line, data->command_line.size()); + data->command_line_changed(&data->command_line); + reader_super_highlight_me_plenty(); reader_repaint(); } } +/* Ensure we have no pager contents */ +static void clear_pager() +{ + if (data) + { + data->pager.clear(); + data->current_page_rendering = page_rendering_t(); + reader_repaint_needed(); + } +} + +static void select_completion_in_direction(enum selection_direction_t dir) +{ + assert(data != NULL); + /* Note: this will probably trigger reader_selected_completion_changed, which will cause us to update stuff */ + bool selection_changed = data->pager.select_next_completion_in_direction(dir, data->current_page_rendering); + if (selection_changed) + { + data->pager_selection_changed(); + } +} + /** Flash the screen. This function only changed the color of the current line, since the flash_screen sequnce is rather painful to @@ -1568,7 +1594,8 @@ static void reader_flash() { 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; } @@ -1579,7 +1606,7 @@ static void reader_flash() pollint.tv_nsec = 100 * 1000000; nanosleep(&pollint, NULL); - reader_super_highlight_me_plenty(data->buff_pos); + reader_super_highlight_me_plenty(); reader_repaint(); } @@ -1677,40 +1704,6 @@ static void prioritize_completions(std::vector<completion_t> &comp) sort(comp.begin(), comp.end(), compare_completions_by_match_type); } -/* Given a list of completions, get the completion at an index past *inout_idx, and then increment it. inout_idx should be initialized to (size_t)(-1) for the first call. */ -static const completion_t *cycle_competions(const std::vector<completion_t> &comp, const wcstring &command_line, size_t *inout_idx) -{ - const size_t size = comp.size(); - if (size == 0) - return NULL; - - // note start_idx will be set to -1 initially, so that when it gets incremented we start at 0 - const size_t start_idx = *inout_idx; - size_t idx = start_idx; - - const completion_t *result = NULL; - size_t remaining = comp.size(); - while (remaining--) - { - /* Bump the index */ - idx = (idx + 1) % size; - - /* Get the completion */ - const completion_t &c = comp.at(idx); - - /* Try this completion */ - if (!(c.flags & COMPLETE_REPLACES_TOKEN) || reader_can_replace(command_line, c.flags)) - { - /* Success */ - result = &c; - break; - } - } - - *inout_idx = idx; - return result; -} - /** Handle the list of completions. This means the following: @@ -1727,18 +1720,20 @@ static const completion_t *cycle_competions(const std::vector<completion_t> &com completions. \param comp the list of completion strings + \param continue_after_prefix_insertion If we have a shared prefix, whether to print the list of completions after inserting it. Return true if we inserted text into the command line, false if we did not. */ -static bool handle_completions(const std::vector<completion_t> &comp) +static bool handle_completions(const std::vector<completion_t> &comp, bool continue_after_prefix_insertion) { bool done = false; bool success = false; - const wchar_t *begin, *end, *buff = data->command_line.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); - end = buff+data->buff_pos; + parse_util_token_extent(buff, el->position, &begin, 0, 0, 0); + end = buff+el->position; const wcstring tok(begin, end - begin); @@ -1867,15 +1862,16 @@ static bool handle_completions(const std::vector<completion_t> &comp) completion_insert(common_prefix.c_str(), flags); success = true; } - else + + if (continue_after_prefix_insertion || common_prefix.empty()) { - /* We didn't get a common prefix. Print the list. */ + /* We didn't get a common prefix, or we want to print the list anyways. */ size_t len, prefix_start = 0; wcstring prefix; - parse_util_get_parameter_info(data->command_line, 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); - len = data->buff_pos - prefix_start; + assert(el->position >= prefix_start); + len = el->position - prefix_start; if (match_type_requires_full_replacement(best_match_type)) { @@ -1884,32 +1880,30 @@ static bool handle_completions(const std::vector<completion_t> &comp) } else if (len <= PREFIX_MAX_LEN) { - prefix.append(data->command_line, prefix_start, len); + prefix.append(el->text, prefix_start, len); } else { // append just the end of the string prefix = wcstring(&ellipsis_char, 1); - prefix.append(data->command_line, prefix_start + len - PREFIX_MAX_LEN, PREFIX_MAX_LEN); + prefix.append(el->text, prefix_start + len - PREFIX_MAX_LEN, PREFIX_MAX_LEN); } - { - int is_quoted; - wchar_t quote; - parse_util_get_parameter_info(data->command_line, data->buff_pos, "e, NULL, NULL); - is_quoted = (quote != L'\0'); + parse_util_get_parameter_info(el->text, el->position, "e, NULL, NULL); - /* Clear the autosuggestion from the old commandline before abandoning it (see #561) */ - if (! data->autosuggestion.empty()) - reader_repaint_without_autosuggestion(); + /* Update the pager data */ + data->pager.set_prefix(prefix); + data->pager.set_completions(surviving_completions); - write_loop(1, "\n", 1); + /* Invalidate our rendering */ + data->current_page_rendering = page_rendering_t(); + + /* Modify the command line to reflect the new pager */ + data->pager_selection_changed(); + + reader_repaint_needed(); - run_pager(prefix, is_quoted, surviving_completions); - } - s_reset(&data->screen, screen_reset_abandon_line); - reader_repaint(); success = false; } } @@ -2118,13 +2112,13 @@ void reader_sanity_check() /* Note: 'data' is non-null if we are interactive, except in the testing environment */ if (get_is_interactive() && data != NULL) { - if (!(data->buff_pos <= data->command_length())) + if (data->command_line.position > data->command_line.size()) sanity_lose(); - if (data->colors.size() != data->command_length()) + if (data->colors.size() != data->command_line.size()) sanity_lose(); - if (data->indents.size() != data->command_length()) + if (data->indents.size() != data->command_line.size()) sanity_lose(); } @@ -2133,13 +2127,13 @@ void reader_sanity_check() /** 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 = new_str; - data->command_line_changed(); - update_buff_pos(pos); - reader_super_highlight_me_plenty(data->buff_pos); - reader_repaint(); + el->text = new_str; + update_buff_pos(el, pos); + data->command_line_changed(el); + reader_super_highlight_me_plenty(); + reader_repaint_needed(); } static void reader_replace_current_token(const wchar_t *new_token) @@ -2149,8 +2143,9 @@ static void reader_replace_current_token(const wchar_t *new_token) size_t new_pos; /* Find current token */ - const wchar_t *buff = data->command_line.c_str(); - parse_util_token_extent(buff, data->buff_pos, &begin, &end, 0, 0); + editable_line_t *el = data->active_edit_line(); + const wchar_t *buff = el->text.c_str(); + parse_util_token_extent(buff, el->position, &begin, &end, 0, 0); if (!begin || !end) return; @@ -2161,7 +2156,7 @@ static void reader_replace_current_token(const wchar_t *new_token) new_buff.append(end); 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); } @@ -2170,9 +2165,10 @@ static void reader_replace_current_token(const wchar_t *new_token) */ static void reset_token_history() { + const editable_line_t *el = data->active_edit_line(); const wchar_t *begin, *end; - const wchar_t *buff = data->command_line.c_str(); - parse_util_token_extent((wchar_t *)buff, data->buff_pos, &begin, &end, 0, 0); + const wchar_t *buff = el->text.c_str(); + parse_util_token_extent((wchar_t *)buff, el->position, &begin, &end, 0, 0); data->search_buff.clear(); if (begin) @@ -2233,7 +2229,7 @@ static void handle_token_history(int forward, int reset) } reader_replace_current_token(str); - reader_super_highlight_me_plenty(data->buff_pos); + reader_super_highlight_me_plenty(); reader_repaint(); } else @@ -2314,7 +2310,7 @@ static void handle_token_history(int forward, int reset) if (str) { reader_replace_current_token(str); - reader_super_highlight_me_plenty(data->buff_pos); + reader_super_highlight_me_plenty(); reader_repaint(); data->search_pos = data->search_prev.size(); data->search_prev.push_back(str); @@ -2342,19 +2338,19 @@ enum move_word_dir_t 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 */ - const size_t boundary = move_right ? data->command_length() : 0; - if (data->buff_pos == boundary) + const size_t boundary = move_right ? el->size() : 0; + if (el->position == boundary) return; /* When moving left, a value of 1 means the character at index 0. */ move_word_state_machine_t state(style); - const wchar_t * const command_line = data->command_line.c_str(); - const size_t start_buff_pos = data->buff_pos; + const wchar_t * const command_line = el->text.c_str(); + 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) { size_t idx = (move_right ? buff_pos : buff_pos - 1); @@ -2368,24 +2364,27 @@ static void move_word(bool move_right, bool erase, enum move_word_style_t style, if (buff_pos == start_buff_pos) buff_pos = (move_right ? buff_pos + 1 : buff_pos - 1); - /* If we are moving left, buff_pos-1 is the index of the first character we do not delete (possibly -1). If we are moving right, then buff_pos is that index - possibly data->command_length(). */ + /* If we are moving left, buff_pos-1 is the index of the first character we do not delete (possibly -1). If we are moving right, then buff_pos is that index - possibly el->size(). */ if (erase) { /* Don't autosuggest after a kill */ + if (el == &data->command_line) + { data->suppress_autosuggestion = true; + } if (move_right) { - reader_kill(start_buff_pos, buff_pos - start_buff_pos, KILL_APPEND, newv); + reader_kill(el, start_buff_pos, buff_pos - start_buff_pos, KILL_APPEND, newv); } else { - reader_kill(buff_pos, start_buff_pos - buff_pos, KILL_PREPEND, newv); + reader_kill(el, buff_pos, start_buff_pos - buff_pos, KILL_PREPEND, newv); } } else { - update_buff_pos(buff_pos); + update_buff_pos(el, buff_pos); reader_repaint(); } @@ -2394,7 +2393,7 @@ static void move_word(bool move_right, bool erase, enum move_word_style_t style, const wchar_t *reader_get_buffer(void) { ASSERT_IS_MAIN_THREAD(); - return data?data->command_line.c_str():NULL; + return data ? data->command_line.text.c_str() : NULL; } history_t *reader_get_history(void) @@ -2403,40 +2402,49 @@ history_t *reader_get_history(void) return data ? data->history : NULL; } -void reader_set_buffer(const wcstring &b, size_t pos) +/* Sets the command line contents, without clearing the pager */ +static void reader_set_buffer_maintaining_pager(const wcstring &b, size_t pos) { - if (!data) - return; - /* Callers like to pass us pointers into ourselves, so be careful! I don't know if we can use operator= with a pointer to our interior, so use an intermediate. */ size_t command_line_len = b.size(); - data->command_line = b; - data->command_line_changed(); + data->command_line.text = b; + data->command_line_changed(&data->command_line); /* Don't set a position past the command line length */ if (pos > command_line_len) pos = command_line_len; - update_buff_pos(pos); + update_buff_pos(&data->command_line, pos); + /* Clear history search and pager contents */ data->search_mode = NO_SEARCH; data->search_buff.clear(); data->history_search.go_to_end(); - reader_super_highlight_me_plenty(data->buff_pos); + reader_super_highlight_me_plenty(); reader_repaint_needed(); } +/* Sets the command line contents, clearing the pager */ +void reader_set_buffer(const wcstring &b, size_t pos) +{ + if (!data) + return; + + clear_pager(); + reader_set_buffer_maintaining_pager(b, pos); +} + size_t reader_get_cursor_pos() { if (!data) return (size_t)(-1); - return data->buff_pos; + return data->command_line.position; } -bool reader_get_selection(size_t &start, size_t &len) +bool reader_get_selection(size_t *start, size_t *len) { if (!data) { @@ -2444,14 +2452,16 @@ bool reader_get_selection(size_t &start, size_t &len) } else { - if(!data->sel_active) - return false; - else - { - start = data->sel_start_pos; - len = std::min(data->sel_stop_pos - data->sel_start_pos + 1, data->command_length()); - return true; - } + if (! data->sel_active) + { + return false; + } + else + { + *start = data->sel_start_pos; + *len = std::min(data->sel_stop_pos - data->sel_start_pos + 1, data->command_line.size()); + return true; + } } } @@ -2580,7 +2590,7 @@ void reader_push(const wchar_t *name) data=n; - data->command_line_changed(); + data->command_line_changed(&data->command_line); if (data->next == 0) { @@ -2716,7 +2726,7 @@ public: { } - int threaded_highlight() + int perform_highlight() { if (generation_count != s_generation_count) { @@ -2736,8 +2746,9 @@ static void highlight_search(void) { 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; - size_t match_pos = data->command_line.find(needle); + size_t match_pos = el->text.find(needle); if (match_pos != wcstring::npos) { size_t end = match_pos + needle.size(); @@ -2752,10 +2763,10 @@ static void highlight_search(void) static void highlight_complete(background_highlight_context_t *ctx, int result) { ASSERT_IS_MAIN_THREAD(); - if (ctx->string_to_highlight == data->command_line) + if (ctx->string_to_highlight == data->command_line.text) { /* The data hasn't changed, so swap in our colors. The colors may not have changed, so do nothing if they have not. */ - assert(ctx->colors.size() == data->command_length()); + assert(ctx->colors.size() == data->command_line.size()); if (data->colors != ctx->colors) { data->colors.swap(ctx->colors); @@ -2771,7 +2782,7 @@ static void highlight_complete(background_highlight_context_t *ctx, int result) static int threaded_highlight(background_highlight_context_t *ctx) { - return ctx->threaded_highlight(); + return ctx->perform_highlight(); } @@ -2781,19 +2792,36 @@ static int threaded_highlight(background_highlight_context_t *ctx) to avoid repaint issues on terminals where e.g. syntax highlighting 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 no_io if true, do a highlight that does not perform I/O, synchronously. If false, perform an asynchronous highlight in the background, which may perform disk I/O. */ -static void reader_super_highlight_me_plenty(size_t match_highlight_pos) +static void reader_super_highlight_me_plenty(int match_highlight_pos_adjust, bool no_io) { + 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(); - background_highlight_context_t *ctx = new background_highlight_context_t(data->command_line, match_highlight_pos, data->highlight_function); + highlight_function_t highlight_func = no_io ? highlight_shell_no_io : data->highlight_function; + background_highlight_context_t *ctx = new background_highlight_context_t(el->text, match_highlight_pos, highlight_func); + if (no_io) + { + // Highlighting without IO, we just do it + // Note that highlight_complete deletes ctx. + int result = ctx->perform_highlight(); + highlight_complete(ctx, result); + } + else + { + // Highlighting including I/O proceeds in the background iothread_perform(threaded_highlight, highlight_complete, ctx); + } 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. */ - const wcstring &cmd = data->command_line, &suggest = data->autosuggestion; + const wcstring &cmd = el->text, &suggest = data->autosuggestion; if (can_autosuggest() && ! suggest.empty() && string_prefixes_string_case_insensitive(cmd, suggest)) { /* The autosuggestion is still reasonable, so do nothing */ @@ -2877,6 +2905,21 @@ static void handle_end_loop() } } +static bool selection_is_at_top() +{ + const pager_t *pager = &data->pager; + size_t row = pager->get_selected_row(data->current_page_rendering); + if (row != 0 && row != PAGER_SELECTION_NONE) + return false; + + size_t col = pager->get_selected_column(data->current_page_rendering); + if (col != 0 && col != PAGER_SELECTION_NONE) + return false; + + return true; +} + + /** Read interactively. Read input from stdin while providing editing facilities. @@ -2923,10 +2966,10 @@ static int read_i(void) } else if (tmp) { - wcstring command = tmp; - update_buff_pos(0); - data->command_line.clear(); - data->command_line_changed(); + const wcstring command = tmp; + update_buff_pos(&data->command_line, 0); + data->command_line.text.clear(); + data->command_line_changed(&data->command_line); reader_run_command(parser, command); if (data->end_loop) { @@ -3018,19 +3061,16 @@ const wchar_t *reader_readline(void) /* Coalesce redundant repaints. When we get a repaint, we set this to true, and skip repaints until we get something else. */ bool coalescing_repaints = false; - /* The cycle index in our completion list */ - size_t completion_cycle_idx = (size_t)(-1); - /* The command line before completion */ - wcstring cycle_command_line; - size_t cycle_cursor_pos = 0; + data->cycle_command_line.clear(); + data->cycle_cursor_pos = 0; data->search_buff.clear(); data->search_mode = NO_SEARCH; exec_prompt(); - reader_super_highlight_me_plenty(data->buff_pos); + reader_super_highlight_me_plenty(); s_reset(&data->screen, screen_reset_abandon_line); reader_repaint(); @@ -3090,7 +3130,7 @@ const wchar_t *reader_readline(void) break; } - insert_string(arr); + insert_string(&data->command_line, arr); } } @@ -3106,31 +3146,47 @@ const wchar_t *reader_readline(void) if (last_char != R_YANK && last_char != R_YANK_POP) yank_len=0; - const wchar_t *buff = data->command_line.c_str(); - switch (c) + /* Restore the text */ + if (c == R_CANCEL && data->is_navigating_pager_contents()) { + set_command_line_and_position(&data->command_line, data->cycle_command_line, data->cycle_cursor_pos); + } + + + /* Clear the pager if necessary */ + bool focused_on_search_field = (data->active_edit_line() == &data->pager.search_field_line); + if (command_ends_paging(c, focused_on_search_field)) + { + clear_pager(); + } + + //fprintf(stderr, "\n\nchar: %ls\n\n", describe_char(c).c_str()); + switch (c) + { /* go to beginning of line*/ case R_BEGINNING_OF_LINE: { - while ((data->buff_pos>0) && - (buff[data->buff_pos-1] != L'\n')) + editable_line_t *el = data->active_edit_line(); + while (el->position > 0 && el->text.at(el->position - 1) != L'\n') { - update_buff_pos(data->buff_pos - 1); + update_buff_pos(el, el->position - 1); } - reader_repaint(); + reader_repaint_needed(); break; } 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] && - buff[data->buff_pos] != L'\n') + const wchar_t *buff = el->text.c_str(); + while (buff[el->position] && + buff[el->position] != L'\n') { - update_buff_pos(data->buff_pos + 1); + update_buff_pos(el, el->position + 1); } } else @@ -3138,31 +3194,35 @@ const wchar_t *reader_readline(void) accept_autosuggestion(true); } - reader_repaint(); + reader_repaint_needed(); break; } case R_BEGINNING_OF_BUFFER: { - update_buff_pos(0); - - reader_repaint(); + update_buff_pos(&data->command_line, 0); + reader_repaint_needed(); break; } /* go to EOL*/ case R_END_OF_BUFFER: { - update_buff_pos(data->command_length()); + update_buff_pos(&data->command_line, data->command_line.size()); - reader_repaint(); + reader_repaint_needed(); break; } case R_NULL: { - reader_repaint_if_needed(); + break; + } + + case R_CANCEL: + { + // The only thing we can cancel right now is paging, which we handled up above break; } @@ -3189,26 +3249,25 @@ const wchar_t *reader_readline(void) /* complete */ case R_COMPLETE: + case R_COMPLETE_AND_SEARCH: { if (!data->complete_func) break; - if (! comp_empty && last_char == R_COMPLETE) + /* Use the command line only; it doesn't make sense to complete in any other line */ + editable_line_t *el = &data->command_line; + if (data->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 */ - const completion_t *next_comp = cycle_competions(comp, cycle_command_line, &completion_cycle_idx); - if (next_comp != NULL) + /* The user typed R_COMPLETE more than once in a row. If we are not yet fully disclosed, then become so; otherwise cycle through our available completions. */ + if (data->current_page_rendering.remaining_to_disclose > 0) { - size_t cursor_pos = cycle_cursor_pos; - const wcstring new_cmd_line = completion_apply_to_command_line(next_comp->completion, next_comp->flags, cycle_command_line, &cursor_pos, false); - reader_set_buffer(new_cmd_line, cursor_pos); - - /* Since we just inserted a completion, don't immediately do a new autosuggestion */ - data->suppress_autosuggestion = true; - - /* Trigger repaint (see #765) */ - reader_repaint_if_needed(); + data->pager.set_fully_disclosed(true); + reader_repaint_needed(); + } + else + { + select_completion_in_direction(c == R_COMPLETE ? direction_next : direction_prev); } } else @@ -3216,24 +3275,24 @@ const wchar_t *reader_readline(void) /* 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. */ - if (is_backslashed(data->command_line, data->buff_pos)) + if (is_backslashed(el->text, el->position)) { remove_backward(); } /* Get the string; we have to do this after removing any trailing backslash */ - const wchar_t * const buff = data->command_line.c_str(); + const wchar_t * const buff = el->text.c_str(); /* Clear the completion list */ 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. */ 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 */ 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. */ if (token_end > cmdsub_end) token_end = cmdsub_end; @@ -3242,9 +3301,9 @@ const wchar_t *reader_readline(void) size_t end_of_token_offset = token_end - buff; /* Move the cursor to the end */ - if (data->buff_pos != end_of_token_offset) + if (el->position != end_of_token_offset) { - update_buff_pos(end_of_token_offset); + update_buff_pos(el, end_of_token_offset); reader_repaint(); } @@ -3259,16 +3318,20 @@ const wchar_t *reader_readline(void) prioritize_completions(comp); /* Record our cycle_command_line */ - cycle_command_line = data->command_line; - cycle_cursor_pos = data->buff_pos; + data->cycle_command_line = el->text; + data->cycle_cursor_pos = el->position; - comp_empty = handle_completions(comp); + bool continue_after_prefix_insertion = (c == R_COMPLETE_AND_SEARCH); + comp_empty = handle_completions(comp, continue_after_prefix_insertion); - /* Start the cycle at the beginning */ - completion_cycle_idx = (size_t)(-1); + /* Show the search field if requested and if we printed a list of completions */ + if (c == R_COMPLETE_AND_SEARCH && ! comp_empty && ! data->pager.empty()) + { + data->pager.set_search_field_shown(true); + select_completion_in_direction(direction_next); + reader_repaint_needed(); + } - /* Repaint */ - reader_repaint_if_needed(); } break; @@ -3277,8 +3340,9 @@ const wchar_t *reader_readline(void) /* kill */ case R_KILL_LINE: { - const wchar_t *buff = data->command_line.c_str(); - const wchar_t *begin = &buff[data->buff_pos]; + editable_line_t *el = data->active_edit_line(); + const wchar_t *buff = el->text.c_str(); + const wchar_t *begin = &buff[el->position]; const wchar_t *end = begin; while (*end && *end != L'\n') @@ -3290,7 +3354,7 @@ const wchar_t *reader_readline(void) size_t len = end-begin; if (len) { - reader_kill(begin - buff, len, KILL_APPEND, last_char!=R_KILL_LINE); + reader_kill(el, begin - buff, len, KILL_APPEND, last_char!=R_KILL_LINE); } break; @@ -3298,10 +3362,11 @@ const wchar_t *reader_readline(void) case R_BACKWARD_KILL_LINE: { - if (data->buff_pos > 0) + editable_line_t *el = data->active_edit_line(); + if (el->position > 0) { - const wchar_t *buff = data->command_line.c_str(); - 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; /* Make sure we delete at least one character (see #580) */ @@ -3319,7 +3384,7 @@ const wchar_t *reader_readline(void) size_t len = maxi<size_t>(end-begin, 1); begin = end - len; - reader_kill(begin - buff, len, KILL_PREPEND, last_char!=R_BACKWARD_KILL_LINE); + reader_kill(el, begin - buff, len, KILL_PREPEND, last_char!=R_BACKWARD_KILL_LINE); } break; @@ -3327,8 +3392,9 @@ const wchar_t *reader_readline(void) case R_KILL_WHOLE_LINE: { - const wchar_t *buff = data->command_line.c_str(); - const wchar_t *end = &buff[data->buff_pos]; + editable_line_t *el = data->active_edit_line(); + const wchar_t *buff = el->text.c_str(); + const wchar_t *end = &buff[el->position]; const wchar_t *begin = end; size_t len; @@ -3351,7 +3417,7 @@ const wchar_t *reader_readline(void) if (len) { - reader_kill(begin - buff, len, KILL_APPEND, last_char!=R_KILL_WHOLE_LINE); + reader_kill(el, begin - buff, len, KILL_APPEND, last_char!=R_KILL_WHOLE_LINE); } break; @@ -3361,7 +3427,7 @@ const wchar_t *reader_readline(void) case R_YANK: { yank_str = kill_yank(); - insert_string(yank_str); + insert_string(data->active_edit_line(), yank_str); yank_len = wcslen(yank_str); break; } @@ -3375,7 +3441,7 @@ const wchar_t *reader_readline(void) remove_backward(); yank_str = kill_yank_rotate(); - insert_string(yank_str); + insert_string(data->active_edit_line(), yank_str); yank_len = wcslen(yank_str); } break; @@ -3399,9 +3465,8 @@ const wchar_t *reader_readline(void) reader_replace_current_token(data->search_buff.c_str()); } data->search_buff.clear(); - reader_super_highlight_me_plenty(data->buff_pos); - reader_repaint(); - + reader_super_highlight_me_plenty(); + reader_repaint_needed(); } break; @@ -3421,9 +3486,10 @@ const wchar_t *reader_readline(void) Remove the current character in the character buffer and on the 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()) { - update_buff_pos(data->buff_pos + 1); + update_buff_pos(el, el->position + 1); remove_backward(); } break; @@ -3439,18 +3505,31 @@ const wchar_t *reader_readline(void) /* Delete any autosuggestion */ data->autosuggestion.clear(); + /* If the user hits return while navigating the pager, it only clears the pager */ + if (data->is_navigating_pager_contents()) + { + clear_pager(); + break; + } + + /* The user may have hit return with pager contents, but while not navigating them. Clear the pager in that event. */ + clear_pager(); + + /* We only execute the command line */ + 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) */ - if (is_backslashed(data->command_line, data->buff_pos)) + if (is_backslashed(el->text, el->position)) { - if (data->buff_pos >= data->command_length() || iswspace(data->command_line.at(data->buff_pos))) + if (el->position >= el->size() || iswspace(el->text.at(el->position))) { - insert_char('\n'); + insert_char(el, '\n'); break; } } /* See if this command is valid */ - int command_test_result = data->test_func(data->command_line.c_str()); + int command_test_result = data->test_func(el->text.c_str()); 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. */ @@ -3458,8 +3537,11 @@ const wchar_t *reader_readline(void) if (abbreviation_expanded) { /* It's our reponsibility to rehighlight and repaint. But everything we do below triggers a repaint. */ - reader_super_highlight_me_plenty(data->buff_pos); - command_test_result = data->test_func(data->command_line.c_str()); + command_test_result = data->test_func(el->text.c_str()); + + /* If the command is OK, then we're going to execute it. We still want to do syntax highlighting, but a synchronous variant that performs no I/O, so as not to block the user */ + bool skip_io = (command_test_result == 0); + reader_super_highlight_me_plenty(0, skip_io); } } @@ -3469,12 +3551,13 @@ const wchar_t *reader_readline(void) case 0: { /* Finished command, execute it. Don't add items that start with a leading space. */ - if (data->history != NULL && ! data->command_line.empty() && data->command_line.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_line); + data->history->add_with_file_detection(el->text); } finished=1; - update_buff_pos(data->command_length()); + update_buff_pos(&data->command_line, data->command_line.size()); reader_repaint(); break; } @@ -3484,7 +3567,7 @@ const wchar_t *reader_readline(void) */ case PARSER_TEST_INCOMPLETE: { - insert_char('\n'); + insert_char(el, '\n'); break; } @@ -3496,7 +3579,7 @@ const wchar_t *reader_readline(void) default: { s_reset(&data->screen, screen_reset_abandon_line); - reader_repaint(); + reader_repaint_needed(); break; } @@ -3512,7 +3595,6 @@ const wchar_t *reader_readline(void) case R_HISTORY_TOKEN_SEARCH_FORWARD: { int reset = 0; - if (data->search_mode == NO_SEARCH) { reset = 1; @@ -3526,7 +3608,8 @@ const wchar_t *reader_readline(void) data->search_mode = TOKEN_SEARCH; } - data->search_buff.append(data->command_line); + 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); /* Skip the autosuggestion as history unless it was truncated */ @@ -3539,7 +3622,6 @@ const wchar_t *reader_readline(void) switch (data->search_mode) { - case LINE_SEARCH: { if ((c == R_HISTORY_SEARCH_BACKWARD) || @@ -3565,7 +3647,7 @@ const wchar_t *reader_readline(void) { 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; } @@ -3593,10 +3675,15 @@ const wchar_t *reader_readline(void) /* Move left*/ case R_BACKWARD_CHAR: { - if (data->buff_pos > 0) + editable_line_t *el = data->active_edit_line(); + if (data->is_navigating_pager_contents()) { - update_buff_pos(data->buff_pos - 1); - reader_repaint(); + select_completion_in_direction(direction_west); + } + else if (el->position > 0) + { + update_buff_pos(el, el->position-1); + reader_repaint_needed(); } break; } @@ -3604,10 +3691,15 @@ const wchar_t *reader_readline(void) /* Move right*/ case R_FORWARD_CHAR: { - if (data->buff_pos < data->command_length()) + editable_line_t *el = data->active_edit_line(); + if (data->is_navigating_pager_contents()) { - update_buff_pos(data->buff_pos + 1); - reader_repaint(); + select_completion_in_direction(direction_east); + } + else if (el->position < el->size()) + { + update_buff_pos(el, el->position + 1); + reader_repaint_needed(); } else { @@ -3622,30 +3714,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); 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; } /* kill one word right */ 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; } /* move one word left*/ 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; } /* move one word right*/ 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 { @@ -3656,12 +3749,13 @@ const wchar_t *reader_readline(void) case R_BEGINNING_OF_HISTORY: { - data->history_search = history_search_t(*data->history, data->command_line, 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(); if (! data->history_search.is_at_end()) { 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; @@ -3676,41 +3770,74 @@ const wchar_t *reader_readline(void) case R_UP_LINE: case R_DOWN_LINE: { - int line_old = parse_util_get_line_from_offset(data->command_line, data->buff_pos); - int line_new; - - if (c == R_UP_LINE) - line_new = line_old-1; + if (data->is_navigating_pager_contents()) + { + /* We are already navigating pager contents. */ + selection_direction_t direction; + if (c == R_DOWN_LINE) + { + /* Down arrow is always south */ + direction = direction_south; + } + else if (selection_is_at_top()) + { + /* Up arrow, but we are in the first column and first row. End navigation */ + direction = direction_deselect; + } + else + { + /* Up arrow, go north */ + direction = direction_north; + } + + /* Now do the selection */ + select_completion_in_direction(direction); + } + else if (c == R_DOWN_LINE && ! data->pager.empty()) + { + /* We pressed down with a non-empty pager contents, begin navigation */ + select_completion_in_direction(direction_south); + } else - line_new = line_old+1; - - int line_count = parse_util_lineno(data->command_line.c_str(), data->command_length())-1; - - if (line_new >= 0 && line_new <= line_count) { - size_t base_pos_new; - size_t base_pos_old; - - int indent_old; - int indent_new; - size_t line_offset_old; - size_t total_offset_new; - - base_pos_new = parse_util_get_offset_from_line(data->command_line, line_new); - - base_pos_old = parse_util_get_offset_from_line(data->command_line, line_old); - - assert(base_pos_new != (size_t)(-1) && base_pos_old != (size_t)(-1)); - indent_old = data->indents.at(base_pos_old); - indent_new = data->indents.at(base_pos_new); - - line_offset_old = data->buff_pos - parse_util_get_offset_from_line(data->command_line, line_old); - total_offset_new = parse_util_get_offset(data->command_line, line_new, line_offset_old - 4*(indent_new-indent_old)); - - update_buff_pos(total_offset_new); - reader_repaint(); + /* Not navigating the pager contents */ + editable_line_t *el = data->active_edit_line(); + int line_old = parse_util_get_line_from_offset(el->text, el->position); + int line_new; + + if (c == R_UP_LINE) + line_new = line_old-1; + else + line_new = line_old+1; + + int line_count = parse_util_lineno(el->text.c_str(), el->size())-1; + + if (line_new >= 0 && line_new <= line_count) + { + size_t base_pos_new; + size_t base_pos_old; + + int indent_old; + int indent_new; + size_t line_offset_old; + size_t total_offset_new; + + base_pos_new = parse_util_get_offset_from_line(el->text, line_new); + + 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)); + indent_old = data->indents.at(base_pos_old); + indent_new = data->indents.at(base_pos_new); + + line_offset_old = el->position - parse_util_get_offset_from_line(el->text, line_old); + total_offset_new = parse_util_get_offset(el->text, line_new, line_offset_old - 4*(indent_new-indent_old)); + update_buff_pos(el, total_offset_new); + el->position = total_offset_new; + reader_repaint_needed(); + } } - + break; } @@ -3718,7 +3845,7 @@ const wchar_t *reader_readline(void) { data->suppress_autosuggestion = true; data->autosuggestion.clear(); - reader_repaint(); + reader_repaint_needed(); break; } @@ -3730,44 +3857,46 @@ const wchar_t *reader_readline(void) case R_TRANSPOSE_CHARS: { - if (data->command_length() < 2) + editable_line_t *el = data->active_edit_line(); + if (el->size() < 2) { break; } /* If the cursor is at the end, transpose the last two characters of the line */ - if (data->buff_pos == data->command_length()) + if (el->position == el->size()) { - update_buff_pos(data->buff_pos - 1); + update_buff_pos(el, el->position - 1); } /* Drag the character before the cursor forward over the character at the cursor, moving the cursor forward as well. */ - if (data->buff_pos > 0) + if (el->position > 0) { - wcstring local_cmd = data->command_line; - std::swap(local_cmd.at(data->buff_pos), local_cmd.at(data->buff_pos-1)); - set_command_line_and_position(local_cmd, data->buff_pos + 1); + wcstring local_cmd = el->text; + std::swap(local_cmd.at(el->position), local_cmd.at(el->position-1)); + set_command_line_and_position(el, local_cmd, el->position + 1); } break; } case R_TRANSPOSE_WORDS: { - size_t len = data->command_length(); - const wchar_t *buff = data->command_line.c_str(); + editable_line_t *el = data->active_edit_line(); + size_t len = el->size(); + const wchar_t *buff = el->text.c_str(); const wchar_t *tok_begin, *tok_end, *prev_begin, *prev_end; /* If we are not in a token, look for one ahead */ - size_t buff_pos = data->buff_pos; + size_t buff_pos = el->position; while (buff_pos != len && !iswalnum(buff[buff_pos])) buff_pos++; - update_buff_pos(buff_pos); + update_buff_pos(el, buff_pos); - 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... */ if (tok_begin == &buff[len]) @@ -3792,7 +3921,7 @@ const wchar_t *reader_readline(void) new_buff.append(prev); new_buff.append(trail); /* 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; } @@ -3801,15 +3930,16 @@ const wchar_t *reader_readline(void) case R_DOWNCASE_WORD: case R_CAPITALIZE_WORD: { + editable_line_t *el = data->active_edit_line(); // For capitalize_word, whether we've capitalized a character so far bool capitalized_first = false; // We apply the operation from the current location to the end of the word - size_t pos = data->buff_pos; - move_word(MOVE_DIR_RIGHT, false, move_word_style_punctuation, false); - for (; pos < data->buff_pos; pos++) + size_t pos = el->position; + move_word(el, MOVE_DIR_RIGHT, false, move_word_style_punctuation, false); + for (; pos < el->position; pos++) { - wchar_t chr = data->command_line.at(pos); + wchar_t chr = el->text.at(pos); // We always change the case; this decides whether we go uppercase (true) or lowercase (false) bool make_uppercase; @@ -3824,29 +3954,29 @@ const wchar_t *reader_readline(void) else chr = towlower(chr); - data->command_line.at(pos) = chr; + data->command_line.text.at(pos) = chr; capitalized_first = capitalized_first || make_uppercase; } - data->command_line_changed(); - reader_super_highlight_me_plenty(data->buff_pos); - reader_repaint(); + data->command_line_changed(el); + reader_super_highlight_me_plenty(); + reader_repaint_needed(); break; } case R_BEGIN_SELECTION: { data->sel_active = true; - data->sel_begin_pos = data->buff_pos; - data->sel_start_pos = data->buff_pos; - data->sel_stop_pos = data->buff_pos; + data->sel_begin_pos = data->command_line.position; + data->sel_start_pos = data->command_line.position; + data->sel_stop_pos = data->command_line.position; break; } case R_END_SELECTION: { data->sel_active = false; - data->sel_start_pos = data->buff_pos; - data->sel_stop_pos = data->buff_pos; + data->sel_start_pos = data->command_line.position; + data->sel_stop_pos = data->command_line.position; break; } @@ -3854,50 +3984,51 @@ const wchar_t *reader_readline(void) { bool newv = (last_char != R_KILL_SELECTION); size_t start, len; - if(reader_get_selection(start, len)) + if (reader_get_selection(&start, &len)) { - reader_kill(start, len, KILL_APPEND, newv); + reader_kill(&data->command_line, start, len, KILL_APPEND, newv); } break; } case R_FORWARD_JUMP: { + editable_line_t *el = data->active_edit_line(); wchar_t target = input_function_pop_arg(); - size_t len = data->command_length(); bool status = false; - for(int i = data->buff_pos + 1; i < len; i++) + for (size_t i = el->position + 1; i < el->size(); i++) { - if(buff[i] == target) + if (el->at(i) == target) { - update_buff_pos(i); + update_buff_pos(el, i); status = true; break; } } input_function_set_status(status); - reader_repaint(); + reader_repaint_needed(); break; } case R_BACKWARD_JUMP: { + editable_line_t *el = data->active_edit_line(); wchar_t target = input_function_pop_arg(); - size_t len = data->command_length(); bool status = false; - for(int i = data->buff_pos - 1; i >= 0; i--) + size_t tmp_pos = el->position; + while (tmp_pos--) { - if(buff[i] == target) + if (el->at(tmp_pos) == target) { - update_buff_pos(i); + update_buff_pos(el, tmp_pos); status = true; break; } } input_function_set_status(status); - reader_repaint(); + reader_repaint_needed(); break; } @@ -3906,11 +4037,28 @@ const wchar_t *reader_readline(void) { if ((!wchar_private(c)) && (((c>31) || (c==L'\n'))&& (c != 127))) { + bool should_expand_abbreviations = false; + if (data->is_navigating_pager_contents()) + { + data->pager.set_search_field_shown(true); + } + else + { /* Expand abbreviations after space */ - bool should_expand_abbreviations = (c == L' '); + should_expand_abbreviations = (c == L' '); + } /* Regular character */ - insert_char(c, should_expand_abbreviations); + editable_line_t *el = data->active_edit_line(); + insert_char(data->active_edit_line(), c, should_expand_abbreviations); + + /* End paging upon inserting into the normal command line */ + if (el == &data->command_line) + { + clear_pager(); + } + + } else { @@ -3939,10 +4087,20 @@ const wchar_t *reader_readline(void) } last_char = c; + + reader_repaint_if_needed(); } writestr(L"\n"); + /* Ensure we have no pager contents when we exit */ + if (! data->pager.empty()) + { + /* Clear to end of screen to erase the pager contents. TODO: this may fail if eos doesn't exist, in which case we should emit newlines */ + screen_force_clear_to_end(); + data->pager.clear(); + } + if (!reader_exit_forced()) { if (tcsetattr(0,TCSANOW,&old_modes)) /* return to previous mode */ @@ -3953,7 +4111,7 @@ const wchar_t *reader_readline(void) set_color(rgb_color_t::reset(), rgb_color_t::reset()); } - return finished ? data->command_line.c_str() : 0; + return finished ? data->command_line.text.c_str() : NULL; } int reader_search_mode() @@ -3966,6 +4124,45 @@ int reader_search_mode() return !!data->search_mode; } +int reader_has_pager_contents() +{ + if (!data) + { + return -1; + } + + return ! data->current_page_rendering.screen_data.empty(); +} + +void reader_selected_completion_changed(pager_t *pager) +{ + /* Only interested in the top level pager */ + if (data == NULL || pager != &data->pager) + return; + + const completion_t *completion = pager->selected_completion(data->current_page_rendering); + + /* Update the cursor and command line */ + size_t cursor_pos = data->cycle_cursor_pos; + wcstring new_cmd_line; + + if (completion == NULL) + { + new_cmd_line = data->cycle_command_line; + } + else + { + new_cmd_line = completion_apply_to_command_line(completion->completion, completion->flags, data->cycle_command_line, &cursor_pos, false); + } + reader_set_buffer_maintaining_pager(new_cmd_line, cursor_pos); + + /* Since we just inserted a completion, don't immediately do a new autosuggestion */ + data->suppress_autosuggestion = true; + + /* Trigger repaint (see #765) */ + reader_repaint_needed(); +} + /** Read non-interactively. Read input from stdin without displaying |