db4c9bb615aad06242d257a60c20fe7ef3f2442a
[nvi.git] / vi / vs_smap.c
blobdb4c9bb615aad06242d257a60c20fe7ef3f2442a
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[] = "$Id: vs_smap.c,v 10.30 2002/01/19 21:59:07 skimo Exp $ (Berkeley) $Date: 2002/01/19 21:59:07 $";
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 *, db_recno_t));
32 static int vs_sm_down __P((SCR *, MARK *, db_recno_t, scroll_t, SMAP *));
33 static int vs_sm_erase __P((SCR *));
34 static int vs_sm_insert __P((SCR *, db_recno_t));
35 static int vs_sm_reset __P((SCR *, db_recno_t));
36 static int vs_sm_up __P((SCR *, MARK *, db_recno_t, scroll_t, SMAP *));
39 * vs_change --
40 * Make a change to the screen.
42 * PUBLIC: int vs_change __P((SCR *, db_recno_t, lnop_t));
44 int
45 vs_change(SCR *sp, db_recno_t lno, lnop_t op)
47 VI_PRIVATE *vip;
48 SMAP *p;
49 size_t cnt, oldy, oldx;
51 vip = VIP(sp);
54 * XXX
55 * Very nasty special case. The historic vi code displays a single
56 * space (or a '$' if the list option is set) for the first line in
57 * an "empty" file. If we "insert" a line, that line gets scrolled
58 * down, not repainted, so it's incorrect when we refresh the screen.
59 * The vi text input functions detect it explicitly and don't insert
60 * a new line.
62 * Check for line #2 before going to the end of the file.
64 if (((op == LINE_APPEND && lno == 0) ||
65 (op == LINE_INSERT && lno == 1)) &&
66 !db_exist(sp, 2)) {
67 lno = 1;
68 op = LINE_RESET;
71 /* Appending is the same as inserting, if the line is incremented. */
72 if (op == LINE_APPEND) {
73 ++lno;
74 op = LINE_INSERT;
77 /* Ignore the change if the line is after the map. */
78 if (lno > TMAP->lno)
79 return (0);
82 * If the line is before the map, and it's a decrement, decrement
83 * the map. If it's an increment, increment the map. Otherwise,
84 * ignore it.
86 if (lno < HMAP->lno) {
87 switch (op) {
88 case LINE_APPEND:
89 abort();
90 /* NOTREACHED */
91 case LINE_DELETE:
92 for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
93 --p->lno;
94 if (sp->lno >= lno)
95 --sp->lno;
96 F_SET(vip, VIP_N_RENUMBER);
97 break;
98 case LINE_INSERT:
99 for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
100 ++p->lno;
101 if (sp->lno >= lno)
102 ++sp->lno;
103 F_SET(vip, VIP_N_RENUMBER);
104 break;
105 case LINE_RESET:
106 break;
108 return (0);
111 F_SET(vip, VIP_N_REFRESH);
114 * Invalidate the line size cache, and invalidate the cursor if it's
115 * on this line,
117 VI_SCR_CFLUSH(vip);
118 if (sp->lno == lno)
119 F_SET(vip, VIP_CUR_INVALID);
122 * If ex modifies the screen after ex output is already on the screen
123 * or if we've switched into ex canonical mode, don't touch it -- we'll
124 * get scrolling wrong, at best.
126 if (!F_ISSET(sp, SC_TINPUT_INFO) &&
127 (F_ISSET(sp, SC_SCR_EXWROTE) || VIP(sp)->totalcount > 1)) {
128 F_SET(vip, VIP_N_EX_REDRAW);
129 return (0);
132 /* Save and restore the cursor for these routines. */
133 (void)sp->gp->scr_cursor(sp, &oldy, &oldx);
135 switch (op) {
136 case LINE_DELETE:
137 if (vs_sm_delete(sp, lno))
138 return (1);
139 if (sp->lno > lno)
140 --sp->lno;
141 F_SET(vip, VIP_N_RENUMBER);
142 break;
143 case LINE_INSERT:
144 if (vs_sm_insert(sp, lno))
145 return (1);
146 if (sp->lno > lno)
147 ++sp->lno;
148 F_SET(vip, VIP_N_RENUMBER);
149 break;
150 case LINE_RESET:
151 if (vs_sm_reset(sp, lno))
152 return (1);
153 break;
154 default:
155 abort();
158 (void)sp->gp->scr_move(sp, oldy, oldx);
159 return (0);
163 * vs_sm_fill --
164 * Fill in the screen map, placing the specified line at the
165 * right position. There isn't any way to tell if an SMAP
166 * entry has been filled in, so this routine had better be
167 * called with P_FILL set before anything else is done.
169 * !!!
170 * Unexported interface: if lno is OOBLNO, P_TOP means that the HMAP
171 * slot is already filled in, P_BOTTOM means that the TMAP slot is
172 * already filled in, and we just finish up the job.
174 * PUBLIC: int vs_sm_fill __P((SCR *, db_recno_t, pos_t));
177 vs_sm_fill(SCR *sp, db_recno_t lno, pos_t pos)
179 SMAP *p, tmp;
180 size_t cnt;
182 /* Flush all cached information from the SMAP. */
183 for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
184 SMAP_FLUSH(p);
187 * If the map is filled, the screen must be redrawn.
189 * XXX
190 * This is a bug. We should try and figure out if the desired line
191 * is already in the map or close by -- scrolling the screen would
192 * be a lot better than redrawing.
194 F_SET(sp, SC_SCR_REDRAW);
196 switch (pos) {
197 case P_FILL:
198 tmp.lno = 1;
199 tmp.coff = 0;
200 tmp.soff = 1;
202 /* See if less than half a screen from the top. */
203 if (vs_sm_nlines(sp,
204 &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {
205 lno = 1;
206 goto top;
209 /* See if less than half a screen from the bottom. */
210 if (db_last(sp, &tmp.lno))
211 return (1);
212 tmp.coff = 0;
213 tmp.soff = vs_screens(sp, tmp.lno, NULL);
214 if (vs_sm_nlines(sp,
215 &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {
216 TMAP->lno = tmp.lno;
217 TMAP->coff = tmp.coff;
218 TMAP->soff = tmp.soff;
219 goto bottom;
221 goto middle;
222 case P_TOP:
223 if (lno != OOBLNO) {
224 top: HMAP->lno = lno;
225 HMAP->coff = 0;
226 HMAP->soff = 1;
228 /* If we fail, just punt. */
229 for (p = HMAP, cnt = sp->t_rows; --cnt; ++p)
230 if (vs_sm_next(sp, p, p + 1))
231 goto err;
232 break;
233 case P_MIDDLE:
234 /* If we fail, guess that the file is too small. */
235 middle: p = HMAP + sp->t_rows / 2;
236 p->lno = lno;
237 p->coff = 0;
238 p->soff = 1;
239 for (; p > HMAP; --p)
240 if (vs_sm_prev(sp, p, p - 1)) {
241 lno = 1;
242 goto top;
245 /* If we fail, just punt. */
246 p = HMAP + sp->t_rows / 2;
247 for (; p < TMAP; ++p)
248 if (vs_sm_next(sp, p, p + 1))
249 goto err;
250 break;
251 case P_BOTTOM:
252 if (lno != OOBLNO) {
253 TMAP->lno = lno;
254 TMAP->coff = 0;
255 TMAP->soff = vs_screens(sp, lno, NULL);
257 /* If we fail, guess that the file is too small. */
258 bottom: for (p = TMAP; p > HMAP; --p)
259 if (vs_sm_prev(sp, p, p - 1)) {
260 lno = 1;
261 goto top;
263 break;
264 default:
265 abort();
267 return (0);
270 * Try and put *something* on the screen. If this fails, we have a
271 * serious hard error.
273 err: HMAP->lno = 1;
274 HMAP->coff = 0;
275 HMAP->soff = 1;
276 for (p = HMAP; p < TMAP; ++p)
277 if (vs_sm_next(sp, p, p + 1))
278 return (1);
279 return (0);
283 * For the routines vs_sm_reset, vs_sm_delete and vs_sm_insert: if the
284 * screen contains only a single line (whether because the screen is small
285 * or the line large), it gets fairly exciting. Skip the fun, set a flag
286 * so the screen map is refilled and the screen redrawn, and return. This
287 * is amazingly slow, but it's not clear that anyone will care.
289 #define HANDLE_WEIRDNESS(cnt) { \
290 if (cnt >= sp->t_rows) { \
291 F_SET(sp, SC_SCR_REFORMAT); \
292 return (0); \
297 * vs_sm_delete --
298 * Delete a line out of the SMAP.
300 static int
301 vs_sm_delete(SCR *sp, db_recno_t lno)
303 SMAP *p, *t;
304 size_t cnt_orig;
307 * Find the line in the map, and count the number of screen lines
308 * which display any part of the deleted line.
310 for (p = HMAP; p->lno != lno; ++p);
311 if (O_ISSET(sp, O_LEFTRIGHT))
312 cnt_orig = 1;
313 else
314 for (cnt_orig = 1, t = p + 1;
315 t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
317 HANDLE_WEIRDNESS(cnt_orig);
319 /* Delete that many lines from the screen. */
320 (void)sp->gp->scr_move(sp, p - HMAP, 0);
321 if (vs_deleteln(sp, cnt_orig))
322 return (1);
324 /* Shift the screen map up. */
325 memmove(p, p + cnt_orig, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
327 /* Decrement the line numbers for the rest of the map. */
328 for (t = TMAP - cnt_orig; p <= t; ++p)
329 --p->lno;
331 /* Display the new lines. */
332 for (p = TMAP - cnt_orig;;) {
333 if (p < TMAP && vs_sm_next(sp, p, p + 1))
334 return (1);
335 /* vs_sm_next() flushed the cache. */
336 if (vs_line(sp, ++p, NULL, NULL))
337 return (1);
338 if (p == TMAP)
339 break;
341 return (0);
345 * vs_sm_insert --
346 * Insert a line into the SMAP.
348 static int
349 vs_sm_insert(SCR *sp, db_recno_t lno)
351 SMAP *p, *t;
352 size_t cnt_orig, cnt, coff;
354 /* Save the offset. */
355 coff = HMAP->coff;
358 * Find the line in the map, find out how many screen lines
359 * needed to display the line.
361 for (p = HMAP; p->lno != lno; ++p);
363 cnt_orig = vs_screens(sp, lno, NULL);
364 HANDLE_WEIRDNESS(cnt_orig);
367 * The lines left in the screen override the number of screen
368 * lines in the inserted line.
370 cnt = (TMAP - p) + 1;
371 if (cnt_orig > cnt)
372 cnt_orig = cnt;
374 /* Push down that many lines. */
375 (void)sp->gp->scr_move(sp, p - HMAP, 0);
376 if (vs_insertln(sp, cnt_orig))
377 return (1);
379 /* Shift the screen map down. */
380 memmove(p + cnt_orig, p, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
382 /* Increment the line numbers for the rest of the map. */
383 for (t = p + cnt_orig; t <= TMAP; ++t)
384 ++t->lno;
386 /* Fill in the SMAP for the new lines, and display. */
387 for (cnt = 1, t = p; cnt <= cnt_orig; ++t, ++cnt) {
388 t->lno = lno;
389 t->coff = coff;
390 t->soff = cnt;
391 SMAP_FLUSH(t);
392 if (vs_line(sp, t, NULL, NULL))
393 return (1);
395 return (0);
399 * vs_sm_reset --
400 * Reset a line in the SMAP.
402 static int
403 vs_sm_reset(SCR *sp, db_recno_t lno)
405 SMAP *p, *t;
406 size_t cnt_orig, cnt_new, cnt, diff;
409 * See if the number of on-screen rows taken up by the old display
410 * for the line is the same as the number needed for the new one.
411 * If so, repaint, otherwise do it the hard way.
413 for (p = HMAP; p->lno != lno; ++p);
414 if (O_ISSET(sp, O_LEFTRIGHT)) {
415 t = p;
416 cnt_orig = cnt_new = 1;
417 } else {
418 for (cnt_orig = 0,
419 t = p; t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
420 cnt_new = vs_screens(sp, lno, NULL);
423 HANDLE_WEIRDNESS(cnt_orig);
425 if (cnt_orig == cnt_new) {
426 do {
427 SMAP_FLUSH(p);
428 if (vs_line(sp, p, NULL, NULL))
429 return (1);
430 } while (++p < t);
431 return (0);
434 if (cnt_orig < cnt_new) {
435 /* Get the difference. */
436 diff = cnt_new - cnt_orig;
439 * The lines left in the screen override the number of screen
440 * lines in the inserted line.
442 cnt = (TMAP - p) + 1;
443 if (diff > cnt)
444 diff = cnt;
446 /* If there are any following lines, push them down. */
447 if (cnt > 1) {
448 (void)sp->gp->scr_move(sp, p - HMAP, 0);
449 if (vs_insertln(sp, diff))
450 return (1);
452 /* Shift the screen map down. */
453 memmove(p + diff, p,
454 (((TMAP - p) - diff) + 1) * sizeof(SMAP));
457 /* Fill in the SMAP for the replaced line, and display. */
458 for (cnt = 1, t = p; cnt_new-- && t <= TMAP; ++t, ++cnt) {
459 t->lno = lno;
460 t->soff = cnt;
461 SMAP_FLUSH(t);
462 if (vs_line(sp, t, NULL, NULL))
463 return (1);
465 } else {
466 /* Get the difference. */
467 diff = cnt_orig - cnt_new;
469 /* Delete that many lines from the screen. */
470 (void)sp->gp->scr_move(sp, p - HMAP, 0);
471 if (vs_deleteln(sp, diff))
472 return (1);
474 /* Shift the screen map up. */
475 memmove(p, p + diff, (((TMAP - p) - diff) + 1) * sizeof(SMAP));
477 /* Fill in the SMAP for the replaced line, and display. */
478 for (cnt = 1, t = p; cnt_new--; ++t, ++cnt) {
479 t->lno = lno;
480 t->soff = cnt;
481 SMAP_FLUSH(t);
482 if (vs_line(sp, t, NULL, NULL))
483 return (1);
486 /* Display the new lines at the bottom of the screen. */
487 for (t = TMAP - diff;;) {
488 if (t < TMAP && vs_sm_next(sp, t, t + 1))
489 return (1);
490 /* vs_sm_next() flushed the cache. */
491 if (vs_line(sp, ++t, NULL, NULL))
492 return (1);
493 if (t == TMAP)
494 break;
497 return (0);
501 * vs_sm_scroll
502 * Scroll the SMAP up/down count logical lines. Different
503 * semantics based on the vi command, *sigh*.
505 * PUBLIC: int vs_sm_scroll __P((SCR *, MARK *, db_recno_t, scroll_t));
508 vs_sm_scroll(SCR *sp, MARK *rp, db_recno_t count, scroll_t scmd)
510 SMAP *smp;
513 * Invalidate the cursor. The line is probably going to change,
514 * (although for ^E and ^Y it may not). In any case, the scroll
515 * routines move the cursor to draw things.
517 F_SET(VIP(sp), VIP_CUR_INVALID);
519 /* Find the cursor in the screen. */
520 if (vs_sm_cursor(sp, &smp))
521 return (1);
523 switch (scmd) {
524 case CNTRL_B:
525 case CNTRL_U:
526 case CNTRL_Y:
527 case Z_CARAT:
528 if (vs_sm_down(sp, rp, count, scmd, smp))
529 return (1);
530 break;
531 case CNTRL_D:
532 case CNTRL_E:
533 case CNTRL_F:
534 case Z_PLUS:
535 if (vs_sm_up(sp, rp, count, scmd, smp))
536 return (1);
537 break;
538 default:
539 abort();
543 * !!!
544 * If we're at the start of a line, go for the first non-blank.
545 * This makes it look like the old vi, even though we're moving
546 * around by logical lines, not physical ones.
548 * XXX
549 * In the presence of a long line, which has more than a screen
550 * width of leading spaces, this code can cause a cursor warp.
551 * Live with it.
553 if (scmd != CNTRL_E && scmd != CNTRL_Y &&
554 rp->cno == 0 && nonblank(sp, rp->lno, &rp->cno))
555 return (1);
557 return (0);
561 * vs_sm_up --
562 * Scroll the SMAP up count logical lines.
564 static int
565 vs_sm_up(SCR *sp, MARK *rp, db_recno_t count, scroll_t scmd, SMAP *smp)
567 int cursor_set, echanged, zset;
568 SMAP *ssmp, s1, s2;
571 * Check to see if movement is possible.
573 * Get the line after the map. If that line is a new one (and if
574 * O_LEFTRIGHT option is set, this has to be true), and the next
575 * line doesn't exist, and the cursor doesn't move, or the cursor
576 * isn't even on the screen, or the cursor is already at the last
577 * line in the map, it's an error. If that test succeeded because
578 * the cursor wasn't at the end of the map, test to see if the map
579 * is mostly empty.
581 if (vs_sm_next(sp, TMAP, &s1))
582 return (1);
583 if (s1.lno > TMAP->lno && !db_exist(sp, s1.lno)) {
584 if (scmd == CNTRL_E || scmd == Z_PLUS || smp == TMAP) {
585 v_eof(sp, NULL);
586 return (1);
588 if (vs_sm_next(sp, smp, &s1))
589 return (1);
590 if (s1.lno > smp->lno && !db_exist(sp, s1.lno)) {
591 v_eof(sp, NULL);
592 return (1);
597 * Small screens: see vs_refresh.c section 6a.
599 * If it's a small screen, and the movement isn't larger than a
600 * screen, i.e some context will remain, open up the screen and
601 * display by scrolling. In this case, the cursor moves down one
602 * line for each line displayed. Otherwise, erase/compress and
603 * repaint, and move the cursor to the first line in the screen.
604 * Note, the ^F command is always in the latter case, for historical
605 * reasons.
607 cursor_set = 0;
608 if (IS_SMALL(sp)) {
609 if (count >= sp->t_maxrows || scmd == CNTRL_F) {
610 s1 = TMAP[0];
611 if (vs_sm_erase(sp))
612 return (1);
613 for (; count--; s1 = s2) {
614 if (vs_sm_next(sp, &s1, &s2))
615 return (1);
616 if (s2.lno != s1.lno && !db_exist(sp, s2.lno))
617 break;
619 TMAP[0] = s2;
620 if (vs_sm_fill(sp, OOBLNO, P_BOTTOM))
621 return (1);
622 return (vs_sm_position(sp, rp, 0, P_TOP));
624 cursor_set = scmd == CNTRL_E || vs_sm_cursor(sp, &ssmp);
625 for (; count &&
626 sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
627 if (vs_sm_next(sp, TMAP, &s1))
628 return (1);
629 if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))
630 break;
631 *++TMAP = s1;
632 /* vs_sm_next() flushed the cache. */
633 if (vs_line(sp, TMAP, NULL, NULL))
634 return (1);
636 if (!cursor_set)
637 ++ssmp;
639 if (!cursor_set) {
640 rp->lno = ssmp->lno;
641 rp->cno = ssmp->c_sboff;
643 if (count == 0)
644 return (0);
647 for (echanged = zset = 0; count; --count) {
648 /* Decide what would show up on the screen. */
649 if (vs_sm_next(sp, TMAP, &s1))
650 return (1);
652 /* If the line doesn't exist, we're done. */
653 if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))
654 break;
656 /* Scroll the screen cursor up one logical line. */
657 if (vs_sm_1up(sp))
658 return (1);
659 switch (scmd) {
660 case CNTRL_E:
661 if (smp > HMAP)
662 --smp;
663 else
664 echanged = 1;
665 break;
666 case Z_PLUS:
667 if (zset) {
668 if (smp > HMAP)
669 --smp;
670 } else {
671 smp = TMAP;
672 zset = 1;
674 /* FALLTHROUGH */
675 default:
676 break;
680 if (cursor_set)
681 return(0);
683 switch (scmd) {
684 case CNTRL_E:
686 * On a ^E that was forced to change lines, try and keep the
687 * cursor as close as possible to the last position, but also
688 * set it up so that the next "real" movement will return the
689 * cursor to the closest position to the last real movement.
691 if (echanged) {
692 rp->lno = smp->lno;
693 rp->cno = vs_colpos(sp, smp->lno,
694 (O_ISSET(sp, O_LEFTRIGHT) ?
695 smp->coff : (smp->soff - 1) * sp->cols) +
696 sp->rcm % sp->cols);
698 return (0);
699 case CNTRL_F:
701 * If there are more lines, the ^F command is positioned at
702 * the first line of the screen.
704 if (!count) {
705 smp = HMAP;
706 break;
708 /* FALLTHROUGH */
709 case CNTRL_D:
711 * The ^D and ^F commands move the cursor towards EOF
712 * if there are more lines to move. Check to be sure
713 * the lines actually exist. (They may not if the
714 * file is smaller than the screen.)
716 for (; count; --count, ++smp)
717 if (smp == TMAP || !db_exist(sp, smp[1].lno))
718 break;
719 break;
720 case Z_PLUS:
721 /* The z+ command moves the cursor to the first new line. */
722 break;
723 default:
724 abort();
727 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
728 return (1);
729 rp->lno = smp->lno;
730 rp->cno = smp->c_scoff == 255 ? 0 : smp->c_sboff;
731 return (0);
735 * vs_sm_1up --
736 * Scroll the SMAP up one.
738 * PUBLIC: int vs_sm_1up __P((SCR *));
741 vs_sm_1up(SCR *sp)
744 * Delete the top line of the screen. Shift the screen map
745 * up and display a new line at the bottom of the screen.
747 (void)sp->gp->scr_move(sp, 0, 0);
748 if (vs_deleteln(sp, 1))
749 return (1);
751 /* One-line screens can fail. */
752 if (IS_ONELINE(sp)) {
753 if (vs_sm_next(sp, TMAP, TMAP))
754 return (1);
755 } else {
756 memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP));
757 if (vs_sm_next(sp, TMAP - 1, TMAP))
758 return (1);
760 /* vs_sm_next() flushed the cache. */
761 return (vs_line(sp, TMAP, NULL, NULL));
765 * vs_deleteln --
766 * Delete a line a la curses, make sure to put the information
767 * line and other screens back.
769 static int
770 vs_deleteln(SCR *sp, int cnt)
772 GS *gp;
773 size_t oldy, oldx;
775 gp = sp->gp;
777 /* If the screen is vertically split, we can't scroll it. */
778 if (IS_VSPLIT(sp)) {
779 F_SET(sp, SC_SCR_REDRAW);
780 return (0);
783 if (IS_ONELINE(sp))
784 (void)gp->scr_clrtoeol(sp);
785 else {
786 (void)gp->scr_cursor(sp, &oldy, &oldx);
787 while (cnt--) {
788 (void)gp->scr_deleteln(sp);
789 (void)gp->scr_move(sp, LASTLINE(sp), 0);
790 (void)gp->scr_insertln(sp);
791 (void)gp->scr_move(sp, oldy, oldx);
794 return (0);
798 * vs_sm_down --
799 * Scroll the SMAP down count logical lines.
801 static int
802 vs_sm_down(SCR *sp, MARK *rp, db_recno_t count, scroll_t scmd, SMAP *smp)
804 SMAP *ssmp, s1, s2;
805 int cursor_set, ychanged, zset;
807 /* Check to see if movement is possible. */
808 if (HMAP->lno == 1 &&
809 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) &&
810 (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) {
811 v_sof(sp, NULL);
812 return (1);
816 * Small screens: see vs_refresh.c section 6a.
818 * If it's a small screen, and the movement isn't larger than a
819 * screen, i.e some context will remain, open up the screen and
820 * display by scrolling. In this case, the cursor moves up one
821 * line for each line displayed. Otherwise, erase/compress and
822 * repaint, and move the cursor to the first line in the screen.
823 * Note, the ^B command is always in the latter case, for historical
824 * reasons.
826 cursor_set = scmd == CNTRL_Y;
827 if (IS_SMALL(sp)) {
828 if (count >= sp->t_maxrows || scmd == CNTRL_B) {
829 s1 = HMAP[0];
830 if (vs_sm_erase(sp))
831 return (1);
832 for (; count--; s1 = s2) {
833 if (vs_sm_prev(sp, &s1, &s2))
834 return (1);
835 if (s2.lno == 1 &&
836 (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1))
837 break;
839 HMAP[0] = s2;
840 if (vs_sm_fill(sp, OOBLNO, P_TOP))
841 return (1);
842 return (vs_sm_position(sp, rp, 0, P_BOTTOM));
844 cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp);
845 for (; count &&
846 sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
847 if (HMAP->lno == 1 &&
848 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
849 break;
850 ++TMAP;
851 if (vs_sm_1down(sp))
852 return (1);
854 if (!cursor_set) {
855 rp->lno = ssmp->lno;
856 rp->cno = ssmp->c_sboff;
858 if (count == 0)
859 return (0);
862 for (ychanged = zset = 0; count; --count) {
863 /* If the line doesn't exist, we're done. */
864 if (HMAP->lno == 1 &&
865 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
866 break;
868 /* Scroll the screen and cursor down one logical line. */
869 if (vs_sm_1down(sp))
870 return (1);
871 switch (scmd) {
872 case CNTRL_Y:
873 if (smp < TMAP)
874 ++smp;
875 else
876 ychanged = 1;
877 break;
878 case Z_CARAT:
879 if (zset) {
880 if (smp < TMAP)
881 ++smp;
882 } else {
883 smp = HMAP;
884 zset = 1;
886 /* FALLTHROUGH */
887 default:
888 break;
892 if (scmd != CNTRL_Y && cursor_set)
893 return(0);
895 switch (scmd) {
896 case CNTRL_B:
898 * If there are more lines, the ^B command is positioned at
899 * the last line of the screen. However, the line may not
900 * exist.
902 if (!count) {
903 for (smp = TMAP; smp > HMAP; --smp)
904 if (db_exist(sp, smp->lno))
905 break;
906 break;
908 /* FALLTHROUGH */
909 case CNTRL_U:
911 * The ^B and ^U commands move the cursor towards SOF
912 * if there are more lines to move.
914 if (count < smp - HMAP)
915 smp -= count;
916 else
917 smp = HMAP;
918 break;
919 case CNTRL_Y:
921 * On a ^Y that was forced to change lines, try and keep the
922 * cursor as close as possible to the last position, but also
923 * set it up so that the next "real" movement will return the
924 * cursor to the closest position to the last real movement.
926 if (ychanged) {
927 rp->lno = smp->lno;
928 rp->cno = vs_colpos(sp, smp->lno,
929 (O_ISSET(sp, O_LEFTRIGHT) ?
930 smp->coff : (smp->soff - 1) * sp->cols) +
931 sp->rcm % sp->cols);
933 return (0);
934 case Z_CARAT:
935 /* The z^ command moves the cursor to the first new line. */
936 break;
937 default:
938 abort();
941 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
942 return (1);
943 rp->lno = smp->lno;
944 rp->cno = smp->c_scoff == 255 ? 0 : smp->c_sboff;
945 return (0);
949 * vs_sm_erase --
950 * Erase the small screen area for the scrolling functions.
952 static int
953 vs_sm_erase(SCR *sp)
955 GS *gp;
957 gp = sp->gp;
958 (void)gp->scr_move(sp, LASTLINE(sp), 0);
959 (void)gp->scr_clrtoeol(sp);
960 for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) {
961 (void)gp->scr_move(sp, TMAP - HMAP, 0);
962 (void)gp->scr_clrtoeol(sp);
964 return (0);
968 * vs_sm_1down --
969 * Scroll the SMAP down one.
971 * PUBLIC: int vs_sm_1down __P((SCR *));
974 vs_sm_1down(SCR *sp)
977 * Insert a line at the top of the screen. Shift the screen map
978 * down and display a new line at the top of the screen.
980 (void)sp->gp->scr_move(sp, 0, 0);
981 if (vs_insertln(sp, 1))
982 return (1);
984 /* One-line screens can fail. */
985 if (IS_ONELINE(sp)) {
986 if (vs_sm_prev(sp, HMAP, HMAP))
987 return (1);
988 } else {
989 memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP));
990 if (vs_sm_prev(sp, HMAP + 1, HMAP))
991 return (1);
993 /* vs_sm_prev() flushed the cache. */
994 return (vs_line(sp, HMAP, NULL, NULL));
998 * vs_insertln --
999 * Insert a line a la curses, make sure to put the information
1000 * line and other screens back.
1002 static int
1003 vs_insertln(SCR *sp, int cnt)
1005 GS *gp;
1006 size_t oldy, oldx;
1008 gp = sp->gp;
1010 /* If the screen is vertically split, we can't scroll it. */
1011 if (IS_VSPLIT(sp)) {
1012 F_SET(sp, SC_SCR_REDRAW);
1013 return (0);
1016 if (IS_ONELINE(sp)) {
1017 (void)gp->scr_move(sp, LASTLINE(sp), 0);
1018 (void)gp->scr_clrtoeol(sp);
1019 } else {
1020 (void)gp->scr_cursor(sp, &oldy, &oldx);
1021 while (cnt--) {
1022 (void)gp->scr_move(sp, LASTLINE(sp) - 1, 0);
1023 (void)gp->scr_deleteln(sp);
1024 (void)gp->scr_move(sp, oldy, oldx);
1025 (void)gp->scr_insertln(sp);
1028 return (0);
1032 * vs_sm_next --
1033 * Fill in the next entry in the SMAP.
1035 * PUBLIC: int vs_sm_next __P((SCR *, SMAP *, SMAP *));
1038 vs_sm_next(SCR *sp, SMAP *p, SMAP *t)
1040 size_t lcnt;
1042 SMAP_FLUSH(t);
1043 if (O_ISSET(sp, O_LEFTRIGHT)) {
1044 t->lno = p->lno + 1;
1045 t->coff = p->coff;
1046 } else {
1047 lcnt = vs_screens(sp, p->lno, NULL);
1048 if (lcnt == p->soff) {
1049 t->lno = p->lno + 1;
1050 t->soff = 1;
1051 } else {
1052 t->lno = p->lno;
1053 t->soff = p->soff + 1;
1056 return (0);
1060 * vs_sm_prev --
1061 * Fill in the previous entry in the SMAP.
1063 * PUBLIC: int vs_sm_prev __P((SCR *, SMAP *, SMAP *));
1066 vs_sm_prev(SCR *sp, SMAP *p, SMAP *t)
1068 SMAP_FLUSH(t);
1069 if (O_ISSET(sp, O_LEFTRIGHT)) {
1070 t->lno = p->lno - 1;
1071 t->coff = p->coff;
1072 } else {
1073 if (p->soff != 1) {
1074 t->lno = p->lno;
1075 t->soff = p->soff - 1;
1076 } else {
1077 t->lno = p->lno - 1;
1078 t->soff = vs_screens(sp, t->lno, NULL);
1081 return (t->lno == 0);
1085 * vs_sm_cursor --
1086 * Return the SMAP entry referenced by the cursor.
1088 * PUBLIC: int vs_sm_cursor __P((SCR *, SMAP **));
1091 vs_sm_cursor(SCR *sp, SMAP **smpp)
1093 SMAP *p;
1095 /* See if the cursor is not in the map. */
1096 if (sp->lno < HMAP->lno || sp->lno > TMAP->lno)
1097 return (1);
1099 /* Find the first occurence of the line. */
1100 for (p = HMAP; p->lno != sp->lno; ++p);
1102 /* Fill in the map information until we find the right line. */
1103 for (; p <= TMAP; ++p) {
1104 /* Short lines are common and easy to detect. */
1105 if (p != TMAP && (p + 1)->lno != p->lno) {
1106 *smpp = p;
1107 return (0);
1109 if (!SMAP_CACHE(p) && vs_line(sp, p, NULL, NULL))
1110 return (1);
1111 if (p->c_eboff >= sp->cno) {
1112 *smpp = p;
1113 return (0);
1117 /* It was past the end of the map after all. */
1118 return (1);
1122 * vs_sm_position --
1123 * Return the line/column of the top, middle or last line on the screen.
1124 * (The vi H, M and L commands.) Here because only the screen routines
1125 * know what's really out there.
1127 * PUBLIC: int vs_sm_position __P((SCR *, MARK *, u_long, pos_t));
1130 vs_sm_position(SCR *sp, MARK *rp, u_long cnt, pos_t pos)
1132 SMAP *smp;
1133 db_recno_t last;
1135 switch (pos) {
1136 case P_TOP:
1138 * !!!
1139 * Historically, an invalid count to the H command failed.
1140 * We do nothing special here, just making sure that H in
1141 * an empty screen works.
1143 if (cnt > TMAP - HMAP)
1144 goto sof;
1145 smp = HMAP + cnt;
1146 if (cnt && !db_exist(sp, smp->lno)) {
1147 sof: msgq(sp, M_BERR, "220|Movement past the end-of-screen");
1148 return (1);
1150 break;
1151 case P_MIDDLE:
1153 * !!!
1154 * Historically, a count to the M command was ignored.
1155 * If the screen isn't filled, find the middle of what's
1156 * real and move there.
1158 if (!db_exist(sp, TMAP->lno)) {
1159 if (db_last(sp, &last))
1160 return (1);
1161 for (smp = TMAP; smp->lno > last && smp > HMAP; --smp);
1162 if (smp > HMAP)
1163 smp -= (smp - HMAP) / 2;
1164 } else
1165 smp = (HMAP + (TMAP - HMAP) / 2) + cnt;
1166 break;
1167 case P_BOTTOM:
1169 * !!!
1170 * Historically, an invalid count to the L command failed.
1171 * If the screen isn't filled, find the bottom of what's
1172 * real and try to offset from there.
1174 if (cnt > TMAP - HMAP)
1175 goto eof;
1176 smp = TMAP - cnt;
1177 if (!db_exist(sp, smp->lno)) {
1178 if (db_last(sp, &last))
1179 return (1);
1180 for (; smp->lno > last && smp > HMAP; --smp);
1181 if (cnt > smp - HMAP) {
1182 eof: msgq(sp, M_BERR,
1183 "221|Movement past the beginning-of-screen");
1184 return (1);
1186 smp -= cnt;
1188 break;
1189 default:
1190 abort();
1193 /* Make sure that the cached information is valid. */
1194 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
1195 return (1);
1196 rp->lno = smp->lno;
1197 rp->cno = smp->c_sboff;
1199 return (0);
1203 * vs_sm_nlines --
1204 * Return the number of screen lines from an SMAP entry to the
1205 * start of some file line, less than a maximum value.
1207 * PUBLIC: db_recno_t vs_sm_nlines __P((SCR *, SMAP *, db_recno_t, size_t));
1209 db_recno_t
1210 vs_sm_nlines(SCR *sp, SMAP *from_sp, db_recno_t to_lno, size_t max)
1212 db_recno_t lno, lcnt;
1214 if (O_ISSET(sp, O_LEFTRIGHT))
1215 return (from_sp->lno > to_lno ?
1216 from_sp->lno - to_lno : to_lno - from_sp->lno);
1218 if (from_sp->lno == to_lno)
1219 return (from_sp->soff - 1);
1221 if (from_sp->lno > to_lno) {
1222 lcnt = from_sp->soff - 1; /* Correct for off-by-one. */
1223 for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;)
1224 lcnt += vs_screens(sp, lno, NULL);
1225 } else {
1226 lno = from_sp->lno;
1227 lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1;
1228 for (; ++lno < to_lno && lcnt <= max;)
1229 lcnt += vs_screens(sp, lno, NULL);
1231 return (lcnt);