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