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