1164 lines
30 KiB
C
1164 lines
30 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, int backward, int 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; ascii_iswhite(line[j]) && j <= i; 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();
|
|
}
|
|
}
|
|
|
|
/// 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();
|
|
}
|