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