Logo Search packages:      
Sourcecode: vile version File versions

display.c

/*
 * The functions in this file handle redisplay. There are two halves, the
 * ones that update the virtual display screen, and the ones that make the
 * physical display screen the same as the virtual display screen. These
 * functions use hints that are left in the windows by the commands.
 *
 *
 * $Header: /usr/build/vile/vile/RCS/display.c,v 1.380 2003/07/27 16:53:03 tom Exp $
 *
 */

#include    "estruct.h"
#include    "edef.h"
#include    "pscreen.h"
#include    "nefsms.h"

#define vMAXINT ((int)((unsigned)(~0)>>1))      /* 0x7fffffff */
#define vMAXNEG (-vMAXINT)    /* 0x80000001 */

#define     NU_WIDTH 8

#define reset_term_attrs() term.rev(0)

#ifdef GVAL_VIDEO
#define VIDEOATTRS (VABOLD|VAUL|VAITAL)
#define set_term_attrs(a)  term.rev(a ^ (global_g_val(GVAL_VIDEO) & VIDEOATTRS))
#else
#define set_term_attrs(a)  term.rev(a)
#endif

#define     MRK_EMPTY        '~'
#define     MRK_EXTEND_LEFT  '<'
#define     MRK_EXTEND_RIGHT '>'

VIDEO **vscreen;        /* Virtual screen. */
VIDEO **pscreen;        /* Physical screen. */
#if FRAMEBUF
#define PSCREEN vscreen
#else
#define PSCREEN pscreen
#endif
static int *lmap;

#if DISP_IBMPC
#define PScreen(n) scread((VIDEO *)0,n)
#else
#define     PScreen(n) pscreen[n]
#endif

#if OPT_SCROLLCODE && (DISP_IBMPC || !FRAMEBUF)
#define CAN_SCROLL 1
#else
#define CAN_SCROLL 0
#endif

static int i_displayed;       /* false until we're in screen-mode */
static int mpresf;            /* zero if message-line empty */
#if OPT_WORKING
static int im_timing;
#endif

/*
 * MARK2COL may be greater than mark2col if the mark does not point to the end
 * of the line, and if it points to a nonprinting character.  We use that value
 * when setting visible attributes, to keep tabs and other nonprinting
 * characters looking 'right'.
 */
#define MARK2COL(wp, mk)  offs2col(wp, mk.l, mk.o + 1) - 1
#define mark2col(wp, mk)  offs2col(wp, mk.l, mk.o)

#ifdef WMDLINEWRAP
#define TopRow(wp) (wp)->w_toprow + (wp)->w_line.o
static int allow_wrap;
#else
#define TopRow(wp) (wp)->w_toprow
#endif

/* for window size changes */
static int chg_width, chg_height;

/******************************************************************************/

typedef void (*OutFunc) (int c);

static OutFunc dfoutfn;

static void vtlistc(int c);

/*--------------------------------------------------------------------------*/

/*
 * Format a number, right-justified, returning a pointer to the formatted
 * buffer.
 */
static char *
right_num(char *buffer, int len, long value)
{
    char temp[NSTRING];
    register char *p = lsprintf(temp, "%ld", value);
    register char *q = buffer + len;

    *q = EOS;
    while (q != buffer)
      *(--q) = (char) ((p != temp) ? *(--p) : ' ');
    return buffer;
}

/*
 * Do format a string.
 */
static int
dfputsn(OutFunc outfunc, const char *s, int n)
{
    int c;
    int l = 0;

    TRACE2(("...str=%s\n", visible_buff(s, (n < 0) ? strlen(s) : n, TRUE)));
    if (s != 0) {
      while ((n-- != 0) && ((c = *s++) != EOS)) {
          (*outfunc) (c);
          l++;
      }
    }
    return l;
}

/* as above, but uses null-terminated string's length */
static int
dfputs(OutFunc outfunc, const char *s)
{
    return dfputsn(outfunc, s, -1);
}

/*
 * Do format an integer, in the specified radix.
 */
static int
dfputi(OutFunc outfunc, UINT i, UINT r)
{
    int q;

    TRACE2(("...int=%d\n", i));

    q = (i >= r) ? dfputi(outfunc, i / r, r) : 0;

    (*outfunc) (hexdigits[i % r]);
    return q + 1;       /* number of digits printed */
}

/*
 * do the same except as a long integer.
 */
static int
dfputli(OutFunc outfunc, ULONG l, UINT r)
{
    int q;

    TRACE2(("...long=%ld\n", l));

    q = (l >= r) ? dfputli(outfunc, (l / r), r) : 0;

    return q + dfputi(outfunc, (l % r), r);
}

/*
 * format a floating value with two decimal places
 */
static int
dfputf(OutFunc outfunc, double s)
{
    int n = 3;
    UINT i;             /* integer portion of number */
    UINT f;             /* fractional portion of number */

    if (s < 0) {
      (*outfunc) ('.');
      s = -s;
      n++;
    }

    /* break it up */
    i = (UINT) s;
    f = (UINT) ((s - (double) i) * 100);

    /* send out the integer portion */
    n += dfputi(outfunc, i, 10);
    (*outfunc) ('.');
    (*outfunc) ((f / 10) + '0');
    (*outfunc) ((f % 10) + '0');
    return n;
}

/*
 * Generic string formatter.  Takes printf-like args, and calls
 * the global function (*dfoutfn)(c) for each c
 */
static void
dofmt(const char *fmt, va_list * app)
{
    register int c;           /* current char in format string */
    register int wid;
    register int n;
    register int nchars = 0;
    int islong;
    int int_value;
    long long_value;
    UINT radix;
    OutFunc outfunc = dfoutfn;      /* local copy, for recursion */

    TRACE2(("dofmt fmt='%s'\n", fmt));
    while ((c = *fmt++) != 0) {
      if (c != '%') {
          (*outfunc) (c);
          nchars++;
          continue;
      }
      c = *fmt++;
      wid = 0;
      islong = FALSE;
      if (c == '*') {
          wid = va_arg(*app, int);
          c = *fmt++;
      } else {
          while (isDigit(c)) {
            wid = (wid * 10) + c - '0';
            c = *fmt++;
          }
      }
      if (c == 'l') {
          islong = TRUE;
          c = *fmt++;
      }
      switch (c) {
      case EOS:
          n = 0;
          break;
      case 'c':
          (*outfunc) (va_arg(*app, int));
          n = 1;
          break;

      case 'd':
          if (!islong) {
            int_value = va_arg(*app, int);
            if (int_value < 0) {
                if (int_value < vMAXNEG) {
                  n = dfputs(outfunc, "OVFL");
                  break;
                }
                int_value = -int_value;
                (*outfunc) ('-');
                nchars++;
            }
            n = dfputi(outfunc, (UINT) int_value, 10);
            break;
          }
          long_value = va_arg(*app, long);
          if (long_value < 0) {
            long_value = -long_value;
            (*outfunc) ('-');
            nchars++;
          }
          n = dfputli(outfunc, (ULONG) long_value, 10);
          break;

      case 'o':
          n = dfputi(outfunc, va_arg(*app, UINT), 8);
          break;

      case 'x':
          if (!islong) {
            n = dfputi(outfunc, va_arg(*app, UINT), 16);
            break;
          }
          /* FALLTHROUGH */
      case 'X':
          n = dfputli(outfunc, va_arg(*app, ULONG), 16);
          break;

      case 'r':
      case 'R':
          radix = va_arg(*app, UINT);
          if (radix < 2 || radix > 36)
            radix = 10;
          if (islong || c == 'R')
            n = dfputli(outfunc,
                      va_arg(*app, ULONG), radix);
          else
            n = dfputi(outfunc,
                     va_arg(*app, UINT), radix);
          break;

      case 's':
          if (wid <= 0) {
            n = dfputs(outfunc, va_arg(*app, char *));
            break;
          }
          /* FALLTHROUGH */

      case 'S':         /* use wid as max width */
          n = dfputsn(outfunc, va_arg(*app, char *), wid);
          break;

      case 'f':
          n = dfputf(outfunc, va_arg(*app, double));
          break;

      case 'P':         /* output padding -- pads total output to
                           "wid" chars, using c as the pad char */
          wid -= nchars;
          /* FALLTHROUGH */

      case 'Q':         /* field padding -- puts out "wid"
                           copies of c */
          n = 0;
          c = va_arg(*app, int);
          while (n < wid) {
            (*outfunc) (c);
            n++;
          }
          break;

      default:
          (*outfunc) (c);
          n = 1;
      }
      wid -= n;
      nchars += n;
      while (wid-- > 0) {
          (*outfunc) (' ');
          nchars++;
      }
    }
}

/******************************************************************************/

/*
 * Line-number mode
 */
int
nu_width(WINDOW *wp)
{
    return w_val(wp, WMDNUMBER) ? NU_WIDTH : 0;
}

int
col_limit(WINDOW *wp)
{
#ifdef WMDLINEWRAP
    if (w_val(wp, WMDLINEWRAP))
      return curcol + 1;      /* effectively unlimited */
#endif
    return term.cols - 1 - nu_width(wp);
}

#if OPT_VIDEO_ATTRS
static void
set_vattrs(int row, int col, VIDEO_ATTR attr, size_t len)
{
    while (len--)
      vscreen[row]->v_attrs[col++] = attr;
}
#else
#define set_vattrs(row, col, attr, len)         /*nothing */
#endif

static void
freeVIDEO(register VIDEO * vp)
{
    if (vp != 0) {
#if OPT_VIDEO_ATTRS
      FreeIfNeeded(vp->v_attrs);
#endif
      free((char *) vp);
    }
}

int
video_alloc(VIDEO ** vpp)
{
    register VIDEO *vp;
    /* struct VIDEO already has 4 of the bytes */
    vp = typeallocplus(VIDEO, term.maxcols - 4);
    if (vp == 0)
      return FALSE;
    (void) memset((char *) vp, 0, sizeof(VIDEO) + term.maxcols - 4);

#if OPT_VIDEO_ATTRS
    vp->v_attrs = typecallocn(VIDEO_ATTR, (size_t) term.maxcols);
    if (vp->v_attrs == 0) {
      free((char *) vp);
      return FALSE;
    }
#endif
    freeVIDEO(*vpp);
    *vpp = vp;
    return TRUE;
}

static int
vtalloc(void)
{
    register int i, first;
    static int vcols, vrows;

    if (term.maxrows > vrows) {
      GROW(vscreen, VIDEO *, vrows, term.maxrows);
#if ! FRAMEBUF
      GROW(pscreen, VIDEO *, vrows, term.maxrows);
#endif
      GROW(lmap, int, vrows, term.maxrows);
    } else {
      for (i = term.maxrows; i < vrows; i++) {
          freeVIDEO(vscreen[i]);
#if ! FRAMEBUF
          freeVIDEO(pscreen[i]);
#endif
      }
    }

    first = (term.maxcols > vcols) ? 0 : vrows;

    for (i = first; i < term.maxrows; ++i) {
      if (!video_alloc(&vscreen[i]))
          return FALSE;
#if ! FRAMEBUF
      if (!video_alloc(&pscreen[i]))
          return FALSE;
#endif /* !FRAMEBUF */
    }
    vcols = term.maxcols;
    vrows = term.maxrows;

    return TRUE;
}

/* free all video memory, in anticipation of a (growing) resize */
#if NO_LEAKS
static void
vtfree(void)
{
    register int i;

    if (vscreen) {
      for (i = 0; i < term.maxrows; ++i) {
          freeVIDEO(vscreen[i]);
      }
      free((char *) vscreen);
      vscreen = 0;
    }
#if ! FRAMEBUF
    if (pscreen) {
      for (i = 0; i < term.maxrows; ++i) {
          freeVIDEO(pscreen[i]);
      }
      free((char *) pscreen);
      pscreen = 0;
    }
#endif
    FreeIfNeeded(lmap);
}
#endif

/*
 * Initialize the data structures used by the display code. The edge vectors
 * used to access the screens are set up.
 */
int
vtinit(void)
{
    int rc = TRUE;
    int i;
    VIDEO *vp;

    beginDisplay();
    /* allocate new display memory */
    if (vtalloc() == FALSE) { /* if we fail, only serious if not a realloc */
      rc = (vscreen != NULL);
    } else {
      for (i = 0; i < term.maxrows; ++i) {
          vp = vscreen[i];
          vp->v_flag = 0;
#if OPT_COLOR
          ReqFcolor(vp) = gfcolor;
          ReqBcolor(vp) = gbcolor;
#endif
      }
#if OPT_WORKING
      if (!i_displayed && !im_displaying)
          imworking(0);
#endif
    }
    endofDisplay();
    return rc;
}

/*
 * Set the virtual cursor to the specified row and column on the virtual
 * screen. There is no checking for nonsense values.
 */
static void
vtmove(int row, int col)
{
    vtrow = row;
    vtcol = col;
}

static int vt_tabstop;

/*
 * Write a character to the virtual screen.  The virtual row and column are
 * updated.  Only print characters if they would be "visible".  If the line is
 * too long put a ">" in the last column.  This routine only puts printing
 * characters into the virtual terminal buffers.  Only column overflow is
 * checked.
 */
static void
vtputc(int c)
{
    /* since we don't allow wrapping on the message line, we only need
     * to evaluate this once.  */
    int lastcol = vtrow == term.rows - 1 ? term.cols - 1 : term.cols;
    register VIDEO *vp;

#ifdef WMDLINEWRAP
    if (vtrow < 0) {
      static VIDEO *fake_line;
      static int length;
      if (length != term.maxcols || fake_line == 0) {
          if (!video_alloc(&fake_line))
            return;
          length = term.maxcols;
      }
      vp = fake_line;
    } else
#endif
      vp = vscreen[vtrow];

    if (isPrint(c) && vtcol >= 0 && vtcol < lastcol) {
      VideoText(vp)[vtcol++] = (char) (c & (N_chars - 1));
#ifdef WMDLINEWRAP
      if ((allow_wrap != 0)
          && (vtcol == lastcol)
          && (vtrow < allow_wrap)) {
          vtcol = 0;
          if (++vtrow >= 0)
            vscreen[vtrow]->v_flag |= VFCHG;
          horscroll += lastcol;
      }
#endif
      return;
    }

    if (vtcol >= lastcol) {
      VideoText(vp)[lastcol - 1] = MRK_EXTEND_RIGHT;
    } else if (c == '\t') {
      do {
          vtputc(' ');
      } while (((vtcol + horscroll) % vt_tabstop) != 0
             && vtcol < lastcol);
    } else if (c == '\n') {
      return;
    } else if (isPrint(c)) {
      ++vtcol;
    } else {
      vtlistc(c);
    }
}

/* how should high-bit unprintable chars be shown? */
static int vt_octal;

/* shows non-printing character */
static void
vtlistc(int c)
{
    if (isPrint(c)) {
      vtputc(c);
      return;
    }

    if (c & HIGHBIT) {
      vtputc(BACKSLASH);
      if (vt_octal) {
          vtputc(((c >> 6) & 3) + '0');
          vtputc(((c >> 3) & 7) + '0');
          vtputc(((c) & 7) + '0');
      } else {
          vtputc('x');
          vtputc(hexdigits[(c >> 4) & 0xf]);
          vtputc(hexdigits[(c) & 0xf]);
      }
    } else {
      vtputc('^');
      vtputc(toalpha(c));
    }
}

static void
vtputsn(const char *s, int n)
{
    int c;
    while (n-- > 0 && (c = *s++) != EOS)
      vtputc(c);
}

/*
 * Write a line to the screen at the current video coordinates, allowing for
 * line-wrap or right-shifting.
 */
static void
vtset(LINEPTR lp, WINDOW *wp)
{
    register char *from;
    register int n = llength(lp);
    BUFFER *bp = wp->w_bufp;
    int skip = -vtcol;
    int list = w_val(wp, WMDLIST);

    vt_octal = w_val(wp, WMDNONPRINTOCTAL);
    vt_tabstop = tabstop_val(wp->w_bufp);

#ifdef WMDLINEWRAP
    /*
     * If the window's offset is negative, we've got a case of linewrap
     * where the line's beginning is forced before the beginning of the
     * window.
     */
    if (wp->w_line.o < 0) {
      vtrow -= wp->w_line.o;
      skip = col2offs(wp, lp, -(wp->w_line.o * term.cols));
      n -= skip;
    } else
#endif
    if (w_val(wp, WMDNUMBER)) {
      register int j, k, jk;
      L_NUM line = line_no(bp, lp);
      int fill = ' ';
      char temp[NU_WIDTH + 2];

      vtcol = 0;        /* make sure we always see line numbers */
      vtputsn(right_num(temp, NU_WIDTH - 2, (long) line), NU_WIDTH - 2);
      vtputsn("  ", 2);
      horscroll = skip - vtcol;

      /* account for leading fill; this repeats logic in vtputc so
       * I don't have to introduce a global variable... */
      from = lp->l_text;
      for (j = k = jk = 0; (j < n) && (k < skip); j++) {
          register int c = from[j];
          if ((list || (c != '\t')) && !isPrint(c)) {
            if (c & HIGHBIT) {
                k += 4;
                fill = BACKSLASH;   /* FIXXXX */
            } else {
                k += 2;
                fill = toalpha(c);
            }
          } else {
            if (c == '\t')
                k += (vt_tabstop - (k % vt_tabstop));
            else if (isPrint(c))
                k++;
            fill = ' ';
          }
          jk = j + 1;
      }
      while (k-- > skip)
          vtputc(fill);
      if ((skip = jk) < 0)
          skip = 0;
      n -= skip;
    } else
      skip = 0;

#if OPT_B_LIMITS
    horscroll -= w_left_margin(wp);
#endif
    from = lp->l_text + skip;
#ifdef WMDLINEWRAP
    allow_wrap = w_val(wp, WMDLINEWRAP) ? mode_row(wp) - 1 : 0;
#endif

    /*
     * The loops below are split up for a reason - there's a hidden side effect
     * which makes the final increment of vtcol inconsistent, so it won't
     * terminate until the end of the line.  For very long lines, that's a
     * performance hit.
     */
#define VTSET_PUT(ch,count) if(list) vtlistc(ch); else vtputc(ch); count
#ifdef WMDLINEWRAP
    if (w_val(wp, WMDLINEWRAP)) {
      while ((vtcol < term.cols)
             && ((vtrow == term.rows - 1) || (vtrow < mode_row(wp)))
             && (n > 0)) {
          VTSET_PUT(*from++, n--);
      }
      if ((vtcol <= term.cols)
          && ((vtrow == term.rows - 1) || (vtrow < mode_row(wp)))
          && (n > 0)) {
          VTSET_PUT(*from++, n--);
      }
    } else
#endif
    {
      while ((vtcol < term.cols)
             && (n > 0)) {
          VTSET_PUT(*from++, n--);
      }
      if ((vtcol <= term.cols)
          && (n > 0)) {
          VTSET_PUT(*from++, n--);
      }
    }

    /* Display a "^J" if 'list' mode is active, unless we've suppressed
     * it for some reason.
     */
    if (list && (n >= 0)) {
      if (b_is_scratch(bp) && listrimmed(lp))
          /*EMPTY */ ;
      else if (!b_val(bp, MDNEWLINE) && (lforw(lp) == buf_head(bp)))
          /*EMPTY */ ;
      else {
          char *s = get_record_sep(bp);
          while (*s != EOS)
            vtlistc(*s++);
      }
    }
#ifdef WMDLINEWRAP
    allow_wrap = 0;
#endif
    TRACE2(("TEXT %4d:%.*s\n", vtrow, term.cols - 1, vscreen[vtrow]->v_text));
}

/*
 * Erase from the end of the software cursor to the end of the line on which
 * the software cursor is located.
 */
static void
vteeol(void)
{
    if (vtcol < term.cols) {
      int n = (vtcol >= 0) ? vtcol : 0;
#ifdef WMDLINEWRAP
      if (vtrow >= 0)
#endif
          if (n >= 0) {
            (void) memset(&vscreen[vtrow]->v_text[n],
                        ' ', (size_t) (term.cols - n));
          }
      vtcol = term.cols;
    }
}

/* upscreen:      user routine to force a screen update
            always finishes complete update           */
#if !SMALLER
/* ARGSUSED */
int
upscreen(int f GCC_UNUSED, int n GCC_UNUSED)
{
    return update(TRUE);
}
#endif

static UINT scrflags;

/* line to virtual column */
int
mk_to_vcol(MARK mark, int expanded, BUFFER *bp, int col, int adjust)
{
    int i = b_left_margin(bp) + adjust;
    int c;
    int lim;
    int t = tabstop_val(bp);
    LINEPTR lp;
    int extra = ((!global_g_val(GMDALTTABPOS) && !insertmode) ? 1 : 0);

    if (i < 0) {
      i = 0;
      col = 0;
    }
    lp = mark.l;
    lim = mark.o + extra;
    if (lim > llength(lp))
      lim = llength(lp);

    while (i < lim) {
      c = lgetc(lp, i++);
      if (c == '\t' && !expanded) {
          col += t - (col % t);
      } else {
          if (!isPrint(c)) {
            col += (c & HIGHBIT) ? 3 : 1;
          }
          ++col;
      }

    }
    if (extra && (col != 0) && (mark.o < llength(lp)))
      col--;
    return col;
}

/*
 * Convert the window's DOT into a virtual column.  This is used both for the
 * ruler, as well as for updating the cursor position, and for very long lines
 * can be slow.  So we try to cache a good starting point to allow rapid
 * movement along the line.
 */
static int
dot_to_vcol(WINDOW *wp)
{
    BUFFER *bp = wp->w_bufp;
    int result;
#if OPT_CACHE_VCOL
    W_TRAITS *wt = &(wp->w_traits);
    int need_col = FALSE;
    int shift = w_val(wp, WVAL_SIDEWAYS)
    ? w_val(wp, WVAL_SIDEWAYS) - 1
    : 0;
    int use_off = w_left_margin(wp);
#if OPT_TRACE
    int check;
#endif

#if OPT_TRACE && OPT_DEBUG
    TRACE(("CHECK left_col %d, shift %d, length %d\n",
         wt->w_left_col, shift, llength(wp->w_dot.l)));
    TRACE(("%s\n", lp_visible(wp->w_dot.l)));
    TRACE(("DOT:%s\n", visible_buff(
                               wp->w_dot.l->l_text + wp->w_dot.o,
                               llength(wp->w_dot.l) - wp->w_dot.o,
                               FALSE)));
#endif

    /*
     * If we've moved to a new line, or if the 'list' mode is changed,
     * we'll need to recompute the whole state.
     */
    if (wt->w_left_dot.l != wp->w_dot.l
      || wt->w_list_opt != w_val(wp, WMDLIST)
      || wt->w_num_cols != term.cols) {
      wt->w_left_dot.l = wp->w_dot.l;
      wt->w_left_dot.o = 0;
      wt->w_left_col = 0;
      wt->w_left_adj = 0;
      wt->w_list_opt = w_val(wp, WMDLIST);
      wt->w_num_cols = term.cols;
      need_col = TRUE;
    }
#ifdef WMDLINEWRAP
    if (w_val(wp, WMDLINEWRAP)) {
      int lo = wt->w_left_dot.o;
      int hi = lo + term.cols;      /* estimate (may be lower) */
      int row;
      int col = wt->w_left_col;
      int tmp = (nu_width(wp) + w_left_margin(wp));

      if (wp->w_dot.o < lo
          || wp->w_dot.o >= hi) {
          col = offs2col(wp, wt->w_left_dot.l, wp->w_dot.o);
          TRACE(("offs2col(%d)) = %d\n", wp->w_dot.o, col));
          TRACE(("...in row %d\n", col % term.cols));
          row = col / term.cols;
          col = row * term.cols;
          if (row != 0)
            col -= (nu_width(wp) + w_left_margin(wp));
          TRACE(("...row %d ends with col %d\n", row, col));
      }
      if (wt->w_left_col != col) {
          TRACE(("left_col %d vs %d\n", wt->w_left_col, col));
          wt->w_left_col = col;
          wt->w_left_dot.o = col2offs(wp, wt->w_left_dot.l, col + tmp);

          col -= (offs2col(wp, wt->w_left_dot.l, wt->w_left_dot.o) - tmp);
          TRACE(("...adjust:%d\n", col));
          wt->w_left_col -= col;
      }
      if (wt->w_left_dot.o)
          use_off = 0;
    } else
#endif
      if ((wt->w_left_col + wt->w_left_adj < shift)
          || (wt->w_left_col > shift)
          || (wp->w_dot.o < wt->w_left_dot.o)) {
      int base = nu_width(wp) + w_left_margin(wp) - 1;
      int col;

      TRACE(("...change dot %d(%d) w_left_col from [%d..%d] to match shift %d, base %d\n",
             wp->w_dot.o,
             wt->w_left_dot.o,
             wt->w_left_col,
             wt->w_left_col + wt->w_left_adj,
             shift, base));

      col = offs2col(wp, wt->w_left_dot.l, wp->w_dot.o) + shift - base;
      TRACE(("...compare col %d to shift %d\n", col, shift));
      if (col < shift) {
          wt->w_left_col = col;
      } else {
          wt->w_left_col = shift;
      }
      wt->w_left_adj = 0;

      wt->w_left_dot.o = col2offs(wp,
                            wt->w_left_dot.l,
                            wt->w_left_col + base - shift);
      TRACE(("...col2offs(%d) = %d\n", wt->w_left_col, wt->w_left_dot.o));

      TRACE(("...compare w_left_dot.o %d to w_dot.o %d\n",
             wt->w_left_dot.o, wp->w_dot.o));
      if (wt->w_left_dot.o > wp->w_dot.o) {
          need_col = TRUE;    /* dot is inconsistent with shift */
          wt->w_left_dot.o = wp->w_dot.o;
      } else {
          col = offs2col(wp, wt->w_left_dot.l, wt->w_left_dot.o) + shift - base;
          TRACE(("...offs2col(%d) = %d\n", wt->w_left_dot.o, col));

          TRACE(("...compare col %d to w_left_col %d\n",
               col, wt->w_left_col));
          if (col < wt->w_left_col) {
            /* if there was a multi-column character at the left
             * side of the shifted screen, adjust */
            TRACE(("...adjust for multi-column character %d\n", col));
            wt->w_left_adj = wt->w_left_col - col;
            wt->w_left_col = col;
          }
          need_col = FALSE;
      }
    }

    if (need_col) {
      wt->w_left_col = 0;
      if (wt->w_left_dot.o > 0) {
          wt->w_left_col = mk_to_vcol(wt->w_left_dot,
                              w_val(wp, WMDLIST),
                              bp,
                              0,
                              0);
          TRACE(("...cache w_left_col %d\n", wt->w_left_col));
      }
    }
    result = mk_to_vcol(wp->w_dot,
                  w_val(wp, WMDLIST),
                  bp,
                  wt->w_left_col,
                  wt->w_left_dot.o + use_off - b_left_margin(bp));
#if OPT_TRACE
    check = mk_to_vcol(wp->w_dot, w_val(wp, WMDLIST), bp, 0, 0);
    if (check != result) {
      TRACE(("dot_to_vcol result %d check %d (off=%d, shift=%d)\n",
             result, check, wp->w_dot.o, shift));
      kbd_alarm();
      TRACE(("-> OOPS:%s %d vs %d+%d %d\n",
             bp->b_bname,
             wp->w_dot.o,
             wt->w_left_dot.o, use_off,
             wt->w_left_col));
    }
#endif
#else
    result = mk_to_vcol(wp->w_dot, w_val(wp, WMDLIST), bp, 0, 0);
#endif
    return result;
}

/*
 * Update a single line. This does not know how to use insert or delete
 * character sequences; we are using VT52 functionality. Update the physical
 * row and column variables. It does try an exploit erase to end of line.
 *
 * row     - row of screen to update
 * colfrom - col to start updating from
 * colto   - col to go to
 */

#if FRAMEBUF
/*
 * UPDATELINE specific code for the IBM-PC and other compatibles
 */
static void
update_line(int row, int colfrom, int colto)
{
    register struct VIDEO *vp1 = vscreen[row];  /* virtual screen image */
    register int req = (vp1->v_flag & VFREQ) == VFREQ;

#if OPT_COLOR
    CurFcolor(vp1) = ReqFcolor(vp1);
    CurBcolor(vp1) = ReqBcolor(vp1);
#endif
#if OPT_VIDEO_ATTRS
    scwrite(row, colfrom, colto - colfrom,
          VideoText(vp1),
          VideoAttr(vp1),
          ReqFcolor(vp1),
          ReqBcolor(vp1));
#else /* highlighting, anyway */
    scwrite(row, colfrom, colto - colfrom,
          VideoText(vp1),
          (VIDEO_ATTR *) 0,
          req ? ReqBcolor(vp1) : ReqFcolor(vp1),
          req ? ReqFcolor(vp1) : ReqBcolor(vp1));
#endif
    vp1->v_flag &= ~(VFCHG | VFCOL);      /* flag this line as updated */
    if (req)
      vp1->v_flag |= VFREV;
    else
      vp1->v_flag &= ~VFREV;
}

#else /* !FRAMEBUF */
#if OPT_PSCREEN
/*
 * Update a single row on the physical screen.
 */
static void
update_line(int row, int colfrom, int colto)
{
    register char *vc, *pc, *evc;
    register VIDEO_ATTR *va, *pa, xx;
    int nchanges = 0;

    if ((vscreen[row]->v_flag & VFCHG) == 0)
      return;

    vc = &vscreen[row]->v_text[colfrom];
    evc = &vscreen[row]->v_text[colto];
    va = &vscreen[row]->v_attrs[colfrom];
    pc = &CELL_TEXT(row, colfrom);
    pa = &CELL_ATTR(row, colfrom);

    while (vc < evc) {
      xx = *va;
#ifdef GVAL_VIDEO
      xx ^= global_g_val(GVAL_VIDEO);
#endif
      if (*vc != *pc || VATTRIB(xx) != VATTRIB(*pa)) {
          *pc = *vc;
          *pa = xx | VADIRTY;
          nchanges++;
      }
      vc++;
      pc++;
      va++;
      pa++;
    }

    if (nchanges > 0)
      MARK_LINE_DIRTY(row);
    vscreen[row]->v_flag &= ~(VFCHG | VFCOL);   /* mark virtual line updated */
}

#else /* !OPT_PSCREEN */

/*
 * UPDATELINE code for all other versions
 */
static void
update_line(int row, int colfrom, int colto)
{
    struct VIDEO *vp1 = vscreen[row];     /* virtual screen image */
    struct VIDEO *vp2 = PSCREEN[row];     /* physical screen image */
    register int xl = colfrom;
    register int xr = colto;
    register int xx;

    register char *cp1 = VideoText(vp1);
    register char *cp2 = VideoText(vp2);
    register int nbflag;      /* non-blanks to the right flag? */

#if OPT_VIDEO_ATTRS
    register VIDEO_ATTR *ap1 = VideoAttr(vp1);
    register VIDEO_ATTR *ap2 = VideoAttr(vp2);
    VIDEO_ATTR Blank = 0;     /* FIXME: Color? */
#else
    UINT rev;                 /* reverse video flag */
    UINT req;                 /* reverse video request flag */
#endif

#if !OPT_VIDEO_ATTRS
#if OPT_COLOR
    term.setfore(ReqFcolor(vp1));
    term.setback(ReqBcolor(vp1));
#endif

#if OPT_REVSTA || OPT_COLOR
    /* if we need to change the reverse video status of the
       current line, we need to re-write the entire line     */
    rev = (vp1->v_flag & VFREV) == VFREV;
    req = (vp1->v_flag & VFREQ) == VFREQ;
    if ((rev != req)
#if OPT_COLOR
      || (CurFcolor(vp1) != ReqFcolor(vp1))
      || (CurBcolor(vp1) != ReqBcolor(vp1))
#endif
      ) {
      movecursor(row, colfrom);     /* Go to start of line. */
      /* set rev video if needed */
      if (req)
          set_term_attrs(req);

      /* scan through the line and dump it to the screen and
         the virtual screen array                             */
      for (; xl < colto; xl++) {
          term.putch(cp1[xl]);
          ++ttcol;
          cp2[xl] = cp1[xl];
      }
      /* turn rev video off */
      if (req)
          reset_term_attrs();

      /* update the needed flags */
      vp1->v_flag &= ~(VFCHG | VFCOL);
      if (req)
          vp1->v_flag |= VFREV;
      else
          vp1->v_flag &= ~VFREV;
#if OPT_COLOR
      CurFcolor(vp1) = ReqFcolor(vp1);
      CurBcolor(vp1) = ReqBcolor(vp1);
#endif
      return;
    }
#else
    rev = FALSE;
#endif /* OPT_REVSTA || OPT_COLOR */
#endif /* !OPT_VIDEO_ATTRS */

    /* advance past any common chars at the left */
#if !OPT_VIDEO_ATTRS
    if (!rev)
#endif /* !OPT_VIDEO_ATTRS */
      while (xl != colto
             && cp1[xl] == cp2[xl]
#if OPT_VIDEO_ATTRS
             && VATTRIB(ap1[xl]) == VATTRIB(ap2[xl])
#endif /* OPT_VIDEO_ATTRS */
          ) {
          ++xl;
      }

    /* This can still happen, even though we only call this routine on changed
     * lines.  A hard update is always done when a line splits, a massive
     * change is done, or a buffer is displayed twice.  This optimizes out most
     * of the excess updating.  A lot of computes are used, but these tend to
     * be hard operations that do a lot of update, so I don't really care.
     */

    /* if both lines are the same, no update needs to be done */
    if (xl == colto) {
      vp1->v_flag &= ~VFCHG;
      return;
    }

    /* find out if there is a match on the right */
    nbflag = FALSE;

#if !OPT_VIDEO_ATTRS
    if (!rev)
#endif
      while (cp1[xr - 1] == cp2[xr - 1]
#if OPT_VIDEO_ATTRS
             && VATTRIB(ap1[xr - 1]) == VATTRIB(ap2[xr - 1])
#endif
          ) {
          --xr;
          /* Note if any nonblank in right match */
          if (cp1[xr] != ' '
#if OPT_VIDEO_ATTRS
            || VATTRIB(ap1[xr] != Blank)
#endif
            )
            nbflag = TRUE;
      }

    xx = xr;

    /* Erase to EOL ? */
    if (nbflag == FALSE
      && eolexist == TRUE
#if OPT_REVSTA && !OPT_VIDEO_ATTRS
      && (req != TRUE)
#endif
      ) {
      while ((xx != xl)
             && cp1[xx - 1] == ' '
#if OPT_VIDEO_ATTRS
             && VATTRIB(ap1[xx - 1]) == Blank
#endif
          )
          xx--;

      if ((xr - xx) <= 3)     /* Use only if erase is */
          xx = xr;            /* fewer characters. */
    }

    movecursor(row, xl - colfrom);  /* Go to start of line. */
#if OPT_VIDEO_ATTRS
    while (xl < xx) {
      register int j = xl;
      VIDEO_ATTR attr = VATTRIB(ap1[j]);
      while ((j < xx) && (attr == VATTRIB(ap1[j])))
          j++;
      set_term_attrs(attr);
      for (; xl < j; xl++) {
          term.putch(cp1[xl]);
          ++ttcol;
          cp2[xl] = cp1[xl];
          ap2[xl] = ap1[xl];
      }
    }
    reset_term_attrs();

    if (xx != xr) {           /* Erase. */
      term.eeol();
      for (; xl < xr; xl++) {
          if (cp2[xl] != cp1[xl]
            || VATTRIB(ap2[xl]) != VATTRIB(ap1[xl]))
            ap2[xl] = ap1[xl];
          cp2[xl] = cp1[xl];
      }
    }
#else /* OPT_VIDEO_ATTRS */
#if OPT_REVSTA
    set_term_attrs(rev);
#endif

    for (; xl < xr; xl++) {   /* Ordinary. */
      term.putch(cp1[xl]);
      ++ttcol;
      cp2[xl] = cp1[xl];
    }

    if (xx != xr) {           /* Erase. */
      term.eeol();
      for (; xl < xr; xl++) {
          cp2[xl] = cp1[xl];
      }
    }
#if OPT_REVSTA
    reset_term_attrs();
#endif
#endif /* OPT_VIDEO_ATTRS */
    vp1->v_flag &= ~(VFCHG | VFCOL);
    return;
}
#endif /* OPT_PSCREEN(update_line) */
#endif /* FRAMEBUF(update_line) */

void
kbd_openup(void)
{
#if !FRAMEBUF && !OPT_PSCREEN
    int i;
    size_t alen = sizeof(VIDEO_ATTR) * term.cols;
#endif
    kbd_flush();
    bottomleft();
    term.putch('\n');
    term.putch('\r');
    term.flush();
#if !FRAMEBUF && !OPT_PSCREEN
    if (pscreen != 0) {
      for (i = 0; i < term.rows - 1; ++i) {
          (void) memcpy(
                       pscreen[i]->v_text,
                       pscreen[i + 1]->v_text,
                       (size_t) (term.cols));
#if OPT_VIDEO_ATTRS
          (void) memcpy(
                       pscreen[i]->v_attrs,
                       pscreen[i + 1]->v_attrs,
                       alen);
#endif
      }
      (void) memset(pscreen[i]->v_text, ' ', (size_t) (term.cols));
#if OPT_VIDEO_ATTRS
      (void) memset(pscreen[i]->v_attrs, VADIRTY, alen);
#endif
    }
#endif
}

/* cannot be allocated since it's used by OPT_HEAPSIZE */
static char my_overlay[20];

/* save/erase text for the overlay on the message line */
void
kbd_overlay(const char *s)
{
    my_overlay[0] = EOS;
    if ((mpresf = (s != 0 && *s != EOS)) != 0) {
      vl_strncpy(my_overlay, s, sizeof(my_overlay));
    }
}

void
kbd_flush(void)
{
    int ok;

    beginDisplay();
    if (vscreen != 0) {
      int row = term.rows - 1;

      vtmove(row, -w_val(wminip, WVAL_SIDEWAYS));

      ok = (wminip != 0
            && wminip->w_dot.l != 0
            && wminip->w_dot.l->l_text != 0);
      if (ok) {
          TRACE(("SHOW:%2d:%s\n",
               llength(wminip->w_dot.l),
               lp_visible(wminip->w_dot.l)));
          lsettrimmed(wminip->w_dot.l);
          vtset(wminip->w_dot.l, wminip);
      }

      vteeol();
      set_vattrs(row, 0,
               (VIDEO_ATTR) (miniedit
                         ? global_g_val(GVAL_MINI_HILITE)
                         : 0),
               term.cols);
      if (my_overlay[0] != EOS) {
          int n = term.cols - strlen(my_overlay) - 1;
          if (n > 0) {
            (void) memcpy(&vscreen[row]->v_text[n],
                        my_overlay,
                        strlen(my_overlay));
          }
      }
      vscreen[row]->v_flag |= VFCHG;
      update_line(row, 0, term.cols);
      if (ok)
          movecursor(row,
                   offs2col(wminip, wminip->w_dot.l, wminip->w_dot.o));
    }
    term.flush();
    endofDisplay();
}

/*
 * Translate offset (into a line's text) into the display-column, taking into
 * account the tabstop, sideways, number- and list-modes.
 *
 * Note: we intentionally compare against '\n' rather than the record separator,
 * since it is simpler than handling ^M^J in one case, and we really only want
 * to know if we're in the record-separator.
 */
static int
offs2col0(
           WINDOW *wp,
           LINEPTR lp,
           C_NUM offset,
           C_NUM * cache_offset,
           int *cache_column)
{
    int column;

    /* this makes the how-much-to-select calculation easier above */
    if (offset < 0) {
      column = offset;
    } else if (lp == win_head(wp)) {
      column = 0;
    } else {
      int length = llength(lp);
      int tabs = tabstop_val(wp->w_bufp);
      int list = w_val(wp, WMDLIST);
      int last = (list ? (N_chars * 2) : '\n');
      int left =
#ifdef WMDLINEWRAP            /* overrides left/right scrolling */
      w_val(wp, WMDLINEWRAP) ? 0 :
#endif
      w_val(wp, WVAL_SIDEWAYS);
      C_NUM n, c;
      C_NUM start_offset;

      if (offset > length + 1)
          offset = length + 1;

      if (cache_offset && cache_column && *cache_offset < offset) {
          start_offset = *cache_offset;
          column = *cache_column;
      } else {
          start_offset = w_left_margin(wp);
          column = 0;
      }

      for (n = start_offset; n < offset; n++) {
          c = (n == length) ? '\n' : lp->l_text[n];
          if (isPrint(c) || (c == last)) {
            column++;
          } else if (list || (c != '\t')) {
            column += (c & HIGHBIT) ? 4 : 2;
          } else if (c == '\t') {
            column = ((column / tabs) + 1) * tabs;
          }
      }

      if (cache_offset && cache_column) {
          *cache_offset = offset;
          *cache_column = column;
      }

      column += (nu_width(wp) + w_left_margin(wp) - left);
    }
    return column;
}

int
offs2col(WINDOW *wp, LINEPTR lp, C_NUM offset)
{
    return offs2col0(wp, lp, offset, 0, 0);
}

/*
 * Translate a display-column (assuming an infinitely-wide display) into the
 * line's offset, taking into account the tabstop, sideways, number and list
 * modes.
 */
#if OPT_MOUSE || defined(WMDLINEWRAP)
int
col2offs(WINDOW *wp, LINEPTR lp, C_NUM col)
{
    int tabs = tabstop_val(wp->w_bufp);
    int list = w_val(wp, WMDLIST);
    int left =
#ifdef WMDLINEWRAP            /* overrides left/right scrolling */
    w_val(wp, WMDLINEWRAP) ? 0 :
#endif
    w_val(wp, WVAL_SIDEWAYS);
    int goal = col + left - nu_width(wp) - w_left_margin(wp);

    register C_NUM n;
    register C_NUM offset;
    register C_NUM len = llength(lp);
    register char *text = lp->l_text;

    if (lp == win_head(wp)) {
      offset = 0;
    } else {
      for (offset = w_left_margin(wp), n = 0;
           (offset < len) && (n < goal);
           offset++) {
          register int c = text[offset];
          if (isPrint(c)) {
            n++;
          } else if (list || (c != '\t')) {
            n += (c & HIGHBIT) ? 4 : 2;
          } else if (c == '\t') {
            n = ((n / tabs) + 1) * tabs;
          }
          if (n > goal)
            break;
      }
    }
    return offset;
}
#endif

/*
 * Compute the number of rows required for displaying a line.
 */
#ifdef WMDLINEWRAP
int
line_height(WINDOW *wp, LINEPTR lp)
{
    int hi = 1;
    if (w_val(wp, WMDLINEWRAP)) {
      int len = llength(lp);
      if (len > 0) {
          int col = offs2col(wp, lp, len) - 1;
          if (ins_mode(wp) != FALSE
            && lp == DOT.l
            && len <= DOT.o) {
            col++;
            if (w_val(wp, WMDLIST))
                col++;
          } else if (w_val(wp, WMDLIST)) {
            col += 2;
          }
          hi = (col / term.cols) + 1;
      }
    }
    return hi;
}
#endif

/*
 * Given a row on the screen, determines which window it belongs to.  Returns
 * null only for the message line.
 */
#if defined(WMDLINEWRAP) || OPT_MOUSE
WINDOW *
row2window(int row)
{
    register WINDOW *wp;

    for_each_visible_window(wp) {
      if (row >= wp->w_toprow && row <= mode_row(wp))
          return wp;
    }
    return 0;
}
#endif

/*
 * Recompute the given buffer. Save/restore its modes and position information
 * so that a redisplay will show as little change as possible.
 */
#if OPT_UPBUFF
typedef struct {
    WINDOW *wp;
    struct VAL w_vals[MAX_W_VALUES];
    int top;
    int line;
    int col;
} SAVEWIN;

static SAVEWIN *recomp_tbl;
static size_t recomp_len;

static void
recompute_buffer(BUFFER *bp)
{
    register WINDOW *wp;
    register SAVEWIN *tbl;

    struct VAL b_vals[MAX_B_VALUES];
    size_t num = 0;
    BUFFER *savebp = curbp;
    WINDOW *savewp = curwp;
    int mygoal = curgoal;

    if (!b_val(bp, MDUPBUFF)) {
      b_clr_obsolete(bp);
      return;
    }
    if (recomp_len < bp->b_nwnd) {
      recomp_len = bp->b_nwnd + 1;
      recomp_tbl = (recomp_tbl != 0)
          ? typereallocn(SAVEWIN, recomp_tbl, recomp_len)
          : typeallocn(SAVEWIN, recomp_len);
      if (recomp_tbl == 0) {
          recomp_len = 0;
          return;
      }
    }
    tbl = recomp_tbl;

    /* remember where we are, to reposition */
    /* ...in case line is deleted from buffer-list */
    relisting_b_vals = 0;
    relisting_w_vals = 0;
    if (curbp == bp) {
      relisting_b_vals = b_vals;
    } else {
      curbp = bp;
      curwp = bp2any_wp(bp);
    }
    for_each_visible_window(wp) {
      if (wp->w_bufp == bp) {
          if (wp == savewp)
            relisting_w_vals = tbl[num].w_vals;
          curwp = wp;         /* to make 'getccol()' work */
          curbp = curwp->w_bufp;
          tbl[num].wp = wp;
          tbl[num].top = line_no(bp, wp->w_line.l);
          tbl[num].line = line_no(bp, wp->w_dot.l);
          tbl[num].col = getccol(FALSE);
          save_vals(NUM_W_VALUES, global_w_values.wv,
                  tbl[num].w_vals, wp->w_values.wv);
          if (++num >= recomp_len)
            break;
      }
    }
    curwp = savewp;
    curbp = savebp;

    save_vals(NUM_B_VALUES, global_b_values.bv, b_vals, bp->b_values.bv);
    (bp->b_upbuff) (bp);
    copy_mvals(NUM_B_VALUES, bp->b_values.bv, b_vals);

    /* reposition and restore */
    while (num-- != 0) {
      curwp = wp = tbl[num].wp;
      curbp = curwp->w_bufp;
      (void) gotoline(TRUE, tbl[num].top);
      wp->w_line.l = wp->w_dot.l;
      wp->w_line.o = 0;
      if (tbl[num].line != tbl[num].top)
          (void) gotoline(TRUE, tbl[num].line);
      (void) gocol(tbl[num].col);
      wp->w_flag |= WFMOVE;
      copy_mvals(NUM_W_VALUES, wp->w_values.wv, tbl[num].w_vals);
    }
    curwp = savewp;
    curbp = savebp;
    curgoal = mygoal;
    b_clr_obsolete(bp);
    relisting_b_vals = 0;
    relisting_w_vals = 0;
}
#endif /* OPT_UPBUFF */

static int
TypeAhead(int force)
{
    if (force != TRUE) {
      if ((force == FALSE && !global_g_val(GMDSMOOTH_SCROLL))
          || (force == SORTOFTRUE))
          return (keystroke_avail());
    }
    return FALSE;
}

/*
 * Update virtual screen line, given a LINE pointer.
 */
static void
update_screen_line(WINDOW *wp, LINEPTR lp, int sline)
{
    C_NUM left;

    /*
     * Mark the screen lines changed, resetting the requests for reverse
     * video.  Set the global 'horscroll' to the amount of horizontal
     * scrolling.
     */
#ifdef WMDLINEWRAP
    if (w_val(wp, WMDLINEWRAP)) {
      int top_line = sline - wp->w_line.o;
      register int m = (top_line >= 0) ? top_line : 0;
      register int n = top_line + line_height(wp, lp);
      while (n > m)
          if (--n < mode_row(wp)) {
            vscreen[n]->v_flag |= VFCHG;
            vscreen[n]->v_flag &= ~VFREQ;
          }
      horscroll = 0;
    } else
#endif
    if (sline >= 0) {
      vscreen[sline]->v_flag |= VFCHG;
      vscreen[sline]->v_flag &= ~VFREQ;
      if (w_val(wp, WVAL_SIDEWAYS))
          horscroll = w_val(wp, WVAL_SIDEWAYS);
    }
    left = horscroll;

    if (lp != win_head(wp)) {
      vtmove(sline, -left);
      vtset(lp, wp);
      if (left && sline >= 0) {
          register int zero = nu_width(wp);
          vscreen[sline]->v_text[zero] = MRK_EXTEND_LEFT;
          if (vtcol <= zero)
            vtcol = zero + 1;
      }
    } else {
      vtmove(sline, 0);
      vtputc(MRK_EMPTY);
    }
    horscroll = 0;
#if OPT_COLOR
    if (sline >= 0) {
      ReqFcolor(vscreen[sline]) = gfcolor;
      ReqBcolor(vscreen[sline]) = gbcolor;
    }
#endif
}

/*
 * Update the current line to the virtual screen.
 *
 * 'wp' points to the window to update current line in.
 */
static void
update_oneline(WINDOW *wp)
{
    register LINEPTR lp;      /* line to update */
    register int sline;       /* physical screen line to update */

    /* search down the line we want */
    lp = wp->w_line.l;
    sline = TopRow(wp);
    while (lp != wp->w_dot.l) {
      sline += line_height(wp, lp);
      lp = lforw(lp);
    }

    update_screen_line(wp, lp, sline);
    vteeol();
}

/*
 * Update all the lines in a window on the virtual screen.
 *
 * 'wp' points to the window to update current line in.
 */
static void
update_all(WINDOW *wp)
{
    register LINEPTR lp;      /* line to update */
    register int sline;       /* physical screen line to update */

    /* search down the lines, updating them */
    lp = wp->w_line.l;
    sline = TopRow(wp);
    while (sline < mode_row(wp)) {
      update_screen_line(wp, lp, sline);
      vteeol();
      sline += line_height(wp, lp);
      if (lp != win_head(wp))
          lp = lforw(lp);
    }
}

#if OPT_VIDEO_ATTRS
/* Merge (or combine) the specified attribute into the vscreen structure */
static void
mergeattr(WINDOW *wp, int row, int start_col, int end_col, VIDEO_ATTR attr)
{
    int col;
#ifdef WMDLINEWRAP
    if (w_val(wp, WMDLINEWRAP)) {
      for (col = start_col; col <= end_col; col++) {
          int x = row + col / term.cols;
          if (x < 0)
            continue;
          if (x < mode_row(wp)) {
            int y = col % term.cols;
            vscreen[x]->v_attrs[y] =
                (VIDEO_ATTR) ((vscreen[x]->v_attrs[y] | (attr & ~VAREV))
                          ^ (attr & VAREV));
          } else
            break;
      }
    } else
#endif
    {
      if (end_col >= term.cols)
          end_col = term.cols - 1;
      for (col = start_col; col <= end_col; col++)
          vscreen[row]->v_attrs[col] =
            (VIDEO_ATTR) ((vscreen[row]->v_attrs[col] | (attr & ~VAREV))
                        ^ (attr & VAREV));
    }
}

#if OPT_LINE_ATTRS
static void
update_line_attrs(WINDOW *wp)
{
    int row;
    LINEPTR lp;
    int linewrap;

#ifdef WMDLINEWRAP
    linewrap = w_val(wp, WMDLINEWRAP);
#else
    linewrap = 0;
#endif

    row = TopRow(wp);
    lp = wp->w_line.l;
    while (row < wp->w_toprow + wp->w_ntrows && lp != win_head(wp)) {
      int save_offset = w_left_margin(wp);
      int save_column = 0;
      if (lp->l_attrs) {
          C_NUM start_col, end_col;
          int i, a;
          /* Attempt to set starting point intelligently.  */
          if (wp->w_line.o < 0)
            i = col2offs(wp, lp, -(wp->w_line.o * term.cols));
          else {
            if (w_val(wp, WVAL_SIDEWAYS))
                i = col2offs(wp, lp, 0);
            else
                i = 0;
          }
          /* If i > 0, make sure it indexes a valid attribute.  */
          if (i > 0) {
            int ii;
            for (ii = 0; ii < i; ii++)
                if (lp->l_attrs[ii] == 0)
                  break;
            i = ii;
          }
          /* Scan attributes... */
          for (;;) {
            if (lp->l_attrs[i] == 0)
                break;  /* get out if at end */
            while (lp->l_attrs[i] == 1)
                i++;    /* skip normals */
            a = lp->l_attrs[i];
            if (a == 0)
                break;  /* get out if at end */
            start_col = offs2col0(wp, lp, i, &save_offset, &save_column);
            i++;
            while (lp->l_attrs[i] == a)
                i++;    /* find run of same attr */
            if (start_col < w_left_margin(wp))
                start_col = w_left_margin(wp);
            end_col = offs2col0(wp, lp, i, &save_offset, &save_column) - 1;
            if (!linewrap) {
                if (start_col > term.cols)
                  break;
                mergeattr(wp, row, start_col, end_col,
                        line_attr_tbl[a].vattr);
                if (end_col > term.cols)
                  break;
            } else {
                /* mergeattr() handles wrapping of attributes for us.  */
                mergeattr(wp, row, start_col, end_col,
                        line_attr_tbl[a].vattr);
                if (row + end_col / term.cols >= mode_row(wp))
                  break;      /* Get out if no more to do.  */
            }
          }
      }
      row += line_height(wp, lp);
      lp = lforw(lp);
    }
}
#endif /* OPT_LINE_ATTRS */

static void
update_window_attrs(WINDOW *wp)
{
    AREGION *ap;
    int i;

    L_NUM start_wlnum, end_wlnum;
    LINEPTR lp;
    int rows;

    /*
     * Clear portion of virtual screen associated with window pointer
     * of all attributes.
     */
    /* FIXME: color; need to set to value indicating fg and bg for window */
    for (i = wp->w_toprow + wp->w_ntrows - 1; i >= wp->w_toprow; i--)
      set_vattrs(i, 0, 0, term.cols);

#if OPT_LINE_ATTRS
    update_line_attrs(wp);
#endif /* OPT_LINE_ATTRS */

    /*
     * No need to do any more work on this window if there are no
     * attributes.
     */
    if (wp->w_bufp->b_attribs == NULL)
      return;

    /*
     * Compute starting and ending line numbers for the window.  We
     * also fill in lmap which is used for mapping line numbers to
     * screen row numbers.
     */
    lp = wp->w_line.l;
    start_wlnum =
      end_wlnum = line_no(wp->w_bufp, lp);
    rows = wp->w_ntrows;
    lmap[end_wlnum - start_wlnum] = TopRow(wp);
    while ((rows -= line_height(wp, lp)) > 0) {
      lp = lforw(lp);
      end_wlnum++;
      lmap[end_wlnum - start_wlnum] = TopRow(wp) + wp->w_ntrows - rows;
    }

    /*
     * Set current attributes in virtual screen associated with window
     * pointer.
     */
    for (ap = wp->w_bufp->b_attribs; ap != NULL;) {
      VIDEO_ATTR attr;
      C_NUM start_col, end_col;
      C_NUM rect_start_col = 0, rect_end_col = 0;
      L_NUM start_rlnum, end_rlnum, lnum, start_lnum, end_lnum;
      start_rlnum = line_no(wp->w_bufp, ap->ar_region.r_orig.l);
      end_rlnum = line_no(wp->w_bufp, ap->ar_region.r_end.l);

      /* Garbage collect empty visible regions */
      if (start_rlnum == end_rlnum
          && VATTRIB(ap->ar_vattr) != 0
          && ap->ar_region.r_orig.o >= ap->ar_region.r_end.o) {
          AREGION *nap = ap->ar_next;
          free_attrib(wp->w_bufp, ap);
          ap = nap;
          continue;
      }

      if (start_rlnum > start_wlnum) {
          start_lnum = start_rlnum;
          lp = ap->ar_region.r_orig.l;
      } else {
          start_lnum = start_wlnum;
          lp = wp->w_line.l;
      }
      end_lnum = (end_rlnum < end_wlnum) ? end_rlnum : end_wlnum;
      attr = ap->ar_vattr;
      if (ap->ar_shape == RECTANGLE) {
          int n;
          rect_start_col = mark2col(wp, ap->ar_region.r_orig);
          rect_end_col = mark2col(wp, ap->ar_region.r_end);
          if (rect_end_col < rect_start_col) {
            C_NUM col = rect_end_col;
            rect_end_col = rect_start_col;
            rect_start_col = col;
            n = MARK2COL(wp, ap->ar_region.r_orig);
          } else {
            n = MARK2COL(wp, ap->ar_region.r_end);
          }
          if (rect_end_col < n)
            rect_end_col = n;
      }
      for (lnum = start_lnum; lnum <= end_lnum; lnum++, lp = lforw(lp)) {
          int row;
          if (ap->ar_shape == RECTANGLE) {
            start_col = rect_start_col;
          } else if (lnum == start_rlnum) {
            start_col = mark2col(wp, ap->ar_region.r_orig);
          } else {
            start_col = w_left_margin(wp) + nu_width(wp);
          }

          if (start_col < w_left_margin(wp))
            start_col = (lnum == start_rlnum)
                ? w_left_margin(wp) + nu_width(wp)
                : w_left_margin(wp);

          if (ap->ar_shape == RECTANGLE) {
            end_col = rect_end_col;
          } else if (lnum == end_rlnum) {
            end_col = mark2col(wp, ap->ar_region.r_end) - 1;
          } else {
            end_col = offs2col(wp, lp, llength(lp) + 1) - 1;
#ifdef WMDLINEWRAP
            if (w_val(wp, WMDLINEWRAP)
                && (end_col % term.cols) == 0)
                end_col--;    /* cannot highlight the newline */
#endif
          }
          row = lmap[lnum - start_wlnum];
          mergeattr(wp, row, start_col, end_col, attr);
      }
      ap = ap->ar_next;
    }
}
#endif /* OPT_VIDEO_ATTRS */

/*
 * If the screen is garbage, clear the physical screen and the virtual screen
 * and force a full update.
 */
static void
update_garbaged_screen(void)
{
#if !FRAMEBUF && !OPT_PSCREEN
    register int j;
#endif
    register int i;

    for (i = 0; i < term.rows; ++i) {
      vscreen[i]->v_flag |= VFCHG;
#if OPT_REVSTA
      vscreen[i]->v_flag &= ~VFREV;
#endif
#if OPT_COLOR
      CurFcolor(vscreen[i]) = -1;
      CurBcolor(vscreen[i]) = -1;
#endif
#if ! FRAMEBUF && ! OPT_PSCREEN
      for (j = 0; j < term.cols; ++j) {
          CELL_TEXT(i, j) = ' ';
#if OPT_VIDEO_ATTRS
          CELL_ATTR(i, j) = 0;
#endif /* OPT_VIDEO_ATTRS */
      }
#endif
    }
#if !OPT_PSCREEN
#if OPT_COLOR
    term.setfore(gfcolor);    /* Need to set before erasing. */
    term.setback(gbcolor);
#endif
    movecursor(0, 0);         /* Erase the screen. */
    reset_term_attrs();
    term.eeop();
#else
    kbd_erase_to_end(0);
#endif
    sgarbf = FALSE;           /* Erase-page clears */
    need_update = FALSE;
    kbd_flush();
}

/*
 * Update the extended line which the cursor is currently on at a column less
 * than the terminal width.  The line will be scrolled right or left to let the
 * user see where the cursor is.
 */
static int
update_extended_line(int col, int excess, int use_excess)
{
    register int rcursor;
    register int zero = nu_width(curwp);
    int scrollsiz;

    /* calculate what column the real cursor will end up in */
    if (!use_excess) {
      curcol = col;
      rcursor = (col % (term.cols - term.cols / 10));
    } else {
      scrollsiz = term.rows - ((term.rows / 10) * 2);
      rcursor = ((excess - 1) % scrollsiz) + term.cols / 10;
    }
    horscroll = col - rcursor;

    /* Scan through the line outputting characters to the virtual screen
     * once we reach the left edge.  */
    vtmove(currow, -horscroll);     /* start scanning offscreen */
    vtset(DOT.l, curwp);

    /* truncate the virtual line */
    vteeol();

    horscroll = 0;

    if (use_excess || col != rcursor) {         /* ... put a marker in column 1 */
      vscreen[currow]->v_text[zero] = MRK_EXTEND_LEFT;
    }
    vscreen[currow]->v_flag |= (VFEXT | VFCHG);
    return rcursor;
}

/*
 * Update the position of the hardware cursor and handle extended lines.  This
 * is the only update for simple moves.  Returns the screen location for the
 * cursor, and a boolean indicating if full sideways scroll was necessary
 */
static int
update_cursor_position(int *screenrowp, int *screencolp)
{
    register LINEPTR lp;
#ifdef WMDLINEWRAP
    register int i;
#endif
    register int col, excess;
    register int collimit;
    int moved = FALSE;
    int nuadj = is_empty_buf(curwp->w_bufp) ? 0 : nu_width(curwp);
    int liadj = (w_val(curwp, WMDLIST)) ? 1 : 0;

    /* find the current row */
    lp = curwp->w_line.l;
    currow = TopRow(curwp);
    while (lp != DOT.l) {
      currow += line_height(curwp, lp);
      lp = lforw(lp);
      if (lp == curwp->w_line.l
          || currow > mode_row(curwp)) {
          mlforce("BUG:  lost dot updpos().  setting at top");
          lp = curwp->w_line.l = DOT.l = lforw(buf_head(curbp));
          currow = TopRow(curwp);
      }
    }

    /*
     * Find the current column.  Reuse the ruler column-value if we
     * computed it.
     */
#ifdef WMDRULER
    if (w_val(curwp, WMDRULER))
      col = curwp->w_ruler_col + w_left_margin(curwp) - 1;
    else
#endif
      col = dot_to_vcol(curwp) + w_left_margin(curwp);

#ifdef WMDLINEWRAP
    if (w_val(curwp, WMDLINEWRAP)) {
      curcol = col;
      collimit = term.cols - nuadj;
      *screenrowp = currow;
      if (col >= collimit) {
          col -= collimit;
          *screenrowp += 1;
          if (col >= term.cols)
            *screenrowp += (col / term.cols);
          *screencolp = col % term.cols;
      } else {
          *screencolp = col + nuadj;
      }
      /* kludge to keep the cursor within the window */
      i = mode_row(curwp) - 1;
      if (*screenrowp > i) {
          *screenrowp = i;
          *screencolp = term.cols - 1;
      }
      return FALSE;
    } else
#endif
      *screenrowp = currow;

    /* ...adjust to offset from shift-margin */
    curcol = col - w_val(curwp, WVAL_SIDEWAYS);
    *screencolp = curcol;

    /* if extended, flag so and update the virtual line image */
    collimit = col_limit(curwp);
    excess = curcol - collimit + liadj;
    if ((excess > 0) || (excess == 0 &&
                   (DOT.o < llength(DOT.l) - 1))) {
      if (w_val(curwp, WMDHORSCROLL)) {
          (void) mvrightwind(TRUE, excess + collimit / 2);
          moved = TRUE;
      } else {
          *screencolp = update_extended_line(col, excess, TRUE);
      }
    } else if (w_val(curwp, WVAL_SIDEWAYS) && (curcol < 1)) {
      if (w_val(curwp, WMDHORSCROLL)) {
          (void) mvleftwind(TRUE, -curcol + collimit / 2 + 1);
          moved = TRUE;
      } else {
          *screencolp = update_extended_line(col, 0, FALSE);
      }
    } else {
      if (vscreen[currow]->v_flag & VFEXT) {
          update_screen_line(curwp, lp, currow);
          vteeol();
          /* this line no longer is extended */
          vscreen[currow]->v_flag &= ~VFEXT;
      }
    }
    if (!moved)
      *screencolp += nuadj;
    return moved;
}

#if CAN_SCROLL

/*
 * return TRUE on text match
 *
 * vrow - virtual row
 * prow - physical row
 */
static int
texttest(int vrow, int prow)
{
    struct VIDEO *vpv = vscreen[vrow];    /* virtual screen image */
    struct VIDEO *vpp = PScreen(prow);    /* physical screen image */

    return (!memcmp(vpv->v_text, vpp->v_text, (size_t) term.cols));
}

/*
 * return the index of the first trailing whitespace character
 */
static int
endofline(char *s, int n)
{
    int i;
    for (i = n - 1; i >= 0; i--)
      if (s[i] != ' ')
          return (i + 1);
    return (0);
}

/* move the "count" lines starting at "from" to "to" */
static void
scroll_screen(int from, int to, int count)
{
    beginDisplay();
    ttrow = ttcol = -1;
    term.scroll(from, to, count);
    endofDisplay();
}

/*
 * Optimize simple scrolled screen region movements.  Argument chooses between
 * looking for inserts or deletes.  Original code by Roger Ove, for an early
 * version of MicroEMACS.  used by permission.
 *
 * inserts - returns true if it does an optimization
 */
static int
simple_scroll(int inserts)
{
    struct VIDEO *vpv;        /* virtual screen image */
    struct VIDEO *vpp;        /* physical screen image */
    int i, j, k;
    int rows, cols;
    int first, match, count, ptarget = 0, vtarget = 0;
    size_t end;
    int longmatch, longcount;
    int longinplace, inplace; /* count of lines which are already
                           in the right place */
    int from, to;

    if (term.scroll == nullterm_scroll)         /* can't scroll */
      return FALSE;

    rows = term.rows - 1;
    cols = term.cols;

    /* find first line that doesn't match */
    first = -1;
    for (i = 0; i < rows; i++) {
      if (!texttest(i, i)) {
          first = i;
          break;
      }
    }
    if (first < 0)
      return FALSE;           /* there isn't one */

    vpv = vscreen[first];
    vpp = PScreen(first);

    if (inserts) {
      /* determine types of potential scrolls */
      end = endofline(vpv->v_text, cols);
      if (end == 0)
          ptarget = first;    /* newlines */
      else if (memcmp(vpp->v_text, vpv->v_text, end) == 0)
          ptarget = first + 1;      /* broken line newlines */
      else
          ptarget = first;
      from = ptarget;
    } else {
      from = vtarget = first + 1;
    }

    /* can we find that text elsewhere ? */
    longmatch = -1;
    longcount = 0;
    longinplace = 0;
    for (i = from + 1; i < rows; i++) {
      if (inserts ? texttest(i, from) : texttest(from, i)) {
          match = i;
          count = 1;
          inplace = texttest(match, match) ? 1 : 0;
          for (j = match + 1, k = from + 1; j < rows && k < rows; j++, k++) {
            if (inserts ? texttest(j, k) : texttest(k, j)) {
                count++;
                if (texttest(j, j))
                  inplace++;
            } else
                break;
          }
          if (longcount - longinplace < count - inplace) {
            longcount = count;
            longmatch = match;
            longinplace = inplace;
          }
      }
    }
    match = longmatch;
    count = longcount;

    if (!inserts) {
      /* full kill case? */
      if (match > 0 && texttest(first, match - 1)) {
          vtarget--;
          match--;
          count++;
      }
    }

    if (match > 0 && count > 2) {   /* got a scroll */
      /* move the count lines starting at ptarget to match */
      /* mlwrite("scrolls: move the %d lines starting at %d to %d",
         count,ptarget,match);
       */
      if (inserts) {
          from = ptarget;
          to = match;
      } else {
          from = match;
          to = vtarget;
      }
#if OPT_PSCREEN
      /*
       * Update lines _before_ the scroll so that they will be available for
       * any updates which need to be done (due to a GraphicsExpose event in
       * X11...these occur when scrolling a partially obscured window).  Note
       * that in the typical case of scrolling a line or two that very few
       * memory accesses are performed.  We mostly shuffle pointers around.
       */
#define SWAP_PLINE(a, b) do { VIDEO *temp = pscreen[a];     \
                        pscreen[a] = pscreen[b];      \
                        pscreen[b] = temp; } one_time
#define CLEAR_PLINE(a)  do {                                \
                      MARK_LINE_DIRTY(a);                   \
                      for (j = 0; j < term.cols; j++) {           \
                        CELL_TEXT(a,j) = ' ';               \
                        CELL_ATTR(a,j) = 0;                 \
                      }                               \
                    } one_time
      if (from < to) {
          /* FIXME: color */
          for (i = from; i < to; i++)
            CLEAR_PLINE(i + count);
          for (i = count - 1; i >= 0; i--)
            SWAP_PLINE(from + i, to + i);
      } else {
          /* FIXME: color */
          for (i = to; i < from; i++)
            CLEAR_PLINE(i);
          for (i = 0; i < count; i++)
            SWAP_PLINE(from + i, to + i);
      }
#endif /* OPT_PSCREEN */
      scroll_screen(from, to, count);
#if !OPT_PSCREEN
      for (i = 0; i < count; i++) {
          vpp = PScreen(to + i);
          vpv = vscreen[to + i];
          (void) memcpy(vpp->v_text, vpv->v_text, (size_t) cols);
      }
#if OPT_VIDEO_ATTRS && !FRAMEBUF
#define SWAP_ATTR_PTR(a, b) do { VIDEO_ATTR *temp = pscreen[a]->v_attrs;  \
                         pscreen[a]->v_attrs = pscreen[b]->v_attrs; \
                         pscreen[b]->v_attrs = temp; } one_time
      if (from < to) {
          /* FIXME: color */
          for (i = from; i < to; i++)
            for (j = 0; j < term.cols; j++)
                CELL_ATTR(i + count, j) = 0;
          for (i = count - 1; i >= 0; i--)
            SWAP_ATTR_PTR(from + i, to + i);

      } else {
          /* FIXME: color */
          for (i = to; i < from; i++)
            for (j = 0; j < term.cols; j++)
                CELL_ATTR(i, j) = 0;
          for (i = 0; i < count; i++)
            SWAP_ATTR_PTR(from + i, to + i);
      }
#undef SWAP_ATTR_PTR
#endif /* OPT_VIDEO_ATTRS */
      if (inserts) {
          from = ptarget;
          to = match;
      } else {
          from = vtarget + count;
          to = match + count;
      }
      for (i = from; i < to; i++) {
          char *txt;
          txt = PScreen(i)->v_text;
          for (j = 0; j < term.cols; ++j)
            txt[j] = ' ';
          vscreen[i]->v_flag |= VFCHG;
      }
#endif /* !OPT_PSCREEN */
      return (TRUE);
    }
    return (FALSE);
}

#endif /* CAN_SCROLL */

/*
 * update the physical screen from the virtual screen.  The 'force' flag
 * forces an update even if there is type-ahead.
 */
static void
update_physical_screen(int force GCC_UNUSED)
{
    register int i;

#if CAN_SCROLL
    if (scrflags & WFKILLS)
      (void) simple_scroll(FALSE);
    if (scrflags & WFINS)
      (void) simple_scroll(TRUE);
    scrflags = 0;
#endif

    for (i = 0; i < term.rows; ++i) {
      /* for each line that needs to be updated */
      if ((vscreen[i]->v_flag & (VFCHG | VFCOL)) != 0) {
#if !DISP_X11
          if (TypeAhead(force))
            return;
#endif
          update_line(i, 0, term.cols);
      }
    }
}

/*
 * Redisplay the mode line for the window pointed to by the "wp".
 * modeline() is the only routine that has any idea of how the modeline is
 * formatted.  You can change the modeline format by hacking at this
 * routine.  Called by "update" any time there is a dirty window.
 */
#if OPT_MLFORMAT || OPT_POSFORMAT || OPT_TITLE
static void
mlfs_prefix(const char **fsp, char **msp, int lchar)
{
    register const char *fs = *fsp;
    register char *ms = *msp;
    if (*fs == ':') {
      fs++;
      while (*fs && *fs != ':') {
          if (*fs != '%')
            *ms++ = *fs++;
          else {
            fs++;
            switch (*fs++) {
            case EOS:
                fs--;
                break;
            case '%':
                *ms++ = '%';
                break;
            case ':':
                *ms++ = ':';
                break;
            case '-':
                *ms++ = (char) lchar;
                break;
            default:
                *ms++ = '%';
                *ms++ = *(fs - 1);
                break;
            }
          }
      }
    }
    *fsp = fs;
    *msp = ms;
}

static void
mlfs_suffix(const char **fsp, char **msp, int lchar)
{
    mlfs_prefix(fsp, msp, lchar);
    if (**fsp == ':')
      (*fsp)++;
}

static void
mlfs_skipfix(const char **fsp)
{
    register const char *fs = *fsp;
    if (*fs == ':') {
      for (fs++; *fs && *fs != ':'; fs++) ;
      if (*fs == ':')
          fs++;
      for (; *fs && *fs != ':'; fs++) ;
      if (*fs == ':')
          fs++;
    }
    *fsp = fs;
}
#endif /* OPT_MLFORMAT */

#define PutModename(format, name) { \
            if (ms != 0) { \
                  ms = lsprintf(ms, format, \
                        mcnt ? ' ' : '[', \
                        name); \
            } \
            mcnt++; \
      }

#define PutMode(mode,name) \
      if (b_val(bp, mode)) PutModename("%c%s", name)

#if OPT_MAJORMODE
#define PutMajormode(bp) if (bp->majr != 0) PutModename("%c%smode", bp->majr->name)
#else
#define PutMajormode(bp)      /*nothing */
#endif

static int
modeline_modes(BUFFER *bp, char **msptr)
{
    register char *ms = msptr ? *msptr : 0;
    register size_t mcnt = 0;

    PutMajormode(bp);
#if COMPLETE_FILES || COMPLETE_DIRS
    if (b_is_directory(bp))
      PutModename("%c%s", "directory");
#endif
#if !OPT_MAJORMODE
    PutMode(MDCMOD, "cmode");
#endif
#if OPT_ENCRYPT
    PutMode(MDCRYPT, "crypt");
#endif
    {
#if OPT_RECORDSEP_CHOICES
      int show = FALSE;

      switch (b_val(bp, VAL_SHOW_FORMAT)) {
      case SF_ALWAYS:
          show = TRUE;
          break;
      case SF_NEVER:
          show = FALSE;
          break;
      case SF_DIFFERS:
          show = b_val(bp, VAL_RECORD_SEP) != global_b_val(VAL_RECORD_SEP);
          break;
      case SF_FOREIGN:
          show = b_val(bp, VAL_RECORD_SEP) != RS_DEFAULT;
          break;
      case SF_LOCAL:
          show = is_local_b_val(bp, VAL_RECORD_SEP);
          break;
      }
      if (show)
          PutModename("%c%s",
                  choice_to_name(fsm_recordsep_choices,
                               b_val(bp, VAL_RECORD_SEP)));
#else
      PutMode(MDDOS, "dos-style");
#endif
    }
    PutMode(MDREADONLY, "read-only");
    PutMode(MDVIEW, "view-only");
    PutMode(MDLOADING, "loading");
#if OPT_LCKFILES
    PutMode(MDLOCKED, "locked by");
    if (ms != 0 && b_val(bp, MDLOCKED))
      ms = lsprintf(ms, " %s", b_val_ptr(bp, VAL_LOCKER));
#endif
    if (mcnt && ms)
      *ms++ = ']';
    if (b_is_changed(bp)) {
      if (ms != 0)
          ms = lsprintf(ms, "%s[modified]", mcnt ? " " : "");
      mcnt++;
    }
    if (kbd_mac_recording()) {
      if (ms != 0)
          ms = lsprintf(ms, "%s[recording]", mcnt ? " " : "");
      mcnt++;
    }
    if (ms != 0)
      *msptr = ms;
    return (mcnt != 0);
}

static int
modeline_show(WINDOW *wp, int lchar)
{
    register int ic = lchar;
    register BUFFER *bp = wp->w_bufp;

    if (b_val(bp, MDSHOWMODE)) {
#ifdef insertmode       /* insert mode is a trait for each window */
      if (wp->w_traits.insmode == INSMODE_INS)
          ic = 'I';
      else if (wp->w_traits.insmode == INSMODE_RPL)
          ic = 'R';
      else if (wp->w_traits.insmode == INSMODE_OVR)
          ic = 'O';
#else /* insertmode is a variable global to all windows */
      if (wp == curwp) {
          if (insertmode == INSMODE_INS)
            ic = 'I';
          else if (insertmode == INSMODE_RPL)
            ic = 'R';
          else if (insertmode == INSMODE_OVR)
            ic = 'O';
      }
#endif /* !defined(insertmode) */
    }
    return ic;
}

static const char *
rough_position(WINDOW *wp)
{
    LINE *lp = wp->w_line.l;
    int rows = wp->w_ntrows;
    const char *msg = 0;

    while (rows-- > 0) {
      lp = lforw(lp);
      if (lp == win_head(wp)) {
          msg = "bot";
          break;
      }
    }
    if (lback(wp->w_line.l) == win_head(wp)) {
      if (msg) {
          if (wp->w_line.l == win_head(wp))
            msg = "emp";
          else
            msg = "all";
      } else {
          msg = "top";
      }
    }
    if (!msg)
      msg = "mid";
    return msg;
}

#if OPT_MLFORMAT || OPT_POSFORMAT || OPT_TITLE

#define L_CURL '{'
#define R_CURL '}'

static int
percentage(WINDOW *wp)
{
    BUFFER *bp = wp->w_bufp;
    L_NUM val;
    L_NUM denom = vl_line_count(bp);

#ifdef WMDRULER
    if (w_val(wp, WMDRULER) && !is_empty_buf(bp))
      val = wp->w_ruler_line;
    else
#endif
      val = line_no(bp, wp->w_dot.l);

    return (denom == 0) ? 100 : ((val * 100) / denom);
}

/*
 * Format special single-use messages, i.e., the modeline format, which has
 * a number of special variables that we would like to output quickly.
 */
void
special_formatter(TBUFF ** result, const char *fs, WINDOW *wp)
{
    BUFFER *bp;
    char *ms;
    const char *save_fs;
    char left_ms[NFILEN * 2];
    char right_ms[NFILEN * 2];
    char temp[NFILEN];
    int col;
    int fc;
    int lchar;
    int need_eighty_column_indicator;
    int right_len;
    int n;

    tb_init(result, EOS);

    left_ms[0] = right_ms[0] = EOS;
    ms = left_ms;
    need_eighty_column_indicator = FALSE;

    bp = wp->w_bufp;
    if (wp == curwp) {        /* mark the current buffer */
      lchar = '=';
    } else {
#if OPT_REVSTA
      if (revexist)
          lchar = ' ';
      else
#endif
          lchar = '-';
    }

    while (*fs) {
      if (*fs != '%')
          *ms++ = *fs++;
      else {
          fs++;
          switch ((fc = *fs++)) {
          case EOS:           /* Null character ! */
            fs--;
            break;
          case '%':
          case ':':
            *ms++ = *(fs - 1);
            break;
          case '|':
            need_eighty_column_indicator = TRUE;
            break;
          case '-':
            *ms++ = (char) lchar;
            break;
          case '=':
            *ms = EOS;
            ms = right_ms;
            break;
          case 'i':           /* insert mode indicator */
            *ms++ = (char) modeline_show(wp, lchar);
            break;
          case 'b':
            ms = lsprintf(ms, "%s", bp->b_bname);
            break;
          case 'm':
            if (modeline_modes(bp, (char **) 0)) {
                mlfs_prefix(&fs, &ms, lchar);
                (void) modeline_modes(bp, &ms);
                mlfs_suffix(&fs, &ms, lchar);
            } else
                mlfs_skipfix(&fs);
            break;
          case 'f':
          case 'F':{
                char *p;
                int skip = TRUE;    /* assumption */

                if (bp->b_fname != 0) {
                  /*
                   * when b_fname is a pipe cmd, it can be
                   * arbitrarily long
                   */

                  vl_strncpy(temp, bp->b_fname, sizeof(temp));
                  temp[sizeof(temp) - 1] = '\0';
                  if ((p = shorten_path(temp, FALSE)) != 0
                      && *p
                      && !eql_bname(bp, p)
                      && (fc == 'f' ? !is_internalname(p)
                        : is_internalname(p))) {
                      mlfs_prefix(&fs, &ms, lchar);
                      for (; *p == ' '; p++) ;
                      ms = lsprintf(ms, "%s", p);
                      mlfs_suffix(&fs, &ms, lchar);
                      skip = FALSE;
                  }
                }
                if (skip)
                  mlfs_skipfix(&fs);
                break;
            }
#ifdef WMDRULER
          case 'l':           /* line number */
          case 'c':           /* column number */
          case 'p':           /* percentage */
          case 'L':           /* number of lines in buffer */

            if (w_val(wp, WMDRULER) && !is_empty_buf(wp->w_bufp)) {
                int val = 0;
                switch (fc) {
                case 'l':
                  val = wp->w_ruler_line;
                  break;
                case 'L':
                  val = vl_line_count(wp->w_bufp);
                  break;
                case 'c':
                  val = wp->w_ruler_col;
                  break;
                case 'p':
                  val = percentage(wp);
                  break;
                }
                mlfs_prefix(&fs, &ms, lchar);
                ms = lsprintf(ms, "%d", val);
                mlfs_suffix(&fs, &ms, lchar);
            } else
                mlfs_skipfix(&fs);
            break;

#endif
          case 'P':
            ms = lsprintf(ms, "%d", percentage(wp));
            break;

          case 'S':
            if (
#ifdef WMDRULER
                   !w_val(wp, WMDRULER) ||
#endif
                   is_empty_buf(wp->w_bufp)) {
                mlfs_prefix(&fs, &ms, lchar);
                ms = lsprintf(ms, " %s ", rough_position(wp));
                mlfs_suffix(&fs, &ms, lchar);
            } else
                mlfs_skipfix(&fs);
            break;
          case L_CURL:
            save_fs = fs;
            while (*fs != EOS && *fs != R_CURL)
                fs++;

            if (fs != save_fs) {
                int flag = clexec;
                char *save_execstr;
                TBUFF *tok = 0;

                save_execstr = execstr;
                clexec = TRUE;
                execstr = temp;

                strncpy0(temp, save_fs, fs + 1 - save_fs);
                execstr = get_token(execstr, &tok, EOS, (int *) 0);
                vl_strncpy(temp, tokval(temp), sizeof(temp));

                tb_free(&tok);
                execstr = save_execstr;
                clexec = flag;
            } else {
                *temp = EOS;
            }
            if (*fs != EOS)
                fs++;
            save_fs = fs;
            /*
             * Allow an optional <number><format> on the end of the
             * token, to reformat it.  Don't bother reformatting if
             * it is just a 'd' added to make the string unambiguous.
             */
            while (isDigit(*fs))
                fs++;
            if (isAlpha(*fs)) {
                if ((*fs != 'd' || fs != save_fs)) {
                  char format[NSTRING];

                  format[0] = '%';
                  strncpy0(format + 1, save_fs, fs + 2 - save_fs);
                  if (strchr("dDxXo", *fs)) {
                      int value = scan_int(temp);
                      ms = lsprintf(ms, format, value);
                  } else {
                      ms = lsprintf(ms, format, temp);
                  }
                } else {
                  ms = lsprintf(ms, "%s", temp);
                }
                fs++;
            } else {
                ms = lsprintf(ms, "%s", temp);
            }
            break;
          default:
            *ms++ = '%';
            *ms++ = *(fs - 1);
            break;
          }
      }
    }
    *ms++ = EOS;

    tb_bappend(result, left_ms, strlen(left_ms));

    if (((int) tb_length(*result) < term.cols)
      && (right_len = strlen(right_ms)) != 0) {
      for (n = term.cols - (int) tb_length(*result) - right_len; n > 0; n--)
          tb_append(result, lchar);
      if ((n = term.cols - right_len) < 0) {
          col = right_len + n;
          n = -n;
      } else {
          col = right_len;
          n = 0;
      }
      if ((col < term.cols)
          && (n < right_len))
          tb_bappend(result, right_ms + n, term.cols - col);
    }

    if (need_eighty_column_indicator) {         /* mark column 80 */
      int left = -nu_width(wp);
#ifdef WMDLINEWRAP
      if (!w_val(wp, WMDLINEWRAP))
#endif
          left += w_val(wp, WVAL_SIDEWAYS);
      n = term.cols + left;
      col = 80 - left;

      if ((n > 80) && (col >= 0)) {
          for (n = tb_length(*result); n < col; n++)
            tb_append(result, lchar);
          if (tb_values(*result)[col] == lchar)
            tb_values(*result)[col] = '|';
      }
    }
    tb_append(result, EOS);
}
#endif

static void
update_modeline(WINDOW *wp)
{
#if OPT_MLFORMAT
    static TBUFF *result;
#else
    BUFFER *bp;
    char temp[NFILEN];
    int col;
    int lchar;
    int right_len;
    char left_ms[NFILEN * 2];
    char *ms;
    char right_ms[NFILEN * 2];
#endif
    register int n;

    term.cursorvis(FALSE);

    n = mode_row(wp);         /* Location. */
#if OPT_VIDEO_ATTRS
    {
      VIDEO_ATTR attr;
      if (wp == curwp)
          attr = VAMLFOC;
      else
          attr = VAML;
#if OPT_REVSTA
#ifdef      GVAL_MCOLOR
      attr |= (global_g_val(GVAL_MCOLOR) & ~VASPCOL);
#else
      attr |= VAREV;
#endif
#endif
      vscreen[n]->v_flag |= VFCHG;
      set_vattrs(n, 0, attr, term.cols);
    }
#else
    vscreen[n]->v_flag |= VFCHG | VFREQ | VFCOL;      /* Redraw next time. */
#endif
#if OPT_COLOR
    ReqFcolor(vscreen[n]) = gfcolor;
    ReqBcolor(vscreen[n]) = gbcolor;
#endif
    vtmove(n, 0);       /* Seek to right line. */

#if OPT_MLFORMAT
    special_formatter(&result, modeline_format, wp);
    vtputsn(tb_values(result), tb_length(result));
#else /* hard-coded format */
    bp = wp->w_bufp;
    if (bp == 0)
      bp = curbp;
    if (bp == 0)
      bp = bminip;
    if (wp == curwp) {        /* current buffer shows as = */
      lchar = '=';
    } else {
#if OPT_REVSTA
      if (revexist)
          lchar = ' ';
      else
#endif
          lchar = '-';
    }
    left_ms[0] = right_ms[0] = EOS;
    ms = left_ms;
    ms = lsprintf(ms, "%c%c%c %s ",
              lchar, modeline_show(wp, lchar), lchar, bp->b_bname);
    if (modeline_modes(bp, &ms))
      *ms++ = ' ';
    if (bp->b_fname != 0
      && (shorten_path(vl_strncpy(temp, bp->b_fname, sizeof(temp)), FALSE))
      && !eql_bname(bp, temp)) {
      if (is_internalname(temp)) {
          for (n = term.cols - (13 + strlen(temp));
             n > 0; n--)
            *ms++ = lchar;
      } else {
          ms = lsprintf(ms, "is");
      }
      ms = lsprintf(ms, " %s ", temp);
    }
#ifdef WMDRULER
    if (w_val(wp, WMDRULER))
      (void) lsprintf(right_ms, " (%d,%d) %3P",
                  wp->w_ruler_line, wp->w_ruler_col, lchar);
    else
#endif
      (void) lsprintf(right_ms, " %s %3P", rough_position(wp), lchar);

    *ms++ = EOS;
    right_len = strlen(right_ms);
    vtputsn(left_ms, term.cols);
    for (n = term.cols - strlen(left_ms) - right_len; n > 0; n--)
      vtputc(lchar);
    vtcol = term.cols - right_len;
    if (vtcol < 0) {
      n = -vtcol;
      vtcol = 0;
    } else
      n = 0;
    vtputsn(right_ms + n, term.cols - vtcol);
    col = -nu_width(wp);
#ifdef WMDLINEWRAP
    if (!w_val(wp, WMDLINEWRAP))
#endif
      col += w_val(wp, WVAL_SIDEWAYS);
    n = term.cols + col;
    col = 80 - col;

    if ((n > 80)
      && (col >= 0)
      && (vscreen[vtrow]->v_text[col] == lchar)) {
      vtcol = col;
      vtputc('|');
    }
#endif /* OPT_MLFORMAT */
    term.cursorvis(TRUE);
}

/*
 * Update all the mode lines.
 */
void
upmode(void)
{
    register WINDOW *wp;

    for_each_window(wp)
      wp->w_flag |= WFMODE;
}

/*
 * Check to see if the cursor is on in the window and re-frame it if needed or
 * wanted.
 */
static void
reframe_cursor_position(WINDOW *wp)
{
    register LINEPTR dlp;
    register LINEPTR lp;
    register int i = 0;
    register int rows;
    int founddot = FALSE;     /* set to true iff we find dot */
    int tildecount;

    /* if not a requested reframe, check for a needed one */
    if ((wp->w_flag & WFFORCE) == 0) {
      /* initial update in main.c may not set these first... */
      if (wp->w_dot.l == (LINE *) 0) {
          wp->w_dot.l = lforw(win_head(wp));
          wp->w_dot.o = 0;
      }
      if (wp->w_line.l == (LINE *) 0) {
          wp->w_line.l = wp->w_dot.l;
          wp->w_line.o = 0;
      }
#if CAN_SCROLL
      /* loop from one line above the window to one line after */
      lp = lback(wp->w_line.l);
      i = -line_height(wp, lp);
#else
      /* loop through the window */
      lp = wp->w_line.l;
      i = 0;
#endif
      for_ever {
          /* if the line is in the window, no reframe */
          if (lp == wp->w_dot.l) {
            founddot = TRUE;
#if CAN_SCROLL
            /* if not _quite_ in, we'll reframe gently */
            if (i < 0 || i >= wp->w_ntrows) {
                /* if the terminal can't help, then
                   we're simply outside */
                if (term.scroll == nullterm_scroll)
                  i = wp->w_force;
                break;
            }
#endif
#ifdef WMDLINEWRAP
            if (w_val(wp, WMDLINEWRAP)
                && i > 0
                && i + line_height(wp, lp) > wp->w_ntrows) {
                i = wp->w_ntrows;
                break;
            }
#endif
            lp = wp->w_line.l;
            goto kill_tildes;
          }

          /* if we are at the end of the file, reframe */
          if (i >= 0 && lp == win_head(wp))
            break;

          /* on to the next line */
          if (i >= wp->w_ntrows) {
            i = 0;            /* dot-not-found */
            break;
          }
          i += line_height(wp, lp);
          lp = lforw(lp);
      }
    }
#if CAN_SCROLL
    if (i < 0) {        /* we're just above the window */
      i = 1;                  /* put dot at first line */
      scrflags |= WFINS;
    } else if (founddot && (i >= wp->w_ntrows)) {
      /* we're just below the window */
      i = -1;                 /* put dot at last line */
      scrflags |= WFKILLS;
    } else              /* put dot where requested */
#endif
      i = wp->w_force;  /* (is 0, unless reposition() was called) */

    wp->w_flag |= WFMODE;
    wp->w_line.o = 0;

    /* w_force specifies which line of the window dot should end up on */
    /*      positive --> lines from the top                         */
    /*      negative --> lines from the bottom                      */
    /*      zero --> middle of window                               */

    lp = wp->w_dot.l;

#ifdef WMDLINEWRAP
    /*
     * Center dot in middle of screen with line-wrapping
     */
    if (i == 0 && w_val(wp, WMDLINEWRAP)) {
      rows = (wp->w_ntrows - line_height(wp, lp) + 2) / 2;
      while (rows > 0) {
          dlp = lback(lp);
          if (dlp == win_head(wp))
            break;
          if ((rows -= line_height(wp, dlp)) < 0)
            break;
          lp = dlp;
      }
    } else
#endif
    {
      rows = (i != 0)
          ? wp->w_ntrows
          : wp->w_ntrows / 2;
      while (rows > 0) {
          if ((i > 0)
            && (--i <= 0))
            break;
          dlp = lback(lp);
          if (dlp == win_head(wp))
            break;
          if ((rows -= line_height(wp, lp)) < 0)
            break;
          lp = dlp;
      }
      if (rows < line_height(wp, lp)
          && (lp != wp->w_dot.l)) {
          while (i++ < 0) {
            dlp = lforw(lp);
            if (dlp == win_head(wp))
                break;
            else
                lp = dlp;
          }
      }
    }
  kill_tildes:
    /* Eliminate as many tildes as possible from bottom */
    dlp = lp;
    rows = wp->w_ntrows;
    while (rows > 0 && (dlp != win_head(wp))) {
      rows -= line_height(wp, dlp);
      dlp = lforw(dlp);
    }
    dlp = lback(lp);

    tildecount = (wp->w_ntrows * ntildes) / 100;
    if (tildecount == wp->w_ntrows)
      tildecount--;

    while (dlp != win_head(wp)
         && (rows -= line_height(wp, dlp)) >= tildecount) {
      lp = dlp;
      dlp = lback(lp);
    }

    /* and reset the current line-at-top-of-window */
    if (lp != win_head(wp)    /* mouse click could be past end */
      &&lp != wp->w_line.l) { /* no need to set it if already there */
      wp->w_line.l = lp;
      wp->w_flag |= WFHARD;
      wp->w_flag &= ~WFFORCE;
    }
#ifdef WMDLINEWRAP
    /*
     * Ensure that dot will be visible, by adjusting the w_line.o value if
     * necessary.  That's used to start the beginning of the first line in
     * a window "before" the start of the window.
     */
    if (w_val(wp, WMDLINEWRAP)
      && sameline(wp->w_line, wp->w_dot)) {
      int want = mark2col(wp, wp->w_dot) / term.cols;
      if (want + wp->w_line.o >= wp->w_ntrows) {
          wp->w_line.o = wp->w_ntrows - want - 1;
          wp->w_flag |= WFHARD;
          wp->w_flag &= ~WFFORCE;
      } else if (want + wp->w_line.o < 0) {
          wp->w_line.o = -want;
          wp->w_flag |= WFHARD;
          wp->w_flag &= ~WFFORCE;
      }
    } else if (!w_val(wp, WMDLINEWRAP)) {
      if (wp->w_line.o < 0) {
          wp->w_line.o = 0;
          wp->w_flag |= WFHARD;
          wp->w_flag &= ~WFFORCE;
      }
    }
#endif
}

/*
 * De-extend any line that deserves it.
 */
static void
de_extend_lines(void)
{
    register WINDOW *wp;
    register LINEPTR lp;
    register int i;

    for_each_visible_window(wp) {
      lp = wp->w_line.l;
      i = TopRow(wp);

      while (i < mode_row(wp)) {
          if (i >= 0
            && (vscreen[i]->v_flag & VFEXT) != 0) {
            if ((wp != curwp)
                || (lp != wp->w_dot.l)
                || ((i != currow)
                  && (curcol < col_limit(wp)))) {
                update_screen_line(wp, lp, i);
                vteeol();
                /* this line no longer is extended */
                vscreen[i]->v_flag &= ~VFEXT;
            }
          }
          i += line_height(wp, lp);
          lp = lforw(lp);
      }
    }
}

/*
 * Make sure that the display is right. This is a three part process. First,
 * scan through all of the windows looking for dirty ones. Check the framing,
 * and refresh the screen. Second, make sure that "currow" and "curcol" are
 * correct for the current window. Third, make the virtual and physical
 * screens the same.
 */
int
update(int force /* force update past type ahead? */ )
{
    register WINDOW *wp;
    int origrow, origcol;
    int screenrow, screencol;
    int updated = FALSE;

    /* Get row and column prior to doing the update in case we are
     * reading the message line.
     */
    origrow = ttrow;
    origcol = ttcol;

    if (clhide || !is_visible_window(curwp))
      return TRUE;
    if (!curbp || !vscreen || !curwp)     /* not initialized */
      return FALSE;
    /* don't try to update if we got called via a read-hook on a window
     * that isn't complete.
     */
    if (curwp->w_bufp == 0
      || curwp->w_bufp->b_nwnd == 0
      || curwp->w_ntrows < 1)
      return FALSE;
    if (TypeAhead(force))
      return SORTOFTRUE;

    /* don't display during keystroke replay */
    if (!force && kbd_replaying(TRUE) && (get_recorded_char(FALSE) != -1))
      return SORTOFTRUE;

    beginDisplay();

#if OPT_TITLE
    /*
     * Only update the title when we have nothing better to do.  The
     * auto_set_title logic is otherwise likely to set the title frequently if
     * [Buffer List] is visible.
     */
    if (tb_values(request_title) != 0
      && (tb_values(current_title) == 0
          || strcmp(tb_values(current_title), tb_values(request_title)))) {
      tb_copy(&current_title, request_title);
      term.set_title(tb_values(request_title));
    }
#endif

    /* propagate mode line changes to all instances of
       a buffer displayed in more than one window */
    for_each_visible_window(wp) {
      if (wp->w_flag & WFMODE) {
          if (wp->w_bufp->b_nwnd > 1) {
            /* make sure all previous windows have this */
            register WINDOW *owp;
            for_each_visible_window(owp) {
                if (owp->w_bufp == wp->w_bufp)
                  owp->w_flag |= WFMODE;
            }
          }
      }
#ifdef WMDLINEWRAP
      /* Make sure that movements in very long wrapped lines
         get updated properly.  */
      if (w_val(wp, WMDLINEWRAP) && line_height(wp, wp->w_dot.l)
          > wp->w_ntrows)
          wp->w_flag |= WFMOVE;
#endif
#if OPT_CACHE_VCOL
      if (wp->w_flag & (WFEDIT | WFHARD | WFMODE | WFKILLS | WFINS))
          wp->w_traits.w_left_dot = nullmark;
#endif
    }

    /* look for scratch-buffers that should be recomputed.  */
#if OPT_UPBUFF
    for_each_visible_window(wp) {
      if (b_is_obsolete(wp->w_bufp))
          recompute_buffer(wp->w_bufp);
    }
#endif

    /* look for windows that need the ruler updated */
#ifdef WMDRULER
    for_each_visible_window(wp) {
      if (w_val(wp, WMDRULER)) {
          int line = line_no(wp->w_bufp, wp->w_dot.l);
          int col = dot_to_vcol(wp) + 1;

          if (line != wp->w_ruler_line
            || col != wp->w_ruler_col) {
            wp->w_ruler_line = line;
            wp->w_ruler_col = col;
            wp->w_flag |= WFMODE;
          }
      } else if (wp->w_flag & WFSTAT) {
          wp->w_flag |= WFMODE;
      }
      wp->w_flag &= ~WFSTAT;
    }
#endif

    do {
      /* update any windows that need refreshing */
      for_each_visible_window(wp) {
          if (wp->w_flag) {
            if ((wp->w_flag & ~(WFMOVE)) && !updated++)
                term.cursorvis(FALSE);
            /* if the window has changed, service it */
            reframe_cursor_position(wp);  /* check the framing */
            if (wp->w_flag & (WFKILLS | WFINS)) {
                scrflags |= (wp->w_flag & (WFINS | WFKILLS));
                wp->w_flag &= ~(WFKILLS | WFINS);
            }
            if ((wp->w_flag & ~(WFMODE)) == WFEDIT)
                update_oneline(wp);       /* update EDITed line */
            else if (wp->w_flag & ~(WFMOVE | WFMODE))
                update_all(wp);     /* update all lines */
#if OPT_SCROLLBARS
            if (wp->w_flag & (WFHARD | WFSBAR))
                gui_update_scrollbar(wp);
#endif /* OPT_SCROLLBARS */

#if OPT_VIDEO_ATTRS
            if (wp->w_flag & (WFHARD | WFEDIT))
                update_window_attrs(wp);
#endif
            if (scrflags || (wp->w_flag & (WFMODE | WFCOLR)))
                update_modeline(wp);      /* update modeline */
            wp->w_flag = 0;
            wp->w_force = 0;
          }
      }

      /* Recalculate the current hardware cursor location.  If true, we've
       * done a horizontal scroll.
       */
    } while (update_cursor_position(&screenrow, &screencol));

    /* check for lines to de-extend */
    de_extend_lines();

    /* if screen is garbage, re-plot it */
    if (sgarbf || need_update)
      update_garbaged_screen();

    /* update the virtual screen to the physical screen */
    update_physical_screen(force);

    /* update the cursor and flush the buffers */
    if (reading_msg_line)
      movecursor(origrow, origcol);
    else
      movecursor(screenrow, screencol);

    if (updated)
      term.cursorvis(TRUE);
#if OPT_ICURSOR
    {
      static int old_insertmode = -1;

      if (insertmode != old_insertmode) {
          old_insertmode = insertmode;
          term.icursor(insertmode);
      }
    }
#endif
    term.flush();
    endofDisplay();
    i_displayed = TRUE;

    while (allow_working_msg() && (chg_width && chg_height))
      newscreensize(chg_height, chg_width);
    return (TRUE);
}

/*
 * Highlight the requested portion of the screen.  We're mucking with the video
 * attributes on the line here, so this is NOT good code - it would be better
 * if there was an individual colour attribute per character, rather than per
 * row, but I didn't write the original code.  Anyway, hilite is called only
 * once so far, so it's not that big a deal.
 *
 * row       - row to start highlighting
 * colfrom   - column to start highlighting
 * colto     - column to end highlighting
 * on        - start highlighting
 */
void
hilite(int row, int colfrom, int colto, int on)
{
#if !OPT_VIDEO_ATTRS
    register VIDEO *vp1 = vscreen[row];
#endif
#ifdef WMDLINEWRAP
    WINDOW *wp = row2window(row);
    if (w_val(wp, WMDLINEWRAP)) {
      if (colfrom < 0)
          colfrom = 0;
      if (colfrom > term.cols) {
          do {
            row++;
            colfrom -= term.cols;
            colto -= term.cols;
            hilite(row, colfrom, colto, on);
          } while (colto > term.cols);
          return;
      }
    }
#endif
    if (row < term.rows - 1 && (colfrom >= 0 || colto <= term.cols)) {
      if (colfrom < 0)
          colfrom = 0;
      if (colto > term.cols)
          colto = term.cols;
#if OPT_VIDEO_ATTRS
      if (on) {
          int col;
          for (col = colfrom; col < colto; col++)
            vscreen[row]->v_attrs[col] |= VAREV;
      } else {
          int col;
          for (col = colfrom; col < colto; col++)
            vscreen[row]->v_attrs[col] &= ~VAREV;
      }
      vscreen[row]->v_flag |= VFCHG;
      update_line(row, 0, term.cols);
#else /* OPT_VIDEO_ATTRS */
      if (on) {
          vp1->v_flag |= VFREQ;
      } else {
          vp1->v_flag &= ~VFREQ;
      }
      update_line(row, colfrom, colto);
#endif /* OPT_VIDEO_ATTRS */
    }
}

/*
 * Send a command to the terminal to move the hardware cursor to row "row"
 * and column "col". The row and column arguments are origin 0. Optimize out
 * random calls. Update "ttrow" and "ttcol".
 */
void
movecursor(int row, int col)
{
    beginDisplay();
    if ((row != ttrow || col != ttcol)
      && (row >= 0 && row < term.rows)
      && (col >= 0 && col < term.cols)) {
      ttrow = row;
      ttcol = col;
      term.curmove(row, col);
    }
    endofDisplay();
}

void
bottomleft(void)
{
    movecursor(term.rows - 1, 0);
}

/*
 * Erase the message line. This is a special routine because the message line
 * is not considered to be part of the virtual screen. It always works
 * immediately; the terminal buffer is flushed via a call to the flusher.
 */
void
mlerase(void)
{
    if (!clhide) {
      beginDisplay();
      kbd_erase_to_end(0);
      kbd_flush();
      endofDisplay();
    }
}

void
mlsavec(int c)
{
    tb_append(&mlsave, c);
}

/*
 * Do the real message-line work.  Keep track of the physical cursor position.
 * A small class of printf like format items is handled.  Set the "message
 * line" flag TRUE.
 */
static void
mlmsg(const char *fmt, va_list * app)
{
    static int recur;
    int end_at;
#if DISP_NTWIN
    int cursor_state = 0;
#endif
    int do_crlf = (strchr(fmt, '\n') != 0
               || strchr(fmt, '\r') != 0);

    if (no_minimsgs) {
      kbd_alarm();
      return;
    }
    if (quiet)
      return;

#if DISP_NTWIN
    if (recur == 0) {
      /*
       * Winvile internally manages its own cursor and this usually works
       * fine.  However, if the user activates functionality that triggers
       * windows message(s) (e.g., invoke the Win32 common open dialog) and
       * if this functionality writes message line status text as a side
       * effect, then winvile may inadvertently enable its cursor during the
       * display update.  If this happens, the GDI drops a cursor glyph in
       * the message line.  Fix this problem by forcing the winvile cursor
       * off now.
       */
      cursor_state = winvile_cursor_state(FALSE, FALSE);
    }
#endif
    if (recur++) {
      /*EMPTY */ ;
    } else if (sgarbf) {
      /* then we'll lose the message on the next update(),
       * so save it now */
      tb_init(&mlsave, EOS);
#if OPT_POPUP_MSGS
      if (global_g_val(GMDPOPUP_MSGS) || (curwp == 0)) {
          TRACE(("mlmsg popup_msgs #1 for '%s'\n", fmt));
          popup_msgs();
          msg_putc('\n');
          dfoutfn = msg_putc;
      } else
#endif
          dfoutfn = mlsavec;
      dofmt(fmt, app);
    } else {
      beginDisplay();

      kbd_expand = -1;
#if OPT_POPUP_MSGS
      if (global_g_val(GMDPOPUP_MSGS)) {
          TRACE(("mlmsg popup_msgs #2 for '%s'\n", fmt));
          popup_msgs();
          if (tb_length(mlsave) == 0) {
            msg_putc('\n');
            dfoutfn = msg_putc;
          } else {
            dfoutfn = kbd_putc;
          }
      } else
#endif
          dfoutfn = kbd_putc;

      if (*fmt != '\n') {
          kbd_erase_to_end(0);
          dofmt(fmt, app);
          kbd_expand = 0;

          /* if we can, erase to the end of screen */
          end_at = wminip->w_dot.o;
          kbd_erase_to_end(end_at);
          tb_init(&mlsave, EOS);
          kbd_flush();
      }
      if (do_crlf) {
          kbd_openup();
      }
      endofDisplay();
    }
    recur--;
#if DISP_NTWIN
    if (recur == 0) {

      /* restore previous cursor state if it was ON. */

      if (cursor_state)
          winvile_cursor_state(TRUE, TRUE);
    }
#endif
}

/*
 * Format a string onto the message line, but only if it's appropriate.
 * Keyboard macro replay, "dot" command replay, command buffer execution,
 * "terse" mode, and the user-accessible "discmd" state variable can all
 * suppress this.
 */
/* VARARGS1 */
void
mlwrite(const char *fmt,...)
{
    va_list ap;
    if (global_b_val(MDTERSE) || kbd_replaying(FALSE) || !vl_msgs) {
      if (!clhide)
          bottomleft();
      return;
    }
    va_start(ap, fmt);
    mlmsg(fmt, &ap);
    va_end(ap);
}

/*
 * Unconditionally format a string out onto the message line.
 */
/* VARARGS1 */
void
mlforce(const char *fmt,...)
{
    va_list ap;
    va_start(ap, fmt);
    mlmsg(fmt, &ap);
    va_end(ap);
}

/* VARARGS1 */
void
mlprompt(const char *fmt,...)
{
    va_list ap;
    int osgarbf = sgarbf;
    if (!vl_msgs) {
      bottomleft();
      return;
    }
    sgarbf = FALSE;
    va_start(ap, fmt);
    mlmsg(fmt, &ap);
    va_end(ap);
    sgarbf = osgarbf;
}

/* VARARGS */
void
dbgwrite(const char *fmt,...)
{
    char temp[80];

    va_list ap;               /* ptr to current data field */
    va_start(ap, fmt);
    lsprintf(temp, "[press ^G to continue] %s", fmt);
    mlmsg(temp, &ap);
    va_end(ap);
    beginDisplay();
    while (term.getch() != '\007') {
      ;
    }
    endofDisplay();
}

/*
 * Do the equivalent of 'perror()' on the message line
 */
void
mlerror(const char *s)
{
    const char *t = 0;
#ifdef HAVE_STRERROR
#if SYS_VMS
    if (errno == EVMSERR)
      t = strerror(errno, vaxc$errno);
    else
#endif /* SYS_VMS */
    if (errno > 0)
      t = strerror(errno);
#else
#ifdef HAVE_SYS_ERRLIST
    if (errno > 0 && errno < sys_nerr)
      t = sys_errlist[errno];
#endif /* HAVE_SYS_ERRLIST */
#endif /* HAVE_STRERROR */
    if (t != 0)
      mlwarn("[Error %s: %s]", s, t);
    else
      mlwarn("[Error %s: unknown system error %d]", s, errno);
}

/*
 * Emit a warning message (with alarm)
 */
/* VARARGS1 */
void
mlwarn(const char *fmt,...)
{
    va_list ap;
    va_start(ap, fmt);
    mlmsg(fmt, &ap);
    va_end(ap);
    kbd_alarm();
}

/*
 * Local sprintf -- similar to standard libc, but
 *  returns pointer to null character at end of buffer, so it can
 *  be called repeatedly, as in:
 *    cp = lsprintf(cp, fmt, args...);
 *
 */

static char *lsp;

static void
lspputc(int c)
{
    *lsp++ = (char) c;
}

/* VARARGS1 */
char *
lsprintf(char *buf, const char *fmt,...)
{
    va_list ap;
    va_start(ap, fmt);

    lsp = buf;
    dfoutfn = lspputc;

    dofmt(fmt, &ap);
    va_end(ap);

    *lsp = EOS;
    return lsp;
}

/*
 * Buffer printf -- like regular printf, but puts characters
 *    into the BUFFER.
 */
void
bputc(int c)
{
    if (c == '\n')
      (void) lnewline();
    else
      (void) linsert(1, c);
}

/* printf into curbp, at DOT */
/* VARARGS */
void
bprintf(const char *fmt,...)
{
    va_list ap;

    dfoutfn = bputc;

    va_start(ap, fmt);
    dofmt(fmt, &ap);
    va_end(ap);
}

#if OPT_EVAL || OPT_DEBUGMACROS
/* printf into [Trace] */
void
tprintf(const char *fmt,...)
{
    static int nested;

    WINDOW *save_wp;
    BUFFER *bp;
    BUFFER *save_bp = curbp;
    W_TRAITS save_w_traits;
    OutFunc save_outfn;

    if (!nested
      && (bp = make_ro_bp(TRACE_BufName, BFINVS)) != 0
      && (save_wp = push_fake_win(bp)) != 0) {
      va_list ap;

      nested = TRUE;

      save_outfn = dfoutfn;
      dfoutfn = bputc;

      save_w_traits = curwp->w_traits;
      DOT.l = lback(buf_head(curbp));
      DOT.o = 0;

      va_start(ap, fmt);
      dofmt(fmt, &ap);
      va_end(ap);

      TRACE(("tprintf {%.*s}\n", llength(lback(DOT.l)), lback(DOT.l)->l_text));

      b_clr_changed(bp);
      curwp->w_traits = save_w_traits;

      pop_fake_win(save_wp, save_bp);
      dfoutfn = save_outfn;
      nested = FALSE;
    }
}
#endif

#if defined(SIGWINCH) && ! DISP_X11
/* ARGSUSED */
SIGT
sizesignal(int ACTUAL_SIG_ARGS GCC_UNUSED)
{
    int w, h;
    int old_errno = errno;

    getscreensize(&w, &h);

    if ((h > 1 && h != term.rows) || (w > 1 && w != term.cols))
      newscreensize(h, w);

    setup_handler(SIGWINCH, sizesignal);
    errno = old_errno;
    SIGRET;
}
#endif

void
newscreensize(int h, int w)
{
    TRACE(("newscreensize(h=%d, w=%d)\n", h, w));
    /* do the change later */
    if (im_displaying
#if OPT_WORKING
      || !i_displayed
#endif
      ) {
      chg_width = w;
      chg_height = h;
    } else {
      beginDisplay();
      chg_width = chg_height = 0;
      if ((h > term.maxrows) || (w > term.maxcols)) {
          int old_r, old_c;
          old_r = term.maxrows;
          old_c = term.maxcols;
          term.maxrows = h;
          term.maxcols = w;
          if (!vtinit()) {    /* allocation failure */
            term.maxrows = old_r;
            term.maxcols = old_c;
            endofDisplay();
            return;
          }
      }
      endofDisplay();

      if (newlength(TRUE, h) && newwidth(TRUE, w))
          (void) update(TRUE);
    }
}

#if OPT_WORKING
/*
 * Start the timer that controls the "working..." message.
 */
static void
start_working(void)
{
    TRACE2(("start_working\n"));
    setup_handler(SIGALRM, imworking);
    (void) alarm(1);
    im_timing = TRUE;
}

/*
 * When we stop the timer, we should cleanup the "working..." message.
 */
static void
stop_working(void)
{
    TRACE2(("stop_working\n"));
    if (mpresf) {       /* erase leftover working-message */
      int save_row = ttrow;
      int save_col = ttcol;
      kbd_overlay(0);
      kbd_flush();
      movecursor(save_row, save_col);
      term.flush();
    }
}

/*
 * Displays alternate
 *    "working..." and
 *    "...working"
 * at the end of the message line if it has been at least a second since
 * displaying anything or waiting for keyboard input.  The cur_working and
 * max_working values are used in 'slowreadf()' to show the progress of reading
 * large files.
 */

/*ARGSUSED*/
SIGT
imworking(int ACTUAL_SIG_ARGS GCC_UNUSED)
{
    static int flip;
    static int skip;

    TRACE2(("imworking(%d)\n", signo));
    /* (if GMDWORKING is _not_ set, or MDTERSE is set, we're allowed to erase,
     * but not to write.  If we do erase, we don't reschedule the alarm, since
     * setting the mode will call us again to start things up)
     */
    if (allow_working_msg()) {
      TRACE2(("...allow_working_msg\n"));
      if (im_waiting(-1)) {
          TRACE2(("...im_waiting(-1)\n"));
          im_timing = FALSE;
          stop_working();
      } else if (ShowWorking()) {
          TRACE2(("...ShowWorking()\n"));
          if (skip) {
            TRACE2(("...skipped()\n"));
            skip = FALSE;
          } else {
#if DISP_X11
            x_working();
#else
            static const char *const msg[] =
            {"working", "..."};
            char result[20];

            TRACE2(("...FINALLY!()\n"));
            result[0] = EOS;
            if (cur_working != 0
                && cur_working != old_working) {
                char temp[20];
                int len = cur_working > 999999L ? 10 : 6;

                old_working = cur_working;
                strcat(result, right_num(temp, len, cur_working));
                if (len == 10)
                  /*EMPTY */ ;
                else if (max_working != 0) {
                  strcat(result, " ");
                  strcat(result, right_num(temp, 2,
                                     (100 * cur_working) / max_working));
                  strcat(result, "%");
                } else
                  strcat(result, " ...");
            } else {
                strcat(result, msg[flip]);
                strcat(result, msg[!flip]);
            }
            kbd_overlay(result);
            kbd_flush();
#endif
          }
          start_working();
          flip = !flip;
      } else {
          TRACE2(("...NOT ShowWorking()\n"));
          stop_working();
          skip = TRUE;
      }
    } else {
      TRACE2(("... NOT allow_working_msg(%d/%d/%d)%s ShowWorking\n",
            vile_is_busy, im_displaying, !i_displayed,
            ShowWorking()? "" : " NOT"));
      if (ShowWorking()) {    /* keep the timer running */
          start_working();
          flip = !flip;
      }
    }
}
#endif /* OPT_WORKING */

/*
 * Returns true if we should/could show a "working..." message or other busy
 * indicator, assuming that 'working' mode is set.
 */
int
allow_working_msg(void)
{
    return !(vile_is_busy || im_displaying || !i_displayed);
}

/*
 * Maintain a flag that records whether we're waiting for keyboard input.  As a
 * side-effect, restart the 'working' timer if we see that it's been made
 * inactive while we were waiting.
 */
int
im_waiting(int flag)
{
    static int waiting;
    if (flag >= 0) {          /* TRUE or FALSE set, negative used to query */
      waiting = flag;
#if OPT_WORKING
      if (!waiting && !im_timing && ShowWorking())
          start_working();
#endif
    }
    return waiting;
}

#if OPT_PSCREEN
/* Most of the code in this section is for making the message line work
 * right...it shouldn't be called to display the rest of the screen.
 */
static int psc_row;
static int psc_col;

#define SWAP_INT(x,y) \
      do { (x) = (x)+(y); (y) = (x)-(y); (x) = (x)-(y); } one_time
#define SWAP_VT_PSC \
      do { SWAP_INT(vtcol, psc_col); SWAP_INT(vtrow, psc_row); } one_time

OUTC_DCL
psc_putchar(OUTC_ARGS)
{
    if (c == '\b') {
      if (psc_col > 0)
          psc_col--;
    } else {
      SWAP_VT_PSC;
      vtputc(c);
      vscreen[vtrow]->v_flag |= VFCHG;
      SWAP_VT_PSC;
    }
    OUTC_RET c;
}

void
psc_flush(void)
{
    update_line(term.rows - 1, 0, term.cols);
    term.pflush();
}

void
psc_move(int row, int col)
{
    psc_row = row;
    psc_col = col;
}

void
psc_eeol(void)
{
    if (ttrow >= 0 && ttrow < term.rows && ttcol >= 0) {
      VIDEO_ATTR *vp = &vscreen[ttrow]->v_attrs[ttcol];
      char *cp = &vscreen[ttrow]->v_text[ttcol];
      char *cplim = &vscreen[ttrow]->v_text[term.cols];
      vscreen[ttrow]->v_flag |= VFCHG;
      while (cp < cplim) {
          *vp++ = 0;
          *cp++ = ' ';
      }
    }
}

void
psc_eeop(void)
{
    int saverow = ttrow;
    int savecol = ttcol;
    while (ttrow < term.rows) {
      psc_eeol();
      ttrow++;
      ttcol = 0;
    }
    ttrow = saverow;
    ttcol = savecol;
}

/* ARGSUSED */
void
psc_rev(UINT huh GCC_UNUSED)
{
    /* do nothing */
}

#endif /* OPT_PSCREEN */

/* For memory-leak testing (only!), releases all display storage. */
#if NO_LEAKS
void
vt_leaks(void)
{
    vtfree();
#if OPT_UPBUFF
    FreeIfNeeded(recomp_tbl);
#endif
}
#endif

Generated by  Doxygen 1.6.0   Back to index