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