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