Mercurial > vim
view src/ui.c @ 33096:828bcb1a37e7 v9.0.1833
patch 9.0.1833: [security] runtime file fixes
Commit: https://github.com/vim/vim/commit/816fbcc262687b81fc46f82f7bbeb1453addfe0c
Author: Christian Brabandt <cb@256bit.org>
Date: Thu Aug 31 23:52:30 2023 +0200
patch 9.0.1833: [security] runtime file fixes
Problem: runtime files may execute code in current dir
Solution: only execute, if not run from current directory
The perl, zig and ruby filetype plugins and the zip and gzip autoload
plugins may try to load malicious executable files from the current
working directory. This is especially a problem on windows, where the
current directory is implicitly in your $PATH and windows may even run a
file with the extension `.bat` because of $PATHEXT.
So make sure that we are not trying to execute a file from the current
directory. If this would be the case, error out (for the zip and gzip)
plugins or silently do not run those commands (for the ftplugins).
This assumes, that only the current working directory is bad. For all
other directories, it is assumed that those directories were
intentionally set to the $PATH by the user.
Signed-off-by: Christian Brabandt <cb@256bit.org>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Fri, 01 Sep 2023 00:00:02 +0200 |
parents | 1009c33499e7 |
children | 664ee4c0daca |
line wrap: on
line source
/* vi:set ts=8 sts=4 sw=4 noet: * * 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. * See README.txt for an overview of the Vim source code. */ /* * ui.c: functions that handle the user interface. * 1. Keyboard input stuff, and a bit of windowing stuff. These are called * before the machine specific stuff (mch_*) so that we can call the GUI * stuff instead if the GUI is running. * 2. Input buffer stuff. */ #include "vim.h" void ui_write(char_u *s, int len, int console UNUSED) { #ifdef FEAT_GUI if (gui.in_use && !gui.dying && !gui.starting # ifndef NO_CONSOLE && !console # endif ) { gui_write(s, len); if (p_wd) gui_wait_for_chars(p_wd, typebuf.tb_change_cnt); return; } #endif #ifndef NO_CONSOLE // Don't output anything in silent mode ("ex -s") unless 'verbose' set if (!(silent_mode && p_verbose == 0)) { # if !defined(MSWIN) char_u *tofree = NULL; if (output_conv.vc_type != CONV_NONE) { // Convert characters from 'encoding' to 'termencoding'. tofree = string_convert(&output_conv, s, &len); if (tofree != NULL) s = tofree; } # endif mch_write(s, len); # if defined(HAVE_FSYNC) if (console && s[len - 1] == '\n') vim_fsync(1); # endif # if !defined(MSWIN) if (output_conv.vc_type != CONV_NONE) vim_free(tofree); # endif } #endif } #if defined(UNIX) || defined(VMS) || defined(PROTO) || defined(MSWIN) /* * When executing an external program, there may be some typed characters that * are not consumed by it. Give them back to ui_inchar() and they are stored * here for the next call. */ static char_u *ta_str = NULL; static int ta_off; // offset for next char to use when ta_str != NULL static int ta_len; // length of ta_str when it's not NULL void ui_inchar_undo(char_u *s, int len) { char_u *new; int newlen; newlen = len; if (ta_str != NULL) newlen += ta_len - ta_off; new = alloc(newlen); if (new == NULL) return; if (ta_str != NULL) { mch_memmove(new, ta_str + ta_off, (size_t)(ta_len - ta_off)); mch_memmove(new + ta_len - ta_off, s, (size_t)len); vim_free(ta_str); } else mch_memmove(new, s, (size_t)len); ta_str = new; ta_len = newlen; ta_off = 0; } #endif /* * ui_inchar(): low level input function. * Get characters from the keyboard. * Return the number of characters that are available. * If "wtime" == 0 do not wait for characters. * If "wtime" == -1 wait forever for characters. * If "wtime" > 0 wait "wtime" milliseconds for a character. * * "tb_change_cnt" is the value of typebuf.tb_change_cnt if "buf" points into * it. When typebuf.tb_change_cnt changes (e.g., when a message is received * from a remote client) "buf" can no longer be used. "tb_change_cnt" is NULL * otherwise. */ int ui_inchar( char_u *buf, int maxlen, long wtime, // don't use "time", MIPS cannot handle it int tb_change_cnt) { int retval = 0; #if defined(FEAT_GUI) && (defined(UNIX) || defined(VMS)) /* * Use the typeahead if there is any. */ if (ta_str != NULL) { if (maxlen >= ta_len - ta_off) { mch_memmove(buf, ta_str + ta_off, (size_t)ta_len); VIM_CLEAR(ta_str); return ta_len; } mch_memmove(buf, ta_str + ta_off, (size_t)maxlen); ta_off += maxlen; return maxlen; } #endif #ifdef FEAT_PROFILE if (do_profiling == PROF_YES && wtime != 0) prof_inchar_enter(); #endif #ifdef NO_CONSOLE_INPUT // Don't wait for character input when the window hasn't been opened yet. // Do try reading, this works when redirecting stdin from a file. // Must return something, otherwise we'll loop forever. If we run into // this very often we probably got stuck, exit Vim. if (no_console_input()) { static int count = 0; # ifndef NO_CONSOLE retval = mch_inchar(buf, maxlen, wtime, tb_change_cnt); if (retval > 0 || typebuf_changed(tb_change_cnt) || wtime >= 0) goto theend; # endif if (wtime == -1 && ++count == 1000) read_error_exit(); buf[0] = CAR; retval = 1; goto theend; } #endif // If we are going to wait for some time or block... if (wtime == -1 || wtime > 100L) { // ... allow signals to kill us. (void)vim_handle_signal(SIGNAL_UNBLOCK); // ... there is no need for CTRL-C to interrupt something, don't let // it set got_int when it was mapped. if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) ctrl_c_interrupts = FALSE; } /* * Here we call gui_inchar() or mch_inchar(), the GUI or machine-dependent * input function. The functionality they implement is like this: * * while (not timed out) * { * handle-resize; * parse-queued-messages; * if (waited for 'updatetime') * trigger-cursorhold; * ui_wait_for_chars_or_timer() * if (character available) * break; * } * * ui_wait_for_chars_or_timer() does: * * while (not timed out) * { * if (any-timer-triggered) * invoke-timer-callback; * wait-for-character(); * if (character available) * break; * } * * wait-for-character() does: * while (not timed out) * { * Wait for event; * if (something on channel) * read/write channel; * else if (resized) * handle_resize(); * else if (system event) * deal-with-system-event; * else if (character available) * break; * } * */ #ifdef FEAT_GUI if (gui.in_use) retval = gui_inchar(buf, maxlen, wtime, tb_change_cnt); #endif #ifndef NO_CONSOLE # ifdef FEAT_GUI else # endif retval = mch_inchar(buf, maxlen, wtime, tb_change_cnt); #endif if (wtime == -1 || wtime > 100L) // block SIGHUP et al. (void)vim_handle_signal(SIGNAL_BLOCK); ctrl_c_interrupts = TRUE; #ifdef NO_CONSOLE_INPUT theend: #endif #ifdef FEAT_PROFILE if (do_profiling == PROF_YES && wtime != 0) prof_inchar_exit(); #endif return retval; } #if defined(UNIX) || defined(VMS) || defined(FEAT_GUI) || defined(PROTO) /* * Common code for mch_inchar() and gui_inchar(): Wait for a while or * indefinitely until characters are available, dealing with timers and * messages on channels. * * "buf" may be NULL if the available characters are not to be returned, only * check if they are available. * * Return the number of characters that are available. * If "wtime" == 0 do not wait for characters. * If "wtime" == n wait a short time for characters. * If "wtime" == -1 wait forever for characters. */ int inchar_loop( char_u *buf, int maxlen, long wtime, // don't use "time", MIPS cannot handle it int tb_change_cnt, int (*wait_func)(long wtime, int *interrupted, int ignore_input), int (*resize_func)(int check_only)) { int len; int interrupted = FALSE; int did_call_wait_func = FALSE; int did_start_blocking = FALSE; long wait_time; long elapsed_time = 0; #ifdef ELAPSED_FUNC elapsed_T start_tv; ELAPSED_INIT(start_tv); #endif // repeat until we got a character or waited long enough for (;;) { // Check if window changed size while we were busy, perhaps the ":set // columns=99" command was used. if (resize_func != NULL) resize_func(FALSE); #ifdef MESSAGE_QUEUE // Only process messages when waiting. if (wtime != 0) { parse_queued_messages(); // If input was put directly in typeahead buffer bail out here. if (typebuf_changed(tb_change_cnt)) return 0; } #endif if (wtime < 0 && did_start_blocking) // blocking and already waited for p_ut wait_time = -1; else { if (wtime >= 0) wait_time = wtime; else // going to block after p_ut wait_time = p_ut; #ifdef ELAPSED_FUNC elapsed_time = ELAPSED_FUNC(start_tv); #endif wait_time -= elapsed_time; // If the waiting time is now zero or less, we timed out. However, // loop at least once to check for characters and events. Matters // when "wtime" is zero. if (wait_time <= 0 && did_call_wait_func) { if (wtime >= 0) // no character available within "wtime" return 0; // No character available within 'updatetime'. did_start_blocking = TRUE; if (trigger_cursorhold() && maxlen >= 3 && !typebuf_changed(tb_change_cnt)) { // Put K_CURSORHOLD in the input buffer or return it. if (buf == NULL) { char_u ibuf[3]; ibuf[0] = CSI; ibuf[1] = KS_EXTRA; ibuf[2] = (int)KE_CURSORHOLD; add_to_input_buf(ibuf, 3); } else { buf[0] = K_SPECIAL; buf[1] = KS_EXTRA; buf[2] = (int)KE_CURSORHOLD; } return 3; } // There is no character available within 'updatetime' seconds: // flush all the swap files to disk. Also done when // interrupted by SIGWINCH. before_blocking(); continue; } } #ifdef FEAT_JOB_CHANNEL if (wait_time < 0 || wait_time > 100L) { // Checking if a job ended requires polling. Do this at least // every 100 msec. if (has_pending_job()) wait_time = 100L; // If there is readahead then parse_queued_messages() timed out and // we should call it again soon. if (channel_any_readahead()) wait_time = 10L; } #endif #ifdef FEAT_BEVAL_GUI if (p_beval && wait_time > 100L) // The 'balloonexpr' may indirectly invoke a callback while waiting // for a character, need to check often. wait_time = 100L; #endif // Wait for a character to be typed or another event, such as the winch // signal or an event on the monitored file descriptors. did_call_wait_func = TRUE; if (wait_func(wait_time, &interrupted, FALSE)) { // If input was put directly in typeahead buffer bail out here. if (typebuf_changed(tb_change_cnt)) return 0; // We might have something to return now. if (buf == NULL) // "buf" is NULL, we were just waiting, not actually getting // input. return input_available(); len = read_from_input_buf(buf, (long)maxlen); if (len > 0) return len; continue; } // Timed out or interrupted with no character available. #ifndef ELAPSED_FUNC // estimate the elapsed time elapsed_time += wait_time; #endif if ((resize_func != NULL && resize_func(TRUE)) #if defined(FEAT_CLIENTSERVER) && defined(UNIX) || server_waiting() #endif #ifdef MESSAGE_QUEUE || interrupted #endif || wait_time > 0 || (wtime < 0 && !did_start_blocking)) // no character available, but something to be done, keep going continue; // no character available or interrupted, return zero break; } return 0; } #endif #if defined(FEAT_TIMERS) || defined(PROTO) /* * Wait for a timer to fire or "wait_func" to return non-zero. * Returns OK when something was read. * Returns FAIL when it timed out or was interrupted. */ int ui_wait_for_chars_or_timer( long wtime, int (*wait_func)(long wtime, int *interrupted, int ignore_input), int *interrupted, int ignore_input) { int due_time; long remaining = wtime; int tb_change_cnt = typebuf.tb_change_cnt; # ifdef FEAT_JOB_CHANNEL int brief_wait = FALSE; # endif // When waiting very briefly don't trigger timers. if (wtime >= 0 && wtime < 10L) return wait_func(wtime, NULL, ignore_input); while (wtime < 0 || remaining > 0) { // Trigger timers and then get the time in wtime until the next one is // due. Wait up to that time. due_time = check_due_timer(); if (typebuf.tb_change_cnt != tb_change_cnt) { // timer may have used feedkeys() return FAIL; } if (due_time <= 0 || (wtime > 0 && due_time > remaining)) due_time = remaining; # if defined(FEAT_JOB_CHANNEL) || defined(FEAT_SOUND_CANBERRA) || defined(FEAT_SOUND_MACOSX) if ((due_time < 0 || due_time > 10L) && ( # if defined(FEAT_JOB_CHANNEL) ( # if defined(FEAT_GUI) !gui.in_use && # endif (has_pending_job() || channel_any_readahead())) # if defined(FEAT_SOUND_CANBERRA) || defined(FEAT_SOUND_MACOSX) || # endif # endif # if defined(FEAT_SOUND_CANBERRA) || defined(FEAT_SOUND_MACOSX) has_any_sound_callback() # endif )) { // There is a pending job or channel, should return soon in order // to handle them ASAP. Do check for input briefly. due_time = 10L; # ifdef FEAT_JOB_CHANNEL brief_wait = TRUE; # endif } # endif if (wait_func(due_time, interrupted, ignore_input)) return OK; if ((interrupted != NULL && *interrupted) # ifdef FEAT_JOB_CHANNEL || brief_wait # endif ) // Nothing available, but need to return so that side effects get // handled, such as handling a message on a channel. return FAIL; if (wtime > 0) remaining -= due_time; } return FAIL; } #endif /* * Return non-zero if a character is available. */ int ui_char_avail(void) { #ifdef FEAT_GUI if (gui.in_use) { gui_mch_update(); return input_available(); } #endif #ifndef NO_CONSOLE # ifdef NO_CONSOLE_INPUT if (no_console_input()) return 0; # endif return mch_char_avail(); #else return 0; #endif } /* * Delay for the given number of milliseconds. If ignoreinput is FALSE then we * cancel the delay if a key is hit. */ void ui_delay(long msec_arg, int ignoreinput) { long msec = msec_arg; #ifdef FEAT_EVAL if (ui_delay_for_testing > 0) msec = ui_delay_for_testing; ch_log(NULL, "ui_delay(%ld)", msec); #endif #ifdef FEAT_GUI if (gui.in_use && !ignoreinput) gui_wait_for_chars(msec, typebuf.tb_change_cnt); else #endif mch_delay(msec, ignoreinput ? MCH_DELAY_IGNOREINPUT : 0); } /* * If the machine has job control, use it to suspend the program, * otherwise fake it by starting a new shell. * When running the GUI iconify the window. */ void ui_suspend(void) { #ifdef FEAT_GUI if (gui.in_use) { gui_mch_iconify(); return; } #endif mch_suspend(); } #if !defined(UNIX) || !defined(SIGTSTP) || defined(PROTO) /* * When the OS can't really suspend, call this function to start a shell. * This is never called in the GUI. */ void suspend_shell(void) { if (*p_sh == NUL) emsg(_(e_shell_option_is_empty)); else { msg_puts(_("new shell started\n")); do_shell(NULL, 0); } } #endif /* * Try to get the current Vim shell size. Put the result in Rows and Columns. * Use the new sizes as defaults for 'columns' and 'lines'. * Return OK when size could be determined, FAIL otherwise. */ int ui_get_shellsize(void) { int retval; #ifdef FEAT_GUI if (gui.in_use) retval = gui_get_shellsize(); else #endif retval = mch_get_shellsize(); check_shellsize(); // adjust the default for 'lines' and 'columns' if (retval == OK) { set_number_default("lines", Rows); set_number_default("columns", Columns); } return retval; } /* * Set the size of the Vim shell according to Rows and Columns, if possible. * The gui_set_shellsize() or mch_set_shellsize() function will try to set the * new size. If this is not possible, it will adjust Rows and Columns. */ void ui_set_shellsize( int mustset UNUSED) // set by the user { #ifdef FEAT_GUI if (gui.in_use) gui_set_shellsize(mustset, TRUE, RESIZE_BOTH); else #endif mch_set_shellsize(); } /* * Called when Rows and/or Columns changed. Adjust scroll region and mouse * region. */ void ui_new_shellsize(void) { if (full_screen && !exiting) { #ifdef FEAT_GUI if (gui.in_use) gui_new_shellsize(); else #endif mch_new_shellsize(); } } #if ((defined(FEAT_EVAL) || defined(FEAT_TERMINAL)) \ && (defined(FEAT_GUI) \ || defined(MSWIN) \ || (defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE)))) \ || defined(PROTO) /* * Get the window position in pixels, if possible. * Return FAIL when not possible. */ int ui_get_winpos(int *x, int *y, varnumber_T timeout UNUSED) { # ifdef FEAT_GUI if (gui.in_use) return gui_mch_get_winpos(x, y); # endif # if defined(MSWIN) && (!defined(FEAT_GUI) || defined(VIMDLL)) return mch_get_winpos(x, y); # else # if defined(HAVE_TGETENT) && defined(FEAT_TERMRESPONSE) return term_get_winpos(x, y, timeout); # else return FAIL; # endif # endif } #endif void ui_breakcheck(void) { ui_breakcheck_force(FALSE); } /* * When "force" is true also check when the terminal is not in raw mode. * This is useful to read input on channels. */ void ui_breakcheck_force(int force) { static int recursive = FALSE; int save_updating_screen = updating_screen; // We could be called recursively if stderr is redirected, calling // fill_input_buf() calls settmode() when stdin isn't a tty. settmode() // calls vgetorpeek() which calls ui_breakcheck() again. if (recursive) return; recursive = TRUE; // We do not want gui_resize_shell() to redraw the screen here. ++updating_screen; #ifdef FEAT_GUI if (gui.in_use) gui_mch_update(); else #endif mch_breakcheck(force); if (save_updating_screen) updating_screen = TRUE; else after_updating_screen(FALSE); recursive = FALSE; } ////////////////////////////////////////////////////////////////////////////// // Functions that handle the input buffer. // This is used for any GUI version, and the unix terminal version. // // For Unix, the input characters are buffered to be able to check for a // CTRL-C. This should be done with signals, but I don't know how to do that // in a portable way for a tty in RAW mode. // // For the client-server code in the console the received keys are put in the // input buffer. #if defined(USE_INPUT_BUF) || defined(PROTO) /* * Internal typeahead buffer. Includes extra space for long key code * descriptions which would otherwise overflow. The buffer is considered full * when only this extra space (or part of it) remains. */ #if defined(FEAT_JOB_CHANNEL) || defined(FEAT_CLIENTSERVER) /* * NetBeans stuffs debugger commands into the input buffer. * This requires a larger buffer... * (Madsen) Go with this for remote input as well ... */ # define INBUFLEN 4096 #else # define INBUFLEN 250 #endif static char_u inbuf[INBUFLEN + MAX_KEY_CODE_LEN]; static int inbufcount = 0; // number of chars in inbuf[] /* * vim_is_input_buf_full(), vim_is_input_buf_empty(), add_to_input_buf(), and * trash_input_buf() are functions for manipulating the input buffer. These * are used by the gui_* calls when a GUI is used to handle keyboard input. */ int vim_is_input_buf_full(void) { return (inbufcount >= INBUFLEN); } int vim_is_input_buf_empty(void) { return (inbufcount == 0); } #if defined(FEAT_OLE) || defined(PROTO) int vim_free_in_input_buf(void) { return (INBUFLEN - inbufcount); } #endif #if defined(FEAT_GUI_GTK) || defined(PROTO) int vim_used_in_input_buf(void) { return inbufcount; } #endif /* * Return the current contents of the input buffer and make it empty. * The returned pointer must be passed to set_input_buf() later. */ char_u * get_input_buf(void) { garray_T *gap; // We use a growarray to store the data pointer and the length. gap = ALLOC_ONE(garray_T); if (gap != NULL) { // Add one to avoid a zero size. gap->ga_data = alloc(inbufcount + 1); if (gap->ga_data != NULL) mch_memmove(gap->ga_data, inbuf, (size_t)inbufcount); gap->ga_len = inbufcount; } trash_input_buf(); return (char_u *)gap; } /* * Restore the input buffer with a pointer returned from get_input_buf(). * The allocated memory is freed, this only works once! * When "overwrite" is FALSE input typed later is kept. */ void set_input_buf(char_u *p, int overwrite) { garray_T *gap = (garray_T *)p; if (gap == NULL) return; if (gap->ga_data != NULL) { if (overwrite || inbufcount + gap->ga_len >= INBUFLEN) { mch_memmove(inbuf, gap->ga_data, gap->ga_len); inbufcount = gap->ga_len; } else { mch_memmove(inbuf + gap->ga_len, inbuf, inbufcount); mch_memmove(inbuf, gap->ga_data, gap->ga_len); inbufcount += gap->ga_len; } vim_free(gap->ga_data); } vim_free(gap); } /* * Add the given bytes to the input buffer * Special keys start with CSI. A real CSI must have been translated to * CSI KS_EXTRA KE_CSI. K_SPECIAL doesn't require translation. */ void add_to_input_buf(char_u *s, int len) { if (inbufcount + len > INBUFLEN + MAX_KEY_CODE_LEN) return; // Shouldn't ever happen! while (len--) inbuf[inbufcount++] = *s++; } /* * Add "str[len]" to the input buffer while escaping CSI bytes. */ void add_to_input_buf_csi(char_u *str, int len) { int i; char_u buf[2]; for (i = 0; i < len; ++i) { add_to_input_buf(str + i, 1); if (str[i] == CSI) { // Turn CSI into K_CSI. buf[0] = KS_EXTRA; buf[1] = (int)KE_CSI; add_to_input_buf(buf, 2); } } } /* * Remove everything from the input buffer. Called when ^C is found. */ void trash_input_buf(void) { inbufcount = 0; } /* * Read as much data from the input buffer as possible up to maxlen, and store * it in buf. */ int read_from_input_buf(char_u *buf, long maxlen) { if (inbufcount == 0) // if the buffer is empty, fill it fill_input_buf(TRUE); if (maxlen > inbufcount) maxlen = inbufcount; mch_memmove(buf, inbuf, (size_t)maxlen); inbufcount -= maxlen; // check "maxlen" to avoid clang warning if (inbufcount > 0 && maxlen > 0) mch_memmove(inbuf, inbuf + maxlen, (size_t)inbufcount); return (int)maxlen; } void fill_input_buf(int exit_on_error UNUSED) { #if defined(UNIX) || defined(VMS) || defined(MACOS_X) int len; int try; static int did_read_something = FALSE; static char_u *rest = NULL; // unconverted rest of previous read static int restlen = 0; int unconverted; #endif #ifdef FEAT_GUI if (gui.in_use # ifdef NO_CONSOLE_INPUT // Don't use the GUI input when the window hasn't been opened yet. // We get here from ui_inchar() when we should try reading from stdin. && !no_console_input() # endif ) { gui_mch_update(); return; } #endif #if defined(UNIX) || defined(VMS) || defined(MACOS_X) if (vim_is_input_buf_full()) return; /* * Fill_input_buf() is only called when we really need a character. * If we can't get any, but there is some in the buffer, just return. * If we can't get any, and there isn't any in the buffer, we give up and * exit Vim. */ if (rest != NULL) { // Use remainder of previous call, starts with an invalid character // that may become valid when reading more. if (restlen > INBUFLEN - inbufcount) unconverted = INBUFLEN - inbufcount; else unconverted = restlen; mch_memmove(inbuf + inbufcount, rest, unconverted); if (unconverted == restlen) VIM_CLEAR(rest); else { restlen -= unconverted; mch_memmove(rest, rest + unconverted, restlen); } inbufcount += unconverted; } else unconverted = 0; len = 0; // to avoid gcc warning for (try = 0; try < 100; ++try) { size_t readlen = (size_t)((INBUFLEN - inbufcount) / input_conv.vc_factor); # ifdef VMS len = vms_read((char *)inbuf + inbufcount, readlen); # else len = read(read_cmd_fd, (char *)inbuf + inbufcount, readlen); # endif # ifdef FEAT_EVAL if (len > 0) { inbuf[inbufcount + len] = NUL; ch_log(NULL, "raw key input: \"%s\"", inbuf + inbufcount); } # endif if (len > 0 || got_int) break; /* * If reading stdin results in an error, continue reading stderr. * This helps when using "foo | xargs vim". */ if (!did_read_something && !isatty(read_cmd_fd) && read_cmd_fd == 0) { int m = cur_tmode; // We probably set the wrong file descriptor to raw mode. Switch // back to cooked mode, use another descriptor and set the mode to // what it was. settmode(TMODE_COOK); #ifdef HAVE_DUP // Use stderr for stdin, also works for shell commands. close(0); vim_ignored = dup(2); #else read_cmd_fd = 2; // read from stderr instead of stdin #endif settmode(m); } if (!exit_on_error) return; } if (len <= 0 && !got_int) read_error_exit(); if (len > 0) did_read_something = TRUE; if (got_int) { // Interrupted, pretend a CTRL-C was typed. inbuf[0] = 3; inbufcount = 1; } else { /* * May perform conversion on the input characters. * Include the unconverted rest of the previous call. * If there is an incomplete char at the end it is kept for the next * time, reading more bytes should make conversion possible. * Don't do this in the unlikely event that the input buffer is too * small ("rest" still contains more bytes). */ if (input_conv.vc_type != CONV_NONE) { inbufcount -= unconverted; len = convert_input_safe(inbuf + inbufcount, len + unconverted, INBUFLEN - inbufcount, rest == NULL ? &rest : NULL, &restlen); } while (len > 0) { // If a CTRL-C was typed, remove it from the buffer and set // got_int. Also recognize CTRL-C with modifyOtherKeys set, lower // and upper case, in two forms. if (ctrl_c_interrupts && (inbuf[inbufcount] == 3 || (len >= 10 && STRNCMP(inbuf + inbufcount, "\033[27;5;99~", 10) == 0) || (len >= 10 && STRNCMP(inbuf + inbufcount, "\033[27;5;67~", 10) == 0) || (len >= 7 && STRNCMP(inbuf + inbufcount, "\033[99;5u", 7) == 0) || (len >= 7 && STRNCMP(inbuf + inbufcount, "\033[67;5u", 7) == 0))) { // remove everything typed before the CTRL-C mch_memmove(inbuf, inbuf + inbufcount, (size_t)(len)); inbufcount = 0; got_int = TRUE; } --len; ++inbufcount; } } #endif // UNIX || VMS || MACOS_X } #endif // USE_INPUT_BUF /* * Exit because of an input read error. */ void read_error_exit(void) { if (silent_mode) // Normal way to exit for "ex -s" getout(0); STRCPY(IObuff, _("Vim: Error reading input, exiting...\n")); preserve_exit(); } #if defined(CURSOR_SHAPE) || defined(PROTO) /* * May update the shape of the cursor. */ void ui_cursor_shape_forced(int forced) { # ifdef FEAT_GUI if (gui.in_use) gui_update_cursor_later(); else # endif term_cursor_mode(forced); # ifdef MCH_CURSOR_SHAPE mch_update_cursor(); # endif # ifdef FEAT_CONCEAL conceal_check_cursor_line(FALSE); # endif } void ui_cursor_shape(void) { ui_cursor_shape_forced(FALSE); } #endif /* * Check bounds for column number */ int check_col(int col) { if (col < 0) return 0; if (col >= screen_Columns) return screen_Columns - 1; return col; } /* * Check bounds for row number */ int check_row(int row) { if (row < 0) return 0; if (row >= screen_Rows) return screen_Rows - 1; return row; } /* * Return length of line "lnum" in screen cells for horizontal scrolling. */ long scroll_line_len(linenr_T lnum) { char_u *p = ml_get(lnum); colnr_T col = 0; if (*p != NUL) for (;;) { int w = chartabsize(p, col); MB_PTR_ADV(p); if (*p == NUL) // don't count the last character break; col += w; } return col; } /* * Find the longest visible line number. This is used for horizontal * scrolling. If this is not possible (or not desired, by setting 'h' in * "guioptions") then the current line number is returned. */ linenr_T ui_find_longest_lnum(void) { linenr_T ret = 0; // Calculate maximum for horizontal scrollbar. Check for reasonable // line numbers, topline and botline can be invalid when displaying is // postponed. if ( # ifdef FEAT_GUI (!gui.in_use || vim_strchr(p_go, GO_HORSCROLL) == NULL) && # endif curwin->w_topline <= curwin->w_cursor.lnum && curwin->w_botline > curwin->w_cursor.lnum && curwin->w_botline <= curbuf->b_ml.ml_line_count + 1) { linenr_T lnum; long n; long max = 0; // Use maximum of all visible lines. Remember the lnum of the // longest line, closest to the cursor line. Used when scrolling // below. for (lnum = curwin->w_topline; lnum < curwin->w_botline; ++lnum) { n = scroll_line_len(lnum); if (n > max) { max = n; ret = lnum; } else if (n == max && abs((int)(lnum - curwin->w_cursor.lnum)) < abs((int)(ret - curwin->w_cursor.lnum))) ret = lnum; } } else // Use cursor line only. ret = curwin->w_cursor.lnum; return ret; } /* * Called when focus changed. Used for the GUI or for systems where this can * be done in the console (Win32). */ void ui_focus_change( int in_focus) // TRUE if focus gained. { static time_t last_time = (time_t)0; int need_redraw = FALSE; // When activated: Check if any file was modified outside of Vim. // Only do this when not done within the last two seconds (could get // several events in a row). if (in_focus && last_time + 2 < time(NULL)) { need_redraw = check_timestamps( # ifdef FEAT_GUI gui.in_use # else FALSE # endif ); last_time = time(NULL); } #ifdef FEAT_TERMINAL term_focus_change(in_focus); #endif /* * Fire the focus gained/lost autocommand. */ need_redraw |= apply_autocmds(in_focus ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST, NULL, NULL, FALSE, curbuf); if (need_redraw) redraw_after_callback(TRUE, TRUE); // File may have been changed from 'readonly' to 'noreadonly' if (need_maketitle) maketitle(); } #if defined(HAVE_INPUT_METHOD) || defined(PROTO) /* * Save current Input Method status to specified place. */ void im_save_status(long *psave) { // Don't save when 'imdisable' is set or "xic" is NULL, IM is always // disabled then (but might start later). // Also don't save when inside a mapping, vgetc_im_active has not been set // then. // And don't save when the keys were stuffed (e.g., for a "." command). // And don't save when the GUI is running but our window doesn't have // input focus (e.g., when a find dialog is open). if (!p_imdisable && KeyTyped && !KeyStuffed # ifdef FEAT_XIM && xic != NULL # endif # ifdef FEAT_GUI && (!gui.in_use || gui.in_focus) # endif ) { // Do save when IM is on, or IM is off and saved status is on. if (vgetc_im_active) *psave = B_IMODE_IM; else if (*psave == B_IMODE_IM) *psave = B_IMODE_NONE; } } #endif