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 = "<group>"; };
 		D025C02915D1FEA100B9DB63 /* tools */ = {isa = PBXFileReference; lastKnownFileType = folder; name = tools; path = share/tools; sourceTree = "<group>"; };
 		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 = "<group>"; };
+		D032388A1849D1980032CF2C /* pager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pager.h; sourceTree = "<group>"; };
 		D03EE83814DF88B200FC7150 /* lru.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = lru.h; sourceTree = "<group>"; };
 		D07B247215BCC15700D4ADB4 /* add-shell */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = "add-shell"; path = "build_tools/osx_package_scripts/add-shell"; sourceTree = "<group>"; };
 		D07B247515BCC4BE00D4ADB4 /* install.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = install.sh; path = osx/install.sh; sourceTree = "<group>"; };
@@ -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 <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <unistd.h>
+#include <termios.h>
+#include <string.h>
+#include <map>
+#include <algorithm>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <fcntl.h>
+
+#include <locale.h>
+
+#if HAVE_NCURSES_H
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+#if HAVE_TERM_H
+#include <term.h>
+#elif HAVE_NCURSES_TERM_H
+#include <ncurses/term.h>
+#endif
+
+#include <signal.h>
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+#include <errno.h>
+#include <vector>
+
+#include "fallback.h"
+#include "util.h"
+
+#include "wutil.h"
+#include "common.h"
+#include "complete.h"
+#include "output.h"
+#include "input_common.h"
+#include "env_universal.h"
+#include "print_help.h"
+
+struct comp_t;
+typedef std::vector<completion_t> completion_list_t;
+typedef std::vector<comp_t> comp_info_list_t;
+
+enum
+{
+    LINE_UP = R_NULL+1,
+    LINE_DOWN,
+    PAGE_UP,
+    PAGE_DOWN
+}
+;
+
+
+enum
+{
+    HIGHLIGHT_PAGER_PREFIX,
+    HIGHLIGHT_PAGER_COMPLETION,
+    HIGHLIGHT_PAGER_DESCRIPTION,
+    HIGHLIGHT_PAGER_PROGRESS,
+    HIGHLIGHT_PAGER_SECONDARY
+}
+;
+
+enum
+{
+    /*
+      Returnd by the pager if no more displaying is needed
+    */
+    PAGER_DONE,
+    /*
+      Returned by the pager if the completions would not fit in the specified number of columns
+    */
+    PAGER_RETRY,
+    /*
+      Returned by the pager if the terminal changes size
+    */
+    PAGER_RESIZE
+}
+;
+
+/**
+   The minimum width (in characters) the terminal may have for fish_pager to not refuse showing the completions
+*/
+#define PAGER_MIN_WIDTH 16
+
+/**
+   The maximum number of columns of completion to attempt to fit onto the screen
+*/
+#define PAGER_MAX_COLS 6
+
+/**
+   The string describing the single-character options accepted by fish_pager
+*/
+#define GETOPT_STRING "c:hr:qvp:"
+
+/**
+   Error to use when given an invalid file descriptor for reading completions or writing output
+*/
+#define ERR_NOT_FD _( L"%ls: Argument '%s' is not a valid file descriptor\n" )
+
+/**
+   This struct should be continually updated by signals as the term
+   resizes, and as such always contain the correct current size.
+*/
+static struct winsize termsize;
+
+/**
+   The termios modes the terminal had when the program started. These
+   should be restored on exit
+*/
+static struct termios saved_modes;
+
+/**
+   This flag is set to 1 of we have sent the enter_ca_mode terminfo
+   sequence to save the previous terminal contents.
+*/
+static int is_ca_mode = 0;
+
+/**
+   This buffer is used to buffer the output of the pager to improve
+   screen redraw performance bu cutting down the number of write()
+   calls to only one.
+*/
+static std::vector<char> pager_buffer;
+
+/**
+   The environment variables used to specify the color of different
+   tokens.
+*/
+static const wchar_t *hightlight_var[] =
+{
+    L"fish_pager_color_prefix",
+    L"fish_pager_color_completion",
+    L"fish_pager_color_description",
+    L"fish_pager_color_progress",
+    L"fish_pager_color_secondary"
+}
+;
+
+/**
+   This string contains the text that should be sent back to the calling program
+*/
+static wcstring out_buff;
+/**
+   This is the file to which the output text should be sent. It is really a pipe.
+*/
+static FILE *out_file;
+
+/**
+   Data structure describing one or a group of related completions
+*/
+struct comp_t
+{
+    /**
+       The list of all completin strings this entry applies to
+    */
+    wcstring_list_t comp;
+    /**
+       The description
+    */
+    wcstring desc;
+    /**
+       On-screen width of the completion string
+    */
+    int comp_width;
+    /**
+       On-screen width of the description information
+    */
+    int desc_width;
+    /**
+       Preffered total width
+    */
+    int pref_width;
+    /**
+       Minimum acceptable width
+    */
+    int min_width;
+    
+    comp_t() : comp(), desc(), comp_width(0), desc_width(0), pref_width(0), min_width(0)
+    {
+    }
+};
+
+/**
+   This function translates from a highlight code to a specific color
+   by check invironement variables
+*/
+static rgb_color_t get_color(int highlight)
+{
+    const wchar_t *val;
+
+    if (highlight < 0)
+        return rgb_color_t::normal();
+    if (highlight >= (5))
+        return rgb_color_t::normal();
+
+    val = wgetenv(hightlight_var[highlight]);
+
+    if (!val)
+    {
+        val = env_universal_get(hightlight_var[highlight]);
+    }
+
+    if (!val)
+    {
+        return rgb_color_t::normal();
+    }
+
+    return parse_color(val, false);
+}
+
+/**
+   This function calculates the minimum width for each completion
+   entry in the specified array_list. This width depends on the
+   terminal size, so this function should be called when the terminal
+   changes size.
+*/
+static void recalc_min_widths(std::vector<comp_t> *lst)
+{
+    for (size_t i=0; i<lst->size(); i++)
+    {
+        comp_t *c = &lst->at(i);
+
+        c->min_width = mini(c->desc_width, maxi(0,termsize.ws_col/3 - 2)) +
+                       mini(c->desc_width, maxi(0,termsize.ws_col/5 - 4)) +4;
+    }
+
+}
+
+/**
+   Test if the specified character sequence has been entered on the
+   keyboard
+*/
+static int try_sequence(const char *seq)
+{
+    int j, k;
+    wint_t c=0;
+
+    for (j=0;
+            seq[j] != '\0' && seq[j] == (c=input_common_readch(j>0));
+            j++)
+        ;
+
+    if (seq[j] == '\0')
+    {
+        return 1;
+    }
+    else
+    {
+        input_common_unreadch(c);
+        for (k=j-1; k>=0; k--)
+            input_common_unreadch(seq[k]);
+    }
+    return 0;
+}
+
+/**
+   Read a character from keyboard
+*/
+static wint_t readch()
+{
+    struct mapping
+    {
+        const char *seq;
+        wint_t bnd;
+    }
+    ;
+
+    struct mapping m[]=
+    {
+        {
+            "\x1b[A", LINE_UP
+        }
+        ,
+        {
+            key_up, LINE_UP
+        }
+        ,
+        {
+            "\x1b[B", LINE_DOWN
+        }
+        ,
+        {
+            key_down, LINE_DOWN
+        }
+        ,
+        {
+            key_ppage, PAGE_UP
+        }
+        ,
+        {
+            key_npage, PAGE_DOWN
+        }
+        ,
+        {
+            " ", PAGE_DOWN
+        }
+        ,
+        {
+            "\t", PAGE_DOWN
+        }
+        ,
+        {
+            0, 0
+        }
+
+    }
+    ;
+    int i;
+
+    for (i=0; m[i].bnd; i++)
+    {
+        if (!m[i].seq)
+        {
+            continue;
+        }
+
+        if (try_sequence(m[i].seq))
+            return m[i].bnd;
+    }
+    return input_common_readch(0);
+}
+
+/**
+   Write specified character to the output buffer \c pager_buffer
+*/
+static int pager_buffered_writer(char c)
+{
+    pager_buffer.push_back(c);
+    return 0;
+}
+
+/**
+   Flush \c pager_buffer to stdout
+*/
+static void pager_flush()
+{
+    if (! pager_buffer.empty())
+    {
+        write_loop(1, & pager_buffer.at(0), pager_buffer.size() * sizeof(char));
+        pager_buffer.clear();
+    }
+}
+
+/**
+   Print the specified string, but use at most the specified amount of
+   space. If the whole string can't be fitted, ellipsize it.
+
+   \param str the string to print
+   \param max the maximum space that may be used for printing
+   \param has_more if this flag is true, this is not the entire string, and the string should be ellisiszed even if the string fits but takes up the whole space.
+*/
+static int print_max(const wcstring &str, int max, int has_more)
+{
+    int written = 0;
+    for (size_t i=0; i < str.size(); i++)
+    {
+        wchar_t c = str.at(i);
+
+        if (written + wcwidth(c) > max)
+            break;
+        if ((written + wcwidth(c) == max) && (has_more || i + 1 < str.size()))
+        {
+            writech(ellipsis_char);
+            written += wcwidth(ellipsis_char);
+            break;
+        }
+
+        writech(c);
+        written+= wcwidth(c);
+    }
+    return written;
+}
+
+/**
+   Print the specified item using at the specified amount of space
+*/
+static void completion_print_item(const wcstring &prefix, const comp_t *c, int width, bool secondary)
+{
+    int comp_width=0, desc_width=0;
+    int written=0;
+
+    if (c->pref_width <= width)
+    {
+        /*
+          The entry fits, we give it as much space as it wants
+        */
+        comp_width = c->comp_width;
+        desc_width = c->desc_width;
+    }
+    else
+    {
+        /*
+          The completion and description won't fit on the
+          allocated space. Give a maximum of 2/3 of the
+          space to the completion, and whatever is left to
+          the description.
+        */
+        int desc_all = c->desc_width?c->desc_width+4:0;
+
+        comp_width = maxi(mini(c->comp_width, 2*(width-4)/3), width - desc_all);
+        if (c->desc_width)
+            desc_width = width-comp_width-4;
+
+    }
+
+    rgb_color_t bg = secondary ? get_color(HIGHLIGHT_PAGER_SECONDARY) : rgb_color_t::normal();
+    for (size_t i=0; i<c->comp.size(); i++)
+    {
+        const wcstring &comp = c->comp.at(i);
+        if (i != 0)
+            written += print_max(L"  ", comp_width - written, 2);
+        set_color(get_color(HIGHLIGHT_PAGER_PREFIX), bg);
+        written += print_max(prefix, comp_width - written, comp.empty()?0:1);
+        set_color(get_color(HIGHLIGHT_PAGER_COMPLETION), bg);
+        written += print_max(comp.c_str(), comp_width - written, i!=(c->comp.size()-1));
+    }
+
+
+    if (desc_width)
+    {
+        while (written < (width-desc_width-2))
+        {
+            written++;
+            writech(L' ');
+        }
+        set_color(get_color(HIGHLIGHT_PAGER_DESCRIPTION), bg);
+        written += print_max(L"(", 1, 0);
+        written += print_max(c->desc.c_str(), desc_width, 0);
+        written += print_max(L")", 1, 0);
+    }
+    else
+    {
+        while (written < width)
+        {
+            written++;
+            writech(L' ');
+        }
+    }
+    if (secondary)
+        set_color(rgb_color_t::normal(), rgb_color_t::normal());
+}
+
+/**
+   Print the specified part of the completion list, using the
+   specified column offsets and quoting style.
+
+   \param l The list of completions to print
+   \param cols number of columns to print in
+   \param width An array specifying the width of each column
+   \param row_start The first row to print
+   \param row_stop the row after the last row to print
+   \param prefix The string to print before each completion
+*/
+
+static void completion_print(int cols,
+                             int *width,
+                             int row_start,
+                             int row_stop,
+                             const wcstring &prefix,
+                             const std::vector<comp_t> &lst)
+{
+
+    size_t rows = (lst.size()-1)/cols+1;
+    size_t i, j;
+
+    for (i = row_start; i<row_stop; i++)
+    {
+        for (j = 0; j < cols; j++)
+        {
+            int is_last = (j==(cols-1));
+
+            if (lst.size() <= j*rows + i)
+                continue;
+
+            const comp_t *el = &lst.at(j*rows + i);
+
+            completion_print_item(prefix, el, width[j] - (is_last?0:2), i%2);
+
+            if (!is_last)
+                writestr(L"  ");
+        }
+        writech(L'\n');
+    }
+}
+
+
+/**
+   Try to print the list of completions l with the prefix prefix using
+   cols as the number of columns. Return 1 if the completion list was
+   printed, 0 if the terminal is to narrow for the specified number of
+   columns. Always succeeds if cols is 1.
+
+   If all the elements do not fit on the screen at once, make the list
+   scrollable using the up, down and space keys to move. The list will
+   exit when any other key is pressed.
+
+   \param cols the number of columns to try to fit onto the screen
+   \param prefix the character string to prefix each completion with
+   \param l the list of completions
+
+   \return one of PAGER_RETRY, PAGER_DONE and PAGER_RESIZE
+*/
+
+static int completion_try_print(int cols,
+                                const wcstring &prefix,
+                                const std::vector<comp_t> &lst)
+{
+    /*
+      The calculated preferred width of each column
+    */
+    int pref_width[PAGER_MAX_COLS] = {0};
+    /*
+      The calculated minimum width of each column
+    */
+    int min_width[PAGER_MAX_COLS] = {0};
+    /*
+      If the list can be printed with this width, width will contain the width of each column
+    */
+    int *width=pref_width;
+    /*
+      Set to one if the list should be printed at this width
+    */
+    int print=0;
+
+    long i, j;
+
+    int rows = (int)((lst.size()-1)/cols+1);
+
+    int pref_tot_width=0;
+    int min_tot_width = 0;
+    int res=PAGER_RETRY;
+    /*
+      Skip completions on tiny terminals
+    */
+
+    if (termsize.ws_col < PAGER_MIN_WIDTH)
+        return PAGER_DONE;
+
+    /* Calculate how wide the list would be */
+    for (j = 0; j < cols; j++)
+    {
+        for (i = 0; i<rows; i++)
+        {
+            int pref,min;
+            const comp_t *c;
+            if (lst.size() <= j*rows + i)
+                continue;
+
+            c = &lst.at(j*rows + i);
+            pref = c->pref_width;
+            min = c->min_width;
+
+            if (j != cols-1)
+            {
+                pref += 2;
+                min += 2;
+            }
+            min_width[j] = maxi(min_width[j],
+                                min);
+            pref_width[j] = maxi(pref_width[j],
+                                 pref);
+        }
+        min_tot_width += min_width[j];
+        pref_tot_width += pref_width[j];
+    }
+    /*
+      Force fit if one column
+    */
+    if (cols == 1)
+    {
+        if (pref_tot_width > termsize.ws_col)
+        {
+            pref_width[0] = termsize.ws_col;
+        }
+        width = pref_width;
+        print=1;
+    }
+    else if (pref_tot_width <= termsize.ws_col)
+    {
+        /* Terminal is wide enough. Print the list! */
+        width = pref_width;
+        print=1;
+    }
+    else
+    {
+        long next_rows = (lst.size()-1)/(cols-1)+1;
+        /*    fwprintf( stderr,
+          L"cols %d, min_tot %d, term %d, rows=%d, nextrows %d, termrows %d, diff %d\n",
+          cols,
+          min_tot_width, termsize.ws_col,
+          rows, next_rows, termsize.ws_row,
+          pref_tot_width-termsize.ws_col );
+        */
+        if (min_tot_width < termsize.ws_col &&
+                (((rows < termsize.ws_row) && (next_rows >= termsize.ws_row)) ||
+                 (pref_tot_width-termsize.ws_col< 4 && cols < 3)))
+        {
+            /*
+              Terminal almost wide enough, or squeezing makes the
+              whole list fit on-screen.
+
+              This part of the code is really important. People hate
+              having to scroll through the completion list. In cases
+              where there are a huge number of completions, it can't
+              be helped, but it is not uncommon for the completions to
+              _almost_ fit on one screen. In those cases, it is almost
+              always desirable to 'squeeze' the completions into a
+              single page.
+
+              If we are using N columns and can get everything to
+              fit using squeezing, but everything would also fit
+              using N-1 columns, don't try.
+            */
+
+            int tot_width = min_tot_width;
+            width = min_width;
+
+            while (tot_width < termsize.ws_col)
+            {
+                for (i=0; (i<cols) && (tot_width < termsize.ws_col); i++)
+                {
+                    if (width[i] < pref_width[i])
+                    {
+                        width[i]++;
+                        tot_width++;
+                    }
+                }
+            }
+            print=1;
+        }
+    }
+
+    if (print)
+    {
+        res=PAGER_DONE;
+        if (rows < termsize.ws_row)
+        {
+            /* List fits on screen. Print it and leave */
+            if (is_ca_mode)
+            {
+                is_ca_mode = 0;
+                writembs(exit_ca_mode);
+            }
+
+            completion_print(cols, width, 0, rows, prefix, lst);
+            pager_flush();
+        }
+        else
+        {
+            int npos, pos = 0;
+            int do_loop = 1;
+
+            /*
+              Enter ca_mode, which means that the terminal
+              content will be restored to the current
+              state on exit.
+            */
+            if (enter_ca_mode && exit_ca_mode)
+            {
+                is_ca_mode=1;
+                writembs(enter_ca_mode);
+            }
+
+
+            completion_print(cols,
+                             width,
+                             0,
+                             termsize.ws_row-1,
+                             prefix,
+                             lst);
+            /*
+              List does not fit on screen. Print one screenfull and
+              leave a scrollable interface
+            */
+            while (do_loop)
+            {
+                set_color(rgb_color_t::black(), get_color(HIGHLIGHT_PAGER_PROGRESS));
+                wcstring msg = format_string(_(L" %d to %d of %d"), pos, pos+termsize.ws_row-1, rows);
+                msg.append(L"   \r");
+
+                writestr(msg.c_str());
+                set_color(rgb_color_t::normal(), rgb_color_t::normal());
+                pager_flush();
+                int c = readch();
+
+                switch (c)
+                {
+                    case LINE_UP:
+                    {
+                        if (pos > 0)
+                        {
+                            pos--;
+                            writembs(tparm(cursor_address, 0, 0));
+                            writembs(scroll_reverse);
+                            completion_print(cols,
+                                             width,
+                                             pos,
+                                             pos+1,
+                                             prefix,
+                                             lst);
+                            writembs(tparm(cursor_address,
+                                           termsize.ws_row-1, 0));
+                            writembs(clr_eol);
+
+                        }
+
+                        break;
+                    }
+
+                    case LINE_DOWN:
+                    {
+                        if (pos <= (rows - termsize.ws_row))
+                        {
+                            pos++;
+                            completion_print(cols,
+                                             width,
+                                             pos+termsize.ws_row-2,
+                                             pos+termsize.ws_row-1,
+                                             prefix,
+                                             lst);
+                        }
+                        break;
+                    }
+
+                    case PAGE_DOWN:
+                    {
+
+                        npos = mini((int)(rows - termsize.ws_row+1), (int)(pos + termsize.ws_row-1));
+                        if (npos != pos)
+                        {
+                            pos = npos;
+                            completion_print(cols,
+                                             width,
+                                             pos,
+                                             pos+termsize.ws_row-1,
+                                             prefix,
+                                             lst);
+                        }
+                        else
+                        {
+                            if (flash_screen)
+                                writembs(flash_screen);
+                        }
+
+                        break;
+                    }
+
+                    case PAGE_UP:
+                    {
+                        npos = maxi(0,
+                                    pos - termsize.ws_row+1);
+
+                        if (npos != pos)
+                        {
+                            pos = npos;
+                            completion_print(cols,
+                                             width,
+                                             pos,
+                                             pos+termsize.ws_row-1,
+                                             prefix,
+                                             lst);
+                        }
+                        else
+                        {
+                            if (flash_screen)
+                                writembs(flash_screen);
+                        }
+                        break;
+                    }
+
+                    case R_NULL:
+                    {
+                        do_loop=0;
+                        res=PAGER_RESIZE;
+                        break;
+
+                    }
+
+                    default:
+                    {
+                        out_buff.push_back(c);
+                        do_loop = 0;
+                        break;
+                    }
+                }
+            }
+            writembs(clr_eol);
+        }
+    }
+    return res;
+}
+
+/* Trim leading and trailing whitespace, and compress other whitespace runs into a single space. */
+static void mangle_1_completion_description(wcstring *str)
+{
+    size_t leading = 0, trailing = 0, len = str->size();
+ 
+    // Skip leading spaces
+    for (; leading < len; leading++)
+    {
+        if (! iswspace(str->at(leading)))
+            break;
+    }
+    
+    // Compress runs of spaces to a single space
+    bool was_space = false;
+    for (; leading < len; leading++)
+    {
+        wchar_t wc = str->at(leading);
+        bool is_space = iswspace(wc);
+        if (! is_space)
+        {
+            // normal character
+            str->at(trailing++) = wc;
+        }
+        else if (! was_space)
+        {
+            // initial space in a run
+            str->at(trailing++) = L' ';
+        }
+        else
+        {
+            // non-initial space in a run, do nothing
+        }
+        was_space = is_space;
+    }
+    
+    // leading is now at len, trailing is the new length of the string
+    // Delete trailing spaces
+    while (trailing > 0 && iswspace(str->at(trailing - 1)))
+    {
+        trailing--;
+    }
+    
+    str->resize(trailing);
+}
+
+static void join_completions(comp_info_list_t *comps)
+{
+    // A map from description to index in the completion list of the element with that description
+    // The indexes are stored +1
+    std::map<wcstring, size_t> desc_table;
+
+    // note that we mutate the completion list as we go, so the size changes
+    for (size_t i=0; i < comps->size(); i++)
+    {
+        const comp_t &new_comp = comps->at(i);
+        const wcstring &desc = new_comp.desc;
+        if (desc.empty())
+            continue;
+        
+        // See if it's in the table
+        size_t prev_idx_plus_one = desc_table[desc];
+        if (prev_idx_plus_one == 0)
+        {
+            // We're the first with this description
+            desc_table[desc] = i+1;
+        }
+        else
+        {
+            // There's a prior completion with this description. Append the new ones to it.
+            comp_t *prior_comp = &comps->at(prev_idx_plus_one - 1);
+            prior_comp->comp.insert(prior_comp->comp.end(), new_comp.comp.begin(), new_comp.comp.end());
+            
+            // Erase the element at this index, and decrement the index to reflect that fact
+            comps->erase(comps->begin() + i);
+            i -= 1;
+        }
+    }
+}
+
+/** Generate a list of comp_t structures from a list of completions */
+static std::vector<comp_t> process_completions_into_infos(const completion_list_t &lst, const wcstring &prefix)
+{
+    const size_t lst_size = lst.size();
+    
+    // Make the list of the correct size up-front
+    std::vector<comp_t> result(lst_size);
+    for (size_t i=0; i<lst_size; i++)
+    {
+        const completion_t &comp = lst.at(i);
+        comp_t *comp_info = &result.at(i);
+        
+        // Append the single completion string. We may later merge these into multiple.
+        comp_info->comp.push_back(escape_string(comp.completion, ESCAPE_ALL | ESCAPE_NO_QUOTED));
+        
+        // Append the mangled description
+        comp_info->desc = comp.description;
+        mangle_1_completion_description(&comp_info->desc);
+    }
+    return result;
+}
+
+static void measure_completion_infos(std::vector<comp_t> *infos, const wcstring &prefix)
+{
+    size_t prefix_len = my_wcswidth(prefix.c_str());
+    for (size_t i=0; i < infos->size(); i++)
+    {
+        comp_t *comp = &infos->at(i);
+        
+        // Compute comp_width
+        const wcstring_list_t &comp_strings = comp->comp;
+        for (size_t j=0; j < comp_strings.size(); j++)
+        {
+            // If there's more than one, append the length of ', '
+            if (j >= 1)
+                comp->comp_width += 2;
+            
+            comp->comp_width += prefix_len + my_wcswidth(comp_strings.at(j).c_str());
+        }
+        
+        // Compute desc_width
+        comp->desc_width = my_wcswidth(comp->desc.c_str());
+        
+        // Compute preferred width
+        comp->pref_width = comp->comp_width + comp->desc_width + (comp->desc_width?4:0);
+    }
+    
+    recalc_min_widths(infos);
+}
+
+/**
+   Respond to a winch signal by checking the terminal size
+*/
+static void handle_winch(int sig)
+{
+    if (ioctl(1,TIOCGWINSZ,&termsize)!=0)
+    {
+        return;
+    }
+}
+
+/**
+   The callback function that the keyboard reading function calls when
+   an interrupt occurs. This makes sure that R_NULL is returned at
+   once when an interrupt has occured.
+*/
+static int interrupt_handler()
+{
+    return R_NULL;
+}
+
+/**
+   Initialize various subsystems. This also closes stdin and replaces
+   it with a copy of stderr, so the reading of completion strings must
+   be done before init is called.
+*/
+static void init(int mangle_descriptors, int out)
+{
+    struct sigaction act;
+
+    static struct termios pager_modes;
+    char *term;
+
+    if (mangle_descriptors)
+    {
+
+        /*
+          Make fd 1 output to screen, and use some other fd for writing
+          the resulting output back to the caller
+        */
+        int in;
+        out = dup(1);
+        close(1);
+        close(0);
+
+        /* OK to not use CLO_EXEC here because fish_pager is single threaded */
+        if ((in = open(ttyname(2), O_RDWR)) != -1)
+        {
+            if (dup2(2, 1) == -1)
+            {
+                debug(0, _(L"Could not set up output file descriptors for pager"));
+                exit(1);
+            }
+
+            if (dup2(in, 0) == -1)
+            {
+                debug(0, _(L"Could not set up input file descriptors for pager"));
+                exit(1);
+            }
+        }
+        else
+        {
+            debug(0, _(L"Could not open tty for pager"));
+            exit(1);
+        }
+    }
+
+    if (!(out_file = fdopen(out, "w")))
+    {
+        debug(0, _(L"Could not initialize result pipe"));
+        exit(1);
+    }
+
+
+    env_universal_init(0, 0, 0, 0);
+    input_common_init(&interrupt_handler);
+    output_set_writer(&pager_buffered_writer);
+
+    sigemptyset(& act.sa_mask);
+    act.sa_flags=0;
+    act.sa_handler=SIG_DFL;
+    act.sa_flags = 0;
+    act.sa_handler= &handle_winch;
+    if (sigaction(SIGWINCH, &act, 0))
+    {
+        wperror(L"sigaction");
+        exit(1);
+    }
+
+    handle_winch(0);                  /* Set handler for window change events */
+
+    tcgetattr(0,&pager_modes);        /* get the current terminal modes */
+    memcpy(&saved_modes,
+           &pager_modes,
+           sizeof(saved_modes));     /* save a copy so we can reset the terminal later */
+
+    pager_modes.c_lflag &= ~ICANON;   /* turn off canonical mode */
+    pager_modes.c_lflag &= ~ECHO;     /* turn off echo mode */
+    pager_modes.c_cc[VMIN]=1;
+    pager_modes.c_cc[VTIME]=0;
+
+    /*
+
+    */
+    if (tcsetattr(0,TCSANOW,&pager_modes))      /* set the new modes */
+    {
+        wperror(L"tcsetattr");
+        exit(1);
+    }
+
+    int errret;
+    if (setupterm(0, STDOUT_FILENO, &errret) == ERR)
+    {
+        debug(0, _(L"Could not set up terminal"));
+        exit(1);
+    }
+
+    term = getenv("TERM");
+    if (term)
+    {
+        wcstring wterm = str2wcstring(term);
+        output_set_term(wterm);
+    }
+
+    /* Infer term256 support */
+    char *fish_term256 = getenv("fish_term256");
+    bool support_term256;
+    if (fish_term256)
+    {
+        support_term256 = from_string<bool>(fish_term256);
+    }
+    else
+    {
+        support_term256 = term && strstr(term, "256color");
+    }
+    output_set_supports_term256(support_term256);
+}
+
+
+void run_pager(const completion_list_t &raw_completions, const wcstring &prefix)
+{
+    // Save old output function so we can restore it
+    int (* const saved_writer_func)(char) = output_get_writer();
+    output_set_writer(&pager_buffered_writer);
+
+    // Get completion infos out of it
+    std::vector<comp_t> completion_infos = process_completions_into_infos(raw_completions, prefix.c_str());
+ 
+    // Maybe join them
+    if (prefix == L"-")
+        join_completions(&completion_infos);
+    
+    // Compute their various widths
+    measure_completion_infos(&completion_infos, prefix);
+    
+    /**
+       Try to print the completions. Start by trying to print the
+       list in PAGER_MAX_COLS columns, if the completions won't
+       fit, reduce the number of columns by one. Printing a single
+       column never fails.
+    */
+    for (int i = PAGER_MAX_COLS; i>0; i--)
+    {
+        switch (completion_try_print(i, prefix, completion_infos))
+        {
+
+            case PAGER_RETRY:
+                break;
+
+            case PAGER_DONE:
+                i=0;
+                break;
+
+            case PAGER_RESIZE:
+                /*
+                  This means we got a resize event, so we start
+                  over from the beginning. Since it the screen got
+                  bigger, we might be able to fit all completions
+                  on-screen.
+                */
+                i=PAGER_MAX_COLS+1;
+                break;
+
+        }
+    }
+
+    fwprintf(out_file, L"%ls", out_buff.c_str());
+    if (is_ca_mode)
+    {
+        writembs(exit_ca_mode);
+        pager_flush();
+        is_ca_mode = 0;
+    }
+    
+    // Restore saved writer function
+    pager_buffer.clear();
+    output_set_writer(saved_writer_func);
+}
+
+
diff --git a/pager.h b/pager.h
new file mode 100644
index 000000000..e69de29bb