Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

9569 lines (8897 sloc) 230.234 kB
/* vi:set ts=8 sts=4 sw=4:
*
* VIM - Vi IMproved by Bram Moolenaar
*
* Do ":help uganda" in Vim to read copying and usage conditions.
* Do ":help credits" in Vim to see a list of people who contributed.
* See README.txt for an overview of the Vim source code.
*/
/*
* normal.c: Contains the main routine for processing characters in command
* mode. Communicates closely with the code in ops.c to handle
* the operators.
*/
#include "vim.h"
#ifdef FEAT_VISUAL
/*
* The Visual area is remembered for reselection.
*/
static int resel_VIsual_mode = NUL; /* 'v', 'V', or Ctrl-V */
static linenr_T resel_VIsual_line_count; /* number of lines */
static colnr_T resel_VIsual_vcol; /* nr of cols or end col */
static int restart_VIsual_select = 0;
#endif
#ifdef FEAT_EVAL
static void set_vcount_ca __ARGS((cmdarg_T *cap, int *set_prevcount));
#endif
static int
#ifdef __BORLANDC__
_RTLENTRYF
#endif
nv_compare __ARGS((const void *s1, const void *s2));
static int find_command __ARGS((int cmdchar));
static void op_colon __ARGS((oparg_T *oap));
static void op_function __ARGS((oparg_T *oap));
#if defined(FEAT_MOUSE) && defined(FEAT_VISUAL)
static void find_start_of_word __ARGS((pos_T *));
static void find_end_of_word __ARGS((pos_T *));
static int get_mouse_class __ARGS((char_u *p));
#endif
static void prep_redo_cmd __ARGS((cmdarg_T *cap));
static void prep_redo __ARGS((int regname, long, int, int, int, int, int));
static int checkclearop __ARGS((oparg_T *oap));
static int checkclearopq __ARGS((oparg_T *oap));
static void clearop __ARGS((oparg_T *oap));
static void clearopbeep __ARGS((oparg_T *oap));
#ifdef FEAT_VISUAL
static void unshift_special __ARGS((cmdarg_T *cap));
#endif
#ifdef FEAT_CMDL_INFO
static void del_from_showcmd __ARGS((int));
#endif
/*
* nv_*(): functions called to handle Normal and Visual mode commands.
* n_*(): functions called to handle Normal mode commands.
* v_*(): functions called to handle Visual mode commands.
*/
static void nv_ignore __ARGS((cmdarg_T *cap));
static void nv_nop __ARGS((cmdarg_T *cap));
static void nv_error __ARGS((cmdarg_T *cap));
static void nv_help __ARGS((cmdarg_T *cap));
static void nv_addsub __ARGS((cmdarg_T *cap));
static void nv_page __ARGS((cmdarg_T *cap));
static void nv_gd __ARGS((oparg_T *oap, int nchar, int thisblock));
static int nv_screengo __ARGS((oparg_T *oap, int dir, long dist));
#ifdef FEAT_MOUSE
static void nv_mousescroll __ARGS((cmdarg_T *cap));
static void nv_mouse __ARGS((cmdarg_T *cap));
#endif
static void nv_scroll_line __ARGS((cmdarg_T *cap));
static void nv_zet __ARGS((cmdarg_T *cap));
#ifdef FEAT_GUI
static void nv_ver_scrollbar __ARGS((cmdarg_T *cap));
static void nv_hor_scrollbar __ARGS((cmdarg_T *cap));
#endif
#ifdef FEAT_GUI_TABLINE
static void nv_tabline __ARGS((cmdarg_T *cap));
static void nv_tabmenu __ARGS((cmdarg_T *cap));
#endif
static void nv_exmode __ARGS((cmdarg_T *cap));
static void nv_colon __ARGS((cmdarg_T *cap));
static void nv_ctrlg __ARGS((cmdarg_T *cap));
static void nv_ctrlh __ARGS((cmdarg_T *cap));
static void nv_clear __ARGS((cmdarg_T *cap));
static void nv_ctrlo __ARGS((cmdarg_T *cap));
static void nv_hat __ARGS((cmdarg_T *cap));
static void nv_Zet __ARGS((cmdarg_T *cap));
static void nv_ident __ARGS((cmdarg_T *cap));
static void nv_tagpop __ARGS((cmdarg_T *cap));
static void nv_scroll __ARGS((cmdarg_T *cap));
static void nv_right __ARGS((cmdarg_T *cap));
static void nv_left __ARGS((cmdarg_T *cap));
static void nv_up __ARGS((cmdarg_T *cap));
static void nv_down __ARGS((cmdarg_T *cap));
#ifdef FEAT_SEARCHPATH
static void nv_gotofile __ARGS((cmdarg_T *cap));
#endif
static void nv_end __ARGS((cmdarg_T *cap));
static void nv_dollar __ARGS((cmdarg_T *cap));
static void nv_search __ARGS((cmdarg_T *cap));
static void nv_next __ARGS((cmdarg_T *cap));
static void normal_search __ARGS((cmdarg_T *cap, int dir, char_u *pat, int opt));
static void nv_csearch __ARGS((cmdarg_T *cap));
static void nv_brackets __ARGS((cmdarg_T *cap));
static void nv_percent __ARGS((cmdarg_T *cap));
static void nv_brace __ARGS((cmdarg_T *cap));
static void nv_mark __ARGS((cmdarg_T *cap));
static void nv_findpar __ARGS((cmdarg_T *cap));
static void nv_undo __ARGS((cmdarg_T *cap));
static void nv_kundo __ARGS((cmdarg_T *cap));
static void nv_Replace __ARGS((cmdarg_T *cap));
#ifdef FEAT_VREPLACE
static void nv_vreplace __ARGS((cmdarg_T *cap));
#endif
#ifdef FEAT_VISUAL
static void v_swap_corners __ARGS((int cmdchar));
#endif
static void nv_replace __ARGS((cmdarg_T *cap));
static void n_swapchar __ARGS((cmdarg_T *cap));
static void nv_cursormark __ARGS((cmdarg_T *cap, int flag, pos_T *pos));
#ifdef FEAT_VISUAL
static void v_visop __ARGS((cmdarg_T *cap));
#endif
static void nv_subst __ARGS((cmdarg_T *cap));
static void nv_abbrev __ARGS((cmdarg_T *cap));
static void nv_optrans __ARGS((cmdarg_T *cap));
static void nv_gomark __ARGS((cmdarg_T *cap));
static void nv_pcmark __ARGS((cmdarg_T *cap));
static void nv_regname __ARGS((cmdarg_T *cap));
#ifdef FEAT_VISUAL
static void nv_visual __ARGS((cmdarg_T *cap));
static void n_start_visual_mode __ARGS((int c));
#endif
static void nv_window __ARGS((cmdarg_T *cap));
static void nv_suspend __ARGS((cmdarg_T *cap));
static void nv_g_cmd __ARGS((cmdarg_T *cap));
static void n_opencmd __ARGS((cmdarg_T *cap));
static void nv_dot __ARGS((cmdarg_T *cap));
static void nv_redo __ARGS((cmdarg_T *cap));
static void nv_Undo __ARGS((cmdarg_T *cap));
static void nv_tilde __ARGS((cmdarg_T *cap));
static void nv_operator __ARGS((cmdarg_T *cap));
#ifdef FEAT_EVAL
static void set_op_var __ARGS((int optype));
#endif
static void nv_lineop __ARGS((cmdarg_T *cap));
static void nv_home __ARGS((cmdarg_T *cap));
static void nv_pipe __ARGS((cmdarg_T *cap));
static void nv_bck_word __ARGS((cmdarg_T *cap));
static void nv_wordcmd __ARGS((cmdarg_T *cap));
static void nv_beginline __ARGS((cmdarg_T *cap));
static void adjust_cursor __ARGS((oparg_T *oap));
#ifdef FEAT_VISUAL
static void adjust_for_sel __ARGS((cmdarg_T *cap));
static int unadjust_for_sel __ARGS((void));
static void nv_select __ARGS((cmdarg_T *cap));
#endif
static void nv_goto __ARGS((cmdarg_T *cap));
static void nv_normal __ARGS((cmdarg_T *cap));
static void nv_esc __ARGS((cmdarg_T *oap));
static void nv_edit __ARGS((cmdarg_T *cap));
static void invoke_edit __ARGS((cmdarg_T *cap, int repl, int cmd, int startln));
#ifdef FEAT_TEXTOBJ
static void nv_object __ARGS((cmdarg_T *cap));
#endif
static void nv_record __ARGS((cmdarg_T *cap));
static void nv_at __ARGS((cmdarg_T *cap));
static void nv_halfpage __ARGS((cmdarg_T *cap));
static void nv_join __ARGS((cmdarg_T *cap));
static void nv_put __ARGS((cmdarg_T *cap));
static void nv_open __ARGS((cmdarg_T *cap));
#ifdef FEAT_SNIFF
static void nv_sniff __ARGS((cmdarg_T *cap));
#endif
#ifdef FEAT_NETBEANS_INTG
static void nv_nbcmd __ARGS((cmdarg_T *cap));
#endif
#ifdef FEAT_DND
static void nv_drop __ARGS((cmdarg_T *cap));
#endif
#ifdef FEAT_AUTOCMD
static void nv_cursorhold __ARGS((cmdarg_T *cap));
#endif
static char *e_noident = N_("E349: No identifier under cursor");
/*
* Function to be called for a Normal or Visual mode command.
* The argument is a cmdarg_T.
*/
typedef void (*nv_func_T) __ARGS((cmdarg_T *cap));
/* Values for cmd_flags. */
#define NV_NCH 0x01 /* may need to get a second char */
#define NV_NCH_NOP (0x02|NV_NCH) /* get second char when no operator pending */
#define NV_NCH_ALW (0x04|NV_NCH) /* always get a second char */
#define NV_LANG 0x08 /* second char needs language adjustment */
#define NV_SS 0x10 /* may start selection */
#define NV_SSS 0x20 /* may start selection with shift modifier */
#define NV_STS 0x40 /* may stop selection without shift modif. */
#define NV_RL 0x80 /* 'rightleft' modifies command */
#define NV_KEEPREG 0x100 /* don't clear regname */
#define NV_NCW 0x200 /* not allowed in command-line window */
/*
* Generally speaking, every Normal mode command should either clear any
* pending operator (with *clearop*()), or set the motion type variable
* oap->motion_type.
*
* When a cursor motion command is made, it is marked as being a character or
* line oriented motion. Then, if an operator is in effect, the operation
* becomes character or line oriented accordingly.
*/
/*
* This table contains one entry for every Normal or Visual mode command.
* The order doesn't matter, init_normal_cmds() will create a sorted index.
* It is faster when all keys from zero to '~' are present.
*/
static const struct nv_cmd
{
int cmd_char; /* (first) command character */
nv_func_T cmd_func; /* function for this command */
short_u cmd_flags; /* NV_ flags */
short cmd_arg; /* value for ca.arg */
} nv_cmds[] =
{
{NUL, nv_error, 0, 0},
{Ctrl_A, nv_addsub, 0, 0},
{Ctrl_B, nv_page, NV_STS, BACKWARD},
{Ctrl_C, nv_esc, 0, TRUE},
{Ctrl_D, nv_halfpage, 0, 0},
{Ctrl_E, nv_scroll_line, 0, TRUE},
{Ctrl_F, nv_page, NV_STS, FORWARD},
{Ctrl_G, nv_ctrlg, 0, 0},
{Ctrl_H, nv_ctrlh, 0, 0},
{Ctrl_I, nv_pcmark, 0, 0},
{NL, nv_down, 0, FALSE},
{Ctrl_K, nv_error, 0, 0},
{Ctrl_L, nv_clear, 0, 0},
{Ctrl_M, nv_down, 0, TRUE},
{Ctrl_N, nv_down, NV_STS, FALSE},
{Ctrl_O, nv_ctrlo, 0, 0},
{Ctrl_P, nv_up, NV_STS, FALSE},
#ifdef FEAT_VISUAL
{Ctrl_Q, nv_visual, 0, FALSE},
#else
{Ctrl_Q, nv_ignore, 0, 0},
#endif
{Ctrl_R, nv_redo, 0, 0},
{Ctrl_S, nv_ignore, 0, 0},
{Ctrl_T, nv_tagpop, NV_NCW, 0},
{Ctrl_U, nv_halfpage, 0, 0},
#ifdef FEAT_VISUAL
{Ctrl_V, nv_visual, 0, FALSE},
{'V', nv_visual, 0, FALSE},
{'v', nv_visual, 0, FALSE},
#else
{Ctrl_V, nv_error, 0, 0},
{'V', nv_error, 0, 0},
{'v', nv_error, 0, 0},
#endif
{Ctrl_W, nv_window, 0, 0},
{Ctrl_X, nv_addsub, 0, 0},
{Ctrl_Y, nv_scroll_line, 0, FALSE},
{Ctrl_Z, nv_suspend, 0, 0},
{ESC, nv_esc, 0, FALSE},
{Ctrl_BSL, nv_normal, NV_NCH_ALW, 0},
{Ctrl_RSB, nv_ident, NV_NCW, 0},
{Ctrl_HAT, nv_hat, NV_NCW, 0},
{Ctrl__, nv_error, 0, 0},
{' ', nv_right, 0, 0},
{'!', nv_operator, 0, 0},
{'"', nv_regname, NV_NCH_NOP|NV_KEEPREG, 0},
{'#', nv_ident, 0, 0},
{'$', nv_dollar, 0, 0},
{'%', nv_percent, 0, 0},
{'&', nv_optrans, 0, 0},
{'\'', nv_gomark, NV_NCH_ALW, TRUE},
{'(', nv_brace, 0, BACKWARD},
{')', nv_brace, 0, FORWARD},
{'*', nv_ident, 0, 0},
{'+', nv_down, 0, TRUE},
{',', nv_csearch, 0, TRUE},
{'-', nv_up, 0, TRUE},
{'.', nv_dot, NV_KEEPREG, 0},
{'/', nv_search, 0, FALSE},
{'0', nv_beginline, 0, 0},
{'1', nv_ignore, 0, 0},
{'2', nv_ignore, 0, 0},
{'3', nv_ignore, 0, 0},
{'4', nv_ignore, 0, 0},
{'5', nv_ignore, 0, 0},
{'6', nv_ignore, 0, 0},
{'7', nv_ignore, 0, 0},
{'8', nv_ignore, 0, 0},
{'9', nv_ignore, 0, 0},
{':', nv_colon, 0, 0},
{';', nv_csearch, 0, FALSE},
{'<', nv_operator, NV_RL, 0},
{'=', nv_operator, 0, 0},
{'>', nv_operator, NV_RL, 0},
{'?', nv_search, 0, FALSE},
{'@', nv_at, NV_NCH_NOP, FALSE},
{'A', nv_edit, 0, 0},
{'B', nv_bck_word, 0, 1},
{'C', nv_abbrev, NV_KEEPREG, 0},
{'D', nv_abbrev, NV_KEEPREG, 0},
{'E', nv_wordcmd, 0, TRUE},
{'F', nv_csearch, NV_NCH_ALW|NV_LANG, BACKWARD},
{'G', nv_goto, 0, TRUE},
{'H', nv_scroll, 0, 0},
{'I', nv_edit, 0, 0},
{'J', nv_join, 0, 0},
{'K', nv_ident, 0, 0},
{'L', nv_scroll, 0, 0},
{'M', nv_scroll, 0, 0},
{'N', nv_next, 0, SEARCH_REV},
{'O', nv_open, 0, 0},
{'P', nv_put, 0, 0},
{'Q', nv_exmode, NV_NCW, 0},
{'R', nv_Replace, 0, FALSE},
{'S', nv_subst, NV_KEEPREG, 0},
{'T', nv_csearch, NV_NCH_ALW|NV_LANG, BACKWARD},
{'U', nv_Undo, 0, 0},
{'W', nv_wordcmd, 0, TRUE},
{'X', nv_abbrev, NV_KEEPREG, 0},
{'Y', nv_abbrev, NV_KEEPREG, 0},
{'Z', nv_Zet, NV_NCH_NOP|NV_NCW, 0},
{'[', nv_brackets, NV_NCH_ALW, BACKWARD},
{'\\', nv_error, 0, 0},
{']', nv_brackets, NV_NCH_ALW, FORWARD},
{'^', nv_beginline, 0, BL_WHITE | BL_FIX},
{'_', nv_lineop, 0, 0},
{'`', nv_gomark, NV_NCH_ALW, FALSE},
{'a', nv_edit, NV_NCH, 0},
{'b', nv_bck_word, 0, 0},
{'c', nv_operator, 0, 0},
{'d', nv_operator, 0, 0},
{'e', nv_wordcmd, 0, FALSE},
{'f', nv_csearch, NV_NCH_ALW|NV_LANG, FORWARD},
{'g', nv_g_cmd, NV_NCH_ALW, FALSE},
{'h', nv_left, NV_RL, 0},
{'i', nv_edit, NV_NCH, 0},
{'j', nv_down, 0, FALSE},
{'k', nv_up, 0, FALSE},
{'l', nv_right, NV_RL, 0},
{'m', nv_mark, NV_NCH_NOP, 0},
{'n', nv_next, 0, 0},
{'o', nv_open, 0, 0},
{'p', nv_put, 0, 0},
{'q', nv_record, NV_NCH, 0},
{'r', nv_replace, NV_NCH_NOP|NV_LANG, 0},
{'s', nv_subst, NV_KEEPREG, 0},
{'t', nv_csearch, NV_NCH_ALW|NV_LANG, FORWARD},
{'u', nv_undo, 0, 0},
{'w', nv_wordcmd, 0, FALSE},
{'x', nv_abbrev, NV_KEEPREG, 0},
{'y', nv_operator, 0, 0},
{'z', nv_zet, NV_NCH_ALW, 0},
{'{', nv_findpar, 0, BACKWARD},
{'|', nv_pipe, 0, 0},
{'}', nv_findpar, 0, FORWARD},
{'~', nv_tilde, 0, 0},
/* pound sign */
{POUND, nv_ident, 0, 0},
#ifdef FEAT_MOUSE
{K_MOUSEUP, nv_mousescroll, 0, MSCR_UP},
{K_MOUSEDOWN, nv_mousescroll, 0, MSCR_DOWN},
{K_MOUSELEFT, nv_mousescroll, 0, MSCR_LEFT},
{K_MOUSERIGHT, nv_mousescroll, 0, MSCR_RIGHT},
{K_LEFTMOUSE, nv_mouse, 0, 0},
{K_LEFTMOUSE_NM, nv_mouse, 0, 0},
{K_LEFTDRAG, nv_mouse, 0, 0},
{K_LEFTRELEASE, nv_mouse, 0, 0},
{K_LEFTRELEASE_NM, nv_mouse, 0, 0},
{K_MIDDLEMOUSE, nv_mouse, 0, 0},
{K_MIDDLEDRAG, nv_mouse, 0, 0},
{K_MIDDLERELEASE, nv_mouse, 0, 0},
{K_RIGHTMOUSE, nv_mouse, 0, 0},
{K_RIGHTDRAG, nv_mouse, 0, 0},
{K_RIGHTRELEASE, nv_mouse, 0, 0},
{K_X1MOUSE, nv_mouse, 0, 0},
{K_X1DRAG, nv_mouse, 0, 0},
{K_X1RELEASE, nv_mouse, 0, 0},
{K_X2MOUSE, nv_mouse, 0, 0},
{K_X2DRAG, nv_mouse, 0, 0},
{K_X2RELEASE, nv_mouse, 0, 0},
#endif
{K_IGNORE, nv_ignore, NV_KEEPREG, 0},
{K_NOP, nv_nop, 0, 0},
{K_INS, nv_edit, 0, 0},
{K_KINS, nv_edit, 0, 0},
{K_BS, nv_ctrlh, 0, 0},
{K_UP, nv_up, NV_SSS|NV_STS, FALSE},
{K_S_UP, nv_page, NV_SS, BACKWARD},
{K_DOWN, nv_down, NV_SSS|NV_STS, FALSE},
{K_S_DOWN, nv_page, NV_SS, FORWARD},
{K_LEFT, nv_left, NV_SSS|NV_STS|NV_RL, 0},
{K_S_LEFT, nv_bck_word, NV_SS|NV_RL, 0},
{K_C_LEFT, nv_bck_word, NV_SSS|NV_RL|NV_STS, 1},
{K_RIGHT, nv_right, NV_SSS|NV_STS|NV_RL, 0},
{K_S_RIGHT, nv_wordcmd, NV_SS|NV_RL, FALSE},
{K_C_RIGHT, nv_wordcmd, NV_SSS|NV_RL|NV_STS, TRUE},
{K_PAGEUP, nv_page, NV_SSS|NV_STS, BACKWARD},
{K_KPAGEUP, nv_page, NV_SSS|NV_STS, BACKWARD},
{K_PAGEDOWN, nv_page, NV_SSS|NV_STS, FORWARD},
{K_KPAGEDOWN, nv_page, NV_SSS|NV_STS, FORWARD},
{K_END, nv_end, NV_SSS|NV_STS, FALSE},
{K_KEND, nv_end, NV_SSS|NV_STS, FALSE},
{K_S_END, nv_end, NV_SS, FALSE},
{K_C_END, nv_end, NV_SSS|NV_STS, TRUE},
{K_HOME, nv_home, NV_SSS|NV_STS, 0},
{K_KHOME, nv_home, NV_SSS|NV_STS, 0},
{K_S_HOME, nv_home, NV_SS, 0},
{K_C_HOME, nv_goto, NV_SSS|NV_STS, FALSE},
{K_DEL, nv_abbrev, 0, 0},
{K_KDEL, nv_abbrev, 0, 0},
{K_UNDO, nv_kundo, 0, 0},
{K_HELP, nv_help, NV_NCW, 0},
{K_F1, nv_help, NV_NCW, 0},
{K_XF1, nv_help, NV_NCW, 0},
#ifdef FEAT_VISUAL
{K_SELECT, nv_select, 0, 0},
#endif
#ifdef FEAT_GUI
{K_VER_SCROLLBAR, nv_ver_scrollbar, 0, 0},
{K_HOR_SCROLLBAR, nv_hor_scrollbar, 0, 0},
#endif
#ifdef FEAT_GUI_TABLINE
{K_TABLINE, nv_tabline, 0, 0},
{K_TABMENU, nv_tabmenu, 0, 0},
#endif
#ifdef FEAT_FKMAP
{K_F8, farsi_fkey, 0, 0},
{K_F9, farsi_fkey, 0, 0},
#endif
#ifdef FEAT_SNIFF
{K_SNIFF, nv_sniff, 0, 0},
#endif
#ifdef FEAT_NETBEANS_INTG
{K_F21, nv_nbcmd, NV_NCH_ALW, 0},
#endif
#ifdef FEAT_DND
{K_DROP, nv_drop, NV_STS, 0},
#endif
#ifdef FEAT_AUTOCMD
{K_CURSORHOLD, nv_cursorhold, NV_KEEPREG, 0},
#endif
};
/* Number of commands in nv_cmds[]. */
#define NV_CMDS_SIZE (sizeof(nv_cmds) / sizeof(struct nv_cmd))
/* Sorted index of commands in nv_cmds[]. */
static short nv_cmd_idx[NV_CMDS_SIZE];
/* The highest index for which
* nv_cmds[idx].cmd_char == nv_cmd_idx[nv_cmds[idx].cmd_char] */
static int nv_max_linear;
/*
* Compare functions for qsort() below, that checks the command character
* through the index in nv_cmd_idx[].
*/
static int
#ifdef __BORLANDC__
_RTLENTRYF
#endif
nv_compare(s1, s2)
const void *s1;
const void *s2;
{
int c1, c2;
/* The commands are sorted on absolute value. */
c1 = nv_cmds[*(const short *)s1].cmd_char;
c2 = nv_cmds[*(const short *)s2].cmd_char;
if (c1 < 0)
c1 = -c1;
if (c2 < 0)
c2 = -c2;
return c1 - c2;
}
/*
* Initialize the nv_cmd_idx[] table.
*/
void
init_normal_cmds()
{
int i;
/* Fill the index table with a one to one relation. */
for (i = 0; i < (int)NV_CMDS_SIZE; ++i)
nv_cmd_idx[i] = i;
/* Sort the commands by the command character. */
qsort((void *)&nv_cmd_idx, (size_t)NV_CMDS_SIZE, sizeof(short), nv_compare);
/* Find the first entry that can't be indexed by the command character. */
for (i = 0; i < (int)NV_CMDS_SIZE; ++i)
if (i != nv_cmds[nv_cmd_idx[i]].cmd_char)
break;
nv_max_linear = i - 1;
}
/*
* Search for a command in the commands table.
* Returns -1 for invalid command.
*/
static int
find_command(cmdchar)
int cmdchar;
{
int i;
int idx;
int top, bot;
int c;
#ifdef FEAT_MBYTE
/* A multi-byte character is never a command. */
if (cmdchar >= 0x100)
return -1;
#endif
/* We use the absolute value of the character. Special keys have a
* negative value, but are sorted on their absolute value. */
if (cmdchar < 0)
cmdchar = -cmdchar;
/* If the character is in the first part: The character is the index into
* nv_cmd_idx[]. */
if (cmdchar <= nv_max_linear)
return nv_cmd_idx[cmdchar];
/* Perform a binary search. */
bot = nv_max_linear + 1;
top = NV_CMDS_SIZE - 1;
idx = -1;
while (bot <= top)
{
i = (top + bot) / 2;
c = nv_cmds[nv_cmd_idx[i]].cmd_char;
if (c < 0)
c = -c;
if (cmdchar == c)
{
idx = nv_cmd_idx[i];
break;
}
if (cmdchar > c)
bot = i + 1;
else
top = i - 1;
}
return idx;
}
/*
* Execute a command in Normal mode.
*/
void
normal_cmd(oap, toplevel)
oparg_T *oap;
int toplevel UNUSED; /* TRUE when called from main() */
{
cmdarg_T ca; /* command arguments */
int c;
int ctrl_w = FALSE; /* got CTRL-W command */
int old_col = curwin->w_curswant;
#ifdef FEAT_CMDL_INFO
int need_flushbuf; /* need to call out_flush() */
#endif
#ifdef FEAT_VISUAL
pos_T old_pos; /* cursor position before command */
int mapped_len;
static int old_mapped_len = 0;
#endif
int idx;
#ifdef FEAT_EVAL
int set_prevcount = FALSE;
#endif
vim_memset(&ca, 0, sizeof(ca)); /* also resets ca.retval */
ca.oap = oap;
/* Use a count remembered from before entering an operator. After typing
* "3d" we return from normal_cmd() and come back here, the "3" is
* remembered in "opcount". */
ca.opcount = opcount;
#ifdef FEAT_SNIFF
want_sniff_request = sniff_connected;
#endif
/*
* If there is an operator pending, then the command we take this time
* will terminate it. Finish_op tells us to finish the operation before
* returning this time (unless the operation was cancelled).
*/
#ifdef CURSOR_SHAPE
c = finish_op;
#endif
finish_op = (oap->op_type != OP_NOP);
#ifdef CURSOR_SHAPE
if (finish_op != c)
{
ui_cursor_shape(); /* may show different cursor shape */
# ifdef FEAT_MOUSESHAPE
update_mouseshape(-1);
# endif
}
#endif
/* When not finishing an operator and no register name typed, reset the
* count. */
if (!finish_op && !oap->regname)
{
ca.opcount = 0;
#ifdef FEAT_EVAL
set_prevcount = TRUE;
#endif
}
#ifdef FEAT_AUTOCMD
/* Restore counts from before receiving K_CURSORHOLD. This means after
* typing "3", handling K_CURSORHOLD and then typing "2" we get "32", not
* "3 * 2". */
if (oap->prev_opcount > 0 || oap->prev_count0 > 0)
{
ca.opcount = oap->prev_opcount;
ca.count0 = oap->prev_count0;
oap->prev_opcount = 0;
oap->prev_count0 = 0;
}
#endif
#ifdef FEAT_VISUAL
mapped_len = typebuf_maplen();
#endif
State = NORMAL_BUSY;
#ifdef USE_ON_FLY_SCROLL
dont_scroll = FALSE; /* allow scrolling here */
#endif
#ifdef FEAT_EVAL
/* Set v:count here, when called from main() and not a stuffed
* command, so that v:count can be used in an expression mapping
* when there is no count. */
if (toplevel && stuff_empty())
set_vcount_ca(&ca, &set_prevcount);
#endif
/*
* Get the command character from the user.
*/
c = safe_vgetc();
LANGMAP_ADJUST(c, TRUE);
#ifdef FEAT_VISUAL
/*
* If a mapping was started in Visual or Select mode, remember the length
* of the mapping. This is used below to not return to Insert mode for as
* long as the mapping is being executed.
*/
if (restart_edit == 0)
old_mapped_len = 0;
else if (old_mapped_len
|| (VIsual_active && mapped_len == 0 && typebuf_maplen() > 0))
old_mapped_len = typebuf_maplen();
#endif
if (c == NUL)
c = K_ZERO;
#ifdef FEAT_VISUAL
/*
* In Select mode, typed text replaces the selection.
*/
if (VIsual_active
&& VIsual_select
&& (vim_isprintc(c) || c == NL || c == CAR || c == K_KENTER))
{
/* Fake a "c"hange command. When "restart_edit" is set (e.g., because
* 'insertmode' is set) fake a "d"elete command, Insert mode will
* restart automatically.
* Insert the typed character in the typeahead buffer, so that it can
* be mapped in Insert mode. Required for ":lmap" to work. */
ins_char_typebuf(c);
if (restart_edit != 0)
c = 'd';
else
c = 'c';
msg_nowait = TRUE; /* don't delay going to insert mode */
}
#endif
#ifdef FEAT_CMDL_INFO
need_flushbuf = add_to_showcmd(c);
#endif
getcount:
#ifdef FEAT_VISUAL
if (!(VIsual_active && VIsual_select))
#endif
{
/*
* Handle a count before a command and compute ca.count0.
* Note that '0' is a command and not the start of a count, but it's
* part of a count after other digits.
*/
while ( (c >= '1' && c <= '9')
|| (ca.count0 != 0 && (c == K_DEL || c == K_KDEL || c == '0')))
{
if (c == K_DEL || c == K_KDEL)
{
ca.count0 /= 10;
#ifdef FEAT_CMDL_INFO
del_from_showcmd(4); /* delete the digit and ~@% */
#endif
}
else
ca.count0 = ca.count0 * 10 + (c - '0');
if (ca.count0 < 0) /* got too large! */
ca.count0 = 999999999L;
#ifdef FEAT_EVAL
/* Set v:count here, when called from main() and not a stuffed
* command, so that v:count can be used in an expression mapping
* right after the count. */
if (toplevel && stuff_empty())
set_vcount_ca(&ca, &set_prevcount);
#endif
if (ctrl_w)
{
++no_mapping;
++allow_keys; /* no mapping for nchar, but keys */
}
++no_zero_mapping; /* don't map zero here */
c = plain_vgetc();
LANGMAP_ADJUST(c, TRUE);
--no_zero_mapping;
if (ctrl_w)
{
--no_mapping;
--allow_keys;
}
#ifdef FEAT_CMDL_INFO
need_flushbuf |= add_to_showcmd(c);
#endif
}
/*
* If we got CTRL-W there may be a/another count
*/
if (c == Ctrl_W && !ctrl_w && oap->op_type == OP_NOP)
{
ctrl_w = TRUE;
ca.opcount = ca.count0; /* remember first count */
ca.count0 = 0;
++no_mapping;
++allow_keys; /* no mapping for nchar, but keys */
c = plain_vgetc(); /* get next character */
LANGMAP_ADJUST(c, TRUE);
--no_mapping;
--allow_keys;
#ifdef FEAT_CMDL_INFO
need_flushbuf |= add_to_showcmd(c);
#endif
goto getcount; /* jump back */
}
}
#ifdef FEAT_AUTOCMD
if (c == K_CURSORHOLD)
{
/* Save the count values so that ca.opcount and ca.count0 are exactly
* the same when coming back here after handling K_CURSORHOLD. */
oap->prev_opcount = ca.opcount;
oap->prev_count0 = ca.count0;
}
else
#endif
if (ca.opcount != 0)
{
/*
* If we're in the middle of an operator (including after entering a
* yank buffer with '"') AND we had a count before the operator, then
* that count overrides the current value of ca.count0.
* What this means effectively, is that commands like "3dw" get turned
* into "d3w" which makes things fall into place pretty neatly.
* If you give a count before AND after the operator, they are
* multiplied.
*/
if (ca.count0)
ca.count0 *= ca.opcount;
else
ca.count0 = ca.opcount;
}
/*
* Always remember the count. It will be set to zero (on the next call,
* above) when there is no pending operator.
* When called from main(), save the count for use by the "count" built-in
* variable.
*/
ca.opcount = ca.count0;
ca.count1 = (ca.count0 == 0 ? 1 : ca.count0);
#ifdef FEAT_EVAL
/*
* Only set v:count when called from main() and not a stuffed command.
*/
if (toplevel && stuff_empty())
set_vcount(ca.count0, ca.count1, set_prevcount);
#endif
/*
* Find the command character in the table of commands.
* For CTRL-W we already got nchar when looking for a count.
*/
if (ctrl_w)
{
ca.nchar = c;
ca.cmdchar = Ctrl_W;
}
else
ca.cmdchar = c;
idx = find_command(ca.cmdchar);
if (idx < 0)
{
/* Not a known command: beep. */
clearopbeep(oap);
goto normal_end;
}
if (text_locked() && (nv_cmds[idx].cmd_flags & NV_NCW))
{
/* This command is not allowed while editing a ccmdline: beep. */
clearopbeep(oap);
text_locked_msg();
goto normal_end;
}
#ifdef FEAT_AUTOCMD
if ((nv_cmds[idx].cmd_flags & NV_NCW) && curbuf_locked())
goto normal_end;
#endif
#ifdef FEAT_VISUAL
/*
* In Visual/Select mode, a few keys are handled in a special way.
*/
if (VIsual_active)
{
/* when 'keymodel' contains "stopsel" may stop Select/Visual mode */
if (km_stopsel
&& (nv_cmds[idx].cmd_flags & NV_STS)
&& !(mod_mask & MOD_MASK_SHIFT))
{
end_visual_mode();
redraw_curbuf_later(INVERTED);
}
/* Keys that work different when 'keymodel' contains "startsel" */
if (km_startsel)
{
if (nv_cmds[idx].cmd_flags & NV_SS)
{
unshift_special(&ca);
idx = find_command(ca.cmdchar);
if (idx < 0)
{
/* Just in case */
clearopbeep(oap);
goto normal_end;
}
}
else if ((nv_cmds[idx].cmd_flags & NV_SSS)
&& (mod_mask & MOD_MASK_SHIFT))
{
mod_mask &= ~MOD_MASK_SHIFT;
}
}
}
#endif
#ifdef FEAT_RIGHTLEFT
if (curwin->w_p_rl && KeyTyped && !KeyStuffed
&& (nv_cmds[idx].cmd_flags & NV_RL))
{
/* Invert horizontal movements and operations. Only when typed by the
* user directly, not when the result of a mapping or "x" translated
* to "dl". */
switch (ca.cmdchar)
{
case 'l': ca.cmdchar = 'h'; break;
case K_RIGHT: ca.cmdchar = K_LEFT; break;
case K_S_RIGHT: ca.cmdchar = K_S_LEFT; break;
case K_C_RIGHT: ca.cmdchar = K_C_LEFT; break;
case 'h': ca.cmdchar = 'l'; break;
case K_LEFT: ca.cmdchar = K_RIGHT; break;
case K_S_LEFT: ca.cmdchar = K_S_RIGHT; break;
case K_C_LEFT: ca.cmdchar = K_C_RIGHT; break;
case '>': ca.cmdchar = '<'; break;
case '<': ca.cmdchar = '>'; break;
}
idx = find_command(ca.cmdchar);
}
#endif
/*
* Get an additional character if we need one.
*/
if ((nv_cmds[idx].cmd_flags & NV_NCH)
&& (((nv_cmds[idx].cmd_flags & NV_NCH_NOP) == NV_NCH_NOP
&& oap->op_type == OP_NOP)
|| (nv_cmds[idx].cmd_flags & NV_NCH_ALW) == NV_NCH_ALW
|| (ca.cmdchar == 'q'
&& oap->op_type == OP_NOP
&& !Recording
&& !Exec_reg)
|| ((ca.cmdchar == 'a' || ca.cmdchar == 'i')
&& (oap->op_type != OP_NOP
#ifdef FEAT_VISUAL
|| VIsual_active
#endif
))))
{
int *cp;
int repl = FALSE; /* get character for replace mode */
int lit = FALSE; /* get extra character literally */
int langmap_active = FALSE; /* using :lmap mappings */
int lang; /* getting a text character */
#ifdef USE_IM_CONTROL
int save_smd; /* saved value of p_smd */
#endif
++no_mapping;
++allow_keys; /* no mapping for nchar, but allow key codes */
#ifdef FEAT_AUTOCMD
/* Don't generate a CursorHold event here, most commands can't handle
* it, e.g., nv_replace(), nv_csearch(). */
did_cursorhold = TRUE;
#endif
if (ca.cmdchar == 'g')
{
/*
* For 'g' get the next character now, so that we can check for
* "gr", "g'" and "g`".
*/
ca.nchar = plain_vgetc();
LANGMAP_ADJUST(ca.nchar, TRUE);
#ifdef FEAT_CMDL_INFO
need_flushbuf |= add_to_showcmd(ca.nchar);
#endif
if (ca.nchar == 'r' || ca.nchar == '\'' || ca.nchar == '`'
|| ca.nchar == Ctrl_BSL)
{
cp = &ca.extra_char; /* need to get a third character */
if (ca.nchar != 'r')
lit = TRUE; /* get it literally */
else
repl = TRUE; /* get it in replace mode */
}
else
cp = NULL; /* no third character needed */
}
else
{
if (ca.cmdchar == 'r') /* get it in replace mode */
repl = TRUE;
cp = &ca.nchar;
}
lang = (repl || (nv_cmds[idx].cmd_flags & NV_LANG));
/*
* Get a second or third character.
*/
if (cp != NULL)
{
#ifdef CURSOR_SHAPE
if (repl)
{
State = REPLACE; /* pretend Replace mode */
ui_cursor_shape(); /* show different cursor shape */
}
#endif
if (lang && curbuf->b_p_iminsert == B_IMODE_LMAP)
{
/* Allow mappings defined with ":lmap". */
--no_mapping;
--allow_keys;
if (repl)
State = LREPLACE;
else
State = LANGMAP;
langmap_active = TRUE;
}
#ifdef USE_IM_CONTROL
save_smd = p_smd;
p_smd = FALSE; /* Don't let the IM code show the mode here */
if (lang && curbuf->b_p_iminsert == B_IMODE_IM)
im_set_active(TRUE);
#endif
*cp = plain_vgetc();
if (langmap_active)
{
/* Undo the decrement done above */
++no_mapping;
++allow_keys;
State = NORMAL_BUSY;
}
#ifdef USE_IM_CONTROL
if (lang)
{
if (curbuf->b_p_iminsert != B_IMODE_LMAP)
im_save_status(&curbuf->b_p_iminsert);
im_set_active(FALSE);
}
p_smd = save_smd;
#endif
#ifdef CURSOR_SHAPE
State = NORMAL_BUSY;
#endif
#ifdef FEAT_CMDL_INFO
need_flushbuf |= add_to_showcmd(*cp);
#endif
if (!lit)
{
#ifdef FEAT_DIGRAPHS
/* Typing CTRL-K gets a digraph. */
if (*cp == Ctrl_K
&& ((nv_cmds[idx].cmd_flags & NV_LANG)
|| cp == &ca.extra_char)
&& vim_strchr(p_cpo, CPO_DIGRAPH) == NULL)
{
c = get_digraph(FALSE);
if (c > 0)
{
*cp = c;
# ifdef FEAT_CMDL_INFO
/* Guessing how to update showcmd here... */
del_from_showcmd(3);
need_flushbuf |= add_to_showcmd(*cp);
# endif
}
}
#endif
/* adjust chars > 127, except after "tTfFr" commands */
LANGMAP_ADJUST(*cp, !lang);
#ifdef FEAT_RIGHTLEFT
/* adjust Hebrew mapped char */
if (p_hkmap && lang && KeyTyped)
*cp = hkmap(*cp);
# ifdef FEAT_FKMAP
/* adjust Farsi mapped char */
if (p_fkmap && lang && KeyTyped)
*cp = fkmap(*cp);
# endif
#endif
}
/*
* When the next character is CTRL-\ a following CTRL-N means the
* command is aborted and we go to Normal mode.
*/
if (cp == &ca.extra_char
&& ca.nchar == Ctrl_BSL
&& (ca.extra_char == Ctrl_N || ca.extra_char == Ctrl_G))
{
ca.cmdchar = Ctrl_BSL;
ca.nchar = ca.extra_char;
idx = find_command(ca.cmdchar);
}
else if (*cp == Ctrl_BSL)
{
long towait = (p_ttm >= 0 ? p_ttm : p_tm);
/* There is a busy wait here when typing "f<C-\>" and then
* something different from CTRL-N. Can't be avoided. */
while ((c = vpeekc()) <= 0 && towait > 0L)
{
do_sleep(towait > 50L ? 50L : towait);
towait -= 50L;
}
if (c > 0)
{
c = plain_vgetc();
if (c != Ctrl_N && c != Ctrl_G)
vungetc(c);
else
{
ca.cmdchar = Ctrl_BSL;
ca.nchar = c;
idx = find_command(ca.cmdchar);
}
}
}
#ifdef FEAT_MBYTE
/* When getting a text character and the next character is a
* multi-byte character, it could be a composing character.
* However, don't wait for it to arrive. */
while (enc_utf8 && lang && (c = vpeekc()) > 0
&& (c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1))
{
c = plain_vgetc();
if (!utf_iscomposing(c))
{
vungetc(c); /* it wasn't, put it back */
break;
}
else if (ca.ncharC1 == 0)
ca.ncharC1 = c;
else
ca.ncharC2 = c;
}
#endif
}
--no_mapping;
--allow_keys;
}
#ifdef FEAT_CMDL_INFO
/*
* Flush the showcmd characters onto the screen so we can see them while
* the command is being executed. Only do this when the shown command was
* actually displayed, otherwise this will slow down a lot when executing
* mappings.
*/
if (need_flushbuf)
out_flush();
#endif
#ifdef FEAT_AUTOCMD
if (ca.cmdchar != K_IGNORE)
did_cursorhold = FALSE;
#endif
State = NORMAL;
if (ca.nchar == ESC)
{
clearop(oap);
if (restart_edit == 0 && goto_im())
restart_edit = 'a';
goto normal_end;
}
if (ca.cmdchar != K_IGNORE)
{
msg_didout = FALSE; /* don't scroll screen up for normal command */
msg_col = 0;
}
#ifdef FEAT_VISUAL
old_pos = curwin->w_cursor; /* remember where cursor was */
/* When 'keymodel' contains "startsel" some keys start Select/Visual
* mode. */
if (!VIsual_active && km_startsel)
{
if (nv_cmds[idx].cmd_flags & NV_SS)
{
start_selection();
unshift_special(&ca);
idx = find_command(ca.cmdchar);
}
else if ((nv_cmds[idx].cmd_flags & NV_SSS)
&& (mod_mask & MOD_MASK_SHIFT))
{
start_selection();
mod_mask &= ~MOD_MASK_SHIFT;
}
}
#endif
/*
* Execute the command!
* Call the command function found in the commands table.
*/
ca.arg = nv_cmds[idx].cmd_arg;
(nv_cmds[idx].cmd_func)(&ca);
/*
* If we didn't start or finish an operator, reset oap->regname, unless we
* need it later.
*/
if (!finish_op
&& !oap->op_type
&& (idx < 0 || !(nv_cmds[idx].cmd_flags & NV_KEEPREG)))
{
clearop(oap);
#ifdef FEAT_EVAL
{
int regname = 0;
/* Adjust the register according to 'clipboard', so that when
* "unnamed" is present it becomes '*' or '+' instead of '"'. */
# ifdef FEAT_CLIPBOARD
adjust_clip_reg(&regname);
# endif
set_reg_var(regname);
}
#endif
}
#ifdef FEAT_VISUAL
/* Get the length of mapped chars again after typing a count, second
* character or "z333<cr>". */
if (old_mapped_len > 0)
old_mapped_len = typebuf_maplen();
#endif
/*
* If an operation is pending, handle it...
*/
do_pending_operator(&ca, old_col, FALSE);
/*
* Wait for a moment when a message is displayed that will be overwritten
* by the mode message.
* In Visual mode and with "^O" in Insert mode, a short message will be
* overwritten by the mode message. Wait a bit, until a key is hit.
* In Visual mode, it's more important to keep the Visual area updated
* than keeping a message (e.g. from a /pat search).
* Only do this if the command was typed, not from a mapping.
* Don't wait when emsg_silent is non-zero.
* Also wait a bit after an error message, e.g. for "^O:".
* Don't redraw the screen, it would remove the message.
*/
if ( ((p_smd
&& msg_silent == 0
&& (restart_edit != 0
#ifdef FEAT_VISUAL
|| (VIsual_active
&& old_pos.lnum == curwin->w_cursor.lnum
&& old_pos.col == curwin->w_cursor.col)
#endif
)
&& (clear_cmdline
|| redraw_cmdline)
&& (msg_didout || (msg_didany && msg_scroll))
&& !msg_nowait
&& KeyTyped)
|| (restart_edit != 0
#ifdef FEAT_VISUAL
&& !VIsual_active
#endif
&& (msg_scroll
|| emsg_on_display)))
&& oap->regname == 0
&& !(ca.retval & CA_COMMAND_BUSY)
&& stuff_empty()
&& typebuf_typed()
&& emsg_silent == 0
&& !did_wait_return
&& oap->op_type == OP_NOP)
{
int save_State = State;
/* Draw the cursor with the right shape here */
if (restart_edit != 0)
State = INSERT;
/* If need to redraw, and there is a "keep_msg", redraw before the
* delay */
if (must_redraw && keep_msg != NULL && !emsg_on_display)
{
char_u *kmsg;
kmsg = keep_msg;
keep_msg = NULL;
/* showmode() will clear keep_msg, but we want to use it anyway */
update_screen(0);
/* now reset it, otherwise it's put in the history again */
keep_msg = kmsg;
msg_attr(kmsg, keep_msg_attr);
vim_free(kmsg);
}
setcursor();
cursor_on();
out_flush();
if (msg_scroll || emsg_on_display)
ui_delay(1000L, TRUE); /* wait at least one second */
ui_delay(3000L, FALSE); /* wait up to three seconds */
State = save_State;
msg_scroll = FALSE;
emsg_on_display = FALSE;
}
/*
* Finish up after executing a Normal mode command.
*/
normal_end:
msg_nowait = FALSE;
/* Reset finish_op, in case it was set */
#ifdef CURSOR_SHAPE
c = finish_op;
#endif
finish_op = FALSE;
#ifdef CURSOR_SHAPE
/* Redraw the cursor with another shape, if we were in Operator-pending
* mode or did a replace command. */
if (c || ca.cmdchar == 'r')
{
ui_cursor_shape(); /* may show different cursor shape */
# ifdef FEAT_MOUSESHAPE
update_mouseshape(-1);
# endif
}
#endif
#ifdef FEAT_CMDL_INFO
if (oap->op_type == OP_NOP && oap->regname == 0
# ifdef FEAT_AUTOCMD
&& ca.cmdchar != K_CURSORHOLD
# endif
)
clear_showcmd();
#endif
checkpcmark(); /* check if we moved since setting pcmark */
vim_free(ca.searchbuf);
#ifdef FEAT_MBYTE
if (has_mbyte)
mb_adjust_cursor();
#endif
#ifdef FEAT_SCROLLBIND
if (curwin->w_p_scb && toplevel)
{
validate_cursor(); /* may need to update w_leftcol */
do_check_scrollbind(TRUE);
}
#endif
#ifdef FEAT_CURSORBIND
if (curwin->w_p_crb && toplevel)
{
validate_cursor(); /* may need to update w_leftcol */
do_check_cursorbind();
}
#endif
/*
* May restart edit(), if we got here with CTRL-O in Insert mode (but not
* if still inside a mapping that started in Visual mode).
* May switch from Visual to Select mode after CTRL-O command.
*/
if ( oap->op_type == OP_NOP
#ifdef FEAT_VISUAL
&& ((restart_edit != 0 && !VIsual_active && old_mapped_len == 0)
|| restart_VIsual_select == 1)
#else
&& restart_edit != 0
#endif
&& !(ca.retval & CA_COMMAND_BUSY)
&& stuff_empty()
&& oap->regname == 0)
{
#ifdef FEAT_VISUAL
if (restart_VIsual_select == 1)
{
VIsual_select = TRUE;
showmode();
restart_VIsual_select = 0;
}
#endif
if (restart_edit != 0
#ifdef FEAT_VISUAL
&& !VIsual_active && old_mapped_len == 0
#endif
)
(void)edit(restart_edit, FALSE, 1L);
}
#ifdef FEAT_VISUAL
if (restart_VIsual_select == 2)
restart_VIsual_select = 1;
#endif
/* Save count before an operator for next time. */
opcount = ca.opcount;
}
#ifdef FEAT_EVAL
/*
* Set v:count and v:count1 according to "cap".
* Set v:prevcount only when "set_prevcount" is TRUE.
*/
static void
set_vcount_ca(cap, set_prevcount)
cmdarg_T *cap;
int *set_prevcount;
{
long count = cap->count0;
/* multiply with cap->opcount the same way as above */
if (cap->opcount != 0)
count = cap->opcount * (count == 0 ? 1 : count);
set_vcount(count, count == 0 ? 1 : count, *set_prevcount);
*set_prevcount = FALSE; /* only set v:prevcount once */
}
#endif
/*
* Handle an operator after visual mode or when the movement is finished
*/
void
do_pending_operator(cap, old_col, gui_yank)
cmdarg_T *cap;
int old_col;
int gui_yank;
{
oparg_T *oap = cap->oap;
pos_T old_cursor;
int empty_region_error;
int restart_edit_save;
#ifdef FEAT_VISUAL
/* The visual area is remembered for redo */
static int redo_VIsual_mode = NUL; /* 'v', 'V', or Ctrl-V */
static linenr_T redo_VIsual_line_count; /* number of lines */
static colnr_T redo_VIsual_vcol; /* number of cols or end column */
static long redo_VIsual_count; /* count for Visual operator */
# ifdef FEAT_VIRTUALEDIT
int include_line_break = FALSE;
# endif
#endif
#if defined(FEAT_CLIPBOARD)
/*
* Yank the visual area into the GUI selection register before we operate
* on it and lose it forever.
* Don't do it if a specific register was specified, so that ""x"*P works.
* This could call do_pending_operator() recursively, but that's OK
* because gui_yank will be TRUE for the nested call.
*/
if ((clip_star.available || clip_plus.available)
&& oap->op_type != OP_NOP
&& !gui_yank
# ifdef FEAT_VISUAL
&& VIsual_active
&& !redo_VIsual_busy
# endif
&& oap->regname == 0)
clip_auto_select();
#endif
old_cursor = curwin->w_cursor;
/*
* If an operation is pending, handle it...
*/
if ((finish_op
#ifdef FEAT_VISUAL
|| VIsual_active
#endif
) && oap->op_type != OP_NOP)
{
#ifdef FEAT_VISUAL
oap->is_VIsual = VIsual_active;
if (oap->motion_force == 'V')
oap->motion_type = MLINE;
else if (oap->motion_force == 'v')
{
/* If the motion was linewise, "inclusive" will not have been set.
* Use "exclusive" to be consistent. Makes "dvj" work nice. */
if (oap->motion_type == MLINE)
oap->inclusive = FALSE;
/* If the motion already was characterwise, toggle "inclusive" */
else if (oap->motion_type == MCHAR)
oap->inclusive = !oap->inclusive;
oap->motion_type = MCHAR;
}
else if (oap->motion_force == Ctrl_V)
{
/* Change line- or characterwise motion into Visual block mode. */
VIsual_active = TRUE;
VIsual = oap->start;
VIsual_mode = Ctrl_V;
VIsual_select = FALSE;
VIsual_reselect = FALSE;
}
#endif
/* only redo yank when 'y' flag is in 'cpoptions' */
/* never redo "zf" (define fold) */
if ((vim_strchr(p_cpo, CPO_YANK) != NULL || oap->op_type != OP_YANK)
#ifdef FEAT_VISUAL
&& (!VIsual_active || oap->motion_force)
#endif
&& cap->cmdchar != 'D'
#ifdef FEAT_FOLDING
&& oap->op_type != OP_FOLD
&& oap->op_type != OP_FOLDOPEN
&& oap->op_type != OP_FOLDOPENREC
&& oap->op_type != OP_FOLDCLOSE
&& oap->op_type != OP_FOLDCLOSEREC
&& oap->op_type != OP_FOLDDEL
&& oap->op_type != OP_FOLDDELREC
#endif
)
{
prep_redo(oap->regname, cap->count0,
get_op_char(oap->op_type), get_extra_op_char(oap->op_type),
oap->motion_force, cap->cmdchar, cap->nchar);
if (cap->cmdchar == '/' || cap->cmdchar == '?') /* was a search */
{
/*
* If 'cpoptions' does not contain 'r', insert the search
* pattern to really repeat the same command.
*/
if (vim_strchr(p_cpo, CPO_REDO) == NULL)
AppendToRedobuffLit(cap->searchbuf, -1);
AppendToRedobuff(NL_STR);
}
else if (cap->cmdchar == ':')
{
/* do_cmdline() has stored the first typed line in
* "repeat_cmdline". When several lines are typed repeating
* won't be possible. */
if (repeat_cmdline == NULL)
ResetRedobuff();
else
{
AppendToRedobuffLit(repeat_cmdline, -1);
AppendToRedobuff(NL_STR);
vim_free(repeat_cmdline);
repeat_cmdline = NULL;
}
}
}
#ifdef FEAT_VISUAL
if (redo_VIsual_busy)
{
/* Redo of an operation on a Visual area. Use the same size from
* redo_VIsual_line_count and redo_VIsual_vcol. */
oap->start = curwin->w_cursor;
curwin->w_cursor.lnum += redo_VIsual_line_count - 1;
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count)
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
VIsual_mode = redo_VIsual_mode;
if (redo_VIsual_vcol == MAXCOL || VIsual_mode == 'v')
{
if (VIsual_mode == 'v')
{
if (redo_VIsual_line_count <= 1)
{
validate_virtcol();
curwin->w_curswant =
curwin->w_virtcol + redo_VIsual_vcol - 1;
}
else
curwin->w_curswant = redo_VIsual_vcol;
}
else
{
curwin->w_curswant = MAXCOL;
}
coladvance(curwin->w_curswant);
}
cap->count0 = redo_VIsual_count;
if (redo_VIsual_count != 0)
cap->count1 = redo_VIsual_count;
else
cap->count1 = 1;
}
else if (VIsual_active)
{
if (!gui_yank)
{
/* Save the current VIsual area for '< and '> marks, and "gv" */
curbuf->b_visual.vi_start = VIsual;
curbuf->b_visual.vi_end = curwin->w_cursor;
curbuf->b_visual.vi_mode = VIsual_mode;
curbuf->b_visual.vi_curswant = curwin->w_curswant;
# ifdef FEAT_EVAL
curbuf->b_visual_mode_eval = VIsual_mode;
# endif
}
/* In Select mode, a linewise selection is operated upon like a
* characterwise selection. */
if (VIsual_select && VIsual_mode == 'V')
{
if (lt(VIsual, curwin->w_cursor))
{
VIsual.col = 0;
curwin->w_cursor.col =
(colnr_T)STRLEN(ml_get(curwin->w_cursor.lnum));
}
else
{
curwin->w_cursor.col = 0;
VIsual.col = (colnr_T)STRLEN(ml_get(VIsual.lnum));
}
VIsual_mode = 'v';
}
/* If 'selection' is "exclusive", backup one character for
* charwise selections. */
else if (VIsual_mode == 'v')
{
# ifdef FEAT_VIRTUALEDIT
include_line_break =
# endif
unadjust_for_sel();
}
oap->start = VIsual;
if (VIsual_mode == 'V')
oap->start.col = 0;
}
#endif /* FEAT_VISUAL */
/*
* Set oap->start to the first position of the operated text, oap->end
* to the end of the operated text. w_cursor is equal to oap->start.
*/
if (lt(oap->start, curwin->w_cursor))
{
#ifdef FEAT_FOLDING
/* Include folded lines completely. */
if (!VIsual_active)
{
if (hasFolding(oap->start.lnum, &oap->start.lnum, NULL))
oap->start.col = 0;
if (hasFolding(curwin->w_cursor.lnum, NULL,
&curwin->w_cursor.lnum))
curwin->w_cursor.col = (colnr_T)STRLEN(ml_get_curline());
}
#endif
oap->end = curwin->w_cursor;
curwin->w_cursor = oap->start;
/* w_virtcol may have been updated; if the cursor goes back to its
* previous position w_virtcol becomes invalid and isn't updated
* automatically. */
curwin->w_valid &= ~VALID_VIRTCOL;
}
else
{
#ifdef FEAT_FOLDING
/* Include folded lines completely. */
if (!VIsual_active && oap->motion_type == MLINE)
{
if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum,
NULL))
curwin->w_cursor.col = 0;
if (hasFolding(oap->start.lnum, NULL, &oap->start.lnum))
oap->start.col = (colnr_T)STRLEN(ml_get(oap->start.lnum));
}
#endif
oap->end = oap->start;
oap->start = curwin->w_cursor;
}
oap->line_count = oap->end.lnum - oap->start.lnum + 1;
#ifdef FEAT_VIRTUALEDIT
/* Set "virtual_op" before resetting VIsual_active. */
virtual_op = virtual_active();
#endif
#ifdef FEAT_VISUAL
if (VIsual_active || redo_VIsual_busy)
{
if (VIsual_mode == Ctrl_V) /* block mode */
{
colnr_T start, end;
oap->block_mode = TRUE;
getvvcol(curwin, &(oap->start),
&oap->start_vcol, NULL, &oap->end_vcol);
if (!redo_VIsual_busy)
{
getvvcol(curwin, &(oap->end), &start, NULL, &end);
if (start < oap->start_vcol)
oap->start_vcol = start;
if (end > oap->end_vcol)
{
if (*p_sel == 'e' && start >= 1
&& start - 1 >= oap->end_vcol)
oap->end_vcol = start - 1;
else
oap->end_vcol = end;
}
}
/* if '$' was used, get oap->end_vcol from longest line */
if (curwin->w_curswant == MAXCOL)
{
curwin->w_cursor.col = MAXCOL;
oap->end_vcol = 0;
for (curwin->w_cursor.lnum = oap->start.lnum;
curwin->w_cursor.lnum <= oap->end.lnum;
++curwin->w_cursor.lnum)
{
getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &end);
if (end > oap->end_vcol)
oap->end_vcol = end;
}
}
else if (redo_VIsual_busy)
oap->end_vcol = oap->start_vcol + redo_VIsual_vcol - 1;
/*
* Correct oap->end.col and oap->start.col to be the
* upper-left and lower-right corner of the block area.
*
* (Actually, this does convert column positions into character
* positions)
*/
curwin->w_cursor.lnum = oap->end.lnum;
coladvance(oap->end_vcol);
oap->end = curwin->w_cursor;
curwin->w_cursor = oap->start;
coladvance(oap->start_vcol);
oap->start = curwin->w_cursor;
}
if (!redo_VIsual_busy && !gui_yank)
{
/*
* Prepare to reselect and redo Visual: this is based on the
* size of the Visual text
*/
resel_VIsual_mode = VIsual_mode;
if (curwin->w_curswant == MAXCOL)
resel_VIsual_vcol = MAXCOL;
else
{
if (VIsual_mode != Ctrl_V)
getvvcol(curwin, &(oap->end),
NULL, NULL, &oap->end_vcol);
if (VIsual_mode == Ctrl_V || oap->line_count <= 1)
{
if (VIsual_mode != Ctrl_V)
getvvcol(curwin, &(oap->start),
&oap->start_vcol, NULL, NULL);
resel_VIsual_vcol = oap->end_vcol - oap->start_vcol + 1;
}
else
resel_VIsual_vcol = oap->end_vcol;
}
resel_VIsual_line_count = oap->line_count;
}
/* can't redo yank (unless 'y' is in 'cpoptions') and ":" */
if ((vim_strchr(p_cpo, CPO_YANK) != NULL || oap->op_type != OP_YANK)
&& oap->op_type != OP_COLON
#ifdef FEAT_FOLDING
&& oap->op_type != OP_FOLD
&& oap->op_type != OP_FOLDOPEN
&& oap->op_type != OP_FOLDOPENREC
&& oap->op_type != OP_FOLDCLOSE
&& oap->op_type != OP_FOLDCLOSEREC
&& oap->op_type != OP_FOLDDEL
&& oap->op_type != OP_FOLDDELREC
#endif
&& oap->motion_force == NUL
)
{
/* Prepare for redoing. Only use the nchar field for "r",
* otherwise it might be the second char of the operator. */
prep_redo(oap->regname, 0L, NUL, 'v',
get_op_char(oap->op_type),
get_extra_op_char(oap->op_type),
oap->op_type == OP_REPLACE ? cap->nchar : NUL);
if (!redo_VIsual_busy)
{
redo_VIsual_mode = resel_VIsual_mode;
redo_VIsual_vcol = resel_VIsual_vcol;
redo_VIsual_line_count = resel_VIsual_line_count;
redo_VIsual_count = cap->count0;
}
}
/*
* oap->inclusive defaults to TRUE.
* If oap->end is on a NUL (empty line) oap->inclusive becomes
* FALSE. This makes "d}P" and "v}dP" work the same.
*/
if (oap->motion_force == NUL || oap->motion_type == MLINE)
oap->inclusive = TRUE;
if (VIsual_mode == 'V')
oap->motion_type = MLINE;
else
{
oap->motion_type = MCHAR;
if (VIsual_mode != Ctrl_V && *ml_get_pos(&(oap->end)) == NUL
# ifdef FEAT_VIRTUALEDIT
&& (include_line_break || !virtual_op)
# endif
)
{
oap->inclusive = FALSE;
/* Try to include the newline, unless it's an operator
* that works on lines only. */
if (*p_sel != 'o' && !op_on_lines(oap->op_type))
{
if (oap->end.lnum < curbuf->b_ml.ml_line_count)
{
++oap->end.lnum;
oap->end.col = 0;
# ifdef FEAT_VIRTUALEDIT
oap->end.coladd = 0;
# endif
++oap->line_count;
}
else
{
/* Cannot move below the last line, make the op
* inclusive to tell the operation to include the
* line break. */
oap->inclusive = TRUE;
}
}
}
}
redo_VIsual_busy = FALSE;
/*
* Switch Visual off now, so screen updating does
* not show inverted text when the screen is redrawn.
* With OP_YANK and sometimes with OP_COLON and OP_FILTER there is
* no screen redraw, so it is done here to remove the inverted
* part.
*/
if (!gui_yank)
{
VIsual_active = FALSE;
# ifdef FEAT_MOUSE
setmouse();
mouse_dragging = 0;
# endif
if (mode_displayed)
clear_cmdline = TRUE; /* unshow visual mode later */
#ifdef FEAT_CMDL_INFO
else
clear_showcmd();
#endif
if ((oap->op_type == OP_YANK
|| oap->op_type == OP_COLON
|| oap->op_type == OP_FUNCTION
|| oap->op_type == OP_FILTER)
&& oap->motion_force == NUL)
redraw_curbuf_later(INVERTED);
}
}
#endif
#ifdef FEAT_MBYTE
/* Include the trailing byte of a multi-byte char. */
if (has_mbyte && oap->inclusive)
{
int l;
l = (*mb_ptr2len)(ml_get_pos(&oap->end));
if (l > 1)
oap->end.col += l - 1;
}
#endif
curwin->w_set_curswant = TRUE;
/*
* oap->empty is set when start and end are the same. The inclusive
* flag affects this too, unless yanking and the end is on a NUL.
*/
oap->empty = (oap->motion_type == MCHAR
&& (!oap->inclusive
|| (oap->op_type == OP_YANK
&& gchar_pos(&oap->end) == NUL))
&& equalpos(oap->start, oap->end)
#ifdef FEAT_VIRTUALEDIT
&& !(virtual_op && oap->start.coladd != oap->end.coladd)
#endif
);
/*
* For delete, change and yank, it's an error to operate on an
* empty region, when 'E' included in 'cpoptions' (Vi compatible).
*/
empty_region_error = (oap->empty
&& vim_strchr(p_cpo, CPO_EMPTYREGION) != NULL);
#ifdef FEAT_VISUAL
/* Force a redraw when operating on an empty Visual region, when
* 'modifiable is off or creating a fold. */
if (oap->is_VIsual && (oap->empty || !curbuf->b_p_ma
# ifdef FEAT_FOLDING
|| oap->op_type == OP_FOLD
# endif
))
redraw_curbuf_later(INVERTED);
#endif
/*
* If the end of an operator is in column one while oap->motion_type
* is MCHAR and oap->inclusive is FALSE, we put op_end after the last
* character in the previous line. If op_start is on or before the
* first non-blank in the line, the operator becomes linewise
* (strange, but that's the way vi does it).
*/
if ( oap->motion_type == MCHAR
&& oap->inclusive == FALSE
&& !(cap->retval & CA_NO_ADJ_OP_END)
&& oap->end.col == 0
#ifdef FEAT_VISUAL
&& (!oap->is_VIsual || *p_sel == 'o')
&& !oap->block_mode
#endif
&& oap->line_count > 1)
{
oap->end_adjusted = TRUE; /* remember that we did this */
--oap->line_count;
--oap->end.lnum;
if (inindent(0))
oap->motion_type = MLINE;
else
{
oap->end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum));
if (oap->end.col)
{
--oap->end.col;
oap->inclusive = TRUE;
}
}
}
else
oap->end_adjusted = FALSE;
switch (oap->op_type)
{
case OP_LSHIFT:
case OP_RSHIFT:
op_shift(oap, TRUE,
#ifdef FEAT_VISUAL
oap->is_VIsual ? (int)cap->count1 :
#endif
1);
auto_format(FALSE, TRUE);
break;
case OP_JOIN_NS:
case OP_JOIN:
if (oap->line_count < 2)
oap->line_count = 2;
if (curwin->w_cursor.lnum + oap->line_count - 1 >
curbuf->b_ml.ml_line_count)
beep_flush();
else
{
(void)do_join(oap->line_count, oap->op_type == OP_JOIN, TRUE, TRUE);
auto_format(FALSE, TRUE);
}
break;
case OP_DELETE:
#ifdef FEAT_VISUAL
VIsual_reselect = FALSE; /* don't reselect now */
#endif
if (empty_region_error)
{
vim_beep();
CancelRedo();
}
else
{
(void)op_delete(oap);
if (oap->motion_type == MLINE && has_format_option(FO_AUTO))
u_save_cursor(); /* cursor line wasn't saved yet */
auto_format(FALSE, TRUE);
}
break;
case OP_YANK:
if (empty_region_error)
{
if (!gui_yank)
{
vim_beep();
CancelRedo();
}
}
else
(void)op_yank(oap, FALSE, !gui_yank);
check_cursor_col();
break;
case OP_CHANGE:
#ifdef FEAT_VISUAL
VIsual_reselect = FALSE; /* don't reselect now */
#endif
if (empty_region_error)
{
vim_beep();
CancelRedo();
}
else
{
/* This is a new edit command, not a restart. Need to
* remember it to make 'insertmode' work with mappings for
* Visual mode. But do this only once and not when typed and
* 'insertmode' isn't set. */
if (p_im || !KeyTyped)
restart_edit_save = restart_edit;
else
restart_edit_save = 0;
restart_edit = 0;
/* Reset finish_op now, don't want it set inside edit(). */
finish_op = FALSE;
if (op_change(oap)) /* will call edit() */
cap->retval |= CA_COMMAND_BUSY;
if (restart_edit == 0)
restart_edit = restart_edit_save;
}
break;
case OP_FILTER:
if (vim_strchr(p_cpo, CPO_FILTER) != NULL)
AppendToRedobuff((char_u *)"!\r"); /* use any last used !cmd */
else
bangredo = TRUE; /* do_bang() will put cmd in redo buffer */
case OP_INDENT:
case OP_COLON:
#if defined(FEAT_LISP) || defined(FEAT_CINDENT)
/*
* If 'equalprg' is empty, do the indenting internally.
*/
if (oap->op_type == OP_INDENT && *get_equalprg() == NUL)
{
# ifdef FEAT_LISP
if (curbuf->b_p_lisp)
{
op_reindent(oap, get_lisp_indent);
break;
}
# endif
# ifdef FEAT_CINDENT
op_reindent(oap,
# ifdef FEAT_EVAL
*curbuf->b_p_inde != NUL ? get_expr_indent :
# endif
get_c_indent);
break;
# endif
}
#endif
op_colon(oap);
break;
case OP_TILDE:
case OP_UPPER:
case OP_LOWER:
case OP_ROT13:
if (empty_region_error)
{
vim_beep();
CancelRedo();
}
else
op_tilde(oap);
check_cursor_col();
break;
case OP_FORMAT:
#if defined(FEAT_EVAL)
if (*curbuf->b_p_fex != NUL)
op_formatexpr(oap); /* use expression */
else
#endif
if (*p_fp != NUL)
op_colon(oap); /* use external command */
else
op_format(oap, FALSE); /* use internal function */
break;
case OP_FORMAT2:
op_format(oap, TRUE); /* use internal function */
break;
case OP_FUNCTION:
op_function(oap); /* call 'operatorfunc' */
break;
case OP_INSERT:
case OP_APPEND:
#ifdef FEAT_VISUAL
VIsual_reselect = FALSE; /* don't reselect now */
#endif
#ifdef FEAT_VISUALEXTRA
if (empty_region_error)
{
vim_beep();
CancelRedo();
}
else
{
/* This is a new edit command, not a restart. Need to
* remember it to make 'insertmode' work with mappings for
* Visual mode. But do this only once. */
restart_edit_save = restart_edit;
restart_edit = 0;
op_insert(oap, cap->count1);
/* TODO: when inserting in several lines, should format all
* the lines. */
auto_format(FALSE, TRUE);
if (restart_edit == 0)
restart_edit = restart_edit_save;
}
#else
vim_beep();
#endif
break;
case OP_REPLACE:
#ifdef FEAT_VISUAL
VIsual_reselect = FALSE; /* don't reselect now */
#endif
#ifdef FEAT_VISUALEXTRA
if (empty_region_error)
#endif
{
vim_beep();
CancelRedo();
}
#ifdef FEAT_VISUALEXTRA
else
op_replace(oap, cap->nchar);
#endif
break;
#ifdef FEAT_FOLDING
case OP_FOLD:
VIsual_reselect = FALSE; /* don't reselect now */
foldCreate(oap->start.lnum, oap->end.lnum);
break;
case OP_FOLDOPEN:
case OP_FOLDOPENREC:
case OP_FOLDCLOSE:
case OP_FOLDCLOSEREC:
VIsual_reselect = FALSE; /* don't reselect now */
opFoldRange(oap->start.lnum, oap->end.lnum,
oap->op_type == OP_FOLDOPEN
|| oap->op_type == OP_FOLDOPENREC,
oap->op_type == OP_FOLDOPENREC
|| oap->op_type == OP_FOLDCLOSEREC,
oap->is_VIsual);
break;
case OP_FOLDDEL:
case OP_FOLDDELREC:
VIsual_reselect = FALSE; /* don't reselect now */
deleteFold(oap->start.lnum, oap->end.lnum,
oap->op_type == OP_FOLDDELREC, oap->is_VIsual);
break;
#endif
default:
clearopbeep(oap);
}
#ifdef FEAT_VIRTUALEDIT
virtual_op = MAYBE;
#endif
if (!gui_yank)
{
/*
* if 'sol' not set, go back to old column for some commands
*/
if (!p_sol && oap->motion_type == MLINE && !oap->end_adjusted
&& (oap->op_type == OP_LSHIFT || oap->op_type == OP_RSHIFT
|| oap->op_type == OP_DELETE))
coladvance(curwin->w_curswant = old_col);
}
else
{
curwin->w_cursor = old_cursor;
}
#ifdef FEAT_VISUAL
oap->block_mode = FALSE;
#endif
clearop(oap);
}
}
/*
* Handle indent and format operators and visual mode ":".
*/
static void
op_colon(oap)
oparg_T *oap;
{
stuffcharReadbuff(':');
#ifdef FEAT_VISUAL
if (oap->is_VIsual)
stuffReadbuff((char_u *)"'<,'>");
else
#endif
{
/*
* Make the range look nice, so it can be repeated.
*/
if (oap->start.lnum == curwin->w_cursor.lnum)
stuffcharReadbuff('.');
else
stuffnumReadbuff((long)oap->start.lnum);
if (oap->end.lnum != oap->start.lnum)
{
stuffcharReadbuff(',');
if (oap->end.lnum == curwin->w_cursor.lnum)
stuffcharReadbuff('.');
else if (oap->end.lnum == curbuf->b_ml.ml_line_count)
stuffcharReadbuff('$');
else if (oap->start.lnum == curwin->w_cursor.lnum)
{
stuffReadbuff((char_u *)".+");
stuffnumReadbuff((long)oap->line_count - 1);
}
else
stuffnumReadbuff((long)oap->end.lnum);
}
}
if (oap->op_type != OP_COLON)
stuffReadbuff((char_u *)"!");
if (oap->op_type == OP_INDENT)
{
#ifndef FEAT_CINDENT
if (*get_equalprg() == NUL)
stuffReadbuff((char_u *)"indent");
else
#endif
stuffReadbuff(get_equalprg());
stuffReadbuff((char_u *)"\n");
}
else if (oap->op_type == OP_FORMAT)
{
if (*p_fp == NUL)
stuffReadbuff((char_u *)"fmt");
else
stuffReadbuff(p_fp);
stuffReadbuff((char_u *)"\n']");
}
/*
* do_cmdline() does the rest
*/
}
/*
* Handle the "g@" operator: call 'operatorfunc'.
*/
static void
op_function(oap)
oparg_T *oap UNUSED;
{
#ifdef FEAT_EVAL
char_u *(argv[1]);
int save_virtual_op = virtual_op;
if (*p_opfunc == NUL)
EMSG(_("E774: 'operatorfunc' is empty"));
else
{
/* Set '[ and '] marks to text to be operated on. */
curbuf->b_op_start = oap->start;
curbuf->b_op_end = oap->end;
if (oap->motion_type != MLINE && !oap->inclusive)
/* Exclude the end position. */
decl(&curbuf->b_op_end);
if (oap->block_mode)
argv[0] = (char_u *)"block";
else if (oap->motion_type == MLINE)
argv[0] = (char_u *)"line";
else
argv[0] = (char_u *)"char";
/* Reset virtual_op so that 'virtualedit' can be changed in the
* function. */
virtual_op = MAYBE;
(void)call_func_retnr(p_opfunc, 1, argv, FALSE);
virtual_op = save_virtual_op;
}
#else
EMSG(_("E775: Eval feature not available"));
#endif
}
#if defined(FEAT_MOUSE) || defined(PROTO)
/*
* Do the appropriate action for the current mouse click in the current mode.
* Not used for Command-line mode.
*
* Normal Mode:
* event modi- position visual change action
* fier cursor window
* left press - yes end yes
* left press C yes end yes "^]" (2)
* left press S yes end yes "*" (2)
* left drag - yes start if moved no
* left relse - yes start if moved no
* middle press - yes if not active no put register
* middle press - yes if active no yank and put
* right press - yes start or extend yes
* right press S yes no change yes "#" (2)
* right drag - yes extend no
* right relse - yes extend no
*
* Insert or Replace Mode:
* event modi- position visual change action
* fier cursor window
* left press - yes (cannot be active) yes
* left press C yes (cannot be active) yes "CTRL-O^]" (2)
* left press S yes (cannot be active) yes "CTRL-O*" (2)
* left drag - yes start or extend (1) no CTRL-O (1)
* left relse - yes start or extend (1) no CTRL-O (1)
* middle press - no (cannot be active) no put register
* right press - yes start or extend yes CTRL-O
* right press S yes (cannot be active) yes "CTRL-O#" (2)
*
* (1) only if mouse pointer moved since press
* (2) only if click is in same buffer
*
* Return TRUE if start_arrow() should be called for edit mode.
*/
int
do_mouse(oap, c, dir, count, fixindent)
oparg_T *oap; /* operator argument, can be NULL */
int c; /* K_LEFTMOUSE, etc */
int dir; /* Direction to 'put' if necessary */
long count;
int fixindent; /* PUT_FIXINDENT if fixing indent necessary */
{
static int do_always = FALSE; /* ignore 'mouse' setting next time */
static int got_click = FALSE; /* got a click some time back */
int which_button; /* MOUSE_LEFT, _MIDDLE or _RIGHT */
int is_click; /* If FALSE it's a drag or release event */
int is_drag; /* If TRUE it's a drag event */
int jump_flags = 0; /* flags for jump_to_mouse() */
pos_T start_visual;
int moved; /* Has cursor moved? */
int in_status_line; /* mouse in status line */
#ifdef FEAT_WINDOWS
static int in_tab_line = FALSE; /* mouse clicked in tab line */
#endif
#ifdef FEAT_VERTSPLIT
int in_sep_line; /* mouse in vertical separator line */
#endif
int c1, c2;
#if defined(FEAT_FOLDING)
pos_T save_cursor;
#endif
win_T *old_curwin = curwin;
#ifdef FEAT_VISUAL
static pos_T orig_cursor;
colnr_T leftcol, rightcol;
pos_T end_visual;
int diff;
int old_active = VIsual_active;
int old_mode = VIsual_mode;
#endif
int regname;
#if defined(FEAT_FOLDING)
save_cursor = curwin->w_cursor;
#endif
/*
* When GUI is active, always recognize mouse events, otherwise:
* - Ignore mouse event in normal mode if 'mouse' doesn't include 'n'.
* - Ignore mouse event in visual mode if 'mouse' doesn't include 'v'.
* - For command line and insert mode 'mouse' is checked before calling
* do_mouse().
*/
if (do_always)
do_always = FALSE;
else
#ifdef FEAT_GUI
if (!gui.in_use)
#endif
{
#ifdef FEAT_VISUAL
if (VIsual_active)
{
if (!mouse_has(MOUSE_VISUAL))
return FALSE;
}
else
#endif
if (State == NORMAL && !mouse_has(MOUSE_NORMAL))
return FALSE;
}
which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag);
#ifdef FEAT_MOUSESHAPE
/* May have stopped dragging the status or separator line. The pointer is
* most likely still on the status or separator line. */
if (!is_drag && drag_status_line)
{
drag_status_line = FALSE;
update_mouseshape(SHAPE_IDX_STATUS);
}
# ifdef FEAT_VERTSPLIT
if (!is_drag && drag_sep_line)
{
drag_sep_line = FALSE;
update_mouseshape(SHAPE_IDX_VSEP);
}
# endif
#endif
/*
* Ignore drag and release events if we didn't get a click.
*/
if (is_click)
got_click = TRUE;
else
{
if (!got_click) /* didn't get click, ignore */
return FALSE;
if (!is_drag) /* release, reset got_click */
{
got_click = FALSE;
#ifdef FEAT_WINDOWS
if (in_tab_line)
{
in_tab_line = FALSE;
return FALSE;
}
#endif
}
}
#ifndef FEAT_VISUAL
/*
* ALT is only used for starging/extending Visual mode.
*/
if ((mod_mask & MOD_MASK_ALT))
return FALSE;
#endif
/*
* CTRL right mouse button does CTRL-T
*/
if (is_click && (mod_mask & MOD_MASK_CTRL) && which_button == MOUSE_RIGHT)
{
if (State & INSERT)
stuffcharReadbuff(Ctrl_O);
if (count > 1)
stuffnumReadbuff(count);
stuffcharReadbuff(Ctrl_T);
got_click = FALSE; /* ignore drag&release now */
return FALSE;
}
/*
* CTRL only works with left mouse button
*/
if ((mod_mask & MOD_MASK_CTRL) && which_button != MOUSE_LEFT)
return FALSE;
/*
* When a modifier is down, ignore drag and release events, as well as
* multiple clicks and the middle mouse button.
* Accept shift-leftmouse drags when 'mousemodel' is "popup.*".
*/
if ((mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT
| MOD_MASK_META))
&& (!is_click
|| (mod_mask & MOD_MASK_MULTI_CLICK)
|| which_button == MOUSE_MIDDLE)
&& !((mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT))
&& mouse_model_popup()
&& which_button == MOUSE_LEFT)
&& !((mod_mask & MOD_MASK_ALT)
&& !mouse_model_popup()
&& which_button == MOUSE_RIGHT)
)
return FALSE;
/*
* If the button press was used as the movement command for an operator
* (eg "d<MOUSE>"), or it is the middle button that is held down, ignore
* drag/release events.
*/
if (!is_click && which_button == MOUSE_MIDDLE)
return FALSE;
if (oap != NULL)
regname = oap->regname;
else
regname = 0;
/*
* Middle mouse button does a 'put' of the selected text
*/
if (which_button == MOUSE_MIDDLE)
{
if (State == NORMAL)
{
/*
* If an operator was pending, we don't know what the user wanted
* to do. Go back to normal mode: Clear the operator and beep().
*/
if (oap != NULL && oap->op_type != OP_NOP)
{
clearopbeep(oap);
return FALSE;
}
#ifdef FEAT_VISUAL
/*
* If visual was active, yank the highlighted text and put it
* before the mouse pointer position.
* In Select mode replace the highlighted text with the clipboard.
*/
if (VIsual_active)
{
if (VIsual_select)
{
stuffcharReadbuff(Ctrl_G);
stuffReadbuff((char_u *)"\"+p");
}
else
{
stuffcharReadbuff('y');
stuffcharReadbuff(K_MIDDLEMOUSE);
}
do_always = TRUE; /* ignore 'mouse' setting next time */
return FALSE;
}
#endif
/*
* The rest is below jump_to_mouse()
*/
}
else if ((State & INSERT) == 0)
return FALSE;
/*
* Middle click in insert mode doesn't move the mouse, just insert the
* contents of a register. '.' register is special, can't insert that
* with do_put().
* Also paste at the cursor if the current mode isn't in 'mouse' (only
* happens for the GUI).
*/
if ((State & INSERT) || !mouse_has(MOUSE_NORMAL))
{
if (regname == '.')
insert_reg(regname, TRUE);
else
{
#ifdef FEAT_CLIPBOARD
if (clip_star.available && regname == 0)
regname = '*';
#endif
if ((State & REPLACE_FLAG) && !yank_register_mline(regname))
insert_reg(regname, TRUE);
else
{
do_put(regname, BACKWARD, 1L, fixindent | PUT_CURSEND);
/* Repeat it with CTRL-R CTRL-O r or CTRL-R CTRL-P r */
AppendCharToRedobuff(Ctrl_R);
AppendCharToRedobuff(fixindent ? Ctrl_P : Ctrl_O);
AppendCharToRedobuff(regname == 0 ? '"' : regname);
}
}
return FALSE;
}
}
/* When dragging or button-up stay in the same window. */
if (!is_click)
jump_flags |= MOUSE_FOCUS | MOUSE_DID_MOVE;
start_visual.lnum = 0;
#ifdef FEAT_WINDOWS
/* Check for clicking in the tab page line. */
if (mouse_row == 0 && firstwin->w_winrow > 0)
{
if (is_drag)
{
if (in_tab_line)
{
c1 = TabPageIdxs[mouse_col];
tabpage_move(c1 <= 0 ? 9999 : c1 - 1);
}
return FALSE;
}
/* click in a tab selects that tab page */
if (is_click
# ifdef FEAT_CMDWIN
&& cmdwin_type == 0
# endif
&& mouse_col < Columns)
{
in_tab_line = TRUE;
c1 = TabPageIdxs[mouse_col];
if (c1 >= 0)
{
if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)
{
/* double click opens new page */
end_visual_mode();
tabpage_new();
tabpage_move(c1 == 0 ? 9999 : c1 - 1);
}
else
{
/* Go to specified tab page, or next one if not clicking
* on a label. */
goto_tabpage(c1);
/* It's like clicking on the status line of a window. */
if (curwin != old_curwin)
end_visual_mode();
}
}
else if (c1 < 0)
{
tabpage_T *tp;
/* Close the current or specified tab page. */
if (c1 == -999)
tp = curtab;
else
tp = find_tabpage(-c1);
if (tp == curtab)
{
if (first_tabpage->tp_next != NULL)
tabpage_close(FALSE);
}
else if (tp != NULL)
tabpage_close_other(tp, FALSE);
}
}
return TRUE;
}
else if (is_drag && in_tab_line)
{
c1 = TabPageIdxs[mouse_col];
tabpage_move(c1 <= 0 ? 9999 : c1 - 1);
return FALSE;
}
#endif
/*
* When 'mousemodel' is "popup" or "popup_setpos", translate mouse events:
* right button up -> pop-up menu
* shift-left button -> right button
* alt-left button -> alt-right button
*/
if (mouse_model_popup())
{
if (which_button == MOUSE_RIGHT
&& !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)))
{
/*
* NOTE: Ignore right button down and drag mouse events.
* Windows only shows the popup menu on the button up event.
*/
#if defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_GTK) \
|| defined(FEAT_GUI_PHOTON) || defined(FEAT_GUI_MAC) \
|| defined(FEAT_GUI_MACVIM)
if (!is_click)
return FALSE;
#endif
#if defined(FEAT_GUI_ATHENA) || defined(FEAT_GUI_MSWIN)
if (is_click || is_drag)
return FALSE;
#endif
#if defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_GTK) \
|| defined(FEAT_GUI_ATHENA) || defined(FEAT_GUI_MSWIN) \
|| defined(FEAT_GUI_MAC) || defined(FEAT_GUI_PHOTON) \
|| defined(FEAT_GUI_MACVIM)
if (gui.in_use)
{
jump_flags = 0;
if (STRCMP(p_mousem, "popup_setpos") == 0)
{
/* First set the cursor position before showing the popup
* menu. */
#ifdef FEAT_VISUAL
if (VIsual_active)
{
pos_T m_pos;
/*
* set MOUSE_MAY_STOP_VIS if we are outside the
* selection or the current window (might have false
* negative here)
*/
if (mouse_row < W_WINROW(curwin)
|| mouse_row
> (W_WINROW(curwin) + curwin->w_height))
jump_flags = MOUSE_MAY_STOP_VIS;
else if (get_fpos_of_mouse(&m_pos) != IN_BUFFER)
jump_flags = MOUSE_MAY_STOP_VIS;
else
{
if ((lt(curwin->w_cursor, VIsual)
&& (lt(m_pos, curwin->w_cursor)
|| lt(VIsual, m_pos)))
|| (lt(VIsual, curwin->w_cursor)
&& (lt(m_pos, VIsual)
|| lt(curwin->w_cursor, m_pos))))
{
jump_flags = MOUSE_MAY_STOP_VIS;
}
else if (VIsual_mode == Ctrl_V)
{
getvcols(curwin, &curwin->w_cursor, &VIsual,
&leftcol, &rightcol);
getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL);
if (m_pos.col < leftcol || m_pos.col > rightcol)
jump_flags = MOUSE_MAY_STOP_VIS;
}
}
}
else
jump_flags = MOUSE_MAY_STOP_VIS;
#endif
}
if (jump_flags)
{
jump_flags = jump_to_mouse(jump_flags, NULL, which_button);
update_curbuf(
#ifdef FEAT_VISUAL
VIsual_active ? INVERTED :
#endif
VALID);
setcursor();
out_flush(); /* Update before showing popup menu */
}
# ifdef FEAT_MENU
gui_show_popupmenu();
# endif
return (jump_flags & CURSOR_MOVED) != 0;
}
else
return FALSE;
#else
return FALSE;
#endif
}
if (which_button == MOUSE_LEFT
&& (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT)))
{
which_button = MOUSE_RIGHT;
mod_mask &= ~MOD_MASK_SHIFT;
}
}
#ifdef FEAT_VISUAL
if ((State & (NORMAL | INSERT))
&& !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)))
{
if (which_button == MOUSE_LEFT)
{
if (is_click)
{
/* stop Visual mode for a left click in a window, but not when
* on a status line */
if (VIsual_active)
jump_flags |= MOUSE_MAY_STOP_VIS;
}
else if (mouse_has(MOUSE_VISUAL))
jump_flags |= MOUSE_MAY_VIS;
}
else if (which_button == MOUSE_RIGHT)
{
if (is_click && VIsual_active)
{
/*
* Remember the start and end of visual before moving the
* cursor.
*/
if (lt(curwin->w_cursor, VIsual))
{
start_visual = curwin->w_cursor;
end_visual = VIsual;
}
else
{
start_visual = VIsual;
end_visual = curwin->w_cursor;
}
}
jump_flags |= MOUSE_FOCUS;
if (mouse_has(MOUSE_VISUAL))
jump_flags |= MOUSE_MAY_VIS;
}
}
#endif
/*
* If an operator is pending, ignore all drags and releases until the
* next mouse click.
*/
if (!is_drag && oap != NULL && oap->op_type != OP_NOP)
{
got_click = FALSE;
oap->motion_type = MCHAR;
}
/* When releasing the button let jump_to_mouse() know. */
if (!is_click && !is_drag)
jump_flags |= MOUSE_RELEASED;
/*
* JUMP!
*/
jump_flags = jump_to_mouse(jump_flags,
oap == NULL ? NULL : &(oap->inclusive), which_button);
moved = (jump_flags & CURSOR_MOVED);
in_status_line = (jump_flags & IN_STATUS_LINE);
#ifdef FEAT_VERTSPLIT
in_sep_line = (jump_flags & IN_SEP_LINE);
#endif
#ifdef FEAT_NETBEANS_INTG
if (isNetbeansBuffer(curbuf)
&& !(jump_flags & (IN_STATUS_LINE | IN_SEP_LINE)))
{
int key = KEY2TERMCAP1(c);
if (key == (int)KE_LEFTRELEASE || key == (int)KE_MIDDLERELEASE
|| key == (int)KE_RIGHTRELEASE)
netbeans_button_release(which_button);
}
#endif
/* When jumping to another window, clear a pending operator. That's a bit
* friendlier than beeping and not jumping to that window. */
if (curwin != old_curwin && oap != NULL && oap->op_type != OP_NOP)
clearop(oap);
#ifdef FEAT_FOLDING
if (mod_mask == 0
&& !is_drag
&& (jump_flags & (MOUSE_FOLD_CLOSE | MOUSE_FOLD_OPEN))
&& which_button == MOUSE_LEFT)
{
/* open or close a fold at this line */
if (jump_flags & MOUSE_FOLD_OPEN)
openFold(curwin->w_cursor.lnum, 1L);
else
closeFold(curwin->w_cursor.lnum, 1L);
/* don't move the cursor if still in the same window */
if (curwin == old_curwin)
curwin->w_cursor = save_cursor;
}
#endif
#if defined(FEAT_CLIPBOARD) && defined(FEAT_CMDWIN)
if ((jump_flags & IN_OTHER_WIN) && !VIsual_active && clip_star.available)
{
clip_modeless(which_button, is_click, is_drag);
return FALSE;
}
#endif
#ifdef FEAT_VISUAL
/* Set global flag that we are extending the Visual area with mouse
* dragging; temporarily minimize 'scrolloff'. */
if (VIsual_active && is_drag && p_so)
{
/* In the very first line, allow scrolling one line */
if (mouse_row == 0)
mouse_dragging = 2;
else
mouse_dragging = 1;
}
/* When dragging the mouse above the window, scroll down. */
if (is_drag && mouse_row < 0 && !in_status_line)
{
scroll_redraw(FALSE, 1L);
mouse_row = 0;
}
if (start_visual.lnum) /* right click in visual mode */
{
/* When ALT is pressed make Visual mode blockwise. */
if (mod_mask & MOD_MASK_ALT)
VIsual_mode = Ctrl_V;
/*
* In Visual-block mode, divide the area in four, pick up the corner
* that is in the quarter that the cursor is in.
*/
if (VIsual_mode == Ctrl_V)
{
getvcols(curwin, &start_visual, &end_visual, &leftcol, &rightcol);
if (curwin->w_curswant > (leftcol + rightcol) / 2)
end_visual.col = leftcol;
else
end_visual.col = rightcol;
if (curwin->w_cursor.lnum <
(start_visual.lnum + end_visual.lnum) / 2)
end_visual.lnum = end_visual.lnum;
else
end_visual.lnum = start_visual.lnum;
/* move VIsual to the right column */
start_visual = curwin->w_cursor; /* save the cursor pos */
curwin->w_cursor = end_visual;
coladvance(end_visual.col);
VIsual = curwin->w_cursor;
curwin->w_cursor = start_visual; /* restore the cursor */
}
else
{
/*
* If the click is before the start of visual, change the start.
* If the click is after the end of visual, change the end. If
* the click is inside the visual, change the closest side.
*/
if (lt(curwin->w_cursor, start_visual))
VIsual = end_visual;
else if (lt(end_visual, curwin->w_cursor))
VIsual = start_visual;
else
{
/* In the same line, compare column number */
if (end_visual.lnum == start_visual.lnum)
{
if (curwin->w_cursor.col - start_visual.col >
end_visual.col - curwin->w_cursor.col)
VIsual = start_visual;
else
VIsual = end_visual;
}
/* In different lines, compare line number */
else
{
diff = (curwin->w_cursor.lnum - start_visual.lnum) -
(end_visual.lnum - curwin->w_cursor.lnum);
if (diff > 0) /* closest to end */
VIsual = start_visual;
else if (diff < 0) /* closest to start */
VIsual = end_visual;
else /* in the middle line */
{
if (curwin->w_cursor.col <
(start_visual.col + end_visual.col) / 2)
VIsual = end_visual;
else
VIsual = start_visual;
}
}
}
}
}
/*
* If Visual mode started in insert mode, execute "CTRL-O"
*/
else if ((State & INSERT) && VIsual_active)
stuffcharReadbuff(Ctrl_O);
#endif
/*
* Middle mouse click: Put text before cursor.
*/
if (which_button == MOUSE_MIDDLE)
{
#ifdef FEAT_CLIPBOARD
if (clip_star.available && regname == 0)
regname = '*';
#endif
if (yank_register_mline(regname))
{
if (mouse_past_bottom)
dir = FORWARD;
}
else if (mouse_past_eol)
dir = FORWARD;
if (fixindent)
{
c1 = (dir == BACKWARD) ? '[' : ']';
c2 = 'p';
}
else
{
c1 = (dir == FORWARD) ? 'p' : 'P';
c2 = NUL;
}
prep_redo(regname, count, NUL, c1, NUL, c2, NUL);
/*
* Remember where the paste started, so in edit() Insstart can be set
* to this position
*/
if (restart_edit != 0)
where_paste_started = curwin->w_cursor;
do_put(regname, dir, count, fixindent | PUT_CURSEND);
}
#if defined(FEAT_WINDOWS) && defined(FEAT_QUICKFIX)
/*
* Ctrl-Mouse click or double click in a quickfix window jumps to the
* error under the mouse pointer.
*/
else if (((mod_mask & MOD_MASK_CTRL)
|| (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)
&& bt_quickfix(curbuf))
{
if (State & INSERT)
stuffcharReadbuff(Ctrl_O);
if (curwin->w_llist_ref == NULL) /* quickfix window */
stuffReadbuff((char_u *)":.cc\n");
else /* location list window */
stuffReadbuff((char_u *)":.ll\n");
got_click = FALSE; /* ignore drag&release now */
}
#endif
/*
* Ctrl-Mouse click (or double click in a help window) jumps to the tag
* under the mouse pointer.
*/
else if ((mod_mask & MOD_MASK_CTRL) || (curbuf->b_help
&& (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK))
{
if (State & INSERT)
stuffcharReadbuff(Ctrl_O);
stuffcharReadbuff(Ctrl_RSB);
got_click = FALSE; /* ignore drag&release now */
}
/*
* Shift-Mouse click searches for the next occurrence of the word under
* the mouse pointer
*/
else if ((mod_mask & MOD_MASK_SHIFT))
{
if (State & INSERT
#ifdef FEAT_VISUAL
|| (VIsual_active && VIsual_select)
#endif
)
stuffcharReadbuff(Ctrl_O);
if (which_button == MOUSE_LEFT)
stuffcharReadbuff('*');
else /* MOUSE_RIGHT */
stuffcharReadbuff('#');
}
/* Handle double clicks, unless on status line */
else if (in_status_line)
{
#ifdef FEAT_MOUSESHAPE
if ((is_drag || is_click) && !drag_status_line)
{
drag_status_line = TRUE;
update_mouseshape(-1);
}
#endif
}
#ifdef FEAT_VERTSPLIT
else if (in_sep_line)
{
# ifdef FEAT_MOUSESHAPE
if ((is_drag || is_click) && !drag_sep_line)
{
drag_sep_line = TRUE;
update_mouseshape(-1);
}
# endif
}
#endif
#ifdef FEAT_VISUAL
else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (NORMAL | INSERT))
&& mouse_has(MOUSE_VISUAL))
{
if (is_click || !VIsual_active)
{
if (VIsual_active)
orig_cursor = VIsual;
else
{
check_visual_highlight();
VIsual = curwin->w_cursor;
orig_cursor = VIsual;
VIsual_active = TRUE;
VIsual_reselect = TRUE;
/* start Select mode if 'selectmode' contains "mouse" */
may_start_select('o');
setmouse();
}
if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)
{
/* Double click with ALT pressed makes it blockwise. */
if (mod_mask & MOD_MASK_ALT)
VIsual_mode = Ctrl_V;
else
VIsual_mode = 'v';
}
else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK)
VIsual_mode = 'V';
else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK)
VIsual_mode = Ctrl_V;
#ifdef FEAT_CLIPBOARD
/* Make sure the clipboard gets updated. Needed because start and
* end may still be the same, and the selection needs to be owned */
clip_star.vmode = NUL;
#endif
}
/*
* A double click selects a word or a block.
*/
if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)
{
pos_T *pos = NULL;
int gc;
if (is_click)
{
/* If the character under the cursor (skipping white space) is
* not a word character, try finding a match and select a (),
* {}, [], #if/#endif, etc. block. */
end_visual = curwin->w_cursor;
while (gc = gchar_pos(&end_visual), vim_iswhite(gc))
inc(&end_visual);
if (oap != NULL)
oap->motion_type = MCHAR;
if (oap != NULL
&& VIsual_mode == 'v'
&& !vim_iswordc(gchar_pos(&end_visual))
&& equalpos(curwin->w_cursor, VIsual)
&& (pos = findmatch(oap, NUL)) != NULL)
{
curwin->w_cursor = *pos;
if (oap->motion_type == MLINE)
VIsual_mode = 'V';
else if (*p_sel == 'e')
{
if (lt(curwin->w_cursor, VIsual))
++VIsual.col;
else
++curwin->w_cursor.col;
}
}
}
if (pos == NULL && (is_click || is_drag))
{
/* When not found a match or when dragging: extend to include
* a word. */
if (lt(curwin->w_cursor, orig_cursor))
{
find_start_of_word(&curwin->w_cursor);
find_end_of_word(&VIsual);
}
else
{
find_start_of_word(&VIsual);
if (*p_sel == 'e' && *ml_get_cursor() != NUL)
#ifdef FEAT_MBYTE
curwin->w_cursor.col +=
(*mb_ptr2len)(ml_get_cursor());
#else
++curwin->w_cursor.col;
#endif
find_end_of_word(&curwin->w_cursor);
}
}
curwin->w_set_curswant = TRUE;
}
if (is_click)
redraw_curbuf_later(INVERTED); /* update the inversion */
}
else if (VIsual_active && !old_active)
{
if (mod_mask & MOD_MASK_ALT)
VIsual_mode = Ctrl_V;
else
VIsual_mode = 'v';
}
/* If Visual mode changed show it later. */
if ((!VIsual_active && old_active && mode_displayed)
|| (VIsual_active && p_smd && msg_silent == 0
&& (!old_active || VIsual_mode != old_mode)))
redraw_cmdline = TRUE;
#endif
return moved;
}
#ifdef FEAT_VISUAL
/*
* Move "pos" back to the start of the word it's in.
*/
static void
find_start_of_word(pos)
pos_T *pos;
{
char_u *line;
int cclass;
int col;
line = ml_get(pos->lnum);
cclass = get_mouse_class(line + pos->col);
while (pos->col > 0)
{
col = pos->col - 1;
#ifdef FEAT_MBYTE
col -= (*mb_head_off)(line, line + col);
#endif
if (get_mouse_class(line + col) != cclass)
break;
pos->col = col;
}
}
/*
* Move "pos" forward to the end of the word it's in.
* When 'selection' is "exclusive", the position is just after the word.
*/
static void
find_end_of_word(pos)
pos_T *pos;
{
char_u *line;
int cclass;
int col;
line = ml_get(pos->lnum);
if (*p_sel == 'e' && pos->col > 0)
{
--pos->col;
#ifdef FEAT_MBYTE
pos->col -= (*mb_head_off)(line, line + pos->col);
#endif
}
cclass = get_mouse_class(line + pos->col);
while (line[pos->col] != NUL)
{
#ifdef FEAT_MBYTE
col = pos->col + (*mb_ptr2len)(line + pos->col);
#else
col = pos->col + 1;
#endif
if (get_mouse_class(line + col) != cclass)
{
if (*p_sel == 'e')
pos->col = col;
break;
}
pos->col = col;
}
}
/*
* Get class of a character for selection: same class means same word.
* 0: blank
* 1: punctuation groups
* 2: normal word character
* >2: multi-byte word character.
*/
static int
get_mouse_class(p)
char_u *p;
{
int c;
#ifdef FEAT_MBYTE
if (has_mbyte && MB_BYTE2LEN(p[0]) > 1)
return mb_get_class(p);
#endif
c = *p;
if (c == ' ' || c == '\t')
return 0;
if (vim_iswordc(c))
return 2;
/*
* There are a few special cases where we want certain combinations of
* characters to be considered as a single word. These are things like
* "->", "/ *", "*=", "+=", "&=", "<=", ">=", "!=" etc. Otherwise, each
* character is in its own class.
*/
if (c != NUL && vim_strchr((char_u *)"-+*/%<>&|^!=", c) != NULL)
return 1;
return c;
}
#endif /* FEAT_VISUAL */
#endif /* FEAT_MOUSE */
#if defined(FEAT_VISUAL) || defined(PROTO)
/*
* Check if highlighting for visual mode is possible, give a warning message
* if not.
*/
void
check_visual_highlight()
{
static int did_check = FALSE;
if (full_screen)
{
if (!did_check && hl_attr(HLF_V) == 0)
MSG(_("Warning: terminal cannot highlight"));
did_check = TRUE;
}
}
/*
* End Visual mode.
* This function should ALWAYS be called to end Visual mode, except from
* do_pending_operator().
*/
void
end_visual_mode()
{
#ifdef FEAT_CLIPBOARD
/*
* If we are using the clipboard, then remember what was selected in case
* we need to paste it somewhere while we still own the selection.
* Only do this when the clipboard is already owned. Don't want to grab
* the selection when hitting ESC.
*/
if (clip_star.available && clip_star.owned)
clip_auto_select();
#endif
VIsual_active = FALSE;
#ifdef FEAT_MOUSE
setmouse();
mouse_dragging = 0;
#endif
/* Save the current VIsual area for '< and '> marks, and "gv" */
curbuf->b_visual.vi_mode = VIsual_mode;
curbuf->b_visual.vi_start = VIsual;
curbuf->b_visual.vi_end = curwin->w_cursor;
curbuf->b_visual.vi_curswant = curwin->w_curswant;
#ifdef FEAT_EVAL
curbuf->b_visual_mode_eval = VIsual_mode;
#endif
#ifdef FEAT_VIRTUALEDIT
if (!virtual_active())
curwin->w_cursor.coladd = 0;
#endif
if (mode_displayed)
clear_cmdline = TRUE; /* unshow visual mode later */
#ifdef FEAT_CMDL_INFO
else
clear_showcmd();
#endif
adjust_cursor_eol();
}
/*
* Reset VIsual_active and VIsual_reselect.
*/
void
reset_VIsual_and_resel()
{
if (VIsual_active)
{
end_visual_mode();
redraw_curbuf_later(INVERTED); /* delete the inversion later */
}
VIsual_reselect = FALSE;
}
/*
* Reset VIsual_active and VIsual_reselect if it's set.
*/
void
reset_VIsual()
{
if (VIsual_active)
{
end_visual_mode();
redraw_curbuf_later(INVERTED); /* delete the inversion later */
VIsual_reselect = FALSE;
}
}
#endif /* FEAT_VISUAL */
#if defined(FEAT_BEVAL)
static int find_is_eval_item __ARGS((char_u *ptr, int *colp, int *nbp, int dir));
/*
* Check for a balloon-eval special item to include when searching for an
* identifier. When "dir" is BACKWARD "ptr[-1]" must be valid!
* Returns TRUE if the character at "*ptr" should be included.
* "dir" is FORWARD or BACKWARD, the direction of searching.
* "*colp" is in/decremented if "ptr[-dir]" should also be included.
* "bnp" points to a counter for square brackets.
*/
static int
find_is_eval_item(ptr, colp, bnp, dir)
char_u *ptr;
int *colp;
int *bnp;
int dir;
{
/* Accept everything inside []. */
if ((*ptr == ']' && dir == BACKWARD) || (*ptr == '[' && dir == FORWARD))
++*bnp;
if (*bnp > 0)
{
if ((*ptr == '[' && dir == BACKWARD) || (*ptr == ']' && dir == FORWARD))
--*bnp;
return TRUE;
}
/* skip over "s.var" */
if (*ptr == '.')
return TRUE;
/* two-character item: s->var */
if (ptr[dir == BACKWARD ? 0 : 1] == '>'
&& ptr[dir == BACKWARD ? -1 : 0] == '-')
{
*colp += dir;
return TRUE;
}
return FALSE;
}
#endif
/*
* Find the identifier under or to the right of the cursor.
* "find_type" can have one of three values:
* FIND_IDENT: find an identifier (keyword)
* FIND_STRING: find any non-white string
* FIND_IDENT + FIND_STRING: find any non-white string, identifier preferred.
* FIND_EVAL: find text useful for C program debugging
*
* There are three steps:
* 1. Search forward for the start of an identifier/string. Doesn't move if
* already on one.
* 2. Search backward for the start of this identifier/string.
* This doesn't match the real Vi but I like it a little better and it
* shouldn't bother anyone.
* 3. Search forward to the end of this identifier/string.
* When FIND_IDENT isn't defined, we backup until a blank.
*
* Returns the length of the string, or zero if no string is found.
* If a string is found, a pointer to the string is put in "*string". This
* string is not always NUL terminated.
*/
int
find_ident_under_cursor(string, find_type)
char_u **string;
int find_type;
{
return find_ident_at_pos(curwin, curwin->w_cursor.lnum,
curwin->w_cursor.col, string, find_type);
}
/*
* Like find_ident_under_cursor(), but for any window and any position.
* However: Uses 'iskeyword' from the current window!.
*/
int
find_ident_at_pos(wp, lnum, startcol, string, find_type)
win_T *wp;
linenr_T lnum;
colnr_T startcol;
char_u **string;
int find_type;
{
char_u *ptr;
int col = 0; /* init to shut up GCC */
int i;
#ifdef FEAT_MBYTE
int this_class = 0;
int prev_class;
int prevcol;
#endif
#if defined(FEAT_BEVAL)
int bn = 0; /* bracket nesting */
#endif
/*
* if i == 0: try to find an identifier
* if i == 1: try to find any non-white string
*/
ptr = ml_get_buf(wp->w_buffer, lnum, FALSE);
for (i = (find_type & FIND_IDENT) ? 0 : 1; i < 2; ++i)
{
/*
* 1. skip to start of identifier/string
*/
col = startcol;
#ifdef FEAT_MBYTE
if (has_mbyte)
{
while (ptr[col] != NUL)
{
# if defined(FEAT_BEVAL)
/* Stop at a ']' to evaluate "a[x]". */
if ((find_type & FIND_EVAL) && ptr[col] == ']')
break;
# endif
this_class = mb_get_class(ptr + col);
if (this_class != 0 && (i == 1 || this_class != 1))
break;
col += (*mb_ptr2len)(ptr + col);
}
}
else
#endif
while (ptr[col] != NUL
&& (i == 0 ? !vim_iswordc(ptr[col]) : vim_iswhite(ptr[col]))
# if defined(FEAT_BEVAL)
&& (!(find_type & FIND_EVAL) || ptr[col] != ']')
# endif
)
++col;
#if defined(FEAT_BEVAL)
/* When starting on a ']' count it, so that we include the '['. */
bn = ptr[col] == ']';
#endif
/*
* 2. Back up to start of identifier/string.
*/
#ifdef FEAT_MBYTE
if (has_mbyte)
{
/* Remember class of character under cursor. */
# if defined(FEAT_BEVAL)
if ((find_type & FIND_EVAL) && ptr[col] == ']')
this_class = mb_get_class((char_u *)"a");
else
# endif
this_class = mb_get_class(ptr + col);
while (col > 0 && this_class != 0)
{
prevcol = col - 1 - (*mb_head_off)(ptr, ptr + col - 1);
prev_class = mb_get_class(ptr + prevcol);
if (this_class != prev_class
&& (i == 0
|| prev_class == 0
|| (find_type & FIND_IDENT))
# if defined(FEAT_BEVAL)
&& (!(find_type & FIND_EVAL)
|| prevcol == 0
|| !find_is_eval_item(ptr + prevcol, &prevcol,
&bn, BACKWARD))
# endif
)
break;
col = prevcol;
}
/* If we don't want just any old string, or we've found an
* identifier, stop searching. */
if (this_class > 2)
this_class = 2;
if (!(find_type & FIND_STRING) || this_class == 2)
break;
}
else
#endif
{
while (col > 0
&& ((i == 0
? vim_iswordc(ptr[col - 1])
: (!vim_iswhite(ptr[col - 1])
&& (!(find_type & FIND_IDENT)
|| !vim_iswordc(ptr[col - 1]))))
#if defined(FEAT_BEVAL)
|| ((find_type & FIND_EVAL)
&& col > 1
&& find_is_eval_item(ptr + col - 1, &col,
&bn, BACKWARD))
#endif
))
--col;
/* If we don't want just any old string, or we've found an
* identifier, stop searching. */
if (!(find_type & FIND_STRING) || vim_iswordc(ptr[col]))
break;
}
}
if (ptr[col] == NUL || (i == 0 && (
#ifdef FEAT_MBYTE
has_mbyte ? this_class != 2 :
#endif
!vim_iswordc(ptr[col]))))
{
/*
* didn't find an identifier or string
*/
if (find_type & FIND_STRING)
EMSG(_("E348: No string under cursor"));
else
EMSG(_(e_noident));
return 0;
}
ptr += col;
*string = ptr;
/*
* 3. Find the end if the identifier/string.
*/
#if defined(FEAT_BEVAL)
bn = 0;
startcol -= col;
#endif
col = 0;
#ifdef FEAT_MBYTE
if (has_mbyte)
{
/* Search for point of changing multibyte character class. */
this_class = mb_get_class(ptr);
while (ptr[col] != NUL
&& ((i == 0 ? mb_get_class(ptr + col) == this_class
: mb_get_class(ptr + col) != 0)
# if defined(FEAT_BEVAL)
|| ((find_type & FIND_EVAL)
&& col <= (int)startcol
&& find_is_eval_item(ptr + col, &col, &bn, FORWARD))
# endif
))
col += (*mb_ptr2len)(ptr + col);
}
else
#endif
while ((i == 0 ? vim_iswordc(ptr[col])
: (ptr[col] != NUL && !vim_iswhite(ptr[col])))
# if defined(FEAT_BEVAL)
|| ((find_type & FIND_EVAL)
&& col <= (int)startcol
&& find_is_eval_item(ptr + col, &col, &bn, FORWARD))
# endif
)
{
++col;
}
return col;
}
/*
* Prepare for redo of a normal command.
*/
static void
prep_redo_cmd(cap)
cmdarg_T *cap;
{
prep_redo(cap->oap->regname, cap->count0,
NUL, cap->cmdchar, NUL, NUL, cap->nchar);
}
/*
* Prepare for redo of any command.
* Note that only the last argument can be a multi-byte char.
*/
static void
prep_redo(regname, num, cmd1, cmd2, cmd3, cmd4, cmd5)
int regname;
long num;
int cmd1;
int cmd2;
int cmd3;
int cmd4;
int cmd5;
{
ResetRedobuff();
if (regname != 0) /* yank from specified buffer */
{
AppendCharToRedobuff('"');
AppendCharToRedobuff(regname);
}
if (num)
AppendNumberToRedobuff(num);
if (cmd1 != NUL)
AppendCharToRedobuff(cmd1);
if (cmd2 != NUL)
AppendCharToRedobuff(cmd2);
if (cmd3 != NUL)
AppendCharToRedobuff(cmd3);
if (cmd4 != NUL)
AppendCharToRedobuff(cmd4);
if (cmd5 != NUL)
AppendCharToRedobuff(cmd5);
}
/*
* check for operator active and clear it
*
* return TRUE if operator was active
*/
static int
checkclearop(oap)
oparg_T *oap;
{
if (oap->op_type == OP_NOP)
return FALSE;
clearopbeep(oap);
return TRUE;
}
/*
* Check for operator or Visual active. Clear active operator.
*
* Return TRUE if operator or Visual was active.
*/
static int
checkclearopq(oap)
oparg_T *oap;
{
if (oap->op_type == OP_NOP
#ifdef FEAT_VISUAL
&& !VIsual_active
#endif
)
return FALSE;
clearopbeep(oap);
return TRUE;
}
static void
clearop(oap)
oparg_T *oap;
{
oap->op_type = OP_NOP;
oap->regname = 0;
oap->motion_force = NUL;
oap->use_reg_one = FALSE;
}
static void
clearopbeep(oap)
oparg_T *oap;
{
clearop(oap);
beep_flush();
}
#ifdef FEAT_VISUAL
/*
* Remove the shift modifier from a special key.
*/
static void
unshift_special(cap)
cmdarg_T *cap;
{
switch (cap->cmdchar)
{
case K_S_RIGHT: cap->cmdchar = K_RIGHT; break;
case K_S_LEFT: cap->cmdchar = K_LEFT; break;
case K_S_UP: cap->cmdchar = K_UP; break;
case K_S_DOWN: cap->cmdchar = K_DOWN; break;
case K_S_HOME: cap->cmdchar = K_HOME; break;
case K_S_END: cap->cmdchar = K_END; break;
}
cap->cmdchar = simplify_key(cap->cmdchar, &mod_mask);
}
#endif
#if defined(FEAT_CMDL_INFO) || defined(PROTO)
/*
* Routines for displaying a partly typed command
*/
#ifdef FEAT_VISUAL /* need room for size of Visual area */
# define SHOWCMD_BUFLEN SHOWCMD_COLS + 1 + 30
#else
# define SHOWCMD_BUFLEN SHOWCMD_COLS + 1
#endif
static char_u showcmd_buf[SHOWCMD_BUFLEN];
static char_u old_showcmd_buf[SHOWCMD_BUFLEN]; /* For push_showcmd() */
static int showcmd_is_clear = TRUE;
static int showcmd_visual = FALSE;
static void display_showcmd __ARGS((void));
void
clear_showcmd()
{
if (!p_sc)
return;
#ifdef FEAT_VISUAL
if (VIsual_active && !char_avail())
{
int cursor_bot = lt(VIsual, curwin->w_cursor);
long lines;
colnr_T leftcol, rightcol;
linenr_T top, bot;
/* Show the size of the Visual area. */
if (cursor_bot)
{
top = VIsual.lnum;
bot = curwin->w_cursor.lnum;
}
else
{
top = curwin->w_cursor.lnum;
bot = VIsual.lnum;
}
# ifdef FEAT_FOLDING
/* Include closed folds as a whole. */
hasFolding(top, &top, NULL);
hasFolding(bot, NULL, &bot);
# endif
lines = bot - top + 1;
if (VIsual_mode == Ctrl_V)
{
# ifdef FEAT_LINEBREAK
char_u *saved_sbr = p_sbr;
/* Make 'sbr' empty for a moment to get the correct size. */
p_sbr = empty_option;
# endif
getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol);
# ifdef FEAT_LINEBREAK
p_sbr = saved_sbr;
# endif
sprintf((char *)showcmd_buf, "%ldx%ld", lines,
(long)(rightcol - leftcol + 1));
}
else if (VIsual_mode == 'V' || VIsual.lnum != curwin->w_cursor.lnum)
sprintf((char *)showcmd_buf, "%ld", lines);
else
{
char_u *s, *e;
int l;
int bytes = 0;
int chars = 0;
if (cursor_bot)
{
s = ml_get_pos(&VIsual);
e = ml_get_cursor();
}
else
{
s = ml_get_cursor();
e = ml_get_pos(&VIsual);
}
while ((*p_sel != 'e') ? s <= e : s < e)
{
# ifdef FEAT_MBYTE
l = (*mb_ptr2len)(s);
# else
l = (*s == NUL) ? 0 : 1;
# endif
if (l == 0)
{
++bytes;
++chars;
break; /* end of line */
}
bytes += l;
++chars;
s += l;
}
if (bytes == chars)
sprintf((char *)showcmd_buf, "%d", chars);
else
sprintf((char *)showcmd_buf, "%d-%d", chars, bytes);
}
showcmd_buf[SHOWCMD_COLS] = NUL; /* truncate */
showcmd_visual = TRUE;
}
else
#endif
{
showcmd_buf[0] = NUL;
showcmd_visual = FALSE;
/* Don't actually display something if there is nothing to clear. */
if (showcmd_is_clear)
return;
}
display_showcmd();
}
/*
* Add 'c' to string of shown command chars.
* Return TRUE if output has been written (and setcursor() has been called).
*/
int
add_to_showcmd(c)
int c;
{
char_u *p;
int old_len;
int extra_len;
int overflow;
#if defined(FEAT_MOUSE)
int i;
static int ignore[] =
{
# ifdef FEAT_GUI
K_VER_SCROLLBAR, K_HOR_SCROLLBAR,
K_LEFTMOUSE_NM, K_LEFTRELEASE_NM,
# endif
K_IGNORE,
K_LEFTMOUSE, K_LEFTDRAG, K_LEFTRELEASE,
K_MIDDLEMOUSE, K_MIDDLEDRAG, K_MIDDLERELEASE,
K_RIGHTMOUSE, K_RIGHTDRAG, K_RIGHTRELEASE,
K_MOUSEDOWN, K_MOUSEUP, K_MOUSELEFT, K_MOUSERIGHT,
K_X1MOUSE, K_X1DRAG, K_X1RELEASE, K_X2MOUSE, K_X2DRAG, K_X2RELEASE,
K_CURSORHOLD,
# ifdef FEAT_GUI_MACVIM
K_SWIPELEFT, K_SWIPERIGHT, K_SWIPEUP, K_SWIPEDOWN,
# endif
0
};
#endif
if (!p_sc || msg_silent != 0)
return FALSE;
if (showcmd_visual)
{
showcmd_buf[0] = NUL;
showcmd_visual = FALSE;
}
#if defined(FEAT_MOUSE)
/* Ignore keys that are scrollbar updates and mouse clicks */
if (IS_SPECIAL(c))
for (i = 0; ignore[i] != 0; ++i)
if (ignore[i] == c)
return FALSE;
#endif
p = transchar(c);
old_len = (int)STRLEN(showcmd_buf);
extra_len = (int)STRLEN(p);
overflow = old_len + extra_len - SHOWCMD_COLS;
if (overflow > 0)
mch_memmove(showcmd_buf, showcmd_buf + overflow,
old_len - overflow + 1);
STRCAT(showcmd_buf, p);
if (char_avail())
return FALSE;
display_showcmd();
return TRUE;
}
void
add_to_showcmd_c(c)
int c;
{
if (!add_to_showcmd(c))
setcursor();
}
/*
* Delete 'len' characters from the end of the shown command.
*/
static void
del_from_showcmd(len)
int len;
{
int old_len;
if (!p_sc)
return;
old_len = (int)STRLEN(showcmd_buf);
if (len > old_len)
len = old_len;
showcmd_buf[old_len - len] = NUL;
if (!char_avail())
display_showcmd();
}
/*
* push_showcmd() and pop_showcmd() are used when waiting for the user to type
* something and there is a partial mapping.
*/
void
push_showcmd()
{
if (p_sc)
STRCPY(old_showcmd_buf, showcmd_buf);
}
void
pop_showcmd()
{
if (!p_sc)
return;
STRCPY(showcmd_buf, old_showcmd_buf);
display_showcmd();
}
static void
display_showcmd()
{
int len;
cursor_off();
len = (int)STRLEN(showcmd_buf);
if (len == 0)
showcmd_is_clear = TRUE;
else
{
screen_puts(showcmd_buf, (int)Rows - 1, sc_col, 0);
showcmd_is_clear = FALSE;
}
/*
* clear the rest of an old message by outputting up to SHOWCMD_COLS
* spaces
*/
screen_puts((char_u *)" " + len, (int)Rows - 1, sc_col + len, 0);
setcursor(); /* put cursor back where it belongs */
}
#endif
#ifdef FEAT_SCROLLBIND
/*
* When "check" is FALSE, prepare for commands that scroll the window.
* When "check" is TRUE, take care of scroll-binding after the window has
* scrolled. Called from normal_cmd() and edit().
*/
void
do_check_scrollbind(check)
int check;
{
static win_T *old_curwin = NULL;
static linenr_T old_topline = 0;
#ifdef FEAT_DIFF
static int old_topfill = 0;
#endif
static buf_T *old_buf = NULL;
static colnr_T old_leftcol = 0;
if (check && curwin->w_p_scb)
{
/* If a ":syncbind" command was just used, don't scroll, only reset
* the values. */
if (did_syncbind)
did_syncbind = FALSE;
else if (curwin == old_curwin)
{
/*
* Synchronize other windows, as necessary according to
* 'scrollbind'. Don't do this after an ":edit" command, except
* when 'diff' is set.
*/
if ((curwin->w_buffer == old_buf
#ifdef FEAT_DIFF
|| curwin->w_p_diff
#endif
)
&& (curwin->w_topline != old_topline
#ifdef FEAT_DIFF
|| curwin->w_topfill != old_topfill
#endif
|| curwin->w_leftcol != old_leftcol))
{
check_scrollbind(curwin->w_topline - old_topline,
(long)(curwin->w_leftcol - old_leftcol));
}
}
else if (vim_strchr(p_sbo, 'j')) /* jump flag set in 'scrollopt' */
{
/*
* When switching between windows, make sure that the relative
* vertical offset is valid for the new window. The relative
* offset is invalid whenever another 'scrollbind' window has
* scrolled to a point that would force the current window to
* scroll past the beginning or end of its buffer. When the
* resync is performed, some of the other 'scrollbind' windows may
* need to jump so that the current window's relative position is
* visible on-screen.
*/
check_scrollbind(curwin->w_topline - curwin->w_scbind_pos, 0L);
}
curwin->w_scbind_pos = curwin->w_topline;
}
old_curwin = curwin;
old_topline = curwin->w_topline;