Expand PMF_FN_* macros.
[netbsd-mini2440.git] / dist / nvi / vi / vs_smap.c
blob4ecc6b2d31e2bd59b28cf0dd90b35443ad33b835
1 /* $NetBSD: vs_smap.c,v 1.1.1.2 2008/05/18 14:31:52 aymeric Exp $ */
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.
9 * See the LICENSE file for redistribution information.
12 #include "config.h"
14 #ifndef lint
15 static const char sccsid[] = "Id: vs_smap.c,v 10.30 2002/01/19 21:59:07 skimo Exp (Berkeley) Date: 2002/01/19 21:59:07";
16 #endif /* not lint */
18 #include <sys/types.h>
19 #include <sys/queue.h>
20 #include <sys/time.h>
22 #include <bitstring.h>
23 #include <limits.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
28 #include "../common/common.h"
29 #include "vi.h"
31 static int vs_deleteln __P((SCR *, int));
32 static int vs_insertln __P((SCR *, int));
33 static int vs_sm_delete __P((SCR *, db_recno_t));
34 static int vs_sm_down __P((SCR *, MARK *, db_recno_t, scroll_t, SMAP *));
35 static int vs_sm_erase __P((SCR *));
36 static int vs_sm_insert __P((SCR *, db_recno_t));
37 static int vs_sm_reset __P((SCR *, db_recno_t));
38 static int vs_sm_up __P((SCR *, MARK *, db_recno_t, scroll_t, SMAP *));
41 * vs_change --
42 * Make a change to the screen.
44 * PUBLIC: int vs_change __P((SCR *, db_recno_t, lnop_t));
46 int
47 vs_change(SCR *sp, db_recno_t lno, lnop_t op)
49 VI_PRIVATE *vip;
50 SMAP *p;
51 size_t cnt, oldy, oldx;
53 vip = VIP(sp);
56 * XXX
57 * Very nasty special case. The historic vi code displays a single
58 * space (or a '$' if the list option is set) for the first line in
59 * an "empty" file. If we "insert" a line, that line gets scrolled
60 * down, not repainted, so it's incorrect when we refresh the screen.
61 * The vi text input functions detect it explicitly and don't insert
62 * a new line.
64 * Check for line #2 before going to the end of the file.
66 if (((op == LINE_APPEND && lno == 0) ||
67 (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 if (sp->lno > lno)
142 --sp->lno;
143 F_SET(vip, VIP_N_RENUMBER);
144 break;
145 case LINE_INSERT:
146 if (vs_sm_insert(sp, lno))
147 return (1);
148 if (sp->lno > lno)
149 ++sp->lno;
150 F_SET(vip, VIP_N_RENUMBER);
151 break;
152 case LINE_RESET:
153 if (vs_sm_reset(sp, lno))
154 return (1);
155 break;
156 default:
157 abort();
160 (void)sp->gp->scr_move(sp, oldy, oldx);
161 return (0);
165 * vs_sm_fill --
166 * Fill in the screen map, placing the specified line at the
167 * right position. There isn't any way to tell if an SMAP
168 * entry has been filled in, so this routine had better be
169 * called with P_FILL set before anything else is done.
171 * !!!
172 * Unexported interface: if lno is OOBLNO, P_TOP means that the HMAP
173 * slot is already filled in, P_BOTTOM means that the TMAP slot is
174 * already filled in, and we just finish up the job.
176 * PUBLIC: int vs_sm_fill __P((SCR *, db_recno_t, pos_t));
179 vs_sm_fill(SCR *sp, db_recno_t lno, pos_t pos)
181 SMAP *p, tmp;
182 size_t cnt;
184 /* Flush all cached information from the SMAP. */
185 for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
186 SMAP_FLUSH(p);
189 * If the map is filled, the screen must be redrawn.
191 * XXX
192 * This is a bug. We should try and figure out if the desired line
193 * is already in the map or close by -- scrolling the screen would
194 * be a lot better than redrawing.
196 F_SET(sp, SC_SCR_REDRAW);
198 switch (pos) {
199 case P_FILL:
200 tmp.lno = 1;
201 tmp.coff = 0;
202 tmp.soff = 1;
204 /* See if less than half a screen from the top. */
205 if (vs_sm_nlines(sp,
206 &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {
207 lno = 1;
208 goto top;
211 /* See if less than half a screen from the bottom. */
212 if (db_last(sp, &tmp.lno))
213 return (1);
214 tmp.coff = 0;
215 tmp.soff = vs_screens(sp, tmp.lno, NULL);
216 if (vs_sm_nlines(sp,
217 &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {
218 TMAP->lno = tmp.lno;
219 TMAP->coff = tmp.coff;
220 TMAP->soff = tmp.soff;
221 goto bottom;
223 goto middle;
224 case P_TOP:
225 if (lno != OOBLNO) {
226 top: HMAP->lno = lno;
227 HMAP->coff = 0;
228 HMAP->soff = 1;
230 /* If we fail, just punt. */
231 for (p = HMAP, cnt = sp->t_rows; --cnt; ++p)
232 if (vs_sm_next(sp, p, p + 1))
233 goto err;
234 break;
235 case P_MIDDLE:
236 /* If we fail, guess that the file is too small. */
237 middle: p = HMAP + sp->t_rows / 2;
238 p->lno = lno;
239 p->coff = 0;
240 p->soff = 1;
241 for (; p > HMAP; --p)
242 if (vs_sm_prev(sp, p, p - 1)) {
243 lno = 1;
244 goto top;
247 /* If we fail, just punt. */
248 p = HMAP + sp->t_rows / 2;
249 for (; p < TMAP; ++p)
250 if (vs_sm_next(sp, p, p + 1))
251 goto err;
252 break;
253 case P_BOTTOM:
254 if (lno != OOBLNO) {
255 TMAP->lno = lno;
256 TMAP->coff = 0;
257 TMAP->soff = vs_screens(sp, lno, NULL);
259 /* If we fail, guess that the file is too small. */
260 bottom: for (p = TMAP; p > HMAP; --p)
261 if (vs_sm_prev(sp, p, p - 1)) {
262 lno = 1;
263 goto top;
265 break;
266 default:
267 abort();
269 return (0);
272 * Try and put *something* on the screen. If this fails, we have a
273 * serious hard error.
275 err: HMAP->lno = 1;
276 HMAP->coff = 0;
277 HMAP->soff = 1;
278 for (p = HMAP; p < TMAP; ++p)
279 if (vs_sm_next(sp, p, p + 1))
280 return (1);
281 return (0);
285 * For the routines vs_sm_reset, vs_sm_delete and vs_sm_insert: if the
286 * screen contains only a single line (whether because the screen is small
287 * or the line large), it gets fairly exciting. Skip the fun, set a flag
288 * so the screen map is refilled and the screen redrawn, and return. This
289 * is amazingly slow, but it's not clear that anyone will care.
291 #define HANDLE_WEIRDNESS(cnt) { \
292 if (cnt >= sp->t_rows) { \
293 F_SET(sp, SC_SCR_REFORMAT); \
294 return (0); \
299 * vs_sm_delete --
300 * Delete a line out of the SMAP.
302 static int
303 vs_sm_delete(SCR *sp, db_recno_t lno)
305 SMAP *p, *t;
306 size_t cnt_orig;
309 * Find the line in the map, and count the number of screen lines
310 * which display any part of the deleted line.
312 for (p = HMAP; p->lno != lno; ++p);
313 if (O_ISSET(sp, O_LEFTRIGHT))
314 cnt_orig = 1;
315 else
316 for (cnt_orig = 1, t = p + 1;
317 t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
319 HANDLE_WEIRDNESS(cnt_orig);
321 /* Delete that many lines from the screen. */
322 (void)sp->gp->scr_move(sp, p - HMAP, 0);
323 if (vs_deleteln(sp, cnt_orig))
324 return (1);
326 /* Shift the screen map up. */
327 memmove(p, p + cnt_orig, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
329 /* Decrement the line numbers for the rest of the map. */
330 for (t = TMAP - cnt_orig; p <= t; ++p)
331 --p->lno;
333 /* Display the new lines. */
334 for (p = TMAP - cnt_orig;;) {
335 if (p < TMAP && vs_sm_next(sp, p, p + 1))
336 return (1);
337 /* vs_sm_next() flushed the cache. */
338 if (vs_line(sp, ++p, NULL, NULL))
339 return (1);
340 if (p == TMAP)
341 break;
343 return (0);
347 * vs_sm_insert --
348 * Insert a line into the SMAP.
350 static int
351 vs_sm_insert(SCR *sp, db_recno_t lno)
353 SMAP *p, *t;
354 size_t cnt_orig, cnt, coff;
356 /* Save the offset. */
357 coff = HMAP->coff;
360 * Find the line in the map, find out how many screen lines
361 * needed to display the line.
363 for (p = HMAP; p->lno != lno; ++p);
365 cnt_orig = vs_screens(sp, lno, NULL);
366 HANDLE_WEIRDNESS(cnt_orig);
369 * The lines left in the screen override the number of screen
370 * lines in the inserted line.
372 cnt = (TMAP - p) + 1;
373 if (cnt_orig > cnt)
374 cnt_orig = cnt;
376 /* Push down that many lines. */
377 (void)sp->gp->scr_move(sp, p - HMAP, 0);
378 if (vs_insertln(sp, cnt_orig))
379 return (1);
381 /* Shift the screen map down. */
382 memmove(p + cnt_orig, p, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
384 /* Increment the line numbers for the rest of the map. */
385 for (t = p + cnt_orig; t <= TMAP; ++t)
386 ++t->lno;
388 /* Fill in the SMAP for the new lines, and display. */
389 for (cnt = 1, t = p; cnt <= cnt_orig; ++t, ++cnt) {
390 t->lno = lno;
391 t->coff = coff;
392 t->soff = cnt;
393 SMAP_FLUSH(t);
394 if (vs_line(sp, t, NULL, NULL))
395 return (1);
397 return (0);
401 * vs_sm_reset --
402 * Reset a line in the SMAP.
404 static int
405 vs_sm_reset(SCR *sp, db_recno_t lno)
407 SMAP *p, *t;
408 size_t cnt_orig, cnt_new, cnt, diff;
411 * See if the number of on-screen rows taken up by the old display
412 * for the line is the same as the number needed for the new one.
413 * If so, repaint, otherwise do it the hard way.
415 for (p = HMAP; p->lno != lno; ++p);
416 if (O_ISSET(sp, O_LEFTRIGHT)) {
417 t = p;
418 cnt_orig = cnt_new = 1;
419 } else {
420 for (cnt_orig = 0,
421 t = p; t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
422 cnt_new = vs_screens(sp, lno, NULL);
425 HANDLE_WEIRDNESS(cnt_orig);
427 if (cnt_orig == cnt_new) {
428 do {
429 SMAP_FLUSH(p);
430 if (vs_line(sp, p, NULL, NULL))
431 return (1);
432 } while (++p < t);
433 return (0);
436 if (cnt_orig < cnt_new) {
437 /* Get the difference. */
438 diff = cnt_new - cnt_orig;
441 * The lines left in the screen override the number of screen
442 * lines in the inserted line.
444 cnt = (TMAP - p) + 1;
445 if (diff > cnt)
446 diff = cnt;
448 /* If there are any following lines, push them down. */
449 if (cnt > 1) {
450 (void)sp->gp->scr_move(sp, p - HMAP, 0);
451 if (vs_insertln(sp, diff))
452 return (1);
454 /* Shift the screen map down. */
455 memmove(p + diff, p,
456 (((TMAP - p) - diff) + 1) * sizeof(SMAP));
459 /* Fill in the SMAP for the replaced line, and display. */
460 for (cnt = 1, t = p; cnt_new-- && t <= TMAP; ++t, ++cnt) {
461 t->lno = lno;
462 t->soff = cnt;
463 SMAP_FLUSH(t);
464 if (vs_line(sp, t, NULL, NULL))
465 return (1);
467 } else {
468 /* Get the difference. */
469 diff = cnt_orig - cnt_new;
471 /* Delete that many lines from the screen. */
472 (void)sp->gp->scr_move(sp, p - HMAP, 0);
473 if (vs_deleteln(sp, diff))
474 return (1);
476 /* Shift the screen map up. */
477 memmove(p, p + diff, (((TMAP - p) - diff) + 1) * sizeof(SMAP));
479 /* Fill in the SMAP for the replaced line, and display. */
480 for (cnt = 1, t = p; cnt_new--; ++t, ++cnt) {
481 t->lno = lno;
482 t->soff = cnt;
483 SMAP_FLUSH(t);
484 if (vs_line(sp, t, NULL, NULL))
485 return (1);
488 /* Display the new lines at the bottom of the screen. */
489 for (t = TMAP - diff;;) {
490 if (t < TMAP && vs_sm_next(sp, t, t + 1))
491 return (1);
492 /* vs_sm_next() flushed the cache. */
493 if (vs_line(sp, ++t, NULL, NULL))
494 return (1);
495 if (t == TMAP)
496 break;
499 return (0);
503 * vs_sm_scroll
504 * Scroll the SMAP up/down count logical lines. Different
505 * semantics based on the vi command, *sigh*.
507 * PUBLIC: int vs_sm_scroll __P((SCR *, MARK *, db_recno_t, scroll_t));
510 vs_sm_scroll(SCR *sp, MARK *rp, db_recno_t count, scroll_t scmd)
512 SMAP *smp;
515 * Invalidate the cursor. The line is probably going to change,
516 * (although for ^E and ^Y it may not). In any case, the scroll
517 * routines move the cursor to draw things.
519 F_SET(VIP(sp), VIP_CUR_INVALID);
521 /* Find the cursor in the screen. */
522 if (vs_sm_cursor(sp, &smp))
523 return (1);
525 switch (scmd) {
526 case CNTRL_B:
527 case CNTRL_U:
528 case CNTRL_Y:
529 case Z_CARAT:
530 if (vs_sm_down(sp, rp, count, scmd, smp))
531 return (1);
532 break;
533 case CNTRL_D:
534 case CNTRL_E:
535 case CNTRL_F:
536 case Z_PLUS:
537 if (vs_sm_up(sp, rp, count, scmd, smp))
538 return (1);
539 break;
540 default:
541 abort();
545 * !!!
546 * If we're at the start of a line, go for the first non-blank.
547 * This makes it look like the old vi, even though we're moving
548 * around by logical lines, not physical ones.
550 * XXX
551 * In the presence of a long line, which has more than a screen
552 * width of leading spaces, this code can cause a cursor warp.
553 * Live with it.
555 if (scmd != CNTRL_E && scmd != CNTRL_Y &&
556 rp->cno == 0 && nonblank(sp, rp->lno, &rp->cno))
557 return (1);
559 return (0);
563 * vs_sm_up --
564 * Scroll the SMAP up count logical lines.
566 static int
567 vs_sm_up(SCR *sp, MARK *rp, db_recno_t count, scroll_t scmd, SMAP *smp)
569 int cursor_set, echanged, zset;
570 SMAP *ssmp, s1, s2;
573 * Check to see if movement is possible.
575 * Get the line after the map. If that line is a new one (and if
576 * O_LEFTRIGHT option is set, this has to be true), and the next
577 * line doesn't exist, and the cursor doesn't move, or the cursor
578 * isn't even on the screen, or the cursor is already at the last
579 * line in the map, it's an error. If that test succeeded because
580 * the cursor wasn't at the end of the map, test to see if the map
581 * is mostly empty.
583 if (vs_sm_next(sp, TMAP, &s1))
584 return (1);
585 if (s1.lno > TMAP->lno && !db_exist(sp, s1.lno)) {
586 if (scmd == CNTRL_E || scmd == Z_PLUS || smp == TMAP) {
587 v_eof(sp, NULL);
588 return (1);
590 if (vs_sm_next(sp, smp, &s1))
591 return (1);
592 if (s1.lno > smp->lno && !db_exist(sp, s1.lno)) {
593 v_eof(sp, NULL);
594 return (1);
599 * Small screens: see vs_refresh.c section 6a.
601 * If it's a small screen, and the movement isn't larger than a
602 * screen, i.e some context will remain, open up the screen and
603 * display by scrolling. In this case, the cursor moves down one
604 * line for each line displayed. Otherwise, erase/compress and
605 * repaint, and move the cursor to the first line in the screen.
606 * Note, the ^F command is always in the latter case, for historical
607 * reasons.
609 cursor_set = 0;
610 if (IS_SMALL(sp)) {
611 if (count >= sp->t_maxrows || scmd == CNTRL_F) {
612 s1 = TMAP[0];
613 if (vs_sm_erase(sp))
614 return (1);
615 for (; count--; s1 = s2) {
616 if (vs_sm_next(sp, &s1, &s2))
617 return (1);
618 if (s2.lno != s1.lno && !db_exist(sp, s2.lno))
619 break;
621 TMAP[0] = s2;
622 if (vs_sm_fill(sp, OOBLNO, P_BOTTOM))
623 return (1);
624 return (vs_sm_position(sp, rp, 0, P_TOP));
626 cursor_set = scmd == CNTRL_E || vs_sm_cursor(sp, &ssmp);
627 for (; count &&
628 sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
629 if (vs_sm_next(sp, TMAP, &s1))
630 return (1);
631 if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))
632 break;
633 *++TMAP = s1;
634 /* vs_sm_next() flushed the cache. */
635 if (vs_line(sp, TMAP, NULL, NULL))
636 return (1);
638 if (!cursor_set)
639 ++ssmp;
641 if (!cursor_set) {
642 rp->lno = ssmp->lno;
643 rp->cno = ssmp->c_sboff;
645 if (count == 0)
646 return (0);
649 for (echanged = zset = 0; count; --count) {
650 /* Decide what would show up on the screen. */
651 if (vs_sm_next(sp, TMAP, &s1))
652 return (1);
654 /* If the line doesn't exist, we're done. */
655 if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))
656 break;
658 /* Scroll the screen cursor up one logical line. */
659 if (vs_sm_1up(sp))
660 return (1);
661 switch (scmd) {
662 case CNTRL_E:
663 if (smp > HMAP)
664 --smp;
665 else
666 echanged = 1;
667 break;
668 case Z_PLUS:
669 if (zset) {
670 if (smp > HMAP)
671 --smp;
672 } else {
673 smp = TMAP;
674 zset = 1;
676 /* FALLTHROUGH */
677 default:
678 break;
682 if (cursor_set)
683 return(0);
685 switch (scmd) {
686 case CNTRL_E:
688 * On a ^E that was forced to change lines, try and keep the
689 * cursor as close as possible to the last position, but also
690 * set it up so that the next "real" movement will return the
691 * cursor to the closest position to the last real movement.
693 if (echanged) {
694 rp->lno = smp->lno;
695 rp->cno = vs_colpos(sp, smp->lno,
696 (O_ISSET(sp, O_LEFTRIGHT) ?
697 smp->coff : (smp->soff - 1) * sp->cols) +
698 sp->rcm % sp->cols);
700 return (0);
701 case CNTRL_F:
703 * If there are more lines, the ^F command is positioned at
704 * the first line of the screen.
706 if (!count) {
707 smp = HMAP;
708 break;
710 /* FALLTHROUGH */
711 case CNTRL_D:
713 * The ^D and ^F commands move the cursor towards EOF
714 * if there are more lines to move. Check to be sure
715 * the lines actually exist. (They may not if the
716 * file is smaller than the screen.)
718 for (; count; --count, ++smp)
719 if (smp == TMAP || !db_exist(sp, smp[1].lno))
720 break;
721 break;
722 case Z_PLUS:
723 /* The z+ command moves the cursor to the first new line. */
724 break;
725 default:
726 abort();
729 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
730 return (1);
731 rp->lno = smp->lno;
732 rp->cno = smp->c_scoff == 255 ? 0 : smp->c_sboff;
733 return (0);
737 * vs_sm_1up --
738 * Scroll the SMAP up one.
740 * PUBLIC: int vs_sm_1up __P((SCR *));
743 vs_sm_1up(SCR *sp)
746 * Delete the top line of the screen. Shift the screen map
747 * up and display a new line at the bottom of the screen.
749 (void)sp->gp->scr_move(sp, 0, 0);
750 if (vs_deleteln(sp, 1))
751 return (1);
753 /* One-line screens can fail. */
754 if (IS_ONELINE(sp)) {
755 if (vs_sm_next(sp, TMAP, TMAP))
756 return (1);
757 } else {
758 memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP));
759 if (vs_sm_next(sp, TMAP - 1, TMAP))
760 return (1);
762 /* vs_sm_next() flushed the cache. */
763 return (vs_line(sp, TMAP, NULL, NULL));
767 * vs_deleteln --
768 * Delete a line a la curses, make sure to put the information
769 * line and other screens back.
771 static int
772 vs_deleteln(SCR *sp, int cnt)
774 GS *gp;
775 size_t oldy, oldx;
777 gp = sp->gp;
779 /* If the screen is vertically split, we can't scroll it. */
780 if (IS_VSPLIT(sp)) {
781 F_SET(sp, SC_SCR_REDRAW);
782 return (0);
785 if (IS_ONELINE(sp))
786 (void)gp->scr_clrtoeol(sp);
787 else {
788 (void)gp->scr_cursor(sp, &oldy, &oldx);
789 while (cnt--) {
790 (void)gp->scr_deleteln(sp);
791 (void)gp->scr_move(sp, LASTLINE(sp), 0);
792 (void)gp->scr_insertln(sp);
793 (void)gp->scr_move(sp, oldy, oldx);
796 return (0);
800 * vs_sm_down --
801 * Scroll the SMAP down count logical lines.
803 static int
804 vs_sm_down(SCR *sp, MARK *rp, db_recno_t count, scroll_t scmd, SMAP *smp)
806 SMAP *ssmp, s1, s2;
807 int cursor_set, ychanged, zset;
809 /* Check to see if movement is possible. */
810 if (HMAP->lno == 1 &&
811 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) &&
812 (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) {
813 v_sof(sp, NULL);
814 return (1);
818 * Small screens: see vs_refresh.c section 6a.
820 * If it's a small screen, and the movement isn't larger than a
821 * screen, i.e some context will remain, open up the screen and
822 * display by scrolling. In this case, the cursor moves up one
823 * line for each line displayed. Otherwise, erase/compress and
824 * repaint, and move the cursor to the first line in the screen.
825 * Note, the ^B command is always in the latter case, for historical
826 * reasons.
828 cursor_set = scmd == CNTRL_Y;
829 if (IS_SMALL(sp)) {
830 if (count >= sp->t_maxrows || scmd == CNTRL_B) {
831 s1 = HMAP[0];
832 if (vs_sm_erase(sp))
833 return (1);
834 for (; count--; s1 = s2) {
835 if (vs_sm_prev(sp, &s1, &s2))
836 return (1);
837 if (s2.lno == 1 &&
838 (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1))
839 break;
841 HMAP[0] = s2;
842 if (vs_sm_fill(sp, OOBLNO, P_TOP))
843 return (1);
844 return (vs_sm_position(sp, rp, 0, P_BOTTOM));
846 cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp);
847 for (; count &&
848 sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
849 if (HMAP->lno == 1 &&
850 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
851 break;
852 ++TMAP;
853 if (vs_sm_1down(sp))
854 return (1);
856 if (!cursor_set) {
857 rp->lno = ssmp->lno;
858 rp->cno = ssmp->c_sboff;
860 if (count == 0)
861 return (0);
864 for (ychanged = zset = 0; count; --count) {
865 /* If the line doesn't exist, we're done. */
866 if (HMAP->lno == 1 &&
867 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
868 break;
870 /* Scroll the screen and cursor down one logical line. */
871 if (vs_sm_1down(sp))
872 return (1);
873 switch (scmd) {
874 case CNTRL_Y:
875 if (smp < TMAP)
876 ++smp;
877 else
878 ychanged = 1;
879 break;
880 case Z_CARAT:
881 if (zset) {
882 if (smp < TMAP)
883 ++smp;
884 } else {
885 smp = HMAP;
886 zset = 1;
888 /* FALLTHROUGH */
889 default:
890 break;
894 if (scmd != CNTRL_Y && cursor_set)
895 return(0);
897 switch (scmd) {
898 case CNTRL_B:
900 * If there are more lines, the ^B command is positioned at
901 * the last line of the screen. However, the line may not
902 * exist.
904 if (!count) {
905 for (smp = TMAP; smp > HMAP; --smp)
906 if (db_exist(sp, smp->lno))
907 break;
908 break;
910 /* FALLTHROUGH */
911 case CNTRL_U:
913 * The ^B and ^U commands move the cursor towards SOF
914 * if there are more lines to move.
916 if (count < (db_recno_t)(smp - HMAP))
917 smp -= count;
918 else
919 smp = HMAP;
920 break;
921 case CNTRL_Y:
923 * On a ^Y that was forced to change lines, try and keep the
924 * cursor as close as possible to the last position, but also
925 * set it up so that the next "real" movement will return the
926 * cursor to the closest position to the last real movement.
928 if (ychanged) {
929 rp->lno = smp->lno;
930 rp->cno = vs_colpos(sp, smp->lno,
931 (O_ISSET(sp, O_LEFTRIGHT) ?
932 smp->coff : (smp->soff - 1) * sp->cols) +
933 sp->rcm % sp->cols);
935 return (0);
936 case Z_CARAT:
937 /* The z^ command moves the cursor to the first new line. */
938 break;
939 default:
940 abort();
943 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
944 return (1);
945 rp->lno = smp->lno;
946 rp->cno = smp->c_scoff == 255 ? 0 : smp->c_sboff;
947 return (0);
951 * vs_sm_erase --
952 * Erase the small screen area for the scrolling functions.
954 static int
955 vs_sm_erase(SCR *sp)
957 GS *gp;
959 gp = sp->gp;
960 (void)gp->scr_move(sp, LASTLINE(sp), 0);
961 (void)gp->scr_clrtoeol(sp);
962 for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) {
963 (void)gp->scr_move(sp, TMAP - HMAP, 0);
964 (void)gp->scr_clrtoeol(sp);
966 return (0);
970 * vs_sm_1down --
971 * Scroll the SMAP down one.
973 * PUBLIC: int vs_sm_1down __P((SCR *));
976 vs_sm_1down(SCR *sp)
979 * Insert a line at the top of the screen. Shift the screen map
980 * down and display a new line at the top of the screen.
982 (void)sp->gp->scr_move(sp, 0, 0);
983 if (vs_insertln(sp, 1))
984 return (1);
986 /* One-line screens can fail. */
987 if (IS_ONELINE(sp)) {
988 if (vs_sm_prev(sp, HMAP, HMAP))
989 return (1);
990 } else {
991 memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP));
992 if (vs_sm_prev(sp, HMAP + 1, HMAP))
993 return (1);
995 /* vs_sm_prev() flushed the cache. */
996 return (vs_line(sp, HMAP, NULL, NULL));
1000 * vs_insertln --
1001 * Insert a line a la curses, make sure to put the information
1002 * line and other screens back.
1004 static int
1005 vs_insertln(SCR *sp, int cnt)
1007 GS *gp;
1008 size_t oldy, oldx;
1010 gp = sp->gp;
1012 /* If the screen is vertically split, we can't scroll it. */
1013 if (IS_VSPLIT(sp)) {
1014 F_SET(sp, SC_SCR_REDRAW);
1015 return (0);
1018 if (IS_ONELINE(sp)) {
1019 (void)gp->scr_move(sp, LASTLINE(sp), 0);
1020 (void)gp->scr_clrtoeol(sp);
1021 } else {
1022 (void)gp->scr_cursor(sp, &oldy, &oldx);
1023 while (cnt--) {
1024 (void)gp->scr_move(sp, LASTLINE(sp) - 1, 0);
1025 (void)gp->scr_deleteln(sp);
1026 (void)gp->scr_move(sp, oldy, oldx);
1027 (void)gp->scr_insertln(sp);
1030 return (0);
1034 * vs_sm_next --
1035 * Fill in the next entry in the SMAP.
1037 * PUBLIC: int vs_sm_next __P((SCR *, SMAP *, SMAP *));
1040 vs_sm_next(SCR *sp, SMAP *p, SMAP *t)
1042 size_t lcnt;
1044 SMAP_FLUSH(t);
1045 if (O_ISSET(sp, O_LEFTRIGHT)) {
1046 t->lno = p->lno + 1;
1047 t->coff = p->coff;
1048 } else {
1049 lcnt = vs_screens(sp, p->lno, NULL);
1050 if (lcnt == p->soff) {
1051 t->lno = p->lno + 1;
1052 t->soff = 1;
1053 } else {
1054 t->lno = p->lno;
1055 t->soff = p->soff + 1;
1058 return (0);
1062 * vs_sm_prev --
1063 * Fill in the previous entry in the SMAP.
1065 * PUBLIC: int vs_sm_prev __P((SCR *, SMAP *, SMAP *));
1068 vs_sm_prev(SCR *sp, SMAP *p, SMAP *t)
1070 SMAP_FLUSH(t);
1071 if (O_ISSET(sp, O_LEFTRIGHT)) {
1072 t->lno = p->lno - 1;
1073 t->coff = p->coff;
1074 } else {
1075 if (p->soff != 1) {
1076 t->lno = p->lno;
1077 t->soff = p->soff - 1;
1078 } else {
1079 t->lno = p->lno - 1;
1080 t->soff = vs_screens(sp, t->lno, NULL);
1083 return (t->lno == 0);
1087 * vs_sm_cursor --
1088 * Return the SMAP entry referenced by the cursor.
1090 * PUBLIC: int vs_sm_cursor __P((SCR *, SMAP **));
1093 vs_sm_cursor(SCR *sp, SMAP **smpp)
1095 SMAP *p;
1097 /* See if the cursor is not in the map. */
1098 if (sp->lno < HMAP->lno || sp->lno > TMAP->lno)
1099 return (1);
1101 /* Find the first occurence of the line. */
1102 for (p = HMAP; p->lno != sp->lno; ++p);
1104 /* Fill in the map information until we find the right line. */
1105 for (; p <= TMAP; ++p) {
1106 /* Short lines are common and easy to detect. */
1107 if (p != TMAP && (p + 1)->lno != p->lno) {
1108 *smpp = p;
1109 return (0);
1111 if (!SMAP_CACHE(p) && vs_line(sp, p, NULL, NULL))
1112 return (1);
1113 if (p->c_eboff >= sp->cno) {
1114 *smpp = p;
1115 return (0);
1119 /* It was past the end of the map after all. */
1120 return (1);
1124 * vs_sm_position --
1125 * Return the line/column of the top, middle or last line on the screen.
1126 * (The vi H, M and L commands.) Here because only the screen routines
1127 * know what's really out there.
1129 * PUBLIC: int vs_sm_position __P((SCR *, MARK *, u_long, pos_t));
1132 vs_sm_position(SCR *sp, MARK *rp, u_long cnt, pos_t pos)
1134 SMAP *smp;
1135 db_recno_t last;
1137 switch (pos) {
1138 case P_TOP:
1140 * !!!
1141 * Historically, an invalid count to the H command failed.
1142 * We do nothing special here, just making sure that H in
1143 * an empty screen works.
1145 if (cnt > (u_long)(TMAP - HMAP))
1146 goto sof;
1147 smp = HMAP + cnt;
1148 if (cnt && !db_exist(sp, smp->lno)) {
1149 sof: msgq(sp, M_BERR, "220|Movement past the end-of-screen");
1150 return (1);
1152 break;
1153 case P_MIDDLE:
1155 * !!!
1156 * Historically, a count to the M command was ignored.
1157 * If the screen isn't filled, find the middle of what's
1158 * real and move there.
1160 if (!db_exist(sp, TMAP->lno)) {
1161 if (db_last(sp, &last))
1162 return (1);
1163 for (smp = TMAP; smp->lno > last && smp > HMAP; --smp);
1164 if (smp > HMAP)
1165 smp -= (smp - HMAP) / 2;
1166 } else
1167 smp = (HMAP + (TMAP - HMAP) / 2) + cnt;
1168 break;
1169 case P_BOTTOM:
1171 * !!!
1172 * Historically, an invalid count to the L command failed.
1173 * If the screen isn't filled, find the bottom of what's
1174 * real and try to offset from there.
1176 if (cnt > (u_long)(TMAP - HMAP))
1177 goto eof;
1178 smp = TMAP - cnt;
1179 if (!db_exist(sp, smp->lno)) {
1180 if (db_last(sp, &last))
1181 return (1);
1182 for (; smp->lno > last && smp > HMAP; --smp);
1183 if (cnt > (u_long)(smp - HMAP)) {
1184 eof: msgq(sp, M_BERR,
1185 "221|Movement past the beginning-of-screen");
1186 return (1);
1188 smp -= cnt;
1190 break;
1191 default:
1192 abort();
1195 /* Make sure that the cached information is valid. */
1196 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
1197 return (1);
1198 rp->lno = smp->lno;
1199 rp->cno = smp->c_sboff;
1201 return (0);
1205 * vs_sm_nlines --
1206 * Return the number of screen lines from an SMAP entry to the
1207 * start of some file line, less than a maximum value.
1209 * PUBLIC: db_recno_t vs_sm_nlines __P((SCR *, SMAP *, db_recno_t, size_t));
1211 db_recno_t
1212 vs_sm_nlines(SCR *sp, SMAP *from_sp, db_recno_t to_lno, size_t max)
1214 db_recno_t lno, lcnt;
1216 if (O_ISSET(sp, O_LEFTRIGHT))
1217 return (from_sp->lno > to_lno ?
1218 from_sp->lno - to_lno : to_lno - from_sp->lno);
1220 if (from_sp->lno == to_lno)
1221 return (from_sp->soff - 1);
1223 if (from_sp->lno > to_lno) {
1224 lcnt = from_sp->soff - 1; /* Correct for off-by-one. */
1225 for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;)
1226 lcnt += vs_screens(sp, lno, NULL);
1227 } else {
1228 lno = from_sp->lno;
1229 lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1;
1230 for (; ++lno < to_lno && lcnt <= max;)
1231 lcnt += vs_screens(sp, lno, NULL);
1233 return (lcnt);