diff options
Diffstat (limited to 'fish_pager.c')
-rw-r--r-- | fish_pager.c | 992 |
1 files changed, 992 insertions, 0 deletions
diff --git a/fish_pager.c b/fish_pager.c new file mode 100644 index 00000000..cc24cf25 --- /dev/null +++ b/fish_pager.c @@ -0,0 +1,992 @@ +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <wchar.h> +#include <unistd.h> +#include <termios.h> +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#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_TERMIO_H +#include <termio.h> +#endif + +#include <term.h> +#include <signal.h> + +#include "util.h" +#include "wutil.h" +#include "common.h" +#include "complete.h" +#include "output.h" +#include "input_common.h" +#include "env_universal.h" + +#define WCHAR_END 0x80000000 + +enum +{ + LINE_UP = R_NULL+1, + LINE_DOWN, + PAGE_UP, + PAGE_DOWN +} + ; + + +enum +{ + HIGHLIGHT_PAGER_PREFIX, + HIGHLIGHT_PAGER_COMPLETION, + HIGHLIGHT_PAGER_DESCRIPTION, + HIGHLIGHT_PAGER_PROGRESS +} +; + +/** + This struct should be continually updated by signals as the term + resizes, and as such always contain the correct current size. +*/ + +static struct winsize termsize; + +static struct termios saved_modes; +static struct termios pager_modes; + +static int is_ca_mode = 0; + + +/** + The environment variables used to specify the color of different + tokens. +*/ +static wchar_t *hightlight_var[] = +{ + L"fish_pager_color_prefix", + L"fish_pager_color_completion", + L"fish_pager_color_description", + L"fish_pager_color_progress" +} + ; + +static string_buffer_t out_buff; +static FILE *out_file; + + +int get_color( int highlight ) +{ + if( highlight < 0 ) + return FISH_COLOR_NORMAL; + if( highlight >= (4) ) + return FISH_COLOR_NORMAL; + + wchar_t *val = env_universal_get( hightlight_var[highlight]); + + if( val == 0 ) + return FISH_COLOR_NORMAL; + + if( val == 0 ) + { + return FISH_COLOR_NORMAL; + } + + return output_color_code( val ); +} + +int try_sequence( 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; +} + +static wint_t readch() +{ + struct mapping + { + char *seq; + wint_t bnd; + } + ; + + struct mapping m[]= + { + { + "\e[A", LINE_UP + } + , + { + key_up, LINE_UP + } + , + { + "\e[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].seq; i++ ) + { + if( try_sequence(m[i].seq ) ) + return m[i].bnd; + } + return input_common_readch(0); +} + + +/** + 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 + \param is_quoted Whether to print the completions are in a quoted environment +*/ + +static void completion_print( int cols, + int *width, + int row_start, + int row_stop, + wchar_t *prefix, + int is_quoted, + array_list_t *l) +{ + + int rows = (al_get_count( l )-1)/cols+1; + int i, j; + int prefix_width= my_wcswidth(prefix); + + for( i = row_start; i<row_stop; i++ ) + { + for( j = 0; j < cols; j++ ) + { + wchar_t *el, *el_end; + + if( al_get_count( l ) <= j*rows + i ) + continue; + + el = (wchar_t *)al_get( l, j*rows + i ); + el_end= wcschr( el, COMPLETE_SEP ); + + set_color( get_color(HIGHLIGHT_PAGER_PREFIX),FISH_COLOR_NORMAL ); + + writestr( prefix ); + + set_color( get_color(HIGHLIGHT_PAGER_COMPLETION),FISH_COLOR_IGNORE ); + + if( el_end == 0 ) + { + /* We do not have a description for this completion */ + int written = 0; + int max_written = width[j] - prefix_width - (j==cols-1?0:2); + + if( is_quoted ) + { + for( i=0; i<max_written; i++ ) + { + if( !el[i] ) + break; + writech( el[i] ); + written+= wcwidth( el[i] ); + } + } + else + { + written = write_escaped_str( el, max_written ); + } + + set_color( get_color( HIGHLIGHT_PAGER_DESCRIPTION ), + FISH_COLOR_IGNORE ); + + writespace( width[j]- + written- + prefix_width ); + } + else + { + int whole_desc_width = my_wcswidth(el_end+1); + int whole_comp_width; + + /* + Temporarily drop the description so that wcswidth et + al only calculate the width of the completion. + */ + *el_end = L'\0'; + + /* + Calculate preferred completion width + */ + if( is_quoted ) + { + whole_comp_width = my_wcswidth(el); + } + else + { + wchar_t *tmp = escape( wcsdup(el), 1 ); + whole_comp_width = my_wcswidth( tmp ); + free(tmp); + } + + /* + Calculate how wide this entry 'wants' to be + */ + int pref_width = whole_desc_width + 4 + prefix_width + 2 - + (j==cols-1?2:0) + whole_comp_width; + + int comp_width, desc_width; + + if( pref_width <= width[j] ) + { + /* + The entry fits, we give it as much space as it wants + */ + comp_width = whole_comp_width; + desc_width = whole_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 sum = width[j] - prefix_width - 4 - 2 + (j==cols-1?2:0); + + comp_width = maxi( mini( whole_comp_width, + 2*sum/3 ), + sum - whole_desc_width ); + desc_width = sum-comp_width; + } + + /* First we must print the completion. */ + if( is_quoted ) + { + writestr_ellipsis( el, comp_width); + } + else + { + write_escaped_str( el, comp_width ); + } + + /* Put the description back */ + *el_end = COMPLETE_SEP; + + /* And print it */ + set_color( get_color(HIGHLIGHT_PAGER_DESCRIPTION), + FISH_COLOR_IGNORE ); + writespace( maxi( 2, + width[j] + - comp_width + - desc_width + - 4 + - prefix_width + + (j==cols-1?2:0) ) ); + /* Print description */ + writestr(L"("); + writestr_ellipsis( el_end+1, desc_width); + writestr(L")"); + + if( j != cols-1) + writestr( L" " ); + + } + } + writech( L'\n' ); + } +} + +/** + Calculates how long the specified string would be when printed on the command line. + + \param str The string to be printed. + \param is_quoted Whether the string would be printed quoted or unquoted + \param pref_width the preferred width for this item + \param min_width the minimum width for this item +*/ +static void printed_length( wchar_t *str, + int is_quoted, + int *pref_width, + int *min_width ) +{ + if( is_quoted ) + { + wchar_t *sep = wcschr(str,COMPLETE_SEP); + if( sep ) + { + *sep=0; + int cw = my_wcswidth( str ); + int dw = my_wcswidth(sep+1); + + if( termsize.ws_col > 80 ) + dw = mini( dw, termsize.ws_col/3 ); + + + *pref_width = cw+dw+4; + + if( dw > termsize.ws_col/3 ) + { + dw = termsize.ws_col/3; + } + + *min_width=cw+dw+4; + + *sep= COMPLETE_SEP; + return; + } + else + { + *pref_width=*min_width= my_wcswidth( str ); + return; + } + + } + else + { + int comp_len=0, desc_len=0; + int has_description = 0; + while( *str != 0 ) + { + switch( *str ) + { + case L'\n': + case L'\b': + case L'\r': + case L'\e': + case L'\t': + case L'\\': + case L'&': + case L'$': + case L' ': + case L'#': + case L'^': + case L'<': + case L'>': + case L'@': + case L'(': + case L')': + case L'{': + case L'}': + case L'?': + case L'*': + case L'|': + case L';': + case L':': + if( has_description ) + desc_len++; + else + comp_len+=2; + break; + + case COMPLETE_SEP: + has_description = 1; + break; + + default: + if( has_description ) + desc_len+= wcwidth(*str); + else + comp_len+= wcwidth(*str); + break; + } + str++; + } + if( has_description ) + { + /* + Mangle long descriptions to make formating look nicer + */ + debug( 3, L"Desc, width = %d %d\n", comp_len, desc_len ); +// if( termsize.ws_col > 80 ) +// desc_len = mini( desc_len, termsize.ws_col/3 ); + + *pref_width = comp_len+ desc_len+4;; + + comp_len = mini( comp_len, maxi(0,termsize.ws_col/3 - 2)); + desc_len = mini( desc_len, maxi(0,termsize.ws_col/5 - 4)); + + *min_width = comp_len+ desc_len+4; + return; + } + else + { + debug( 3, L"No desc, width = %d\n", comp_len ); + + *pref_width=*min_width= comp_len; + return; + } + + } +} + + +/** + 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 is_quoted whether the completions should be quoted + \param l the list of completions + + \return zero if the specified number of columns do not fit, no-zero otherwise +*/ + +static int completion_try_print( int cols, + wchar_t *prefix, + int is_quoted, + array_list_t *l ) +{ + /* + The calculated preferred width of each column + */ + int pref_width[32]; + /* + The calculated minimum width of each column + */ + int min_width[32]; + /* + 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; + + int i, j; + + int rows = (al_get_count( l )-1)/cols+1; + + int pref_tot_width=0; + int min_tot_width = 0; + int prefix_width = my_wcswidth( prefix ); + + int res=0; + /* + Skip completions on tiny terminals + */ + + if( termsize.ws_col < 16 ) + return 1; + + memset( pref_width, 0, sizeof(pref_width) ); + memset( min_width, 0, sizeof(min_width) ); + + /* Calculated how wide the list would be */ + for( j = 0; j < cols; j++ ) + { + for( i = 0; i<rows; i++ ) + { + int pref,min; + wchar_t *el; + if( al_get_count( l ) <= j*rows + i ) + continue; + + el = (wchar_t *)al_get( l, j*rows + i ); + printed_length( el, is_quoted, &pref, &min ); + + pref += prefix_width; + min += prefix_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) + { + if( pref_tot_width > termsize.ws_col ) + { + pref_width[0] = termsize.ws_col; + } + width = pref_width; + print=1; + } + else if( pref_tot_width <= termsize.ws_col ) + { + /* Terminal is wide enough. Print the list! */ + width = pref_width; + print=1; + } + else + { + int next_rows = (al_get_count( l )-1)/(cols-1)+1; +/* fwprintf( stderr, + L"cols %d, min_tot %d, term %d, rows=%d, nextrows %d, termrows %d, diff %d\n", + cols, + min_tot_width, termsize.ws_col, + rows, next_rows, termsize.ws_row, + pref_tot_width-termsize.ws_col ); +*/ + if( min_tot_width < termsize.ws_col && + ( ( (rows < termsize.ws_row) && (next_rows >= termsize.ws_row ) ) || + ( pref_tot_width-termsize.ws_col< 4 && cols < 3 ) ) ) + { + /* + Terminal almost wide enough, or squeezing makes the whole list fit on-screen + */ + int tot_width = min_tot_width; + width = min_width; + + while( tot_width < termsize.ws_col ) + { + for( i=0; (i<cols) && ( tot_width < termsize.ws_col ); i++ ) + { + if( width[i] < pref_width[i] ) + { + width[i]++; + tot_width++; + } + } + } + print=1; + } + } + +// return cols==1; + + if( print ) + { + res=1; + if( rows < termsize.ws_row ) + { + /* List fits on screen. Print it and leave */ + if( is_ca_mode ) + { + is_ca_mode = 0; + writembs(exit_ca_mode); + } + + completion_print( cols, width, 0, rows, prefix, is_quoted, l); + } + else + { + int npos, pos = 0; + int do_loop = 1; + + is_ca_mode=1; + writembs(enter_ca_mode); + + completion_print( cols, + width, + 0, + termsize.ws_row-1, + prefix, + is_quoted, + l); + /* + List does not fit on screen. Print one screenfull and + leave a scrollable interface + */ + while(do_loop) + { + wchar_t msg[10]; + int percent = 100*pos/(rows-termsize.ws_row+1); + set_color( FISH_COLOR_BLACK, + get_color(HIGHLIGHT_PAGER_PROGRESS) ); + swprintf( msg, 12, + L" %ls(%d%%) \r", + percent==100?L"":(percent >=10?L" ": L" "), + percent ); + writestr(msg); + set_color( FISH_COLOR_NORMAL, FISH_COLOR_NORMAL ); + int c = readch(); + + switch( c ) + { + case LINE_UP: + { + if( pos > 0 ) + { + pos--; + writembs(tparm( cursor_address, 0, 0)); + writembs(scroll_reverse); + completion_print( cols, + width, + pos, + pos+1, + prefix, + is_quoted, + l ); + writembs( tparm( cursor_address, + termsize.ws_row-1, 0) ); + writembs(clr_eol ); + + } + + break; + } + + case LINE_DOWN: + { + if( pos <= (rows - termsize.ws_row ) ) + { + pos++; + completion_print( cols, + width, + pos+termsize.ws_row-2, + pos+termsize.ws_row-1, + prefix, + is_quoted, + l ); + } + break; + } + + case PAGE_DOWN: + { + + npos = mini( rows - termsize.ws_row+1, + pos + termsize.ws_row-1 ); + if( npos != pos ) + { + pos = npos; + completion_print( cols, + width, + pos, + pos+termsize.ws_row-1, + prefix, + is_quoted, + l ); + } + else + { + writembs( flash_screen ); + } + + break; + } + + case PAGE_UP: + { + npos = maxi( 0, + pos - termsize.ws_row+1 ); + + if( npos != pos ) + { + pos = npos; + completion_print( cols, + width, + pos, + pos+termsize.ws_row-1, + prefix, + is_quoted, + l ); + } + else + { + writembs( flash_screen ); + } + break; + } + + case R_NULL: + { + do_loop=0; + res=2; + break; + + } + + default: + { + sb_append_char( &out_buff, c ); + do_loop = 0; + break; + } + } + } + writembs(clr_eol); + } + } + return res; +} + +/** + Substitute any series of tabs, newlines, etc. with a single space character in completion description +*/ +static void mangle_descriptions( array_list_t *l ) +{ + int i, skip; + for( i=0; i<al_get_count( l ); i++ ) + { + wchar_t *next = (wchar_t *)al_get(l, i); + wchar_t *in, *out; + skip=0; + + while( *next != COMPLETE_SEP && *next ) + next++; + + if( !*next ) + continue; + + in=out=(next+1); + + while( *in != 0 ) + { + if( *in == L' ' || *in==L'\t' || *in<32 ) + { + if( !skip ) + *out++=L' '; + skip=1; + } + else + { + *out++ = *in; + skip=0; + } + in++; + } + *out=0; + } +} + +/** + Respond to a winch signal by checking the terminal size +*/ +static void handle_winch( int sig ) +{ + if (ioctl(1,TIOCGWINSZ,&termsize)!=0) + { + return; + } +} + +static int interrupt_handler() +{ + return R_NULL; +} + + +static void init() +{ + struct sigaction act; + program_name = L"fish_pager"; + fish_setlocale( LC_ALL, L"" ); + + + int out = dup( 1 ); + close(1); + if( open( ttyname(0), O_WRONLY ) != 1 ) + { + debug( 0, L"Could not set up file descriptors for pager" ); + exit( 1 ); + + } + out_file = fdopen( out, "w" ); + sb_init( &out_buff ); + + + + env_universal_init( 0, 0, 0); + input_common_init( &interrupt_handler ); + + sigemptyset( & act.sa_mask ); + act.sa_flags=0; + act.sa_handler=SIG_DFL; + act.sa_flags = 0; + act.sa_handler= &handle_winch; + if( sigaction( SIGWINCH, &act, 0 ) ) + { + wperror( L"sigaction" ); + exit(1); + } + + /* Loop until we are in the foreground. */ + while (tcgetpgrp( 0 ) != getpid()) + { + kill (- getpid(), SIGTTIN); + } + + /* Put ourselves in our own process group. */ + if( getpgrp() != getpid() ) + { + if (setpgid (getpid(), getpid()) < 0) + { + debug( 1, + L"Couldn't put the shell in its own process group"); + wperror( L"setpgid" ); + exit (1); + } + } + + /* Grab control of the terminal. */ + if( tcsetpgrp (STDIN_FILENO, getpid()) ) + { + debug( 1, + L"Couldn't grab control of terminal" ); + wperror( L"tcsetpgrp" ); + exit(1); + } + + handle_winch( 0 ); /* Set handler for window change events */ + + tcgetattr(0,&pager_modes); /* get the current terminal modes */ + memcpy( &saved_modes, + &pager_modes, + sizeof(saved_modes)); /* save a copy so we can reset the terminal later */ + + pager_modes.c_lflag &= ~ICANON; /* turn off canonical mode */ + pager_modes.c_lflag &= ~ECHO; /* turn off echo mode */ + pager_modes.c_cc[VMIN]=1; + pager_modes.c_cc[VTIME]=0; + + if( tcsetattr(0,TCSANOW,&pager_modes)) /* set the new modes */ + { + wperror(L"tcsetattr"); + exit(1); + } + + if( setupterm( 0, STDOUT_FILENO, 0) == ERR ) + { + debug( 0, L"Could not set up terminal" ); + exit(1); + } + + +// writembs( tparm( eat_char, ' ', c ) ); + +} + +void destroy() +{ + env_universal_destroy(); + input_common_destroy(); + del_curterm( cur_term ); + sb_destroy( &out_buff ); + fclose( out_file ); + +} + +int main( int argc, char **argv ) +{ + int i; + int is_quoted=0; + array_list_t comp; + wchar_t *prefix; + + + init(); + + + prefix = str2wcs( argv[2] ); + is_quoted = strcmp( "1", argv[1] )==0; + + debug( 3, L"prefix is '%ls'", prefix ); + + al_init( &comp ); + + for( i=3; i<argc; i++ ) + { + al_push( &comp, str2wcs( argv[i] ) ); + } + + mangle_descriptions( &comp ); + + for( i = 6; i>0; i-- ) + { + switch( completion_try_print( i, prefix, is_quoted, &comp ) ) + { + case 0: + break; + case 1: + i=0; + break; + case 2: + i=7; + break; + } + + } + + al_foreach( &comp, (void(*)(const void *))&free ); + al_destroy( &comp ); + free(prefix ); + + fwprintf( out_file, L"%ls", (wchar_t *)out_buff.buff ); + if( is_ca_mode ) + writembs(exit_ca_mode); + + + destroy(); +} + |