Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

10234 lines (9393 sloc) 254.51 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.
*/
/*
* edit.c: functions for Insert mode
*/
#include "vim.h"
#ifdef FEAT_INS_EXPAND
/*
* definitions used for CTRL-X submode
*/
#define CTRL_X_WANT_IDENT 0x100
#define CTRL_X_NOT_DEFINED_YET 1
#define CTRL_X_SCROLL 2
#define CTRL_X_WHOLE_LINE 3
#define CTRL_X_FILES 4
#define CTRL_X_TAGS (5 + CTRL_X_WANT_IDENT)
#define CTRL_X_PATH_PATTERNS (6 + CTRL_X_WANT_IDENT)
#define CTRL_X_PATH_DEFINES (7 + CTRL_X_WANT_IDENT)
#define CTRL_X_FINISHED 8
#define CTRL_X_DICTIONARY (9 + CTRL_X_WANT_IDENT)
#define CTRL_X_THESAURUS (10 + CTRL_X_WANT_IDENT)
#define CTRL_X_CMDLINE 11
#define CTRL_X_FUNCTION 12
#define CTRL_X_OMNI 13
#define CTRL_X_SPELL 14
#define CTRL_X_LOCAL_MSG 15 /* only used in "ctrl_x_msgs" */
#define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT]
static char *ctrl_x_msgs[] =
{
N_(" Keyword completion (^N^P)"), /* ctrl_x_mode == 0, ^P/^N compl. */
N_(" ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"),
NULL,
N_(" Whole line completion (^L^N^P)"),
N_(" File name completion (^F^N^P)"),
N_(" Tag completion (^]^N^P)"),
N_(" Path pattern completion (^N^P)"),
N_(" Definition completion (^D^N^P)"),
NULL,
N_(" Dictionary completion (^K^N^P)"),
N_(" Thesaurus completion (^T^N^P)"),
N_(" Command-line completion (^V^N^P)"),
N_(" User defined completion (^U^N^P)"),
N_(" Omni completion (^O^N^P)"),
N_(" Spelling suggestion (s^N^P)"),
N_(" Keyword Local completion (^N^P)"),
};
static char e_hitend[] = N_("Hit end of paragraph");
#ifdef FEAT_COMPL_FUNC
static char e_complwin[] = N_("E839: Completion function changed window");
static char e_compldel[] = N_("E840: Completion function deleted text");
#endif
/*
* Structure used to store one match for insert completion.
*/
typedef struct compl_S compl_T;
struct compl_S
{
compl_T *cp_next;
compl_T *cp_prev;
char_u *cp_str; /* matched text */
char cp_icase; /* TRUE or FALSE: ignore case */
char_u *(cp_text[CPT_COUNT]); /* text for the menu */
char_u *cp_fname; /* file containing the match, allocated when
* cp_flags has FREE_FNAME */
int cp_flags; /* ORIGINAL_TEXT, CONT_S_IPOS or FREE_FNAME */
int cp_number; /* sequence number */
};
#define ORIGINAL_TEXT (1) /* the original text when the expansion begun */
#define FREE_FNAME (2)
/*
* All the current matches are stored in a list.
* "compl_first_match" points to the start of the list.
* "compl_curr_match" points to the currently selected entry.
* "compl_shown_match" is different from compl_curr_match during
* ins_compl_get_exp().
*/
static compl_T *compl_first_match = NULL;
static compl_T *compl_curr_match = NULL;
static compl_T *compl_shown_match = NULL;
/* After using a cursor key <Enter> selects a match in the popup menu,
* otherwise it inserts a line break. */
static int compl_enter_selects = FALSE;
/* When "compl_leader" is not NULL only matches that start with this string
* are used. */
static char_u *compl_leader = NULL;
static int compl_get_longest = FALSE; /* put longest common string
in compl_leader */
static int compl_used_match; /* Selected one of the matches. When
FALSE the match was edited or using
the longest common string. */
static int compl_was_interrupted = FALSE; /* didn't finish finding
completions. */
static int compl_restarting = FALSE; /* don't insert match */
/* When the first completion is done "compl_started" is set. When it's
* FALSE the word to be completed must be located. */
static int compl_started = FALSE;
/* Set when doing something for completion that may call edit() recursively,
* which is not allowed. */
static int compl_busy = FALSE;
static int compl_matches = 0;
static char_u *compl_pattern = NULL;
static int compl_direction = FORWARD;
static int compl_shows_dir = FORWARD;
static int compl_pending = 0; /* > 1 for postponed CTRL-N */
static pos_T compl_startpos;
static colnr_T compl_col = 0; /* column where the text starts
* that is being completed */
static char_u *compl_orig_text = NULL; /* text as it was before
* completion started */
static int compl_cont_mode = 0;
static expand_T compl_xp;
static int compl_opt_refresh_always = FALSE;
static void ins_ctrl_x __ARGS((void));
static int has_compl_option __ARGS((int dict_opt));
static int ins_compl_accept_char __ARGS((int c));
static int ins_compl_add __ARGS((char_u *str, int len, int icase, char_u *fname, char_u **cptext, int cdir, int flags, int adup));
static int ins_compl_equal __ARGS((compl_T *match, char_u *str, int len));
static void ins_compl_longest_match __ARGS((compl_T *match));
static void ins_compl_add_matches __ARGS((int num_matches, char_u **matches, int icase));
static int ins_compl_make_cyclic __ARGS((void));
static void ins_compl_upd_pum __ARGS((void));
static void ins_compl_del_pum __ARGS((void));
static int pum_wanted __ARGS((void));
static int pum_enough_matches __ARGS((void));
static void ins_compl_dictionaries __ARGS((char_u *dict, char_u *pat, int flags, int thesaurus));
static void ins_compl_files __ARGS((int count, char_u **files, int thesaurus, int flags, regmatch_T *regmatch, char_u *buf, int *dir));
static char_u *find_line_end __ARGS((char_u *ptr));
static void ins_compl_free __ARGS((void));
static void ins_compl_clear __ARGS((void));
static int ins_compl_bs __ARGS((void));
static int ins_compl_need_restart __ARGS((void));
static void ins_compl_new_leader __ARGS((void));
static void ins_compl_addleader __ARGS((int c));
static int ins_compl_len __ARGS((void));
static void ins_compl_restart __ARGS((void));
static void ins_compl_set_original_text __ARGS((char_u *str));
static void ins_compl_addfrommatch __ARGS((void));
static int ins_compl_prep __ARGS((int c));
static void ins_compl_fixRedoBufForLeader __ARGS((char_u *ptr_arg));
static buf_T *ins_compl_next_buf __ARGS((buf_T *buf, int flag));
#if defined(FEAT_COMPL_FUNC) || defined(FEAT_EVAL)
static void ins_compl_add_list __ARGS((list_T *list));
static void ins_compl_add_dict __ARGS((dict_T *dict));
#endif
static int ins_compl_get_exp __ARGS((pos_T *ini));
static void ins_compl_delete __ARGS((void));
static void ins_compl_insert __ARGS((void));
static int ins_compl_next __ARGS((int allow_get_expansion, int count, int insert_match));
static int ins_compl_key2dir __ARGS((int c));
static int ins_compl_pum_key __ARGS((int c));
static int ins_compl_key2count __ARGS((int c));
static int ins_compl_use_match __ARGS((int c));
static int ins_complete __ARGS((int c));
static unsigned quote_meta __ARGS((char_u *dest, char_u *str, int len));
#endif /* FEAT_INS_EXPAND */
#define BACKSPACE_CHAR 1
#define BACKSPACE_WORD 2
#define BACKSPACE_WORD_NOT_SPACE 3
#define BACKSPACE_LINE 4
static void ins_redraw __ARGS((int ready));
static void ins_ctrl_v __ARGS((void));
static void undisplay_dollar __ARGS((void));
static void insert_special __ARGS((int, int, int));
static void internal_format __ARGS((int textwidth, int second_indent, int flags, int format_only, int c));
static void check_auto_format __ARGS((int));
static void redo_literal __ARGS((int c));
static void start_arrow __ARGS((pos_T *end_insert_pos));
#ifdef FEAT_SPELL
static void check_spell_redraw __ARGS((void));
static void spell_back_to_badword __ARGS((void));
static int spell_bad_len = 0; /* length of located bad word */
#endif
static void stop_insert __ARGS((pos_T *end_insert_pos, int esc));
static int echeck_abbr __ARGS((int));
static int replace_pop __ARGS((void));
static void replace_join __ARGS((int off));
static void replace_pop_ins __ARGS((void));
#ifdef FEAT_MBYTE
static void mb_replace_pop_ins __ARGS((int cc));
#endif
static void replace_flush __ARGS((void));
static void replace_do_bs __ARGS((int limit_col));
static int del_char_after_col __ARGS((int limit_col));
#ifdef FEAT_CINDENT
static int cindent_on __ARGS((void));
#endif
static void ins_reg __ARGS((void));
static void ins_ctrl_g __ARGS((void));
static void ins_ctrl_hat __ARGS((void));
static int ins_esc __ARGS((long *count, int cmdchar, int nomove));
#ifdef FEAT_RIGHTLEFT
static void ins_ctrl_ __ARGS((void));
#endif
#ifdef FEAT_VISUAL
static int ins_start_select __ARGS((int c));
#endif
static void ins_insert __ARGS((int replaceState));
static void ins_ctrl_o __ARGS((void));
static void ins_shift __ARGS((int c, int lastc));
static void ins_del __ARGS((void));
static int ins_bs __ARGS((int c, int mode, int *inserted_space_p));
#ifdef FEAT_MOUSE
static void ins_mouse __ARGS((int c));
static void ins_mousescroll __ARGS((int dir));
#endif
#if defined(FEAT_GUI_TABLINE) || defined(PROTO)
static void ins_tabline __ARGS((int c));
#endif
static void ins_left __ARGS((void));
static void ins_home __ARGS((int c));
static void ins_end __ARGS((int c));
static void ins_s_left __ARGS((void));
static void ins_right __ARGS((void));
static void ins_s_right __ARGS((void));
static void ins_up __ARGS((int startcol));
static void ins_pageup __ARGS((void));
static void ins_down __ARGS((int startcol));
static void ins_pagedown __ARGS((void));
#ifdef FEAT_DND
static void ins_drop __ARGS((void));
#endif
static int ins_tab __ARGS((void));
static int ins_eol __ARGS((int c));
#ifdef FEAT_DIGRAPHS
static int ins_digraph __ARGS((void));
#endif
static int ins_ctrl_ey __ARGS((int tc));
#ifdef FEAT_SMARTINDENT
static void ins_try_si __ARGS((int c));
#endif
static colnr_T get_nolist_virtcol __ARGS((void));
#ifdef FEAT_AUTOCMD
static char_u *do_insert_char_pre __ARGS((int c));
#endif
static colnr_T Insstart_textlen; /* length of line when insert started */
static colnr_T Insstart_blank_vcol; /* vcol for first inserted blank */
static char_u *last_insert = NULL; /* the text of the previous insert,
K_SPECIAL and CSI are escaped */
static int last_insert_skip; /* nr of chars in front of previous insert */
static int new_insert_skip; /* nr of chars in front of current insert */
static int did_restart_edit; /* "restart_edit" when calling edit() */
#ifdef FEAT_CINDENT
static int can_cindent; /* may do cindenting on this line */
#endif
static int old_indent = 0; /* for ^^D command in insert mode */
#ifdef FEAT_RIGHTLEFT
static int revins_on; /* reverse insert mode on */
static int revins_chars; /* how much to skip after edit */
static int revins_legal; /* was the last char 'legal'? */
static int revins_scol; /* start column of revins session */
#endif
static int ins_need_undo; /* call u_save() before inserting a
char. Set when edit() is called.
after that arrow_used is used. */
static int did_add_space = FALSE; /* auto_format() added an extra space
under the cursor */
/*
* edit(): Start inserting text.
*
* "cmdchar" can be:
* 'i' normal insert command
* 'a' normal append command
* 'R' replace command
* 'r' "r<CR>" command: insert one <CR>. Note: count can be > 1, for redo,
* but still only one <CR> is inserted. The <Esc> is not used for redo.
* 'g' "gI" command.
* 'V' "gR" command for Virtual Replace mode.
* 'v' "gr" command for single character Virtual Replace mode.
*
* This function is not called recursively. For CTRL-O commands, it returns
* and lets the caller handle the Normal-mode command.
*
* Return TRUE if a CTRL-O command caused the return (insert mode pending).
*/
int
edit(cmdchar, startln, count)
int cmdchar;
int startln; /* if set, insert at start of line */
long count;
{
int c = 0;
char_u *ptr;
int lastc;
int mincol;
static linenr_T o_lnum = 0;
int i;
int did_backspace = TRUE; /* previous char was backspace */
#ifdef FEAT_CINDENT
int line_is_white = FALSE; /* line is empty before insert */
#endif
linenr_T old_topline = 0; /* topline before insertion */
#ifdef FEAT_DIFF
int old_topfill = -1;
#endif
int inserted_space = FALSE; /* just inserted a space */
int replaceState = REPLACE;
int nomove = FALSE; /* don't move cursor on return */
/* Remember whether editing was restarted after CTRL-O. */
did_restart_edit = restart_edit;
/* sleep before redrawing, needed for "CTRL-O :" that results in an
* error message */
check_for_delay(TRUE);
#ifdef HAVE_SANDBOX
/* Don't allow inserting in the sandbox. */
if (sandbox != 0)
{
EMSG(_(e_sandbox));
return FALSE;
}
#endif
/* Don't allow changes in the buffer while editing the cmdline. The
* caller of getcmdline() may get confused. */
if (textlock != 0)
{
EMSG(_(e_secure));
return FALSE;
}
#ifdef FEAT_INS_EXPAND
/* Don't allow recursive insert mode when busy with completion. */
if (compl_started || compl_busy || pum_visible())
{
EMSG(_(e_secure));
return FALSE;
}
ins_compl_clear(); /* clear stuff for CTRL-X mode */
#endif
#ifdef FEAT_AUTOCMD
/*
* Trigger InsertEnter autocommands. Do not do this for "r<CR>" or "grx".
*/
if (cmdchar != 'r' && cmdchar != 'v')
{
# ifdef FEAT_EVAL
if (cmdchar == 'R')
ptr = (char_u *)"r";
else if (cmdchar == 'V')
ptr = (char_u *)"v";
else
ptr = (char_u *)"i";
set_vim_var_string(VV_INSERTMODE, ptr, 1);
# endif
apply_autocmds(EVENT_INSERTENTER, NULL, NULL, FALSE, curbuf);
}
#endif
#ifdef FEAT_CONCEAL
/* Check if the cursor line needs redrawing before changing State. If
* 'concealcursor' is "n" it needs to be redrawn without concealing. */
conceal_check_cursur_line();
#endif
#ifdef FEAT_MOUSE
/*
* When doing a paste with the middle mouse button, Insstart is set to
* where the paste started.
*/
if (where_paste_started.lnum != 0)
Insstart = where_paste_started;
else
#endif
{
Insstart = curwin->w_cursor;
if (startln)
Insstart.col = 0;
}
Insstart_textlen = (colnr_T)linetabsize(ml_get_curline());
Insstart_blank_vcol = MAXCOL;
if (!did_ai)
ai_col = 0;
if (cmdchar != NUL && restart_edit == 0)
{
ResetRedobuff();
AppendNumberToRedobuff(count);
#ifdef FEAT_VREPLACE
if (cmdchar == 'V' || cmdchar == 'v')
{
/* "gR" or "gr" command */
AppendCharToRedobuff('g');
AppendCharToRedobuff((cmdchar == 'v') ? 'r' : 'R');
}
else
#endif
{
AppendCharToRedobuff(cmdchar);
if (cmdchar == 'g') /* "gI" command */
AppendCharToRedobuff('I');
else if (cmdchar == 'r') /* "r<CR>" command */
count = 1; /* insert only one <CR> */
}
}
if (cmdchar == 'R')
{
#ifdef FEAT_FKMAP
if (p_fkmap && p_ri)
{
beep_flush();
EMSG(farsi_text_3); /* encoded in Farsi */
State = INSERT;
}
else
#endif
State = REPLACE;
}
#ifdef FEAT_VREPLACE
else if (cmdchar == 'V' || cmdchar == 'v')
{
State = VREPLACE;
replaceState = VREPLACE;
orig_line_count = curbuf->b_ml.ml_line_count;
vr_lines_changed = 1;
}
#endif
else
State = INSERT;
stop_insert_mode = FALSE;
/*
* Need to recompute the cursor position, it might move when the cursor is
* on a TAB or special character.
*/
curs_columns(TRUE);
/*
* Enable langmap or IME, indicated by 'iminsert'.
* Note that IME may enabled/disabled without us noticing here, thus the
* 'iminsert' value may not reflect what is actually used. It is updated
* when hitting <Esc>.
*/
if (curbuf->b_p_iminsert == B_IMODE_LMAP)
State |= LANGMAP;
#ifdef USE_IM_CONTROL
im_set_active(curbuf->b_p_iminsert == B_IMODE_IM);
#endif
#ifdef FEAT_MOUSE
setmouse();
#endif
#ifdef FEAT_CMDL_INFO
clear_showcmd();
#endif
#ifdef FEAT_RIGHTLEFT
/* there is no reverse replace mode */
revins_on = (State == INSERT && p_ri);
if (revins_on)
undisplay_dollar();
revins_chars = 0;
revins_legal = 0;
revins_scol = -1;
#endif
/*
* Handle restarting Insert mode.
* Don't do this for "CTRL-O ." (repeat an insert): we get here with
* restart_edit non-zero, and something in the stuff buffer.
*/
if (restart_edit != 0 && stuff_empty())
{
#ifdef FEAT_MOUSE
/*
* After a paste we consider text typed to be part of the insert for
* the pasted text. You can backspace over the pasted text too.
*/
if (where_paste_started.lnum)
arrow_used = FALSE;
else
#endif
arrow_used = TRUE;
restart_edit = 0;
/*
* If the cursor was after the end-of-line before the CTRL-O and it is
* now at the end-of-line, put it after the end-of-line (this is not
* correct in very rare cases).
* Also do this if curswant is greater than the current virtual
* column. Eg after "^O$" or "^O80|".
*/
validate_virtcol();
update_curswant();
if (((ins_at_eol && curwin->w_cursor.lnum == o_lnum)
|| curwin->w_curswant > curwin->w_virtcol)
&& *(ptr = ml_get_curline() + curwin->w_cursor.col) != NUL)
{
if (ptr[1] == NUL)
++curwin->w_cursor.col;
#ifdef FEAT_MBYTE
else if (has_mbyte)
{
i = (*mb_ptr2len)(ptr);
if (ptr[i] == NUL)
curwin->w_cursor.col += i;
}
#endif
}
ins_at_eol = FALSE;
}
else
arrow_used = FALSE;
/* we are in insert mode now, don't need to start it anymore */
need_start_insertmode = FALSE;
/* Need to save the line for undo before inserting the first char. */
ins_need_undo = TRUE;
#ifdef FEAT_MOUSE
where_paste_started.lnum = 0;
#endif
#ifdef FEAT_CINDENT
can_cindent = TRUE;
#endif
#ifdef FEAT_FOLDING
/* The cursor line is not in a closed fold, unless 'insertmode' is set or
* restarting. */
if (!p_im && did_restart_edit == 0)
foldOpenCursor();
#endif
/*
* If 'showmode' is set, show the current (insert/replace/..) mode.
* A warning message for changing a readonly file is given here, before
* actually changing anything. It's put after the mode, if any.
*/
i = 0;
if (p_smd && msg_silent == 0)
i = showmode();
if (!p_im && did_restart_edit == 0)
change_warning(i == 0 ? 0 : i + 1);
#ifdef CURSOR_SHAPE
ui_cursor_shape(); /* may show different cursor shape */
#endif
#ifdef FEAT_DIGRAPHS
do_digraph(-1); /* clear digraphs */
#endif
/*
* Get the current length of the redo buffer, those characters have to be
* skipped if we want to get to the inserted characters.
*/
ptr = get_inserted();
if (ptr == NULL)
new_insert_skip = 0;
else
{
new_insert_skip = (int)STRLEN(ptr);
vim_free(ptr);
}
old_indent = 0;
/*
* Main loop in Insert mode: repeat until Insert mode is left.
*/
for (;;)
{
#ifdef FEAT_RIGHTLEFT
if (!revins_legal)
revins_scol = -1; /* reset on illegal motions */
else
revins_legal = 0;
#endif
if (arrow_used) /* don't repeat insert when arrow key used */
count = 0;
if (stop_insert_mode)
{
/* ":stopinsert" used or 'insertmode' reset */
count = 0;
goto doESCkey;
}
/* set curwin->w_curswant for next K_DOWN or K_UP */
if (!arrow_used)
curwin->w_set_curswant = TRUE;
/* If there is no typeahead may check for timestamps (e.g., for when a
* menu invoked a shell command). */
if (stuff_empty())
{
did_check_timestamps = FALSE;
if (need_check_timestamps)
check_timestamps(FALSE);
}
/*
* When emsg() was called msg_scroll will have been set.
*/
msg_scroll = FALSE;
#ifdef FEAT_GUI
/* When 'mousefocus' is set a mouse movement may have taken us to
* another window. "need_mouse_correct" may then be set because of an
* autocommand. */
if (need_mouse_correct)
gui_mouse_correct();
#endif
#ifdef FEAT_FOLDING
/* Open fold at the cursor line, according to 'foldopen'. */
if (fdo_flags & FDO_INSERT)
foldOpenCursor();
/* Close folds where the cursor isn't, according to 'foldclose' */
if (!char_avail())
foldCheckClose();
#endif
/*
* If we inserted a character at the last position of the last line in
* the window, scroll the window one line up. This avoids an extra
* redraw.
* This is detected when the cursor column is smaller after inserting
* something.
* Don't do this when the topline changed already, it has
* already been adjusted (by insertchar() calling open_line())).
*/
if (curbuf->b_mod_set
&& curwin->w_p_wrap
&& !did_backspace
&& curwin->w_topline == old_topline
#ifdef FEAT_DIFF
&& curwin->w_topfill == old_topfill
#endif
)
{
mincol = curwin->w_wcol;
validate_cursor_col();
if ((int)curwin->w_wcol < mincol - curbuf->b_p_ts
&& curwin->w_wrow == W_WINROW(curwin)
+ curwin->w_height - 1 - p_so
&& (curwin->w_cursor.lnum != curwin->w_topline
#ifdef FEAT_DIFF
|| curwin->w_topfill > 0
#endif
))
{
#ifdef FEAT_DIFF
if (curwin->w_topfill > 0)
--curwin->w_topfill;
else
#endif
#ifdef FEAT_FOLDING
if (hasFolding(curwin->w_topline, NULL, &old_topline))
set_topline(curwin, old_topline + 1);
else
#endif
set_topline(curwin, curwin->w_topline + 1);
}
}
/* May need to adjust w_topline to show the cursor. */
update_topline();
did_backspace = FALSE;
validate_cursor(); /* may set must_redraw */
/*
* Redraw the display when no characters are waiting.
* Also shows mode, ruler and positions cursor.
*/
ins_redraw(TRUE);
#ifdef FEAT_SCROLLBIND
if (curwin->w_p_scb)
do_check_scrollbind(TRUE);
#endif
#ifdef FEAT_CURSORBIND
if (curwin->w_p_crb)
do_check_cursorbind();
#endif
update_curswant();
old_topline = curwin->w_topline;
#ifdef FEAT_DIFF
old_topfill = curwin->w_topfill;
#endif
#ifdef USE_ON_FLY_SCROLL
dont_scroll = FALSE; /* allow scrolling here */
#endif
/*
* Get a character for Insert mode. Ignore K_IGNORE.
*/
lastc = c; /* remember previous char for CTRL-D */
do
{
c = safe_vgetc();
} while (c == K_IGNORE);
#ifdef FEAT_AUTOCMD
/* Don't want K_CURSORHOLD for the second key, e.g., after CTRL-V. */
did_cursorhold = TRUE;
#endif
#ifdef FEAT_RIGHTLEFT
if (p_hkmap && KeyTyped)
c = hkmap(c); /* Hebrew mode mapping */
#endif
#ifdef FEAT_FKMAP
if (p_fkmap && KeyTyped)
c = fkmap(c); /* Farsi mode mapping */
#endif
#ifdef FEAT_INS_EXPAND
/*
* Special handling of keys while the popup menu is visible or wanted
* and the cursor is still in the completed word. Only when there is
* a match, skip this when no matches were found.
*/
if (compl_started
&& pum_wanted()
&& curwin->w_cursor.col >= compl_col
&& (compl_shown_match == NULL
|| compl_shown_match != compl_shown_match->cp_next))
{
/* BS: Delete one character from "compl_leader". */
if ((c == K_BS || c == Ctrl_H)
&& curwin->w_cursor.col > compl_col
&& (c = ins_compl_bs()) == NUL)
continue;
/* When no match was selected or it was edited. */
if (!compl_used_match)
{
/* CTRL-L: Add one character from the current match to
* "compl_leader". Except when at the original match and
* there is nothing to add, CTRL-L works like CTRL-P then. */
if (c == Ctrl_L
&& (ctrl_x_mode != CTRL_X_WHOLE_LINE
|| (int)STRLEN(compl_shown_match->cp_str)
> curwin->w_cursor.col - compl_col))
{
ins_compl_addfrommatch();
continue;
}
/* A non-white character that fits in with the current
* completion: Add to "compl_leader". */
if (ins_compl_accept_char(c))
{
#ifdef FEAT_AUTOCMD
/* Trigger InsertCharPre. */
char_u *str = do_insert_char_pre(c);
char_u *p;
if (str != NULL)
{
for (p = str; *p != NUL; mb_ptr_adv(p))
ins_compl_addleader(PTR2CHAR(p));
vim_free(str);
}
else
#endif
ins_compl_addleader(c);
continue;
}
/* Pressing CTRL-Y selects the current match. When
* compl_enter_selects is set the Enter key does the same. */
if (c == Ctrl_Y || (compl_enter_selects
&& (c == CAR || c == K_KENTER || c == NL)))
{
ins_compl_delete();
ins_compl_insert();
}
}
}
/* Prepare for or stop CTRL-X mode. This doesn't do completion, but
* it does fix up the text when finishing completion. */
compl_get_longest = FALSE;
if (ins_compl_prep(c))
continue;
#endif
/* CTRL-\ CTRL-N goes to Normal mode,
* CTRL-\ CTRL-G goes to mode selected with 'insertmode',
* CTRL-\ CTRL-O is like CTRL-O but without moving the cursor. */
if (c == Ctrl_BSL)
{
/* may need to redraw when no more chars available now */
ins_redraw(FALSE);
++no_mapping;
++allow_keys;
c = plain_vgetc();
--no_mapping;
--allow_keys;
if (c != Ctrl_N && c != Ctrl_G && c != Ctrl_O)
{
/* it's something else */
vungetc(c);
c = Ctrl_BSL;
}
else if (c == Ctrl_G && p_im)
continue;
else
{
if (c == Ctrl_O)
{
ins_ctrl_o();
ins_at_eol = FALSE; /* cursor keeps its column */
nomove = TRUE;
}
count = 0;
goto doESCkey;
}
}
#ifdef FEAT_DIGRAPHS
c = do_digraph(c);
#endif
#ifdef FEAT_INS_EXPAND
if ((c == Ctrl_V || c == Ctrl_Q) && ctrl_x_mode == CTRL_X_CMDLINE)
goto docomplete;
#endif
if (c == Ctrl_V || c == Ctrl_Q)
{
ins_ctrl_v();
c = Ctrl_V; /* pretend CTRL-V is last typed character */
continue;
}
#ifdef FEAT_CINDENT
if (cindent_on()
# ifdef FEAT_INS_EXPAND
&& ctrl_x_mode == 0
# endif
)
{
/* A key name preceded by a bang means this key is not to be
* inserted. Skip ahead to the re-indenting below.
* A key name preceded by a star means that indenting has to be
* done before inserting the key. */
line_is_white = inindent(0);
if (in_cinkeys(c, '!', line_is_white))
goto force_cindent;
if (can_cindent && in_cinkeys(c, '*', line_is_white)
&& stop_arrow() == OK)
do_c_expr_indent();
}
#endif
#ifdef FEAT_RIGHTLEFT
if (curwin->w_p_rl)
switch (c)
{
case K_LEFT: c = K_RIGHT; break;
case K_S_LEFT: c = K_S_RIGHT; break;
case K_C_LEFT: c = K_C_RIGHT; break;
case K_RIGHT: c = K_LEFT; break;
case K_S_RIGHT: c = K_S_LEFT; break;
case K_C_RIGHT: c = K_C_LEFT; break;
}
#endif
#ifdef FEAT_VISUAL
/*
* If 'keymodel' contains "startsel", may start selection. If it
* does, a CTRL-O and c will be stuffed, we need to get these
* characters.
*/
if (ins_start_select(c))
continue;
#endif
/*
* The big switch to handle a character in insert mode.
*/
switch (c)
{
case ESC: /* End input mode */
if (echeck_abbr(ESC + ABBR_OFF))
break;
/*FALLTHROUGH*/
case Ctrl_C: /* End input mode */
#ifdef FEAT_CMDWIN
if (c == Ctrl_C && cmdwin_type != 0)
{
/* Close the cmdline window. */
cmdwin_result = K_IGNORE;
got_int = FALSE; /* don't stop executing autocommands et al. */
nomove = TRUE;
goto doESCkey;
}
#endif
#ifdef UNIX
do_intr:
#endif
/* when 'insertmode' set, and not halfway a mapping, don't leave
* Insert mode */
if (goto_im())
{
if (got_int)
{
(void)vgetc(); /* flush all buffers */
got_int = FALSE;
}
else
vim_beep();
break;
}
doESCkey:
/*
* This is the ONLY return from edit()!
*/
/* Always update o_lnum, so that a "CTRL-O ." that adds a line
* still puts the cursor back after the inserted text. */
if (ins_at_eol && gchar_cursor() == NUL)
o_lnum = curwin->w_cursor.lnum;
if (ins_esc(&count, cmdchar, nomove))
{
#ifdef FEAT_AUTOCMD
if (cmdchar != 'r' && cmdchar != 'v')
apply_autocmds(EVENT_INSERTLEAVE, NULL, NULL,
FALSE, curbuf);
did_cursorhold = FALSE;
#endif
return (c == Ctrl_O);
}
continue;
case Ctrl_Z: /* suspend when 'insertmode' set */
if (!p_im)
goto normalchar; /* insert CTRL-Z as normal char */
stuffReadbuff((char_u *)":st\r");
c = Ctrl_O;
/*FALLTHROUGH*/
case Ctrl_O: /* execute one command */
#ifdef FEAT_COMPL_FUNC
if (ctrl_x_mode == CTRL_X_OMNI)
goto docomplete;
#endif
if (echeck_abbr(Ctrl_O + ABBR_OFF))
break;
ins_ctrl_o();
#ifdef FEAT_VIRTUALEDIT
/* don't move the cursor left when 'virtualedit' has "onemore". */
if (ve_flags & VE_ONEMORE)
{
ins_at_eol = FALSE;
nomove = TRUE;
}
#endif
count = 0;
goto doESCkey;
case K_INS: /* toggle insert/replace mode */
case K_KINS:
ins_insert(replaceState);
break;
case K_SELECT: /* end of Select mode mapping - ignore */
break;
#ifdef FEAT_SNIFF
case K_SNIFF: /* Sniff command received */
stuffcharReadbuff(K_SNIFF);
goto doESCkey;
#endif
case K_HELP: /* Help key works like <ESC> <Help> */
case K_F1:
case K_XF1:
stuffcharReadbuff(K_HELP);
if (p_im)
need_start_insertmode = TRUE;
goto doESCkey;
#ifdef FEAT_NETBEANS_INTG
case K_F21: /* NetBeans command */
++no_mapping; /* don't map the next key hits */
i = plain_vgetc();
--no_mapping;
netbeans_keycommand(i);
break;
#endif
case K_ZERO: /* Insert the previously inserted text. */
case NUL:
case Ctrl_A:
/* For ^@ the trailing ESC will end the insert, unless there is an
* error. */
if (stuff_inserted(NUL, 1L, (c == Ctrl_A)) == FAIL
&& c != Ctrl_A && !p_im)
goto doESCkey; /* quit insert mode */
inserted_space = FALSE;
break;
case Ctrl_R: /* insert the contents of a register */
ins_reg();
auto_format(FALSE, TRUE);
inserted_space = FALSE;
break;
case Ctrl_G: /* commands starting with CTRL-G */
ins_ctrl_g();
break;
case Ctrl_HAT: /* switch input mode and/or langmap */
ins_ctrl_hat();
break;
#ifdef FEAT_RIGHTLEFT
case Ctrl__: /* switch between languages */
if (!p_ari)
goto normalchar;
ins_ctrl_();
break;
#endif
case Ctrl_D: /* Make indent one shiftwidth smaller. */
#if defined(FEAT_INS_EXPAND) && defined(FEAT_FIND_ID)
if (ctrl_x_mode == CTRL_X_PATH_DEFINES)
goto docomplete;
#endif
/* FALLTHROUGH */
case Ctrl_T: /* Make indent one shiftwidth greater. */
# ifdef FEAT_INS_EXPAND
if (c == Ctrl_T && ctrl_x_mode == CTRL_X_THESAURUS)
{
if (has_compl_option(FALSE))
goto docomplete;
break;
}
# endif
ins_shift(c, lastc);
auto_format(FALSE, TRUE);
inserted_space = FALSE;
break;
case K_DEL: /* delete character under the cursor */
case K_KDEL:
ins_del();
auto_format(FALSE, TRUE);
break;
case K_BS: /* delete character before the cursor */
case Ctrl_H:
did_backspace = ins_bs(c, BACKSPACE_CHAR, &inserted_space);
auto_format(FALSE, TRUE);
break;
case Ctrl_W: /* delete word before the cursor */
did_backspace = ins_bs(c, BACKSPACE_WORD, &inserted_space);
auto_format(FALSE, TRUE);
break;
case Ctrl_U: /* delete all inserted text in current line */
# ifdef FEAT_COMPL_FUNC
/* CTRL-X CTRL-U completes with 'completefunc'. */
if (ctrl_x_mode == CTRL_X_FUNCTION)
goto docomplete;
# endif
did_backspace = ins_bs(c, BACKSPACE_LINE, &inserted_space);
auto_format(FALSE, TRUE);
inserted_space = FALSE;
break;
#ifdef FEAT_MOUSE
case K_LEFTMOUSE: /* mouse keys */
case K_LEFTMOUSE_NM:
case K_LEFTDRAG:
case K_LEFTRELEASE:
case K_LEFTRELEASE_NM:
case K_MIDDLEMOUSE:
case K_MIDDLEDRAG:
case K_MIDDLERELEASE:
case K_RIGHTMOUSE:
case K_RIGHTDRAG:
case K_RIGHTRELEASE:
case K_X1MOUSE:
case K_X1DRAG:
case K_X1RELEASE:
case K_X2MOUSE:
case K_X2DRAG:
case K_X2RELEASE:
ins_mouse(c);
break;
case K_MOUSEDOWN: /* Default action for scroll wheel up: scroll up */
ins_mousescroll(MSCR_DOWN);
break;
case K_MOUSEUP: /* Default action for scroll wheel down: scroll down */
ins_mousescroll(MSCR_UP);
break;
case K_MOUSELEFT: /* Scroll wheel left */
ins_mousescroll(MSCR_LEFT);
break;
case K_MOUSERIGHT: /* Scroll wheel right */
ins_mousescroll(MSCR_RIGHT);
break;
# ifdef FEAT_GUI_MACVIM
/* Gestures are ignored */
case K_SWIPELEFT:
case K_SWIPERIGHT:
case K_SWIPEUP:
case K_SWIPEDOWN:
break;
# endif
#endif
#ifdef FEAT_GUI_TABLINE
case K_TABLINE:
case K_TABMENU:
ins_tabline(c);
break;
#endif
case K_IGNORE: /* Something mapped to nothing */
break;
#ifdef FEAT_AUTOCMD
case K_CURSORHOLD: /* Didn't type something for a while. */
apply_autocmds(EVENT_CURSORHOLDI, NULL, NULL, FALSE, curbuf);
did_cursorhold = TRUE;
break;
#endif
#ifdef FEAT_GUI_W32
/* On Win32 ignore <M-F4>, we get it when closing the window was
* cancelled. */
case K_F4:
if (mod_mask != MOD_MASK_ALT)
goto normalchar;
break;
#endif
#ifdef FEAT_GUI
case K_VER_SCROLLBAR:
ins_scroll();
break;
case K_HOR_SCROLLBAR:
ins_horscroll();
break;
#endif
case K_HOME: /* <Home> */
case K_KHOME:
case K_S_HOME:
case K_C_HOME:
ins_home(c);
break;
case K_END: /* <End> */
case K_KEND:
case K_S_END:
case K_C_END:
ins_end(c);
break;
case K_LEFT: /* <Left> */
if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))
ins_s_left();
else
ins_left();
break;
case K_S_LEFT: /* <S-Left> */
case K_C_LEFT:
ins_s_left();
break;
case K_RIGHT: /* <Right> */
if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))
ins_s_right();
else
ins_right();
break;
case K_S_RIGHT: /* <S-Right> */
case K_C_RIGHT:
ins_s_right();
break;
case K_UP: /* <Up> */
#ifdef FEAT_INS_EXPAND
if (pum_visible())
goto docomplete;
#endif
if (mod_mask & MOD_MASK_SHIFT)
ins_pageup();
else
ins_up(FALSE);
break;
case K_S_UP: /* <S-Up> */
case K_PAGEUP:
case K_KPAGEUP:
#ifdef FEAT_INS_EXPAND
if (pum_visible())
goto docomplete;
#endif
ins_pageup();
break;
case K_DOWN: /* <Down> */
#ifdef FEAT_INS_EXPAND
if (pum_visible())
goto docomplete;
#endif
if (mod_mask & MOD_MASK_SHIFT)
ins_pagedown();
else
ins_down(FALSE);
break;
case K_S_DOWN: /* <S-Down> */
case K_PAGEDOWN:
case K_KPAGEDOWN:
#ifdef FEAT_INS_EXPAND
if (pum_visible())
goto docomplete;
#endif
ins_pagedown();
break;
#ifdef FEAT_DND
case K_DROP: /* drag-n-drop event */
ins_drop();
break;
#endif
case K_S_TAB: /* When not mapped, use like a normal TAB */
c = TAB;
/* FALLTHROUGH */
case TAB: /* TAB or Complete patterns along path */
#if defined(FEAT_INS_EXPAND) && defined(FEAT_FIND_ID)
if (ctrl_x_mode == CTRL_X_PATH_PATTERNS)
goto docomplete;
#endif
inserted_space = FALSE;
if (ins_tab())
goto normalchar; /* insert TAB as a normal char */
auto_format(FALSE, TRUE);
break;
case K_KENTER: /* <Enter> */
c = CAR;
/* FALLTHROUGH */
case CAR:
case NL:
#if defined(FEAT_WINDOWS) && defined(FEAT_QUICKFIX)
/* In a quickfix window a <CR> jumps to the error under the
* cursor. */
if (bt_quickfix(curbuf) && c == CAR)
{
if (curwin->w_llist_ref == NULL) /* quickfix window */
do_cmdline_cmd((char_u *)".cc");
else /* location list window */
do_cmdline_cmd((char_u *)".ll");
break;
}
#endif
#ifdef FEAT_CMDWIN
if (cmdwin_type != 0)
{
/* Execute the command in the cmdline window. */
cmdwin_result = CAR;
goto doESCkey;
}
#endif
if (ins_eol(c) && !p_im)
goto doESCkey; /* out of memory */
auto_format(FALSE, FALSE);
inserted_space = FALSE;
break;
#if defined(FEAT_DIGRAPHS) || defined(FEAT_INS_EXPAND)
case Ctrl_K: /* digraph or keyword completion */
# ifdef FEAT_INS_EXPAND
if (ctrl_x_mode == CTRL_X_DICTIONARY)
{
if (has_compl_option(TRUE))
goto docomplete;
break;
}
# endif
# ifdef FEAT_DIGRAPHS
c = ins_digraph();
if (c == NUL)
break;
# endif
goto normalchar;
#endif
#ifdef FEAT_INS_EXPAND
case Ctrl_X: /* Enter CTRL-X mode */
ins_ctrl_x();
break;
case Ctrl_RSB: /* Tag name completion after ^X */
if (ctrl_x_mode != CTRL_X_TAGS)
goto normalchar;
goto docomplete;
case Ctrl_F: /* File name completion after ^X */
if (ctrl_x_mode != CTRL_X_FILES)
goto normalchar;
goto docomplete;
case 's': /* Spelling completion after ^X */
case Ctrl_S:
if (ctrl_x_mode != CTRL_X_SPELL)
goto normalchar;
goto docomplete;
#endif
case Ctrl_L: /* Whole line completion after ^X */
#ifdef FEAT_INS_EXPAND
if (ctrl_x_mode != CTRL_X_WHOLE_LINE)
#endif
{
/* CTRL-L with 'insertmode' set: Leave Insert mode */
if (p_im)
{
if (echeck_abbr(Ctrl_L + ABBR_OFF))
break;
goto doESCkey;
}
goto normalchar;
}
#ifdef FEAT_INS_EXPAND
/* FALLTHROUGH */
case Ctrl_P: /* Do previous/next pattern completion */
case Ctrl_N:
/* if 'complete' is empty then plain ^P is no longer special,
* but it is under other ^X modes */
if (*curbuf->b_p_cpt == NUL
&& ctrl_x_mode != 0
&& !(compl_cont_status & CONT_LOCAL))
goto normalchar;
docomplete:
compl_busy = TRUE;
if (ins_complete(c) == FAIL)
compl_cont_status = 0;
compl_busy = FALSE;
break;
#endif /* FEAT_INS_EXPAND */
case Ctrl_Y: /* copy from previous line or scroll down */
case Ctrl_E: /* copy from next line or scroll up */
c = ins_ctrl_ey(c);
break;
default:
#ifdef UNIX
if (c == intr_char) /* special interrupt char */
goto do_intr;
#endif
normalchar:
/*
* Insert a nomal character.
*/
#ifdef FEAT_AUTOCMD
if (!p_paste)
{
/* Trigger InsertCharPre. */
char_u *str = do_insert_char_pre(c);
char_u *p;
if (str != NULL)
{
if (*str != NUL && stop_arrow() != FAIL)
{
/* Insert the new value of v:char literally. */
for (p = str; *p != NUL; mb_ptr_adv(p))
{
c = PTR2CHAR(p);
if (c == CAR || c == K_KENTER || c == NL)
ins_eol(c);
else
ins_char(c);
}
AppendToRedobuffLit(str, -1);
}
vim_free(str);
c = NUL;
}
/* If the new value is already inserted or an empty string
* then don't insert any character. */
if (c == NUL)
break;
}
#endif
#ifdef FEAT_SMARTINDENT
/* Try to perform smart-indenting. */
ins_try_si(c);
#endif
if (c == ' ')
{
inserted_space = TRUE;
#ifdef FEAT_CINDENT
if (inindent(0))
can_cindent = FALSE;
#endif
if (Insstart_blank_vcol == MAXCOL
&& curwin->w_cursor.lnum == Insstart.lnum)
Insstart_blank_vcol = get_nolist_virtcol();
}
/* Insert a normal character and check for abbreviations on a
* special character. Let CTRL-] expand abbreviations without
* inserting it. */
if (vim_iswordc(c) || (!echeck_abbr(
#ifdef FEAT_MBYTE
/* Add ABBR_OFF for characters above 0x100, this is
* what check_abbr() expects. */
(has_mbyte && c >= 0x100) ? (c + ABBR_OFF) :
#endif
c) && c != Ctrl_RSB))
{
insert_special(c, FALSE, FALSE);
#ifdef FEAT_RIGHTLEFT
revins_legal++;
revins_chars++;
#endif
}
auto_format(FALSE, TRUE);
#ifdef FEAT_FOLDING
/* When inserting a character the cursor line must never be in a
* closed fold. */
foldOpenCursor();
#endif
break;
} /* end of switch (c) */
#ifdef FEAT_AUTOCMD
/* If typed something may trigger CursorHoldI again. */
if (c != K_CURSORHOLD)
did_cursorhold = FALSE;
#endif
/* If the cursor was moved we didn't just insert a space */
if (arrow_used)
inserted_space = FALSE;
#ifdef FEAT_CINDENT
if (can_cindent && cindent_on()
# ifdef FEAT_INS_EXPAND
&& ctrl_x_mode == 0
# endif
)
{
force_cindent:
/*
* Indent now if a key was typed that is in 'cinkeys'.
*/
if (in_cinkeys(c, ' ', line_is_white))
{
if (stop_arrow() == OK)
/* re-indent the current line */
do_c_expr_indent();
}
}
#endif /* FEAT_CINDENT */
} /* for (;;) */
/* NOTREACHED */
}
/*
* Redraw for Insert mode.
* This is postponed until getting the next character to make '$' in the 'cpo'
* option work correctly.
* Only redraw when there are no characters available. This speeds up
* inserting sequences of characters (e.g., for CTRL-R).
*/
static void
ins_redraw(ready)
int ready UNUSED; /* not busy with something */
{
#ifdef FEAT_CONCEAL
linenr_T conceal_old_cursor_line = 0;
linenr_T conceal_new_cursor_line = 0;
int conceal_update_lines = FALSE;
#endif
if (!char_avail())
{
#if defined(FEAT_AUTOCMD) || defined(FEAT_CONCEAL)
/* Trigger CursorMoved if the cursor moved. Not when the popup menu is
* visible, the command might delete it. */
if (ready && (
# ifdef FEAT_AUTOCMD
has_cursormovedI()
# endif
# if defined(FEAT_AUTOCMD) && defined(FEAT_CONCEAL)
||
# endif
# ifdef FEAT_CONCEAL
curwin->w_p_cole > 0
# endif
)
&& !equalpos(last_cursormoved, curwin->w_cursor)
# ifdef FEAT_INS_EXPAND
&& !pum_visible()
# endif
)
{
# ifdef FEAT_SYN_HL
/* Need to update the screen first, to make sure syntax
* highlighting is correct after making a change (e.g., inserting
* a "(". The autocommand may also require a redraw, so it's done
* again below, unfortunately. */
if (syntax_present(curwin) && must_redraw)
update_screen(0);
# endif
# ifdef FEAT_AUTOCMD
if (has_cursormovedI())
apply_autocmds(EVENT_CURSORMOVEDI, NULL, NULL, FALSE, curbuf);
# endif
# ifdef FEAT_CONCEAL
if (curwin->w_p_cole > 0)
{
conceal_old_cursor_line = last_cursormoved.lnum;
conceal_new_cursor_line = curwin->w_cursor.lnum;
conceal_update_lines = TRUE;
}
# endif
last_cursormoved = curwin->w_cursor;
}
#endif
if (must_redraw)
update_screen(0);
else if (clear_cmdline || redraw_cmdline)
showmode(); /* clear cmdline and show mode */
# if defined(FEAT_CONCEAL)
if ((conceal_update_lines
&& (conceal_old_cursor_line != conceal_new_cursor_line
|| conceal_cursor_line(curwin)))
|| need_cursor_line_redraw)
{
if (conceal_old_cursor_line != conceal_new_cursor_line)
update_single_line(curwin, conceal_old_cursor_line);
update_single_line(curwin, conceal_new_cursor_line == 0
? curwin->w_cursor.lnum : conceal_new_cursor_line);
curwin->w_valid &= ~VALID_CROW;
}
# endif
showruler(FALSE);
setcursor();
emsg_on_display = FALSE; /* may remove error message now */
}
}
/*
* Handle a CTRL-V or CTRL-Q typed in Insert mode.
*/
static void
ins_ctrl_v()
{
int c;
int did_putchar = FALSE;
/* may need to redraw when no more chars available now */
ins_redraw(FALSE);
if (redrawing() && !char_avail())
{
edit_putchar('^', TRUE);
did_putchar = TRUE;
}
AppendToRedobuff((char_u *)CTRL_V_STR); /* CTRL-V */
#ifdef FEAT_CMDL_INFO
add_to_showcmd_c(Ctrl_V);
#endif
c = get_literal();
if (did_putchar)
/* when the line fits in 'columns' the '^' is at the start of the next
* line and will not removed by the redraw */
edit_unputchar();
#ifdef FEAT_CMDL_INFO
clear_showcmd();
#endif
insert_special(c, FALSE, TRUE);
#ifdef FEAT_RIGHTLEFT
revins_chars++;
revins_legal++;
#endif
}
/*
* Put a character directly onto the screen. It's not stored in a buffer.
* Used while handling CTRL-K, CTRL-V, etc. in Insert mode.
*/
static int pc_status;
#define PC_STATUS_UNSET 0 /* pc_bytes was not set */
#define PC_STATUS_RIGHT 1 /* right halve of double-wide char */
#define PC_STATUS_LEFT 2 /* left halve of double-wide char */
#define PC_STATUS_SET 3 /* pc_bytes was filled */
static char_u pc_bytes[MB_MAXBYTES + 1]; /* saved bytes */
static int pc_attr;
static int pc_row;
static int pc_col;
void
edit_putchar(c, highlight)
int c;
int highlight;
{
int attr;
if (ScreenLines != NULL)
{
update_topline(); /* just in case w_topline isn't valid */
validate_cursor();
if (highlight)
attr = hl_attr(HLF_8);
else
attr = 0;
pc_row = W_WINROW(curwin) + curwin->w_wrow;
pc_col = W_WINCOL(curwin);
#if defined(FEAT_RIGHTLEFT) || defined(FEAT_MBYTE)
pc_status = PC_STATUS_UNSET;
#endif
#ifdef FEAT_RIGHTLEFT
if (curwin->w_p_rl)
{
pc_col += W_WIDTH(curwin) - 1 - curwin->w_wcol;
# ifdef FEAT_MBYTE
if (has_mbyte)
{
int fix_col = mb_fix_col(pc_col, pc_row);
if (fix_col != pc_col)
{
screen_putchar(' ', pc_row, fix_col, attr);
--curwin->w_wcol;
pc_status = PC_STATUS_RIGHT;
}
}
# endif
}
else
#endif
{
pc_col += curwin->w_wcol;
#ifdef FEAT_MBYTE
if (mb_lefthalve(pc_row, pc_col))
pc_status = PC_STATUS_LEFT;
#endif
}
/* save the character to be able to put it back */
#if defined(FEAT_RIGHTLEFT) || defined(FEAT_MBYTE)
if (pc_status == PC_STATUS_UNSET)
#endif
{
screen_getbytes(pc_row, pc_col, pc_bytes, &pc_attr);
pc_status = PC_STATUS_SET;
}
screen_putchar(c, pc_row, pc_col, attr);
}
}
/*
* Undo the previous edit_putchar().
*/
void
edit_unputchar()
{
if (pc_status != PC_STATUS_UNSET && pc_row >= msg_scrolled)
{
#if defined(FEAT_MBYTE)
if (pc_status == PC_STATUS_RIGHT)
++curwin->w_wcol;
if (pc_status == PC_STATUS_RIGHT || pc_status == PC_STATUS_LEFT)
redrawWinline(curwin->w_cursor.lnum, FALSE);
else
#endif
screen_puts(pc_bytes, pc_row - msg_scrolled, pc_col, pc_attr);
}
}
/*
* Called when p_dollar is set: display a '$' at the end of the changed text
* Only works when cursor is in the line that changes.
*/
void
display_dollar(col)
colnr_T col;
{
colnr_T save_col;
if (!redrawing())
return;
cursor_off();
save_col = curwin->w_cursor.col;
curwin->w_cursor.col = col;
#ifdef FEAT_MBYTE
if (has_mbyte)
{
char_u *p;
/* If on the last byte of a multi-byte move to the first byte. */
p = ml_get_curline();
curwin->w_cursor.col -= (*mb_head_off)(p, p + col);
}
#endif
curs_columns(FALSE); /* recompute w_wrow and w_wcol */
if (curwin->w_wcol < W_WIDTH(curwin))
{
edit_putchar('$', FALSE);
dollar_vcol = curwin->w_virtcol;
}
curwin->w_cursor.col = save_col;
}
/*
* Call this function before moving the cursor from the normal insert position
* in insert mode.
*/
static void
undisplay_dollar()
{
if (dollar_vcol >= 0)
{
dollar_vcol = -1;
redrawWinline(curwin->w_cursor.lnum, FALSE);
}
}
/*
* Insert an indent (for <Tab> or CTRL-T) or delete an indent (for CTRL-D).
* Keep the cursor on the same character.
* type == INDENT_INC increase indent (for CTRL-T or <Tab>)
* type == INDENT_DEC decrease indent (for CTRL-D)
* type == INDENT_SET set indent to "amount"
* if round is TRUE, round the indent to 'shiftwidth' (only with _INC and _Dec).
*/
void
change_indent(type, amount, round, replaced, call_changed_bytes)
int type;
int amount;
int round;
int replaced; /* replaced character, put on replace stack */
int call_changed_bytes; /* call changed_bytes() */
{
int vcol;
int last_vcol;
int insstart_less; /* reduction for Insstart.col */
int new_cursor_col;
int i;
char_u *ptr;
int save_p_list;
int start_col;
colnr_T vc;
#ifdef FEAT_VREPLACE
colnr_T orig_col = 0; /* init for GCC */
char_u *new_line, *orig_line = NULL; /* init for GCC */
/* VREPLACE mode needs to know what the line was like before changing */
if (State & VREPLACE_FLAG)
{
orig_line = vim_strsave(ml_get_curline()); /* Deal with NULL below */
orig_col = curwin->w_cursor.col;
}
#endif
/* for the following tricks we don't want list mode */
save_p_list = curwin->w_p_list;
curwin->w_p_list = FALSE;
vc = getvcol_nolist(&curwin->w_cursor);
vcol = vc;
/*
* For Replace mode we need to fix the replace stack later, which is only
* possible when the cursor is in the indent. Remember the number of
* characters before the cursor if it's possible.
*/
start_col = curwin->w_cursor.col;
/* determine offset from first non-blank */
new_cursor_col = curwin->w_cursor.col;
beginline(BL_WHITE);
new_cursor_col -= curwin->w_cursor.col;
insstart_less = curwin->w_cursor.col;
/*
* If the cursor is in the indent, compute how many screen columns the
* cursor is to the left of the first non-blank.
*/
if (new_cursor_col < 0)
vcol = get_indent() - vcol;
if (new_cursor_col > 0) /* can't fix replace stack */
start_col = -1;
/*
* Set the new indent. The cursor will be put on the first non-blank.
*/
if (type == INDENT_SET)
(void)set_indent(amount, call_changed_bytes ? SIN_CHANGED : 0);
else
{
#ifdef FEAT_VREPLACE
int save_State = State;
/* Avoid being called recursively. */
if (State & VREPLACE_FLAG)
State = INSERT;
#endif
shift_line(type == INDENT_DEC, round, 1, call_changed_bytes);
#ifdef FEAT_VREPLACE
State = save_State;
#endif
}
insstart_less -= curwin->w_cursor.col;
/*
* Try to put cursor on same character.
* If the cursor is at or after the first non-blank in the line,
* compute the cursor column relative to the column of the first
* non-blank character.
* If we are not in insert mode, leave the cursor on the first non-blank.
* If the cursor is before the first non-blank, position it relative
* to the first non-blank, counted in screen columns.
*/
if (new_cursor_col >= 0)
{
/*
* When changing the indent while the cursor is touching it, reset
* Insstart_col to 0.
*/
if (new_cursor_col == 0)
insstart_less = MAXCOL;
new_cursor_col += curwin->w_cursor.col;
}
else if (!(State & INSERT))
new_cursor_col = curwin->w_cursor.col;
else
{
/*
* Compute the screen column where the cursor should be.
*/
vcol = get_indent() - vcol;
curwin->w_virtcol = (colnr_T)((vcol < 0) ? 0 : vcol);
/*
* Advance the cursor until we reach the right screen column.
*/
vcol = last_vcol = 0;
new_cursor_col = -1;
ptr = ml_get_curline();
while (vcol <= (int)curwin->w_virtcol)
{
last_vcol = vcol;
#ifdef FEAT_MBYTE
if (has_mbyte && new_cursor_col >= 0)
new_cursor_col += (*mb_ptr2len)(ptr + new_cursor_col);
else
#endif
++new_cursor_col;
vcol += lbr_chartabsize(ptr + new_cursor_col, (colnr_T)vcol);
}
vcol = last_vcol;
/*
* May need to insert spaces to be able to position the cursor on
* the right screen column.
*/
if (vcol != (int)curwin->w_virtcol)
{
curwin->w_cursor.col = (colnr_T)new_cursor_col;
i = (int)curwin->w_virtcol - vcol;
ptr = alloc((unsigned)(i + 1));
if (ptr != NULL)
{
new_cursor_col += i;
ptr[i] = NUL;
while (--i >= 0)
ptr[i] = ' ';
ins_str(ptr);
vim_free(ptr);
}
}
/*
* When changing the indent while the cursor is in it, reset
* Insstart_col to 0.
*/
insstart_less = MAXCOL;
}
curwin->w_p_list = save_p_list;
if (new_cursor_col <= 0)
curwin->w_cursor.col = 0;
else
curwin->w_cursor.col = (colnr_T)new_cursor_col;
curwin->w_set_curswant = TRUE;
changed_cline_bef_curs();
/*
* May have to adjust the start of the insert.
*/
if (State & INSERT)
{
if (curwin->w_cursor.lnum == Insstart.lnum && Insstart.col != 0)
{
if ((int)Insstart.col <= insstart_less)
Insstart.col = 0;
else
Insstart.col -= insstart_less;
}
if ((int)ai_col <= insstart_less)
ai_col = 0;
else
ai_col -= insstart_less;
}
/*
* For REPLACE mode, may have to fix the replace stack, if it's possible.
* If the number of characters before the cursor decreased, need to pop a
* few characters from the replace stack.
* If the number of characters before the cursor increased, need to push a
* few NULs onto the replace stack.
*/
if (REPLACE_NORMAL(State) && start_col >= 0)
{
while (start_col > (int)curwin->w_cursor.col)
{
replace_join(0); /* remove a NUL from the replace stack */
--start_col;
}
while (start_col < (int)curwin->w_cursor.col || replaced)
{
replace_push(NUL);
if (replaced)
{
replace_push(replaced);
replaced = NUL;
}
++start_col;
}
}
#ifdef FEAT_VREPLACE
/*
* For VREPLACE mode, we also have to fix the replace stack. In this case
* it is always possible because we backspace over the whole line and then
* put it back again the way we wanted it.
*/
if (State & VREPLACE_FLAG)
{
/* If orig_line didn't allocate, just return. At least we did the job,
* even if you can't backspace. */
if (orig_line == NULL)
return;
/* Save new line */
new_line = vim_strsave(ml_get_curline());
if (new_line == NULL)
return;
/* We only put back the new line up to the cursor */
new_line[curwin->w_cursor.col] = NUL;
/* Put back original line */
ml_replace(curwin->w_cursor.lnum, orig_line, FALSE);
curwin->w_cursor.col = orig_col;
/* Backspace from cursor to start of line */
backspace_until_column(0);
/* Insert new stuff into line again */
ins_bytes(new_line);
vim_free(new_line);
}
#endif
}
/*
* Truncate the space at the end of a line. This is to be used only in an
* insert mode. It handles fixing the replace stack for REPLACE and VREPLACE
* modes.
*/
void
truncate_spaces(line)
char_u *line;
{
int i;
/* find start of trailing white space */
for (i = (int)STRLEN(line) - 1; i >= 0 && vim_iswhite(line[i]); i--)
{
if (State & REPLACE_FLAG)
replace_join(0); /* remove a NUL from the replace stack */
}
line[i + 1] = NUL;
}
#if defined(FEAT_VREPLACE) || defined(FEAT_INS_EXPAND) \
|| defined(FEAT_COMMENTS) || defined(PROTO)
/*
* Backspace the cursor until the given column. Handles REPLACE and VREPLACE
* modes correctly. May also be used when not in insert mode at all.
* Will attempt not to go before "col" even when there is a composing
* character.
*/
void
backspace_until_column(col)
int col;
{
while ((int)curwin->w_cursor.col > col)
{
curwin->w_cursor.col--;
if (State & REPLACE_FLAG)
replace_do_bs(col);
else if (!del_char_after_col(col))
break;
}
}
#endif
/*
* Like del_char(), but make sure not to go before column "limit_col".
* Only matters when there are composing characters.
* Return TRUE when something was deleted.
*/
static int
del_char_after_col(limit_col)
int limit_col UNUSED;
{
#ifdef FEAT_MBYTE
if (enc_utf8 && limit_col >= 0)
{
colnr_T ecol = curwin->w_cursor.col + 1;
/* Make sure the cursor is at the start of a character, but
* skip forward again when going too far back because of a
* composing character. */
mb_adjust_cursor();
while (curwin->w_cursor.col < (colnr_T)limit_col)
{
int l = utf_ptr2len(ml_get_cursor());
if (l == 0) /* end of line */
break;
curwin->w_cursor.col += l;
}
if (*ml_get_cursor() == NUL || curwin->w_cursor.col == ecol)
return FALSE;
del_bytes((long)((int)ecol - curwin->w_cursor.col), FALSE, TRUE);
}
else
#endif
(void)del_char(FALSE);
return TRUE;
}
#if defined(FEAT_INS_EXPAND) || defined(PROTO)
/*
* CTRL-X pressed in Insert mode.
*/
static void
ins_ctrl_x()
{
/* CTRL-X after CTRL-X CTRL-V doesn't do anything, so that CTRL-X
* CTRL-V works like CTRL-N */
if (ctrl_x_mode != CTRL_X_CMDLINE)
{
/* if the next ^X<> won't ADD nothing, then reset
* compl_cont_status */
if (compl_cont_status & CONT_N_ADDS)
compl_cont_status |= CONT_INTRPT;
else
compl_cont_status = 0;
/* We're not sure which CTRL-X mode it will be yet */
ctrl_x_mode = CTRL_X_NOT_DEFINED_YET;
edit_submode = (char_u *)_(CTRL_X_MSG(ctrl_x_mode));
edit_submode_pre = NULL;
showmode();
}
}
/*
* Return TRUE if the 'dict' or 'tsr' option can be used.
*/
static int
has_compl_option(dict_opt)
int dict_opt;
{
if (dict_opt ? (*curbuf->b_p_dict == NUL && *p_dict == NUL
# ifdef FEAT_SPELL
&& !curwin->w_p_spell
# endif
)
: (*curbuf->b_p_tsr == NUL && *p_tsr == NUL))
{
ctrl_x_mode = 0;
edit_submode = NULL;
msg_attr(dict_opt ? (char_u *)_("'dictionary' option is empty")
: (char_u *)_("'thesaurus' option is empty"),
hl_attr(HLF_E));
if (emsg_silent == 0)
{
vim_beep();
setcursor();
out_flush();
ui_delay(2000L, FALSE);
}
return FALSE;
}
return TRUE;
}
/*
* Is the character 'c' a valid key to go to or keep us in CTRL-X mode?
* This depends on the current mode.
*/
int
vim_is_ctrl_x_key(c)
int c;
{
/* Always allow ^R - let it's results then be checked */
if (c == Ctrl_R)
return TRUE;
/* Accept <PageUp> and <PageDown> if the popup menu is visible. */
if (ins_compl_pum_key(c))
return TRUE;
switch (ctrl_x_mode)
{
case 0: /* Not in any CTRL-X mode */
return (c == Ctrl_N || c == Ctrl_P || c == Ctrl_X);
case CTRL_X_NOT_DEFINED_YET:
return ( c == Ctrl_X || c == Ctrl_Y || c == Ctrl_E
|| c == Ctrl_L || c == Ctrl_F || c == Ctrl_RSB
|| c == Ctrl_I || c == Ctrl_D || c == Ctrl_P
|| c == Ctrl_N || c == Ctrl_T || c == Ctrl_V
|| c == Ctrl_Q || c == Ctrl_U || c == Ctrl_O
|| c == Ctrl_S || c == Ctrl_K || c == 's');
case CTRL_X_SCROLL:
return (c == Ctrl_Y || c == Ctrl_E);
case CTRL_X_WHOLE_LINE:
return (c == Ctrl_L || c == Ctrl_P || c == Ctrl_N);
case CTRL_X_FILES:
return (c == Ctrl_F || c == Ctrl_P || c == Ctrl_N);
case CTRL_X_DICTIONARY:
return (c == Ctrl_K || c == Ctrl_P || c == Ctrl_N);
case CTRL_X_THESAURUS:
return (c == Ctrl_T || c == Ctrl_P || c == Ctrl_N);
case CTRL_X_TAGS:
return (c == Ctrl_RSB || c == Ctrl_P || c == Ctrl_N);
#ifdef FEAT_FIND_ID
case CTRL_X_PATH_PATTERNS:
return (c == Ctrl_P || c == Ctrl_N);
case CTRL_X_PATH_DEFINES:
return (c == Ctrl_D || c == Ctrl_P || c == Ctrl_N);
#endif
case CTRL_X_CMDLINE:
return (c == Ctrl_V || c == Ctrl_Q || c == Ctrl_P || c == Ctrl_N
|| c == Ctrl_X);
#ifdef FEAT_COMPL_FUNC
case CTRL_X_FUNCTION:
return (c == Ctrl_U || c == Ctrl_P || c == Ctrl_N);
case CTRL_X_OMNI:
return (c == Ctrl_O || c == Ctrl_P || c == Ctrl_N);
#endif
case CTRL_X_SPELL:
return (c == Ctrl_S || c == Ctrl_P || c == Ctrl_N);
}
EMSG(_(e_internal));
return FALSE;
}
/*
* Return TRUE when character "c" is part of the item currently being
* completed. Used to decide whether to abandon complete mode when the menu
* is visible.
*/
static int
ins_compl_accept_char(c)
int c;
{
if (ctrl_x_mode & CTRL_X_WANT_IDENT)
/* When expanding an identifier only accept identifier chars. */
return vim_isIDc(c);
switch (ctrl_x_mode)
{
case CTRL_X_FILES:
/* When expanding file name only accept file name chars. But not
* path separators, so that "proto/<Tab>" expands files in
* "proto", not "proto/" as a whole */
return vim_isfilec(c) && !vim_ispathsep(c);
case CTRL_X_CMDLINE:
case CTRL_X_OMNI:
/* Command line and Omni completion can work with just about any
* printable character, but do stop at white space. */
return vim_isprintc(c) && !vim_iswhite(c);
case CTRL_X_WHOLE_LINE:
/* For while line completion a space can be part of the line. */
return vim_isprintc(c);
}
return vim_iswordc(c);
}
/*
* This is like ins_compl_add(), but if 'ic' and 'inf' are set, then the
* case of the originally typed text is used, and the case of the completed
* text is inferred, ie this tries to work out what case you probably wanted
* the rest of the word to be in -- webb
*/
int
ins_compl_add_infercase(str, len, icase, fname, dir, flags)
char_u *str;
int len;
int icase;
char_u *fname;
int dir;
int flags;
{
char_u *p;
int i, c;
int actual_len; /* Take multi-byte characters */
int actual_compl_length; /* into account. */
int min_len;
int *wca; /* Wide character array. */
int has_lower = FALSE;
int was_letter = FALSE;
if (p_ic && curbuf->b_p_inf && len > 0)
{
/* Infer case of completed part. */
/* Find actual length of completion. */
#ifdef FEAT_MBYTE
if (has_mbyte)
{
p = str;
actual_len = 0;
while (*p != NUL)
{
mb_ptr_adv(p);
++actual_len;
}
}
else
#endif
actual_len = len;
/* Find actual length of original text. */
#ifdef FEAT_MBYTE
if (has_mbyte)
{
p = compl_orig_text;
actual_compl_length = 0;
while (*p != NUL)
{
mb_ptr_adv(p);
++actual_compl_length;
}
}
else
#endif
actual_compl_length = compl_length;
/* "actual_len" may be smaller than "actual_compl_length" when using
* thesaurus, only use the minimum when comparing. */
min_len = actual_len < actual_compl_length
? actual_len : actual_compl_length;
/* Allocate wide character array for the completion and fill it. */
wca = (int *)alloc((unsigned)(actual_len * sizeof(int)));
if (wca != NULL)
{
p = str;
for (i = 0; i < actual_len; ++i)
#ifdef FEAT_MBYTE
if (has_mbyte)
wca[i] = mb_ptr2char_adv(&p);
else
#endif
wca[i] = *(p++);
/* Rule 1: Were any chars converted to lower? */
p = compl_orig_text;
for (i = 0; i < min_len; ++i)
{
#ifdef FEAT_MBYTE
if (has_mbyte)
c = mb_ptr2char_adv(&p);
else
#endif
c = *(p++);
if (MB_ISLOWER(c))
{
has_lower = TRUE;
if (MB_ISUPPER(wca[i]))
{
/* Rule 1 is satisfied. */
for (i = actual_compl_length; i < actual_len; ++i)
wca[i] = MB_TOLOWER(wca[i]);
break;
}
}
}
/*
* Rule 2: No lower case, 2nd consecutive letter converted to
* upper case.
*/
if (!has_lower)
{
p = compl_orig_text;
for (i = 0; i < min_len; ++i)
{
#ifdef FEAT_MBYTE
if (has_mbyte)
c = mb_ptr2char_adv(&p);
else
#endif
c = *(p++);
if (was_letter && MB_ISUPPER(c) && MB_ISLOWER(wca[i]))
{
/* Rule 2 is satisfied. */
for (i = actual_compl_length; i < actual_len; ++i)
wca[i] = MB_TOUPPER(wca[i]);
break;
}
was_letter = MB_ISLOWER(c) || MB_ISUPPER(c);
}
}
/* Copy the original case of the part we typed. */
p = compl_orig_text;
for (i = 0; i < min_len; ++i)
{
#ifdef FEAT_MBYTE
if (has_mbyte)
c = mb_ptr2char_adv(&p);
else
#endif
c = *(p++);
if (MB_ISLOWER(c))
wca[i] = MB_TOLOWER(wca[i]);
else if (MB_ISUPPER(c))
wca[i] = MB_TOUPPER(wca[i]);
}
/*
* Generate encoding specific output from wide character array.
* Multi-byte characters can occupy up to five bytes more than
* ASCII characters, and we also need one byte for NUL, so stay
* six bytes away from the edge of IObuff.
*/
p = IObuff;
i = 0;
while (i < actual_len && (p - IObuff + 6) < IOSIZE)
#ifdef FEAT_MBYTE
if (has_mbyte)
p += (*mb_char2bytes)(wca[i++], p);
else
#endif
*(p++) = wca[i++];
*p = NUL;
vim_free(wca);
}
return ins_compl_add(IObuff, len, icase, fname, NULL, dir,
flags, FALSE);
}
return ins_compl_add(str, len, icase, fname, NULL, dir, flags, FALSE);
}
/*
* Add a match to the list of matches.
* If the given string is already in the list of completions, then return
* NOTDONE, otherwise add it to the list and return OK. If there is an error,
* maybe because alloc() returns NULL, then FAIL is returned.
*/
static int
ins_compl_add(str, len, icase, fname, cptext, cdir, flags, adup)
char_u *str;
int len;
int icase;
char_u *fname;
char_u **cptext; /* extra text for popup menu or NULL */
int cdir;
int flags;
int adup; /* accept duplicate match */
{
compl_T *match;
int dir = (cdir == 0 ? compl_direction : cdir);
ui_breakcheck();
if (got_int)
return FAIL;
if (len < 0)
len = (int)STRLEN(str);
/*
* If the same match is already present, don't add it.
*/
if (compl_first_match != NULL && !adup)
{
match = compl_first_match;
do
{
if ( !(match->cp_flags & ORIGINAL_TEXT)
&& STRNCMP(match->cp_str, str, len) == 0
&& match->cp_str[len] == NUL)
return NOTDONE;
match = match->cp_next;
} while (match != NULL && match != compl_first_match);
}
/* Remove any popup menu before changing the list of matches. */
ins_compl_del_pum();
/*
* Allocate a new match structure.
* Copy the values to the new match structure.
*/
match = (compl_T *)alloc_clear((unsigned)sizeof(compl_T));
if (match == NULL)
return FAIL;
match->cp_number = -1;
if (flags & ORIGINAL_TEXT)
match->cp_number = 0;
if ((match->cp_str = vim_strnsave(str, len)) == NULL)
{
vim_free(match);
return FAIL;
}
match->cp_icase = icase;
/* match-fname is:
* - compl_curr_match->cp_fname if it is a string equal to fname.
* - a copy of fname, FREE_FNAME is set to free later THE allocated mem.
* - NULL otherwise. --Acevedo */
if (fname != NULL
&& compl_curr_match != NULL
&& compl_curr_match->cp_fname != NULL
&& STRCMP(fname, compl_curr_match->cp_fname) == 0)
match->cp_fname = compl_curr_match->cp_fname;
else if (fname != NULL)
{
match->cp_fname = vim_strsave(fname);
flags |= FREE_FNAME;
}
else
match->cp_fname = NULL;
match->cp_flags = flags;
if (cptext != NULL)
{
int i;
for (i = 0; i < CPT_COUNT; ++i)
if (cptext[i] != NULL && *cptext[i] != NUL)
match->cp_text[i] = vim_strsave(cptext[i]);
}
/*
* Link the new match structure in the list of matches.
*/
if (compl_first_match == NULL)
match->cp_next = match->cp_prev = NULL;
else if (dir == FORWARD)
{
match->cp_next = compl_curr_match->cp_next;
match->cp_prev = compl_curr_match;
}
else /* BACKWARD */
{
match->cp_next = compl_curr_match;
match->cp_prev = compl_curr_match->cp_prev;
}
if (match->cp_next)
match->cp_next->cp_prev = match;
if (match->cp_prev)
match->cp_prev->cp_next = match;
else /* if there's nothing before, it is the first match */
compl_first_match = match;
compl_curr_match = match;
/*
* Find the longest common string if still doing that.
*/
if (compl_get_longest && (flags & ORIGINAL_TEXT) == 0)
ins_compl_longest_match(match);
return OK;
}
/*
* Return TRUE if "str[len]" matches with match->cp_str, considering
* match->cp_icase.
*/
static int
ins_compl_equal(match, str, len)
compl_T *match;
char_u *str;
int len;
{
if (match->cp_icase)
return STRNICMP(match->cp_str, str, (size_t)len) == 0;
return STRNCMP(match->cp_str, str, (size_t)len) == 0;
}
/*
* Reduce the longest common string for match "match".
*/
static void
ins_compl_longest_match(match)
compl_T *match;
{
char_u *p, *s;
int c1, c2;
int had_match;
if (compl_leader == NULL)
{
/* First match, use it as a whole. */
compl_leader = vim_strsave(match->cp_str);
if (compl_leader != NULL)
{
had_match = (curwin->w_cursor.col > compl_col);
ins_compl_delete();
ins_bytes(compl_leader + ins_compl_len());
ins_redraw(FALSE);
/* When the match isn't there (to avoid matching itself) remove it
* again after redrawing. */
if (!had_match)
ins_compl_delete();
compl_used_match = FALSE;
}
}
else
{
/* Reduce the text if this match differs from compl_leader. */
p = compl_leader;
s = match->cp_str;
while (*p != NUL)
{
#ifdef FEAT_MBYTE
if (has_mbyte)
{
c1 = mb_ptr2char(p);
c2 = mb_ptr2char(s);
}
else
#endif
{
c1 = *p;
c2 = *s;
}
if (match->cp_icase ? (MB_TOLOWER(c1) != MB_TOLOWER(c2))
: (c1 != c2))
break;
#ifdef FEAT_MBYTE
if (has_mbyte)
{
mb_ptr_adv(p);
mb_ptr_adv(s);
}
else
#endif
{
++p;
++s;
}
}
if (*p != NUL)
{
/* Leader was shortened, need to change the inserted text. */
*p = NUL;
had_match = (curwin->w_cursor.col > compl_col);
ins_compl_delete();
ins_bytes(compl_leader + ins_compl_len());
ins_redraw(FALSE);
/* When the match isn't there (to avoid matching itself) remove it
* again after redrawing. */
if (!had_match)
ins_compl_delete();
}
compl_used_match = FALSE;
}
}
/*
* Add an array of matches to the list of matches.
* Frees matches[].
*/
static void
ins_compl_add_matches(num_matches, matches, icase)
int num_matches;
char_u **matches;
int icase;
{
int i;
int add_r = OK;
int dir = compl_direction;
for (i = 0; i < num_matches && add_r != FAIL; i++)
if ((add_r = ins_compl_add(matches[i], -1, icase,
NULL, NULL, dir, 0, FALSE)) == OK)
/* if dir was BACKWARD then honor it just once */
dir = FORWARD;
FreeWild(num_matches, matches);
}
/* Make the completion list cyclic.
* Return the number of matches (excluding the original).
*/
static int
ins_compl_make_cyclic()
{
compl_T *match;
int count = 0;
if (compl_first_match != NULL)
{
/*
* Find the end of the list.
*/
match = compl_first_match;
/* there's always an entry for the compl_orig_text, it doesn't count. */
while (match->cp_next != NULL && match->cp_next != compl_first_match)
{
match = match->cp_next;
++count;
}
match->cp_next = compl_first_match;
compl_first_match->cp_prev = match;
}
return count;
}
/*
* Start completion for the complete() function.
* "startcol" is where the matched text starts (1 is first column).
* "list" is the list of matches.
*/
void
set_completion(startcol, list)
colnr_T startcol;
list_T *list;
{
/* If already doing completions stop it. */
if (ctrl_x_mode != 0)
ins_compl_prep(' ');
ins_compl_clear();
if (stop_arrow() == FAIL)
return;
compl_direction = FORWARD;
if (startcol > curwin->w_cursor.col)
startcol = curwin->w_cursor.col;
compl_col = startcol;
compl_length = (int)curwin->w_cursor.col - (int)startcol;
/* compl_pattern doesn't need to be set */
compl_orig_text = vim_strnsave(ml_get_curline() + compl_col, compl_length);
if (compl_orig_text == NULL || ins_compl_add(compl_orig_text,
-1, p_ic, NULL, NULL, 0, ORIGINAL_TEXT, FALSE) != OK)
return;
/* Handle like dictionary completion. */
ctrl_x_mode = CTRL_X_WHOLE_LINE;
ins_compl_add_list(list);
compl_matches = ins_compl_make_cyclic();
compl_started = TRUE;
compl_used_match = TRUE;
compl_cont_status = 0;
compl_curr_match = compl_first_match;
ins_complete(Ctrl_N);
out_flush();
}
/* "compl_match_array" points the currently displayed list of entries in the
* popup menu. It is NULL when there is no popup menu. */
static pumitem_T *compl_match_array = NULL;
static int compl_match_arraysize;
/*
* Update the screen and when there is any scrolling remove the popup menu.
*/
static void
ins_compl_upd_pum()
{
int h;
if (compl_match_array != NULL)
{
h = curwin->w_cline_height;
update_screen(0);
if (h != curwin->w_cline_height)
ins_compl_del_pum();
}
}
/*
* Remove any popup menu.
*/
static void
ins_compl_del_pum()
{
if (compl_match_array != NULL)
{
pum_undisplay();
vim_free(compl_match_array);
compl_match_array = NULL;
}
}
/*
* Return TRUE if the popup menu should be displayed.
*/
static int
pum_wanted()
{
/* 'completeopt' must contain "menu" or "menuone" */
if (vim_strchr(p_cot, 'm') == NULL)
return FALSE;
/* The display looks bad on a B&W display. */
if (t_colors < 8
#ifdef FEAT_GUI
&& !gui.in_use
#endif
)
return FALSE;
return TRUE;
}
/*
* Return TRUE if there are two or more matches to be shown in the popup menu.
* One if 'completopt' contains "menuone".
*/
static int
pum_enough_matches()
{
compl_T *compl;
int i;
/* Don't display the popup menu if there are no matches or there is only
* one (ignoring the original text). */
compl = compl_first_match;
i = 0;
do
{
if (compl == NULL
|| ((compl->cp_flags & ORIGINAL_TEXT) == 0 && ++i == 2))
break;
compl = compl->cp_next;
} while (compl != compl_first_match);
if (strstr((char *)p_cot, "menuone") != NULL)
return (i >= 1);
return (i >= 2);
}
/*
* Show the popup menu for the list of matches.
* Also adjusts "compl_shown_match" to an entry that is actually displayed.
*/
void
ins_compl_show_pum()
{
compl_T *compl;
compl_T *shown_compl = NULL;
int did_find_shown_match = FALSE;
int shown_match_ok = FALSE;
int i;
int cur = -1;
colnr_T col;
int lead_len = 0;
if (!pum_wanted() || !pum_enough_matches())
return;
#if defined(FEAT_EVAL)
/* Dirty hard-coded hack: remove any matchparen highlighting. */
do_cmdline_cmd((char_u *)"if exists('g:loaded_matchparen')|3match none|endif");
#endif
/* Update the screen before drawing the popup menu over it. */
update_screen(0);
if (compl_match_array == NULL)
{
/* Need to build the popup menu list. */
compl_match_arraysize = 0;
compl = compl_first_match;
if (compl_leader != NULL)
lead_len = (int)STRLEN(compl_leader);
do
{
if ((compl->cp_flags & ORIGINAL_TEXT) == 0
&& (compl_leader == NULL
|| ins_compl_equal(compl, compl_leader, lead_len)))
++compl_match_arraysize;
compl = compl->cp_next;
} while (compl != NULL && compl != compl_first_match);
if (compl_match_arraysize == 0)
return;
compl_match_array = (pumitem_T *)alloc_clear(
(unsigned)(sizeof(pumitem_T)
* compl_match_arraysize));
if (compl_match_array != NULL)
{
/* If the current match is the original text don't find the first
* match after it, don't highlight anything. */
if (compl_shown_match->cp_flags & ORIGINAL_TEXT)
shown_match_ok = TRUE;
i = 0;
compl = compl_first_match;
do
{
if ((compl->cp_flags & ORIGINAL_TEXT) == 0
&& (compl_leader == NULL
|| ins_compl_equal(compl, compl_leader, lead_len)))
{
if (!shown_match_ok)
{
if (compl == compl_shown_match || did_find_shown_match)
{
/* This item is the shown match or this is the
* first displayed item after the shown match. */
compl_shown_match = compl;
did_find_shown_match = TRUE;
shown_match_ok = TRUE;
}
else
/* Remember this displayed match for when the
* shown match is just below it. */
shown_compl = compl;
cur = i;
}
if (compl->cp_text[CPT_ABBR] != NULL)
compl_match_array[i].pum_text =
compl->cp_text[CPT_ABBR];
else
compl_match_array[i].pum_text = compl->cp_str;
compl_match_array[i].pum_kind = compl->cp_text[CPT_KIND];
compl_match_array[i].pum_info = compl->cp_text[CPT_INFO];
if (compl->cp_text[CPT_MENU] != NULL)
compl_match_array[i++].pum_extra =
compl->cp_text[CPT_MENU];
else
compl_match_array[i++].pum_extra = compl->cp_fname;
}
if (compl == compl_shown_match)
{
did_find_shown_match = TRUE;
/* When the original text is the shown match don't set
* compl_shown_match. */
if (compl->cp_flags & ORIGINAL_TEXT)
shown_match_ok = TRUE;
if (!shown_match_ok && shown_compl != NULL)
{
/* The shown match isn't displayed, set it to the
* previously displayed match. */
compl_shown_match = shown_compl;
shown_match_ok = TRUE;
}
}
compl = compl->cp_next;
} while (compl != NULL && compl != compl_first_match);
if (!shown_match_ok) /* no displayed match at all */
cur = -1;
}
}
else
{
/* popup menu already exists, only need to find the current item.*/
for (i = 0; i < compl_match_arraysize; ++i)
if (compl_match_array[i].pum_text == compl_shown_match->cp_str
|| compl_match_array[i].pum_text
== compl_shown_match->cp_text[CPT_ABBR])
{
cur = i;
break;
}
}
if (compl_match_array != NULL)
{
/* Compute the screen column of the start of the completed text.
* Use the cursor to get all wrapping and other settings right. */
col = curwin->w_cursor.col;
curwin->w_cursor.col = compl_col;
pum_display(compl_match_array, compl_match_arraysize, cur);
curwin->w_cursor.col = col;
}
}
#define DICT_FIRST (1) /* use just first element in "dict" */
#define DICT_EXACT (2) /* "dict" is the exact name of a file */
/*
* Add any identifiers that match the given pattern in the list of dictionary
* files "dict_start" to the list of completions.
*/
static void
ins_compl_dictionaries(dict_start, pat, flags, thesaurus)
char_u *dict_start;
char_u *pat;
int flags; /* DICT_FIRST and/or DICT_EXACT */
int thesaurus; /* Thesaurus completion */
{
char_u *dict = dict_start;
char_u *ptr;
char_u *buf;
regmatch_T regmatch;
char_u **files;
int count;
int save_p_scs;
int dir = compl_direction;
if (*dict == NUL)
{
#ifdef FEAT_SPELL
/* When 'dictionary' is empty and spell checking is enabled use
* "spell". */
if (!thesaurus && curwin->w_p_spell)
dict = (char_u *)"spell";
else
#endif
return;
}
buf = alloc(LSIZE);
if (buf == NULL)
return;
regmatch.regprog = NULL; /* so that we can goto theend */
/* If 'infercase' is set, don't use 'smartcase' here */
save_p_scs = p_scs;
if (curbuf->b_p_inf)
p_scs = FALSE;
/* When invoked to match whole lines for CTRL-X CTRL-L adjust the pattern
* to only match at the start of a line. Otherwise just match the
* pattern. Also need to double backslashes. */
if (ctrl_x_mode == CTRL_X_WHOLE_LINE)
{
char_u *pat_esc = vim_strsave_escaped(pat, (char_u *)"\\");
size_t len;
if (pat_esc == NULL)
goto theend;
len = STRLEN(pat_esc) + 10;
ptr = alloc((unsigned)len);
if (ptr == NULL)
{
vim_free(pat_esc);
goto theend;
}
vim_snprintf((char *)ptr, len, "^\\s*\\zs\\V%s", pat_esc);
regmatch.regprog = vim_regcomp(ptr, RE_MAGIC);
vim_free(pat_esc);
vim_free(ptr);
}
else
{
regmatch.regprog = vim_regcomp(pat, p_magic ? RE_MAGIC : 0);
if (regmatch.regprog == NULL)
goto theend;
}
/* ignore case depends on 'ignorecase', 'smartcase' and "pat" */
regmatch.rm_ic = ignorecase(pat);
while (*dict != NUL && !got_int && !compl_interrupted)
{
/* copy one dictionary file name into buf */
if (flags == DICT_EXACT)
{
count = 1;
files = &dict;
}
else
{
/* Expand wildcards in the dictionary name, but do not allow
* backticks (for security, the 'dict' option may have been set in
* a modeline). */
copy_option_part(&dict, buf, LSIZE, ",");
# ifdef FEAT_SPELL
if (!thesaurus && STRCMP(buf, "spell") == 0)
count = -1;
else
# endif
if (vim_strchr(buf, '`') != NULL
|| expand_wildcards(1, &buf, &count, &files,
EW_FILE|EW_SILENT) != OK)
count = 0;
}
# ifdef FEAT_SPELL
if (count == -1)
{
/* Complete from active spelling. Skip "\<" in the pattern, we
* don't use it as a RE. */
if (pat[0] == '\\' && pat[1] == '<')
ptr = pat + 2;
else
ptr = pat;
spell_dump_compl(ptr, regmatch.rm_ic, &dir, 0);
}
else
# endif
if (count > 0) /* avoid warning for using "files" uninit */
{
ins_compl_files(count, files, thesaurus, flags,
&regmatch, buf, &dir);
if (flags != DICT_EXACT)
FreeWild(count, files);
}
if (flags != 0)
break;
}
theend:
p_scs = save_p_scs;
vim_free(regmatch.regprog);
vim_free(buf);
}
static void
ins_compl_files(count, files, thesaurus, flags, regmatch, buf, dir)
int count;
char_u **files;
int thesaurus;
int flags;
regmatch_T *regmatch;
char_u *buf;
int *dir;
{
char_u *ptr;
int i;
FILE *fp;
int add_r;
for (i = 0; i < count && !got_int && !compl_interrupted; i++)
{
fp = mch_fopen((char *)files[i], "r"); /* open dictionary file */
if (flags != DICT_EXACT)
{
vim_snprintf((char *)IObuff, IOSIZE,
_("Scanning dictionary: %s"), (char *)files[i]);
(void)msg_trunc_attr(IObuff, TRUE, hl_attr(HLF_R));
}
if (fp != NULL)
{
/*
* Read dictionary file line by line.
* Check each line for a match.
*/
while (!got_int && !compl_interrupted
&& !vim_fgets(buf, LSIZE, fp))
{
ptr = buf;
while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf)))
{
ptr = regmatch->startp[0];
if (ctrl_x_mode == CTRL_X_WHOLE_LINE)
ptr = find_line_end(ptr);
else
ptr = find_word_end(ptr);
add_r = ins_compl_add_infercase(regmatch->startp[0],
(int)(ptr - regmatch->startp[0]),
p_ic, files[i], *dir, 0);
if (thesaurus)
{
char_u *wstart;
/*
* Add the other matches on the line
*/
ptr = buf;
while (!got_int)
{
/* Find start of the next word. Skip white
* space and punctuation. */
ptr = find_word_start(ptr);
if (*ptr == NUL || *ptr == NL)
break;
wstart = ptr;
/* Find end of the word. */
#ifdef FEAT_MBYTE
if (has_mbyte)
/* Japanese words may have characters in
* different classes, only separate words
* with single-byte non-word characters. */
while (*ptr != NUL)
{
int l = (*mb_ptr2len)(ptr);
if (l < 2 && !vim_iswordc(*ptr))
break;
ptr += l;
}
else
#endif
ptr = find_word_end(ptr);
/* Add the word. Skip the regexp match. */
if (wstart != regmatch->startp[0])
add_r = ins_compl_add_infercase(wstart,
(int)(ptr - wstart),
p_ic, files[i], *dir, 0);
}
}
if (add_r == OK)
/* if dir was BACKWARD then honor it just once */
*dir = FORWARD;
else if (add_r == FAIL)
break;
/* avoid expensive call to vim_regexec() when at end
* of line */
if (*ptr == '\n' || got_int)
break;
}
line_breakcheck();
ins_compl_check_keys(50);
}
fclose(fp);
}
}
}
/*
* Find the start of the next word.
* Returns a pointer to the first char of the word. Also stops at a NUL.
*/
char_u *
find_word_start(ptr)
char_u *ptr;
{
#ifdef FEAT_MBYTE
if (has_mbyte)
while (*ptr != NUL && *ptr != '\n' && mb_get_class(ptr) <= 1)
ptr += (*mb_ptr2len)(ptr);
else
#endif
while (*ptr != NUL && *ptr != '\n' && !vim_iswordc(*ptr))
++ptr;
return ptr;
}
/*
* Find the end of the word. Assumes it starts inside a word.
* Returns a pointer to just after the word.
*/
char_u *
find_word_end(ptr)
char_u *ptr;
{
#ifdef FEAT_MBYTE
int start_class;
if (has_mbyte)
{
start_class = mb_get_class(ptr);
if (start_class > 1)
while (*ptr != NUL)
{
ptr += (*mb_ptr2len)(ptr);
if (mb_get_class(ptr) != start_class)
break;
}
}
else
#endif
while (vim_iswordc(*ptr))
++ptr;
return ptr;
}
/*
* Find the end of the line, omitting CR and NL at the end.
* Returns a pointer to just after the line.
*/
static char_u *
find_line_end(ptr)
char_u *ptr;
{
char_u *s;
s = ptr + STRLEN(ptr);
while (s > ptr && (s[-1] == CAR || s[-1] == NL))
--s;
return s;
}
/*
* Free the list of completions
*/
static void
ins_compl_free()
{
compl_T *match;
int i;
vim_free(compl_pattern);
compl_pattern = NULL;
vim_free(compl_leader);
compl_leader = NULL;
if (compl_first_match == NULL)
return;
ins_compl_del_pum();
pum_clear();
compl_curr_match = compl_first_match;
do
{
match = compl_curr_match;
compl_curr_match = compl_curr_match->cp_next;
vim_free(match->cp_str);
/* several entries may use the same fname, free it just once. */
if (match->cp_flags & FREE_FNAME)
vim_free(match->cp_fname);
for (i = 0; i < CPT_COUNT; ++i)
vim_free(match->cp_text[i]);
vim_free(match);
} while (compl_curr_match != NULL && compl_curr_match != compl_first_match);
compl_first_match = compl_curr_match = NULL;
compl_shown_match = NULL;
}
static void
ins_compl_clear()
{
compl_cont_status = 0;
compl_started = FALSE;
compl_matches = 0;
vim_free(compl_pattern);
compl_pattern = NULL;
vim_free(compl_leader);
compl_leader = NULL;
edit_submode_extra = NULL;
vim_free(compl_orig_text);
compl_orig_text = NULL;
compl_enter_selects = FALSE;
}
/*
* Return TRUE when Insert completion is active.
*/
int
ins_compl_active()
{
return compl_started;
}
/*
* Delete one character before the cursor and show the subset of the matches
* that match the word that is now before the cursor.
* Returns the character to be used, NUL if the work is done and another char
* to be got from the user.
*/
static int
ins_compl_bs()
{
char_u *line;
char_u *p;
line = ml_get_curline();
p = line + curwin->w_cursor.col;
mb_ptr_back(line, p);
/* Stop completion when the whole word was deleted. For Omni completion
* allow the word to be deleted, we won't match everything. */
if ((int)(p - line) - (int)compl_col < 0
|| ((int)(p - line) - (int)compl_col == 0
&& (ctrl_x_mode & CTRL_X_OMNI) == 0))
return K_BS;
/* Deleted more than what was used to find matches or didn't finish
* finding all matches: need to look for matches all over again. */
if (curwin->w_cursor.col <= compl_col + compl_length
|| ins_compl_need_restart())
ins_compl_restart();
vim_free(compl_leader);
compl_leader = vim_strnsave(line + compl_col, (int)(p - line) - compl_col);
if (compl_leader != NULL)
{
ins_compl_new_leader();
return NUL;
}
return K_BS;
}
/*
* Return TRUE when we need to find matches again, ins_compl_restart() is to
* be called.
*/
static int
ins_compl_need_restart()
{
/* Return TRUE if we didn't complete finding matches or when the
* 'completefunc' returned "always" in the "refresh" dictionary item. */
return compl_was_interrupted
|| ((ctrl_x_mode == CTRL_X_FUNCTION || ctrl_x_mode == CTRL_X_OMNI)
&& compl_opt_refresh_always);
}
/*
* Called after changing "compl_leader".
* Show the popup menu with a different set of matches.
* May also search for matches again if the previous search was interrupted.
*/
static void
ins_compl_new_leader()
{
ins_compl_del_pum();
ins_compl_delete();
ins_bytes(compl_leader + ins_compl_len());
compl_used_match = FALSE;
if (compl_started)
ins_compl_set_original_text(compl_leader);
else
{
#ifdef FEAT_SPELL
spell_bad_len = 0; /* need to redetect bad word */
#endif
/*
* Matches were cleared, need to search for them now. First display
* the changed text before the cursor. Set "compl_restarting" to
* avoid that the first match is inserted.
*/
update_screen(0);
#ifdef FEAT_GUI
if (gui.in_use)
{
/* Show the cursor after the match, not after the redrawn text. */
setcursor();
out_flush();
gui_update_cursor(FALSE, FALSE);
}
#endif
compl_restarting = TRUE;
if (ins_complete(Ctrl_N) == FAIL)
compl_cont_status = 0;
compl_restarting = FALSE;
}
compl_enter_selects = !compl_used_match;
/* Show the popup menu with a different set of matches. */
ins_compl_show_pum();
/* Don't let Enter select the original text when there is no popup menu. */
if (compl_match_array == NULL)
compl_enter_selects = FALSE;
}
/*
* Return the length of the completion, from the completion start column to
* the cursor column. Making sure it never goes below zero.
*/
static int
ins_compl_len()
{
int off = (int)curwin->w_cursor.col - (int)compl_col;
if (off < 0)
return 0;
return off;
}
/*
* Append one character to the match leader. May reduce the number of
* matches.
*/
static void
ins_compl_addleader(c)
int c;
{
#ifdef FEAT_MBYTE
int cc;
if (has_mbyte && (cc = (*mb_char2len)(c)) > 1)
{
char_u buf[MB_MAXBYTES + 1];
(*mb_char2bytes)(c, buf);
buf[cc] = NUL;
ins_char_bytes(buf, cc);
if (compl_opt_refresh_always)
AppendToRedobuff(buf);
}
else
#endif
{
ins_char(c);
if (compl_opt_refresh_always)
AppendCharToRedobuff(c);
}
/* If we didn't complete finding matches we must search again. */
if (ins_compl_need_restart())
ins_compl_restart();
/* When 'always' is set, don't reset compl_leader. While completing,
* cursor doesn't point original position, changing compl_leader would
* break redo. */
if (!compl_opt_refresh_always)
{
vim_free(compl_leader);
compl_leader = vim_strnsave(ml_get_curline() + compl_col,
(int)(curwin->w_cursor.col - compl_col));
if (compl_leader != NULL)
ins_compl_new_leader();
}
}
/*
* Setup for finding completions again without leaving CTRL-X mode. Used when
* BS or a key was typed while still searching for matches.
*/
static void
ins_compl_restart()
{
ins_compl_free();
compl_started = FALSE;
compl_matches = 0;
compl_cont_status = 0;
compl_cont_mode = 0;
}
/*
* Set the first match, the original text.
*/
static void
ins_compl_set_original_text(str)
char_u *str;
{
char_u *p;
/* Replace the original text entry. */
if (compl_first_match->cp_flags & ORIGINAL_TEXT) /* safety check */
{
p = vim_strsave(str);
if (p != NULL)
{
vim_free(compl_first_match->cp_str);
compl_first_match->cp_str = p;
}
}
}
/*
* Append one character to the match leader. May reduce the number of
* matches.
*/
static void
ins_compl_addfrommatch()
{
char_u *p;
int len = (int)curwin->w_cursor.col - (int)compl_col;
int c;
compl_T *cp;
p = compl_shown_match->cp_str;
if ((int)STRLEN(p) <= len) /* the match is too short */
{
/* When still at the original match use the first entry that matches
* the leader. */
if (compl_shown_match->cp_flags & ORIGINAL_TEXT)
{
p = NULL;
for (cp = compl_shown_match->cp_next; cp != NULL
&& cp != compl_first_match; cp = cp->cp_next)
{
if (compl_leader == NULL
|| ins_compl_equal(cp, compl_leader,
(int)STRLEN(compl_leader)))
{
p = cp->cp_str;
break;
}
}
if (p == NULL || (int)STRLEN(p) <= len)
return;
}
else
return;
}
p += len;
c = PTR2CHAR(p);
ins_compl_addleader(c);
}
/*
* Prepare for Insert mode completion, or stop it.
* Called just after typing a character in Insert mode.
* Returns TRUE when the character is not to be inserted;
*/
static int
ins_compl_prep(c)
int c;
{
char_u *ptr;
int want_cindent;
int retval = FALSE;
/* Forget any previous 'special' messages if this is actually
* a ^X mode key - bar ^R, in which case we wait to see what it gives us.
*/
if (c != Ctrl_R && vim_is_ctrl_x_key(c))
edit_submode_extra = NULL;
/* Ignore end of Select mode mapping and mouse scroll buttons. */
if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP
|| c == K_MOUSELEFT || c == K_MOUSERIGHT
# ifdef FEAT_GUI_MACVIM
|| c == K_SWIPELEFT || c == K_SWIPERIGHT || c == K_SWIPEUP
|| c == K_SWIPEDOWN
# endif
)
return retval;
/* Set "compl_get_longest" when finding the first matches. */
if (ctrl_x_mode == CTRL_X_NOT_DEFINED_YET
|| (ctrl_x_mode == 0 && !compl_started))
{
compl_get_longest = (vim_strchr(p_cot, 'l') != NULL);
compl_used_match = TRUE;
}
if (ctrl_x_mode == CTRL_X_NOT_DEFINED_YET)
{
/*
* We have just typed CTRL-X and aren't quite sure which CTRL-X mode
* it will be yet. Now we decide.
*/
switch (c)
{
case Ctrl_E:
case Ctrl_Y:
ctrl_x_mode = CTRL_X_SCROLL;
if (!(State & REPLACE_FLAG))
edit_submode = (char_u *)_(" (insert) Scroll (^E/^Y)");
else
edit_submode = (char_u *)_(" (replace) Scroll (^E/^Y)");
edit_submode_pre = NULL;
showmode();
break;
case Ctrl_L:
ctrl_x_mode = CTRL_X_WHOLE_LINE;
break;
case Ctrl_F:
ctrl_x_mode = CTRL_X_FILES;
break;
case Ctrl_K:
ctrl_x_mode = CTRL_X_DICTIONARY;
break;
case Ctrl_R:
/* Simply allow ^R to happen without affecting ^X mode */
break;
case Ctrl_T:
ctrl_x_mode = CTRL_X_THESAURUS;
break;
#ifdef FEAT_COMPL_FUNC
case Ctrl_U:
ctrl_x_mode = CTRL_X_FUNCTION;
break;
case Ctrl_O:
ctrl_x_mode = CTRL_X_OMNI;
break;
#endif
case 's':
case Ctrl_S:
ctrl_x_mode = CTRL_X_SPELL;
#ifdef FEAT_SPELL
++emsg_off; /* Avoid getting the E756 error twice. */
spell_back_to_badword();
--emsg_off;
#endif
break;
case Ctrl_RSB:
ctrl_x_mode = CTRL_X_TAGS;
break;
#ifdef FEAT_FIND_ID
case Ctrl_I:
case K_S_TAB:
ctrl_x_mode = CTRL_X_PATH_PATTERNS;
break;
case Ctrl_D:
ctrl_x_mode = CTRL_X_PATH_DEFINES;
break;
#endif
case Ctrl_V:
case Ctrl_Q:
ctrl_x_mode = CTRL_X_CMDLINE;
break;
case Ctrl_P:
case Ctrl_N:
/* ^X^P means LOCAL expansion if nothing interrupted (eg we
* just started ^X mode, or there were enough ^X's to cancel
* the previous mode, say ^X^F^X^X^P or ^P^X^X^X^P, see below)
* do normal expansion when interrupting a different mode (say
* ^X^F^X^P or ^P^X^X^P, see below)
* nothing changes if interrupting mode 0, (eg, the flag
* doesn't change when going to ADDING mode -- Acevedo */
if (!(compl_cont_status & CONT_INTRPT))
compl_cont_status |= CONT_LOCAL;
else if (compl_cont_mode != 0)
compl_cont_status &= ~CONT_LOCAL;
/* FALLTHROUGH */
default:
/* If we have typed at least 2 ^X's... for modes != 0, we set
* compl_cont_status = 0 (eg, as if we had just started ^X
* mode).
* For mode 0, we set "compl_cont_mode" to an impossible
* value, in both cases ^X^X can be used to restart the same
* mode (avoiding ADDING mode).
* Undocumented feature: In a mode != 0 ^X^P and ^X^X^P start
* 'complete' and local ^P expansions respectively.
* In mode 0 an extra ^X is needed since ^X^P goes to ADDING
* mode -- Acevedo */
if (c == Ctrl_X)
{
if (compl_cont_mode != 0)
compl_cont_status = 0;
else
compl_cont_mode = CTRL_X_NOT_DEFINED_YET;
}
ctrl_x_mode = 0;
edit_submode = NULL;
showmode();
break;
}
}
else if (ctrl_x_mode != 0)
{
/* We're already in CTRL-X mode, do we stay in it? */
if (!vim_is_ctrl_x_key(c))
{
if (ctrl_x_mode == CTRL_X_SCROLL)
ctrl_x_mode = 0;
else
ctrl_x_mode = CTRL_X_FINISHED;
edit_submode = NULL;
}
showmode();
}
if (compl_started || ctrl_x_mode == CTRL_X_FINISHED)
{
/* Show error message from attempted keyword completion (probably
* 'Pattern not found') until another key is hit, then go back to
* showing what mode we are in. */
showmode();
if ((ctrl_x_mode == 0 && c != Ctrl_N && c != Ctrl_P && c != Ctrl_R
&& !ins_compl_pum_key(c))
|| ctrl_x_mode == CTRL_X_FINISHED)
{
/* Get here when we have finished typing a sequence of ^N and
* ^P or other completion characters in CTRL-X mode. Free up
* memory that was used, and make sure we can redo the insert. */
if (compl_curr_match != NULL || compl_leader != NULL || c == Ctrl_E)
{
/*
* If any of the original typed text has been changed, eg when
* ignorecase is set, we must add back-spaces to the redo
* buffer. We add as few as necessary to delete just the part
* of the original text that has changed.
* When using the longest match, edited the match or used
* CTRL-E then don't use the current match.
*/
if (compl_curr_match != NULL && compl_used_match && c != Ctrl_E)
ptr = compl_curr_match->cp_str;
else
ptr = NULL;
ins_compl_fixRedoBufForLeader(ptr);
}
#ifdef FEAT_CINDENT
want_cindent = (can_cindent && cindent_on());
#endif
/*
* When completing whole lines: fix indent for 'cindent'.
* Otherwise, break line if it's too long.
*/
if (compl_cont_mode == CTRL_X_WHOLE_LINE)
{
#ifdef FEAT_CINDENT
/* re-indent the current line */
if (want_cindent)
{
do_c_expr_indent();
want_cindent = FALSE; /* don't do it again */
}
#endif
}
else
{
int prev_col = curwin->w_cursor.col;
/* put the cursor on the last char, for 'tw' formatting */
if (prev_col > 0)
dec_cursor();
if (stop_arrow() == OK)
insertchar(NUL, 0, -1);
if (prev_col > 0
&& ml_get_curline()[curwin->w_cursor.col] != NUL)
inc_cursor();
}
/* If the popup menu is displayed pressing CTRL-Y means accepting
* the selection without inserting anything. When
* compl_enter_selects is set the Enter key does the same. */
if ((c == Ctrl_Y || (compl_enter_selects
&& (c == CAR || c == K_KENTER || c == NL)))
&& pum_visible())
retval = TRUE;
/* CTRL-E means completion is Ended, go back to the typed text. */
if (c == Ctrl_E)
{
ins_compl_delete();
if (compl_leader != NULL)
ins_bytes(compl_leader + ins_compl_len());
else if (compl_first_match != NULL)
ins_bytes(compl_orig_text + ins_compl_len());
retval = TRUE;
}
auto_format(FALSE, TRUE);
ins_compl_free();
compl_started = FALSE;
compl_matches = 0;
msg_clr_cmdline(); /* necessary for "noshowmode" */
ctrl_x_mode = 0;
compl_enter_selects = FALSE;
if (edit_submode != NULL)
{
edit_submode = NULL;
showmode();
}
#ifdef FEAT_CINDENT
/*
* Indent now if a key was typed that is in 'cinkeys'.
*/
if (want_cindent && in_cinkeys(KEY_COMPLETE, ' ', inindent(0)))
do_c_expr_indent();
#endif
#ifdef FEAT_AUTOCMD
/* Trigger the CompleteDone event to give scripts a chance to act
* upon the completion. */
apply_autocmds(EVENT_COMPLETEDONE, NULL, NULL, FALSE, curbuf);
#endif
}
}
/* reset continue_* if we left expansion-mode, if we stay they'll be
* (re)set properly in ins_complete() */
if (!vim_is_ctrl_x_key(c))
{
compl_cont_status = 0;
compl_cont_mode = 0;
}
return retval;
}
/*
* Fix the redo buffer for the completion leader replacing some of the typed
* text. This inserts backspaces and appends the changed text.
* "ptr" is the known leader text or NUL.
*/
static void
ins_compl_fixRedoBufForLeader(ptr_arg)
char_u *ptr_arg;
{
int len;
char_u *p;
char_u *ptr = ptr_arg;
if (ptr == NULL)
{
if (compl_leader != NULL)
ptr = compl_leader;
else
return; /* nothing to do */
}
if (compl_orig_text != NULL)
{
p = compl_orig_text;
for (len = 0; p[len] != NUL && p[len] == ptr[len]; ++len)
;
#ifdef FEAT_MBYTE
if (len > 0)
len -= (*mb_head_off)(p, p + len);
#endif
for (p += len; *p != NUL; mb_ptr_adv(p))
AppendCharToRedobuff(K_BS);
}
else
len = 0;
if (ptr != NULL)
AppendToRedobuffLit(ptr + len, -1);
}
/*
* Loops through the list of windows, loaded-buffers or non-loaded-buffers
* (depending on flag) starting from buf and looking for a non-scanned
* buffer (other than curbuf). curbuf is special, if it is called with
* buf=curbuf then it has to be the first call for a given flag/expansion.
*
* Returns the buffer to scan, if any, otherwise returns curbuf -- Acevedo
*/
static buf_T *
ins_compl_next_buf(buf, flag)
buf_T *buf;
int flag;
{
#ifdef FEAT_WINDOWS
static win_T *wp;
#endif
if (flag == 'w') /* just windows */
{
#ifdef FEAT_WINDOWS
if (buf == curbuf) /* first call for this flag/expansion */
wp = curwin;
while ((wp = (wp->w_next != NULL ? wp->w_next : firstwin)) != curwin
&& wp->w_buffer->b_scanned)
;
buf = wp->w_buffer;
#else
buf = curbuf;
#endif
}
else
/* 'b' (just loaded buffers), 'u' (just non-loaded buffers) or 'U'
* (unlisted buffers)
* When completing whole lines skip unloaded buffers. */
while ((buf = (buf->b_next != NULL ? buf->b_next : firstbuf)) != curbuf
&& ((flag == 'U'
? buf->b_p_bl
: (!buf->b_p_bl
|| (buf->b_ml.ml_mfp == NULL) != (flag == 'u')))
|| buf->b_scanned))
;
return buf;
}
#ifdef FEAT_COMPL_FUNC
static void expand_by_function __ARGS((int type, char_u *base));
/*
* Execute user defined complete function 'completefunc' or 'omnifunc', and
* get matches in "matches".
*/
static void
expand_by_function(type, base)
int type; /* CTRL_X_OMNI or CTRL_X_FUNCTION */
char_u *base;
{
list_T *matchlist = NULL;
dict_T *matchdict = NULL;
char_u *args[2];
char_u *funcname;
pos_T pos;
win_T *curwin_save;
buf_T *curbuf_save;
typval_T rettv;
funcname = (type == CTRL_X_FUNCTION) ? curbuf->b_p_cfu : curbuf->b_p_ofu;
if (*funcname == NUL)
return;
/* Call 'completefunc' to obtain the list of matches. */
args[0] = (char_u *)"0";
args[1] = base;
pos = curwin->w_cursor;
curwin_save = curwin;
curbuf_save = curbuf;
/* Call a function, which returns a list or dict. */
if (call_vim_function(funcname, 2, args, FALSE, FALSE, &rettv) == OK)
{
switch (rettv.v_type)
{
case VAR_LIST:
matchlist = rettv.vval.v_list;
break;
case VAR_DICT:
matchdict = rettv.vval.v_dict;
break;
default:
/* TODO: Give error message? */
clear_tv(&rettv);
break;
}
}
if (curwin_save != curwin || curbuf_save != curbuf)
{
EMSG(_(e_complwin));
goto theend;
}
curwin->w_cursor = pos; /* restore the cursor position */
check_cursor();
if (!equalpos(curwin->w_cursor, pos))
{
EMSG(_(e_compldel));
goto theend;
}
if (matchlist != NULL)
ins_compl_add_list(matchlist);
else if (matchdict != NULL)
ins_compl_add_dict(matchdict);
theend:
if (matchdict != NULL)
dict_unref(matchdict);
if (matchlist != NULL)
list_unref(matchlist);
}
#endif /* FEAT_COMPL_FUNC */
#if defined(FEAT_COMPL_FUNC) || defined(FEAT_EVAL) || defined(PROTO)
/*
* Add completions from a list.
*/
static void
ins_compl_add_list(list)
list_T *list;
{
listitem_T *li;
int dir = compl_direction;
/* Go through the List with matches and add each of them. */
for (li = list->lv_first; li != NULL; li = li->li_next)
{
if (ins_compl_add_tv(&li->li_tv, dir) == OK)
/* if dir was BACKWARD then honor it just once */
dir = FORWARD;
else if (did_emsg)
break;
}
}
/*
* Add completions from a dict.
*/
static void
ins_compl_add_dict(dict)
dict_T *dict;
{
dictitem_T *di_refresh;
dictitem_T *di_words;
/* Check for optional "refresh" item. */
compl_opt_refresh_always = FALSE;
di_refresh = dict_find(dict, (char_u *)"refresh", 7);
if (di_refresh != NULL && di_refresh->di_tv.v_type == VAR_STRING)
{
char_u *v = di_refresh->di_tv.vval.v_string;
if (v != NULL && STRCMP(v, (char_u *)"always") == 0)
compl_opt_refresh_always = TRUE;
}
/* Add completions from a "words" list. */
di_words = dict_find(dict, (char_u *)"words", 5);
if (di_words != NULL && di_words->di_tv.v_type == VAR_LIST)
ins_compl_add_list(di_words->di_tv.vval.v_list);
}
/*
* Add a match to the list of matches from a typeval_T.
* If the given string is already in the list of completions, then return
* NOTDONE, otherwise add it to the list and return OK. If there is an error,
* maybe because alloc() returns NULL, then FAIL is returned.
*/
int
ins_compl_add_tv(tv, dir)
typval_T *tv;
int dir;
{
char_u *word;
int icase = FALSE;
int adup = FALSE;
int aempty = FALSE;
char_u *(cptext[CPT_COUNT]);
if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL)
{
word = get_dict_string(tv->vval.v_dict, (char_u *)"word", FALSE);
cptext[CPT_ABBR] = get_dict_string(tv->vval.v_dict,
(char_u *)"abbr", FALSE);
cptext[CPT_MENU] = get_dict_string(tv->vval.v_dict,
(char_u *)"menu", FALSE);
cptext[CPT_KIND] = get_dict_string(tv->vval.v_dict,
(char_u *)"kind", FALSE);
cptext[CPT_INFO] = get_dict_string(tv->vval.v_dict,
(char_u *)"info", FALSE);
if (get_dict_string(tv->vval.v_dict, (char_u *)"icase", FALSE) != NULL)
icase = get_dict_number(tv->vval.v_dict, (char_u *)"icase");
if (get_dict_string(tv->vval.v_dict, (char_u *)"dup", FALSE) != NULL)
adup = get_dict_number(tv->vval.v_dict, (char_u *)"dup");
if (get_dict_string(tv->vval.v_dict, (char_u *)"empty", FALSE) != NULL)
aempty = get_dict_number(tv->vval.v_dict, (char_u *)"empty");
}
else
{
word = get_tv_string_chk(tv);
vim_memset(cptext, 0, sizeof(cptext));
}
if (word == NULL || (!aempty && *word == NUL))
return FAIL;
return ins_compl_add(word, -1, icase, NULL, cptext, dir, 0, adup);
}
#endif
/*
* Get the next expansion(s), using "compl_pattern".
* The search starts at position "ini" in curbuf and in the direction
* compl_direction.
* When "compl_started" is FALSE start at that position, otherwise continue
* where we stopped searching before.
* This may return before finding all the matches.
* Return the total number of matches or -1 if still unknown -- Acevedo
*/
static int
ins_compl_get_exp(ini)
pos_T *ini;
{
static pos_T first_match_pos;
static pos_T last_match_pos;
static char_u *e_cpt = (char_u *)""; /* curr. entry in 'complete' */
static int found_all = FALSE; /* Found all matches of a
certain type. */
static buf_T *ins_buf = NULL; /* buffer being scanned */
pos_T *pos;
char_u **matches;
int save_p_scs;
int save_p_ws;
int save_p_ic;
int i;
int num_matches;
int len;
int found_new_match;
int type = ctrl_x_mode;
char_u *ptr;
char_u *dict = NULL;
int dict_f = 0;
compl_T *old_match;
if (!compl_started)
{
for (ins_buf = firstbuf; ins_buf != NULL; ins_buf = ins_buf->b_next)
ins_buf->b_scanned = 0;
found_all = FALSE;
ins_buf = curbuf;
e_cpt = (compl_cont_status & CONT_LOCAL)
? (char_u *)"." : curbuf->b_p_cpt;
last_match_pos = first_match_pos = *ini;
}
old_match = compl_curr_match; /* remember the last current match */
pos = (compl_direction == FORWARD) ? &last_match_pos : &first_match_pos;
/* For ^N/^P loop over all the flags/windows/buffers in 'complete' */
for (;;)
{
found_new_match = FAIL;
/* For ^N/^P pick a new entry from e_cpt if compl_started is off,
* or if found_all says this entry is done. For ^X^L only use the
* entries from 'complete' that look in loaded buffers. */
if ((ctrl_x_mode == 0 || ctrl_x_mode == CTRL_X_WHOLE_LINE)
&& (!compl_started || found_all))
{
found_all = FALSE;
while (*e_cpt == ',' || *e_cpt == ' ')
e_cpt++;
if (*e_cpt == '.' && !curbuf->b_scanned)
{
ins_buf = curbuf;
first_match_pos = *ini;
/* So that ^N can match word immediately after cursor */
if (ctrl_x_mode == 0)
dec(&first_match_pos);
last_match_pos = first_match_pos;
type = 0;
}
else if (vim_strchr((char_u *)"buwU", *e_cpt) != NULL
&& (ins_buf = ins_compl_next_buf(ins_buf, *e_cpt)) != curbuf)
{
/* Scan a buffer, but not the current one. */
if (ins_buf->b_ml.ml_mfp != NULL) /* loaded buffer */
{
compl_started = TRUE;