Bump to 608
[platform/upstream/less.git] / line.c
diff --git a/line.c b/line.c
old mode 100755 (executable)
new mode 100644 (file)
index 1eb3914..2f1b3fb
--- a/line.c
+++ b/line.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1984-2012  Mark Nudelman
+ * Copyright (C) 1984-2022  Mark Nudelman
  *
  * You may distribute under the terms of either the GNU General Public
  * License or the Less License, as specified in the README file.
@@ -7,7 +7,6 @@
  * For more information, see the README file.
  */
 
-
 /*
  * Routines to manipulate the "line buffer".
  * The line buffer holds a line of output as it is being built
 
 #include "less.h"
 #include "charset.h"
+#include "position.h"
+
+#if MSDOS_COMPILER==WIN32C
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
 
-static char *linebuf = NULL;   /* Buffer which holds the current output line */
-static char *attr = NULL;      /* Extension of linebuf to hold attributes */
-public int size_linebuf = 0;   /* Size of line buffer (and attr buffer) */
+#define MAX_PFX_WIDTH (MAX_LINENUM_WIDTH + MAX_STATUSCOL_WIDTH + 1)
+static struct {
+       char *buf;    /* Buffer which holds the current output line */
+       int *attr;   /* Parallel to buf, to hold attributes */
+       int print;    /* Index in buf of first printable char */
+       int end;      /* Number of chars in buf */
+       char pfx[MAX_PFX_WIDTH]; /* Holds status column and line number */
+       int pfx_attr[MAX_PFX_WIDTH];
+       int pfx_end;  /* Number of chars in pfx */
+} linebuf;
 
-static int cshift;             /* Current left-shift of output line buffer */
-public int hshift;             /* Desired left-shift of output line buffer */
+/*
+ * Buffer of ansi sequences which have been shifted off the left edge 
+ * of the screen. 
+ */
+struct xbuffer shifted_ansi;
+
+/*
+ * Ring buffer of last ansi sequences sent.
+ * While sending a line, these will be resent at the end
+ * of any highlighted string, to restore text modes.
+ * {{ Not ideal, since we don't really know how many to resend. }}
+ */
+#define NUM_LAST_ANSIS 3
+static struct xbuffer last_ansi;
+static struct xbuffer last_ansis[NUM_LAST_ANSIS];
+static int curr_last_ansi;
+
+public int size_linebuf = 0; /* Size of line buffer (and attr buffer) */
+static struct ansi_state *line_ansi = NULL;
+static int ansi_in_line;
+static int hlink_in_line;
+static int line_mark_attr;
+static int cshift;   /* Current left-shift of output line buffer */
+public int hshift;   /* Desired left-shift of output line buffer */
 public int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */
-public int ntabstops = 1;      /* Number of tabstops */
-public int tabdefault = 8;     /* Default repeated tabstops */
-public POSITION highest_hilite;        /* Pos of last hilite in file found so far */
-
-static int curr;               /* Index into linebuf */
-static int column;             /* Printable length, accounting for
-                                  backspaces, etc. */
-static int overstrike;         /* Next char should overstrike previous char */
+public int ntabstops = 1;        /* Number of tabstops */
+public int tabdefault = 8;       /* Default repeated tabstops */
+public POSITION highest_hilite;  /* Pos of last hilite in file found so far */
+
+static int end_column;  /* Printable length, accounting for backspaces, etc. */
+static int right_curr;
+static int right_column;
+static int overstrike;  /* Next char should overstrike previous char */
 static int last_overstrike = AT_NORMAL;
-static int is_null_line;       /* There is no current line */
-static int lmargin;            /* Left margin */
-static char pendc;
+static int is_null_line;  /* There is no current line */
+static LWCHAR pendc;
 static POSITION pendpos;
 static char *end_ansi_chars;
 static char *mid_ansi_chars;
+static int in_hilite;
 
-static int attr_swidth();
-static int attr_ewidth();
-static int do_append();
+static int attr_swidth LESSPARAMS ((int a));
+static int attr_ewidth LESSPARAMS ((int a));
+static int do_append LESSPARAMS ((LWCHAR ch, char *rep, POSITION pos));
 
 extern int sigs;
 extern int bs_mode;
@@ -51,6 +85,8 @@ extern int ctldisp;
 extern int twiddle;
 extern int binattr;
 extern int status_col;
+extern int status_col_width;
+extern int linenum_width;
 extern int auto_wrap, ignaw;
 extern int bo_s_width, bo_e_width;
 extern int ul_s_width, ul_e_width;
@@ -60,48 +96,77 @@ extern int sc_width, sc_height;
 extern int utf_mode;
 extern POSITION start_attnpos;
 extern POSITION end_attnpos;
+extern char rscroll_char;
+extern int rscroll_attr;
+extern int use_color;
+extern int status_line;
 
 static char mbc_buf[MAX_UTF_CHAR_LEN];
 static int mbc_buf_len = 0;
 static int mbc_buf_index = 0;
 static POSITION mbc_pos;
 
+/* Configurable color map */
+static char color_map[AT_NUM_COLORS][12] = {
+       "Wm",  /* AT_COLOR_ATTN */
+       "kR",  /* AT_COLOR_BIN */
+       "kR",  /* AT_COLOR_CTRL */
+       "kY",  /* AT_COLOR_ERROR */
+       "c",   /* AT_COLOR_LINENUM */
+       "Wb",  /* AT_COLOR_MARK */
+       "kC",  /* AT_COLOR_PROMPT */
+       "kc",  /* AT_COLOR_RSCROLL */
+       "kG",  /* AT_COLOR_SEARCH */
+       "",    /* AT_COLOR_HEADER */
+       "",    /* AT_UNDERLINE */
+       "",    /* AT_BOLD */
+       "",    /* AT_BLINK */
+       "",    /* AT_STANDOUT */
+};
+
+/* State while processing an ANSI escape sequence */
+struct ansi_state {
+       int hindex;   /* Index into hyperlink prefix */
+       int hlink;    /* Processing hyperlink address? */
+       int prev_esc; /* Prev char was ESC (to detect ESC-\ seq) */
+};
+
 /*
  * Initialize from environment variables.
  */
        public void
-init_line()
+init_line(VOID_PARAM)
 {
+       int ax;
+
        end_ansi_chars = lgetenv("LESSANSIENDCHARS");
-       if (end_ansi_chars == NULL || *end_ansi_chars == '\0')
+       if (isnullenv(end_ansi_chars))
                end_ansi_chars = "m";
 
        mid_ansi_chars = lgetenv("LESSANSIMIDCHARS");
-       if (mid_ansi_chars == NULL || *mid_ansi_chars == '\0')
-               mid_ansi_chars = "0123456789;[?!\"'#%()*+ ";
+       if (isnullenv(mid_ansi_chars))
+               mid_ansi_chars = "0123456789:;[?!\"'#%()*+ ";
 
-       linebuf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char));
-       attr = (char *) ecalloc(LINEBUF_SIZE, sizeof(char));
+       linebuf.buf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char));
+       linebuf.attr = (int *) ecalloc(LINEBUF_SIZE, sizeof(int));
        size_linebuf = LINEBUF_SIZE;
+       xbuf_init(&shifted_ansi);
+       xbuf_init(&last_ansi);
+       for (ax = 0;  ax < NUM_LAST_ANSIS;  ax++)
+               xbuf_init(&last_ansis[ax]);
+       curr_last_ansi = 0;
 }
 
 /*
  * Expand the line buffer.
  */
        static int
-expand_linebuf()
+expand_linebuf(VOID_PARAM)
 {
        /* Double the size of the line buffer. */
        int new_size = size_linebuf * 2;
-
-       /* Just realloc to expand the buffer, if we can. */
-#if HAVE_REALLOC
-       char *new_buf = (char *) realloc(linebuf, new_size);
-       char *new_attr = (char *) realloc(attr, new_size);
-#else
        char *new_buf = (char *) calloc(new_size, sizeof(char));
-       char *new_attr = (char *) calloc(new_size, sizeof(char));
-#endif
+       int *new_attr = (int *) calloc(new_size, sizeof(int));
        if (new_buf == NULL || new_attr == NULL)
        {
                if (new_attr != NULL)
@@ -110,25 +175,15 @@ expand_linebuf()
                        free(new_buf);
                return 1;
        }
-#if HAVE_REALLOC
-       /*
-        * We realloc'd the buffers; they already have the old contents.
-        */
-       #if 0
-       memset(new_buf + size_linebuf, 0, new_size - size_linebuf);
-       memset(new_attr + size_linebuf, 0, new_size - size_linebuf);
-       #endif
-#else
        /*
         * We just calloc'd the buffers; copy the old contents.
         */
-       memcpy(new_buf, linebuf, size_linebuf * sizeof(char));
-       memcpy(new_attr, attr, size_linebuf * sizeof(char));
-       free(attr);
-       free(linebuf);
-#endif
-       linebuf = new_buf;
-       attr = new_attr;
+       memcpy(new_buf, linebuf.buf, size_linebuf * sizeof(char));
+       memcpy(new_attr, linebuf.attr, size_linebuf * sizeof(int));
+       free(linebuf.attr);
+       free(linebuf.buf);
+       linebuf.buf = new_buf;
+       linebuf.attr = new_attr;
        size_linebuf = new_size;
        return 0;
 }
@@ -144,33 +199,127 @@ is_ascii_char(ch)
 }
 
 /*
+ */
+       static void
+inc_end_column(w)
+       int w;
+{
+       if (end_column > right_column && w > 0)
+       {
+               right_column = end_column;
+               right_curr = linebuf.end;
+       }
+       end_column += w;
+}
+
+/*
  * Rewind the line buffer.
  */
        public void
-prewind()
+prewind(VOID_PARAM)
 {
-       curr = 0;
-       column = 0;
+       int ax;
+
+       linebuf.print = 6; /* big enough for longest UTF-8 sequence */
+       linebuf.pfx_end = 0;
+       for (linebuf.end = 0; linebuf.end < linebuf.print; linebuf.end++)
+       {
+               linebuf.buf[linebuf.end] = '\0';
+               linebuf.attr[linebuf.end] = 0;
+       }
+
+       end_column = 0;
+       right_curr = 0;
+       right_column = 0;
        cshift = 0;
        overstrike = 0;
        last_overstrike = AT_NORMAL;
        mbc_buf_len = 0;
        is_null_line = 0;
        pendc = '\0';
-       lmargin = 0;
-       if (status_col)
-               lmargin += 1;
+       in_hilite = 0;
+       ansi_in_line = 0;
+       hlink_in_line = 0;
+       line_mark_attr = 0;
+       xbuf_reset(&shifted_ansi);
+       xbuf_reset(&last_ansi);
+       for (ax = 0;  ax < NUM_LAST_ANSIS;  ax++)
+               xbuf_reset(&last_ansis[ax]);
+       curr_last_ansi = 0;
+}
+
+/*
+ * Set a character in the line buffer.
+ */
+       static void
+set_linebuf(n, ch, attr)
+       int n;
+       char ch;
+       int attr;
+{
+       linebuf.buf[n] = ch;
+       linebuf.attr[n] = attr;
+}
+
+/*
+ * Append a character to the line buffer.
+ */
+       static void
+add_linebuf(ch, attr, w)
+       char ch;
+       int attr;
+       int w;
+{
+       set_linebuf(linebuf.end++, ch, attr);
+       inc_end_column(w);
 }
 
 /*
- * Insert the line number (of the given position) into the line buffer.
+ * Append a string to the line buffer.
+ */
+       static void
+addstr_linebuf(s, attr, cw)
+       char *s;
+       int attr;
+       int cw;
+{
+       for ( ;  *s != '\0';  s++)
+               add_linebuf(*s, attr, cw);
+}
+
+/*
+ * Set a character in the line prefix buffer.
+ */
+       static void
+set_pfx(n, ch, attr)
+       int n;
+       char ch;
+       int attr;
+{
+       linebuf.pfx[n] = ch;
+       linebuf.pfx_attr[n] = attr;
+}
+
+/*
+ * Append a character to the line prefix buffer.
+ */
+       static void
+add_pfx(ch, attr)
+       char ch;
+       int attr;
+{
+       set_pfx(linebuf.pfx_end++, ch, attr);
+}
+
+/*
+ * Insert the status column and line number into the line buffer.
  */
        public void
-plinenum(pos)
+plinestart(pos)
        POSITION pos;
 {
-       register LINENUM linenum = 0;
-       register int i;
+       LINENUM linenum = 0;
+       int i;
 
        if (linenums == OPT_ONPLUS)
        {
@@ -178,7 +327,7 @@ plinenum(pos)
                 * Get the line number and put it in the current line.
                 * {{ Note: since find_linenum calls forw_raw_line,
                 *    it may seek in the input file, requiring the caller 
-                *    of plinenum to re-seek if necessary. }}
+                *    of plinestart to re-seek if necessary. }}
                 * {{ Since forw_raw_line modifies linebuf, we must
                 *    do this first, before storing anything in linebuf. }}
                 */
@@ -188,176 +337,76 @@ plinenum(pos)
        /*
         * Display a status column if the -J option is set.
         */
-       if (status_col)
+       if (status_col || status_line)
        {
-               linebuf[curr] = ' ';
-               if (start_attnpos != NULL_POSITION &&
-                   pos >= start_attnpos && pos < end_attnpos)
-                       attr[curr] = AT_NORMAL|AT_HILITE;
-               else
-                       attr[curr] = AT_NORMAL;
-               curr++;
-               column++;
+               char c = posmark(pos);
+               if (c != 0)
+                       line_mark_attr = AT_HILITE|AT_COLOR_MARK;
+               else if (start_attnpos != NULL_POSITION &&
+                        pos >= start_attnpos && pos <= end_attnpos)
+                       line_mark_attr = AT_HILITE|AT_COLOR_ATTN;
+               if (status_col)
+               {
+                       add_pfx(c ? c : ' ', line_mark_attr); /* column 0: status */
+                       while (linebuf.pfx_end < status_col_width)
+                               add_pfx(' ', AT_NORMAL);
+               }
        }
+
        /*
         * Display the line number at the start of each line
         * if the -N option is set.
         */
        if (linenums == OPT_ONPLUS)
        {
-               char buf[INT_STRLEN_BOUND(pos) + 2];
-               int n;
-
-               linenumtoa(linenum, buf);
-               n = strlen(buf);
-               if (n < MIN_LINENUM_WIDTH)
-                       n = MIN_LINENUM_WIDTH;
-               sprintf(linebuf+curr, "%*s ", n, buf);
-               n++;  /* One space after the line number. */
-               for (i = 0; i < n; i++)
-                       attr[curr+i] = AT_NORMAL;
-               curr += n;
-               column += n;
-               lmargin += n;
-       }
+               char buf[INT_STRLEN_BOUND(linenum) + 2];
+               int len;
 
-       /*
-        * Append enough spaces to bring us to the lmargin.
-        */
-       while (column < lmargin)
-       {
-               linebuf[curr] = ' ';
-               attr[curr++] = AT_NORMAL;
-               column++;
+               linenum = vlinenum(linenum);
+               if (linenum == 0)
+                       len = 0;
+               else
+               {
+                       linenumtoa(linenum, buf);
+                       len = (int) strlen(buf);
+               }
+               for (i = 0; i < linenum_width - len; i++)
+                       add_pfx(' ', AT_NORMAL);
+               for (i = 0; i < len; i++)
+                       add_pfx(buf[i], AT_BOLD|AT_COLOR_LINENUM);
+               add_pfx(' ', AT_NORMAL);
        }
+       end_column = linebuf.pfx_end;
 }
 
 /*
- * Shift the input line left.
- * This means discarding N printable chars at the start of the buffer.
+ * Return the width of the line prefix (status column and line number).
+ * {{ Actual line number can be wider than linenum_width. }}
  */
-       static void
-pshift(shift)
-       int shift;
+       public int
+line_pfx_width(VOID_PARAM)
 {
-       LWCHAR prev_ch = 0;
-       unsigned char c;
-       int shifted = 0;
-       int to;
-       int from;
-       int len;
-       int width;
-       int prev_attr;
-       int next_attr;
-
-       if (shift > column - lmargin)
-               shift = column - lmargin;
-       if (shift > curr - lmargin)
-               shift = curr - lmargin;
-
-       to = from = lmargin;
-       /*
-        * We keep on going when shifted == shift
-        * to get all combining chars.
-        */
-       while (shifted <= shift && from < curr)
-       {
-               c = linebuf[from];
-               if (ctldisp == OPT_ONPLUS && IS_CSI_START(c))
-               {
-                       /* Keep cumulative effect.  */
-                       linebuf[to] = c;
-                       attr[to++] = attr[from++];
-                       while (from < curr && linebuf[from])
-                       {
-                               linebuf[to] = linebuf[from];
-                               attr[to++] = attr[from];
-                               if (!is_ansi_middle(linebuf[from++]))
-                                       break;
-                       } 
-                       continue;
-               }
-
-               width = 0;
-
-               if (!IS_ASCII_OCTET(c) && utf_mode)
-               {
-                       /* Assumes well-formedness validation already done.  */
-                       LWCHAR ch;
-
-                       len = utf_len(c);
-                       if (from + len > curr)
-                               break;
-                       ch = get_wchar(linebuf + from);
-                       if (!is_composing_char(ch) && !is_combining_char(prev_ch, ch))
-                               width = is_wide_char(ch) ? 2 : 1;
-                       prev_ch = ch;
-               } else
-               {
-                       len = 1;
-                       if (c == '\b')
-                               /* XXX - Incorrect if several '\b' in a row.  */
-                               width = (utf_mode && is_wide_char(prev_ch)) ? -2 : -1;
-                       else if (!control_char(c))
-                               width = 1;
-                       prev_ch = 0;
-               }
-
-               if (width == 2 && shift - shifted == 1) {
-                       /* Should never happen when called by pshift_all().  */
-                       attr[to] = attr[from];
-                       /*
-                        * Assume a wide_char will never be the first half of a
-                        * combining_char pair, so reset prev_ch in case we're
-                        * followed by a '\b'.
-                        */
-                       prev_ch = linebuf[to++] = ' ';
-                       from += len;
-                       shifted++;
-                       continue;
-               }
-
-               /* Adjust width for magic cookies. */
-               prev_attr = (to > 0) ? attr[to-1] : AT_NORMAL;
-               next_attr = (from + len < curr) ? attr[from + len] : prev_attr;
-               if (!is_at_equiv(attr[from], prev_attr) && 
-                       !is_at_equiv(attr[from], next_attr))
-               {
-                       width += attr_swidth(attr[from]);
-                       if (from + len < curr)
-                               width += attr_ewidth(attr[from]);
-                       if (is_at_equiv(prev_attr, next_attr))
-                       {
-                               width += attr_ewidth(prev_attr);
-                               if (from + len < curr)
-                                       width += attr_swidth(next_attr);
-                       }
-               }
-
-               if (shift - shifted < width)
-                       break;
-               from += len;
-               shifted += width;
-               if (shifted < 0)
-                       shifted = 0;
-       }
-       while (from < curr)
-       {
-               linebuf[to] = linebuf[from];
-               attr[to++] = attr[from++];
-       }
-       curr = to;
-       column -= shifted;
-       cshift += shifted;
+       int width = 0;
+       if (status_col)
+               width += status_col_width;
+       if (linenums == OPT_ONPLUS)
+               width += linenum_width + 1;
+       return width;
 }
 
 /*
- *
+ * Shift line left so that the last char is just to the left
+ * of the first visible column.
  */
        public void
-pshift_all()
+pshift_all(VOID_PARAM)
 {
-       pshift(column);
+       int i;
+       for (i = linebuf.print;  i < linebuf.end;  i++)
+               if (linebuf.attr[i] == AT_ANSI)
+                       xbuf_add(&shifted_ansi, linebuf.buf[i]);
+       linebuf.end = linebuf.print;
+       end_column = linebuf.pfx_end;
 }
 
 /*
@@ -410,24 +459,28 @@ attr_ewidth(a)
 
 /*
  * Return the printing width of a given character and attribute,
- * if the character were added to the current position in the line buffer.
+ * if the character were added after prev_ch.
  * Adding a character with a given attribute may cause an enter or exit
  * attribute sequence to be inserted, so this must be taken into account.
  */
-       static int
-pwidth(ch, a, prev_ch)
+       public int
+pwidth(ch, a, prev_ch, prev_a)
        LWCHAR ch;
        int a;
        LWCHAR prev_ch;
+       int prev_a;
 {
        int w;
 
        if (ch == '\b')
+       {
                /*
                 * Backspace moves backwards one or two positions.
-                * XXX - Incorrect if several '\b' in a row.
                 */
+               if (prev_a & (AT_ANSI|AT_BINARY))
+                       return strlen(prchar('\b'));
                return (utf_mode && is_wide_char(prev_ch)) ? -2 : -1;
+       }
 
        if (!utf_mode || is_ascii_char(ch))
        {
@@ -449,7 +502,7 @@ pwidth(ch, a, prev_ch)
                         *
                         * Some terminals, upon failure to compose a
                         * composing character with the character(s) that
-                        * precede(s) it will actually take up one column
+                        * precede(s) it will actually take up one end_column
                         * for the composing character; there isn't much
                         * we could do short of testing the (complex)
                         * composition process ourselves and printing
@@ -466,64 +519,42 @@ pwidth(ch, a, prev_ch)
        w = 1;
        if (is_wide_char(ch))
                w++;
-       if (curr > 0 && !is_at_equiv(attr[curr-1], a))
-               w += attr_ewidth(attr[curr-1]);
-       if ((apply_at_specials(a) != AT_NORMAL) &&
-           (curr == 0 || !is_at_equiv(attr[curr-1], a)))
+       if (linebuf.end > 0 && !is_at_equiv(linebuf.attr[linebuf.end-1], a))
+               w += attr_ewidth(linebuf.attr[linebuf.end-1]);
+       if (apply_at_specials(a) != AT_NORMAL &&
+           (linebuf.end == 0 || !is_at_equiv(linebuf.attr[linebuf.end-1], a)))
                w += attr_swidth(a);
        return (w);
 }
 
 /*
  * Delete to the previous base character in the line buffer.
- * Return 1 if one is found.
- */
-       static int
-backc()
-{
-       LWCHAR prev_ch;
-       char *p = linebuf + curr;
-       LWCHAR ch = step_char(&p, -1, linebuf + lmargin);
-       int width;
-
-       /* This assumes that there is no '\b' in linebuf.  */
-       while (   curr > lmargin
-              && column > lmargin
-              && (!(attr[curr - 1] & (AT_ANSI|AT_BINARY))))
-       {
-               curr = p - linebuf;
-               prev_ch = step_char(&p, -1, linebuf + lmargin);
-               width = pwidth(ch, attr[curr], prev_ch);
-               column -= width;
-               if (width > 0)
-                       return 1;
-               ch = prev_ch;
-       }
-
-       return 0;
-}
-
-/*
- * Are we currently within a recognized ANSI escape sequence?
  */
        static int
-in_ansi_esc_seq()
+backc(VOID_PARAM)
 {
+       LWCHAR ch;
        char *p;
 
-       /*
-        * Search backwards for either an ESC (which means we ARE in a seq);
-        * or an end char (which means we're NOT in a seq).
-        */
-       for (p = &linebuf[curr];  p > linebuf; )
+       if (linebuf.end == 0)
+               return (0);
+       p = &linebuf.buf[linebuf.end];
+       ch = step_char(&p, -1, linebuf.buf);
+       /* Skip back to the next nonzero-width char. */
+       while (p > linebuf.buf)
        {
-               LWCHAR ch = step_char(&p, -1, linebuf);
-               if (IS_CSI_START(ch))
-                       return (1);
-               if (!is_ansi_middle(ch))
-                       return (0);
+               LWCHAR prev_ch;
+               int width;
+               linebuf.end = (int) (p - linebuf.buf);
+               prev_ch = step_char(&p, -1, linebuf.buf);
+               width = pwidth(ch, linebuf.attr[linebuf.end], prev_ch, linebuf.attr[linebuf.end-1]);
+               end_column -= width;
+               /* {{ right_column? }} */
+               if (width > 0)
+                       break;
+               ch = prev_ch;
        }
-       return (0);
+       return (1);
 }
 
 /*
@@ -539,7 +570,7 @@ is_ansi_end(ch)
 }
 
 /*
- *
+ * Can a char appear in an ANSI escape sequence, before the end char?
  */
        public int
 is_ansi_middle(ch)
@@ -553,9 +584,109 @@ is_ansi_middle(ch)
 }
 
 /*
+ * Skip past an ANSI escape sequence.
+ * pp is initially positioned just after the CSI_START char.
+ */
+       public void
+skip_ansi(pansi, pp, limit)
+       struct ansi_state *pansi;
+       char **pp;
+       constant char *limit;
+{
+       LWCHAR c;
+       do {
+               c = step_char(pp, +1, limit);
+       } while (*pp < limit && ansi_step(pansi, c) == ANSI_MID);
+       /* Note that we discard final char, for which is_ansi_end is true. */
+}
+
+/*
+ * Determine if a character starts an ANSI escape sequence.
+ * If so, return an ansi_state struct; otherwise return NULL.
+ */
+       public struct ansi_state *
+ansi_start(ch)
+       LWCHAR ch;
+{
+       struct ansi_state *pansi;
+
+       if (!IS_CSI_START(ch))
+               return NULL;
+       pansi = ecalloc(1, sizeof(struct ansi_state));
+       pansi->hindex = 0;
+       pansi->hlink = 0;
+       pansi->prev_esc = 0;
+       return pansi;
+}
+
+/*
+ * Determine whether the next char in an ANSI escape sequence
+ * ends the sequence.
+ */
+       public int
+ansi_step(pansi, ch)
+       struct ansi_state *pansi;
+       LWCHAR ch;
+{
+       if (pansi->hlink)
+       {
+               /* Hyperlink ends with \7 or ESC-backslash. */
+               if (ch == '\7')
+                       return ANSI_END;
+               if (pansi->prev_esc && ch == '\\')
+                       return ANSI_END;
+               pansi->prev_esc = (ch == ESC);
+               return ANSI_MID;
+       }
+       if (pansi->hindex >= 0)
+       {
+               static char hlink_prefix[] = ESCS "]8;";
+               if (ch == hlink_prefix[pansi->hindex] ||
+                   (pansi->hindex == 0 && IS_CSI_START(ch)))
+               {
+                       pansi->hindex++;
+                       if (hlink_prefix[pansi->hindex] == '\0')
+                               pansi->hlink = 1; /* now processing hyperlink addr */
+                       return ANSI_MID;
+               }
+               pansi->hindex = -1; /* not a hyperlink */
+       }
+       /* Check for SGR sequences */
+       if (is_ansi_middle(ch))
+               return ANSI_MID;
+       if (is_ansi_end(ch))
+               return ANSI_END;
+       return ANSI_ERR;
+}
+
+/*
+ * Free an ansi_state structure.
+ */
+       public void
+ansi_done(pansi)
+       struct ansi_state *pansi;
+{
+       free(pansi);
+}
+
+/*
+ * Will w characters in attribute a fit on the screen?
+ */
+       static int
+fits_on_screen(w, a)
+       int w;
+       int a;
+{
+       if (ctldisp == OPT_ON)
+               /* We're not counting, so say that everything fits. */
+               return 1;
+       return (end_column - cshift + w + attr_ewidth(a) <= sc_width);
+}
+
+/*
  * Append a character and attribute to the line buffer.
  */
-#define        STORE_CHAR(ch,a,rep,pos) \
+#define STORE_CHAR(ch,a,rep,pos) \
        do { \
                if (store_char((ch),(a),(rep),(pos))) return (1); \
        } while (0)
@@ -568,17 +699,31 @@ store_char(ch, a, rep, pos)
        POSITION pos;
 {
        int w;
+       int i;
        int replen;
        char cs;
 
-       w = (a & (AT_UNDERLINE|AT_BOLD));       /* Pre-use w.  */
-       if (w != AT_NORMAL)
-               last_overstrike = w;
+       i = (a & (AT_UNDERLINE|AT_BOLD));
+       if (i != AT_NORMAL)
+               last_overstrike = i;
 
 #if HILITE_SEARCH
        {
                int matches;
-               if (is_hilited(pos, pos+1, 0, &matches))
+               int resend_last = 0;
+               int hl_attr;
+
+               if (pos == NULL_POSITION)
+               {
+                       /* Color the prompt unless it has ansi sequences in it. */
+                       hl_attr = ansi_in_line ? 0 : AT_STANDOUT|AT_COLOR_PROMPT;
+               } else
+               {
+                       hl_attr = is_hilited_attr(pos, pos+1, 0, &matches);
+                       if (hl_attr == 0 && status_line)
+                               hl_attr = line_mark_attr;
+               }
+               if (hl_attr)
                {
                        /*
                         * This character should be highlighted.
@@ -586,46 +731,46 @@ store_char(ch, a, rep, pos)
                         */
                        if (a != AT_ANSI)
                        {
-                               if (highest_hilite != NULL_POSITION &&
-                                   pos > highest_hilite)
-                                       highest_hilite = pos;
-                               a |= AT_HILITE;
+                               if (highest_hilite != NULL_POSITION && pos != NULL_POSITION && pos > highest_hilite)
+                                       highest_hilite = pos;
+                               a |= hl_attr;
+                       }
+                       in_hilite = 1;
+               } else 
+               {
+                       if (in_hilite)
+                       {
+                               /*
+                                * This is the first non-hilited char after a hilite.
+                                * Resend the last ANSI seq to restore color.
+                                */
+                               resend_last = 1;
+                       }
+                       in_hilite = 0;
+               }
+               if (resend_last)
+               {
+                       int ai;
+                       for (ai = 0;  ai < NUM_LAST_ANSIS;  ai++)
+                       {
+                               int ax = (curr_last_ansi + ai) % NUM_LAST_ANSIS;
+                               for (i = 0;  i < last_ansis[ax].end;  i++)
+                                       STORE_CHAR(last_ansis[ax].data[i], AT_ANSI, NULL, pos);
                        }
                }
        }
 #endif
 
-       if (ctldisp == OPT_ONPLUS && in_ansi_esc_seq())
-       {
-               if (!is_ansi_end(ch) && !is_ansi_middle(ch)) {
-                       /* Remove whole unrecognized sequence.  */
-                       char *p = &linebuf[curr];
-                       LWCHAR bch;
-                       do {
-                               bch = step_char(&p, -1, linebuf);
-                       } while (p > linebuf && !IS_CSI_START(bch));
-                       curr = p - linebuf;
-                       return 0;
-               }
-               a = AT_ANSI;    /* Will force re-AT_'ing around it.  */
+       if (a == AT_ANSI) {
                w = 0;
-       }
-       else if (ctldisp == OPT_ONPLUS && IS_CSI_START(ch))
-       {
-               a = AT_ANSI;    /* Will force re-AT_'ing around it.  */
-               w = 0;
-       }
-       else
-       {
-               char *p = &linebuf[curr];
-               LWCHAR prev_ch = step_char(&p, -1, linebuf);
-               w = pwidth(ch, a, prev_ch);
+       } else {
+               char *p = &linebuf.buf[linebuf.end];
+               LWCHAR prev_ch = (linebuf.end > 0) ? step_char(&p, -1, linebuf.buf) : 0;
+               int prev_a = (linebuf.end > 0) ? linebuf.attr[linebuf.end-1] : 0;
+               w = pwidth(ch, a, prev_ch, prev_a);
        }
 
-       if (ctldisp != OPT_ON && column + w + attr_ewidth(a) > sc_width)
-               /*
-                * Won't fit on screen.
-                */
+       if (!fits_on_screen(w, a))
                return (1);
 
        if (rep == NULL)
@@ -637,7 +782,7 @@ store_char(ch, a, rep, pos)
        {
                replen = utf_len(rep[0]);
        }
-       if (curr + replen >= size_linebuf-6)
+       if (linebuf.end + replen >= size_linebuf-6)
        {
                /*
                 * Won't fit in line buffer.
@@ -647,21 +792,70 @@ store_char(ch, a, rep, pos)
                        return (1);
        }
 
-       while (replen-- > 0)
+       if (cshift == hshift && shifted_ansi.end > 0)
+       {
+               /* Copy shifted ANSI sequences to beginning of line. */
+               for (i = 0;  i < shifted_ansi.end;  i++)
+                       add_linebuf(shifted_ansi.data[i], AT_ANSI, 0);
+               xbuf_reset(&shifted_ansi);
+       }
+       /* Add the char to the buf, even if we will left-shift it next. */
+       inc_end_column(w);
+       for (i = 0;  i < replen;  i++)
+               add_linebuf(*rep++, a, 0);
+
+       if (cshift < hshift)
        {
-               linebuf[curr] = *rep++;
-               attr[curr] = a;
-               curr++;
+               /* We haven't left-shifted enough yet. */
+               if (a == AT_ANSI)
+                       xbuf_add(&shifted_ansi, ch); /* Save ANSI attributes */
+               if (linebuf.end > linebuf.print)
+               {
+                       /* Shift left enough to put last byte of this char at print-1. */
+                       int i;
+                       for (i = 0; i < linebuf.print; i++)
+                       {
+                               linebuf.buf[i] = linebuf.buf[i+replen];
+                               linebuf.attr[i] = linebuf.attr[i+replen];
+                       }
+                       linebuf.end -= replen;
+                       cshift += w;
+                       /*
+                        * If the char we just left-shifted was double width,
+                        * the 2 spaces we shifted may be too much.
+                        * Represent the "half char" at start of line with a highlighted space.
+                        */
+                       while (cshift > hshift)
+                       {
+                               add_linebuf(' ', rscroll_attr, 0);
+                               cshift--;
+                       }
+               }
        }
-       column += w;
        return (0);
 }
 
+#define STORE_STRING(s,a,pos) \
+       do { if (store_string((s),(a),(pos))) return (1); } while (0)
+
+       static int
+store_string(s, a, pos)
+       char *s;
+       int a;
+       POSITION pos;
+{
+       if (!fits_on_screen(strlen(s), a))
+               return 1;
+       for ( ;  *s != 0;  s++)
+               STORE_CHAR(*s, a, NULL, pos);
+       return 0;
+}
+
 /*
  * Append a tab to the line buffer.
  * Store spaces to represent the tab.
  */
-#define        STORE_TAB(a,pos) \
+#define STORE_TAB(a,pos) \
        do { if (store_tab((a),(pos))) return (1); } while (0)
 
        static int
@@ -669,23 +863,20 @@ store_tab(attr, pos)
        int attr;
        POSITION pos;
 {
-       int to_tab = column + cshift - lmargin;
-       int i;
+       int to_tab = end_column - linebuf.pfx_end;
 
        if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1])
                to_tab = tabdefault -
                     ((to_tab - tabstops[ntabstops-1]) % tabdefault);
        else
        {
+               int i;
                for (i = ntabstops - 2;  i >= 0;  i--)
                        if (to_tab >= tabstops[i])
                                break;
                to_tab = tabstops[i+1] - to_tab;
        }
 
-       if (column + to_tab - 1 + pwidth(' ', attr, 0) + attr_ewidth(attr) > sc_width)
-               return 1;
-
        do {
                STORE_CHAR(' ', attr, " ", pos);
        } while (--to_tab > 0);
@@ -697,27 +888,13 @@ store_tab(attr, pos)
 
        static int
 store_prchar(c, pos)
-       char c;
+       LWCHAR c;
        POSITION pos;
 {
-       char *s;
-
        /*
         * Convert to printable representation.
         */
-       s = prchar(c);
-
-       /*
-        * Make sure we can get the entire representation
-        * of the character on this line.
-        */
-       if (column + (int) strlen(s) - 1 +
-            pwidth(' ', binattr, 0) + attr_ewidth(binattr) > sc_width)
-               return 1;
-
-       for ( ;  *s != 0;  s++)
-               STORE_CHAR(*s, AT_BINARY, NULL, pos);
-
+       STORE_STRING(prchar(c), AT_BINARY|AT_COLOR_CTRL, pos);
        return 0;
 }
 
@@ -730,7 +907,6 @@ flush_mbc_buf(pos)
        for (i = 0; i < mbc_buf_index; i++)
                if (store_prchar(mbc_buf[i], pos))
                        return mbc_buf_index - i;
-
        return 0;
 }
 
@@ -741,13 +917,15 @@ flush_mbc_buf(pos)
  */
        public int
 pappend(c, pos)
-       char c;
+       int c;
        POSITION pos;
 {
        int r;
 
        if (pendc)
        {
+               if (c == '\r' && pendc == '\r')
+                       return (0);
                if (do_append(pendc, NULL, pendpos))
                        /*
                         * Oops.  We've probably lost the char which
@@ -781,7 +959,7 @@ pappend(c, pos)
 
        if (!utf_mode)
        {
-               r = do_append((LWCHAR) c, NULL, pos);
+               r = do_append(c, NULL, pos);
        } else
        {
                /* Perform strict validation in all possible cases. */
@@ -791,7 +969,7 @@ pappend(c, pos)
                        mbc_buf_index = 1;
                        *mbc_buf = c;
                        if (IS_ASCII_OCTET(c))
-                               r = do_append((LWCHAR) c, NULL, pos);
+                               r = do_append(c, NULL, pos);
                        else if (IS_UTF8_LEAD(c))
                        {
                                mbc_buf_len = utf_len(c);
@@ -805,7 +983,7 @@ pappend(c, pos)
                        mbc_buf[mbc_buf_index++] = c;
                        if (mbc_buf_index < mbc_buf_len)
                                return (0);
-                       if (is_utf8_well_formed(mbc_buf))
+                       if (is_utf8_well_formed(mbc_buf, mbc_buf_index))
                                r = do_append(get_wchar(mbc_buf), mbc_buf, mbc_pos);
                        else
                                /* Complete, but not shortest form, sequence. */
@@ -820,19 +998,7 @@ pappend(c, pos)
                        /* Handle new char.  */
                        if (!r)
                                goto retry;
-               }
-       }
-
-       /*
-        * If we need to shift the line, do it.
-        * But wait until we get to at least the middle of the screen,
-        * so shifting it doesn't affect the chars we're currently
-        * pappending.  (Bold & underline can get messed up otherwise.)
-        */
-       if (cshift < hshift && column > sc_width / 2)
-       {
-               linebuf[curr] = '\0';
-               pshift(hshift - cshift);
+               }
        }
        if (r)
        {
@@ -843,38 +1009,106 @@ pappend(c, pos)
 }
 
        static int
-do_append(ch, rep, pos)
+store_control_char(ch, rep, pos)
        LWCHAR ch;
        char *rep;
        POSITION pos;
 {
-       register int a;
-       LWCHAR prev_ch;
-
-       a = AT_NORMAL;
+       if (ctldisp == OPT_ON)
+       {
+               /* Output the character itself. */
+               STORE_CHAR(ch, AT_NORMAL, rep, pos);
+       } else 
+       {
+               /* Output a printable representation of the character. */
+               STORE_PRCHAR((char) ch, pos);
+       }
+       return (0);
+}
 
-       if (ch == '\b')
+       static int
+store_ansi(ch, rep, pos)
+       LWCHAR ch;
+       char *rep;
+       POSITION pos;
+{
+       switch (ansi_step(line_ansi, ch))
        {
-               if (bs_mode == BS_CONTROL)
-                       goto do_control_char;
+       case ANSI_MID:
+               STORE_CHAR(ch, AT_ANSI, rep, pos);
+               if (line_ansi->hlink)
+                       hlink_in_line = 1;
+               xbuf_add(&last_ansi, ch);
+               break;
+       case ANSI_END:
+               STORE_CHAR(ch, AT_ANSI, rep, pos);
+               ansi_done(line_ansi);
+               line_ansi = NULL;
+               xbuf_add(&last_ansi, ch);
+               xbuf_set(&last_ansis[curr_last_ansi], &last_ansi);
+               xbuf_reset(&last_ansi);
+               curr_last_ansi = (curr_last_ansi + 1) % NUM_LAST_ANSIS;
+               break;
+       case ANSI_ERR:
+               {
+                       /* Remove whole unrecognized sequence.  */
+                       char *start = (cshift < hshift) ? shifted_ansi.data : linebuf.buf;
+                       int *end = (cshift < hshift) ? &shifted_ansi.end : &linebuf.end;
+                       char *p = start + *end;
+                       LWCHAR bch;
+                       do {
+                               bch = step_char(&p, -1, start);
+                       } while (p > start && !IS_CSI_START(bch));
+                       *end = (int) (p - start);
+               }
+               xbuf_reset(&last_ansi);
+               ansi_done(line_ansi);
+               line_ansi = NULL;
+               break;
+       }
+       return (0);
+} 
 
-               /*
-                * A better test is needed here so we don't
-                * backspace over part of the printed
-                * representation of a binary character.
-                */
-               if (   curr <= lmargin
-                   || column <= lmargin
-                   || (attr[curr - 1] & (AT_ANSI|AT_BINARY)))
-                       STORE_PRCHAR('\b', pos);
-               else if (bs_mode == BS_NORMAL)
-                       STORE_CHAR(ch, AT_NORMAL, NULL, pos);
-               else if (bs_mode == BS_SPECIAL)
-                       overstrike = backc();
+       static int
+store_bs(ch, rep, pos)
+       LWCHAR ch;
+       char *rep;
+       POSITION pos;
+{
+       if (bs_mode == BS_CONTROL)
+               return store_control_char(ch, rep, pos);
+       if (linebuf.end > 0 &&
+               ((linebuf.end <= linebuf.print && linebuf.buf[linebuf.end-1] == '\0') ||
+            (linebuf.end > 0 && linebuf.attr[linebuf.end - 1] & (AT_ANSI|AT_BINARY))))
+               STORE_PRCHAR('\b', pos);
+       else if (bs_mode == BS_NORMAL)
+               STORE_CHAR(ch, AT_NORMAL, NULL, pos);
+       else if (bs_mode == BS_SPECIAL)
+               overstrike = backc();
+       return 0;
+}
 
-               return 0;
+       static int
+do_append(ch, rep, pos)
+       LWCHAR ch;
+       char *rep;
+       POSITION pos;
+{
+       int a = AT_NORMAL;
+
+       if (ctldisp == OPT_ONPLUS && line_ansi == NULL)
+       {
+               line_ansi = ansi_start(ch);
+               if (line_ansi != NULL)
+                       ansi_in_line = 1;
        }
 
+       if (line_ansi != NULL)
+               return store_ansi(ch, rep, pos);
+
+       if (ch == '\b')
+               return store_bs(ch, rep, pos);
+
        if (overstrike > 0)
        {
                /*
@@ -882,12 +1116,19 @@ do_append(ch, rep, pos)
                 * in the line buffer.  This will cause either 
                 * underline (if a "_" is overstruck), 
                 * bold (if an identical character is overstruck),
-                * or just deletion of the character in the buffer.
+                * or just replacing the character in the buffer.
                 */
+               LWCHAR prev_ch;
                overstrike = utf_mode ? -1 : 0;
-               /* To be correct, this must be a base character.  */
-               prev_ch = get_wchar(linebuf + curr);
-               a = attr[curr];
+               if (utf_mode)
+               {
+                       /* To be correct, this must be a base character.  */
+                       prev_ch = get_wchar(&linebuf.buf[linebuf.end]);
+               } else
+               {
+                       prev_ch = (unsigned char) linebuf.buf[linebuf.end];
+               }
+               a = linebuf.attr[linebuf.end];
                if (ch == prev_ch)
                {
                        /*
@@ -911,7 +1152,7 @@ do_append(ch, rep, pos)
                {
                        a |= AT_UNDERLINE;
                        ch = prev_ch;
-                       rep = linebuf + curr;
+                       rep = &linebuf.buf[linebuf.end];
                } else if (prev_ch == '_')
                {
                        a |= AT_UNDERLINE;
@@ -920,14 +1161,14 @@ do_append(ch, rep, pos)
        } else if (overstrike < 0)
        {
                if (   is_composing_char(ch)
-                   || is_combining_char(get_wchar(linebuf + curr), ch))
+                   || is_combining_char(get_wchar(&linebuf.buf[linebuf.end]), ch))
                        /* Continuation of the same overstrike.  */
                        a = last_overstrike;
                else
                        overstrike = 0;
        }
 
-       if (ch == '\t') 
+       if (ch == '\t')
        {
                /*
                 * Expand a tab into spaces.
@@ -935,49 +1176,32 @@ do_append(ch, rep, pos)
                switch (bs_mode)
                {
                case BS_CONTROL:
-                       goto do_control_char;
+                       return store_control_char(ch, rep, pos);
                case BS_NORMAL:
                case BS_SPECIAL:
                        STORE_TAB(a, pos);
                        break;
                }
-       } else if ((!utf_mode || is_ascii_char(ch)) && control_char((char)ch))
+               return (0);
+       }
+       if ((!utf_mode || is_ascii_char(ch)) && control_char((char)ch))
        {
-       do_control_char:
-               if (ctldisp == OPT_ON || (ctldisp == OPT_ONPLUS && IS_CSI_START(ch)))
-               {
-                       /*
-                        * Output as a normal character.
-                        */
-                       STORE_CHAR(ch, AT_NORMAL, rep, pos);
-               } else 
-               {
-                       STORE_PRCHAR((char) ch, pos);
-               }
+               return store_control_char(ch, rep, pos);
        } else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch))
        {
-               char *s;
-
-               s = prutfchar(ch);
-
-               if (column + (int) strlen(s) - 1 +
-                   pwidth(' ', binattr, 0) + attr_ewidth(binattr) > sc_width)
-                       return (1);
-
-               for ( ;  *s != 0;  s++)
-                       STORE_CHAR(*s, AT_BINARY, NULL, pos);
-       } else
+               STORE_STRING(prutfchar(ch), AT_BINARY, pos);
+       } else
        {
                STORE_CHAR(ch, a, rep, pos);
        }
-       return (0);
+       return (0);
 }
 
 /*
  *
  */
        public int
-pflushmbc()
+pflushmbc(VOID_PARAM)
 {
        int r = 0;
 
@@ -991,11 +1215,25 @@ pflushmbc()
 }
 
 /*
+ * Switch to normal attribute at end of line.
+ */
+       static void
+add_attr_normal(VOID_PARAM)
+{
+       if (ctldisp != OPT_ONPLUS || !is_ansi_end('m'))
+               return;
+       addstr_linebuf("\033[m", AT_ANSI, 0);
+       if (hlink_in_line) /* Don't send hyperlink clear if we know we don't need to. */
+               addstr_linebuf("\033]8;;\033\\", AT_ANSI, 0);
+}
+
+/*
  * Terminate the line in the line buffer.
  */
        public void
-pdone(endline, forw)
+pdone(endline, chopped, forw)
        int endline;
+       int chopped;
        int forw;
 {
        (void) pflushmbc();
@@ -1008,21 +1246,42 @@ pdone(endline, forw)
                 */
                (void) do_append(pendc, NULL, pendpos);
 
-       /*
-        * Make sure we've shifted the line, if we need to.
-        */
-       if (cshift < hshift)
-               pshift(hshift - cshift);
-
-       if (ctldisp == OPT_ONPLUS && is_ansi_end('m'))
+       if (chopped && rscroll_char)
        {
-               /* Switch to normal attribute at end of line. */
-               char *p = "\033[m";
-               for ( ;  *p != '\0';  p++)
+               /*
+                * Display the right scrolling char.
+                * If we've already filled the rightmost screen char 
+                * (in the buffer), overwrite it.
+                */
+               if (end_column >= sc_width + cshift)
                {
-                       linebuf[curr] = *p;
-                       attr[curr++] = AT_ANSI;
+                       /* We've already written in the rightmost char. */
+                       end_column = right_column;
+                       linebuf.end = right_curr;
                }
+               add_attr_normal();
+               while (end_column < sc_width-1 + cshift) 
+               {
+                       /*
+                        * Space to last (rightmost) char on screen.
+                        * This may be necessary if the char we overwrote
+                        * was double-width.
+                        */
+                       add_linebuf(' ', rscroll_attr, 1);
+               }
+               /* Print rscroll char. It must be single-width. */
+               add_linebuf(rscroll_char, rscroll_attr, 1);
+       } else
+       {
+               add_attr_normal();
+       }
+
+       /*
+        * If we're coloring a status line, fill out the line with spaces.
+        */
+       if (status_line && line_mark_attr != 0) {
+               while (end_column +1 < sc_width + cshift)
+                       add_linebuf(' ', line_mark_attr, 1);
        }
 
        /*
@@ -1038,13 +1297,11 @@ pdone(endline, forw)
         * the next line is blank.  In that case the single newline output for
         * that blank line would be ignored!)
         */
-       if (column < sc_width || !auto_wrap || (endline && ignaw) || ctldisp == OPT_ON)
+       if (end_column < sc_width + cshift || !auto_wrap || (endline && ignaw) || ctldisp == OPT_ON)
        {
-               linebuf[curr] = '\n';
-               attr[curr] = AT_NORMAL;
-               curr++;
+               add_linebuf('\n', AT_NORMAL, 0);
        } 
-       else if (ignaw && column >= sc_width && forw)
+       else if (ignaw && end_column >= sc_width + cshift && forw)
        {
                /*
                 * Terminals with "ignaw" don't wrap until they *really* need
@@ -1060,24 +1317,34 @@ pdone(endline, forw)
                 * char on the next line.  We don't need to do this "nudge" 
                 * at the top of the screen anyway.
                 */
-               linebuf[curr] = ' ';
-               attr[curr++] = AT_NORMAL;
-               linebuf[curr] = '\b'; 
-               attr[curr++] = AT_NORMAL;
+               add_linebuf(' ', AT_NORMAL, 1);
+               add_linebuf('\b', AT_NORMAL, -1);
        }
-       linebuf[curr] = '\0';
-       attr[curr] = AT_NORMAL;
+       set_linebuf(linebuf.end, '\0', AT_NORMAL);
 }
 
 /*
- *
+ * Set an attribute on each char of the line in the line buffer.
  */
        public void
-set_status_col(c)
-       char c;
+set_attr_line(a)
+       int a;
 {
-       linebuf[0] = c;
-       attr[0] = AT_NORMAL|AT_HILITE;
+       int i;
+
+       for (i = linebuf.print;  i < linebuf.end;  i++)
+               linebuf.attr[i] |= a;
+}
+
+/*
+ * Set the char to be displayed in the status column.
+ */
+       public void
+set_status_col(c, attr)
+       int c;
+       int attr;
+{
+       set_pfx(0, c, attr);
 }
 
 /*
@@ -1087,8 +1354,8 @@ set_status_col(c)
  */
        public int
 gline(i, ap)
-       register int i;
-       register int *ap;
+       int i;
+       int *ap;
 {
        if (is_null_line)
        {
@@ -1110,15 +1377,21 @@ gline(i, ap)
                return i ? '\0' : '\n';
        }
 
-       *ap = attr[i];
-       return (linebuf[i] & 0xFF);
+       if (i < linebuf.pfx_end)
+       {
+               *ap = linebuf.pfx_attr[i];
+               return linebuf.pfx[i];
+       }
+       i += linebuf.print - linebuf.pfx_end;
+       *ap = linebuf.attr[i];
+       return (linebuf.buf[i] & 0xFF);
 }
 
 /*
  * Indicate that there is no current line.
  */
        public void
-null_line()
+null_line(VOID_PARAM)
 {
        is_null_line = 1;
        cshift = 0;
@@ -1135,8 +1408,8 @@ forw_raw_line(curr_pos, linep, line_lenp)
        char **linep;
        int *line_lenp;
 {
-       register int n;
-       register int c;
+       int n;
+       int c;
        POSITION new_pos;
 
        if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
@@ -1163,12 +1436,12 @@ forw_raw_line(curr_pos, linep, line_lenp)
                                break;
                        }
                }
-               linebuf[n++] = c;
+               linebuf.buf[n++] = c;
                c = ch_forw_get();
        }
-       linebuf[n] = '\0';
+       linebuf.buf[n] = '\0';
        if (linep != NULL)
-               *linep = linebuf;
+               *linep = linebuf.buf;
        if (line_lenp != NULL)
                *line_lenp = n;
        return (new_pos);
@@ -1184,8 +1457,8 @@ back_raw_line(curr_pos, linep, line_lenp)
        char **linep;
        int *line_lenp;
 {
-       register int n;
-       register int c;
+       int n;
+       int c;
        POSITION new_pos;
 
        if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() ||
@@ -1193,7 +1466,7 @@ back_raw_line(curr_pos, linep, line_lenp)
                return (NULL_POSITION);
 
        n = size_linebuf;
-       linebuf[--n] = '\0';
+       linebuf.buf[--n] = '\0';
        for (;;)
        {
                c = ch_back_get();
@@ -1233,17 +1506,153 @@ back_raw_line(curr_pos, linep, line_lenp)
                        /*
                         * Shift the data to the end of the new linebuf.
                         */
-                       for (fm = linebuf + old_size_linebuf - 1,
-                             to = linebuf + size_linebuf - 1;
-                            fm >= linebuf;  fm--, to--)
+                       for (fm = linebuf.buf + old_size_linebuf - 1,
+                             to = linebuf.buf + size_linebuf - 1;
+                            fm >= linebuf.buf;  fm--, to--)
                                *to = *fm;
                        n = size_linebuf - old_size_linebuf;
                }
-               linebuf[--n] = c;
+               linebuf.buf[--n] = c;
        }
        if (linep != NULL)
-               *linep = &linebuf[n];
+               *linep = &linebuf.buf[n];
        if (line_lenp != NULL)
                *line_lenp = size_linebuf - 1 - n;
        return (new_pos);
 }
+
+/*
+ * Append a string to the line buffer.
+ */
+       static int
+pappstr(str)
+       constant char *str;
+{
+       while (*str != '\0')
+       {
+               if (pappend(*str++, NULL_POSITION))
+                       /* Doesn't fit on screen. */
+                       return 1;
+       }
+       return 0;
+}
+
+/*
+ * Load a string into the line buffer.
+ * If the string is too long to fit on the screen,
+ * truncate the beginning of the string to fit.
+ */
+       public void
+load_line(str)
+       constant char *str;
+{
+       int save_hshift = hshift;
+
+       hshift = 0;
+       for (;;)
+       {
+               prewind();
+               if (pappstr(str) == 0)
+                       break;
+               /*
+                * Didn't fit on screen; increase left shift by one.
+                * {{ This gets very inefficient if the string
+                * is much longer than the screen width. }}
+                */
+               hshift += 1;
+       }
+       set_linebuf(linebuf.end, '\0', AT_NORMAL);
+       hshift = save_hshift;
+}
+
+/*
+ * Find the shift necessary to show the end of the longest displayed line.
+ */
+       public int
+rrshift(VOID_PARAM)
+{
+       POSITION pos;
+       int save_width;
+       int line;
+       int longest = 0;
+
+       save_width = sc_width;
+       sc_width = INT_MAX;
+       pos = position(TOP);
+       for (line = 0; line < sc_height && pos != NULL_POSITION; line++)
+       {
+               pos = forw_line(pos);
+               if (end_column > longest)
+                       longest = end_column;
+       }
+       sc_width = save_width;
+       if (longest < sc_width)
+               return 0;
+       return longest - sc_width;
+}
+
+/*
+ * Get the color_map index associated with a given attribute.
+ */
+       static int
+color_index(attr)
+       int attr;
+{
+       if (use_color)
+       {
+               switch (attr & AT_COLOR)
+               {
+               case AT_COLOR_ATTN:    return 0;
+               case AT_COLOR_BIN:     return 1;
+               case AT_COLOR_CTRL:    return 2;
+               case AT_COLOR_ERROR:   return 3;
+               case AT_COLOR_LINENUM: return 4;
+               case AT_COLOR_MARK:    return 5;
+               case AT_COLOR_PROMPT:  return 6;
+               case AT_COLOR_RSCROLL: return 7;
+               case AT_COLOR_SEARCH:  return 8;
+               case AT_COLOR_HEADER:  return 9;
+               }
+       }
+       if (attr & AT_UNDERLINE)
+               return 10;
+       if (attr & AT_BOLD)
+               return 11;
+       if (attr & AT_BLINK)
+               return 12;
+       if (attr & AT_STANDOUT)
+               return 13;
+       return -1;
+}
+
+/*
+ * Set the color string to use for a given attribute.
+ */
+       public int
+set_color_map(attr, colorstr)
+       int attr;
+       char *colorstr;
+{
+       int cx = color_index(attr);
+       if (cx < 0)
+               return -1;
+       if (strlen(colorstr)+1 > sizeof(color_map[cx]))
+               return -1;
+       if (*colorstr != '\0' && parse_color(colorstr, NULL, NULL) == CT_NULL)
+               return -1;
+       strcpy(color_map[cx], colorstr);
+       return 0;
+}
+
+/*
+ * Get the color string to use for a given attribute.
+ */
+       public char *
+get_color_map(attr)
+       int attr;
+{
+       int cx = color_index(attr);
+       if (cx < 0)
+               return NULL;
+       return color_map[cx];
+}