Files
neovim/src/nvim/misc1.c
Jan Edmund Lazo db8682b94e vim-patch:8.2.0420: Vim9: cannot interrupt a loop with CTRL-C
Problem:    Vim9: cannot interrupt a loop with CTRL-C.
Solution:   Check for CTRL-C once in a while.  Doesn't fully work yet.
f1ec378b01

Vim9 test, commented out in this patch, is restored in patch 8.2.0421.
Vim9 tests were not ported over so skip the temporary change.

Vim should stop pushing broken commits on master.
2020-07-19 11:38:35 -04:00

1197 lines
31 KiB
C

// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
/*
* misc1.c: functions that didn't seem to fit elsewhere
*/
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#include "nvim/vim.h"
#include "nvim/ascii.h"
#include "nvim/misc1.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/func_attr.h"
#include "nvim/fold.h"
#include "nvim/getchar.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/buffer_updates.h"
#include "nvim/main.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/garray.h"
#include "nvim/move.h"
#include "nvim/mouse.h"
#include "nvim/option.h"
#include "nvim/os_unix.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/window.h"
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/os/signal.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/event/stream.h"
#include "nvim/buffer.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "misc1.c.generated.h"
#endif
// All user names (for ~user completion as done by shell).
static garray_T ga_users = GA_EMPTY_INIT_VALUE;
/*
* get_leader_len() returns the length in bytes of the prefix of the given
* string which introduces a comment. If this string is not a comment then
* 0 is returned.
* When "flags" is not NULL, it is set to point to the flags of the recognized
* comment leader.
* "backward" must be true for the "O" command.
* If "include_space" is set, include trailing whitespace while calculating the
* length.
*/
int get_leader_len(char_u *line, char_u **flags,
bool backward, bool include_space)
{
int i, j;
int result;
int got_com = FALSE;
int found_one;
char_u part_buf[COM_MAX_LEN]; /* buffer for one option part */
char_u *string; /* pointer to comment string */
char_u *list;
int middle_match_len = 0;
char_u *prev_list;
char_u *saved_flags = NULL;
result = i = 0;
while (ascii_iswhite(line[i])) /* leading white space is ignored */
++i;
/*
* Repeat to match several nested comment strings.
*/
while (line[i] != NUL) {
/*
* scan through the 'comments' option for a match
*/
found_one = FALSE;
for (list = curbuf->b_p_com; *list; ) {
/* Get one option part into part_buf[]. Advance "list" to next
* one. Put "string" at start of string. */
if (!got_com && flags != NULL)
*flags = list; /* remember where flags started */
prev_list = list;
(void)copy_option_part(&list, part_buf, COM_MAX_LEN, ",");
string = vim_strchr(part_buf, ':');
if (string == NULL) /* missing ':', ignore this part */
continue;
*string++ = NUL; /* isolate flags from string */
/* If we found a middle match previously, use that match when this
* is not a middle or end. */
if (middle_match_len != 0
&& vim_strchr(part_buf, COM_MIDDLE) == NULL
&& vim_strchr(part_buf, COM_END) == NULL)
break;
/* When we already found a nested comment, only accept further
* nested comments. */
if (got_com && vim_strchr(part_buf, COM_NEST) == NULL)
continue;
/* When 'O' flag present and using "O" command skip this one. */
if (backward && vim_strchr(part_buf, COM_NOBACK) != NULL)
continue;
/* Line contents and string must match.
* When string starts with white space, must have some white space
* (but the amount does not need to match, there might be a mix of
* TABs and spaces). */
if (ascii_iswhite(string[0])) {
if (i == 0 || !ascii_iswhite(line[i - 1]))
continue; /* missing white space */
while (ascii_iswhite(string[0]))
++string;
}
for (j = 0; string[j] != NUL && string[j] == line[i + j]; ++j)
;
if (string[j] != NUL)
continue; /* string doesn't match */
/* When 'b' flag used, there must be white space or an
* end-of-line after the string in the line. */
if (vim_strchr(part_buf, COM_BLANK) != NULL
&& !ascii_iswhite(line[i + j]) && line[i + j] != NUL)
continue;
/* We have found a match, stop searching unless this is a middle
* comment. The middle comment can be a substring of the end
* comment in which case it's better to return the length of the
* end comment and its flags. Thus we keep searching with middle
* and end matches and use an end match if it matches better. */
if (vim_strchr(part_buf, COM_MIDDLE) != NULL) {
if (middle_match_len == 0) {
middle_match_len = j;
saved_flags = prev_list;
}
continue;
}
if (middle_match_len != 0 && j > middle_match_len)
/* Use this match instead of the middle match, since it's a
* longer thus better match. */
middle_match_len = 0;
if (middle_match_len == 0)
i += j;
found_one = TRUE;
break;
}
if (middle_match_len != 0) {
/* Use the previously found middle match after failing to find a
* match with an end. */
if (!got_com && flags != NULL)
*flags = saved_flags;
i += middle_match_len;
found_one = TRUE;
}
/* No match found, stop scanning. */
if (!found_one)
break;
result = i;
/* Include any trailing white space. */
while (ascii_iswhite(line[i]))
++i;
if (include_space)
result = i;
/* If this comment doesn't nest, stop here. */
got_com = TRUE;
if (vim_strchr(part_buf, COM_NEST) == NULL)
break;
}
return result;
}
/*
* Return the offset at which the last comment in line starts. If there is no
* comment in the whole line, -1 is returned.
*
* When "flags" is not null, it is set to point to the flags describing the
* recognized comment leader.
*/
int get_last_leader_offset(char_u *line, char_u **flags)
{
int result = -1;
int i, j;
int lower_check_bound = 0;
char_u *string;
char_u *com_leader;
char_u *com_flags;
char_u *list;
int found_one;
char_u part_buf[COM_MAX_LEN]; /* buffer for one option part */
/*
* Repeat to match several nested comment strings.
*/
i = (int)STRLEN(line);
while (--i >= lower_check_bound) {
/*
* scan through the 'comments' option for a match
*/
found_one = FALSE;
for (list = curbuf->b_p_com; *list; ) {
char_u *flags_save = list;
/*
* Get one option part into part_buf[]. Advance list to next one.
* put string at start of string.
*/
(void)copy_option_part(&list, part_buf, COM_MAX_LEN, ",");
string = vim_strchr(part_buf, ':');
if (string == NULL) { /* If everything is fine, this cannot actually
* happen. */
continue;
}
*string++ = NUL; /* Isolate flags from string. */
com_leader = string;
/*
* Line contents and string must match.
* When string starts with white space, must have some white space
* (but the amount does not need to match, there might be a mix of
* TABs and spaces).
*/
if (ascii_iswhite(string[0])) {
if (i == 0 || !ascii_iswhite(line[i - 1]))
continue;
while (ascii_iswhite(*string)) {
string++;
}
}
for (j = 0; string[j] != NUL && string[j] == line[i + j]; ++j)
/* do nothing */;
if (string[j] != NUL)
continue;
/*
* When 'b' flag used, there must be white space or an
* end-of-line after the string in the line.
*/
if (vim_strchr(part_buf, COM_BLANK) != NULL
&& !ascii_iswhite(line[i + j]) && line[i + j] != NUL) {
continue;
}
if (vim_strchr(part_buf, COM_MIDDLE) != NULL) {
// For a middlepart comment, only consider it to match if
// everything before the current position in the line is
// whitespace. Otherwise we would think we are inside a
// comment if the middle part appears somewhere in the middle
// of the line. E.g. for C the "*" appears often.
for (j = 0; j <= i && ascii_iswhite(line[j]); j++) {
}
if (j < i) {
continue;
}
}
/*
* We have found a match, stop searching.
*/
found_one = TRUE;
if (flags)
*flags = flags_save;
com_flags = flags_save;
break;
}
if (found_one) {
char_u part_buf2[COM_MAX_LEN]; /* buffer for one option part */
int len1, len2, off;
result = i;
/*
* If this comment nests, continue searching.
*/
if (vim_strchr(part_buf, COM_NEST) != NULL)
continue;
lower_check_bound = i;
/* Let's verify whether the comment leader found is a substring
* of other comment leaders. If it is, let's adjust the
* lower_check_bound so that we make sure that we have determined
* the comment leader correctly.
*/
while (ascii_iswhite(*com_leader))
++com_leader;
len1 = (int)STRLEN(com_leader);
for (list = curbuf->b_p_com; *list; ) {
char_u *flags_save = list;
(void)copy_option_part(&list, part_buf2, COM_MAX_LEN, ",");
if (flags_save == com_flags)
continue;
string = vim_strchr(part_buf2, ':');
++string;
while (ascii_iswhite(*string))
++string;
len2 = (int)STRLEN(string);
if (len2 == 0)
continue;
/* Now we have to verify whether string ends with a substring
* beginning the com_leader. */
for (off = (len2 > i ? i : len2); off > 0 && off + len1 > len2; ) {
--off;
if (!STRNCMP(string + off, com_leader, len2 - off)) {
if (i - off < lower_check_bound)
lower_check_bound = i - off;
}
}
}
}
}
return result;
}
/*
* Return the number of window lines occupied by buffer line "lnum".
*/
int plines(const linenr_T lnum)
{
return plines_win(curwin, lnum, true);
}
int plines_win(
win_T *const wp,
const linenr_T lnum,
const bool winheight // when true limit to window height
)
{
/* Check for filler lines above this buffer line. When folded the result
* is one line anyway. */
return plines_win_nofill(wp, lnum, winheight) + diff_check_fill(wp, lnum);
}
int plines_nofill(const linenr_T lnum)
{
return plines_win_nofill(curwin, lnum, true);
}
int plines_win_nofill(
win_T *const wp,
const linenr_T lnum,
const bool winheight // when true limit to window height
)
{
if (!wp->w_p_wrap) {
return 1;
}
if (wp->w_width_inner == 0) {
return 1;
}
// A folded lines is handled just like an empty line.
if (lineFolded(wp, lnum)) {
return 1;
}
const int lines = plines_win_nofold(wp, lnum);
if (winheight && lines > wp->w_height_inner) {
return wp->w_height_inner;
}
return lines;
}
/*
* Return number of window lines physical line "lnum" will occupy in window
* "wp". Does not care about folding, 'wrap' or 'diff'.
*/
int plines_win_nofold(win_T *wp, linenr_T lnum)
{
char_u *s;
unsigned int col;
int width;
s = ml_get_buf(wp->w_buffer, lnum, FALSE);
if (*s == NUL) /* empty line */
return 1;
col = win_linetabsize(wp, s, (colnr_T)MAXCOL);
// If list mode is on, then the '$' at the end of the line may take up one
// extra column.
if (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL) {
col += 1;
}
/*
* Add column offset for 'number', 'relativenumber' and 'foldcolumn'.
*/
width = wp->w_width_inner - win_col_off(wp);
if (width <= 0 || col > 32000) {
return 32000; // bigger than the number of screen columns
}
if (col <= (unsigned int)width) {
return 1;
}
col -= (unsigned int)width;
width += win_col_off2(wp);
assert(col <= INT_MAX && (int)col < INT_MAX - (width -1));
return ((int)col + (width - 1)) / width + 1;
}
/*
* Like plines_win(), but only reports the number of physical screen lines
* used from the start of the line to the given column number.
*/
int plines_win_col(win_T *wp, linenr_T lnum, long column)
{
// Check for filler lines above this buffer line. When folded the result
// is one line anyway.
int lines = diff_check_fill(wp, lnum);
if (!wp->w_p_wrap)
return lines + 1;
if (wp->w_width_inner == 0) {
return lines + 1;
}
char_u *line = ml_get_buf(wp->w_buffer, lnum, false);
char_u *s = line;
colnr_T col = 0;
while (*s != NUL && --column >= 0) {
col += win_lbr_chartabsize(wp, line, s, col, NULL);
MB_PTR_ADV(s);
}
// If *s is a TAB, and the TAB is not displayed as ^I, and we're not in
// INSERT mode, then col must be adjusted so that it represents the last
// screen position of the TAB. This only fixes an error when the TAB wraps
// from one screen line to the next (when 'columns' is not a multiple of
// 'ts') -- webb.
if (*s == TAB && (State & NORMAL)
&& (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
col += win_lbr_chartabsize(wp, line, s, col, NULL) - 1;
}
// Add column offset for 'number', 'relativenumber', 'foldcolumn', etc.
int width = wp->w_width_inner - win_col_off(wp);
if (width <= 0) {
return 9999;
}
lines += 1;
if (col > width)
lines += (col - width) / (width + win_col_off2(wp)) + 1;
return lines;
}
/// Get the number of screen lines lnum takes up. This takes care of
/// both folds and topfill, and limits to the current window height.
///
/// @param[in] wp window line is in
/// @param[in] lnum line number
/// @param[out] nextp if not NULL, the line after a fold
/// @param[out] foldedp if not NULL, whether lnum is on a fold
/// @param[in] cache whether to use the window's cache for folds
///
/// @return the total number of screen lines
int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp,
bool *const foldedp, const bool cache)
{
bool folded = hasFoldingWin(wp, lnum, NULL, nextp, cache, NULL);
if (foldedp) {
*foldedp = folded;
}
if (folded) {
return 1;
} else if (lnum == wp->w_topline) {
return plines_win_nofill(wp, lnum, true) + wp->w_topfill;
}
return plines_win(wp, lnum, true);
}
int plines_m_win(win_T *wp, linenr_T first, linenr_T last)
{
int count = 0;
while (first <= last) {
linenr_T next = first;
count += plines_win_full(wp, first, &next, NULL, false);
first = next + 1;
}
return count;
}
int gchar_pos(pos_T *pos)
FUNC_ATTR_NONNULL_ARG(1)
{
// When searching columns is sometimes put at the end of a line.
if (pos->col == MAXCOL) {
return NUL;
}
return utf_ptr2char(ml_get_pos(pos));
}
/*
* check_status: called when the status bars for the buffer 'buf'
* need to be updated
*/
void check_status(buf_T *buf)
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_buffer == buf && wp->w_status_height) {
wp->w_redr_status = TRUE;
if (must_redraw < VALID) {
must_redraw = VALID;
}
}
}
}
/// Ask for a reply from the user, 'y' or 'n'
///
/// No other characters are accepted, the message is repeated until a valid
/// reply is entered or <C-c> is hit.
///
/// @param[in] str Prompt: question to ask user. Is always followed by
/// " (y/n)?".
/// @param[in] direct Determines what function to use to get user input. If
/// true then ui_inchar() will be used, otherwise vgetc().
/// I.e. when direct is true then characters are obtained
/// directly from the user without buffers involved.
///
/// @return 'y' or 'n'. Last is also what will be returned in case of interrupt.
int ask_yesno(const char *const str, const bool direct)
{
const int save_State = State;
no_wait_return++;
State = CONFIRM; // Mouse behaves like with :confirm.
setmouse(); // Disable mouse in xterm.
no_mapping++;
int r = ' ';
while (r != 'y' && r != 'n') {
// Same highlighting as for wait_return.
smsg_attr(HL_ATTR(HLF_R), "%s (y/n)?", str);
if (direct) {
r = get_keystroke(NULL);
} else {
r = plain_vgetc();
}
if (r == Ctrl_C || r == ESC) {
r = 'n';
}
msg_putchar(r); // Show what you typed.
ui_flush();
}
no_wait_return--;
State = save_State;
setmouse();
no_mapping--;
return r;
}
/*
* Return TRUE if "c" is a mouse key.
*/
int is_mouse_key(int c)
{
return c == K_LEFTMOUSE
|| c == K_LEFTMOUSE_NM
|| c == K_LEFTDRAG
|| c == K_LEFTRELEASE
|| c == K_LEFTRELEASE_NM
|| c == K_MIDDLEMOUSE
|| c == K_MIDDLEDRAG
|| c == K_MIDDLERELEASE
|| c == K_RIGHTMOUSE
|| c == K_RIGHTDRAG
|| c == K_RIGHTRELEASE
|| c == K_MOUSEDOWN
|| c == K_MOUSEUP
|| c == K_MOUSELEFT
|| c == K_MOUSERIGHT
|| c == K_X1MOUSE
|| c == K_X1DRAG
|| c == K_X1RELEASE
|| c == K_X2MOUSE
|| c == K_X2DRAG
|| c == K_X2RELEASE;
}
/*
* Get a key stroke directly from the user.
* Ignores mouse clicks and scrollbar events, except a click for the left
* button (used at the more prompt).
* Doesn't use vgetc(), because it syncs undo and eats mapped characters.
* Disadvantage: typeahead is ignored.
* Translates the interrupt character for unix to ESC.
*/
int get_keystroke(MultiQueue *events)
{
char_u *buf = NULL;
int buflen = 150;
int maxlen;
int len = 0;
int n;
int save_mapped_ctrl_c = mapped_ctrl_c;
int waited = 0;
mapped_ctrl_c = 0; // mappings are not used here
for (;; ) {
// flush output before waiting
ui_flush();
/* Leave some room for check_termcode() to insert a key code into (max
* 5 chars plus NUL). And fix_input_buffer() can triple the number of
* bytes. */
maxlen = (buflen - 6 - len) / 3;
if (buf == NULL) {
buf = xmalloc((size_t)buflen);
} else if (maxlen < 10) {
// Need some more space. This might happen when receiving a long
// escape sequence.
buflen += 100;
buf = xrealloc(buf, (size_t)buflen);
maxlen = (buflen - 6 - len) / 3;
}
/* First time: blocking wait. Second time: wait up to 100ms for a
* terminal code to complete. */
n = os_inchar(buf + len, maxlen, len == 0 ? -1L : 100L, 0, events);
if (n > 0) {
// Replace zero and CSI by a special key code.
n = fix_input_buffer(buf + len, n);
len += n;
waited = 0;
} else if (len > 0)
++waited; /* keep track of the waiting time */
if (n > 0) { // found a termcode: adjust length
len = n;
}
if (len == 0) { // nothing typed yet
continue;
}
/* Handle modifier and/or special key code. */
n = buf[0];
if (n == K_SPECIAL) {
n = TO_SPECIAL(buf[1], buf[2]);
if (buf[1] == KS_MODIFIER
|| n == K_IGNORE
|| (is_mouse_key(n) && n != K_LEFTMOUSE)
) {
if (buf[1] == KS_MODIFIER)
mod_mask = buf[2];
len -= 3;
if (len > 0)
memmove(buf, buf + 3, (size_t)len);
continue;
}
break;
}
if (MB_BYTE2LEN(n) > len) {
// more bytes to get.
continue;
}
buf[len >= buflen ? buflen - 1 : len] = NUL;
n = utf_ptr2char(buf);
break;
}
xfree(buf);
mapped_ctrl_c = save_mapped_ctrl_c;
return n;
}
/*
* Get a number from the user.
* When "mouse_used" is not NULL allow using the mouse.
*/
int
get_number (
int colon, /* allow colon to abort */
int *mouse_used
)
{
int n = 0;
int c;
int typed = 0;
if (mouse_used != NULL)
*mouse_used = FALSE;
/* When not printing messages, the user won't know what to type, return a
* zero (as if CR was hit). */
if (msg_silent != 0)
return 0;
no_mapping++;
for (;; ) {
ui_cursor_goto(msg_row, msg_col);
c = safe_vgetc();
if (ascii_isdigit(c)) {
n = n * 10 + c - '0';
msg_putchar(c);
++typed;
} else if (c == K_DEL || c == K_KDEL || c == K_BS || c == Ctrl_H) {
if (typed > 0) {
MSG_PUTS("\b \b");
--typed;
}
n /= 10;
} else if (mouse_used != NULL && c == K_LEFTMOUSE) {
*mouse_used = TRUE;
n = mouse_row + 1;
break;
} else if (n == 0 && c == ':' && colon) {
stuffcharReadbuff(':');
if (!exmode_active)
cmdline_row = msg_row;
skip_redraw = TRUE; /* skip redraw once */
do_redraw = FALSE;
break;
} else if (c == CAR || c == NL || c == Ctrl_C || c == ESC)
break;
}
no_mapping--;
return n;
}
/*
* Ask the user to enter a number.
* When "mouse_used" is not NULL allow using the mouse and in that case return
* the line number.
*/
int prompt_for_number(int *mouse_used)
{
int i;
int save_cmdline_row;
int save_State;
/* When using ":silent" assume that <CR> was entered. */
if (mouse_used != NULL)
MSG_PUTS(_("Type number and <Enter> or click with mouse (empty cancels): "));
else
MSG_PUTS(_("Type number and <Enter> (empty cancels): "));
/* Set the state such that text can be selected/copied/pasted and we still
* get mouse events. */
save_cmdline_row = cmdline_row;
cmdline_row = 0;
save_State = State;
State = ASKMORE; // prevents a screen update when using a timer
// May show different mouse shape.
setmouse();
i = get_number(TRUE, mouse_used);
if (KeyTyped) {
// don't call wait_return() now
if (msg_row > 0) {
cmdline_row = msg_row - 1;
}
need_wait_return = false;
msg_didany = false;
msg_didout = false;
} else {
cmdline_row = save_cmdline_row;
}
State = save_State;
// May need to restore mouse shape.
setmouse();
return i;
}
void msgmore(long n)
{
long pn;
if (global_busy /* no messages now, wait until global is finished */
|| !messaging()) /* 'lazyredraw' set, don't do messages now */
return;
/* We don't want to overwrite another important message, but do overwrite
* a previous "more lines" or "fewer lines" message, so that "5dd" and
* then "put" reports the last action. */
if (keep_msg != NULL && !keep_msg_more)
return;
if (n > 0)
pn = n;
else
pn = -n;
if (pn > p_report) {
if (pn == 1) {
if (n > 0)
STRLCPY(msg_buf, _("1 more line"), MSG_BUF_LEN);
else
STRLCPY(msg_buf, _("1 line less"), MSG_BUF_LEN);
} else {
if (n > 0)
vim_snprintf((char *)msg_buf, MSG_BUF_LEN,
_("%" PRId64 " more lines"), (int64_t)pn);
else
vim_snprintf((char *)msg_buf, MSG_BUF_LEN,
_("%" PRId64 " fewer lines"), (int64_t)pn);
}
if (got_int) {
xstrlcat((char *)msg_buf, _(" (Interrupted)"), MSG_BUF_LEN);
}
if (msg(msg_buf)) {
set_keep_msg(msg_buf, 0);
keep_msg_more = TRUE;
}
}
}
/*
* flush map and typeahead buffers and give a warning for an error
*/
void beep_flush(void)
{
if (emsg_silent == 0) {
flush_buffers(FLUSH_MINIMAL);
vim_beep(BO_ERROR);
}
}
// Give a warning for an error
// val is one of the BO_ values, e.g., BO_OPER
void vim_beep(unsigned val)
{
called_vim_beep = true;
if (emsg_silent == 0) {
if (!((bo_flags & val) || (bo_flags & BO_ALL))) {
static int beeps = 0;
static uint64_t start_time = 0;
// Only beep up to three times per half a second,
// otherwise a sequence of beeps would freeze Vim.
if (start_time == 0 || os_hrtime() - start_time > 500000000u) {
beeps = 0;
start_time = os_hrtime();
}
beeps++;
if (beeps <= 3) {
if (p_vb) {
ui_call_visual_bell();
} else {
ui_call_bell();
}
}
}
// When 'debug' contains "beep" produce a message. If we are sourcing
// a script or executing a function give the user a hint where the beep
// comes from.
if (vim_strchr(p_debug, 'e') != NULL) {
msg_source(HL_ATTR(HLF_W));
msg_attr(_("Beep!"), HL_ATTR(HLF_W));
}
}
}
#if defined(EXITFREE)
void free_users(void)
{
ga_clear_strings(&ga_users);
}
#endif
/*
* Find all user names for user completion.
* Done only once and then cached.
*/
static void init_users(void)
{
static int lazy_init_done = FALSE;
if (lazy_init_done) {
return;
}
lazy_init_done = TRUE;
os_get_usernames(&ga_users);
}
/*
* Function given to ExpandGeneric() to obtain an user names.
*/
char_u *get_users(expand_T *xp, int idx)
{
init_users();
if (idx < ga_users.ga_len)
return ((char_u **)ga_users.ga_data)[idx];
return NULL;
}
/*
* Check whether name matches a user name. Return:
* 0 if name does not match any user name.
* 1 if name partially matches the beginning of a user name.
* 2 is name fully matches a user name.
*/
int match_user(char_u *name)
{
int n = (int)STRLEN(name);
int result = 0;
init_users();
for (int i = 0; i < ga_users.ga_len; i++) {
if (STRCMP(((char_u **)ga_users.ga_data)[i], name) == 0)
return 2; /* full match */
if (STRNCMP(((char_u **)ga_users.ga_data)[i], name, n) == 0)
result = 1; /* partial match */
}
return result;
}
/// Preserve files and exit.
/// @note IObuff must contain a message.
/// @note This may be called from deadly_signal() in a signal handler, avoid
/// unsafe functions, such as allocating memory.
void preserve_exit(void)
FUNC_ATTR_NORETURN
{
// 'true' when we are sure to exit, e.g., after a deadly signal
static bool really_exiting = false;
// Prevent repeated calls into this method.
if (really_exiting) {
if (input_global_fd() >= 0) {
// normalize stream (#2598)
stream_set_blocking(input_global_fd(), true);
}
exit(2);
}
really_exiting = true;
// Ignore SIGHUP while we are already exiting. #9274
signal_reject_deadly();
mch_errmsg(IObuff);
mch_errmsg("\n");
ui_flush();
ml_close_notmod(); // close all not-modified buffers
FOR_ALL_BUFFERS(buf) {
if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) {
mch_errmsg((uint8_t *)"Vim: preserving files...\n");
ui_flush();
ml_sync_all(false, false, true); // preserve all swap files
break;
}
}
ml_close_all(false); // close all memfiles, without deleting
mch_errmsg("Vim: Finished.\n");
getout(1);
}
/*
* Check for CTRL-C pressed, but only once in a while.
* Should be used instead of os_breakcheck() for functions that check for
* each line in the file. Calling os_breakcheck() each time takes too much
* time, because it can be a system call.
*/
#ifndef BREAKCHECK_SKIP
# define BREAKCHECK_SKIP 1000
#endif
static int breakcheck_count = 0;
void line_breakcheck(void)
{
if (++breakcheck_count >= BREAKCHECK_SKIP) {
breakcheck_count = 0;
os_breakcheck();
}
}
/*
* Like line_breakcheck() but check 10 times less often.
*/
void fast_breakcheck(void)
{
if (++breakcheck_count >= BREAKCHECK_SKIP * 10) {
breakcheck_count = 0;
os_breakcheck();
}
}
// Like line_breakcheck() but check 100 times less often.
void veryfast_breakcheck(void)
{
if (++breakcheck_count >= BREAKCHECK_SKIP * 100) {
breakcheck_count = 0;
os_breakcheck();
}
}
/// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error.
/// Invalidates cached tags.
///
/// @return shell command exit code
int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg)
{
int retval;
proftime_T wait_time;
if (p_verbose > 3) {
verbose_enter();
smsg(_("Executing command: \"%s\""), cmd == NULL ? p_sh : cmd);
msg_putchar('\n');
verbose_leave();
}
if (do_profiling == PROF_YES) {
prof_child_enter(&wait_time);
}
if (*p_sh == NUL) {
EMSG(_(e_shellempty));
retval = -1;
} else {
// The external command may update a tags file, clear cached tags.
tag_freematch();
retval = os_call_shell(cmd, opts, extra_shell_arg);
}
set_vim_var_nr(VV_SHELL_ERROR, (varnumber_T)retval);
if (do_profiling == PROF_YES) {
prof_child_exit(&wait_time);
}
return retval;
}
/// Get the stdout of an external command.
/// If "ret_len" is NULL replace NUL characters with NL. When "ret_len" is not
/// NULL store the length there.
///
/// @param cmd command to execute
/// @param infile optional input file name
/// @param flags can be kShellOptSilent or 0
/// @param ret_len length of the stdout
///
/// @return an allocated string, or NULL for error.
char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags,
size_t *ret_len)
{
char_u *buffer = NULL;
if (check_restricted() || check_secure())
return NULL;
// get a name for the temp file
char_u *tempname = vim_tempname();
if (tempname == NULL) {
EMSG(_(e_notmp));
return NULL;
}
// Add the redirection stuff
char_u *command = make_filter_cmd(cmd, infile, tempname);
/*
* Call the shell to execute the command (errors are ignored).
* Don't check timestamps here.
*/
++no_check_timestamps;
call_shell(command, kShellOptDoOut | kShellOptExpand | flags, NULL);
--no_check_timestamps;
xfree(command);
// read the names from the file into memory
FILE *fd = os_fopen((char *)tempname, READBIN);
if (fd == NULL) {
EMSG2(_(e_notopen), tempname);
goto done;
}
fseek(fd, 0L, SEEK_END);
size_t len = (size_t)ftell(fd); // get size of temp file
fseek(fd, 0L, SEEK_SET);
buffer = xmalloc(len + 1);
size_t i = fread((char *)buffer, 1, len, fd);
fclose(fd);
os_remove((char *)tempname);
if (i != len) {
EMSG2(_(e_notread), tempname);
XFREE_CLEAR(buffer);
} else if (ret_len == NULL) {
/* Change NUL into SOH, otherwise the string is truncated. */
for (i = 0; i < len; ++i)
if (buffer[i] == NUL)
buffer[i] = 1;
buffer[len] = NUL; /* make sure the buffer is terminated */
} else {
*ret_len = len;
}
done:
xfree(tempname);
return buffer;
}
/*
* Free the list of files returned by expand_wildcards() or other expansion
* functions.
*/
void FreeWild(int count, char_u **files)
{
if (count <= 0 || files == NULL)
return;
while (count--)
xfree(files[count]);
xfree(files);
}
/*
* Return TRUE when need to go to Insert mode because of 'insertmode'.
* Don't do this when still processing a command or a mapping.
* Don't do this when inside a ":normal" command.
*/
int goto_im(void)
{
return p_im && stuff_empty() && typebuf_typed();
}
/// Put the timestamp of an undo header in "buf[buflen]" in a nice format.
void add_time(char_u *buf, size_t buflen, time_t tt)
{
struct tm curtime;
if (time(NULL) - tt >= 100) {
os_localtime_r(&tt, &curtime);
if (time(NULL) - tt < (60L * 60L * 12L)) {
// within 12 hours
(void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime);
} else {
// longer ago
(void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime);
}
} else {
int64_t seconds = time(NULL) - tt;
vim_snprintf((char *)buf, buflen,
NGETTEXT("%" PRId64 " second ago",
"%" PRId64 " seconds ago", (uint32_t)seconds),
seconds);
}
}