Home | History | Annotate | Line # | Download | only in vi
vs_refresh.c revision 1.6
      1 /*	$NetBSD: vs_refresh.c,v 1.6 2014/01/26 21:43:45 christos Exp $ */
      2 /*-
      3  * Copyright (c) 1992, 1993, 1994
      4  *	The Regents of the University of California.  All rights reserved.
      5  * Copyright (c) 1992, 1993, 1994, 1995, 1996
      6  *	Keith Bostic.  All rights reserved.
      7  *
      8  * See the LICENSE file for redistribution information.
      9  */
     10 
     11 #include "config.h"
     12 
     13 #include <sys/cdefs.h>
     14 #if 0
     15 #ifndef lint
     16 static const char sccsid[] = "Id: vs_refresh.c,v 10.50 2001/06/25 15:19:37 skimo Exp  (Berkeley) Date: 2001/06/25 15:19:37 ";
     17 #endif /* not lint */
     18 #else
     19 __RCSID("$NetBSD: vs_refresh.c,v 1.6 2014/01/26 21:43:45 christos Exp $");
     20 #endif
     21 
     22 #include <sys/types.h>
     23 #include <sys/queue.h>
     24 #include <sys/time.h>
     25 
     26 #include <bitstring.h>
     27 #include <ctype.h>
     28 #include <limits.h>
     29 #include <stdio.h>
     30 #include <stdlib.h>
     31 #include <string.h>
     32 
     33 #include "../common/common.h"
     34 #include "vi.h"
     35 
     36 #define	UPDATE_CURSOR	0x01			/* Update the cursor. */
     37 #define	UPDATE_SCREEN	0x02			/* Flush to screen. */
     38 
     39 static void	vs_modeline __P((SCR *));
     40 static int	vs_paint __P((SCR *, u_int));
     41 
     42 /*
     43  * vs_refresh --
     44  *	Refresh all screens.
     45  *
     46  * PUBLIC: int vs_refresh __P((SCR *, int));
     47  */
     48 int
     49 vs_refresh(SCR *sp, int forcepaint)
     50 {
     51 	GS *gp;
     52 	SCR *tsp;
     53 	int need_refresh;
     54 	u_int priv_paint, pub_paint;
     55 
     56 	gp = sp->gp;
     57 
     58 	/*
     59 	 * 1: Refresh the screen.
     60 	 *
     61 	 * If SC_SCR_REDRAW is set in the current screen, repaint everything
     62 	 * that we can find, including status lines.
     63 	 */
     64 	if (F_ISSET(sp, SC_SCR_REDRAW))
     65 		TAILQ_FOREACH(tsp, &sp->wp->scrq, q)
     66 			if (tsp != sp)
     67 				F_SET(tsp, SC_SCR_REDRAW | SC_STATUS);
     68 
     69 	/*
     70 	 * 2: Related or dirtied screens, or screens with messages.
     71 	 *
     72 	 * If related screens share a view into a file, they may have been
     73 	 * modified as well.  Refresh any screens that aren't exiting that
     74 	 * have paint or dirty bits set.  Always update their screens, we
     75 	 * are not likely to get another chance.  Finally, if we refresh any
     76 	 * screens other than the current one, the cursor will be trashed.
     77 	 */
     78 	pub_paint = SC_SCR_REFORMAT | SC_SCR_REDRAW;
     79 	priv_paint = VIP_CUR_INVALID | VIP_N_REFRESH;
     80 	if (O_ISSET(sp, O_NUMBER))
     81 		priv_paint |= VIP_N_RENUMBER;
     82 	TAILQ_FOREACH(tsp, &sp->wp->scrq, q)
     83 		if (tsp != sp && !F_ISSET(tsp, SC_EXIT | SC_EXIT_FORCE) &&
     84 		    (F_ISSET(tsp, pub_paint) ||
     85 		    F_ISSET(VIP(tsp), priv_paint))) {
     86 			(void)vs_paint(tsp,
     87 			    (F_ISSET(VIP(tsp), VIP_CUR_INVALID) ?
     88 			    UPDATE_CURSOR : 0) | UPDATE_SCREEN);
     89 			F_SET(VIP(sp), VIP_CUR_INVALID);
     90 		}
     91 
     92 	/*
     93 	 * 3: Refresh the current screen.
     94 	 *
     95 	 * Always refresh the current screen, it may be a cursor movement.
     96 	 * Also, always do it last -- that way, SC_SCR_REDRAW can be set
     97 	 * in the current screen only, and the screen won't flash.
     98 	 */
     99 	if (vs_paint(sp, UPDATE_CURSOR | (!forcepaint &&
    100 	    F_ISSET(sp, SC_SCR_VI) && KEYS_WAITING(sp) ? 0 : UPDATE_SCREEN)))
    101 		return (1);
    102 
    103 	/*
    104 	 * 4: Paint any missing status lines.
    105 	 *
    106 	 * XXX
    107 	 * This is fairly evil.  Status lines are written using the vi message
    108 	 * mechanism, since we have no idea how long they are.  Since we may be
    109 	 * painting screens other than the current one, we don't want to make
    110 	 * the user wait.  We depend heavily on there not being any other lines
    111 	 * currently waiting to be displayed and the message truncation code in
    112 	 * the msgq_status routine working.
    113 	 *
    114 	 * And, finally, if we updated any status lines, make sure the cursor
    115 	 * gets back to where it belongs.
    116 	 */
    117 	need_refresh = 0;
    118 	TAILQ_FOREACH(tsp, &sp->wp->scrq, q)
    119 		if (F_ISSET(tsp, SC_STATUS)) {
    120 			need_refresh = 1;
    121 			vs_resolve(tsp, sp, 0);
    122 		}
    123 	if (need_refresh)
    124 		(void)gp->scr_refresh(sp, 0);
    125 
    126 	/*
    127 	 * A side-effect of refreshing the screen is that it's now ready
    128 	 * for everything else, i.e. messages.
    129 	 */
    130 	F_SET(sp, SC_SCR_VI);
    131 	return (0);
    132 }
    133 
    134 /*
    135  * vs_paint --
    136  *	This is the guts of the vi curses screen code.  The idea is that
    137  *	the SCR structure passed in contains the new coordinates of the
    138  *	screen.  What makes this hard is that we don't know how big
    139  *	characters are, doing input can put the cursor in illegal places,
    140  *	and we're frantically trying to avoid repainting unless it's
    141  *	absolutely necessary.  If you change this code, you'd better know
    142  *	what you're doing.  It's subtle and quick to anger.
    143  */
    144 static int
    145 vs_paint(SCR *sp, u_int flags)
    146 {
    147 	GS *gp;
    148 	SMAP *smp, tmp;
    149 	VI_PRIVATE *vip;
    150 	db_recno_t lastline, lcnt;
    151 	size_t cwtotal, cnt, len, notused, off, y;
    152 	int ch = 0, didpaint, isempty, leftright_warp;
    153 	CHAR_T *p;
    154 
    155 #define	 LNO	sp->lno			/* Current file line. */
    156 #define	OLNO	vip->olno		/* Remembered file line. */
    157 #define	 CNO	sp->cno			/* Current file column. */
    158 #define	OCNO	vip->ocno		/* Remembered file column. */
    159 #define	SCNO	vip->sc_col		/* Current screen column. */
    160 
    161 	gp = sp->gp;
    162 	vip = VIP(sp);
    163 	if (vip == NULL)
    164 		return 0;
    165 	didpaint = leftright_warp = 0;
    166 
    167 	/*
    168 	 * 5: Reformat the lines.
    169 	 *
    170 	 * If the lines themselves have changed (:set list, for example),
    171 	 * fill in the map from scratch.  Adjust the screen that's being
    172 	 * displayed if the leftright flag is set.
    173 	 */
    174 	if (F_ISSET(sp, SC_SCR_REFORMAT)) {
    175 		/* Invalidate the line size cache. */
    176 		VI_SCR_CFLUSH(vip);
    177 
    178 		/* Toss vs_line() cached information. */
    179 		if (F_ISSET(sp, SC_SCR_TOP)) {
    180 			if (vs_sm_fill(sp, LNO, P_TOP))
    181 				return (1);
    182 		}
    183 		else if (F_ISSET(sp, SC_SCR_CENTER)) {
    184 			if (vs_sm_fill(sp, LNO, P_MIDDLE))
    185 				return (1);
    186 		} else
    187 			if (vs_sm_fill(sp, OOBLNO, P_TOP))
    188 				return (1);
    189 		F_SET(sp, SC_SCR_REDRAW);
    190 	}
    191 
    192 	/*
    193 	 * 6: Line movement.
    194 	 *
    195 	 * Line changes can cause the top line to change as well.  As
    196 	 * before, if the movement is large, the screen is repainted.
    197 	 *
    198 	 * 6a: Small screens.
    199 	 *
    200 	 * Users can use the window, w300, w1200 and w9600 options to make
    201 	 * the screen artificially small.  The behavior of these options
    202 	 * in the historic vi wasn't all that consistent, and, in fact, it
    203 	 * was never documented how various screen movements affected the
    204 	 * screen size.  Generally, one of three things would happen:
    205 	 *	1: The screen would expand in size, showing the line
    206 	 *	2: The screen would scroll, showing the line
    207 	 *	3: The screen would compress to its smallest size and
    208 	 *		repaint.
    209 	 * In general, scrolling didn't cause compression (200^D was handled
    210 	 * the same as ^D), movement to a specific line would (:N where N
    211 	 * was 1 line below the screen caused a screen compress), and cursor
    212 	 * movement would scroll if it was 11 lines or less, and compress if
    213 	 * it was more than 11 lines.  (And, no, I have no idea where the 11
    214 	 * comes from.)
    215 	 *
    216 	 * What we do is try and figure out if the line is less than half of
    217 	 * a full screen away.  If it is, we expand the screen if there's
    218 	 * room, and then scroll as necessary.  The alternative is to compress
    219 	 * and repaint.
    220 	 *
    221 	 * !!!
    222 	 * This code is a special case from beginning to end.  Unfortunately,
    223 	 * home modems are still slow enough that it's worth having.
    224 	 *
    225 	 * XXX
    226 	 * If the line a really long one, i.e. part of the line is on the
    227 	 * screen but the column offset is not, we'll end up in the adjust
    228 	 * code, when we should probably have compressed the screen.
    229 	 */
    230 	if (IS_SMALL(sp)) {
    231 		if (LNO < HMAP->lno) {
    232 			lcnt = vs_sm_nlines(sp, HMAP, LNO, sp->t_maxrows);
    233 			if (lcnt <= HALFSCREEN(sp))
    234 				for (; lcnt && sp->t_rows != sp->t_maxrows;
    235 				     --lcnt, ++sp->t_rows) {
    236 					++TMAP;
    237 					if (vs_sm_1down(sp))
    238 						return (1);
    239 				}
    240 			else
    241 				goto small_fill;
    242 		} else if (LNO > TMAP->lno) {
    243 			lcnt = vs_sm_nlines(sp, TMAP, LNO, sp->t_maxrows);
    244 			if (lcnt <= HALFSCREEN(sp))
    245 				for (; lcnt && sp->t_rows != sp->t_maxrows;
    246 				     --lcnt, ++sp->t_rows) {
    247 					if (vs_sm_next(sp, TMAP, TMAP + 1))
    248 						return (1);
    249 					++TMAP;
    250 					if (vs_line(sp, TMAP, NULL, NULL))
    251 						return (1);
    252 				}
    253 			else {
    254 small_fill:			(void)gp->scr_move(sp, LASTLINE(sp), 0);
    255 				(void)gp->scr_clrtoeol(sp);
    256 				for (; sp->t_rows > sp->t_minrows;
    257 				    --sp->t_rows, --TMAP) {
    258 					(void)gp->scr_move(sp, TMAP - HMAP, 0);
    259 					(void)gp->scr_clrtoeol(sp);
    260 				}
    261 				if (vs_sm_fill(sp, LNO, P_FILL))
    262 					return (1);
    263 				F_SET(sp, SC_SCR_REDRAW);
    264 				goto adjust;
    265 			}
    266 		}
    267 	}
    268 
    269 	/*
    270 	 * 6b: Line down, or current screen.
    271 	 */
    272 	if (LNO >= HMAP->lno) {
    273 		/* Current screen. */
    274 		if (LNO <= TMAP->lno)
    275 			goto adjust;
    276 		if (F_ISSET(sp, SC_SCR_TOP))
    277 			goto top;
    278 		if (F_ISSET(sp, SC_SCR_CENTER))
    279 			goto middle;
    280 
    281 		/*
    282 		 * If less than half a screen above the line, scroll down
    283 		 * until the line is on the screen.
    284 		 */
    285 		lcnt = vs_sm_nlines(sp, TMAP, LNO, HALFTEXT(sp));
    286 		if (lcnt < HALFTEXT(sp)) {
    287 			while (lcnt--)
    288 				if (vs_sm_1up(sp))
    289 					return (1);
    290 			goto adjust;
    291 		}
    292 		goto bottom;
    293 	}
    294 
    295 	/*
    296 	 * 6c: If not on the current screen, may request center or top.
    297 	 */
    298 	if (F_ISSET(sp, SC_SCR_TOP))
    299 		goto top;
    300 	if (F_ISSET(sp, SC_SCR_CENTER))
    301 		goto middle;
    302 
    303 	/*
    304 	 * 6d: Line up.
    305 	 */
    306 	lcnt = vs_sm_nlines(sp, HMAP, LNO, HALFTEXT(sp));
    307 	if (lcnt < HALFTEXT(sp)) {
    308 		/*
    309 		 * If less than half a screen below the line, scroll up until
    310 		 * the line is the first line on the screen.  Special check so
    311 		 * that if the screen has been emptied, we refill it.
    312 		 */
    313 		if (db_exist(sp, HMAP->lno)) {
    314 			while (lcnt--)
    315 				if (vs_sm_1down(sp))
    316 					return (1);
    317 			goto adjust;
    318 		}
    319 
    320 		/*
    321 		 * If less than a half screen from the bottom of the file,
    322 		 * put the last line of the file on the bottom of the screen.
    323 		 */
    324 bottom:		if (db_last(sp, &lastline))
    325 			return (1);
    326 		tmp.lno = LNO;
    327 		tmp.coff = HMAP->coff;
    328 		tmp.soff = 1;
    329 		lcnt = vs_sm_nlines(sp, &tmp, lastline+1, sp->t_rows);
    330 		if (lcnt < HALFTEXT(sp)) {
    331 			if (vs_sm_fill(sp, lastline, P_BOTTOM))
    332 				return (1);
    333 			F_SET(sp, SC_SCR_REDRAW);
    334 			goto adjust;
    335 		}
    336 		/* It's not close, just put the line in the middle. */
    337 		goto middle;
    338 	}
    339 
    340 	/*
    341 	 * If less than half a screen from the top of the file, put the first
    342 	 * line of the file at the top of the screen.  Otherwise, put the line
    343 	 * in the middle of the screen.
    344 	 */
    345 	tmp.lno = 1;
    346 	tmp.coff = HMAP->coff;
    347 	tmp.soff = 1;
    348 	lcnt = vs_sm_nlines(sp, &tmp, LNO, HALFTEXT(sp));
    349 	if (lcnt < HALFTEXT(sp)) {
    350 		if (vs_sm_fill(sp, 1, P_TOP))
    351 			return (1);
    352 	} else
    353 middle:		if (vs_sm_fill(sp, LNO, P_MIDDLE))
    354 			return (1);
    355 	if (0) {
    356 top:		if (vs_sm_fill(sp, LNO, P_TOP))
    357 			return (1);
    358 	}
    359 	F_SET(sp, SC_SCR_REDRAW);
    360 
    361 	/*
    362 	 * At this point we know part of the line is on the screen.  Since
    363 	 * scrolling is done using logical lines, not physical, all of the
    364 	 * line may not be on the screen.  While that's not necessarily bad,
    365 	 * if the part the cursor is on isn't there, we're going to lose.
    366 	 * This can be tricky; if the line covers the entire screen, lno
    367 	 * may be the same as both ends of the map, that's why we test BOTH
    368 	 * the top and the bottom of the map.  This isn't a problem for
    369 	 * left-right scrolling, the cursor movement code handles the problem.
    370 	 *
    371 	 * There's a performance issue here if editing *really* long lines.
    372 	 * This gets to the right spot by scrolling, and, in a binary, by
    373 	 * scrolling hundreds of lines.  If the adjustment looks like it's
    374 	 * going to be a serious problem, refill the screen and repaint.
    375 	 */
    376 adjust:	if (!O_ISSET(sp, O_LEFTRIGHT) &&
    377 	    (LNO == HMAP->lno || LNO == TMAP->lno)) {
    378 		cnt = vs_screens(sp, LNO, &CNO);
    379 		if (LNO == HMAP->lno && cnt < HMAP->soff) {
    380 			if ((HMAP->soff - cnt) > HALFTEXT(sp)) {
    381 				HMAP->soff = cnt;
    382 				vs_sm_fill(sp, OOBLNO, P_TOP);
    383 				F_SET(sp, SC_SCR_REDRAW);
    384 			} else
    385 				while (cnt < HMAP->soff)
    386 					if (vs_sm_1down(sp))
    387 						return (1);
    388 		}
    389 		if (LNO == TMAP->lno && cnt > TMAP->soff) {
    390 			if ((cnt - TMAP->soff) > HALFTEXT(sp)) {
    391 				TMAP->soff = cnt;
    392 				vs_sm_fill(sp, OOBLNO, P_BOTTOM);
    393 				F_SET(sp, SC_SCR_REDRAW);
    394 			} else
    395 				while (cnt > TMAP->soff)
    396 					if (vs_sm_1up(sp))
    397 						return (1);
    398 		}
    399 	}
    400 
    401 	/*
    402 	 * If the screen needs to be repainted, skip cursor optimization.
    403 	 * However, in the code above we skipped leftright scrolling on
    404 	 * the grounds that the cursor code would handle it.  Make sure
    405 	 * the right screen is up.
    406 	 */
    407 	if (F_ISSET(sp, SC_SCR_REDRAW)) {
    408 		if (O_ISSET(sp, O_LEFTRIGHT))
    409 			goto slow;
    410 		goto paint;
    411 	}
    412 
    413 	/*
    414 	 * 7: Cursor movements (current screen only).
    415 	 */
    416 	if (!LF_ISSET(UPDATE_CURSOR))
    417 		goto number;
    418 
    419 	/*
    420 	 * Decide cursor position.  If the line has changed, the cursor has
    421 	 * moved over a tab, or don't know where the cursor was, reparse the
    422 	 * line.  Otherwise, we've just moved over fixed-width characters,
    423 	 * and can calculate the left/right scrolling and cursor movement
    424 	 * without reparsing the line.  Note that we don't know which (if any)
    425 	 * of the characters between the old and new cursor positions changed.
    426 	 *
    427 	 * XXX
    428 	 * With some work, it should be possible to handle tabs quickly, at
    429 	 * least in obvious situations, like moving right and encountering
    430 	 * a tab, without reparsing the whole line.
    431 	 *
    432 	 * If the line we're working with has changed, reread it..
    433 	 */
    434 	if (F_ISSET(vip, VIP_CUR_INVALID) || LNO != OLNO)
    435 		goto slow;
    436 
    437 	/* Otherwise, if nothing's changed, ignore the cursor. */
    438 	if (CNO == OCNO)
    439 		goto fast;
    440 
    441 	/*
    442 	 * Get the current line.  If this fails, we either have an empty
    443 	 * file and can just repaint, or there's a real problem.  This
    444 	 * isn't a performance issue because there aren't any ways to get
    445 	 * here repeatedly.
    446 	 */
    447 	if (db_eget(sp, LNO, &p, &len, &isempty)) {
    448 		if (isempty)
    449 			goto slow;
    450 		return (1);
    451 	}
    452 
    453 #ifdef DEBUG
    454 	/* Sanity checking. */
    455 	if (CNO >= len && len != 0) {
    456 		msgq(sp, M_ERR, "Error: %s/%d: cno (%u) >= len (%u)",
    457 		     tail(__FILE__), __LINE__, CNO, len);
    458 		return (1);
    459 	}
    460 #endif
    461 	/*
    462 	 * The basic scheme here is to look at the characters in between
    463 	 * the old and new positions and decide how big they are on the
    464 	 * screen, and therefore, how many screen positions to move.
    465 	 */
    466 	if (CNO < OCNO) {
    467 		/*
    468 		 * 7a: Cursor moved left.
    469 		 *
    470 		 * Point to the old character.  The old cursor position can
    471 		 * be past EOL if, for example, we just deleted the rest of
    472 		 * the line.  In this case, since we don't know the width of
    473 		 * the characters we traversed, we have to do it slowly.
    474 		 */
    475 		p += OCNO;
    476 		cnt = (OCNO - CNO) + 1;
    477 		if (OCNO >= len)
    478 			goto slow;
    479 
    480 		/*
    481 		 * Quick sanity check -- it's hard to figure out exactly when
    482 		 * we cross a screen boundary as we do in the cursor right
    483 		 * movement.  If cnt is so large that we're going to cross the
    484 		 * boundary no matter what, stop now.
    485 		 */
    486 		if (SCNO + 1 + MAX_CHARACTER_COLUMNS < cnt)
    487 			goto slow;
    488 
    489 		/*
    490 		 * Count up the widths of the characters.  If it's a tab
    491 		 * character, go do it the the slow way.
    492 		 */
    493 		for (cwtotal = 0; cnt--; cwtotal += KEY_COL(sp, ch))
    494 			if ((ch = *(UCHAR_T *)p--) == '\t')
    495 				goto slow;
    496 
    497 		/*
    498 		 * Decrement the screen cursor by the total width of the
    499 		 * characters minus 1.
    500 		 */
    501 		cwtotal -= 1;
    502 
    503 		/*
    504 		 * If we're moving left, and there's a wide character in the
    505 		 * current position, go to the end of the character.
    506 		 */
    507 		if (KEY_COL(sp, ch) > 1)
    508 			cwtotal -= KEY_COL(sp, ch) - 1;
    509 
    510 		/*
    511 		 * If the new column moved us off of the current logical line,
    512 		 * calculate a new one.  If doing leftright scrolling, we've
    513 		 * moved off of the current screen, as well.
    514 		 */
    515 		if (SCNO < cwtotal)
    516 			goto slow;
    517 		SCNO -= cwtotal;
    518 	} else {
    519 		/*
    520 		 * 7b: Cursor moved right.
    521 		 *
    522 		 * Point to the first character to the right.
    523 		 */
    524 		p += OCNO + 1;
    525 		cnt = CNO - OCNO;
    526 
    527 		/*
    528 		 * Count up the widths of the characters.  If it's a tab
    529 		 * character, go do it the the slow way.  If we cross a
    530 		 * screen boundary, we can quit.
    531 		 */
    532 		for (cwtotal = SCNO; cnt--;) {
    533 			if ((ch = *(UCHAR_T *)p++) == '\t')
    534 				goto slow;
    535 			if ((cwtotal += KEY_COL(sp, ch)) >= SCREEN_COLS(sp))
    536 				break;
    537 		}
    538 
    539 		/*
    540 		 * Increment the screen cursor by the total width of the
    541 		 * characters.
    542 		 */
    543 		SCNO = cwtotal;
    544 
    545 		/* See screen change comment in section 6a. */
    546 		if (SCNO >= SCREEN_COLS(sp))
    547 			goto slow;
    548 	}
    549 
    550 	/*
    551 	 * 7c: Fast cursor update.
    552 	 *
    553 	 * We have the current column, retrieve the current row.
    554 	 */
    555 fast:	(void)gp->scr_cursor(sp, &y, &notused);
    556 	goto done_cursor;
    557 
    558 	/*
    559 	 * 7d: Slow cursor update.
    560 	 *
    561 	 * Walk through the map and find the current line.
    562 	 */
    563 slow:	for (smp = HMAP; smp->lno != LNO; ++smp);
    564 
    565 	/*
    566 	 * 7e: Leftright scrolling adjustment.
    567 	 *
    568 	 * If doing left-right scrolling and the cursor movement has changed
    569 	 * the displayed screen, scroll the screen left or right, unless we're
    570 	 * updating the info line in which case we just scroll that one line.
    571 	 * We adjust the offset up or down until we have a window that covers
    572 	 * the current column, making sure that we adjust differently for the
    573 	 * first screen as compared to subsequent ones.
    574 	 */
    575 	if (O_ISSET(sp, O_LEFTRIGHT)) {
    576 		/*
    577 		 * Get the screen column for this character, and correct
    578 		 * for the number option offset.
    579 		 */
    580 		cnt = vs_columns(sp, NULL, LNO, &CNO, NULL);
    581 		if (O_ISSET(sp, O_NUMBER))
    582 			cnt -= O_NUMBER_LENGTH;
    583 
    584 		/* Adjust the window towards the beginning of the line. */
    585 		off = smp->coff;
    586 		if (off >= cnt) {
    587 			do {
    588 				if (off >= O_VAL(sp, O_SIDESCROLL))
    589 					off -= O_VAL(sp, O_SIDESCROLL);
    590 				else {
    591 					off = 0;
    592 					break;
    593 				}
    594 			} while (off >= cnt);
    595 			goto shifted;
    596 		}
    597 
    598 		/* Adjust the window towards the end of the line. */
    599 		if ((off == 0 && off + SCREEN_COLS(sp) < cnt) ||
    600 		    (off != 0 && off + sp->cols < cnt)) {
    601 			do {
    602 				off += O_VAL(sp, O_SIDESCROLL);
    603 			} while (off + sp->cols < cnt);
    604 
    605 shifted:		/* Fill in screen map with the new offset. */
    606 			if (F_ISSET(sp, SC_TINPUT_INFO))
    607 				smp->coff = off;
    608 			else {
    609 				for (smp = HMAP; smp <= TMAP; ++smp)
    610 					smp->coff = off;
    611 				leftright_warp = 1;
    612 			}
    613 			goto paint;
    614 		}
    615 
    616 		/*
    617 		 * We may have jumped here to adjust a leftright screen because
    618 		 * redraw was set.  If so, we have to paint the entire screen.
    619 		 */
    620 		if (F_ISSET(sp, SC_SCR_REDRAW))
    621 			goto paint;
    622 	}
    623 
    624 	/*
    625 	 * Update the screen lines for this particular file line until we
    626 	 * have a new screen cursor position.
    627 	 */
    628 	for (y = -1,
    629 	    vip->sc_smap = NULL; smp <= TMAP && smp->lno == LNO; ++smp) {
    630 		if (vs_line(sp, smp, &y, &SCNO))
    631 			return (1);
    632 		if (y != (size_t)-1) {
    633 			vip->sc_smap = smp;
    634 			break;
    635 		}
    636 	}
    637 	goto done_cursor;
    638 
    639 	/*
    640 	 * 8: Repaint the entire screen.
    641 	 *
    642 	 * Lost big, do what you have to do.  We flush the cache, since
    643 	 * SC_SCR_REDRAW gets set when the screen isn't worth fixing, and
    644 	 * it's simpler to repaint.  So, don't trust anything that we
    645 	 * think we know about it.
    646 	 */
    647 paint:	for (smp = HMAP; smp <= TMAP; ++smp)
    648 		SMAP_FLUSH(smp);
    649 	for (y = -1, vip->sc_smap = NULL, smp = HMAP; smp <= TMAP; ++smp) {
    650 		if (vs_line(sp, smp, &y, &SCNO))
    651 			return (1);
    652 		if (y != (size_t)-1 && vip->sc_smap == NULL)
    653 			vip->sc_smap = smp;
    654 	}
    655 	/*
    656 	 * If it's a small screen and we're redrawing, clear the unused lines,
    657 	 * ex may have overwritten them.
    658 	 */
    659 	if (F_ISSET(sp, SC_SCR_REDRAW) && IS_SMALL(sp))
    660 		for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) {
    661 			(void)gp->scr_move(sp, cnt, 0);
    662 			(void)gp->scr_clrtoeol(sp);
    663 		}
    664 
    665 	didpaint = 1;
    666 
    667 done_cursor:
    668 	/*
    669 	 * Sanity checking.  When the repainting code messes up, the usual
    670 	 * result is we don't repaint the cursor and so sc_smap will be
    671 	 * NULL.  If we're debugging, die, otherwise restart from scratch.
    672 	 */
    673 #ifdef DEBUG
    674 	if (vip->sc_smap == NULL) {
    675 		fprintf(stderr, "smap error\n");
    676 		sleep(100);
    677 		abort();
    678 	}
    679 #else
    680 	if (vip->sc_smap == NULL) {
    681 		F_SET(sp, SC_SCR_REFORMAT);
    682 		return (vs_paint(sp, flags));
    683 	}
    684 #endif
    685 
    686 	/*
    687 	 * 9: Set the remembered cursor values.
    688 	 */
    689 	OCNO = CNO;
    690 	OLNO = LNO;
    691 
    692 	/*
    693 	 * 10: Repaint the line numbers.
    694 	 *
    695 	 * If O_NUMBER is set and the VIP_N_RENUMBER bit is set, and we
    696 	 * didn't repaint the screen, repaint all of the line numbers,
    697 	 * they've changed.
    698 	 */
    699 number:	if (O_ISSET(sp, O_NUMBER) &&
    700 	    F_ISSET(vip, VIP_N_RENUMBER) && !didpaint && vs_number(sp))
    701 		return (1);
    702 
    703 	/*
    704 	 * 11: Update the mode line, position the cursor, and flush changes.
    705 	 *
    706 	 * If we warped the screen, we have to refresh everything.
    707 	 */
    708 	if (leftright_warp)
    709 		LF_SET(UPDATE_CURSOR | UPDATE_SCREEN);
    710 
    711 	if (LF_ISSET(UPDATE_SCREEN) && !IS_ONELINE(sp) &&
    712 	    !F_ISSET(vip, VIP_S_MODELINE) && !F_ISSET(sp, SC_TINPUT_INFO))
    713 		vs_modeline(sp);
    714 
    715 	if (LF_ISSET(UPDATE_CURSOR)) {
    716 		(void)gp->scr_move(sp, y, SCNO);
    717 
    718 		/*
    719 		 * XXX
    720 		 * If the screen shifted, we recalculate the "most favorite"
    721 		 * cursor position.  Vi won't know that we've warped the
    722 		 * screen, so it's going to have a wrong idea about where the
    723 		 * cursor should be.  This is vi's problem, and fixing it here
    724 		 * is a gross layering violation.
    725 		 */
    726 		if (leftright_warp)
    727 			(void)vs_column(sp, &sp->rcm);
    728 	}
    729 
    730 	if (LF_ISSET(UPDATE_SCREEN))
    731 		(void)gp->scr_refresh(sp, F_ISSET(vip, VIP_N_EX_PAINT));
    732 
    733 	/* 12: Clear the flags that are handled by this routine. */
    734 	F_CLR(sp, SC_SCR_CENTER | SC_SCR_REDRAW | SC_SCR_REFORMAT | SC_SCR_TOP);
    735 	F_CLR(vip, VIP_CUR_INVALID |
    736 	    VIP_N_EX_PAINT | VIP_N_REFRESH | VIP_N_RENUMBER | VIP_S_MODELINE);
    737 
    738 	return (0);
    739 
    740 #undef	 LNO
    741 #undef	OLNO
    742 #undef	 CNO
    743 #undef	OCNO
    744 #undef	SCNO
    745 }
    746 
    747 /*
    748  * vs_modeline --
    749  *	Update the mode line.
    750  */
    751 static void
    752 vs_modeline(SCR *sp)
    753 {
    754 	static const char * const modes[] = {
    755 		"215|Append",			/* SM_APPEND */
    756 		"216|Change",			/* SM_CHANGE */
    757 		"217|Command",			/* SM_COMMAND */
    758 		"218|Insert",			/* SM_INSERT */
    759 		"219|Replace",			/* SM_REPLACE */
    760 	};
    761 	GS *gp;
    762 	size_t cols, curcol, curlen, endpoint, len, midpoint;
    763 	const char *t = NULL;
    764 	int ellipsis;
    765 	char *p, buf[20];
    766 
    767 	gp = sp->gp;
    768 
    769 	/*
    770 	 * We put down the file name, the ruler, the mode and the dirty flag.
    771 	 * If there's not enough room, there's not enough room, we don't play
    772 	 * any special games.  We try to put the ruler in the middle and the
    773 	 * mode and dirty flag at the end.
    774 	 *
    775 	 * !!!
    776 	 * Leave the last character blank, in case it's a really dumb terminal
    777 	 * with hardware scroll.  Second, don't paint the last character in the
    778 	 * screen, SunOS 4.1.1 and Ultrix 4.2 curses won't let you.
    779 	 *
    780 	 * Move to the last line on the screen.
    781 	 */
    782 	(void)gp->scr_move(sp, LASTLINE(sp), 0);
    783 
    784 	/* If more than one screen in the display, show the file name. */
    785 	curlen = 0;
    786 	if (IS_SPLIT(sp)) {
    787 		for (p = sp->frp->name; *p != '\0'; ++p);
    788 		for (ellipsis = 0, cols = sp->cols / 2; --p > sp->frp->name;) {
    789 			if (*p == '/') {
    790 				++p;
    791 				break;
    792 			}
    793 			if ((curlen += KEY_LEN(sp, *p)) > cols) {
    794 				ellipsis = 3;
    795 				curlen +=
    796 				    KEY_LEN(sp, '.') * 3 + KEY_LEN(sp, ' ');
    797 				while (curlen > cols) {
    798 					++p;
    799 					curlen -= KEY_LEN(sp, *p);
    800 				}
    801 				break;
    802 			}
    803 		}
    804 		if (ellipsis) {
    805 			while (ellipsis--)
    806 				(void)gp->scr_addstr(sp,
    807 				    (const char *)KEY_NAME(sp, '.'),
    808 				    KEY_LEN(sp, '.'));
    809 			(void)gp->scr_addstr(sp,
    810 			    (const char *)KEY_NAME(sp, ' '), KEY_LEN(sp, ' '));
    811 		}
    812 		for (; *p != '\0'; ++p)
    813 			(void)gp->scr_addstr(sp,
    814 			    (const char *)KEY_NAME(sp, *p), KEY_LEN(sp, *p));
    815 	}
    816 
    817 	/* Clear the rest of the line. */
    818 	(void)gp->scr_clrtoeol(sp);
    819 
    820 	/*
    821 	 * Display the ruler.  If we're not at the midpoint yet, move there.
    822 	 * Otherwise, add in two extra spaces.
    823 	 *
    824 	 * Adjust the current column for the fact that the editor uses it as
    825 	 * a zero-based number.
    826 	 *
    827 	 * XXX
    828 	 * Assume that numbers, commas, and spaces only take up a single
    829 	 * column on the screen.
    830 	 */
    831 	cols = sp->cols - 1;
    832 	if (O_ISSET(sp, O_RULER)) {
    833 		vs_column(sp, &curcol);
    834 		len =
    835 		    snprintf(buf, sizeof(buf), "%lu,%lu",
    836 			(unsigned long)sp->lno, (unsigned long)curcol + 1);
    837 
    838 		midpoint = (cols - ((len + 1) / 2)) / 2;
    839 		if (curlen < midpoint) {
    840 			(void)gp->scr_move(sp, LASTLINE(sp), midpoint);
    841 			curlen += len;
    842 		} else if (curlen + 2 + len < cols) {
    843 			(void)gp->scr_addstr(sp, "  ", 2);
    844 			curlen += 2 + len;
    845 		}
    846 		(void)gp->scr_addstr(sp, buf, len);
    847 	}
    848 
    849 	/*
    850 	 * Display the mode and the modified flag, as close to the end of the
    851 	 * line as possible, but guaranteeing at least two spaces between the
    852 	 * ruler and the modified flag.
    853 	 */
    854 #define	MODESIZE	9
    855 	endpoint = cols;
    856 	if (O_ISSET(sp, O_SHOWMODE)) {
    857 		if (F_ISSET(sp->ep, F_MODIFIED))
    858 			--endpoint;
    859 		t = msg_cat(sp, modes[sp->showmode], &len);
    860 		endpoint -= len;
    861 	}
    862 
    863 	if (endpoint > curlen + 2) {
    864 		(void)gp->scr_move(sp, LASTLINE(sp), endpoint);
    865 		if (O_ISSET(sp, O_SHOWMODE)) {
    866 			if (F_ISSET(sp->ep, F_MODIFIED))
    867 				(void)gp->scr_addstr(sp,
    868 				    (const char *)KEY_NAME(sp, '*'),
    869 				    KEY_LEN(sp, '*'));
    870 			(void)gp->scr_addstr(sp, t, len);
    871 		}
    872 	}
    873 }
    874