align CHAR_T string in log
[nvi.git] / vi / vs_smap.c
blobfcf1b383acdc2f74e2c35c69191c8ef4e11d5ec8
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.28 2000/04/30 16:36:14 skimo Exp $ (Berkeley) $Date: 2000/04/30 16:36:14 $";
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(sp, lno, op)
46 SCR *sp;
47 db_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) ||
68 (op == LINE_INSERT && lno == 1)) &&
69 !db_exist(sp, 2)) {
70 lno = 1;
71 op = LINE_RESET;
74 /* Appending is the same as inserting, if the line is incremented. */
75 if (op == LINE_APPEND) {
76 ++lno;
77 op = LINE_INSERT;
80 /* Ignore the change if the line is after the map. */
81 if (lno > TMAP->lno)
82 return (0);
85 * If the line is before the map, and it's a decrement, decrement
86 * the map. If it's an increment, increment the map. Otherwise,
87 * ignore it.
89 if (lno < HMAP->lno) {
90 switch (op) {
91 case LINE_APPEND:
92 abort();
93 /* NOTREACHED */
94 case LINE_DELETE:
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_INSERT:
102 for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
103 ++p->lno;
104 if (sp->lno >= lno)
105 ++sp->lno;
106 F_SET(vip, VIP_N_RENUMBER);
107 break;
108 case LINE_RESET:
109 break;
111 return (0);
114 F_SET(vip, VIP_N_REFRESH);
117 * Invalidate the line size cache, and invalidate the cursor if it's
118 * on this line,
120 VI_SCR_CFLUSH(vip);
121 if (sp->lno == lno)
122 F_SET(vip, VIP_CUR_INVALID);
125 * If ex modifies the screen after ex output is already on the screen
126 * or if we've switched into ex canonical mode, don't touch it -- we'll
127 * get scrolling wrong, at best.
129 if (!F_ISSET(sp, SC_TINPUT_INFO) &&
130 (F_ISSET(sp, SC_SCR_EXWROTE) || VIP(sp)->totalcount > 1)) {
131 F_SET(vip, VIP_N_EX_REDRAW);
132 return (0);
135 /* Save and restore the cursor for these routines. */
136 (void)sp->gp->scr_cursor(sp, &oldy, &oldx);
138 switch (op) {
139 case LINE_DELETE:
140 if (vs_sm_delete(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_INSERT:
147 if (vs_sm_insert(sp, lno))
148 return (1);
149 if (sp->lno > lno)
150 ++sp->lno;
151 F_SET(vip, VIP_N_RENUMBER);
152 break;
153 case LINE_RESET:
154 if (vs_sm_reset(sp, lno))
155 return (1);
156 break;
157 default:
158 abort();
161 (void)sp->gp->scr_move(sp, oldy, oldx);
162 return (0);
166 * vs_sm_fill --
167 * Fill in the screen map, placing the specified line at the
168 * right position. There isn't any way to tell if an SMAP
169 * entry has been filled in, so this routine had better be
170 * called with P_FILL set before anything else is done.
172 * !!!
173 * Unexported interface: if lno is OOBLNO, P_TOP means that the HMAP
174 * slot is already filled in, P_BOTTOM means that the TMAP slot is
175 * already filled in, and we just finish up the job.
177 * PUBLIC: int vs_sm_fill __P((SCR *, db_recno_t, pos_t));
180 vs_sm_fill(sp, lno, pos)
181 SCR *sp;
182 db_recno_t lno;
183 pos_t pos;
185 SMAP *p, tmp;
186 size_t cnt;
188 /* Flush all cached information from the SMAP. */
189 for (p = HMAP, cnt = sp->t_rows; cnt--; ++p)
190 SMAP_FLUSH(p);
193 * If the map is filled, the screen must be redrawn.
195 * XXX
196 * This is a bug. We should try and figure out if the desired line
197 * is already in the map or close by -- scrolling the screen would
198 * be a lot better than redrawing.
200 F_SET(sp, SC_SCR_REDRAW);
202 switch (pos) {
203 case P_FILL:
204 tmp.lno = 1;
205 tmp.coff = 0;
206 tmp.soff = 1;
208 /* See if less than half a screen from the top. */
209 if (vs_sm_nlines(sp,
210 &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {
211 lno = 1;
212 goto top;
215 /* See if less than half a screen from the bottom. */
216 if (db_last(sp, &tmp.lno))
217 return (1);
218 tmp.coff = 0;
219 tmp.soff = vs_screens(sp, tmp.lno, NULL);
220 if (vs_sm_nlines(sp,
221 &tmp, lno, HALFTEXT(sp)) <= HALFTEXT(sp)) {
222 TMAP->lno = tmp.lno;
223 TMAP->coff = tmp.coff;
224 TMAP->soff = tmp.soff;
225 goto bottom;
227 goto middle;
228 case P_TOP:
229 if (lno != OOBLNO) {
230 top: HMAP->lno = lno;
231 HMAP->coff = 0;
232 HMAP->soff = 1;
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;
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);
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;
269 break;
270 default:
271 abort();
273 return (0);
276 * Try and put *something* on the screen. If this fails, we have a
277 * serious hard error.
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);
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.
295 #define HANDLE_WEIRDNESS(cnt) { \
296 if (cnt >= sp->t_rows) { \
297 F_SET(sp, SC_SCR_REFORMAT); \
298 return (0); \
303 * vs_sm_delete --
304 * Delete a line out of the SMAP.
306 static int
307 vs_sm_delete(sp, lno)
308 SCR *sp;
309 db_recno_t lno;
311 SMAP *p, *t;
312 size_t cnt_orig;
315 * Find the line in the map, and count the number of screen lines
316 * which display any part of the deleted line.
318 for (p = HMAP; p->lno != lno; ++p);
319 if (O_ISSET(sp, O_LEFTRIGHT))
320 cnt_orig = 1;
321 else
322 for (cnt_orig = 1, t = p + 1;
323 t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
325 HANDLE_WEIRDNESS(cnt_orig);
327 /* Delete that many lines from the screen. */
328 (void)sp->gp->scr_move(sp, p - HMAP, 0);
329 if (vs_deleteln(sp, cnt_orig))
330 return (1);
332 /* Shift the screen map up. */
333 memmove(p, p + cnt_orig, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
335 /* Decrement the line numbers for the rest of the map. */
336 for (t = TMAP - cnt_orig; p <= t; ++p)
337 --p->lno;
339 /* Display the new lines. */
340 for (p = TMAP - cnt_orig;;) {
341 if (p < TMAP && vs_sm_next(sp, p, p + 1))
342 return (1);
343 /* vs_sm_next() flushed the cache. */
344 if (vs_line(sp, ++p, NULL, NULL))
345 return (1);
346 if (p == TMAP)
347 break;
349 return (0);
353 * vs_sm_insert --
354 * Insert a line into the SMAP.
356 static int
357 vs_sm_insert(sp, lno)
358 SCR *sp;
359 db_recno_t lno;
361 SMAP *p, *t;
362 size_t cnt_orig, cnt, coff;
364 /* Save the offset. */
365 coff = HMAP->coff;
368 * Find the line in the map, find out how many screen lines
369 * needed to display the line.
371 for (p = HMAP; p->lno != lno; ++p);
373 cnt_orig = vs_screens(sp, lno, NULL);
374 HANDLE_WEIRDNESS(cnt_orig);
377 * The lines left in the screen override the number of screen
378 * lines in the inserted line.
380 cnt = (TMAP - p) + 1;
381 if (cnt_orig > cnt)
382 cnt_orig = cnt;
384 /* Push down that many lines. */
385 (void)sp->gp->scr_move(sp, p - HMAP, 0);
386 if (vs_insertln(sp, cnt_orig))
387 return (1);
389 /* Shift the screen map down. */
390 memmove(p + cnt_orig, p, (((TMAP - p) - cnt_orig) + 1) * sizeof(SMAP));
392 /* Increment the line numbers for the rest of the map. */
393 for (t = p + cnt_orig; t <= TMAP; ++t)
394 ++t->lno;
396 /* Fill in the SMAP for the new lines, and display. */
397 for (cnt = 1, t = p; cnt <= cnt_orig; ++t, ++cnt) {
398 t->lno = lno;
399 t->coff = coff;
400 t->soff = cnt;
401 SMAP_FLUSH(t);
402 if (vs_line(sp, t, NULL, NULL))
403 return (1);
405 return (0);
409 * vs_sm_reset --
410 * Reset a line in the SMAP.
412 static int
413 vs_sm_reset(sp, lno)
414 SCR *sp;
415 db_recno_t lno;
417 SMAP *p, *t;
418 size_t cnt_orig, cnt_new, cnt, diff;
421 * See if the number of on-screen rows taken up by the old display
422 * for the line is the same as the number needed for the new one.
423 * If so, repaint, otherwise do it the hard way.
425 for (p = HMAP; p->lno != lno; ++p);
426 if (O_ISSET(sp, O_LEFTRIGHT)) {
427 t = p;
428 cnt_orig = cnt_new = 1;
429 } else {
430 for (cnt_orig = 0,
431 t = p; t <= TMAP && t->lno == lno; ++cnt_orig, ++t);
432 cnt_new = vs_screens(sp, lno, NULL);
435 HANDLE_WEIRDNESS(cnt_orig);
437 if (cnt_orig == cnt_new) {
438 do {
439 SMAP_FLUSH(p);
440 if (vs_line(sp, p, NULL, NULL))
441 return (1);
442 } while (++p < t);
443 return (0);
446 if (cnt_orig < cnt_new) {
447 /* Get the difference. */
448 diff = cnt_new - cnt_orig;
451 * The lines left in the screen override the number of screen
452 * lines in the inserted line.
454 cnt = (TMAP - p) + 1;
455 if (diff > cnt)
456 diff = cnt;
458 /* If there are any following lines, push them down. */
459 if (cnt > 1) {
460 (void)sp->gp->scr_move(sp, p - HMAP, 0);
461 if (vs_insertln(sp, diff))
462 return (1);
464 /* Shift the screen map down. */
465 memmove(p + diff, p,
466 (((TMAP - p) - diff) + 1) * sizeof(SMAP));
469 /* Fill in the SMAP for the replaced line, and display. */
470 for (cnt = 1, t = p; cnt_new-- && t <= TMAP; ++t, ++cnt) {
471 t->lno = lno;
472 t->soff = cnt;
473 SMAP_FLUSH(t);
474 if (vs_line(sp, t, NULL, NULL))
475 return (1);
477 } else {
478 /* Get the difference. */
479 diff = cnt_orig - cnt_new;
481 /* Delete that many lines from the screen. */
482 (void)sp->gp->scr_move(sp, p - HMAP, 0);
483 if (vs_deleteln(sp, diff))
484 return (1);
486 /* Shift the screen map up. */
487 memmove(p, p + diff, (((TMAP - p) - diff) + 1) * sizeof(SMAP));
489 /* Fill in the SMAP for the replaced line, and display. */
490 for (cnt = 1, t = p; cnt_new--; ++t, ++cnt) {
491 t->lno = lno;
492 t->soff = cnt;
493 SMAP_FLUSH(t);
494 if (vs_line(sp, t, NULL, NULL))
495 return (1);
498 /* Display the new lines at the bottom of the screen. */
499 for (t = TMAP - diff;;) {
500 if (t < TMAP && vs_sm_next(sp, t, t + 1))
501 return (1);
502 /* vs_sm_next() flushed the cache. */
503 if (vs_line(sp, ++t, NULL, NULL))
504 return (1);
505 if (t == TMAP)
506 break;
509 return (0);
513 * vs_sm_scroll
514 * Scroll the SMAP up/down count logical lines. Different
515 * semantics based on the vi command, *sigh*.
517 * PUBLIC: int vs_sm_scroll __P((SCR *, MARK *, db_recno_t, scroll_t));
520 vs_sm_scroll(sp, rp, count, scmd)
521 SCR *sp;
522 MARK *rp;
523 db_recno_t count;
524 scroll_t scmd;
526 SMAP *smp;
529 * Invalidate the cursor. The line is probably going to change,
530 * (although for ^E and ^Y it may not). In any case, the scroll
531 * routines move the cursor to draw things.
533 F_SET(VIP(sp), VIP_CUR_INVALID);
535 /* Find the cursor in the screen. */
536 if (vs_sm_cursor(sp, &smp))
537 return (1);
539 switch (scmd) {
540 case CNTRL_B:
541 case CNTRL_U:
542 case CNTRL_Y:
543 case Z_CARAT:
544 if (vs_sm_down(sp, rp, count, scmd, smp))
545 return (1);
546 break;
547 case CNTRL_D:
548 case CNTRL_E:
549 case CNTRL_F:
550 case Z_PLUS:
551 if (vs_sm_up(sp, rp, count, scmd, smp))
552 return (1);
553 break;
554 default:
555 abort();
559 * !!!
560 * If we're at the start of a line, go for the first non-blank.
561 * This makes it look like the old vi, even though we're moving
562 * around by logical lines, not physical ones.
564 * XXX
565 * In the presence of a long line, which has more than a screen
566 * width of leading spaces, this code can cause a cursor warp.
567 * Live with it.
569 if (scmd != CNTRL_E && scmd != CNTRL_Y &&
570 rp->cno == 0 && nonblank(sp, rp->lno, &rp->cno))
571 return (1);
573 return (0);
577 * vs_sm_up --
578 * Scroll the SMAP up count logical lines.
580 static int
581 vs_sm_up(sp, rp, count, scmd, smp)
582 SCR *sp;
583 MARK *rp;
584 scroll_t scmd;
585 db_recno_t count;
586 SMAP *smp;
588 int cursor_set, echanged, zset;
589 SMAP *ssmp, s1, s2;
592 * Check to see if movement is possible.
594 * Get the line after the map. If that line is a new one (and if
595 * O_LEFTRIGHT option is set, this has to be true), and the next
596 * line doesn't exist, and the cursor doesn't move, or the cursor
597 * isn't even on the screen, or the cursor is already at the last
598 * line in the map, it's an error. If that test succeeded because
599 * the cursor wasn't at the end of the map, test to see if the map
600 * is mostly empty.
602 if (vs_sm_next(sp, TMAP, &s1))
603 return (1);
604 if (s1.lno > TMAP->lno && !db_exist(sp, s1.lno)) {
605 if (scmd == CNTRL_E || scmd == Z_PLUS || smp == TMAP) {
606 v_eof(sp, NULL);
607 return (1);
609 if (vs_sm_next(sp, smp, &s1))
610 return (1);
611 if (s1.lno > smp->lno && !db_exist(sp, s1.lno)) {
612 v_eof(sp, NULL);
613 return (1);
618 * Small screens: see vs_refresh.c section 6a.
620 * If it's a small screen, and the movement isn't larger than a
621 * screen, i.e some context will remain, open up the screen and
622 * display by scrolling. In this case, the cursor moves down one
623 * line for each line displayed. Otherwise, erase/compress and
624 * repaint, and move the cursor to the first line in the screen.
625 * Note, the ^F command is always in the latter case, for historical
626 * reasons.
628 cursor_set = 0;
629 if (IS_SMALL(sp)) {
630 if (count >= sp->t_maxrows || scmd == CNTRL_F) {
631 s1 = TMAP[0];
632 if (vs_sm_erase(sp))
633 return (1);
634 for (; count--; s1 = s2) {
635 if (vs_sm_next(sp, &s1, &s2))
636 return (1);
637 if (s2.lno != s1.lno && !db_exist(sp, s2.lno))
638 break;
640 TMAP[0] = s2;
641 if (vs_sm_fill(sp, OOBLNO, P_BOTTOM))
642 return (1);
643 return (vs_sm_position(sp, rp, 0, P_TOP));
645 cursor_set = scmd == CNTRL_E || vs_sm_cursor(sp, &ssmp);
646 for (; count &&
647 sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
648 if (vs_sm_next(sp, TMAP, &s1))
649 return (1);
650 if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))
651 break;
652 *++TMAP = s1;
653 /* vs_sm_next() flushed the cache. */
654 if (vs_line(sp, TMAP, NULL, NULL))
655 return (1);
657 if (!cursor_set)
658 ++ssmp;
660 if (!cursor_set) {
661 rp->lno = ssmp->lno;
662 rp->cno = ssmp->c_sboff;
664 if (count == 0)
665 return (0);
668 for (echanged = zset = 0; count; --count) {
669 /* Decide what would show up on the screen. */
670 if (vs_sm_next(sp, TMAP, &s1))
671 return (1);
673 /* If the line doesn't exist, we're done. */
674 if (TMAP->lno != s1.lno && !db_exist(sp, s1.lno))
675 break;
677 /* Scroll the screen cursor up one logical line. */
678 if (vs_sm_1up(sp))
679 return (1);
680 switch (scmd) {
681 case CNTRL_E:
682 if (smp > HMAP)
683 --smp;
684 else
685 echanged = 1;
686 break;
687 case Z_PLUS:
688 if (zset) {
689 if (smp > HMAP)
690 --smp;
691 } else {
692 smp = TMAP;
693 zset = 1;
695 /* FALLTHROUGH */
696 default:
697 break;
701 if (cursor_set)
702 return(0);
704 switch (scmd) {
705 case CNTRL_E:
707 * On a ^E that was forced to change lines, try and keep the
708 * cursor as close as possible to the last position, but also
709 * set it up so that the next "real" movement will return the
710 * cursor to the closest position to the last real movement.
712 if (echanged) {
713 rp->lno = smp->lno;
714 rp->cno = vs_colpos(sp, smp->lno,
715 (O_ISSET(sp, O_LEFTRIGHT) ?
716 smp->coff : (smp->soff - 1) * sp->cols) +
717 sp->rcm % sp->cols);
719 return (0);
720 case CNTRL_F:
722 * If there are more lines, the ^F command is positioned at
723 * the first line of the screen.
725 if (!count) {
726 smp = HMAP;
727 break;
729 /* FALLTHROUGH */
730 case CNTRL_D:
732 * The ^D and ^F commands move the cursor towards EOF
733 * if there are more lines to move. Check to be sure
734 * the lines actually exist. (They may not if the
735 * file is smaller than the screen.)
737 for (; count; --count, ++smp)
738 if (smp == TMAP || !db_exist(sp, smp[1].lno))
739 break;
740 break;
741 case Z_PLUS:
742 /* The z+ command moves the cursor to the first new line. */
743 break;
744 default:
745 abort();
748 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
749 return (1);
750 rp->lno = smp->lno;
751 rp->cno = smp->c_sboff;
752 return (0);
756 * vs_sm_1up --
757 * Scroll the SMAP up one.
759 * PUBLIC: int vs_sm_1up __P((SCR *));
762 vs_sm_1up(sp)
763 SCR *sp;
766 * Delete the top line of the screen. Shift the screen map
767 * up and display a new line at the bottom of the screen.
769 (void)sp->gp->scr_move(sp, 0, 0);
770 if (vs_deleteln(sp, 1))
771 return (1);
773 /* One-line screens can fail. */
774 if (IS_ONELINE(sp)) {
775 if (vs_sm_next(sp, TMAP, TMAP))
776 return (1);
777 } else {
778 memmove(HMAP, HMAP + 1, (sp->rows - 1) * sizeof(SMAP));
779 if (vs_sm_next(sp, TMAP - 1, TMAP))
780 return (1);
782 /* vs_sm_next() flushed the cache. */
783 return (vs_line(sp, TMAP, NULL, NULL));
787 * vs_deleteln --
788 * Delete a line a la curses, make sure to put the information
789 * line and other screens back.
791 static int
792 vs_deleteln(sp, cnt)
793 SCR *sp;
794 int cnt;
796 GS *gp;
797 size_t oldy, oldx;
799 gp = sp->gp;
801 /* If the screen is vertically split, we can't scroll it. */
802 if (IS_VSPLIT(sp)) {
803 F_SET(sp, SC_SCR_REDRAW);
804 return (0);
807 if (IS_ONELINE(sp))
808 (void)gp->scr_clrtoeol(sp);
809 else {
810 (void)gp->scr_cursor(sp, &oldy, &oldx);
811 while (cnt--) {
812 (void)gp->scr_deleteln(sp);
813 (void)gp->scr_move(sp, LASTLINE(sp), 0);
814 (void)gp->scr_insertln(sp);
815 (void)gp->scr_move(sp, oldy, oldx);
818 return (0);
822 * vs_sm_down --
823 * Scroll the SMAP down count logical lines.
825 static int
826 vs_sm_down(sp, rp, count, scmd, smp)
827 SCR *sp;
828 MARK *rp;
829 db_recno_t count;
830 SMAP *smp;
831 scroll_t scmd;
833 SMAP *ssmp, s1, s2;
834 int cursor_set, ychanged, zset;
836 /* Check to see if movement is possible. */
837 if (HMAP->lno == 1 &&
838 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1) &&
839 (scmd == CNTRL_Y || scmd == Z_CARAT || smp == HMAP)) {
840 v_sof(sp, NULL);
841 return (1);
845 * Small screens: see vs_refresh.c section 6a.
847 * If it's a small screen, and the movement isn't larger than a
848 * screen, i.e some context will remain, open up the screen and
849 * display by scrolling. In this case, the cursor moves up one
850 * line for each line displayed. Otherwise, erase/compress and
851 * repaint, and move the cursor to the first line in the screen.
852 * Note, the ^B command is always in the latter case, for historical
853 * reasons.
855 cursor_set = scmd == CNTRL_Y;
856 if (IS_SMALL(sp)) {
857 if (count >= sp->t_maxrows || scmd == CNTRL_B) {
858 s1 = HMAP[0];
859 if (vs_sm_erase(sp))
860 return (1);
861 for (; count--; s1 = s2) {
862 if (vs_sm_prev(sp, &s1, &s2))
863 return (1);
864 if (s2.lno == 1 &&
865 (O_ISSET(sp, O_LEFTRIGHT) || s2.soff == 1))
866 break;
868 HMAP[0] = s2;
869 if (vs_sm_fill(sp, OOBLNO, P_TOP))
870 return (1);
871 return (vs_sm_position(sp, rp, 0, P_BOTTOM));
873 cursor_set = scmd == CNTRL_Y || vs_sm_cursor(sp, &ssmp);
874 for (; count &&
875 sp->t_rows != sp->t_maxrows; --count, ++sp->t_rows) {
876 if (HMAP->lno == 1 &&
877 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
878 break;
879 ++TMAP;
880 if (vs_sm_1down(sp))
881 return (1);
883 if (!cursor_set) {
884 rp->lno = ssmp->lno;
885 rp->cno = ssmp->c_sboff;
887 if (count == 0)
888 return (0);
891 for (ychanged = zset = 0; count; --count) {
892 /* If the line doesn't exist, we're done. */
893 if (HMAP->lno == 1 &&
894 (O_ISSET(sp, O_LEFTRIGHT) || HMAP->soff == 1))
895 break;
897 /* Scroll the screen and cursor down one logical line. */
898 if (vs_sm_1down(sp))
899 return (1);
900 switch (scmd) {
901 case CNTRL_Y:
902 if (smp < TMAP)
903 ++smp;
904 else
905 ychanged = 1;
906 break;
907 case Z_CARAT:
908 if (zset) {
909 if (smp < TMAP)
910 ++smp;
911 } else {
912 smp = HMAP;
913 zset = 1;
915 /* FALLTHROUGH */
916 default:
917 break;
921 if (scmd != CNTRL_Y && cursor_set)
922 return(0);
924 switch (scmd) {
925 case CNTRL_B:
927 * If there are more lines, the ^B command is positioned at
928 * the last line of the screen. However, the line may not
929 * exist.
931 if (!count) {
932 for (smp = TMAP; smp > HMAP; --smp)
933 if (db_exist(sp, smp->lno))
934 break;
935 break;
937 /* FALLTHROUGH */
938 case CNTRL_U:
940 * The ^B and ^U commands move the cursor towards SOF
941 * if there are more lines to move.
943 if (count < smp - HMAP)
944 smp -= count;
945 else
946 smp = HMAP;
947 break;
948 case CNTRL_Y:
950 * On a ^Y that was forced to change lines, try and keep the
951 * cursor as close as possible to the last position, but also
952 * set it up so that the next "real" movement will return the
953 * cursor to the closest position to the last real movement.
955 if (ychanged) {
956 rp->lno = smp->lno;
957 rp->cno = vs_colpos(sp, smp->lno,
958 (O_ISSET(sp, O_LEFTRIGHT) ?
959 smp->coff : (smp->soff - 1) * sp->cols) +
960 sp->rcm % sp->cols);
962 return (0);
963 case Z_CARAT:
964 /* The z^ command moves the cursor to the first new line. */
965 break;
966 default:
967 abort();
970 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
971 return (1);
972 rp->lno = smp->lno;
973 rp->cno = smp->c_sboff;
974 return (0);
978 * vs_sm_erase --
979 * Erase the small screen area for the scrolling functions.
981 static int
982 vs_sm_erase(sp)
983 SCR *sp;
985 GS *gp;
987 gp = sp->gp;
988 (void)gp->scr_move(sp, LASTLINE(sp), 0);
989 (void)gp->scr_clrtoeol(sp);
990 for (; sp->t_rows > sp->t_minrows; --sp->t_rows, --TMAP) {
991 (void)gp->scr_move(sp, TMAP - HMAP, 0);
992 (void)gp->scr_clrtoeol(sp);
994 return (0);
998 * vs_sm_1down --
999 * Scroll the SMAP down one.
1001 * PUBLIC: int vs_sm_1down __P((SCR *));
1004 vs_sm_1down(sp)
1005 SCR *sp;
1008 * Insert a line at the top of the screen. Shift the screen map
1009 * down and display a new line at the top of the screen.
1011 (void)sp->gp->scr_move(sp, 0, 0);
1012 if (vs_insertln(sp, 1))
1013 return (1);
1015 /* One-line screens can fail. */
1016 if (IS_ONELINE(sp)) {
1017 if (vs_sm_prev(sp, HMAP, HMAP))
1018 return (1);
1019 } else {
1020 memmove(HMAP + 1, HMAP, (sp->rows - 1) * sizeof(SMAP));
1021 if (vs_sm_prev(sp, HMAP + 1, HMAP))
1022 return (1);
1024 /* vs_sm_prev() flushed the cache. */
1025 return (vs_line(sp, HMAP, NULL, NULL));
1029 * vs_insertln --
1030 * Insert a line a la curses, make sure to put the information
1031 * line and other screens back.
1033 static int
1034 vs_insertln(sp, cnt)
1035 SCR *sp;
1036 int cnt;
1038 GS *gp;
1039 size_t oldy, oldx;
1041 gp = sp->gp;
1043 /* If the screen is vertically split, we can't scroll it. */
1044 if (IS_VSPLIT(sp)) {
1045 F_SET(sp, SC_SCR_REDRAW);
1046 return (0);
1049 if (IS_ONELINE(sp)) {
1050 (void)gp->scr_move(sp, LASTLINE(sp), 0);
1051 (void)gp->scr_clrtoeol(sp);
1052 } else {
1053 (void)gp->scr_cursor(sp, &oldy, &oldx);
1054 while (cnt--) {
1055 (void)gp->scr_move(sp, LASTLINE(sp) - 1, 0);
1056 (void)gp->scr_deleteln(sp);
1057 (void)gp->scr_move(sp, oldy, oldx);
1058 (void)gp->scr_insertln(sp);
1061 return (0);
1065 * vs_sm_next --
1066 * Fill in the next entry in the SMAP.
1068 * PUBLIC: int vs_sm_next __P((SCR *, SMAP *, SMAP *));
1071 vs_sm_next(sp, p, t)
1072 SCR *sp;
1073 SMAP *p, *t;
1075 size_t lcnt;
1077 SMAP_FLUSH(t);
1078 if (O_ISSET(sp, O_LEFTRIGHT)) {
1079 t->lno = p->lno + 1;
1080 t->coff = p->coff;
1081 } else {
1082 lcnt = vs_screens(sp, p->lno, NULL);
1083 if (lcnt == p->soff) {
1084 t->lno = p->lno + 1;
1085 t->soff = 1;
1086 } else {
1087 t->lno = p->lno;
1088 t->soff = p->soff + 1;
1091 return (0);
1095 * vs_sm_prev --
1096 * Fill in the previous entry in the SMAP.
1098 * PUBLIC: int vs_sm_prev __P((SCR *, SMAP *, SMAP *));
1101 vs_sm_prev(sp, p, t)
1102 SCR *sp;
1103 SMAP *p, *t;
1105 SMAP_FLUSH(t);
1106 if (O_ISSET(sp, O_LEFTRIGHT)) {
1107 t->lno = p->lno - 1;
1108 t->coff = p->coff;
1109 } else {
1110 if (p->soff != 1) {
1111 t->lno = p->lno;
1112 t->soff = p->soff - 1;
1113 } else {
1114 t->lno = p->lno - 1;
1115 t->soff = vs_screens(sp, t->lno, NULL);
1118 return (t->lno == 0);
1122 * vs_sm_cursor --
1123 * Return the SMAP entry referenced by the cursor.
1125 * PUBLIC: int vs_sm_cursor __P((SCR *, SMAP **));
1128 vs_sm_cursor(sp, smpp)
1129 SCR *sp;
1130 SMAP **smpp;
1132 SMAP *p;
1134 /* See if the cursor is not in the map. */
1135 if (sp->lno < HMAP->lno || sp->lno > TMAP->lno)
1136 return (1);
1138 /* Find the first occurence of the line. */
1139 for (p = HMAP; p->lno != sp->lno; ++p);
1141 /* Fill in the map information until we find the right line. */
1142 for (; p <= TMAP; ++p) {
1143 /* Short lines are common and easy to detect. */
1144 if (p != TMAP && (p + 1)->lno != p->lno) {
1145 *smpp = p;
1146 return (0);
1148 if (!SMAP_CACHE(p) && vs_line(sp, p, NULL, NULL))
1149 return (1);
1150 if (p->c_eboff >= sp->cno) {
1151 *smpp = p;
1152 return (0);
1156 /* It was past the end of the map after all. */
1157 return (1);
1161 * vs_sm_position --
1162 * Return the line/column of the top, middle or last line on the screen.
1163 * (The vi H, M and L commands.) Here because only the screen routines
1164 * know what's really out there.
1166 * PUBLIC: int vs_sm_position __P((SCR *, MARK *, u_long, pos_t));
1169 vs_sm_position(sp, rp, cnt, pos)
1170 SCR *sp;
1171 MARK *rp;
1172 u_long cnt;
1173 pos_t pos;
1175 SMAP *smp;
1176 db_recno_t last;
1178 switch (pos) {
1179 case P_TOP:
1181 * !!!
1182 * Historically, an invalid count to the H command failed.
1183 * We do nothing special here, just making sure that H in
1184 * an empty screen works.
1186 if (cnt > TMAP - HMAP)
1187 goto sof;
1188 smp = HMAP + cnt;
1189 if (cnt && !db_exist(sp, smp->lno)) {
1190 sof: msgq(sp, M_BERR, "220|Movement past the end-of-screen");
1191 return (1);
1193 break;
1194 case P_MIDDLE:
1196 * !!!
1197 * Historically, a count to the M command was ignored.
1198 * If the screen isn't filled, find the middle of what's
1199 * real and move there.
1201 if (!db_exist(sp, TMAP->lno)) {
1202 if (db_last(sp, &last))
1203 return (1);
1204 for (smp = TMAP; smp->lno > last && smp > HMAP; --smp);
1205 if (smp > HMAP)
1206 smp -= (smp - HMAP) / 2;
1207 } else
1208 smp = (HMAP + (TMAP - HMAP) / 2) + cnt;
1209 break;
1210 case P_BOTTOM:
1212 * !!!
1213 * Historically, an invalid count to the L command failed.
1214 * If the screen isn't filled, find the bottom of what's
1215 * real and try to offset from there.
1217 if (cnt > TMAP - HMAP)
1218 goto eof;
1219 smp = TMAP - cnt;
1220 if (!db_exist(sp, smp->lno)) {
1221 if (db_last(sp, &last))
1222 return (1);
1223 for (; smp->lno > last && smp > HMAP; --smp);
1224 if (cnt > smp - HMAP) {
1225 eof: msgq(sp, M_BERR,
1226 "221|Movement past the beginning-of-screen");
1227 return (1);
1229 smp -= cnt;
1231 break;
1232 default:
1233 abort();
1236 /* Make sure that the cached information is valid. */
1237 if (!SMAP_CACHE(smp) && vs_line(sp, smp, NULL, NULL))
1238 return (1);
1239 rp->lno = smp->lno;
1240 rp->cno = smp->c_sboff;
1242 return (0);
1246 * vs_sm_nlines --
1247 * Return the number of screen lines from an SMAP entry to the
1248 * start of some file line, less than a maximum value.
1250 * PUBLIC: db_recno_t vs_sm_nlines __P((SCR *, SMAP *, db_recno_t, size_t));
1252 db_recno_t
1253 vs_sm_nlines(sp, from_sp, to_lno, max)
1254 SCR *sp;
1255 SMAP *from_sp;
1256 db_recno_t to_lno;
1257 size_t max;
1259 db_recno_t lno, lcnt;
1261 if (O_ISSET(sp, O_LEFTRIGHT))
1262 return (from_sp->lno > to_lno ?
1263 from_sp->lno - to_lno : to_lno - from_sp->lno);
1265 if (from_sp->lno == to_lno)
1266 return (from_sp->soff - 1);
1268 if (from_sp->lno > to_lno) {
1269 lcnt = from_sp->soff - 1; /* Correct for off-by-one. */
1270 for (lno = from_sp->lno; --lno >= to_lno && lcnt <= max;)
1271 lcnt += vs_screens(sp, lno, NULL);
1272 } else {
1273 lno = from_sp->lno;
1274 lcnt = (vs_screens(sp, lno, NULL) - from_sp->soff) + 1;
1275 for (; ++lno < to_lno && lcnt <= max;)
1276 lcnt += vs_screens(sp, lno, NULL);
1278 return (lcnt);