MFC r1.27:
[dragonfly.git] / contrib / nvi / vi / vs_smap.c
blobaf38057e815f089498a6d706e41557a93820da3c
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.
7 * See the LICENSE file for redistribution information.
8 */
10 #include "config.h"
12 #ifndef lint
13 static const char sccsid[] = "@(#)vs_smap.c 10.25 (Berkeley) 7/12/96";
14 #endif /* not lint */
16 #include <sys/types.h>
17 #include <sys/queue.h>
18 #include <sys/time.h>
20 #include <bitstring.h>
21 #include <limits.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
26 #include "../common/common.h"
27 #include "vi.h"
29 static int vs_deleteln __P((SCR *, int));
30 static int vs_insertln __P((SCR *, int));
31 static int vs_sm_delete __P((SCR *, recno_t));
32 static int vs_sm_down __P((SCR *, MARK *, recno_t, scroll_t, SMAP *));
33 static int vs_sm_erase __P((SCR *));
34 static int vs_sm_insert __P((SCR *, recno_t));
35 static int vs_sm_reset __P((SCR *, recno_t));
36 static int vs_sm_up __P((SCR *, MARK *, recno_t, scroll_t, SMAP *));
39 * vs_change --
40 * Make a change to the screen.
42 * PUBLIC: int vs_change __P((SCR *, recno_t, lnop_t));
44 int
45 vs_change(sp, lno, op)
46 SCR *sp;
47 recno_t lno;
48 lnop_t op;
50 VI_PRIVATE *vip;
51 SMAP *p;
52 size_t cnt, oldy, oldx;
54 vip = VIP(sp);
57 * XXX
58 * Very nasty special case. The historic vi code displays a single
59 * space (or a '$' if the list option is set) for the first line in
60 * an "empty" file. If we "insert" a line, that line gets scrolled
61 * down, not repainted, so it's incorrect when we refresh the screen.
62 * The vi text input functions detect it explicitly and don't insert
63 * a new line.
65 * Check for line #2 before going to the end of the file.
67 if ((op == LINE_APPEND && lno == 0 || op == LINE_INSERT && lno == 1) &&
68 !db_exist(sp, 2)) {
69 lno = 1;
70 op = LINE_RESET;
73 /* Appending is the same as inserting, if the line is incremented. */
74 if (op == LINE_APPEND) {
75 ++lno;
76 op = LINE_INSERT;
79 /* Ignore the change if the line is after the map. */
80 if (lno > TMAP->lno)
81 return (0);
84 * If the line is before the map, and it's a decrement, decrement
85 * the map. If it's an increment, increment the map. Otherwise,
86 * ignore it.
88 if (lno < HMAP->lno) {
89 switch (op) {
90 case LINE_APPEND:
91 abort();
92 /* NOTREACHED */
93 case LINE_DELETE:
94 for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
95 --p->lno;
96 if (sp->lno >= lno)
97 --sp->lno;
98 F_SET(vip, VIP_N_RENUMBER);
99 break;
100 case LINE_INSERT:
101 for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
102 ++p->lno;
103 if (sp->lno >= lno)
104 ++sp->lno;
105 F_SET(vip, VIP_N_RENUMBER);
106 break;
107 case LINE_RESET:
108 break;
110 return (0);
113 F_SET(vip, VIP_N_REFRESH);
116 * Invalidate the line size cache, and invalidate the cursor if it's
117 * on this line,
119 VI_SCR_CFLUSH(vip);
120 if (sp->lno == lno)
121 F_SET(vip, VIP_CUR_INVALID);
124 * If ex modifies the screen after ex output is already on the screen
125 * or if we've switched into ex canonical mode, don't touch it -- we'll
126 * get scrolling wrong, at best.
128 if (!F_ISSET(sp, SC_TINPUT_INFO) &&
129 (F_ISSET(sp, SC_SCR_EXWROTE) || VIP(sp)->totalcount > 1)) {
130 F_SET(vip, VIP_N_EX_REDRAW);
131 return (0);
134 /* Save and restore the cursor for these routines. */
135 (void)sp->gp->scr_cursor(sp, &oldy, &oldx);
137 switch (op) {
138 case LINE_DELETE:
139 if (vs_sm_delete(sp, lno))
140 return (1);
141 F_SET(vip, VIP_N_RENUMBER);
142 break;
143 case LINE_INSERT:
144 if (vs_sm_insert(sp, lno))
145 return (1);
146 F_SET(vip, VIP_N_RENUMBER);
147 break;
148 case LINE_RESET:
149 if (vs_sm_reset(sp, lno))
150 return (1);
151 break;
152 default:
153 abort();
156 (void)sp->gp->scr_move(sp, oldy, oldx);
157 return (0);
161 * vs_sm_fill --
162 * Fill in the screen map, placing the specified line at the
163 * right position. There isn't any way to tell if an SMAP
164 * entry has been filled in, so this routine had better be
165 * called with P_FILL set before anything else is done.
167 * !!!
168 * Unexported interface: if lno is OOBLNO, P_TOP means that the HMAP
169 * slot is already filled in, P_BOTTOM means that the TMAP slot is
170 * already filled in, and we just finish up the job.
172 * PUBLIC: int vs_sm_fill __P((SCR *, recno_t, pos_t));
175 vs_sm_fill(sp, lno, pos)
176 SCR *sp;
177 recno_t lno;
178 pos_t pos;
180 SMAP *p, tmp;
181 size_t cnt;
183 /* Flush all cached information from the SMAP. */
184 for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
185 SMAP_FLUSH(p);
188 * If the map is filled, the screen must be redrawn.
190 * XXX
191 * This is a bug. We should try and figure out if the desired line
192 * is already in the map or close by -- scrolling the screen would
193 * be a lot better than redrawing.
195 F_SET(sp, SC_SCR_REDRAW);
197 switch (pos) {
198 case P_FILL:
199 tmp.lno = 1;
200 tmp.coff = 0;
201 tmp.soff = 1;
203 /* See if less than half a screen from the top. */
204 if (vs_sm_nlines(sp,
205 &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {
206 lno = 1;
207 goto top;
210 /* See if less than half a screen from the bottom. */
211 if (db_last(sp, &tmp.lno))
212 return (1);
213 tmp.coff = 0;
214 tmp.soff = vs_screens(sp, tmp.lno, NULL);
215 if (vs_sm_nlines(sp,
216 &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {
217 TMAP->lno = tmp.lno;
218 TMAP->coff = tmp.coff;
219 TMAP->soff = tmp.soff;
220 goto bottom;
222 goto middle;
223 case P_TOP:
224 if (lno != OOBLNO) {
225 top: HMAP->lno = lno;
226 HMAP->coff = 0;
227 HMAP->soff = 1;
229 /* If we fail, just punt. */
230 for (p = HMAP, cnt = sp->t_rows; --cnt; ++p)
231 if (vs_sm_next(sp, p, p + 1))
232 goto err;
233 break;
234 case P_MIDDLE:
235 /* If we fail, guess that the file is too small. */
236 middle: p = HMAP + sp->t_rows / 2;
237 p->lno = lno;
238 p->coff = 0;
239 p->soff = 1;
240 for (; p > HMAP; --p)
241 if (vs_sm_prev(sp, p, p - 1)) {
242 lno = 1;
243 goto top;
246 /* If we fail, just punt. */
247 p = HMAP + sp->t_rows / 2;
248 for (; p < TMAP; ++p)
249 if (vs_sm_next(sp, p, p + 1))
250 goto err;
251 break;
252 case P_BOTTOM:
253 if (lno != OOBLNO) {
254 TMAP->lno = lno;
255 TMAP->coff = 0;
256 TMAP->soff = vs_screens(sp, lno, NULL);
258 /* If we fail, guess that the file is too small. */
259 bottom: for (p = TMAP; p > HMAP; --p)
260 if (vs_sm_prev(sp, p, p - 1)) {
261 lno = 1;
262 goto top;
264 break;
265 default:
266 abort();
268 return (0);
271 * Try and put *something* on the screen. If this fails, we have a
272 * serious hard error.
274 err: HMAP->lno = 1;
275 HMAP->coff = 0;
276 HMAP->soff = 1;
277 for (p = HMAP; p < TMAP; ++p)
278 if (vs_sm_next(sp, p, p + 1))
279 return (1);
280 return (0);
284 * For the routines vs_sm_reset, vs_sm_delete and vs_sm_insert: if the
285 * screen contains only a single line (whether because the screen is small
286 * or the line large), it gets fairly exciting. Skip the fun, set a flag
287 * so the screen map is refilled and the screen redrawn, and return. This
288 * is amazingly slow, but it's not clear that anyone will care.
290 #define HANDLE_WEIRDNESS(cnt) { \
291 if (cnt >= sp->t_rows) { \
292 F_SET(sp, SC_SCR_REFORMAT); \
293 return (0); \
298 * vs_sm_delete --
299 * Delete a line out of the SMAP.
301 static int
302 vs_sm_delete(sp, lno)
303 SCR *sp;
304 recno_t lno;
306 SMAP *p, *t;
307 size_t cnt_orig;
310 * Find the line in the map, and count the number of screen lines
311 * which display any part of the deleted line.
313 for (p = HMAP; p->lno != lno; ++p);
314 if (O_ISSET(sp, O_LEFTRIGHT))
315 cnt_orig = 1;
316 else
317 for (cnt_orig = 1, t = p + 1;
318 t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
320 HANDLE_WEIRDNESS(cnt_orig);
322 /* Delete that many lines from the screen. */
323 (void)sp->gp->scr_move(sp, p - HMAP, 0);
324 if (vs_deleteln(sp, cnt_orig))
325 return (1);
327 /* Shift the screen map up. */
328 memmove(p, p + cnt_orig, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
330 /* Decrement the line numbers for the rest of the map. */
331 for (t = TMAP - cnt_orig; p <= t; ++p)
332 --p->lno;
334 /* Display the new lines. */
335 for (p = TMAP - cnt_orig;;) {
336 if (p < TMAP && vs_sm_next(sp, p, p + 1))
337 return (1);
338 /* vs_sm_next() flushed the cache. */
339 if (vs_line(sp, ++p, NULL, NULL))
340 return (1);
341 if (p == TMAP)
342 break;
344 return (0);
348 * vs_sm_insert --
349 * Insert a line into the SMAP.
351 static int
352 vs_sm_insert(sp, lno)
353 SCR *sp;
354 recno_t lno;
356 SMAP *p, *t;
357 size_t cnt_orig, cnt, coff;
359 /* Save the offset. */
360 coff = HMAP->coff;
363 * Find the line in the map, find out how many screen lines
364 * needed to display the line.
366 for (p = HMAP; p->lno != lno; ++p);
368 cnt_orig = vs_screens(sp, lno, NULL);
369 HANDLE_WEIRDNESS(cnt_orig);
372 * The lines left in the screen override the number of screen
373 * lines in the inserted line.
375 cnt = (TMAP - p) + 1;
376 if (cnt_orig > cnt)
377 cnt_orig = cnt;
379 /* Push down that many lines. */
380 (void)sp->gp->scr_move(sp, p - HMAP, 0);
381 if (vs_insertln(sp, cnt_orig))
382 return (1);
384 /* Shift the screen map down. */
385 memmove(p + cnt_orig, p, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
387 /* Increment the line numbers for the rest of the map. */
388 for (t = p + cnt_orig; t <= TMAP; ++t)
389 ++t->lno;
391 /* Fill in the SMAP for the new lines, and display. */
392 for (cnt = 1, t = p; cnt <= cnt_orig; ++t, ++cnt) {
393 t->lno = lno;
394 t->coff = coff;
395 t->soff = cnt;
396 SMAP_FLUSH(t);
397 if (vs_line(sp, t, NULL, NULL))
398 return (1);
400 return (0);
404 * vs_sm_reset --
405 * Reset a line in the SMAP.
407 static int
408 vs_sm_reset(sp, lno)
409 SCR *sp;
410 recno_t lno;
412 SMAP *p, *t;
413 size_t cnt_orig, cnt_new, cnt, diff;
416 * See if the number of on-screen rows taken up by the old display
417 * for the line is the same as the number needed for the new one.
418 * If so, repaint, otherwise do it the hard way.
420 for (p = HMAP; p->lno != lno; ++p);
421 if (O_ISSET(sp, O_LEFTRIGHT)) {
422 t = p;
423 cnt_orig = cnt_new = 1;
424 } else {
425 for (cnt_orig = 0,
426 t = p; t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
427 cnt_new = vs_screens(sp, lno, NULL);
430 HANDLE_WEIRDNESS(cnt_orig);
432 if (cnt_orig == cnt_new) {
433 do {
434 SMAP_FLUSH(p);
435 if (vs_line(sp, p, NULL, NULL))
436 return (1);
437 } while (++p < t);
438 return (0);
441 if (cnt_orig < cnt_new) {
442 /* Get the difference. */
443 diff = cnt_new - cnt_orig;
446 * The lines left in the screen override the number of screen
447 * lines in the inserted line.
449 cnt = (TMAP - p) + 1;
450 if (diff > cnt)
451 diff = cnt;
453 /* If there are any following lines, push them down. */
454 if (cnt > 1) {
455 (void)sp->gp->scr_move(sp, p - HMAP, 0);
456 if (vs_insertln(sp, diff))
457 return (1);
459 /* Shift the screen map down. */
460 memmove(p + diff, p,
461 (((TMAP - p) - diff) + 1) * sizeof(SMAP));
464 /* Fill in the SMAP for the replaced line, and display. */
465 for (cnt = 1, t = p; cnt_new-- && t <= TMAP; ++t, ++cnt) {
466 t->lno = lno;
467 t->soff = cnt;
468 SMAP_FLUSH(t);
469 if (vs_line(sp, t, NULL, NULL))
470 return (1);
472 } else {
473 /* Get the difference. */
474 diff = cnt_orig - cnt_new;
476 /* Delete that many lines from the screen. */
477 (void)sp->gp->scr_move(sp, p - HMAP, 0);
478 if (vs_deleteln(sp, diff))
479 return (1);
481 /* Shift the screen map up. */
482 memmove(p, p + diff, (((TMAP - p) - diff) + 1) * sizeof(SMAP));
484 /* Fill in the SMAP for the replaced line, and display. */
485 for (cnt = 1, t = p; cnt_new--; ++t, ++cnt) {
486 t->lno = lno;
487 t->soff = cnt;
488 SMAP_FLUSH(t);
489 if (vs_line(sp, t, NULL, NULL))
490 return (1);
493 /* Display the new lines at the bottom of the screen. */
494 for (t = TMAP - diff;;) {
495 if (t < TMAP && vs_sm_next(sp, t, t + 1))
496 return (1);
497 /* vs_sm_next() flushed the cache. */
498 if (vs_line(sp, ++t, NULL, NULL))
499 return (1);
500 if (t == TMAP)
501 break;
504 return (0);
508 * vs_sm_scroll
509 * Scroll the SMAP up/down count logical lines. Different
510 * semantics based on the vi command, *sigh*.
512 * PUBLIC: int vs_sm_scroll __P((SCR *, MARK *, recno_t, scroll_t));
515 vs_sm_scroll(sp, rp, count, scmd)
516 SCR *sp;
517 MARK *rp;
518 recno_t count;
519 scroll_t scmd;
521 SMAP *smp;
524 * Invalidate the cursor. The line is probably going to change,
525 * (although for ^E and ^Y it may not). In any case, the scroll
526 * routines move the cursor to draw things.
528 F_SET(VIP(sp), VIP_CUR_INVALID);
530 /* Find the cursor in the screen. */
531 if (vs_sm_cursor(sp, &smp))
532 return (1);
534 switch (scmd) {
535 case CNTRL_B:
536 case CNTRL_U:
537 case CNTRL_Y:
538 case Z_CARAT:
539 if (vs_sm_down(sp, rp, count, scmd, smp))
540 return (1);
541 break;
542 case CNTRL_D:
543 case CNTRL_E:
544 case CNTRL_F:
545 case Z_PLUS:
546 if (vs_sm_up(sp, rp, count, scmd, smp))
547 return (1);
548 break;
549 default:
550 abort();
554 * !!!
555 * If we're at the start of a line, go for the first non-blank.
556 * This makes it look like the old vi, even though we're moving
557 * around by logical lines, not physical ones.
559 * XXX
560 * In the presence of a long line, which has more than a screen
561 * width of leading spaces, this code can cause a cursor warp.
562 * Live with it.
564 if (scmd != CNTRL_E && scmd != CNTRL_Y &&
565 rp->cno == 0 && nonblank(sp, rp->lno, &rp->cno))
566 return (1);
568 return (0);
572 * vs_sm_up --
573 * Scroll the SMAP up count logical lines.
575 static int
576 vs_sm_up(sp, rp, count, scmd, smp)
577 SCR *sp;
578 MARK *rp;
579 scroll_t scmd;
580 recno_t count;
581 SMAP *smp;
583 int cursor_set, echanged, zset;
584 SMAP *ssmp, s1, s2;
587 * Check to see if movement is possible.
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.
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);
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);
613 * Small screens: see vs_refresh.c section 6a.
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.
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;
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));
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);
652 if (!cursor_set)
653 ++ssmp;
655 if (!cursor_set) {
656 rp->lno = ssmp->lno;
657 rp->cno = ssmp->c_sboff;
659 if (count == 0)
660 return (0);
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);
668 /* If the line doesn't exist, we're done. */
669 if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))
670 break;
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;
690 /* FALLTHROUGH */
691 default:
692 break;
696 if (cursor_set)
697 return(0);
699 switch (scmd) {
700 case CNTRL_E:
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.
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);
714 return (0);
715 case CNTRL_F:
717 * If there are more lines, the ^F command is positioned at
718 * the first line of the screen.
720 if (!count) {
721 smp = HMAP;
722 break;
724 /* FALLTHROUGH */
725 case CNTRL_D:
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.)
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();
743 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
744 return (1);
745 rp->lno = smp->lno;
746 rp->cno = smp->c_sboff;
747 return (0);
751 * vs_sm_1up --
752 * Scroll the SMAP up one.
754 * PUBLIC: int vs_sm_1up __P((SCR *));
757 vs_sm_1up(sp)
758 SCR *sp;
761 * Delete the top line of the screen. Shift the screen map
762 * up and display a new line at the bottom of the screen.
764 (void)sp->gp->scr_move(sp, 0, 0);
765 if (vs_deleteln(sp, 1))
766 return (1);
768 /* One-line screens can fail. */
769 if (IS_ONELINE(sp)) {
770 if (vs_sm_next(sp, TMAP, TMAP))
771 return (1);
772 } else {
773 memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP));
774 if (vs_sm_next(sp, TMAP - 1, TMAP))
775 return (1);
777 /* vs_sm_next() flushed the cache. */
778 return (vs_line(sp, TMAP, NULL, NULL));
782 * vs_deleteln --
783 * Delete a line a la curses, make sure to put the information
784 * line and other screens back.
786 static int
787 vs_deleteln(sp, cnt)
788 SCR *sp;
789 int cnt;
791 GS *gp;
792 size_t oldy, oldx;
794 gp = sp->gp;
795 if (IS_ONELINE(sp))
796 (void)gp->scr_clrtoeol(sp);
797 else {
798 (void)gp->scr_cursor(sp, &oldy, &oldx);
799 while (cnt--) {
800 (void)gp->scr_deleteln(sp);
801 (void)gp->scr_move(sp, LASTLINE(sp), 0);
802 (void)gp->scr_insertln(sp);
803 (void)gp->scr_move(sp, oldy, oldx);
806 return (0);
810 * vs_sm_down --
811 * Scroll the SMAP down count logical lines.
813 static int
814 vs_sm_down(sp, rp, count, scmd, smp)
815 SCR *sp;
816 MARK *rp;
817 recno_t count;
818 SMAP *smp;
819 scroll_t scmd;
821 SMAP *ssmp, s1, s2;
822 int cursor_set, ychanged, zset;
824 /* Check to see if movement is possible. */
825 if (HMAP->lno == 1 &&
826 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) &&
827 (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) {
828 v_sof(sp, NULL);
829 return (1);
833 * Small screens: see vs_refresh.c section 6a.
835 * If it's a small screen, and the movement isn't larger than a
836 * screen, i.e some context will remain, open up the screen and
837 * display by scrolling. In this case, the cursor moves up one
838 * line for each line displayed. Otherwise, erase/compress and
839 * repaint, and move the cursor to the first line in the screen.
840 * Note, the ^B command is always in the latter case, for historical
841 * reasons.
843 cursor_set = scmd == CNTRL_Y;
844 if (IS_SMALL(sp)) {
845 if (count >= sp->t_maxrows || scmd == CNTRL_B) {
846 s1 = HMAP[0];
847 if (vs_sm_erase(sp))
848 return (1);
849 for (; count--; s1 = s2) {
850 if (vs_sm_prev(sp, &s1, &s2))
851 return (1);
852 if (s2.lno == 1 &&
853 (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1))
854 break;
856 HMAP[0] = s2;
857 if (vs_sm_fill(sp, OOBLNO, P_TOP))
858 return (1);
859 return (vs_sm_position(sp, rp, 0, P_BOTTOM));
861 cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp);
862 for (; count &&
863 sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
864 if (HMAP->lno == 1 &&
865 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
866 break;
867 ++TMAP;
868 if (vs_sm_1down(sp))
869 return (1);
871 if (!cursor_set) {
872 rp->lno = ssmp->lno;
873 rp->cno = ssmp->c_sboff;
875 if (count == 0)
876 return (0);
879 for (ychanged = zset = 0; count; --count) {
880 /* If the line doesn't exist, we're done. */
881 if (HMAP->lno == 1 &&
882 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
883 break;
885 /* Scroll the screen and cursor down one logical line. */
886 if (vs_sm_1down(sp))
887 return (1);
888 switch (scmd) {
889 case CNTRL_Y:
890 if (smp < TMAP)
891 ++smp;
892 else
893 ychanged = 1;
894 break;
895 case Z_CARAT:
896 if (zset) {
897 if (smp < TMAP)
898 ++smp;
899 } else {
900 smp = HMAP;
901 zset = 1;
903 /* FALLTHROUGH */
904 default:
905 break;
909 if (scmd != CNTRL_Y && cursor_set)
910 return(0);
912 switch (scmd) {
913 case CNTRL_B:
915 * If there are more lines, the ^B command is positioned at
916 * the last line of the screen. However, the line may not
917 * exist.
919 if (!count) {
920 for (smp = TMAP; smp > HMAP; --smp)
921 if (db_exist(sp, smp->lno))
922 break;
923 break;
925 /* FALLTHROUGH */
926 case CNTRL_U:
928 * The ^B and ^U commands move the cursor towards SOF
929 * if there are more lines to move.
931 if (count < smp - HMAP)
932 smp -= count;
933 else
934 smp = HMAP;
935 break;
936 case CNTRL_Y:
938 * On a ^Y that was forced to change lines, try and keep the
939 * cursor as close as possible to the last position, but also
940 * set it up so that the next "real" movement will return the
941 * cursor to the closest position to the last real movement.
943 if (ychanged) {
944 rp->lno = smp->lno;
945 rp->cno = vs_colpos(sp, smp->lno,
946 (O_ISSET(sp, O_LEFTRIGHT) ?
947 smp->coff : (smp->soff - 1) * sp->cols) +
948 sp->rcm % sp->cols);
950 return (0);
951 case Z_CARAT:
952 /* The z^ command moves the cursor to the first new line. */
953 break;
954 default:
955 abort();
958 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
959 return (1);
960 rp->lno = smp->lno;
961 rp->cno = smp->c_sboff;
962 return (0);
966 * vs_sm_erase --
967 * Erase the small screen area for the scrolling functions.
969 static int
970 vs_sm_erase(sp)
971 SCR *sp;
973 GS *gp;
975 gp = sp->gp;
976 (void)gp->scr_move(sp, LASTLINE(sp), 0);
977 (void)gp->scr_clrtoeol(sp);
978 for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) {
979 (void)gp->scr_move(sp, TMAP - HMAP, 0);
980 (void)gp->scr_clrtoeol(sp);
982 return (0);
986 * vs_sm_1down --
987 * Scroll the SMAP down one.
989 * PUBLIC: int vs_sm_1down __P((SCR *));
992 vs_sm_1down(sp)
993 SCR *sp;
996 * Insert a line at the top of the screen. Shift the screen map
997 * down and display a new line at the top of the screen.
999 (void)sp->gp->scr_move(sp, 0, 0);
1000 if (vs_insertln(sp, 1))
1001 return (1);
1003 /* One-line screens can fail. */
1004 if (IS_ONELINE(sp)) {
1005 if (vs_sm_prev(sp, HMAP, HMAP))
1006 return (1);
1007 } else {
1008 memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP));
1009 if (vs_sm_prev(sp, HMAP + 1, HMAP))
1010 return (1);
1012 /* vs_sm_prev() flushed the cache. */
1013 return (vs_line(sp, HMAP, NULL, NULL));
1017 * vs_insertln --
1018 * Insert a line a la curses, make sure to put the information
1019 * line and other screens back.
1021 static int
1022 vs_insertln(sp, cnt)
1023 SCR *sp;
1024 int cnt;
1026 GS *gp;
1027 size_t oldy, oldx;
1029 gp = sp->gp;
1030 if (IS_ONELINE(sp)) {
1031 (void)gp->scr_move(sp, LASTLINE(sp), 0);
1032 (void)gp->scr_clrtoeol(sp);
1033 } else {
1034 (void)gp->scr_cursor(sp, &oldy, &oldx);
1035 while (cnt--) {
1036 (void)gp->scr_move(sp, LASTLINE(sp) - 1, 0);
1037 (void)gp->scr_deleteln(sp);
1038 (void)gp->scr_move(sp, oldy, oldx);
1039 (void)gp->scr_insertln(sp);
1042 return (0);
1046 * vs_sm_next --
1047 * Fill in the next entry in the SMAP.
1049 * PUBLIC: int vs_sm_next __P((SCR *, SMAP *, SMAP *));
1052 vs_sm_next(sp, p, t)
1053 SCR *sp;
1054 SMAP *p, *t;
1056 size_t lcnt;
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;
1072 return (0);
1076 * vs_sm_prev --
1077 * Fill in the previous entry in the SMAP.
1079 * PUBLIC: int vs_sm_prev __P((SCR *, SMAP *, SMAP *));
1082 vs_sm_prev(sp, p, t)
1083 SCR *sp;
1084 SMAP *p, *t;
1086 SMAP_FLUSH(t);
1087 if (O_ISSET(sp, O_LEFTRIGHT)) {
1088 t->lno = p->lno - 1;
1089 t->coff = p->coff;
1090 } else {
1091 if (p->soff != 1) {
1092 t->lno = p->lno;
1093 t->soff = p->soff - 1;
1094 } else {
1095 t->lno = p->lno - 1;
1096 t->soff = vs_screens(sp, t->lno, NULL);
1099 return (t->lno == 0);
1103 * vs_sm_cursor --
1104 * Return the SMAP entry referenced by the cursor.
1106 * PUBLIC: int vs_sm_cursor __P((SCR *, SMAP **));
1109 vs_sm_cursor(sp, smpp)
1110 SCR *sp;
1111 SMAP **smpp;
1113 SMAP *p;
1115 /* See if the cursor is not in the map. */
1116 if (sp->lno < HMAP->lno || sp->lno > TMAP->lno)
1117 return (1);
1119 /* Find the first occurence of the line. */
1120 for (p = HMAP; p->lno != sp->lno; ++p);
1122 /* Fill in the map information until we find the right line. */
1123 for (; p <= TMAP; ++p) {
1124 /* Short lines are common and easy to detect. */
1125 if (p != TMAP && (p + 1)->lno != p->lno) {
1126 *smpp = p;
1127 return (0);
1129 if (!SMAP_CACHE(p) && vs_line(sp, p, NULL, NULL))
1130 return (1);
1131 if (p->c_eboff >= sp->cno) {
1132 *smpp = p;
1133 return (0);
1137 /* It was past the end of the map after all. */
1138 return (1);
1142 * vs_sm_position --
1143 * Return the line/column of the top, middle or last line on the screen.
1144 * (The vi H, M and L commands.) Here because only the screen routines
1145 * know what's really out there.
1147 * PUBLIC: int vs_sm_position __P((SCR *, MARK *, u_long, pos_t));
1150 vs_sm_position(sp, rp, cnt, pos)
1151 SCR *sp;
1152 MARK *rp;
1153 u_long cnt;
1154 pos_t pos;
1156 SMAP *smp;
1157 recno_t last;
1159 switch (pos) {
1160 case P_TOP:
1162 * !!!
1163 * Historically, an invalid count to the H command failed.
1164 * We do nothing special here, just making sure that H in
1165 * an empty screen works.
1167 if (cnt > TMAP - HMAP)
1168 goto sof;
1169 smp = HMAP + cnt;
1170 if (cnt && !db_exist(sp, smp->lno)) {
1171 sof: msgq(sp, M_BERR, "220|Movement past the end-of-screen");
1172 return (1);
1174 break;
1175 case P_MIDDLE:
1177 * !!!
1178 * Historically, a count to the M command was ignored.
1179 * If the screen isn't filled, find the middle of what's
1180 * real and move there.
1182 if (!db_exist(sp, TMAP->lno)) {
1183 if (db_last(sp, &last))
1184 return (1);
1185 for (smp = TMAP; smp->lno > last && smp > HMAP; --smp);
1186 if (smp > HMAP)
1187 smp -= (smp - HMAP) / 2;
1188 } else
1189 smp = (HMAP + (TMAP - HMAP) / 2) + cnt;
1190 break;
1191 case P_BOTTOM:
1193 * !!!
1194 * Historically, an invalid count to the L command failed.
1195 * If the screen isn't filled, find the bottom of what's
1196 * real and try to offset from there.
1198 if (cnt > TMAP - HMAP)
1199 goto eof;
1200 smp = TMAP - cnt;
1201 if (!db_exist(sp, smp->lno)) {
1202 if (db_last(sp, &last))
1203 return (1);
1204 for (; smp->lno > last && smp > HMAP; --smp);
1205 if (cnt > smp - HMAP) {
1206 eof: msgq(sp, M_BERR,
1207 "221|Movement past the beginning-of-screen");
1208 return (1);
1210 smp -= cnt;
1212 break;
1213 default:
1214 abort();
1217 /* Make sure that the cached information is valid. */
1218 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
1219 return (1);
1220 rp->lno = smp->lno;
1221 rp->cno = smp->c_sboff;
1223 return (0);
1227 * vs_sm_nlines --
1228 * Return the number of screen lines from an SMAP entry to the
1229 * start of some file line, less than a maximum value.
1231 * PUBLIC: recno_t vs_sm_nlines __P((SCR *, SMAP *, recno_t, size_t));
1233 recno_t
1234 vs_sm_nlines(sp, from_sp, to_lno, max)
1235 SCR *sp;
1236 SMAP *from_sp;
1237 recno_t to_lno;
1238 size_t max;
1240 recno_t lno, lcnt;
1242 if (O_ISSET(sp, O_LEFTRIGHT))
1243 return (from_sp->lno > to_lno ?
1244 from_sp->lno - to_lno : to_lno - from_sp->lno);
1246 if (from_sp->lno == to_lno)
1247 return (from_sp->soff - 1);
1249 if (from_sp->lno > to_lno) {
1250 lcnt = from_sp->soff - 1; /* Correct for off-by-one. */
1251 for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;)
1252 lcnt += vs_screens(sp, lno, NULL);
1253 } else {
1254 lno = from_sp->lno;
1255 lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1;
1256 for (; ++lno < to_lno && lcnt <= max;)
1257 lcnt += vs_screens(sp, lno, NULL);
1259 return (lcnt);