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