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[] = "$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
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(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 __P((SCR *, recno_t, pos_t));
175 */
176int
177vs_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) {
224top:			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. */
245middle:		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. */
268bottom:		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	 */
283err:	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 */
310static int
311vs_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 */
358static int
359vs_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 */
412static int
413vs_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 __P((SCR *, MARK *, recno_t, scroll_t));
516 */
517int
518vs_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 */
574static int
575vs_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 __P((SCR *));
749 */
750int
751vs_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 */
779static int
780vs_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 */
811static int
812vs_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 */
962static int
963vs_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 __P((SCR *));
982 */
983int
984vs_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 */
1012static int
1013vs_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 __P((SCR *, SMAP *, SMAP *));
1046 */
1047int
1048vs_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 __P((SCR *, SMAP *, SMAP *));
1074 */
1075int
1076vs_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 __P((SCR *, SMAP **));
1099 */
1100int
1101vs_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 __P((SCR *, MARK *, u_long, pos_t));
1138 */
1139int
1140vs_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)) {
1157sof:			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) {
1192eof:				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 __P((SCR *, SMAP *, recno_t, size_t));
1218 */
1219recno_t
1220vs_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