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