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