2013-12-01 23:11:25 +00:00
|
|
|
#include "config.h"
|
|
|
|
|
2013-12-07 20:43:40 +00:00
|
|
|
#include "pager.h"
|
|
|
|
|
2013-12-01 23:11:25 +00: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"
|
|
|
|
|
|
|
|
struct comp_t;
|
|
|
|
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
|
|
|
|
{
|
|
|
|
HIGHLIGHT_PAGER_PREFIX,
|
|
|
|
HIGHLIGHT_PAGER_COMPLETION,
|
|
|
|
HIGHLIGHT_PAGER_DESCRIPTION,
|
|
|
|
HIGHLIGHT_PAGER_PROGRESS,
|
|
|
|
HIGHLIGHT_PAGER_SECONDARY
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
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 struct should be continually updated by signals as the term
|
|
|
|
resizes, and as such always contain the correct current size.
|
|
|
|
*/
|
|
|
|
static struct winsize termsize;
|
|
|
|
|
|
|
|
/**
|
|
|
|
The termios modes the terminal had when the program started. These
|
|
|
|
should be restored on exit
|
|
|
|
*/
|
|
|
|
static struct termios saved_modes;
|
|
|
|
|
|
|
|
/**
|
|
|
|
This flag is set to 1 of we have sent the enter_ca_mode terminfo
|
|
|
|
sequence to save the previous terminal contents.
|
|
|
|
*/
|
|
|
|
static int is_ca_mode = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
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;
|
|
|
|
|
|
|
|
/**
|
|
|
|
The environment variables used to specify the color of different
|
|
|
|
tokens.
|
|
|
|
*/
|
|
|
|
static const wchar_t *hightlight_var[] =
|
|
|
|
{
|
|
|
|
L"fish_pager_color_prefix",
|
|
|
|
L"fish_pager_color_completion",
|
|
|
|
L"fish_pager_color_description",
|
|
|
|
L"fish_pager_color_progress",
|
|
|
|
L"fish_pager_color_secondary"
|
|
|
|
}
|
|
|
|
;
|
|
|
|
|
|
|
|
/**
|
|
|
|
This string contains the text that should be sent back to the calling program
|
|
|
|
*/
|
|
|
|
static wcstring out_buff;
|
|
|
|
/**
|
|
|
|
This is the file to which the output text should be sent. It is really a pipe.
|
|
|
|
*/
|
|
|
|
static FILE *out_file;
|
|
|
|
|
|
|
|
/**
|
|
|
|
Data structure describing one or a group of related completions
|
|
|
|
*/
|
|
|
|
struct comp_t
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
The list of all completin strings this entry applies to
|
|
|
|
*/
|
|
|
|
wcstring_list_t comp;
|
|
|
|
/**
|
|
|
|
The description
|
|
|
|
*/
|
|
|
|
wcstring desc;
|
|
|
|
/**
|
|
|
|
On-screen width of the completion string
|
|
|
|
*/
|
|
|
|
int comp_width;
|
|
|
|
/**
|
|
|
|
On-screen width of the description information
|
|
|
|
*/
|
|
|
|
int desc_width;
|
|
|
|
/**
|
|
|
|
Preffered total width
|
|
|
|
*/
|
|
|
|
int pref_width;
|
|
|
|
/**
|
|
|
|
Minimum acceptable width
|
|
|
|
*/
|
|
|
|
int min_width;
|
|
|
|
|
|
|
|
comp_t() : comp(), desc(), comp_width(0), desc_width(0), pref_width(0), min_width(0)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
This function translates from a highlight code to a specific color
|
|
|
|
by check invironement variables
|
|
|
|
*/
|
|
|
|
static rgb_color_t get_color(int highlight)
|
|
|
|
{
|
|
|
|
const wchar_t *val;
|
|
|
|
|
|
|
|
if (highlight < 0)
|
|
|
|
return rgb_color_t::normal();
|
|
|
|
if (highlight >= (5))
|
|
|
|
return rgb_color_t::normal();
|
|
|
|
|
|
|
|
val = wgetenv(hightlight_var[highlight]);
|
|
|
|
|
|
|
|
if (!val)
|
|
|
|
{
|
|
|
|
val = env_universal_get(hightlight_var[highlight]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!val)
|
|
|
|
{
|
|
|
|
return rgb_color_t::normal();
|
|
|
|
}
|
|
|
|
|
|
|
|
return parse_color(val, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
static void recalc_min_widths(std::vector<comp_t> *lst)
|
|
|
|
{
|
|
|
|
for (size_t i=0; i<lst->size(); i++)
|
|
|
|
{
|
|
|
|
comp_t *c = &lst->at(i);
|
|
|
|
|
|
|
|
c->min_width = mini(c->desc_width, maxi(0,termsize.ws_col/3 - 2)) +
|
|
|
|
mini(c->desc_width, maxi(0,termsize.ws_col/5 - 4)) +4;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Write specified character to the output buffer \c pager_buffer
|
|
|
|
*/
|
|
|
|
static int pager_buffered_writer(char c)
|
|
|
|
{
|
|
|
|
pager_buffer.push_back(c);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Flush \c pager_buffer to stdout
|
|
|
|
*/
|
|
|
|
static void pager_flush()
|
|
|
|
{
|
|
|
|
if (! pager_buffer.empty())
|
|
|
|
{
|
|
|
|
write_loop(1, & pager_buffer.at(0), pager_buffer.size() * sizeof(char));
|
|
|
|
pager_buffer.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
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
|
|
|
|
\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.
|
|
|
|
*/
|
|
|
|
static int print_max(const wcstring &str, int max, int has_more)
|
|
|
|
{
|
|
|
|
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()))
|
|
|
|
{
|
|
|
|
writech(ellipsis_char);
|
|
|
|
written += wcwidth(ellipsis_char);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
writech(c);
|
|
|
|
written+= wcwidth(c);
|
|
|
|
}
|
|
|
|
return written;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Print the specified item using at the specified amount of space
|
|
|
|
*/
|
|
|
|
static void completion_print_item(const wcstring &prefix, const comp_t *c, int width, bool secondary)
|
|
|
|
{
|
|
|
|
int comp_width=0, desc_width=0;
|
|
|
|
int written=0;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
rgb_color_t bg = secondary ? get_color(HIGHLIGHT_PAGER_SECONDARY) : rgb_color_t::normal();
|
|
|
|
for (size_t i=0; i<c->comp.size(); i++)
|
|
|
|
{
|
|
|
|
const wcstring &comp = c->comp.at(i);
|
|
|
|
if (i != 0)
|
|
|
|
written += print_max(L" ", comp_width - written, 2);
|
|
|
|
set_color(get_color(HIGHLIGHT_PAGER_PREFIX), bg);
|
|
|
|
written += print_max(prefix, comp_width - written, comp.empty()?0:1);
|
|
|
|
set_color(get_color(HIGHLIGHT_PAGER_COMPLETION), bg);
|
|
|
|
written += print_max(comp.c_str(), comp_width - written, i!=(c->comp.size()-1));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (desc_width)
|
|
|
|
{
|
|
|
|
while (written < (width-desc_width-2))
|
|
|
|
{
|
|
|
|
written++;
|
|
|
|
writech(L' ');
|
|
|
|
}
|
|
|
|
set_color(get_color(HIGHLIGHT_PAGER_DESCRIPTION), bg);
|
|
|
|
written += print_max(L"(", 1, 0);
|
|
|
|
written += print_max(c->desc.c_str(), desc_width, 0);
|
|
|
|
written += print_max(L")", 1, 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
while (written < width)
|
|
|
|
{
|
|
|
|
written++;
|
|
|
|
writech(L' ');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (secondary)
|
|
|
|
set_color(rgb_color_t::normal(), rgb_color_t::normal());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void completion_print(int cols,
|
|
|
|
int *width,
|
|
|
|
int row_start,
|
|
|
|
int row_stop,
|
|
|
|
const wcstring &prefix,
|
|
|
|
const std::vector<comp_t> &lst)
|
|
|
|
{
|
|
|
|
|
|
|
|
size_t rows = (lst.size()-1)/cols+1;
|
|
|
|
size_t i, j;
|
|
|
|
|
|
|
|
for (i = row_start; i<row_stop; i++)
|
|
|
|
{
|
|
|
|
for (j = 0; j < cols; j++)
|
|
|
|
{
|
|
|
|
int is_last = (j==(cols-1));
|
|
|
|
|
|
|
|
if (lst.size() <= j*rows + i)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const comp_t *el = &lst.at(j*rows + i);
|
|
|
|
|
|
|
|
completion_print_item(prefix, el, width[j] - (is_last?0:2), i%2);
|
|
|
|
|
|
|
|
if (!is_last)
|
|
|
|
writestr(L" ");
|
|
|
|
}
|
|
|
|
writech(L'\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int completion_try_print(int cols,
|
|
|
|
const wcstring &prefix,
|
|
|
|
const std::vector<comp_t> &lst)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
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;
|
|
|
|
|
|
|
|
long i, j;
|
|
|
|
|
|
|
|
int rows = (int)((lst.size()-1)/cols+1);
|
|
|
|
|
|
|
|
int pref_tot_width=0;
|
|
|
|
int min_tot_width = 0;
|
|
|
|
int res=PAGER_RETRY;
|
|
|
|
/*
|
|
|
|
Skip completions on tiny terminals
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (termsize.ws_col < PAGER_MIN_WIDTH)
|
|
|
|
return PAGER_DONE;
|
|
|
|
|
|
|
|
/* Calculate how wide the list would be */
|
|
|
|
for (j = 0; j < cols; j++)
|
|
|
|
{
|
|
|
|
for (i = 0; i<rows; i++)
|
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
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
|
|
|
|
{
|
|
|
|
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,
|
|
|
|
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.
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (print)
|
|
|
|
{
|
|
|
|
res=PAGER_DONE;
|
|
|
|
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, lst);
|
|
|
|
pager_flush();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int npos, pos = 0;
|
|
|
|
int do_loop = 1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
Enter ca_mode, which means that the terminal
|
|
|
|
content will be restored to the current
|
|
|
|
state on exit.
|
|
|
|
*/
|
|
|
|
if (enter_ca_mode && exit_ca_mode)
|
|
|
|
{
|
|
|
|
is_ca_mode=1;
|
|
|
|
writembs(enter_ca_mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
completion_print(cols,
|
|
|
|
width,
|
|
|
|
0,
|
|
|
|
termsize.ws_row-1,
|
|
|
|
prefix,
|
|
|
|
lst);
|
|
|
|
/*
|
|
|
|
List does not fit on screen. Print one screenfull and
|
|
|
|
leave a scrollable interface
|
|
|
|
*/
|
|
|
|
while (do_loop)
|
|
|
|
{
|
|
|
|
set_color(rgb_color_t::black(), get_color(HIGHLIGHT_PAGER_PROGRESS));
|
|
|
|
wcstring msg = format_string(_(L" %d to %d of %d"), pos, pos+termsize.ws_row-1, rows);
|
|
|
|
msg.append(L" \r");
|
|
|
|
|
|
|
|
writestr(msg.c_str());
|
|
|
|
set_color(rgb_color_t::normal(), rgb_color_t::normal());
|
|
|
|
pager_flush();
|
|
|
|
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,
|
|
|
|
lst);
|
|
|
|
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,
|
|
|
|
lst);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case PAGE_DOWN:
|
|
|
|
{
|
|
|
|
|
|
|
|
npos = mini((int)(rows - termsize.ws_row+1), (int)(pos + termsize.ws_row-1));
|
|
|
|
if (npos != pos)
|
|
|
|
{
|
|
|
|
pos = npos;
|
|
|
|
completion_print(cols,
|
|
|
|
width,
|
|
|
|
pos,
|
|
|
|
pos+termsize.ws_row-1,
|
|
|
|
prefix,
|
|
|
|
lst);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (flash_screen)
|
|
|
|
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,
|
|
|
|
lst);
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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 std::vector<comp_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
|
|
|
|
std::vector<comp_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;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void measure_completion_infos(std::vector<comp_t> *infos, const wcstring &prefix)
|
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Respond to a winch signal by checking the terminal size
|
|
|
|
*/
|
|
|
|
static void handle_winch(int sig)
|
|
|
|
{
|
|
|
|
if (ioctl(1,TIOCGWINSZ,&termsize)!=0)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
The callback function that the keyboard reading function calls when
|
|
|
|
an interrupt occurs. This makes sure that R_NULL is returned at
|
|
|
|
once when an interrupt has occured.
|
|
|
|
*/
|
|
|
|
static int interrupt_handler()
|
|
|
|
{
|
|
|
|
return R_NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
Initialize various subsystems. This also closes stdin and replaces
|
|
|
|
it with a copy of stderr, so the reading of completion strings must
|
|
|
|
be done before init is called.
|
|
|
|
*/
|
|
|
|
static void init(int mangle_descriptors, int out)
|
|
|
|
{
|
|
|
|
struct sigaction act;
|
|
|
|
|
|
|
|
static struct termios pager_modes;
|
|
|
|
char *term;
|
|
|
|
|
|
|
|
if (mangle_descriptors)
|
|
|
|
{
|
|
|
|
|
|
|
|
/*
|
|
|
|
Make fd 1 output to screen, and use some other fd for writing
|
|
|
|
the resulting output back to the caller
|
|
|
|
*/
|
|
|
|
int in;
|
|
|
|
out = dup(1);
|
|
|
|
close(1);
|
|
|
|
close(0);
|
|
|
|
|
|
|
|
/* OK to not use CLO_EXEC here because fish_pager is single threaded */
|
|
|
|
if ((in = open(ttyname(2), O_RDWR)) != -1)
|
|
|
|
{
|
|
|
|
if (dup2(2, 1) == -1)
|
|
|
|
{
|
|
|
|
debug(0, _(L"Could not set up output file descriptors for pager"));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dup2(in, 0) == -1)
|
|
|
|
{
|
|
|
|
debug(0, _(L"Could not set up input file descriptors for pager"));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
debug(0, _(L"Could not open tty for pager"));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(out_file = fdopen(out, "w")))
|
|
|
|
{
|
|
|
|
debug(0, _(L"Could not initialize result pipe"));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
env_universal_init(0, 0, 0, 0);
|
|
|
|
input_common_init(&interrupt_handler);
|
|
|
|
output_set_writer(&pager_buffered_writer);
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
int errret;
|
|
|
|
if (setupterm(0, STDOUT_FILENO, &errret) == ERR)
|
|
|
|
{
|
|
|
|
debug(0, _(L"Could not set up terminal"));
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
term = getenv("TERM");
|
|
|
|
if (term)
|
|
|
|
{
|
|
|
|
wcstring wterm = str2wcstring(term);
|
|
|
|
output_set_term(wterm);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Infer term256 support */
|
|
|
|
char *fish_term256 = getenv("fish_term256");
|
|
|
|
bool support_term256;
|
|
|
|
if (fish_term256)
|
|
|
|
{
|
|
|
|
support_term256 = from_string<bool>(fish_term256);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
support_term256 = term && strstr(term, "256color");
|
|
|
|
}
|
|
|
|
output_set_supports_term256(support_term256);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void run_pager(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
|
|
|
|
std::vector<comp_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;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fwprintf(out_file, L"%ls", out_buff.c_str());
|
|
|
|
if (is_ca_mode)
|
|
|
|
{
|
|
|
|
writembs(exit_ca_mode);
|
|
|
|
pager_flush();
|
|
|
|
is_ca_mode = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Restore saved writer function
|
|
|
|
pager_buffer.clear();
|
|
|
|
output_set_writer(saved_writer_func);
|
|
|
|
}
|
|
|
|
|
|
|
|
|