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