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