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