From 76ecf897ceace2fa83f83acdde9a5ec89139dcc6 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 1 Dec 2013 15:11:25 -0800 Subject: [PATCH 01/21] 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 From ef4465efdb07c27d4da3482e8ffc99c2a6ed18d4 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sat, 7 Dec 2013 12:43:40 -0800 Subject: [PATCH 02/21] More work on builtin pager --- fish.xcodeproj/project.pbxproj | 2 +- pager.cpp | 2 ++ pager.h | 15 +++++++++++++++ screen.h | 1 + 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/fish.xcodeproj/project.pbxproj b/fish.xcodeproj/project.pbxproj index a69bb526e..81de12554 100644 --- a/fish.xcodeproj/project.pbxproj +++ b/fish.xcodeproj/project.pbxproj @@ -1083,7 +1083,6 @@ 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 */, @@ -1107,6 +1106,7 @@ D0D02A7915983888008E62BD /* intern.cpp in Sources */, D0D02A7A15983916008E62BD /* env_universal.cpp in Sources */, D0D02A7B15983928008E62BD /* env_universal_common.cpp in Sources */, + D032388B1849D1980032CF2C /* pager.cpp in Sources */, D0D02A89159839DF008E62BD /* fish.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/pager.cpp b/pager.cpp index 62e2b1bb2..646975ed0 100644 --- a/pager.cpp +++ b/pager.cpp @@ -1,5 +1,7 @@ #include "config.h" +#include "pager.h" + #include #include #include diff --git a/pager.h b/pager.h index e69de29bb..218f0530a 100644 --- a/pager.h +++ b/pager.h @@ -0,0 +1,15 @@ +/** \file pager.h + Pager support +*/ + +#include "complete.h" +#include "screen.h" + +/* Represents rendering from the pager */ +class page_rendering_t +{ + screen_data_t screen_data; +}; + +typedef std::vector completion_list_t; +page_rendering_t render_completions(const completion_list_t &raw_completions, const wcstring &prefix); diff --git a/screen.h b/screen.h index ef74383d7..d3676a0be 100644 --- a/screen.h +++ b/screen.h @@ -13,6 +13,7 @@ #define FISH_SCREEN_H #include +#include /** A class representing a single line of a screen. From 899dafb33fec62ed9258f2b65e2330a2cd483ab5 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 13 Jan 2014 16:41:22 -0800 Subject: [PATCH 03/21] Migrating new pager implementation into a class. Further work on constructing a screen_data_t from it. --- pager.cpp | 935 ++++++++++++++++++++++-------------------------------- pager.h | 57 +++- screen.h | 6 + 3 files changed, 441 insertions(+), 557 deletions(-) diff --git a/pager.cpp b/pager.cpp index 646975ed0..fefe604ab 100644 --- a/pager.cpp +++ b/pager.cpp @@ -1,6 +1,7 @@ #include "config.h" #include "pager.h" +#include "highlight.h" #include #include @@ -57,7 +58,7 @@ #include "env_universal.h" #include "print_help.h" -struct comp_t; +typedef pager_t::comp_t comp_t; typedef std::vector completion_list_t; typedef std::vector comp_info_list_t; @@ -118,24 +119,6 @@ enum */ #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() @@ -161,45 +144,7 @@ static const wchar_t *hightlight_var[] = 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 @@ -235,14 +180,14 @@ static rgb_color_t get_color(int highlight) terminal size, so this function should be called when the terminal changes size. */ -static void recalc_min_widths(std::vector *lst) +void pager_t::recalc_min_widths(comp_info_list_t * lst) const { 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; + c->min_width = mini(c->desc_width, maxi(0, term_width/3 - 2)) + + mini(c->desc_width, maxi(0, term_width/5 - 4)) +4; } } @@ -350,18 +295,6 @@ static int pager_buffered_writer(char 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. @@ -370,7 +303,7 @@ static void pager_flush() \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) +static int print_max(const wcstring &str, int max, bool has_more) { int written = 0; for (size_t i=0; i < str.size(); i++) @@ -392,13 +325,38 @@ static int print_max(const wcstring &str, int max, int has_more) return written; } +static int print_max(const wcstring &str, int color, int max, bool has_more, line_t *line) +{ + int written = 0; + for (size_t i=0; i < str.size(); i++) + { + wchar_t c = str.at(i); + + if (written + wcwidth(c) > max) + break; + if ((written + wcwidth(c) == max) && (has_more || i + 1 < str.size())) + { + line->append(ellipsis_char, color); + written += wcwidth(ellipsis_char); + break; + } + + line->append(c, color); + written += wcwidth(c); + } + return written; +} + + /** 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) +line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, size_t row, size_t column, int width, bool secondary, page_rendering_t *rendering) const { int comp_width=0, desc_width=0; int written=0; + + line_t line_data; if (c->pref_width <= width) { @@ -423,42 +381,42 @@ static void completion_print_item(const wcstring &prefix, const comp_t *c, int w desc_width = width-comp_width-4; } - - rgb_color_t bg = secondary ? get_color(HIGHLIGHT_PAGER_SECONDARY) : rgb_color_t::normal(); + + int bg_color = secondary ? HIGHLIGHT_PAGER_SECONDARY : HIGHLIGHT_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 (i != 0) + written += print_max(L" ", 0 /* default color */, comp_width - written, true /* has_more */, &line_data); + + int packed_color = HIGHLIGHT_PAGER_PREFIX | (bg_color << 16); + written += print_max(prefix, packed_color, comp_width - written, ! comp.empty(), &line_data); + + packed_color = HIGHLIGHT_PAGER_COMPLETION | (bg_color << 16); + written += print_max(comp, packed_color, comp_width - written, i + 1 < c->comp.size(), &line_data); + } if (desc_width) { + int packed_color = HIGHLIGHT_PAGER_DESCRIPTION | (bg_color << 16); while (written < (width-desc_width-2)) { - written++; - writech(L' '); + written += print_max(L" ", packed_color, 1, false, &line_data); } - 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); + written += print_max(L"(", packed_color, 1, false, &line_data); + written += print_max(c->desc, packed_color, desc_width, false, &line_data); + written += print_max(L")", packed_color, 1, false, &line_data); } else { while (written < width) { - written++; - writech(L' '); + written += print_max(L" ", 0, 1, false, &line_data); } } - if (secondary) - set_color(rgb_color_t::normal(), rgb_color_t::normal()); + return line_data; } /** @@ -473,344 +431,32 @@ static void completion_print_item(const wcstring &prefix, const comp_t *c, int w \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) +void pager_t::completion_print(int cols, int *width_per_column, int row_start, int row_stop, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const { size_t rows = (lst.size()-1)/cols+1; - size_t i, j; - for (i = row_start; iscreen_data.create_line(row).append_line(line); } - 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 &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) { @@ -891,12 +537,12 @@ static void join_completions(comp_info_list_t *comps) } /** 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) +static comp_info_list_t process_completions_into_infos(const completion_list_t &lst, const wcstring &prefix) { const size_t lst_size = lst.size(); // Make the list of the correct size up-front - std::vector result(lst_size); + comp_info_list_t result(lst_size); for (size_t i=0; i process_completions_into_infos(const completion_list_ return result; } -static void measure_completion_infos(std::vector *infos, const wcstring &prefix) +void pager_t::measure_completion_infos(comp_info_list_t *infos, const wcstring &prefix) const { size_t prefix_len = my_wcswidth(prefix.c_str()); for (size_t i=0; i < infos->size(); i++) @@ -940,17 +586,6 @@ static void measure_completion_infos(std::vector *infos, const wcstring 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 @@ -961,132 +596,15 @@ 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) +#if 0 +page_rendering_t render_completions(const completion_list_t &raw_completions, const wcstring &prefix) { // Save old output function so we can restore it int (* const saved_writer_func)(char) = output_get_writer(); output_set_writer(&pager_buffered_writer); // Get completion infos out of it - std::vector completion_infos = process_completions_into_infos(raw_completions, prefix.c_str()); + comp_info_list_t completion_infos = process_completions_into_infos(raw_completions, prefix.c_str()); // Maybe join them if (prefix == L"-") @@ -1127,16 +645,321 @@ void run_pager(const completion_list_t &raw_completions, const wcstring &prefix) } 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); } +#endif + +void pager_t::set_completions(const completion_list_t &raw_completions) +{ + completions = raw_completions; + + // Get completion infos out of it + completion_infos = process_completions_into_infos(raw_completions, prefix.c_str()); + + // Maybe join them + if (prefix == L"-") + join_completions(&completion_infos); + + // Compute their various widths + measure_completion_infos(&completion_infos, prefix); +} + +void pager_t::set_term_size(int w, int h) +{ + assert(w > 0); + assert(h > 0); + term_width = w; + term_height = h; +} + +/** + 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 +*/ + +int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const +{ + /* + 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; + + 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 (term_width < PAGER_MIN_WIDTH) + return PAGER_DONE; + + /* Calculate how wide the list would be */ + for (long j = 0; j < cols; j++) + { + for (long 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 > term_width) + { + pref_width[0] = term_width; + } + width = pref_width; + print=1; + } + else if (pref_tot_width <= term_width) + { + /* 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, term_width, + rows, next_rows, term_height, + pref_tot_width-term_width ); + */ + if (min_tot_width < term_width && + (((rows < term_height) && (next_rows >= term_height)) || + (pref_tot_width-term_width< 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 < term_width) + { + for (long i=0; (i 0) + { + pos--; + writembs(tparm(cursor_address, 0, 0)); + writembs(scroll_reverse); + completion_print(cols, width, pos, pos+1, prefix, lst, rendering); + writembs(tparm(cursor_address, term_height-1, 0)); + writembs(clr_eol); + + } + + break; + } + + case LINE_DOWN: + { + if (pos <= (rows - term_height)) + { + pos++; + completion_print(cols, width, pos+term_height-2, pos+term_height-1, prefix, lst, rendering); + } + break; + } + + case PAGE_DOWN: + { + + npos = mini((int)(rows - term_height+1), (int)(pos + term_height-1)); + if (npos != pos) + { + pos = npos; + completion_print(cols, width, pos, pos+term_height-1, prefix, lst, rendering); + } + else + { + if (flash_screen) + writembs(flash_screen); + } + + break; + } + + case PAGE_UP: + { + npos = maxi(0, pos - term_height+1); + + if (npos != pos) + { + pos = npos; + completion_print(cols, width, pos, pos+term_height-1, prefix, lst, rendering); + } + 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; +} +page_rendering_t pager_t::render() const +{ + /** + 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. + */ + page_rendering_t rendering; + for (int i = PAGER_MAX_COLS; i>0; i--) + { + /* Initially empty rendering */ + rendering.screen_data.resize(0); + + switch (completion_try_print(i, prefix, completion_infos, &rendering)) + { + 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; + } + } + return rendering; +} diff --git a/pager.h b/pager.h index 218f0530a..8cf84f8d3 100644 --- a/pager.h +++ b/pager.h @@ -6,10 +6,65 @@ #include "screen.h" /* Represents rendering from the pager */ -class page_rendering_t +struct page_rendering_t { screen_data_t screen_data; }; typedef std::vector completion_list_t; page_rendering_t render_completions(const completion_list_t &raw_completions, const wcstring &prefix); + +class pager_t +{ + int term_width; + int term_height; + + completion_list_t completions; + + /** Data structure describing one or a group of related completions */ + public: + 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) + { + } + }; + + private: + typedef std::vector comp_info_list_t; + comp_info_list_t completion_infos; + + int completion_try_print(int cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const; + + void recalc_min_widths(comp_info_list_t * lst) const; + void measure_completion_infos(std::vector *infos, const wcstring &prefix) const; + + void completion_print(int cols, int *width_per_column, int row_start, int row_stop, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const; + line_t completion_print_item(const wcstring &prefix, const comp_t *c, size_t row, size_t column, int width, bool secondary, page_rendering_t *rendering) const; + + + public: + void set_completions(const completion_list_t &comp); + void set_term_size(int w, int h); + wcstring prefix; + + page_rendering_t render() const; +}; diff --git a/screen.h b/screen.h index d3676a0be..8e23002eb 100644 --- a/screen.h +++ b/screen.h @@ -54,6 +54,12 @@ struct line_t { return colors.at(idx); } + + void append_line(const line_t &line) + { + text.insert(text.end(), line.text.begin(), line.text.end()); + colors.insert(colors.end(), line.colors.begin(), line.colors.end()); + } }; From 295c8f48a6192e25053c5f0366ecfae6bcfa3bdc Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Tue, 14 Jan 2014 15:39:53 -0800 Subject: [PATCH 04/21] Teach screen how to render completion page. Correct spacing in completion page contents. --- pager.cpp | 10 ++++++++++ parse_productions.cpp | 2 +- reader.cpp | 32 ++++++++++++++++++++++++-------- screen.cpp | 10 +++++++++- screen.h | 8 +++++++- 5 files changed, 51 insertions(+), 11 deletions(-) diff --git a/pager.cpp b/pager.cpp index fefe604ab..8eb729f8e 100644 --- a/pager.cpp +++ b/pager.cpp @@ -416,6 +416,7 @@ line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, s written += print_max(L" ", 0, 1, false, &line_data); } } + return line_data; } @@ -436,6 +437,8 @@ void pager_t::completion_print(int cols, int *width_per_column, int row_start, i size_t rows = (lst.size()-1)/cols+1; + fprintf(stderr, "prefix: %ls\n", prefix.c_str()); + for (size_t row = row_start; row < row_stop; row++) { for (size_t col = 0; col < cols; col++) @@ -450,6 +453,13 @@ void pager_t::completion_print(int cols, int *width_per_column, int row_start, i /* Print this completion on its own "line" */ line_t line = completion_print_item(prefix, el, row, col, width_per_column[col] - (is_last?0:2), row%2, rendering); + /* If there's more to come, append two spaces */ + if (col + 1 < cols) + { + line.append(L' ', 0); + line.append(L' ', 0); + } + /* Append this to the real line */ rendering->screen_data.create_line(row).append_line(line); } diff --git a/parse_productions.cpp b/parse_productions.cpp index 311d68086..4f40ce614 100644 --- a/parse_productions.cpp +++ b/parse_productions.cpp @@ -126,7 +126,7 @@ RESOLVE(statement) if (token1.type == parse_token_type_string) { // If we are a function, then look for help arguments - // Othewrise, if the next token looks like an option (starts with a dash), then parse it as a decorated statement + // Otherwise, if the next token looks like an option (starts with a dash), then parse it as a decorated statement if (token1.keyword == parse_keyword_function && token2.is_help_argument) { return 4; diff --git a/reader.cpp b/reader.cpp index 6cf77ae41..5a45cd9e3 100644 --- a/reader.cpp +++ b/reader.cpp @@ -100,6 +100,7 @@ commence. #include "parse_util.h" #include "parser_keywords.h" #include "parse_tree.h" +#include "pager.h" /** Maximum length of prefix string when printing completion @@ -199,6 +200,9 @@ public: /** String containing the autosuggestion */ wcstring autosuggestion; + + /** Current completions */ + page_rendering_t completion_page_rendering; /** Whether autosuggesting is allowed at all */ bool allow_autosuggestion; @@ -541,7 +545,8 @@ static void reader_repaint() data->command_length(), &colors[0], &indents[0], - data->buff_pos); + data->buff_pos, + &data->completion_page_rendering.screen_data); data->repaint_needed = false; } @@ -1852,14 +1857,25 @@ static bool handle_completions(const std::vector &comp) wchar_t quote; parse_util_get_parameter_info(data->command_line, data->buff_pos, "e, NULL, NULL); is_quoted = (quote != L'\0'); + + if (1) + { + pager_t pager; + pager.set_term_size(common_get_width(), common_get_height()); + pager.prefix = prefix; + pager.set_completions(surviving_completions); + data->completion_page_rendering = pager.render(); + } + else + { + /* Clear the autosuggestion from the old commandline before abandoning it (see #561) */ + if (! data->autosuggestion.empty()) + reader_repaint_without_autosuggestion(); - /* Clear the autosuggestion from the old commandline before abandoning it (see #561) */ - if (! data->autosuggestion.empty()) - reader_repaint_without_autosuggestion(); + write_loop(1, "\n", 1); - write_loop(1, "\n", 1); - - run_pager(prefix, is_quoted, surviving_completions); + run_pager(prefix, is_quoted, surviving_completions); + } } s_reset(&data->screen, screen_reset_abandon_line); reader_repaint(); @@ -3189,7 +3205,7 @@ const wchar_t *reader_readline(void) /* Munge our completions */ sort_and_make_unique(comp); prioritize_completions(comp); - + /* Record our cycle_command_line */ cycle_command_line = data->command_line; cycle_cursor_pos = data->buff_pos; diff --git a/screen.cpp b/screen.cpp index 711aebabd..fe283ac84 100644 --- a/screen.cpp +++ b/screen.cpp @@ -1234,7 +1234,8 @@ void s_write(screen_t *s, size_t explicit_len, const int *colors, const int *indent, - size_t cursor_pos) + size_t cursor_pos, + const screen_data_t *pager_data) { screen_data_t::cursor_t cursor_arr; @@ -1321,6 +1322,13 @@ void s_write(screen_t *s, } s->desired.cursor = cursor_arr; + + /* append pager_data */ + if (pager_data != NULL) + { + s->desired.append_lines(*pager_data); + } + s_update(s, layout.left_prompt.c_str(), layout.right_prompt.c_str()); s_save_status(s); } diff --git a/screen.h b/screen.h index 8e23002eb..7ff8fb0f5 100644 --- a/screen.h +++ b/screen.h @@ -109,6 +109,11 @@ public: { return line_datas.size(); } + + void append_lines(const screen_data_t &d) + { + this->line_datas.insert(this->line_datas.end(), d.line_datas.begin(), d.line_datas.end()); + } }; /** @@ -196,7 +201,8 @@ void s_write(screen_t *s, size_t explicit_len, const int *colors, const int *indent, - size_t cursor_pos); + size_t cursor_pos, + const screen_data_t *pager_data); /** This function resets the screen buffers internal knowledge about From 5953170f142a6cccd0d8f7e5af08c70f18f42689 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Wed, 15 Jan 2014 01:36:09 -0800 Subject: [PATCH 05/21] Update pager colors to use new non-bitmask architecture. --- highlight.cpp | 9 ++++- highlight.h | 9 +++++ pager.cpp | 109 +++++--------------------------------------------- 3 files changed, 26 insertions(+), 101 deletions(-) diff --git a/highlight.cpp b/highlight.cpp index 3878a208c..35db0dfc1 100644 --- a/highlight.cpp +++ b/highlight.cpp @@ -61,7 +61,14 @@ static const wchar_t * const highlight_var[] = L"fish_color_escape", L"fish_color_quote", L"fish_color_redirection", - L"fish_color_autosuggestion" + L"fish_color_autosuggestion", + + 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" + }; /* If the given path looks like it's relative to the working directory, then prepend that working directory. */ diff --git a/highlight.h b/highlight.h index 7c6000b17..bd439e88e 100644 --- a/highlight.h +++ b/highlight.h @@ -29,6 +29,14 @@ enum highlight_spec_redirection, //redirection highlight_spec_autosuggestion, //autosuggestion + // Pager support + highlight_spec_pager_prefix, + highlight_spec_pager_completion, + highlight_spec_pager_description, + highlight_spec_pager_progress, + highlight_spec_pager_secondary, + + HIGHLIGHT_SPEC_PRIMARY_MASK = 0xFF, /* The following values are modifiers */ @@ -47,6 +55,7 @@ inline highlight_spec_t highlight_get_primary(highlight_spec_t val) inline highlight_spec_t highlight_make_background(highlight_spec_t val) { + assert(val >> 16 == 0); return val << 16; } diff --git a/pager.cpp b/pager.cpp index 8eb729f8e..6afb9f8e9 100644 --- a/pager.cpp +++ b/pager.cpp @@ -57,6 +57,7 @@ #include "input_common.h" #include "env_universal.h" #include "print_help.h" +#include "highlight.h" typedef pager_t::comp_t comp_t; typedef std::vector completion_list_t; @@ -72,16 +73,6 @@ enum ; -enum -{ - HIGHLIGHT_PAGER_PREFIX, - HIGHLIGHT_PAGER_COMPLETION, - HIGHLIGHT_PAGER_DESCRIPTION, - HIGHLIGHT_PAGER_PROGRESS, - HIGHLIGHT_PAGER_SECONDARY -} -; - enum { /* @@ -126,54 +117,11 @@ enum */ 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 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 @@ -286,46 +234,17 @@ static wint_t readch() 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; -} - /** 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 color the color to apply to every printed character \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, bool 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; -} - -static int print_max(const wcstring &str, int color, int max, bool has_more, line_t *line) +static int print_max(const wcstring &str, highlight_spec_t color, int max, bool has_more, line_t *line) { int written = 0; for (size_t i=0; i < str.size(); i++) @@ -382,25 +301,25 @@ line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, s } - int bg_color = secondary ? HIGHLIGHT_PAGER_SECONDARY : HIGHLIGHT_NORMAL; + int bg_color = secondary ? highlight_spec_pager_secondary : highlight_spec_normal; for (size_t i=0; icomp.size(); i++) { const wcstring &comp = c->comp.at(i); if (i != 0) - written += print_max(L" ", 0 /* default color */, comp_width - written, true /* has_more */, &line_data); + written += print_max(L" ", highlight_spec_normal, comp_width - written, true /* has_more */, &line_data); - int packed_color = HIGHLIGHT_PAGER_PREFIX | (bg_color << 16); + int packed_color = highlight_spec_pager_prefix | highlight_make_background(bg_color); written += print_max(prefix, packed_color, comp_width - written, ! comp.empty(), &line_data); - packed_color = HIGHLIGHT_PAGER_COMPLETION | (bg_color << 16); + packed_color = highlight_spec_pager_completion | highlight_make_background(bg_color); written += print_max(comp, packed_color, comp_width - written, i + 1 < c->comp.size(), &line_data); } if (desc_width) { - int packed_color = HIGHLIGHT_PAGER_DESCRIPTION | (bg_color << 16); + int packed_color = highlight_spec_pager_description | highlight_make_background(bg_color); while (written < (width-desc_width-2)) { written += print_max(L" ", packed_color, 1, false, &line_data); @@ -596,16 +515,6 @@ void pager_t::measure_completion_infos(comp_info_list_t *infos, const wcstring & recalc_min_widths(infos); } -/** - 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; -} - #if 0 page_rendering_t render_completions(const completion_list_t &raw_completions, const wcstring &prefix) { @@ -843,7 +752,7 @@ int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_i /* List does not fit on screen. Print one screenful and leave a scrollable interface */ while (do_loop) { - set_color(rgb_color_t::black(), get_color(HIGHLIGHT_PAGER_PROGRESS)); + set_color(rgb_color_t::black(), highlight_get_color(highlight_spec_pager_progress, true)); wcstring msg = format_string(_(L" %d to %d of %d"), pos, pos+term_height-1, rows); msg.append(L" \r"); From 0627ae82fb2366aa140b986e80f2e68b822e4242 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Wed, 15 Jan 2014 18:21:38 -0800 Subject: [PATCH 06/21] Clean up pager on exit from interactive read --- pager.cpp | 41 ++++++++++++++++++++++++++++++++++++----- pager.h | 31 +++++++++++++++++++++++++++---- reader.cpp | 25 ++++++++++++++++++++----- screen.cpp | 26 ++++++++++++++++++++++---- screen.h | 7 ++++++- 5 files changed, 111 insertions(+), 19 deletions(-) diff --git a/pager.cpp b/pager.cpp index 6afb9f8e9..aa5d361ff 100644 --- a/pager.cpp +++ b/pager.cpp @@ -270,7 +270,7 @@ static int print_max(const wcstring &str, highlight_spec_t color, int max, bool /** Print the specified item using at the specified amount of space */ -line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, size_t row, size_t column, int width, bool secondary, page_rendering_t *rendering) const +line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, size_t row, size_t column, int width, bool secondary, bool selected, page_rendering_t *rendering) const { int comp_width=0, desc_width=0; int written=0; @@ -302,6 +302,10 @@ line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, s } int bg_color = secondary ? highlight_spec_pager_secondary : highlight_spec_normal; + if (selected) + { + bg_color = highlight_spec_search_match; + } for (size_t i=0; icomp.size(); i++) { @@ -356,8 +360,6 @@ void pager_t::completion_print(int cols, int *width_per_column, int row_start, i size_t rows = (lst.size()-1)/cols+1; - fprintf(stderr, "prefix: %ls\n", prefix.c_str()); - for (size_t row = row_start; row < row_stop; row++) { for (size_t col = 0; col < cols; col++) @@ -367,10 +369,12 @@ void pager_t::completion_print(int cols, int *width_per_column, int row_start, i if (lst.size() <= col * rows + row) continue; - const comp_t *el = &lst.at(col * rows + row); + size_t idx = col * rows + row; + const comp_t *el = &lst.at(idx); + bool is_selected = (idx == this->selected_completion_idx); /* Print this completion on its own "line" */ - line_t line = completion_print_item(prefix, el, row, col, width_per_column[col] - (is_last?0:2), row%2, rendering); + line_t line = completion_print_item(prefix, el, row, col, width_per_column[col] - (is_last?0:2), row%2, is_selected, rendering); /* If there's more to come, append two spaces */ if (col + 1 < cols) @@ -586,6 +590,11 @@ void pager_t::set_completions(const completion_list_t &raw_completions) measure_completion_infos(&completion_infos, prefix); } +void pager_t::set_prefix(const wcstring &pref) +{ + prefix = pref; +} + void pager_t::set_term_size(int w, int h) { assert(w > 0); @@ -882,3 +891,25 @@ page_rendering_t pager_t::render() const } return rendering; } + +pager_t::pager_t() : term_width(0), term_height(0), selected_completion_idx(-1) +{ +} + +bool pager_t::empty() const +{ + return completions.empty(); +} + +void pager_t::set_selected_completion(size_t idx) +{ + this->selected_completion_idx = idx; +} + + +void pager_t::clear() +{ + completions.clear(); + completion_infos.clear(); + prefix.clear(); +} diff --git a/pager.h b/pager.h index 8cf84f8d3..38c7f166e 100644 --- a/pager.h +++ b/pager.h @@ -21,6 +21,8 @@ class pager_t completion_list_t completions; + size_t selected_completion_idx; + /** Data structure describing one or a group of related completions */ public: struct comp_t @@ -52,19 +54,40 @@ class pager_t typedef std::vector comp_info_list_t; comp_info_list_t completion_infos; + wcstring prefix; + int completion_try_print(int cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const; void recalc_min_widths(comp_info_list_t * lst) const; void measure_completion_infos(std::vector *infos, const wcstring &prefix) const; void completion_print(int cols, int *width_per_column, int row_start, int row_stop, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const; - line_t completion_print_item(const wcstring &prefix, const comp_t *c, size_t row, size_t column, int width, bool secondary, page_rendering_t *rendering) const; + line_t completion_print_item(const wcstring &prefix, const comp_t *c, size_t row, size_t column, int width, bool secondary, bool selected, page_rendering_t *rendering) const; public: - void set_completions(const completion_list_t &comp); - void set_term_size(int w, int h); - wcstring prefix; + /* Sets the set of completions */ + void set_completions(const completion_list_t &comp); + + /* Sets the prefix */ + void set_prefix(const wcstring &pref); + + /* Sets the terminal width and height */ + void set_term_size(int w, int h); + + /* Sets the index of the selected completion */ + void set_selected_completion(size_t completion_idx); + + /* Produces a rendering of the completions, at the given term size */ page_rendering_t render() const; + + /* Indicates if there are no completions, and therefore nothing to render */ + bool empty() const; + + /* Clears all completions and the prefix */ + void clear(); + + /* Constructor */ + pager_t(); }; diff --git a/reader.cpp b/reader.cpp index 9b82550b8..11b5acadb 100644 --- a/reader.cpp +++ b/reader.cpp @@ -198,8 +198,11 @@ public: /** String containing the autosuggestion */ wcstring autosuggestion; - /** Current completions */ - page_rendering_t completion_page_rendering; + /** Current pager */ + pager_t current_pager; + + /** Whether we are navigating the pager */ + bool is_navigating_pager; /** Whether autosuggesting is allowed at all */ bool allow_autosuggestion; @@ -334,6 +337,7 @@ public: /** Constructor */ reader_data_t() : + is_navigating_pager(0), allow_autosuggestion(0), suppress_autosuggestion(0), expand_abbreviations(0), @@ -543,7 +547,7 @@ static void reader_repaint() &colors[0], &indents[0], data->buff_pos, - &data->completion_page_rendering.screen_data); + data->current_pager); data->repaint_needed = false; } @@ -1859,9 +1863,9 @@ static bool handle_completions(const std::vector &comp) { pager_t pager; pager.set_term_size(common_get_width(), common_get_height()); - pager.prefix = prefix; + pager.set_prefix(prefix); pager.set_completions(surviving_completions); - data->completion_page_rendering = pager.render(); + data->current_pager = pager; } else { @@ -3143,6 +3147,9 @@ const wchar_t *reader_readline(void) { /* The user typed R_COMPLETE more than once in a row. Cycle through our available completions */ const completion_t *next_comp = cycle_competions(comp, cycle_command_line, &completion_cycle_idx); + + data->current_pager.set_selected_completion(completion_cycle_idx); + if (next_comp != NULL) { size_t cursor_pos = cycle_cursor_pos; @@ -3815,6 +3822,14 @@ const wchar_t *reader_readline(void) } writestr(L"\n"); + + /* Ensure we have no pager contents when we exit */ + if (! data->current_pager.empty()) + { + /* Clear to end of screen to erase the pager contents. TODO: this may fail if eos doesn't exist, in which case we should emit newlines */ + screen_force_clear_to_end(); + data->current_pager.clear(); + } if (!reader_exit_forced()) { diff --git a/screen.cpp b/screen.cpp index f6c49b762..7d402a61e 100644 --- a/screen.cpp +++ b/screen.cpp @@ -45,6 +45,7 @@ efficient way for transforming that to the desired screen content. #include "highlight.h" #include "screen.h" #include "env.h" +#include "pager.h" /** The number of characters to indent new blocks */ #define INDENT_STEP 4 @@ -1027,7 +1028,7 @@ static void s_update(screen_t *scr, const wchar_t *left_prompt, const wchar_t *r if (! output.empty()) { - write_loop(1, &output.at(0), output.size()); + write_loop(STDOUT_FILENO, &output.at(0), output.size()); } /* We have now synced our actual screen against our desired screen. Note that this is a big assignment! */ @@ -1236,7 +1237,7 @@ void s_write(screen_t *s, const highlight_spec_t *colors, const int *indent, size_t cursor_pos, - const screen_data_t *pager_data) + const pager_t &pager) { screen_data_t::cursor_t cursor_arr; @@ -1325,9 +1326,10 @@ void s_write(screen_t *s, s->desired.cursor = cursor_arr; /* append pager_data */ - if (pager_data != NULL) + if (! pager.empty()) { - s->desired.append_lines(*pager_data); + const page_rendering_t rendering = pager.render(); + s->desired.append_lines(rendering.screen_data); } s_update(s, layout.left_prompt.c_str(), layout.right_prompt.c_str()); @@ -1435,6 +1437,22 @@ void s_reset(screen_t *s, screen_reset_mode_t mode) fstat(2, &s->prev_buff_2); } +bool screen_force_clear_to_end() +{ + bool result = false; + if (clr_eos) + { + data_buffer_t output; + s_write_mbs(&output, clr_eos); + if (! output.empty()) + { + write_loop(STDOUT_FILENO, &output.at(0), output.size()); + result = true; + } + } + return result; +} + screen_t::screen_t() : desired(), actual(), diff --git a/screen.h b/screen.h index 4fb581a5d..1419194cc 100644 --- a/screen.h +++ b/screen.h @@ -16,6 +16,8 @@ #include #include "highlight.h" +class pager_t; + /** A class representing a single line of a screen. */ @@ -203,7 +205,7 @@ void s_write(screen_t *s, const highlight_spec_t *colors, const int *indent, size_t cursor_pos, - const screen_data_t *pager_data); + const pager_t &pager_data); /** This function resets the screen buffers internal knowledge about @@ -241,6 +243,9 @@ enum screen_reset_mode_t void s_reset(screen_t *s, screen_reset_mode_t mode); +/* Issues an immediate clr_eos, returning if it existed */ +bool screen_force_clear_to_end(); + /* Returns the length of an escape code. Exposed for testing purposes only. */ size_t escape_code_length(const wchar_t *code); From c6e5201e15daf86fc25857164d51d5b9f29e0808 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 17 Jan 2014 12:04:03 -0800 Subject: [PATCH 07/21] Initial support for navigating completions that appear under the commandline using arrow keys --- builtin_commandline.cpp | 2 +- common.h | 9 +++ pager.cpp | 127 +++++++++++++++++++++++++++++++++------- pager.h | 17 +++++- reader.cpp | 123 ++++++++++++++++++++++---------------- screen.cpp | 10 +--- screen.h | 9 ++- 7 files changed, 215 insertions(+), 82 deletions(-) diff --git a/builtin_commandline.cpp b/builtin_commandline.cpp index f121cb644..8b6c409ac 100644 --- a/builtin_commandline.cpp +++ b/builtin_commandline.cpp @@ -149,7 +149,7 @@ static void write_part(const wchar_t *begin, { wchar_t *buff = wcsndup(begin, end-begin); // fwprintf( stderr, L"Subshell: %ls, end char %lc\n", buff, *end ); - wcstring out; + wcstring out; tokenizer_t tok(buff, TOK_ACCEPT_UNFINISHED); for (; tok_has_next(&tok); tok_next(&tok)) { diff --git a/common.h b/common.h index 86c5bc6c9..f789b7499 100644 --- a/common.h +++ b/common.h @@ -87,6 +87,15 @@ enum }; typedef unsigned int escape_flags_t; +/* Directions */ +enum cardinal_direction_t +{ + direction_north, + direction_east, + direction_south, + direction_west +}; + /** Helper macro for errors */ diff --git a/pager.cpp b/pager.cpp index aa5d361ff..ebf5fc939 100644 --- a/pager.cpp +++ b/pager.cpp @@ -122,6 +122,12 @@ static std::vector pager_buffer; */ static wcstring out_buff; +/* Returns numer / denom, rounding up */ +static size_t divide_round_up(size_t numer, size_t denom) +{ + return numer / denom + (numer % denom ? 1 : 0); +} + /** This function calculates the minimum width for each completion entry in the specified array_list. This width depends on the @@ -639,7 +645,7 @@ int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_i */ int print=0; - int rows = (int)((lst.size()-1)/cols+1); + int rows = (int)divide_round_up(lst.size(), cols); int pref_tot_width=0; int min_tot_width = 0; @@ -857,6 +863,7 @@ int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_i page_rendering_t pager_t::render() const { + /** Try to print the completions. Start by trying to print the list in PAGER_MAX_COLS columns, if the completions won't @@ -864,34 +871,54 @@ page_rendering_t pager_t::render() const column never fails. */ page_rendering_t rendering; - for (int i = PAGER_MAX_COLS; i>0; i--) + rendering.term_width = this->term_width; + rendering.term_height = this->term_height; + rendering.selected_completion_idx = this->selected_completion_idx; + + if (! this->empty()) { - /* Initially empty rendering */ - rendering.screen_data.resize(0); - - switch (completion_try_print(i, prefix, completion_infos, &rendering)) + int cols; + bool done = false; + for (cols = PAGER_MAX_COLS; cols > 0 && ! done; cols--) { - case PAGER_RETRY: - break; + /* Initially empty rendering */ + rendering.screen_data.resize(0); + + switch (completion_try_print(cols, prefix, completion_infos, &rendering)) + { + case PAGER_RETRY: + break; - case PAGER_DONE: - i=0; - break; + case PAGER_DONE: + done = true; + break; - case PAGER_RESIZE: - /* - This means we got a resize event, so we start - over from the beginning. Since it the screen got - bigger, we might be able to fit all completions - on-screen. - */ - i=PAGER_MAX_COLS+1; - break; + case PAGER_RESIZE: + /* + This means we got a resize event, so we start + over from the beginning. Since it the screen got + bigger, we might be able to fit all completions + on-screen. + */ + cols=PAGER_MAX_COLS+1; + break; + } } + assert(cols >= 0); + rendering.cols = (size_t)cols; + rendering.rows = divide_round_up(completion_infos.size(), rendering.cols); } return rendering; } +void pager_t::update_rendering(page_rendering_t *rendering) const +{ + if (rendering->term_width != this->term_width || rendering->term_height != this->term_height || rendering->selected_completion_idx != this->selected_completion_idx) + { + *rendering = this->render(); + } +} + pager_t::pager_t() : term_width(0), term_height(0), selected_completion_idx(-1) { } @@ -906,6 +933,62 @@ void pager_t::set_selected_completion(size_t idx) this->selected_completion_idx = idx; } +bool pager_t::select_next_completion_in_direction(cardinal_direction_t direction, const page_rendering_t &rendering) +{ + /* Handle the case of nothing selected yet */ + if (selected_completion_idx == (size_t)(-1)) + { + return false; + } + + /* We have a completion index; we wish to compute its row and column. Completions are rendered column first, i.e. we go south before we go west. */ + size_t current_row = selected_completion_idx % rendering.rows; + size_t current_col = selected_completion_idx / rendering.rows; + + switch (direction) + { + case direction_north: + { + /* Go up a whole row */ + if (current_row > 0) + current_row--; + break; + } + + case direction_south: + { + /* Go down, unless we are in the last row. Note that this means that we may set selected_completion_idx to an out-of-bounds value if the last row is incomplete; this is a feature (it allows "last column memory"). */ + if (current_row + 1 < rendering.rows) + current_row++; + break; + } + + case direction_east: + { + if (current_col + 1 < rendering.cols) + current_col++; + break; + } + + case direction_west: + { + if (current_col > 0) + current_col--; + break; + } + } + + size_t new_selected_completion_idx = current_col * rendering.rows + current_row; + if (new_selected_completion_idx != selected_completion_idx) + { + selected_completion_idx = new_selected_completion_idx; + return true; + } + else + { + return false; + } +} void pager_t::clear() { @@ -913,3 +996,7 @@ void pager_t::clear() completion_infos.clear(); prefix.clear(); } + +page_rendering_t::page_rendering_t() : term_width(-1), term_height(-1), rows(0), cols(0), selected_completion_idx(-1) +{ +} diff --git a/pager.h b/pager.h index 38c7f166e..e53e919c7 100644 --- a/pager.h +++ b/pager.h @@ -6,9 +6,18 @@ #include "screen.h" /* Represents rendering from the pager */ -struct page_rendering_t +class page_rendering_t { + public: + int term_width; + int term_height; + size_t rows; + size_t cols; + size_t selected_completion_idx; screen_data_t screen_data; + + /* Returns a rendering with invalid data, useful to indicate "no rendering" */ + page_rendering_t(); }; typedef std::vector completion_list_t; @@ -79,9 +88,15 @@ class pager_t /* Sets the index of the selected completion */ void set_selected_completion(size_t completion_idx); + /* Changes the selected completion in the given direction according to the layout of the given rendering. Returns true if the values changed. */ + bool select_next_completion_in_direction(cardinal_direction_t direction, const page_rendering_t &rendering); + /* Produces a rendering of the completions, at the given term size */ page_rendering_t render() const; + /* Updates the rendering if it's stale */ + void update_rendering(page_rendering_t *rendering) const; + /* Indicates if there are no completions, and therefore nothing to render */ bool empty() const; diff --git a/reader.cpp b/reader.cpp index 11b5acadb..95fa15bbf 100644 --- a/reader.cpp +++ b/reader.cpp @@ -201,6 +201,9 @@ public: /** Current pager */ pager_t current_pager; + /** Current page rendering */ + page_rendering_t current_page_rendering; + /** Whether we are navigating the pager */ bool is_navigating_pager; @@ -538,6 +541,10 @@ static void reader_repaint() std::vector indents = data->indents; indents.resize(len); + + // Re-render our completions page if necessary + data->current_pager.set_term_size(common_get_width(), common_get_height()); + data->current_pager.update_rendering(&data->current_page_rendering); s_write(&data->screen, data->left_prompt_buff, @@ -547,7 +554,7 @@ static void reader_repaint() &colors[0], &indents[0], data->buff_pos, - data->current_pager); + data->current_page_rendering); data->repaint_needed = false; } @@ -1852,33 +1859,31 @@ static bool handle_completions(const std::vector &comp) prefix.append(data->command_line, prefix_start + len - PREFIX_MAX_LEN, PREFIX_MAX_LEN); } + wchar_t quote; + parse_util_get_parameter_info(data->command_line, data->buff_pos, "e, NULL, NULL); + bool is_quoted = (quote != L'\0'); + + if (1) { - int is_quoted; - - wchar_t quote; - parse_util_get_parameter_info(data->command_line, data->buff_pos, "e, NULL, NULL); - is_quoted = (quote != L'\0'); + data->current_pager.set_prefix(prefix); + data->current_pager.set_completions(surviving_completions); - if (1) - { - pager_t pager; - pager.set_term_size(common_get_width(), common_get_height()); - pager.set_prefix(prefix); - pager.set_completions(surviving_completions); - data->current_pager = pager; - } - else - { - /* Clear the autosuggestion from the old commandline before abandoning it (see #561) */ - if (! data->autosuggestion.empty()) - reader_repaint_without_autosuggestion(); - - write_loop(1, "\n", 1); - - run_pager(prefix, is_quoted, surviving_completions); - } + /* Invalidate our rendering */ + data->current_page_rendering = page_rendering_t(); + } + else + { + /* Clear the autosuggestion from the old commandline before abandoning it (see #561) */ + if (! data->autosuggestion.empty()) + reader_repaint_without_autosuggestion(); + + write_loop(1, "\n", 1); + + run_pager(prefix, is_quoted, surviving_completions); + + s_reset(&data->screen, screen_reset_abandon_line); + } - s_reset(&data->screen, screen_reset_abandon_line); reader_repaint(); success = false; } @@ -2970,6 +2975,9 @@ const wchar_t *reader_readline(void) /* The cycle index in our completion list */ size_t completion_cycle_idx = (size_t)(-1); + + /* Indicates if we are currently navigating pager contents */ + bool is_navigating_pager_contents = false; /* The command line before completion */ wcstring cycle_command_line; @@ -3152,6 +3160,8 @@ const wchar_t *reader_readline(void) if (next_comp != NULL) { + is_navigating_pager_contents = true; + size_t cursor_pos = cycle_cursor_pos; const wcstring new_cmd_line = completion_apply_to_command_line(next_comp->completion, next_comp->flags, cycle_command_line, &cursor_pos, false); reader_set_buffer(new_cmd_line, cursor_pos); @@ -3628,38 +3638,49 @@ const wchar_t *reader_readline(void) case R_UP_LINE: case R_DOWN_LINE: { - int line_old = parse_util_get_line_from_offset(data->command_line, data->buff_pos); - int line_new; - - if (c == R_UP_LINE) - line_new = line_old-1; - else - line_new = line_old+1; - - int line_count = parse_util_lineno(data->command_line.c_str(), data->command_length())-1; - - if (line_new >= 0 && line_new <= line_count) + if (is_navigating_pager_contents) { - size_t base_pos_new; - size_t base_pos_old; + if (data->current_pager.select_next_completion_in_direction(c == R_UP_LINE ? direction_north : direction_south, data->current_page_rendering)) + { + reader_repaint(); + } + } + else + { + /* Not navigating the pager contents */ + int line_old = parse_util_get_line_from_offset(data->command_line, data->buff_pos); + int line_new; - int indent_old; - int indent_new; - size_t line_offset_old; - size_t total_offset_new; + if (c == R_UP_LINE) + line_new = line_old-1; + else + line_new = line_old+1; - base_pos_new = parse_util_get_offset_from_line(data->command_line, line_new); + int line_count = parse_util_lineno(data->command_line.c_str(), data->command_length())-1; - base_pos_old = parse_util_get_offset_from_line(data->command_line, line_old); + if (line_new >= 0 && line_new <= line_count) + { + size_t base_pos_new; + size_t base_pos_old; - assert(base_pos_new != (size_t)(-1) && base_pos_old != (size_t)(-1)); - indent_old = data->indents.at(base_pos_old); - indent_new = data->indents.at(base_pos_new); + int indent_old; + int indent_new; + size_t line_offset_old; + size_t total_offset_new; - line_offset_old = data->buff_pos - parse_util_get_offset_from_line(data->command_line, line_old); - total_offset_new = parse_util_get_offset(data->command_line, line_new, line_offset_old - 4*(indent_new-indent_old)); - data->buff_pos = total_offset_new; - reader_repaint(); + base_pos_new = parse_util_get_offset_from_line(data->command_line, line_new); + + base_pos_old = parse_util_get_offset_from_line(data->command_line, line_old); + + assert(base_pos_new != (size_t)(-1) && base_pos_old != (size_t)(-1)); + indent_old = data->indents.at(base_pos_old); + indent_new = data->indents.at(base_pos_new); + + line_offset_old = data->buff_pos - parse_util_get_offset_from_line(data->command_line, line_old); + total_offset_new = parse_util_get_offset(data->command_line, line_new, line_offset_old - 4*(indent_new-indent_old)); + data->buff_pos = total_offset_new; + reader_repaint(); + } } break; diff --git a/screen.cpp b/screen.cpp index 7d402a61e..24c10d105 100644 --- a/screen.cpp +++ b/screen.cpp @@ -1237,7 +1237,7 @@ void s_write(screen_t *s, const highlight_spec_t *colors, const int *indent, size_t cursor_pos, - const pager_t &pager) + const page_rendering_t &pager) { screen_data_t::cursor_t cursor_arr; @@ -1325,12 +1325,8 @@ void s_write(screen_t *s, s->desired.cursor = cursor_arr; - /* append pager_data */ - if (! pager.empty()) - { - const page_rendering_t rendering = pager.render(); - s->desired.append_lines(rendering.screen_data); - } + /* Append pager_data (none if empty) */ + s->desired.append_lines(pager.screen_data); s_update(s, layout.left_prompt.c_str(), layout.right_prompt.c_str()); s_save_status(s); diff --git a/screen.h b/screen.h index 1419194cc..cfc12ba36 100644 --- a/screen.h +++ b/screen.h @@ -16,7 +16,7 @@ #include #include "highlight.h" -class pager_t; +class page_rendering_t; /** A class representing a single line of a screen. @@ -117,6 +117,11 @@ public: { this->line_datas.insert(this->line_datas.end(), d.line_datas.begin(), d.line_datas.end()); } + + bool empty() const + { + return line_datas.empty(); + } }; /** @@ -205,7 +210,7 @@ void s_write(screen_t *s, const highlight_spec_t *colors, const int *indent, size_t cursor_pos, - const pager_t &pager_data); + const page_rendering_t &pager_data); /** This function resets the screen buffers internal knowledge about From 32054b6f326abab45c6d1e6047b50e643be49a7b Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 17 Jan 2014 12:53:01 -0800 Subject: [PATCH 08/21] Implement and document new -P / --paging-mode flags to commandline, to support new pager --- builtin_commandline.cpp | 101 +++++++++++++--------------------------- doc_src/commandline.txt | 10 ++++ reader.cpp | 12 ++++- reader.h | 10 +++- 4 files changed, 62 insertions(+), 71 deletions(-) diff --git a/builtin_commandline.cpp b/builtin_commandline.cpp index 8b6c409ac..564eee7bc 100644 --- a/builtin_commandline.cpp +++ b/builtin_commandline.cpp @@ -213,6 +213,7 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv) int cursor_mode = 0; int line_mode = 0; int search_mode = 0; + int paging_mode = 0; const wchar_t *begin, *end; current_buffer = (wchar_t *)builtin_complete_get_temporary_buffer(); @@ -251,71 +252,24 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv) static const struct woption long_options[] = { - { - L"append", no_argument, 0, 'a' - } - , - { - L"insert", no_argument, 0, 'i' - } - , - { - L"replace", no_argument, 0, 'r' - } - , - { - L"current-job", no_argument, 0, 'j' - } - , - { - L"current-process", no_argument, 0, 'p' - } - , - { - L"current-token", no_argument, 0, 't' - } - , - { - L"current-buffer", no_argument, 0, 'b' - } - , - { - L"cut-at-cursor", no_argument, 0, 'c' - } - , - { - L"function", no_argument, 0, 'f' - } - , - { - L"tokenize", no_argument, 0, 'o' - } - , - { - L"help", no_argument, 0, 'h' - } - , - { - L"input", required_argument, 0, 'I' - } - , - { - L"cursor", no_argument, 0, 'C' - } - , - { - L"line", no_argument, 0, 'L' - } - , - { - L"search-mode", no_argument, 0, 'S' - } - , - { - 0, 0, 0, 0 - } - } - ; + { L"append", no_argument, 0, 'a' }, + { L"insert", no_argument, 0, 'i' }, + { L"replace", no_argument, 0, 'r' }, + { L"current-job", no_argument, 0, 'j' }, + { L"current-process", no_argument, 0, 'p' }, + { L"current-token", no_argument, 0, 't' }, + { L"current-buffer", no_argument, 0, 'b' }, + { L"cut-at-cursor", no_argument, 0, 'c' }, + { L"function", no_argument, 0, 'f' }, + { L"tokenize", no_argument, 0, 'o' }, + { L"help", no_argument, 0, 'h' }, + { L"input", required_argument, 0, 'I' }, + { L"cursor", no_argument, 0, 'C' }, + { L"line", no_argument, 0, 'L' }, + { L"search-mode", no_argument, 0, 'S' }, + { L"paging-mode", no_argument, 0, 'P' }, + { 0, 0, 0, 0 } + }; int opt_index = 0; @@ -397,6 +351,10 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv) case 'S': search_mode = 1; break; + + case 'P': + paging_mode = 1; + break; case 'h': builtin_print_help(parser, argv[0], stdout_buffer); @@ -415,7 +373,7 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv) /* Check for invalid switch combinations */ - if (buffer_part || cut_at_cursor || append_mode || tokenize || cursor_mode || line_mode || search_mode) + if (buffer_part || cut_at_cursor || append_mode || tokenize || cursor_mode || line_mode || search_mode || paging_mode) { append_format(stderr_buffer, BUILTIN_ERR_COMBO, @@ -464,7 +422,7 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv) /* Check for invalid switch combinations */ - if ((search_mode || line_mode || cursor_mode) && (argc-woptind > 1)) + if ((search_mode || line_mode || cursor_mode || paging_mode) && (argc-woptind > 1)) { append_format(stderr_buffer, @@ -475,7 +433,7 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv) return 1; } - if ((buffer_part || tokenize || cut_at_cursor) && (cursor_mode || line_mode || search_mode)) + if ((buffer_part || tokenize || cut_at_cursor) && (cursor_mode || line_mode || search_mode || paging_mode)) { append_format(stderr_buffer, BUILTIN_ERR_COMBO, @@ -564,7 +522,12 @@ static int builtin_commandline(parser_t &parser, wchar_t **argv) if (search_mode) { - return !reader_search_mode(); + return ! reader_search_mode(); + } + + if (paging_mode) + { + return ! reader_has_pager_contents(); } diff --git a/doc_src/commandline.txt b/doc_src/commandline.txt index ab771e96d..1d13f79e5 100644 --- a/doc_src/commandline.txt +++ b/doc_src/commandline.txt @@ -57,6 +57,16 @@ If \c commandline is called during a call to complete a given string using complete -C STRING, \c commandline will consider the specified string to be the current contents of the command line. +The following options output metadata about the commandline state: + +- \c -L or \c --line print the line that the cursor is on, with the topmost +line starting at 1 +- \c -S or \c --search-mode evaluates to true if the commandline is performing +a history search +- \c -P or \c --paging-mode evaluates to true if the commandline is showing +pager contents, such as tab completions + + \subsection commandline-example Example commandline -j $history[3] replaces the job under the cursor with the diff --git a/reader.cpp b/reader.cpp index 95fa15bbf..8c6d484e1 100644 --- a/reader.cpp +++ b/reader.cpp @@ -3872,7 +3872,17 @@ int reader_search_mode() return -1; } - return !!data->search_mode; + return !! data->search_mode; +} + +int reader_has_pager_contents() +{ + if (!data) + { + return -1; + } + + return data->current_page_rendering.screen_data.empty() ? 1 : 0; } diff --git a/reader.h b/reader.h index cd32e751b..82694e21b 100644 --- a/reader.h +++ b/reader.h @@ -239,10 +239,18 @@ int reader_shell_test(const wchar_t *b); /** Test whether the interactive reader is in search mode. - \return o if not in search mode, 1 if in search mode and -1 if not in interactive mode + \return 0 if not in search mode, 1 if in search mode and -1 if not in interactive mode */ int reader_search_mode(); +/** + Test whether the interactive reader has visible pager contents. + + \return 0 if it has pager contents, 1 if it does not have pager contents, and -1 if not in interactive mode + */ +int reader_has_pager_contents(); + + /* Given a command line and an autosuggestion, return the string that gets shown to the user. Exposed for testing purposes only. */ wcstring combine_command_and_autosuggestion(const wcstring &cmdline, const wcstring &autosuggestion); From 808bc42f2a75e4af70ae992f6b7c1a4bf59eac50 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sat, 18 Jan 2014 12:42:53 -0800 Subject: [PATCH 09/21] Further work on keyboard navigating the completion list --- Makefile.in | 3 +- common.h | 23 +++- fish.cpp | 2 +- pager.cpp | 215 +++++++++++++++++++++++------- pager.h | 10 +- reader.cpp | 67 +++++----- share/functions/up-or-search.fish | 6 + 7 files changed, 240 insertions(+), 86 deletions(-) diff --git a/Makefile.in b/Makefile.in index c016d9f7a..b67ffac01 100644 --- a/Makefile.in +++ b/Makefile.in @@ -91,7 +91,8 @@ FISH_OBJS := function.o builtin.o complete.o env.o exec.o expand.o \ env_universal.o env_universal_common.o input_common.o event.o \ signal.o io.o parse_util.o common.o screen.o path.o autoload.o \ parser_keywords.o iothread.o color.o postfork.o \ - builtin_test.o parse_tree.o parse_productions.o parse_execution.cpp + builtin_test.o parse_tree.o parse_productions.o parse_execution.cpp \ + pager.cpp FISH_INDENT_OBJS := fish_indent.o print_help.o common.o \ parser_keywords.o wutil.o tokenizer.o diff --git a/common.h b/common.h index f789b7499..2a96b7498 100644 --- a/common.h +++ b/common.h @@ -88,14 +88,33 @@ enum typedef unsigned int escape_flags_t; /* Directions */ -enum cardinal_direction_t +enum selection_direction_t { + /* visual directions */ direction_north, direction_east, direction_south, - direction_west + direction_west, + + /* logical directions */ + direction_next, + direction_prev }; +inline bool selection_direction_is_cardinal(selection_direction_t dir) +{ + switch (dir) + { + case direction_north: + case direction_east: + case direction_south: + case direction_west: + return true; + default: + return false; + } +} + /** Helper macro for errors */ diff --git a/fish.cpp b/fish.cpp index 77686195a..bf6470f8a 100644 --- a/fish.cpp +++ b/fish.cpp @@ -182,7 +182,7 @@ static struct config_paths_t determine_config_directory_paths(const char *argv0) { wcstring base_path = str2wcstring(exec_path); base_path.resize(base_path.size() - strlen(suffix)); - + paths.data = base_path + L"/share/fish"; paths.sysconf = base_path + L"/etc/fish"; paths.doc = base_path + L"/share/doc/fish"; diff --git a/pager.cpp b/pager.cpp index ebf5fc939..4093cca01 100644 --- a/pager.cpp +++ b/pager.cpp @@ -59,6 +59,8 @@ #include "print_help.h" #include "highlight.h" +#define PAGER_SELECTION_NONE ((size_t)(-1)) + typedef pager_t::comp_t comp_t; typedef std::vector completion_list_t; typedef std::vector comp_info_list_t; @@ -365,7 +367,9 @@ void pager_t::completion_print(int cols, int *width_per_column, int row_start, i { size_t rows = (lst.size()-1)/cols+1; - + + size_t effective_selected_idx = this->saturated_selected_completion_index(); + for (size_t row = row_start; row < row_stop; row++) { for (size_t col = 0; col < cols; col++) @@ -377,7 +381,7 @@ void pager_t::completion_print(int cols, int *width_per_column, int row_start, i size_t idx = col * rows + row; const comp_t *el = &lst.at(idx); - bool is_selected = (idx == this->selected_completion_idx); + bool is_selected = (idx == effective_selected_idx); /* Print this completion on its own "line" */ line_t line = completion_print_item(prefix, el, row, col, width_per_column[col] - (is_last?0:2), row%2, is_selected, rendering); @@ -873,7 +877,7 @@ page_rendering_t pager_t::render() const page_rendering_t rendering; rendering.term_width = this->term_width; rendering.term_height = this->term_height; - rendering.selected_completion_idx = this->selected_completion_idx; + rendering.selected_completion_idx = this->saturated_selected_completion_index(); if (! this->empty()) { @@ -913,7 +917,7 @@ page_rendering_t pager_t::render() const void pager_t::update_rendering(page_rendering_t *rendering) const { - if (rendering->term_width != this->term_width || rendering->term_height != this->term_height || rendering->selected_completion_idx != this->selected_completion_idx) + if (rendering->term_width != this->term_width || rendering->term_height != this->term_height || rendering->selected_completion_idx != this->saturated_selected_completion_index()) { *rendering = this->render(); } @@ -928,66 +932,177 @@ bool pager_t::empty() const return completions.empty(); } -void pager_t::set_selected_completion(size_t idx) -{ - this->selected_completion_idx = idx; -} - -bool pager_t::select_next_completion_in_direction(cardinal_direction_t direction, const page_rendering_t &rendering) +const completion_t *pager_t::select_next_completion_in_direction(selection_direction_t direction, const page_rendering_t &rendering) { + /* Must have something to select */ + if (completions.empty()) + { + return NULL; + } + /* Handle the case of nothing selected yet */ - if (selected_completion_idx == (size_t)(-1)) + if (selected_completion_idx == PAGER_SELECTION_NONE) { - return false; - } - - /* We have a completion index; we wish to compute its row and column. Completions are rendered column first, i.e. we go south before we go west. */ - size_t current_row = selected_completion_idx % rendering.rows; - size_t current_col = selected_completion_idx / rendering.rows; - - switch (direction) - { - case direction_north: + if (selection_direction_is_cardinal(direction)) { - /* Go up a whole row */ - if (current_row > 0) - current_row--; - break; + /* Cardinal directions do nothing unless something is selected */ + return NULL; } - - case direction_south: + else { - /* Go down, unless we are in the last row. Note that this means that we may set selected_completion_idx to an out-of-bounds value if the last row is incomplete; this is a feature (it allows "last column memory"). */ - if (current_row + 1 < rendering.rows) - current_row++; - break; - } - - case direction_east: - { - if (current_col + 1 < rendering.cols) - current_col++; - break; - } - - case direction_west: - { - if (current_col > 0) - current_col--; - break; + /* Forward/backward do something sane */ + selected_completion_idx = (direction == direction_next ? 0 : completions.size() - 1); + return selected_completion(); } } - size_t new_selected_completion_idx = current_col * rendering.rows + current_row; - if (new_selected_completion_idx != selected_completion_idx) + /* Ok, we had something selected already. Select something different. */ + size_t new_selected_completion_idx = selected_completion_idx; + if (! selection_direction_is_cardinal(direction)) { - selected_completion_idx = new_selected_completion_idx; - return true; + /* Next / previous, easy */ + if (direction == direction_next) + { + new_selected_completion_idx = selected_completion_idx + 1; + if (new_selected_completion_idx > completion_infos.size()) + { + new_selected_completion_idx = 0; + } + } + else if (direction == direction_prev) + { + if (selected_completion_idx == 0) + { + new_selected_completion_idx = completion_infos.size() - 1; + } + else + { + new_selected_completion_idx = selected_completion_idx - 1; + } + } + else + { + assert(0 && "Unknown non-cardinal direction"); + } } else { - return false; + /* Cardinal directions. We have a completion index; we wish to compute its row and column. Completions are rendered column first, i.e. we go south before we go west. */ + size_t current_row = selected_completion_idx % rendering.rows; + size_t current_col = selected_completion_idx / rendering.rows; + + switch (direction) + { + case direction_north: + { + /* Go up a whole row. If we cycle, go to the previous column. */ + if (current_row > 0) + { + current_row--; + } + else + { + current_row = rendering.rows - 1; + if (current_col > 0) + current_col--; + } + break; + } + + case direction_south: + { + /* Go down, unless we are in the last row. Note that this means that we may set selected_completion_idx to an out-of-bounds value if the last row is incomplete; this is a feature (it allows "last column memory"). */ + if (current_row + 1 < rendering.rows) + { + current_row++; + } + else + { + current_row = 0; + if (current_col + 1 < rendering.cols) + current_col++; + + } + break; + } + + case direction_east: + { + /* Go east, wrapping to the next row */ + if (current_col + 1 < rendering.cols) + { + current_col++; + } + else + { + current_col = 0; + if (current_row + 1 < rendering.rows) + current_row++; + } + break; + } + + case direction_west: + { + /* Go west, wrapping to the previous row */ + if (current_col > 0) + { + current_col--; + } + else + { + current_col = rendering.cols - 1; + if (current_row > 0) + current_row--; + } + break; + } + + default: + assert(0 && "Unknown cardinal direction"); + break; + } + + /* Compute the new index based on the changed row */ + new_selected_completion_idx = current_col * rendering.rows + current_row; } + + if (new_selected_completion_idx != selected_completion_idx) + { + selected_completion_idx = new_selected_completion_idx; + return selected_completion(); + } + else + { + return NULL; + } +} + +size_t pager_t::saturated_selected_completion_index() const +{ + size_t result = selected_completion_idx; + if (result != PAGER_SELECTION_NONE && result >= completions.size()) + { + result = completions.size() - 1; + } + assert(result == PAGER_SELECTION_NONE || result < completions.size()); + return result; +} + +void pager_t::set_selected_completion_index(size_t completion_idx) +{ + selected_completion_idx = completion_idx; +} + +const completion_t *pager_t::selected_completion() const +{ + const completion_t * result = NULL; + size_t idx = saturated_selected_completion_index(); + if (idx != PAGER_SELECTION_NONE) + { + result = &completions.at(idx); + } + return result; } void pager_t::clear() diff --git a/pager.h b/pager.h index e53e919c7..b3561d384 100644 --- a/pager.h +++ b/pager.h @@ -32,6 +32,9 @@ class pager_t size_t selected_completion_idx; + /* Returns the selected completion index, but not to exceed completions.size() */ + size_t saturated_selected_completion_index() const; + /** Data structure describing one or a group of related completions */ public: struct comp_t @@ -86,10 +89,13 @@ class pager_t void set_term_size(int w, int h); /* Sets the index of the selected completion */ - void set_selected_completion(size_t completion_idx); + void set_selected_completion_index(size_t completion_idx); /* Changes the selected completion in the given direction according to the layout of the given rendering. Returns true if the values changed. */ - bool select_next_completion_in_direction(cardinal_direction_t direction, const page_rendering_t &rendering); + const completion_t *select_next_completion_in_direction(selection_direction_t direction, const page_rendering_t &rendering); + + /* Returns the currently selected completion */ + const completion_t *selected_completion() const; /* Produces a rendering of the completions, at the given term size */ page_rendering_t render() const; diff --git a/reader.cpp b/reader.cpp index 8c6d484e1..f01c9207c 100644 --- a/reader.cpp +++ b/reader.cpp @@ -1525,6 +1525,28 @@ static void accept_autosuggestion(bool full) } } +static bool is_navigating_pager_contents() +{ + return data && data->current_pager.selected_completion() != NULL; +} + +static void select_completion_in_direction(enum selection_direction_t dir, const wcstring &cycle_command_line, size_t cycle_cursor_pos) +{ + const completion_t *next_comp = data->current_pager.select_next_completion_in_direction(dir, data->current_page_rendering); + if (next_comp != NULL) + { + size_t cursor_pos = cycle_cursor_pos; + const wcstring new_cmd_line = completion_apply_to_command_line(next_comp->completion, next_comp->flags, cycle_command_line, &cursor_pos, false); + reader_set_buffer(new_cmd_line, cursor_pos); + + /* Since we just inserted a completion, don't immediately do a new autosuggestion */ + data->suppress_autosuggestion = true; + + /* Trigger repaint (see #765) */ + reader_repaint(); + } +} + /** Flash the screen. This function only changed the color of the current line, since the flash_screen sequnce is rather painful to @@ -2975,9 +2997,6 @@ const wchar_t *reader_readline(void) /* The cycle index in our completion list */ size_t completion_cycle_idx = (size_t)(-1); - - /* Indicates if we are currently navigating pager contents */ - bool is_navigating_pager_contents = false; /* The command line before completion */ wcstring cycle_command_line; @@ -3153,25 +3172,8 @@ const wchar_t *reader_readline(void) if (! comp_empty && last_char == R_COMPLETE) { - /* The user typed R_COMPLETE more than once in a row. Cycle through our available completions */ - const completion_t *next_comp = cycle_competions(comp, cycle_command_line, &completion_cycle_idx); - - data->current_pager.set_selected_completion(completion_cycle_idx); - - if (next_comp != NULL) - { - is_navigating_pager_contents = true; - - size_t cursor_pos = cycle_cursor_pos; - const wcstring new_cmd_line = completion_apply_to_command_line(next_comp->completion, next_comp->flags, cycle_command_line, &cursor_pos, false); - reader_set_buffer(new_cmd_line, cursor_pos); - - /* Since we just inserted a completion, don't immediately do a new autosuggestion */ - data->suppress_autosuggestion = true; - - /* Trigger repaint (see #765) */ - reader_repaint_if_needed(); - } + /* The user typed R_COMPLETE more than once in a row. Cycle through our available completions. */ + select_completion_in_direction(direction_next, cycle_command_line, cycle_cursor_pos); } else { @@ -3555,7 +3557,11 @@ const wchar_t *reader_readline(void) /* Move left*/ case R_BACKWARD_CHAR: { - if (data->buff_pos > 0) + if (is_navigating_pager_contents()) + { + select_completion_in_direction(direction_west, cycle_command_line, cycle_cursor_pos); + } + else if (data->buff_pos > 0) { data->buff_pos--; reader_repaint(); @@ -3566,7 +3572,11 @@ const wchar_t *reader_readline(void) /* Move right*/ case R_FORWARD_CHAR: { - if (data->buff_pos < data->command_length()) + if (is_navigating_pager_contents()) + { + select_completion_in_direction(direction_east, cycle_command_line, cycle_cursor_pos); + } + else if (data->buff_pos < data->command_length()) { data->buff_pos++; reader_repaint(); @@ -3638,12 +3648,9 @@ const wchar_t *reader_readline(void) case R_UP_LINE: case R_DOWN_LINE: { - if (is_navigating_pager_contents) + if (is_navigating_pager_contents()) { - if (data->current_pager.select_next_completion_in_direction(c == R_UP_LINE ? direction_north : direction_south, data->current_page_rendering)) - { - reader_repaint(); - } + select_completion_in_direction(c == R_UP_LINE ? direction_north : direction_south, cycle_command_line, cycle_cursor_pos); } else { @@ -3882,7 +3889,7 @@ int reader_has_pager_contents() return -1; } - return data->current_page_rendering.screen_data.empty() ? 1 : 0; + return ! data->current_page_rendering.screen_data.empty(); } diff --git a/share/functions/up-or-search.fish b/share/functions/up-or-search.fish index fc51d7106..98179ad7c 100644 --- a/share/functions/up-or-search.fish +++ b/share/functions/up-or-search.fish @@ -5,6 +5,12 @@ function up-or-search -d "Depending on cursor position and current mode, either return end + # If we are navigating the pager, then up always navigates + if commandline --paging-mode + commandline -f up-line + return + end + # We are not already in search mode. # If we are on the top line, start search mode, # otherwise move up From d9d65577f4f2f7ca7b3866c10c44136c75d6e6ef Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 19 Jan 2014 16:41:26 -0800 Subject: [PATCH 10/21] Improved navigation of pager list. Added tests for it too. --- fish_tests.cpp | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++ pager.cpp | 78 +++++++++++++++++++++++++---------------- pager.h | 12 ++++--- reader.cpp | 2 +- screen.h | 11 ++++++ 5 files changed, 163 insertions(+), 35 deletions(-) diff --git a/fish_tests.cpp b/fish_tests.cpp index 8b08365b9..0c04d9631 100644 --- a/fish_tests.cpp +++ b/fish_tests.cpp @@ -61,6 +61,7 @@ #include "signal.h" #include "parse_tree.h" #include "parse_util.h" +#include "pager.h" static const char * const * s_arguments; static int s_test_run_count = 0; @@ -1174,6 +1175,99 @@ static void test_path() if (! paths_are_equivalent(L"/", L"/")) err(L"Bug in canonical PATH code on line %ld", (long)__LINE__); } +static void test_pager_navigation() +{ + say(L"Testing pager navigation"); + + /* Generate 19 strings of width 10. There's 2 spaces between completions, and our term size is 80; these can therefore fit into 6 columns (6 * 12 - 2 = 70) or 5 columns (58) but not 7 columns (7 * 12 - 2 = 82). + + You can simulate this test by creating 19 files named "file00.txt" through "file_18.txt". + */ + completion_list_t completions; + for (size_t i=0; i < 19; i++) + { + append_completion(completions, L"abcdefghij"); + } + + pager_t pager; + pager.set_completions(completions); + pager.set_term_size(80, 24); + page_rendering_t render = pager.render(); + + if (render.term_width != 80) + err(L"Wrong term width"); + if (render.term_height != 24) + err(L"Wrong term height"); + + size_t rows = 4, cols = 5; + + /* We have 19 completions. We can fit into 6 columns with 4 rows or 5 columns with 4 rows; the second one is better and so is what we ought to have picked. */ + if (render.rows != rows) + err(L"Wrong row count"); + if (render.cols != cols) + err(L"Wrong column count"); + + /* Initially expect to have no completion index */ + if (render.selected_completion_idx != (size_t)(-1)) + { + err(L"Wrong initial selection"); + } + + /* Here are navigation directions and where we expect the selection to be */ + const struct + { + selection_direction_t dir; + size_t sel; + } + cmds[] = + { + /* Tab completion to get into the list */ + {direction_next, 0}, + + /* Westward motion in upper left wraps along the top row */ + {direction_west, 16}, + {direction_east, 1}, + + /* "Next" motion goes down the column */ + {direction_next, 2}, + {direction_next, 3}, + + {direction_west, 18}, + {direction_east, 3}, + {direction_east, 7}, + {direction_east, 11}, + {direction_east, 15}, + {direction_east, 3}, + + {direction_west, 18}, + {direction_east, 3}, + + /* Eastward motion wraps along the bottom, westward goes to the prior column */ + {direction_east, 7}, + {direction_east, 11}, + {direction_east, 15}, + {direction_east, 3}, + + /* Column memory */ + {direction_west, 18}, + {direction_south, 15}, + {direction_north, 18}, + {direction_west, 14}, + {direction_south, 15}, + {direction_north, 14} + }; + for (size_t i=0; i < sizeof cmds / sizeof *cmds; i++) + { + pager.select_next_completion_in_direction(cmds[i].dir, render); + pager.update_rendering(&render); + if (cmds[i].sel != render.selected_completion_idx) + { + err(L"For command %lu, expected selection %lu, but found instead %lu\n", i, cmds[i].sel, render.selected_completion_idx); + } + } + +} + enum word_motion_t { word_motion_left, @@ -2785,6 +2879,7 @@ int main(int argc, char **argv) if (should_test_function("abbreviations")) test_abbreviations(); if (should_test_function("test")) test_test(); if (should_test_function("path")) test_path(); + if (should_test_function("pager_navigation")) test_pager_navigation(); if (should_test_function("word_motion")) test_word_motion(); if (should_test_function("is_potential_path")) test_is_potential_path(); if (should_test_function("colors")) test_colors(); diff --git a/pager.cpp b/pager.cpp index 4093cca01..3d311b858 100644 --- a/pager.cpp +++ b/pager.cpp @@ -320,7 +320,7 @@ line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, s const wcstring &comp = c->comp.at(i); if (i != 0) - written += print_max(L" ", highlight_spec_normal, comp_width - written, true /* has_more */, &line_data); + written += print_max(PAGER_SPACER_STRING, highlight_spec_normal, comp_width - written, true /* has_more */, &line_data); int packed_color = highlight_spec_pager_prefix | highlight_make_background(bg_color); written += print_max(prefix, packed_color, comp_width - written, ! comp.empty(), &line_data); @@ -332,7 +332,7 @@ line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, s if (desc_width) { int packed_color = highlight_spec_pager_description | highlight_make_background(bg_color); - while (written < (width-desc_width-2)) + while (written < (width-desc_width-2)) //the 2 here refers to the parenthesis below { written += print_max(L" ", packed_color, 1, false, &line_data); } @@ -368,7 +368,7 @@ void pager_t::completion_print(int cols, int *width_per_column, int row_start, i size_t rows = (lst.size()-1)/cols+1; - size_t effective_selected_idx = this->saturated_selected_completion_index(); + size_t effective_selected_idx = this->visual_selected_completion_index(rows, cols); for (size_t row = row_start; row < row_stop; row++) { @@ -384,13 +384,12 @@ void pager_t::completion_print(int cols, int *width_per_column, int row_start, i bool is_selected = (idx == effective_selected_idx); /* Print this completion on its own "line" */ - line_t line = completion_print_item(prefix, el, row, col, width_per_column[col] - (is_last?0:2), row%2, is_selected, rendering); + line_t line = completion_print_item(prefix, el, row, col, width_per_column[col] - (is_last ? 0 : PAGER_SPACER_STRING_WIDTH), row%2, is_selected, rendering); /* If there's more to come, append two spaces */ if (col + 1 < cols) { - line.append(L' ', 0); - line.append(L' ', 0); + line.append(PAGER_SPACER_STRING, 0); } /* Append this to the real line */ @@ -660,31 +659,31 @@ int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_i return PAGER_DONE; /* Calculate how wide the list would be */ - for (long j = 0; j < cols; j++) + for (long col = 0; col < cols; col++) { - for (long i = 0; ipref_width; min = c->min_width; - if (j != cols-1) + if (col != cols-1) { pref += 2; min += 2; } - min_width[j] = maxi(min_width[j], + min_width[col] = maxi(min_width[col], min); - pref_width[j] = maxi(pref_width[j], + pref_width[col] = maxi(pref_width[col], pref); } - min_tot_width += min_width[j]; - pref_tot_width += pref_width[j]; + min_tot_width += min_width[col]; + pref_tot_width += pref_width[col]; } /* Force fit if one column @@ -877,17 +876,32 @@ page_rendering_t pager_t::render() const page_rendering_t rendering; rendering.term_width = this->term_width; rendering.term_height = this->term_height; - rendering.selected_completion_idx = this->saturated_selected_completion_index(); if (! this->empty()) { int cols; - bool done = false; - for (cols = PAGER_MAX_COLS; cols > 0 && ! done; cols--) + for (cols = PAGER_MAX_COLS; cols > 0; cols--) { /* Initially empty rendering */ rendering.screen_data.resize(0); + /* Determine how many rows we would need if we had 'cols' columns. Then determine how many columns we want from that. For example, say we had 19 completions. We can fit them into 6 columns, 4 rows, with the last row containing only 1 entry. Or we can fit them into 5 columns, 4 rows, the last row containing 4 entries. Since fewer columns with the same number of rows is better, skip cases where we know we can do better. */ + size_t min_rows_required_for_cols = divide_round_up(completion_infos.size(), cols); + size_t min_cols_required_for_rows = divide_round_up(completion_infos.size(), min_rows_required_for_cols); + + assert(min_cols_required_for_rows <= cols); + if (min_cols_required_for_rows < cols) + { + /* Next iteration will be better, so skip this one */ + continue; + } + + + rendering.cols = (size_t)cols; + rendering.rows = divide_round_up(completion_infos.size(), rendering.cols); + rendering.selected_completion_idx = this->visual_selected_completion_index(rendering.rows, rendering.cols); + + bool done = false; switch (completion_try_print(cols, prefix, completion_infos, &rendering)) { case PAGER_RETRY: @@ -907,17 +921,17 @@ page_rendering_t pager_t::render() const cols=PAGER_MAX_COLS+1; break; } + if (done) + break; } assert(cols >= 0); - rendering.cols = (size_t)cols; - rendering.rows = divide_round_up(completion_infos.size(), rendering.cols); } return rendering; } void pager_t::update_rendering(page_rendering_t *rendering) const { - if (rendering->term_width != this->term_width || rendering->term_height != this->term_height || rendering->selected_completion_idx != this->saturated_selected_completion_index()) + if (rendering->term_width != this->term_width || rendering->term_height != this->term_height || rendering->selected_completion_idx != this->visual_selected_completion_index(rendering->rows, rendering->cols)) { *rendering = this->render(); } @@ -952,7 +966,7 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc { /* Forward/backward do something sane */ selected_completion_idx = (direction == direction_next ? 0 : completions.size() - 1); - return selected_completion(); + return selected_completion(rendering); } } @@ -1028,8 +1042,8 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc case direction_east: { - /* Go east, wrapping to the next row */ - if (current_col + 1 < rendering.cols) + /* Go east, wrapping to the next row. There is no "row memory," so if we run off the end, wrap. */ + if (current_col + 1 < rendering.cols && (current_col + 1) * rendering.rows + current_row < completion_infos.size()) { current_col++; } @@ -1070,7 +1084,7 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc if (new_selected_completion_idx != selected_completion_idx) { selected_completion_idx = new_selected_completion_idx; - return selected_completion(); + return selected_completion(rendering); } else { @@ -1078,12 +1092,16 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc } } -size_t pager_t::saturated_selected_completion_index() const +size_t pager_t::visual_selected_completion_index(size_t rows, size_t cols) const { size_t result = selected_completion_idx; - if (result != PAGER_SELECTION_NONE && result >= completions.size()) + if (result != PAGER_SELECTION_NONE) { - result = completions.size() - 1; + /* If the selected completion is beyond the last selection, go left by columns until it's within it. This is how we implement "column memory." */ + while (result >= completions.size() && result >= rows) + { + result -= rows; + } } assert(result == PAGER_SELECTION_NONE || result < completions.size()); return result; @@ -1094,10 +1112,10 @@ void pager_t::set_selected_completion_index(size_t completion_idx) selected_completion_idx = completion_idx; } -const completion_t *pager_t::selected_completion() const +const completion_t *pager_t::selected_completion(const page_rendering_t &rendering) const { const completion_t * result = NULL; - size_t idx = saturated_selected_completion_index(); + size_t idx = visual_selected_completion_index(rendering.rows, rendering.cols); if (idx != PAGER_SELECTION_NONE) { result = &completions.at(idx); diff --git a/pager.h b/pager.h index b3561d384..31dcffbef 100644 --- a/pager.h +++ b/pager.h @@ -20,6 +20,10 @@ class page_rendering_t page_rendering_t(); }; +/* The space between adjacent completions */ +#define PAGER_SPACER_STRING L" " +#define PAGER_SPACER_STRING_WIDTH 2 + typedef std::vector completion_list_t; page_rendering_t render_completions(const completion_list_t &raw_completions, const wcstring &prefix); @@ -32,8 +36,8 @@ class pager_t size_t selected_completion_idx; - /* Returns the selected completion index, but not to exceed completions.size() */ - size_t saturated_selected_completion_index() const; + /* Returns the index of the completion that should draw selected, using the given number of columns */ + size_t visual_selected_completion_index(size_t rows, size_t cols) const; /** Data structure describing one or a group of related completions */ public: @@ -94,8 +98,8 @@ class pager_t /* Changes the selected completion in the given direction according to the layout of the given rendering. Returns true if the values changed. */ const completion_t *select_next_completion_in_direction(selection_direction_t direction, const page_rendering_t &rendering); - /* Returns the currently selected completion */ - const completion_t *selected_completion() const; + /* Returns the currently selected completion for the given rendering */ + const completion_t *selected_completion(const page_rendering_t &rendering) const; /* Produces a rendering of the completions, at the given term size */ page_rendering_t render() const; diff --git a/reader.cpp b/reader.cpp index f01c9207c..7e86267f9 100644 --- a/reader.cpp +++ b/reader.cpp @@ -1527,7 +1527,7 @@ static void accept_autosuggestion(bool full) static bool is_navigating_pager_contents() { - return data && data->current_pager.selected_completion() != NULL; + return data && data->current_pager.selected_completion(data->current_page_rendering) != NULL; } static void select_completion_in_direction(enum selection_direction_t dir, const wcstring &cycle_command_line, size_t cycle_cursor_pos) diff --git a/screen.h b/screen.h index cfc12ba36..33eb58ab5 100644 --- a/screen.h +++ b/screen.h @@ -42,6 +42,17 @@ struct line_t text.push_back(txt); colors.push_back(color); } + + void append(const wchar_t *txt, highlight_spec_t color) + { + for (size_t i=0; txt[i]; i++) + { + text.push_back(txt[i]); + colors.push_back(color); + } + } + + size_t size(void) const { From 998ce1fe89800994fb378e5f2c5554dcab6782fb Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 19 Jan 2014 23:52:35 -0800 Subject: [PATCH 11/21] Support for correctly resizing pager contents. --- pager.cpp | 5 ++-- reader.cpp | 84 +++++++++++++++++++++++++----------------------------- 2 files changed, 42 insertions(+), 47 deletions(-) diff --git a/pager.cpp b/pager.cpp index 3d311b858..9b1676e90 100644 --- a/pager.cpp +++ b/pager.cpp @@ -937,7 +937,7 @@ void pager_t::update_rendering(page_rendering_t *rendering) const } } -pager_t::pager_t() : term_width(0), term_height(0), selected_completion_idx(-1) +pager_t::pager_t() : term_width(0), term_height(0), selected_completion_idx(PAGER_SELECTION_NONE) { } @@ -978,7 +978,7 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc if (direction == direction_next) { new_selected_completion_idx = selected_completion_idx + 1; - if (new_selected_completion_idx > completion_infos.size()) + if (new_selected_completion_idx >= completion_infos.size()) { new_selected_completion_idx = 0; } @@ -1128,6 +1128,7 @@ void pager_t::clear() completions.clear(); completion_infos.clear(); prefix.clear(); + selected_completion_idx = PAGER_SELECTION_NONE; } page_rendering_t::page_rendering_t() : term_width(-1), term_height(-1), rows(0), cols(0), selected_completion_idx(-1) diff --git a/reader.cpp b/reader.cpp index 7e86267f9..3df18ca9a 100644 --- a/reader.cpp +++ b/reader.cpp @@ -199,7 +199,7 @@ public: wcstring autosuggestion; /** Current pager */ - pager_t current_pager; + pager_t pager; /** Current page rendering */ page_rendering_t current_page_rendering; @@ -543,8 +543,8 @@ static void reader_repaint() indents.resize(len); // Re-render our completions page if necessary - data->current_pager.set_term_size(common_get_width(), common_get_height()); - data->current_pager.update_rendering(&data->current_page_rendering); + data->pager.set_term_size(common_get_width(), common_get_height()); + data->pager.update_rendering(&data->current_page_rendering); s_write(&data->screen, data->left_prompt_buff, @@ -1527,12 +1527,22 @@ static void accept_autosuggestion(bool full) static bool is_navigating_pager_contents() { - return data && data->current_pager.selected_completion(data->current_page_rendering) != NULL; + return data && data->pager.selected_completion(data->current_page_rendering) != NULL; +} + +/* Ensure we have no pager contents */ +static void clear_pager() +{ + if (data) + { + data->pager.clear(); + data->current_page_rendering = page_rendering_t(); + } } static void select_completion_in_direction(enum selection_direction_t dir, const wcstring &cycle_command_line, size_t cycle_cursor_pos) { - const completion_t *next_comp = data->current_pager.select_next_completion_in_direction(dir, data->current_page_rendering); + const completion_t *next_comp = data->pager.select_next_completion_in_direction(dir, data->current_page_rendering); if (next_comp != NULL) { size_t cursor_pos = cycle_cursor_pos; @@ -1665,40 +1675,6 @@ static void prioritize_completions(std::vector &comp) sort(comp.begin(), comp.end(), compare_completions_by_match_type); } -/* Given a list of completions, get the completion at an index past *inout_idx, and then increment it. inout_idx should be initialized to (size_t)(-1) for the first call. */ -static const completion_t *cycle_competions(const std::vector &comp, const wcstring &command_line, size_t *inout_idx) -{ - const size_t size = comp.size(); - if (size == 0) - return NULL; - - // note start_idx will be set to -1 initially, so that when it gets incremented we start at 0 - const size_t start_idx = *inout_idx; - size_t idx = start_idx; - - const completion_t *result = NULL; - size_t remaining = comp.size(); - while (remaining--) - { - /* Bump the index */ - idx = (idx + 1) % size; - - /* Get the completion */ - const completion_t &c = comp.at(idx); - - /* Try this completion */ - if (!(c.flags & COMPLETE_REPLACES_TOKEN) || reader_can_replace(command_line, c.flags)) - { - /* Success */ - result = &c; - break; - } - } - - *inout_idx = idx; - return result; -} - /** Handle the list of completions. This means the following: @@ -1887,8 +1863,8 @@ static bool handle_completions(const std::vector &comp) if (1) { - data->current_pager.set_prefix(prefix); - data->current_pager.set_completions(surviving_completions); + data->pager.set_prefix(prefix); + data->pager.set_completions(surviving_completions); /* Invalidate our rendering */ data->current_page_rendering = page_rendering_t(); @@ -3082,8 +3058,26 @@ const wchar_t *reader_readline(void) if (last_char != R_YANK && last_char != R_YANK_POP) yank_len=0; + + /* We clear pager contents for most events, except for a few */ + switch (c) + { + case R_COMPLETE: + case R_BACKWARD_CHAR: + case R_FORWARD_CHAR: + case R_UP_LINE: + case R_DOWN_LINE: + case R_NULL: + case R_REPAINT: + case R_SUPPRESS_AUTOSUGGESTION: + break; + + default: + clear_pager(); + break; + } - const wchar_t *buff = data->command_line.c_str(); + const wchar_t * const buff = data->command_line.c_str(); switch (c) { @@ -3170,7 +3164,7 @@ const wchar_t *reader_readline(void) if (!data->complete_func) break; - if (! comp_empty && last_char == R_COMPLETE) + if (is_navigating_pager_contents() || (! comp_empty && last_char == R_COMPLETE)) { /* The user typed R_COMPLETE more than once in a row. Cycle through our available completions. */ select_completion_in_direction(direction_next, cycle_command_line, cycle_cursor_pos); @@ -3852,11 +3846,11 @@ const wchar_t *reader_readline(void) writestr(L"\n"); /* Ensure we have no pager contents when we exit */ - if (! data->current_pager.empty()) + if (! data->pager.empty()) { /* Clear to end of screen to erase the pager contents. TODO: this may fail if eos doesn't exist, in which case we should emit newlines */ screen_force_clear_to_end(); - data->current_pager.clear(); + data->pager.clear(); } if (!reader_exit_forced()) From 605c306bef3de7c2894130d856c837ca82905549 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 20 Jan 2014 12:38:56 -0800 Subject: [PATCH 12/21] Correctly clear pager contents on ctrl-C --- reader.cpp | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/reader.cpp b/reader.cpp index 3df18ca9a..14d480a18 100644 --- a/reader.cpp +++ b/reader.cpp @@ -362,6 +362,9 @@ public: } }; +/* Sets the command line contents, without clearing the pager */ +static void reader_set_buffer_maintaining_pager(const wcstring &b, size_t pos); + /** The current interactive reading context */ @@ -1211,7 +1214,7 @@ static void completion_insert(const wchar_t *val, complete_flags_t flags) { size_t cursor = data->buff_pos; wcstring new_command_line = completion_apply_to_command_line(val, flags, data->command_line, &cursor, false /* not append only */); - reader_set_buffer(new_command_line, cursor); + reader_set_buffer_maintaining_pager(new_command_line, cursor); /* Since we just inserted a completion, don't immediately do a new autosuggestion */ data->suppress_autosuggestion = true; @@ -1547,7 +1550,7 @@ static void select_completion_in_direction(enum selection_direction_t dir, const { size_t cursor_pos = cycle_cursor_pos; const wcstring new_cmd_line = completion_apply_to_command_line(next_comp->completion, next_comp->flags, cycle_command_line, &cursor_pos, false); - reader_set_buffer(new_cmd_line, cursor_pos); + reader_set_buffer_maintaining_pager(new_cmd_line, cursor_pos); /* Since we just inserted a completion, don't immediately do a new autosuggestion */ data->suppress_autosuggestion = true; @@ -2376,11 +2379,9 @@ history_t *reader_get_history(void) return data ? data->history : NULL; } -void reader_set_buffer(const wcstring &b, size_t pos) +/* Sets the command line contents, without clearing the pager */ +static void reader_set_buffer_maintaining_pager(const wcstring &b, size_t pos) { - if (!data) - return; - /* Callers like to pass us pointers into ourselves, so be careful! I don't know if we can use operator= with a pointer to our interior, so use an intermediate. */ size_t command_line_len = b.size(); data->command_line = b; @@ -2391,15 +2392,26 @@ void reader_set_buffer(const wcstring &b, size_t pos) pos = command_line_len; data->buff_pos = pos; - + + /* Clear history search and pager contents */ data->search_mode = NO_SEARCH; data->search_buff.clear(); data->history_search.go_to_end(); - + reader_super_highlight_me_plenty(data->buff_pos); reader_repaint_needed(); } +/* Sets the command line contents, clearing the pager */ +void reader_set_buffer(const wcstring &b, size_t pos) +{ + if (!data) + return; + + clear_pager(); + reader_set_buffer_maintaining_pager(b, pos); +} + size_t reader_get_cursor_pos() { From f714d80c93f25da00e942f1dd8625529090c3193 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Tue, 21 Jan 2014 14:35:18 -0800 Subject: [PATCH 13/21] Support for scrolling through the pager --- pager.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++----------- pager.h | 9 ++++-- reader.cpp | 2 +- 3 files changed, 78 insertions(+), 22 deletions(-) diff --git a/pager.cpp b/pager.cpp index 9b1676e90..0960fe507 100644 --- a/pager.cpp +++ b/pager.cpp @@ -363,8 +363,13 @@ line_t pager_t::completion_print_item(const wcstring &prefix, const comp_t *c, s \param prefix The string to print before each completion */ -void pager_t::completion_print(int cols, int *width_per_column, int row_start, int row_stop, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const +void pager_t::completion_print(size_t cols, int *width_per_column, size_t row_start, size_t row_stop, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const { + /* Teach the rendering about the rows it printed */ + assert(row_start >= 0); + assert(row_stop >= row_start); + rendering->row_start = row_start; + rendering->row_end = row_stop; size_t rows = (lst.size()-1)/cols+1; @@ -393,7 +398,7 @@ void pager_t::completion_print(int cols, int *width_per_column, int row_start, i } /* Append this to the real line */ - rendering->screen_data.create_line(row).append_line(line); + rendering->screen_data.create_line(row - row_start).append_line(line); } } } @@ -629,7 +634,7 @@ void pager_t::set_term_size(int w, int h) \return one of PAGER_RETRY, PAGER_DONE and PAGER_RESIZE */ -int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const +int pager_t::completion_try_print(size_t cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering, size_t suggested_start_row) const { /* The calculated preferred width of each column @@ -648,7 +653,7 @@ int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_i */ int print=0; - int rows = (int)divide_round_up(lst.size(), cols); + size_t row_count = divide_round_up(lst.size(), cols); int pref_tot_width=0; int min_tot_width = 0; @@ -661,14 +666,14 @@ int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_i /* Calculate how wide the list would be */ for (long col = 0; col < cols; col++) { - for (long row = 0; rowpref_width; min = c->min_width; @@ -714,7 +719,7 @@ int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_i pref_tot_width-term_width ); */ if (min_tot_width < term_width && - (((rows < term_height) && (next_rows >= term_height)) || + (((row_count < term_height) && (next_rows >= term_height)) || (pref_tot_width-term_width< 4 && cols < 3))) { /* @@ -754,13 +759,39 @@ int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_i if (print) { - res=PAGER_DONE; - if (rows < term_height) + /* Determine the starting and stop row */ + size_t start_row = 0, stop_row = 0; + if (row_count <= term_height) { - completion_print(cols, width, 0, rows, prefix, lst, rendering); + /* Easy, we can show everything */ + start_row = 0; + stop_row = row_count; } else { + /* We can only show part of the full list. Determine which part based on the suggested_start_row */ + assert(row_count > term_height); + size_t last_starting_row = row_count - term_height; + start_row = mini(suggested_start_row, last_starting_row); + stop_row = start_row + term_height; + assert(start_row >= 0 && start_row <= last_starting_row); + } + + assert(stop_row >= start_row); + assert(stop_row <= row_count); + assert(stop_row - start_row <= term_height); + completion_print(cols, width, start_row, stop_row, prefix, lst, rendering); + return PAGER_DONE; + + res=PAGER_DONE; + if (row_count < term_height) + { + completion_print(cols, width, 0, row_count, prefix, lst, rendering); + } + else + { + completion_print(cols, width, 0, term_height - 1, prefix, lst, rendering); + assert(0); int npos, pos = 0; int do_loop = 1; @@ -771,7 +802,7 @@ int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_i while (do_loop) { set_color(rgb_color_t::black(), highlight_get_color(highlight_spec_pager_progress, true)); - wcstring msg = format_string(_(L" %d to %d of %d"), pos, pos+term_height-1, rows); + wcstring msg = format_string(_(L" %d to %d of %d"), pos, pos+term_height-1, row_count); msg.append(L" \r"); writestr(msg.c_str()); @@ -798,7 +829,7 @@ int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_i case LINE_DOWN: { - if (pos <= (rows - term_height)) + if (pos <= (row_count - term_height)) { pos++; completion_print(cols, width, pos+term_height-2, pos+term_height-1, prefix, lst, rendering); @@ -809,7 +840,7 @@ int pager_t::completion_try_print(int cols, const wcstring &prefix, const comp_i case PAGE_DOWN: { - npos = mini((int)(rows - term_height+1), (int)(pos + term_height-1)); + npos = mini((int)(row_count - term_height+1), (int)(pos + term_height-1)); if (npos != pos) { pos = npos; @@ -902,7 +933,7 @@ page_rendering_t pager_t::render() const rendering.selected_completion_idx = this->visual_selected_completion_index(rendering.rows, rendering.cols); bool done = false; - switch (completion_try_print(cols, prefix, completion_infos, &rendering)) + switch (completion_try_print(cols, prefix, completion_infos, &rendering, suggested_row_start)) { case PAGER_RETRY: break; @@ -937,7 +968,7 @@ void pager_t::update_rendering(page_rendering_t *rendering) const } } -pager_t::pager_t() : term_width(0), term_height(0), selected_completion_idx(PAGER_SELECTION_NONE) +pager_t::pager_t() : term_width(0), term_height(0), selected_completion_idx(PAGER_SELECTION_NONE), suggested_row_start(0) { } @@ -1001,7 +1032,7 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc } else { - /* Cardinal directions. We have a completion index; we wish to compute its row and column. Completions are rendered column first, i.e. we go south before we go west. */ + /* Cardinal directions. We have a completion index; we wish to compute its row and column. Completions are rendered column first, i.e. we go south before we go west. So if we have N rows, and our selected index is N + 2, then our row is 2 (mod by N) and our column is 1 (divide by N) */ size_t current_row = selected_completion_idx % rendering.rows; size_t current_col = selected_completion_idx / rendering.rows; @@ -1084,6 +1115,28 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc if (new_selected_completion_idx != selected_completion_idx) { selected_completion_idx = new_selected_completion_idx; + + /* Update suggested_row_start to ensure the selection is visible. suggested_row_start * rendering.cols is the first suggested visible completion; add the visible completion count to that to get the last one */ + size_t visible_row_count = rendering.row_end - rendering.row_start; + + if (visible_row_count > 0 && selected_completion_idx != PAGER_SELECTION_NONE) //paranoia + { + size_t row_containing_selection = selected_completion_idx % rendering.rows; + + /* Ensure our suggested row start is not past the selected row */ + if (suggested_row_start > row_containing_selection) + { + suggested_row_start = row_containing_selection; + } + + /* Ensure our suggested row start is not too early before it */ + if (suggested_row_start + visible_row_count <= row_containing_selection) + { + suggested_row_start = row_containing_selection - visible_row_count + 1; + } + } + + return selected_completion(rendering); } else @@ -1131,6 +1184,6 @@ void pager_t::clear() selected_completion_idx = PAGER_SELECTION_NONE; } -page_rendering_t::page_rendering_t() : term_width(-1), term_height(-1), rows(0), cols(0), selected_completion_idx(-1) +page_rendering_t::page_rendering_t() : term_width(-1), term_height(-1), rows(0), cols(0), row_start(0), row_end(0), selected_completion_idx(-1) { } diff --git a/pager.h b/pager.h index 31dcffbef..9a3c022ca 100644 --- a/pager.h +++ b/pager.h @@ -13,6 +13,8 @@ class page_rendering_t int term_height; size_t rows; size_t cols; + size_t row_start; + size_t row_end; size_t selected_completion_idx; screen_data_t screen_data; @@ -35,6 +37,7 @@ class pager_t completion_list_t completions; size_t selected_completion_idx; + size_t suggested_row_start; /* Returns the index of the completion that should draw selected, using the given number of columns */ size_t visual_selected_completion_index(size_t rows, size_t cols) const; @@ -72,12 +75,12 @@ class pager_t wcstring prefix; - int completion_try_print(int cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const; + int completion_try_print(size_t cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering, size_t suggested_start_row) const; void recalc_min_widths(comp_info_list_t * lst) const; void measure_completion_infos(std::vector *infos, const wcstring &prefix) const; - void completion_print(int cols, int *width_per_column, int row_start, int row_stop, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const; + void completion_print(size_t cols, int *width_per_column, size_t row_start, size_t row_stop, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering) const; line_t completion_print_item(const wcstring &prefix, const comp_t *c, size_t row, size_t column, int width, bool secondary, bool selected, page_rendering_t *rendering) const; @@ -95,7 +98,7 @@ class pager_t /* Sets the index of the selected completion */ void set_selected_completion_index(size_t completion_idx); - /* Changes the selected completion in the given direction according to the layout of the given rendering. Returns true if the values changed. */ + /* Changes the selected completion in the given direction according to the layout of the given rendering. Returns the newly selected completion if it changed, NULL if nothing was selected or it did not change. */ const completion_t *select_next_completion_in_direction(selection_direction_t direction, const page_rendering_t &rendering); /* Returns the currently selected completion for the given rendering */ diff --git a/reader.cpp b/reader.cpp index 14d480a18..76c5490a6 100644 --- a/reader.cpp +++ b/reader.cpp @@ -546,7 +546,7 @@ static void reader_repaint() indents.resize(len); // Re-render our completions page if necessary - data->pager.set_term_size(common_get_width(), common_get_height()); + data->pager.set_term_size(common_get_width(), 8 /* common_get_height() */); data->pager.update_rendering(&data->current_page_rendering); s_write(&data->screen, From ee9a4082b6a313dc15fcb24801cc54937777c662 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Tue, 21 Jan 2014 16:25:55 -0800 Subject: [PATCH 14/21] Allow down arrow to enter pager. Clean up repainting within reader_readline --- pager.cpp | 30 +++++++++++++++++++++--------- reader.cpp | 37 +++++++++++++++++++------------------ 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/pager.cpp b/pager.cpp index 0960fe507..b1fc92265 100644 --- a/pager.cpp +++ b/pager.cpp @@ -988,16 +988,28 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc /* Handle the case of nothing selected yet */ if (selected_completion_idx == PAGER_SELECTION_NONE) { - if (selection_direction_is_cardinal(direction)) + switch (direction) { - /* Cardinal directions do nothing unless something is selected */ - return NULL; - } - else - { - /* Forward/backward do something sane */ - selected_completion_idx = (direction == direction_next ? 0 : completions.size() - 1); - return selected_completion(rendering); + /* These directions do something sane */ + case direction_south: + case direction_next: + case direction_prev: + if (direction == direction_prev) + { + selected_completion_idx = completions.size() - 1; + } + else + { + selected_completion_idx = 0; + } + return selected_completion(rendering); + + /* These do nothing */ + case direction_north: + case direction_east: + case direction_west: + default: + return NULL; } } diff --git a/reader.cpp b/reader.cpp index 76c5490a6..fe06a386e 100644 --- a/reader.cpp +++ b/reader.cpp @@ -1540,6 +1540,7 @@ static void clear_pager() { data->pager.clear(); data->current_page_rendering = page_rendering_t(); + reader_repaint_needed(); } } @@ -3088,6 +3089,8 @@ const wchar_t *reader_readline(void) clear_pager(); break; } + + //fprintf(stderr, "\n\nchar: %ls\n\n", describe_char(c).c_str()); const wchar_t * const buff = data->command_line.c_str(); switch (c) @@ -3102,7 +3105,7 @@ const wchar_t *reader_readline(void) data->buff_pos--; } - reader_repaint(); + reader_repaint_needed(); break; } @@ -3121,7 +3124,7 @@ const wchar_t *reader_readline(void) accept_autosuggestion(true); } - reader_repaint(); + reader_repaint_needed(); break; } @@ -3130,7 +3133,7 @@ const wchar_t *reader_readline(void) { data->buff_pos = 0; - reader_repaint(); + reader_repaint_needed(); break; } @@ -3139,13 +3142,12 @@ const wchar_t *reader_readline(void) { data->buff_pos = data->command_length(); - reader_repaint(); + reader_repaint_needed(); break; } case R_NULL: { - reader_repaint_if_needed(); break; } @@ -3236,9 +3238,6 @@ const wchar_t *reader_readline(void) /* Start the cycle at the beginning */ completion_cycle_idx = (size_t)(-1); - - /* Repaint */ - reader_repaint_if_needed(); } break; @@ -3370,8 +3369,7 @@ const wchar_t *reader_readline(void) } data->search_buff.clear(); reader_super_highlight_me_plenty(data->buff_pos); - reader_repaint(); - + reader_repaint_needed(); } break; @@ -3445,7 +3443,7 @@ const wchar_t *reader_readline(void) } finished=1; data->buff_pos=data->command_length(); - reader_repaint(); + reader_repaint_needed(); break; } @@ -3466,7 +3464,7 @@ const wchar_t *reader_readline(void) default: { s_reset(&data->screen, screen_reset_abandon_line); - reader_repaint(); + reader_repaint_needed(); break; } @@ -3570,7 +3568,7 @@ const wchar_t *reader_readline(void) else if (data->buff_pos > 0) { data->buff_pos--; - reader_repaint(); + reader_repaint_needed(); } break; } @@ -3585,7 +3583,7 @@ const wchar_t *reader_readline(void) else if (data->buff_pos < data->command_length()) { data->buff_pos++; - reader_repaint(); + reader_repaint_needed(); } else { @@ -3654,7 +3652,8 @@ const wchar_t *reader_readline(void) case R_UP_LINE: case R_DOWN_LINE: { - if (is_navigating_pager_contents()) + /* If we are already navigating the pager, or if we pressed down with non-empty pager contents, begin navigation */ + if (is_navigating_pager_contents() || (c == R_DOWN_LINE && ! data->pager.empty())) { select_completion_in_direction(c == R_UP_LINE ? direction_north : direction_south, cycle_command_line, cycle_cursor_pos); } @@ -3692,7 +3691,7 @@ const wchar_t *reader_readline(void) line_offset_old = data->buff_pos - parse_util_get_offset_from_line(data->command_line, line_old); total_offset_new = parse_util_get_offset(data->command_line, line_new, line_offset_old - 4*(indent_new-indent_old)); data->buff_pos = total_offset_new; - reader_repaint(); + reader_repaint_needed(); } } @@ -3703,7 +3702,7 @@ const wchar_t *reader_readline(void) { data->suppress_autosuggestion = true; data->autosuggestion.clear(); - reader_repaint(); + reader_repaint_needed(); break; } @@ -3811,7 +3810,7 @@ const wchar_t *reader_readline(void) } data->command_line_changed(); reader_super_highlight_me_plenty(data->buff_pos); - reader_repaint(); + reader_repaint_needed(); break; } @@ -3853,6 +3852,8 @@ const wchar_t *reader_readline(void) } last_char = c; + + reader_repaint_if_needed(); } writestr(L"\n"); From 7d3f808e4c48d3cb54cb603b94901eaa6078b70c Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Wed, 22 Jan 2014 17:45:27 -0800 Subject: [PATCH 15/21] Various tweaks and improvements to new pager, including disclosure feature --- highlight.cpp | 9 ++++--- highlight.h | 3 ++- pager.cpp | 74 ++++++++++++++++++++++++++++++++++++++++++--------- pager.h | 12 +++++++-- reader.cpp | 3 ++- 5 files changed, 82 insertions(+), 19 deletions(-) diff --git a/highlight.cpp b/highlight.cpp index 31ea2c8e0..f2ae460c0 100644 --- a/highlight.cpp +++ b/highlight.cpp @@ -361,7 +361,10 @@ bool plain_statement_get_expanded_command(const wcstring &src, const parse_node_ rgb_color_t highlight_get_color(highlight_spec_t highlight, bool is_background) { rgb_color_t result = rgb_color_t::normal(); - + + /* If sloppy_background is set, then we look at the foreground color even if is_background is set */ + bool treat_as_background = is_background && ! (highlight & highlight_modifier_sloppy_background); + /* Get the primary variable */ size_t idx = highlight_get_primary(highlight); if (idx >= VAR_COUNT) @@ -377,9 +380,9 @@ rgb_color_t highlight_get_color(highlight_spec_t highlight, bool is_background) val_wstr = env_get_string(highlight_var[0]); if (! val_wstr.missing()) - result = parse_color(val_wstr, is_background); + result = parse_color(val_wstr, treat_as_background); - /* Handle modifiers. Just one for now */ + /* Handle modifiers. */ if (highlight & highlight_modifier_valid_path) { env_var_t val2_wstr = env_get_string(L"fish_color_valid_path"); diff --git a/highlight.h b/highlight.h index 9b191a78b..dfe3f9310 100644 --- a/highlight.h +++ b/highlight.h @@ -40,6 +40,7 @@ enum /* The following values are modifiers */ highlight_modifier_valid_path = 0x100, + highlight_modifier_sloppy_background = 0x200, //hackish, indicates that we should treat a foreground color as background, per certain historical behavior /* Very special value */ highlight_spec_invalid = 0xFFFF @@ -54,7 +55,7 @@ inline highlight_spec_t highlight_get_primary(highlight_spec_t val) inline highlight_spec_t highlight_make_background(highlight_spec_t val) { - assert(val >> 16 == 0); + assert(val >> 16 == 0); //should have nothing in upper bits, otherwise this is already a background return val << 16; } diff --git a/pager.cpp b/pager.cpp index b1fc92265..b08e8359e 100644 --- a/pager.cpp +++ b/pager.cpp @@ -142,8 +142,8 @@ void pager_t::recalc_min_widths(comp_info_list_t * lst) const { comp_t *c = &lst->at(i); - c->min_width = mini(c->desc_width, maxi(0, term_width/3 - 2)) + - mini(c->desc_width, maxi(0, term_width/5 - 4)) +4; + c->min_width = mini(c->desc_width, maxi(0, available_term_width/3 - 2)) + + mini(c->desc_width, maxi(0, available_term_width/5 - 4)) +4; } } @@ -613,8 +613,8 @@ void pager_t::set_term_size(int w, int h) { assert(w > 0); assert(h > 0); - term_width = w; - term_height = h; + available_term_width = w; + available_term_height = h; } /** @@ -652,8 +652,24 @@ int pager_t::completion_try_print(size_t cols, const wcstring &prefix, const com Set to one if the list should be printed at this width */ int print=0; + + /* Compute the effective term width and term height, accounting for disclosure */ + int term_width = this->available_term_width; + int term_height = this->available_term_height - 1; // we always subtract 1 to make room for a comment row + if (! this->fully_disclosed) + term_height = mini(term_height, PAGER_UNDISCLOSED_MAX_ROWS); size_t row_count = divide_round_up(lst.size(), cols); + + /* We have more to disclose if we are not fully disclosed and there's more rows than we have in our term height */ + if (! this->fully_disclosed && row_count > term_height) + { + rendering->remaining_to_disclose = row_count - term_height; + } + else + { + rendering->remaining_to_disclose = 0; + } int pref_tot_width=0; int min_tot_width = 0; @@ -781,6 +797,26 @@ int pager_t::completion_try_print(size_t cols, const wcstring &prefix, const com assert(stop_row <= row_count); assert(stop_row - start_row <= term_height); completion_print(cols, width, start_row, stop_row, prefix, lst, rendering); + + /* Add the progress line. It's a "more to disclose" line if necessary. */ + wcstring progress_text; + if (rendering->remaining_to_disclose == 1) + { + /* I don't expect this case to ever happen */ + progress_text = L"and 1 more row"; + } + else if (rendering->remaining_to_disclose > 1) + { + progress_text = format_string(L"and %lu more rows", (unsigned long)rendering->remaining_to_disclose); + } + else + { + /* The +1 here is because we are zero indexed, but want to present things as 1-indexed. We do not add 1 to stop_row or row_count because these are the "past the last value" */ + progress_text = format_string(L"rows %lu to %lu of %lu", start_row + 1, stop_row, row_count); + } + line_t &line = rendering->screen_data.add_line(); + print_max(progress_text.c_str(), highlight_spec_pager_progress | highlight_make_background(highlight_spec_pager_progress), term_width, true /* has_more */, &line); + return PAGER_DONE; res=PAGER_DONE; @@ -905,8 +941,8 @@ page_rendering_t pager_t::render() const column never fails. */ page_rendering_t rendering; - rendering.term_width = this->term_width; - rendering.term_height = this->term_height; + rendering.term_width = this->available_term_width; + rendering.term_height = this->available_term_height; if (! this->empty()) { @@ -926,10 +962,9 @@ page_rendering_t pager_t::render() const /* Next iteration will be better, so skip this one */ continue; } - rendering.cols = (size_t)cols; - rendering.rows = divide_round_up(completion_infos.size(), rendering.cols); + rendering.rows = min_rows_required_for_cols; rendering.selected_completion_idx = this->visual_selected_completion_index(rendering.rows, rendering.cols); bool done = false; @@ -962,13 +997,13 @@ page_rendering_t pager_t::render() const void pager_t::update_rendering(page_rendering_t *rendering) const { - if (rendering->term_width != this->term_width || rendering->term_height != this->term_height || rendering->selected_completion_idx != this->visual_selected_completion_index(rendering->rows, rendering->cols)) + if (rendering->term_width != this->available_term_width || rendering->term_height != this->available_term_height || rendering->selected_completion_idx != this->visual_selected_completion_index(rendering->rows, rendering->cols)) { *rendering = this->render(); } } -pager_t::pager_t() : term_width(0), term_height(0), selected_completion_idx(PAGER_SELECTION_NONE), suggested_row_start(0) +pager_t::pager_t() : available_term_width(0), available_term_height(0), selected_completion_idx(PAGER_SELECTION_NONE), suggested_row_start(0), fully_disclosed(false) { } @@ -1135,6 +1170,7 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc { size_t row_containing_selection = selected_completion_idx % rendering.rows; + /* Ensure our suggested row start is not past the selected row */ if (suggested_row_start > row_containing_selection) { @@ -1144,7 +1180,20 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc /* Ensure our suggested row start is not too early before it */ if (suggested_row_start + visible_row_count <= row_containing_selection) { - suggested_row_start = row_containing_selection - visible_row_count + 1; + /* The user moved south past the bottom completion */ + if (! fully_disclosed && rendering.remaining_to_disclose > 0) + { + /* Perform disclosure */ + fully_disclosed = true; + } + else + { + /* Scroll */ + suggested_row_start = row_containing_selection - visible_row_count + 1; + + /* Ensure fully_disclosed is set. I think we can hit this case if the user resizes the window - we don't want to drop back to the disclosed style */ + fully_disclosed = true; + } } } @@ -1194,8 +1243,9 @@ void pager_t::clear() completion_infos.clear(); prefix.clear(); selected_completion_idx = PAGER_SELECTION_NONE; + fully_disclosed = false; } -page_rendering_t::page_rendering_t() : term_width(-1), term_height(-1), rows(0), cols(0), row_start(0), row_end(0), selected_completion_idx(-1) +page_rendering_t::page_rendering_t() : term_width(-1), term_height(-1), rows(0), cols(0), row_start(0), row_end(0), selected_completion_idx(-1), remaining_to_disclose(0) { } diff --git a/pager.h b/pager.h index 9a3c022ca..df2d00d26 100644 --- a/pager.h +++ b/pager.h @@ -18,6 +18,8 @@ class page_rendering_t size_t selected_completion_idx; screen_data_t screen_data; + size_t remaining_to_disclose; + /* Returns a rendering with invalid data, useful to indicate "no rendering" */ page_rendering_t(); }; @@ -26,19 +28,25 @@ class page_rendering_t #define PAGER_SPACER_STRING L" " #define PAGER_SPACER_STRING_WIDTH 2 +/* How many rows we will show in the "initial" pager */ +#define PAGER_UNDISCLOSED_MAX_ROWS 4 + typedef std::vector completion_list_t; page_rendering_t render_completions(const completion_list_t &raw_completions, const wcstring &prefix); class pager_t { - int term_width; - int term_height; + int available_term_width; + int available_term_height; completion_list_t completions; size_t selected_completion_idx; size_t suggested_row_start; + /* Fully disclosed means that we show all completions */ + bool fully_disclosed; + /* Returns the index of the completion that should draw selected, using the given number of columns */ size_t visual_selected_completion_index(size_t rows, size_t cols) const; diff --git a/reader.cpp b/reader.cpp index fe06a386e..d1ad4bf4e 100644 --- a/reader.cpp +++ b/reader.cpp @@ -546,7 +546,8 @@ static void reader_repaint() indents.resize(len); // Re-render our completions page if necessary - data->pager.set_term_size(common_get_width(), 8 /* common_get_height() */); + // We set the term size to 1 less than the true term height. This means we will always show the (bottom) line of the prompt. + data->pager.set_term_size(maxi(1, common_get_width()), maxi(1, common_get_height() - 1)); data->pager.update_rendering(&data->current_page_rendering); s_write(&data->screen, From 0f2ee308deefcc4e796bece6deae1ad383a22427 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Wed, 22 Jan 2014 17:50:03 -0800 Subject: [PATCH 16/21] Use ellipsis in pager progress message --- pager.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pager.cpp b/pager.cpp index b08e8359e..40a5c9dd6 100644 --- a/pager.cpp +++ b/pager.cpp @@ -798,16 +798,19 @@ int pager_t::completion_try_print(size_t cols, const wcstring &prefix, const com assert(stop_row - start_row <= term_height); completion_print(cols, width, start_row, stop_row, prefix, lst, rendering); + /* Ellipsis helper string. Either empty or containing the ellipsis char */ + const wchar_t ellipsis_string[] = {ellipsis_char == L'\x2026' ? L'\x2026' : L'\0', L'\0'}; + /* Add the progress line. It's a "more to disclose" line if necessary. */ wcstring progress_text; if (rendering->remaining_to_disclose == 1) { /* I don't expect this case to ever happen */ - progress_text = L"and 1 more row"; + progress_text = format_string(L"%lsand 1 more row", ellipsis_string); } else if (rendering->remaining_to_disclose > 1) { - progress_text = format_string(L"and %lu more rows", (unsigned long)rendering->remaining_to_disclose); + progress_text = format_string(L"%lsand %lu more rows", ellipsis_string, (unsigned long)rendering->remaining_to_disclose); } else { From 0a1960865ed36e1e07615f72267b939459edebd4 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 24 Jan 2014 15:59:18 -0800 Subject: [PATCH 17/21] Support for "merged completions" (multiple completions on the same line) in new pager. Support for using up-arrow to end pager navigation. --- common.h | 5 ++++- pager.cpp | 53 ++++++++++++++++++++++++++++++++--------------------- pager.h | 16 +++++++++------- reader.cpp | 45 +++++++++++++++++++++++++++++++++++++++------ 4 files changed, 84 insertions(+), 35 deletions(-) diff --git a/common.h b/common.h index 2a96b7498..3e9be1063 100644 --- a/common.h +++ b/common.h @@ -98,7 +98,10 @@ enum selection_direction_t /* logical directions */ direction_next, - direction_prev + direction_prev, + + /* special value that means deselect */ + direction_deselect }; inline bool selection_direction_is_cardinal(selection_direction_t dir) diff --git a/pager.cpp b/pager.cpp index 40a5c9dd6..bd9ed15b6 100644 --- a/pager.cpp +++ b/pager.cpp @@ -501,6 +501,9 @@ static comp_info_list_t process_completions_into_infos(const completion_list_t & // Append the mangled description comp_info->desc = comp.description; mangle_1_completion_description(&comp_info->desc); + + // Set the representative completion + comp_info->representative = comp; } return result; } @@ -591,8 +594,6 @@ page_rendering_t render_completions(const completion_list_t &raw_completions, co void pager_t::set_completions(const completion_list_t &raw_completions) { - completions = raw_completions; - // Get completion infos out of it completion_infos = process_completions_into_infos(raw_completions, prefix.c_str()); @@ -1012,13 +1013,13 @@ pager_t::pager_t() : available_term_width(0), available_term_height(0), selected bool pager_t::empty() const { - return completions.empty(); + return completion_infos.empty(); } const completion_t *pager_t::select_next_completion_in_direction(selection_direction_t direction, const page_rendering_t &rendering) { /* Must have something to select */ - if (completions.empty()) + if (this->empty()) { return NULL; } @@ -1034,7 +1035,7 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc case direction_prev: if (direction == direction_prev) { - selected_completion_idx = completions.size() - 1; + selected_completion_idx = completion_infos.size() - 1; } else { @@ -1046,6 +1047,7 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc case direction_north: case direction_east: case direction_west: + case direction_deselect: default: return NULL; } @@ -1055,8 +1057,12 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc size_t new_selected_completion_idx = selected_completion_idx; if (! selection_direction_is_cardinal(direction)) { - /* Next / previous, easy */ - if (direction == direction_next) + /* Next, previous, or deselect, all easy */ + if (direction == direction_deselect) + { + new_selected_completion_idx = PAGER_SELECTION_NONE; + } + else if (direction == direction_next) { new_selected_completion_idx = selected_completion_idx + 1; if (new_selected_completion_idx >= completion_infos.size()) @@ -1082,9 +1088,9 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc } else { - /* Cardinal directions. We have a completion index; we wish to compute its row and column. Completions are rendered column first, i.e. we go south before we go west. So if we have N rows, and our selected index is N + 2, then our row is 2 (mod by N) and our column is 1 (divide by N) */ - size_t current_row = selected_completion_idx % rendering.rows; - size_t current_col = selected_completion_idx / rendering.rows; + /* Cardinal directions. We have a completion index; we wish to compute its row and column. */ + size_t current_row = this->get_selected_row(rendering); + size_t current_col = this->get_selected_column(rendering); switch (direction) { @@ -1171,8 +1177,7 @@ const completion_t *pager_t::select_next_completion_in_direction(selection_direc if (visible_row_count > 0 && selected_completion_idx != PAGER_SELECTION_NONE) //paranoia { - size_t row_containing_selection = selected_completion_idx % rendering.rows; - + size_t row_containing_selection = this->get_selected_row(rendering); /* Ensure our suggested row start is not past the selected row */ if (suggested_row_start > row_containing_selection) @@ -1215,34 +1220,40 @@ size_t pager_t::visual_selected_completion_index(size_t rows, size_t cols) const if (result != PAGER_SELECTION_NONE) { /* If the selected completion is beyond the last selection, go left by columns until it's within it. This is how we implement "column memory." */ - while (result >= completions.size() && result >= rows) + while (result >= completion_infos.size() && result >= rows) { result -= rows; } } - assert(result == PAGER_SELECTION_NONE || result < completions.size()); + assert(result == PAGER_SELECTION_NONE || result < completion_infos.size()); return result; } -void pager_t::set_selected_completion_index(size_t completion_idx) -{ - selected_completion_idx = completion_idx; -} - const completion_t *pager_t::selected_completion(const page_rendering_t &rendering) const { const completion_t * result = NULL; size_t idx = visual_selected_completion_index(rendering.rows, rendering.cols); if (idx != PAGER_SELECTION_NONE) { - result = &completions.at(idx); + result = &completion_infos.at(idx).representative; } return result; } +/* Completions are rendered column first, i.e. we go south before we go west. So if we have N rows, and our selected index is N + 2, then our row is 2 (mod by N) and our column is 1 (divide by N) */ + +size_t pager_t::get_selected_row(const page_rendering_t &rendering) const +{ + return selected_completion_idx == PAGER_SELECTION_NONE ? PAGER_SELECTION_NONE : selected_completion_idx % rendering.rows; +} + +size_t pager_t::get_selected_column(const page_rendering_t &rendering) const +{ + return selected_completion_idx == PAGER_SELECTION_NONE ? PAGER_SELECTION_NONE : selected_completion_idx / rendering.rows; +} + void pager_t::clear() { - completions.clear(); completion_infos.clear(); prefix.clear(); selected_completion_idx = PAGER_SELECTION_NONE; diff --git a/pager.h b/pager.h index df2d00d26..a1fa5a42e 100644 --- a/pager.h +++ b/pager.h @@ -39,8 +39,6 @@ class pager_t int available_term_width; int available_term_height; - completion_list_t completions; - size_t selected_completion_idx; size_t suggested_row_start; @@ -60,19 +58,22 @@ class pager_t /** The description */ wcstring desc; + /** The representative completion */ + completion_t representative; + /** On-screen width of the completion string */ int comp_width; /** On-screen width of the description information */ int desc_width; - /** Preffered total width */ + /** Preferred 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) + comp_t() : comp(), desc(), representative(L""), comp_width(0), desc_width(0), pref_width(0), min_width(0) { } }; @@ -103,15 +104,16 @@ class pager_t /* Sets the terminal width and height */ void set_term_size(int w, int h); - /* Sets the index of the selected completion */ - void set_selected_completion_index(size_t completion_idx); - /* Changes the selected completion in the given direction according to the layout of the given rendering. Returns the newly selected completion if it changed, NULL if nothing was selected or it did not change. */ const completion_t *select_next_completion_in_direction(selection_direction_t direction, const page_rendering_t &rendering); /* Returns the currently selected completion for the given rendering */ const completion_t *selected_completion(const page_rendering_t &rendering) const; + /* Indicates the row and column for the given rendering. Returns -1 if no selection. */ + size_t get_selected_row(const page_rendering_t &rendering) const; + size_t get_selected_column(const page_rendering_t &rendering) const; + /* Produces a rendering of the completions, at the given term size */ page_rendering_t render() const; diff --git a/reader.cpp b/reader.cpp index d1ad4bf4e..1c313a63b 100644 --- a/reader.cpp +++ b/reader.cpp @@ -1548,17 +1548,27 @@ static void clear_pager() static void select_completion_in_direction(enum selection_direction_t dir, const wcstring &cycle_command_line, size_t cycle_cursor_pos) { const completion_t *next_comp = data->pager.select_next_completion_in_direction(dir, data->current_page_rendering); - if (next_comp != NULL) + if (next_comp != NULL || dir == direction_deselect) { + /* Update the cursor and command line */ size_t cursor_pos = cycle_cursor_pos; - const wcstring new_cmd_line = completion_apply_to_command_line(next_comp->completion, next_comp->flags, cycle_command_line, &cursor_pos, false); + wcstring new_cmd_line; + if (dir == direction_deselect) + { + new_cmd_line = cycle_command_line; + } + else + { + new_cmd_line = completion_apply_to_command_line(next_comp->completion, next_comp->flags, cycle_command_line, &cursor_pos, false); + } reader_set_buffer_maintaining_pager(new_cmd_line, cursor_pos); + /* Since we just inserted a completion, don't immediately do a new autosuggestion */ data->suppress_autosuggestion = true; /* Trigger repaint (see #765) */ - reader_repaint(); + reader_repaint_needed(); } } @@ -3653,10 +3663,33 @@ const wchar_t *reader_readline(void) case R_UP_LINE: case R_DOWN_LINE: { - /* If we are already navigating the pager, or if we pressed down with non-empty pager contents, begin navigation */ - if (is_navigating_pager_contents() || (c == R_DOWN_LINE && ! data->pager.empty())) + if (is_navigating_pager_contents()) { - select_completion_in_direction(c == R_UP_LINE ? direction_north : direction_south, cycle_command_line, cycle_cursor_pos); + /* We are already navigating pager contents. */ + selection_direction_t direction; + if (c == R_DOWN_LINE) + { + /* Down arrow is always south */ + direction = direction_south; + } + else if (data->pager.get_selected_row(data->current_page_rendering) == 0 && data->pager.get_selected_column(data->current_page_rendering) == 0) + { + /* Up arrow, but we are in the first column and first row. End navigation */ + direction = direction_deselect; + } + else + { + /* Up arrow, go north */ + direction = direction_north; + } + + /* Now do the selection */ + select_completion_in_direction(direction, cycle_command_line, cycle_cursor_pos); + } + else if (c == R_DOWN_LINE && ! data->pager.empty()) + { + /* We pressed down with a non-empty pager contents, begin navigation */ + select_completion_in_direction(direction_south, cycle_command_line, cycle_cursor_pos); } else { From 5849cd3a2e1f5bfc9192bd443749b44601c272af Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 24 Jan 2014 16:51:52 -0800 Subject: [PATCH 18/21] Remove some unnecessary fish_pager.cpp specific code from the new pager --- pager.cpp | 376 ++++-------------------------------------------------- pager.h | 2 +- 2 files changed, 24 insertions(+), 354 deletions(-) diff --git a/pager.cpp b/pager.cpp index bd9ed15b6..fc2ae5cf2 100644 --- a/pager.cpp +++ b/pager.cpp @@ -2,6 +2,10 @@ #include "pager.h" #include "highlight.h" +#include "input_common.h" + + +#if 1 #include #include @@ -58,6 +62,7 @@ #include "env_universal.h" #include "print_help.h" #include "highlight.h" +#endif #define PAGER_SELECTION_NONE ((size_t)(-1)) @@ -75,55 +80,12 @@ enum ; -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 -*/ +/** 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 -*/ +/** The maximum number of columns of completion to attempt to fit onto the screen */ #define PAGER_MAX_COLS 6 -/** - The string describing the single-character options accepted by fish_pager -*/ -#define GETOPT_STRING "c:hr:qvp:" - -/** - Error to use when given an invalid file descriptor for reading completions or writing output -*/ -#define ERR_NOT_FD _( L"%ls: Argument '%s' is not a valid file descriptor\n" ) - -/** - This buffer is used to buffer the output of the pager to improve - screen redraw performance bu cutting down the number of write() - calls to only one. -*/ -static std::vector pager_buffer; - -/** - This string contains the text that should be sent back to the calling program -*/ -static wcstring out_buff; - /* Returns numer / denom, rounding up */ static size_t divide_round_up(size_t numer, size_t denom) { @@ -148,100 +110,6 @@ void pager_t::recalc_min_widths(comp_info_list_t * lst) const } -/** - Test if the specified character sequence has been entered on the - keyboard -*/ -static int try_sequence(const char *seq) -{ - int j, k; - wint_t c=0; - - for (j=0; - seq[j] != '\0' && seq[j] == (c=input_common_readch(j>0)); - j++) - ; - - if (seq[j] == '\0') - { - return 1; - } - else - { - input_common_unreadch(c); - for (k=j-1; k>=0; k--) - input_common_unreadch(seq[k]); - } - return 0; -} - -/** - Read a character from keyboard -*/ -static wint_t readch() -{ - struct mapping - { - const char *seq; - wint_t bnd; - } - ; - - struct mapping m[]= - { - { - "\x1b[A", LINE_UP - } - , - { - key_up, LINE_UP - } - , - { - "\x1b[B", LINE_DOWN - } - , - { - key_down, LINE_DOWN - } - , - { - key_ppage, PAGE_UP - } - , - { - key_npage, PAGE_DOWN - } - , - { - " ", PAGE_DOWN - } - , - { - "\t", PAGE_DOWN - } - , - { - 0, 0 - } - - } - ; - int i; - - for (i=0; m[i].bnd; i++) - { - if (!m[i].seq) - { - continue; - } - - if (try_sequence(m[i].seq)) - return m[i].bnd; - } - return input_common_readch(0); -} - /** Print the specified string, but use at most the specified amount of space. If the whole string can't be fitted, ellipsize it. @@ -536,62 +404,6 @@ void pager_t::measure_completion_infos(comp_info_list_t *infos, const wcstring & recalc_min_widths(infos); } -#if 0 -page_rendering_t render_completions(const completion_list_t &raw_completions, const wcstring &prefix) -{ - // Save old output function so we can restore it - int (* const saved_writer_func)(char) = output_get_writer(); - output_set_writer(&pager_buffered_writer); - - // Get completion infos out of it - comp_info_list_t completion_infos = process_completions_into_infos(raw_completions, prefix.c_str()); - - // Maybe join them - if (prefix == L"-") - join_completions(&completion_infos); - - // Compute their various widths - measure_completion_infos(&completion_infos, prefix); - - /** - Try to print the completions. Start by trying to print the - list in PAGER_MAX_COLS columns, if the completions won't - fit, reduce the number of columns by one. Printing a single - column never fails. - */ - for (int i = PAGER_MAX_COLS; i>0; i--) - { - switch (completion_try_print(i, prefix, completion_infos)) - { - - case PAGER_RETRY: - break; - - case PAGER_DONE: - i=0; - break; - - case PAGER_RESIZE: - /* - This means we got a resize event, so we start - over from the beginning. Since it the screen got - bigger, we might be able to fit all completions - on-screen. - */ - i=PAGER_MAX_COLS+1; - break; - - } - } - - fwprintf(out_file, L"%ls", out_buff.c_str()); - - // Restore saved writer function - pager_buffer.clear(); - output_set_writer(saved_writer_func); -} -#endif - void pager_t::set_completions(const completion_list_t &raw_completions) { // Get completion infos out of it @@ -620,22 +432,12 @@ void pager_t::set_term_size(int w, int h) /** 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 + cols as the number of columns. Return true if the completion list was + printed, false 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 */ -int pager_t::completion_try_print(size_t cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering, size_t suggested_start_row) const +bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering, size_t suggested_start_row) const { /* The calculated preferred width of each column @@ -649,10 +451,9 @@ int pager_t::completion_try_print(size_t cols, const wcstring &prefix, const com 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; + + /* Set to one if the list should be printed at this width */ + bool print = false; /* Compute the effective term width and term height, accounting for disclosure */ int term_width = this->available_term_width; @@ -674,11 +475,10 @@ int pager_t::completion_try_print(size_t cols, const wcstring &prefix, const com int pref_tot_width=0; int min_tot_width = 0; - int res=PAGER_RETRY; /* Skip completions on tiny terminals */ if (term_width < PAGER_MIN_WIDTH) - return PAGER_DONE; + return true; /* Calculate how wide the list would be */ for (long col = 0; col < cols; col++) @@ -717,13 +517,13 @@ int pager_t::completion_try_print(size_t cols, const wcstring &prefix, const com pref_width[0] = term_width; } width = pref_width; - print=1; + print = true; } else if (pref_tot_width <= term_width) { /* Terminal is wide enough. Print the list! */ width = pref_width; - print=1; + print = true; } else { @@ -770,7 +570,7 @@ int pager_t::completion_try_print(size_t cols, const wcstring &prefix, const com } } } - print=1; + print = true; } } @@ -820,118 +620,8 @@ int pager_t::completion_try_print(size_t cols, const wcstring &prefix, const com } line_t &line = rendering->screen_data.add_line(); print_max(progress_text.c_str(), highlight_spec_pager_progress | highlight_make_background(highlight_spec_pager_progress), term_width, true /* has_more */, &line); - - return PAGER_DONE; - - res=PAGER_DONE; - if (row_count < term_height) - { - completion_print(cols, width, 0, row_count, prefix, lst, rendering); - } - else - { - completion_print(cols, width, 0, term_height - 1, prefix, lst, rendering); - - assert(0); - int npos, pos = 0; - int do_loop = 1; - - completion_print(cols, width, 0, term_height-1, prefix, lst, rendering); - - /* List does not fit on screen. Print one screenful and leave a scrollable interface */ - while (do_loop) - { - set_color(rgb_color_t::black(), highlight_get_color(highlight_spec_pager_progress, true)); - wcstring msg = format_string(_(L" %d to %d of %d"), pos, pos+term_height-1, row_count); - msg.append(L" \r"); - - writestr(msg.c_str()); - set_color(rgb_color_t::normal(), rgb_color_t::normal()); - int c = readch(); - - switch (c) - { - case LINE_UP: - { - if (pos > 0) - { - pos--; - writembs(tparm(cursor_address, 0, 0)); - writembs(scroll_reverse); - completion_print(cols, width, pos, pos+1, prefix, lst, rendering); - writembs(tparm(cursor_address, term_height-1, 0)); - writembs(clr_eol); - - } - - break; - } - - case LINE_DOWN: - { - if (pos <= (row_count - term_height)) - { - pos++; - completion_print(cols, width, pos+term_height-2, pos+term_height-1, prefix, lst, rendering); - } - break; - } - - case PAGE_DOWN: - { - - npos = mini((int)(row_count - term_height+1), (int)(pos + term_height-1)); - if (npos != pos) - { - pos = npos; - completion_print(cols, width, pos, pos+term_height-1, prefix, lst, rendering); - } - else - { - if (flash_screen) - writembs(flash_screen); - } - - break; - } - - case PAGE_UP: - { - npos = maxi(0, pos - term_height+1); - - if (npos != pos) - { - pos = npos; - completion_print(cols, width, pos, pos+term_height-1, prefix, lst, rendering); - } - 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; + return print; } @@ -950,8 +640,7 @@ page_rendering_t pager_t::render() const if (! this->empty()) { - int cols; - for (cols = PAGER_MAX_COLS; cols > 0; cols--) + for (int cols = PAGER_MAX_COLS; cols > 0; cols--) { /* Initially empty rendering */ rendering.screen_data.resize(0); @@ -971,30 +660,11 @@ page_rendering_t pager_t::render() const rendering.rows = min_rows_required_for_cols; rendering.selected_completion_idx = this->visual_selected_completion_index(rendering.rows, rendering.cols); - bool done = false; - switch (completion_try_print(cols, prefix, completion_infos, &rendering, suggested_row_start)) + if (completion_try_print(cols, prefix, completion_infos, &rendering, suggested_row_start)) { - case PAGER_RETRY: - break; - - case PAGER_DONE: - done = true; - break; - - case PAGER_RESIZE: - /* - This means we got a resize event, so we start - over from the beginning. Since it the screen got - bigger, we might be able to fit all completions - on-screen. - */ - cols=PAGER_MAX_COLS+1; - break; - } - if (done) break; + } } - assert(cols >= 0); } return rendering; } @@ -1240,8 +910,7 @@ const completion_t *pager_t::selected_completion(const page_rendering_t &renderi return result; } -/* Completions are rendered column first, i.e. we go south before we go west. So if we have N rows, and our selected index is N + 2, then our row is 2 (mod by N) and our column is 1 (divide by N) */ - +/* Get the selected row and column. Completions are rendered column first, i.e. we go south before we go west. So if we have N rows, and our selected index is N + 2, then our row is 2 (mod by N) and our column is 1 (divide by N) */ size_t pager_t::get_selected_row(const page_rendering_t &rendering) const { return selected_completion_idx == PAGER_SELECTION_NONE ? PAGER_SELECTION_NONE : selected_completion_idx % rendering.rows; @@ -1260,6 +929,7 @@ void pager_t::clear() fully_disclosed = false; } +/* Constructor */ page_rendering_t::page_rendering_t() : term_width(-1), term_height(-1), rows(0), cols(0), row_start(0), row_end(0), selected_completion_idx(-1), remaining_to_disclose(0) { } diff --git a/pager.h b/pager.h index a1fa5a42e..1407e3624 100644 --- a/pager.h +++ b/pager.h @@ -84,7 +84,7 @@ class pager_t wcstring prefix; - int completion_try_print(size_t cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering, size_t suggested_start_row) const; + bool completion_try_print(size_t cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering, size_t suggested_start_row) const; void recalc_min_widths(comp_info_list_t * lst) const; void measure_completion_infos(std::vector *infos, const wcstring &prefix) const; From 4ffd2380c3956206e279d6d1deab8c680cf192b3 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 24 Jan 2014 16:58:22 -0800 Subject: [PATCH 19/21] Remove yet more unnecessary fish_pager.cpp specific code from the new pager --- pager.cpp | 70 +------------------------------------------------------ 1 file changed, 1 insertion(+), 69 deletions(-) diff --git a/pager.cpp b/pager.cpp index fc2ae5cf2..91d7e7ad9 100644 --- a/pager.cpp +++ b/pager.cpp @@ -3,66 +3,8 @@ #include "pager.h" #include "highlight.h" #include "input_common.h" - - -#if 1 - -#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" -#include "highlight.h" -#endif +#include #define PAGER_SELECTION_NONE ((size_t)(-1)) @@ -70,16 +12,6 @@ typedef pager_t::comp_t 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 -} -; - - /** The minimum width (in characters) the terminal may have for fish_pager to not refuse showing the completions */ #define PAGER_MIN_WIDTH 16 From 4c5c1fc9ef22fff4e03db4632b4692b0f7e77966 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 24 Jan 2014 17:38:20 -0800 Subject: [PATCH 20/21] Suppress the pager progress message if the listing fits onscreen --- pager.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pager.cpp b/pager.cpp index 91d7e7ad9..f4ecce7c6 100644 --- a/pager.cpp +++ b/pager.cpp @@ -360,6 +360,7 @@ void pager_t::set_term_size(int w, int h) assert(h > 0); available_term_width = w; available_term_height = h; + recalc_min_widths(&completion_infos); } /** @@ -534,7 +535,7 @@ bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const co /* Ellipsis helper string. Either empty or containing the ellipsis char */ const wchar_t ellipsis_string[] = {ellipsis_char == L'\x2026' ? L'\x2026' : L'\0', L'\0'}; - /* Add the progress line. It's a "more to disclose" line if necessary. */ + /* Add the progress line. It's a "more to disclose" line if necessary, or a row listing if it's scrollable; otherwise ignore it */ wcstring progress_text; if (rendering->remaining_to_disclose == 1) { @@ -545,13 +546,17 @@ bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const co { progress_text = format_string(L"%lsand %lu more rows", ellipsis_string, (unsigned long)rendering->remaining_to_disclose); } - else + else if (start_row > 0 || stop_row < row_count) { - /* The +1 here is because we are zero indexed, but want to present things as 1-indexed. We do not add 1 to stop_row or row_count because these are the "past the last value" */ + /* We have a scrollable interface. The +1 here is because we are zero indexed, but want to present things as 1-indexed. We do not add 1 to stop_row or row_count because these are the "past the last value" */ progress_text = format_string(L"rows %lu to %lu of %lu", start_row + 1, stop_row, row_count); } - line_t &line = rendering->screen_data.add_line(); - print_max(progress_text.c_str(), highlight_spec_pager_progress | highlight_make_background(highlight_spec_pager_progress), term_width, true /* has_more */, &line); + + if (! progress_text.empty()) + { + line_t &line = rendering->screen_data.add_line(); + print_max(progress_text.c_str(), highlight_spec_pager_progress | highlight_make_background(highlight_spec_pager_progress), term_width, true /* has_more */, &line); + } } return print; } From 0fbddb0df1495e1cb275fa5ab15ba7f0ab3cc9fa Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 24 Jan 2014 17:51:28 -0800 Subject: [PATCH 21/21] Add fish_new_pager variable to enable new pager on request, disabled by default. --- parser.cpp | 13 +++++++++++++ parser.h | 1 + reader.cpp | 5 +++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/parser.cpp b/parser.cpp index 9bb1e6490..4badfff83 100644 --- a/parser.cpp +++ b/parser.cpp @@ -3122,3 +3122,16 @@ bool parser_use_ast(void) return from_string(var); } } + +bool pager_use_inline(void) +{ + env_var_t var = env_get_string(L"fish_new_pager"); + if (var.missing_or_empty()) + { + return 0; + } + else + { + return from_string(var); + } +} diff --git a/parser.h b/parser.h index 32add3fb1..6e43c8da0 100644 --- a/parser.h +++ b/parser.h @@ -547,6 +547,7 @@ public: /* Temporary */ bool parser_use_ast(void); +bool pager_use_inline(void); #endif diff --git a/reader.cpp b/reader.cpp index 1c313a63b..2bba5f971 100644 --- a/reader.cpp +++ b/reader.cpp @@ -1876,8 +1876,9 @@ static bool handle_completions(const std::vector &comp) parse_util_get_parameter_info(data->command_line, data->buff_pos, "e, NULL, NULL); bool is_quoted = (quote != L'\0'); - if (1) + if (pager_use_inline()) { + /* Inline pager */ data->pager.set_prefix(prefix); data->pager.set_completions(surviving_completions); @@ -1886,7 +1887,7 @@ static bool handle_completions(const std::vector &comp) } else { - /* Clear the autosuggestion from the old commandline before abandoning it (see #561) */ + /* Classic pager. Clear the autosuggestion from the old commandline before abandoning it (see #561) */ if (! data->autosuggestion.empty()) reader_repaint_without_autosuggestion();