GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/vi/build/../vi/vs_smap.c Lines: 0 471 0.0 %
Date: 2017-11-07 Branches: 0 526 0.0 %

Line Branch Exec Source
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
}