xref: /netbsd/external/bsd/nvi/dist/vi/vs_refresh.c (revision 4479fa1a)
1 /*	$NetBSD: vs_refresh.c,v 1.10 2018/04/10 12:44:41 rin 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.10 2018/04/10 12:44:41 rin 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, chlen;
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 		 * The old cursor position can be past EOL if, for example,
471 		 * we just deleted the rest of the line.  In this case, since
472 		 * we don't know the width of the characters we traversed, we
473 		 * have to do it slowly.
474 		 */
475 		if (OCNO >= len)
476 			goto slow;
477 
478 		/*
479 		 * cwtotal acts as new value for SCNO.  Set cwtotal to the
480 		 * first char for content on CNO byte, for ease handling of
481 		 * wide characters.
482 		 *
483 		 * If the character we're stepping on lies across a screen
484 		 * boundary, we have no hope to speed it up.  Do it slowly.
485 		 */
486 		p += OCNO;
487 		ch = (UCHAR_T)*p;
488 		if (INTISWIDE(ch))
489 			cwtotal = SCNO;
490 		else {
491 			if (ch == '\t' || (chlen = KEY_LEN(sp, ch)) > SCNO + 1)
492 				goto slow;
493 			cwtotal = SCNO + 1 - chlen;
494 		}
495 		cnt = OCNO - CNO;
496 
497 		/*
498 		 * Quick sanity check -- it's hard to figure out exactly when
499 		 * we cross a screen boundary as we do in the cursor right
500 		 * movement.  If cnt is so large that we're going to cross the
501 		 * boundary no matter what, stop now.
502 		 */
503 		if (SCNO + 1 + MAX_CHARACTER_COLUMNS < cnt)
504 			goto slow;
505 
506 		/*
507 		 * Count up the widths of the characters.  If it's a tab
508 		 * character, go do it the slow way.
509 		 */
510 		while (cnt--) {
511 			if ((ch = (UCHAR_T)*--p) == '\t'
512 			    || (chlen = KEY_COL(sp, ch)) > cwtotal)
513 				goto slow;
514 			cwtotal -= chlen;
515 		}
516 
517 		/*
518 		 * If we're moving left, and there's a multi-width char in the
519 		 * current position, go to the end of the character.
520 		 */
521 		if (!INTISWIDE(ch) && (chlen = KEY_LEN(sp, ch)) > 1)
522 			cwtotal += chlen - 1;
523 
524 		/*
525 		 * At last, update the screen cursor.
526 		 */
527 		SCNO = cwtotal;
528 	} else {
529 		/*
530 		 * 7b: Cursor moved right.
531 		 */
532 		if (OCNO >= len)
533 			goto slow;
534 
535 		/*
536 		 * cwtotal acts as new value for SCNO.  Set cwtotal to the
537 		 * first char for content on CNO byte, for ease handling
538 		 * of wide characters.
539 		 */
540 		p += OCNO;
541 		ch = (UCHAR_T)*p;
542 		if (INTISWIDE(ch))
543 			cwtotal = SCNO;
544 		else
545 			cwtotal = SCNO + 1 - KEY_LEN(sp, ch);
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.
551 		 *
552 		 * If a multi-width char seems to occupy the screen boundary,
553 		 * that will be pushed to the next line.  Adjust the cursor
554 		 * in that case.
555 		 *
556 		 * If we cross a screen boundary, we can quit.
557 		 */
558 		while (cnt) {
559 			if (ch == '\t')
560 				goto slow;
561 			cwtotal += KEY_COL(sp, ch);
562 			cnt--;
563 			ch = (UCHAR_T)*++p;
564 			if (INTISWIDE(ch)
565 			    && (chlen = CHAR_WIDTH(sp, ch)) > 1
566 			    && cwtotal + chlen >= SCREEN_COLS(sp))
567 				cwtotal = SCREEN_COLS(sp);
568 			if (cwtotal >= SCREEN_COLS(sp))
569 				break;
570 		}
571 
572 		/*
573 		 * If we are on the tab character, we must do it slowly.
574 		 *
575 		 * If we're on a multi-width character in the current position,
576 		 * go to the end of the character.
577 		 */
578 		if (ch == '\t')
579 			goto slow;
580 		if (!INTISWIDE(ch) && (chlen = KEY_LEN(sp, ch)) > 1)
581 			cwtotal += chlen - 1;
582 
583 		/* See screen change comment in section 6a. */
584 		if (cwtotal >= SCREEN_COLS(sp))
585 			goto slow;
586 
587 		/*
588 		 * At last, update the screen cursor.
589 		 */
590 		SCNO = cwtotal;
591 	}
592 
593 	/*
594 	 * 7c: Fast cursor update.
595 	 *
596 	 * We have the current column, retrieve the current row.
597 	 */
598 fast:	(void)gp->scr_cursor(sp, &y, &notused);
599 	goto done_cursor;
600 
601 	/*
602 	 * 7d: Slow cursor update.
603 	 *
604 	 * Walk through the map and find the current line.
605 	 */
606 slow:	for (smp = HMAP; smp->lno != LNO; ++smp)
607 		;
608 
609 	/*
610 	 * 7e: Leftright scrolling adjustment.
611 	 *
612 	 * If doing left-right scrolling and the cursor movement has changed
613 	 * the displayed screen, scroll the screen left or right, unless we're
614 	 * updating the info line in which case we just scroll that one line.
615 	 * We adjust the offset up or down until we have a window that covers
616 	 * the current column, making sure that we adjust differently for the
617 	 * first screen as compared to subsequent ones.
618 	 */
619 	if (O_ISSET(sp, O_LEFTRIGHT)) {
620 		/*
621 		 * Get the screen column for this character, and correct
622 		 * for the number option offset.
623 		 */
624 		cnt = vs_columns(sp, NULL, LNO, &CNO, NULL);
625 		if (O_ISSET(sp, O_NUMBER))
626 			cnt -= O_NUMBER_LENGTH;
627 
628 		/* Adjust the window towards the beginning of the line. */
629 		off = smp->coff;
630 		if (off >= cnt) {
631 			do {
632 				if (off >= O_VAL(sp, O_SIDESCROLL))
633 					off -= O_VAL(sp, O_SIDESCROLL);
634 				else {
635 					off = 0;
636 					break;
637 				}
638 			} while (off >= cnt);
639 			goto shifted;
640 		}
641 
642 		/* Adjust the window towards the end of the line. */
643 		if ((off == 0 && off + SCREEN_COLS(sp) < cnt) ||
644 		    (off != 0 && off + sp->cols < cnt)) {
645 			do {
646 				off += O_VAL(sp, O_SIDESCROLL);
647 			} while (off + sp->cols < cnt);
648 
649 shifted:		/* Fill in screen map with the new offset. */
650 			if (F_ISSET(sp, SC_TINPUT_INFO))
651 				smp->coff = off;
652 			else {
653 				for (smp = HMAP; smp <= TMAP; ++smp)
654 					smp->coff = off;
655 				leftright_warp = 1;
656 			}
657 			goto paint;
658 		}
659 
660 		/*
661 		 * We may have jumped here to adjust a leftright screen because
662 		 * redraw was set.  If so, we have to paint the entire screen.
663 		 */
664 		if (F_ISSET(sp, SC_SCR_REDRAW))
665 			goto paint;
666 	}
667 
668 	/*
669 	 * Update the screen lines for this particular file line until we
670 	 * have a new screen cursor position.
671 	 */
672 	for (y = -1,
673 	    vip->sc_smap = NULL; smp <= TMAP && smp->lno == LNO; ++smp) {
674 		if (vs_line(sp, smp, &y, &SCNO))
675 			return (1);
676 		if (y != (size_t)-1) {
677 			vip->sc_smap = smp;
678 			break;
679 		}
680 	}
681 	goto done_cursor;
682 
683 	/*
684 	 * 8: Repaint the entire screen.
685 	 *
686 	 * Lost big, do what you have to do.  We flush the cache, since
687 	 * SC_SCR_REDRAW gets set when the screen isn't worth fixing, and
688 	 * it's simpler to repaint.  So, don't trust anything that we
689 	 * think we know about it.
690 	 */
691 paint:	for (smp = HMAP; smp <= TMAP; ++smp)
692 		SMAP_FLUSH(smp);
693 	for (y = -1, vip->sc_smap = NULL, smp = HMAP; smp <= TMAP; ++smp) {
694 		if (vs_line(sp, smp, &y, &SCNO))
695 			return (1);
696 		if (y != (size_t)-1 && vip->sc_smap == NULL)
697 			vip->sc_smap = smp;
698 	}
699 	/*
700 	 * If it's a small screen and we're redrawing, clear the unused lines,
701 	 * ex may have overwritten them.
702 	 */
703 	if (F_ISSET(sp, SC_SCR_REDRAW) && IS_SMALL(sp))
704 		for (cnt = sp->t_rows; cnt <= sp->t_maxrows; ++cnt) {
705 			(void)gp->scr_move(sp, cnt, 0);
706 			(void)gp->scr_clrtoeol(sp);
707 		}
708 
709 	didpaint = 1;
710 
711 done_cursor:
712 	/*
713 	 * Sanity checking.  When the repainting code messes up, the usual
714 	 * result is we don't repaint the cursor and so sc_smap will be
715 	 * NULL.  If we're debugging, die, otherwise restart from scratch.
716 	 */
717 #ifdef DEBUG
718 	if (vip->sc_smap == NULL) {
719 		fprintf(stderr, "smap error\n");
720 		sleep(100);
721 		abort();
722 	}
723 #else
724 	if (vip->sc_smap == NULL) {
725 		if (F_ISSET(sp, SC_SCR_REFORMAT))
726 			abort(); /* XXX infinite recursion */
727 		F_SET(sp, SC_SCR_REFORMAT);
728 		return (vs_paint(sp, flags));
729 	}
730 #endif
731 
732 	/*
733 	 * 9: Set the remembered cursor values.
734 	 */
735 	OCNO = CNO;
736 	OLNO = LNO;
737 
738 	/*
739 	 * 10: Repaint the line numbers.
740 	 *
741 	 * If O_NUMBER is set and the VIP_N_RENUMBER bit is set, and we
742 	 * didn't repaint the screen, repaint all of the line numbers,
743 	 * they've changed.
744 	 */
745 number:	if (O_ISSET(sp, O_NUMBER) &&
746 	    F_ISSET(vip, VIP_N_RENUMBER) && !didpaint && vs_number(sp))
747 		return (1);
748 
749 	/*
750 	 * 11: Update the mode line, position the cursor, and flush changes.
751 	 *
752 	 * If we warped the screen, we have to refresh everything.
753 	 */
754 	if (leftright_warp)
755 		LF_SET(UPDATE_CURSOR | UPDATE_SCREEN);
756 
757 	if (LF_ISSET(UPDATE_SCREEN) && !IS_ONELINE(sp) &&
758 	    !F_ISSET(vip, VIP_S_MODELINE) && !F_ISSET(sp, SC_TINPUT_INFO))
759 		vs_modeline(sp);
760 
761 	if (LF_ISSET(UPDATE_CURSOR)) {
762 		(void)gp->scr_move(sp, y, SCNO);
763 
764 		/*
765 		 * XXX
766 		 * If the screen shifted, we recalculate the "most favorite"
767 		 * cursor position.  Vi won't know that we've warped the
768 		 * screen, so it's going to have a wrong idea about where the
769 		 * cursor should be.  This is vi's problem, and fixing it here
770 		 * is a gross layering violation.
771 		 */
772 		if (leftright_warp)
773 			(void)vs_column(sp, &sp->rcm);
774 	}
775 
776 	if (LF_ISSET(UPDATE_SCREEN))
777 		(void)gp->scr_refresh(sp, F_ISSET(vip, VIP_N_EX_PAINT));
778 
779 	/* 12: Clear the flags that are handled by this routine. */
780 	F_CLR(sp, SC_SCR_CENTER | SC_SCR_REDRAW | SC_SCR_REFORMAT | SC_SCR_TOP);
781 	F_CLR(vip, VIP_CUR_INVALID |
782 	    VIP_N_EX_PAINT | VIP_N_REFRESH | VIP_N_RENUMBER | VIP_S_MODELINE);
783 
784 	return (0);
785 
786 #undef	 LNO
787 #undef	OLNO
788 #undef	 CNO
789 #undef	OCNO
790 #undef	SCNO
791 }
792 
793 /*
794  * vs_modeline --
795  *	Update the mode line.
796  */
797 static void
vs_modeline(SCR * sp)798 vs_modeline(SCR *sp)
799 {
800 	static const char * const modes[] = {
801 		"215|Append",			/* SM_APPEND */
802 		"216|Change",			/* SM_CHANGE */
803 		"217|Command",			/* SM_COMMAND */
804 		"218|Insert",			/* SM_INSERT */
805 		"219|Replace",			/* SM_REPLACE */
806 	};
807 	GS *gp;
808 	size_t cols, curcol, curlen, endpoint, len, midpoint;
809 	const char *t = NULL;
810 	int ellipsis;
811 	char *p, buf[20];
812 
813 	gp = sp->gp;
814 
815 	/*
816 	 * We put down the file name, the ruler, the mode and the dirty flag.
817 	 * If there's not enough room, there's not enough room, we don't play
818 	 * any special games.  We try to put the ruler in the middle and the
819 	 * mode and dirty flag at the end.
820 	 *
821 	 * !!!
822 	 * Leave the last character blank, in case it's a really dumb terminal
823 	 * with hardware scroll.  Second, don't paint the last character in the
824 	 * screen, SunOS 4.1.1 and Ultrix 4.2 curses won't let you.
825 	 *
826 	 * Move to the last line on the screen.
827 	 */
828 	(void)gp->scr_move(sp, LASTLINE(sp), 0);
829 
830 	/* If more than one screen in the display, show the file name. */
831 	curlen = 0;
832 	if (IS_SPLIT(sp)) {
833 		for (p = sp->frp->name; *p != '\0'; ++p);
834 		for (ellipsis = 0, cols = sp->cols / 2; --p > sp->frp->name;) {
835 			if (*p == '/') {
836 				++p;
837 				break;
838 			}
839 			if ((curlen += KEY_LEN(sp, *p)) > cols) {
840 				ellipsis = 3;
841 				curlen +=
842 				    KEY_LEN(sp, '.') * 3 + KEY_LEN(sp, ' ');
843 				while (curlen > cols) {
844 					++p;
845 					curlen -= KEY_LEN(sp, *p);
846 				}
847 				break;
848 			}
849 		}
850 		if (ellipsis) {
851 			while (ellipsis--)
852 				(void)gp->scr_addstr(sp,
853 				    (const char *)KEY_NAME(sp, '.'),
854 				    KEY_LEN(sp, '.'));
855 			(void)gp->scr_addstr(sp,
856 			    (const char *)KEY_NAME(sp, ' '), KEY_LEN(sp, ' '));
857 		}
858 		for (; *p != '\0'; ++p)
859 			(void)gp->scr_addstr(sp,
860 			    (const char *)KEY_NAME(sp, *p), KEY_LEN(sp, *p));
861 	}
862 
863 	/* Clear the rest of the line. */
864 	(void)gp->scr_clrtoeol(sp);
865 
866 	/*
867 	 * Display the ruler.  If we're not at the midpoint yet, move there.
868 	 * Otherwise, add in two extra spaces.
869 	 *
870 	 * Adjust the current column for the fact that the editor uses it as
871 	 * a zero-based number.
872 	 *
873 	 * XXX
874 	 * Assume that numbers, commas, and spaces only take up a single
875 	 * column on the screen.
876 	 */
877 	cols = sp->cols - 1;
878 	if (O_ISSET(sp, O_RULER)) {
879 		vs_column(sp, &curcol);
880 		len =
881 		    snprintf(buf, sizeof(buf), "%lu,%lu",
882 			(unsigned long)sp->lno, (unsigned long)curcol + 1);
883 
884 		midpoint = (cols - ((len + 1) / 2)) / 2;
885 		if (curlen < midpoint) {
886 			(void)gp->scr_move(sp, LASTLINE(sp), midpoint);
887 			curlen += len;
888 		} else if (curlen + 2 + len < cols) {
889 			(void)gp->scr_addstr(sp, "  ", 2);
890 			curlen += 2 + len;
891 		}
892 		(void)gp->scr_addstr(sp, buf, len);
893 	}
894 
895 	/*
896 	 * Display the mode and the modified flag, as close to the end of the
897 	 * line as possible, but guaranteeing at least two spaces between the
898 	 * ruler and the modified flag.
899 	 */
900 #define	MODESIZE	9
901 	endpoint = cols;
902 	if (O_ISSET(sp, O_SHOWMODE)) {
903 		if (F_ISSET(sp->ep, F_MODIFIED))
904 			--endpoint;
905 		t = msg_cat(sp, modes[sp->showmode], &len);
906 		endpoint -= len;
907 	}
908 
909 	if (endpoint > curlen + 2) {
910 		(void)gp->scr_move(sp, LASTLINE(sp), endpoint);
911 		if (O_ISSET(sp, O_SHOWMODE)) {
912 			if (F_ISSET(sp->ep, F_MODIFIED))
913 				(void)gp->scr_addstr(sp,
914 				    (const char *)KEY_NAME(sp, '*'),
915 				    KEY_LEN(sp, '*'));
916 			(void)gp->scr_addstr(sp, t, len);
917 		}
918 	}
919 }
920