Home | History | Annotate | Line # | Download | only in vi
vs_smap.c revision 1.2
      1 /*	$NetBSD: vs_smap.c,v 1.2 2013/11/22 15:52:06 christos Exp $ */
      2 /*-
      3  * Copyright (c) 1993, 1994
      4  *	The Regents of the University of California.  All rights reserved.
      5  * Copyright (c) 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 #ifndef lint
     14 static const char sccsid[] = "Id: vs_smap.c,v 10.30 2002/01/19 21:59:07 skimo Exp  (Berkeley) Date: 2002/01/19 21:59:07 ";
     15 #endif /* not lint */
     16 
     17 #include <sys/types.h>
     18 #include <sys/queue.h>
     19 #include <sys/time.h>
     20 
     21 #include <bitstring.h>
     22 #include <limits.h>
     23 #include <stdio.h>
     24 #include <stdlib.h>
     25 #include <string.h>
     26 
     27 #include "../common/common.h"
     28 #include "vi.h"
     29 
     30 static int	vs_deleteln __P((SCR *, int));
     31 static int	vs_insertln __P((SCR *, int));
     32 static int	vs_sm_delete __P((SCR *, db_recno_t));
     33 static int	vs_sm_down __P((SCR *, MARK *, db_recno_t, scroll_t, SMAP *));
     34 static int	vs_sm_erase __P((SCR *));
     35 static int	vs_sm_insert __P((SCR *, db_recno_t));
     36 static int	vs_sm_reset __P((SCR *, db_recno_t));
     37 static int	vs_sm_up __P((SCR *, MARK *, db_recno_t, scroll_t, SMAP *));
     38 
     39 /*
     40  * vs_change --
     41  *	Make a change to the screen.
     42  *
     43  * PUBLIC: int vs_change __P((SCR *, db_recno_t, lnop_t));
     44  */
     45 int
     46 vs_change(SCR *sp, db_recno_t lno, lnop_t op)
     47 {
     48 	VI_PRIVATE *vip;
     49 	SMAP *p;
     50 	size_t cnt, oldy, oldx;
     51 
     52 	vip = VIP(sp);
     53 
     54 	/*
     55 	 * XXX
     56 	 * Very nasty special case.  The historic vi code displays a single
     57 	 * space (or a '$' if the list option is set) for the first line in
     58 	 * an "empty" file.  If we "insert" a line, that line gets scrolled
     59 	 * down, not repainted, so it's incorrect when we refresh the screen.
     60 	 * The vi text input functions detect it explicitly and don't insert
     61 	 * a new line.
     62 	 *
     63 	 * Check for line #2 before going to the end of the file.
     64 	 */
     65 	if (((op == LINE_APPEND && lno == 0) ||
     66 	    (op == LINE_INSERT && lno == 1)) &&
     67 	    !db_exist(sp, 2)) {
     68 		lno = 1;
     69 		op = LINE_RESET;
     70 	}
     71 
     72 	/* Appending is the same as inserting, if the line is incremented. */
     73 	if (op == LINE_APPEND) {
     74 		++lno;
     75 		op = LINE_INSERT;
     76 	}
     77 
     78 	/* Ignore the change if the line is after the map. */
     79 	if (lno > TMAP->lno)
     80 		return (0);
     81 
     82 	/*
     83 	 * If the line is before the map, and it's a decrement, decrement
     84 	 * the map.  If it's an increment, increment the map.  Otherwise,
     85 	 * ignore it.
     86 	 */
     87 	if (lno < HMAP->lno) {
     88 		switch (op) {
     89 		case LINE_APPEND:
     90 			abort();
     91 			/* NOTREACHED */
     92 		case LINE_DELETE:
     93 			for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
     94 				--p->lno;
     95 			if (sp->lno >= lno)
     96 				--sp->lno;
     97 			F_SET(vip, VIP_N_RENUMBER);
     98 			break;
     99 		case LINE_INSERT:
    100 			for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
    101 				++p->lno;
    102 			if (sp->lno >= lno)
    103 				++sp->lno;
    104 			F_SET(vip, VIP_N_RENUMBER);
    105 			break;
    106 		case LINE_RESET:
    107 			break;
    108 		}
    109 		return (0);
    110 	}
    111 
    112 	F_SET(vip, VIP_N_REFRESH);
    113 
    114 	/*
    115 	 * Invalidate the line size cache, and invalidate the cursor if it's
    116 	 * on this line,
    117 	 */
    118 	VI_SCR_CFLUSH(vip);
    119 	if (sp->lno == lno)
    120 		F_SET(vip, VIP_CUR_INVALID);
    121 
    122 	/*
    123 	 * If ex modifies the screen after ex output is already on the screen
    124 	 * or if we've switched into ex canonical mode, don't touch it -- we'll
    125 	 * get scrolling wrong, at best.
    126 	 */
    127 	if (!F_ISSET(sp, SC_TINPUT_INFO) &&
    128 	    (F_ISSET(sp, SC_SCR_EXWROTE) || VIP(sp)->totalcount > 1)) {
    129 		F_SET(vip, VIP_N_EX_REDRAW);
    130 		return (0);
    131 	}
    132 
    133 	/* Save and restore the cursor for these routines. */
    134 	(void)sp->gp->scr_cursor(sp, &oldy, &oldx);
    135 
    136 	switch (op) {
    137 	case LINE_DELETE:
    138 		if (vs_sm_delete(sp, lno))
    139 			return (1);
    140 		if (sp->lno > lno)
    141 			--sp->lno;
    142 		F_SET(vip, VIP_N_RENUMBER);
    143 		break;
    144 	case LINE_INSERT:
    145 		if (vs_sm_insert(sp, lno))
    146 			return (1);
    147 		if (sp->lno > lno)
    148 			++sp->lno;
    149 		F_SET(vip, VIP_N_RENUMBER);
    150 		break;
    151 	case LINE_RESET:
    152 		if (vs_sm_reset(sp, lno))
    153 			return (1);
    154 		break;
    155 	default:
    156 		abort();
    157 	}
    158 
    159 	(void)sp->gp->scr_move(sp, oldy, oldx);
    160 	return (0);
    161 }
    162 
    163 /*
    164  * vs_sm_fill --
    165  *	Fill in the screen map, placing the specified line at the
    166  *	right position.  There isn't any way to tell if an SMAP
    167  *	entry has been filled in, so this routine had better be
    168  *	called with P_FILL set before anything else is done.
    169  *
    170  * !!!
    171  * Unexported interface: if lno is OOBLNO, P_TOP means that the HMAP
    172  * slot is already filled in, P_BOTTOM means that the TMAP slot is
    173  * already filled in, and we just finish up the job.
    174  *
    175  * PUBLIC: int vs_sm_fill __P((SCR *, db_recno_t, pos_t));
    176  */
    177 int
    178 vs_sm_fill(SCR *sp, db_recno_t lno, pos_t pos)
    179 {
    180 	SMAP *p, tmp;
    181 	size_t cnt;
    182 
    183 	/* Flush all cached information from the SMAP. */
    184 	for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
    185 		SMAP_FLUSH(p);
    186 
    187 	/*
    188 	 * If the map is filled, the screen must be redrawn.
    189 	 *
    190 	 * XXX
    191 	 * This is a bug.  We should try and figure out if the desired line
    192 	 * is already in the map or close by -- scrolling the screen would
    193 	 * be a lot better than redrawing.
    194 	 */
    195 	F_SET(sp, SC_SCR_REDRAW);
    196 
    197 	switch (pos) {
    198 	case P_FILL:
    199 		tmp.lno = 1;
    200 		tmp.coff = 0;
    201 		tmp.soff = 1;
    202 
    203 		/* See if less than half a screen from the top. */
    204 		if (vs_sm_nlines(sp,
    205 		    &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {
    206 			lno = 1;
    207 			goto top;
    208 		}
    209 
    210 		/* See if less than half a screen from the bottom. */
    211 		if (db_last(sp, &tmp.lno))
    212 			return (1);
    213 		tmp.coff = 0;
    214 		tmp.soff = vs_screens(sp, tmp.lno, NULL);
    215 		if (vs_sm_nlines(sp,
    216 		    &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {
    217 			TMAP->lno = tmp.lno;
    218 			TMAP->coff = tmp.coff;
    219 			TMAP->soff = tmp.soff;
    220 			goto bottom;
    221 		}
    222 		goto middle;
    223 	case P_TOP:
    224 		if (lno != OOBLNO) {
    225 top:			HMAP->lno = lno;
    226 			HMAP->coff = 0;
    227 			HMAP->soff = 1;
    228 		} else {
    229 			/*
    230 			 * If number of lines HMAP->lno (top line) spans
    231 			 * changed due to, say reformatting, and now is
    232 			 * fewer than HMAP->soff, reset so the line is
    233 			 * redrawn at the top of the screen.
    234 			 */
    235 			cnt = vs_screens(sp, HMAP->lno, NULL);
    236 			if (cnt < HMAP->soff)
    237 				HMAP->soff = 1;
    238 		}
    239 		/* If we fail, just punt. */
    240 		for (p = HMAP, cnt = sp->t_rows; --cnt; ++p)
    241 			if (vs_sm_next(sp, p, p + 1))
    242 				goto err;
    243 		break;
    244 	case P_MIDDLE:
    245 		/* If we fail, guess that the file is too small. */
    246 middle:		p = HMAP + sp->t_rows / 2;
    247 		p->lno = lno;
    248 		p->coff = 0;
    249 		p->soff = 1;
    250 		for (; p > HMAP; --p)
    251 			if (vs_sm_prev(sp, p, p - 1)) {
    252 				lno = 1;
    253 				goto top;
    254 			}
    255 
    256 		/* If we fail, just punt. */
    257 		p = HMAP + sp->t_rows / 2;
    258 		for (; p < TMAP; ++p)
    259 			if (vs_sm_next(sp, p, p + 1))
    260 				goto err;
    261 		break;
    262 	case P_BOTTOM:
    263 		if (lno != OOBLNO) {
    264 			TMAP->lno = lno;
    265 			TMAP->coff = 0;
    266 			TMAP->soff = vs_screens(sp, lno, NULL);
    267 		}
    268 		/* If we fail, guess that the file is too small. */
    269 bottom:		for (p = TMAP; p > HMAP; --p)
    270 			if (vs_sm_prev(sp, p, p - 1)) {
    271 				lno = 1;
    272 				goto top;
    273 			}
    274 		break;
    275 	default:
    276 		abort();
    277 	}
    278 	return (0);
    279 
    280 	/*
    281 	 * Try and put *something* on the screen.  If this fails, we have a
    282 	 * serious hard error.
    283 	 */
    284 err:	HMAP->lno = 1;
    285 	HMAP->coff = 0;
    286 	HMAP->soff = 1;
    287 	for (p = HMAP; p < TMAP; ++p)
    288 		if (vs_sm_next(sp, p, p + 1))
    289 			return (1);
    290 	return (0);
    291 }
    292 
    293 /*
    294  * For the routines vs_sm_reset, vs_sm_delete and vs_sm_insert: if the
    295  * screen contains only a single line (whether because the screen is small
    296  * or the line large), it gets fairly exciting.  Skip the fun, set a flag
    297  * so the screen map is refilled and the screen redrawn, and return.  This
    298  * is amazingly slow, but it's not clear that anyone will care.
    299  */
    300 #define	HANDLE_WEIRDNESS(cnt) {						\
    301 	if (cnt >= sp->t_rows) {					\
    302 		F_SET(sp, SC_SCR_REFORMAT);				\
    303 		return (0);						\
    304 	}								\
    305 }
    306 
    307 /*
    308  * vs_sm_delete --
    309  *	Delete a line out of the SMAP.
    310  */
    311 static int
    312 vs_sm_delete(SCR *sp, db_recno_t lno)
    313 {
    314 	SMAP *p, *t;
    315 	size_t cnt_orig;
    316 
    317 	/*
    318 	 * Find the line in the map, and count the number of screen lines
    319 	 * which display any part of the deleted line.
    320 	 */
    321 	for (p = HMAP; p->lno != lno; ++p);
    322 	if (O_ISSET(sp, O_LEFTRIGHT))
    323 		cnt_orig = 1;
    324 	else
    325 		for (cnt_orig = 1, t = p + 1;
    326 		    t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
    327 
    328 	HANDLE_WEIRDNESS(cnt_orig);
    329 
    330 	/* Delete that many lines from the screen. */
    331 	(void)sp->gp->scr_move(sp, p - HMAP, 0);
    332 	if (vs_deleteln(sp, cnt_orig))
    333 		return (1);
    334 
    335 	/* Shift the screen map up. */
    336 	memmove(p, p + cnt_orig, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
    337 
    338 	/* Decrement the line numbers for the rest of the map. */
    339 	for (t = TMAP - cnt_orig; p <= t; ++p)
    340 		--p->lno;
    341 
    342 	/* Display the new lines. */
    343 	for (p = TMAP - cnt_orig;;) {
    344 		if (p < TMAP && vs_sm_next(sp, p, p + 1))
    345 			return (1);
    346 		/* vs_sm_next() flushed the cache. */
    347 		if (vs_line(sp, ++p, NULL, NULL))
    348 			return (1);
    349 		if (p == TMAP)
    350 			break;
    351 	}
    352 	return (0);
    353 }
    354 
    355 /*
    356  * vs_sm_insert --
    357  *	Insert a line into the SMAP.
    358  */
    359 static int
    360 vs_sm_insert(SCR *sp, db_recno_t lno)
    361 {
    362 	SMAP *p, *t;
    363 	size_t cnt_orig, cnt, coff;
    364 
    365 	/* Save the offset. */
    366 	coff = HMAP->coff;
    367 
    368 	/*
    369 	 * Find the line in the map, find out how many screen lines
    370 	 * needed to display the line.
    371 	 */
    372 	for (p = HMAP; p->lno != lno; ++p);
    373 
    374 	cnt_orig = vs_screens(sp, lno, NULL);
    375 	HANDLE_WEIRDNESS(cnt_orig);
    376 
    377 	/*
    378 	 * The lines left in the screen override the number of screen
    379 	 * lines in the inserted line.
    380 	 */
    381 	cnt = (TMAP - p) + 1;
    382 	if (cnt_orig > cnt)
    383 		cnt_orig = cnt;
    384 
    385 	/* Push down that many lines. */
    386 	(void)sp->gp->scr_move(sp, p - HMAP, 0);
    387 	if (vs_insertln(sp, cnt_orig))
    388 		return (1);
    389 
    390 	/* Shift the screen map down. */
    391 	memmove(p + cnt_orig, p, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
    392 
    393 	/* Increment the line numbers for the rest of the map. */
    394 	for (t = p + cnt_orig; t <= TMAP; ++t)
    395 		++t->lno;
    396 
    397 	/* Fill in the SMAP for the new lines, and display. */
    398 	for (cnt = 1, t = p; cnt <= cnt_orig; ++t, ++cnt) {
    399 		t->lno = lno;
    400 		t->coff = coff;
    401 		t->soff = cnt;
    402 		SMAP_FLUSH(t);
    403 		if (vs_line(sp, t, NULL, NULL))
    404 			return (1);
    405 	}
    406 	return (0);
    407 }
    408 
    409 /*
    410  * vs_sm_reset --
    411  *	Reset a line in the SMAP.
    412  */
    413 static int
    414 vs_sm_reset(SCR *sp, db_recno_t lno)
    415 {
    416 	SMAP *p, *t;
    417 	size_t cnt_orig, cnt_new, cnt, diff;
    418 
    419 	/*
    420 	 * See if the number of on-screen rows taken up by the old display
    421 	 * for the line is the same as the number needed for the new one.
    422 	 * If so, repaint, otherwise do it the hard way.
    423 	 */
    424 	for (p = HMAP; p->lno != lno; ++p);
    425 	if (O_ISSET(sp, O_LEFTRIGHT)) {
    426 		t = p;
    427 		cnt_orig = cnt_new = 1;
    428 	} else {
    429 		for (cnt_orig = 0,
    430 		    t = p; t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
    431 		cnt_new = vs_screens(sp, lno, NULL);
    432 	}
    433 
    434 	HANDLE_WEIRDNESS(cnt_orig);
    435 
    436 	if (cnt_orig == cnt_new) {
    437 		do {
    438 			SMAP_FLUSH(p);
    439 			if (vs_line(sp, p, NULL, NULL))
    440 				return (1);
    441 		} while (++p < t);
    442 		return (0);
    443 	}
    444 
    445 	if (cnt_orig < cnt_new) {
    446 		/* Get the difference. */
    447 		diff = cnt_new - cnt_orig;
    448 
    449 		/*
    450 		 * The lines left in the screen override the number of screen
    451 		 * lines in the inserted line.
    452 		 */
    453 		cnt = (TMAP - p) + 1;
    454 		if (diff > cnt)
    455 			diff = cnt;
    456 
    457 		/* If there are any following lines, push them down. */
    458 		if (cnt > 1) {
    459 			(void)sp->gp->scr_move(sp, p - HMAP, 0);
    460 			if (vs_insertln(sp, diff))
    461 				return (1);
    462 
    463 			/* Shift the screen map down. */
    464 			memmove(p + diff, p,
    465 			    (((TMAP - p) - diff) + 1) * sizeof(SMAP));
    466 		}
    467 
    468 		/* Fill in the SMAP for the replaced line, and display. */
    469 		for (cnt = 1, t = p; cnt_new-- && t <= TMAP; ++t, ++cnt) {
    470 			t->lno = lno;
    471 			t->soff = cnt;
    472 			SMAP_FLUSH(t);
    473 			if (vs_line(sp, t, NULL, NULL))
    474 				return (1);
    475 		}
    476 	} else {
    477 		/* Get the difference. */
    478 		diff = cnt_orig - cnt_new;
    479 
    480 		/* Delete that many lines from the screen. */
    481 		(void)sp->gp->scr_move(sp, p - HMAP, 0);
    482 		if (vs_deleteln(sp, diff))
    483 			return (1);
    484 
    485 		/* Shift the screen map up. */
    486 		memmove(p, p + diff, (((TMAP - p) - diff) + 1) * sizeof(SMAP));
    487 
    488 		/* Fill in the SMAP for the replaced line, and display. */
    489 		for (cnt = 1, t = p; cnt_new--; ++t, ++cnt) {
    490 			t->lno = lno;
    491 			t->soff = cnt;
    492 			SMAP_FLUSH(t);
    493 			if (vs_line(sp, t, NULL, NULL))
    494 				return (1);
    495 		}
    496 
    497 		/* Display the new lines at the bottom of the screen. */
    498 		for (t = TMAP - diff;;) {
    499 			if (t < TMAP && vs_sm_next(sp, t, t + 1))
    500 				return (1);
    501 			/* vs_sm_next() flushed the cache. */
    502 			if (vs_line(sp, ++t, NULL, NULL))
    503 				return (1);
    504 			if (t == TMAP)
    505 				break;
    506 		}
    507 	}
    508 	return (0);
    509 }
    510 
    511 /*
    512  * vs_sm_scroll
    513  *	Scroll the SMAP up/down count logical lines.  Different
    514  *	semantics based on the vi command, *sigh*.
    515  *
    516  * PUBLIC: int vs_sm_scroll __P((SCR *, MARK *, db_recno_t, scroll_t));
    517  */
    518 int
    519 vs_sm_scroll(SCR *sp, MARK *rp, db_recno_t count, scroll_t scmd)
    520 {
    521 	SMAP *smp;
    522 
    523 	/*
    524 	 * Invalidate the cursor.  The line is probably going to change,
    525 	 * (although for ^E and ^Y it may not).  In any case, the scroll
    526 	 * routines move the cursor to draw things.
    527 	 */
    528 	F_SET(VIP(sp), VIP_CUR_INVALID);
    529 
    530 	/* Find the cursor in the screen. */
    531 	if (vs_sm_cursor(sp, &smp))
    532 		return (1);
    533 
    534 	switch (scmd) {
    535 	case CNTRL_B:
    536 	case CNTRL_U:
    537 	case CNTRL_Y:
    538 	case Z_CARAT:
    539 		if (vs_sm_down(sp, rp, count, scmd, smp))
    540 			return (1);
    541 		break;
    542 	case CNTRL_D:
    543 	case CNTRL_E:
    544 	case CNTRL_F:
    545 	case Z_PLUS:
    546 		if (vs_sm_up(sp, rp, count, scmd, smp))
    547 			return (1);
    548 		break;
    549 	default:
    550 		abort();
    551 	}
    552 
    553 	/*
    554 	 * !!!
    555 	 * If we're at the start of a line, go for the first non-blank.
    556 	 * This makes it look like the old vi, even though we're moving
    557 	 * around by logical lines, not physical ones.
    558 	 *
    559 	 * XXX
    560 	 * In the presence of a long line, which has more than a screen
    561 	 * width of leading spaces, this code can cause a cursor warp.
    562 	 * Live with it.
    563 	 */
    564 	if (scmd != CNTRL_E && scmd != CNTRL_Y &&
    565 	    rp->cno == 0 && nonblank(sp, rp->lno, &rp->cno))
    566 		return (1);
    567 
    568 	return (0);
    569 }
    570 
    571 /*
    572  * vs_sm_up --
    573  *	Scroll the SMAP up count logical lines.
    574  */
    575 static int
    576 vs_sm_up(SCR *sp, MARK *rp, db_recno_t count, scroll_t scmd, SMAP *smp)
    577 {
    578 	int cursor_set, echanged, zset;
    579 	SMAP *ssmp, s1, s2;
    580 
    581 	/*
    582 	 * Check to see if movement is possible.
    583 	 *
    584 	 * Get the line after the map.  If that line is a new one (and if
    585 	 * O_LEFTRIGHT option is set, this has to be true), and the next
    586 	 * line doesn't exist, and the cursor doesn't move, or the cursor
    587 	 * isn't even on the screen, or the cursor is already at the last
    588 	 * line in the map, it's an error.  If that test succeeded because
    589 	 * the cursor wasn't at the end of the map, test to see if the map
    590 	 * is mostly empty.
    591 	 */
    592 	if (vs_sm_next(sp, TMAP, &s1))
    593 		return (1);
    594 	if (s1.lno > TMAP->lno && !db_exist(sp, s1.lno)) {
    595 		if (scmd == CNTRL_E || scmd == Z_PLUS || smp == TMAP) {
    596 			v_eof(sp, NULL);
    597 			return (1);
    598 		}
    599 		if (vs_sm_next(sp, smp, &s1))
    600 			return (1);
    601 		if (s1.lno > smp->lno && !db_exist(sp, s1.lno)) {
    602 			v_eof(sp, NULL);
    603 			return (1);
    604 		}
    605 	}
    606 
    607 	/*
    608 	 * Small screens: see vs_refresh.c section 6a.
    609 	 *
    610 	 * If it's a small screen, and the movement isn't larger than a
    611 	 * screen, i.e some context will remain, open up the screen and
    612 	 * display by scrolling.  In this case, the cursor moves down one
    613 	 * line for each line displayed.  Otherwise, erase/compress and
    614 	 * repaint, and move the cursor to the first line in the screen.
    615 	 * Note, the ^F command is always in the latter case, for historical
    616 	 * reasons.
    617 	 */
    618 	cursor_set = 0;
    619 	if (IS_SMALL(sp)) {
    620 		if (count >= sp->t_maxrows || scmd == CNTRL_F) {
    621 			s1 = TMAP[0];
    622 			if (vs_sm_erase(sp))
    623 				return (1);
    624 			for (; count--; s1 = s2) {
    625 				if (vs_sm_next(sp, &s1, &s2))
    626 					return (1);
    627 				if (s2.lno != s1.lno && !db_exist(sp, s2.lno))
    628 					break;
    629 			}
    630 			TMAP[0] = s2;
    631 			if (vs_sm_fill(sp, OOBLNO, P_BOTTOM))
    632 				return (1);
    633 			return (vs_sm_position(sp, rp, 0, P_TOP));
    634 		}
    635 		cursor_set = scmd == CNTRL_E || vs_sm_cursor(sp, &ssmp);
    636 		for (; count &&
    637 		    sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
    638 			if (vs_sm_next(sp, TMAP, &s1))
    639 				return (1);
    640 			if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))
    641 				break;
    642 			*++TMAP = s1;
    643 			/* vs_sm_next() flushed the cache. */
    644 			if (vs_line(sp, TMAP, NULL, NULL))
    645 				return (1);
    646 
    647 			if (!cursor_set)
    648 				++ssmp;
    649 		}
    650 		if (!cursor_set) {
    651 			rp->lno = ssmp->lno;
    652 			rp->cno = ssmp->c_sboff;
    653 		}
    654 		if (count == 0)
    655 			return (0);
    656 	}
    657 
    658 	for (echanged = zset = 0; count; --count) {
    659 		/* Decide what would show up on the screen. */
    660 		if (vs_sm_next(sp, TMAP, &s1))
    661 			return (1);
    662 
    663 		/* If the line doesn't exist, we're done. */
    664 		if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))
    665 			break;
    666 
    667 		/* Scroll the screen cursor up one logical line. */
    668 		if (vs_sm_1up(sp))
    669 			return (1);
    670 		switch (scmd) {
    671 		case CNTRL_E:
    672 			if (smp > HMAP)
    673 				--smp;
    674 			else
    675 				echanged = 1;
    676 			break;
    677 		case Z_PLUS:
    678 			if (zset) {
    679 				if (smp > HMAP)
    680 					--smp;
    681 			} else {
    682 				smp = TMAP;
    683 				zset = 1;
    684 			}
    685 			/* FALLTHROUGH */
    686 		default:
    687 			break;
    688 		}
    689 	}
    690 
    691 	if (cursor_set)
    692 		return(0);
    693 
    694 	switch (scmd) {
    695 	case CNTRL_E:
    696 		/*
    697 		 * On a ^E that was forced to change lines, try and keep the
    698 		 * cursor as close as possible to the last position, but also
    699 		 * set it up so that the next "real" movement will return the
    700 		 * cursor to the closest position to the last real movement.
    701 		 */
    702 		if (echanged) {
    703 			rp->lno = smp->lno;
    704 			rp->cno = vs_colpos(sp, smp->lno,
    705 			    (O_ISSET(sp, O_LEFTRIGHT) ?
    706 			    smp->coff : (smp->soff - 1) * sp->cols) +
    707 			    sp->rcm % sp->cols);
    708 		}
    709 		return (0);
    710 	case CNTRL_F:
    711 		/*
    712 		 * If there are more lines, the ^F command is positioned at
    713 		 * the first line of the screen.
    714 		 */
    715 		if (!count) {
    716 			smp = HMAP;
    717 			break;
    718 		}
    719 		/* FALLTHROUGH */
    720 	case CNTRL_D:
    721 		/*
    722 		 * The ^D and ^F commands move the cursor towards EOF
    723 		 * if there are more lines to move.  Check to be sure
    724 		 * the lines actually exist.  (They may not if the
    725 		 * file is smaller than the screen.)
    726 		 */
    727 		for (; count; --count, ++smp)
    728 			if (smp == TMAP || !db_exist(sp, smp[1].lno))
    729 				break;
    730 		break;
    731 	case Z_PLUS:
    732 		 /* The z+ command moves the cursor to the first new line. */
    733 		break;
    734 	default:
    735 		abort();
    736 	}
    737 
    738 	if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
    739 		return (1);
    740 	rp->lno = smp->lno;
    741 	rp->cno = smp->c_scoff == 255 ? 0 : smp->c_sboff;
    742 	return (0);
    743 }
    744 
    745 /*
    746  * vs_sm_1up --
    747  *	Scroll the SMAP up one.
    748  *
    749  * PUBLIC: int vs_sm_1up __P((SCR *));
    750  */
    751 int
    752 vs_sm_1up(SCR *sp)
    753 {
    754 	/*
    755 	 * Delete the top line of the screen.  Shift the screen map
    756 	 * up and display a new line at the bottom of the screen.
    757 	 */
    758 	(void)sp->gp->scr_move(sp, 0, 0);
    759 	if (vs_deleteln(sp, 1))
    760 		return (1);
    761 
    762 	/* One-line screens can fail. */
    763 	if (IS_ONELINE(sp)) {
    764 		if (vs_sm_next(sp, TMAP, TMAP))
    765 			return (1);
    766 	} else {
    767 		memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP));
    768 		if (vs_sm_next(sp, TMAP - 1, TMAP))
    769 			return (1);
    770 	}
    771 	/* vs_sm_next() flushed the cache. */
    772 	return (vs_line(sp, TMAP, NULL, NULL));
    773 }
    774 
    775 /*
    776  * vs_deleteln --
    777  *	Delete a line a la curses, make sure to put the information
    778  *	line and other screens back.
    779  */
    780 static int
    781 vs_deleteln(SCR *sp, int cnt)
    782 {
    783 	GS *gp;
    784 	size_t oldy, oldx;
    785 
    786 	gp = sp->gp;
    787 
    788 	/* If the screen is vertically split, we can't scroll it. */
    789 	if (IS_VSPLIT(sp)) {
    790 		F_SET(sp, SC_SCR_REDRAW);
    791 		return (0);
    792 	}
    793 
    794 	if (IS_ONELINE(sp))
    795 		(void)gp->scr_clrtoeol(sp);
    796 	else {
    797 		(void)gp->scr_cursor(sp, &oldy, &oldx);
    798 		while (cnt--) {
    799 			(void)gp->scr_deleteln(sp);
    800 			(void)gp->scr_move(sp, LASTLINE(sp), 0);
    801 			(void)gp->scr_insertln(sp);
    802 			(void)gp->scr_move(sp, oldy, oldx);
    803 		}
    804 	}
    805 	return (0);
    806 }
    807 
    808 /*
    809  * vs_sm_down --
    810  *	Scroll the SMAP down count logical lines.
    811  */
    812 static int
    813 vs_sm_down(SCR *sp, MARK *rp, db_recno_t count, scroll_t scmd, SMAP *smp)
    814 {
    815 	SMAP *ssmp, s1, s2;
    816 	int cursor_set, ychanged, zset;
    817 
    818 	/* Check to see if movement is possible. */
    819 	if (HMAP->lno == 1 &&
    820 	    (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) &&
    821 	    (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) {
    822 		v_sof(sp, NULL);
    823 		return (1);
    824 	}
    825 
    826 	/*
    827 	 * Small screens: see vs_refresh.c section 6a.
    828 	 *
    829 	 * If it's a small screen, and the movement isn't larger than a
    830 	 * screen, i.e some context will remain, open up the screen and
    831 	 * display by scrolling.  In this case, the cursor moves up one
    832 	 * line for each line displayed.  Otherwise, erase/compress and
    833 	 * repaint, and move the cursor to the first line in the screen.
    834 	 * Note, the ^B command is always in the latter case, for historical
    835 	 * reasons.
    836 	 */
    837 	cursor_set = scmd == CNTRL_Y;
    838 	if (IS_SMALL(sp)) {
    839 		if (count >= sp->t_maxrows || scmd == CNTRL_B) {
    840 			s1 = HMAP[0];
    841 			if (vs_sm_erase(sp))
    842 				return (1);
    843 			for (; count--; s1 = s2) {
    844 				if (vs_sm_prev(sp, &s1, &s2))
    845 					return (1);
    846 				if (s2.lno == 1 &&
    847 				    (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1))
    848 					break;
    849 			}
    850 			HMAP[0] = s2;
    851 			if (vs_sm_fill(sp, OOBLNO, P_TOP))
    852 				return (1);
    853 			return (vs_sm_position(sp, rp, 0, P_BOTTOM));
    854 		}
    855 		cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp);
    856 		for (; count &&
    857 		    sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
    858 			if (HMAP->lno == 1 &&
    859 			    (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
    860 				break;
    861 			++TMAP;
    862 			if (vs_sm_1down(sp))
    863 				return (1);
    864 		}
    865 		if (!cursor_set) {
    866 			rp->lno = ssmp->lno;
    867 			rp->cno = ssmp->c_sboff;
    868 		}
    869 		if (count == 0)
    870 			return (0);
    871 	}
    872 
    873 	for (ychanged = zset = 0; count; --count) {
    874 		/* If the line doesn't exist, we're done. */
    875 		if (HMAP->lno == 1 &&
    876 		    (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
    877 			break;
    878 
    879 		/* Scroll the screen and cursor down one logical line. */
    880 		if (vs_sm_1down(sp))
    881 			return (1);
    882 		switch (scmd) {
    883 		case CNTRL_Y:
    884 			if (smp < TMAP)
    885 				++smp;
    886 			else
    887 				ychanged = 1;
    888 			break;
    889 		case Z_CARAT:
    890 			if (zset) {
    891 				if (smp < TMAP)
    892 					++smp;
    893 			} else {
    894 				smp = HMAP;
    895 				zset = 1;
    896 			}
    897 			/* FALLTHROUGH */
    898 		default:
    899 			break;
    900 		}
    901 	}
    902 
    903 	if (scmd != CNTRL_Y && cursor_set)
    904 		return(0);
    905 
    906 	switch (scmd) {
    907 	case CNTRL_B:
    908 		/*
    909 		 * If there are more lines, the ^B command is positioned at
    910 		 * the last line of the screen.  However, the line may not
    911 		 * exist.
    912 		 */
    913 		if (!count) {
    914 			for (smp = TMAP; smp > HMAP; --smp)
    915 				if (db_exist(sp, smp->lno))
    916 					break;
    917 			break;
    918 		}
    919 		/* FALLTHROUGH */
    920 	case CNTRL_U:
    921 		/*
    922 		 * The ^B and ^U commands move the cursor towards SOF
    923 		 * if there are more lines to move.
    924 		 */
    925 		if (count < (db_recno_t)(smp - HMAP))
    926 			smp -= count;
    927 		else
    928 			smp = HMAP;
    929 		break;
    930 	case CNTRL_Y:
    931 		/*
    932 		 * On a ^Y that was forced to change lines, try and keep the
    933 		 * cursor as close as possible to the last position, but also
    934 		 * set it up so that the next "real" movement will return the
    935 		 * cursor to the closest position to the last real movement.
    936 		 */
    937 		if (ychanged) {
    938 			rp->lno = smp->lno;
    939 			rp->cno = vs_colpos(sp, smp->lno,
    940 			    (O_ISSET(sp, O_LEFTRIGHT) ?
    941 			    smp->coff : (smp->soff - 1) * sp->cols) +
    942 			    sp->rcm % sp->cols);
    943 		}
    944 		return (0);
    945 	case Z_CARAT:
    946 		 /* The z^ command moves the cursor to the first new line. */
    947 		break;
    948 	default:
    949 		abort();
    950 	}
    951 
    952 	if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
    953 		return (1);
    954 	rp->lno = smp->lno;
    955 	rp->cno = smp->c_scoff == 255 ? 0 : smp->c_sboff;
    956 	return (0);
    957 }
    958 
    959 /*
    960  * vs_sm_erase --
    961  *	Erase the small screen area for the scrolling functions.
    962  */
    963 static int
    964 vs_sm_erase(SCR *sp)
    965 {
    966 	GS *gp;
    967 
    968 	gp = sp->gp;
    969 	(void)gp->scr_move(sp, LASTLINE(sp), 0);
    970 	(void)gp->scr_clrtoeol(sp);
    971 	for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) {
    972 		(void)gp->scr_move(sp, TMAP - HMAP, 0);
    973 		(void)gp->scr_clrtoeol(sp);
    974 	}
    975 	return (0);
    976 }
    977 
    978 /*
    979  * vs_sm_1down --
    980  *	Scroll the SMAP down one.
    981  *
    982  * PUBLIC: int vs_sm_1down __P((SCR *));
    983  */
    984 int
    985 vs_sm_1down(SCR *sp)
    986 {
    987 	/*
    988 	 * Insert a line at the top of the screen.  Shift the screen map
    989 	 * down and display a new line at the top of the screen.
    990 	 */
    991 	(void)sp->gp->scr_move(sp, 0, 0);
    992 	if (vs_insertln(sp, 1))
    993 		return (1);
    994 
    995 	/* One-line screens can fail. */
    996 	if (IS_ONELINE(sp)) {
    997 		if (vs_sm_prev(sp, HMAP, HMAP))
    998 			return (1);
    999 	} else {
   1000 		memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP));
   1001 		if (vs_sm_prev(sp, HMAP + 1, HMAP))
   1002 			return (1);
   1003 	}
   1004 	/* vs_sm_prev() flushed the cache. */
   1005 	return (vs_line(sp, HMAP, NULL, NULL));
   1006 }
   1007 
   1008 /*
   1009  * vs_insertln --
   1010  *	Insert a line a la curses, make sure to put the information
   1011  *	line and other screens back.
   1012  */
   1013 static int
   1014 vs_insertln(SCR *sp, int cnt)
   1015 {
   1016 	GS *gp;
   1017 	size_t oldy, oldx;
   1018 
   1019 	gp = sp->gp;
   1020 
   1021 	/* If the screen is vertically split, we can't scroll it. */
   1022 	if (IS_VSPLIT(sp)) {
   1023 		F_SET(sp, SC_SCR_REDRAW);
   1024 		return (0);
   1025 	}
   1026 
   1027 	if (IS_ONELINE(sp)) {
   1028 		(void)gp->scr_move(sp, LASTLINE(sp), 0);
   1029 		(void)gp->scr_clrtoeol(sp);
   1030 	} else {
   1031 		(void)gp->scr_cursor(sp, &oldy, &oldx);
   1032 		while (cnt--) {
   1033 			(void)gp->scr_move(sp, LASTLINE(sp) - 1, 0);
   1034 			(void)gp->scr_deleteln(sp);
   1035 			(void)gp->scr_move(sp, oldy, oldx);
   1036 			(void)gp->scr_insertln(sp);
   1037 		}
   1038 	}
   1039 	return (0);
   1040 }
   1041 
   1042 /*
   1043  * vs_sm_next --
   1044  *	Fill in the next entry in the SMAP.
   1045  *
   1046  * PUBLIC: int vs_sm_next __P((SCR *, SMAP *, SMAP *));
   1047  */
   1048 int
   1049 vs_sm_next(SCR *sp, SMAP *p, SMAP *t)
   1050 {
   1051 	size_t lcnt;
   1052 
   1053 	SMAP_FLUSH(t);
   1054 	if (O_ISSET(sp, O_LEFTRIGHT)) {
   1055 		t->lno = p->lno + 1;
   1056 		t->coff = p->coff;
   1057 	} else {
   1058 		lcnt = vs_screens(sp, p->lno, NULL);
   1059 		if (lcnt == p->soff) {
   1060 			t->lno = p->lno + 1;
   1061 			t->soff = 1;
   1062 		} else {
   1063 			t->lno = p->lno;
   1064 			t->soff = p->soff + 1;
   1065 		}
   1066 	}
   1067 	return (0);
   1068 }
   1069 
   1070 /*
   1071  * vs_sm_prev --
   1072  *	Fill in the previous entry in the SMAP.
   1073  *
   1074  * PUBLIC: int vs_sm_prev __P((SCR *, SMAP *, SMAP *));
   1075  */
   1076 int
   1077 vs_sm_prev(SCR *sp, SMAP *p, SMAP *t)
   1078 {
   1079 	SMAP_FLUSH(t);
   1080 	if (O_ISSET(sp, O_LEFTRIGHT)) {
   1081 		t->lno = p->lno - 1;
   1082 		t->coff = p->coff;
   1083 	} else {
   1084 		if (p->soff != 1) {
   1085 			t->lno = p->lno;
   1086 			t->soff = p->soff - 1;
   1087 		} else {
   1088 			t->lno = p->lno - 1;
   1089 			t->soff = vs_screens(sp, t->lno, NULL);
   1090 		}
   1091 	}
   1092 	return (t->lno == 0);
   1093 }
   1094 
   1095 /*
   1096  * vs_sm_cursor --
   1097  *	Return the SMAP entry referenced by the cursor.
   1098  *
   1099  * PUBLIC: int vs_sm_cursor __P((SCR *, SMAP **));
   1100  */
   1101 int
   1102 vs_sm_cursor(SCR *sp, SMAP **smpp)
   1103 {
   1104 	SMAP *p;
   1105 
   1106 	/* See if the cursor is not in the map. */
   1107 	if (sp->lno < HMAP->lno || sp->lno > TMAP->lno)
   1108 		return (1);
   1109 
   1110 	/* Find the first occurence of the line. */
   1111 	for (p = HMAP; p->lno != sp->lno; ++p);
   1112 
   1113 	/* Fill in the map information until we find the right line. */
   1114 	for (; p <= TMAP; ++p) {
   1115 		/* Short lines are common and easy to detect. */
   1116 		if (p != TMAP && (p + 1)->lno != p->lno) {
   1117 			*smpp = p;
   1118 			return (0);
   1119 		}
   1120 		if (!SMAP_CACHE(p) && vs_line(sp, p, NULL, NULL))
   1121 			return (1);
   1122 		if (p->c_eboff >= sp->cno) {
   1123 			*smpp = p;
   1124 			return (0);
   1125 		}
   1126 	}
   1127 
   1128 	/* It was past the end of the map after all. */
   1129 	return (1);
   1130 }
   1131 
   1132 /*
   1133  * vs_sm_position --
   1134  *	Return the line/column of the top, middle or last line on the screen.
   1135  *	(The vi H, M and L commands.)  Here because only the screen routines
   1136  *	know what's really out there.
   1137  *
   1138  * PUBLIC: int vs_sm_position __P((SCR *, MARK *, u_long, pos_t));
   1139  */
   1140 int
   1141 vs_sm_position(SCR *sp, MARK *rp, u_long cnt, pos_t pos)
   1142 {
   1143 	SMAP *smp;
   1144 	db_recno_t last;
   1145 
   1146 	switch (pos) {
   1147 	case P_TOP:
   1148 		/*
   1149 		 * !!!
   1150 		 * Historically, an invalid count to the H command failed.
   1151 		 * We do nothing special here, just making sure that H in
   1152 		 * an empty screen works.
   1153 		 */
   1154 		if (cnt > (u_long)(TMAP - HMAP))
   1155 			goto sof;
   1156 		smp = HMAP + cnt;
   1157 		if (cnt && !db_exist(sp, smp->lno)) {
   1158 sof:			msgq(sp, M_BERR, "220|Movement past the end-of-screen");
   1159 			return (1);
   1160 		}
   1161 		break;
   1162 	case P_MIDDLE:
   1163 		/*
   1164 		 * !!!
   1165 		 * Historically, a count to the M command was ignored.
   1166 		 * If the screen isn't filled, find the middle of what's
   1167 		 * real and move there.
   1168 		 */
   1169 		if (!db_exist(sp, TMAP->lno)) {
   1170 			if (db_last(sp, &last))
   1171 				return (1);
   1172 			for (smp = TMAP; smp->lno > last && smp > HMAP; --smp);
   1173 			if (smp > HMAP)
   1174 				smp -= (smp - HMAP) / 2;
   1175 		} else
   1176 			smp = (HMAP + (TMAP - HMAP) / 2) + cnt;
   1177 		break;
   1178 	case P_BOTTOM:
   1179 		/*
   1180 		 * !!!
   1181 		 * Historically, an invalid count to the L command failed.
   1182 		 * If the screen isn't filled, find the bottom of what's
   1183 		 * real and try to offset from there.
   1184 		 */
   1185 		if (cnt > (u_long)(TMAP - HMAP))
   1186 			goto eof;
   1187 		smp = TMAP - cnt;
   1188 		if (!db_exist(sp, smp->lno)) {
   1189 			if (db_last(sp, &last))
   1190 				return (1);
   1191 			for (; smp->lno > last && smp > HMAP; --smp);
   1192 			if (cnt > (u_long)(smp - HMAP)) {
   1193 eof:				msgq(sp, M_BERR,
   1194 			    "221|Movement past the beginning-of-screen");
   1195 				return (1);
   1196 			}
   1197 			smp -= cnt;
   1198 		}
   1199 		break;
   1200 	default:
   1201 		abort();
   1202 	}
   1203 
   1204 	/* Make sure that the cached information is valid. */
   1205 	if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
   1206 		return (1);
   1207 	rp->lno = smp->lno;
   1208 	rp->cno = smp->c_sboff;
   1209 
   1210 	return (0);
   1211 }
   1212 
   1213 /*
   1214  * vs_sm_nlines --
   1215  *	Return the number of screen lines from an SMAP entry to the
   1216  *	start of some file line, less than a maximum value.
   1217  *
   1218  * PUBLIC: db_recno_t vs_sm_nlines __P((SCR *, SMAP *, db_recno_t, size_t));
   1219  */
   1220 db_recno_t
   1221 vs_sm_nlines(SCR *sp, SMAP *from_sp, db_recno_t to_lno, size_t max)
   1222 {
   1223 	db_recno_t lno, lcnt;
   1224 
   1225 	if (O_ISSET(sp, O_LEFTRIGHT))
   1226 		return (from_sp->lno > to_lno ?
   1227 		    from_sp->lno - to_lno : to_lno - from_sp->lno);
   1228 
   1229 	if (from_sp->lno == to_lno)
   1230 		return (from_sp->soff - 1);
   1231 
   1232 	if (from_sp->lno > to_lno) {
   1233 		lcnt = from_sp->soff - 1;	/* Correct for off-by-one. */
   1234 		for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;)
   1235 			lcnt += vs_screens(sp, lno, NULL);
   1236 	} else {
   1237 		lno = from_sp->lno;
   1238 		lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1;
   1239 		for (; ++lno < to_lno && lcnt <= max;)
   1240 			lcnt += vs_screens(sp, lno, NULL);
   1241 	}
   1242 	return (lcnt);
   1243 }
   1244