xref: /minix/external/bsd/nvi/dist/vi/vs_smap.c (revision e1cdaee1)
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
16 static 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 
35 static int	vs_deleteln __P((SCR *, int));
36 static int	vs_insertln __P((SCR *, int));
37 static int	vs_sm_delete __P((SCR *, db_recno_t));
38 static int	vs_sm_down __P((SCR *, MARK *, db_recno_t, scroll_t, SMAP *));
39 static int	vs_sm_erase __P((SCR *));
40 static int	vs_sm_insert __P((SCR *, db_recno_t));
41 static int	vs_sm_reset __P((SCR *, db_recno_t));
42 static 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  */
50 int
vs_change(SCR * sp,db_recno_t lno,lnop_t op)51 vs_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  */
182 int
vs_sm_fill(SCR * sp,db_recno_t lno,pos_t pos)183 vs_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) {
230 top:			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. */
251 middle:		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. */
274 bottom:		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 	 */
289 err:	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  */
316 static int
vs_sm_delete(SCR * sp,db_recno_t lno)317 vs_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  */
364 static int
vs_sm_insert(SCR * sp,db_recno_t lno)365 vs_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  */
418 static int
vs_sm_reset(SCR * sp,db_recno_t lno)419 vs_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  */
523 int
vs_sm_scroll(SCR * sp,MARK * rp,db_recno_t count,scroll_t scmd)524 vs_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  */
580 static int
vs_sm_up(SCR * sp,MARK * rp,db_recno_t count,scroll_t scmd,SMAP * smp)581 vs_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 #if defined(__minix)
586 	/* LSC: -Werror=maybe-uninitialized, with -O3 */
587 	ssmp = NULL;
588 #endif /* defined(__minix) */
589 
590 	/*
591 	 * Check to see if movement is possible.
592 	 *
593 	 * Get the line after the map.  If that line is a new one (and if
594 	 * O_LEFTRIGHT option is set, this has to be true), and the next
595 	 * line doesn't exist, and the cursor doesn't move, or the cursor
596 	 * isn't even on the screen, or the cursor is already at the last
597 	 * line in the map, it's an error.  If that test succeeded because
598 	 * the cursor wasn't at the end of the map, test to see if the map
599 	 * is mostly empty.
600 	 */
601 	if (vs_sm_next(sp, TMAP, &s1))
602 		return (1);
603 	if (s1.lno > TMAP->lno && !db_exist(sp, s1.lno)) {
604 		if (scmd == CNTRL_E || scmd == Z_PLUS || smp == TMAP) {
605 			v_eof(sp, NULL);
606 			return (1);
607 		}
608 		if (vs_sm_next(sp, smp, &s1))
609 			return (1);
610 		if (s1.lno > smp->lno && !db_exist(sp, s1.lno)) {
611 			v_eof(sp, NULL);
612 			return (1);
613 		}
614 	}
615 
616 	/*
617 	 * Small screens: see vs_refresh.c section 6a.
618 	 *
619 	 * If it's a small screen, and the movement isn't larger than a
620 	 * screen, i.e some context will remain, open up the screen and
621 	 * display by scrolling.  In this case, the cursor moves down one
622 	 * line for each line displayed.  Otherwise, erase/compress and
623 	 * repaint, and move the cursor to the first line in the screen.
624 	 * Note, the ^F command is always in the latter case, for historical
625 	 * reasons.
626 	 */
627 	cursor_set = 0;
628 	if (IS_SMALL(sp)) {
629 		if (count >= sp->t_maxrows || scmd == CNTRL_F) {
630 			s1 = TMAP[0];
631 			if (vs_sm_erase(sp))
632 				return (1);
633 			for (; count--; s1 = s2) {
634 				if (vs_sm_next(sp, &s1, &s2))
635 					return (1);
636 				if (s2.lno != s1.lno && !db_exist(sp, s2.lno))
637 					break;
638 			}
639 			TMAP[0] = s2;
640 			if (vs_sm_fill(sp, OOBLNO, P_BOTTOM))
641 				return (1);
642 			return (vs_sm_position(sp, rp, 0, P_TOP));
643 		}
644 		cursor_set = scmd == CNTRL_E || vs_sm_cursor(sp, &ssmp);
645 		for (; count &&
646 		    sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
647 			if (vs_sm_next(sp, TMAP, &s1))
648 				return (1);
649 			if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))
650 				break;
651 			*++TMAP = s1;
652 			/* vs_sm_next() flushed the cache. */
653 			if (vs_line(sp, TMAP, NULL, NULL))
654 				return (1);
655 
656 			if (!cursor_set)
657 				++ssmp;
658 		}
659 		if (!cursor_set) {
660 			rp->lno = ssmp->lno;
661 			rp->cno = ssmp->c_sboff;
662 		}
663 		if (count == 0)
664 			return (0);
665 	}
666 
667 	for (echanged = zset = 0; count; --count) {
668 		/* Decide what would show up on the screen. */
669 		if (vs_sm_next(sp, TMAP, &s1))
670 			return (1);
671 
672 		/* If the line doesn't exist, we're done. */
673 		if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))
674 			break;
675 
676 		/* Scroll the screen cursor up one logical line. */
677 		if (vs_sm_1up(sp))
678 			return (1);
679 		switch (scmd) {
680 		case CNTRL_E:
681 			if (smp > HMAP)
682 				--smp;
683 			else
684 				echanged = 1;
685 			break;
686 		case Z_PLUS:
687 			if (zset) {
688 				if (smp > HMAP)
689 					--smp;
690 			} else {
691 				smp = TMAP;
692 				zset = 1;
693 			}
694 			/* FALLTHROUGH */
695 		default:
696 			break;
697 		}
698 	}
699 
700 	if (cursor_set)
701 		return(0);
702 
703 	switch (scmd) {
704 	case CNTRL_E:
705 		/*
706 		 * On a ^E that was forced to change lines, try and keep the
707 		 * cursor as close as possible to the last position, but also
708 		 * set it up so that the next "real" movement will return the
709 		 * cursor to the closest position to the last real movement.
710 		 */
711 		if (echanged) {
712 			rp->lno = smp->lno;
713 			rp->cno = vs_colpos(sp, smp->lno,
714 			    (O_ISSET(sp, O_LEFTRIGHT) ?
715 			    smp->coff : (smp->soff - 1) * sp->cols) +
716 			    sp->rcm % sp->cols);
717 		}
718 		return (0);
719 	case CNTRL_F:
720 		/*
721 		 * If there are more lines, the ^F command is positioned at
722 		 * the first line of the screen.
723 		 */
724 		if (!count) {
725 			smp = HMAP;
726 			break;
727 		}
728 		/* FALLTHROUGH */
729 	case CNTRL_D:
730 		/*
731 		 * The ^D and ^F commands move the cursor towards EOF
732 		 * if there are more lines to move.  Check to be sure
733 		 * the lines actually exist.  (They may not if the
734 		 * file is smaller than the screen.)
735 		 */
736 		for (; count; --count, ++smp)
737 			if (smp == TMAP || !db_exist(sp, smp[1].lno))
738 				break;
739 		break;
740 	case Z_PLUS:
741 		 /* The z+ command moves the cursor to the first new line. */
742 		break;
743 	default:
744 		abort();
745 	}
746 
747 	if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
748 		return (1);
749 	rp->lno = smp->lno;
750 	rp->cno = smp->c_scoff == 255 ? 0 : smp->c_sboff;
751 	return (0);
752 }
753 
754 /*
755  * vs_sm_1up --
756  *	Scroll the SMAP up one.
757  *
758  * PUBLIC: int vs_sm_1up __P((SCR *));
759  */
760 int
vs_sm_1up(SCR * sp)761 vs_sm_1up(SCR *sp)
762 {
763 	/*
764 	 * Delete the top line of the screen.  Shift the screen map
765 	 * up and display a new line at the bottom of the screen.
766 	 */
767 	(void)sp->gp->scr_move(sp, 0, 0);
768 	if (vs_deleteln(sp, 1))
769 		return (1);
770 
771 	/* One-line screens can fail. */
772 	if (IS_ONELINE(sp)) {
773 		if (vs_sm_next(sp, TMAP, TMAP))
774 			return (1);
775 	} else {
776 		memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP));
777 		if (vs_sm_next(sp, TMAP - 1, TMAP))
778 			return (1);
779 	}
780 	/* vs_sm_next() flushed the cache. */
781 	return (vs_line(sp, TMAP, NULL, NULL));
782 }
783 
784 /*
785  * vs_deleteln --
786  *	Delete a line a la curses, make sure to put the information
787  *	line and other screens back.
788  */
789 static int
vs_deleteln(SCR * sp,int cnt)790 vs_deleteln(SCR *sp, int cnt)
791 {
792 	GS *gp;
793 	size_t oldy, oldx;
794 
795 	gp = sp->gp;
796 
797 	/* If the screen is vertically split, we can't scroll it. */
798 	if (IS_VSPLIT(sp)) {
799 		F_SET(sp, SC_SCR_REDRAW);
800 		return (0);
801 	}
802 
803 	if (IS_ONELINE(sp))
804 		(void)gp->scr_clrtoeol(sp);
805 	else {
806 		(void)gp->scr_cursor(sp, &oldy, &oldx);
807 		while (cnt--) {
808 			(void)gp->scr_deleteln(sp);
809 			(void)gp->scr_move(sp, LASTLINE(sp), 0);
810 			(void)gp->scr_insertln(sp);
811 			(void)gp->scr_move(sp, oldy, oldx);
812 		}
813 	}
814 	return (0);
815 }
816 
817 /*
818  * vs_sm_down --
819  *	Scroll the SMAP down count logical lines.
820  */
821 static int
vs_sm_down(SCR * sp,MARK * rp,db_recno_t count,scroll_t scmd,SMAP * smp)822 vs_sm_down(SCR *sp, MARK *rp, db_recno_t count, scroll_t scmd, SMAP *smp)
823 {
824 	SMAP *ssmp, s1, s2;
825 	int cursor_set, ychanged, zset;
826 
827 	/* Check to see if movement is possible. */
828 	if (HMAP->lno == 1 &&
829 	    (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) &&
830 	    (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) {
831 		v_sof(sp, NULL);
832 		return (1);
833 	}
834 
835 	/*
836 	 * Small screens: see vs_refresh.c section 6a.
837 	 *
838 	 * If it's a small screen, and the movement isn't larger than a
839 	 * screen, i.e some context will remain, open up the screen and
840 	 * display by scrolling.  In this case, the cursor moves up one
841 	 * line for each line displayed.  Otherwise, erase/compress and
842 	 * repaint, and move the cursor to the first line in the screen.
843 	 * Note, the ^B command is always in the latter case, for historical
844 	 * reasons.
845 	 */
846 	cursor_set = scmd == CNTRL_Y;
847 	if (IS_SMALL(sp)) {
848 		if (count >= sp->t_maxrows || scmd == CNTRL_B) {
849 			s1 = HMAP[0];
850 			if (vs_sm_erase(sp))
851 				return (1);
852 			for (; count--; s1 = s2) {
853 				if (vs_sm_prev(sp, &s1, &s2))
854 					return (1);
855 				if (s2.lno == 1 &&
856 				    (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1))
857 					break;
858 			}
859 			HMAP[0] = s2;
860 			if (vs_sm_fill(sp, OOBLNO, P_TOP))
861 				return (1);
862 			return (vs_sm_position(sp, rp, 0, P_BOTTOM));
863 		}
864 		cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp);
865 		for (; count &&
866 		    sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
867 			if (HMAP->lno == 1 &&
868 			    (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
869 				break;
870 			++TMAP;
871 			if (vs_sm_1down(sp))
872 				return (1);
873 		}
874 		if (!cursor_set) {
875 			rp->lno = ssmp->lno;
876 			rp->cno = ssmp->c_sboff;
877 		}
878 		if (count == 0)
879 			return (0);
880 	}
881 
882 	for (ychanged = zset = 0; count; --count) {
883 		/* If the line doesn't exist, we're done. */
884 		if (HMAP->lno == 1 &&
885 		    (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
886 			break;
887 
888 		/* Scroll the screen and cursor down one logical line. */
889 		if (vs_sm_1down(sp))
890 			return (1);
891 		switch (scmd) {
892 		case CNTRL_Y:
893 			if (smp < TMAP)
894 				++smp;
895 			else
896 				ychanged = 1;
897 			break;
898 		case Z_CARAT:
899 			if (zset) {
900 				if (smp < TMAP)
901 					++smp;
902 			} else {
903 				smp = HMAP;
904 				zset = 1;
905 			}
906 			/* FALLTHROUGH */
907 		default:
908 			break;
909 		}
910 	}
911 
912 	if (scmd != CNTRL_Y && cursor_set)
913 		return(0);
914 
915 	switch (scmd) {
916 	case CNTRL_B:
917 		/*
918 		 * If there are more lines, the ^B command is positioned at
919 		 * the last line of the screen.  However, the line may not
920 		 * exist.
921 		 */
922 		if (!count) {
923 			for (smp = TMAP; smp > HMAP; --smp)
924 				if (db_exist(sp, smp->lno))
925 					break;
926 			break;
927 		}
928 		/* FALLTHROUGH */
929 	case CNTRL_U:
930 		/*
931 		 * The ^B and ^U commands move the cursor towards SOF
932 		 * if there are more lines to move.
933 		 */
934 		if (count < (db_recno_t)(smp - HMAP))
935 			smp -= count;
936 		else
937 			smp = HMAP;
938 		break;
939 	case CNTRL_Y:
940 		/*
941 		 * On a ^Y that was forced to change lines, try and keep the
942 		 * cursor as close as possible to the last position, but also
943 		 * set it up so that the next "real" movement will return the
944 		 * cursor to the closest position to the last real movement.
945 		 */
946 		if (ychanged) {
947 			rp->lno = smp->lno;
948 			rp->cno = vs_colpos(sp, smp->lno,
949 			    (O_ISSET(sp, O_LEFTRIGHT) ?
950 			    smp->coff : (smp->soff - 1) * sp->cols) +
951 			    sp->rcm % sp->cols);
952 		}
953 		return (0);
954 	case Z_CARAT:
955 		 /* The z^ command moves the cursor to the first new line. */
956 		break;
957 	default:
958 		abort();
959 	}
960 
961 	if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
962 		return (1);
963 	rp->lno = smp->lno;
964 	rp->cno = smp->c_scoff == 255 ? 0 : smp->c_sboff;
965 	return (0);
966 }
967 
968 /*
969  * vs_sm_erase --
970  *	Erase the small screen area for the scrolling functions.
971  */
972 static int
vs_sm_erase(SCR * sp)973 vs_sm_erase(SCR *sp)
974 {
975 	GS *gp;
976 
977 	gp = sp->gp;
978 	(void)gp->scr_move(sp, LASTLINE(sp), 0);
979 	(void)gp->scr_clrtoeol(sp);
980 	for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) {
981 		(void)gp->scr_move(sp, TMAP - HMAP, 0);
982 		(void)gp->scr_clrtoeol(sp);
983 	}
984 	return (0);
985 }
986 
987 /*
988  * vs_sm_1down --
989  *	Scroll the SMAP down one.
990  *
991  * PUBLIC: int vs_sm_1down __P((SCR *));
992  */
993 int
vs_sm_1down(SCR * sp)994 vs_sm_1down(SCR *sp)
995 {
996 	/*
997 	 * Insert a line at the top of the screen.  Shift the screen map
998 	 * down and display a new line at the top of the screen.
999 	 */
1000 	(void)sp->gp->scr_move(sp, 0, 0);
1001 	if (vs_insertln(sp, 1))
1002 		return (1);
1003 
1004 	/* One-line screens can fail. */
1005 	if (IS_ONELINE(sp)) {
1006 		if (vs_sm_prev(sp, HMAP, HMAP))
1007 			return (1);
1008 	} else {
1009 		memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP));
1010 		if (vs_sm_prev(sp, HMAP + 1, HMAP))
1011 			return (1);
1012 	}
1013 	/* vs_sm_prev() flushed the cache. */
1014 	return (vs_line(sp, HMAP, NULL, NULL));
1015 }
1016 
1017 /*
1018  * vs_insertln --
1019  *	Insert a line a la curses, make sure to put the information
1020  *	line and other screens back.
1021  */
1022 static int
vs_insertln(SCR * sp,int cnt)1023 vs_insertln(SCR *sp, int cnt)
1024 {
1025 	GS *gp;
1026 	size_t oldy, oldx;
1027 
1028 	gp = sp->gp;
1029 
1030 	/* If the screen is vertically split, we can't scroll it. */
1031 	if (IS_VSPLIT(sp)) {
1032 		F_SET(sp, SC_SCR_REDRAW);
1033 		return (0);
1034 	}
1035 
1036 	if (IS_ONELINE(sp)) {
1037 		(void)gp->scr_move(sp, LASTLINE(sp), 0);
1038 		(void)gp->scr_clrtoeol(sp);
1039 	} else {
1040 		(void)gp->scr_cursor(sp, &oldy, &oldx);
1041 		while (cnt--) {
1042 			(void)gp->scr_move(sp, LASTLINE(sp) - 1, 0);
1043 			(void)gp->scr_deleteln(sp);
1044 			(void)gp->scr_move(sp, oldy, oldx);
1045 			(void)gp->scr_insertln(sp);
1046 		}
1047 	}
1048 	return (0);
1049 }
1050 
1051 /*
1052  * vs_sm_next --
1053  *	Fill in the next entry in the SMAP.
1054  *
1055  * PUBLIC: int vs_sm_next __P((SCR *, SMAP *, SMAP *));
1056  */
1057 int
vs_sm_next(SCR * sp,SMAP * p,SMAP * t)1058 vs_sm_next(SCR *sp, SMAP *p, SMAP *t)
1059 {
1060 	size_t lcnt;
1061 
1062 	SMAP_FLUSH(t);
1063 	if (O_ISSET(sp, O_LEFTRIGHT)) {
1064 		t->lno = p->lno + 1;
1065 		t->coff = p->coff;
1066 	} else {
1067 		lcnt = vs_screens(sp, p->lno, NULL);
1068 		if (lcnt == p->soff) {
1069 			t->lno = p->lno + 1;
1070 			t->soff = 1;
1071 		} else {
1072 			t->lno = p->lno;
1073 			t->soff = p->soff + 1;
1074 		}
1075 	}
1076 	return (0);
1077 }
1078 
1079 /*
1080  * vs_sm_prev --
1081  *	Fill in the previous entry in the SMAP.
1082  *
1083  * PUBLIC: int vs_sm_prev __P((SCR *, SMAP *, SMAP *));
1084  */
1085 int
vs_sm_prev(SCR * sp,SMAP * p,SMAP * t)1086 vs_sm_prev(SCR *sp, SMAP *p, SMAP *t)
1087 {
1088 	SMAP_FLUSH(t);
1089 	if (O_ISSET(sp, O_LEFTRIGHT)) {
1090 		t->lno = p->lno - 1;
1091 		t->coff = p->coff;
1092 	} else {
1093 		if (p->soff != 1) {
1094 			t->lno = p->lno;
1095 			t->soff = p->soff - 1;
1096 		} else {
1097 			t->lno = p->lno - 1;
1098 			t->soff = vs_screens(sp, t->lno, NULL);
1099 		}
1100 	}
1101 	return (t->lno == 0);
1102 }
1103 
1104 /*
1105  * vs_sm_cursor --
1106  *	Return the SMAP entry referenced by the cursor.
1107  *
1108  * PUBLIC: int vs_sm_cursor __P((SCR *, SMAP **));
1109  */
1110 int
vs_sm_cursor(SCR * sp,SMAP ** smpp)1111 vs_sm_cursor(SCR *sp, 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  */
1149 int
vs_sm_position(SCR * sp,MARK * rp,u_long cnt,pos_t pos)1150 vs_sm_position(SCR *sp, MARK *rp, u_long cnt, pos_t pos)
1151 {
1152 	SMAP *smp;
1153 	db_recno_t last;
1154 
1155 	switch (pos) {
1156 	case P_TOP:
1157 		/*
1158 		 * !!!
1159 		 * Historically, an invalid count to the H command failed.
1160 		 * We do nothing special here, just making sure that H in
1161 		 * an empty screen works.
1162 		 */
1163 		if (cnt > (u_long)(TMAP - HMAP))
1164 			goto sof;
1165 		smp = HMAP + cnt;
1166 		if (cnt && !db_exist(sp, smp->lno)) {
1167 sof:			msgq(sp, M_BERR, "220|Movement past the end-of-screen");
1168 			return (1);
1169 		}
1170 		break;
1171 	case P_MIDDLE:
1172 		/*
1173 		 * !!!
1174 		 * Historically, a count to the M command was ignored.
1175 		 * If the screen isn't filled, find the middle of what's
1176 		 * real and move there.
1177 		 */
1178 		if (!db_exist(sp, TMAP->lno)) {
1179 			if (db_last(sp, &last))
1180 				return (1);
1181 			for (smp = TMAP; smp->lno > last && smp > HMAP; --smp);
1182 			if (smp > HMAP)
1183 				smp -= (smp - HMAP) / 2;
1184 		} else
1185 			smp = (HMAP + (TMAP - HMAP) / 2) + cnt;
1186 		break;
1187 	case P_BOTTOM:
1188 		/*
1189 		 * !!!
1190 		 * Historically, an invalid count to the L command failed.
1191 		 * If the screen isn't filled, find the bottom of what's
1192 		 * real and try to offset from there.
1193 		 */
1194 		if (cnt > (u_long)(TMAP - HMAP))
1195 			goto eof;
1196 		smp = TMAP - cnt;
1197 		if (!db_exist(sp, smp->lno)) {
1198 			if (db_last(sp, &last))
1199 				return (1);
1200 			for (; smp->lno > last && smp > HMAP; --smp);
1201 			if (cnt > (u_long)(smp - HMAP)) {
1202 eof:				msgq(sp, M_BERR,
1203 			    "221|Movement past the beginning-of-screen");
1204 				return (1);
1205 			}
1206 			smp -= cnt;
1207 		}
1208 		break;
1209 	default:
1210 		abort();
1211 	}
1212 
1213 	/* Make sure that the cached information is valid. */
1214 	if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
1215 		return (1);
1216 	rp->lno = smp->lno;
1217 	rp->cno = smp->c_sboff;
1218 
1219 	return (0);
1220 }
1221 
1222 /*
1223  * vs_sm_nlines --
1224  *	Return the number of screen lines from an SMAP entry to the
1225  *	start of some file line, less than a maximum value.
1226  *
1227  * PUBLIC: db_recno_t vs_sm_nlines __P((SCR *, SMAP *, db_recno_t, size_t));
1228  */
1229 db_recno_t
vs_sm_nlines(SCR * sp,SMAP * from_sp,db_recno_t to_lno,size_t max)1230 vs_sm_nlines(SCR *sp, SMAP *from_sp, db_recno_t to_lno, size_t max)
1231 {
1232 	db_recno_t lno, lcnt;
1233 
1234 	if (O_ISSET(sp, O_LEFTRIGHT))
1235 		return (from_sp->lno > to_lno ?
1236 		    from_sp->lno - to_lno : to_lno - from_sp->lno);
1237 
1238 	if (from_sp->lno == to_lno)
1239 		return (from_sp->soff - 1);
1240 
1241 	if (from_sp->lno > to_lno) {
1242 		lcnt = from_sp->soff - 1;	/* Correct for off-by-one. */
1243 		for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;)
1244 			lcnt += vs_screens(sp, lno, NULL);
1245 	} else {
1246 		lno = from_sp->lno;
1247 		lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1;
1248 		for (; ++lno < to_lno && lcnt <= max;)
1249 			lcnt += vs_screens(sp, lno, NULL);
1250 	}
1251 	return (lcnt);
1252 }
1253