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.
7 * See the LICENSE file for redistribution information.
12 #include <sys/types.h>
13 #include <sys/queue.h>
16 #include <bitstring.h>
23 #include "../common/common.h"
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. */
31 * SCROLL_W_QUIT has another semantic
32 * -- only wait if the screen is full
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);
44 * Display, update or clear a busy message.
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.
52 * PUBLIC: void vs_busy(SCR *, const char *, busy_t);
55 vs_busy(SCR *sp, const char *msg, busy_t btype)
59 static const char flagc[] = "|/-\\";
60 struct timespec ts, ts_diff;
61 const struct timespec ts_min = { 0, 125000000 };
65 /* Ex doesn't display busy messages. */
66 if (F_ISSET(sp, SC_EX | SC_SCR_EXWROTE))
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.
81 if (vip->totalcount != 0 || vip->busy_ref != 1)
84 /* Initialize state for updates. */
86 timepoint_steady(&vip->busy_ts);
88 /* Save the current cursor. */
89 (void)gp->scr_cursor(sp, &vip->busy_oldy, &vip->busy_oldx);
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, ¬used, &vip->busy_fx);
96 (void)gp->scr_clrtoeol(sp);
97 (void)gp->scr_move(sp, LASTLINE(sp), vip->busy_fx);
100 if (vip->busy_ref == 0)
105 * If the line isn't in use for another purpose, clear it.
106 * Always return to the original position.
108 if (vip->totalcount == 0 && vip->busy_ref == 0) {
109 (void)gp->scr_move(sp, LASTLINE(sp), 0);
110 (void)gp->scr_clrtoeol(sp);
112 (void)gp->scr_move(sp, vip->busy_oldy, vip->busy_oldx);
115 if (vip->totalcount != 0 || vip->busy_ref == 0)
118 /* Update no more than every 1/8 of a second. */
119 timepoint_steady(&ts);
121 timespecsub(&ts_diff, &vip->busy_ts);
122 if (timespeccmp(&ts_diff, &ts_min, <))
126 /* Display the update. */
127 if (vip->busy_ch == sizeof(flagc) - 1)
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);
134 (void)gp->scr_refresh(sp, 0);
139 * Home the cursor to the bottom row, left-most column.
141 * PUBLIC: void vs_home(SCR *);
146 (void)sp->gp->scr_move(sp, LASTLINE(sp), 0);
147 (void)sp->gp->scr_refresh(sp, 0);
154 * PUBLIC: void vs_update(SCR *, const char *, const CHAR_T *);
157 vs_update(SCR *sp, const char *m1, const CHAR_T *m2)
160 size_t len, mlen, oldx, oldy;
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.
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.
174 if (F_ISSET(sp, SC_SCR_EXWROTE)) {
176 INT2CHAR(sp, m2, STRLEN(m2) + 1, np, nlen);
178 "%s\n", m1 == NULL? "" : m1, m2 == NULL ? "" : np);
183 * Save the cursor position, the substitute-with-confirmation code
184 * will have already set it correctly.
186 (void)gp->scr_cursor(sp, &oldy, &oldx);
188 /* Clear the bottom line. */
189 (void)gp->scr_move(sp, LASTLINE(sp), 0);
190 (void)gp->scr_clrtoeol(sp);
194 * Don't let long file names screw up the screen.
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);
205 if (len + mlen > sp->cols - 2)
206 mlen = (sp->cols - 2) - len;
207 (void)gp->scr_waddstr(sp, m2, mlen);
210 (void)gp->scr_move(sp, oldy, oldx);
211 (void)gp->scr_refresh(sp, 0);
216 * Display ex output or error messages for the screen.
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.
224 * PUBLIC: void vs_msg(SCR *, mtype_t, char *, size_t);
227 vs_msg(SCR *sp, mtype_t mtype, char *line, size_t len)
231 size_t maxcols, oldx, oldy, padding;
232 const char *e, *s, *t;
238 * Ring the bell if it's scheduled.
241 * Shouldn't we save this, too?
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);
248 F_SET(gp, G_BELLSCHED);
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.
256 if (F_ISSET(sp, SC_TINPUT_INFO))
260 * Ex or ex controlled screen output.
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.
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
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.
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))
285 (void)gp->scr_attr(sp, SA_INVERSE, 1);
286 (void)printf("%.*s", (int)len, line);
288 (void)gp->scr_attr(sp, SA_INVERSE, 0);
289 (void)fflush(stdout);
291 F_CLR(sp, SC_EX_WAIT_NO);
293 if (!F_ISSET(sp, SC_SCR_EX))
294 (void)sp->gp->scr_screen(sp, SC_VI);
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);
304 /* Save the cursor position. */
305 (void)gp->scr_cursor(sp, &oldy, &oldx);
307 /* If it's an ex output message, just write it out. */
308 if (mtype == M_NONE) {
309 vs_output(sp, mtype, line, len);
314 * If it's a vi message, strip the trailing <newline> so we can
315 * try and paste messages together.
317 if (line[len - 1] == '\n')
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.
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.
330 * Assume that periods and semi-colons take up a single column on the
334 * There are almost certainly pathological cases that will break this
338 (void)msg_cmsg(sp, CMSG_CONT_S, &padding);
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);
348 vs_output(sp, vip->mtype, ";", 1);
349 vs_output(sp, M_NONE, " ", 1);
353 for (s = line;; s = t) {
354 for (; len > 0 && isblank((u_char)*s); --len, ++s);
357 if (len + vip->lcontinue > maxcols) {
358 for (e = s + (maxcols - vip->lcontinue);
359 e > s && !isblank((u_char)*e); --e);
361 e = t = s + (maxcols - vip->lcontinue);
363 for (t = e; isblank((u_char)e[-1]); --e);
368 * If the message ends in a period, discard it, we want to
369 * gang messages where possible.
372 if (len == 0 && (e - s) > 1 && s[(e - s) - 1] == '.')
374 vs_output(sp, mtype, s, e - s);
377 vs_output(sp, M_NONE, "\n", 1);
383 ret: (void)gp->scr_move(sp, oldy, oldx);
384 (void)gp->scr_refresh(sp, 0);
389 * Output the text to the screen.
392 vs_output(SCR *sp, mtype_t mtype, const char *line, int llen)
399 char *cbp, *ecbp, cbuf[128];
403 for (p = line, rlen = llen; llen > 0;) {
404 /* Get the next physical line. */
405 if ((p = memchr(line, '\n', llen)) == NULL)
411 * The max is sp->cols characters, and we may have already
412 * written part of the line.
414 if (len + vip->lcontinue > sp->cols)
415 len = sp->cols - vip->lcontinue;
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.
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);
434 if (vip->totalcount == sp->t_maxrows &&
435 F_ISSET(vip, VIP_DIVIDER)) {
438 F_CLR(vip, VIP_DIVIDER);
441 if (vip->totalcount != 0)
442 vs_scroll(sp, NULL, SCROLL_W_QUIT);
444 (void)gp->scr_move(sp, LASTLINE(sp), 0);
451 (void)gp->scr_move(sp, LASTLINE(sp), vip->lcontinue);
453 /* Error messages are in inverse video. */
455 (void)gp->scr_attr(sp, SA_INVERSE, 1);
457 /* Display the line, doing character translation. */
460 (void)gp->scr_addstr(sp, cbuf, cbp - cbuf); \
463 ecbp = (cbp = cbuf) + sizeof(cbuf) - 1;
464 for (t = line, tlen = len; tlen--; ++t) {
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.
474 *cbp++ = *t == '\t' ? ' ' : *t;
479 (void)gp->scr_attr(sp, SA_INVERSE, 0);
481 /* Clear the rest of the line. */
482 (void)gp->scr_clrtoeol(sp);
484 /* If we loop, it's a new line. */
487 /* Reset for the next line. */
496 /* Set up next continuation line. */
498 gp->scr_cursor(sp, ¬used, &vip->lcontinue);
503 * Deal with ex message output.
505 * This routine is called when exiting a colon command to resolve any ex
506 * output that may have occurred.
508 * PUBLIC: int vs_ex_resolve(SCR *, int *);
511 vs_ex_resolve(SCR *sp, int *continuep)
522 /* If we ran any ex command, we can't trust the cursor position. */
523 F_SET(vip, VIP_CUR_INVALID);
525 /* Terminate any partially written message. */
526 if (vip->lcontinue != 0) {
527 vs_output(sp, vip->mtype, ".", 1);
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.
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
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).
548 if (F_ISSET(sp, SC_SCR_EXWROTE)) {
549 if (sp->gp->scr_screen(sp, SC_VI))
552 if (!F_ISSET(sp, SC_EX_WAIT_YES) && vip->totalcount < 2) {
553 F_CLR(sp, SC_EX_WAIT_NO);
557 /* Clear the required wait flag, it's no longer needed. */
558 F_CLR(sp, SC_EX_WAIT_YES);
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.
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);
571 vs_scroll(sp, continuep, wtype);
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);
581 * If we're not the bottom of the split screen stack, the screen
582 * image itself is wrong, so redraw everything.
584 if (TAILQ_NEXT(sp, q) != NULL)
585 F_SET(sp, SC_SCR_REDRAW);
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);
591 /* Ex may have switched out of the alternate screen, return. */
592 (void)gp->scr_attr(sp, SA_ALTERNATE, 1);
595 * Whew. We're finally back home, after what feels like years.
598 F_CLR(sp, SC_SCR_EXWROTE | SC_EX_WAIT_NO);
601 * We may need to repaint some of the screen, e.g.:
606 * gives us a combination of some lines that are "wrong", and a need
607 * for a full refresh.
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;
616 /* Reset the count of overwriting lines. */
617 vip->linecount = vip->lcontinue = vip->totalcount = 0;
620 (void)vs_repaint(sp, &ev);
622 /* Reset the count of overwriting lines. */
623 vip->linecount = vip->lcontinue = vip->totalcount = 0;
630 * Deal with message output.
632 * PUBLIC: int vs_resolve(SCR *, SCR *, int);
635 vs_resolve(SCR *sp, SCR *csp, int forcewait)
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.
656 /* Save the cursor position. */
657 (void)gp->scr_cursor(csp, &oldy, &oldx);
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);
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);
671 /* Report on line modifications. */
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.
680 if (!SLIST_EMPTY(gp->msgq)) {
681 if (!F_ISSET(sp, SC_SCR_VI) && vs_refresh(sp, 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);
689 F_SET(vip, VIP_CUR_INVALID);
692 switch (vip->totalcount) {
698 * If we're switching screens, we have to wait for messages,
699 * regardless. If we don't wait, skip updating the modeline.
702 vs_scroll(sp, NULL, SCROLL_W);
704 F_SET(vip, VIP_S_MODELINE);
710 * If >1 message line in use, prompt the user to continue and
711 * repaint overwritten lines.
713 vs_scroll(sp, NULL, SCROLL_W);
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;
724 /* Reset the count of overwriting lines. */
725 vip->linecount = vip->lcontinue = vip->totalcount = 0;
729 (void)vs_repaint(sp, &ev);
731 /* Restore the cursor position. */
732 (void)gp->scr_move(csp, oldy, oldx);
739 * Scroll the screen for output.
742 vs_scroll(SCR *sp, int *continuep, sw_t wtype)
749 if (!IS_ONELINE(sp)) {
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.
755 (void)gp->scr_move(sp, vip->totalcount <
756 sp->rows ? LASTLINE(sp) - vip->totalcount : 0, 0);
757 (void)gp->scr_deleteln(sp);
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);
765 if (wtype == SCROLL_W_QUIT && vip->linecount < sp->t_maxrows)
767 vs_wait(sp, continuep, wtype);
772 * Prompt the user to continue.
775 vs_wait(SCR *sp, int *continuep, sw_t wtype)
786 (void)gp->scr_move(sp, LASTLINE(sp), 0);
788 p = msg_cmsg(sp, CMSG_CONT_S, &len);
792 p = msg_cmsg(sp, CMSG_CONT_Q, &len);
795 p = msg_cmsg(sp, CMSG_CONT_EX, &len);
798 p = msg_cmsg(sp, CMSG_CONT, &len);
804 (void)gp->scr_addstr(sp, p, len);
809 (void)gp->scr_clrtoeol(sp);
810 (void)gp->scr_refresh(sp, 0);
812 /* Get a single character from the terminal. */
813 if (continuep != NULL)
816 if (v_event_get(sp, &ev, 0, 0))
818 if (ev.e_event == E_CHARACTER)
820 if (ev.e_event == E_INTERRUPT) {
822 F_SET(gp, G_INTERRUPTED);
825 (void)gp->scr_bell(sp);
829 if (ev.e_c == CH_QUIT)
830 F_SET(gp, G_INTERRUPTED);
833 if (ev.e_c == ':' && continuep != NULL)
843 * Draw a dividing line between the screen and the output.
851 #define DIVIDESTR "+=+=+=+=+=+=+=+"
853 sizeof(DIVIDESTR) - 1 > sp->cols ? sp->cols : sizeof(DIVIDESTR) - 1;
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);
862 * Save a message for later display.
865 vs_msgsave(SCR *sp, mtype_t mt, char *p, size_t len)
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.
877 CALLOC_GOTO(sp, mp_n, 1, sizeof(MSGS));
878 MALLOC_GOTO(sp, mp_n->buf, len);
880 memmove(mp_n->buf, p, len);
885 if (SLIST_EMPTY(gp->msgq)) {
886 SLIST_INSERT_HEAD(gp->msgq, mp_n, q);
888 SLIST_FOREACH(mp_c, gp->msgq, q)
889 if (SLIST_NEXT(mp_c, q) == NULL)
891 SLIST_INSERT_AFTER(mp_c, mp_n, q);
897 (void)fprintf(stderr, "%.*s\n", (int)len, p);