mirror of
https://github.com/neovim/neovim.git
synced 2025-10-09 19:36:40 +00:00

Problem: Internal error when ModeChanged is triggered when v:event is
already in use.
Solution: Save and restore v:event if needed.
3075a45592
In the vim codebase there is no occurrence of get_vim_var_dict(VV_EVENT)
after the above patch, so in order to hold the same invariant in the
neovim codebase we needed to replace more occurrences than the related
vim patch.
6710 lines
194 KiB
C
6710 lines
194 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
|
|
|
|
/*
|
|
* ex_getln.c: Functions for entering and editing an Ex command line.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "nvim/api/private/helpers.h"
|
|
#include "nvim/arabic.h"
|
|
#include "nvim/ascii.h"
|
|
#include "nvim/assert.h"
|
|
#include "nvim/buffer.h"
|
|
#include "nvim/charset.h"
|
|
#include "nvim/cursor.h"
|
|
#include "nvim/cursor_shape.h"
|
|
#include "nvim/digraph.h"
|
|
#include "nvim/edit.h"
|
|
#include "nvim/eval.h"
|
|
#include "nvim/eval/userfunc.h"
|
|
#include "nvim/event/loop.h"
|
|
#include "nvim/ex_cmds.h"
|
|
#include "nvim/ex_cmds2.h"
|
|
#include "nvim/ex_docmd.h"
|
|
#include "nvim/ex_eval.h"
|
|
#include "nvim/ex_getln.h"
|
|
#include "nvim/fileio.h"
|
|
#include "nvim/func_attr.h"
|
|
#include "nvim/garray.h"
|
|
#include "nvim/getchar.h"
|
|
#include "nvim/highlight.h"
|
|
#include "nvim/highlight_defs.h"
|
|
#include "nvim/if_cscope.h"
|
|
#include "nvim/indent.h"
|
|
#include "nvim/keymap.h"
|
|
#include "nvim/lib/kvec.h"
|
|
#include "nvim/log.h"
|
|
#include "nvim/lua/executor.h"
|
|
#include "nvim/main.h"
|
|
#include "nvim/mark.h"
|
|
#include "nvim/mbyte.h"
|
|
#include "nvim/memline.h"
|
|
#include "nvim/memory.h"
|
|
#include "nvim/menu.h"
|
|
#include "nvim/message.h"
|
|
#include "nvim/misc1.h"
|
|
#include "nvim/mouse.h"
|
|
#include "nvim/move.h"
|
|
#include "nvim/ops.h"
|
|
#include "nvim/option.h"
|
|
#include "nvim/os/input.h"
|
|
#include "nvim/os/os.h"
|
|
#include "nvim/os/time.h"
|
|
#include "nvim/os_unix.h"
|
|
#include "nvim/path.h"
|
|
#include "nvim/popupmnu.h"
|
|
#include "nvim/regexp.h"
|
|
#include "nvim/screen.h"
|
|
#include "nvim/search.h"
|
|
#include "nvim/sign.h"
|
|
#include "nvim/state.h"
|
|
#include "nvim/strings.h"
|
|
#include "nvim/syntax.h"
|
|
#include "nvim/tag.h"
|
|
#include "nvim/ui.h"
|
|
#include "nvim/vim.h"
|
|
#include "nvim/viml/parser/expressions.h"
|
|
#include "nvim/viml/parser/parser.h"
|
|
#include "nvim/window.h"
|
|
|
|
/// Command-line colors: one chunk
|
|
///
|
|
/// Defines a region which has the same highlighting.
|
|
typedef struct {
|
|
int start; ///< Colored chunk start.
|
|
int end; ///< Colored chunk end (exclusive, > start).
|
|
int attr; ///< Highlight attr.
|
|
} CmdlineColorChunk;
|
|
|
|
/// Command-line colors
|
|
///
|
|
/// Holds data about all colors.
|
|
typedef kvec_t(CmdlineColorChunk) CmdlineColors;
|
|
|
|
/// Command-line coloring
|
|
///
|
|
/// Holds both what are the colors and what have been colored. Latter is used to
|
|
/// suppress unnecessary calls to coloring callbacks.
|
|
typedef struct {
|
|
unsigned prompt_id; ///< ID of the prompt which was colored last.
|
|
char *cmdbuff; ///< What exactly was colored last time or NULL.
|
|
CmdlineColors colors; ///< Last colors.
|
|
} ColoredCmdline;
|
|
|
|
/// Keeps track how much state must be sent to external ui.
|
|
typedef enum {
|
|
kCmdRedrawNone,
|
|
kCmdRedrawPos,
|
|
kCmdRedrawAll,
|
|
} CmdRedraw;
|
|
|
|
/*
|
|
* Variables shared between getcmdline(), redrawcmdline() and others.
|
|
* These need to be saved when using CTRL-R |, that's why they are in a
|
|
* structure.
|
|
*/
|
|
struct cmdline_info {
|
|
char_u *cmdbuff; // pointer to command line buffer
|
|
int cmdbufflen; // length of cmdbuff
|
|
int cmdlen; // number of chars in command line
|
|
int cmdpos; // current cursor position
|
|
int cmdspos; // cursor column on screen
|
|
int cmdfirstc; // ':', '/', '?', '=', '>' or NUL
|
|
int cmdindent; // number of spaces before cmdline
|
|
char_u *cmdprompt; // message in front of cmdline
|
|
int cmdattr; // attributes for prompt
|
|
int overstrike; // Typing mode on the command line. Shared by
|
|
// getcmdline() and put_on_cmdline().
|
|
expand_T *xpc; // struct being used for expansion, xp_pattern
|
|
// may point into cmdbuff
|
|
int xp_context; // type of expansion
|
|
char_u *xp_arg; // user-defined expansion arg
|
|
int input_fn; // when TRUE Invoked for input() function
|
|
unsigned prompt_id; ///< Prompt number, used to disable coloring on errors.
|
|
Callback highlight_callback; ///< Callback used for coloring user input.
|
|
ColoredCmdline last_colors; ///< Last cmdline colors
|
|
int level; // current cmdline level
|
|
struct cmdline_info *prev_ccline; ///< pointer to saved cmdline state
|
|
char special_char; ///< last putcmdline char (used for redraws)
|
|
bool special_shift; ///< shift of last putcmdline char
|
|
CmdRedraw redraw_state; ///< needed redraw for external cmdline
|
|
};
|
|
/// Last value of prompt_id, incremented when doing new prompt
|
|
static unsigned last_prompt_id = 0;
|
|
|
|
// Struct to store the viewstate during 'incsearch' highlighting.
|
|
typedef struct {
|
|
colnr_T vs_curswant;
|
|
colnr_T vs_leftcol;
|
|
linenr_T vs_topline;
|
|
int vs_topfill;
|
|
linenr_T vs_botline;
|
|
int vs_empty_rows;
|
|
} viewstate_T;
|
|
|
|
// Struct to store the state of 'incsearch' highlighting.
|
|
typedef struct {
|
|
pos_T search_start; // where 'incsearch' starts searching
|
|
pos_T save_cursor;
|
|
viewstate_T init_viewstate;
|
|
viewstate_T old_viewstate;
|
|
pos_T match_start;
|
|
pos_T match_end;
|
|
bool did_incsearch;
|
|
bool incsearch_postponed;
|
|
int magic_save;
|
|
} incsearch_state_T;
|
|
|
|
typedef struct command_line_state {
|
|
VimState state;
|
|
int firstc;
|
|
long count;
|
|
int indent;
|
|
int c;
|
|
int gotesc; // TRUE when <ESC> just typed
|
|
int do_abbr; // when TRUE check for abbr.
|
|
char_u *lookfor; // string to match
|
|
int hiscnt; // current history line in use
|
|
int save_hiscnt; // history line before attempting
|
|
// to jump to next match
|
|
int histype; // history type to be used
|
|
incsearch_state_T is_state;
|
|
int did_wild_list; // did wild_list() recently
|
|
int wim_index; // index in wim_flags[]
|
|
int res;
|
|
int save_msg_scroll;
|
|
int save_State; // remember State when called
|
|
char_u *save_p_icm;
|
|
int some_key_typed; // one of the keys was typed
|
|
// mouse drag and release events are ignored, unless they are
|
|
// preceded with a mouse down event
|
|
int ignore_drag_release;
|
|
int break_ctrl_c;
|
|
expand_T xpc;
|
|
long *b_im_ptr;
|
|
} CommandLineState;
|
|
|
|
typedef struct cmdline_info CmdlineInfo;
|
|
|
|
/* The current cmdline_info. It is initialized in getcmdline() and after that
|
|
* used by other functions. When invoking getcmdline() recursively it needs
|
|
* to be saved with save_cmdline() and restored with restore_cmdline().
|
|
* TODO: make it local to getcmdline() and pass it around. */
|
|
static struct cmdline_info ccline;
|
|
|
|
static int cmd_showtail; // Only show path tail in lists ?
|
|
|
|
static int new_cmdpos; // position set by set_cmdline_pos()
|
|
|
|
/// currently displayed block of context
|
|
static Array cmdline_block = ARRAY_DICT_INIT;
|
|
|
|
/*
|
|
* Type used by call_user_expand_func
|
|
*/
|
|
typedef void *(*user_expand_func_T)(const char_u *, int, typval_T *);
|
|
|
|
static histentry_T *(history[HIST_COUNT]) = { NULL, NULL, NULL, NULL, NULL };
|
|
static int hisidx[HIST_COUNT] = { -1, -1, -1, -1, -1 }; // lastused entry
|
|
static int hisnum[HIST_COUNT] = { 0, 0, 0, 0, 0 };
|
|
// identifying (unique) number of newest history entry
|
|
static int hislen = 0; // actual length of history tables
|
|
|
|
/// Flag for command_line_handle_key to ignore <C-c>
|
|
///
|
|
/// Used if it was received while processing highlight function in order for
|
|
/// user interrupting highlight function to not interrupt command-line.
|
|
static bool getln_interrupted_highlight = false;
|
|
|
|
// "compl_match_array" points the currently displayed list of entries in the
|
|
// popup menu. It is NULL when there is no popup menu.
|
|
static pumitem_T *compl_match_array = NULL;
|
|
static int compl_match_arraysize;
|
|
// First column in cmdline of the matched item for completion.
|
|
static int compl_startcol;
|
|
static int compl_selected;
|
|
|
|
/// |:checkhealth| completion items
|
|
///
|
|
/// Regenerates on every new command line prompt, to accomodate changes on the
|
|
/// runtime files.
|
|
typedef struct {
|
|
garray_T names; // healthcheck names
|
|
unsigned last_gen; // last_prompt_id where names were generated
|
|
} CheckhealthComp;
|
|
|
|
/// Cookie used when converting filepath to name
|
|
struct healthchecks_cookie {
|
|
garray_T *names; // global healthchecks
|
|
bool is_lua; // true if the current entry is a Lua healthcheck
|
|
};
|
|
|
|
static CheckhealthComp healthchecks = { GA_INIT(sizeof(char_u *), 10), 0 };
|
|
|
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
|
# include "ex_getln.c.generated.h"
|
|
#endif
|
|
|
|
static int cmd_hkmap = 0; // Hebrew mapping during command line
|
|
|
|
static void save_viewstate(viewstate_T *vs)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
vs->vs_curswant = curwin->w_curswant;
|
|
vs->vs_leftcol = curwin->w_leftcol;
|
|
vs->vs_topline = curwin->w_topline;
|
|
vs->vs_topfill = curwin->w_topfill;
|
|
vs->vs_botline = curwin->w_botline;
|
|
vs->vs_empty_rows = curwin->w_empty_rows;
|
|
}
|
|
|
|
static void restore_viewstate(viewstate_T *vs)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
curwin->w_curswant = vs->vs_curswant;
|
|
curwin->w_leftcol = vs->vs_leftcol;
|
|
curwin->w_topline = vs->vs_topline;
|
|
curwin->w_topfill = vs->vs_topfill;
|
|
curwin->w_botline = vs->vs_botline;
|
|
curwin->w_empty_rows = vs->vs_empty_rows;
|
|
}
|
|
|
|
static void init_incsearch_state(incsearch_state_T *s)
|
|
{
|
|
s->match_start = curwin->w_cursor;
|
|
s->did_incsearch = false;
|
|
s->incsearch_postponed = false;
|
|
s->magic_save = p_magic;
|
|
clearpos(&s->match_end);
|
|
s->save_cursor = curwin->w_cursor; // may be restored later
|
|
s->search_start = curwin->w_cursor;
|
|
save_viewstate(&s->init_viewstate);
|
|
save_viewstate(&s->old_viewstate);
|
|
}
|
|
|
|
/// Completion for |:checkhealth| command.
|
|
///
|
|
/// Given to ExpandGeneric() to obtain all available heathcheck names.
|
|
/// @param[in] idx Index of the healthcheck item.
|
|
/// @param[in] xp Not used.
|
|
static char_u *get_healthcheck_names(expand_T *xp, int idx)
|
|
{
|
|
// Generate the first time or on new prompt.
|
|
if (healthchecks.last_gen == 0 || healthchecks.last_gen != last_prompt_id) {
|
|
ga_clear_strings(&healthchecks.names);
|
|
char *patterns[3] = { "autoload/health/**.vim", "lua/**/**/health/init.lua", // NOLINT
|
|
"lua/**/**/health.lua" }; // NOLINT
|
|
for (int i = 0; i < 3; i++) {
|
|
struct healthchecks_cookie hcookie = { .names = &healthchecks.names, .is_lua = i != 0 };
|
|
do_in_runtimepath((char_u *)patterns[i], DIP_ALL, get_healthcheck_cb, &hcookie);
|
|
|
|
if (healthchecks.names.ga_len > 0) {
|
|
ga_remove_duplicate_strings(&healthchecks.names);
|
|
}
|
|
}
|
|
// Tracked to regenerate items on next prompt.
|
|
healthchecks.last_gen = last_prompt_id;
|
|
}
|
|
return idx <
|
|
(int)healthchecks.names.ga_len ? ((char_u **)(healthchecks.names.ga_data))[idx] : NULL;
|
|
}
|
|
|
|
/// Transform healthcheck file path into it's name.
|
|
///
|
|
/// Used as a callback for do_in_runtimepath
|
|
/// @param[in] path Expanded path to a possible healthcheck.
|
|
/// @param[out] cookie Array where names will be inserted.
|
|
static void get_healthcheck_cb(char_u *path, void *cookie)
|
|
{
|
|
if (path != NULL) {
|
|
struct healthchecks_cookie *hcookie = (struct healthchecks_cookie *)cookie;
|
|
char *pattern;
|
|
char *sub = "\\1";
|
|
char_u *res;
|
|
|
|
if (hcookie->is_lua) {
|
|
// Lua: transform "../lua/vim/lsp/health.lua" into "vim.lsp"
|
|
pattern = ".*lua[\\/]\\(.\\{-}\\)[\\/]health\\([\\/]init\\)\\?\\.lua$";
|
|
} else {
|
|
// Vim: transform "../autoload/health/provider.vim" into "provider"
|
|
pattern = ".*[\\/]\\([^\\/]*\\)\\.vim$";
|
|
}
|
|
|
|
res = do_string_sub(path, (char_u *)pattern, (char_u *)sub, NULL, (char_u *)"g");
|
|
if (hcookie->is_lua && res != NULL) {
|
|
// Replace slashes with dots as represented by the healthcheck plugin.
|
|
char_u *ares = do_string_sub(res, (char_u *)"[\\/]", (char_u *)".", NULL, (char_u *)"g");
|
|
xfree(res);
|
|
res = ares;
|
|
}
|
|
|
|
if (res != NULL) {
|
|
GA_APPEND(char_u *, hcookie->names, res);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return true when 'incsearch' highlighting is to be done.
|
|
// Sets search_first_line and search_last_line to the address range.
|
|
static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_state_T *s,
|
|
int *skiplen, int *patlen)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
char_u *cmd;
|
|
cmdmod_T save_cmdmod = cmdmod;
|
|
char_u *p;
|
|
bool delim_optional = false;
|
|
int delim;
|
|
char_u *end;
|
|
char *dummy;
|
|
exarg_T ea;
|
|
pos_T save_cursor;
|
|
bool use_last_pat;
|
|
bool retval = false;
|
|
|
|
*skiplen = 0;
|
|
*patlen = ccline.cmdlen;
|
|
|
|
if (!p_is || cmd_silent) {
|
|
return false;
|
|
}
|
|
|
|
// by default search all lines
|
|
search_first_line = 0;
|
|
search_last_line = MAXLNUM;
|
|
|
|
if (firstc == '/' || firstc == '?') {
|
|
*search_delim = firstc;
|
|
return true;
|
|
}
|
|
if (firstc != ':') {
|
|
return false;
|
|
}
|
|
|
|
emsg_off++;
|
|
memset(&ea, 0, sizeof(ea));
|
|
ea.line1 = 1;
|
|
ea.line2 = 1;
|
|
ea.cmd = ccline.cmdbuff;
|
|
ea.addr_type = ADDR_LINES;
|
|
|
|
parse_command_modifiers(&ea, &dummy, true);
|
|
cmdmod = save_cmdmod;
|
|
|
|
cmd = skip_range(ea.cmd, NULL);
|
|
if (vim_strchr((char_u *)"sgvl", *cmd) == NULL) {
|
|
goto theend;
|
|
}
|
|
|
|
// Skip over "substitute" to find the pattern separator.
|
|
for (p = cmd; ASCII_ISALPHA(*p); p++) {}
|
|
if (*skipwhite(p) == NUL) {
|
|
goto theend;
|
|
}
|
|
|
|
if (STRNCMP(cmd, "substitute", p - cmd) == 0
|
|
|| STRNCMP(cmd, "smagic", p - cmd) == 0
|
|
|| STRNCMP(cmd, "snomagic", MAX(p - cmd, 3)) == 0
|
|
|| STRNCMP(cmd, "vglobal", p - cmd) == 0) {
|
|
if (*cmd == 's' && cmd[1] == 'm') {
|
|
p_magic = true;
|
|
} else if (*cmd == 's' && cmd[1] == 'n') {
|
|
p_magic = false;
|
|
}
|
|
} else if (STRNCMP(cmd, "sort", MAX(p - cmd, 3)) == 0) {
|
|
// skip over ! and flags
|
|
if (*p == '!') {
|
|
p = skipwhite(p + 1);
|
|
}
|
|
while (ASCII_ISALPHA(*(p = skipwhite(p)))) {
|
|
p++;
|
|
}
|
|
if (*p == NUL) {
|
|
goto theend;
|
|
}
|
|
} else if (STRNCMP(cmd, "vimgrep", MAX(p - cmd, 3)) == 0
|
|
|| STRNCMP(cmd, "vimgrepadd", MAX(p - cmd, 8)) == 0
|
|
|| STRNCMP(cmd, "lvimgrep", MAX(p - cmd, 2)) == 0
|
|
|| STRNCMP(cmd, "lvimgrepadd", MAX(p - cmd, 9)) == 0
|
|
|| STRNCMP(cmd, "global", p - cmd) == 0) {
|
|
// skip over "!/".
|
|
if (*p == '!') {
|
|
p++;
|
|
if (*skipwhite(p) == NUL) {
|
|
goto theend;
|
|
}
|
|
}
|
|
if (*cmd != 'g') {
|
|
delim_optional = true;
|
|
}
|
|
} else {
|
|
goto theend;
|
|
}
|
|
|
|
p = skipwhite(p);
|
|
delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++;
|
|
*search_delim = delim;
|
|
end = skip_regexp(p, delim, p_magic, NULL);
|
|
|
|
use_last_pat = end == p && *end == delim;
|
|
if (end == p && !use_last_pat) {
|
|
goto theend;
|
|
}
|
|
|
|
// Don't do 'hlsearch' highlighting if the pattern matches everything.
|
|
if (!use_last_pat) {
|
|
char_u c = *end;
|
|
int empty;
|
|
|
|
*end = NUL;
|
|
empty = empty_pattern(p);
|
|
*end = c;
|
|
if (empty) {
|
|
goto theend;
|
|
}
|
|
}
|
|
|
|
// found a non-empty pattern or //
|
|
*skiplen = (int)(p - ccline.cmdbuff);
|
|
*patlen = (int)(end - p);
|
|
|
|
// parse the address range
|
|
save_cursor = curwin->w_cursor;
|
|
curwin->w_cursor = s->search_start;
|
|
parse_cmd_address(&ea, &dummy, true);
|
|
if (ea.addr_count > 0) {
|
|
// Allow for reverse match.
|
|
if (ea.line2 < ea.line1) {
|
|
search_first_line = ea.line2;
|
|
search_last_line = ea.line1;
|
|
} else {
|
|
search_first_line = ea.line1;
|
|
search_last_line = ea.line2;
|
|
}
|
|
} else if (cmd[0] == 's' && cmd[1] != 'o') {
|
|
// :s defaults to the current line
|
|
search_first_line = curwin->w_cursor.lnum;
|
|
search_last_line = curwin->w_cursor.lnum;
|
|
}
|
|
|
|
curwin->w_cursor = save_cursor;
|
|
retval = true;
|
|
theend:
|
|
emsg_off--;
|
|
return retval;
|
|
}
|
|
|
|
// May do 'incsearch' highlighting if desired.
|
|
static void may_do_incsearch_highlighting(int firstc, long count, incsearch_state_T *s)
|
|
{
|
|
pos_T end_pos;
|
|
proftime_T tm;
|
|
searchit_arg_T sia;
|
|
int skiplen, patlen;
|
|
char_u next_char;
|
|
char_u use_last_pat;
|
|
int search_delim;
|
|
|
|
// Parsing range may already set the last search pattern.
|
|
// NOTE: must call restore_last_search_pattern() before returning!
|
|
save_last_search_pattern();
|
|
|
|
if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen,
|
|
&patlen)) {
|
|
restore_last_search_pattern();
|
|
finish_incsearch_highlighting(false, s, true);
|
|
return;
|
|
}
|
|
|
|
// if there is a character waiting, search and redraw later
|
|
if (char_avail()) {
|
|
restore_last_search_pattern();
|
|
s->incsearch_postponed = true;
|
|
return;
|
|
}
|
|
s->incsearch_postponed = false;
|
|
|
|
if (search_first_line == 0) {
|
|
// start at the original cursor position
|
|
curwin->w_cursor = s->search_start;
|
|
} else if (search_first_line > curbuf->b_ml.ml_line_count) {
|
|
// start after the last line
|
|
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
|
|
curwin->w_cursor.col = MAXCOL;
|
|
} else {
|
|
// start at the first line in the range
|
|
curwin->w_cursor.lnum = search_first_line;
|
|
curwin->w_cursor.col = 0;
|
|
}
|
|
int found; // do_search() result
|
|
|
|
// Use the previous pattern for ":s//".
|
|
next_char = ccline.cmdbuff[skiplen + patlen];
|
|
use_last_pat = patlen == 0 && skiplen > 0
|
|
&& ccline.cmdbuff[skiplen - 1] == next_char;
|
|
|
|
// If there is no pattern, don't do anything.
|
|
if (patlen == 0 && !use_last_pat) {
|
|
found = 0;
|
|
set_no_hlsearch(true); // turn off previous highlight
|
|
redraw_all_later(SOME_VALID);
|
|
} else {
|
|
int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK;
|
|
ui_busy_start();
|
|
ui_flush();
|
|
emsg_off++; // So it doesn't beep if bad expr
|
|
// Set the time limit to half a second.
|
|
tm = profile_setlimit(500L);
|
|
if (!p_hls) {
|
|
search_flags += SEARCH_KEEP;
|
|
}
|
|
if (search_first_line != 0) {
|
|
search_flags += SEARCH_START;
|
|
}
|
|
ccline.cmdbuff[skiplen + patlen] = NUL;
|
|
memset(&sia, 0, sizeof(sia));
|
|
sia.sa_tm = &tm;
|
|
found = do_search(NULL, firstc == ':' ? '/' : firstc, search_delim,
|
|
ccline.cmdbuff + skiplen, count,
|
|
search_flags, &sia);
|
|
ccline.cmdbuff[skiplen + patlen] = next_char;
|
|
emsg_off--;
|
|
if (curwin->w_cursor.lnum < search_first_line
|
|
|| curwin->w_cursor.lnum > search_last_line) {
|
|
// match outside of address range
|
|
found = 0;
|
|
curwin->w_cursor = s->search_start;
|
|
}
|
|
|
|
// if interrupted while searching, behave like it failed
|
|
if (got_int) {
|
|
(void)vpeekc(); // remove <C-C> from input stream
|
|
got_int = false; // don't abandon the command line
|
|
found = 0;
|
|
} else if (char_avail()) {
|
|
// cancelled searching because a char was typed
|
|
s->incsearch_postponed = true;
|
|
}
|
|
ui_busy_stop();
|
|
}
|
|
|
|
if (found != 0) {
|
|
highlight_match = true; // highlight position
|
|
} else {
|
|
highlight_match = false; // remove highlight
|
|
}
|
|
|
|
// first restore the old curwin values, so the screen is
|
|
// positioned in the same way as the actual search command
|
|
restore_viewstate(&s->old_viewstate);
|
|
changed_cline_bef_curs();
|
|
update_topline(curwin);
|
|
|
|
if (found != 0) {
|
|
pos_T save_pos = curwin->w_cursor;
|
|
|
|
s->match_start = curwin->w_cursor;
|
|
set_search_match(&curwin->w_cursor);
|
|
validate_cursor();
|
|
end_pos = curwin->w_cursor;
|
|
s->match_end = end_pos;
|
|
curwin->w_cursor = save_pos;
|
|
} else {
|
|
end_pos = curwin->w_cursor; // shutup gcc 4
|
|
}
|
|
//
|
|
// Disable 'hlsearch' highlighting if the pattern matches
|
|
// everything. Avoids a flash when typing "foo\|".
|
|
if (!use_last_pat) {
|
|
next_char = ccline.cmdbuff[skiplen + patlen];
|
|
ccline.cmdbuff[skiplen + patlen] = NUL;
|
|
if (empty_pattern(ccline.cmdbuff) && !no_hlsearch) {
|
|
redraw_all_later(SOME_VALID);
|
|
set_no_hlsearch(true);
|
|
}
|
|
ccline.cmdbuff[skiplen + patlen] = next_char;
|
|
}
|
|
|
|
validate_cursor();
|
|
// May redraw the status line to show the cursor position.
|
|
if (p_ru && curwin->w_status_height > 0) {
|
|
curwin->w_redr_status = true;
|
|
}
|
|
|
|
update_screen(SOME_VALID);
|
|
highlight_match = false;
|
|
restore_last_search_pattern();
|
|
|
|
// Leave it at the end to make CTRL-R CTRL-W work. But not when beyond the
|
|
// end of the pattern, e.g. for ":s/pat/".
|
|
if (ccline.cmdbuff[skiplen + patlen] != NUL) {
|
|
curwin->w_cursor = s->search_start;
|
|
} else if (found != 0) {
|
|
curwin->w_cursor = end_pos;
|
|
}
|
|
|
|
msg_starthere();
|
|
redrawcmdline();
|
|
s->did_incsearch = true;
|
|
}
|
|
|
|
// When CTRL-L typed: add character from the match to the pattern.
|
|
// May set "*c" to the added character.
|
|
// Return OK when calling command_line_not_changed.
|
|
static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
int skiplen, patlen;
|
|
int search_delim;
|
|
|
|
// Parsing range may already set the last search pattern.
|
|
// NOTE: must call restore_last_search_pattern() before returning!
|
|
save_last_search_pattern();
|
|
|
|
// Add a character from under the cursor for 'incsearch'
|
|
if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen,
|
|
&patlen)) {
|
|
restore_last_search_pattern();
|
|
return FAIL;
|
|
}
|
|
restore_last_search_pattern();
|
|
|
|
if (s->did_incsearch) {
|
|
curwin->w_cursor = s->match_end;
|
|
*c = gchar_cursor();
|
|
if (*c != NUL) {
|
|
// If 'ignorecase' and 'smartcase' are set and the
|
|
// command line has no uppercase characters, convert
|
|
// the character to lowercase
|
|
if (p_ic && p_scs
|
|
&& !pat_has_uppercase(ccline.cmdbuff + skiplen)) {
|
|
*c = mb_tolower(*c);
|
|
}
|
|
if (*c == search_delim
|
|
|| vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), *c)
|
|
!= NULL) {
|
|
// put a backslash before special characters
|
|
stuffcharReadbuff(*c);
|
|
*c = '\\';
|
|
}
|
|
return FAIL;
|
|
}
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool call_update_screen)
|
|
{
|
|
if (s->did_incsearch) {
|
|
s->did_incsearch = false;
|
|
if (gotesc) {
|
|
curwin->w_cursor = s->save_cursor;
|
|
} else {
|
|
if (!equalpos(s->save_cursor, s->search_start)) {
|
|
// put the '" mark at the original position
|
|
curwin->w_cursor = s->save_cursor;
|
|
setpcmark();
|
|
}
|
|
curwin->w_cursor = s->search_start; // -V519
|
|
}
|
|
restore_viewstate(&s->old_viewstate);
|
|
highlight_match = false;
|
|
|
|
// by default search all lines
|
|
search_first_line = 0;
|
|
search_last_line = MAXLNUM;
|
|
|
|
p_magic = s->magic_save;
|
|
|
|
validate_cursor(); // needed for TAB
|
|
redraw_all_later(SOME_VALID);
|
|
if (call_update_screen) {
|
|
update_screen(SOME_VALID);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Internal entry point for cmdline mode.
|
|
///
|
|
/// caller must use save_cmdline and restore_cmdline. Best is to use
|
|
/// getcmdline or getcmdline_prompt, instead of calling this directly.
|
|
static uint8_t *command_line_enter(int firstc, long count, int indent)
|
|
{
|
|
// can be invoked recursively, identify each level
|
|
static int cmdline_level = 0;
|
|
cmdline_level++;
|
|
|
|
CommandLineState state = {
|
|
.firstc = firstc,
|
|
.count = count,
|
|
.indent = indent,
|
|
.save_msg_scroll = msg_scroll,
|
|
.save_State = State,
|
|
.ignore_drag_release = true,
|
|
};
|
|
CommandLineState *s = &state;
|
|
s->save_p_icm = vim_strsave(p_icm);
|
|
init_incsearch_state(&s->is_state);
|
|
|
|
if (s->firstc == -1) {
|
|
s->firstc = NUL;
|
|
s->break_ctrl_c = true;
|
|
}
|
|
|
|
// start without Hebrew mapping for a command line
|
|
if (s->firstc == ':' || s->firstc == '=' || s->firstc == '>') {
|
|
cmd_hkmap = 0;
|
|
}
|
|
|
|
ccline.prompt_id = last_prompt_id++;
|
|
ccline.level = cmdline_level;
|
|
ccline.overstrike = false; // always start in insert mode
|
|
|
|
assert(indent >= 0);
|
|
|
|
// set some variables for redrawcmd()
|
|
ccline.cmdfirstc = (s->firstc == '@' ? 0 : s->firstc);
|
|
ccline.cmdindent = (s->firstc > 0 ? s->indent : 0);
|
|
|
|
// alloc initial ccline.cmdbuff
|
|
alloc_cmdbuff(exmode_active ? 250 : s->indent + 1);
|
|
ccline.cmdlen = ccline.cmdpos = 0;
|
|
ccline.cmdbuff[0] = NUL;
|
|
|
|
ccline.last_colors = (ColoredCmdline){ .cmdbuff = NULL,
|
|
.colors = KV_INITIAL_VALUE };
|
|
sb_text_start_cmdline();
|
|
|
|
// autoindent for :insert and :append
|
|
if (s->firstc <= 0) {
|
|
memset(ccline.cmdbuff, ' ', (size_t)s->indent);
|
|
ccline.cmdbuff[s->indent] = NUL;
|
|
ccline.cmdpos = s->indent;
|
|
ccline.cmdspos = s->indent;
|
|
ccline.cmdlen = s->indent;
|
|
}
|
|
|
|
ExpandInit(&s->xpc);
|
|
ccline.xpc = &s->xpc;
|
|
|
|
if (curwin->w_p_rl && *curwin->w_p_rlc == 's'
|
|
&& (s->firstc == '/' || s->firstc == '?')) {
|
|
cmdmsg_rl = true;
|
|
} else {
|
|
cmdmsg_rl = false;
|
|
}
|
|
|
|
msg_grid_validate();
|
|
|
|
redir_off = true; // don't redirect the typed command
|
|
if (!cmd_silent) {
|
|
gotocmdline(true);
|
|
redrawcmdprompt(); // draw prompt or indent
|
|
ccline.cmdspos = cmd_startcol();
|
|
if (!msg_scroll) {
|
|
msg_ext_clear(false);
|
|
}
|
|
}
|
|
s->xpc.xp_context = EXPAND_NOTHING;
|
|
s->xpc.xp_backslash = XP_BS_NONE;
|
|
#ifndef BACKSLASH_IN_FILENAME
|
|
s->xpc.xp_shell = false;
|
|
#endif
|
|
|
|
if (ccline.input_fn) {
|
|
s->xpc.xp_context = ccline.xp_context;
|
|
s->xpc.xp_pattern = ccline.cmdbuff;
|
|
s->xpc.xp_arg = ccline.xp_arg;
|
|
}
|
|
|
|
// Avoid scrolling when called by a recursive do_cmdline(), e.g. when
|
|
// doing ":@0" when register 0 doesn't contain a CR.
|
|
msg_scroll = false;
|
|
|
|
State = CMDLINE;
|
|
|
|
if (s->firstc == '/' || s->firstc == '?' || s->firstc == '@') {
|
|
// Use ":lmap" mappings for search pattern and input().
|
|
if (curbuf->b_p_imsearch == B_IMODE_USE_INSERT) {
|
|
s->b_im_ptr = &curbuf->b_p_iminsert;
|
|
} else {
|
|
s->b_im_ptr = &curbuf->b_p_imsearch;
|
|
}
|
|
|
|
if (*s->b_im_ptr == B_IMODE_LMAP) {
|
|
State |= LANGMAP;
|
|
}
|
|
}
|
|
|
|
setmouse();
|
|
ui_cursor_shape(); // may show different cursor shape
|
|
|
|
init_history();
|
|
s->hiscnt = hislen; // set hiscnt to impossible history value
|
|
s->histype = hist_char2type(s->firstc);
|
|
do_digraph(-1); // init digraph typeahead
|
|
|
|
// If something above caused an error, reset the flags, we do want to type
|
|
// and execute commands. Display may be messed up a bit.
|
|
if (did_emsg) {
|
|
redrawcmd();
|
|
}
|
|
|
|
// Redraw the statusline in case it uses the current mode using the mode()
|
|
// function.
|
|
if (!cmd_silent && msg_scrolled == 0) {
|
|
bool found_one = false;
|
|
|
|
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
|
if (*p_stl != NUL || *wp->w_p_stl != NUL) {
|
|
wp->w_redr_status = true;
|
|
found_one = true;
|
|
}
|
|
}
|
|
if (found_one) {
|
|
redraw_statuslines();
|
|
}
|
|
}
|
|
|
|
did_emsg = false;
|
|
got_int = false;
|
|
s->state.check = command_line_check;
|
|
s->state.execute = command_line_execute;
|
|
|
|
TryState tstate;
|
|
Error err = ERROR_INIT;
|
|
bool tl_ret = true;
|
|
save_v_event_T save_v_event;
|
|
dict_T *dict = get_v_event(&save_v_event);
|
|
char firstcbuf[2];
|
|
firstcbuf[0] = (char)(firstc > 0 ? firstc : '-');
|
|
firstcbuf[1] = 0;
|
|
|
|
if (has_event(EVENT_CMDLINEENTER)) {
|
|
// set v:event to a dictionary with information about the commandline
|
|
tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf);
|
|
tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level);
|
|
tv_dict_set_keys_readonly(dict);
|
|
try_enter(&tstate);
|
|
|
|
apply_autocmds(EVENT_CMDLINEENTER, (char_u *)firstcbuf, (char_u *)firstcbuf,
|
|
false, curbuf);
|
|
restore_v_event(dict, &save_v_event);
|
|
|
|
|
|
tl_ret = try_leave(&tstate, &err);
|
|
if (!tl_ret && ERROR_SET(&err)) {
|
|
msg_putchar('\n');
|
|
msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg);
|
|
api_clear_error(&err);
|
|
redrawcmd();
|
|
}
|
|
tl_ret = true;
|
|
}
|
|
trigger_modechanged();
|
|
|
|
state_enter(&s->state);
|
|
|
|
if (has_event(EVENT_CMDLINELEAVE)) {
|
|
tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf);
|
|
tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level);
|
|
tv_dict_set_keys_readonly(dict);
|
|
// not readonly:
|
|
tv_dict_add_bool(dict, S_LEN("abort"),
|
|
s->gotesc ? kBoolVarTrue : kBoolVarFalse);
|
|
try_enter(&tstate);
|
|
apply_autocmds(EVENT_CMDLINELEAVE, (char_u *)firstcbuf, (char_u *)firstcbuf,
|
|
false, curbuf);
|
|
// error printed below, to avoid redraw issues
|
|
tl_ret = try_leave(&tstate, &err);
|
|
if (tv_dict_get_number(dict, "abort") != 0) {
|
|
s->gotesc = 1;
|
|
}
|
|
restore_v_event(dict, &save_v_event);
|
|
}
|
|
|
|
cmdmsg_rl = false;
|
|
|
|
ExpandCleanup(&s->xpc);
|
|
ccline.xpc = NULL;
|
|
|
|
if (s->gotesc) {
|
|
// There might be a preview window open for inccommand. Close it.
|
|
close_preview_windows();
|
|
}
|
|
|
|
finish_incsearch_highlighting(s->gotesc, &s->is_state, false);
|
|
|
|
if (ccline.cmdbuff != NULL) {
|
|
// Put line in history buffer (":" and "=" only when it was typed).
|
|
if (s->histype != HIST_INVALID
|
|
&& ccline.cmdlen
|
|
&& s->firstc != NUL
|
|
&& (s->some_key_typed || s->histype == HIST_SEARCH)) {
|
|
add_to_history(s->histype, ccline.cmdbuff, true,
|
|
s->histype == HIST_SEARCH ? s->firstc : NUL);
|
|
if (s->firstc == ':') {
|
|
xfree(new_last_cmdline);
|
|
new_last_cmdline = vim_strsave(ccline.cmdbuff);
|
|
}
|
|
}
|
|
|
|
if (s->gotesc) {
|
|
abandon_cmdline();
|
|
}
|
|
}
|
|
|
|
// If the screen was shifted up, redraw the whole screen (later).
|
|
// If the line is too long, clear it, so ruler and shown command do
|
|
// not get printed in the middle of it.
|
|
msg_check();
|
|
msg_scroll = s->save_msg_scroll;
|
|
redir_off = false;
|
|
|
|
if (!tl_ret && ERROR_SET(&err)) {
|
|
msg_putchar('\n');
|
|
msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg);
|
|
api_clear_error(&err);
|
|
}
|
|
|
|
// When the command line was typed, no need for a wait-return prompt.
|
|
if (s->some_key_typed && tl_ret) {
|
|
need_wait_return = false;
|
|
}
|
|
|
|
set_string_option_direct("icm", -1, s->save_p_icm, OPT_FREE,
|
|
SID_NONE);
|
|
State = s->save_State;
|
|
setmouse();
|
|
ui_cursor_shape(); // may show different cursor shape
|
|
xfree(s->save_p_icm);
|
|
xfree(ccline.last_colors.cmdbuff);
|
|
kv_destroy(ccline.last_colors.colors);
|
|
|
|
sb_text_end_cmdline();
|
|
|
|
char_u *p = ccline.cmdbuff;
|
|
|
|
if (ui_has(kUICmdline)) {
|
|
ui_call_cmdline_hide(ccline.level);
|
|
msg_ext_clear_later();
|
|
}
|
|
|
|
cmdline_level--;
|
|
return p;
|
|
}
|
|
|
|
static int command_line_check(VimState *state)
|
|
{
|
|
redir_off = true; // Don't redirect the typed command.
|
|
// Repeated, because a ":redir" inside
|
|
// completion may switch it on.
|
|
quit_more = false; // reset after CTRL-D which had a more-prompt
|
|
|
|
did_emsg = false; // There can't really be a reason why an error
|
|
// that occurs while typing a command should
|
|
// cause the command not to be executed.
|
|
|
|
cursorcmd(); // set the cursor on the right spot
|
|
ui_cursor_shape();
|
|
return 1;
|
|
}
|
|
|
|
static int command_line_execute(VimState *state, int key)
|
|
{
|
|
if (key == K_IGNORE || key == K_NOP) {
|
|
return -1; // get another key
|
|
}
|
|
|
|
CommandLineState *s = (CommandLineState *)state;
|
|
s->c = key;
|
|
|
|
if (s->c == K_EVENT || s->c == K_COMMAND) {
|
|
if (s->c == K_EVENT) {
|
|
state_handle_k_event();
|
|
} else {
|
|
do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT);
|
|
}
|
|
|
|
if (!cmdline_was_last_drawn) {
|
|
redrawcmdline();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if (KeyTyped) {
|
|
s->some_key_typed = true;
|
|
if (cmd_hkmap) {
|
|
s->c = hkmap(s->c);
|
|
}
|
|
|
|
if (cmdmsg_rl && !KeyStuffed) {
|
|
// Invert horizontal movements and operations. Only when
|
|
// typed by the user directly, not when the result of a
|
|
// mapping.
|
|
switch (s->c) {
|
|
case K_RIGHT:
|
|
s->c = K_LEFT; break;
|
|
case K_S_RIGHT:
|
|
s->c = K_S_LEFT; break;
|
|
case K_C_RIGHT:
|
|
s->c = K_C_LEFT; break;
|
|
case K_LEFT:
|
|
s->c = K_RIGHT; break;
|
|
case K_S_LEFT:
|
|
s->c = K_S_RIGHT; break;
|
|
case K_C_LEFT:
|
|
s->c = K_C_RIGHT; break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ignore got_int when CTRL-C was typed here.
|
|
// Don't ignore it in :global, we really need to break then, e.g., for
|
|
// ":g/pat/normal /pat" (without the <CR>).
|
|
// Don't ignore it for the input() function.
|
|
if ((s->c == Ctrl_C)
|
|
&& s->firstc != '@'
|
|
&& !s->break_ctrl_c
|
|
&& !global_busy) {
|
|
got_int = false;
|
|
}
|
|
|
|
// free old command line when finished moving around in the history
|
|
// list
|
|
if (s->lookfor != NULL
|
|
&& s->c != K_S_DOWN && s->c != K_S_UP
|
|
&& s->c != K_DOWN && s->c != K_UP
|
|
&& s->c != K_PAGEDOWN && s->c != K_PAGEUP
|
|
&& s->c != K_KPAGEDOWN && s->c != K_KPAGEUP
|
|
&& s->c != K_LEFT && s->c != K_RIGHT
|
|
&& (s->xpc.xp_numfiles > 0 || (s->c != Ctrl_P && s->c != Ctrl_N))) {
|
|
XFREE_CLEAR(s->lookfor);
|
|
}
|
|
|
|
// When there are matching completions to select <S-Tab> works like
|
|
// CTRL-P (unless 'wc' is <S-Tab>).
|
|
if (s->c != p_wc && s->c == K_S_TAB && s->xpc.xp_numfiles > 0) {
|
|
s->c = Ctrl_P;
|
|
}
|
|
|
|
// Special translations for 'wildmenu'
|
|
if (s->did_wild_list && p_wmnu) {
|
|
if (s->c == K_LEFT) {
|
|
s->c = Ctrl_P;
|
|
} else if (s->c == K_RIGHT) {
|
|
s->c = Ctrl_N;
|
|
}
|
|
}
|
|
if (compl_match_array || s->did_wild_list) {
|
|
if (s->c == Ctrl_E) {
|
|
s->res = nextwild(&s->xpc, WILD_CANCEL, WILD_NO_BEEP,
|
|
s->firstc != '@');
|
|
} else if (s->c == Ctrl_Y) {
|
|
s->res = nextwild(&s->xpc, WILD_APPLY, WILD_NO_BEEP,
|
|
s->firstc != '@');
|
|
s->c = Ctrl_E;
|
|
}
|
|
}
|
|
|
|
// Hitting CR after "emenu Name.": complete submenu
|
|
if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu
|
|
&& ccline.cmdpos > 1
|
|
&& ccline.cmdbuff[ccline.cmdpos - 1] == '.'
|
|
&& ccline.cmdbuff[ccline.cmdpos - 2] != '\\'
|
|
&& (s->c == '\n' || s->c == '\r' || s->c == K_KENTER)) {
|
|
s->c = K_DOWN;
|
|
}
|
|
|
|
// free expanded names when finished walking through matches
|
|
if (!(s->c == p_wc && KeyTyped) && s->c != p_wcm && s->c != Ctrl_Z
|
|
&& s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A
|
|
&& s->c != Ctrl_L) {
|
|
if (compl_match_array) {
|
|
pum_undisplay(true);
|
|
XFREE_CLEAR(compl_match_array);
|
|
}
|
|
if (s->xpc.xp_numfiles != -1) {
|
|
(void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE);
|
|
}
|
|
s->did_wild_list = false;
|
|
if (!p_wmnu || (s->c != K_UP && s->c != K_DOWN)) {
|
|
s->xpc.xp_context = EXPAND_NOTHING;
|
|
}
|
|
s->wim_index = 0;
|
|
if (p_wmnu && wild_menu_showing != 0) {
|
|
const bool skt = KeyTyped;
|
|
int old_RedrawingDisabled = RedrawingDisabled;
|
|
|
|
if (ccline.input_fn) {
|
|
RedrawingDisabled = 0;
|
|
}
|
|
|
|
if (wild_menu_showing == WM_SCROLLED) {
|
|
// Entered command line, move it up
|
|
cmdline_row--;
|
|
redrawcmd();
|
|
wild_menu_showing = 0;
|
|
} else if (save_p_ls != -1) {
|
|
// restore 'laststatus' and 'winminheight'
|
|
p_ls = save_p_ls;
|
|
p_wmh = save_p_wmh;
|
|
last_status(false);
|
|
update_screen(VALID); // redraw the screen NOW
|
|
redrawcmd();
|
|
save_p_ls = -1;
|
|
wild_menu_showing = 0;
|
|
// don't redraw statusline if WM_LIST is showing
|
|
} else if (wild_menu_showing != WM_LIST) {
|
|
win_redraw_last_status(topframe);
|
|
wild_menu_showing = 0; // must be before redraw_statuslines #8385
|
|
redraw_statuslines();
|
|
} else {
|
|
wild_menu_showing = 0;
|
|
}
|
|
KeyTyped = skt;
|
|
if (ccline.input_fn) {
|
|
RedrawingDisabled = old_RedrawingDisabled;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Special translations for 'wildmenu'
|
|
if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu) {
|
|
// Hitting <Down> after "emenu Name.": complete submenu
|
|
if (s->c == K_DOWN && ccline.cmdpos > 0
|
|
&& ccline.cmdbuff[ccline.cmdpos - 1] == '.') {
|
|
s->c = (int)p_wc;
|
|
KeyTyped = true; // in case the key was mapped
|
|
} else if (s->c == K_UP) {
|
|
// Hitting <Up>: Remove one submenu name in front of the
|
|
// cursor
|
|
int found = false;
|
|
|
|
int j = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
|
|
int i = 0;
|
|
while (--j > 0) {
|
|
// check for start of menu name
|
|
if (ccline.cmdbuff[j] == ' '
|
|
&& ccline.cmdbuff[j - 1] != '\\') {
|
|
i = j + 1;
|
|
break;
|
|
}
|
|
|
|
// check for start of submenu name
|
|
if (ccline.cmdbuff[j] == '.'
|
|
&& ccline.cmdbuff[j - 1] != '\\') {
|
|
if (found) {
|
|
i = j + 1;
|
|
break;
|
|
} else {
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
if (i > 0) {
|
|
cmdline_del(i);
|
|
}
|
|
s->c = (int)p_wc;
|
|
KeyTyped = true; // in case the key was mapped
|
|
s->xpc.xp_context = EXPAND_NOTHING;
|
|
}
|
|
}
|
|
if ((s->xpc.xp_context == EXPAND_FILES
|
|
|| s->xpc.xp_context == EXPAND_DIRECTORIES
|
|
|| s->xpc.xp_context == EXPAND_SHELLCMD) && p_wmnu) {
|
|
char_u upseg[5];
|
|
|
|
upseg[0] = PATHSEP;
|
|
upseg[1] = '.';
|
|
upseg[2] = '.';
|
|
upseg[3] = PATHSEP;
|
|
upseg[4] = NUL;
|
|
|
|
if (s->c == K_DOWN
|
|
&& ccline.cmdpos > 0
|
|
&& ccline.cmdbuff[ccline.cmdpos - 1] == PATHSEP
|
|
&& (ccline.cmdpos < 3
|
|
|| ccline.cmdbuff[ccline.cmdpos - 2] != '.'
|
|
|| ccline.cmdbuff[ccline.cmdpos - 3] != '.')) {
|
|
// go down a directory
|
|
s->c = (int)p_wc;
|
|
KeyTyped = true; // in case the key was mapped
|
|
} else if (STRNCMP(s->xpc.xp_pattern, upseg + 1, 3) == 0
|
|
&& s->c == K_DOWN) {
|
|
// If in a direct ancestor, strip off one ../ to go down
|
|
int found = false;
|
|
|
|
int j = ccline.cmdpos;
|
|
int i = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
|
|
while (--j > i) {
|
|
j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + j);
|
|
if (vim_ispathsep(ccline.cmdbuff[j])) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found
|
|
&& ccline.cmdbuff[j - 1] == '.'
|
|
&& ccline.cmdbuff[j - 2] == '.'
|
|
&& (vim_ispathsep(ccline.cmdbuff[j - 3]) || j == i + 2)) {
|
|
cmdline_del(j - 2);
|
|
s->c = (int)p_wc;
|
|
KeyTyped = true; // in case the key was mapped
|
|
}
|
|
} else if (s->c == K_UP) {
|
|
// go up a directory
|
|
int found = false;
|
|
|
|
int j = ccline.cmdpos - 1;
|
|
int i = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
|
|
while (--j > i) {
|
|
j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + j);
|
|
if (vim_ispathsep(ccline.cmdbuff[j])
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
&& vim_strchr((const char_u *)" *?[{`$%#", ccline.cmdbuff[j + 1])
|
|
== NULL
|
|
#endif
|
|
) {
|
|
if (found) {
|
|
i = j + 1;
|
|
break;
|
|
} else {
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
j = i;
|
|
} else if (STRNCMP(ccline.cmdbuff + j, upseg, 4) == 0) {
|
|
j += 4;
|
|
} else if (STRNCMP(ccline.cmdbuff + j, upseg + 1, 3) == 0
|
|
&& j == i) {
|
|
j += 3;
|
|
} else {
|
|
j = 0;
|
|
}
|
|
|
|
if (j > 0) {
|
|
// TODO(tarruda): this is only for DOS/Unix systems - need to put in
|
|
// machine-specific stuff here and in upseg init
|
|
cmdline_del(j);
|
|
put_on_cmdline(upseg + 1, 3, false);
|
|
} else if (ccline.cmdpos > i) {
|
|
cmdline_del(i);
|
|
}
|
|
|
|
// Now complete in the new directory. Set KeyTyped in case the
|
|
// Up key came from a mapping.
|
|
s->c = (int)p_wc;
|
|
KeyTyped = true;
|
|
}
|
|
}
|
|
|
|
// CTRL-\ CTRL-N goes to Normal mode, CTRL-\ CTRL-G goes to Insert
|
|
// mode when 'insertmode' is set, CTRL-\ e prompts for an expression.
|
|
if (s->c == Ctrl_BSL) {
|
|
no_mapping++;
|
|
s->c = plain_vgetc();
|
|
no_mapping--;
|
|
// CTRL-\ e doesn't work when obtaining an expression, unless it
|
|
// is in a mapping.
|
|
if (s->c != Ctrl_N
|
|
&& s->c != Ctrl_G
|
|
&& (s->c != 'e'
|
|
|| (ccline.cmdfirstc == '=' && KeyTyped)
|
|
|| cmdline_star > 0)) {
|
|
vungetc(s->c);
|
|
s->c = Ctrl_BSL;
|
|
} else if (s->c == 'e') {
|
|
char_u *p = NULL;
|
|
int len;
|
|
|
|
// Replace the command line with the result of an expression.
|
|
// Need to save and restore the current command line, to be
|
|
// able to enter a new one...
|
|
if (ccline.cmdpos == ccline.cmdlen) {
|
|
new_cmdpos = 99999; // keep it at the end
|
|
} else {
|
|
new_cmdpos = ccline.cmdpos;
|
|
}
|
|
|
|
s->c = get_expr_register();
|
|
if (s->c == '=') {
|
|
// Need to save and restore ccline. And set "textlock"
|
|
// to avoid nasty things like going to another buffer when
|
|
// evaluating an expression.
|
|
CmdlineInfo save_ccline;
|
|
save_cmdline(&save_ccline);
|
|
textlock++;
|
|
p = get_expr_line();
|
|
textlock--;
|
|
restore_cmdline(&save_ccline);
|
|
|
|
if (p != NULL) {
|
|
len = (int)STRLEN(p);
|
|
realloc_cmdbuff(len + 1);
|
|
ccline.cmdlen = len;
|
|
STRCPY(ccline.cmdbuff, p);
|
|
xfree(p);
|
|
|
|
// Restore the cursor or use the position set with
|
|
// set_cmdline_pos().
|
|
if (new_cmdpos > ccline.cmdlen) {
|
|
ccline.cmdpos = ccline.cmdlen;
|
|
} else {
|
|
ccline.cmdpos = new_cmdpos;
|
|
}
|
|
|
|
KeyTyped = false; // Don't do p_wc completion.
|
|
redrawcmd();
|
|
return command_line_changed(s);
|
|
}
|
|
}
|
|
beep_flush();
|
|
got_int = false; // don't abandon the command line
|
|
did_emsg = false;
|
|
emsg_on_display = false;
|
|
redrawcmd();
|
|
return command_line_not_changed(s);
|
|
} else {
|
|
if (s->c == Ctrl_G && p_im && restart_edit == 0) {
|
|
restart_edit = 'a';
|
|
}
|
|
s->gotesc = true; // will free ccline.cmdbuff after putting it
|
|
// in history
|
|
return 0; // back to Normal mode
|
|
}
|
|
}
|
|
|
|
if (s->c == cedit_key || s->c == K_CMDWIN) {
|
|
// TODO(vim): why is ex_normal_busy checked here?
|
|
if ((s->c == K_CMDWIN || ex_normal_busy == 0)
|
|
&& got_int == false) {
|
|
// Open a window to edit the command line (and history).
|
|
s->c = open_cmdwin();
|
|
s->some_key_typed = true;
|
|
}
|
|
} else {
|
|
s->c = do_digraph(s->c);
|
|
}
|
|
|
|
if (s->c == '\n'
|
|
|| s->c == '\r'
|
|
|| s->c == K_KENTER
|
|
|| (s->c == ESC
|
|
&& (!KeyTyped || vim_strchr(p_cpo, CPO_ESC) != NULL))) {
|
|
// In Ex mode a backslash escapes a newline.
|
|
if (exmode_active
|
|
&& s->c != ESC
|
|
&& ccline.cmdpos == ccline.cmdlen
|
|
&& ccline.cmdpos > 0
|
|
&& ccline.cmdbuff[ccline.cmdpos - 1] == '\\') {
|
|
if (s->c == K_KENTER) {
|
|
s->c = '\n';
|
|
}
|
|
} else {
|
|
s->gotesc = false; // Might have typed ESC previously, don't
|
|
// truncate the cmdline now.
|
|
if (ccheck_abbr(s->c + ABBR_OFF)) {
|
|
return command_line_changed(s);
|
|
}
|
|
|
|
if (!cmd_silent) {
|
|
if (!ui_has(kUICmdline)) {
|
|
cmd_cursor_goto(msg_row, 0);
|
|
}
|
|
ui_flush();
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Completion for 'wildchar' or 'wildcharm' key.
|
|
// - hitting <ESC> twice means: abandon command line.
|
|
// - wildcard expansion is only done when the 'wildchar' key is really
|
|
// typed, not when it comes from a macro
|
|
if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm
|
|
|| s->c == Ctrl_Z) {
|
|
int options = WILD_NO_BEEP;
|
|
if (wim_flags[s->wim_index] & WIM_BUFLASTUSED) {
|
|
options |= WILD_BUFLASTUSED;
|
|
}
|
|
if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice
|
|
// if 'wildmode' contains "list" may still need to list
|
|
if (s->xpc.xp_numfiles > 1
|
|
&& !s->did_wild_list
|
|
&& ((wim_flags[s->wim_index] & WIM_LIST)
|
|
|| (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0))) {
|
|
(void)showmatches(&s->xpc, p_wmnu
|
|
&& ((wim_flags[s->wim_index] & WIM_LIST) == 0));
|
|
redrawcmd();
|
|
s->did_wild_list = true;
|
|
}
|
|
|
|
if (wim_flags[s->wim_index] & WIM_LONGEST) {
|
|
s->res = nextwild(&s->xpc, WILD_LONGEST, options,
|
|
s->firstc != '@');
|
|
} else if (wim_flags[s->wim_index] & WIM_FULL) {
|
|
s->res = nextwild(&s->xpc, WILD_NEXT, options,
|
|
s->firstc != '@');
|
|
} else {
|
|
s->res = OK; // don't insert 'wildchar' now
|
|
}
|
|
} else { // typed p_wc first time
|
|
s->wim_index = 0;
|
|
int j = ccline.cmdpos;
|
|
|
|
// if 'wildmode' first contains "longest", get longest
|
|
// common part
|
|
if (wim_flags[0] & WIM_LONGEST) {
|
|
s->res = nextwild(&s->xpc, WILD_LONGEST, options,
|
|
s->firstc != '@');
|
|
} else {
|
|
s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options,
|
|
s->firstc != '@');
|
|
}
|
|
|
|
// if interrupted while completing, behave like it failed
|
|
if (got_int) {
|
|
(void)vpeekc(); // remove <C-C> from input stream
|
|
got_int = false; // don't abandon the command line
|
|
(void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE);
|
|
s->xpc.xp_context = EXPAND_NOTHING;
|
|
return command_line_changed(s);
|
|
}
|
|
|
|
// when more than one match, and 'wildmode' first contains
|
|
// "list", or no change and 'wildmode' contains "longest,list",
|
|
// list all matches
|
|
if (s->res == OK && s->xpc.xp_numfiles > 1) {
|
|
// a "longest" that didn't do anything is skipped (but not
|
|
// "list:longest")
|
|
if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j) {
|
|
s->wim_index = 1;
|
|
}
|
|
if ((wim_flags[s->wim_index] & WIM_LIST)
|
|
|| (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0)) {
|
|
if (!(wim_flags[0] & WIM_LONGEST)) {
|
|
int p_wmnu_save = p_wmnu;
|
|
p_wmnu = 0;
|
|
// remove match
|
|
nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@');
|
|
p_wmnu = p_wmnu_save;
|
|
}
|
|
|
|
(void)showmatches(&s->xpc, p_wmnu
|
|
&& ((wim_flags[s->wim_index] & WIM_LIST) == 0));
|
|
redrawcmd();
|
|
s->did_wild_list = true;
|
|
|
|
if (wim_flags[s->wim_index] & WIM_LONGEST) {
|
|
nextwild(&s->xpc, WILD_LONGEST, options,
|
|
s->firstc != '@');
|
|
} else if (wim_flags[s->wim_index] & WIM_FULL) {
|
|
nextwild(&s->xpc, WILD_NEXT, options,
|
|
s->firstc != '@');
|
|
}
|
|
} else {
|
|
vim_beep(BO_WILD);
|
|
}
|
|
} else if (s->xpc.xp_numfiles == -1) {
|
|
s->xpc.xp_context = EXPAND_NOTHING;
|
|
}
|
|
}
|
|
|
|
if (s->wim_index < 3) {
|
|
++s->wim_index;
|
|
}
|
|
|
|
if (s->c == ESC) {
|
|
s->gotesc = true;
|
|
}
|
|
|
|
if (s->res == OK) {
|
|
return command_line_changed(s);
|
|
}
|
|
}
|
|
|
|
s->gotesc = false;
|
|
|
|
// <S-Tab> goes to last match, in a clumsy way
|
|
if (s->c == K_S_TAB && KeyTyped) {
|
|
if (nextwild(&s->xpc, WILD_EXPAND_KEEP, 0, s->firstc != '@') == OK) {
|
|
showmatches(&s->xpc, p_wmnu
|
|
&& ((wim_flags[s->wim_index] & WIM_LIST) == 0));
|
|
nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@');
|
|
nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@');
|
|
return command_line_changed(s);
|
|
}
|
|
}
|
|
|
|
if (s->c == NUL || s->c == K_ZERO) {
|
|
// NUL is stored as NL
|
|
s->c = NL;
|
|
}
|
|
|
|
s->do_abbr = true; // default: check for abbreviation
|
|
return command_line_handle_key(s);
|
|
}
|
|
|
|
// May adjust 'incsearch' highlighting for typing CTRL-G and CTRL-T, go to next
|
|
// or previous match.
|
|
// Returns FAIL when calling command_line_not_changed.
|
|
static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_state_T *s,
|
|
bool next_match)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
int skiplen, patlen, search_delim;
|
|
|
|
// Parsing range may already set the last search pattern.
|
|
// NOTE: must call restore_last_search_pattern() before returning!
|
|
save_last_search_pattern();
|
|
|
|
if (!do_incsearch_highlighting(firstc, &search_delim, s, &skiplen,
|
|
&patlen)) {
|
|
restore_last_search_pattern();
|
|
return OK;
|
|
}
|
|
if (patlen == 0 && ccline.cmdbuff[skiplen] == NUL) {
|
|
restore_last_search_pattern();
|
|
return FAIL;
|
|
}
|
|
|
|
ui_busy_start();
|
|
ui_flush();
|
|
|
|
pos_T t;
|
|
char_u *pat;
|
|
int search_flags = SEARCH_NOOF;
|
|
char_u save;
|
|
|
|
|
|
if (search_delim == ccline.cmdbuff[skiplen]) {
|
|
pat = last_search_pattern();
|
|
skiplen = 0;
|
|
patlen = (int)STRLEN(pat);
|
|
} else {
|
|
pat = ccline.cmdbuff + skiplen;
|
|
}
|
|
|
|
if (next_match) {
|
|
t = s->match_end;
|
|
if (lt(s->match_start, s->match_end)) {
|
|
// start searching at the end of the match
|
|
// not at the beginning of the next column
|
|
(void)decl(&t);
|
|
}
|
|
search_flags += SEARCH_COL;
|
|
} else {
|
|
t = s->match_start;
|
|
}
|
|
if (!p_hls) {
|
|
search_flags += SEARCH_KEEP;
|
|
}
|
|
emsg_off++;
|
|
save = pat[patlen];
|
|
pat[patlen] = NUL;
|
|
int found = searchit(curwin, curbuf, &t, NULL,
|
|
next_match ? FORWARD : BACKWARD,
|
|
pat, count, search_flags,
|
|
RE_SEARCH, NULL);
|
|
emsg_off--;
|
|
pat[patlen] = save;
|
|
ui_busy_stop();
|
|
if (found) {
|
|
s->search_start = s->match_start;
|
|
s->match_end = t;
|
|
s->match_start = t;
|
|
if (!next_match && firstc != '?') {
|
|
// move just before the current match, so that
|
|
// when nv_search finishes the cursor will be
|
|
// put back on the match
|
|
s->search_start = t;
|
|
(void)decl(&s->search_start);
|
|
} else if (next_match && firstc == '?') {
|
|
// move just after the current match, so that
|
|
// when nv_search finishes the cursor will be
|
|
// put back on the match
|
|
s->search_start = t;
|
|
(void)incl(&s->search_start);
|
|
}
|
|
if (lt(t, s->search_start) && next_match) {
|
|
// wrap around
|
|
s->search_start = t;
|
|
if (firstc == '?') {
|
|
(void)incl(&s->search_start);
|
|
} else {
|
|
(void)decl(&s->search_start);
|
|
}
|
|
}
|
|
|
|
set_search_match(&s->match_end);
|
|
curwin->w_cursor = s->match_start;
|
|
changed_cline_bef_curs();
|
|
update_topline(curwin);
|
|
validate_cursor();
|
|
highlight_match = true;
|
|
save_viewstate(&s->old_viewstate);
|
|
update_screen(NOT_VALID);
|
|
highlight_match = false;
|
|
redrawcmdline();
|
|
curwin->w_cursor = s->match_end;
|
|
} else {
|
|
vim_beep(BO_ERROR);
|
|
}
|
|
restore_last_search_pattern();
|
|
return FAIL;
|
|
}
|
|
|
|
static void command_line_next_histidx(CommandLineState *s, bool next_match)
|
|
{
|
|
int j = (int)STRLEN(s->lookfor);
|
|
for (;; ) {
|
|
// one step backwards
|
|
if (!next_match) {
|
|
if (s->hiscnt == hislen) {
|
|
// first time
|
|
s->hiscnt = hisidx[s->histype];
|
|
} else if (s->hiscnt == 0 && hisidx[s->histype] != hislen - 1) {
|
|
s->hiscnt = hislen - 1;
|
|
} else if (s->hiscnt != hisidx[s->histype] + 1) {
|
|
s->hiscnt--;
|
|
} else {
|
|
// at top of list
|
|
s->hiscnt = s->save_hiscnt;
|
|
break;
|
|
}
|
|
} else { // one step forwards
|
|
// on last entry, clear the line
|
|
if (s->hiscnt == hisidx[s->histype]) {
|
|
s->hiscnt = hislen;
|
|
break;
|
|
}
|
|
|
|
// not on a history line, nothing to do
|
|
if (s->hiscnt == hislen) {
|
|
break;
|
|
}
|
|
|
|
if (s->hiscnt == hislen - 1) {
|
|
// wrap around
|
|
s->hiscnt = 0;
|
|
} else {
|
|
s->hiscnt++;
|
|
}
|
|
}
|
|
|
|
if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) {
|
|
s->hiscnt = s->save_hiscnt;
|
|
break;
|
|
}
|
|
|
|
if ((s->c != K_UP && s->c != K_DOWN)
|
|
|| s->hiscnt == s->save_hiscnt
|
|
|| STRNCMP(history[s->histype][s->hiscnt].hisstr,
|
|
s->lookfor, (size_t)j) == 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int command_line_handle_key(CommandLineState *s)
|
|
{
|
|
// Big switch for a typed command line character.
|
|
switch (s->c) {
|
|
case K_BS:
|
|
case Ctrl_H:
|
|
case K_DEL:
|
|
case K_KDEL:
|
|
case Ctrl_W:
|
|
if (s->c == K_KDEL) {
|
|
s->c = K_DEL;
|
|
}
|
|
|
|
// delete current character is the same as backspace on next
|
|
// character, except at end of line
|
|
if (s->c == K_DEL && ccline.cmdpos != ccline.cmdlen) {
|
|
++ccline.cmdpos;
|
|
}
|
|
|
|
if (s->c == K_DEL) {
|
|
ccline.cmdpos += mb_off_next(ccline.cmdbuff,
|
|
ccline.cmdbuff + ccline.cmdpos);
|
|
}
|
|
|
|
if (ccline.cmdpos > 0) {
|
|
char_u *p;
|
|
|
|
int j = ccline.cmdpos;
|
|
p = mb_prevptr(ccline.cmdbuff, ccline.cmdbuff + j);
|
|
|
|
if (s->c == Ctrl_W) {
|
|
while (p > ccline.cmdbuff && ascii_isspace(*p)) {
|
|
p = mb_prevptr(ccline.cmdbuff, p);
|
|
}
|
|
|
|
int i = mb_get_class(p);
|
|
while (p > ccline.cmdbuff && mb_get_class(p) == i) {
|
|
p = mb_prevptr(ccline.cmdbuff, p);
|
|
}
|
|
|
|
if (mb_get_class(p) != i) {
|
|
p += utfc_ptr2len(p);
|
|
}
|
|
}
|
|
|
|
ccline.cmdpos = (int)(p - ccline.cmdbuff);
|
|
ccline.cmdlen -= j - ccline.cmdpos;
|
|
int i = ccline.cmdpos;
|
|
|
|
while (i < ccline.cmdlen) {
|
|
ccline.cmdbuff[i++] = ccline.cmdbuff[j++];
|
|
}
|
|
|
|
// Truncate at the end, required for multi-byte chars.
|
|
ccline.cmdbuff[ccline.cmdlen] = NUL;
|
|
if (ccline.cmdlen == 0) {
|
|
s->is_state.search_start = s->is_state.save_cursor;
|
|
// save view settings, so that the screen won't be restored at the
|
|
// wrong position
|
|
s->is_state.old_viewstate = s->is_state.init_viewstate;
|
|
}
|
|
redrawcmd();
|
|
} else if (ccline.cmdlen == 0 && s->c != Ctrl_W
|
|
&& ccline.cmdprompt == NULL && s->indent == 0) {
|
|
// In ex and debug mode it doesn't make sense to return.
|
|
if (exmode_active || ccline.cmdfirstc == '>') {
|
|
return command_line_not_changed(s);
|
|
}
|
|
|
|
XFREE_CLEAR(ccline.cmdbuff); // no commandline to return
|
|
if (!cmd_silent && !ui_has(kUICmdline)) {
|
|
if (cmdmsg_rl) {
|
|
msg_col = Columns;
|
|
} else {
|
|
msg_col = 0;
|
|
}
|
|
msg_putchar(' '); // delete ':'
|
|
}
|
|
s->is_state.search_start = s->is_state.save_cursor;
|
|
redraw_cmdline = true;
|
|
return 0; // back to cmd mode
|
|
}
|
|
return command_line_changed(s);
|
|
|
|
case K_INS:
|
|
case K_KINS:
|
|
ccline.overstrike = !ccline.overstrike;
|
|
|
|
ui_cursor_shape(); // may show different cursor shape
|
|
return command_line_not_changed(s);
|
|
|
|
case Ctrl_HAT:
|
|
if (map_to_exists_mode("", LANGMAP, false)) {
|
|
// ":lmap" mappings exists, toggle use of mappings.
|
|
State ^= LANGMAP;
|
|
if (s->b_im_ptr != NULL) {
|
|
if (State & LANGMAP) {
|
|
*s->b_im_ptr = B_IMODE_LMAP;
|
|
} else {
|
|
*s->b_im_ptr = B_IMODE_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (s->b_im_ptr != NULL) {
|
|
if (s->b_im_ptr == &curbuf->b_p_iminsert) {
|
|
set_iminsert_global();
|
|
} else {
|
|
set_imsearch_global();
|
|
}
|
|
}
|
|
ui_cursor_shape(); // may show different cursor shape
|
|
// Show/unshow value of 'keymap' in status lines later.
|
|
status_redraw_curbuf();
|
|
return command_line_not_changed(s);
|
|
|
|
case Ctrl_U: {
|
|
// delete all characters left of the cursor
|
|
int j = ccline.cmdpos;
|
|
ccline.cmdlen -= j;
|
|
int i = ccline.cmdpos = 0;
|
|
while (i < ccline.cmdlen) {
|
|
ccline.cmdbuff[i++] = ccline.cmdbuff[j++];
|
|
}
|
|
|
|
// Truncate at the end, required for multi-byte chars.
|
|
ccline.cmdbuff[ccline.cmdlen] = NUL;
|
|
if (ccline.cmdlen == 0) {
|
|
s->is_state.search_start = s->is_state.save_cursor;
|
|
}
|
|
redrawcmd();
|
|
return command_line_changed(s);
|
|
}
|
|
|
|
case ESC: // get here if p_wc != ESC or when ESC typed twice
|
|
case Ctrl_C:
|
|
// In exmode it doesn't make sense to return. Except when
|
|
// ":normal" runs out of characters. Also when highlight callback is active
|
|
// <C-c> should interrupt only it.
|
|
if ((exmode_active && (ex_normal_busy == 0 || typebuf.tb_len > 0))
|
|
|| (getln_interrupted_highlight && s->c == Ctrl_C)) {
|
|
getln_interrupted_highlight = false;
|
|
return command_line_not_changed(s);
|
|
}
|
|
|
|
s->gotesc = true; // will free ccline.cmdbuff after
|
|
// putting it in history
|
|
return 0; // back to cmd mode
|
|
|
|
case Ctrl_R: { // insert register
|
|
putcmdline('"', true);
|
|
no_mapping++;
|
|
int i = s->c = plain_vgetc(); // CTRL-R <char>
|
|
if (i == Ctrl_O) {
|
|
i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R
|
|
}
|
|
|
|
if (i == Ctrl_R) {
|
|
s->c = plain_vgetc(); // CTRL-R CTRL-R <char>
|
|
}
|
|
--no_mapping;
|
|
// Insert the result of an expression.
|
|
// Need to save the current command line, to be able to enter
|
|
// a new one...
|
|
new_cmdpos = -1;
|
|
if (s->c == '=') {
|
|
if (ccline.cmdfirstc == '=' // can't do this recursively
|
|
|| cmdline_star > 0) { // or when typing a password
|
|
beep_flush();
|
|
s->c = ESC;
|
|
} else {
|
|
CmdlineInfo save_ccline;
|
|
save_cmdline(&save_ccline);
|
|
s->c = get_expr_register();
|
|
restore_cmdline(&save_ccline);
|
|
}
|
|
}
|
|
|
|
if (s->c != ESC) { // use ESC to cancel inserting register
|
|
cmdline_paste(s->c, i == Ctrl_R, false);
|
|
|
|
// When there was a serious error abort getting the
|
|
// command line.
|
|
if (aborting()) {
|
|
s->gotesc = true; // will free ccline.cmdbuff after
|
|
// putting it in history
|
|
return 0; // back to cmd mode
|
|
}
|
|
KeyTyped = false; // Don't do p_wc completion.
|
|
if (new_cmdpos >= 0) {
|
|
// set_cmdline_pos() was used
|
|
if (new_cmdpos > ccline.cmdlen) {
|
|
ccline.cmdpos = ccline.cmdlen;
|
|
} else {
|
|
ccline.cmdpos = new_cmdpos;
|
|
}
|
|
}
|
|
}
|
|
ccline.special_char = NUL;
|
|
redrawcmd();
|
|
return command_line_changed(s);
|
|
}
|
|
|
|
case Ctrl_D:
|
|
if (showmatches(&s->xpc, false) == EXPAND_NOTHING) {
|
|
break; // Use ^D as normal char instead
|
|
}
|
|
|
|
wild_menu_showing = WM_LIST;
|
|
redrawcmd();
|
|
return 1; // don't do incremental search now
|
|
|
|
case K_RIGHT:
|
|
case K_S_RIGHT:
|
|
case K_C_RIGHT:
|
|
do {
|
|
if (ccline.cmdpos >= ccline.cmdlen) {
|
|
break;
|
|
}
|
|
|
|
int cells = cmdline_charsize(ccline.cmdpos);
|
|
if (KeyTyped && ccline.cmdspos + cells >= Columns * Rows) {
|
|
break;
|
|
}
|
|
|
|
ccline.cmdspos += cells;
|
|
ccline.cmdpos += utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos);
|
|
} while ((s->c == K_S_RIGHT || s->c == K_C_RIGHT
|
|
|| (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)))
|
|
&& ccline.cmdbuff[ccline.cmdpos] != ' ');
|
|
ccline.cmdspos = cmd_screencol(ccline.cmdpos);
|
|
return command_line_not_changed(s);
|
|
|
|
case K_LEFT:
|
|
case K_S_LEFT:
|
|
case K_C_LEFT:
|
|
if (ccline.cmdpos == 0) {
|
|
return command_line_not_changed(s);
|
|
}
|
|
do {
|
|
ccline.cmdpos--;
|
|
// Move to first byte of possibly multibyte char.
|
|
ccline.cmdpos -= utf_head_off(ccline.cmdbuff,
|
|
ccline.cmdbuff + ccline.cmdpos);
|
|
ccline.cmdspos -= cmdline_charsize(ccline.cmdpos);
|
|
} while (ccline.cmdpos > 0
|
|
&& (s->c == K_S_LEFT || s->c == K_C_LEFT
|
|
|| (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)))
|
|
&& ccline.cmdbuff[ccline.cmdpos - 1] != ' ');
|
|
|
|
ccline.cmdspos = cmd_screencol(ccline.cmdpos);
|
|
if (ccline.special_char != NUL) {
|
|
putcmdline(ccline.special_char, ccline.special_shift);
|
|
}
|
|
|
|
return command_line_not_changed(s);
|
|
|
|
case K_IGNORE:
|
|
// Ignore mouse event or open_cmdwin() result.
|
|
return command_line_not_changed(s);
|
|
|
|
|
|
case K_MIDDLEDRAG:
|
|
case K_MIDDLERELEASE:
|
|
return command_line_not_changed(s); // Ignore mouse
|
|
|
|
case K_MIDDLEMOUSE:
|
|
cmdline_paste(eval_has_provider("clipboard") ? '*' : 0, true, true);
|
|
redrawcmd();
|
|
return command_line_changed(s);
|
|
|
|
|
|
case K_LEFTDRAG:
|
|
case K_LEFTRELEASE:
|
|
case K_RIGHTDRAG:
|
|
case K_RIGHTRELEASE:
|
|
// Ignore drag and release events when the button-down wasn't
|
|
// seen before.
|
|
if (s->ignore_drag_release) {
|
|
return command_line_not_changed(s);
|
|
}
|
|
FALLTHROUGH;
|
|
case K_LEFTMOUSE:
|
|
case K_RIGHTMOUSE:
|
|
if (s->c == K_LEFTRELEASE || s->c == K_RIGHTRELEASE) {
|
|
s->ignore_drag_release = true;
|
|
} else {
|
|
s->ignore_drag_release = false;
|
|
}
|
|
|
|
ccline.cmdspos = cmd_startcol();
|
|
for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen;
|
|
ccline.cmdpos++) {
|
|
int cells = cmdline_charsize(ccline.cmdpos);
|
|
if (mouse_row <= cmdline_row + ccline.cmdspos / Columns
|
|
&& mouse_col < ccline.cmdspos % Columns + cells) {
|
|
break;
|
|
}
|
|
|
|
// Count ">" for double-wide char that doesn't fit.
|
|
correct_screencol(ccline.cmdpos, cells, &ccline.cmdspos);
|
|
ccline.cmdpos += utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos) - 1;
|
|
ccline.cmdspos += cells;
|
|
}
|
|
return command_line_not_changed(s);
|
|
|
|
// Mouse scroll wheel: ignored here
|
|
case K_MOUSEDOWN:
|
|
case K_MOUSEUP:
|
|
case K_MOUSELEFT:
|
|
case K_MOUSERIGHT:
|
|
// Alternate buttons ignored here
|
|
case K_X1MOUSE:
|
|
case K_X1DRAG:
|
|
case K_X1RELEASE:
|
|
case K_X2MOUSE:
|
|
case K_X2DRAG:
|
|
case K_X2RELEASE:
|
|
case K_MOUSEMOVE:
|
|
return command_line_not_changed(s);
|
|
|
|
|
|
case K_SELECT: // end of Select mode mapping - ignore
|
|
return command_line_not_changed(s);
|
|
|
|
case Ctrl_B: // begin of command line
|
|
case K_HOME:
|
|
case K_KHOME:
|
|
case K_S_HOME:
|
|
case K_C_HOME:
|
|
ccline.cmdpos = 0;
|
|
ccline.cmdspos = cmd_startcol();
|
|
return command_line_not_changed(s);
|
|
|
|
case Ctrl_E: // end of command line
|
|
case K_END:
|
|
case K_KEND:
|
|
case K_S_END:
|
|
case K_C_END:
|
|
ccline.cmdpos = ccline.cmdlen;
|
|
ccline.cmdspos = cmd_screencol(ccline.cmdpos);
|
|
return command_line_not_changed(s);
|
|
|
|
case Ctrl_A: // all matches
|
|
if (nextwild(&s->xpc, WILD_ALL, 0, s->firstc != '@') == FAIL) {
|
|
break;
|
|
}
|
|
return command_line_changed(s);
|
|
|
|
case Ctrl_L:
|
|
if (may_add_char_to_search(s->firstc, &s->c, &s->is_state) == OK) {
|
|
return command_line_not_changed(s);
|
|
}
|
|
|
|
// completion: longest common part
|
|
if (nextwild(&s->xpc, WILD_LONGEST, 0, s->firstc != '@') == FAIL) {
|
|
break;
|
|
}
|
|
return command_line_changed(s);
|
|
|
|
case Ctrl_N: // next match
|
|
case Ctrl_P: // previous match
|
|
if (s->xpc.xp_numfiles > 0) {
|
|
if (nextwild(&s->xpc, (s->c == Ctrl_P) ? WILD_PREV : WILD_NEXT,
|
|
0, s->firstc != '@') == FAIL) {
|
|
break;
|
|
}
|
|
return command_line_not_changed(s);
|
|
}
|
|
FALLTHROUGH;
|
|
|
|
case K_UP:
|
|
case K_DOWN:
|
|
case K_S_UP:
|
|
case K_S_DOWN:
|
|
case K_PAGEUP:
|
|
case K_KPAGEUP:
|
|
case K_PAGEDOWN:
|
|
case K_KPAGEDOWN:
|
|
if (s->histype == HIST_INVALID || hislen == 0 || s->firstc == NUL) {
|
|
// no history
|
|
return command_line_not_changed(s);
|
|
}
|
|
|
|
s->save_hiscnt = s->hiscnt;
|
|
|
|
// save current command string so it can be restored later
|
|
if (s->lookfor == NULL) {
|
|
s->lookfor = vim_strsave(ccline.cmdbuff);
|
|
s->lookfor[ccline.cmdpos] = NUL;
|
|
}
|
|
|
|
bool next_match = (s->c == K_DOWN || s->c == K_S_DOWN || s->c == Ctrl_N
|
|
|| s->c == K_PAGEDOWN || s->c == K_KPAGEDOWN);
|
|
command_line_next_histidx(s, next_match);
|
|
|
|
if (s->hiscnt != s->save_hiscnt) {
|
|
// jumped to other entry
|
|
char_u *p;
|
|
int len = 0;
|
|
int old_firstc;
|
|
|
|
xfree(ccline.cmdbuff);
|
|
s->xpc.xp_context = EXPAND_NOTHING;
|
|
if (s->hiscnt == hislen) {
|
|
p = s->lookfor; // back to the old one
|
|
} else {
|
|
p = history[s->histype][s->hiscnt].hisstr;
|
|
}
|
|
|
|
if (s->histype == HIST_SEARCH
|
|
&& p != s->lookfor
|
|
&& (old_firstc = p[STRLEN(p) + 1]) != s->firstc) {
|
|
// Correct for the separator character used when
|
|
// adding the history entry vs the one used now.
|
|
// First loop: count length.
|
|
// Second loop: copy the characters.
|
|
for (int i = 0; i <= 1; i++) {
|
|
len = 0;
|
|
for (int j = 0; p[j] != NUL; j++) {
|
|
// Replace old sep with new sep, unless it is
|
|
// escaped.
|
|
if (p[j] == old_firstc
|
|
&& (j == 0 || p[j - 1] != '\\')) {
|
|
if (i > 0) {
|
|
ccline.cmdbuff[len] = (char_u)s->firstc;
|
|
}
|
|
} else {
|
|
// Escape new sep, unless it is already
|
|
// escaped.
|
|
if (p[j] == s->firstc
|
|
&& (j == 0 || p[j - 1] != '\\')) {
|
|
if (i > 0) {
|
|
ccline.cmdbuff[len] = '\\';
|
|
}
|
|
++len;
|
|
}
|
|
|
|
if (i > 0) {
|
|
ccline.cmdbuff[len] = p[j];
|
|
}
|
|
}
|
|
++len;
|
|
}
|
|
|
|
if (i == 0) {
|
|
alloc_cmdbuff(len);
|
|
}
|
|
}
|
|
ccline.cmdbuff[len] = NUL;
|
|
} else {
|
|
alloc_cmdbuff((int)STRLEN(p));
|
|
STRCPY(ccline.cmdbuff, p);
|
|
}
|
|
|
|
ccline.cmdpos = ccline.cmdlen = (int)STRLEN(ccline.cmdbuff);
|
|
redrawcmd();
|
|
return command_line_changed(s);
|
|
}
|
|
beep_flush();
|
|
return command_line_not_changed(s);
|
|
|
|
case Ctrl_G: // next match
|
|
case Ctrl_T: // previous match
|
|
if (may_do_command_line_next_incsearch(s->firstc, s->count, &s->is_state,
|
|
s->c == Ctrl_G) == FAIL) {
|
|
return command_line_not_changed(s);
|
|
}
|
|
break;
|
|
|
|
case Ctrl_V:
|
|
case Ctrl_Q:
|
|
s->ignore_drag_release = true;
|
|
putcmdline('^', true);
|
|
s->c = get_literal(); // get next (two) character(s)
|
|
s->do_abbr = false; // don't do abbreviation now
|
|
ccline.special_char = NUL;
|
|
// may need to remove ^ when composing char was typed
|
|
if (utf_iscomposing(s->c) && !cmd_silent) {
|
|
if (ui_has(kUICmdline)) {
|
|
// TODO(bfredl): why not make unputcmdline also work with true?
|
|
unputcmdline();
|
|
} else {
|
|
draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
|
|
msg_putchar(' ');
|
|
cursorcmd();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Ctrl_K:
|
|
s->ignore_drag_release = true;
|
|
putcmdline('?', true);
|
|
s->c = get_digraph(true);
|
|
ccline.special_char = NUL;
|
|
|
|
if (s->c != NUL) {
|
|
break;
|
|
}
|
|
|
|
redrawcmd();
|
|
return command_line_not_changed(s);
|
|
|
|
case Ctrl__: // CTRL-_: switch language mode
|
|
if (!p_ari) {
|
|
break;
|
|
}
|
|
cmd_hkmap = !cmd_hkmap;
|
|
return command_line_not_changed(s);
|
|
|
|
default:
|
|
// Normal character with no special meaning. Just set mod_mask
|
|
// to 0x0 so that typing Shift-Space in the GUI doesn't enter
|
|
// the string <S-Space>. This should only happen after ^V.
|
|
if (!IS_SPECIAL(s->c)) {
|
|
mod_mask = 0x0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// End of switch on command line character.
|
|
// We come here if we have a normal character.
|
|
if (s->do_abbr && (IS_SPECIAL(s->c) || !vim_iswordc(s->c))
|
|
// Add ABBR_OFF for characters above 0x100, this is
|
|
// what check_abbr() expects.
|
|
&& (ccheck_abbr((s->c >= 0x100) ? (s->c + ABBR_OFF) : s->c)
|
|
|| s->c == Ctrl_RSB)) {
|
|
return command_line_changed(s);
|
|
}
|
|
|
|
// put the character in the command line
|
|
if (IS_SPECIAL(s->c) || mod_mask != 0) {
|
|
put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true);
|
|
} else {
|
|
int j = utf_char2bytes(s->c, IObuff);
|
|
IObuff[j] = NUL; // exclude composing chars
|
|
put_on_cmdline(IObuff, j, true);
|
|
}
|
|
return command_line_changed(s);
|
|
}
|
|
|
|
|
|
static int command_line_not_changed(CommandLineState *s)
|
|
{
|
|
// Incremental searches for "/" and "?":
|
|
// Enter command_line_not_changed() when a character has been read but the
|
|
// command line did not change. Then we only search and redraw if something
|
|
// changed in the past.
|
|
// Enter command_line_changed() when the command line did change.
|
|
if (!s->is_state.incsearch_postponed) {
|
|
return 1;
|
|
}
|
|
return command_line_changed(s);
|
|
}
|
|
|
|
/// Guess that the pattern matches everything. Only finds specific cases, such
|
|
/// as a trailing \|, which can happen while typing a pattern.
|
|
static int empty_pattern(char_u *p)
|
|
{
|
|
size_t n = STRLEN(p);
|
|
|
|
// remove trailing \v and the like
|
|
while (n >= 2 && p[n - 2] == '\\'
|
|
&& vim_strchr((char_u *)"mMvVcCZ", p[n - 1]) != NULL) {
|
|
n -= 2;
|
|
}
|
|
return n == 0 || (n >= 2 && p[n - 2] == '\\' && p[n - 1] == '|');
|
|
}
|
|
|
|
static int command_line_changed(CommandLineState *s)
|
|
{
|
|
// Trigger CmdlineChanged autocommands.
|
|
if (has_event(EVENT_CMDLINECHANGED)) {
|
|
TryState tstate;
|
|
Error err = ERROR_INIT;
|
|
save_v_event_T save_v_event;
|
|
dict_T *dict = get_v_event(&save_v_event);
|
|
|
|
char firstcbuf[2];
|
|
firstcbuf[0] = (char)(s->firstc > 0 ? s->firstc : '-');
|
|
firstcbuf[1] = 0;
|
|
|
|
// set v:event to a dictionary with information about the commandline
|
|
tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf);
|
|
tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level);
|
|
tv_dict_set_keys_readonly(dict);
|
|
try_enter(&tstate);
|
|
|
|
apply_autocmds(EVENT_CMDLINECHANGED, (char_u *)firstcbuf,
|
|
(char_u *)firstcbuf, false, curbuf);
|
|
restore_v_event(dict, &save_v_event);
|
|
|
|
bool tl_ret = try_leave(&tstate, &err);
|
|
if (!tl_ret && ERROR_SET(&err)) {
|
|
msg_putchar('\n');
|
|
msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg);
|
|
api_clear_error(&err);
|
|
redrawcmd();
|
|
}
|
|
}
|
|
|
|
// 'incsearch' highlighting.
|
|
if (s->firstc == ':'
|
|
&& current_sctx.sc_sid == 0 // only if interactive
|
|
&& *p_icm != NUL // 'inccommand' is set
|
|
&& curbuf->b_p_ma // buffer is modifiable
|
|
&& cmdline_star == 0 // not typing a password
|
|
&& cmd_can_preview(ccline.cmdbuff)
|
|
&& !vpeekc_any()) {
|
|
// Show 'inccommand' preview. It works like this:
|
|
// 1. Do the command.
|
|
// 2. Command implementation detects CMDPREVIEW state, then:
|
|
// - Update the screen while the effects are in place.
|
|
// - Immediately undo the effects.
|
|
State |= CMDPREVIEW;
|
|
emsg_silent++; // Block error reporting as the command may be incomplete
|
|
msg_silent++; // Block messages, namely ones that prompt
|
|
do_cmdline(ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_NOWAIT|DOCMD_PREVIEW);
|
|
msg_silent--; // Unblock messages
|
|
emsg_silent--; // Unblock error reporting
|
|
|
|
// Restore the window "view".
|
|
curwin->w_cursor = s->is_state.save_cursor;
|
|
restore_viewstate(&s->is_state.old_viewstate);
|
|
update_topline(curwin);
|
|
|
|
redrawcmdline();
|
|
} else if (State & CMDPREVIEW) {
|
|
State = (State & ~CMDPREVIEW);
|
|
close_preview_windows();
|
|
update_screen(SOME_VALID); // Clear 'inccommand' preview.
|
|
} else {
|
|
if (s->xpc.xp_context == EXPAND_NOTHING && (KeyTyped || vpeekc() == NUL)) {
|
|
may_do_incsearch_highlighting(s->firstc, s->count, &s->is_state);
|
|
}
|
|
}
|
|
|
|
if (cmdmsg_rl || (p_arshape && !p_tbidi)) {
|
|
// Always redraw the whole command line to fix shaping and
|
|
// right-left typing. Not efficient, but it works.
|
|
// Do it only when there are no characters left to read
|
|
// to avoid useless intermediate redraws.
|
|
// if cmdline is external the ui handles shaping, no redraw needed.
|
|
if (!ui_has(kUICmdline) && vpeekc() == NUL) {
|
|
redrawcmd();
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/// Abandon the command line.
|
|
static void abandon_cmdline(void)
|
|
{
|
|
XFREE_CLEAR(ccline.cmdbuff);
|
|
ccline.redraw_state = kCmdRedrawNone;
|
|
if (msg_scrolled == 0) {
|
|
compute_cmdrow();
|
|
}
|
|
msg("");
|
|
redraw_cmdline = true;
|
|
}
|
|
|
|
/// getcmdline() - accept a command line starting with firstc.
|
|
///
|
|
/// firstc == ':' get ":" command line.
|
|
/// firstc == '/' or '?' get search pattern
|
|
/// firstc == '=' get expression
|
|
/// firstc == '@' get text for input() function
|
|
/// firstc == '>' get text for debug mode
|
|
/// firstc == NUL get text for :insert command
|
|
/// firstc == -1 like NUL, and break on CTRL-C
|
|
///
|
|
/// The line is collected in ccline.cmdbuff, which is reallocated to fit the
|
|
/// command line.
|
|
///
|
|
/// Careful: getcmdline() can be called recursively!
|
|
///
|
|
/// Return pointer to allocated string if there is a commandline, NULL
|
|
/// otherwise.
|
|
///
|
|
/// @param count only used for incremental search
|
|
/// @param indent indent for inside conditionals
|
|
char_u *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_UNUSED)
|
|
{
|
|
// Be prepared for situations where cmdline can be invoked recursively.
|
|
// That includes cmd mappings, event handlers, as well as update_screen()
|
|
// (custom status line eval), which all may invoke ":normal :".
|
|
CmdlineInfo save_ccline;
|
|
save_cmdline(&save_ccline);
|
|
char_u *retval = command_line_enter(firstc, count, indent);
|
|
restore_cmdline(&save_ccline);
|
|
return retval;
|
|
}
|
|
|
|
/// Get a command line with a prompt
|
|
///
|
|
/// This is prepared to be called recursively from getcmdline() (e.g. by
|
|
/// f_input() when evaluating an expression from `<C-r>=`).
|
|
///
|
|
/// @param[in] firstc Prompt type: e.g. '@' for input(), '>' for debug.
|
|
/// @param[in] prompt Prompt string: what is displayed before the user text.
|
|
/// @param[in] attr Prompt highlighting.
|
|
/// @param[in] xp_context Type of expansion.
|
|
/// @param[in] xp_arg User-defined expansion argument.
|
|
/// @param[in] highlight_callback Callback used for highlighting user input.
|
|
///
|
|
/// @return [allocated] Command line or NULL.
|
|
char *getcmdline_prompt(const char firstc, const char *const prompt, const int attr,
|
|
const int xp_context, const char *const xp_arg,
|
|
const Callback highlight_callback)
|
|
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
|
|
{
|
|
const int msg_col_save = msg_col;
|
|
|
|
CmdlineInfo save_ccline;
|
|
save_cmdline(&save_ccline);
|
|
|
|
ccline.prompt_id = last_prompt_id++;
|
|
ccline.cmdprompt = (char_u *)prompt;
|
|
ccline.cmdattr = attr;
|
|
ccline.xp_context = xp_context;
|
|
ccline.xp_arg = (char_u *)xp_arg;
|
|
ccline.input_fn = (firstc == '@');
|
|
ccline.highlight_callback = highlight_callback;
|
|
|
|
int msg_silent_saved = msg_silent;
|
|
msg_silent = 0;
|
|
|
|
char *const ret = (char *)command_line_enter(firstc, 1L, 0);
|
|
|
|
restore_cmdline(&save_ccline);
|
|
msg_silent = msg_silent_saved;
|
|
// Restore msg_col, the prompt from input() may have changed it.
|
|
// But only if called recursively and the commandline is therefore being
|
|
// restored to an old one; if not, the input() prompt stays on the screen,
|
|
// so we need its modified msg_col left intact.
|
|
if (ccline.cmdbuff != NULL) {
|
|
msg_col = msg_col_save;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when the text must not be changed and we can't switch to
|
|
* another window or buffer. Used when editing the command line etc.
|
|
*/
|
|
int text_locked(void)
|
|
{
|
|
if (cmdwin_type != 0) {
|
|
return TRUE;
|
|
}
|
|
return textlock != 0;
|
|
}
|
|
|
|
/*
|
|
* Give an error message for a command that isn't allowed while the cmdline
|
|
* window is open or editing the cmdline in another way.
|
|
*/
|
|
void text_locked_msg(void)
|
|
{
|
|
emsg(_(get_text_locked_msg()));
|
|
}
|
|
|
|
char *get_text_locked_msg(void)
|
|
{
|
|
if (cmdwin_type != 0) {
|
|
return e_cmdwin;
|
|
} else {
|
|
return e_secure;
|
|
}
|
|
}
|
|
|
|
/// Check if "curbuf->b_ro_locked" or "allbuf_lock" is set and
|
|
/// return TRUE when it is and give an error message.
|
|
int curbuf_locked(void)
|
|
{
|
|
if (curbuf->b_ro_locked > 0) {
|
|
emsg(_("E788: Not allowed to edit another buffer now"));
|
|
return TRUE;
|
|
}
|
|
return allbuf_locked();
|
|
}
|
|
|
|
/*
|
|
* Check if "allbuf_lock" is set and return TRUE when it is and give an error
|
|
* message.
|
|
*/
|
|
int allbuf_locked(void)
|
|
{
|
|
if (allbuf_lock > 0) {
|
|
emsg(_("E811: Not allowed to change buffer information now"));
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static int cmdline_charsize(int idx)
|
|
{
|
|
if (cmdline_star > 0) { // showing '*', always 1 position
|
|
return 1;
|
|
}
|
|
return ptr2cells(ccline.cmdbuff + idx);
|
|
}
|
|
|
|
/// Compute the offset of the cursor on the command line for the prompt and
|
|
/// indent.
|
|
static int cmd_startcol(void)
|
|
{
|
|
return ccline.cmdindent + ((ccline.cmdfirstc != NUL) ? 1 : 0);
|
|
}
|
|
|
|
|
|
/// Compute the column position for a byte position on the command line.
|
|
static int cmd_screencol(int bytepos)
|
|
{
|
|
int m; // maximum column
|
|
|
|
int col = cmd_startcol();
|
|
if (KeyTyped) {
|
|
m = Columns * Rows;
|
|
if (m < 0) { // overflow, Columns or Rows at weird value
|
|
m = MAXCOL;
|
|
}
|
|
} else {
|
|
m = MAXCOL;
|
|
}
|
|
|
|
for (int i = 0; i < ccline.cmdlen && i < bytepos;
|
|
i += utfc_ptr2len(ccline.cmdbuff + i)) {
|
|
int c = cmdline_charsize(i);
|
|
// Count ">" for double-wide multi-byte char that doesn't fit.
|
|
correct_screencol(i, c, &col);
|
|
|
|
// If the cmdline doesn't fit, show cursor on last visible char.
|
|
// Don't move the cursor itself, so we can still append.
|
|
if ((col += c) >= m) {
|
|
col -= c;
|
|
break;
|
|
}
|
|
}
|
|
return col;
|
|
}
|
|
|
|
/// Check if the character at "idx", which is "cells" wide, is a multi-byte
|
|
/// character that doesn't fit, so that a ">" must be displayed.
|
|
static void correct_screencol(int idx, int cells, int *col)
|
|
{
|
|
if (utfc_ptr2len(ccline.cmdbuff + idx) > 1
|
|
&& utf_ptr2cells(ccline.cmdbuff + idx) > 1
|
|
&& (*col) % Columns + cells > Columns) {
|
|
(*col)++;
|
|
}
|
|
}
|
|
|
|
/// Get an Ex command line for the ":" command.
|
|
///
|
|
/// @param c normally ':', NUL for ":append"
|
|
/// @param indent indent for inside conditionals
|
|
char_u *getexline(int c, void *cookie, int indent, bool do_concat)
|
|
{
|
|
// When executing a register, remove ':' that's in front of each line.
|
|
if (exec_from_reg && vpeekc() == ':') {
|
|
(void)vgetc();
|
|
}
|
|
|
|
return getcmdline(c, 1L, indent, do_concat);
|
|
}
|
|
|
|
bool cmdline_overstrike(void)
|
|
{
|
|
return ccline.overstrike;
|
|
}
|
|
|
|
|
|
/// Return true if the cursor is at the end of the cmdline.
|
|
bool cmdline_at_end(void)
|
|
{
|
|
return (ccline.cmdpos >= ccline.cmdlen);
|
|
}
|
|
|
|
/*
|
|
* Allocate a new command line buffer.
|
|
* Assigns the new buffer to ccline.cmdbuff and ccline.cmdbufflen.
|
|
* Returns the new value of ccline.cmdbuff and ccline.cmdbufflen.
|
|
*/
|
|
static void alloc_cmdbuff(int len)
|
|
{
|
|
/*
|
|
* give some extra space to avoid having to allocate all the time
|
|
*/
|
|
if (len < 80) {
|
|
len = 100;
|
|
} else {
|
|
len += 20;
|
|
}
|
|
|
|
ccline.cmdbuff = xmalloc((size_t)len);
|
|
ccline.cmdbufflen = len;
|
|
}
|
|
|
|
/*
|
|
* Re-allocate the command line to length len + something extra.
|
|
*/
|
|
static void realloc_cmdbuff(int len)
|
|
{
|
|
if (len < ccline.cmdbufflen) {
|
|
return; // no need to resize
|
|
}
|
|
|
|
char_u *p = ccline.cmdbuff;
|
|
alloc_cmdbuff(len); // will get some more
|
|
// There isn't always a NUL after the command, but it may need to be
|
|
// there, thus copy up to the NUL and add a NUL.
|
|
memmove(ccline.cmdbuff, p, (size_t)ccline.cmdlen);
|
|
ccline.cmdbuff[ccline.cmdlen] = NUL;
|
|
xfree(p);
|
|
|
|
if (ccline.xpc != NULL
|
|
&& ccline.xpc->xp_pattern != NULL
|
|
&& ccline.xpc->xp_context != EXPAND_NOTHING
|
|
&& ccline.xpc->xp_context != EXPAND_UNSUCCESSFUL) {
|
|
int i = (int)(ccline.xpc->xp_pattern - p);
|
|
|
|
// If xp_pattern points inside the old cmdbuff it needs to be adjusted
|
|
// to point into the newly allocated memory.
|
|
if (i >= 0 && i <= ccline.cmdlen) {
|
|
ccline.xpc->xp_pattern = ccline.cmdbuff + i;
|
|
}
|
|
}
|
|
}
|
|
|
|
static char_u *arshape_buf = NULL;
|
|
|
|
#if defined(EXITFREE)
|
|
void free_arshape_buf(void)
|
|
{
|
|
xfree(arshape_buf);
|
|
}
|
|
|
|
#endif
|
|
|
|
enum { MAX_CB_ERRORS = 1, };
|
|
|
|
/// Color expression cmdline using built-in expressions parser
|
|
///
|
|
/// @param[in] colored_ccline Command-line to color.
|
|
/// @param[out] ret_ccline_colors What should be colored.
|
|
///
|
|
/// Always colors the whole cmdline.
|
|
static void color_expr_cmdline(const CmdlineInfo *const colored_ccline,
|
|
ColoredCmdline *const ret_ccline_colors)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
ParserLine parser_lines[] = {
|
|
{
|
|
.data = (const char *)colored_ccline->cmdbuff,
|
|
.size = STRLEN(colored_ccline->cmdbuff),
|
|
.allocated = false,
|
|
},
|
|
{ NULL, 0, false },
|
|
};
|
|
ParserLine *plines_p = parser_lines;
|
|
ParserHighlight colors;
|
|
kvi_init(colors);
|
|
ParserState pstate;
|
|
viml_parser_init(&pstate, parser_simple_get_line, &plines_p, &colors);
|
|
ExprAST east = viml_pexpr_parse(&pstate, kExprFlagsDisallowEOC);
|
|
viml_pexpr_free_ast(east);
|
|
viml_parser_destroy(&pstate);
|
|
kv_resize(ret_ccline_colors->colors, kv_size(colors));
|
|
size_t prev_end = 0;
|
|
for (size_t i = 0 ; i < kv_size(colors) ; i++) {
|
|
const ParserHighlightChunk chunk = kv_A(colors, i);
|
|
assert(chunk.start.col < INT_MAX);
|
|
assert(chunk.end_col < INT_MAX);
|
|
if (chunk.start.col != prev_end) {
|
|
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
|
|
.start = (int)prev_end,
|
|
.end = (int)chunk.start.col,
|
|
.attr = 0,
|
|
}));
|
|
}
|
|
const int id = syn_name2id(chunk.group);
|
|
const int attr = (id == 0 ? 0 : syn_id2attr(id));
|
|
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
|
|
.start = (int)chunk.start.col,
|
|
.end = (int)chunk.end_col,
|
|
.attr = attr,
|
|
}));
|
|
prev_end = chunk.end_col;
|
|
}
|
|
if (prev_end < (size_t)colored_ccline->cmdlen) {
|
|
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
|
|
.start = (int)prev_end,
|
|
.end = colored_ccline->cmdlen,
|
|
.attr = 0,
|
|
}));
|
|
}
|
|
kvi_destroy(colors);
|
|
}
|
|
|
|
/// Color command-line
|
|
///
|
|
/// Should use built-in command parser or user-specified one. Currently only the
|
|
/// latter is supported.
|
|
///
|
|
/// @param[in,out] colored_ccline Command-line to color. Also holds a cache:
|
|
/// if ->prompt_id and ->cmdbuff values happen
|
|
/// to be equal to those from colored_cmdline it
|
|
/// will just do nothing, assuming that ->colors
|
|
/// already contains needed data.
|
|
///
|
|
/// Always colors the whole cmdline.
|
|
///
|
|
/// @return true if draw_cmdline may proceed, false if it does not need anything
|
|
/// to do.
|
|
static bool color_cmdline(CmdlineInfo *colored_ccline)
|
|
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
|
|
{
|
|
bool printed_errmsg = false;
|
|
|
|
#define PRINT_ERRMSG(...) \
|
|
do { \
|
|
msg_putchar('\n'); \
|
|
msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, __VA_ARGS__); \
|
|
printed_errmsg = true; \
|
|
} while (0)
|
|
bool ret = true;
|
|
|
|
ColoredCmdline *ccline_colors = &colored_ccline->last_colors;
|
|
|
|
// Check whether result of the previous call is still valid.
|
|
if (ccline_colors->prompt_id == colored_ccline->prompt_id
|
|
&& ccline_colors->cmdbuff != NULL
|
|
&& STRCMP(ccline_colors->cmdbuff, colored_ccline->cmdbuff) == 0) {
|
|
return ret;
|
|
}
|
|
|
|
kv_size(ccline_colors->colors) = 0;
|
|
|
|
if (colored_ccline->cmdbuff == NULL || *colored_ccline->cmdbuff == NUL) {
|
|
// Nothing to do, exiting.
|
|
XFREE_CLEAR(ccline_colors->cmdbuff);
|
|
return ret;
|
|
}
|
|
|
|
bool arg_allocated = false;
|
|
typval_T arg = {
|
|
.v_type = VAR_STRING,
|
|
.vval.v_string = colored_ccline->cmdbuff,
|
|
};
|
|
typval_T tv = { .v_type = VAR_UNKNOWN };
|
|
|
|
static unsigned prev_prompt_id = UINT_MAX;
|
|
static int prev_prompt_errors = 0;
|
|
Callback color_cb = CALLBACK_NONE;
|
|
bool can_free_cb = false;
|
|
TryState tstate;
|
|
Error err = ERROR_INIT;
|
|
const char *err_errmsg = (const char *)e_intern2;
|
|
bool dgc_ret = true;
|
|
bool tl_ret = true;
|
|
|
|
if (colored_ccline->prompt_id != prev_prompt_id) {
|
|
prev_prompt_errors = 0;
|
|
prev_prompt_id = colored_ccline->prompt_id;
|
|
} else if (prev_prompt_errors >= MAX_CB_ERRORS) {
|
|
goto color_cmdline_end;
|
|
}
|
|
if (colored_ccline->highlight_callback.type != kCallbackNone) {
|
|
// Currently this should only happen while processing input() prompts.
|
|
assert(colored_ccline->input_fn);
|
|
color_cb = colored_ccline->highlight_callback;
|
|
} else if (colored_ccline->cmdfirstc == ':') {
|
|
try_enter(&tstate);
|
|
err_errmsg = N_("E5408: Unable to get g:Nvim_color_cmdline callback: %s");
|
|
dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_cmdline"),
|
|
&color_cb);
|
|
tl_ret = try_leave(&tstate, &err);
|
|
can_free_cb = true;
|
|
} else if (colored_ccline->cmdfirstc == '=') {
|
|
color_expr_cmdline(colored_ccline, ccline_colors);
|
|
}
|
|
if (!tl_ret || !dgc_ret) {
|
|
goto color_cmdline_error;
|
|
}
|
|
|
|
if (color_cb.type == kCallbackNone) {
|
|
goto color_cmdline_end;
|
|
}
|
|
if (colored_ccline->cmdbuff[colored_ccline->cmdlen] != NUL) {
|
|
arg_allocated = true;
|
|
arg.vval.v_string = xmemdupz((const char *)colored_ccline->cmdbuff,
|
|
(size_t)colored_ccline->cmdlen);
|
|
}
|
|
// msg_start() called by e.g. :echo may shift command-line to the first column
|
|
// even though msg_silent is here. Two ways to workaround this problem without
|
|
// altering message.c: use full_screen or save and restore msg_col.
|
|
//
|
|
// Saving and restoring full_screen does not work well with :redraw!. Saving
|
|
// and restoring msg_col is neither ideal, but while with full_screen it
|
|
// appears shifted one character to the right and cursor position is no longer
|
|
// correct, with msg_col it just misses leading `:`. Since `redraw!` in
|
|
// callback lags this is least of the user problems.
|
|
//
|
|
// Also using try_enter() because error messages may overwrite typed
|
|
// command-line which is not expected.
|
|
getln_interrupted_highlight = false;
|
|
try_enter(&tstate);
|
|
err_errmsg = N_("E5407: Callback has thrown an exception: %s");
|
|
const int saved_msg_col = msg_col;
|
|
msg_silent++;
|
|
const bool cbcall_ret = callback_call(&color_cb, 1, &arg, &tv);
|
|
msg_silent--;
|
|
msg_col = saved_msg_col;
|
|
if (got_int) {
|
|
getln_interrupted_highlight = true;
|
|
}
|
|
if (!try_leave(&tstate, &err) || !cbcall_ret) {
|
|
goto color_cmdline_error;
|
|
}
|
|
if (tv.v_type != VAR_LIST) {
|
|
PRINT_ERRMSG("%s", _("E5400: Callback should return list"));
|
|
goto color_cmdline_error;
|
|
}
|
|
if (tv.vval.v_list == NULL) {
|
|
goto color_cmdline_end;
|
|
}
|
|
varnumber_T prev_end = 0;
|
|
int i = 0;
|
|
TV_LIST_ITER_CONST(tv.vval.v_list, li, {
|
|
if (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST) {
|
|
PRINT_ERRMSG(_("E5401: List item %i is not a List"), i);
|
|
goto color_cmdline_error;
|
|
}
|
|
const list_T *const l = TV_LIST_ITEM_TV(li)->vval.v_list;
|
|
if (tv_list_len(l) != 3) {
|
|
PRINT_ERRMSG(_("E5402: List item %i has incorrect length: %d /= 3"),
|
|
i, tv_list_len(l));
|
|
goto color_cmdline_error;
|
|
}
|
|
bool error = false;
|
|
const varnumber_T start = (
|
|
tv_get_number_chk(TV_LIST_ITEM_TV(tv_list_first(l)), &error));
|
|
if (error) {
|
|
goto color_cmdline_error;
|
|
} else if (!(prev_end <= start && start < colored_ccline->cmdlen)) {
|
|
PRINT_ERRMSG(_("E5403: Chunk %i start %" PRIdVARNUMBER " not in range "
|
|
"[%" PRIdVARNUMBER ", %i)"),
|
|
i, start, prev_end, colored_ccline->cmdlen);
|
|
goto color_cmdline_error;
|
|
} else if (utf8len_tab_zero[(uint8_t)colored_ccline->cmdbuff[start]] == 0) {
|
|
PRINT_ERRMSG(_("E5405: Chunk %i start %" PRIdVARNUMBER " splits "
|
|
"multibyte character"), i, start);
|
|
goto color_cmdline_error;
|
|
}
|
|
if (start != prev_end) {
|
|
kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
|
|
.start = (int)prev_end,
|
|
.end = (int)start,
|
|
.attr = 0,
|
|
}));
|
|
}
|
|
const varnumber_T end =
|
|
tv_get_number_chk(TV_LIST_ITEM_TV(TV_LIST_ITEM_NEXT(l, tv_list_first(l))), &error);
|
|
if (error) {
|
|
goto color_cmdline_error;
|
|
} else if (!(start < end && end <= colored_ccline->cmdlen)) {
|
|
PRINT_ERRMSG(_("E5404: Chunk %i end %" PRIdVARNUMBER " not in range "
|
|
"(%" PRIdVARNUMBER ", %i]"),
|
|
i, end, start, colored_ccline->cmdlen);
|
|
goto color_cmdline_error;
|
|
} else if (end < colored_ccline->cmdlen
|
|
&& (utf8len_tab_zero[(uint8_t)colored_ccline->cmdbuff[end]]
|
|
== 0)) {
|
|
PRINT_ERRMSG(_("E5406: Chunk %i end %" PRIdVARNUMBER " splits multibyte "
|
|
"character"), i, end);
|
|
goto color_cmdline_error;
|
|
}
|
|
prev_end = end;
|
|
const char *const group = tv_get_string_chk(TV_LIST_ITEM_TV(tv_list_last(l)));
|
|
if (group == NULL) {
|
|
goto color_cmdline_error;
|
|
}
|
|
const int id = syn_name2id(group);
|
|
const int attr = (id == 0 ? 0 : syn_id2attr(id));
|
|
kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
|
|
.start = (int)start,
|
|
.end = (int)end,
|
|
.attr = attr,
|
|
}));
|
|
i++;
|
|
});
|
|
if (prev_end < colored_ccline->cmdlen) {
|
|
kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
|
|
.start = (int)prev_end,
|
|
.end = colored_ccline->cmdlen,
|
|
.attr = 0,
|
|
}));
|
|
}
|
|
prev_prompt_errors = 0;
|
|
color_cmdline_end:
|
|
assert(!ERROR_SET(&err));
|
|
if (can_free_cb) {
|
|
callback_free(&color_cb);
|
|
}
|
|
xfree(ccline_colors->cmdbuff);
|
|
// Note: errors “output” is cached just as well as regular results.
|
|
ccline_colors->prompt_id = colored_ccline->prompt_id;
|
|
if (arg_allocated) {
|
|
ccline_colors->cmdbuff = (char *)arg.vval.v_string;
|
|
} else {
|
|
ccline_colors->cmdbuff = xmemdupz((const char *)colored_ccline->cmdbuff,
|
|
(size_t)colored_ccline->cmdlen);
|
|
}
|
|
tv_clear(&tv);
|
|
return ret;
|
|
color_cmdline_error:
|
|
if (ERROR_SET(&err)) {
|
|
PRINT_ERRMSG(_(err_errmsg), err.msg);
|
|
api_clear_error(&err);
|
|
}
|
|
assert(printed_errmsg);
|
|
(void)printed_errmsg;
|
|
|
|
prev_prompt_errors++;
|
|
kv_size(ccline_colors->colors) = 0;
|
|
redrawcmdline();
|
|
ret = false;
|
|
goto color_cmdline_end;
|
|
#undef PRINT_ERRMSG
|
|
}
|
|
|
|
/*
|
|
* Draw part of the cmdline at the current cursor position. But draw stars
|
|
* when cmdline_star is TRUE.
|
|
*/
|
|
static void draw_cmdline(int start, int len)
|
|
{
|
|
if (!color_cmdline(&ccline)) {
|
|
return;
|
|
}
|
|
|
|
if (ui_has(kUICmdline)) {
|
|
ccline.special_char = NUL;
|
|
ccline.redraw_state = kCmdRedrawAll;
|
|
return;
|
|
}
|
|
|
|
if (cmdline_star > 0) {
|
|
for (int i = 0; i < len; i++) {
|
|
msg_putchar('*');
|
|
i += utfc_ptr2len(ccline.cmdbuff + start + i) - 1;
|
|
}
|
|
} else if (p_arshape && !p_tbidi && len > 0) {
|
|
bool do_arabicshape = false;
|
|
int mb_l;
|
|
for (int i = start; i < start + len; i += mb_l) {
|
|
char_u *p = ccline.cmdbuff + i;
|
|
int u8cc[MAX_MCO];
|
|
int u8c = utfc_ptr2char_len(p, u8cc, start + len - i);
|
|
mb_l = utfc_ptr2len_len(p, start + len - i);
|
|
if (arabic_char(u8c)) {
|
|
do_arabicshape = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!do_arabicshape) {
|
|
goto draw_cmdline_no_arabicshape;
|
|
}
|
|
|
|
static size_t buflen = 0;
|
|
assert(len >= 0);
|
|
|
|
// Do arabic shaping into a temporary buffer. This is very
|
|
// inefficient!
|
|
if ((size_t)len * 2 + 2 > buflen) {
|
|
// Re-allocate the buffer. We keep it around to avoid a lot of
|
|
// alloc()/free() calls.
|
|
xfree(arshape_buf);
|
|
buflen = (size_t)len * 2 + 2;
|
|
arshape_buf = xmalloc(buflen);
|
|
}
|
|
|
|
int newlen = 0;
|
|
if (utf_iscomposing(utf_ptr2char(ccline.cmdbuff + start))) {
|
|
// Prepend a space to draw the leading composing char on.
|
|
arshape_buf[0] = ' ';
|
|
newlen = 1;
|
|
}
|
|
|
|
int prev_c = 0;
|
|
int prev_c1 = 0;
|
|
for (int i = start; i < start + len; i += mb_l) {
|
|
char_u *p = ccline.cmdbuff + i;
|
|
int u8cc[MAX_MCO];
|
|
int u8c = utfc_ptr2char_len(p, u8cc, start + len - i);
|
|
mb_l = utfc_ptr2len_len(p, start + len - i);
|
|
if (arabic_char(u8c)) {
|
|
int pc;
|
|
int pc1 = 0;
|
|
int nc = 0;
|
|
// Do Arabic shaping.
|
|
if (cmdmsg_rl) {
|
|
// Displaying from right to left.
|
|
pc = prev_c;
|
|
pc1 = prev_c1;
|
|
prev_c1 = u8cc[0];
|
|
if (i + mb_l >= start + len) {
|
|
nc = NUL;
|
|
} else {
|
|
nc = utf_ptr2char(p + mb_l);
|
|
}
|
|
} else {
|
|
// Displaying from left to right.
|
|
if (i + mb_l >= start + len) {
|
|
pc = NUL;
|
|
} else {
|
|
int pcc[MAX_MCO];
|
|
|
|
pc = utfc_ptr2char_len(p + mb_l, pcc, start + len - i - mb_l);
|
|
pc1 = pcc[0];
|
|
}
|
|
nc = prev_c;
|
|
}
|
|
prev_c = u8c;
|
|
|
|
u8c = arabic_shape(u8c, NULL, &u8cc[0], pc, pc1, nc);
|
|
|
|
newlen += utf_char2bytes(u8c, arshape_buf + newlen);
|
|
if (u8cc[0] != 0) {
|
|
newlen += utf_char2bytes(u8cc[0], arshape_buf + newlen);
|
|
if (u8cc[1] != 0) {
|
|
newlen += utf_char2bytes(u8cc[1], arshape_buf + newlen);
|
|
}
|
|
}
|
|
} else {
|
|
prev_c = u8c;
|
|
memmove(arshape_buf + newlen, p, (size_t)mb_l);
|
|
newlen += mb_l;
|
|
}
|
|
}
|
|
|
|
msg_outtrans_len(arshape_buf, newlen);
|
|
} else {
|
|
draw_cmdline_no_arabicshape:
|
|
if (kv_size(ccline.last_colors.colors)) {
|
|
for (size_t i = 0; i < kv_size(ccline.last_colors.colors); i++) {
|
|
CmdlineColorChunk chunk = kv_A(ccline.last_colors.colors, i);
|
|
if (chunk.end <= start) {
|
|
continue;
|
|
}
|
|
const int chunk_start = MAX(chunk.start, start);
|
|
msg_outtrans_len_attr(ccline.cmdbuff + chunk_start,
|
|
chunk.end - chunk_start,
|
|
chunk.attr);
|
|
}
|
|
} else {
|
|
msg_outtrans_len(ccline.cmdbuff + start, len);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ui_ext_cmdline_show(CmdlineInfo *line)
|
|
{
|
|
Array content = ARRAY_DICT_INIT;
|
|
if (cmdline_star) {
|
|
size_t len = 0;
|
|
for (char_u *p = ccline.cmdbuff; *p; MB_PTR_ADV(p)) {
|
|
len++;
|
|
}
|
|
char *buf = xmallocz(len);
|
|
memset(buf, '*', len);
|
|
Array item = ARRAY_DICT_INIT;
|
|
ADD(item, INTEGER_OBJ(0));
|
|
ADD(item, STRING_OBJ(((String) { .data = buf, .size = len })));
|
|
ADD(content, ARRAY_OBJ(item));
|
|
} else if (kv_size(line->last_colors.colors)) {
|
|
for (size_t i = 0; i < kv_size(line->last_colors.colors); i++) {
|
|
CmdlineColorChunk chunk = kv_A(line->last_colors.colors, i);
|
|
Array item = ARRAY_DICT_INIT;
|
|
ADD(item, INTEGER_OBJ(chunk.attr));
|
|
|
|
assert(chunk.end >= chunk.start);
|
|
ADD(item, STRING_OBJ(cbuf_to_string((char *)line->cmdbuff + chunk.start,
|
|
(size_t)(chunk.end-chunk.start))));
|
|
ADD(content, ARRAY_OBJ(item));
|
|
}
|
|
} else {
|
|
Array item = ARRAY_DICT_INIT;
|
|
ADD(item, INTEGER_OBJ(0));
|
|
ADD(item, STRING_OBJ(cstr_to_string((char *)(line->cmdbuff))));
|
|
ADD(content, ARRAY_OBJ(item));
|
|
}
|
|
ui_call_cmdline_show(content, line->cmdpos,
|
|
cchar_to_string((char)line->cmdfirstc),
|
|
cstr_to_string((char *)(line->cmdprompt)),
|
|
line->cmdindent,
|
|
line->level);
|
|
if (line->special_char) {
|
|
ui_call_cmdline_special_char(cchar_to_string(line->special_char),
|
|
line->special_shift,
|
|
line->level);
|
|
}
|
|
}
|
|
|
|
void ui_ext_cmdline_block_append(size_t indent, const char *line)
|
|
{
|
|
char *buf = xmallocz(indent + strlen(line));
|
|
memset(buf, ' ', indent);
|
|
memcpy(buf + indent, line, strlen(line)); // -V575
|
|
|
|
Array item = ARRAY_DICT_INIT;
|
|
ADD(item, INTEGER_OBJ(0));
|
|
ADD(item, STRING_OBJ(cstr_as_string(buf)));
|
|
Array content = ARRAY_DICT_INIT;
|
|
ADD(content, ARRAY_OBJ(item));
|
|
ADD(cmdline_block, ARRAY_OBJ(content));
|
|
if (cmdline_block.size > 1) {
|
|
ui_call_cmdline_block_append(copy_array(content));
|
|
} else {
|
|
ui_call_cmdline_block_show(copy_array(cmdline_block));
|
|
}
|
|
}
|
|
|
|
void ui_ext_cmdline_block_leave(void)
|
|
{
|
|
api_free_array(cmdline_block);
|
|
cmdline_block = (Array)ARRAY_DICT_INIT;
|
|
ui_call_cmdline_block_hide();
|
|
}
|
|
|
|
/// Extra redrawing needed for redraw! and on ui_attach
|
|
/// assumes "redrawcmdline()" will already be invoked
|
|
void cmdline_screen_cleared(void)
|
|
{
|
|
if (!ui_has(kUICmdline)) {
|
|
return;
|
|
}
|
|
|
|
if (cmdline_block.size) {
|
|
ui_call_cmdline_block_show(copy_array(cmdline_block));
|
|
}
|
|
|
|
int prev_level = ccline.level-1;
|
|
CmdlineInfo *line = ccline.prev_ccline;
|
|
while (prev_level > 0 && line) {
|
|
if (line->level == prev_level) {
|
|
// don't redraw a cmdline already shown in the cmdline window
|
|
if (prev_level != cmdwin_level) {
|
|
line->redraw_state = kCmdRedrawAll;
|
|
}
|
|
prev_level--;
|
|
}
|
|
line = line->prev_ccline;
|
|
}
|
|
}
|
|
|
|
/// called by ui_flush, do what redraws necessary to keep cmdline updated.
|
|
void cmdline_ui_flush(void)
|
|
{
|
|
if (!ui_has(kUICmdline)) {
|
|
return;
|
|
}
|
|
int level = ccline.level;
|
|
CmdlineInfo *line = &ccline;
|
|
while (level > 0 && line) {
|
|
if (line->level == level) {
|
|
if (line->redraw_state == kCmdRedrawAll) {
|
|
ui_ext_cmdline_show(line);
|
|
} else if (line->redraw_state == kCmdRedrawPos) {
|
|
ui_call_cmdline_pos(line->cmdpos, line->level);
|
|
}
|
|
line->redraw_state = kCmdRedrawNone;
|
|
level--;
|
|
}
|
|
line = line->prev_ccline;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Put a character on the command line. Shifts the following text to the
|
|
* right when "shift" is TRUE. Used for CTRL-V, CTRL-K, etc.
|
|
* "c" must be printable (fit in one display cell)!
|
|
*/
|
|
void putcmdline(char c, int shift)
|
|
{
|
|
if (cmd_silent) {
|
|
return;
|
|
}
|
|
if (!ui_has(kUICmdline)) {
|
|
msg_no_more = true;
|
|
msg_putchar(c);
|
|
if (shift) {
|
|
draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
|
|
}
|
|
msg_no_more = false;
|
|
} else if (ccline.redraw_state != kCmdRedrawAll) {
|
|
ui_call_cmdline_special_char(cchar_to_string(c), shift,
|
|
ccline.level);
|
|
}
|
|
cursorcmd();
|
|
ccline.special_char = c;
|
|
ccline.special_shift = shift;
|
|
ui_cursor_shape();
|
|
}
|
|
|
|
/// Undo a putcmdline(c, FALSE).
|
|
void unputcmdline(void)
|
|
{
|
|
if (cmd_silent) {
|
|
return;
|
|
}
|
|
msg_no_more = true;
|
|
if (ccline.cmdlen == ccline.cmdpos && !ui_has(kUICmdline)) {
|
|
msg_putchar(' ');
|
|
} else {
|
|
draw_cmdline(ccline.cmdpos, utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos));
|
|
}
|
|
msg_no_more = false;
|
|
cursorcmd();
|
|
ccline.special_char = NUL;
|
|
ui_cursor_shape();
|
|
}
|
|
|
|
/*
|
|
* Put the given string, of the given length, onto the command line.
|
|
* If len is -1, then STRLEN() is used to calculate the length.
|
|
* If 'redraw' is TRUE then the new part of the command line, and the remaining
|
|
* part will be redrawn, otherwise it will not. If this function is called
|
|
* twice in a row, then 'redraw' should be FALSE and redrawcmd() should be
|
|
* called afterwards.
|
|
*/
|
|
void put_on_cmdline(char_u *str, int len, int redraw)
|
|
{
|
|
int i;
|
|
int m;
|
|
int c;
|
|
|
|
if (len < 0) {
|
|
len = (int)STRLEN(str);
|
|
}
|
|
|
|
realloc_cmdbuff(ccline.cmdlen + len + 1);
|
|
|
|
if (!ccline.overstrike) {
|
|
memmove(ccline.cmdbuff + ccline.cmdpos + len,
|
|
ccline.cmdbuff + ccline.cmdpos,
|
|
(size_t)(ccline.cmdlen - ccline.cmdpos));
|
|
ccline.cmdlen += len;
|
|
} else {
|
|
// Count nr of characters in the new string.
|
|
m = 0;
|
|
for (i = 0; i < len; i += utfc_ptr2len(str + i)) {
|
|
m++;
|
|
}
|
|
// Count nr of bytes in cmdline that are overwritten by these
|
|
// characters.
|
|
for (i = ccline.cmdpos; i < ccline.cmdlen && m > 0;
|
|
i += utfc_ptr2len(ccline.cmdbuff + i)) {
|
|
m--;
|
|
}
|
|
if (i < ccline.cmdlen) {
|
|
memmove(ccline.cmdbuff + ccline.cmdpos + len,
|
|
ccline.cmdbuff + i, (size_t)(ccline.cmdlen - i));
|
|
ccline.cmdlen += ccline.cmdpos + len - i;
|
|
} else {
|
|
ccline.cmdlen = ccline.cmdpos + len;
|
|
}
|
|
}
|
|
memmove(ccline.cmdbuff + ccline.cmdpos, str, (size_t)len);
|
|
ccline.cmdbuff[ccline.cmdlen] = NUL;
|
|
|
|
{
|
|
// When the inserted text starts with a composing character,
|
|
// backup to the character before it. There could be two of them.
|
|
i = 0;
|
|
c = utf_ptr2char(ccline.cmdbuff + ccline.cmdpos);
|
|
while (ccline.cmdpos > 0 && utf_iscomposing(c)) {
|
|
i = utf_head_off(ccline.cmdbuff, ccline.cmdbuff + ccline.cmdpos - 1) + 1;
|
|
ccline.cmdpos -= i;
|
|
len += i;
|
|
c = utf_ptr2char(ccline.cmdbuff + ccline.cmdpos);
|
|
}
|
|
if (i == 0 && ccline.cmdpos > 0 && arabic_maycombine(c)) {
|
|
// Check the previous character for Arabic combining pair.
|
|
i = utf_head_off(ccline.cmdbuff, ccline.cmdbuff + ccline.cmdpos - 1) + 1;
|
|
if (arabic_combine(utf_ptr2char(ccline.cmdbuff + ccline.cmdpos - i), c)) {
|
|
ccline.cmdpos -= i;
|
|
len += i;
|
|
} else {
|
|
i = 0;
|
|
}
|
|
}
|
|
if (i != 0) {
|
|
// Also backup the cursor position.
|
|
i = ptr2cells(ccline.cmdbuff + ccline.cmdpos);
|
|
ccline.cmdspos -= i;
|
|
msg_col -= i;
|
|
if (msg_col < 0) {
|
|
msg_col += Columns;
|
|
--msg_row;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (redraw && !cmd_silent) {
|
|
msg_no_more = TRUE;
|
|
i = cmdline_row;
|
|
cursorcmd();
|
|
draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
|
|
// Avoid clearing the rest of the line too often.
|
|
if (cmdline_row != i || ccline.overstrike) {
|
|
msg_clr_eos();
|
|
}
|
|
msg_no_more = FALSE;
|
|
}
|
|
if (KeyTyped) {
|
|
m = Columns * Rows;
|
|
if (m < 0) { // overflow, Columns or Rows at weird value
|
|
m = MAXCOL;
|
|
}
|
|
} else {
|
|
m = MAXCOL;
|
|
}
|
|
for (i = 0; i < len; i++) {
|
|
c = cmdline_charsize(ccline.cmdpos);
|
|
// count ">" for a double-wide char that doesn't fit.
|
|
correct_screencol(ccline.cmdpos, c, &ccline.cmdspos);
|
|
// Stop cursor at the end of the screen, but do increment the
|
|
// insert position, so that entering a very long command
|
|
// works, even though you can't see it.
|
|
if (ccline.cmdspos + c < m) {
|
|
ccline.cmdspos += c;
|
|
}
|
|
c = utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos) - 1;
|
|
if (c > len - i - 1) {
|
|
c = len - i - 1;
|
|
}
|
|
ccline.cmdpos += c;
|
|
i += c;
|
|
ccline.cmdpos++;
|
|
}
|
|
|
|
if (redraw) {
|
|
msg_check();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Save ccline, because obtaining the "=" register may execute "normal :cmd"
|
|
* and overwrite it. But get_cmdline_str() may need it, thus make it
|
|
* available globally in prev_ccline.
|
|
*/
|
|
static void save_cmdline(struct cmdline_info *ccp)
|
|
{
|
|
*ccp = ccline;
|
|
ccline.prev_ccline = ccp;
|
|
ccline.cmdbuff = NULL;
|
|
ccline.cmdprompt = NULL;
|
|
ccline.xpc = NULL;
|
|
ccline.special_char = NUL;
|
|
ccline.level = 0;
|
|
}
|
|
|
|
/*
|
|
* Restore ccline after it has been saved with save_cmdline().
|
|
*/
|
|
static void restore_cmdline(struct cmdline_info *ccp)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
ccline = *ccp;
|
|
}
|
|
|
|
/*
|
|
* Save the command line into allocated memory. Returns a pointer to be
|
|
* passed to restore_cmdline_alloc() later.
|
|
*/
|
|
char_u *save_cmdline_alloc(void)
|
|
FUNC_ATTR_NONNULL_RET
|
|
{
|
|
struct cmdline_info *p = xmalloc(sizeof(struct cmdline_info));
|
|
save_cmdline(p);
|
|
return (char_u *)p;
|
|
}
|
|
|
|
/*
|
|
* Restore the command line from the return value of save_cmdline_alloc().
|
|
*/
|
|
void restore_cmdline_alloc(char_u *p)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
restore_cmdline((struct cmdline_info *)p);
|
|
xfree(p);
|
|
}
|
|
|
|
/// Paste a yank register into the command line.
|
|
/// Used by CTRL-R command in command-line mode.
|
|
/// insert_reg() can't be used here, because special characters from the
|
|
/// register contents will be interpreted as commands.
|
|
///
|
|
/// @param regname Register name.
|
|
/// @param literally Insert text literally instead of "as typed".
|
|
/// @param remcr When true, remove trailing CR.
|
|
///
|
|
/// @returns FAIL for failure, OK otherwise
|
|
static bool cmdline_paste(int regname, bool literally, bool remcr)
|
|
{
|
|
char_u *arg;
|
|
char_u *p;
|
|
bool allocated;
|
|
struct cmdline_info save_ccline;
|
|
|
|
// check for valid regname; also accept special characters for CTRL-R in
|
|
// the command line
|
|
if (regname != Ctrl_F && regname != Ctrl_P && regname != Ctrl_W
|
|
&& regname != Ctrl_A && regname != Ctrl_L
|
|
&& !valid_yank_reg(regname, false)) {
|
|
return FAIL;
|
|
}
|
|
|
|
// A register containing CTRL-R can cause an endless loop. Allow using
|
|
// CTRL-C to break the loop.
|
|
line_breakcheck();
|
|
if (got_int) {
|
|
return FAIL;
|
|
}
|
|
|
|
|
|
// Need to save and restore ccline. And set "textlock" to avoid nasty
|
|
// things like going to another buffer when evaluating an expression.
|
|
save_cmdline(&save_ccline);
|
|
textlock++;
|
|
const bool i = get_spec_reg(regname, &arg, &allocated, true);
|
|
textlock--;
|
|
restore_cmdline(&save_ccline);
|
|
|
|
if (i) {
|
|
// Got the value of a special register in "arg".
|
|
if (arg == NULL) {
|
|
return FAIL;
|
|
}
|
|
|
|
// When 'incsearch' is set and CTRL-R CTRL-W used: skip the duplicate
|
|
// part of the word.
|
|
p = arg;
|
|
if (p_is && regname == Ctrl_W) {
|
|
char_u *w;
|
|
int len;
|
|
|
|
// Locate start of last word in the cmd buffer.
|
|
for (w = ccline.cmdbuff + ccline.cmdpos; w > ccline.cmdbuff; ) {
|
|
len = utf_head_off(ccline.cmdbuff, w - 1) + 1;
|
|
if (!vim_iswordc(utf_ptr2char(w - len))) {
|
|
break;
|
|
}
|
|
w -= len;
|
|
}
|
|
len = (int)((ccline.cmdbuff + ccline.cmdpos) - w);
|
|
if (p_ic ? STRNICMP(w, arg, len) == 0 : STRNCMP(w, arg, len) == 0) {
|
|
p += len;
|
|
}
|
|
}
|
|
|
|
cmdline_paste_str(p, literally);
|
|
if (allocated) {
|
|
xfree(arg);
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
return cmdline_paste_reg(regname, literally, remcr);
|
|
}
|
|
|
|
/*
|
|
* Put a string on the command line.
|
|
* When "literally" is TRUE, insert literally.
|
|
* When "literally" is FALSE, insert as typed, but don't leave the command
|
|
* line.
|
|
*/
|
|
void cmdline_paste_str(char_u *s, int literally)
|
|
{
|
|
int c, cv;
|
|
|
|
if (literally) {
|
|
put_on_cmdline(s, -1, TRUE);
|
|
} else {
|
|
while (*s != NUL) {
|
|
cv = *s;
|
|
if (cv == Ctrl_V && s[1]) {
|
|
s++;
|
|
}
|
|
c = mb_cptr2char_adv((const char_u **)&s);
|
|
if (cv == Ctrl_V || c == ESC || c == Ctrl_C
|
|
|| c == CAR || c == NL || c == Ctrl_L
|
|
|| (c == Ctrl_BSL && *s == Ctrl_N)) {
|
|
stuffcharReadbuff(Ctrl_V);
|
|
}
|
|
stuffcharReadbuff(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Delete characters on the command line, from "from" to the current position.
|
|
static void cmdline_del(int from)
|
|
{
|
|
assert(ccline.cmdpos <= ccline.cmdlen);
|
|
memmove(ccline.cmdbuff + from, ccline.cmdbuff + ccline.cmdpos,
|
|
(size_t)ccline.cmdlen - (size_t)ccline.cmdpos + 1);
|
|
ccline.cmdlen -= ccline.cmdpos - from;
|
|
ccline.cmdpos = from;
|
|
}
|
|
|
|
// This function is called when the screen size changes and with incremental
|
|
// search and in other situations where the command line may have been
|
|
// overwritten.
|
|
void redrawcmdline(void)
|
|
{
|
|
if (cmd_silent) {
|
|
return;
|
|
}
|
|
need_wait_return = false;
|
|
compute_cmdrow();
|
|
redrawcmd();
|
|
cursorcmd();
|
|
ui_cursor_shape();
|
|
}
|
|
|
|
static void redrawcmdprompt(void)
|
|
{
|
|
int i;
|
|
|
|
if (cmd_silent) {
|
|
return;
|
|
}
|
|
if (ui_has(kUICmdline)) {
|
|
ccline.redraw_state = kCmdRedrawAll;
|
|
return;
|
|
}
|
|
if (ccline.cmdfirstc != NUL) {
|
|
msg_putchar(ccline.cmdfirstc);
|
|
}
|
|
if (ccline.cmdprompt != NULL) {
|
|
msg_puts_attr((const char *)ccline.cmdprompt, ccline.cmdattr);
|
|
ccline.cmdindent = msg_col + (msg_row - cmdline_row) * Columns;
|
|
// do the reverse of cmd_startcol()
|
|
if (ccline.cmdfirstc != NUL) {
|
|
ccline.cmdindent--;
|
|
}
|
|
} else {
|
|
for (i = ccline.cmdindent; i > 0; i--) {
|
|
msg_putchar(' ');
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Redraw what is currently on the command line.
|
|
*/
|
|
void redrawcmd(void)
|
|
{
|
|
if (cmd_silent) {
|
|
return;
|
|
}
|
|
|
|
if (ui_has(kUICmdline)) {
|
|
draw_cmdline(0, ccline.cmdlen);
|
|
return;
|
|
}
|
|
|
|
// when 'incsearch' is set there may be no command line while redrawing
|
|
if (ccline.cmdbuff == NULL) {
|
|
cmd_cursor_goto(cmdline_row, 0);
|
|
msg_clr_eos();
|
|
return;
|
|
}
|
|
|
|
redrawing_cmdline = true;
|
|
|
|
msg_start();
|
|
redrawcmdprompt();
|
|
|
|
// Don't use more prompt, truncate the cmdline if it doesn't fit.
|
|
msg_no_more = TRUE;
|
|
draw_cmdline(0, ccline.cmdlen);
|
|
msg_clr_eos();
|
|
msg_no_more = FALSE;
|
|
|
|
ccline.cmdspos = cmd_screencol(ccline.cmdpos);
|
|
|
|
if (ccline.special_char != NUL) {
|
|
putcmdline(ccline.special_char, ccline.special_shift);
|
|
}
|
|
|
|
/*
|
|
* An emsg() before may have set msg_scroll. This is used in normal mode,
|
|
* in cmdline mode we can reset them now.
|
|
*/
|
|
msg_scroll = FALSE; // next message overwrites cmdline
|
|
|
|
// Typing ':' at the more prompt may set skip_redraw. We don't want this
|
|
// in cmdline mode.
|
|
skip_redraw = false;
|
|
|
|
redrawing_cmdline = false;
|
|
}
|
|
|
|
void compute_cmdrow(void)
|
|
{
|
|
if (exmode_active || msg_scrolled != 0) {
|
|
cmdline_row = Rows - 1;
|
|
} else {
|
|
win_T *wp = lastwin_nofloating();
|
|
cmdline_row = wp->w_winrow + wp->w_height
|
|
+ wp->w_status_height;
|
|
}
|
|
lines_left = cmdline_row;
|
|
}
|
|
|
|
static void cursorcmd(void)
|
|
{
|
|
if (cmd_silent) {
|
|
return;
|
|
}
|
|
|
|
if (ui_has(kUICmdline)) {
|
|
if (ccline.redraw_state < kCmdRedrawPos) {
|
|
ccline.redraw_state = kCmdRedrawPos;
|
|
}
|
|
setcursor();
|
|
return;
|
|
}
|
|
|
|
if (cmdmsg_rl) {
|
|
msg_row = cmdline_row + (ccline.cmdspos / (Columns - 1));
|
|
msg_col = Columns - (ccline.cmdspos % (Columns - 1)) - 1;
|
|
if (msg_row <= 0) {
|
|
msg_row = Rows - 1;
|
|
}
|
|
} else {
|
|
msg_row = cmdline_row + (ccline.cmdspos / Columns);
|
|
msg_col = ccline.cmdspos % Columns;
|
|
if (msg_row >= Rows) {
|
|
msg_row = Rows - 1;
|
|
}
|
|
}
|
|
|
|
cmd_cursor_goto(msg_row, msg_col);
|
|
}
|
|
|
|
static void cmd_cursor_goto(int row, int col)
|
|
{
|
|
ScreenGrid *grid = &msg_grid_adj;
|
|
screen_adjust_grid(&grid, &row, &col);
|
|
ui_grid_cursor_goto(grid->handle, row, col);
|
|
}
|
|
|
|
void gotocmdline(bool clr)
|
|
{
|
|
if (ui_has(kUICmdline)) {
|
|
return;
|
|
}
|
|
msg_start();
|
|
if (cmdmsg_rl) {
|
|
msg_col = Columns - 1;
|
|
} else {
|
|
msg_col = 0; // always start in column 0
|
|
}
|
|
if (clr) { // clear the bottom line(s)
|
|
msg_clr_eos(); // will reset clear_cmdline
|
|
}
|
|
cmd_cursor_goto(cmdline_row, 0);
|
|
}
|
|
|
|
/*
|
|
* Check the word in front of the cursor for an abbreviation.
|
|
* Called when the non-id character "c" has been entered.
|
|
* When an abbreviation is recognized it is removed from the text with
|
|
* backspaces and the replacement string is inserted, followed by "c".
|
|
*/
|
|
static int ccheck_abbr(int c)
|
|
{
|
|
int spos = 0;
|
|
|
|
if (p_paste || no_abbr) { // no abbreviations or in paste mode
|
|
return false;
|
|
}
|
|
|
|
// Do not consider '<,'> be part of the mapping, skip leading whitespace.
|
|
// Actually accepts any mark.
|
|
while (spos < ccline.cmdlen && ascii_iswhite(ccline.cmdbuff[spos])) {
|
|
spos++;
|
|
}
|
|
if (ccline.cmdlen - spos > 5
|
|
&& ccline.cmdbuff[spos] == '\''
|
|
&& ccline.cmdbuff[spos + 2] == ','
|
|
&& ccline.cmdbuff[spos + 3] == '\'') {
|
|
spos += 5;
|
|
} else {
|
|
// check abbreviation from the beginning of the commandline
|
|
spos = 0;
|
|
}
|
|
|
|
return check_abbr(c, ccline.cmdbuff, ccline.cmdpos, spos);
|
|
}
|
|
|
|
static int sort_func_compare(const void *s1, const void *s2)
|
|
{
|
|
char_u *p1 = *(char_u **)s1;
|
|
char_u *p2 = *(char_u **)s2;
|
|
|
|
if (*p1 != '<' && *p2 == '<') {
|
|
return -1;
|
|
}
|
|
if (*p1 == '<' && *p2 != '<') {
|
|
return 1;
|
|
}
|
|
return STRCMP(p1, p2);
|
|
}
|
|
|
|
/// Return FAIL if this is not an appropriate context in which to do
|
|
/// completion of anything, return OK if it is (even if there are no matches).
|
|
/// For the caller, this means that the character is just passed through like a
|
|
/// normal character (instead of being expanded). This allows :s/^I^D etc.
|
|
///
|
|
/// @param options extra options for ExpandOne()
|
|
/// @param escape if TRUE, escape the returned matches
|
|
static int nextwild(expand_T *xp, int type, int options, int escape)
|
|
{
|
|
int i, j;
|
|
char_u *p1;
|
|
char_u *p2;
|
|
int difflen;
|
|
|
|
if (xp->xp_numfiles == -1) {
|
|
set_expand_context(xp);
|
|
cmd_showtail = expand_showtail(xp);
|
|
}
|
|
|
|
if (xp->xp_context == EXPAND_UNSUCCESSFUL) {
|
|
beep_flush();
|
|
return OK; // Something illegal on command line
|
|
}
|
|
if (xp->xp_context == EXPAND_NOTHING) {
|
|
// Caller can use the character as a normal char instead
|
|
return FAIL;
|
|
}
|
|
|
|
if (!(ui_has(kUICmdline) || ui_has(kUIWildmenu))) {
|
|
msg_puts("..."); // show that we are busy
|
|
ui_flush();
|
|
}
|
|
|
|
i = (int)(xp->xp_pattern - ccline.cmdbuff);
|
|
assert(ccline.cmdpos >= i);
|
|
xp->xp_pattern_len = (size_t)ccline.cmdpos - (size_t)i;
|
|
|
|
if (type == WILD_NEXT || type == WILD_PREV) {
|
|
// Get next/previous match for a previous expanded pattern.
|
|
p2 = ExpandOne(xp, NULL, NULL, 0, type);
|
|
} else {
|
|
// Translate string into pattern and expand it.
|
|
p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
|
|
const int use_options = (
|
|
options
|
|
| WILD_HOME_REPLACE
|
|
| WILD_ADD_SLASH
|
|
| WILD_SILENT
|
|
| (escape ? WILD_ESCAPE : 0)
|
|
| (p_wic ? WILD_ICASE : 0));
|
|
p2 = ExpandOne(xp, p1, vim_strnsave(&ccline.cmdbuff[i], xp->xp_pattern_len),
|
|
use_options, type);
|
|
xfree(p1);
|
|
|
|
// xp->xp_pattern might have been modified by ExpandOne (for example,
|
|
// in lua completion), so recompute the pattern index and length
|
|
i = (int)(xp->xp_pattern - ccline.cmdbuff);
|
|
xp->xp_pattern_len = (size_t)ccline.cmdpos - (size_t)i;
|
|
|
|
// Longest match: make sure it is not shorter, happens with :help.
|
|
if (p2 != NULL && type == WILD_LONGEST) {
|
|
for (j = 0; (size_t)j < xp->xp_pattern_len; j++) {
|
|
if (ccline.cmdbuff[i + j] == '*'
|
|
|| ccline.cmdbuff[i + j] == '?') {
|
|
break;
|
|
}
|
|
}
|
|
if ((int)STRLEN(p2) < j) {
|
|
XFREE_CLEAR(p2);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p2 != NULL && !got_int) {
|
|
difflen = (int)STRLEN(p2) - (int)(xp->xp_pattern_len);
|
|
if (ccline.cmdlen + difflen + 4 > ccline.cmdbufflen) {
|
|
realloc_cmdbuff(ccline.cmdlen + difflen + 4);
|
|
xp->xp_pattern = ccline.cmdbuff + i;
|
|
}
|
|
assert(ccline.cmdpos <= ccline.cmdlen);
|
|
memmove(&ccline.cmdbuff[ccline.cmdpos + difflen],
|
|
&ccline.cmdbuff[ccline.cmdpos],
|
|
(size_t)ccline.cmdlen - (size_t)ccline.cmdpos + 1);
|
|
memmove(&ccline.cmdbuff[i], p2, STRLEN(p2));
|
|
ccline.cmdlen += difflen;
|
|
ccline.cmdpos += difflen;
|
|
}
|
|
xfree(p2);
|
|
|
|
redrawcmd();
|
|
cursorcmd();
|
|
|
|
/* When expanding a ":map" command and no matches are found, assume that
|
|
* the key is supposed to be inserted literally */
|
|
if (xp->xp_context == EXPAND_MAPPINGS && p2 == NULL) {
|
|
return FAIL;
|
|
}
|
|
|
|
if (xp->xp_numfiles <= 0 && p2 == NULL) {
|
|
beep_flush();
|
|
} else if (xp->xp_numfiles == 1) {
|
|
// free expanded pattern
|
|
(void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/// Do wildcard expansion on the string 'str'.
|
|
/// Chars that should not be expanded must be preceded with a backslash.
|
|
/// Return a pointer to allocated memory containing the new string.
|
|
/// Return NULL for failure.
|
|
///
|
|
/// "orig" is the originally expanded string, copied to allocated memory. It
|
|
/// should either be kept in orig_save or freed. When "mode" is WILD_NEXT or
|
|
/// WILD_PREV "orig" should be NULL.
|
|
///
|
|
/// Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode"
|
|
/// is WILD_EXPAND_FREE or WILD_ALL.
|
|
///
|
|
/// mode = WILD_FREE: just free previously expanded matches
|
|
/// mode = WILD_EXPAND_FREE: normal expansion, do not keep matches
|
|
/// mode = WILD_EXPAND_KEEP: normal expansion, keep matches
|
|
/// mode = WILD_NEXT: use next match in multiple match, wrap to first
|
|
/// mode = WILD_PREV: use previous match in multiple match, wrap to first
|
|
/// mode = WILD_ALL: return all matches concatenated
|
|
/// mode = WILD_LONGEST: return longest matched part
|
|
/// mode = WILD_ALL_KEEP: get all matches, keep matches
|
|
///
|
|
/// options = WILD_LIST_NOTFOUND: list entries without a match
|
|
/// options = WILD_HOME_REPLACE: do home_replace() for buffer names
|
|
/// options = WILD_USE_NL: Use '\n' for WILD_ALL
|
|
/// options = WILD_NO_BEEP: Don't beep for multiple matches
|
|
/// options = WILD_ADD_SLASH: add a slash after directory names
|
|
/// options = WILD_KEEP_ALL: don't remove 'wildignore' entries
|
|
/// options = WILD_SILENT: don't print warning messages
|
|
/// options = WILD_ESCAPE: put backslash before special chars
|
|
/// options = WILD_ICASE: ignore case for files
|
|
///
|
|
/// The variables xp->xp_context and xp->xp_backslash must have been set!
|
|
///
|
|
/// @param orig allocated copy of original of expanded string
|
|
char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode)
|
|
{
|
|
char_u *ss = NULL;
|
|
static int findex;
|
|
static char_u *orig_save = NULL; // kept value of orig
|
|
int orig_saved = FALSE;
|
|
int i;
|
|
int non_suf_match; // number without matching suffix
|
|
|
|
/*
|
|
* first handle the case of using an old match
|
|
*/
|
|
if (mode == WILD_NEXT || mode == WILD_PREV) {
|
|
if (xp->xp_numfiles > 0) {
|
|
if (mode == WILD_PREV) {
|
|
if (findex == -1) {
|
|
findex = xp->xp_numfiles;
|
|
}
|
|
--findex;
|
|
} else { // mode == WILD_NEXT
|
|
++findex;
|
|
}
|
|
|
|
/*
|
|
* When wrapping around, return the original string, set findex to
|
|
* -1.
|
|
*/
|
|
if (findex < 0) {
|
|
if (orig_save == NULL) {
|
|
findex = xp->xp_numfiles - 1;
|
|
} else {
|
|
findex = -1;
|
|
}
|
|
}
|
|
if (findex >= xp->xp_numfiles) {
|
|
if (orig_save == NULL) {
|
|
findex = 0;
|
|
} else {
|
|
findex = -1;
|
|
}
|
|
}
|
|
if (compl_match_array) {
|
|
compl_selected = findex;
|
|
cmdline_pum_display(false);
|
|
} else if (p_wmnu) {
|
|
win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files,
|
|
findex, cmd_showtail);
|
|
}
|
|
if (findex == -1) {
|
|
return vim_strsave(orig_save);
|
|
}
|
|
return vim_strsave(xp->xp_files[findex]);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (mode == WILD_CANCEL) {
|
|
ss = vim_strsave(orig_save ? orig_save : (char_u *)"");
|
|
} else if (mode == WILD_APPLY) {
|
|
ss = vim_strsave(findex == -1 ? (orig_save ? orig_save : (char_u *)"") :
|
|
xp->xp_files[findex]);
|
|
}
|
|
|
|
// free old names
|
|
if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) {
|
|
FreeWild(xp->xp_numfiles, xp->xp_files);
|
|
xp->xp_numfiles = -1;
|
|
XFREE_CLEAR(orig_save);
|
|
}
|
|
findex = 0;
|
|
|
|
if (mode == WILD_FREE) { // only release file name
|
|
return NULL;
|
|
}
|
|
|
|
if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) {
|
|
xfree(orig_save);
|
|
orig_save = orig;
|
|
orig_saved = TRUE;
|
|
|
|
/*
|
|
* Do the expansion.
|
|
*/
|
|
if (ExpandFromContext(xp, str, &xp->xp_numfiles, &xp->xp_files,
|
|
options) == FAIL) {
|
|
#ifdef FNAME_ILLEGAL
|
|
/* Illegal file name has been silently skipped. But when there
|
|
* are wildcards, the real problem is that there was no match,
|
|
* causing the pattern to be added, which has illegal characters.
|
|
*/
|
|
if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) {
|
|
semsg(_(e_nomatch2), str);
|
|
}
|
|
#endif
|
|
} else if (xp->xp_numfiles == 0) {
|
|
if (!(options & WILD_SILENT)) {
|
|
semsg(_(e_nomatch2), str);
|
|
}
|
|
} else {
|
|
// Escape the matches for use on the command line.
|
|
ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options);
|
|
|
|
/*
|
|
* Check for matching suffixes in file names.
|
|
*/
|
|
if (mode != WILD_ALL && mode != WILD_ALL_KEEP
|
|
&& mode != WILD_LONGEST) {
|
|
if (xp->xp_numfiles) {
|
|
non_suf_match = xp->xp_numfiles;
|
|
} else {
|
|
non_suf_match = 1;
|
|
}
|
|
if ((xp->xp_context == EXPAND_FILES
|
|
|| xp->xp_context == EXPAND_DIRECTORIES)
|
|
&& xp->xp_numfiles > 1) {
|
|
/*
|
|
* More than one match; check suffix.
|
|
* The files will have been sorted on matching suffix in
|
|
* expand_wildcards, only need to check the first two.
|
|
*/
|
|
non_suf_match = 0;
|
|
for (i = 0; i < 2; ++i) {
|
|
if (match_suffix(xp->xp_files[i])) {
|
|
++non_suf_match;
|
|
}
|
|
}
|
|
}
|
|
if (non_suf_match != 1) {
|
|
/* Can we ever get here unless it's while expanding
|
|
* interactively? If not, we can get rid of this all
|
|
* together. Don't really want to wait for this message
|
|
* (and possibly have to hit return to continue!).
|
|
*/
|
|
if (!(options & WILD_SILENT)) {
|
|
emsg(_(e_toomany));
|
|
} else if (!(options & WILD_NO_BEEP)) {
|
|
beep_flush();
|
|
}
|
|
}
|
|
if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) {
|
|
ss = vim_strsave(xp->xp_files[0]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find longest common part
|
|
if (mode == WILD_LONGEST && xp->xp_numfiles > 0) {
|
|
size_t len = 0;
|
|
|
|
for (size_t mb_len; xp->xp_files[0][len]; len += mb_len) {
|
|
mb_len = (size_t)utfc_ptr2len(&xp->xp_files[0][len]);
|
|
int c0 = utf_ptr2char(&xp->xp_files[0][len]);
|
|
for (i = 1; i < xp->xp_numfiles; i++) {
|
|
int ci = utf_ptr2char(&xp->xp_files[i][len]);
|
|
|
|
if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES
|
|
|| xp->xp_context == EXPAND_FILES
|
|
|| xp->xp_context == EXPAND_SHELLCMD
|
|
|| xp->xp_context == EXPAND_BUFFERS)) {
|
|
if (mb_tolower(c0) != mb_tolower(ci)) {
|
|
break;
|
|
}
|
|
} else if (c0 != ci) {
|
|
break;
|
|
}
|
|
}
|
|
if (i < xp->xp_numfiles) {
|
|
if (!(options & WILD_NO_BEEP)) {
|
|
vim_beep(BO_WILD);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
ss = (char_u *)xstrndup((char *)xp->xp_files[0], len);
|
|
findex = -1; // next p_wc gets first one
|
|
}
|
|
|
|
// Concatenate all matching names
|
|
// TODO(philix): use xstpcpy instead of strcat in a loop (ExpandOne)
|
|
if (mode == WILD_ALL && xp->xp_numfiles > 0) {
|
|
size_t len = 0;
|
|
for (i = 0; i < xp->xp_numfiles; ++i) {
|
|
len += STRLEN(xp->xp_files[i]) + 1;
|
|
}
|
|
ss = xmalloc(len);
|
|
*ss = NUL;
|
|
for (i = 0; i < xp->xp_numfiles; ++i) {
|
|
STRCAT(ss, xp->xp_files[i]);
|
|
if (i != xp->xp_numfiles - 1) {
|
|
STRCAT(ss, (options & WILD_USE_NL) ? "\n" : " ");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) {
|
|
ExpandCleanup(xp);
|
|
}
|
|
|
|
// Free "orig" if it wasn't stored in "orig_save".
|
|
if (!orig_saved) {
|
|
xfree(orig);
|
|
}
|
|
|
|
return ss;
|
|
}
|
|
|
|
/*
|
|
* Prepare an expand structure for use.
|
|
*/
|
|
void ExpandInit(expand_T *xp)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
CLEAR_POINTER(xp);
|
|
xp->xp_backslash = XP_BS_NONE;
|
|
xp->xp_numfiles = -1;
|
|
}
|
|
|
|
/*
|
|
* Cleanup an expand structure after use.
|
|
*/
|
|
void ExpandCleanup(expand_T *xp)
|
|
{
|
|
if (xp->xp_numfiles >= 0) {
|
|
FreeWild(xp->xp_numfiles, xp->xp_files);
|
|
xp->xp_numfiles = -1;
|
|
}
|
|
}
|
|
|
|
void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char_u **files, int options)
|
|
{
|
|
int i;
|
|
char_u *p;
|
|
|
|
/*
|
|
* May change home directory back to "~"
|
|
*/
|
|
if (options & WILD_HOME_REPLACE) {
|
|
tilde_replace(str, numfiles, files);
|
|
}
|
|
|
|
if (options & WILD_ESCAPE) {
|
|
if (xp->xp_context == EXPAND_FILES
|
|
|| xp->xp_context == EXPAND_FILES_IN_PATH
|
|
|| xp->xp_context == EXPAND_SHELLCMD
|
|
|| xp->xp_context == EXPAND_BUFFERS
|
|
|| xp->xp_context == EXPAND_DIRECTORIES) {
|
|
/*
|
|
* Insert a backslash into a file name before a space, \, %, #
|
|
* and wildmatch characters, except '~'.
|
|
*/
|
|
for (i = 0; i < numfiles; ++i) {
|
|
// for ":set path=" we need to escape spaces twice
|
|
if (xp->xp_backslash == XP_BS_THREE) {
|
|
p = vim_strsave_escaped(files[i], (char_u *)" ");
|
|
xfree(files[i]);
|
|
files[i] = p;
|
|
#if defined(BACKSLASH_IN_FILENAME)
|
|
p = vim_strsave_escaped(files[i], (char_u *)" ");
|
|
xfree(files[i]);
|
|
files[i] = p;
|
|
#endif
|
|
}
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
p = (char_u *)vim_strsave_fnameescape((const char *)files[i], false);
|
|
#else
|
|
p = (char_u *)vim_strsave_fnameescape((const char *)files[i],
|
|
xp->xp_shell);
|
|
#endif
|
|
xfree(files[i]);
|
|
files[i] = p;
|
|
|
|
/* If 'str' starts with "\~", replace "~" at start of
|
|
* files[i] with "\~". */
|
|
if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') {
|
|
escape_fname(&files[i]);
|
|
}
|
|
}
|
|
xp->xp_backslash = XP_BS_NONE;
|
|
|
|
/* If the first file starts with a '+' escape it. Otherwise it
|
|
* could be seen as "+cmd". */
|
|
if (*files[0] == '+') {
|
|
escape_fname(&files[0]);
|
|
}
|
|
} else if (xp->xp_context == EXPAND_TAGS) {
|
|
/*
|
|
* Insert a backslash before characters in a tag name that
|
|
* would terminate the ":tag" command.
|
|
*/
|
|
for (i = 0; i < numfiles; ++i) {
|
|
p = vim_strsave_escaped(files[i], (char_u *)"\\|\"");
|
|
xfree(files[i]);
|
|
files[i] = p;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Escape special characters in a file name for use as a command argument
|
|
///
|
|
/// @param[in] fname File name to escape.
|
|
/// @param[in] shell What to escape for: if false, escapes for VimL command,
|
|
/// if true then it escapes for a shell command.
|
|
///
|
|
/// @return [allocated] escaped file name.
|
|
char *vim_strsave_fnameescape(const char *const fname, const bool shell FUNC_ATTR_UNUSED)
|
|
FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
# define PATH_ESC_CHARS " \t\n*?[{`%#'\"|!<"
|
|
char_u buf[sizeof(PATH_ESC_CHARS)];
|
|
int j = 0;
|
|
|
|
// Don't escape '[', '{' and '!' if they are in 'isfname'.
|
|
for (const char *s = PATH_ESC_CHARS; *s != NUL; s++) {
|
|
if ((*s != '[' && *s != '{' && *s != '!') || !vim_isfilec(*s)) {
|
|
buf[j++] = *s;
|
|
}
|
|
}
|
|
buf[j] = NUL;
|
|
char *p = (char *)vim_strsave_escaped((const char_u *)fname,
|
|
(const char_u *)buf);
|
|
#else
|
|
# define PATH_ESC_CHARS ((char_u *)" \t\n*?[{`$\\%#'\"|!<")
|
|
# define SHELL_ESC_CHARS ((char_u *)" \t\n*?[{`$\\%#'\"|!<>();&")
|
|
char *p =
|
|
(char *)vim_strsave_escaped((const char_u *)fname, (shell ? SHELL_ESC_CHARS : PATH_ESC_CHARS));
|
|
if (shell && csh_like_shell()) {
|
|
// For csh and similar shells need to put two backslashes before '!'.
|
|
// One is taken by Vim, one by the shell.
|
|
char *s = (char *)vim_strsave_escaped((const char_u *)p,
|
|
(const char_u *)"!");
|
|
xfree(p);
|
|
p = s;
|
|
}
|
|
#endif
|
|
|
|
// '>' and '+' are special at the start of some commands, e.g. ":edit" and
|
|
// ":write". "cd -" has a special meaning.
|
|
if (*p == '>' || *p == '+' || (*p == '-' && p[1] == NUL)) {
|
|
escape_fname((char_u **)&p);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
/*
|
|
* Put a backslash before the file name in "pp", which is in allocated memory.
|
|
*/
|
|
static void escape_fname(char_u **pp)
|
|
{
|
|
char_u *p = xmalloc(STRLEN(*pp) + 2);
|
|
p[0] = '\\';
|
|
STRCPY(p + 1, *pp);
|
|
xfree(*pp);
|
|
*pp = p;
|
|
}
|
|
|
|
/*
|
|
* For each file name in files[num_files]:
|
|
* If 'orig_pat' starts with "~/", replace the home directory with "~".
|
|
*/
|
|
void tilde_replace(char_u *orig_pat, int num_files, char_u **files)
|
|
{
|
|
int i;
|
|
char_u *p;
|
|
|
|
if (orig_pat[0] == '~' && vim_ispathsep(orig_pat[1])) {
|
|
for (i = 0; i < num_files; ++i) {
|
|
p = home_replace_save(NULL, files[i]);
|
|
xfree(files[i]);
|
|
files[i] = p;
|
|
}
|
|
}
|
|
}
|
|
|
|
void cmdline_pum_display(bool changed_array)
|
|
{
|
|
pum_display(compl_match_array, compl_match_arraysize, compl_selected,
|
|
changed_array, compl_startcol);
|
|
}
|
|
|
|
/*
|
|
* Show all matches for completion on the command line.
|
|
* Returns EXPAND_NOTHING when the character that triggered expansion should
|
|
* be inserted like a normal character.
|
|
*/
|
|
static int showmatches(expand_T *xp, int wildmenu)
|
|
{
|
|
#define L_SHOWFILE(m) (showtail \
|
|
? sm_gettail(files_found[m], false) : files_found[m])
|
|
int num_files;
|
|
char_u **files_found;
|
|
int i, j, k;
|
|
int maxlen;
|
|
int lines;
|
|
int columns;
|
|
char_u *p;
|
|
int lastlen;
|
|
int attr;
|
|
int showtail;
|
|
|
|
if (xp->xp_numfiles == -1) {
|
|
set_expand_context(xp);
|
|
i = expand_cmdline(xp, ccline.cmdbuff, ccline.cmdpos,
|
|
&num_files, &files_found);
|
|
showtail = expand_showtail(xp);
|
|
if (i != EXPAND_OK) {
|
|
return i;
|
|
}
|
|
} else {
|
|
num_files = xp->xp_numfiles;
|
|
files_found = xp->xp_files;
|
|
showtail = cmd_showtail;
|
|
}
|
|
|
|
bool compl_use_pum = (ui_has(kUICmdline)
|
|
? ui_has(kUIPopupmenu)
|
|
: wildmenu && (wop_flags & WOP_PUM))
|
|
|| ui_has(kUIWildmenu);
|
|
|
|
if (compl_use_pum) {
|
|
assert(num_files >= 0);
|
|
compl_match_arraysize = num_files;
|
|
compl_match_array = xcalloc((size_t)compl_match_arraysize,
|
|
sizeof(pumitem_T));
|
|
for (i = 0; i < num_files; i++) {
|
|
compl_match_array[i].pum_text = L_SHOWFILE(i);
|
|
}
|
|
char_u *endpos = (showtail
|
|
? sm_gettail(xp->xp_pattern, true) : xp->xp_pattern);
|
|
if (ui_has(kUICmdline)) {
|
|
compl_startcol = (int)(endpos - ccline.cmdbuff);
|
|
} else {
|
|
compl_startcol = cmd_screencol((int)(endpos - ccline.cmdbuff));
|
|
}
|
|
compl_selected = -1;
|
|
cmdline_pum_display(true);
|
|
return EXPAND_OK;
|
|
}
|
|
|
|
if (!wildmenu) {
|
|
msg_didany = false; // lines_left will be set
|
|
msg_start(); // prepare for paging
|
|
msg_putchar('\n');
|
|
ui_flush();
|
|
cmdline_row = msg_row;
|
|
msg_didany = false; // lines_left will be set again
|
|
msg_start(); // prepare for paging
|
|
}
|
|
|
|
if (got_int) {
|
|
got_int = false; // only int. the completion, not the cmd line
|
|
} else if (wildmenu) {
|
|
win_redr_status_matches(xp, num_files, files_found, -1, showtail);
|
|
} else {
|
|
// find the length of the longest file name
|
|
maxlen = 0;
|
|
for (i = 0; i < num_files; ++i) {
|
|
if (!showtail && (xp->xp_context == EXPAND_FILES
|
|
|| xp->xp_context == EXPAND_SHELLCMD
|
|
|| xp->xp_context == EXPAND_BUFFERS)) {
|
|
home_replace(NULL, files_found[i], NameBuff, MAXPATHL, TRUE);
|
|
j = vim_strsize(NameBuff);
|
|
} else {
|
|
j = vim_strsize(L_SHOWFILE(i));
|
|
}
|
|
if (j > maxlen) {
|
|
maxlen = j;
|
|
}
|
|
}
|
|
|
|
if (xp->xp_context == EXPAND_TAGS_LISTFILES) {
|
|
lines = num_files;
|
|
} else {
|
|
// compute the number of columns and lines for the listing
|
|
maxlen += 2; // two spaces between file names
|
|
columns = (Columns + 2) / maxlen;
|
|
if (columns < 1) {
|
|
columns = 1;
|
|
}
|
|
lines = (num_files + columns - 1) / columns;
|
|
}
|
|
|
|
attr = HL_ATTR(HLF_D); // find out highlighting for directories
|
|
|
|
if (xp->xp_context == EXPAND_TAGS_LISTFILES) {
|
|
msg_puts_attr(_("tagname"), HL_ATTR(HLF_T));
|
|
msg_clr_eos();
|
|
msg_advance(maxlen - 3);
|
|
msg_puts_attr(_(" kind file\n"), HL_ATTR(HLF_T));
|
|
}
|
|
|
|
// list the files line by line
|
|
for (i = 0; i < lines; ++i) {
|
|
lastlen = 999;
|
|
for (k = i; k < num_files; k += lines) {
|
|
if (xp->xp_context == EXPAND_TAGS_LISTFILES) {
|
|
msg_outtrans_attr(files_found[k], HL_ATTR(HLF_D));
|
|
p = files_found[k] + STRLEN(files_found[k]) + 1;
|
|
msg_advance(maxlen + 1);
|
|
msg_puts((const char *)p);
|
|
msg_advance(maxlen + 3);
|
|
msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D));
|
|
break;
|
|
}
|
|
for (j = maxlen - lastlen; --j >= 0; ) {
|
|
msg_putchar(' ');
|
|
}
|
|
if (xp->xp_context == EXPAND_FILES
|
|
|| xp->xp_context == EXPAND_SHELLCMD
|
|
|| xp->xp_context == EXPAND_BUFFERS) {
|
|
// highlight directories
|
|
if (xp->xp_numfiles != -1) {
|
|
// Expansion was done before and special characters
|
|
// were escaped, need to halve backslashes. Also
|
|
// $HOME has been replaced with ~/.
|
|
char_u *exp_path = expand_env_save_opt(files_found[k], true);
|
|
char_u *path = exp_path != NULL ? exp_path : files_found[k];
|
|
char_u *halved_slash = backslash_halve_save(path);
|
|
j = os_isdir(halved_slash);
|
|
xfree(exp_path);
|
|
if (halved_slash != path) {
|
|
xfree(halved_slash);
|
|
}
|
|
} else {
|
|
// Expansion was done here, file names are literal.
|
|
j = os_isdir(files_found[k]);
|
|
}
|
|
if (showtail) {
|
|
p = L_SHOWFILE(k);
|
|
} else {
|
|
home_replace(NULL, files_found[k], NameBuff, MAXPATHL,
|
|
TRUE);
|
|
p = NameBuff;
|
|
}
|
|
} else {
|
|
j = FALSE;
|
|
p = L_SHOWFILE(k);
|
|
}
|
|
lastlen = msg_outtrans_attr(p, j ? attr : 0);
|
|
}
|
|
if (msg_col > 0) { // when not wrapped around
|
|
msg_clr_eos();
|
|
msg_putchar('\n');
|
|
}
|
|
ui_flush(); // show one line at a time
|
|
if (got_int) {
|
|
got_int = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* we redraw the command below the lines that we have just listed
|
|
* This is a bit tricky, but it saves a lot of screen updating.
|
|
*/
|
|
cmdline_row = msg_row; // will put it back later
|
|
}
|
|
|
|
if (xp->xp_numfiles == -1) {
|
|
FreeWild(num_files, files_found);
|
|
}
|
|
|
|
return EXPAND_OK;
|
|
}
|
|
|
|
/*
|
|
* Private path_tail for showmatches() (and win_redr_status_matches()):
|
|
* Find tail of file name path, but ignore trailing "/".
|
|
*/
|
|
char_u *sm_gettail(char_u *s, bool eager)
|
|
{
|
|
char_u *p;
|
|
char_u *t = s;
|
|
int had_sep = FALSE;
|
|
|
|
for (p = s; *p != NUL; ) {
|
|
if (vim_ispathsep(*p)
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
&& !rem_backslash(p)
|
|
#endif
|
|
) {
|
|
if (eager) {
|
|
t = p+1;
|
|
} else {
|
|
had_sep = true;
|
|
}
|
|
} else if (had_sep) {
|
|
t = p;
|
|
had_sep = FALSE;
|
|
}
|
|
MB_PTR_ADV(p);
|
|
}
|
|
return t;
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if we only need to show the tail of completion matches.
|
|
* When not completing file names or there is a wildcard in the path FALSE is
|
|
* returned.
|
|
*/
|
|
static int expand_showtail(expand_T *xp)
|
|
{
|
|
char_u *s;
|
|
char_u *end;
|
|
|
|
// When not completing file names a "/" may mean something different.
|
|
if (xp->xp_context != EXPAND_FILES
|
|
&& xp->xp_context != EXPAND_SHELLCMD
|
|
&& xp->xp_context != EXPAND_DIRECTORIES) {
|
|
return FALSE;
|
|
}
|
|
|
|
end = path_tail(xp->xp_pattern);
|
|
if (end == xp->xp_pattern) { // there is no path separator
|
|
return FALSE;
|
|
}
|
|
|
|
for (s = xp->xp_pattern; s < end; s++) {
|
|
/* Skip escaped wildcards. Only when the backslash is not a path
|
|
* separator, on DOS the '*' "path\*\file" must not be skipped. */
|
|
if (rem_backslash(s)) {
|
|
++s;
|
|
} else if (vim_strchr((char_u *)"*?[", *s) != NULL) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/// Prepare a string for expansion.
|
|
///
|
|
/// When expanding file names: The string will be used with expand_wildcards().
|
|
/// Copy "fname[len]" into allocated memory and add a '*' at the end.
|
|
/// When expanding other names: The string will be used with regcomp(). Copy
|
|
/// the name into allocated memory and prepend "^".
|
|
///
|
|
/// @param context EXPAND_FILES etc.
|
|
char_u *addstar(char_u *fname, size_t len, int context)
|
|
FUNC_ATTR_NONNULL_RET
|
|
{
|
|
char_u *retval;
|
|
size_t i, j;
|
|
size_t new_len;
|
|
char_u *tail;
|
|
int ends_in_star;
|
|
|
|
if (context != EXPAND_FILES
|
|
&& context != EXPAND_FILES_IN_PATH
|
|
&& context != EXPAND_SHELLCMD
|
|
&& context != EXPAND_DIRECTORIES) {
|
|
/*
|
|
* Matching will be done internally (on something other than files).
|
|
* So we convert the file-matching-type wildcards into our kind for
|
|
* use with vim_regcomp(). First work out how long it will be:
|
|
*/
|
|
|
|
// For help tags the translation is done in find_help_tags().
|
|
// For a tag pattern starting with "/" no translation is needed.
|
|
if (context == EXPAND_HELP
|
|
|| context == EXPAND_CHECKHEALTH
|
|
|| context == EXPAND_COLORS
|
|
|| context == EXPAND_COMPILER
|
|
|| context == EXPAND_OWNSYNTAX
|
|
|| context == EXPAND_FILETYPE
|
|
|| context == EXPAND_PACKADD
|
|
|| ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS)
|
|
&& fname[0] == '/')) {
|
|
retval = vim_strnsave(fname, len);
|
|
} else {
|
|
new_len = len + 2; // +2 for '^' at start, NUL at end
|
|
for (i = 0; i < len; i++) {
|
|
if (fname[i] == '*' || fname[i] == '~') {
|
|
new_len++; /* '*' needs to be replaced by ".*"
|
|
'~' needs to be replaced by "\~" */
|
|
}
|
|
// Buffer names are like file names. "." should be literal
|
|
if (context == EXPAND_BUFFERS && fname[i] == '.') {
|
|
new_len++; // "." becomes "\."
|
|
}
|
|
/* Custom expansion takes care of special things, match
|
|
* backslashes literally (perhaps also for other types?) */
|
|
if ((context == EXPAND_USER_DEFINED
|
|
|| context == EXPAND_USER_LIST) && fname[i] == '\\') {
|
|
new_len++; // '\' becomes "\\"
|
|
}
|
|
}
|
|
retval = xmalloc(new_len);
|
|
{
|
|
retval[0] = '^';
|
|
j = 1;
|
|
for (i = 0; i < len; i++, j++) {
|
|
/* Skip backslash. But why? At least keep it for custom
|
|
* expansion. */
|
|
if (context != EXPAND_USER_DEFINED
|
|
&& context != EXPAND_USER_LIST
|
|
&& fname[i] == '\\'
|
|
&& ++i == len) {
|
|
break;
|
|
}
|
|
|
|
switch (fname[i]) {
|
|
case '*':
|
|
retval[j++] = '.';
|
|
break;
|
|
case '~':
|
|
retval[j++] = '\\';
|
|
break;
|
|
case '?':
|
|
retval[j] = '.';
|
|
continue;
|
|
case '.':
|
|
if (context == EXPAND_BUFFERS) {
|
|
retval[j++] = '\\';
|
|
}
|
|
break;
|
|
case '\\':
|
|
if (context == EXPAND_USER_DEFINED
|
|
|| context == EXPAND_USER_LIST) {
|
|
retval[j++] = '\\';
|
|
}
|
|
break;
|
|
}
|
|
retval[j] = fname[i];
|
|
}
|
|
retval[j] = NUL;
|
|
}
|
|
}
|
|
} else {
|
|
retval = xmalloc(len + 4);
|
|
STRLCPY(retval, fname, len + 1);
|
|
|
|
/*
|
|
* Don't add a star to *, ~, ~user, $var or `cmd`.
|
|
* * would become **, which walks the whole tree.
|
|
* ~ would be at the start of the file name, but not the tail.
|
|
* $ could be anywhere in the tail.
|
|
* ` could be anywhere in the file name.
|
|
* When the name ends in '$' don't add a star, remove the '$'.
|
|
*/
|
|
tail = path_tail(retval);
|
|
ends_in_star = (len > 0 && retval[len - 1] == '*');
|
|
#ifndef BACKSLASH_IN_FILENAME
|
|
for (ssize_t k = (ssize_t)len - 2; k >= 0; k--) {
|
|
if (retval[k] != '\\') {
|
|
break;
|
|
}
|
|
ends_in_star = !ends_in_star;
|
|
}
|
|
#endif
|
|
if ((*retval != '~' || tail != retval)
|
|
&& !ends_in_star
|
|
&& vim_strchr(tail, '$') == NULL
|
|
&& vim_strchr(retval, '`') == NULL) {
|
|
retval[len++] = '*';
|
|
} else if (len > 0 && retval[len - 1] == '$') {
|
|
--len;
|
|
}
|
|
retval[len] = NUL;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Must parse the command line so far to work out what context we are in.
|
|
* Completion can then be done based on that context.
|
|
* This routine sets the variables:
|
|
* xp->xp_pattern The start of the pattern to be expanded within
|
|
* the command line (ends at the cursor).
|
|
* xp->xp_context The type of thing to expand. Will be one of:
|
|
*
|
|
* EXPAND_UNSUCCESSFUL Used sometimes when there is something illegal on
|
|
* the command line, like an unknown command. Caller
|
|
* should beep.
|
|
* EXPAND_NOTHING Unrecognised context for completion, use char like
|
|
* a normal char, rather than for completion. eg
|
|
* :s/^I/
|
|
* EXPAND_COMMANDS Cursor is still touching the command, so complete
|
|
* it.
|
|
* EXPAND_BUFFERS Complete file names for :buf and :sbuf commands.
|
|
* EXPAND_FILES After command with EX_XFILE set, or after setting
|
|
* with P_EXPAND set. eg :e ^I, :w>>^I
|
|
* EXPAND_DIRECTORIES In some cases this is used instead of the latter
|
|
* when we know only directories are of interest. eg
|
|
* :set dir=^I
|
|
* EXPAND_SHELLCMD After ":!cmd", ":r !cmd" or ":w !cmd".
|
|
* EXPAND_SETTINGS Complete variable names. eg :set d^I
|
|
* EXPAND_BOOL_SETTINGS Complete boolean variables only, eg :set no^I
|
|
* EXPAND_TAGS Complete tags from the files in p_tags. eg :ta a^I
|
|
* EXPAND_TAGS_LISTFILES As above, but list filenames on ^D, after :tselect
|
|
* EXPAND_HELP Complete tags from the file 'helpfile'/tags
|
|
* EXPAND_EVENTS Complete event names
|
|
* EXPAND_SYNTAX Complete :syntax command arguments
|
|
* EXPAND_HIGHLIGHT Complete highlight (syntax) group names
|
|
* EXPAND_AUGROUP Complete autocommand group names
|
|
* EXPAND_USER_VARS Complete user defined variable names, eg :unlet a^I
|
|
* EXPAND_MAPPINGS Complete mapping and abbreviation names,
|
|
* eg :unmap a^I , :cunab x^I
|
|
* EXPAND_FUNCTIONS Complete internal or user defined function names,
|
|
* eg :call sub^I
|
|
* EXPAND_USER_FUNC Complete user defined function names, eg :delf F^I
|
|
* EXPAND_EXPRESSION Complete internal or user defined function/variable
|
|
* names in expressions, eg :while s^I
|
|
* EXPAND_ENV_VARS Complete environment variable names
|
|
* EXPAND_USER Complete user names
|
|
*/
|
|
static void set_expand_context(expand_T *xp)
|
|
{
|
|
// only expansion for ':', '>' and '=' command-lines
|
|
if (ccline.cmdfirstc != ':'
|
|
&& ccline.cmdfirstc != '>' && ccline.cmdfirstc != '='
|
|
&& !ccline.input_fn) {
|
|
xp->xp_context = EXPAND_NOTHING;
|
|
return;
|
|
}
|
|
set_cmd_context(xp, ccline.cmdbuff, ccline.cmdlen, ccline.cmdpos, true);
|
|
}
|
|
|
|
/// @param str start of command line
|
|
/// @param len length of command line (excl. NUL)
|
|
/// @param col position of cursor
|
|
/// @param use_ccline use ccline for info
|
|
void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline)
|
|
{
|
|
char_u old_char = NUL;
|
|
|
|
/*
|
|
* Avoid a UMR warning from Purify, only save the character if it has been
|
|
* written before.
|
|
*/
|
|
if (col < len) {
|
|
old_char = str[col];
|
|
}
|
|
str[col] = NUL;
|
|
const char *nextcomm = (const char *)str;
|
|
|
|
if (use_ccline && ccline.cmdfirstc == '=') {
|
|
// pass CMD_SIZE because there is no real command
|
|
set_context_for_expression(xp, str, CMD_SIZE);
|
|
} else if (use_ccline && ccline.input_fn) {
|
|
xp->xp_context = ccline.xp_context;
|
|
xp->xp_pattern = ccline.cmdbuff;
|
|
xp->xp_arg = ccline.xp_arg;
|
|
} else {
|
|
while (nextcomm != NULL) {
|
|
nextcomm = set_one_cmd_context(xp, nextcomm);
|
|
}
|
|
}
|
|
|
|
/* Store the string here so that call_user_expand_func() can get to them
|
|
* easily. */
|
|
xp->xp_line = str;
|
|
xp->xp_col = col;
|
|
|
|
str[col] = old_char;
|
|
}
|
|
|
|
/// Expand the command line "str" from context "xp".
|
|
/// "xp" must have been set by set_cmd_context().
|
|
/// xp->xp_pattern points into "str", to where the text that is to be expanded
|
|
/// starts.
|
|
/// Returns EXPAND_UNSUCCESSFUL when there is something illegal before the
|
|
/// cursor.
|
|
/// Returns EXPAND_NOTHING when there is nothing to expand, might insert the
|
|
/// key that triggered expansion literally.
|
|
/// Returns EXPAND_OK otherwise.
|
|
///
|
|
/// @param str start of command line
|
|
/// @param col position of cursor
|
|
/// @param matchcount return: nr of matches
|
|
/// @param matches return: array of pointers to matches
|
|
int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char_u ***matches)
|
|
{
|
|
char_u *file_str = NULL;
|
|
int options = WILD_ADD_SLASH|WILD_SILENT;
|
|
|
|
if (xp->xp_context == EXPAND_UNSUCCESSFUL) {
|
|
beep_flush();
|
|
return EXPAND_UNSUCCESSFUL; // Something illegal on command line
|
|
}
|
|
if (xp->xp_context == EXPAND_NOTHING) {
|
|
// Caller can use the character as a normal char instead
|
|
return EXPAND_NOTHING;
|
|
}
|
|
|
|
// add star to file name, or convert to regexp if not exp. files.
|
|
assert((str + col) - xp->xp_pattern >= 0);
|
|
xp->xp_pattern_len = (size_t)((str + col) - xp->xp_pattern);
|
|
file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
|
|
|
|
if (p_wic) {
|
|
options += WILD_ICASE;
|
|
}
|
|
|
|
// find all files that match the description
|
|
if (ExpandFromContext(xp, file_str, matchcount, matches, options) == FAIL) {
|
|
*matchcount = 0;
|
|
*matches = NULL;
|
|
}
|
|
xfree(file_str);
|
|
|
|
return EXPAND_OK;
|
|
}
|
|
|
|
// Cleanup matches for help tags:
|
|
// Remove "@ab" if the top of 'helplang' is "ab" and the language of the first
|
|
// tag matches it. Otherwise remove "@en" if "en" is the only language.
|
|
static void cleanup_help_tags(int num_file, char_u **file)
|
|
{
|
|
char_u buf[4];
|
|
char_u *p = buf;
|
|
|
|
if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) {
|
|
*p++ = '@';
|
|
*p++ = p_hlg[0];
|
|
*p++ = p_hlg[1];
|
|
}
|
|
*p = NUL;
|
|
|
|
for (int i = 0; i < num_file; i++) {
|
|
int len = (int)STRLEN(file[i]) - 3;
|
|
if (len <= 0) {
|
|
continue;
|
|
}
|
|
if (STRCMP(file[i] + len, "@en") == 0) {
|
|
// Sorting on priority means the same item in another language may
|
|
// be anywhere. Search all items for a match up to the "@en".
|
|
int j;
|
|
for (j = 0; j < num_file; j++) {
|
|
if (j != i
|
|
&& (int)STRLEN(file[j]) == len + 3
|
|
&& STRNCMP(file[i], file[j], len + 1) == 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (j == num_file) {
|
|
// item only exists with @en, remove it
|
|
file[i][len] = NUL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (*buf != NUL) {
|
|
for (int i = 0; i < num_file; i++) {
|
|
int len = (int)STRLEN(file[i]) - 3;
|
|
if (len <= 0) {
|
|
continue;
|
|
}
|
|
if (STRCMP(file[i] + len, buf) == 0) {
|
|
// remove the default language
|
|
file[i][len] = NUL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
typedef char_u *(*ExpandFunc)(expand_T *, int);
|
|
|
|
/// Do the expansion based on xp->xp_context and "pat".
|
|
///
|
|
/// @param options WILD_ flags
|
|
static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char_u ***file, int options)
|
|
{
|
|
regmatch_T regmatch;
|
|
int ret;
|
|
int flags;
|
|
|
|
flags = EW_DIR; // include directories
|
|
if (options & WILD_LIST_NOTFOUND) {
|
|
flags |= EW_NOTFOUND;
|
|
}
|
|
if (options & WILD_ADD_SLASH) {
|
|
flags |= EW_ADDSLASH;
|
|
}
|
|
if (options & WILD_KEEP_ALL) {
|
|
flags |= EW_KEEPALL;
|
|
}
|
|
if (options & WILD_SILENT) {
|
|
flags |= EW_SILENT;
|
|
}
|
|
if (options & WILD_NOERROR) {
|
|
flags |= EW_NOERROR;
|
|
}
|
|
if (options & WILD_ALLLINKS) {
|
|
flags |= EW_ALLLINKS;
|
|
}
|
|
|
|
if (xp->xp_context == EXPAND_FILES
|
|
|| xp->xp_context == EXPAND_DIRECTORIES
|
|
|| xp->xp_context == EXPAND_FILES_IN_PATH) {
|
|
/*
|
|
* Expand file or directory names.
|
|
*/
|
|
int free_pat = FALSE;
|
|
int i;
|
|
|
|
// for ":set path=" and ":set tags=" halve backslashes for escaped space
|
|
if (xp->xp_backslash != XP_BS_NONE) {
|
|
free_pat = TRUE;
|
|
pat = vim_strsave(pat);
|
|
for (i = 0; pat[i]; ++i) {
|
|
if (pat[i] == '\\') {
|
|
if (xp->xp_backslash == XP_BS_THREE
|
|
&& pat[i + 1] == '\\'
|
|
&& pat[i + 2] == '\\'
|
|
&& pat[i + 3] == ' ') {
|
|
STRMOVE(pat + i, pat + i + 3);
|
|
}
|
|
if (xp->xp_backslash == XP_BS_ONE
|
|
&& pat[i + 1] == ' ') {
|
|
STRMOVE(pat + i, pat + i + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (xp->xp_context == EXPAND_FILES) {
|
|
flags |= EW_FILE;
|
|
} else if (xp->xp_context == EXPAND_FILES_IN_PATH) {
|
|
flags |= (EW_FILE | EW_PATH);
|
|
} else {
|
|
flags = (flags | EW_DIR) & ~EW_FILE;
|
|
}
|
|
if (options & WILD_ICASE) {
|
|
flags |= EW_ICASE;
|
|
}
|
|
|
|
// Expand wildcards, supporting %:h and the like.
|
|
ret = expand_wildcards_eval(&pat, num_file, file, flags);
|
|
if (free_pat) {
|
|
xfree(pat);
|
|
}
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) {
|
|
for (int i = 0; i < *num_file; i++) {
|
|
char_u *ptr = (*file)[i];
|
|
while (*ptr != NUL) {
|
|
if (p_csl[0] == 's' && *ptr == '\\') {
|
|
*ptr = '/';
|
|
} else if (p_csl[0] == 'b' && *ptr == '/') {
|
|
*ptr = '\\';
|
|
}
|
|
ptr += utfc_ptr2len(ptr);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
*file = NULL;
|
|
*num_file = 0;
|
|
if (xp->xp_context == EXPAND_HELP) {
|
|
/* With an empty argument we would get all the help tags, which is
|
|
* very slow. Get matches for "help" instead. */
|
|
if (find_help_tags(*pat == NUL ? (char_u *)"help" : pat,
|
|
num_file, file, false) == OK) {
|
|
cleanup_help_tags(*num_file, *file);
|
|
return OK;
|
|
}
|
|
return FAIL;
|
|
}
|
|
|
|
if (xp->xp_context == EXPAND_SHELLCMD) {
|
|
*file = NULL;
|
|
expand_shellcmd(pat, num_file, file, flags);
|
|
return OK;
|
|
}
|
|
if (xp->xp_context == EXPAND_OLD_SETTING) {
|
|
ExpandOldSetting(num_file, file);
|
|
return OK;
|
|
}
|
|
if (xp->xp_context == EXPAND_BUFFERS) {
|
|
return ExpandBufnames(pat, num_file, file, options);
|
|
}
|
|
if (xp->xp_context == EXPAND_DIFF_BUFFERS) {
|
|
return ExpandBufnames(pat, num_file, file, options | BUF_DIFF_FILTER);
|
|
}
|
|
if (xp->xp_context == EXPAND_TAGS
|
|
|| xp->xp_context == EXPAND_TAGS_LISTFILES) {
|
|
return expand_tags(xp->xp_context == EXPAND_TAGS, pat, num_file, file);
|
|
}
|
|
if (xp->xp_context == EXPAND_COLORS) {
|
|
char *directories[] = { "colors", NULL };
|
|
return ExpandRTDir(pat, DIP_START + DIP_OPT + DIP_LUA, num_file, file,
|
|
directories);
|
|
}
|
|
if (xp->xp_context == EXPAND_COMPILER) {
|
|
char *directories[] = { "compiler", NULL };
|
|
return ExpandRTDir(pat, DIP_LUA, num_file, file, directories);
|
|
}
|
|
if (xp->xp_context == EXPAND_OWNSYNTAX) {
|
|
char *directories[] = { "syntax", NULL };
|
|
return ExpandRTDir(pat, 0, num_file, file, directories);
|
|
}
|
|
if (xp->xp_context == EXPAND_FILETYPE) {
|
|
char *directories[] = { "syntax", "indent", "ftplugin", NULL };
|
|
return ExpandRTDir(pat, DIP_LUA, num_file, file, directories);
|
|
}
|
|
if (xp->xp_context == EXPAND_USER_LIST) {
|
|
return ExpandUserList(xp, num_file, file);
|
|
}
|
|
if (xp->xp_context == EXPAND_PACKADD) {
|
|
return ExpandPackAddDir(pat, num_file, file);
|
|
}
|
|
|
|
// When expanding a function name starting with s:, match the <SNR>nr_
|
|
// prefix.
|
|
char *tofree = NULL;
|
|
if (xp->xp_context == EXPAND_USER_FUNC && STRNCMP(pat, "^s:", 3) == 0) {
|
|
const size_t len = STRLEN(pat) + 20;
|
|
|
|
tofree = xmalloc(len);
|
|
snprintf(tofree, len, "^<SNR>\\d\\+_%s", pat + 3);
|
|
pat = (char_u *)tofree;
|
|
}
|
|
|
|
if (xp->xp_context == EXPAND_LUA) {
|
|
ILOG("PAT %s", pat);
|
|
return nlua_expand_pat(xp, pat, num_file, file);
|
|
}
|
|
|
|
regmatch.regprog = vim_regcomp(pat, p_magic ? RE_MAGIC : 0);
|
|
if (regmatch.regprog == NULL) {
|
|
return FAIL;
|
|
}
|
|
|
|
// set ignore-case according to p_ic, p_scs and pat
|
|
regmatch.rm_ic = ignorecase(pat);
|
|
|
|
if (xp->xp_context == EXPAND_SETTINGS
|
|
|| xp->xp_context == EXPAND_BOOL_SETTINGS) {
|
|
ret = ExpandSettings(xp, ®match, num_file, file);
|
|
} else if (xp->xp_context == EXPAND_MAPPINGS) {
|
|
ret = ExpandMappings(®match, num_file, file);
|
|
} else if (xp->xp_context == EXPAND_USER_DEFINED) {
|
|
ret = ExpandUserDefined(xp, ®match, num_file, file);
|
|
} else {
|
|
static struct expgen {
|
|
int context;
|
|
ExpandFunc func;
|
|
int ic;
|
|
int escaped;
|
|
} tab[] = {
|
|
{ EXPAND_COMMANDS, get_command_name, false, true },
|
|
{ EXPAND_BEHAVE, get_behave_arg, true, true },
|
|
{ EXPAND_MAPCLEAR, get_mapclear_arg, true, true },
|
|
{ EXPAND_MESSAGES, get_messages_arg, true, true },
|
|
{ EXPAND_HISTORY, get_history_arg, true, true },
|
|
{ EXPAND_USER_COMMANDS, get_user_commands, false, true },
|
|
{ EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true },
|
|
{ EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, false, true },
|
|
{ EXPAND_USER_NARGS, get_user_cmd_nargs, false, true },
|
|
{ EXPAND_USER_COMPLETE, get_user_cmd_complete, false, true },
|
|
{ EXPAND_USER_VARS, get_user_var_name, false, true },
|
|
{ EXPAND_FUNCTIONS, get_function_name, false, true },
|
|
{ EXPAND_USER_FUNC, get_user_func_name, false, true },
|
|
{ EXPAND_EXPRESSION, get_expr_name, false, true },
|
|
{ EXPAND_MENUS, get_menu_name, false, true },
|
|
{ EXPAND_MENUNAMES, get_menu_names, false, true },
|
|
{ EXPAND_SYNTAX, get_syntax_name, true, true },
|
|
{ EXPAND_SYNTIME, get_syntime_arg, true, true },
|
|
{ EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true },
|
|
{ EXPAND_EVENTS, get_event_name, true, true },
|
|
{ EXPAND_AUGROUP, get_augroup_name, true, true },
|
|
{ EXPAND_CSCOPE, get_cscope_name, true, true },
|
|
{ EXPAND_SIGN, get_sign_name, true, true },
|
|
{ EXPAND_PROFILE, get_profile_name, true, true },
|
|
#ifdef HAVE_WORKING_LIBINTL
|
|
{ EXPAND_LANGUAGE, get_lang_arg, true, false },
|
|
{ EXPAND_LOCALES, get_locales, true, false },
|
|
#endif
|
|
{ EXPAND_ENV_VARS, get_env_name, true, true },
|
|
{ EXPAND_USER, get_users, true, false },
|
|
{ EXPAND_ARGLIST, get_arglist_name, true, false },
|
|
{ EXPAND_CHECKHEALTH, get_healthcheck_names, true, false },
|
|
};
|
|
int i;
|
|
|
|
/*
|
|
* Find a context in the table and call the ExpandGeneric() with the
|
|
* right function to do the expansion.
|
|
*/
|
|
ret = FAIL;
|
|
for (i = 0; i < (int)ARRAY_SIZE(tab); ++i) {
|
|
if (xp->xp_context == tab[i].context) {
|
|
if (tab[i].ic) {
|
|
regmatch.rm_ic = TRUE;
|
|
}
|
|
ExpandGeneric(xp, ®match, num_file, file, tab[i].func,
|
|
tab[i].escaped);
|
|
ret = OK;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
vim_regfree(regmatch.regprog);
|
|
xfree(tofree);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/// Expand a list of names.
|
|
///
|
|
/// Generic function for command line completion. It calls a function to
|
|
/// obtain strings, one by one. The strings are matched against a regexp
|
|
/// program. Matching strings are copied into an array, which is returned.
|
|
///
|
|
/// @param func returns a string from the list
|
|
static void ExpandGeneric(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file,
|
|
CompleteListItemGetter func, int escaped)
|
|
{
|
|
int i;
|
|
size_t count = 0;
|
|
char_u *str;
|
|
|
|
// count the number of matching names
|
|
for (i = 0;; ++i) {
|
|
str = (*func)(xp, i);
|
|
if (str == NULL) { // end of list
|
|
break;
|
|
}
|
|
if (*str == NUL) { // skip empty strings
|
|
continue;
|
|
}
|
|
if (vim_regexec(regmatch, str, (colnr_T)0)) {
|
|
++count;
|
|
}
|
|
}
|
|
if (count == 0) {
|
|
return;
|
|
}
|
|
assert(count < INT_MAX);
|
|
*num_file = (int)count;
|
|
*file = (char_u **)xmalloc(count * sizeof(char_u *));
|
|
|
|
// copy the matching names into allocated memory
|
|
count = 0;
|
|
for (i = 0;; i++) {
|
|
str = (*func)(xp, i);
|
|
if (str == NULL) { // End of list.
|
|
break;
|
|
}
|
|
if (*str == NUL) { // Skip empty strings.
|
|
continue;
|
|
}
|
|
if (vim_regexec(regmatch, str, (colnr_T)0)) {
|
|
if (escaped) {
|
|
str = vim_strsave_escaped(str, (char_u *)" \t\\.");
|
|
} else {
|
|
str = vim_strsave(str);
|
|
}
|
|
(*file)[count++] = str;
|
|
if (func == get_menu_names) {
|
|
// Test for separator added by get_menu_names().
|
|
str += STRLEN(str) - 1;
|
|
if (*str == '\001') {
|
|
*str = '.';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort the results. Keep menu's in the specified order.
|
|
if (xp->xp_context != EXPAND_MENUNAMES && xp->xp_context != EXPAND_MENUS) {
|
|
if (xp->xp_context == EXPAND_EXPRESSION
|
|
|| xp->xp_context == EXPAND_FUNCTIONS
|
|
|| xp->xp_context == EXPAND_USER_FUNC) {
|
|
// <SNR> functions should be sorted to the end.
|
|
qsort((void *)*file, (size_t)*num_file, sizeof(char_u *),
|
|
sort_func_compare);
|
|
} else {
|
|
sort_strings(*file, *num_file);
|
|
}
|
|
}
|
|
|
|
/* Reset the variables used for special highlight names expansion, so that
|
|
* they don't show up when getting normal highlight names by ID. */
|
|
reset_expand_highlight();
|
|
}
|
|
|
|
/// Complete a shell command.
|
|
///
|
|
/// @param filepat is a pattern to match with command names.
|
|
/// @param[out] num_file is pointer to number of matches.
|
|
/// @param[out] file is pointer to array of pointers to matches.
|
|
/// *file will either be set to NULL or point to
|
|
/// allocated memory.
|
|
/// @param flagsarg is a combination of EW_* flags.
|
|
static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, int flagsarg)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
char_u *pat;
|
|
int i;
|
|
char_u *path = NULL;
|
|
garray_T ga;
|
|
char_u *buf = xmalloc(MAXPATHL);
|
|
size_t l;
|
|
char_u *s, *e;
|
|
int flags = flagsarg;
|
|
int ret;
|
|
bool did_curdir = false;
|
|
|
|
// for ":set path=" and ":set tags=" halve backslashes for escaped space
|
|
pat = vim_strsave(filepat);
|
|
for (i = 0; pat[i]; ++i) {
|
|
if (pat[i] == '\\' && pat[i + 1] == ' ') {
|
|
STRMOVE(pat + i, pat + i + 1);
|
|
}
|
|
}
|
|
|
|
flags |= EW_FILE | EW_EXEC | EW_SHELLCMD;
|
|
|
|
bool mustfree = false; // Track memory allocation for *path.
|
|
if (pat[0] == '.' && (vim_ispathsep(pat[1])
|
|
|| (pat[1] == '.' && vim_ispathsep(pat[2])))) {
|
|
path = (char_u *)".";
|
|
} else {
|
|
// For an absolute name we don't use $PATH.
|
|
if (!path_is_absolute(pat)) {
|
|
path = (char_u *)vim_getenv("PATH");
|
|
}
|
|
if (path == NULL) {
|
|
path = (char_u *)"";
|
|
} else {
|
|
mustfree = true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Go over all directories in $PATH. Expand matches in that directory and
|
|
* collect them in "ga". When "." is not in $PATH also expaned for the
|
|
* current directory, to find "subdir/cmd".
|
|
*/
|
|
ga_init(&ga, (int)sizeof(char *), 10);
|
|
hashtab_T found_ht;
|
|
hash_init(&found_ht);
|
|
for (s = path; ; s = e) {
|
|
e = vim_strchr(s, ENV_SEPCHAR);
|
|
if (e == NULL) {
|
|
e = s + STRLEN(s);
|
|
}
|
|
|
|
if (*s == NUL) {
|
|
if (did_curdir) {
|
|
break;
|
|
}
|
|
// Find directories in the current directory, path is empty.
|
|
did_curdir = true;
|
|
flags |= EW_DIR;
|
|
} else if (STRNCMP(s, ".", e - s) == 0) {
|
|
did_curdir = true;
|
|
flags |= EW_DIR;
|
|
} else {
|
|
// Do not match directories inside a $PATH item.
|
|
flags &= ~EW_DIR;
|
|
}
|
|
|
|
l = (size_t)(e - s);
|
|
if (l > MAXPATHL - 5) {
|
|
break;
|
|
}
|
|
STRLCPY(buf, s, l + 1);
|
|
add_pathsep((char *)buf);
|
|
l = STRLEN(buf);
|
|
STRLCPY(buf + l, pat, MAXPATHL - l);
|
|
|
|
// Expand matches in one directory of $PATH.
|
|
ret = expand_wildcards(1, &buf, num_file, file, flags);
|
|
if (ret == OK) {
|
|
ga_grow(&ga, *num_file);
|
|
{
|
|
for (i = 0; i < *num_file; i++) {
|
|
char_u *name = (*file)[i];
|
|
|
|
if (STRLEN(name) > l) {
|
|
// Check if this name was already found.
|
|
hash_T hash = hash_hash(name + l);
|
|
hashitem_T *hi =
|
|
hash_lookup(&found_ht, (const char *)(name + l),
|
|
STRLEN(name + l), hash);
|
|
if (HASHITEM_EMPTY(hi)) {
|
|
// Remove the path that was prepended.
|
|
STRMOVE(name, name + l);
|
|
((char_u **)ga.ga_data)[ga.ga_len++] = name;
|
|
hash_add_item(&found_ht, hi, name, hash);
|
|
name = NULL;
|
|
}
|
|
}
|
|
xfree(name);
|
|
}
|
|
xfree(*file);
|
|
}
|
|
}
|
|
if (*e != NUL) {
|
|
++e;
|
|
}
|
|
}
|
|
*file = ga.ga_data;
|
|
*num_file = ga.ga_len;
|
|
|
|
xfree(buf);
|
|
xfree(pat);
|
|
if (mustfree) {
|
|
xfree(path);
|
|
}
|
|
hash_clear(&found_ht);
|
|
}
|
|
|
|
/// Call "user_expand_func()" to invoke a user defined Vim script function and
|
|
/// return the result (either a string, a List or NULL).
|
|
static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T *xp, int *num_file,
|
|
char_u ***file)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
char_u keep = 0;
|
|
typval_T args[4];
|
|
char_u *pat = NULL;
|
|
const sctx_T save_current_sctx = current_sctx;
|
|
struct cmdline_info save_ccline;
|
|
|
|
if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) {
|
|
return NULL;
|
|
}
|
|
*num_file = 0;
|
|
*file = NULL;
|
|
|
|
if (ccline.cmdbuff != NULL) {
|
|
keep = ccline.cmdbuff[ccline.cmdlen];
|
|
ccline.cmdbuff[ccline.cmdlen] = 0;
|
|
}
|
|
|
|
pat = vim_strnsave(xp->xp_pattern, xp->xp_pattern_len);
|
|
args[0].v_type = VAR_STRING;
|
|
args[1].v_type = VAR_STRING;
|
|
args[2].v_type = VAR_NUMBER;
|
|
args[3].v_type = VAR_UNKNOWN;
|
|
args[0].vval.v_string = pat;
|
|
args[1].vval.v_string = xp->xp_line;
|
|
args[2].vval.v_number = xp->xp_col;
|
|
|
|
// Save the cmdline, we don't know what the function may do.
|
|
save_ccline = ccline;
|
|
ccline.cmdbuff = NULL;
|
|
ccline.cmdprompt = NULL;
|
|
current_sctx = xp->xp_script_ctx;
|
|
|
|
void *const ret = user_expand_func(xp->xp_arg, 3, args);
|
|
|
|
ccline = save_ccline;
|
|
current_sctx = save_current_sctx;
|
|
if (ccline.cmdbuff != NULL) {
|
|
ccline.cmdbuff[ccline.cmdlen] = keep;
|
|
}
|
|
|
|
xfree(pat);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Expand names with a function defined by the user.
|
|
*/
|
|
static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file)
|
|
{
|
|
char_u *e;
|
|
garray_T ga;
|
|
|
|
char_u *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp, num_file,
|
|
file);
|
|
|
|
if (retstr == NULL) {
|
|
return FAIL;
|
|
}
|
|
|
|
ga_init(&ga, (int)sizeof(char *), 3);
|
|
for (char_u *s = retstr; *s != NUL; s = e) {
|
|
e = vim_strchr(s, '\n');
|
|
if (e == NULL) {
|
|
e = s + STRLEN(s);
|
|
}
|
|
const char_u keep = *e;
|
|
*e = NUL;
|
|
|
|
const bool skip = xp->xp_pattern[0]
|
|
&& vim_regexec(regmatch, s, (colnr_T)0) == 0;
|
|
*e = keep;
|
|
if (!skip) {
|
|
GA_APPEND(char_u *, &ga, vim_strnsave(s, (size_t)(e - s)));
|
|
}
|
|
|
|
if (*e != NUL) {
|
|
e++;
|
|
}
|
|
}
|
|
xfree(retstr);
|
|
*file = ga.ga_data;
|
|
*num_file = ga.ga_len;
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Expand names with a list returned by a function defined by the user.
|
|
*/
|
|
static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file)
|
|
{
|
|
list_T *const retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp, num_file,
|
|
file);
|
|
if (retlist == NULL) {
|
|
return FAIL;
|
|
}
|
|
|
|
garray_T ga;
|
|
ga_init(&ga, (int)sizeof(char *), 3);
|
|
// Loop over the items in the list.
|
|
TV_LIST_ITER_CONST(retlist, li, {
|
|
if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING
|
|
|| TV_LIST_ITEM_TV(li)->vval.v_string == NULL) {
|
|
continue; // Skip non-string items and empty strings.
|
|
}
|
|
|
|
GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string));
|
|
});
|
|
tv_list_unref(retlist);
|
|
|
|
*file = ga.ga_data;
|
|
*num_file = ga.ga_len;
|
|
return OK;
|
|
}
|
|
|
|
/// Expand color scheme, compiler or filetype names.
|
|
/// Search from 'runtimepath':
|
|
/// 'runtimepath'/{dirnames}/{pat}.vim
|
|
/// When "flags" has DIP_START: search also from 'start' of 'packpath':
|
|
/// 'packpath'/pack/ * /start/ * /{dirnames}/{pat}.vim
|
|
/// When "flags" has DIP_OPT: search also from 'opt' of 'packpath':
|
|
/// 'packpath'/pack/ * /opt/ * /{dirnames}/{pat}.vim
|
|
/// When "flags" has DIP_LUA: search also performed for .lua files
|
|
/// "dirnames" is an array with one or more directory names.
|
|
static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file, char *dirnames[])
|
|
{
|
|
*num_file = 0;
|
|
*file = NULL;
|
|
size_t pat_len = STRLEN(pat);
|
|
|
|
garray_T ga;
|
|
ga_init(&ga, (int)sizeof(char *), 10);
|
|
|
|
// TODO(bfredl): this is bullshit, exandpath should not reinvent path logic.
|
|
for (int i = 0; dirnames[i] != NULL; i++) {
|
|
size_t size = STRLEN(dirnames[i]) + pat_len + 7;
|
|
char_u *s = xmalloc(size);
|
|
snprintf((char *)s, size, "%s/%s*.vim", dirnames[i], pat);
|
|
globpath(p_rtp, s, &ga, 0);
|
|
if (flags & DIP_LUA) {
|
|
snprintf((char *)s, size, "%s/%s*.lua", dirnames[i], pat);
|
|
globpath(p_rtp, s, &ga, 0);
|
|
}
|
|
xfree(s);
|
|
}
|
|
|
|
if (flags & DIP_START) {
|
|
for (int i = 0; dirnames[i] != NULL; i++) {
|
|
size_t size = STRLEN(dirnames[i]) + pat_len + 22;
|
|
char_u *s = xmalloc(size);
|
|
snprintf((char *)s, size, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
|
|
globpath(p_pp, s, &ga, 0);
|
|
if (flags & DIP_LUA) {
|
|
snprintf((char *)s, size, "pack/*/start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
|
|
globpath(p_pp, s, &ga, 0);
|
|
}
|
|
xfree(s);
|
|
}
|
|
|
|
for (int i = 0; dirnames[i] != NULL; i++) {
|
|
size_t size = STRLEN(dirnames[i]) + pat_len + 22;
|
|
char_u *s = xmalloc(size);
|
|
snprintf((char *)s, size, "start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
|
|
globpath(p_pp, s, &ga, 0);
|
|
if (flags & DIP_LUA) {
|
|
snprintf((char *)s, size, "start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
|
|
globpath(p_pp, s, &ga, 0);
|
|
}
|
|
xfree(s);
|
|
}
|
|
}
|
|
|
|
if (flags & DIP_OPT) {
|
|
for (int i = 0; dirnames[i] != NULL; i++) {
|
|
size_t size = STRLEN(dirnames[i]) + pat_len + 20;
|
|
char_u *s = xmalloc(size);
|
|
snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
|
|
globpath(p_pp, s, &ga, 0);
|
|
if (flags & DIP_LUA) {
|
|
snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
|
|
globpath(p_pp, s, &ga, 0);
|
|
}
|
|
xfree(s);
|
|
}
|
|
|
|
for (int i = 0; dirnames[i] != NULL; i++) {
|
|
size_t size = STRLEN(dirnames[i]) + pat_len + 20;
|
|
char_u *s = xmalloc(size);
|
|
snprintf((char *)s, size, "opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT
|
|
globpath(p_pp, s, &ga, 0);
|
|
if (flags & DIP_LUA) {
|
|
snprintf((char *)s, size, "opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT
|
|
globpath(p_pp, s, &ga, 0);
|
|
}
|
|
xfree(s);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < ga.ga_len; i++) {
|
|
char_u *match = ((char_u **)ga.ga_data)[i];
|
|
char_u *s = match;
|
|
char_u *e = s + STRLEN(s);
|
|
if (e - s > 4 && (STRNICMP(e - 4, ".vim", 4) == 0
|
|
|| ((flags & DIP_LUA)
|
|
&& STRNICMP(e - 4, ".lua", 4) == 0))) {
|
|
e -= 4;
|
|
for (s = e; s > match; MB_PTR_BACK(match, s)) {
|
|
if (vim_ispathsep(*s)) {
|
|
break;
|
|
}
|
|
}
|
|
s++;
|
|
*e = NUL;
|
|
assert((e - s) + 1 >= 0);
|
|
memmove(match, s, (size_t)(e - s) + 1);
|
|
}
|
|
}
|
|
|
|
if (GA_EMPTY(&ga)) {
|
|
return FAIL;
|
|
}
|
|
|
|
/* Sort and remove duplicates which can happen when specifying multiple
|
|
* directories in dirnames. */
|
|
ga_remove_duplicate_strings(&ga);
|
|
|
|
*file = ga.ga_data;
|
|
*num_file = ga.ga_len;
|
|
return OK;
|
|
}
|
|
|
|
/// Expand loadplugin names:
|
|
/// 'packpath'/pack/ * /opt/{pat}
|
|
static int ExpandPackAddDir(char_u *pat, int *num_file, char_u ***file)
|
|
{
|
|
garray_T ga;
|
|
|
|
*num_file = 0;
|
|
*file = NULL;
|
|
size_t pat_len = STRLEN(pat);
|
|
ga_init(&ga, (int)sizeof(char *), 10);
|
|
|
|
size_t buflen = pat_len + 26;
|
|
char_u *s = xmalloc(buflen);
|
|
snprintf((char *)s, buflen, "pack/*/opt/%s*", pat); // NOLINT
|
|
globpath(p_pp, s, &ga, 0);
|
|
snprintf((char *)s, buflen, "opt/%s*", pat); // NOLINT
|
|
globpath(p_pp, s, &ga, 0);
|
|
xfree(s);
|
|
|
|
for (int i = 0; i < ga.ga_len; i++) {
|
|
char_u *match = ((char_u **)ga.ga_data)[i];
|
|
s = path_tail(match);
|
|
memmove(match, s, STRLEN(s)+1);
|
|
}
|
|
|
|
if (GA_EMPTY(&ga)) {
|
|
return FAIL;
|
|
}
|
|
|
|
// Sort and remove duplicates which can happen when specifying multiple
|
|
// directories in dirnames.
|
|
ga_remove_duplicate_strings(&ga);
|
|
|
|
*file = ga.ga_data;
|
|
*num_file = ga.ga_len;
|
|
return OK;
|
|
}
|
|
|
|
|
|
/// Expand `file` for all comma-separated directories in `path`.
|
|
/// Adds matches to `ga`.
|
|
void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options)
|
|
{
|
|
expand_T xpc;
|
|
ExpandInit(&xpc);
|
|
xpc.xp_context = EXPAND_FILES;
|
|
|
|
char_u *buf = xmalloc(MAXPATHL);
|
|
|
|
// Loop over all entries in {path}.
|
|
while (*path != NUL) {
|
|
// Copy one item of the path to buf[] and concatenate the file name.
|
|
copy_option_part(&path, buf, MAXPATHL, ",");
|
|
if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) {
|
|
add_pathsep((char *)buf);
|
|
STRCAT(buf, file); // NOLINT
|
|
|
|
char_u **p;
|
|
int num_p = 0;
|
|
(void)ExpandFromContext(&xpc, buf, &num_p, &p,
|
|
WILD_SILENT | expand_options);
|
|
if (num_p > 0) {
|
|
ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT | expand_options);
|
|
|
|
// Concatenate new results to previous ones.
|
|
ga_grow(ga, num_p);
|
|
// take over the pointers and put them in "ga"
|
|
for (int i = 0; i < num_p; i++) {
|
|
((char_u **)ga->ga_data)[ga->ga_len] = p[i];
|
|
ga->ga_len++;
|
|
}
|
|
xfree(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
xfree(buf);
|
|
}
|
|
|
|
|
|
/*********************************
|
|
* Command line history stuff *
|
|
*********************************/
|
|
|
|
/// Translate a history character to the associated type number
|
|
static HistoryType hist_char2type(const int c)
|
|
FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
|
|
{
|
|
switch (c) {
|
|
case ':':
|
|
return HIST_CMD;
|
|
case '=':
|
|
return HIST_EXPR;
|
|
case '@':
|
|
return HIST_INPUT;
|
|
case '>':
|
|
return HIST_DEBUG;
|
|
case NUL:
|
|
case '/':
|
|
case '?':
|
|
return HIST_SEARCH;
|
|
default:
|
|
return HIST_INVALID;
|
|
}
|
|
// Silence -Wreturn-type
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Table of history names.
|
|
* These names are used in :history and various hist...() functions.
|
|
* It is sufficient to give the significant prefix of a history name.
|
|
*/
|
|
|
|
static char *(history_names[]) =
|
|
{
|
|
"cmd",
|
|
"search",
|
|
"expr",
|
|
"input",
|
|
"debug",
|
|
NULL
|
|
};
|
|
|
|
/*
|
|
* Function given to ExpandGeneric() to obtain the possible first
|
|
* arguments of the ":history command.
|
|
*/
|
|
static char_u *get_history_arg(expand_T *xp, int idx)
|
|
{
|
|
static char_u compl[2] = { NUL, NUL };
|
|
char *short_names = ":=@>?/";
|
|
int short_names_count = (int)STRLEN(short_names);
|
|
int history_name_count = ARRAY_SIZE(history_names) - 1;
|
|
|
|
if (idx < short_names_count) {
|
|
compl[0] = (char_u)short_names[idx];
|
|
return compl;
|
|
}
|
|
if (idx < short_names_count + history_name_count) {
|
|
return (char_u *)history_names[idx - short_names_count];
|
|
}
|
|
if (idx == short_names_count + history_name_count) {
|
|
return (char_u *)"all";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/// Initialize command line history.
|
|
/// Also used to re-allocate history tables when size changes.
|
|
void init_history(void)
|
|
{
|
|
assert(p_hi >= 0 && p_hi <= INT_MAX);
|
|
int newlen = (int)p_hi;
|
|
int oldlen = hislen;
|
|
|
|
// If history tables size changed, reallocate them.
|
|
// Tables are circular arrays (current position marked by hisidx[type]).
|
|
// On copying them to the new arrays, we take the chance to reorder them.
|
|
if (newlen != oldlen) {
|
|
for (int type = 0; type < HIST_COUNT; type++) {
|
|
histentry_T *temp = (newlen
|
|
? xmalloc((size_t)newlen * sizeof(*temp))
|
|
: NULL);
|
|
|
|
int j = hisidx[type];
|
|
if (j >= 0) {
|
|
// old array gets partitioned this way:
|
|
// [0 , i1 ) --> newest entries to be deleted
|
|
// [i1 , i1 + l1) --> newest entries to be copied
|
|
// [i1 + l1 , i2 ) --> oldest entries to be deleted
|
|
// [i2 , i2 + l2) --> oldest entries to be copied
|
|
int l1 = MIN(j + 1, newlen); // how many newest to copy
|
|
int l2 = MIN(newlen, oldlen) - l1; // how many oldest to copy
|
|
int i1 = j + 1 - l1; // copy newest from here
|
|
int i2 = MAX(l1, oldlen - newlen + l1); // copy oldest from here
|
|
|
|
// copy as much entries as they fit to new table, reordering them
|
|
if (newlen) {
|
|
// copy oldest entries
|
|
memcpy(&temp[0], &history[type][i2], (size_t)l2 * sizeof(*temp));
|
|
// copy newest entries
|
|
memcpy(&temp[l2], &history[type][i1], (size_t)l1 * sizeof(*temp));
|
|
}
|
|
|
|
// delete entries that don't fit in newlen, if any
|
|
for (int i = 0; i < i1; i++) {
|
|
hist_free_entry(history[type] + i);
|
|
}
|
|
for (int i = i1 + l1; i < i2; i++) {
|
|
hist_free_entry(history[type] + i);
|
|
}
|
|
}
|
|
|
|
// clear remaining space, if any
|
|
int l3 = j < 0 ? 0 : MIN(newlen, oldlen); // number of copied entries
|
|
if (newlen) {
|
|
memset(temp + l3, 0, (size_t)(newlen - l3) * sizeof(*temp));
|
|
}
|
|
|
|
hisidx[type] = l3 - 1;
|
|
xfree(history[type]);
|
|
history[type] = temp;
|
|
}
|
|
hislen = newlen;
|
|
}
|
|
}
|
|
|
|
static inline void hist_free_entry(histentry_T *hisptr)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
xfree(hisptr->hisstr);
|
|
tv_list_unref(hisptr->additional_elements);
|
|
clear_hist_entry(hisptr);
|
|
}
|
|
|
|
static inline void clear_hist_entry(histentry_T *hisptr)
|
|
FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
memset(hisptr, 0, sizeof(*hisptr));
|
|
}
|
|
|
|
/// Check if command line 'str' is already in history.
|
|
/// If 'move_to_front' is TRUE, matching entry is moved to end of history.
|
|
///
|
|
/// @param move_to_front Move the entry to the front if it exists
|
|
static int in_history(int type, char_u *str, int move_to_front, int sep)
|
|
{
|
|
int i;
|
|
int last_i = -1;
|
|
char_u *p;
|
|
|
|
if (hisidx[type] < 0) {
|
|
return FALSE;
|
|
}
|
|
i = hisidx[type];
|
|
do {
|
|
if (history[type][i].hisstr == NULL) {
|
|
return FALSE;
|
|
}
|
|
|
|
/* For search history, check that the separator character matches as
|
|
* well. */
|
|
p = history[type][i].hisstr;
|
|
if (STRCMP(str, p) == 0
|
|
&& (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) {
|
|
if (!move_to_front) {
|
|
return TRUE;
|
|
}
|
|
last_i = i;
|
|
break;
|
|
}
|
|
if (--i < 0) {
|
|
i = hislen - 1;
|
|
}
|
|
} while (i != hisidx[type]);
|
|
|
|
if (last_i >= 0) {
|
|
list_T *const list = history[type][i].additional_elements;
|
|
str = history[type][i].hisstr;
|
|
while (i != hisidx[type]) {
|
|
if (++i >= hislen) {
|
|
i = 0;
|
|
}
|
|
history[type][last_i] = history[type][i];
|
|
last_i = i;
|
|
}
|
|
tv_list_unref(list);
|
|
history[type][i].hisnum = ++hisnum[type];
|
|
history[type][i].hisstr = str;
|
|
history[type][i].timestamp = os_time();
|
|
history[type][i].additional_elements = NULL;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Convert history name to its HIST_ equivalent
|
|
///
|
|
/// Names are taken from the table above. When `name` is empty returns currently
|
|
/// active history or HIST_DEFAULT, depending on `return_default` argument.
|
|
///
|
|
/// @param[in] name Converted name.
|
|
/// @param[in] len Name length.
|
|
/// @param[in] return_default Determines whether HIST_DEFAULT should be
|
|
/// returned or value based on `ccline.cmdfirstc`.
|
|
///
|
|
/// @return Any value from HistoryType enum, including HIST_INVALID. May not
|
|
/// return HIST_DEFAULT unless return_default is true.
|
|
HistoryType get_histtype(const char *const name, const size_t len, const bool return_default)
|
|
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
|
|
{
|
|
// No argument: use current history.
|
|
if (len == 0) {
|
|
return return_default ? HIST_DEFAULT : hist_char2type(ccline.cmdfirstc);
|
|
}
|
|
|
|
for (HistoryType i = 0; history_names[i] != NULL; i++) {
|
|
if (STRNICMP(name, history_names[i], len) == 0) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
if (vim_strchr((char_u *)":=@>?/", name[0]) != NULL && len == 1) {
|
|
return hist_char2type(name[0]);
|
|
}
|
|
|
|
return HIST_INVALID;
|
|
}
|
|
|
|
static int last_maptick = -1; // last seen maptick
|
|
|
|
/// Add the given string to the given history. If the string is already in the
|
|
/// history then it is moved to the front. "histype" may be one of he HIST_
|
|
/// values.
|
|
///
|
|
/// @parma in_map consider maptick when inside a mapping
|
|
/// @param sep separator character used (search hist)
|
|
void add_to_history(int histype, char_u *new_entry, int in_map, int sep)
|
|
{
|
|
histentry_T *hisptr;
|
|
|
|
if (hislen == 0 || histype == HIST_INVALID) { // no history
|
|
return;
|
|
}
|
|
assert(histype != HIST_DEFAULT);
|
|
|
|
if (cmdmod.keeppatterns && histype == HIST_SEARCH) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Searches inside the same mapping overwrite each other, so that only
|
|
* the last line is kept. Be careful not to remove a line that was moved
|
|
* down, only lines that were added.
|
|
*/
|
|
if (histype == HIST_SEARCH && in_map) {
|
|
if (maptick == last_maptick && hisidx[HIST_SEARCH] >= 0) {
|
|
// Current line is from the same mapping, remove it
|
|
hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]];
|
|
hist_free_entry(hisptr);
|
|
--hisnum[histype];
|
|
if (--hisidx[HIST_SEARCH] < 0) {
|
|
hisidx[HIST_SEARCH] = hislen - 1;
|
|
}
|
|
}
|
|
last_maptick = -1;
|
|
}
|
|
if (!in_history(histype, new_entry, true, sep)) {
|
|
if (++hisidx[histype] == hislen) {
|
|
hisidx[histype] = 0;
|
|
}
|
|
hisptr = &history[histype][hisidx[histype]];
|
|
hist_free_entry(hisptr);
|
|
|
|
// Store the separator after the NUL of the string.
|
|
size_t len = STRLEN(new_entry);
|
|
hisptr->hisstr = vim_strnsave(new_entry, len + 2);
|
|
hisptr->timestamp = os_time();
|
|
hisptr->additional_elements = NULL;
|
|
hisptr->hisstr[len + 1] = (char_u)sep;
|
|
|
|
hisptr->hisnum = ++hisnum[histype];
|
|
if (histype == HIST_SEARCH && in_map) {
|
|
last_maptick = maptick;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Get identifier of newest history entry.
|
|
* "histype" may be one of the HIST_ values.
|
|
*/
|
|
int get_history_idx(int histype)
|
|
{
|
|
if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
|
|
|| hisidx[histype] < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return history[histype][hisidx[histype]].hisnum;
|
|
}
|
|
|
|
|
|
/*
|
|
* Get pointer to the command line info to use. cmdline_paste() may clear
|
|
* ccline and put the previous value in prev_ccline.
|
|
*/
|
|
static struct cmdline_info *get_ccline_ptr(void)
|
|
{
|
|
if ((State & CMDLINE) == 0) {
|
|
return NULL;
|
|
} else if (ccline.cmdbuff != NULL) {
|
|
return &ccline;
|
|
} else if (ccline.prev_ccline && ccline.prev_ccline->cmdbuff != NULL) {
|
|
return ccline.prev_ccline;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get the current command line in allocated memory.
|
|
* Only works when the command line is being edited.
|
|
* Returns NULL when something is wrong.
|
|
*/
|
|
char_u *get_cmdline_str(void)
|
|
{
|
|
if (cmdline_star > 0) {
|
|
return NULL;
|
|
}
|
|
struct cmdline_info *p = get_ccline_ptr();
|
|
|
|
if (p == NULL) {
|
|
return NULL;
|
|
}
|
|
return vim_strnsave(p->cmdbuff, (size_t)p->cmdlen);
|
|
}
|
|
|
|
/*
|
|
* Get the current command line position, counted in bytes.
|
|
* Zero is the first position.
|
|
* Only works when the command line is being edited.
|
|
* Returns -1 when something is wrong.
|
|
*/
|
|
int get_cmdline_pos(void)
|
|
{
|
|
struct cmdline_info *p = get_ccline_ptr();
|
|
|
|
if (p == NULL) {
|
|
return -1;
|
|
}
|
|
return p->cmdpos;
|
|
}
|
|
|
|
/*
|
|
* Set the command line byte position to "pos". Zero is the first position.
|
|
* Only works when the command line is being edited.
|
|
* Returns 1 when failed, 0 when OK.
|
|
*/
|
|
int set_cmdline_pos(int pos)
|
|
{
|
|
struct cmdline_info *p = get_ccline_ptr();
|
|
|
|
if (p == NULL) {
|
|
return 1;
|
|
}
|
|
|
|
// The position is not set directly but after CTRL-\ e or CTRL-R = has
|
|
// changed the command line.
|
|
if (pos < 0) {
|
|
new_cmdpos = 0;
|
|
} else {
|
|
new_cmdpos = pos;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Get the current command-line type.
|
|
* Returns ':' or '/' or '?' or '@' or '>' or '-'
|
|
* Only works when the command line is being edited.
|
|
* Returns NUL when something is wrong.
|
|
*/
|
|
int get_cmdline_type(void)
|
|
{
|
|
struct cmdline_info *p = get_ccline_ptr();
|
|
|
|
if (p == NULL) {
|
|
return NUL;
|
|
}
|
|
if (p->cmdfirstc == NUL) {
|
|
return (p->input_fn) ? '@' : '-';
|
|
}
|
|
return p->cmdfirstc;
|
|
}
|
|
|
|
/*
|
|
* Calculate history index from a number:
|
|
* num > 0: seen as identifying number of a history entry
|
|
* num < 0: relative position in history wrt newest entry
|
|
* "histype" may be one of the HIST_ values.
|
|
*/
|
|
static int calc_hist_idx(int histype, int num)
|
|
{
|
|
int i;
|
|
histentry_T *hist;
|
|
int wrapped = FALSE;
|
|
|
|
if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
|
|
|| (i = hisidx[histype]) < 0 || num == 0) {
|
|
return -1;
|
|
}
|
|
|
|
hist = history[histype];
|
|
if (num > 0) {
|
|
while (hist[i].hisnum > num) {
|
|
if (--i < 0) {
|
|
if (wrapped) {
|
|
break;
|
|
}
|
|
i += hislen;
|
|
wrapped = TRUE;
|
|
}
|
|
}
|
|
if (hist[i].hisnum == num && hist[i].hisstr != NULL) {
|
|
return i;
|
|
}
|
|
} else if (-num <= hislen) {
|
|
i += num + 1;
|
|
if (i < 0) {
|
|
i += hislen;
|
|
}
|
|
if (hist[i].hisstr != NULL) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Get a history entry by its index.
|
|
* "histype" may be one of the HIST_ values.
|
|
*/
|
|
char_u *get_history_entry(int histype, int idx)
|
|
{
|
|
idx = calc_hist_idx(histype, idx);
|
|
if (idx >= 0) {
|
|
return history[histype][idx].hisstr;
|
|
} else {
|
|
return (char_u *)"";
|
|
}
|
|
}
|
|
|
|
/// Clear all entries in a history
|
|
///
|
|
/// @param[in] histype One of the HIST_ values.
|
|
///
|
|
/// @return OK if there was something to clean and histype was one of HIST_
|
|
/// values, FAIL otherwise.
|
|
int clr_history(const int histype)
|
|
{
|
|
if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) {
|
|
histentry_T *hisptr = history[histype];
|
|
for (int i = hislen; i--; hisptr++) {
|
|
hist_free_entry(hisptr);
|
|
}
|
|
hisidx[histype] = -1; // mark history as cleared
|
|
hisnum[histype] = 0; // reset identifier counter
|
|
return OK;
|
|
}
|
|
return FAIL;
|
|
}
|
|
|
|
/*
|
|
* Remove all entries matching {str} from a history.
|
|
* "histype" may be one of the HIST_ values.
|
|
*/
|
|
int del_history_entry(int histype, char_u *str)
|
|
{
|
|
regmatch_T regmatch;
|
|
histentry_T *hisptr;
|
|
int idx;
|
|
int i;
|
|
int last;
|
|
bool found = false;
|
|
|
|
regmatch.regprog = NULL;
|
|
regmatch.rm_ic = FALSE; // always match case
|
|
if (hislen != 0
|
|
&& histype >= 0
|
|
&& histype < HIST_COUNT
|
|
&& *str != NUL
|
|
&& (idx = hisidx[histype]) >= 0
|
|
&& (regmatch.regprog = vim_regcomp(str, RE_MAGIC + RE_STRING))
|
|
!= NULL) {
|
|
i = last = idx;
|
|
do {
|
|
hisptr = &history[histype][i];
|
|
if (hisptr->hisstr == NULL) {
|
|
break;
|
|
}
|
|
if (vim_regexec(®match, hisptr->hisstr, (colnr_T)0)) {
|
|
found = true;
|
|
hist_free_entry(hisptr);
|
|
} else {
|
|
if (i != last) {
|
|
history[histype][last] = *hisptr;
|
|
clear_hist_entry(hisptr);
|
|
}
|
|
if (--last < 0) {
|
|
last += hislen;
|
|
}
|
|
}
|
|
if (--i < 0) {
|
|
i += hislen;
|
|
}
|
|
} while (i != idx);
|
|
if (history[histype][idx].hisstr == NULL) {
|
|
hisidx[histype] = -1;
|
|
}
|
|
}
|
|
vim_regfree(regmatch.regprog);
|
|
return found;
|
|
}
|
|
|
|
/*
|
|
* Remove an indexed entry from a history.
|
|
* "histype" may be one of the HIST_ values.
|
|
*/
|
|
int del_history_idx(int histype, int idx)
|
|
{
|
|
int i, j;
|
|
|
|
i = calc_hist_idx(histype, idx);
|
|
if (i < 0) {
|
|
return FALSE;
|
|
}
|
|
idx = hisidx[histype];
|
|
hist_free_entry(&history[histype][i]);
|
|
|
|
/* When deleting the last added search string in a mapping, reset
|
|
* last_maptick, so that the last added search string isn't deleted again.
|
|
*/
|
|
if (histype == HIST_SEARCH && maptick == last_maptick && i == idx) {
|
|
last_maptick = -1;
|
|
}
|
|
|
|
while (i != idx) {
|
|
j = (i + 1) % hislen;
|
|
history[histype][i] = history[histype][j];
|
|
i = j;
|
|
}
|
|
clear_hist_entry(&history[histype][idx]);
|
|
if (--i < 0) {
|
|
i += hislen;
|
|
}
|
|
hisidx[histype] = i;
|
|
return TRUE;
|
|
}
|
|
|
|
/// Get indices that specify a range within a list (not a range of text lines
|
|
/// in a buffer!) from a string. Used for ":history" and ":clist".
|
|
///
|
|
/// @param str string to parse range from
|
|
/// @param num1 from
|
|
/// @param num2 to
|
|
///
|
|
/// @return OK if parsed successfully, otherwise FAIL.
|
|
int get_list_range(char_u **str, int *num1, int *num2)
|
|
{
|
|
int len;
|
|
int first = false;
|
|
varnumber_T num;
|
|
|
|
*str = skipwhite(*str);
|
|
if (**str == '-' || ascii_isdigit(**str)) { // parse "from" part of range
|
|
vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false);
|
|
*str += len;
|
|
*num1 = (int)num;
|
|
first = true;
|
|
}
|
|
*str = skipwhite(*str);
|
|
if (**str == ',') { // parse "to" part of range
|
|
*str = skipwhite(*str + 1);
|
|
vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false);
|
|
if (len > 0) {
|
|
*num2 = (int)num;
|
|
*str = skipwhite(*str + len);
|
|
} else if (!first) { // no number given at all
|
|
return FAIL;
|
|
}
|
|
} else if (first) { // only one number given
|
|
*num2 = *num1;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* :history command - print a history
|
|
*/
|
|
void ex_history(exarg_T *eap)
|
|
{
|
|
histentry_T *hist;
|
|
int histype1 = HIST_CMD;
|
|
int histype2 = HIST_CMD;
|
|
int hisidx1 = 1;
|
|
int hisidx2 = -1;
|
|
int idx;
|
|
int i, j, k;
|
|
char_u *end;
|
|
char_u *arg = eap->arg;
|
|
|
|
if (hislen == 0) {
|
|
msg(_("'history' option is zero"));
|
|
return;
|
|
}
|
|
|
|
if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) {
|
|
end = arg;
|
|
while (ASCII_ISALPHA(*end)
|
|
|| vim_strchr((char_u *)":=@>/?", *end) != NULL) {
|
|
end++;
|
|
}
|
|
histype1 = get_histtype((const char *)arg, (size_t)(end - arg), false);
|
|
if (histype1 == HIST_INVALID) {
|
|
if (STRNICMP(arg, "all", end - arg) == 0) {
|
|
histype1 = 0;
|
|
histype2 = HIST_COUNT-1;
|
|
} else {
|
|
emsg(_(e_trailing));
|
|
return;
|
|
}
|
|
} else {
|
|
histype2 = histype1;
|
|
}
|
|
} else {
|
|
end = arg;
|
|
}
|
|
if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) {
|
|
emsg(_(e_trailing));
|
|
return;
|
|
}
|
|
|
|
for (; !got_int && histype1 <= histype2; ++histype1) {
|
|
STRCPY(IObuff, "\n # ");
|
|
assert(history_names[histype1] != NULL);
|
|
STRCAT(STRCAT(IObuff, history_names[histype1]), " history");
|
|
msg_puts_title((char *)IObuff);
|
|
idx = hisidx[histype1];
|
|
hist = history[histype1];
|
|
j = hisidx1;
|
|
k = hisidx2;
|
|
if (j < 0) {
|
|
j = (-j > hislen) ? 0 : hist[(hislen+j+idx+1) % hislen].hisnum;
|
|
}
|
|
if (k < 0) {
|
|
k = (-k > hislen) ? 0 : hist[(hislen+k+idx+1) % hislen].hisnum;
|
|
}
|
|
if (idx >= 0 && j <= k) {
|
|
for (i = idx + 1; !got_int; ++i) {
|
|
if (i == hislen) {
|
|
i = 0;
|
|
}
|
|
if (hist[i].hisstr != NULL
|
|
&& hist[i].hisnum >= j && hist[i].hisnum <= k) {
|
|
msg_putchar('\n');
|
|
snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ',
|
|
hist[i].hisnum);
|
|
if (vim_strsize(hist[i].hisstr) > Columns - 10) {
|
|
trunc_string(hist[i].hisstr, IObuff + STRLEN(IObuff),
|
|
Columns - 10, IOSIZE - (int)STRLEN(IObuff));
|
|
} else {
|
|
STRCAT(IObuff, hist[i].hisstr);
|
|
}
|
|
msg_outtrans(IObuff);
|
|
ui_flush();
|
|
}
|
|
if (i == idx) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Translate a history type number to the associated character
|
|
int hist_type2char(int type)
|
|
FUNC_ATTR_CONST
|
|
{
|
|
switch (type) {
|
|
case HIST_CMD:
|
|
return ':';
|
|
case HIST_SEARCH:
|
|
return '/';
|
|
case HIST_EXPR:
|
|
return '=';
|
|
case HIST_INPUT:
|
|
return '@';
|
|
case HIST_DEBUG:
|
|
return '>';
|
|
default:
|
|
abort();
|
|
}
|
|
return NUL;
|
|
}
|
|
|
|
/// Open a window on the current command line and history. Allow editing in
|
|
/// the window. Returns when the window is closed.
|
|
/// Returns:
|
|
/// CR if the command is to be executed
|
|
/// Ctrl_C if it is to be abandoned
|
|
/// K_IGNORE if editing continues
|
|
static int open_cmdwin(void)
|
|
{
|
|
struct cmdline_info save_ccline;
|
|
bufref_T old_curbuf;
|
|
bufref_T bufref;
|
|
win_T *old_curwin = curwin;
|
|
win_T *wp;
|
|
int i;
|
|
linenr_T lnum;
|
|
garray_T winsizes;
|
|
char_u typestr[2];
|
|
int save_restart_edit = restart_edit;
|
|
int save_State = State;
|
|
bool save_exmode = exmode_active;
|
|
int save_cmdmsg_rl = cmdmsg_rl;
|
|
|
|
// Can't do this recursively. Can't do it when typing a password.
|
|
if (cmdwin_type != 0
|
|
|| cmdline_star > 0) {
|
|
beep_flush();
|
|
return K_IGNORE;
|
|
}
|
|
|
|
set_bufref(&old_curbuf, curbuf);
|
|
|
|
// Save current window sizes.
|
|
win_size_save(&winsizes);
|
|
|
|
// When using completion in Insert mode with <C-R>=<C-F> one can open the
|
|
// command line window, but we don't want the popup menu then.
|
|
pum_undisplay(true);
|
|
|
|
// don't use a new tab page
|
|
cmdmod.tab = 0;
|
|
cmdmod.noswapfile = 1;
|
|
|
|
// Create a window for the command-line buffer.
|
|
if (win_split((int)p_cwh, WSP_BOT) == FAIL) {
|
|
beep_flush();
|
|
return K_IGNORE;
|
|
}
|
|
cmdwin_type = get_cmdline_type();
|
|
cmdwin_level = ccline.level;
|
|
|
|
// Create empty command-line buffer.
|
|
buf_open_scratch(0, _("[Command Line]"));
|
|
// Command-line buffer has bufhidden=wipe, unlike a true "scratch" buffer.
|
|
set_option_value("bh", 0L, "wipe", OPT_LOCAL);
|
|
curwin->w_p_rl = cmdmsg_rl;
|
|
cmdmsg_rl = false;
|
|
curbuf->b_p_ma = true;
|
|
curwin->w_p_fen = false;
|
|
|
|
// Don't allow switching to another buffer.
|
|
curbuf->b_ro_locked++;
|
|
|
|
// Showing the prompt may have set need_wait_return, reset it.
|
|
need_wait_return = false;
|
|
|
|
const int histtype = hist_char2type(cmdwin_type);
|
|
if (histtype == HIST_CMD || histtype == HIST_DEBUG) {
|
|
if (p_wc == TAB) {
|
|
add_map((char_u *)"<buffer> <Tab> <C-X><C-V>", INSERT, false);
|
|
add_map((char_u *)"<buffer> <Tab> a<C-X><C-V>", NORMAL, false);
|
|
}
|
|
set_option_value("ft", 0L, "vim", OPT_LOCAL);
|
|
}
|
|
curbuf->b_ro_locked--;
|
|
|
|
// Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin
|
|
// sets 'textwidth' to 78).
|
|
curbuf->b_p_tw = 0;
|
|
|
|
// Fill the buffer with the history.
|
|
init_history();
|
|
if (hislen > 0 && histtype != HIST_INVALID) {
|
|
i = hisidx[histtype];
|
|
if (i >= 0) {
|
|
lnum = 0;
|
|
do {
|
|
if (++i == hislen) {
|
|
i = 0;
|
|
}
|
|
if (history[histtype][i].hisstr != NULL) {
|
|
ml_append(lnum++, history[histtype][i].hisstr, (colnr_T)0, false);
|
|
}
|
|
} while (i != hisidx[histtype]);
|
|
}
|
|
}
|
|
|
|
// Replace the empty last line with the current command-line and put the
|
|
// cursor there.
|
|
ml_replace(curbuf->b_ml.ml_line_count, ccline.cmdbuff, true);
|
|
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
|
|
curwin->w_cursor.col = ccline.cmdpos;
|
|
changed_line_abv_curs();
|
|
invalidate_botline();
|
|
if (ui_has(kUICmdline)) {
|
|
ccline.redraw_state = kCmdRedrawNone;
|
|
ui_call_cmdline_hide(ccline.level);
|
|
}
|
|
redraw_later(curwin, SOME_VALID);
|
|
|
|
// Save the command line info, can be used recursively.
|
|
save_cmdline(&save_ccline);
|
|
|
|
// No Ex mode here!
|
|
exmode_active = false;
|
|
|
|
State = NORMAL;
|
|
setmouse();
|
|
|
|
// Reset here so it can be set by a CmdWinEnter autocommand.
|
|
cmdwin_result = 0;
|
|
|
|
// Trigger CmdwinEnter autocommands.
|
|
typestr[0] = (char_u)cmdwin_type;
|
|
typestr[1] = NUL;
|
|
apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, false, curbuf);
|
|
if (restart_edit != 0) { // autocmd with ":startinsert"
|
|
stuffcharReadbuff(K_NOP);
|
|
}
|
|
|
|
i = RedrawingDisabled;
|
|
RedrawingDisabled = 0;
|
|
int save_count = save_batch_count();
|
|
|
|
/*
|
|
* Call the main loop until <CR> or CTRL-C is typed.
|
|
*/
|
|
normal_enter(true, false);
|
|
|
|
RedrawingDisabled = i;
|
|
restore_batch_count(save_count);
|
|
|
|
const bool save_KeyTyped = KeyTyped;
|
|
|
|
// Trigger CmdwinLeave autocommands.
|
|
apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, false, curbuf);
|
|
|
|
// Restore KeyTyped in case it is modified by autocommands
|
|
KeyTyped = save_KeyTyped;
|
|
|
|
// Restore the command line info.
|
|
restore_cmdline(&save_ccline);
|
|
cmdwin_type = 0;
|
|
cmdwin_level = 0;
|
|
|
|
exmode_active = save_exmode;
|
|
|
|
// Safety check: The old window or buffer was deleted: It's a bug when
|
|
// this happens!
|
|
if (!win_valid(old_curwin) || !bufref_valid(&old_curbuf)) {
|
|
cmdwin_result = Ctrl_C;
|
|
emsg(_("E199: Active window or buffer deleted"));
|
|
} else {
|
|
// autocmds may abort script processing
|
|
if (aborting() && cmdwin_result != K_IGNORE) {
|
|
cmdwin_result = Ctrl_C;
|
|
}
|
|
// Set the new command line from the cmdline buffer.
|
|
xfree(ccline.cmdbuff);
|
|
if (cmdwin_result == K_XF1 || cmdwin_result == K_XF2) { // :qa[!] typed
|
|
const char *p = (cmdwin_result == K_XF2) ? "qa" : "qa!";
|
|
|
|
if (histtype == HIST_CMD) {
|
|
// Execute the command directly.
|
|
ccline.cmdbuff = (char_u *)xstrdup(p);
|
|
cmdwin_result = CAR;
|
|
} else {
|
|
// First need to cancel what we were doing.
|
|
ccline.cmdbuff = NULL;
|
|
stuffcharReadbuff(':');
|
|
stuffReadbuff(p);
|
|
stuffcharReadbuff(CAR);
|
|
}
|
|
} else if (cmdwin_result == Ctrl_C) {
|
|
// :q or :close, don't execute any command
|
|
// and don't modify the cmd window.
|
|
ccline.cmdbuff = NULL;
|
|
} else {
|
|
ccline.cmdbuff = vim_strsave(get_cursor_line_ptr());
|
|
}
|
|
if (ccline.cmdbuff == NULL) {
|
|
ccline.cmdbuff = vim_strsave((char_u *)"");
|
|
ccline.cmdlen = 0;
|
|
ccline.cmdbufflen = 1;
|
|
ccline.cmdpos = 0;
|
|
cmdwin_result = Ctrl_C;
|
|
} else {
|
|
ccline.cmdlen = (int)STRLEN(ccline.cmdbuff);
|
|
ccline.cmdbufflen = ccline.cmdlen + 1;
|
|
ccline.cmdpos = curwin->w_cursor.col;
|
|
if (ccline.cmdpos > ccline.cmdlen) {
|
|
ccline.cmdpos = ccline.cmdlen;
|
|
}
|
|
if (cmdwin_result == K_IGNORE) {
|
|
ccline.cmdspos = cmd_screencol(ccline.cmdpos);
|
|
redrawcmd();
|
|
}
|
|
}
|
|
|
|
// Avoid command-line window first character being concealed.
|
|
curwin->w_p_cole = 0;
|
|
wp = curwin;
|
|
set_bufref(&bufref, curbuf);
|
|
win_goto(old_curwin);
|
|
if (win_valid(wp) && wp != curwin) {
|
|
win_close(wp, true);
|
|
}
|
|
|
|
// win_close() may have already wiped the buffer when 'bh' is
|
|
// set to 'wipe', autocommands may have closed other windows
|
|
if (bufref_valid(&bufref) && bufref.br_buf != curbuf) {
|
|
close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false);
|
|
}
|
|
|
|
// Restore window sizes.
|
|
win_size_restore(&winsizes);
|
|
}
|
|
|
|
ga_clear(&winsizes);
|
|
restart_edit = save_restart_edit;
|
|
cmdmsg_rl = save_cmdmsg_rl;
|
|
|
|
State = save_State;
|
|
trigger_modechanged();
|
|
setmouse();
|
|
|
|
return cmdwin_result;
|
|
}
|
|
|
|
/// Get script string
|
|
///
|
|
/// Used for commands which accept either `:command script` or
|
|
///
|
|
/// :command << endmarker
|
|
/// script
|
|
/// endmarker
|
|
///
|
|
/// @param eap Command being run.
|
|
/// @param[out] lenp Location where length of resulting string is saved. Will
|
|
/// be set to zero when skipping.
|
|
///
|
|
/// @return [allocated] NULL or script. Does not show any error messages.
|
|
/// NULL is returned when skipping and on error.
|
|
char *script_get(exarg_T *const eap, size_t *const lenp)
|
|
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
|
|
{
|
|
const char *const cmd = (const char *)eap->arg;
|
|
|
|
if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL) {
|
|
*lenp = STRLEN(eap->arg);
|
|
return eap->skip ? NULL : xmemdupz(eap->arg, *lenp);
|
|
}
|
|
|
|
garray_T ga = { .ga_data = NULL, .ga_len = 0 };
|
|
if (!eap->skip) {
|
|
ga_init(&ga, 1, 0x400);
|
|
}
|
|
|
|
const char *const end_pattern = (
|
|
cmd[2] != NUL
|
|
? (const char *)skipwhite((const char_u *)cmd + 2)
|
|
: ".");
|
|
for (;;) {
|
|
char *const theline = (char *)eap->getline(eap->cstack->cs_looplevel > 0 ? -1 :
|
|
NUL, eap->cookie, 0, true);
|
|
|
|
if (theline == NULL || strcmp(end_pattern, theline) == 0) {
|
|
xfree(theline);
|
|
break;
|
|
}
|
|
|
|
if (!eap->skip) {
|
|
ga_concat(&ga, theline);
|
|
ga_append(&ga, '\n');
|
|
}
|
|
xfree(theline);
|
|
}
|
|
*lenp = (size_t)ga.ga_len; // Set length without trailing NUL.
|
|
if (!eap->skip) {
|
|
ga_append(&ga, NUL);
|
|
}
|
|
|
|
return (char *)ga.ga_data;
|
|
}
|
|
|
|
/// Iterate over history items
|
|
///
|
|
/// @warning No history-editing functions must be run while iteration is in
|
|
/// progress.
|
|
///
|
|
/// @param[in] iter Pointer to the last history entry.
|
|
/// @param[in] history_type Type of the history (HIST_*). Ignored if iter
|
|
/// parameter is not NULL.
|
|
/// @param[in] zero If true then zero (but not free) returned items.
|
|
///
|
|
/// @warning When using this parameter user is
|
|
/// responsible for calling clr_history()
|
|
/// itself after iteration is over. If
|
|
/// clr_history() is not called behaviour is
|
|
/// undefined. No functions that work with
|
|
/// history must be called during iteration
|
|
/// in this case.
|
|
/// @param[out] hist Next history entry.
|
|
///
|
|
/// @return Pointer used in next iteration or NULL to indicate that iteration
|
|
/// was finished.
|
|
const void *hist_iter(const void *const iter, const uint8_t history_type, const bool zero,
|
|
histentry_T *const hist)
|
|
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4)
|
|
{
|
|
*hist = (histentry_T) {
|
|
.hisstr = NULL
|
|
};
|
|
if (hisidx[history_type] == -1) {
|
|
return NULL;
|
|
}
|
|
histentry_T *const hstart = &(history[history_type][0]);
|
|
histentry_T *const hlast = (
|
|
&(history[history_type][hisidx[history_type]]));
|
|
const histentry_T *const hend = &(history[history_type][hislen - 1]);
|
|
histentry_T *hiter;
|
|
if (iter == NULL) {
|
|
histentry_T *hfirst = hlast;
|
|
do {
|
|
hfirst++;
|
|
if (hfirst > hend) {
|
|
hfirst = hstart;
|
|
}
|
|
if (hfirst->hisstr != NULL) {
|
|
break;
|
|
}
|
|
} while (hfirst != hlast);
|
|
hiter = hfirst;
|
|
} else {
|
|
hiter = (histentry_T *)iter;
|
|
}
|
|
if (hiter == NULL) {
|
|
return NULL;
|
|
}
|
|
*hist = *hiter;
|
|
if (zero) {
|
|
memset(hiter, 0, sizeof(*hiter));
|
|
}
|
|
if (hiter == hlast) {
|
|
return NULL;
|
|
}
|
|
hiter++;
|
|
return (const void *)((hiter > hend) ? hstart : hiter);
|
|
}
|
|
|
|
/// Get array of history items
|
|
///
|
|
/// @param[in] history_type Type of the history to get array for.
|
|
/// @param[out] new_hisidx Location where last index in the new array should
|
|
/// be saved.
|
|
/// @param[out] new_hisnum Location where last history number in the new
|
|
/// history should be saved.
|
|
///
|
|
/// @return Pointer to the array or NULL.
|
|
histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx,
|
|
int **const new_hisnum)
|
|
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
|
|
{
|
|
init_history();
|
|
*new_hisidx = &(hisidx[history_type]);
|
|
*new_hisnum = &(hisnum[history_type]);
|
|
return history[history_type];
|
|
}
|
|
|
|
static void set_search_match(pos_T *t)
|
|
{
|
|
// First move cursor to end of match, then to the start. This
|
|
// moves the whole match onto the screen when 'nowrap' is set.
|
|
t->lnum += search_match_lines;
|
|
t->col = search_match_endcol;
|
|
if (t->lnum > curbuf->b_ml.ml_line_count) {
|
|
t->lnum = curbuf->b_ml.ml_line_count;
|
|
coladvance(MAXCOL);
|
|
}
|
|
}
|