aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--builtin.cpp37
-rw-r--r--input.cpp2
-rw-r--r--input.h1
-rw-r--r--input_common.cpp1
-rw-r--r--pager.cpp200
-rw-r--r--pager.h32
-rw-r--r--reader.cpp284
-rw-r--r--reader.h9
-rw-r--r--screen.cpp17
-rw-r--r--screen.h5
-rw-r--r--share/functions/fish_default_key_bindings.fish4
11 files changed, 425 insertions, 167 deletions
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 <vector>
#include <map>
@@ -18,9 +19,19 @@ typedef std::vector<comp_t> 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_t> 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<comp_t> *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<completion_t> &comp)
/* Invalidate our rendering */
data->current_page_rendering = page_rendering_t();
+
}
else
{
@@ -1913,9 +1990,10 @@ static bool handle_completions(const std::vector<completion_t> &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
+