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