aboutsummaryrefslogtreecommitdiffhomepage
path: root/pager.cpp
diff options
context:
space:
mode:
authorGravatar ridiculousfish <corydoras@ridiculousfish.com>2013-12-01 15:11:25 -0800
committerGravatar ridiculousfish <corydoras@ridiculousfish.com>2013-12-01 15:11:25 -0800
commit76ecf897ceace2fa83f83acdde9a5ec89139dcc6 (patch)
tree6341cfea090d3b7ce567f5241fa4b2f0f3f8e6e4 /pager.cpp
parent263f919bebf2e5be3a95c7445034fab70fed9fab (diff)
First round of changes to migrate pager inside fish, in preparation for
presenting completions underneath.
Diffstat (limited to 'pager.cpp')
-rw-r--r--pager.cpp1140
1 files changed, 1140 insertions, 0 deletions
diff --git a/pager.cpp b/pager.cpp
new file mode 100644
index 00000000..62e2b1bb
--- /dev/null
+++ b/pager.cpp
@@ -0,0 +1,1140 @@
+#include "config.h"
+
+#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);
+}
+
+