Home | History | Annotate | Line # | Download | only in vi
      1 /*	$NetBSD: vs_refresh.c,v 1.10 2018/04/10 12:44:41 rin 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.10 2018/04/10 12:44:41 rin 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, chlen;
    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 		 * The old cursor position can be past EOL if, for example,
    471 		 * we just deleted the rest of the line.  In this case, since
    472 		 * we don't know the width of the characters we traversed, we
    473 		 * have to do it slowly.
    474 		 */
    475 		if (OCNO >= len)
    476 			goto slow;
    477 
    478 		/*
    479 		 * cwtotal acts as new value for SCNO.  Set cwtotal to the
    480 		 * first char for content on CNO byte, for ease handling of
    481 		 * wide characters.
    482 		 *
    483 		 * If the character we're stepping on lies across a screen
    484 		 * boundary, we have no hope to speed it up.  Do it slowly.
    485 		 */
    486 		p += OCNO;
    487 		ch = (UCHAR_T)*p;
    488 		if (INTISWIDE(ch))
    489 			cwtotal = SCNO;
    490 		else {
    491 			if (ch == '\t' || (chlen = KEY_LEN(sp, ch)) > SCNO + 1)
    492 				goto slow;
    493 			cwtotal = SCNO + 1 - chlen;
    494 		}
    495 		cnt = OCNO - CNO;
    496 
    497 		/*
    498 		 * Quick sanity check -- it's hard to figure out exactly when
    499 		 * we cross a screen boundary as we do in the cursor right
    500 		 * movement.  If cnt is so large that we're going to cross the
    501 		 * boundary no matter what, stop now.
    502 		 */
    503 		if (SCNO + 1 + MAX_CHARACTER_COLUMNS < cnt)
    504 			goto slow;
    505 
    506 		/*
    507 		 * Count up the widths of the characters.  If it's a tab
    508 		 * character, go do it the slow way.
    509 		 */
    510 		while (cnt--) {
    511 			if ((ch = (UCHAR_T)*--p) == '\t'
    512 			    || (chlen = KEY_COL(sp, ch)) > cwtotal)
    513 				goto slow;
    514 			cwtotal -= chlen;
    515 		}
    516 
    517 		/*
    518 		 * If we're moving left, and there's a multi-width char in the
    519 		 * current position, go to the end of the character.
    520 		 */
    521 		if (!INTISWIDE(ch) && (chlen = KEY_LEN(sp, ch)) > 1)
    522 			cwtotal += chlen - 1;
    523 
    524 		/*
    525 		 * At last, update the screen cursor.
    526 		 */
    527 		SCNO = cwtotal;
    528 	} else {
    529 		/*
    530 		 * 7b: Cursor moved right.
    531 		 */
    532 		if (OCNO >= len)
    533 			goto slow;
    534 
    535 		/*
    536 		 * cwtotal acts as new value for SCNO.  Set cwtotal to the
    537 		 * first char for content on CNO byte, for ease handling
    538 		 * of wide characters.
    539 		 */
    540 		p += OCNO;
    541 		ch = (UCHAR_T)*p;
    542 		if (INTISWIDE(ch))
    543 			cwtotal = SCNO;
    544 		else
    545 			cwtotal = SCNO + 1 - KEY_LEN(sp, ch);
    546 		cnt = CNO - OCNO;
    547 
    548 		/*
    549 		 * Count up the widths of the characters.  If it's a tab
    550 		 * character, go do it the the slow way.
    551 		 *
    552 		 * If a multi-width char seems to occupy the screen boundary,
    553 		 * that will be pushed to the next line.  Adjust the cursor
    554 		 * in that case.
    555 		 *
    556 		 * If we cross a screen boundary, we can quit.
    557 		 */
    558 		while (cnt) {
    559 			if (ch == '\t')
    560 				goto slow;
    561 			cwtotal += KEY_COL(sp, ch);
    562 			cnt--;
    563 			ch = (UCHAR_T)*++p;
    564 			if (INTISWIDE(ch)
    565 			    && (chlen = CHAR_WIDTH(sp, ch)) > 1
    566 			    && cwtotal + chlen >= SCREEN_COLS(sp))
    567 				cwtotal = SCREEN_COLS(sp);
    568 			if (cwtotal >= SCREEN_COLS(sp))
    569 				break;
    570 		}
    571 
    572 		/*
    573 		 * If we are on the tab character, we must do it slowly.
    574 		 *
    575 		 * If we're on a multi-width character in the current position,
    576 		 * go to the end of the character.
    577 		 */
    578 		if (ch == '\t')
    579 			goto slow;
    580 		if (!INTISWIDE(ch) && (chlen = KEY_LEN(sp, ch)) > 1)
    581 			cwtotal += chlen - 1;
    582 
    583 		/* See screen change comment in section 6a. */
    584 		if (cwtotal >= SCREEN_COLS(sp))
    585 			goto slow;
    586 
    587 		/*
    588 		 * At last, update the screen cursor.
    589 		 */
    590 		SCNO = cwtotal;
    591 	}
    592 
    593 	/*
    594 	 * 7c: Fast cursor update.
    595 	 *
    596 	 * We have the current column, retrieve the current row.
    597 	 */
    598 fast:	(void)gp->scr_cursor(sp, &y, &notused);
    599 	goto done_cursor;
    600 
    601 	/*
    602 	 * 7d: Slow cursor update.
    603 	 *
    604 	 * Walk through the map and find the current line.
    605 	 */
    606 slow:	for (smp = HMAP; smp->lno != LNO; ++smp)
    607 		;
    608 
    609 	/*
    610 	 * 7e: Leftright scrolling adjustment.
    611 	 *
    612 	 * If doing left-right scrolling and the cursor movement has changed
    613 	 * the displayed screen, scroll the screen left or right, unless we're
    614 	 * updating the info line in which case we just scroll that one line.
    615 	 * We adjust the offset up or down until we have a window that covers
    616 	 * the current column, making sure that we adjust differently for the
    617 	 * first screen as compared to subsequent ones.
    618 	 */
    619 	if (O_ISSET(sp, O_LEFTRIGHT)) {
    620 		/*
    621 		 * Get the screen column for this character, and correct
    622 		 * for the number option offset.
    623 		 */
    624 		cnt = vs_columns(sp, NULL, LNO, &CNO, NULL);
    625 		if (O_ISSET(sp, O_NUMBER))
    626 			cnt -= O_NUMBER_LENGTH;
    627 
    628 		/* Adjust the window towards the beginning of the line. */
    629 		off = smp->coff;
    630 		if (off >= cnt) {
    631 			do {
    632 				if (off >= O_VAL(sp, O_SIDESCROLL))
    633 					off -= O_VAL(sp, O_SIDESCROLL);
    634 				else {
    635 					off = 0;
    636 					break;
    637 				}
    638 			} while (off >= cnt);
    639 			goto shifted;
    640 		}
    641 
    642 		/* Adjust the window towards the end of the line. */
    643 		if ((off == 0 && off + SCREEN_COLS(sp) < cnt) ||
    644 		    (off != 0 && off + sp->cols < cnt)) {
    645 			do {
    646 				off += O_VAL(sp, O_SIDESCROLL);
    647 			} while (off + sp->cols < cnt);
    648 
    649 shifted:		/* Fill in screen map with the new offset. */
    650 			if (F_ISSET(sp, SC_TINPUT_INFO))
    651 				smp->coff = off;
    652 			else {
    653 				for (smp = HMAP; smp <= TMAP; ++smp)
    654 					smp->coff = off;
    655 				leftright_warp = 1;
    656 			}
    657 			goto paint;
    658 		}
    659 
    660 		/*
    661 		 * We may have jumped here to adjust a leftright screen because
    662 		 * redraw was set.  If so, we have to paint the entire screen.
    663 		 */
    664 		if (F_ISSET(sp, SC_SCR_REDRAW))
    665 			goto paint;
    666 	}
    667 
    668 	/*
    669 	 * Update the screen lines for this particular file line until we
    670 	 * have a new screen cursor position.
    671 	 */
    672 	for (y = -1,
    673 	    vip->sc_smap = NULL; smp <= TMAP && smp->lno == LNO; ++smp) {
    674 		if (vs_line(sp, smp, &y, &SCNO))
    675 			return (1);
    676 		if (y != (size_t)-1) {
    677 			vip->sc_smap = smp;
    678 			break;
    679 		}
    680 	}
    681 	goto done_cursor;
    682 
    683 	/*
    684 	 * 8: Repaint the entire screen.
    685 	 *
    686 	 * Lost big, do what you have to do.  We flush the cache, since
    687 	 * SC_SCR_REDRAW gets set when the screen isn't worth fixing, and
    688 	 * it's simpler to repaint.  So, don't trust anything that we
    689 	 * think we know about it.
    690 	 */
    691 paint:	for (smp = HMAP; smp <= TMAP; ++smp)
    692 		SMAP_FLUSH(smp);
    693 	for (y = -1, vip->sc_smap = NULL, smp = HMAP; smp <= TMAP; ++smp) {
    694 		if (vs_line(sp, smp, &y, &SCNO))
    695 			return (1);
    696 		if (y != (size_t)-1 && vip->sc_smap == NULL)
    697 			vip->sc_smap = smp;
    698 	}
    699 	/*
    700 	 * If it's a small screen and we're redrawing, clear the unused lines,
    701 	 * ex may have overwritten them.
    702 	 */
    703 	if (F_ISSET(sp, SC_SCR_REDRAW) && IS_SMALL(sp))
    704 		for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) {
    705 			(void)gp->scr_move(sp, cnt, 0);
    706 			(void)gp->scr_clrtoeol(sp);
    707 		}
    708 
    709 	didpaint = 1;
    710 
    711 done_cursor:
    712 	/*
    713 	 * Sanity checking.  When the repainting code messes up, the usual
    714 	 * result is we don't repaint the cursor and so sc_smap will be
    715 	 * NULL.  If we're debugging, die, otherwise restart from scratch.
    716 	 */
    717 #ifdef DEBUG
    718 	if (vip->sc_smap == NULL) {
    719 		fprintf(stderr, "smap error\n");
    720 		sleep(100);
    721 		abort();
    722 	}
    723 #else
    724 	if (vip->sc_smap == NULL) {
    725 		if (F_ISSET(sp, SC_SCR_REFORMAT))
    726 			abort(); /* XXX infinite recursion */
    727 		F_SET(sp, SC_SCR_REFORMAT);
    728 		return (vs_paint(sp, flags));
    729 	}
    730 #endif
    731 
    732 	/*
    733 	 * 9: Set the remembered cursor values.
    734 	 */
    735 	OCNO = CNO;
    736 	OLNO = LNO;
    737 
    738 	/*
    739 	 * 10: Repaint the line numbers.
    740 	 *
    741 	 * If O_NUMBER is set and the VIP_N_RENUMBER bit is set, and we
    742 	 * didn't repaint the screen, repaint all of the line numbers,
    743 	 * they've changed.
    744 	 */
    745 number:	if (O_ISSET(sp, O_NUMBER) &&
    746 	    F_ISSET(vip, VIP_N_RENUMBER) && !didpaint && vs_number(sp))
    747 		return (1);
    748 
    749 	/*
    750 	 * 11: Update the mode line, position the cursor, and flush changes.
    751 	 *
    752 	 * If we warped the screen, we have to refresh everything.
    753 	 */
    754 	if (leftright_warp)
    755 		LF_SET(UPDATE_CURSOR | UPDATE_SCREEN);
    756 
    757 	if (LF_ISSET(UPDATE_SCREEN) && !IS_ONELINE(sp) &&
    758 	    !F_ISSET(vip, VIP_S_MODELINE) && !F_ISSET(sp, SC_TINPUT_INFO))
    759 		vs_modeline(sp);
    760 
    761 	if (LF_ISSET(UPDATE_CURSOR)) {
    762 		(void)gp->scr_move(sp, y, SCNO);
    763 
    764 		/*
    765 		 * XXX
    766 		 * If the screen shifted, we recalculate the "most favorite"
    767 		 * cursor position.  Vi won't know that we've warped the
    768 		 * screen, so it's going to have a wrong idea about where the
    769 		 * cursor should be.  This is vi's problem, and fixing it here
    770 		 * is a gross layering violation.
    771 		 */
    772 		if (leftright_warp)
    773 			(void)vs_column(sp, &sp->rcm);
    774 	}
    775 
    776 	if (LF_ISSET(UPDATE_SCREEN))
    777 		(void)gp->scr_refresh(sp, F_ISSET(vip, VIP_N_EX_PAINT));
    778 
    779 	/* 12: Clear the flags that are handled by this routine. */
    780 	F_CLR(sp, SC_SCR_CENTER | SC_SCR_REDRAW | SC_SCR_REFORMAT | SC_SCR_TOP);
    781 	F_CLR(vip, VIP_CUR_INVALID |
    782 	    VIP_N_EX_PAINT | VIP_N_REFRESH | VIP_N_RENUMBER | VIP_S_MODELINE);
    783 
    784 	return (0);
    785 
    786 #undef	 LNO
    787 #undef	OLNO
    788 #undef	 CNO
    789 #undef	OCNO
    790 #undef	SCNO
    791 }
    792 
    793 /*
    794  * vs_modeline --
    795  *	Update the mode line.
    796  */
    797 static void
    798 vs_modeline(SCR *sp)
    799 {
    800 	static const char * const modes[] = {
    801 		"215|Append",			/* SM_APPEND */
    802 		"216|Change",			/* SM_CHANGE */
    803 		"217|Command",			/* SM_COMMAND */
    804 		"218|Insert",			/* SM_INSERT */
    805 		"219|Replace",			/* SM_REPLACE */
    806 	};
    807 	GS *gp;
    808 	size_t cols, curcol, curlen, endpoint, len, midpoint;
    809 	const char *t = NULL;
    810 	int ellipsis;
    811 	char *p, buf[20];
    812 
    813 	gp = sp->gp;
    814 
    815 	/*
    816 	 * We put down the file name, the ruler, the mode and the dirty flag.
    817 	 * If there's not enough room, there's not enough room, we don't play
    818 	 * any special games.  We try to put the ruler in the middle and the
    819 	 * mode and dirty flag at the end.
    820 	 *
    821 	 * !!!
    822 	 * Leave the last character blank, in case it's a really dumb terminal
    823 	 * with hardware scroll.  Second, don't paint the last character in the
    824 	 * screen, SunOS 4.1.1 and Ultrix 4.2 curses won't let you.
    825 	 *
    826 	 * Move to the last line on the screen.
    827 	 */
    828 	(void)gp->scr_move(sp, LASTLINE(sp), 0);
    829 
    830 	/* If more than one screen in the display, show the file name. */
    831 	curlen = 0;
    832 	if (IS_SPLIT(sp)) {
    833 		for (p = sp->frp->name; *p != '\0'; ++p);
    834 		for (ellipsis = 0, cols = sp->cols / 2; --p > sp->frp->name;) {
    835 			if (*p == '/') {
    836 				++p;
    837 				break;
    838 			}
    839 			if ((curlen += KEY_LEN(sp, *p)) > cols) {
    840 				ellipsis = 3;
    841 				curlen +=
    842 				    KEY_LEN(sp, '.') * 3 + KEY_LEN(sp, ' ');
    843 				while (curlen > cols) {
    844 					++p;
    845 					curlen -= KEY_LEN(sp, *p);
    846 				}
    847 				break;
    848 			}
    849 		}
    850 		if (ellipsis) {
    851 			while (ellipsis--)
    852 				(void)gp->scr_addstr(sp,
    853 				    (const char *)KEY_NAME(sp, '.'),
    854 				    KEY_LEN(sp, '.'));
    855 			(void)gp->scr_addstr(sp,
    856 			    (const char *)KEY_NAME(sp, ' '), KEY_LEN(sp, ' '));
    857 		}
    858 		for (; *p != '\0'; ++p)
    859 			(void)gp->scr_addstr(sp,
    860 			    (const char *)KEY_NAME(sp, *p), KEY_LEN(sp, *p));
    861 	}
    862 
    863 	/* Clear the rest of the line. */
    864 	(void)gp->scr_clrtoeol(sp);
    865 
    866 	/*
    867 	 * Display the ruler.  If we're not at the midpoint yet, move there.
    868 	 * Otherwise, add in two extra spaces.
    869 	 *
    870 	 * Adjust the current column for the fact that the editor uses it as
    871 	 * a zero-based number.
    872 	 *
    873 	 * XXX
    874 	 * Assume that numbers, commas, and spaces only take up a single
    875 	 * column on the screen.
    876 	 */
    877 	cols = sp->cols - 1;
    878 	if (O_ISSET(sp, O_RULER)) {
    879 		vs_column(sp, &curcol);
    880 		len =
    881 		    snprintf(buf, sizeof(buf), "%lu,%lu",
    882 			(unsigned long)sp->lno, (unsigned long)curcol + 1);
    883 
    884 		midpoint = (cols - ((len + 1) / 2)) / 2;
    885 		if (curlen < midpoint) {
    886 			(void)gp->scr_move(sp, LASTLINE(sp), midpoint);
    887 			curlen += len;
    888 		} else if (curlen + 2 + len < cols) {
    889 			(void)gp->scr_addstr(sp, "  ", 2);
    890 			curlen += 2 + len;
    891 		}
    892 		(void)gp->scr_addstr(sp, buf, len);
    893 	}
    894 
    895 	/*
    896 	 * Display the mode and the modified flag, as close to the end of the
    897 	 * line as possible, but guaranteeing at least two spaces between the
    898 	 * ruler and the modified flag.
    899 	 */
    900 #define	MODESIZE	9
    901 	endpoint = cols;
    902 	if (O_ISSET(sp, O_SHOWMODE)) {
    903 		if (F_ISSET(sp->ep, F_MODIFIED))
    904 			--endpoint;
    905 		t = msg_cat(sp, modes[sp->showmode], &len);
    906 		endpoint -= len;
    907 	}
    908 
    909 	if (endpoint > curlen + 2) {
    910 		(void)gp->scr_move(sp, LASTLINE(sp), endpoint);
    911 		if (O_ISSET(sp, O_SHOWMODE)) {
    912 			if (F_ISSET(sp->ep, F_MODIFIED))
    913 				(void)gp->scr_addstr(sp,
    914 				    (const char *)KEY_NAME(sp, '*'),
    915 				    KEY_LEN(sp, '*'));
    916 			(void)gp->scr_addstr(sp, t, len);
    917 		}
    918 	}
    919 }
    920