/* $OpenBSD: message.c,v 1.1.1.1 1996/09/07 21:40:25 downsj Exp $ */ /* vi:set ts=4 sw=4: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. */ /* * message.c: functions for displaying messages on the command line */ #include "vim.h" #include "globals.h" #define MESSAGE /* don't include prototype for smsg() */ #include "proto.h" #include "option.h" static void msg_screen_outchar __ARGS((int c)); static int msg_check_screen __ARGS((void)); static int lines_left = -1; /* lines left for listing */ /* * msg(s) - displays the string 's' on the status line * When terminal not initialized (yet) fprintf(stderr,..) is used. * return TRUE if wait_return not called */ int msg(s) char_u *s; { msg_start(); if (msg_highlight) start_highlight(); msg_outtrans(s); if (msg_highlight) { stop_highlight(); msg_highlight = FALSE; /* clear for next call */ } msg_clr_eos(); return msg_end(); } /* * automatic prototype generation does not understand this function */ #ifndef PROTO int smsg __ARGS((char_u *, long, long, long, long, long, long, long, long, long, long)); /* VARARGS */ int smsg(s, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) char_u *s; long a1, a2, a3, a4, a5, a6, a7, a8, a9, a10; { sprintf((char *)IObuff, (char *)s, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); return msg(IObuff); } #endif /* * emsg() - display an error message * * Rings the bell, if appropriate, and calls message() to do the real work * When terminal not initialized (yet) fprintf(stderr,..) is used. * * return TRUE if wait_return not called */ int emsg(s) char_u *s; { char_u *Buf; #ifdef SLEEP_IN_EMSG int retval; #endif static int last_lnum = 0; static char_u *last_sourcing_name = NULL; if (emsg_off) /* no error messages at the moment */ return TRUE; if (global_busy) /* break :global command */ ++global_busy; if (p_eb) beep_flush(); /* also includes flush_buffers() */ else flush_buffers(FALSE); /* flush internal buffers */ did_emsg = TRUE; /* flag for DoOneCmd() */ ++msg_scroll; /* don't overwrite a previous message */ (void)set_highlight('e'); /* set highlight mode for error messages */ msg_highlight = TRUE; if (msg_scrolled) need_wait_return = TRUE; /* needed in case emsg() is called after * wait_return has reset need_wait_return * and a redraw is expected because * msg_scrolled is non-zero */ /* * First output name and line number of source of error message */ if (sourcing_name != NULL && (sourcing_name != last_sourcing_name || sourcing_lnum != last_lnum) && (Buf = alloc(MAXPATHL + 30)) != NULL) { ++no_wait_return; if (sourcing_name != last_sourcing_name) { sprintf((char *)Buf, "Error detected while processing %s:", sourcing_name); msg(Buf); msg_highlight = TRUE; } /* lnum is 0 when executing a command from the command line * argument, we don't want a line number then */ if (sourcing_lnum != 0) { (void)set_highlight('n'); /* highlight mode for line numbers */ sprintf((char *)Buf, "line %4ld:", sourcing_lnum); msg(Buf); (void)set_highlight('e'); /* highlight mode for error messages */ msg_highlight = TRUE; } --no_wait_return; last_lnum = sourcing_lnum; /* only once for each line */ vim_free(Buf); } last_sourcing_name = sourcing_name; /* do this also when it is NULL */ #ifdef SLEEP_IN_EMSG /* * Msg returns TRUE if wait_return() was not called. * In that case may call sleep() to give the user a chance to read the message. * Don't call sleep() if dont_sleep is set. */ retval = msg(s); if (retval) { if (dont_sleep || need_wait_return) need_sleep = TRUE; /* sleep before removing the message */ else mch_delay(1000L, TRUE); /* give user chance to read message */ } /* --msg_scroll; don't overwrite this message */ return retval; #else emsg_on_display = TRUE; /* remember there is an error message */ return msg(s); #endif } int emsg2(s, a1) char_u *s, *a1; { /* Check for NULL strings (just in case) */ if (a1 == NULL) a1 = (char_u *)"[NULL]"; /* Check for very long strings (can happen with ":help ^A") */ if (STRLEN(s) + STRLEN(a1) >= IOSIZE) a1 = (char_u *)"[string too long]"; sprintf((char *)IObuff, (char *)s, (char *)a1); return emsg(IObuff); } int emsgn(s, n) char_u *s; long n; { sprintf((char *)IObuff, (char *)s, n); return emsg(IObuff); } /* * Like msg(), but truncate to a single line if p_shm contains 't'. * Careful: The string may be changed! */ int msg_trunc(s) char_u *s; { int n; if (shortmess(SHM_TRUNC) && (n = (int)STRLEN(s) - (int)(Rows - cmdline_row - 1) * Columns - sc_col + 1) > 0) { s[n] = '<'; return msg(s + n); } else return msg(s); } /* * wait for the user to hit a key (normally a return) * if 'redraw' is TRUE, clear and redraw the screen * if 'redraw' is FALSE, just redraw the screen * if 'redraw' is -1, don't redraw at all */ void wait_return(redraw) int redraw; { int c; int oldState; int tmpState; if (redraw == TRUE) must_redraw = CLEAR; /* * With the global command (and some others) we only need one return at the * end. Adjust cmdline_row to avoid the next message overwriting the last one. */ if (no_wait_return) { need_wait_return = TRUE; cmdline_row = msg_row; return; } oldState = State; if (quit_more) { c = CR; /* just pretend CR was hit */ quit_more = FALSE; got_int = FALSE; } else { State = HITRETURN; #ifdef USE_MOUSE setmouse(); #endif if (msg_didout) /* start on a new line */ msg_outchar('\n'); if (got_int) MSG_OUTSTR("Interrupt: "); (void)set_highlight('r'); start_highlight(); #ifdef ORG_HITRETURN MSG_OUTSTR("Press RETURN to continue"); stop_highlight(); do { c = vgetc(); } while (vim_strchr((char_u *)"\r\n: ", c) == NULL); if (c == ':') /* this can vi too (but not always!) */ stuffcharReadbuff(c); #else MSG_OUTSTR("Press RETURN or enter command to continue"); stop_highlight(); do { c = vgetc(); got_int = FALSE; } while (c == Ctrl('C') #ifdef USE_GUI || c == K_SCROLLBAR || c == K_HORIZ_SCROLLBAR #endif #ifdef USE_MOUSE || c == K_LEFTDRAG || c == K_LEFTRELEASE || c == K_MIDDLEDRAG || c == K_MIDDLERELEASE || c == K_RIGHTDRAG || c == K_RIGHTRELEASE || c == K_IGNORE || (!mouse_has(MOUSE_RETURN) && (c == K_LEFTMOUSE || c == K_MIDDLEMOUSE || c == K_RIGHTMOUSE)) #endif ); mch_breakcheck(); #ifdef USE_MOUSE /* * Avoid that the mouse-up event causes visual mode to start. */ if (c == K_LEFTMOUSE || c == K_MIDDLEMOUSE || c == K_RIGHTMOUSE) jump_to_mouse(MOUSE_SETPOS); else #endif if (vim_strchr((char_u *)"\r\n ", c) == NULL) { stuffcharReadbuff(c); do_redraw = TRUE; /* need a redraw even though there is something in the stuff buffer */ } #endif } /* * If the user hits ':', '?' or '/' we get a command line from the next * line. */ if (c == ':' || c == '?' || c == '/') { cmdline_row = msg_row; skip_redraw = TRUE; /* skip redraw once */ do_redraw = FALSE; } /* * If the window size changed set_winsize() will redraw the screen. * Otherwise the screen is only redrawn if 'redraw' is set and no ':' typed. */ tmpState = State; State = oldState; /* restore State before set_winsize */ #ifdef USE_MOUSE setmouse(); #endif msg_check(); need_wait_return = FALSE; emsg_on_display = FALSE; /* can delete error message now */ #ifdef SLEEP_IN_EMSG need_sleep = FALSE; /* no need to call sleep() anymore */ #endif msg_didany = FALSE; /* reset lines_left at next msg_start() */ lines_left = -1; if (keep_msg != NULL && linetabsize(keep_msg) >= (Rows - cmdline_row - 1) * Columns + sc_col) keep_msg = NULL; /* don't redisplay message, it's too long */ if (tmpState == SETWSIZE) /* got resize event while in vgetc() */ { starttermcap(); /* start termcap before redrawing */ set_winsize(0, 0, FALSE); } else if (!skip_redraw && (redraw == TRUE || (msg_scrolled && redraw != -1))) { starttermcap(); /* start termcap before redrawing */ updateScreen(VALID); } dont_wait_return = TRUE; /* don't wait again in main() */ } /* * Prepare for outputting characters in the command line. */ void msg_start() { keep_msg = NULL; /* don't display old message now */ keep_msg_highlight = 0; if (!msg_scroll && full_screen) /* overwrite last message */ msg_pos(cmdline_row, 0); else if (msg_didout) /* start message on next line */ { msg_outchar('\n'); cmdline_row = msg_row; } if (!msg_didany) lines_left = cmdline_row; msg_didout = FALSE; /* no output on current line yet */ cursor_off(); } /* * Move message position. This should always be used after moving the cursor. * Use negative value if row or col does not have to be changed. */ void msg_pos(row, col) int row, col; { if (row >= 0) msg_row = row; if (col >= 0) msg_col = col; } void msg_outchar(c) int c; { char_u buf[4]; if (IS_SPECIAL(c)) { buf[0] = K_SPECIAL; buf[1] = K_SECOND(c); buf[2] = K_THIRD(c); buf[3] = NUL; } else { buf[0] = c; buf[1] = NUL; } msg_outstr(buf); } void msg_outnum(n) long n; { char_u buf[20]; sprintf((char *)buf, "%ld", n); msg_outstr(buf); } void msg_home_replace(fname) char_u *fname; { char_u *name; name = home_replace_save(NULL, fname); if (name != NULL) msg_outtrans(name); vim_free(name); } /* * output 'len' characters in 'str' (including NULs) with translation * if 'len' is -1, output upto a NUL character * return the number of characters it takes on the screen */ int msg_outtrans(str) register char_u *str; { return msg_outtrans_len(str, (int)STRLEN(str)); } int msg_outtrans_len(str, len) register char_u *str; register int len; { int retval = 0; while (--len >= 0) { msg_outstr(transchar(*str)); retval += charsize(*str); ++str; } return retval; } /* * Output the string 'str' upto a NUL character. * Return the number of characters it takes on the screen. * * If K_SPECIAL is encountered, then it is taken in conjunction with the * following character and shown as , etc. In addition, if 'all' * is TRUE, then any other character which has its 8th bit set is shown as * , where x is the equivalent character without its 8th bit set. If a * character is displayed in one of these special ways, is also highlighted * (its highlight name is '8' in the p_hl variable). * This function is used to show mappings, where we want to see how to type * the character/string -- webb */ int msg_outtrans_special(str, all) register char_u *str; register int all; /* etc as well as etc */ { int retval = 0; char_u *string; int c; int modifiers; set_highlight('8'); for (; *str; ++str) { c = *str; if (c == K_SPECIAL && str[1] != NUL && str[2] != NUL) { modifiers = 0x0; if (str[1] == KS_MODIFIER) { modifiers = str[2]; str += 3; c = *str; } if (c == K_SPECIAL) { c = TO_SPECIAL(str[1], str[2]); str += 2; if (c == K_ZERO) /* display as ^@ */ c = NUL; } if (IS_SPECIAL(c) || modifiers) /* special key */ { string = get_special_key_name(c, modifiers); start_highlight(); msg_outstr(string); retval += STRLEN(string); stop_highlight(); flushbuf(); /* Otherwise gets overwritten by spaces */ continue; } } if ((c & 0x80) && all) { start_highlight(); MSG_OUTSTR(""); stop_highlight(); } else { msg_outstr(transchar(c)); retval += charsize(c); } } return retval; } /* * print line for :p command */ void msg_prt_line(s) char_u *s; { register int si = 0; register int c; register int col = 0; int n_extra = 0; int n_spaces = 0; char_u *p = NULL; /* init to make SASC shut up */ int n; for (;;) { if (n_extra) { --n_extra; c = *p++; } else if (n_spaces) { --n_spaces; c = ' '; } else { c = s[si++]; if (c == TAB && !curwin->w_p_list) { /* tab amount depends on current column */ n_spaces = curbuf->b_p_ts - col % curbuf->b_p_ts - 1; c = ' '; } else if (c == NUL && curwin->w_p_list) { p = (char_u *)""; n_extra = 1; c = '$'; } else if (c != NUL && (n = charsize(c)) > 1) { n_extra = n - 1; p = transchar(c); c = *p++; } } if (c == NUL) break; msg_outchar(c); col++; } } /* * output a string to the screen at position msg_row, msg_col * Update msg_row and msg_col for the next message. */ void msg_outstr(s) char_u *s; { int oldState; char_u buf[20]; /* * If there is no valid screen, use fprintf so we can see error messages. * If termcap is not active, we may be writing in an alternate console * window, cursor positioning may not work correctly (window size may be * different, e.g. for WIN32 console). */ if (!msg_check_screen() #ifdef WIN32 || !termcap_active #endif ) { #ifdef WIN32 mch_settmode(0); /* cook so that \r and \n are handled correctly */ #endif fprintf(stderr, (char *)s); msg_didout = TRUE; /* assume that line is not empty */ #ifdef WIN32 mch_settmode(1); #endif return; } msg_didany = TRUE; /* remember that something was outputted */ while (*s) { /* * The screen is scrolled up when: * - When outputting a newline in the last row * - when outputting a character in the last column of the last row * (some terminals scroll automatically, some don't. To avoid * problems we scroll ourselves) */ if (msg_row >= Rows - 1 && (*s == '\n' || msg_col >= Columns - 1 || (*s == TAB && msg_col >= ((Columns - 1) & ~7)))) { screen_del_lines(0, 0, 1, (int)Rows, TRUE); /* always works */ msg_row = Rows - 2; if (msg_col >= Columns) /* can happen after screen resize */ msg_col = Columns - 1; ++msg_scrolled; need_wait_return = TRUE; /* may need wait_return in main() */ if (cmdline_row > 0) --cmdline_row; /* * if screen is completely filled wait for a character */ if (p_more && --lines_left == 0 && State != HITRETURN) { oldState = State; State = ASKMORE; #ifdef USE_MOUSE setmouse(); #endif msg_moremsg(FALSE); for (;;) { /* * Get a typed character directly from the user. * Don't use vgetc(), it syncs undo and eats mapped * characters. Disadvantage: Special keys and mouse * cannot be used here, typeahead is ignored. */ flushbuf(); (void)mch_inchar(buf, 20, -1L); switch (buf[0]) { case CR: /* one extra line */ case NL: lines_left = 1; break; case ':': /* start new command line */ stuffcharReadbuff(':'); cmdline_row = Rows - 1; /* put ':' on this line */ skip_redraw = TRUE; /* skip redraw once */ dont_wait_return = TRUE; /* don't wait in main() */ /*FALLTHROUGH*/ case 'q': /* quit */ case Ctrl('C'): got_int = TRUE; quit_more = TRUE; break; case 'd': /* Down half a page */ lines_left = Rows / 2; break; case ' ': /* one extra page */ lines_left = Rows - 1; break; default: /* no valid response */ msg_moremsg(TRUE); continue; } break; } /* clear the --more-- message */ screen_fill((int)Rows - 1, (int)Rows, 0, (int)Columns, ' ', ' '); State = oldState; #ifdef USE_MOUSE setmouse(); #endif if (quit_more) { msg_row = Rows - 1; msg_col = 0; return; /* the string is not displayed! */ } } } if (*s == '\n') /* go to next line */ { msg_didout = FALSE; /* remember that line is empty */ msg_col = 0; if (++msg_row >= Rows) /* safety check */ msg_row = Rows - 1; } else if (*s == '\r') /* go to column 0 */ { msg_col = 0; } else if (*s == '\b') /* go to previous char */ { if (msg_col) --msg_col; } else if (*s == TAB) /* translate into spaces */ { do msg_screen_outchar(' '); while (msg_col & 7); } else msg_screen_outchar(*s); ++s; } } static void msg_screen_outchar(c) int c; { msg_didout = TRUE; /* remember that line is not empty */ screen_outchar(c, msg_row, msg_col); if (++msg_col >= Columns) { msg_col = 0; ++msg_row; } } void msg_moremsg(full) int full; { /* * Need to restore old highlighting when we've finished with it * because the output that's paging may be relying on it not * changing -- webb */ remember_highlight(); set_highlight('m'); start_highlight(); screen_msg((char_u *)"-- More --", (int)Rows - 1, 0); if (full) screen_msg((char_u *)" (RET: line, SPACE: page, d: half page, q: quit)", (int)Rows - 1, 10); stop_highlight(); recover_old_highlight(); } /* * msg_check_screen - check if the screen is initialized. * Also check msg_row and msg_col, if they are too big it may cause a crash. */ static int msg_check_screen() { if (!full_screen || !screen_valid(FALSE)) return FALSE; if (msg_row >= Rows) msg_row = Rows - 1; if (msg_col >= Columns) msg_col = Columns - 1; return TRUE; } /* * clear from current message position to end of screen * Note: msg_col is not updated, so we remember the end of the message * for msg_check(). */ void msg_clr_eos() { if (!msg_check_screen() #ifdef WIN32 || !termcap_active #endif ) return; screen_fill(msg_row, msg_row + 1, msg_col, (int)Columns, ' ', ' '); screen_fill(msg_row + 1, (int)Rows, 0, (int)Columns, ' ', ' '); } /* * end putting a message on the screen * call wait_return if the message does not fit in the available space * return TRUE if wait_return not called. */ int msg_end() { /* * if the string is larger than the window, * or the ruler option is set and we run into it, * we have to redraw the window. * Do not do this if we are abandoning the file or editing the command line. */ if (!exiting && msg_check() && State != CMDLINE) { wait_return(FALSE); return FALSE; } flushbuf(); return TRUE; } /* * If the written message has caused the screen to scroll up, or if we * run into the shown command or ruler, we have to redraw the window later. */ int msg_check() { if (msg_scrolled || (msg_row == Rows - 1 && msg_col >= sc_col)) { redraw_later(NOT_VALID); redraw_cmdline = TRUE; return TRUE; } return FALSE; }