xref: /freebsd/contrib/nvi/vi/vs_smap.c (revision 1e413cf93298b5b97441a21d9a50fdcd0ee9945e)
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[] = "@(#)vs_smap.c	10.25 (Berkeley) 7/12/96";
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 *, recno_t));
32 static int	vs_sm_down __P((SCR *, MARK *, recno_t, scroll_t, SMAP *));
33 static int	vs_sm_erase __P((SCR *));
34 static int	vs_sm_insert __P((SCR *, recno_t));
35 static int	vs_sm_reset __P((SCR *, recno_t));
36 static int	vs_sm_up __P((SCR *, MARK *, 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 *, recno_t, lnop_t));
43  */
44 int
45 vs_change(sp, lno, op)
46 	SCR *sp;
47 	recno_t lno;
48 	lnop_t op;
49 {
50 	VI_PRIVATE *vip;
51 	SMAP *p;
52 	size_t cnt, oldy, oldx;
53 
54 	vip = VIP(sp);
55 
56 	/*
57 	 * XXX
58 	 * Very nasty special case.  The historic vi code displays a single
59 	 * space (or a '$' if the list option is set) for the first line in
60 	 * an "empty" file.  If we "insert" a line, that line gets scrolled
61 	 * down, not repainted, so it's incorrect when we refresh the screen.
62 	 * The vi text input functions detect it explicitly and don't insert
63 	 * a new line.
64 	 *
65 	 * Check for line #2 before going to the end of the file.
66 	 */
67 	if ((op == LINE_APPEND && lno == 0 || op == LINE_INSERT && lno == 1) &&
68 	    !db_exist(sp, 2)) {
69 		lno = 1;
70 		op = LINE_RESET;
71 	}
72 
73 	/* Appending is the same as inserting, if the line is incremented. */
74 	if (op == LINE_APPEND) {
75 		++lno;
76 		op = LINE_INSERT;
77 	}
78 
79 	/* Ignore the change if the line is after the map. */
80 	if (lno > TMAP->lno)
81 		return (0);
82 
83 	/*
84 	 * If the line is before the map, and it's a decrement, decrement
85 	 * the map.  If it's an increment, increment the map.  Otherwise,
86 	 * ignore it.
87 	 */
88 	if (lno < HMAP->lno) {
89 		switch (op) {
90 		case LINE_APPEND:
91 			abort();
92 			/* NOTREACHED */
93 		case LINE_DELETE:
94 			for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
95 				--p->lno;
96 			if (sp->lno >= lno)
97 				--sp->lno;
98 			F_SET(vip, VIP_N_RENUMBER);
99 			break;
100 		case LINE_INSERT:
101 			for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
102 				++p->lno;
103 			if (sp->lno >= lno)
104 				++sp->lno;
105 			F_SET(vip, VIP_N_RENUMBER);
106 			break;
107 		case LINE_RESET:
108 			break;
109 		}
110 		return (0);
111 	}
112 
113 	F_SET(vip, VIP_N_REFRESH);
114 
115 	/*
116 	 * Invalidate the line size cache, and invalidate the cursor if it's
117 	 * on this line,
118 	 */
119 	VI_SCR_CFLUSH(vip);
120 	if (sp->lno == lno)
121 		F_SET(vip, VIP_CUR_INVALID);
122 
123 	/*
124 	 * If ex modifies the screen after ex output is already on the screen
125 	 * or if we've switched into ex canonical mode, don't touch it -- we'll
126 	 * get scrolling wrong, at best.
127 	 */
128 	if (!F_ISSET(sp, SC_TINPUT_INFO) &&
129 	    (F_ISSET(sp, SC_SCR_EXWROTE) || VIP(sp)->totalcount > 1)) {
130 		F_SET(vip, VIP_N_EX_REDRAW);
131 		return (0);
132 	}
133 
134 	/* Save and restore the cursor for these routines. */
135 	(void)sp->gp->scr_cursor(sp, &oldy, &oldx);
136 
137 	switch (op) {
138 	case LINE_DELETE:
139 		if (vs_sm_delete(sp, lno))
140 			return (1);
141 		F_SET(vip, VIP_N_RENUMBER);
142 		break;
143 	case LINE_INSERT:
144 		if (vs_sm_insert(sp, lno))
145 			return (1);
146 		F_SET(vip, VIP_N_RENUMBER);
147 		break;
148 	case LINE_RESET:
149 		if (vs_sm_reset(sp, lno))
150 			return (1);
151 		break;
152 	default:
153 		abort();
154 	}
155 
156 	(void)sp->gp->scr_move(sp, oldy, oldx);
157 	return (0);
158 }
159 
160 /*
161  * vs_sm_fill --
162  *	Fill in the screen map, placing the specified line at the
163  *	right position.  There isn't any way to tell if an SMAP
164  *	entry has been filled in, so this routine had better be
165  *	called with P_FILL set before anything else is done.
166  *
167  * !!!
168  * Unexported interface: if lno is OOBLNO, P_TOP means that the HMAP
169  * slot is already filled in, P_BOTTOM means that the TMAP slot is
170  * already filled in, and we just finish up the job.
171  *
172  * PUBLIC: int vs_sm_fill __P((SCR *, recno_t, pos_t));
173  */
174 int
175 vs_sm_fill(sp, lno, pos)
176 	SCR *sp;
177 	recno_t lno;
178 	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 		}
229 		/* If we fail, just punt. */
230 		for (p = HMAP, cnt = sp->t_rows; --cnt; ++p)
231 			if (vs_sm_next(sp, p, p + 1))
232 				goto err;
233 		break;
234 	case P_MIDDLE:
235 		/* If we fail, guess that the file is too small. */
236 middle:		p = HMAP + sp->t_rows / 2;
237 		p->lno = lno;
238 		p->coff = 0;
239 		p->soff = 1;
240 		for (; p > HMAP; --p)
241 			if (vs_sm_prev(sp, p, p - 1)) {
242 				lno = 1;
243 				goto top;
244 			}
245 
246 		/* If we fail, just punt. */
247 		p = HMAP + sp->t_rows / 2;
248 		for (; p < TMAP; ++p)
249 			if (vs_sm_next(sp, p, p + 1))
250 				goto err;
251 		break;
252 	case P_BOTTOM:
253 		if (lno != OOBLNO) {
254 			TMAP->lno = lno;
255 			TMAP->coff = 0;
256 			TMAP->soff = vs_screens(sp, lno, NULL);
257 		}
258 		/* If we fail, guess that the file is too small. */
259 bottom:		for (p = TMAP; p > HMAP; --p)
260 			if (vs_sm_prev(sp, p, p - 1)) {
261 				lno = 1;
262 				goto top;
263 			}
264 		break;
265 	default:
266 		abort();
267 	}
268 	return (0);
269 
270 	/*
271 	 * Try and put *something* on the screen.  If this fails, we have a
272 	 * serious hard error.
273 	 */
274 err:	HMAP->lno = 1;
275 	HMAP->coff = 0;
276 	HMAP->soff = 1;
277 	for (p = HMAP; p < TMAP; ++p)
278 		if (vs_sm_next(sp, p, p + 1))
279 			return (1);
280 	return (0);
281 }
282 
283 /*
284  * For the routines vs_sm_reset, vs_sm_delete and vs_sm_insert: if the
285  * screen contains only a single line (whether because the screen is small
286  * or the line large), it gets fairly exciting.  Skip the fun, set a flag
287  * so the screen map is refilled and the screen redrawn, and return.  This
288  * is amazingly slow, but it's not clear that anyone will care.
289  */
290 #define	HANDLE_WEIRDNESS(cnt) {						\
291 	if (cnt >= sp->t_rows) {					\
292 		F_SET(sp, SC_SCR_REFORMAT);				\
293 		return (0);						\
294 	}								\
295 }
296 
297 /*
298  * vs_sm_delete --
299  *	Delete a line out of the SMAP.
300  */
301 static int
302 vs_sm_delete(sp, lno)
303 	SCR *sp;
304 	recno_t lno;
305 {
306 	SMAP *p, *t;
307 	size_t cnt_orig;
308 
309 	/*
310 	 * Find the line in the map, and count the number of screen lines
311 	 * which display any part of the deleted line.
312 	 */
313 	for (p = HMAP; p->lno != lno; ++p);
314 	if (O_ISSET(sp, O_LEFTRIGHT))
315 		cnt_orig = 1;
316 	else
317 		for (cnt_orig = 1, t = p + 1;
318 		    t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
319 
320 	HANDLE_WEIRDNESS(cnt_orig);
321 
322 	/* Delete that many lines from the screen. */
323 	(void)sp->gp->scr_move(sp, p - HMAP, 0);
324 	if (vs_deleteln(sp, cnt_orig))
325 		return (1);
326 
327 	/* Shift the screen map up. */
328 	memmove(p, p + cnt_orig, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
329 
330 	/* Decrement the line numbers for the rest of the map. */
331 	for (t = TMAP - cnt_orig; p <= t; ++p)
332 		--p->lno;
333 
334 	/* Display the new lines. */
335 	for (p = TMAP - cnt_orig;;) {
336 		if (p < TMAP && vs_sm_next(sp, p, p + 1))
337 			return (1);
338 		/* vs_sm_next() flushed the cache. */
339 		if (vs_line(sp, ++p, NULL, NULL))
340 			return (1);
341 		if (p == TMAP)
342 			break;
343 	}
344 	return (0);
345 }
346 
347 /*
348  * vs_sm_insert --
349  *	Insert a line into the SMAP.
350  */
351 static int
352 vs_sm_insert(sp, lno)
353 	SCR *sp;
354 	recno_t lno;
355 {
356 	SMAP *p, *t;
357 	size_t cnt_orig, cnt, coff;
358 
359 	/* Save the offset. */
360 	coff = HMAP->coff;
361 
362 	/*
363 	 * Find the line in the map, find out how many screen lines
364 	 * needed to display the line.
365 	 */
366 	for (p = HMAP; p->lno != lno; ++p);
367 
368 	cnt_orig = vs_screens(sp, lno, NULL);
369 	HANDLE_WEIRDNESS(cnt_orig);
370 
371 	/*
372 	 * The lines left in the screen override the number of screen
373 	 * lines in the inserted line.
374 	 */
375 	cnt = (TMAP - p) + 1;
376 	if (cnt_orig > cnt)
377 		cnt_orig = cnt;
378 
379 	/* Push down that many lines. */
380 	(void)sp->gp->scr_move(sp, p - HMAP, 0);
381 	if (vs_insertln(sp, cnt_orig))
382 		return (1);
383 
384 	/* Shift the screen map down. */
385 	memmove(p + cnt_orig, p, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
386 
387 	/* Increment the line numbers for the rest of the map. */
388 	for (t = p + cnt_orig; t <= TMAP; ++t)
389 		++t->lno;
390 
391 	/* Fill in the SMAP for the new lines, and display. */
392 	for (cnt = 1, t = p; cnt <= cnt_orig; ++t, ++cnt) {
393 		t->lno = lno;
394 		t->coff = coff;
395 		t->soff = cnt;
396 		SMAP_FLUSH(t);
397 		if (vs_line(sp, t, NULL, NULL))
398 			return (1);
399 	}
400 	return (0);
401 }
402 
403 /*
404  * vs_sm_reset --
405  *	Reset a line in the SMAP.
406  */
407 static int
408 vs_sm_reset(sp, lno)
409 	SCR *sp;
410 	recno_t lno;
411 {
412 	SMAP *p, *t;
413 	size_t cnt_orig, cnt_new, cnt, diff;
414 
415 	/*
416 	 * See if the number of on-screen rows taken up by the old display
417 	 * for the line is the same as the number needed for the new one.
418 	 * If so, repaint, otherwise do it the hard way.
419 	 */
420 	for (p = HMAP; p->lno != lno; ++p);
421 	if (O_ISSET(sp, O_LEFTRIGHT)) {
422 		t = p;
423 		cnt_orig = cnt_new = 1;
424 	} else {
425 		for (cnt_orig = 0,
426 		    t = p; t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
427 		cnt_new = vs_screens(sp, lno, NULL);
428 	}
429 
430 	HANDLE_WEIRDNESS(cnt_orig);
431 
432 	if (cnt_orig == cnt_new) {
433 		do {
434 			SMAP_FLUSH(p);
435 			if (vs_line(sp, p, NULL, NULL))
436 				return (1);
437 		} while (++p < t);
438 		return (0);
439 	}
440 
441 	if (cnt_orig < cnt_new) {
442 		/* Get the difference. */
443 		diff = cnt_new - cnt_orig;
444 
445 		/*
446 		 * The lines left in the screen override the number of screen
447 		 * lines in the inserted line.
448 		 */
449 		cnt = (TMAP - p) + 1;
450 		if (diff > cnt)
451 			diff = cnt;
452 
453 		/* If there are any following lines, push them down. */
454 		if (cnt > 1) {
455 			(void)sp->gp->scr_move(sp, p - HMAP, 0);
456 			if (vs_insertln(sp, diff))
457 				return (1);
458 
459 			/* Shift the screen map down. */
460 			memmove(p + diff, p,
461 			    (((TMAP - p) - diff) + 1) * sizeof(SMAP));
462 		}
463 
464 		/* Fill in the SMAP for the replaced line, and display. */
465 		for (cnt = 1, t = p; cnt_new-- && t <= TMAP; ++t, ++cnt) {
466 			t->lno = lno;
467 			t->soff = cnt;
468 			SMAP_FLUSH(t);
469 			if (vs_line(sp, t, NULL, NULL))
470 				return (1);
471 		}
472 	} else {
473 		/* Get the difference. */
474 		diff = cnt_orig - cnt_new;
475 
476 		/* Delete that many lines from the screen. */
477 		(void)sp->gp->scr_move(sp, p - HMAP, 0);
478 		if (vs_deleteln(sp, diff))
479 			return (1);
480 
481 		/* Shift the screen map up. */
482 		memmove(p, p + diff, (((TMAP - p) - diff) + 1) * sizeof(SMAP));
483 
484 		/* Fill in the SMAP for the replaced line, and display. */
485 		for (cnt = 1, t = p; cnt_new--; ++t, ++cnt) {
486 			t->lno = lno;
487 			t->soff = cnt;
488 			SMAP_FLUSH(t);
489 			if (vs_line(sp, t, NULL, NULL))
490 				return (1);
491 		}
492 
493 		/* Display the new lines at the bottom of the screen. */
494 		for (t = TMAP - diff;;) {
495 			if (t < TMAP && vs_sm_next(sp, t, t + 1))
496 				return (1);
497 			/* vs_sm_next() flushed the cache. */
498 			if (vs_line(sp, ++t, NULL, NULL))
499 				return (1);
500 			if (t == TMAP)
501 				break;
502 		}
503 	}
504 	return (0);
505 }
506 
507 /*
508  * vs_sm_scroll
509  *	Scroll the SMAP up/down count logical lines.  Different
510  *	semantics based on the vi command, *sigh*.
511  *
512  * PUBLIC: int vs_sm_scroll __P((SCR *, MARK *, recno_t, scroll_t));
513  */
514 int
515 vs_sm_scroll(sp, rp, count, scmd)
516 	SCR *sp;
517 	MARK *rp;
518 	recno_t count;
519 	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(sp, rp, count, scmd, smp)
577 	SCR *sp;
578 	MARK *rp;
579 	scroll_t scmd;
580 	recno_t count;
581 	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(sp)
758 	SCR *sp;
759 {
760 	/*
761 	 * Delete the top line of the screen.  Shift the screen map
762 	 * up and display a new line at the bottom of the screen.
763 	 */
764 	(void)sp->gp->scr_move(sp, 0, 0);
765 	if (vs_deleteln(sp, 1))
766 		return (1);
767 
768 	/* One-line screens can fail. */
769 	if (IS_ONELINE(sp)) {
770 		if (vs_sm_next(sp, TMAP, TMAP))
771 			return (1);
772 	} else {
773 		memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP));
774 		if (vs_sm_next(sp, TMAP - 1, TMAP))
775 			return (1);
776 	}
777 	/* vs_sm_next() flushed the cache. */
778 	return (vs_line(sp, TMAP, NULL, NULL));
779 }
780 
781 /*
782  * vs_deleteln --
783  *	Delete a line a la curses, make sure to put the information
784  *	line and other screens back.
785  */
786 static int
787 vs_deleteln(sp, cnt)
788 	SCR *sp;
789 	int cnt;
790 {
791 	GS *gp;
792 	size_t oldy, oldx;
793 
794 	gp = sp->gp;
795 	if (IS_ONELINE(sp))
796 		(void)gp->scr_clrtoeol(sp);
797 	else {
798 		(void)gp->scr_cursor(sp, &oldy, &oldx);
799 		while (cnt--) {
800 			(void)gp->scr_deleteln(sp);
801 			(void)gp->scr_move(sp, LASTLINE(sp), 0);
802 			(void)gp->scr_insertln(sp);
803 			(void)gp->scr_move(sp, oldy, oldx);
804 		}
805 	}
806 	return (0);
807 }
808 
809 /*
810  * vs_sm_down --
811  *	Scroll the SMAP down count logical lines.
812  */
813 static int
814 vs_sm_down(sp, rp, count, scmd, smp)
815 	SCR *sp;
816 	MARK *rp;
817 	recno_t count;
818 	SMAP *smp;
819 	scroll_t scmd;
820 {
821 	SMAP *ssmp, s1, s2;
822 	int cursor_set, ychanged, zset;
823 
824 	/* Check to see if movement is possible. */
825 	if (HMAP->lno == 1 &&
826 	    (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) &&
827 	    (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) {
828 		v_sof(sp, NULL);
829 		return (1);
830 	}
831 
832 	/*
833 	 * Small screens: see vs_refresh.c section 6a.
834 	 *
835 	 * If it's a small screen, and the movement isn't larger than a
836 	 * screen, i.e some context will remain, open up the screen and
837 	 * display by scrolling.  In this case, the cursor moves up one
838 	 * line for each line displayed.  Otherwise, erase/compress and
839 	 * repaint, and move the cursor to the first line in the screen.
840 	 * Note, the ^B command is always in the latter case, for historical
841 	 * reasons.
842 	 */
843 	cursor_set = scmd == CNTRL_Y;
844 	if (IS_SMALL(sp)) {
845 		if (count >= sp->t_maxrows || scmd == CNTRL_B) {
846 			s1 = HMAP[0];
847 			if (vs_sm_erase(sp))
848 				return (1);
849 			for (; count--; s1 = s2) {
850 				if (vs_sm_prev(sp, &s1, &s2))
851 					return (1);
852 				if (s2.lno == 1 &&
853 				    (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1))
854 					break;
855 			}
856 			HMAP[0] = s2;
857 			if (vs_sm_fill(sp, OOBLNO, P_TOP))
858 				return (1);
859 			return (vs_sm_position(sp, rp, 0, P_BOTTOM));
860 		}
861 		cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp);
862 		for (; count &&
863 		    sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
864 			if (HMAP->lno == 1 &&
865 			    (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
866 				break;
867 			++TMAP;
868 			if (vs_sm_1down(sp))
869 				return (1);
870 		}
871 		if (!cursor_set) {
872 			rp->lno = ssmp->lno;
873 			rp->cno = ssmp->c_sboff;
874 		}
875 		if (count == 0)
876 			return (0);
877 	}
878 
879 	for (ychanged = zset = 0; count; --count) {
880 		/* If the line doesn't exist, we're done. */
881 		if (HMAP->lno == 1 &&
882 		    (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
883 			break;
884 
885 		/* Scroll the screen and cursor down one logical line. */
886 		if (vs_sm_1down(sp))
887 			return (1);
888 		switch (scmd) {
889 		case CNTRL_Y:
890 			if (smp < TMAP)
891 				++smp;
892 			else
893 				ychanged = 1;
894 			break;
895 		case Z_CARAT:
896 			if (zset) {
897 				if (smp < TMAP)
898 					++smp;
899 			} else {
900 				smp = HMAP;
901 				zset = 1;
902 			}
903 			/* FALLTHROUGH */
904 		default:
905 			break;
906 		}
907 	}
908 
909 	if (scmd != CNTRL_Y && cursor_set)
910 		return(0);
911 
912 	switch (scmd) {
913 	case CNTRL_B:
914 		/*
915 		 * If there are more lines, the ^B command is positioned at
916 		 * the last line of the screen.  However, the line may not
917 		 * exist.
918 		 */
919 		if (!count) {
920 			for (smp = TMAP; smp > HMAP; --smp)
921 				if (db_exist(sp, smp->lno))
922 					break;
923 			break;
924 		}
925 		/* FALLTHROUGH */
926 	case CNTRL_U:
927 		/*
928 		 * The ^B and ^U commands move the cursor towards SOF
929 		 * if there are more lines to move.
930 		 */
931 		if (count < smp - HMAP)
932 			smp -= count;
933 		else
934 			smp = HMAP;
935 		break;
936 	case CNTRL_Y:
937 		/*
938 		 * On a ^Y that was forced to change lines, try and keep the
939 		 * cursor as close as possible to the last position, but also
940 		 * set it up so that the next "real" movement will return the
941 		 * cursor to the closest position to the last real movement.
942 		 */
943 		if (ychanged) {
944 			rp->lno = smp->lno;
945 			rp->cno = vs_colpos(sp, smp->lno,
946 			    (O_ISSET(sp, O_LEFTRIGHT) ?
947 			    smp->coff : (smp->soff - 1) * sp->cols) +
948 			    sp->rcm % sp->cols);
949 		}
950 		return (0);
951 	case Z_CARAT:
952 		 /* The z^ command moves the cursor to the first new line. */
953 		break;
954 	default:
955 		abort();
956 	}
957 
958 	if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
959 		return (1);
960 	rp->lno = smp->lno;
961 	rp->cno = smp->c_scoff == 255 ? 0 : smp->c_sboff;
962 	return (0);
963 }
964 
965 /*
966  * vs_sm_erase --
967  *	Erase the small screen area for the scrolling functions.
968  */
969 static int
970 vs_sm_erase(sp)
971 	SCR *sp;
972 {
973 	GS *gp;
974 
975 	gp = sp->gp;
976 	(void)gp->scr_move(sp, LASTLINE(sp), 0);
977 	(void)gp->scr_clrtoeol(sp);
978 	for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) {
979 		(void)gp->scr_move(sp, TMAP - HMAP, 0);
980 		(void)gp->scr_clrtoeol(sp);
981 	}
982 	return (0);
983 }
984 
985 /*
986  * vs_sm_1down --
987  *	Scroll the SMAP down one.
988  *
989  * PUBLIC: int vs_sm_1down __P((SCR *));
990  */
991 int
992 vs_sm_1down(sp)
993 	SCR *sp;
994 {
995 	/*
996 	 * Insert a line at the top of the screen.  Shift the screen map
997 	 * down and display a new line at the top of the screen.
998 	 */
999 	(void)sp->gp->scr_move(sp, 0, 0);
1000 	if (vs_insertln(sp, 1))
1001 		return (1);
1002 
1003 	/* One-line screens can fail. */
1004 	if (IS_ONELINE(sp)) {
1005 		if (vs_sm_prev(sp, HMAP, HMAP))
1006 			return (1);
1007 	} else {
1008 		memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP));
1009 		if (vs_sm_prev(sp, HMAP + 1, HMAP))
1010 			return (1);
1011 	}
1012 	/* vs_sm_prev() flushed the cache. */
1013 	return (vs_line(sp, HMAP, NULL, NULL));
1014 }
1015 
1016 /*
1017  * vs_insertln --
1018  *	Insert a line a la curses, make sure to put the information
1019  *	line and other screens back.
1020  */
1021 static int
1022 vs_insertln(sp, cnt)
1023 	SCR *sp;
1024 	int cnt;
1025 {
1026 	GS *gp;
1027 	size_t oldy, oldx;
1028 
1029 	gp = sp->gp;
1030 	if (IS_ONELINE(sp)) {
1031 		(void)gp->scr_move(sp, LASTLINE(sp), 0);
1032 		(void)gp->scr_clrtoeol(sp);
1033 	} else {
1034 		(void)gp->scr_cursor(sp, &oldy, &oldx);
1035 		while (cnt--) {
1036 			(void)gp->scr_move(sp, LASTLINE(sp) - 1, 0);
1037 			(void)gp->scr_deleteln(sp);
1038 			(void)gp->scr_move(sp, oldy, oldx);
1039 			(void)gp->scr_insertln(sp);
1040 		}
1041 	}
1042 	return (0);
1043 }
1044 
1045 /*
1046  * vs_sm_next --
1047  *	Fill in the next entry in the SMAP.
1048  *
1049  * PUBLIC: int vs_sm_next __P((SCR *, SMAP *, SMAP *));
1050  */
1051 int
1052 vs_sm_next(sp, p, t)
1053 	SCR *sp;
1054 	SMAP *p, *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(sp, p, t)
1083 	SCR *sp;
1084 	SMAP *p, *t;
1085 {
1086 	SMAP_FLUSH(t);
1087 	if (O_ISSET(sp, O_LEFTRIGHT)) {
1088 		t->lno = p->lno - 1;
1089 		t->coff = p->coff;
1090 	} else {
1091 		if (p->soff != 1) {
1092 			t->lno = p->lno;
1093 			t->soff = p->soff - 1;
1094 		} else {
1095 			t->lno = p->lno - 1;
1096 			t->soff = vs_screens(sp, t->lno, NULL);
1097 		}
1098 	}
1099 	return (t->lno == 0);
1100 }
1101 
1102 /*
1103  * vs_sm_cursor --
1104  *	Return the SMAP entry referenced by the cursor.
1105  *
1106  * PUBLIC: int vs_sm_cursor __P((SCR *, SMAP **));
1107  */
1108 int
1109 vs_sm_cursor(sp, smpp)
1110 	SCR *sp;
1111 	SMAP **smpp;
1112 {
1113 	SMAP *p;
1114 
1115 	/* See if the cursor is not in the map. */
1116 	if (sp->lno < HMAP->lno || sp->lno > TMAP->lno)
1117 		return (1);
1118 
1119 	/* Find the first occurence of the line. */
1120 	for (p = HMAP; p->lno != sp->lno; ++p);
1121 
1122 	/* Fill in the map information until we find the right line. */
1123 	for (; p <= TMAP; ++p) {
1124 		/* Short lines are common and easy to detect. */
1125 		if (p != TMAP && (p + 1)->lno != p->lno) {
1126 			*smpp = p;
1127 			return (0);
1128 		}
1129 		if (!SMAP_CACHE(p) && vs_line(sp, p, NULL, NULL))
1130 			return (1);
1131 		if (p->c_eboff >= sp->cno) {
1132 			*smpp = p;
1133 			return (0);
1134 		}
1135 	}
1136 
1137 	/* It was past the end of the map after all. */
1138 	return (1);
1139 }
1140 
1141 /*
1142  * vs_sm_position --
1143  *	Return the line/column of the top, middle or last line on the screen.
1144  *	(The vi H, M and L commands.)  Here because only the screen routines
1145  *	know what's really out there.
1146  *
1147  * PUBLIC: int vs_sm_position __P((SCR *, MARK *, u_long, pos_t));
1148  */
1149 int
1150 vs_sm_position(sp, rp, cnt, pos)
1151 	SCR *sp;
1152 	MARK *rp;
1153 	u_long cnt;
1154 	pos_t pos;
1155 {
1156 	SMAP *smp;
1157 	recno_t last;
1158 
1159 	switch (pos) {
1160 	case P_TOP:
1161 		/*
1162 		 * !!!
1163 		 * Historically, an invalid count to the H command failed.
1164 		 * We do nothing special here, just making sure that H in
1165 		 * an empty screen works.
1166 		 */
1167 		if (cnt > TMAP - HMAP)
1168 			goto sof;
1169 		smp = HMAP + cnt;
1170 		if (cnt && !db_exist(sp, smp->lno)) {
1171 sof:			msgq(sp, M_BERR, "220|Movement past the end-of-screen");
1172 			return (1);
1173 		}
1174 		break;
1175 	case P_MIDDLE:
1176 		/*
1177 		 * !!!
1178 		 * Historically, a count to the M command was ignored.
1179 		 * If the screen isn't filled, find the middle of what's
1180 		 * real and move there.
1181 		 */
1182 		if (!db_exist(sp, TMAP->lno)) {
1183 			if (db_last(sp, &last))
1184 				return (1);
1185 			for (smp = TMAP; smp->lno > last && smp > HMAP; --smp);
1186 			if (smp > HMAP)
1187 				smp -= (smp - HMAP) / 2;
1188 		} else
1189 			smp = (HMAP + (TMAP - HMAP) / 2) + cnt;
1190 		break;
1191 	case P_BOTTOM:
1192 		/*
1193 		 * !!!
1194 		 * Historically, an invalid count to the L command failed.
1195 		 * If the screen isn't filled, find the bottom of what's
1196 		 * real and try to offset from there.
1197 		 */
1198 		if (cnt > TMAP - HMAP)
1199 			goto eof;
1200 		smp = TMAP - cnt;
1201 		if (!db_exist(sp, smp->lno)) {
1202 			if (db_last(sp, &last))
1203 				return (1);
1204 			for (; smp->lno > last && smp > HMAP; --smp);
1205 			if (cnt > smp - HMAP) {
1206 eof:				msgq(sp, M_BERR,
1207 			    "221|Movement past the beginning-of-screen");
1208 				return (1);
1209 			}
1210 			smp -= cnt;
1211 		}
1212 		break;
1213 	default:
1214 		abort();
1215 	}
1216 
1217 	/* Make sure that the cached information is valid. */
1218 	if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
1219 		return (1);
1220 	rp->lno = smp->lno;
1221 	rp->cno = smp->c_sboff;
1222 
1223 	return (0);
1224 }
1225 
1226 /*
1227  * vs_sm_nlines --
1228  *	Return the number of screen lines from an SMAP entry to the
1229  *	start of some file line, less than a maximum value.
1230  *
1231  * PUBLIC: recno_t vs_sm_nlines __P((SCR *, SMAP *, recno_t, size_t));
1232  */
1233 recno_t
1234 vs_sm_nlines(sp, from_sp, to_lno, max)
1235 	SCR *sp;
1236 	SMAP *from_sp;
1237 	recno_t to_lno;
1238 	size_t max;
1239 {
1240 	recno_t lno, lcnt;
1241 
1242 	if (O_ISSET(sp, O_LEFTRIGHT))
1243 		return (from_sp->lno > to_lno ?
1244 		    from_sp->lno - to_lno : to_lno - from_sp->lno);
1245 
1246 	if (from_sp->lno == to_lno)
1247 		return (from_sp->soff - 1);
1248 
1249 	if (from_sp->lno > to_lno) {
1250 		lcnt = from_sp->soff - 1;	/* Correct for off-by-one. */
1251 		for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;)
1252 			lcnt += vs_screens(sp, lno, NULL);
1253 	} else {
1254 		lno = from_sp->lno;
1255 		lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1;
1256 		for (; ++lno < to_lno && lcnt <= max;)
1257 			lcnt += vs_screens(sp, lno, NULL);
1258 	}
1259 	return (lcnt);
1260 }
1261