xref: /dragonfly/contrib/nvi2/vi/vs_refresh.c (revision 07bc39c2f4bbca56f12568e06d89da17f2eeb965)
1 /*-
2  * Copyright (c) 1992, 1993, 1994
3  *        The Regents of the University of California.  All rights reserved.
4  * Copyright (c) 1992, 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 <ctype.h>
18 #include <libgen.h>
19 #include <limits.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 
24 #include "../common/common.h"
25 #include "vi.h"
26 
27 #define   UPDATE_CURSOR       0x01                          /* Update the cursor. */
28 #define   UPDATE_SCREEN       0x02                          /* Flush to screen. */
29 
30 static void         vs_modeline(SCR *);
31 static int          vs_paint(SCR *, u_int);
32 
33 /*
34  * v_repaint --
35  *        Repaint selected lines from the screen.
36  *
37  * PUBLIC: int vs_repaint(SCR *, EVENT *);
38  */
39 int
vs_repaint(SCR * sp,EVENT * evp)40 vs_repaint(
41           SCR *sp,
42           EVENT *evp)
43 {
44           SMAP *smp;
45 
46           for (; evp->e_flno <= evp->e_tlno; ++evp->e_flno) {
47                     smp = HMAP + evp->e_flno - 1;
48                     SMAP_FLUSH(smp);
49                     if (vs_line(sp, smp, NULL, NULL))
50                               return (1);
51           }
52           return (0);
53 }
54 
55 /*
56  * vs_refresh --
57  *        Refresh all screens.
58  *
59  * PUBLIC: int vs_refresh(SCR *, int);
60  */
61 int
vs_refresh(SCR * sp,int forcepaint)62 vs_refresh(
63           SCR *sp,
64           int forcepaint)
65 {
66           GS *gp;
67           SCR *tsp;
68           int need_refresh = 0;
69           u_int priv_paint, pub_paint;
70 
71           gp = sp->gp;
72 
73           /*
74            * 1: Refresh the screen.
75            *
76            * If SC_SCR_REDRAW is set in the current screen, repaint everything
77            * that we can find, including status lines.
78            */
79           if (F_ISSET(sp, SC_SCR_REDRAW))
80                     TAILQ_FOREACH(tsp, gp->dq, q)
81                               if (tsp != sp)
82                                         F_SET(tsp, SC_SCR_REDRAW | SC_STATUS);
83 
84           /*
85            * 2: Related or dirtied screens, or screens with messages.
86            *
87            * If related screens share a view into a file, they may have been
88            * modified as well.  Refresh any screens that aren't exiting that
89            * have paint or dirty bits set.  Always update their screens, we
90            * are not likely to get another chance.  Finally, if we refresh any
91            * screens other than the current one, the cursor will be trashed.
92            */
93           pub_paint = SC_SCR_REFORMAT | SC_SCR_REDRAW;
94           priv_paint = VIP_CUR_INVALID | VIP_N_REFRESH;
95           if (O_ISSET(sp, O_NUMBER))
96                     priv_paint |= VIP_N_RENUMBER;
97           TAILQ_FOREACH(tsp, gp->dq, q)
98                     if (tsp != sp && !F_ISSET(tsp, SC_EXIT | SC_EXIT_FORCE) &&
99                         (F_ISSET(tsp, pub_paint) ||
100                         F_ISSET(VIP(tsp), priv_paint))) {
101                               (void)vs_paint(tsp,
102                                   (F_ISSET(VIP(tsp), VIP_CUR_INVALID) ?
103                                   UPDATE_CURSOR : 0) | UPDATE_SCREEN);
104                               F_SET(VIP(sp), VIP_CUR_INVALID);
105                     }
106 
107           /*
108            * 3: Refresh the current screen.
109            *
110            * Always refresh the current screen, it may be a cursor movement.
111            * Also, always do it last -- that way, SC_SCR_REDRAW can be set
112            * in the current screen only, and the screen won't flash.
113            */
114           if (vs_paint(sp, UPDATE_CURSOR | (!forcepaint &&
115               F_ISSET(sp, SC_SCR_VI) && KEYS_WAITING(sp) ? 0 : UPDATE_SCREEN)))
116                     return (1);
117 
118           /*
119            * 4: Paint any missing status lines.
120            *
121            * XXX
122            * This is fairly evil.  Status lines are written using the vi message
123            * mechanism, since we have no idea how long they are.  Since we may be
124            * painting screens other than the current one, we don't want to make
125            * the user wait.  We depend heavily on there not being any other lines
126            * currently waiting to be displayed and the message truncation code in
127            * the msgq_status routine working.
128            *
129            * And, finally, if we updated any status lines, make sure the cursor
130            * gets back to where it belongs.
131            */
132           TAILQ_FOREACH(tsp, gp->dq, q)
133                     if (F_ISSET(tsp, SC_STATUS)) {
134                               need_refresh = 1;
135                               vs_resolve(tsp, sp, 0);
136                     }
137           if (need_refresh)
138                     (void)gp->scr_refresh(sp, 0);
139 
140           /*
141            * A side-effect of refreshing the screen is that it's now ready
142            * for everything else, i.e. messages.
143            */
144           F_SET(sp, SC_SCR_VI);
145           return (0);
146 }
147 
148 /*
149  * vs_paint --
150  *        This is the guts of the vi curses screen code.  The idea is that
151  *        the SCR structure passed in contains the new coordinates of the
152  *        screen.  What makes this hard is that we don't know how big
153  *        characters are, doing input can put the cursor in illegal places,
154  *        and we're frantically trying to avoid repainting unless it's
155  *        absolutely necessary.  If you change this code, you'd better know
156  *        what you're doing.  It's subtle and quick to anger.
157  */
158 static int
vs_paint(SCR * sp,u_int flags)159 vs_paint(
160           SCR *sp,
161           u_int flags)
162 {
163           GS *gp;
164           SMAP *smp, tmp;
165           VI_PRIVATE *vip;
166           recno_t lastline, lcnt;
167           size_t cwtotal, cnt, len, notused, off, y;
168           int ch = 0, didpaint, isempty, leftright_warp;
169           CHAR_T *p;
170 
171 #define    LNO      sp->lno                       /* Current file line. */
172 #define   OLNO      vip->olno           /* Remembered file line. */
173 #define    CNO      sp->cno                       /* Current file column. */
174 #define   OCNO      vip->ocno           /* Remembered file column. */
175 #define   SCNO      vip->sc_col                   /* Current screen column. */
176 
177           gp = sp->gp;
178           vip = VIP(sp);
179           didpaint = leftright_warp = 0;
180 
181           /*
182            * 5: Reformat the lines.
183            *
184            * If the lines themselves have changed (:set list, for example),
185            * fill in the map from scratch.  Adjust the screen that's being
186            * displayed if the leftright flag is set.
187            */
188           if (F_ISSET(sp, SC_SCR_REFORMAT)) {
189                     /* Invalidate the line size cache. */
190                     VI_SCR_CFLUSH(vip);
191 
192                     /* Toss vs_line() cached information. */
193                     if (F_ISSET(sp, SC_SCR_TOP)) {
194                               if (vs_sm_fill(sp, LNO, P_TOP))
195                                         return (1);
196                     }
197                     else if (F_ISSET(sp, SC_SCR_CENTER)) {
198                               if (vs_sm_fill(sp, LNO, P_MIDDLE))
199                                         return (1);
200                     } else
201                               if (vs_sm_fill(sp, OOBLNO, P_TOP))
202                                         return (1);
203                     F_SET(sp, SC_SCR_REDRAW);
204           }
205 
206           /*
207            * 6: Line movement.
208            *
209            * Line changes can cause the top line to change as well.  As
210            * before, if the movement is large, the screen is repainted.
211            *
212            * 6a: Small screens.
213            *
214            * Users can use the window, w300, w1200 and w9600 options to make
215            * the screen artificially small.  The behavior of these options
216            * in the historic vi wasn't all that consistent, and, in fact, it
217            * was never documented how various screen movements affected the
218            * screen size.  Generally, one of three things would happen:
219            *        1: The screen would expand in size, showing the line
220            *        2: The screen would scroll, showing the line
221            *        3: The screen would compress to its smallest size and
222            *                  repaint.
223            * In general, scrolling didn't cause compression (200^D was handled
224            * the same as ^D), movement to a specific line would (:N where N
225            * was 1 line below the screen caused a screen compress), and cursor
226            * movement would scroll if it was 11 lines or less, and compress if
227            * it was more than 11 lines.  (And, no, I have no idea where the 11
228            * comes from.)
229            *
230            * What we do is try and figure out if the line is less than half of
231            * a full screen away.  If it is, we expand the screen if there's
232            * room, and then scroll as necessary.  The alternative is to compress
233            * and repaint.
234            *
235            * !!!
236            * This code is a special case from beginning to end.  Unfortunately,
237            * home modems are still slow enough that it's worth having.
238            *
239            * XXX
240            * If the line a really long one, i.e. part of the line is on the
241            * screen but the column offset is not, we'll end up in the adjust
242            * code, when we should probably have compressed the screen.
243            */
244           if (IS_SMALL(sp))
245                     if (LNO < HMAP->lno) {
246                               lcnt = vs_sm_nlines(sp, HMAP, LNO, sp->t_maxrows);
247                               if (lcnt <= HALFSCREEN(sp))
248                                         for (; lcnt && sp->t_rows != sp->t_maxrows;
249                                              --lcnt, ++sp->t_rows) {
250                                                   ++TMAP;
251                                                   if (vs_sm_1down(sp))
252                                                             return (1);
253                                         }
254                               else
255                                         goto small_fill;
256                     } else if (LNO > TMAP->lno) {
257                               lcnt = vs_sm_nlines(sp, TMAP, LNO, sp->t_maxrows);
258                               if (lcnt <= HALFSCREEN(sp))
259                                         for (; lcnt && sp->t_rows != sp->t_maxrows;
260                                              --lcnt, ++sp->t_rows) {
261                                                   if (vs_sm_next(sp, TMAP, TMAP + 1))
262                                                             return (1);
263                                                   ++TMAP;
264                                                   if (vs_line(sp, TMAP, NULL, NULL))
265                                                             return (1);
266                                         }
267                               else {
268 small_fill:                             (void)gp->scr_move(sp, LASTLINE(sp), 0);
269                                         (void)gp->scr_clrtoeol(sp);
270                                         for (; sp->t_rows > sp->t_minrows;
271                                             --sp->t_rows, --TMAP) {
272                                                   (void)gp->scr_move(sp, TMAP - HMAP, 0);
273                                                   (void)gp->scr_clrtoeol(sp);
274                                         }
275                                         if (vs_sm_fill(sp, LNO, P_FILL))
276                                                   return (1);
277                                         F_SET(sp, SC_SCR_REDRAW);
278                                         goto adjust;
279                               }
280                     }
281 
282           /*
283            * 6b: Line down, or current screen.
284            */
285           if (LNO >= HMAP->lno) {
286                     /* Current screen. */
287                     if (LNO <= TMAP->lno)
288                               goto adjust;
289                     if (F_ISSET(sp, SC_SCR_TOP))
290                               goto top;
291                     if (F_ISSET(sp, SC_SCR_CENTER))
292                               goto middle;
293 
294                     /*
295                      * If less than half a screen above the line, scroll down
296                      * until the line is on the screen.
297                      */
298                     lcnt = vs_sm_nlines(sp, TMAP, LNO, HALFTEXT(sp));
299                     if (lcnt < HALFTEXT(sp)) {
300                               while (lcnt--)
301                                         if (vs_sm_1up(sp))
302                                                   return (1);
303                               goto adjust;
304                     }
305                     goto bottom;
306           }
307 
308           /*
309            * 6c: If not on the current screen, may request center or top.
310            */
311           if (F_ISSET(sp, SC_SCR_TOP))
312                     goto top;
313           if (F_ISSET(sp, SC_SCR_CENTER))
314                     goto middle;
315 
316           /*
317            * 6d: Line up.
318            */
319           lcnt = vs_sm_nlines(sp, HMAP, LNO, HALFTEXT(sp));
320           if (lcnt < HALFTEXT(sp)) {
321                     /*
322                      * If less than half a screen below the line, scroll up until
323                      * the line is the first line on the screen.  Special check so
324                      * that if the screen has been emptied, we refill it.
325                      */
326                     if (db_exist(sp, HMAP->lno)) {
327                               while (lcnt--)
328                                         if (vs_sm_1down(sp))
329                                                   return (1);
330                               goto adjust;
331                     } else
332                               goto top; /* XXX No such line. */
333 
334                     /*
335                      * If less than a half screen from the bottom of the file,
336                      * put the last line of the file on the bottom of the screen.
337                      */
338 bottom:             if (db_last(sp, &lastline))
339                               return (1);
340                     tmp.lno = LNO;
341                     tmp.coff = HMAP->coff;
342                     tmp.soff = 1;
343                     lcnt = vs_sm_nlines(sp, &tmp, lastline, sp->t_rows);
344                     if (lcnt < HALFTEXT(sp)) {
345                               if (vs_sm_fill(sp, lastline, P_BOTTOM))
346                                         return (1);
347                               F_SET(sp, SC_SCR_REDRAW);
348                               goto adjust;
349                     }
350                     /* It's not close, just put the line in the middle. */
351                     goto middle;
352           }
353 
354           /*
355            * If less than half a screen from the top of the file, put the first
356            * line of the file at the top of the screen.  Otherwise, put the line
357            * in the middle of the screen.
358            */
359           tmp.lno = 1;
360           tmp.coff = HMAP->coff;
361           tmp.soff = 1;
362           lcnt = vs_sm_nlines(sp, &tmp, LNO, HALFTEXT(sp));
363           if (lcnt < HALFTEXT(sp)) {
364                     if (vs_sm_fill(sp, 1, P_TOP))
365                               return (1);
366           } else
367 middle:             if (vs_sm_fill(sp, LNO, P_MIDDLE))
368                               return (1);
369           if (0) {
370 top:                if (vs_sm_fill(sp, LNO, P_TOP))
371                               return (1);
372           }
373           F_SET(sp, SC_SCR_REDRAW);
374 
375           /*
376            * At this point we know part of the line is on the screen.  Since
377            * scrolling is done using logical lines, not physical, all of the
378            * line may not be on the screen.  While that's not necessarily bad,
379            * if the part the cursor is on isn't there, we're going to lose.
380            * This can be tricky; if the line covers the entire screen, lno
381            * may be the same as both ends of the map, that's why we test BOTH
382            * the top and the bottom of the map.  This isn't a problem for
383            * left-right scrolling, the cursor movement code handles the problem.
384            *
385            * There's a performance issue here if editing *really* long lines.
386            * This gets to the right spot by scrolling, and, in a binary, by
387            * scrolling hundreds of lines.  If the adjustment looks like it's
388            * going to be a serious problem, refill the screen and repaint.
389            */
390 adjust:   if (!O_ISSET(sp, O_LEFTRIGHT) &&
391               (LNO == HMAP->lno || LNO == TMAP->lno)) {
392                     cnt = vs_screens(sp, LNO, &CNO);
393                     if (LNO == HMAP->lno && cnt < HMAP->soff)
394                               if ((HMAP->soff - cnt) > HALFTEXT(sp)) {
395                                         HMAP->soff = cnt;
396                                         vs_sm_fill(sp, OOBLNO, P_TOP);
397                                         F_SET(sp, SC_SCR_REDRAW);
398                               } else
399                                         while (cnt < HMAP->soff)
400                                                   if (vs_sm_1down(sp))
401                                                             return (1);
402                     if (LNO == TMAP->lno && cnt > TMAP->soff)
403                               if ((cnt - TMAP->soff) > HALFTEXT(sp)) {
404                                         TMAP->soff = cnt;
405                                         vs_sm_fill(sp, OOBLNO, P_BOTTOM);
406                                         F_SET(sp, SC_SCR_REDRAW);
407                               } else
408                                         while (cnt > TMAP->soff)
409                                                   if (vs_sm_1up(sp))
410                                                             return (1);
411           }
412 
413           /*
414            * If the screen needs to be repainted, skip cursor optimization.
415            * However, in the code above we skipped leftright scrolling on
416            * the grounds that the cursor code would handle it.  Make sure
417            * the right screen is up.
418            */
419           if (F_ISSET(sp, SC_SCR_REDRAW)) {
420                     if (O_ISSET(sp, O_LEFTRIGHT))
421                               goto slow;
422                     goto paint;
423           }
424 
425           /*
426            * 7: Cursor movements (current screen only).
427            */
428           if (!LF_ISSET(UPDATE_CURSOR))
429                     goto number;
430 
431           /*
432            * Decide cursor position.  If the line has changed, the cursor has
433            * moved over a tab, or don't know where the cursor was, reparse the
434            * line.  Otherwise, we've just moved over fixed-width characters,
435            * and can calculate the left/right scrolling and cursor movement
436            * without reparsing the line.  Note that we don't know which (if any)
437            * of the characters between the old and new cursor positions changed.
438            *
439            * XXX
440            * With some work, it should be possible to handle tabs quickly, at
441            * least in obvious situations, like moving right and encountering
442            * a tab, without reparsing the whole line.
443            *
444            * If the line we're working with has changed, reread it..
445            */
446           if (F_ISSET(vip, VIP_CUR_INVALID) || LNO != OLNO)
447                     goto slow;
448 
449           /* Otherwise, if nothing's changed, ignore the cursor. */
450           if (CNO == OCNO)
451                     goto fast;
452 
453           /*
454            * Get the current line.  If this fails, we either have an empty
455            * file and can just repaint, or there's a real problem.  This
456            * isn't a performance issue because there aren't any ways to get
457            * here repeatedly.
458            */
459           if (db_eget(sp, LNO, &p, &len, &isempty)) {
460                     if (isempty)
461                               goto slow;
462                     return (1);
463           }
464 
465 #ifdef DEBUG
466           /* Sanity checking. */
467           if (CNO >= len && len != 0) {
468                     msgq(sp, M_ERR, "Error: %s/%d: cno (%zu) >= len (%zu)",
469                          basename(__FILE__), __LINE__, CNO, len);
470                     return (1);
471           }
472 #endif
473           /*
474            * The basic scheme here is to look at the characters in between
475            * the old and new positions and decide how big they are on the
476            * screen, and therefore, how many screen positions to move.
477            */
478           if (CNO < OCNO) {
479                     /*
480                      * 7a: Cursor moved left.
481                      *
482                      * Point to the old character.  The old cursor position can
483                      * be past EOL if, for example, we just deleted the rest of
484                      * the line.  In this case, since we don't know the width of
485                      * the characters we traversed, we have to do it slowly.
486                      */
487                     p += OCNO;
488                     cnt = (OCNO - CNO) + 1;
489                     if (OCNO >= len)
490                               goto slow;
491 
492                     /*
493                      * Quick sanity check -- it's hard to figure out exactly when
494                      * we cross a screen boundary as we do in the cursor right
495                      * movement.  If cnt is so large that we're going to cross the
496                      * boundary no matter what, stop now.
497                      */
498                     if (SCNO + 1 + MAX_CHARACTER_COLUMNS < cnt)
499                               goto slow;
500 
501                     /*
502                      * Count up the widths of the characters.  If it's a tab
503                      * character, go do it the slow way.
504                      */
505                     for (cwtotal = 0; cnt--; cwtotal += KEY_COL(sp, ch))
506                               if ((ch = *(UCHAR_T *)p--) == '\t')
507                                         goto slow;
508 
509                     /*
510                      * Decrement the screen cursor by the total width of the
511                      * characters minus 1.
512                      */
513                     cwtotal -= 1;
514 
515                     /*
516                      * If we're moving left, and there's a wide character in the
517                      * current position, go to the end of the character.
518                      */
519                     if (KEY_COL(sp, ch) > 1)
520                               cwtotal -= KEY_COL(sp, ch) - 1;
521 
522                     /*
523                      * If the new column moved us off of the current logical line,
524                      * calculate a new one.  If doing leftright scrolling, we've
525                      * moved off of the current screen, as well.
526                      */
527                     if (SCNO < cwtotal)
528                               goto slow;
529                     SCNO -= cwtotal;
530           } else {
531                     /*
532                      * 7b: Cursor moved right.
533                      *
534                      * Point to the first character to the right.
535                      */
536                     p += OCNO + 1;
537                     cnt = CNO - OCNO;
538 
539                     /*
540                      * Count up the widths of the characters.  If it's a tab
541                      * character, go do it the slow way.  If we cross a
542                      * screen boundary, we can quit.
543                      */
544                     for (cwtotal = SCNO; cnt--;) {
545                               if ((ch = *(UCHAR_T *)p++) == '\t')
546                                         goto slow;
547                               if ((cwtotal += KEY_COL(sp, ch)) >= SCREEN_COLS(sp))
548                                         break;
549                     }
550 
551                     /*
552                      * Increment the screen cursor by the total width of the
553                      * characters.
554                      */
555                     SCNO = cwtotal;
556 
557                     /* See screen change comment in section 6a. */
558                     if (SCNO >= SCREEN_COLS(sp))
559                               goto slow;
560           }
561 
562           /*
563            * 7c: Fast cursor update.
564            *
565            * We have the current column, retrieve the current row.
566            */
567 fast:     (void)gp->scr_cursor(sp, &y, &notused);
568           goto done_cursor;
569 
570           /*
571            * 7d: Slow cursor update.
572            *
573            * Walk through the map and find the current line.
574            */
575 slow:     for (smp = HMAP; smp->lno != LNO; ++smp);
576 
577           /*
578            * 7e: Leftright scrolling adjustment.
579            *
580            * If doing left-right scrolling and the cursor movement has changed
581            * the displayed screen, scroll the screen left or right, unless we're
582            * updating the info line in which case we just scroll that one line.
583            * We adjust the offset up or down until we have a window that covers
584            * the current column, making sure that we adjust differently for the
585            * first screen as compared to subsequent ones.
586            */
587           if (O_ISSET(sp, O_LEFTRIGHT)) {
588                     /*
589                      * Get the screen column for this character, and correct
590                      * for the number option offset.
591                      */
592                     cnt = vs_columns(sp, NULL, LNO, &CNO, NULL);
593                     if (O_ISSET(sp, O_NUMBER))
594                               cnt -= O_NUMBER_LENGTH;
595 
596                     /* Adjust the window towards the beginning of the line. */
597                     off = smp->coff;
598                     if (off >= cnt) {
599                               do {
600                                         if (off >= O_VAL(sp, O_SIDESCROLL))
601                                                   off -= O_VAL(sp, O_SIDESCROLL);
602                                         else {
603                                                   off = 0;
604                                                   break;
605                                         }
606                               } while (off >= cnt);
607                               goto shifted;
608                     }
609 
610                     /* Adjust the window towards the end of the line. */
611                     if ((off == 0 && off + SCREEN_COLS(sp) < cnt) ||
612                         (off != 0 && off + sp->cols < cnt)) {
613                               do {
614                                         off += O_VAL(sp, O_SIDESCROLL);
615                               } while (off + sp->cols < cnt);
616 
617 shifted:            /* Fill in screen map with the new offset. */
618                               if (F_ISSET(sp, SC_TINPUT_INFO))
619                                         smp->coff = off;
620                               else {
621                                         for (smp = HMAP; smp <= TMAP; ++smp)
622                                                   smp->coff = off;
623                                         leftright_warp = 1;
624                               }
625                               goto paint;
626                     }
627 
628                     /*
629                      * We may have jumped here to adjust a leftright screen because
630                      * redraw was set.  If so, we have to paint the entire screen.
631                      */
632                     if (F_ISSET(sp, SC_SCR_REDRAW))
633                               goto paint;
634           }
635 
636           /*
637            * Update the screen lines for this particular file line until we
638            * have a new screen cursor position.
639            */
640           for (y = -1,
641               vip->sc_smap = NULL; smp <= TMAP && smp->lno == LNO; ++smp) {
642                     if (vs_line(sp, smp, &y, &SCNO))
643                               return (1);
644                     if (y != -1) {
645                               vip->sc_smap = smp;
646                               break;
647                     }
648           }
649           goto done_cursor;
650 
651           /*
652            * 8: Repaint the entire screen.
653            *
654            * Lost big, do what you have to do.  We flush the cache, since
655            * SC_SCR_REDRAW gets set when the screen isn't worth fixing, and
656            * it's simpler to repaint.  So, don't trust anything that we
657            * think we know about it.
658            */
659 paint:    for (smp = HMAP; smp <= TMAP; ++smp)
660                     SMAP_FLUSH(smp);
661           for (y = -1, vip->sc_smap = NULL, smp = HMAP; smp <= TMAP; ++smp) {
662                     if (vs_line(sp, smp, &y, &SCNO))
663                               return (1);
664                     if (y != -1 && vip->sc_smap == NULL)
665                               vip->sc_smap = smp;
666           }
667           /*
668            * If it's a small screen and we're redrawing, clear the unused lines,
669            * ex may have overwritten them.
670            */
671           if (F_ISSET(sp, SC_SCR_REDRAW) && IS_SMALL(sp))
672                     for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) {
673                               (void)gp->scr_move(sp, cnt, 0);
674                               (void)gp->scr_clrtoeol(sp);
675                     }
676 
677           didpaint = 1;
678 
679 done_cursor:
680           /*
681            * Sanity checking.  When the repainting code messes up, the usual
682            * result is we don't repaint the cursor and so sc_smap will be
683            * NULL.  If we're debugging, die, otherwise restart from scratch.
684            */
685 #ifdef DEBUG
686           if (vip->sc_smap == NULL)
687                     abort();
688 #else
689           if (vip->sc_smap == NULL) {
690                     F_SET(sp, SC_SCR_REFORMAT);
691                     return (vs_paint(sp, flags));
692           }
693 #endif
694 
695           /*
696            * 9: Set the remembered cursor values.
697            */
698           OCNO = CNO;
699           OLNO = LNO;
700 
701           /*
702            * 10: Repaint the line numbers.
703            *
704            * If O_NUMBER is set and the VIP_N_RENUMBER bit is set, and we
705            * didn't repaint the screen, repaint all of the line numbers,
706            * they've changed.
707            */
708 number:   if (O_ISSET(sp, O_NUMBER) &&
709               F_ISSET(vip, VIP_N_RENUMBER) && !didpaint && vs_number(sp))
710                     return (1);
711 
712           /*
713            * 11: Update the mode line, position the cursor, and flush changes.
714            *
715            * If we warped the screen, we have to refresh everything.
716            */
717           if (leftright_warp)
718                     LF_SET(UPDATE_CURSOR | UPDATE_SCREEN);
719 
720           if (LF_ISSET(UPDATE_SCREEN) && !IS_ONELINE(sp) &&
721               !F_ISSET(vip, VIP_S_MODELINE) && !F_ISSET(sp, SC_TINPUT_INFO))
722                     vs_modeline(sp);
723 
724           if (LF_ISSET(UPDATE_CURSOR)) {
725                     (void)gp->scr_move(sp, y, SCNO);
726 
727                     /*
728                      * XXX
729                      * If the screen shifted, we recalculate the "most favorite"
730                      * cursor position.  Vi won't know that we've warped the
731                      * screen, so it's going to have a wrong idea about where the
732                      * cursor should be.  This is vi's problem, and fixing it here
733                      * is a gross layering violation.
734                      */
735                     if (leftright_warp)
736                               (void)vs_column(sp, &sp->rcm);
737           }
738 
739           if (LF_ISSET(UPDATE_SCREEN))
740                     (void)gp->scr_refresh(sp, F_ISSET(vip, VIP_N_EX_PAINT));
741 
742           /* 12: Clear the flags that are handled by this routine. */
743           F_CLR(sp, SC_SCR_CENTER | SC_SCR_REDRAW | SC_SCR_REFORMAT | SC_SCR_TOP);
744           F_CLR(vip, VIP_CUR_INVALID |
745               VIP_N_EX_PAINT | VIP_N_REFRESH | VIP_N_RENUMBER | VIP_S_MODELINE);
746 
747           return (0);
748 
749 #undef     LNO
750 #undef    OLNO
751 #undef     CNO
752 #undef    OCNO
753 #undef    SCNO
754 }
755 
756 /*
757  * vs_modeline --
758  *        Update the mode line.
759  */
760 static void
vs_modeline(SCR * sp)761 vs_modeline(SCR *sp)
762 {
763           static char * const modes[] = {
764                     "215|Append",                           /* SM_APPEND */
765                     "216|Change",                           /* SM_CHANGE */
766                     "217|Command",                          /* SM_COMMAND */
767                     "218|Insert",                           /* SM_INSERT */
768                     "219|Replace",                          /* SM_REPLACE */
769           };
770           GS *gp;
771           size_t cols, curcol, curlen, endpoint, len, midpoint;
772           const char *t = NULL;
773           int ellipsis;
774           char buf[20];
775 
776           gp = sp->gp;
777 
778           /*
779            * We put down the file name, the ruler, the mode and the dirty flag.
780            * If there's not enough room, there's not enough room, we don't play
781            * any special games.  We try to put the ruler in the middle and the
782            * mode and dirty flag at the end.
783            *
784            * !!!
785            * Leave the last character blank, in case it's a really dumb terminal
786            * with hardware scroll.  Second, don't paint the last character in the
787            * screen, SunOS 4.1.1 and Ultrix 4.2 curses won't let you.
788            *
789            * Move to the last line on the screen.
790            */
791           (void)gp->scr_move(sp, LASTLINE(sp), 0);
792 
793           /* If more than one screen in the display, show the file name. */
794           curlen = 0;
795           if (IS_SPLIT(sp)) {
796                     CHAR_T *wp, *p;
797                     size_t l;
798 
799                     CHAR2INT(sp, sp->frp->name, strlen(sp->frp->name) + 1, wp, l);
800                     p = wp + l;
801                     for (ellipsis = 0, cols = sp->cols / 2; --p > wp;) {
802                               if (*p == '/') {
803                                         ++p;
804                                         break;
805                               }
806                               if ((curlen += KEY_COL(sp, *p)) > cols) {
807                                         ellipsis = 3;
808                                         curlen +=
809                                             KEY_LEN(sp, '.') * 3 + KEY_LEN(sp, ' ');
810                                         while (curlen > cols) {
811                                                   ++p;
812                                                   curlen -= KEY_COL(sp, *p);
813                                         }
814                                         break;
815                               }
816                     }
817                     if (ellipsis) {
818                               while (ellipsis--)
819                                         (void)gp->scr_addstr(sp,
820                                             KEY_NAME(sp, '.'), KEY_LEN(sp, '.'));
821                               (void)gp->scr_addstr(sp,
822                                   KEY_NAME(sp, ' '), KEY_LEN(sp, ' '));
823                     }
824                     for (; *p != '\0'; ++p)
825                               (void)gp->scr_addstr(sp,
826                                   KEY_NAME(sp, *p), KEY_COL(sp, *p));
827           }
828 
829           /* Clear the rest of the line. */
830           (void)gp->scr_clrtoeol(sp);
831 
832           /*
833            * Display the ruler.  If we're not at the midpoint yet, move there.
834            * Otherwise, add in two extra spaces.
835            *
836            * Adjust the current column for the fact that the editor uses it as
837            * a zero-based number.
838            *
839            * XXX
840            * Assume that numbers, commas, and spaces only take up a single
841            * column on the screen.
842            */
843           cols = sp->cols - 1;
844           if (O_ISSET(sp, O_RULER)) {
845                     vs_column(sp, &curcol);
846                     len = snprintf(buf, sizeof(buf), "%lu,%lu",
847                         (u_long)sp->lno, (u_long)(curcol + 1));
848 
849                     midpoint = (cols - ((len + 1) / 2)) / 2;
850                     if (curlen < midpoint) {
851                               (void)gp->scr_move(sp, LASTLINE(sp), midpoint);
852                               curlen += len;
853                     } else if (curlen + 2 + len < cols) {
854                               (void)gp->scr_addstr(sp, "  ", 2);
855                               curlen += 2 + len;
856                     }
857                     (void)gp->scr_addstr(sp, buf, len);
858           }
859 
860           /*
861            * Display the mode and the modified flag, as close to the end of the
862            * line as possible, but guaranteeing at least two spaces between the
863            * ruler and the modified flag.
864            */
865 #define   MODESIZE  9
866           endpoint = cols;
867           if (O_ISSET(sp, O_SHOWMODE)) {
868                     if (F_ISSET(sp->ep, F_MODIFIED))
869                               --endpoint;
870                     t = msg_cat(sp, modes[sp->showmode], &len);
871                     endpoint -= len;
872           }
873 
874           if (endpoint > curlen + 2) {
875                     (void)gp->scr_move(sp, LASTLINE(sp), endpoint);
876                     if (O_ISSET(sp, O_SHOWMODE)) {
877                               if (F_ISSET(sp->ep, F_MODIFIED))
878                                         (void)gp->scr_addstr(sp,
879                                             KEY_NAME(sp, '*'), KEY_LEN(sp, '*'));
880                               (void)gp->scr_addstr(sp, t, len);
881                     }
882           }
883 }
884