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