2013-12-02 07:11:25 +08:00
# include "config.h"
2013-12-08 04:43:40 +08:00
# include "pager.h"
2014-01-14 08:41:22 +08:00
# include "highlight.h"
2013-12-08 04:43:40 +08:00
2013-12-02 07:11:25 +08:00
# include <stdlib.h>
# include <stdio.h>
# include <wchar.h>
# include <unistd.h>
# include <termios.h>
# include <string.h>
# include <map>
# include <algorithm>
# include <sys/types.h>
# include <sys/stat.h>
# ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
# endif
# include <sys/time.h>
# include <sys/wait.h>
# include <dirent.h>
# include <fcntl.h>
# include <locale.h>
# if HAVE_NCURSES_H
# include <ncurses.h>
# else
# include <curses.h>
# endif
# if HAVE_TERM_H
# include <term.h>
# elif HAVE_NCURSES_TERM_H
# include <ncurses/term.h>
# endif
# include <signal.h>
# ifdef HAVE_GETOPT_H
# include <getopt.h>
# endif
# include <errno.h>
# include <vector>
# include "fallback.h"
# include "util.h"
# include "wutil.h"
# include "common.h"
# include "complete.h"
# include "output.h"
# include "input_common.h"
# include "env_universal.h"
# include "print_help.h"
2014-01-15 17:36:09 +08:00
# include "highlight.h"
2013-12-02 07:11:25 +08:00
2014-01-14 08:41:22 +08:00
typedef pager_t : : comp_t comp_t ;
2013-12-02 07:11:25 +08:00
typedef std : : vector < completion_t > completion_list_t ;
typedef std : : vector < comp_t > comp_info_list_t ;
enum
{
LINE_UP = R_NULL + 1 ,
LINE_DOWN ,
PAGE_UP ,
PAGE_DOWN
}
;
enum
{
/*
Returnd by the pager if no more displaying is needed
*/
PAGER_DONE ,
/*
Returned by the pager if the completions would not fit in the specified number of columns
*/
PAGER_RETRY ,
/*
Returned by the pager if the terminal changes size
*/
PAGER_RESIZE
}
;
/**
The minimum width ( in characters ) the terminal may have for fish_pager to not refuse showing the completions
*/
# define PAGER_MIN_WIDTH 16
/**
The maximum number of columns of completion to attempt to fit onto the screen
*/
# define PAGER_MAX_COLS 6
/**
The string describing the single - character options accepted by fish_pager
*/
# define GETOPT_STRING "c:hr:qvp:"
/**
Error to use when given an invalid file descriptor for reading completions or writing output
*/
# define ERR_NOT_FD _( L"%ls: Argument '%s' is not a valid file descriptor\n" )
/**
This buffer is used to buffer the output of the pager to improve
screen redraw performance bu cutting down the number of write ( )
calls to only one .
*/
static std : : vector < char > pager_buffer ;
/**
This string contains the text that should be sent back to the calling program
*/
static wcstring out_buff ;
2014-01-18 04:04:03 +08:00
/* Returns numer / denom, rounding up */
static size_t divide_round_up ( size_t numer , size_t denom )
{
return numer / denom + ( numer % denom ? 1 : 0 ) ;
}
2013-12-02 07:11:25 +08:00
/**
This function calculates the minimum width for each completion
entry in the specified array_list . This width depends on the
terminal size , so this function should be called when the terminal
changes size .
*/
2014-01-14 08:41:22 +08:00
void pager_t : : recalc_min_widths ( comp_info_list_t * lst ) const
2013-12-02 07:11:25 +08:00
{
for ( size_t i = 0 ; i < lst - > size ( ) ; i + + )
{
comp_t * c = & lst - > at ( i ) ;
2014-01-14 08:41:22 +08:00
c - > min_width = mini ( c - > desc_width , maxi ( 0 , term_width / 3 - 2 ) ) +
mini ( c - > desc_width , maxi ( 0 , term_width / 5 - 4 ) ) + 4 ;
2013-12-02 07:11:25 +08:00
}
}
/**
Test if the specified character sequence has been entered on the
keyboard
*/
static int try_sequence ( const char * seq )
{
int j , k ;
wint_t c = 0 ;
for ( j = 0 ;
seq [ j ] ! = ' \0 ' & & seq [ j ] = = ( c = input_common_readch ( j > 0 ) ) ;
j + + )
;
if ( seq [ j ] = = ' \0 ' )
{
return 1 ;
}
else
{
input_common_unreadch ( c ) ;
for ( k = j - 1 ; k > = 0 ; k - - )
input_common_unreadch ( seq [ k ] ) ;
}
return 0 ;
}
/**
Read a character from keyboard
*/
static wint_t readch ( )
{
struct mapping
{
const char * seq ;
wint_t bnd ;
}
;
struct mapping m [ ] =
{
{
" \x1b [A " , LINE_UP
}
,
{
key_up , LINE_UP
}
,
{
" \x1b [B " , LINE_DOWN
}
,
{
key_down , LINE_DOWN
}
,
{
key_ppage , PAGE_UP
}
,
{
key_npage , PAGE_DOWN
}
,
{
" " , PAGE_DOWN
}
,
{
" \t " , PAGE_DOWN
}
,
{
0 , 0
}
}
;
int i ;
for ( i = 0 ; m [ i ] . bnd ; i + + )
{
if ( ! m [ i ] . seq )
{
continue ;
}
if ( try_sequence ( m [ i ] . seq ) )
return m [ i ] . bnd ;
}
return input_common_readch ( 0 ) ;
}
/**
Print the specified string , but use at most the specified amount of
space . If the whole string can ' t be fitted , ellipsize it .
\ param str the string to print
2014-01-15 17:36:09 +08:00
\ param color the color to apply to every printed character
2013-12-02 07:11:25 +08:00
\ param max the maximum space that may be used for printing
\ param has_more if this flag is true , this is not the entire string , and the string should be ellisiszed even if the string fits but takes up the whole space .
*/
2014-01-15 17:36:09 +08:00
static int print_max ( const wcstring & str , highlight_spec_t color , int max , bool has_more , line_t * line )
2014-01-14 08:41:22 +08:00
{
int written = 0 ;
for ( size_t i = 0 ; i < str . size ( ) ; i + + )
{
wchar_t c = str . at ( i ) ;
if ( written + wcwidth ( c ) > max )
break ;
if ( ( written + wcwidth ( c ) = = max ) & & ( has_more | | i + 1 < str . size ( ) ) )
{
line - > append ( ellipsis_char , color ) ;
written + = wcwidth ( ellipsis_char ) ;
break ;
}
line - > append ( c , color ) ;
written + = wcwidth ( c ) ;
}
return written ;
}
2013-12-02 07:11:25 +08:00
/**
Print the specified item using at the specified amount of space
*/
2014-01-16 10:21:38 +08:00
line_t pager_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
2013-12-02 07:11:25 +08:00
{
int comp_width = 0 , desc_width = 0 ;
int written = 0 ;
2014-01-14 08:41:22 +08:00
line_t line_data ;
2013-12-02 07:11:25 +08:00
if ( c - > pref_width < = width )
{
/*
The entry fits , we give it as much space as it wants
*/
comp_width = c - > comp_width ;
desc_width = c - > desc_width ;
}
else
{
/*
The completion and description won ' t fit on the
allocated space . Give a maximum of 2 / 3 of the
space to the completion , and whatever is left to
the description .
*/
int desc_all = c - > desc_width ? c - > desc_width + 4 : 0 ;
comp_width = maxi ( mini ( c - > comp_width , 2 * ( width - 4 ) / 3 ) , width - desc_all ) ;
if ( c - > desc_width )
desc_width = width - comp_width - 4 ;
}
2014-01-14 08:41:22 +08:00
2014-01-15 17:36:09 +08:00
int bg_color = secondary ? highlight_spec_pager_secondary : highlight_spec_normal ;
2014-01-16 10:21:38 +08:00
if ( selected )
{
bg_color = highlight_spec_search_match ;
}
2014-01-14 08:41:22 +08:00
2013-12-02 07:11:25 +08:00
for ( size_t i = 0 ; i < c - > comp . size ( ) ; i + + )
{
const wcstring & comp = c - > comp . at ( i ) ;
2014-01-14 08:41:22 +08:00
2013-12-02 07:11:25 +08:00
if ( i ! = 0 )
2014-01-15 17:36:09 +08:00
written + = print_max ( L " " , highlight_spec_normal , comp_width - written , true /* has_more */ , & line_data ) ;
2014-01-14 08:41:22 +08:00
2014-01-15 17:36:09 +08:00
int packed_color = highlight_spec_pager_prefix | highlight_make_background ( bg_color ) ;
2014-01-14 08:41:22 +08:00
written + = print_max ( prefix , packed_color , comp_width - written , ! comp . empty ( ) , & line_data ) ;
2014-01-15 17:36:09 +08:00
packed_color = highlight_spec_pager_completion | highlight_make_background ( bg_color ) ;
2014-01-14 08:41:22 +08:00
written + = print_max ( comp , packed_color , comp_width - written , i + 1 < c - > comp . size ( ) , & line_data ) ;
2013-12-02 07:11:25 +08:00
}
if ( desc_width )
{
2014-01-15 17:36:09 +08:00
int packed_color = highlight_spec_pager_description | highlight_make_background ( bg_color ) ;
2013-12-02 07:11:25 +08:00
while ( written < ( width - desc_width - 2 ) )
{
2014-01-14 08:41:22 +08:00
written + = print_max ( L " " , packed_color , 1 , false , & line_data ) ;
2013-12-02 07:11:25 +08:00
}
2014-01-14 08:41:22 +08:00
written + = print_max ( L " ( " , packed_color , 1 , false , & line_data ) ;
written + = print_max ( c - > desc , packed_color , desc_width , false , & line_data ) ;
written + = print_max ( L " ) " , packed_color , 1 , false , & line_data ) ;
2013-12-02 07:11:25 +08:00
}
else
{
while ( written < width )
{
2014-01-14 08:41:22 +08:00
written + = print_max ( L " " , 0 , 1 , false , & line_data ) ;
2013-12-02 07:11:25 +08:00
}
}
2014-01-15 07:39:53 +08:00
2014-01-14 08:41:22 +08:00
return line_data ;
2013-12-02 07:11:25 +08:00
}
/**
Print the specified part of the completion list , using the
specified column offsets and quoting style .
\ param l The list of completions to print
\ param cols number of columns to print in
\ param width An array specifying the width of each column
\ param row_start The first row to print
\ param row_stop the row after the last row to print
\ param prefix The string to print before each completion
*/
2014-01-14 08:41:22 +08:00
void pager_t : : completion_print ( int cols , int * width_per_column , int row_start , int row_stop , const wcstring & prefix , const comp_info_list_t & lst , page_rendering_t * rendering ) const
2013-12-02 07:11:25 +08:00
{
size_t rows = ( lst . size ( ) - 1 ) / cols + 1 ;
2014-01-14 08:41:22 +08:00
for ( size_t row = row_start ; row < row_stop ; row + + )
2013-12-02 07:11:25 +08:00
{
2014-01-14 08:41:22 +08:00
for ( size_t col = 0 ; col < cols ; col + + )
2013-12-02 07:11:25 +08:00
{
2014-01-14 08:41:22 +08:00
int is_last = ( col = = ( cols - 1 ) ) ;
2013-12-02 07:11:25 +08:00
2014-01-14 08:41:22 +08:00
if ( lst . size ( ) < = col * rows + row )
2013-12-02 07:11:25 +08:00
continue ;
2014-01-16 10:21:38 +08:00
size_t idx = col * rows + row ;
const comp_t * el = & lst . at ( idx ) ;
bool is_selected = ( idx = = this - > selected_completion_idx ) ;
2014-01-14 08:41:22 +08:00
/* Print this completion on its own "line" */
2014-01-16 10:21:38 +08:00
line_t line = completion_print_item ( prefix , el , row , col , width_per_column [ col ] - ( is_last ? 0 : 2 ) , row % 2 , is_selected , rendering ) ;
2014-01-14 08:41:22 +08:00
2014-01-15 07:39:53 +08:00
/* If there's more to come, append two spaces */
if ( col + 1 < cols )
{
line . append ( L ' ' , 0 ) ;
line . append ( L ' ' , 0 ) ;
}
2014-01-14 08:41:22 +08:00
/* Append this to the real line */
rendering - > screen_data . create_line ( row ) . append_line ( line ) ;
}
}
}
/* Trim leading and trailing whitespace, and compress other whitespace runs into a single space. */
static void mangle_1_completion_description ( wcstring * str )
{
size_t leading = 0 , trailing = 0 , len = str - > size ( ) ;
// Skip leading spaces
for ( ; leading < len ; leading + + )
{
if ( ! iswspace ( str - > at ( leading ) ) )
break ;
}
// Compress runs of spaces to a single space
bool was_space = false ;
for ( ; leading < len ; leading + + )
{
wchar_t wc = str - > at ( leading ) ;
bool is_space = iswspace ( wc ) ;
if ( ! is_space )
{
// normal character
str - > at ( trailing + + ) = wc ;
}
else if ( ! was_space )
{
// initial space in a run
str - > at ( trailing + + ) = L ' ' ;
}
else
{
// non-initial space in a run, do nothing
}
was_space = is_space ;
}
// leading is now at len, trailing is the new length of the string
// Delete trailing spaces
while ( trailing > 0 & & iswspace ( str - > at ( trailing - 1 ) ) )
{
trailing - - ;
}
str - > resize ( trailing ) ;
}
static void join_completions ( comp_info_list_t * comps )
{
// A map from description to index in the completion list of the element with that description
// The indexes are stored +1
std : : map < wcstring , size_t > desc_table ;
// note that we mutate the completion list as we go, so the size changes
for ( size_t i = 0 ; i < comps - > size ( ) ; i + + )
{
const comp_t & new_comp = comps - > at ( i ) ;
const wcstring & desc = new_comp . desc ;
if ( desc . empty ( ) )
continue ;
// See if it's in the table
size_t prev_idx_plus_one = desc_table [ desc ] ;
if ( prev_idx_plus_one = = 0 )
{
// We're the first with this description
desc_table [ desc ] = i + 1 ;
}
else
{
// There's a prior completion with this description. Append the new ones to it.
comp_t * prior_comp = & comps - > at ( prev_idx_plus_one - 1 ) ;
prior_comp - > comp . insert ( prior_comp - > comp . end ( ) , new_comp . comp . begin ( ) , new_comp . comp . end ( ) ) ;
// Erase the element at this index, and decrement the index to reflect that fact
comps - > erase ( comps - > begin ( ) + i ) ;
i - = 1 ;
}
}
}
/** Generate a list of comp_t structures from a list of completions */
static comp_info_list_t process_completions_into_infos ( const completion_list_t & lst , const wcstring & prefix )
{
const size_t lst_size = lst . size ( ) ;
// Make the list of the correct size up-front
comp_info_list_t result ( lst_size ) ;
for ( size_t i = 0 ; i < lst_size ; i + + )
{
const completion_t & comp = lst . at ( i ) ;
comp_t * comp_info = & result . at ( i ) ;
// Append the single completion string. We may later merge these into multiple.
comp_info - > comp . push_back ( escape_string ( comp . completion , ESCAPE_ALL | ESCAPE_NO_QUOTED ) ) ;
// Append the mangled description
comp_info - > desc = comp . description ;
mangle_1_completion_description ( & comp_info - > desc ) ;
}
return result ;
}
2013-12-02 07:11:25 +08:00
2014-01-14 08:41:22 +08:00
void pager_t : : measure_completion_infos ( comp_info_list_t * infos , const wcstring & prefix ) const
{
size_t prefix_len = my_wcswidth ( prefix . c_str ( ) ) ;
for ( size_t i = 0 ; i < infos - > size ( ) ; i + + )
{
comp_t * comp = & infos - > at ( i ) ;
// Compute comp_width
const wcstring_list_t & comp_strings = comp - > comp ;
for ( size_t j = 0 ; j < comp_strings . size ( ) ; j + + )
{
// If there's more than one, append the length of ', '
if ( j > = 1 )
comp - > comp_width + = 2 ;
comp - > comp_width + = prefix_len + my_wcswidth ( comp_strings . at ( j ) . c_str ( ) ) ;
}
// Compute desc_width
comp - > desc_width = my_wcswidth ( comp - > desc . c_str ( ) ) ;
// Compute preferred width
comp - > pref_width = comp - > comp_width + comp - > desc_width + ( comp - > desc_width ? 4 : 0 ) ;
}
recalc_min_widths ( infos ) ;
}
#if 0
page_rendering_t render_completions ( const completion_list_t & raw_completions , const wcstring & prefix )
{
// Save old output function so we can restore it
int ( * const saved_writer_func ) ( char ) = output_get_writer ( ) ;
output_set_writer ( & pager_buffered_writer ) ;
// Get completion infos out of it
comp_info_list_t completion_infos = process_completions_into_infos ( raw_completions , prefix . c_str ( ) ) ;
// Maybe join them
if ( prefix = = L " - " )
join_completions ( & completion_infos ) ;
// Compute their various widths
measure_completion_infos ( & completion_infos , prefix ) ;
/**
Try to print the completions . Start by trying to print the
list in PAGER_MAX_COLS columns , if the completions won ' t
fit , reduce the number of columns by one . Printing a single
column never fails .
*/
for ( int i = PAGER_MAX_COLS ; i > 0 ; i - - )
{
switch ( completion_try_print ( i , prefix , completion_infos ) )
{
case PAGER_RETRY :
break ;
case PAGER_DONE :
i = 0 ;
break ;
case PAGER_RESIZE :
/*
This means we got a resize event , so we start
over from the beginning . Since it the screen got
bigger , we might be able to fit all completions
on - screen .
*/
i = PAGER_MAX_COLS + 1 ;
break ;
2013-12-02 07:11:25 +08:00
}
}
2014-01-14 08:41:22 +08:00
fwprintf ( out_file , L " %ls " , out_buff . c_str ( ) ) ;
// Restore saved writer function
pager_buffer . clear ( ) ;
output_set_writer ( saved_writer_func ) ;
}
# endif
void pager_t : : set_completions ( const completion_list_t & raw_completions )
{
completions = raw_completions ;
// Get completion infos out of it
completion_infos = process_completions_into_infos ( raw_completions , prefix . c_str ( ) ) ;
// Maybe join them
if ( prefix = = L " - " )
join_completions ( & completion_infos ) ;
// Compute their various widths
measure_completion_infos ( & completion_infos , prefix ) ;
2013-12-02 07:11:25 +08:00
}
2014-01-16 10:21:38 +08:00
void pager_t : : set_prefix ( const wcstring & pref )
{
prefix = pref ;
}
2014-01-14 08:41:22 +08:00
void pager_t : : set_term_size ( int w , int h )
{
assert ( w > 0 ) ;
assert ( h > 0 ) ;
term_width = w ;
term_height = h ;
}
2013-12-02 07:11:25 +08:00
/**
Try to print the list of completions l with the prefix prefix using
cols as the number of columns . Return 1 if the completion list was
printed , 0 if the terminal is to narrow for the specified number of
columns . Always succeeds if cols is 1.
If all the elements do not fit on the screen at once , make the list
scrollable using the up , down and space keys to move . The list will
exit when any other key is pressed .
\ param cols the number of columns to try to fit onto the screen
\ param prefix the character string to prefix each completion with
\ param l the list of completions
\ return one of PAGER_RETRY , PAGER_DONE and PAGER_RESIZE
*/
2014-01-14 08:41:22 +08:00
int pager_t : : completion_try_print ( int cols , const wcstring & prefix , const comp_info_list_t & lst , page_rendering_t * rendering ) const
2013-12-02 07:11:25 +08:00
{
/*
The calculated preferred width of each column
*/
int pref_width [ PAGER_MAX_COLS ] = { 0 } ;
/*
The calculated minimum width of each column
*/
int min_width [ PAGER_MAX_COLS ] = { 0 } ;
/*
If the list can be printed with this width , width will contain the width of each column
*/
int * width = pref_width ;
/*
Set to one if the list should be printed at this width
*/
int print = 0 ;
2014-01-18 04:04:03 +08:00
int rows = ( int ) divide_round_up ( lst . size ( ) , cols ) ;
2013-12-02 07:11:25 +08:00
int pref_tot_width = 0 ;
int min_tot_width = 0 ;
int res = PAGER_RETRY ;
2014-01-14 08:41:22 +08:00
/* Skip completions on tiny terminals */
if ( term_width < PAGER_MIN_WIDTH )
2013-12-02 07:11:25 +08:00
return PAGER_DONE ;
/* Calculate how wide the list would be */
2014-01-14 08:41:22 +08:00
for ( long j = 0 ; j < cols ; j + + )
2013-12-02 07:11:25 +08:00
{
2014-01-14 08:41:22 +08:00
for ( long i = 0 ; i < rows ; i + + )
2013-12-02 07:11:25 +08:00
{
int pref , min ;
const comp_t * c ;
if ( lst . size ( ) < = j * rows + i )
continue ;
c = & lst . at ( j * rows + i ) ;
pref = c - > pref_width ;
min = c - > min_width ;
if ( j ! = cols - 1 )
{
pref + = 2 ;
min + = 2 ;
}
min_width [ j ] = maxi ( min_width [ j ] ,
min ) ;
pref_width [ j ] = maxi ( pref_width [ j ] ,
pref ) ;
}
min_tot_width + = min_width [ j ] ;
pref_tot_width + = pref_width [ j ] ;
}
/*
Force fit if one column
*/
if ( cols = = 1 )
{
2014-01-14 08:41:22 +08:00
if ( pref_tot_width > term_width )
2013-12-02 07:11:25 +08:00
{
2014-01-14 08:41:22 +08:00
pref_width [ 0 ] = term_width ;
2013-12-02 07:11:25 +08:00
}
width = pref_width ;
print = 1 ;
}
2014-01-14 08:41:22 +08:00
else if ( pref_tot_width < = term_width )
2013-12-02 07:11:25 +08:00
{
/* Terminal is wide enough. Print the list! */
width = pref_width ;
print = 1 ;
}
else
{
long next_rows = ( lst . size ( ) - 1 ) / ( cols - 1 ) + 1 ;
/* fwprintf( stderr,
L " cols %d, min_tot %d, term %d, rows=%d, nextrows %d, termrows %d, diff %d \n " ,
cols ,
2014-01-14 08:41:22 +08:00
min_tot_width , term_width ,
rows , next_rows , term_height ,
pref_tot_width - term_width ) ;
2013-12-02 07:11:25 +08:00
*/
2014-01-14 08:41:22 +08:00
if ( min_tot_width < term_width & &
( ( ( rows < term_height ) & & ( next_rows > = term_height ) ) | |
( pref_tot_width - term_width < 4 & & cols < 3 ) ) )
2013-12-02 07:11:25 +08:00
{
/*
Terminal almost wide enough , or squeezing makes the
whole list fit on - screen .
This part of the code is really important . People hate
having to scroll through the completion list . In cases
where there are a huge number of completions , it can ' t
be helped , but it is not uncommon for the completions to
_almost_ fit on one screen . In those cases , it is almost
always desirable to ' squeeze ' the completions into a
single page .
If we are using N columns and can get everything to
fit using squeezing , but everything would also fit
using N - 1 columns , don ' t try .
*/
int tot_width = min_tot_width ;
width = min_width ;
2014-01-14 08:41:22 +08:00
while ( tot_width < term_width )
2013-12-02 07:11:25 +08:00
{
2014-01-14 08:41:22 +08:00
for ( long i = 0 ; ( i < cols ) & & ( tot_width < term_width ) ; i + + )
2013-12-02 07:11:25 +08:00
{
if ( width [ i ] < pref_width [ i ] )
{
width [ i ] + + ;
tot_width + + ;
}
}
}
print = 1 ;
}
}
if ( print )
{
res = PAGER_DONE ;
2014-01-14 08:41:22 +08:00
if ( rows < term_height )
2013-12-02 07:11:25 +08:00
{
2014-01-14 08:41:22 +08:00
completion_print ( cols , width , 0 , rows , prefix , lst , rendering ) ;
2013-12-02 07:11:25 +08:00
}
else
{
2014-01-14 08:41:22 +08:00
assert ( 0 ) ;
2013-12-02 07:11:25 +08:00
int npos , pos = 0 ;
int do_loop = 1 ;
2014-01-14 08:41:22 +08:00
completion_print ( cols , width , 0 , term_height - 1 , prefix , lst , rendering ) ;
/* List does not fit on screen. Print one screenful and leave a scrollable interface */
2013-12-02 07:11:25 +08:00
while ( do_loop )
{
2014-01-15 17:36:09 +08:00
set_color ( rgb_color_t : : black ( ) , highlight_get_color ( highlight_spec_pager_progress , true ) ) ;
2014-01-14 08:41:22 +08:00
wcstring msg = format_string ( _ ( L " %d to %d of %d " ) , pos , pos + term_height - 1 , rows ) ;
2013-12-02 07:11:25 +08:00
msg . append ( L " \r " ) ;
writestr ( msg . c_str ( ) ) ;
set_color ( rgb_color_t : : normal ( ) , rgb_color_t : : normal ( ) ) ;
int c = readch ( ) ;
switch ( c )
{
case LINE_UP :
{
if ( pos > 0 )
{
pos - - ;
writembs ( tparm ( cursor_address , 0 , 0 ) ) ;
writembs ( scroll_reverse ) ;
2014-01-14 08:41:22 +08:00
completion_print ( cols , width , pos , pos + 1 , prefix , lst , rendering ) ;
writembs ( tparm ( cursor_address , term_height - 1 , 0 ) ) ;
2013-12-02 07:11:25 +08:00
writembs ( clr_eol ) ;
}
break ;
}
case LINE_DOWN :
{
2014-01-14 08:41:22 +08:00
if ( pos < = ( rows - term_height ) )
2013-12-02 07:11:25 +08:00
{
pos + + ;
2014-01-14 08:41:22 +08:00
completion_print ( cols , width , pos + term_height - 2 , pos + term_height - 1 , prefix , lst , rendering ) ;
2013-12-02 07:11:25 +08:00
}
break ;
}
case PAGE_DOWN :
{
2014-01-14 08:41:22 +08:00
npos = mini ( ( int ) ( rows - term_height + 1 ) , ( int ) ( pos + term_height - 1 ) ) ;
2013-12-02 07:11:25 +08:00
if ( npos ! = pos )
{
pos = npos ;
2014-01-14 08:41:22 +08:00
completion_print ( cols , width , pos , pos + term_height - 1 , prefix , lst , rendering ) ;
2013-12-02 07:11:25 +08:00
}
else
{
if ( flash_screen )
writembs ( flash_screen ) ;
}
break ;
}
case PAGE_UP :
{
2014-01-14 08:41:22 +08:00
npos = maxi ( 0 , pos - term_height + 1 ) ;
2013-12-02 07:11:25 +08:00
if ( npos ! = pos )
{
pos = npos ;
2014-01-14 08:41:22 +08:00
completion_print ( cols , width , pos , pos + term_height - 1 , prefix , lst , rendering ) ;
2013-12-02 07:11:25 +08:00
}
else
{
if ( flash_screen )
writembs ( flash_screen ) ;
}
break ;
}
case R_NULL :
{
do_loop = 0 ;
res = PAGER_RESIZE ;
break ;
}
default :
{
out_buff . push_back ( c ) ;
do_loop = 0 ;
break ;
}
}
}
writembs ( clr_eol ) ;
}
}
return res ;
}
2014-01-14 08:41:22 +08:00
page_rendering_t pager_t : : render ( ) const
2013-12-02 07:11:25 +08:00
{
2014-01-18 04:04:03 +08:00
2013-12-02 07:11:25 +08:00
/**
Try to print the completions . Start by trying to print the
list in PAGER_MAX_COLS columns , if the completions won ' t
fit , reduce the number of columns by one . Printing a single
column never fails .
*/
2014-01-14 08:41:22 +08:00
page_rendering_t rendering ;
2014-01-18 04:04:03 +08:00
rendering . term_width = this - > term_width ;
rendering . term_height = this - > term_height ;
rendering . selected_completion_idx = this - > selected_completion_idx ;
if ( ! this - > empty ( ) )
2013-12-02 07:11:25 +08:00
{
2014-01-18 04:04:03 +08:00
int cols ;
bool done = false ;
for ( cols = PAGER_MAX_COLS ; cols > 0 & & ! done ; cols - - )
2013-12-02 07:11:25 +08:00
{
2014-01-18 04:04:03 +08:00
/* Initially empty rendering */
rendering . screen_data . resize ( 0 ) ;
switch ( completion_try_print ( cols , prefix , completion_infos , & rendering ) )
{
case PAGER_RETRY :
break ;
case PAGER_DONE :
done = true ;
break ;
case PAGER_RESIZE :
/*
This means we got a resize event , so we start
over from the beginning . Since it the screen got
bigger , we might be able to fit all completions
on - screen .
*/
cols = PAGER_MAX_COLS + 1 ;
break ;
}
2013-12-02 07:11:25 +08:00
}
2014-01-18 04:04:03 +08:00
assert ( cols > = 0 ) ;
rendering . cols = ( size_t ) cols ;
rendering . rows = divide_round_up ( completion_infos . size ( ) , rendering . cols ) ;
2013-12-02 07:11:25 +08:00
}
2014-01-14 08:41:22 +08:00
return rendering ;
2013-12-02 07:11:25 +08:00
}
2014-01-16 10:21:38 +08:00
2014-01-18 04:04:03 +08:00
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 ( ) ;
}
}
2014-01-16 10:21:38 +08:00
pager_t : : pager_t ( ) : term_width ( 0 ) , term_height ( 0 ) , selected_completion_idx ( - 1 )
{
}
bool pager_t : : empty ( ) const
{
return completions . empty ( ) ;
}
void pager_t : : set_selected_completion ( size_t idx )
{
this - > selected_completion_idx = idx ;
}
2014-01-18 04:04:03 +08:00
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 ;
}
}
2014-01-16 10:21:38 +08:00
void pager_t : : clear ( )
{
completions . clear ( ) ;
completion_infos . clear ( ) ;
prefix . clear ( ) ;
}
2014-01-18 04:04:03 +08:00
page_rendering_t : : page_rendering_t ( ) : term_width ( - 1 ) , term_height ( - 1 ) , rows ( 0 ) , cols ( 0 ) , selected_completion_idx ( - 1 )
{
}