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