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