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