From 76ecf897ceace2fa83f83acdde9a5ec89139dcc6 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 1 Dec 2013 15:11:25 -0800 Subject: [PATCH] First round of changes to migrate pager inside fish, in preparation for presenting completions underneath. --- fish.xcodeproj/project.pbxproj | 6 + pager.cpp | 1140 ++++++++++++++++++++++++++++++++ pager.h | 0 3 files changed, 1146 insertions(+) create mode 100644 pager.cpp create mode 100644 pager.h diff --git a/fish.xcodeproj/project.pbxproj b/fish.xcodeproj/project.pbxproj index adb43ad0b..a69bb526e 100644 --- a/fish.xcodeproj/project.pbxproj +++ b/fish.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ D01A2D24169B736200767098 /* man1 in Copy Files */ = {isa = PBXBuildFile; fileRef = D01A2D23169B730A00767098 /* man1 */; }; D01A2D25169B737700767098 /* man1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = D01A2D23169B730A00767098 /* man1 */; }; D031890C15E36E4600D9CC39 /* base in Resources */ = {isa = PBXBuildFile; fileRef = D031890915E36D9800D9CC39 /* base */; }; + D032388B1849D1980032CF2C /* pager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D03238891849D1980032CF2C /* pager.cpp */; }; D033781115DC6D4C00A634BA /* completions in CopyFiles */ = {isa = PBXBuildFile; fileRef = D025C02715D1FEA100B9DB63 /* completions */; }; D033781215DC6D5200A634BA /* functions in CopyFiles */ = {isa = PBXBuildFile; fileRef = D025C02815D1FEA100B9DB63 /* functions */; }; D033781315DC6D5400A634BA /* tools in CopyFiles */ = {isa = PBXBuildFile; fileRef = D025C02915D1FEA100B9DB63 /* tools */; }; @@ -333,6 +334,8 @@ D025C02815D1FEA100B9DB63 /* functions */ = {isa = PBXFileReference; lastKnownFileType = folder; name = functions; path = share/functions; sourceTree = ""; }; D025C02915D1FEA100B9DB63 /* tools */ = {isa = PBXFileReference; lastKnownFileType = folder; name = tools; path = share/tools; sourceTree = ""; }; D031890915E36D9800D9CC39 /* base */ = {isa = PBXFileReference; lastKnownFileType = text; path = base; sourceTree = BUILT_PRODUCTS_DIR; }; + D03238891849D1980032CF2C /* pager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pager.cpp; sourceTree = ""; }; + D032388A1849D1980032CF2C /* pager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pager.h; sourceTree = ""; }; D03EE83814DF88B200FC7150 /* lru.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = lru.h; sourceTree = ""; }; D07B247215BCC15700D4ADB4 /* add-shell */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "add-shell"; path = "build_tools/osx_package_scripts/add-shell"; sourceTree = ""; }; D07B247515BCC4BE00D4ADB4 /* install.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = install.sh; path = osx/install.sh; sourceTree = ""; }; @@ -613,6 +616,8 @@ D0A0855013B3ACEE0099B651 /* mimedb.cpp */, D0A0851A13B3ACEE0099B651 /* output.h */, D0A0855113B3ACEE0099B651 /* output.cpp */, + D032388A1849D1980032CF2C /* pager.h */, + D03238891849D1980032CF2C /* pager.cpp */, D0A0851B13B3ACEE0099B651 /* parse_util.h */, D0A0855213B3ACEE0099B651 /* parse_util.cpp */, D0A0851C13B3ACEE0099B651 /* parser_keywords.h */, @@ -1078,6 +1083,7 @@ D0D02A86159839D5008E62BD /* postfork.cpp in Sources */, D0D02A87159839D5008E62BD /* screen.cpp in Sources */, D0D02A88159839D5008E62BD /* signal.cpp in Sources */, + D032388B1849D1980032CF2C /* pager.cpp in Sources */, D0D2694A15983779005D9B9C /* builtin.cpp in Sources */, D0D2694915983772005D9B9C /* function.cpp in Sources */, D0D02A67159837AD008E62BD /* complete.cpp in Sources */, diff --git a/pager.cpp b/pager.cpp new file mode 100644 index 000000000..62e2b1bb2 --- /dev/null +++ b/pager.cpp @@ -0,0 +1,1140 @@ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#include +#include +#include +#include + +#include + +#if HAVE_NCURSES_H +#include +#else +#include +#endif + +#if HAVE_TERM_H +#include +#elif HAVE_NCURSES_TERM_H +#include +#endif + +#include + +#ifdef HAVE_GETOPT_H +#include +#endif + +#include +#include + +#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_list_t; +typedef std::vector 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 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 *lst) +{ + for (size_t i=0; isize(); 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; icomp.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 &lst) +{ + + size_t rows = (lst.size()-1)/cols+1; + size_t i, j; + + for (i = row_start; i &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; ipref_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 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 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 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 result(lst_size); + for (size_t i=0; icomp.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 *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(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 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); +} + + diff --git a/pager.h b/pager.h new file mode 100644 index 000000000..e69de29bb