xref: /freebsd/contrib/nvi/vi/vs_msg.c (revision b111430e54537a66080df115c3a2950a2922da5a)
1 /*-
2  * Copyright (c) 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  * Copyright (c) 1992, 1993, 1994, 1995, 1996
5  *	Keith Bostic.  All rights reserved.
6  *
7  * See the LICENSE file for redistribution information.
8  */
9 
10 #include "config.h"
11 
12 #include <sys/types.h>
13 #include <sys/queue.h>
14 #include <sys/time.h>
15 
16 #include <bitstring.h>
17 #include <ctype.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22 
23 #include "../common/common.h"
24 #include "vi.h"
25 
26 typedef enum {
27 	SCROLL_W,			/* User wait. */
28 	SCROLL_W_EX,			/* User wait, or enter : to continue. */
29 	SCROLL_W_QUIT			/* User wait, or enter q to quit. */
30 					/*
31 					 * SCROLL_W_QUIT has another semantic
32 					 * -- only wait if the screen is full
33 					 */
34 } sw_t;
35 
36 static void	vs_divider(SCR *);
37 static void	vs_msgsave(SCR *, mtype_t, char *, size_t);
38 static void	vs_output(SCR *, mtype_t, const char *, int);
39 static void	vs_scroll(SCR *, int *, sw_t);
40 static void	vs_wait(SCR *, int *, sw_t);
41 
42 /*
43  * vs_busy --
44  *	Display, update or clear a busy message.
45  *
46  * This routine is the default editor interface for vi busy messages.  It
47  * implements a standard strategy of stealing lines from the bottom of the
48  * vi text screen.  Screens using an alternate method of displaying busy
49  * messages, e.g. X11 clock icons, should set their scr_busy function to the
50  * correct function before calling the main editor routine.
51  *
52  * PUBLIC: void vs_busy(SCR *, const char *, busy_t);
53  */
54 void
55 vs_busy(SCR *sp, const char *msg, busy_t btype)
56 {
57 	GS *gp;
58 	VI_PRIVATE *vip;
59 	static const char flagc[] = "|/-\\";
60 	struct timespec ts, ts_diff;
61 	const struct timespec ts_min = { 0, 125000000 };
62 	size_t len, notused;
63 	const char *p;
64 
65 	/* Ex doesn't display busy messages. */
66 	if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE))
67 		return;
68 
69 	gp = sp->gp;
70 	vip = VIP(sp);
71 
72 	/*
73 	 * Most of this routine is to deal with the screen sharing real estate
74 	 * between the normal edit messages and the busy messages.  Logically,
75 	 * all that's needed is something that puts up a message, periodically
76 	 * updates it, and then goes away.
77 	 */
78 	switch (btype) {
79 	case BUSY_ON:
80 		++vip->busy_ref;
81 		if (vip->totalcount != 0 || vip->busy_ref != 1)
82 			break;
83 
84 		/* Initialize state for updates. */
85 		vip->busy_ch = 0;
86 		timepoint_steady(&vip->busy_ts);
87 
88 		/* Save the current cursor. */
89 		(void)gp->scr_cursor(sp, &vip->busy_oldy, &vip->busy_oldx);
90 
91 		/* Display the busy message. */
92 		p = msg_cat(sp, msg, &len);
93 		(void)gp->scr_move(sp, LASTLINE(sp), 0);
94 		(void)gp->scr_addstr(sp, p, len);
95 		(void)gp->scr_cursor(sp, &notused, &vip->busy_fx);
96 		(void)gp->scr_clrtoeol(sp);
97 		(void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx);
98 		break;
99 	case BUSY_OFF:
100 		if (vip->busy_ref == 0)
101 			break;
102 		--vip->busy_ref;
103 
104 		/*
105 		 * If the line isn't in use for another purpose, clear it.
106 		 * Always return to the original position.
107 		 */
108 		if (vip->totalcount == 0 && vip->busy_ref == 0) {
109 			(void)gp->scr_move(sp, LASTLINE(sp), 0);
110 			(void)gp->scr_clrtoeol(sp);
111 		}
112 		(void)gp->scr_move(sp, vip->busy_oldy, vip->busy_oldx);
113 		break;
114 	case BUSY_UPDATE:
115 		if (vip->totalcount != 0 || vip->busy_ref == 0)
116 			break;
117 
118 		/* Update no more than every 1/8 of a second. */
119 		timepoint_steady(&ts);
120 		ts_diff = ts;
121 		timespecsub(&ts_diff, &vip->busy_ts);
122 		if (timespeccmp(&ts_diff, &ts_min, <))
123 			return;
124 		vip->busy_ts = ts;
125 
126 		/* Display the update. */
127 		if (vip->busy_ch == sizeof(flagc) - 1)
128 			vip->busy_ch = 0;
129 		(void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx);
130 		(void)gp->scr_addstr(sp, flagc + vip->busy_ch++, 1);
131 		(void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx);
132 		break;
133 	}
134 	(void)gp->scr_refresh(sp, 0);
135 }
136 
137 /*
138  * vs_home --
139  *	Home the cursor to the bottom row, left-most column.
140  *
141  * PUBLIC: void vs_home(SCR *);
142  */
143 void
144 vs_home(SCR *sp)
145 {
146 	(void)sp->gp->scr_move(sp, LASTLINE(sp), 0);
147 	(void)sp->gp->scr_refresh(sp, 0);
148 }
149 
150 /*
151  * vs_update --
152  *	Update a command.
153  *
154  * PUBLIC: void vs_update(SCR *, const char *, const CHAR_T *);
155  */
156 void
157 vs_update(SCR *sp, const char *m1, const CHAR_T *m2)
158 {
159 	GS *gp;
160 	size_t len, mlen, oldx, oldy;
161 	CONST char *np;
162 	size_t nlen;
163 
164 	gp = sp->gp;
165 
166 	/*
167 	 * This routine displays a message on the bottom line of the screen,
168 	 * without updating any of the command structures that would keep it
169 	 * there for any period of time, i.e. it is overwritten immediately.
170 	 *
171 	 * It's used by the ex read and ! commands when the user's command is
172 	 * expanded, and by the ex substitution confirmation prompt.
173 	 */
174 	if (F_ISSET(sp, SC_SCR_EXWROTE)) {
175 		if (m2 != NULL)
176 			INT2CHAR(sp, m2, STRLEN(m2) + 1, np, nlen);
177 		(void)ex_printf(sp,
178 		    "%s\n", m1 == NULL? "" : m1, m2 == NULL ? "" : np);
179 		(void)ex_fflush(sp);
180 	}
181 
182 	/*
183 	 * Save the cursor position, the substitute-with-confirmation code
184 	 * will have already set it correctly.
185 	 */
186 	(void)gp->scr_cursor(sp, &oldy, &oldx);
187 
188 	/* Clear the bottom line. */
189 	(void)gp->scr_move(sp, LASTLINE(sp), 0);
190 	(void)gp->scr_clrtoeol(sp);
191 
192 	/*
193 	 * XXX
194 	 * Don't let long file names screw up the screen.
195 	 */
196 	if (m1 != NULL) {
197 		mlen = len = strlen(m1);
198 		if (len > sp->cols - 2)
199 			mlen = len = sp->cols - 2;
200 		(void)gp->scr_addstr(sp, m1, mlen);
201 	} else
202 		len = 0;
203 	if (m2 != NULL) {
204 		mlen = STRLEN(m2);
205 		if (len + mlen > sp->cols - 2)
206 			mlen = (sp->cols - 2) - len;
207 		(void)gp->scr_waddstr(sp, m2, mlen);
208 	}
209 
210 	(void)gp->scr_move(sp, oldy, oldx);
211 	(void)gp->scr_refresh(sp, 0);
212 }
213 
214 /*
215  * vs_msg --
216  *	Display ex output or error messages for the screen.
217  *
218  * This routine is the default editor interface for all ex output, and all ex
219  * and vi error/informational messages.  It implements the standard strategy
220  * of stealing lines from the bottom of the vi text screen.  Screens using an
221  * alternate method of displaying messages, e.g. dialog boxes, should set their
222  * scr_msg function to the correct function before calling the editor.
223  *
224  * PUBLIC: void vs_msg(SCR *, mtype_t, char *, size_t);
225  */
226 void
227 vs_msg(SCR *sp, mtype_t mtype, char *line, size_t len)
228 {
229 	GS *gp;
230 	VI_PRIVATE *vip;
231 	size_t maxcols, oldx, oldy, padding;
232 	const char *e, *s, *t;
233 
234 	gp = sp->gp;
235 	vip = VIP(sp);
236 
237 	/*
238 	 * Ring the bell if it's scheduled.
239 	 *
240 	 * XXX
241 	 * Shouldn't we save this, too?
242 	 */
243 	if (F_ISSET(sp, SC_TINPUT_INFO) || F_ISSET(gp, G_BELLSCHED)) {
244 		if (F_ISSET(sp, SC_SCR_VI)) {
245 			F_CLR(gp, G_BELLSCHED);
246 			(void)gp->scr_bell(sp);
247 		} else
248 			F_SET(gp, G_BELLSCHED);
249 	}
250 
251 	/*
252 	 * If vi is using the error line for text input, there's no screen
253 	 * real-estate for the error message.  Nothing to do without some
254 	 * information as to how important the error message is.
255 	 */
256 	if (F_ISSET(sp, SC_TINPUT_INFO))
257 		return;
258 
259 	/*
260 	 * Ex or ex controlled screen output.
261 	 *
262 	 * If output happens during startup, e.g., a .exrc file, we may be
263 	 * in ex mode but haven't initialized the screen.  Initialize here,
264 	 * and in this case, stay in ex mode.
265 	 *
266 	 * If the SC_SCR_EXWROTE bit is set, then we're switching back and
267 	 * forth between ex and vi, but the screen is trashed and we have
268 	 * to respect that.  Switch to ex mode long enough to put out the
269 	 * message.
270 	 *
271 	 * If the SC_EX_WAIT_NO bit is set, turn it off -- we're writing to
272 	 * the screen, so previous opinions are ignored.
273 	 */
274 	if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)) {
275 		if (!F_ISSET(sp, SC_SCR_EX)) {
276 			if (F_ISSET(sp, SC_SCR_EXWROTE)) {
277 				if (sp->gp->scr_screen(sp, SC_EX))
278 					return;
279 			} else
280 				if (ex_init(sp))
281 					return;
282 		}
283 
284 		if (mtype == M_ERR)
285 			(void)gp->scr_attr(sp, SA_INVERSE, 1);
286 		(void)printf("%.*s", (int)len, line);
287 		if (mtype == M_ERR)
288 			(void)gp->scr_attr(sp, SA_INVERSE, 0);
289 		(void)fflush(stdout);
290 
291 		F_CLR(sp, SC_EX_WAIT_NO);
292 
293 		if (!F_ISSET(sp, SC_SCR_EX))
294 			(void)sp->gp->scr_screen(sp, SC_VI);
295 		return;
296 	}
297 
298 	/* If the vi screen isn't ready, save the message. */
299 	if (!F_ISSET(sp, SC_SCR_VI)) {
300 		(void)vs_msgsave(sp, mtype, line, len);
301 		return;
302 	}
303 
304 	/* Save the cursor position. */
305 	(void)gp->scr_cursor(sp, &oldy, &oldx);
306 
307 	/* If it's an ex output message, just write it out. */
308 	if (mtype == M_NONE) {
309 		vs_output(sp, mtype, line, len);
310 		goto ret;
311 	}
312 
313 	/*
314 	 * If it's a vi message, strip the trailing <newline> so we can
315 	 * try and paste messages together.
316 	 */
317 	if (line[len - 1] == '\n')
318 		--len;
319 
320 	/*
321 	 * If a message won't fit on a single line, try to split on a <blank>.
322 	 * If a subsequent message fits on the same line, write a separator
323 	 * and output it.  Otherwise, put out a newline.
324 	 *
325 	 * Need up to two padding characters normally; a semi-colon and a
326 	 * separating space.  If only a single line on the screen, add some
327 	 * more for the trailing continuation message.
328 	 *
329 	 * XXX
330 	 * Assume that periods and semi-colons take up a single column on the
331 	 * screen.
332 	 *
333 	 * XXX
334 	 * There are almost certainly pathological cases that will break this
335 	 * code.
336 	 */
337 	if (IS_ONELINE(sp))
338 		(void)msg_cmsg(sp, CMSG_CONT_S, &padding);
339 	else
340 		padding = 0;
341 	padding += 2;
342 
343 	maxcols = sp->cols - 1;
344 	if (vip->lcontinue != 0) {
345 		if (len + vip->lcontinue + padding > maxcols)
346 			vs_output(sp, vip->mtype, ".\n", 2);
347 		else  {
348 			vs_output(sp, vip->mtype, ";", 1);
349 			vs_output(sp, M_NONE, " ", 1);
350 		}
351 	}
352 	vip->mtype = mtype;
353 	for (s = line;; s = t) {
354 		for (; len > 0 && isblank((u_char)*s); --len, ++s);
355 		if (len == 0)
356 			break;
357 		if (len + vip->lcontinue > maxcols) {
358 			for (e = s + (maxcols - vip->lcontinue);
359 			    e > s && !isblank((u_char)*e); --e);
360 			if (e == s)
361 				 e = t = s + (maxcols - vip->lcontinue);
362 			else
363 				for (t = e; isblank((u_char)e[-1]); --e);
364 		} else
365 			e = t = s + len;
366 
367 		/*
368 		 * If the message ends in a period, discard it, we want to
369 		 * gang messages where possible.
370 		 */
371 		len -= t - s;
372 		if (len == 0 && (e - s) > 1 && s[(e - s) - 1] == '.')
373 			--e;
374 		vs_output(sp, mtype, s, e - s);
375 
376 		if (len != 0)
377 			vs_output(sp, M_NONE, "\n", 1);
378 
379 		if (INTERRUPTED(sp))
380 			break;
381 	}
382 
383 ret:	(void)gp->scr_move(sp, oldy, oldx);
384 	(void)gp->scr_refresh(sp, 0);
385 }
386 
387 /*
388  * vs_output --
389  *	Output the text to the screen.
390  */
391 static void
392 vs_output(SCR *sp, mtype_t mtype, const char *line, int llen)
393 {
394 	GS *gp;
395 	VI_PRIVATE *vip;
396 	size_t notused;
397 	int len, rlen, tlen;
398 	const char *p, *t;
399 	char *cbp, *ecbp, cbuf[128];
400 
401 	gp = sp->gp;
402 	vip = VIP(sp);
403 	for (p = line, rlen = llen; llen > 0;) {
404 		/* Get the next physical line. */
405 		if ((p = memchr(line, '\n', llen)) == NULL)
406 			len = llen;
407 		else
408 			len = p - line;
409 
410 		/*
411 		 * The max is sp->cols characters, and we may have already
412 		 * written part of the line.
413 		 */
414 		if (len + vip->lcontinue > sp->cols)
415 			len = sp->cols - vip->lcontinue;
416 
417 		/*
418 		 * If the first line output, do nothing.  If the second line
419 		 * output, draw the divider line.  If drew a full screen, we
420 		 * remove the divider line.  If it's a continuation line, move
421 		 * to the continuation point, else, move the screen up.
422 		 */
423 		if (vip->lcontinue == 0) {
424 			if (!IS_ONELINE(sp)) {
425 				if (vip->totalcount == 1) {
426 					(void)gp->scr_move(sp,
427 					    LASTLINE(sp) - 1, 0);
428 					(void)gp->scr_clrtoeol(sp);
429 					(void)vs_divider(sp);
430 					F_SET(vip, VIP_DIVIDER);
431 					++vip->totalcount;
432 					++vip->linecount;
433 				}
434 				if (vip->totalcount == sp->t_maxrows &&
435 				    F_ISSET(vip, VIP_DIVIDER)) {
436 					--vip->totalcount;
437 					--vip->linecount;
438 					F_CLR(vip, VIP_DIVIDER);
439 				}
440 			}
441 			if (vip->totalcount != 0)
442 				vs_scroll(sp, NULL, SCROLL_W_QUIT);
443 
444 			(void)gp->scr_move(sp, LASTLINE(sp), 0);
445 			++vip->totalcount;
446 			++vip->linecount;
447 
448 			if (INTERRUPTED(sp))
449 				break;
450 		} else
451 			(void)gp->scr_move(sp, LASTLINE(sp), vip->lcontinue);
452 
453 		/* Error messages are in inverse video. */
454 		if (mtype == M_ERR)
455 			(void)gp->scr_attr(sp, SA_INVERSE, 1);
456 
457 		/* Display the line, doing character translation. */
458 #define	FLUSH do {							\
459 	*cbp = '\0';							\
460 	(void)gp->scr_addstr(sp, cbuf, cbp - cbuf);			\
461 	cbp = cbuf;							\
462 } while (0)
463 		ecbp = (cbp = cbuf) + sizeof(cbuf) - 1;
464 		for (t = line, tlen = len; tlen--; ++t) {
465 			/*
466 			 * Replace tabs with spaces, there are places in
467 			 * ex that do column calculations without looking
468 			 * at <tabs> -- and all routines that care about
469 			 * <tabs> do their own expansions.  This catches
470 			 * <tabs> in things like tag search strings.
471 			 */
472 			if (cbp + 1 >= ecbp)
473 				FLUSH;
474 			*cbp++ = *t == '\t' ? ' ' : *t;
475 		}
476 		if (cbp > cbuf)
477 			FLUSH;
478 		if (mtype == M_ERR)
479 			(void)gp->scr_attr(sp, SA_INVERSE, 0);
480 
481 		/* Clear the rest of the line. */
482 		(void)gp->scr_clrtoeol(sp);
483 
484 		/* If we loop, it's a new line. */
485 		vip->lcontinue = 0;
486 
487 		/* Reset for the next line. */
488 		line += len;
489 		llen -= len;
490 		if (p != NULL) {
491 			++line;
492 			--llen;
493 		}
494 	}
495 
496 	/* Set up next continuation line. */
497 	if (p == NULL)
498 		gp->scr_cursor(sp, &notused, &vip->lcontinue);
499 }
500 
501 /*
502  * vs_ex_resolve --
503  *	Deal with ex message output.
504  *
505  * This routine is called when exiting a colon command to resolve any ex
506  * output that may have occurred.
507  *
508  * PUBLIC: int vs_ex_resolve(SCR *, int *);
509  */
510 int
511 vs_ex_resolve(SCR *sp, int *continuep)
512 {
513 	EVENT ev;
514 	GS *gp;
515 	VI_PRIVATE *vip;
516 	sw_t wtype;
517 
518 	gp = sp->gp;
519 	vip = VIP(sp);
520 	*continuep = 0;
521 
522 	/* If we ran any ex command, we can't trust the cursor position. */
523 	F_SET(vip, VIP_CUR_INVALID);
524 
525 	/* Terminate any partially written message. */
526 	if (vip->lcontinue != 0) {
527 		vs_output(sp, vip->mtype, ".", 1);
528 		vip->lcontinue = 0;
529 
530 		vip->mtype = M_NONE;
531 	}
532 
533 	/*
534 	 * If we switched out of the vi screen into ex, switch back while we
535 	 * figure out what to do with the screen and potentially get another
536 	 * command to execute.
537 	 *
538 	 * If we didn't switch into ex, we're not required to wait, and less
539 	 * than 2 lines of output, we can continue without waiting for the
540 	 * wait.
541 	 *
542 	 * Note, all other code paths require waiting, so we leave the report
543 	 * of modified lines until later, so that we won't wait for no other
544 	 * reason than a threshold number of lines were modified.  This means
545 	 * we display cumulative line modification reports for groups of ex
546 	 * commands.  That seems right to me (well, at least not wrong).
547 	 */
548 	if (F_ISSET(sp, SC_SCR_EXWROTE)) {
549 		if (sp->gp->scr_screen(sp, SC_VI))
550 			return (1);
551 	} else
552 		if (!F_ISSET(sp, SC_EX_WAIT_YES) && vip->totalcount < 2) {
553 			F_CLR(sp, SC_EX_WAIT_NO);
554 			return (0);
555 		}
556 
557 	/* Clear the required wait flag, it's no longer needed. */
558 	F_CLR(sp, SC_EX_WAIT_YES);
559 
560 	/*
561 	 * Wait, unless explicitly told not to wait or the user interrupted
562 	 * the command.  If the user is leaving the screen, for any reason,
563 	 * they can't continue with further ex commands.
564 	 */
565 	if (!F_ISSET(sp, SC_EX_WAIT_NO) && !INTERRUPTED(sp)) {
566 		wtype = F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE |
567 		    SC_FSWITCH | SC_SSWITCH) ? SCROLL_W : SCROLL_W_EX;
568 		if (F_ISSET(sp, SC_SCR_EXWROTE))
569 			vs_wait(sp, continuep, wtype);
570 		else
571 			vs_scroll(sp, continuep, wtype);
572 		if (*continuep)
573 			return (0);
574 	}
575 
576 	/* If ex wrote on the screen, refresh the screen image. */
577 	if (F_ISSET(sp, SC_SCR_EXWROTE))
578 		F_SET(vip, VIP_N_EX_PAINT);
579 
580 	/*
581 	 * If we're not the bottom of the split screen stack, the screen
582 	 * image itself is wrong, so redraw everything.
583 	 */
584 	if (TAILQ_NEXT(sp, q) != NULL)
585 		F_SET(sp, SC_SCR_REDRAW);
586 
587 	/* If ex changed the underlying file, the map itself is wrong. */
588 	if (F_ISSET(vip, VIP_N_EX_REDRAW))
589 		F_SET(sp, SC_SCR_REFORMAT);
590 
591 	/* Ex may have switched out of the alternate screen, return. */
592 	(void)gp->scr_attr(sp, SA_ALTERNATE, 1);
593 
594 	/*
595 	 * Whew.  We're finally back home, after what feels like years.
596 	 * Kiss the ground.
597 	 */
598 	F_CLR(sp, SC_SCR_EXWROTE | SC_EX_WAIT_NO);
599 
600 	/*
601 	 * We may need to repaint some of the screen, e.g.:
602 	 *
603 	 *	:set
604 	 *	:!ls
605 	 *
606 	 * gives us a combination of some lines that are "wrong", and a need
607 	 * for a full refresh.
608 	 */
609 	if (vip->totalcount > 1) {
610 		/* Set up the redraw of the overwritten lines. */
611 		ev.e_event = E_REPAINT;
612 		ev.e_flno = vip->totalcount >=
613 		    sp->rows ? 1 : sp->rows - vip->totalcount;
614 		ev.e_tlno = sp->rows;
615 
616 		/* Reset the count of overwriting lines. */
617 		vip->linecount = vip->lcontinue = vip->totalcount = 0;
618 
619 		/* Redraw. */
620 		(void)vs_repaint(sp, &ev);
621 	} else
622 		/* Reset the count of overwriting lines. */
623 		vip->linecount = vip->lcontinue = vip->totalcount = 0;
624 
625 	return (0);
626 }
627 
628 /*
629  * vs_resolve --
630  *	Deal with message output.
631  *
632  * PUBLIC: int vs_resolve(SCR *, SCR *, int);
633  */
634 int
635 vs_resolve(SCR *sp, SCR *csp, int forcewait)
636 {
637 	EVENT ev;
638 	GS *gp;
639 	MSGS *mp;
640 	VI_PRIVATE *vip;
641 	size_t oldy, oldx;
642 	int redraw;
643 
644 	/*
645 	 * Vs_resolve is called from the main vi loop and the refresh function
646 	 * to periodically ensure that the user has seen any messages that have
647 	 * been displayed and that any status lines are correct.  The sp screen
648 	 * is the screen we're checking, usually the current screen.  When it's
649 	 * not, csp is the current screen, used for final cursor positioning.
650 	 */
651 	gp = sp->gp;
652 	vip = VIP(sp);
653 	if (csp == NULL)
654 		csp = sp;
655 
656 	/* Save the cursor position. */
657 	(void)gp->scr_cursor(csp, &oldy, &oldx);
658 
659 	/* Ring the bell if it's scheduled. */
660 	if (F_ISSET(gp, G_BELLSCHED)) {
661 		F_CLR(gp, G_BELLSCHED);
662 		(void)gp->scr_bell(sp);
663 	}
664 
665 	/* Display new file status line. */
666 	if (F_ISSET(sp, SC_STATUS)) {
667 		F_CLR(sp, SC_STATUS);
668 		msgq_status(sp, sp->lno, MSTAT_TRUNCATE);
669 	}
670 
671 	/* Report on line modifications. */
672 	mod_rpt(sp);
673 
674 	/*
675 	 * Flush any saved messages.  If the screen isn't ready, refresh
676 	 * it.  (A side-effect of screen refresh is that we can display
677 	 * messages.)  Once this is done, don't trust the cursor.  That
678 	 * extra refresh screwed the pooch.
679 	 */
680 	if (!SLIST_EMPTY(gp->msgq)) {
681 		if (!F_ISSET(sp, SC_SCR_VI) && vs_refresh(sp, 1))
682 			return (1);
683 		while ((mp = SLIST_FIRST(gp->msgq)) != NULL) {
684 			gp->scr_msg(sp, mp->mtype, mp->buf, mp->len);
685 			SLIST_REMOVE_HEAD(gp->msgq, q);
686 			free(mp->buf);
687 			free(mp);
688 		}
689 		F_SET(vip, VIP_CUR_INVALID);
690 	}
691 
692 	switch (vip->totalcount) {
693 	case 0:
694 		redraw = 0;
695 		break;
696 	case 1:
697 		/*
698 		 * If we're switching screens, we have to wait for messages,
699 		 * regardless.  If we don't wait, skip updating the modeline.
700 		 */
701 		if (forcewait)
702 			vs_scroll(sp, NULL, SCROLL_W);
703 		else
704 			F_SET(vip, VIP_S_MODELINE);
705 
706 		redraw = 0;
707 		break;
708 	default:
709 		/*
710 		 * If >1 message line in use, prompt the user to continue and
711 		 * repaint overwritten lines.
712 		 */
713 		vs_scroll(sp, NULL, SCROLL_W);
714 
715 		ev.e_event = E_REPAINT;
716 		ev.e_flno = vip->totalcount >=
717 		    sp->rows ? 1 : sp->rows - vip->totalcount;
718 		ev.e_tlno = sp->rows;
719 
720 		redraw = 1;
721 		break;
722 	}
723 
724 	/* Reset the count of overwriting lines. */
725 	vip->linecount = vip->lcontinue = vip->totalcount = 0;
726 
727 	/* Redraw. */
728 	if (redraw)
729 		(void)vs_repaint(sp, &ev);
730 
731 	/* Restore the cursor position. */
732 	(void)gp->scr_move(csp, oldy, oldx);
733 
734 	return (0);
735 }
736 
737 /*
738  * vs_scroll --
739  *	Scroll the screen for output.
740  */
741 static void
742 vs_scroll(SCR *sp, int *continuep, sw_t wtype)
743 {
744 	GS *gp;
745 	VI_PRIVATE *vip;
746 
747 	gp = sp->gp;
748 	vip = VIP(sp);
749 	if (!IS_ONELINE(sp)) {
750 		/*
751 		 * Scroll the screen.  Instead of scrolling the entire screen,
752 		 * delete the line above the first line output so preserve the
753 		 * maximum amount of the screen.
754 		 */
755 		(void)gp->scr_move(sp, vip->totalcount <
756 		    sp->rows ? LASTLINE(sp) - vip->totalcount : 0, 0);
757 		(void)gp->scr_deleteln(sp);
758 
759 		/* If there are screens below us, push them back into place. */
760 		if (TAILQ_NEXT(sp, q) != NULL) {
761 			(void)gp->scr_move(sp, LASTLINE(sp), 0);
762 			(void)gp->scr_insertln(sp);
763 		}
764 	}
765 	if (wtype == SCROLL_W_QUIT && vip->linecount < sp->t_maxrows)
766 		return;
767 	vs_wait(sp, continuep, wtype);
768 }
769 
770 /*
771  * vs_wait --
772  *	Prompt the user to continue.
773  */
774 static void
775 vs_wait(SCR *sp, int *continuep, sw_t wtype)
776 {
777 	EVENT ev;
778 	VI_PRIVATE *vip;
779 	const char *p;
780 	GS *gp;
781 	size_t len;
782 
783 	gp = sp->gp;
784 	vip = VIP(sp);
785 
786 	(void)gp->scr_move(sp, LASTLINE(sp), 0);
787 	if (IS_ONELINE(sp))
788 		p = msg_cmsg(sp, CMSG_CONT_S, &len);
789 	else
790 		switch (wtype) {
791 		case SCROLL_W_QUIT:
792 			p = msg_cmsg(sp, CMSG_CONT_Q, &len);
793 			break;
794 		case SCROLL_W_EX:
795 			p = msg_cmsg(sp, CMSG_CONT_EX, &len);
796 			break;
797 		case SCROLL_W:
798 			p = msg_cmsg(sp, CMSG_CONT, &len);
799 			break;
800 		default:
801 			abort();
802 			/* NOTREACHED */
803 		}
804 	(void)gp->scr_addstr(sp, p, len);
805 
806 	++vip->totalcount;
807 	vip->linecount = 0;
808 
809 	(void)gp->scr_clrtoeol(sp);
810 	(void)gp->scr_refresh(sp, 0);
811 
812 	/* Get a single character from the terminal. */
813 	if (continuep != NULL)
814 		*continuep = 0;
815 	for (;;) {
816 		if (v_event_get(sp, &ev, 0, 0))
817 			return;
818 		if (ev.e_event == E_CHARACTER)
819 			break;
820 		if (ev.e_event == E_INTERRUPT) {
821 			ev.e_c = CH_QUIT;
822 			F_SET(gp, G_INTERRUPTED);
823 			break;
824 		}
825 		(void)gp->scr_bell(sp);
826 	}
827 	switch (wtype) {
828 	case SCROLL_W_QUIT:
829 		if (ev.e_c == CH_QUIT)
830 			F_SET(gp, G_INTERRUPTED);
831 		break;
832 	case SCROLL_W_EX:
833 		if (ev.e_c == ':' && continuep != NULL)
834 			*continuep = 1;
835 		break;
836 	case SCROLL_W:
837 		break;
838 	}
839 }
840 
841 /*
842  * vs_divider --
843  *	Draw a dividing line between the screen and the output.
844  */
845 static void
846 vs_divider(SCR *sp)
847 {
848 	GS *gp;
849 	size_t len;
850 
851 #define	DIVIDESTR	"+=+=+=+=+=+=+=+"
852 	len =
853 	    sizeof(DIVIDESTR) - 1 > sp->cols ? sp->cols : sizeof(DIVIDESTR) - 1;
854 	gp = sp->gp;
855 	(void)gp->scr_attr(sp, SA_INVERSE, 1);
856 	(void)gp->scr_addstr(sp, DIVIDESTR, len);
857 	(void)gp->scr_attr(sp, SA_INVERSE, 0);
858 }
859 
860 /*
861  * vs_msgsave --
862  *	Save a message for later display.
863  */
864 static void
865 vs_msgsave(SCR *sp, mtype_t mt, char *p, size_t len)
866 {
867 	GS *gp;
868 	MSGS *mp_c, *mp_n;
869 
870 	/*
871 	 * We have to handle messages before we have any place to put them.
872 	 * If there's no screen support yet, allocate a msg structure, copy
873 	 * in the message, and queue it on the global structure.  If we can't
874 	 * allocate memory here, we're genuinely screwed, dump the message
875 	 * to stderr in the (probably) vain hope that someone will see it.
876 	 */
877 	CALLOC_GOTO(sp, mp_n, 1, sizeof(MSGS));
878 	MALLOC_GOTO(sp, mp_n->buf, len);
879 
880 	memmove(mp_n->buf, p, len);
881 	mp_n->len = len;
882 	mp_n->mtype = mt;
883 
884 	gp = sp->gp;
885 	if (SLIST_EMPTY(gp->msgq)) {
886 		SLIST_INSERT_HEAD(gp->msgq, mp_n, q);
887 	} else {
888 		SLIST_FOREACH(mp_c, gp->msgq, q)
889 			if (SLIST_NEXT(mp_c, q) == NULL)
890 				break;
891 		SLIST_INSERT_AFTER(mp_c, mp_n, q);
892 	}
893 	return;
894 
895 alloc_err:
896 	free(mp_n);
897 	(void)fprintf(stderr, "%.*s\n", (int)len, p);
898 }
899