xref: /freebsd/contrib/nvi/vi/vs_smap.c (revision ccfb965433c67f3bda935a3cdf334be2e3c4348d)
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.31 2011/02/26 13:56:21 skimo Exp $";
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(SCR *, int);
30 static int	vs_insertln(SCR *, int);
31 static int	vs_sm_delete(SCR *, recno_t);
32 static int	vs_sm_down(SCR *, MARK *, recno_t, scroll_t, SMAP *);
33 static int	vs_sm_erase(SCR *);
34 static int	vs_sm_insert(SCR *, recno_t);
35 static int	vs_sm_reset(SCR *, recno_t);
36 static int	vs_sm_up(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(SCR *, recno_t, lnop_t);
43  */
44 int
45 vs_change(SCR *sp, 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(SCR *, recno_t, pos_t);
175  */
176 int
177 vs_sm_fill(SCR *sp, 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, 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, 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, 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(SCR *, MARK *, recno_t, scroll_t);
516  */
517 int
518 vs_sm_scroll(SCR *sp, MARK *rp, 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, 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(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, 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(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(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(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(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(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 	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: recno_t vs_sm_nlines(SCR *, SMAP *, recno_t, size_t);
1218  */
1219 recno_t
1220 vs_sm_nlines(SCR *sp, SMAP *from_sp, recno_t to_lno, size_t max)
1221 {
1222 	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