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