From 9edf9ad2acb61a3d7e523c4903abda62655e45c9 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Tue, 21 Jan 2014 16:08:35 -0800 Subject: Correct the correspondence between name_arr and the input codes. --- input.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'input.h') diff --git a/input.h b/input.h index c5722b2f..3818859b 100644 --- a/input.h +++ b/input.h @@ -14,6 +14,8 @@ inputrc information for key bindings. /** Key codes for inputrc-style keyboard functions that are passed on to the caller of input_read() + + NOTE: IF YOU MODIFY THIS YOU MUST UPDATE THE name_arr AND code_arr VARIABLES TO MATCH! */ enum { @@ -38,7 +40,6 @@ enum R_KILL_WORD, R_BACKWARD_KILL_WORD, R_BACKWARD_KILL_PATH_COMPONENT, - R_DUMP_FUNCTIONS, R_HISTORY_TOKEN_SEARCH_BACKWARD, R_HISTORY_TOKEN_SEARCH_FORWARD, R_SELF_INSERT, @@ -57,8 +58,9 @@ enum R_DOWN_LINE, R_SUPPRESS_AUTOSUGGESTION, R_ACCEPT_AUTOSUGGESTION -} -; +}; + +wcstring describe_char(wchar_t c); /** Initialize the terminal by calling setupterm, and set up arrays -- cgit v1.2.3 From 5be3606236bd2185d93162fcad097d8c30f84885 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 27 Jan 2014 00:56:13 -0800 Subject: Increased support for completion search field. Use btab (shift-tab) to complete-and-search. --- builtin.cpp | 37 +--- input.cpp | 2 + input.h | 1 + input_common.cpp | 1 - pager.cpp | 200 +++++++++++++---- pager.h | 32 ++- reader.cpp | 284 +++++++++++++++++-------- reader.h | 9 + screen.cpp | 17 +- screen.h | 5 +- share/functions/fish_default_key_bindings.fish | 4 + 11 files changed, 425 insertions(+), 167 deletions(-) (limited to 'input.h') diff --git a/builtin.cpp b/builtin.cpp index 17b0caaa..81738ffb 100644 --- a/builtin.cpp +++ b/builtin.cpp @@ -571,35 +571,14 @@ static int builtin_bind(parser_t &parser, wchar_t **argv) static const struct woption long_options[] = { - { - L"all", no_argument, 0, 'a' - } - , - { - L"erase", no_argument, 0, 'e' - } - , - { - L"function-names", no_argument, 0, 'f' - } - , - { - L"help", no_argument, 0, 'h' - } - , - { - L"key", no_argument, 0, 'k' - } - , - { - L"key-names", no_argument, 0, 'K' - } - , - { - 0, 0, 0, 0 - } - } - ; + { L"all", no_argument, 0, 'a' }, + { L"erase", no_argument, 0, 'e' }, + { L"function-names", no_argument, 0, 'f' }, + { L"help", no_argument, 0, 'h' }, + { L"key", no_argument, 0, 'k' }, + { L"key-names", no_argument, 0, 'K' }, + { 0, 0, 0, 0 } + }; while (1) { diff --git a/input.cpp b/input.cpp index 8327de38..ccdbc240 100644 --- a/input.cpp +++ b/input.cpp @@ -104,6 +104,7 @@ static const wchar_t * const name_arr[] = L"yank", L"yank-pop", L"complete", + L"complete-and-search", L"beginning-of-history", L"end-of-history", L"backward-kill-line", @@ -201,6 +202,7 @@ static const wchar_t code_arr[] = R_YANK, R_YANK_POP, R_COMPLETE, + R_COMPLETE_AND_SEARCH, R_BEGINNING_OF_HISTORY, R_END_OF_HISTORY, R_BACKWARD_KILL_LINE, diff --git a/input.h b/input.h index 3818859b..2d71a5ae 100644 --- a/input.h +++ b/input.h @@ -33,6 +33,7 @@ enum R_YANK, R_YANK_POP, R_COMPLETE, + R_COMPLETE_AND_SEARCH, R_BEGINNING_OF_HISTORY, R_END_OF_HISTORY, R_BACKWARD_KILL_LINE, diff --git a/input_common.cpp b/input_common.cpp index 77c8f168..d26f30b9 100644 --- a/input_common.cpp +++ b/input_common.cpp @@ -251,7 +251,6 @@ wchar_t input_common_readch(int timed) case 0: return 0; default: - return res; } } diff --git a/pager.cpp b/pager.cpp index e05ac380..49fc6a8e 100644 --- a/pager.cpp +++ b/pager.cpp @@ -3,6 +3,7 @@ #include "pager.h" #include "highlight.h" #include "input_common.h" +#include "wutil.h" #include #include @@ -18,9 +19,19 @@ typedef std::vector comp_info_list_t; /** The maximum number of columns of completion to attempt to fit onto the screen */ #define PAGER_MAX_COLS 6 -/* Returns numer / denom, rounding up */ +/** Width of the search field */ +#define PAGER_SEARCH_FIELD_WIDTH 12 + +/** Text we use for the search field */ +#define SEARCH_FIELD_PROMPT _(L"search: ") + +/* Returns numer / denom, rounding up. As a "courtesy" 0/0 is 0. */ static size_t divide_round_up(size_t numer, size_t denom) { + if (numer == 0) + return 0; + + assert(denom > 0); return numer / denom + (numer % denom ? 1 : 0); } @@ -336,17 +347,66 @@ void pager_t::measure_completion_infos(comp_info_list_t *infos, const wcstring & recalc_min_widths(infos); } +/* Indicates if the given completion info passes any filtering we have */ +bool pager_t::completion_info_passes_filter(const comp_t &info) const +{ + /* If we have no filter, everything passes */ + if (! search_field_shown || this->search_field_line.empty()) + return true; + + const wcstring &needle = this->search_field_line.text; + + /* We do substring matching */ + const fuzzy_match_type_t limit = fuzzy_match_substring; + + /* Match against the description */ + if (string_fuzzy_match_string(needle, info.desc, limit).type != fuzzy_match_none) + { + return true; + } + + /* Match against the completion strings */ + for (size_t i=0; i < info.comp.size(); i++) + { + if (string_fuzzy_match_string(needle, info.comp.at(i), limit).type != fuzzy_match_none) + { + return true; + } + } + + /* No match */ + return false; +} + +/* Update completion_infos from unfiltered_completion_infos, to reflect the filter */ +void pager_t::refilter_completions() +{ + this->completion_infos.clear(); + for (size_t i=0; i < this->unfiltered_completion_infos.size(); i++) + { + const comp_t &info = this->unfiltered_completion_infos.at(i); + if (this->completion_info_passes_filter(info)) + { + this->completion_infos.push_back(info); + } + } + note_selection_changed(); +} + void pager_t::set_completions(const completion_list_t &raw_completions) { // Get completion infos out of it - completion_infos = process_completions_into_infos(raw_completions, prefix.c_str()); + unfiltered_completion_infos = process_completions_into_infos(raw_completions, prefix.c_str()); // Maybe join them if (prefix == L"-") - join_completions(&completion_infos); + join_completions(&unfiltered_completion_infos); // Compute their various widths - measure_completion_infos(&completion_infos, prefix); + measure_completion_infos(&unfiltered_completion_infos, prefix); + + // Refilter them + this->refilter_completions(); } void pager_t::set_prefix(const wcstring &pref) @@ -390,7 +450,7 @@ bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const co /* Compute the effective term width and term height, accounting for disclosure */ int term_width = this->available_term_width; - int term_height = this->available_term_height - 1; // we always subtract 1 to make room for a comment row + int term_height = this->available_term_height - 1 - (search_field_shown ? 1 : 0); // we always subtract 1 to make room for a comment row if (! this->fully_disclosed) term_height = mini(term_height, PAGER_UNDISCLOSED_MAX_ROWS); @@ -551,6 +611,11 @@ bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const co /* We have a scrollable interface. The +1 here is because we are zero indexed, but want to present things as 1-indexed. We do not add 1 to stop_row or row_count because these are the "past the last value" */ progress_text = format_string(L"rows %lu to %lu of %lu", start_row + 1, stop_row, row_count); } + else if (completion_infos.empty() && ! unfiltered_completion_infos.empty()) + { + /* Everything is filtered */ + progress_text = L"(no matches)"; + } if (! progress_text.empty()) { @@ -561,11 +626,17 @@ bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const co if (search_field_shown) { /* Add the search field */ - wcstring spaces(8, L' '); - spaces.insert(spaces.begin(), 1, L'h'); + wcstring search_field_text = search_field_line.text; + /* Append spaces to make it at least the required width */ + if (search_field_text.size() < PAGER_SEARCH_FIELD_WIDTH) + { + search_field_text.append(PAGER_SEARCH_FIELD_WIDTH - search_field_text.size(), L' '); + } line_t *search_field = &rendering->screen_data.insert_line_at_index(0); - int search_field_written = print_max(L"filter: ", highlight_spec_normal, term_width, false, search_field); - search_field_written += print_max(spaces, highlight_modifier_force_underline, term_width - search_field_written, false, search_field); + + /* We limit the width to term_width - 1 */ + int search_field_written = print_max(SEARCH_FIELD_PROMPT, highlight_spec_normal, term_width - 1, false, search_field); + search_field_written += print_max(search_field_text, highlight_modifier_force_underline, term_width - search_field_written - 1, false, search_field); } } @@ -585,33 +656,32 @@ page_rendering_t pager_t::render() const page_rendering_t rendering; rendering.term_width = this->available_term_width; rendering.term_height = this->available_term_height; + rendering.search_field_shown = this->search_field_shown; + rendering.search_field_line = this->search_field_line; - if (! this->empty()) + for (int cols = PAGER_MAX_COLS; cols > 0; cols--) { - for (int cols = PAGER_MAX_COLS; cols > 0; cols--) + /* Initially empty rendering */ + rendering.screen_data.resize(0); + + /* Determine how many rows we would need if we had 'cols' columns. Then determine how many columns we want from that. For example, say we had 19 completions. We can fit them into 6 columns, 4 rows, with the last row containing only 1 entry. Or we can fit them into 5 columns, 4 rows, the last row containing 4 entries. Since fewer columns with the same number of rows is better, skip cases where we know we can do better. */ + size_t min_rows_required_for_cols = divide_round_up(completion_infos.size(), cols); + size_t min_cols_required_for_rows = divide_round_up(completion_infos.size(), min_rows_required_for_cols); + + assert(min_cols_required_for_rows <= cols); + if (cols > 1 && min_cols_required_for_rows < cols) { - /* Initially empty rendering */ - rendering.screen_data.resize(0); - - /* Determine how many rows we would need if we had 'cols' columns. Then determine how many columns we want from that. For example, say we had 19 completions. We can fit them into 6 columns, 4 rows, with the last row containing only 1 entry. Or we can fit them into 5 columns, 4 rows, the last row containing 4 entries. Since fewer columns with the same number of rows is better, skip cases where we know we can do better. */ - size_t min_rows_required_for_cols = divide_round_up(completion_infos.size(), cols); - size_t min_cols_required_for_rows = divide_round_up(completion_infos.size(), min_rows_required_for_cols); - - assert(min_cols_required_for_rows <= cols); - if (min_cols_required_for_rows < cols) - { - /* Next iteration will be better, so skip this one */ - continue; - } - - rendering.cols = (size_t)cols; - rendering.rows = min_rows_required_for_cols; - rendering.selected_completion_idx = this->visual_selected_completion_index(rendering.rows, rendering.cols); - - if (completion_try_print(cols, prefix, completion_infos, &rendering, suggested_row_start)) - { - break; - } + /* Next iteration will be better, so skip this one */ + continue; + } + + rendering.cols = (size_t)cols; + rendering.rows = min_rows_required_for_cols; + rendering.selected_completion_idx = this->visual_selected_completion_index(rendering.rows, rendering.cols); + + if (completion_try_print(cols, prefix, completion_infos, &rendering, suggested_row_start)) + { + break; } } return rendering; @@ -619,7 +689,12 @@ page_rendering_t pager_t::render() const void pager_t::update_rendering(page_rendering_t *rendering) const { - if (rendering->term_width != this->available_term_width || rendering->term_height != this->available_term_height || rendering->selected_completion_idx != this->visual_selected_completion_index(rendering->rows, rendering->cols)) + if (rendering->term_width != this->available_term_width || + rendering->term_height != this->available_term_height || + rendering->selected_completion_idx != this->visual_selected_completion_index(rendering->rows, rendering->cols) || + rendering->search_field_shown != this->search_field_shown || + rendering->search_field_line.text != this->search_field_line.text || + rendering->search_field_line.position != this->search_field_line.position) { *rendering = this->render(); } @@ -631,13 +706,13 @@ pager_t::pager_t() : available_term_width(0), available_term_height(0), selected bool pager_t::empty() const { - return completion_infos.empty(); + return unfiltered_completion_infos.empty(); } const completion_t *pager_t::select_next_completion_in_direction(selection_direction_t direction, const page_rendering_t &rendering) { /* Must have something to select */ - if (this->empty()) + if (this->completion_infos.empty()) { return NULL; } @@ -834,6 +909,12 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc size_t pager_t::visual_selected_completion_index(size_t rows, size_t cols) const { + /* No completions -> no selection */ + if (completion_infos.empty()) + { + return PAGER_SELECTION_NONE; + } + size_t result = selected_completion_idx; if (result != PAGER_SELECTION_NONE) { @@ -842,11 +923,21 @@ size_t pager_t::visual_selected_completion_index(size_t rows, size_t cols) const { result -= rows; } + + /* If we are still beyond the last selection, clamp it */ + if (result >= completion_infos.size()) + result = completion_infos.size() - 1; } assert(result == PAGER_SELECTION_NONE || result < completion_infos.size()); return result; } +/* It's possible we have no visual selection but are still navigating the contents, e.g. every completion is filtered */ +bool pager_t::is_navigating_contents() const +{ + return selected_completion_idx != PAGER_SELECTION_NONE; +} + const completion_t *pager_t::selected_completion(const page_rendering_t &rendering) const { const completion_t * result = NULL; @@ -861,25 +952,58 @@ const completion_t *pager_t::selected_completion(const page_rendering_t &renderi /* Get the selected row and column. Completions are rendered column first, i.e. we go south before we go west. So if we have N rows, and our selected index is N + 2, then our row is 2 (mod by N) and our column is 1 (divide by N) */ size_t pager_t::get_selected_row(const page_rendering_t &rendering) const { + if (rendering.rows == 0) + return PAGER_SELECTION_NONE; + return selected_completion_idx == PAGER_SELECTION_NONE ? PAGER_SELECTION_NONE : selected_completion_idx % rendering.rows; } size_t pager_t::get_selected_column(const page_rendering_t &rendering) const { + if (rendering.rows == 0) + return PAGER_SELECTION_NONE; + return selected_completion_idx == PAGER_SELECTION_NONE ? PAGER_SELECTION_NONE : selected_completion_idx / rendering.rows; } void pager_t::clear() { + unfiltered_completion_infos.clear(); completion_infos.clear(); prefix.clear(); selected_completion_idx = PAGER_SELECTION_NONE; fully_disclosed = false; search_field_shown = false; - search_field_string.clear(); + search_field_line.clear(); +} + +void pager_t::set_search_field_shown(bool flag) +{ + this->search_field_shown = flag; +} + +bool pager_t::is_search_field_shown() const +{ + return this->search_field_shown; +} + +size_t pager_t::cursor_position() const +{ + size_t result = wcslen(SEARCH_FIELD_PROMPT) + this->search_field_line.position; + /* Clamp it to the right edge */ + if (available_term_width > 0 && result + 1 > available_term_width) + { + result = available_term_width - 1; + } + return result; +} + +void pager_t::note_selection_changed() +{ + reader_selected_completion_changed(this); } /* Constructor */ -page_rendering_t::page_rendering_t() : term_width(-1), term_height(-1), rows(0), cols(0), row_start(0), row_end(0), selected_completion_idx(-1), remaining_to_disclose(0) +page_rendering_t::page_rendering_t() : term_width(-1), term_height(-1), rows(0), cols(0), row_start(0), row_end(0), selected_completion_idx(-1), remaining_to_disclose(0), search_field_shown(false) { } diff --git a/pager.h b/pager.h index 39af0335..922e9503 100644 --- a/pager.h +++ b/pager.h @@ -4,6 +4,7 @@ #include "complete.h" #include "screen.h" +#include "reader.h" /* Represents rendering from the pager */ class page_rendering_t @@ -20,6 +21,9 @@ class page_rendering_t size_t remaining_to_disclose; + bool search_field_shown; + editable_line_t search_field_line; + /* Returns a rendering with invalid data, useful to indicate "no rendering" */ page_rendering_t(); }; @@ -47,7 +51,6 @@ class pager_t /* Whether we show the search field */ bool search_field_shown; - wcstring search_field_string; /* Returns the index of the completion that should draw selected, using the given number of columns */ size_t visual_selected_completion_index(size_t rows, size_t cols) const; @@ -84,21 +87,33 @@ class pager_t private: typedef std::vector comp_info_list_t; + + /* The filtered list of completion infos */ comp_info_list_t completion_infos; + /* The unfiltered list. Note there's a lot of duplication here. */ + comp_info_list_t unfiltered_completion_infos; + wcstring prefix; + void note_selection_changed(); + bool completion_try_print(size_t cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering, size_t suggested_start_row) const; void recalc_min_widths(comp_info_list_t * lst) const; void measure_completion_infos(std::vector *infos, const wcstring &prefix) const; + bool completion_info_passes_filter(const comp_t &info) const; + void completion_print(size_t cols, int *width_per_column, size_t row_start, size_t row_stop, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const; line_t completion_print_item(const wcstring &prefix, const comp_t *c, size_t row, size_t column, int width, bool secondary, bool selected, page_rendering_t *rendering) const; public: + /* The text of the search field */ + editable_line_t search_field_line; + /* Sets the set of completions */ void set_completions(const completion_list_t &comp); @@ -130,6 +145,21 @@ class pager_t /* Clears all completions and the prefix */ void clear(); + /* Updates the completions list per the filter */ + void refilter_completions(); + + /* Sets whether the search field is shown */ + void set_search_field_shown(bool flag); + + /* Gets whether the search field shown */ + bool is_search_field_shown() const; + + /* Indicates if we are navigating our contents */ + bool is_navigating_contents() const; + + /* Position of the cursor */ + size_t cursor_position() const; + /* Constructor */ pager_t(); }; diff --git a/reader.cpp b/reader.cpp index 4eb3b0ee..492d8fb7 100644 --- a/reader.cpp +++ b/reader.cpp @@ -210,9 +210,6 @@ public: /** Current page rendering */ page_rendering_t current_page_rendering; - /** Whether we are navigating the pager */ - bool is_navigating_pager; - /** Whether autosuggesting is allowed at all */ bool allow_autosuggestion; @@ -254,14 +251,26 @@ public: /** The current position in search_prev */ size_t search_pos; + 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; + 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); /** Expand abbreviations at the current cursor position, minus backtrack_amt. */ bool expand_abbreviation_as_necessary(size_t cursor_backtrack); @@ -278,6 +287,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 @@ -343,13 +356,13 @@ public: /** Constructor */ reader_data_t() : - is_navigating_pager(0), allow_autosuggestion(0), suppress_autosuggestion(0), expand_abbreviations(0), history(0), token_history_pos(0), search_pos(0), + cycle_cursor_pos(0), complete_func(0), highlight_function(0), test_func(0), @@ -531,12 +544,12 @@ wcstring combine_command_and_autosuggestion(const wcstring &cmdline, const wcstr static void reader_repaint() { - editable_line_t *el = &data->command_line; + editable_line_t *cmd_line = &data->command_line; // Update the indentation - data->indents = parse_util_compute_indents(el->text); + data->indents = parse_util_compute_indents(cmd_line->text); // Combine the command and autosuggestion into one string - wcstring full_line = combine_command_and_autosuggestion(el->text, data->autosuggestion); + wcstring full_line = combine_command_and_autosuggestion(cmd_line->text, data->autosuggestion); size_t len = full_line.size(); if (len < 1) @@ -552,16 +565,20 @@ static void reader_repaint() // 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, - el->size(), + cmd_line->size(), &colors[0], &indents[0], - el->position, - data->current_page_rendering); + cursor_position, + data->current_page_rendering, + focused_on_pager); data->repaint_needed = false; } @@ -609,7 +626,7 @@ static void reader_kill(editable_line_t *el, size_t begin_idx, size_t length, in } el->text.erase(begin_idx, length); - data->command_line_changed(); + data->command_line_changed(el); reader_super_highlight_me_plenty(); reader_repaint(); @@ -650,19 +667,26 @@ 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 = this->command_line.size(); + 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(); - colors.resize(len, last_color); + /* 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(); + colors.resize(len, last_color); - indents.resize(len); + indents.resize(len); - /* Update the gen count */ - s_generation_count++; + /* Update the gen count */ + s_generation_count++; + } + else if (el == &this->pager.search_field_line) + { + this->pager.refilter_completions(); + } } /* Expand abbreviations at the given cursor position. Does NOT inspect 'data'. */ @@ -752,7 +776,7 @@ bool reader_data_t::expand_abbreviation_as_necessary(size_t cursor_backtrack) el->text.swap(new_cmdline); el->position = new_buff_pos; - data->command_line_changed(); + data->command_line_changed(el); result = true; } } @@ -1024,6 +1048,64 @@ 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_EXECUTE: + case R_ACCEPT_AUTOSUGGESTION: + 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; + + /* 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. @@ -1044,7 +1126,7 @@ static void remove_backward() el->text.erase(el->position, 1); } while (width == 0 && el->position > 0); - data->command_line_changed(); + data->command_line_changed(el); data->suppress_autosuggestion = true; reader_super_highlight_me_plenty(); @@ -1060,16 +1142,15 @@ 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; - editable_line_t *el = data->active_edit_line(); el->insert_string(str); - data->command_line_changed(); + data->command_line_changed(el); if (el == &data->command_line) { @@ -1091,9 +1172,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); } @@ -1538,17 +1619,12 @@ static void accept_autosuggestion(bool full) } } data->command_line.position = data->command_line.size(); - data->command_line_changed(); + data->command_line_changed(&data->command_line); reader_super_highlight_me_plenty(); reader_repaint(); } } -static bool is_navigating_pager_contents() -{ - return data && data->pager.selected_completion(data->current_page_rendering) != NULL; -} - /* Ensure we have no pager contents */ static void clear_pager() { @@ -1901,6 +1977,7 @@ static bool handle_completions(const std::vector &comp) /* Invalidate our rendering */ data->current_page_rendering = page_rendering_t(); + } else { @@ -1913,9 +1990,10 @@ static bool handle_completions(const std::vector &comp) run_pager(prefix, is_quoted, surviving_completions); s_reset(&data->screen, screen_reset_abandon_line); - } - reader_repaint(); + + reader_repaint_needed(); + success = false; } } @@ -2143,7 +2221,7 @@ static void set_command_line_and_position(editable_line_t *el, const wcstring &n { el->text = new_str; el->position = pos; - data->command_line_changed(); + data->command_line_changed(el); reader_super_highlight_me_plenty(); reader_repaint(); } @@ -2420,7 +2498,7 @@ static void reader_set_buffer_maintaining_pager(const wcstring &b, size_t pos) /* 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.text = b; - data->command_line_changed(); + data->command_line_changed(&data->command_line); /* Don't set a position past the command line length */ if (pos > command_line_len) @@ -2580,7 +2658,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) { @@ -2931,7 +3009,7 @@ static int read_i(void) wcstring command = tmp; data->command_line.position=0; data->command_line.text.clear(); - data->command_line_changed(); + data->command_line_changed(&data->command_line); reader_run_command(parser, command); if (data->end_loop) { @@ -3023,12 +3101,9 @@ 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; @@ -3095,7 +3170,7 @@ const wchar_t *reader_readline(void) break; } - insert_string(arr); + insert_string(&data->command_line, arr); } } @@ -3111,30 +3186,18 @@ const wchar_t *reader_readline(void) if (last_char != R_YANK && last_char != R_YANK_POP) yank_len=0; - /* We clear pager contents for most events, except for a few */ - switch (c) + /* 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)) { - case R_COMPLETE: - 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: - break; - - default: - clear_pager(); - break; + clear_pager(); } //fprintf(stderr, "\n\nchar: %ls\n\n", describe_char(c).c_str()); switch (c) { - - /* go to beginning of line*/ + /* go to beginning of line*/ case R_BEGINNING_OF_LINE: { editable_line_t *el = data->active_edit_line(); @@ -3213,6 +3276,7 @@ const wchar_t *reader_readline(void) /* complete */ case R_COMPLETE: + case R_COMPLETE_AND_SEARCH: { if (!data->complete_func) @@ -3220,10 +3284,10 @@ const wchar_t *reader_readline(void) /* 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 (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. */ - select_completion_in_direction(direction_next, cycle_command_line, cycle_cursor_pos); + select_completion_in_direction(c == R_COMPLETE ? direction_next : direction_prev, data->cycle_command_line, data->cycle_cursor_pos); } else { @@ -3273,13 +3337,19 @@ const wchar_t *reader_readline(void) prioritize_completions(comp); /* Record our cycle_command_line */ - cycle_command_line = el->text; - cycle_cursor_pos = el->position; - + data->cycle_command_line = el->text; + data->cycle_cursor_pos = el->position; + comp_empty = handle_completions(comp); + + /* 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, data->cycle_command_line, data->cycle_cursor_pos); + reader_repaint_needed(); + } - /* Start the cycle at the beginning */ - completion_cycle_idx = (size_t)(-1); } break; @@ -3375,7 +3445,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; } @@ -3389,7 +3459,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; @@ -3454,14 +3524,14 @@ const wchar_t *reader_readline(void) data->autosuggestion.clear(); /* We only execute the command line */ - const editable_line_t *el = &data->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(el->text, el->position)) { if (el->position >= el->size() || iswspace(el->text.at(el->position))) { - insert_char('\n'); + insert_char(el, '\n'); break; } } @@ -3502,7 +3572,7 @@ const wchar_t *reader_readline(void) */ case PARSER_TEST_INCOMPLETE: { - insert_char('\n'); + insert_char(el, '\n'); break; } @@ -3611,9 +3681,9 @@ const wchar_t *reader_readline(void) case R_BACKWARD_CHAR: { editable_line_t *el = data->active_edit_line(); - if (is_navigating_pager_contents()) + if (data->is_navigating_pager_contents() && ! data->pager.is_search_field_shown()) { - select_completion_in_direction(direction_west, cycle_command_line, cycle_cursor_pos); + select_completion_in_direction(direction_west, data->cycle_command_line, data->cycle_cursor_pos); } else if (el->position > 0) { @@ -3627,9 +3697,9 @@ const wchar_t *reader_readline(void) case R_FORWARD_CHAR: { editable_line_t *el = data->active_edit_line(); - if (is_navigating_pager_contents()) + if (data->is_navigating_pager_contents() && ! data->pager.is_search_field_shown()) { - select_completion_in_direction(direction_east, cycle_command_line, cycle_cursor_pos); + select_completion_in_direction(direction_east, data->cycle_command_line, data->cycle_cursor_pos); } else if (el->position < el->size()) { @@ -3705,7 +3775,7 @@ const wchar_t *reader_readline(void) case R_UP_LINE: case R_DOWN_LINE: { - if (is_navigating_pager_contents()) + if (data->is_navigating_pager_contents()) { /* We are already navigating pager contents. */ selection_direction_t direction; @@ -3718,6 +3788,10 @@ const wchar_t *reader_readline(void) { /* Up arrow, but we are in the first column and first row. End navigation */ direction = direction_deselect; + + /* Also hide the search field */ + data->pager.search_field_line.clear(); + data->pager.set_search_field_shown(false); } else { @@ -3726,12 +3800,12 @@ const wchar_t *reader_readline(void) } /* Now do the selection */ - select_completion_in_direction(direction, cycle_command_line, cycle_cursor_pos); + select_completion_in_direction(direction, data->cycle_command_line, data->cycle_cursor_pos); } 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, cycle_command_line, cycle_cursor_pos); + select_completion_in_direction(direction_south, data->cycle_command_line, data->cycle_cursor_pos); } else { @@ -3888,7 +3962,7 @@ const wchar_t *reader_readline(void) data->command_line.text.at(pos) = chr; capitalized_first = capitalized_first || make_uppercase; } - data->command_line_changed(); + data->command_line_changed(el); reader_super_highlight_me_plenty(); reader_repaint_needed(); break; @@ -3899,19 +3973,20 @@ const wchar_t *reader_readline(void) { if ((!wchar_private(c)) && (((c>31) || (c==L'\n'))&& (c != 127))) { - - if (is_navigating_pager_contents()) + 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' '); - - /* Regular character */ - insert_char(c, should_expand_abbreviations); + should_expand_abbreviations = (c == L' '); } + + /* Regular character */ + insert_char(data->active_edit_line(), c, should_expand_abbreviations); + } else { @@ -3987,6 +4062,35 @@ int reader_has_pager_contents() 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 diff --git a/reader.h b/reader.h index 025358c0..1519aee8 100644 --- a/reader.h +++ b/reader.h @@ -49,6 +49,12 @@ class editable_line_t return text.empty(); } + void clear() + { + text.clear(); + position = 0; + } + editable_line_t() : text(), position(0) { } @@ -295,5 +301,8 @@ bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t curso /* Apply a completion string. Exposed for testing only. */ wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flags_t flags, const wcstring &command_line, size_t *inout_cursor_pos, bool append_only); +/* Called by pager */ +class pager_t; +void reader_selected_completion_changed(pager_t *pager); #endif diff --git a/screen.cpp b/screen.cpp index 24c10d10..7c02c384 100644 --- a/screen.cpp +++ b/screen.cpp @@ -1237,7 +1237,8 @@ void s_write(screen_t *s, const highlight_spec_t *colors, const int *indent, size_t cursor_pos, - const page_rendering_t &pager) + const page_rendering_t &pager, + bool cursor_position_is_within_pager) { screen_data_t::cursor_t cursor_arr; @@ -1306,25 +1307,27 @@ void s_write(screen_t *s, { int color = colors[i]; - if (i == cursor_pos) + if (! cursor_position_is_within_pager && i == cursor_pos) { color = 0; - } - - if (i == cursor_pos) - { cursor_arr = s->desired.cursor; } s_desired_append_char(s, effective_commandline.at(i), color, indent[i], first_line_prompt_space); } - if (i == cursor_pos) + if (! cursor_position_is_within_pager && i == cursor_pos) { cursor_arr = s->desired.cursor; } s->desired.cursor = cursor_arr; + if (cursor_position_is_within_pager) + { + s->desired.cursor.x = (int)cursor_pos; + s->desired.cursor.y = (int)s->desired.line_count(); + } + /* Append pager_data (none if empty) */ s->desired.append_lines(pager.screen_data); diff --git a/screen.h b/screen.h index 0733b804..8a16fad0 100644 --- a/screen.h +++ b/screen.h @@ -218,6 +218,8 @@ public: \param colors the colors to use for the comand line \param indent the indent to use for the command line \param cursor_pos where the cursor is + \param pager_data any pager data, to append to the screen + \param position_is_within_pager whether the position is within the pager line (first line) */ void s_write(screen_t *s, const wcstring &left_prompt, @@ -227,7 +229,8 @@ void s_write(screen_t *s, const highlight_spec_t *colors, const int *indent, size_t cursor_pos, - const page_rendering_t &pager_data); + const page_rendering_t &pager_data, + bool position_is_within_pager); /** This function resets the screen buffers internal knowledge about diff --git a/share/functions/fish_default_key_bindings.fish b/share/functions/fish_default_key_bindings.fish index a03faaba..63903f66 100644 --- a/share/functions/fish_default_key_bindings.fish +++ b/share/functions/fish_default_key_bindings.fish @@ -114,6 +114,9 @@ function fish_default_key_bindings -d "Default (Emacs-like) key bindings for fis # This will make sure the output of the current command is paged using the less pager when you press Meta-p bind \ep '__fish_paginate' + # shift-tab does a tab complete followed by a search + bind --key btab complete-and-search + # term-specific special bindings switch "$TERM" case 'rxvt*' @@ -122,3 +125,4 @@ function fish_default_key_bindings -d "Default (Emacs-like) key bindings for fis bind \eOd backward-word end end + -- cgit v1.2.3 From 7d8766980b0b3604d9fe77850faa62092ff5c786 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 27 Jan 2014 02:17:31 -0800 Subject: Support escape or up-arrow to cancel the completion search field. --- input.cpp | 6 ++- input.h | 3 +- pager.cpp | 15 +++--- pager.h | 6 ++- reader.cpp | 65 ++++++++++++++------------ share/functions/fish_default_key_bindings.fish | 5 +- 6 files changed, 55 insertions(+), 45 deletions(-) (limited to 'input.h') diff --git a/input.cpp b/input.cpp index ccdbc240..bd855c2b 100644 --- a/input.cpp +++ b/input.cpp @@ -129,7 +129,8 @@ static const wchar_t * const name_arr[] = L"up-line", L"down-line", L"suppress-autosuggestion", - L"accept-autosuggestion" + L"accept-autosuggestion", + L"cancel" }; wcstring describe_char(wchar_t c) @@ -227,7 +228,8 @@ static const wchar_t code_arr[] = R_UP_LINE, R_DOWN_LINE, R_SUPPRESS_AUTOSUGGESTION, - R_ACCEPT_AUTOSUGGESTION + R_ACCEPT_AUTOSUGGESTION, + R_CANCEL }; /** Mappings for the current input mode */ diff --git a/input.h b/input.h index 2d71a5ae..56f6e249 100644 --- a/input.h +++ b/input.h @@ -58,7 +58,8 @@ enum R_UP_LINE, R_DOWN_LINE, R_SUPPRESS_AUTOSUGGESTION, - R_ACCEPT_AUTOSUGGESTION + R_ACCEPT_AUTOSUGGESTION, + R_CANCEL }; wcstring describe_char(wchar_t c); diff --git a/pager.cpp b/pager.cpp index 49fc6a8e..1dd4ecc4 100644 --- a/pager.cpp +++ b/pager.cpp @@ -7,8 +7,6 @@ #include #include -#define PAGER_SELECTION_NONE ((size_t)(-1)) - typedef pager_t::comp_t comp_t; typedef std::vector completion_list_t; typedef std::vector comp_info_list_t; @@ -709,7 +707,7 @@ bool pager_t::empty() const return unfiltered_completion_infos.empty(); } -const completion_t *pager_t::select_next_completion_in_direction(selection_direction_t direction, const page_rendering_t &rendering) +bool pager_t::select_next_completion_in_direction(selection_direction_t direction, const page_rendering_t &rendering) { /* Must have something to select */ if (this->completion_infos.empty()) @@ -734,7 +732,8 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc { selected_completion_idx = 0; } - return selected_completion(rendering); + note_selection_changed(); + return true; /* These do nothing */ case direction_north: @@ -742,7 +741,7 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc case direction_west: case direction_deselect: default: - return NULL; + return false; } } @@ -898,12 +897,12 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc } } - - return selected_completion(rendering); + this->note_selection_changed(); + return true; } else { - return NULL; + return false; } } diff --git a/pager.h b/pager.h index 922e9503..8253dc11 100644 --- a/pager.h +++ b/pager.h @@ -6,6 +6,8 @@ #include "screen.h" #include "reader.h" +#define PAGER_SELECTION_NONE ((size_t)(-1)) + /* Represents rendering from the pager */ class page_rendering_t { @@ -123,8 +125,8 @@ class pager_t /* Sets the terminal width and height */ void set_term_size(int w, int h); - /* Changes the selected completion in the given direction according to the layout of the given rendering. Returns the newly selected completion if it changed, NULL if nothing was selected or it did not change. */ - const completion_t *select_next_completion_in_direction(selection_direction_t direction, const page_rendering_t &rendering); + /* Changes the selected completion in the given direction according to the layout of the given rendering. Returns true if the selection changed. */ + bool select_next_completion_in_direction(selection_direction_t direction, const page_rendering_t &rendering); /* Returns the currently selected completion for the given rendering */ const completion_t *selected_completion(const page_rendering_t &rendering) const; diff --git a/reader.cpp b/reader.cpp index 492d8fb7..9b09ae83 100644 --- a/reader.cpp +++ b/reader.cpp @@ -1062,6 +1062,7 @@ static bool command_ends_paging(wchar_t c, bool focused_on_search_field) case R_HISTORY_TOKEN_SEARCH_FORWARD: case R_EXECUTE: case R_ACCEPT_AUTOSUGGESTION: + case R_CANCEL: return true; /* These commands never do */ @@ -1636,31 +1637,11 @@ static void clear_pager() } } -static void select_completion_in_direction(enum selection_direction_t dir, const wcstring &cycle_command_line, size_t cycle_cursor_pos) +static void select_completion_in_direction(enum selection_direction_t dir) { - const completion_t *next_comp = data->pager.select_next_completion_in_direction(dir, data->current_page_rendering); - if (next_comp != NULL || dir == direction_deselect) - { - /* Update the cursor and command line */ - size_t cursor_pos = cycle_cursor_pos; - wcstring new_cmd_line; - if (dir == direction_deselect) - { - new_cmd_line = cycle_command_line; - } - else - { - new_cmd_line = completion_apply_to_command_line(next_comp->completion, next_comp->flags, 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(); - } + assert(data != NULL); + /* Note: this will probably trigger reader_selected_completion_changed, which will cause us to update stuff */ + data->pager.select_next_completion_in_direction(dir, data->current_page_rendering); } /** @@ -2960,6 +2941,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. @@ -3253,6 +3249,12 @@ const wchar_t *reader_readline(void) { break; } + + case R_CANCEL: + { + // The only thing we can cancel right now is paging, which we handled up above + break; + } case R_REPAINT: { @@ -3287,7 +3289,7 @@ const wchar_t *reader_readline(void) 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. */ - select_completion_in_direction(c == R_COMPLETE ? direction_next : direction_prev, data->cycle_command_line, data->cycle_cursor_pos); + select_completion_in_direction(c == R_COMPLETE ? direction_next : direction_prev); } else { @@ -3346,7 +3348,7 @@ const wchar_t *reader_readline(void) if (c == R_COMPLETE_AND_SEARCH && ! comp_empty && ! data->pager.empty()) { data->pager.set_search_field_shown(true); - select_completion_in_direction(direction_next, data->cycle_command_line, data->cycle_cursor_pos); + select_completion_in_direction(direction_next); reader_repaint_needed(); } @@ -3683,7 +3685,7 @@ const wchar_t *reader_readline(void) editable_line_t *el = data->active_edit_line(); if (data->is_navigating_pager_contents() && ! data->pager.is_search_field_shown()) { - select_completion_in_direction(direction_west, data->cycle_command_line, data->cycle_cursor_pos); + select_completion_in_direction(direction_west); } else if (el->position > 0) { @@ -3699,7 +3701,7 @@ const wchar_t *reader_readline(void) editable_line_t *el = data->active_edit_line(); if (data->is_navigating_pager_contents() && ! data->pager.is_search_field_shown()) { - select_completion_in_direction(direction_east, data->cycle_command_line, data->cycle_cursor_pos); + select_completion_in_direction(direction_east); } else if (el->position < el->size()) { @@ -3784,7 +3786,7 @@ const wchar_t *reader_readline(void) /* Down arrow is always south */ direction = direction_south; } - else if (data->pager.get_selected_row(data->current_page_rendering) == 0 && data->pager.get_selected_column(data->current_page_rendering) == 0) + else if (selection_is_at_top()) { /* Up arrow, but we are in the first column and first row. End navigation */ direction = direction_deselect; @@ -3792,6 +3794,7 @@ const wchar_t *reader_readline(void) /* Also hide the search field */ data->pager.search_field_line.clear(); data->pager.set_search_field_shown(false); + data->pager.refilter_completions(); } else { @@ -3800,12 +3803,12 @@ const wchar_t *reader_readline(void) } /* Now do the selection */ - select_completion_in_direction(direction, data->cycle_command_line, data->cycle_cursor_pos); + 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, data->cycle_command_line, data->cycle_cursor_pos); + select_completion_in_direction(direction_south); } else { diff --git a/share/functions/fish_default_key_bindings.fish b/share/functions/fish_default_key_bindings.fish index 63903f66..2fec74da 100644 --- a/share/functions/fish_default_key_bindings.fish +++ b/share/functions/fish_default_key_bindings.fish @@ -115,7 +115,10 @@ function fish_default_key_bindings -d "Default (Emacs-like) key bindings for fis bind \ep '__fish_paginate' # shift-tab does a tab complete followed by a search - bind --key btab complete-and-search + bind --key btab complete-and-search + + # escape cancels stuff + bind \e cancel # term-specific special bindings switch "$TERM" -- cgit v1.2.3 From 503bbd85b56f8c7cac4065fa0cd7e7738d9d87a8 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Wed, 12 Feb 2014 12:49:32 -0800 Subject: Test and fix issue where, if binding X is a prefix of binding Y, and X is specified before Y, then Y will never be invoked because X will always get there first. Now instead we order bindings in descending order by length, so that we always test the binding before any others that prefixes it. Fixes #1283. --- builtin.cpp | 3 +- fish_tests.cpp | 26 ++++++++++++++++++ input.cpp | 87 ++++++++++++++++++++++++++++++++-------------------------- input.h | 2 +- 4 files changed, 76 insertions(+), 42 deletions(-) (limited to 'input.h') diff --git a/builtin.cpp b/builtin.cpp index fb5fbeb4..8b8b8d5c 100644 --- a/builtin.cpp +++ b/builtin.cpp @@ -556,8 +556,7 @@ static int builtin_bind(parser_t &parser, wchar_t **argv) BIND_ERASE, BIND_KEY_NAMES, BIND_FUNCTION_NAMES - } - ; + }; int argc=builtin_count_args(argv); int mode = BIND_INSERT; diff --git a/fish_tests.cpp b/fish_tests.cpp index 984c740b..1b66991c 100644 --- a/fish_tests.cpp +++ b/fish_tests.cpp @@ -61,6 +61,7 @@ #include "parse_tree.h" #include "parse_util.h" #include "pager.h" +#include "input.h" static const char * const * s_arguments; static int s_test_run_count = 0; @@ -1798,6 +1799,30 @@ static bool history_contains(history_t *history, const wcstring &txt) return result; } +static void test_input() +{ + say(L"Testing input"); + /* Ensure sequences are order independent. Here we add two bindings where the first is a prefix of the second, and then emit the second key list. The second binding should be invoked, not the first! */ + wcstring prefix_binding = L"qqqqqqqa"; + wcstring desired_binding = prefix_binding + L'a'; + input_mapping_add(prefix_binding.c_str(), L"up-line"); + input_mapping_add(desired_binding.c_str(), L"down-line"); + + /* Push the desired binding on the stack (backwards!) */ + size_t idx = desired_binding.size(); + while (idx--) + { + input_unreadch(desired_binding.at(idx)); + } + + /* Now test */ + wint_t c = input_readch(); + if (c != R_DOWN_LINE) + { + err(L"Expected to read char R_DOWN_LINE, but instead got %ls\n", describe_char(c).c_str()); + } +} + class history_tests_t { public: @@ -2836,6 +2861,7 @@ int main(int argc, char **argv) if (should_test_function("is_potential_path")) test_is_potential_path(); if (should_test_function("colors")) test_colors(); if (should_test_function("complete")) test_complete(); + if (should_test_function("input")) test_input(); if (should_test_function("completion_insertions")) test_completion_insertions(); if (should_test_function("autosuggestion_combining")) test_autosuggestion_combining(); if (should_test_function("autosuggest_suggest_special")) test_autosuggest_suggest_special(); diff --git a/input.cpp b/input.cpp index bd855c2b..e4edaa30 100644 --- a/input.cpp +++ b/input.cpp @@ -62,16 +62,20 @@ #define DEFAULT_TERM L"ansi" -/** - Struct representing a keybinding. Returned by input_get_mappings. - */ +/** Struct representing a keybinding. Returned by input_get_mappings. */ struct input_mapping_t { wcstring seq; /**< Character sequence which generates this event */ wcstring command; /**< command that should be evaluated by this mapping */ + + /* We wish to preserve the user-specified order. This is just an incrementing value. */ + unsigned int specification_order; - - input_mapping_t(const wcstring &s, const wcstring &c) : seq(s), command(c) {} + input_mapping_t(const wcstring &s, const wcstring &c) : seq(s), command(c) + { + static unsigned int s_last_input_mapping_specification_order = 0; + specification_order = ++s_last_input_mapping_specification_order; + } }; /** @@ -81,7 +85,6 @@ struct terminfo_mapping_t { const wchar_t *name; /**< Name of key */ const char *seq; /**< Character sequence generated on keypress. Constant string. */ - }; @@ -133,7 +136,7 @@ static const wchar_t * const name_arr[] = L"cancel" }; -wcstring describe_char(wchar_t c) +wcstring describe_char(wint_t c) { wchar_t initial_cmd_char = R_BEGINNING_OF_LINE; size_t name_count = sizeof name_arr / sizeof *name_arr; @@ -257,28 +260,44 @@ static bool is_init = false; */ static void input_terminfo_init(); +/* Helper function to compare the lengths of sequences */ +static bool length_is_greater_than(const input_mapping_t &m1, const input_mapping_t &m2) +{ + return m1.seq.size() > m2.seq.size(); +} + +static bool specification_order_is_less_than(const input_mapping_t &m1, const input_mapping_t &m2) +{ + return m1.specification_order < m2.specification_order; +} -/** - Returns the function description for the given function code. -*/ +/* Inserts an input mapping at the correct position. We sort them in descending order by length, so that we test longer sequences first. */ +static void input_mapping_insert_sorted(const input_mapping_t &new_mapping) +{ + std::vector::iterator loc = std::lower_bound(mapping_list.begin(), mapping_list.end(), new_mapping, length_is_greater_than); + mapping_list.insert(loc, new_mapping); +} + +/* Adds an input mapping */ void input_mapping_add(const wchar_t *sequence, const wchar_t *command) { CHECK(sequence,); CHECK(command,); - - // debug( 0, L"Add mapping from %ls to %ls", escape(sequence, 1), escape(command, 1 ) ); - - for (size_t i=0; i local_list = mapping_list; + std::sort(local_list.begin(), local_list.end(), specification_order_is_less_than); + + for (size_t i=0; i