vs_smap.c revision 19304
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
13static 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
29static int	vs_deleteln __P((SCR *, int));
30static int	vs_insertln __P((SCR *, int));
31static int	vs_sm_delete __P((SCR *, recno_t));
32static int	vs_sm_down __P((SCR *, MARK *, recno_t, scroll_t, SMAP *));
33static int	vs_sm_erase __P((SCR *));
34static int	vs_sm_insert __P((SCR *, recno_t));
35static int	vs_sm_reset __P((SCR *, recno_t));
36static 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 */
44int
45vs_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 */
174int
175vs_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) {
225top:			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. */
236middle:		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. */
259bottom:		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	 */
274err:	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 */
301static int
302vs_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 */
351static int
352vs_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 */
407static int
408vs_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 */
514int
515vs_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 */
575static int
576vs_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_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 */
756int
757vs_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 */
786static int
787vs_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 */
813static int
814vs_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_sboff;
962	return (0);
963}
964
965/*
966 * vs_sm_erase --
967 *	Erase the small screen area for the scrolling functions.
968 */
969static int
970vs_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 */
991int
992vs_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 */
1021static int
1022vs_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 */
1051int
1052vs_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 */
1081int
1082vs_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 */
1108int
1109vs_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 */
1149int
1150vs_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)) {
1171sof:			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) {
1206eof:				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 */
1233recno_t
1234vs_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