Initial support for navigating completions that appear under the

commandline using arrow keys
This commit is contained in:
ridiculousfish 2014-01-17 12:04:03 -08:00
parent 64b1b5ca38
commit c6e5201e15
7 changed files with 215 additions and 82 deletions

View file

@ -87,6 +87,15 @@ enum
}; };
typedef unsigned int escape_flags_t; typedef unsigned int escape_flags_t;
/* Directions */
enum cardinal_direction_t
{
direction_north,
direction_east,
direction_south,
direction_west
};
/** /**
Helper macro for errors Helper macro for errors
*/ */

View file

@ -122,6 +122,12 @@ static std::vector<char> pager_buffer;
*/ */
static wcstring out_buff; static wcstring out_buff;
/* Returns numer / denom, rounding up */
static size_t divide_round_up(size_t numer, size_t denom)
{
return numer / denom + (numer % denom ? 1 : 0);
}
/** /**
This function calculates the minimum width for each completion This function calculates the minimum width for each completion
entry in the specified array_list. This width depends on the entry in the specified array_list. This width depends on the
@ -639,7 +645,7 @@ int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_i
*/ */
int print=0; int print=0;
int rows = (int)((lst.size()-1)/cols+1); int rows = (int)divide_round_up(lst.size(), cols);
int pref_tot_width=0; int pref_tot_width=0;
int min_tot_width = 0; int min_tot_width = 0;
@ -857,6 +863,7 @@ int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_i
page_rendering_t pager_t::render() const page_rendering_t pager_t::render() const
{ {
/** /**
Try to print the completions. Start by trying to print the Try to print the completions. Start by trying to print the
list in PAGER_MAX_COLS columns, if the completions won't list in PAGER_MAX_COLS columns, if the completions won't
@ -864,18 +871,26 @@ page_rendering_t pager_t::render() const
column never fails. column never fails.
*/ */
page_rendering_t rendering; page_rendering_t rendering;
for (int i = PAGER_MAX_COLS; i>0; i--) rendering.term_width = this->term_width;
rendering.term_height = this->term_height;
rendering.selected_completion_idx = this->selected_completion_idx;
if (! this->empty())
{
int cols;
bool done = false;
for (cols = PAGER_MAX_COLS; cols > 0 && ! done; cols--)
{ {
/* Initially empty rendering */ /* Initially empty rendering */
rendering.screen_data.resize(0); rendering.screen_data.resize(0);
switch (completion_try_print(i, prefix, completion_infos, &rendering)) switch (completion_try_print(cols, prefix, completion_infos, &rendering))
{ {
case PAGER_RETRY: case PAGER_RETRY:
break; break;
case PAGER_DONE: case PAGER_DONE:
i=0; done = true;
break; break;
case PAGER_RESIZE: case PAGER_RESIZE:
@ -885,13 +900,25 @@ page_rendering_t pager_t::render() const
bigger, we might be able to fit all completions bigger, we might be able to fit all completions
on-screen. on-screen.
*/ */
i=PAGER_MAX_COLS+1; cols=PAGER_MAX_COLS+1;
break; break;
} }
} }
assert(cols >= 0);
rendering.cols = (size_t)cols;
rendering.rows = divide_round_up(completion_infos.size(), rendering.cols);
}
return rendering; return rendering;
} }
void pager_t::update_rendering(page_rendering_t *rendering) const
{
if (rendering->term_width != this->term_width || rendering->term_height != this->term_height || rendering->selected_completion_idx != this->selected_completion_idx)
{
*rendering = this->render();
}
}
pager_t::pager_t() : term_width(0), term_height(0), selected_completion_idx(-1) pager_t::pager_t() : term_width(0), term_height(0), selected_completion_idx(-1)
{ {
} }
@ -906,6 +933,62 @@ void pager_t::set_selected_completion(size_t idx)
this->selected_completion_idx = idx; this->selected_completion_idx = idx;
} }
bool pager_t::select_next_completion_in_direction(cardinal_direction_t direction, const page_rendering_t &rendering)
{
/* Handle the case of nothing selected yet */
if (selected_completion_idx == (size_t)(-1))
{
return false;
}
/* We have a completion index; we wish to compute its row and column. Completions are rendered column first, i.e. we go south before we go west. */
size_t current_row = selected_completion_idx % rendering.rows;
size_t current_col = selected_completion_idx / rendering.rows;
switch (direction)
{
case direction_north:
{
/* Go up a whole row */
if (current_row > 0)
current_row--;
break;
}
case direction_south:
{
/* Go down, unless we are in the last row. Note that this means that we may set selected_completion_idx to an out-of-bounds value if the last row is incomplete; this is a feature (it allows "last column memory"). */
if (current_row + 1 < rendering.rows)
current_row++;
break;
}
case direction_east:
{
if (current_col + 1 < rendering.cols)
current_col++;
break;
}
case direction_west:
{
if (current_col > 0)
current_col--;
break;
}
}
size_t new_selected_completion_idx = current_col * rendering.rows + current_row;
if (new_selected_completion_idx != selected_completion_idx)
{
selected_completion_idx = new_selected_completion_idx;
return true;
}
else
{
return false;
}
}
void pager_t::clear() void pager_t::clear()
{ {
@ -913,3 +996,7 @@ void pager_t::clear()
completion_infos.clear(); completion_infos.clear();
prefix.clear(); prefix.clear();
} }
page_rendering_t::page_rendering_t() : term_width(-1), term_height(-1), rows(0), cols(0), selected_completion_idx(-1)
{
}

17
pager.h
View file

@ -6,9 +6,18 @@
#include "screen.h" #include "screen.h"
/* Represents rendering from the pager */ /* Represents rendering from the pager */
struct page_rendering_t class page_rendering_t
{ {
public:
int term_width;
int term_height;
size_t rows;
size_t cols;
size_t selected_completion_idx;
screen_data_t screen_data; screen_data_t screen_data;
/* Returns a rendering with invalid data, useful to indicate "no rendering" */
page_rendering_t();
}; };
typedef std::vector<completion_t> completion_list_t; typedef std::vector<completion_t> completion_list_t;
@ -79,9 +88,15 @@ class pager_t
/* Sets the index of the selected completion */ /* Sets the index of the selected completion */
void set_selected_completion(size_t completion_idx); void set_selected_completion(size_t completion_idx);
/* Changes the selected completion in the given direction according to the layout of the given rendering. Returns true if the values changed. */
bool select_next_completion_in_direction(cardinal_direction_t direction, const page_rendering_t &rendering);
/* Produces a rendering of the completions, at the given term size */ /* Produces a rendering of the completions, at the given term size */
page_rendering_t render() const; page_rendering_t render() const;
/* Updates the rendering if it's stale */
void update_rendering(page_rendering_t *rendering) const;
/* Indicates if there are no completions, and therefore nothing to render */ /* Indicates if there are no completions, and therefore nothing to render */
bool empty() const; bool empty() const;

View file

@ -201,6 +201,9 @@ public:
/** Current pager */ /** Current pager */
pager_t current_pager; pager_t current_pager;
/** Current page rendering */
page_rendering_t current_page_rendering;
/** Whether we are navigating the pager */ /** Whether we are navigating the pager */
bool is_navigating_pager; bool is_navigating_pager;
@ -539,6 +542,10 @@ static void reader_repaint()
std::vector<int> indents = data->indents; std::vector<int> indents = data->indents;
indents.resize(len); indents.resize(len);
// Re-render our completions page if necessary
data->current_pager.set_term_size(common_get_width(), common_get_height());
data->current_pager.update_rendering(&data->current_page_rendering);
s_write(&data->screen, s_write(&data->screen,
data->left_prompt_buff, data->left_prompt_buff,
data->right_prompt_buff, data->right_prompt_buff,
@ -547,7 +554,7 @@ static void reader_repaint()
&colors[0], &colors[0],
&indents[0], &indents[0],
data->buff_pos, data->buff_pos,
data->current_pager); data->current_page_rendering);
data->repaint_needed = false; data->repaint_needed = false;
} }
@ -1852,20 +1859,17 @@ static bool handle_completions(const std::vector<completion_t> &comp)
prefix.append(data->command_line, prefix_start + len - PREFIX_MAX_LEN, PREFIX_MAX_LEN); prefix.append(data->command_line, prefix_start + len - PREFIX_MAX_LEN, PREFIX_MAX_LEN);
} }
{
int is_quoted;
wchar_t quote; wchar_t quote;
parse_util_get_parameter_info(data->command_line, data->buff_pos, &quote, NULL, NULL); parse_util_get_parameter_info(data->command_line, data->buff_pos, &quote, NULL, NULL);
is_quoted = (quote != L'\0'); bool is_quoted = (quote != L'\0');
if (1) if (1)
{ {
pager_t pager; data->current_pager.set_prefix(prefix);
pager.set_term_size(common_get_width(), common_get_height()); data->current_pager.set_completions(surviving_completions);
pager.set_prefix(prefix);
pager.set_completions(surviving_completions); /* Invalidate our rendering */
data->current_pager = pager; data->current_page_rendering = page_rendering_t();
} }
else else
{ {
@ -1876,9 +1880,10 @@ static bool handle_completions(const std::vector<completion_t> &comp)
write_loop(1, "\n", 1); write_loop(1, "\n", 1);
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();
success = false; success = false;
} }
@ -2971,6 +2976,9 @@ const wchar_t *reader_readline(void)
/* The cycle index in our completion list */ /* The cycle index in our completion list */
size_t completion_cycle_idx = (size_t)(-1); size_t completion_cycle_idx = (size_t)(-1);
/* Indicates if we are currently navigating pager contents */
bool is_navigating_pager_contents = false;
/* The command line before completion */ /* The command line before completion */
wcstring cycle_command_line; wcstring cycle_command_line;
size_t cycle_cursor_pos = 0; size_t cycle_cursor_pos = 0;
@ -3152,6 +3160,8 @@ const wchar_t *reader_readline(void)
if (next_comp != NULL) if (next_comp != NULL)
{ {
is_navigating_pager_contents = true;
size_t cursor_pos = cycle_cursor_pos; size_t cursor_pos = cycle_cursor_pos;
const wcstring new_cmd_line = completion_apply_to_command_line(next_comp->completion, next_comp->flags, cycle_command_line, &cursor_pos, false); const wcstring new_cmd_line = completion_apply_to_command_line(next_comp->completion, next_comp->flags, cycle_command_line, &cursor_pos, false);
reader_set_buffer(new_cmd_line, cursor_pos); reader_set_buffer(new_cmd_line, cursor_pos);
@ -3628,6 +3638,16 @@ 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->current_pager.select_next_completion_in_direction(c == R_UP_LINE ? direction_north : direction_south, data->current_page_rendering))
{
reader_repaint();
}
}
else
{
/* Not navigating the pager contents */
int line_old = parse_util_get_line_from_offset(data->command_line, data->buff_pos); int line_old = parse_util_get_line_from_offset(data->command_line, data->buff_pos);
int line_new; int line_new;
@ -3661,6 +3681,7 @@ const wchar_t *reader_readline(void)
data->buff_pos = total_offset_new; data->buff_pos = total_offset_new;
reader_repaint(); reader_repaint();
} }
}
break; break;
} }

View file

@ -1237,7 +1237,7 @@ 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 pager_t &pager) const page_rendering_t &pager)
{ {
screen_data_t::cursor_t cursor_arr; screen_data_t::cursor_t cursor_arr;
@ -1325,12 +1325,8 @@ void s_write(screen_t *s,
s->desired.cursor = cursor_arr; s->desired.cursor = cursor_arr;
/* append pager_data */ /* Append pager_data (none if empty) */
if (! pager.empty()) s->desired.append_lines(pager.screen_data);
{
const page_rendering_t rendering = pager.render();
s->desired.append_lines(rendering.screen_data);
}
s_update(s, layout.left_prompt.c_str(), layout.right_prompt.c_str()); s_update(s, layout.left_prompt.c_str(), layout.right_prompt.c_str());
s_save_status(s); s_save_status(s);

View file

@ -16,7 +16,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include "highlight.h" #include "highlight.h"
class pager_t; class page_rendering_t;
/** /**
A class representing a single line of a screen. A class representing a single line of a screen.
@ -117,6 +117,11 @@ public:
{ {
this->line_datas.insert(this->line_datas.end(), d.line_datas.begin(), d.line_datas.end()); this->line_datas.insert(this->line_datas.end(), d.line_datas.begin(), d.line_datas.end());
} }
bool empty() const
{
return line_datas.empty();
}
}; };
/** /**
@ -205,7 +210,7 @@ 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 pager_t &pager_data); const page_rendering_t &pager_data);
/** /**
This function resets the screen buffers internal knowledge about This function resets the screen buffers internal knowledge about