Increased support for completion search field. Use btab (shift-tab) to

complete-and-search.
This commit is contained in:
ridiculousfish 2014-01-27 00:56:13 -08:00
parent ce4c145f1c
commit 5be3606236
11 changed files with 425 additions and 167 deletions

View file

@ -571,35 +571,14 @@ static int builtin_bind(parser_t &parser, wchar_t **argv)
static const struct woption static const struct woption
long_options[] = long_options[] =
{ {
{ { L"all", no_argument, 0, 'a' },
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"erase", no_argument, 0, 'e' { L"key-names", no_argument, 0, 'K' },
} { 0, 0, 0, 0 }
, };
{
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) while (1)
{ {

View file

@ -104,6 +104,7 @@ static const wchar_t * const name_arr[] =
L"yank", L"yank",
L"yank-pop", L"yank-pop",
L"complete", L"complete",
L"complete-and-search",
L"beginning-of-history", L"beginning-of-history",
L"end-of-history", L"end-of-history",
L"backward-kill-line", L"backward-kill-line",
@ -201,6 +202,7 @@ static const wchar_t code_arr[] =
R_YANK, R_YANK,
R_YANK_POP, R_YANK_POP,
R_COMPLETE, R_COMPLETE,
R_COMPLETE_AND_SEARCH,
R_BEGINNING_OF_HISTORY, R_BEGINNING_OF_HISTORY,
R_END_OF_HISTORY, R_END_OF_HISTORY,
R_BACKWARD_KILL_LINE, R_BACKWARD_KILL_LINE,

View file

@ -33,6 +33,7 @@ enum
R_YANK, R_YANK,
R_YANK_POP, R_YANK_POP,
R_COMPLETE, R_COMPLETE,
R_COMPLETE_AND_SEARCH,
R_BEGINNING_OF_HISTORY, R_BEGINNING_OF_HISTORY,
R_END_OF_HISTORY, R_END_OF_HISTORY,
R_BACKWARD_KILL_LINE, R_BACKWARD_KILL_LINE,

View file

@ -251,7 +251,6 @@ wchar_t input_common_readch(int timed)
case 0: case 0:
return 0; return 0;
default: default:
return res; return res;
} }
} }

160
pager.cpp
View file

@ -3,6 +3,7 @@
#include "pager.h" #include "pager.h"
#include "highlight.h" #include "highlight.h"
#include "input_common.h" #include "input_common.h"
#include "wutil.h"
#include <vector> #include <vector>
#include <map> #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 */ /** The maximum number of columns of completion to attempt to fit onto the screen */
#define PAGER_MAX_COLS 6 #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) 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); 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); 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) void pager_t::set_completions(const completion_list_t &raw_completions)
{ {
// Get completion infos out of it // 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 // Maybe join them
if (prefix == L"-") if (prefix == L"-")
join_completions(&completion_infos); join_completions(&unfiltered_completion_infos);
// Compute their various widths // 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) 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 */ /* Compute the effective term width and term height, accounting for disclosure */
int term_width = this->available_term_width; 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) if (! this->fully_disclosed)
term_height = mini(term_height, PAGER_UNDISCLOSED_MAX_ROWS); 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" */ /* 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); 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()) 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) if (search_field_shown)
{ {
/* Add the search field */ /* Add the search field */
wcstring spaces(8, L' '); wcstring search_field_text = search_field_line.text;
spaces.insert(spaces.begin(), 1, L'h'); /* 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); 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,9 +656,9 @@ page_rendering_t pager_t::render() const
page_rendering_t rendering; page_rendering_t rendering;
rendering.term_width = this->available_term_width; rendering.term_width = this->available_term_width;
rendering.term_height = this->available_term_height; 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 */ /* Initially empty rendering */
@ -598,7 +669,7 @@ page_rendering_t pager_t::render() const
size_t min_cols_required_for_rows = divide_round_up(completion_infos.size(), min_rows_required_for_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); assert(min_cols_required_for_rows <= cols);
if (min_cols_required_for_rows < cols) if (cols > 1 && min_cols_required_for_rows < cols)
{ {
/* Next iteration will be better, so skip this one */ /* Next iteration will be better, so skip this one */
continue; continue;
@ -613,13 +684,17 @@ page_rendering_t pager_t::render() const
break; break;
} }
} }
}
return rendering; return rendering;
} }
void pager_t::update_rendering(page_rendering_t *rendering) 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(); *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 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) const completion_t *pager_t::select_next_completion_in_direction(selection_direction_t direction, const page_rendering_t &rendering)
{ {
/* Must have something to select */ /* Must have something to select */
if (this->empty()) if (this->completion_infos.empty())
{ {
return NULL; 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 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; size_t result = selected_completion_idx;
if (result != PAGER_SELECTION_NONE) 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; 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()); assert(result == PAGER_SELECTION_NONE || result < completion_infos.size());
return result; 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 *pager_t::selected_completion(const page_rendering_t &rendering) const
{ {
const completion_t * result = NULL; 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) */ /* 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 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; 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 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; return selected_completion_idx == PAGER_SELECTION_NONE ? PAGER_SELECTION_NONE : selected_completion_idx / rendering.rows;
} }
void pager_t::clear() void pager_t::clear()
{ {
unfiltered_completion_infos.clear();
completion_infos.clear(); completion_infos.clear();
prefix.clear(); prefix.clear();
selected_completion_idx = PAGER_SELECTION_NONE; selected_completion_idx = PAGER_SELECTION_NONE;
fully_disclosed = false; fully_disclosed = false;
search_field_shown = 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 */ /* 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)
{ {
} }

32
pager.h
View file

@ -4,6 +4,7 @@
#include "complete.h" #include "complete.h"
#include "screen.h" #include "screen.h"
#include "reader.h"
/* Represents rendering from the pager */ /* Represents rendering from the pager */
class page_rendering_t class page_rendering_t
@ -20,6 +21,9 @@ class page_rendering_t
size_t remaining_to_disclose; 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" */ /* Returns a rendering with invalid data, useful to indicate "no rendering" */
page_rendering_t(); page_rendering_t();
}; };
@ -47,7 +51,6 @@ class pager_t
/* Whether we show the search field */ /* Whether we show the search field */
bool search_field_shown; bool search_field_shown;
wcstring search_field_string;
/* Returns the index of the completion that should draw selected, using the given number of columns */ /* 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; size_t visual_selected_completion_index(size_t rows, size_t cols) const;
@ -84,21 +87,33 @@ class pager_t
private: private:
typedef std::vector<comp_t> comp_info_list_t; typedef std::vector<comp_t> comp_info_list_t;
/* The filtered list of completion infos */
comp_info_list_t 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; 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; 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 recalc_min_widths(comp_info_list_t * lst) const;
void measure_completion_infos(std::vector<comp_t> *infos, const wcstring &prefix) 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; 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; 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: public:
/* The text of the search field */
editable_line_t search_field_line;
/* Sets the set of completions */ /* Sets the set of completions */
void set_completions(const completion_list_t &comp); void set_completions(const completion_list_t &comp);
@ -130,6 +145,21 @@ class pager_t
/* Clears all completions and the prefix */ /* Clears all completions and the prefix */
void clear(); 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 */ /* Constructor */
pager_t(); pager_t();
}; };

View file

@ -210,9 +210,6 @@ public:
/** Current page rendering */ /** Current page rendering */
page_rendering_t 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 */ /** Whether autosuggesting is allowed at all */
bool allow_autosuggestion; bool allow_autosuggestion;
@ -254,14 +251,26 @@ public:
/** The current position in search_prev */ /** The current position in search_prev */
size_t search_pos; 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 */ /* The line that is currently being edited. Typically the command line, but may be the search field */
editable_line_t *active_edit_line() 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 */ /** 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. */ /** Expand abbreviations at the current cursor position, minus backtrack_amt. */
bool expand_abbreviation_as_necessary(size_t cursor_backtrack); bool expand_abbreviation_as_necessary(size_t cursor_backtrack);
@ -279,6 +288,10 @@ public:
/** The output of the last evaluation of the right prompt command */ /** The output of the last evaluation of the right prompt command */
wcstring right_prompt_buff; wcstring right_prompt_buff;
/* Completion support */
wcstring cycle_command_line;
size_t cycle_cursor_pos;
/** /**
Color is the syntax highlighting for buff. The format is that Color is the syntax highlighting for buff. The format is that
color[i] is the classification (according to the enum in color[i] is the classification (according to the enum in
@ -343,13 +356,13 @@ public:
/** Constructor */ /** Constructor */
reader_data_t() : reader_data_t() :
is_navigating_pager(0),
allow_autosuggestion(0), allow_autosuggestion(0),
suppress_autosuggestion(0), suppress_autosuggestion(0),
expand_abbreviations(0), expand_abbreviations(0),
history(0), history(0),
token_history_pos(0), token_history_pos(0),
search_pos(0), search_pos(0),
cycle_cursor_pos(0),
complete_func(0), complete_func(0),
highlight_function(0), highlight_function(0),
test_func(0), test_func(0),
@ -531,12 +544,12 @@ wcstring combine_command_and_autosuggestion(const wcstring &cmdline, const wcstr
static void reader_repaint() static void reader_repaint()
{ {
editable_line_t *el = &data->command_line; editable_line_t *cmd_line = &data->command_line;
// Update the indentation // 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 // 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(); size_t len = full_line.size();
if (len < 1) if (len < 1)
@ -553,15 +566,19 @@ static void reader_repaint()
data->pager.set_term_size(maxi(1, common_get_width()), maxi(1, common_get_height() - 1)); data->pager.set_term_size(maxi(1, common_get_width()), maxi(1, common_get_height() - 1));
data->pager.update_rendering(&data->current_page_rendering); 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, s_write(&data->screen,
data->left_prompt_buff, data->left_prompt_buff,
data->right_prompt_buff, data->right_prompt_buff,
full_line, full_line,
el->size(), cmd_line->size(),
&colors[0], &colors[0],
&indents[0], &indents[0],
el->position, cursor_position,
data->current_page_rendering); data->current_page_rendering,
focused_on_pager);
data->repaint_needed = false; 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); el->text.erase(begin_idx, length);
data->command_line_changed(); data->command_line_changed(el);
reader_super_highlight_me_plenty(); reader_super_highlight_me_plenty();
reader_repaint(); reader_repaint();
@ -650,9 +667,11 @@ void reader_pop_current_filename()
/** Make sure buffers are large enough to hold the current string length */ /** 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(); ASSERT_IS_MAIN_THREAD();
if (el == &this->command_line)
{
size_t len = this->command_line.size(); 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. */ /* 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. */
@ -664,6 +683,11 @@ void reader_data_t::command_line_changed()
/* Update the gen count */ /* Update the gen count */
s_generation_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'. */ /* Expand abbreviations at the given cursor position. Does NOT inspect 'data'. */
bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t cursor_pos, wcstring *output) bool reader_expand_abbreviation_in_command(const wcstring &cmdline, size_t cursor_pos, wcstring *output)
@ -752,7 +776,7 @@ bool reader_data_t::expand_abbreviation_as_necessary(size_t cursor_backtrack)
el->text.swap(new_cmdline); el->text.swap(new_cmdline);
el->position = new_buff_pos; el->position = new_buff_pos;
data->command_line_changed(); data->command_line_changed(el);
result = true; 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 Remove the previous character in the character buffer and on the
screen using syntax highlighting, etc. screen using syntax highlighting, etc.
@ -1044,7 +1126,7 @@ static void remove_backward()
el->text.erase(el->position, 1); el->text.erase(el->position, 1);
} }
while (width == 0 && el->position > 0); while (width == 0 && el->position > 0);
data->command_line_changed(); data->command_line_changed(el);
data->suppress_autosuggestion = true; data->suppress_autosuggestion = true;
reader_super_highlight_me_plenty(); reader_super_highlight_me_plenty();
@ -1060,16 +1142,15 @@ static void remove_backward()
Optionally also expand abbreviations. Optionally also expand abbreviations.
Returns true if the string changed. 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(); size_t len = str.size();
if (len == 0) if (len == 0)
return false; return false;
editable_line_t *el = data->active_edit_line();
el->insert_string(str); el->insert_string(str);
data->command_line_changed(); data->command_line_changed(el);
if (el == &data->command_line) 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 Insert the character into the command line buffer and print it to
the screen using syntax highlighting, etc. 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.position = data->command_line.size();
data->command_line_changed(); data->command_line_changed(&data->command_line);
reader_super_highlight_me_plenty(); reader_super_highlight_me_plenty();
reader_repaint(); 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 */ /* Ensure we have no pager contents */
static void clear_pager() static void clear_pager()
{ {
@ -1901,6 +1977,7 @@ static bool handle_completions(const std::vector<completion_t> &comp)
/* Invalidate our rendering */ /* Invalidate our rendering */
data->current_page_rendering = page_rendering_t(); data->current_page_rendering = page_rendering_t();
} }
else else
{ {
@ -1913,9 +1990,10 @@ static bool handle_completions(const std::vector<completion_t> &comp)
run_pager(prefix, is_quoted, surviving_completions); run_pager(prefix, is_quoted, surviving_completions);
s_reset(&data->screen, screen_reset_abandon_line); s_reset(&data->screen, screen_reset_abandon_line);
} }
reader_repaint();
reader_repaint_needed();
success = false; 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->text = new_str;
el->position = pos; el->position = pos;
data->command_line_changed(); data->command_line_changed(el);
reader_super_highlight_me_plenty(); reader_super_highlight_me_plenty();
reader_repaint(); 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. */ /* 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(); size_t command_line_len = b.size();
data->command_line.text = b; 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 */ /* Don't set a position past the command line length */
if (pos > command_line_len) if (pos > command_line_len)
@ -2580,7 +2658,7 @@ void reader_push(const wchar_t *name)
data=n; data=n;
data->command_line_changed(); data->command_line_changed(&data->command_line);
if (data->next == 0) if (data->next == 0)
{ {
@ -2931,7 +3009,7 @@ static int read_i(void)
wcstring command = tmp; wcstring command = tmp;
data->command_line.position=0; data->command_line.position=0;
data->command_line.text.clear(); data->command_line.text.clear();
data->command_line_changed(); data->command_line_changed(&data->command_line);
reader_run_command(parser, command); reader_run_command(parser, command);
if (data->end_loop) 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. */ /* 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; bool coalescing_repaints = false;
/* The cycle index in our completion list */
size_t completion_cycle_idx = (size_t)(-1);
/* The command line before completion */ /* The command line before completion */
wcstring cycle_command_line; data->cycle_command_line.clear();
size_t cycle_cursor_pos = 0; data->cycle_cursor_pos = 0;
data->search_buff.clear(); data->search_buff.clear();
data->search_mode = NO_SEARCH; data->search_mode = NO_SEARCH;
@ -3095,7 +3170,7 @@ const wchar_t *reader_readline(void)
break; break;
} }
insert_string(arr); insert_string(&data->command_line, arr);
} }
} }
@ -3111,29 +3186,17 @@ const wchar_t *reader_readline(void)
if (last_char != R_YANK && last_char != R_YANK_POP) if (last_char != R_YANK && last_char != R_YANK_POP)
yank_len=0; yank_len=0;
/* We clear pager contents for most events, except for a few */ /* Clear the pager if necessary */
switch (c) 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(); clear_pager();
break;
} }
//fprintf(stderr, "\n\nchar: %ls\n\n", describe_char(c).c_str()); //fprintf(stderr, "\n\nchar: %ls\n\n", describe_char(c).c_str());
switch (c) switch (c)
{ {
/* go to beginning of line*/ /* go to beginning of line*/
case R_BEGINNING_OF_LINE: case R_BEGINNING_OF_LINE:
{ {
@ -3213,6 +3276,7 @@ const wchar_t *reader_readline(void)
/* complete */ /* complete */
case R_COMPLETE: case R_COMPLETE:
case R_COMPLETE_AND_SEARCH:
{ {
if (!data->complete_func) 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 */ /* Use the command line only; it doesn't make sense to complete in any other line */
editable_line_t *el = &data->command_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. */ /* 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 else
{ {
@ -3273,13 +3337,19 @@ const wchar_t *reader_readline(void)
prioritize_completions(comp); prioritize_completions(comp);
/* Record our cycle_command_line */ /* Record our cycle_command_line */
cycle_command_line = el->text; data->cycle_command_line = el->text;
cycle_cursor_pos = el->position; data->cycle_cursor_pos = el->position;
comp_empty = handle_completions(comp); comp_empty = handle_completions(comp);
/* Start the cycle at the beginning */ /* Show the search field if requested and if we printed a list of completions */
completion_cycle_idx = (size_t)(-1); 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();
}
} }
break; break;
@ -3375,7 +3445,7 @@ const wchar_t *reader_readline(void)
case R_YANK: case R_YANK:
{ {
yank_str = kill_yank(); yank_str = kill_yank();
insert_string(yank_str); insert_string(data->active_edit_line(), yank_str);
yank_len = wcslen(yank_str); yank_len = wcslen(yank_str);
break; break;
} }
@ -3389,7 +3459,7 @@ const wchar_t *reader_readline(void)
remove_backward(); remove_backward();
yank_str = kill_yank_rotate(); yank_str = kill_yank_rotate();
insert_string(yank_str); insert_string(data->active_edit_line(), yank_str);
yank_len = wcslen(yank_str); yank_len = wcslen(yank_str);
} }
break; break;
@ -3454,14 +3524,14 @@ const wchar_t *reader_readline(void)
data->autosuggestion.clear(); data->autosuggestion.clear();
/* We only execute the command line */ /* 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) */ /* 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 (is_backslashed(el->text, el->position))
{ {
if (el->position >= el->size() || iswspace(el->text.at(el->position))) if (el->position >= el->size() || iswspace(el->text.at(el->position)))
{ {
insert_char('\n'); insert_char(el, '\n');
break; break;
} }
} }
@ -3502,7 +3572,7 @@ const wchar_t *reader_readline(void)
*/ */
case PARSER_TEST_INCOMPLETE: case PARSER_TEST_INCOMPLETE:
{ {
insert_char('\n'); insert_char(el, '\n');
break; break;
} }
@ -3611,9 +3681,9 @@ const wchar_t *reader_readline(void)
case R_BACKWARD_CHAR: case R_BACKWARD_CHAR:
{ {
editable_line_t *el = data->active_edit_line(); 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) else if (el->position > 0)
{ {
@ -3627,9 +3697,9 @@ const wchar_t *reader_readline(void)
case R_FORWARD_CHAR: case R_FORWARD_CHAR:
{ {
editable_line_t *el = data->active_edit_line(); 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()) else if (el->position < el->size())
{ {
@ -3705,7 +3775,7 @@ const wchar_t *reader_readline(void)
case R_UP_LINE: case R_UP_LINE:
case R_DOWN_LINE: case R_DOWN_LINE:
{ {
if (is_navigating_pager_contents()) if (data->is_navigating_pager_contents())
{ {
/* We are already navigating pager contents. */ /* We are already navigating pager contents. */
selection_direction_t direction; 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 */ /* Up arrow, but we are in the first column and first row. End navigation */
direction = direction_deselect; direction = direction_deselect;
/* Also hide the search field */
data->pager.search_field_line.clear();
data->pager.set_search_field_shown(false);
} }
else else
{ {
@ -3726,12 +3800,12 @@ const wchar_t *reader_readline(void)
} }
/* Now do the selection */ /* 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()) else if (c == R_DOWN_LINE && ! data->pager.empty())
{ {
/* We pressed down with a non-empty pager contents, begin navigation */ /* 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 else
{ {
@ -3888,7 +3962,7 @@ const wchar_t *reader_readline(void)
data->command_line.text.at(pos) = chr; data->command_line.text.at(pos) = chr;
capitalized_first = capitalized_first || make_uppercase; capitalized_first = capitalized_first || make_uppercase;
} }
data->command_line_changed(); data->command_line_changed(el);
reader_super_highlight_me_plenty(); reader_super_highlight_me_plenty();
reader_repaint_needed(); reader_repaint_needed();
break; break;
@ -3899,19 +3973,20 @@ const wchar_t *reader_readline(void)
{ {
if ((!wchar_private(c)) && (((c>31) || (c==L'\n'))&& (c != 127))) if ((!wchar_private(c)) && (((c>31) || (c==L'\n'))&& (c != 127)))
{ {
bool should_expand_abbreviations = false;
if (is_navigating_pager_contents()) if (data->is_navigating_pager_contents())
{ {
data->pager.set_search_field_shown(true);
} }
else else
{ {
/* Expand abbreviations after space */ /* Expand abbreviations after space */
bool should_expand_abbreviations = (c == L' '); should_expand_abbreviations = (c == L' ');
}
/* Regular character */ /* Regular character */
insert_char(c, should_expand_abbreviations); insert_char(data->active_edit_line(), c, should_expand_abbreviations);
}
} }
else else
{ {
@ -3987,6 +4062,35 @@ int reader_has_pager_contents()
return ! data->current_page_rendering.screen_data.empty(); 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 Read non-interactively. Read input from stdin without displaying

View file

@ -49,6 +49,12 @@ class editable_line_t
return text.empty(); return text.empty();
} }
void clear()
{
text.clear();
position = 0;
}
editable_line_t() : text(), 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. */ /* 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); 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 #endif

View file

@ -1237,7 +1237,8 @@ void s_write(screen_t *s,
const highlight_spec_t *colors, const highlight_spec_t *colors,
const int *indent, const int *indent,
size_t cursor_pos, 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; screen_data_t::cursor_t cursor_arr;
@ -1306,25 +1307,27 @@ void s_write(screen_t *s,
{ {
int color = colors[i]; int color = colors[i];
if (i == cursor_pos) if (! cursor_position_is_within_pager && i == cursor_pos)
{ {
color = 0; color = 0;
}
if (i == cursor_pos)
{
cursor_arr = s->desired.cursor; cursor_arr = s->desired.cursor;
} }
s_desired_append_char(s, effective_commandline.at(i), color, indent[i], first_line_prompt_space); 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; cursor_arr = s->desired.cursor;
} }
s->desired.cursor = cursor_arr; 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) */ /* Append pager_data (none if empty) */
s->desired.append_lines(pager.screen_data); s->desired.append_lines(pager.screen_data);

View file

@ -218,6 +218,8 @@ public:
\param colors the colors to use for the comand line \param colors the colors to use for the comand line
\param indent the indent to use for the command line \param indent the indent to use for the command line
\param cursor_pos where the cursor is \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, void s_write(screen_t *s,
const wcstring &left_prompt, const wcstring &left_prompt,
@ -227,7 +229,8 @@ void s_write(screen_t *s,
const highlight_spec_t *colors, const highlight_spec_t *colors,
const int *indent, const int *indent,
size_t cursor_pos, 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 This function resets the screen buffers internal knowledge about

View file

@ -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 # 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' bind \ep '__fish_paginate'
# shift-tab does a tab complete followed by a search
bind --key btab complete-and-search
# term-specific special bindings # term-specific special bindings
switch "$TERM" switch "$TERM"
case 'rxvt*' case 'rxvt*'
@ -122,3 +125,4 @@ function fish_default_key_bindings -d "Default (Emacs-like) key bindings for fis
bind \eOd backward-word bind \eOd backward-word
end end
end end