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
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)
{

View file

@ -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,

View file

@ -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,

View file

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

160
pager.cpp
View file

@ -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,9 +656,9 @@ 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--)
{
/* 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);
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 */
continue;
@ -613,13 +684,17 @@ page_rendering_t pager_t::render() const
break;
}
}
}
return rendering;
}
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)
{
}

32
pager.h
View file

@ -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();
};

View file

@ -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);
@ -279,6 +288,10 @@ public:
/** The output of the last evaluation of the right prompt command */
wcstring right_prompt_buff;
/* Completion support */
wcstring cycle_command_line;
size_t cycle_cursor_pos;
/**
Color is the syntax highlighting for buff. The format is that
color[i] is the classification (according to the enum in
@ -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)
@ -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.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,9 +667,11 @@ 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();
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. */
@ -663,6 +682,11 @@ void reader_data_t::command_line_changed()
/* Update the gen count */
s_generation_count++;
}
else if (el == &this->pager.search_field_line)
{
this->pager.refilter_completions();
}
}
/* 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,29 +3186,17 @@ 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;
}
//fprintf(stderr, "\n\nchar: %ls\n\n", describe_char(c).c_str());
switch (c)
{
/* go to beginning of line*/
case R_BEGINNING_OF_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);
/* Start the cycle at the beginning */
completion_cycle_idx = (size_t)(-1);
/* Show the search field if requested and if we printed a list of completions */
if (c == R_COMPLETE_AND_SEARCH && ! comp_empty && ! data->pager.empty())
{
data->pager.set_search_field_shown(true);
select_completion_in_direction(direction_next, data->cycle_command_line, data->cycle_cursor_pos);
reader_repaint_needed();
}
}
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' ');
should_expand_abbreviations = (c == L' ');
}
/* Regular character */
insert_char(c, should_expand_abbreviations);
}
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

View file

@ -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

View file

@ -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);

View file

@ -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

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
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