diff options
Diffstat (limited to 'usr.bin/vim/edit.c')
-rw-r--r-- | usr.bin/vim/edit.c | 3579 |
1 files changed, 3579 insertions, 0 deletions
diff --git a/usr.bin/vim/edit.c b/usr.bin/vim/edit.c new file mode 100644 index 00000000000..1c2d604b5ed --- /dev/null +++ b/usr.bin/vim/edit.c @@ -0,0 +1,3579 @@ +/* $OpenBSD: edit.c,v 1.1 1996/09/07 21:40:26 downsj Exp $ */ +/* vi:set ts=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. + */ + +/* + * edit.c: functions for insert mode + */ + +#include "vim.h" +#include "globals.h" +#include "proto.h" +#include "option.h" +#include "ops.h" /* for op_type */ + +#ifdef INSERT_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) + +struct Completion +{ + char_u *str; + char_u *fname; + int original; + struct Completion *next; + struct Completion *prev; +}; + +struct Completion *first_match = NULL; +struct Completion *curr_match = NULL; + +static int add_completion __ARGS((char_u *str, int len, char_u *, int dir)); +static int make_cyclic __ARGS((void)); +static void complete_dictionaries __ARGS((char_u *, int)); +static void free_completions __ARGS((void)); +static int count_completions __ARGS((void)); +#endif /* INSERT_EXPAND */ + +#define BACKSPACE_CHAR 1 +#define BACKSPACE_WORD 2 +#define BACKSPACE_WORD_NOT_SPACE 3 +#define BACKSPACE_LINE 4 + +static void change_indent __ARGS((int type, int amount, int round)); +static void insert_special __ARGS((int, int)); +static void start_arrow __ARGS((FPOS *end_insert_pos)); +static void stop_arrow __ARGS((void)); +static void stop_insert __ARGS((FPOS *end_insert_pos)); +static int echeck_abbr __ARGS((int)); + +static FPOS Insstart; /* This is where the latest insert/append + * mode started. */ +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 */ +static int last_insert_skip; + /* number of chars in front of previous insert */ +static int new_insert_skip; + /* number of chars in front of the current insert */ +#ifdef INSERT_EXPAND +static char_u *original_text = NULL; + /* Original text typed before completion */ +#endif + +#ifdef CINDENT +static int can_cindent; /* may do cindenting on this line */ +#endif + +/* + * edit() returns TRUE if it returns because of a CTRL-O command + */ + int +edit(initstr, startln, count) + int initstr; + int startln; /* if set, insert at start of line */ + long count; +{ + int c; + int cc; + char_u *ptr; + linenr_t lnum; + int temp = 0; + int mode; + int lastc = 0; + colnr_t mincol; + static linenr_t o_lnum = 0; + static int o_eol = FALSE; + int need_redraw = FALSE; + int i; + int did_backspace = TRUE; /* previous char was backspace */ +#ifdef RIGHTLEFT + int revins; /* reverse insert mode */ + int revinschars = 0; /* how much to skip after edit */ + int revinslegal = 0; /* was the last char 'legal'? */ + int revinsscol = -1; /* start column of revins session */ +#endif +#ifdef INSERT_EXPAND + FPOS first_match_pos; + FPOS last_match_pos; + FPOS *complete_pos; + char_u *complete_pat = NULL; + char_u *tmp_ptr; + char_u *mesg = NULL; /* Message about completion */ + char_u *quick_m; /* Message without sleep */ + int started_completion = FALSE; + colnr_t complete_col = 0; /* init for gcc */ + int complete_direction; + int done_dir = 0; /* Found all matches in this + * direction */ + int num_matches; + char_u **matches; + regexp *prog; + int save_sm = -1; /* init for gcc */ + int save_p_scs; +#endif +#ifdef CINDENT + int line_is_white = FALSE; /* line is empty before insert */ +#endif + FPOS tpos; + + /* sleep before redrawing, needed for "CTRL-O :" that results in an + * error message */ + if (msg_scroll || emsg_on_display) + { + mch_delay(1000L, TRUE); + msg_scroll = FALSE; + emsg_on_display = FALSE; + } +#ifdef SLEEP_IN_EMSG + if (need_sleep) + { + mch_delay(1000L, TRUE); + need_sleep = FALSE; + } +#endif + +#ifdef USE_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 = linetabsize(ml_get_curline()); + Insstart_blank_vcol = MAXCOL; + + if (initstr != NUL && !restart_edit) + { + ResetRedobuff(); + AppendNumberToRedobuff(count); + AppendCharToRedobuff(initstr); + if (initstr == 'g') /* "gI" command */ + AppendCharToRedobuff('I'); + } + + if (initstr == 'R') + State = REPLACE; + else + State = INSERT; + +#ifdef USE_MOUSE + setmouse(); +#endif + clear_showcmd(); +#ifdef RIGHTLEFT + revins = (State == INSERT && p_ri); /* there is no reverse replace mode */ + if (revins) + undisplay_dollar(); +#endif + + /* + * When CTRL-O . is used to repeat an insert, we get here with + * restart_edit non-zero, but something in the stuff buffer + */ + if (restart_edit && stuff_empty()) + { +#ifdef USE_MOUSE + /* + * After a paste we consider text typed to be part of the insert for + * the pasted text. You can backspace over the paste 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|". + */ + if (((o_eol && curwin->w_cursor.lnum == o_lnum) || + curwin->w_curswant > curwin->w_virtcol) && + *(ptr = ml_get_curline() + curwin->w_cursor.col) + != NUL && + *(ptr + 1) == NUL) + ++curwin->w_cursor.col; + } + else + { + arrow_used = FALSE; + o_eol = FALSE; + } +#ifdef USE_MOUSE + where_paste_started.lnum = 0; +#endif +#ifdef CINDENT + can_cindent = TRUE; +#endif + + if (p_smd) + showmode(); + + if (!p_im) + change_warning(); /* give a warning if readonly */ + +#ifdef 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(); + new_insert_skip = STRLEN(ptr); + vim_free(ptr); + + old_indent = 0; + + for (;;) + { +#ifdef RIGHTLEFT + if (!revinslegal) + revinsscol = -1; /* reset on illegal motions */ + else + revinslegal = 0; +#endif + if (arrow_used) /* don't repeat insert when arrow key used */ + count = 0; + + /* set curwin->w_curswant for next K_DOWN or K_UP */ + if (!arrow_used) + curwin->w_set_curswant = TRUE; + + /* Figure out where the cursor is based on curwin->w_cursor. */ + mincol = curwin->w_col; + i = curwin->w_row; + cursupdate(); + + /* + * When emsg() was called msg_scroll will have been set. + */ + msg_scroll = FALSE; + + /* + * 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. + */ + if (curwin->w_p_wrap && !did_backspace && + (int)curwin->w_col < (int)mincol - curbuf->b_p_ts && + i == curwin->w_winpos + curwin->w_height - 1 && + curwin->w_cursor.lnum != curwin->w_topline) + { + ++curwin->w_topline; + updateScreen(VALID_TO_CURSCHAR); + cursupdate(); + need_redraw = FALSE; + } + did_backspace = FALSE; + + /* + * redraw is postponed until after cursupdate() to make 'dollar' + * option work correctly. + */ + if (need_redraw) + { + updateline(); + need_redraw = FALSE; + } + + showruler(0); + setcursor(); + emsg_on_display = FALSE; /* may remove error message now */ + + c = vgetc(); + if (c == Ctrl('C')) + got_int = FALSE; + +#ifdef RIGHTLEFT + if (p_hkmap && KeyTyped) + c = hkmap(c); /* Hebrew mode mapping */ +#endif + +#ifdef INSERT_EXPAND + if (ctrl_x_mode == CTRL_X_NOT_DEFINED_YET) + { + /* We have just entered ctrl-x mode and aren't quite sure which + * ctrl-x mode it will be yet. Now we decide -- webb + */ + switch (c) + { + case Ctrl('E'): + case Ctrl('Y'): + ctrl_x_mode = CTRL_X_SCROLL; + if (State == INSERT) + edit_submode = (char_u *)" (insert) Scroll (^E/^Y)"; + else + edit_submode = (char_u *)" (replace) Scroll (^E/^Y)"; + break; + case Ctrl('L'): + ctrl_x_mode = CTRL_X_WHOLE_LINE; + edit_submode = (char_u *)" Whole line completion (^L/^N/^P)"; + break; + case Ctrl('F'): + ctrl_x_mode = CTRL_X_FILES; + edit_submode = (char_u *)" File name completion (^F/^N/^P)"; + break; + case Ctrl('K'): + ctrl_x_mode = CTRL_X_DICTIONARY; + edit_submode = (char_u *)" Dictionary completion (^K/^N/^P)"; + break; + case Ctrl(']'): + ctrl_x_mode = CTRL_X_TAGS; + edit_submode = (char_u *)" Tag completion (^]/^N/^P)"; + break; + case Ctrl('I'): + ctrl_x_mode = CTRL_X_PATH_PATTERNS; + edit_submode = (char_u *)" Path pattern completion (^N/^P)"; + break; + case Ctrl('D'): + ctrl_x_mode = CTRL_X_PATH_DEFINES; + edit_submode = (char_u *)" Definition completion (^D/^N/^P)"; + break; + default: + ctrl_x_mode = 0; + break; + } + showmode(); + } + else if (ctrl_x_mode) + { + /* We we're already in ctrl-x mode, do we stay in it? */ + if (!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 (started_completion || 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')) || + 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 + * -- webb. + */ + if (curr_match != NULL) + { + /* + * 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 + * -- webb + */ + ptr = curr_match->str; + tmp_ptr = original_text; + while (*tmp_ptr && *tmp_ptr == *ptr) + { + ++tmp_ptr; + ++ptr; + } + for (temp = 0; tmp_ptr[temp]; ++temp) + AppendCharToRedobuff(K_BS); + if (*ptr) + AppendToRedobuff(ptr); + } + /* Break line if it's too long */ + lnum = curwin->w_cursor.lnum; + insertchar(NUL, FALSE, -1); + if (lnum != curwin->w_cursor.lnum) + updateScreen(CURSUPD); + else + need_redraw = TRUE; + + vim_free(complete_pat); + complete_pat = NULL; + vim_free(original_text); + original_text = NULL; + free_completions(); + started_completion = FALSE; + ctrl_x_mode = 0; + p_sm = save_sm; + if (edit_submode != NULL) + { + edit_submode = NULL; + showmode(); + } + } + } +#endif /* INSERT_EXPAND */ + + if (c != Ctrl('D')) /* remember to detect ^^D and 0^D */ + lastc = c; + +#ifdef DIGRAPHS + c = do_digraph(c); +#endif /* DIGRAPHS */ + + if (c == Ctrl('V') || c == Ctrl('Q')) + { + if (NextScreen != NULL) + screen_outchar('^', curwin->w_winpos + curwin->w_row, +#ifdef RIGHTLEFT + curwin->w_p_rl ? (int)Columns - 1 - curwin->w_col : +#endif + curwin->w_col); + AppendToRedobuff((char_u *)"\026"); /* CTRL-V */ + cursupdate(); + + if (!add_to_showcmd(c, FALSE)) + setcursor(); + + c = get_literal(); + clear_showcmd(); + insert_special(c, TRUE); + need_redraw = TRUE; +#ifdef RIGHTLEFT + revinschars++; + revinslegal++; +#endif + continue; + } + +#ifdef CINDENT + if (curbuf->b_p_cin +# ifdef INSERT_EXPAND + && !ctrl_x_mode +# endif + ) + { + line_is_white = inindent(0); + + /* + * A key name preceded by a bang means that this + * key wasn't destined to be inserted. Skip ahead + * to the re-indenting if we find one. + */ + if (in_cinkeys(c, '!', line_is_white)) + goto force_cindent; + + /* + * A key name preceded by a star means that indenting + * has to be done before inserting the key. + */ + if (can_cindent && in_cinkeys(c, '*', line_is_white)) + { + stop_arrow(); + + /* re-indent the current line */ + fixthisline(get_c_indent); + + /* draw the changes on the screen later */ + need_redraw = TRUE; + } + } +#endif /* CINDENT */ + +#ifdef 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_RIGHT: c = K_LEFT; break; + case K_S_RIGHT: c = K_S_LEFT; break; + } +#endif + + switch (c) /* handle character in insert mode */ + { + case K_INS: /* toggle insert/replace mode */ + if (State == REPLACE) + State = INSERT; + else + State = REPLACE; + AppendCharToRedobuff(K_INS); + showmode(); + break; + +#ifdef INSERT_EXPAND + case Ctrl('X'): /* Enter ctrl-x mode */ + /* We're not sure which ctrl-x mode it will be yet */ + ctrl_x_mode = CTRL_X_NOT_DEFINED_YET; + MSG("^X mode (^E/^Y/^L/^]/^F/^I/^K/^D)"); + break; +#endif /* INSERT_EXPAND */ + + case Ctrl('O'): /* execute one command */ + if (echeck_abbr(Ctrl('O') + ABBR_OFF)) + break; + count = 0; + if (State == INSERT) + restart_edit = 'I'; + else + restart_edit = 'R'; + o_lnum = curwin->w_cursor.lnum; + o_eol = (gchar_cursor() == NUL); + goto doESCkey; + + /* Hitting the help key in insert mode is like <ESC> <Help> */ + case K_HELP: + case K_F1: + stuffcharReadbuff(K_HELP); + /*FALLTHROUGH*/ + + case ESC: /* an escape ends input mode */ + if (echeck_abbr(ESC + ABBR_OFF)) + break; + /*FALLTHROUGH*/ + + case Ctrl('C'): +doESCkey: + temp = curwin->w_cursor.col; + if (!arrow_used) + { + AppendToRedobuff(ESC_STR); + + if (--count > 0) /* repeat what was typed */ + { + (void)start_redo_ins(); + continue; + } + stop_insert(&curwin->w_cursor); + if (dollar_vcol) + { + dollar_vcol = 0; + /* may have to redraw status line if this was the + * first change, show "[+]" */ + if (curwin->w_redr_status == TRUE) + must_redraw = NOT_VALID; + else + need_redraw = TRUE; + } + } + if (need_redraw) + updateline(); + + /* When an autoindent was removed, curswant stays after the + * indent */ + if (!restart_edit && (colnr_t)temp == curwin->w_cursor.col) + curwin->w_set_curswant = TRUE; + + /* + * The cursor should end up on the last inserted character. + */ + if (curwin->w_cursor.col != 0 && + (!restart_edit || gchar_cursor() == NUL) +#ifdef RIGHTLEFT + && !revins +#endif + ) + --curwin->w_cursor.col; + if (State == REPLACE) + replace_flush(); /* free replace stack */ + State = NORMAL; +#ifdef USE_MOUSE + setmouse(); +#endif + /* inchar() may have deleted the "INSERT" message */ + /* for CTRL-O we display -- INSERT COMMAND -- */ + if (Recording || restart_edit) + showmode(); + else if (p_smd) + MSG(""); + old_indent = 0; + return (c == Ctrl('O')); + + /* + * Insert the previously inserted text. + * For ^@ the trailing ESC will end the insert, unless there + * is an error. + */ + case K_ZERO: + case NUL: + case Ctrl('A'): + if (stuff_inserted(NUL, 1L, (c == Ctrl('A'))) == FAIL && + c != Ctrl('A')) + goto doESCkey; /* quit insert mode */ + break; + + /* + * insert the contents of a register + */ + case Ctrl('R'): + if (NextScreen != NULL) + screen_outchar('"', curwin->w_winpos + curwin->w_row, +#ifdef RIGHTLEFT + curwin->w_p_rl ? (int)Columns - 1 - curwin->w_col : +#endif + curwin->w_col); + if (!add_to_showcmd(c, FALSE)) + setcursor(); + /* don't map the register name. This also prevents the + * mode message to be deleted when ESC is hit */ + ++no_mapping; +#ifdef HAVE_LANGMAP + cc = vgetc(); + LANGMAP_ADJUST(cc, TRUE); + if (insertbuf(cc) == FAIL) +#else + if (insertbuf(vgetc()) == FAIL) +#endif + { + beep_flush(); + need_redraw = TRUE; /* remove the '"' */ + } + --no_mapping; + clear_showcmd(); + break; + +#ifdef RIGHTLEFT + case Ctrl('B'): /* toggle reverse insert mode */ + p_ri = !p_ri; + revins = (State == INSERT && p_ri); + if (revins) + undisplay_dollar(); + showmode(); + break; + + case Ctrl('_'): /* toggle language: khmap and revins */ + /* Move to end of reverse inserted text */ + if (revins && revinschars && revinsscol >= 0) + while (gchar_cursor() != NUL && revinschars--) + ++curwin->w_cursor.col; + p_ri = !p_ri; + revins = (State == INSERT && p_ri); + if (revins) + { + revinsscol = curwin->w_cursor.col; + revinslegal++; + revinschars = 0; + undisplay_dollar(); + } + else + revinsscol = -1; + p_hkmap = curwin->w_p_rl ^ p_ri; /* be consistent! */ + showmode(); + break; +#endif + + /* + * If the cursor is on an indent, ^T/^D insert/delete one + * shiftwidth. Otherwise ^T/^D behave like a "<<" or ">>". + * Always round the indent to 'shiftwith', this is compatible + * with vi. But vi only supports ^T and ^D after an + * autoindent, we support it everywhere. + */ + case Ctrl('D'): /* make indent one shiftwidth smaller */ +#ifdef INSERT_EXPAND + if (ctrl_x_mode == CTRL_X_PATH_DEFINES) + goto docomplete; +#endif /* INSERT_EXPAND */ + /* FALLTHROUGH */ + case Ctrl('T'): /* make indent one shiftwidth greater */ + stop_arrow(); + AppendCharToRedobuff(c); + + /* + * 0^D and ^^D: remove all indent. + */ + if ((lastc == '0' || lastc == '^') && curwin->w_cursor.col) + { + --curwin->w_cursor.col; + (void)delchar(FALSE); /* delete the '^' or '0' */ + if (lastc == '^') + old_indent = get_indent(); /* remember curr. indent */ + change_indent(INDENT_SET, 0, TRUE); + } + else + change_indent(c == Ctrl('D') ? INDENT_DEC : INDENT_INC, + 0, TRUE); + + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; +#ifdef CINDENT + can_cindent = FALSE; /* no cindenting after ^D or ^T */ +#endif + goto redraw; + + case K_DEL: + stop_arrow(); + if (gchar_cursor() == NUL) /* delete newline */ + { + temp = curwin->w_cursor.col; + if (!p_bs || /* only if 'bs' set */ + u_save((linenr_t)(curwin->w_cursor.lnum - 1), + (linenr_t)(curwin->w_cursor.lnum + 2)) == FAIL || + do_join(FALSE, TRUE) == FAIL) + beep_flush(); + else + curwin->w_cursor.col = temp; + } + else if (delchar(FALSE) == FAIL)/* delete char under cursor */ + beep_flush(); + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + AppendCharToRedobuff(c); + goto redraw; + + case K_BS: + case Ctrl('H'): + mode = BACKSPACE_CHAR; +dodel: + /* can't delete anything in an empty file */ + /* can't backup past first character in buffer */ + /* can't backup past starting point unless 'backspace' > 1 */ + /* can backup to a previous line if 'backspace' == 0 */ + if (bufempty() || ( +#ifdef RIGHTLEFT + !revins && +#endif + ((curwin->w_cursor.lnum == 1 && + curwin->w_cursor.col <= 0) || + (p_bs < 2 && (arrow_used || + (curwin->w_cursor.lnum == Insstart.lnum && + curwin->w_cursor.col <= Insstart.col) || + (curwin->w_cursor.col <= 0 && p_bs == 0)))))) + { + beep_flush(); + goto redraw; + } + + stop_arrow(); +#ifdef CINDENT + if (inindent(0)) + can_cindent = FALSE; +#endif +#ifdef RIGHTLEFT + if (revins) /* put cursor after last inserted char */ + inc_cursor(); +#endif + if (curwin->w_cursor.col <= 0) /* delete newline! */ + { + lnum = Insstart.lnum; + if (curwin->w_cursor.lnum == Insstart.lnum +#ifdef RIGHTLEFT + || revins +#endif + ) + { + if (u_save((linenr_t)(curwin->w_cursor.lnum - 2), + (linenr_t)(curwin->w_cursor.lnum + 1)) == FAIL) + goto redraw; + --Insstart.lnum; + Insstart.col = 0; + } + /* + * In replace mode: + * cc < 0: NL was inserted, delete it + * cc >= 0: NL was replaced, put original characters back + */ + cc = -1; + if (State == REPLACE) + cc = replace_pop(); + /* in replace mode, in the line we started replacing, we + only move the cursor */ + if (State != REPLACE || curwin->w_cursor.lnum > lnum) + { + temp = gchar_cursor(); /* remember current char */ + --curwin->w_cursor.lnum; + (void)do_join(FALSE, curs_rows() == OK); + if (temp == NUL && gchar_cursor() != NUL) + ++curwin->w_cursor.col; + /* + * in REPLACE mode we have to put back the text that + * was replace by the NL. On the replace stack is + * first a NUL-terminated sequence of characters that + * were deleted and then the character that NL + * replaced. + */ + if (State == REPLACE) + { + /* + * Do the next ins_char() in NORMAL state, to + * prevent ins_char() from replacing characters and + * avoiding showmatch(). + */ + State = NORMAL; + /* + * restore blanks deleted after cursor + */ + while (cc > 0) + { + temp = curwin->w_cursor.col; + ins_char(cc); + curwin->w_cursor.col = temp; + cc = replace_pop(); + } + cc = replace_pop(); + if (cc > 0) + { + ins_char(cc); + dec_cursor(); + } + State = REPLACE; + } + } + else + dec_cursor(); + did_ai = FALSE; + } + else + { +#ifdef RIGHTLEFT + if (revins) /* put cursor on last inserted char */ + dec_cursor(); +#endif + mincol = 0; + /* keep indent */ + if (mode == BACKSPACE_LINE && curbuf->b_p_ai +#ifdef RIGHTLEFT + && !revins +#endif + ) + { + temp = curwin->w_cursor.col; + beginline(TRUE); + if (curwin->w_cursor.col < (colnr_t)temp) + mincol = curwin->w_cursor.col; + curwin->w_cursor.col = temp; + } + + /* delete upto starting point, start of line or previous + * word */ + do + { +#ifdef RIGHTLEFT + if (!revins) /* put cursor on char to be deleted */ +#endif + dec_cursor(); + + /* start of word? */ + if (mode == BACKSPACE_WORD && + !vim_isspace(gchar_cursor())) + { + mode = BACKSPACE_WORD_NOT_SPACE; + temp = iswordchar(gchar_cursor()); + } + /* end of word? */ + else if (mode == BACKSPACE_WORD_NOT_SPACE && + (vim_isspace(cc = gchar_cursor()) || + iswordchar(cc) != temp)) + { +#ifdef RIGHTLEFT + if (!revins) +#endif + inc_cursor(); +#ifdef RIGHTLEFT + else if (State == REPLACE) + dec_cursor(); +#endif + break; + } + if (State == REPLACE) + { + /* + * cc < 0: replace stack empty, just move cursor + * cc == 0: character was inserted, delete it + * cc > 0: character was replace, put original back + */ + cc = replace_pop(); + if (cc > 0) + pchar_cursor(cc); + else if (cc == 0) + (void)delchar(FALSE); + } + else /* State != REPLACE */ + { + (void)delchar(FALSE); +#ifdef RIGHTLEFT + if (revinschars) + { + revinschars--; + revinslegal++; + } + if (revins && gchar_cursor() == NUL) + break; +#endif + } + /* Just a single backspace?: */ + if (mode == BACKSPACE_CHAR) + break; + } while ( +#ifdef RIGHTLEFT + revins || +#endif + (curwin->w_cursor.col > mincol && + (curwin->w_cursor.lnum != Insstart.lnum || + curwin->w_cursor.col != Insstart.col))); + did_backspace = TRUE; + } + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + if (curwin->w_cursor.col <= 1) + did_ai = FALSE; + /* + * It's a little strange to put backspaces into the redo + * buffer, but it makes auto-indent a lot easier to deal + * with. + */ + AppendCharToRedobuff(c); +redraw: + need_redraw = TRUE; + break; + + case Ctrl('W'): /* delete word before cursor */ + mode = BACKSPACE_WORD; + goto dodel; + + case Ctrl('U'): /* delete inserted text in current line */ + mode = BACKSPACE_LINE; + goto dodel; + +#ifdef USE_MOUSE + case K_LEFTMOUSE: + case K_LEFTDRAG: + case K_LEFTRELEASE: + case K_MIDDLEMOUSE: + case K_MIDDLEDRAG: + case K_MIDDLERELEASE: + case K_RIGHTMOUSE: + case K_RIGHTDRAG: + case K_RIGHTRELEASE: +#ifdef USE_GUI + /* When GUI is active, also move/paste when 'mouse' is empty */ + if (!gui.in_use) +#endif + if (!mouse_has(MOUSE_INSERT)) + break; + + undisplay_dollar(); + tpos = curwin->w_cursor; + if (do_mouse(c, BACKWARD, 1L, FALSE)) + { + start_arrow(&tpos); +# ifdef CINDENT + can_cindent = TRUE; +# endif + } + + break; + + case K_IGNORE: + break; +#endif + +#ifdef USE_GUI + case K_SCROLLBAR: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (gui_do_scroll()) + { + start_arrow(&tpos); +# ifdef CINDENT + can_cindent = TRUE; +# endif + } + break; + + case K_HORIZ_SCROLLBAR: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (gui_do_horiz_scroll()) + { + start_arrow(&tpos); +#ifdef CINDENT + can_cindent = TRUE; +#endif + } + break; +#endif + + case K_LEFT: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (oneleft() == OK) + { + start_arrow(&tpos); +#ifdef RIGHTLEFT + /* If exit reversed string, position is fixed */ + if (revinsscol != -1 && + (int)curwin->w_cursor.col >= revinsscol) + revinslegal++; + revinschars++; +#endif + } + + /* + * if 'whichwrap' set for cursor in insert mode may go to + * previous line + */ + else if (vim_strchr(p_ww, '[') != NULL && + curwin->w_cursor.lnum > 1) + { + start_arrow(&tpos); + --(curwin->w_cursor.lnum); + coladvance(MAXCOL); + curwin->w_set_curswant = TRUE; /* so we stay at the end */ + } + else + beep_flush(); + break; + + case K_HOME: + undisplay_dollar(); + tpos = curwin->w_cursor; + if ((mod_mask & MOD_MASK_CTRL)) + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + curwin->w_curswant = 0; + start_arrow(&tpos); + break; + + case K_END: + undisplay_dollar(); + tpos = curwin->w_cursor; + if ((mod_mask & MOD_MASK_CTRL)) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + coladvance(MAXCOL); + curwin->w_curswant = MAXCOL; + start_arrow(&tpos); + break; + + case K_S_LEFT: + undisplay_dollar(); + if (curwin->w_cursor.lnum > 1 || curwin->w_cursor.col > 0) + { + start_arrow(&curwin->w_cursor); + (void)bck_word(1L, 0, FALSE); + } + else + beep_flush(); + break; + + case K_RIGHT: + undisplay_dollar(); + if (gchar_cursor() != NUL) + { + start_arrow(&curwin->w_cursor); + curwin->w_set_curswant = TRUE; + ++curwin->w_cursor.col; +#ifdef RIGHTLEFT + revinslegal++; + if (revinschars) + revinschars--; +#endif + } + /* if 'whichwrap' set for cursor in insert mode may go + * to next line */ + else if (vim_strchr(p_ww, ']') != NULL && + curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) + { + start_arrow(&curwin->w_cursor); + curwin->w_set_curswant = TRUE; + ++curwin->w_cursor.lnum; + curwin->w_cursor.col = 0; + } + else + beep_flush(); + break; + + case K_S_RIGHT: + undisplay_dollar(); + if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count || + gchar_cursor() != NUL) + { + start_arrow(&curwin->w_cursor); + (void)fwd_word(1L, 0, 0); + } + else + beep_flush(); + break; + + case K_UP: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (cursor_up(1L) == OK) + { + start_arrow(&tpos); +#ifdef CINDENT + can_cindent = TRUE; +#endif + } + else + beep_flush(); + break; + + case K_S_UP: + case K_PAGEUP: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (onepage(BACKWARD, 1L) == OK) + { + start_arrow(&tpos); +#ifdef CINDENT + can_cindent = TRUE; +#endif + } + else + beep_flush(); + break; + + case K_DOWN: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (cursor_down(1L) == OK) + { + start_arrow(&tpos); +#ifdef CINDENT + can_cindent = TRUE; +#endif + } + else + beep_flush(); + break; + + case K_S_DOWN: + case K_PAGEDOWN: + undisplay_dollar(); + tpos = curwin->w_cursor; + if (onepage(FORWARD, 1L) == OK) + { + start_arrow(&tpos); +#ifdef CINDENT + can_cindent = TRUE; +#endif + } + else + beep_flush(); + break; + + case TAB: /* TAB or Complete patterns along path */ +#ifdef INSERT_EXPAND + if (ctrl_x_mode == CTRL_X_PATH_PATTERNS) + goto docomplete; +#endif /* INSERT_EXPAND */ + + if (Insstart_blank_vcol == MAXCOL && + curwin->w_cursor.lnum == Insstart.lnum) + Insstart_blank_vcol = curwin->w_virtcol; + if (echeck_abbr(TAB + ABBR_OFF)) + break; + i = inindent(0); +#ifdef CINDENT + if (i) + can_cindent = FALSE; +#endif + if (!curbuf->b_p_et && !(p_sta && i)) + goto normalchar; + + stop_arrow(); + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + AppendToRedobuff((char_u *)"\t"); + + if (p_sta && i) /* insert tab in indent */ + { + change_indent(INDENT_INC, 0, p_sr); + goto redraw; + } + + /* + * p_et is set: expand a tab into spaces + */ + temp = (int)curbuf->b_p_ts; + temp -= curwin->w_virtcol % temp; + + /* + * insert the first space with ins_char(); it will delete one + * char in replace mode. Insert the rest with ins_str(); it + * will not delete any chars + */ + ins_char(' '); + while (--temp) + { + ins_str((char_u *)" "); + if (State == REPLACE) /* no char replaced */ + replace_push(NUL); + } + goto redraw; + + case CR: + case NL: + if (echeck_abbr(c + ABBR_OFF)) + break; + stop_arrow(); + if (State == REPLACE) + replace_push(NUL); +#ifdef RIGHTLEFT + /* NL in reverse insert will allways start in the end of + * current line. */ + if (revins) + while (gchar_cursor() != NUL) + ++curwin->w_cursor.col; +#endif + + AppendToRedobuff(NL_STR); + if (has_format_option(FO_RET_COMS)) + fo_do_comments = TRUE; + i = Opencmd(FORWARD, TRUE, FALSE); + fo_do_comments = FALSE; +#ifdef CINDENT + can_cindent = TRUE; +#endif + if (!i) + goto doESCkey; /* out of memory */ + break; + +#ifdef DIGRAPHS + case Ctrl('K'): +#ifdef INSERT_EXPAND + if (ctrl_x_mode == CTRL_X_DICTIONARY) + goto docomplete; +#endif + if (NextScreen != NULL) + screen_outchar('?', curwin->w_winpos + curwin->w_row, +#ifdef RIGHTLEFT + curwin->w_p_rl ? (int)Columns - 1 - curwin->w_col : +#endif + curwin->w_col); + if (!add_to_showcmd(c, FALSE)) + setcursor(); + /* don't map the digraph chars. This also prevents the + * mode message to be deleted when ESC is hit */ + ++no_mapping; + ++allow_keys; + c = vgetc(); + --no_mapping; + --allow_keys; + if (IS_SPECIAL(c)) /* special key */ + { + clear_showcmd(); + insert_special(c, TRUE); + need_redraw = TRUE; + break; + } + if (c != ESC) + { + if (charsize(c) == 1 && NextScreen != NULL) + screen_outchar(c, curwin->w_winpos + curwin->w_row, +#ifdef RIGHTLEFT + curwin->w_p_rl ? (int)Columns - 1 - curwin->w_col : +#endif + curwin->w_col); + if (!add_to_showcmd(c, FALSE)) + setcursor(); + ++no_mapping; + ++allow_keys; + cc = vgetc(); + --no_mapping; + --allow_keys; + if (cc != ESC) + { + AppendToRedobuff((char_u *)"\026"); /* CTRL-V */ + c = getdigraph(c, cc, TRUE); + clear_showcmd(); + goto normalchar; + } + } + clear_showcmd(); + need_redraw = TRUE; + break; +#else /* DIGRAPHS */ +# ifdef INSERT_EXPAND + case Ctrl('K'): + if (ctrl_x_mode != CTRL_X_DICTIONARY) + goto normalchar; + goto docomplete; +# endif /* INSERT_EXPAND */ +#endif /* DIGRAPHS */ + +#ifdef INSERT_EXPAND + case Ctrl(']'): /* 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 Ctrl('L'): /* Whole line completion after ^X */ + if (ctrl_x_mode != CTRL_X_WHOLE_LINE) + goto normalchar; + /* FALLTHROUGH */ + + case Ctrl('P'): /* Do previous pattern completion */ + case Ctrl('N'): /* Do next pattern completion */ +docomplete: + if (c == Ctrl('P') || c == Ctrl('L')) + complete_direction = BACKWARD; + else + complete_direction = FORWARD; + quick_m = mesg = NULL; /* No message by default */ + if (!started_completion) + { + /* First time we hit ^N or ^P (in a row, I mean) */ + + /* Turn off 'sm' so we don't show matches with ^X^L */ + save_sm = p_sm; + p_sm = FALSE; + + if (ctrl_x_mode == 0) + { + edit_submode = (char_u *)" Keyword completion (^P/^N)"; + showmode(); + } + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + stop_arrow(); + done_dir = 0; + first_match_pos = curwin->w_cursor; + ptr = tmp_ptr = ml_get(first_match_pos.lnum); + complete_col = first_match_pos.col; + temp = (int)complete_col - 1; + + /* Work out completion pattern and original text -- webb */ + if (ctrl_x_mode == 0 || (ctrl_x_mode & CTRL_X_WANT_IDENT)) + { + if (temp < 0 || !iswordchar(ptr[temp])) + { + /* Match any word of at least two chars */ + complete_pat = strsave((char_u *)"\\<\\k\\k"); + if (complete_pat == NULL) + break; + tmp_ptr += complete_col; + temp = 0; + } + else + { + while (temp >= 0 && iswordchar(ptr[temp])) + temp--; + tmp_ptr += ++temp; + if ((temp = (int)complete_col - temp) == 1) + { + /* Only match word with at least two + * chars -- webb + */ + sprintf((char *)IObuff, "\\<%c\\k", *tmp_ptr); + complete_pat = strsave(IObuff); + if (complete_pat == NULL) + break; + } + else + { + complete_pat = alloc(temp + 3); + if (complete_pat == NULL) + break; + sprintf((char *)complete_pat, "\\<%.*s", temp, + tmp_ptr); + } + } + } + else if (ctrl_x_mode == CTRL_X_WHOLE_LINE) + { + tmp_ptr = skipwhite(ptr); + temp = (int)complete_col - (tmp_ptr - ptr); + complete_pat = strnsave(tmp_ptr, temp); + if (complete_pat == NULL) + break; + } + else if (ctrl_x_mode == CTRL_X_FILES) + { + while (temp >= 0 && isfilechar(ptr[temp])) + temp--; + tmp_ptr += ++temp; + temp = (int)complete_col - temp; + complete_pat = addstar(tmp_ptr, temp); + if (complete_pat == NULL) + break; + } + original_text = strnsave(tmp_ptr, temp); + if (original_text == NULL) + { + vim_free(complete_pat); + complete_pat = NULL; + break; + } + + complete_col = tmp_ptr - ptr; + first_match_pos.col -= temp; + + /* So that ^N can match word immediately after cursor */ + if (ctrl_x_mode == 0) + dec(&first_match_pos); + + last_match_pos = first_match_pos; + + /* Get list of all completions now, if appropriate */ + if (ctrl_x_mode == CTRL_X_PATH_PATTERNS || + ctrl_x_mode == CTRL_X_PATH_DEFINES) + { + started_completion = TRUE; + find_pattern_in_path(complete_pat, + (int)STRLEN(complete_pat), FALSE, FALSE, + (ctrl_x_mode == CTRL_X_PATH_DEFINES) ? FIND_DEFINE + : FIND_ANY, 1L, ACTION_EXPAND, + (linenr_t)1, (linenr_t)MAXLNUM); + + if (make_cyclic() > 1) + { + sprintf((char *)IObuff, "There are %d matches", + count_completions()); + mesg = IObuff; + } + } + else if (ctrl_x_mode == CTRL_X_DICTIONARY) + { + started_completion = TRUE; + if (*p_dict == NUL) + mesg = (char_u *)"'dictionary' option is empty"; + else + { + complete_dictionaries(complete_pat, + complete_direction); + if (make_cyclic() > 1) + { + sprintf((char *)IObuff, + "There are %d matching words", + count_completions()); + mesg = IObuff; + } + } + } + else if (ctrl_x_mode == CTRL_X_TAGS) + { + started_completion = TRUE; + /* set reg_ic according to p_ic, p_scs and pat */ + set_reg_ic(complete_pat); + prog = vim_regcomp(complete_pat); + if (prog != NULL && + find_tags(NULL, prog, &num_matches, &matches, FALSE) + == OK && num_matches > 0) + { + for (i = 0; i < num_matches; i++) + if (add_completion(matches[i], -1, NULL, + FORWARD) == RET_ERROR) + break; + FreeWild(num_matches, matches); + vim_free(prog); + if (make_cyclic() > 1) + { + sprintf((char *)IObuff, + "There are %d matching tags", + count_completions()); + mesg = IObuff; + } + } + else + { + vim_free(prog); + vim_free(complete_pat); + complete_pat = NULL; + } + } + else if (ctrl_x_mode == CTRL_X_FILES) + { + started_completion = TRUE; + expand_interactively = TRUE; + if (ExpandWildCards(1, &complete_pat, &num_matches, + &matches, FALSE, FALSE) == OK) + { + for (i = 0; i < num_matches; i++) + if (add_completion(matches[i], -1, NULL, + FORWARD) == RET_ERROR) + break; + FreeWild(num_matches, matches); + if (make_cyclic() > 1) + { + sprintf((char *)IObuff, + "There are %d matching file names", + count_completions()); + mesg = IObuff; + } + } + else + { + vim_free(complete_pat); + complete_pat = NULL; + } + expand_interactively = FALSE; + } + } + /* + * In insert mode: Delete the typed part. + * In replace mode: Put the old characters back, if any. + */ + while (curwin->w_cursor.col > complete_col) + { + curwin->w_cursor.col--; + if (State == REPLACE) + { + if ((cc = replace_pop()) > 0) + pchar(curwin->w_cursor, cc); + } + else + delchar(FALSE); + } + complete_pos = NULL; + if (started_completion && curr_match == NULL && + (p_ws || done_dir == BOTH_DIRECTIONS)) + quick_m = e_patnotf; + else if (curr_match != NULL && complete_direction == FORWARD && + curr_match->next != NULL) + curr_match = curr_match->next; + else if (curr_match != NULL && complete_direction == BACKWARD && + curr_match->prev != NULL) + curr_match = curr_match->prev; + else + { + complete_pos = (complete_direction == FORWARD) ? + &last_match_pos : &first_match_pos; + /* + * If 'infercase' is set, don't use 'smartcase' here + */ + save_p_scs = p_scs; + if (curbuf->b_p_inf) + p_scs = FALSE; + for (;;) + { + if (ctrl_x_mode == CTRL_X_WHOLE_LINE) + temp = search_for_exact_line(complete_pos, + complete_direction, complete_pat); + else + temp = searchit(complete_pos, complete_direction, + complete_pat, 1L, + SEARCH_KEEP + SEARCH_NFMSG, RE_LAST); + if (temp == FAIL) + { + if (!p_ws && done_dir != -complete_direction) + { + /* + * With nowrapscan, we haven't finished + * looking in the other direction yet -- webb + */ + temp = OK; + done_dir = complete_direction; + } + else if (!p_ws) + done_dir = BOTH_DIRECTIONS; + break; + } + if (!started_completion) + { + started_completion = TRUE; + first_match_pos = *complete_pos; + last_match_pos = *complete_pos; + } + else if (first_match_pos.lnum == last_match_pos.lnum && + first_match_pos.col == last_match_pos.col) + { + /* We have found all the matches in this file */ + temp = FAIL; + break; + } + ptr = ml_get_pos(complete_pos); + if (ctrl_x_mode == CTRL_X_WHOLE_LINE) + temp = STRLEN(ptr); + else + { + tmp_ptr = ptr; + temp = 0; + while (*tmp_ptr != NUL && iswordchar(*tmp_ptr++)) + temp++; + } + if (add_completion_and_infercase(ptr, temp, NULL, + complete_direction) != FAIL) + { + temp = OK; + break; + } + } + p_scs = save_p_scs; + } + if (complete_pos != NULL && temp == FAIL) + { + int tot; + + tot = count_completions(); /* Total num matches */ + if (curr_match != NULL) + (void)make_cyclic(); + if (tot > 1) + { + sprintf((char *)IObuff, + "All %d matches have now been found", tot); + mesg = IObuff; + } + else if (tot == 0) + quick_m = e_patnotf; + } + + /* eat the ESC to avoid leaving insert mode */ + if (got_int) + { + (void)vgetc(); + got_int = FALSE; + } + + /* + * When using match from another file, show the file name. + */ + if (curr_match != NULL) + ptr = curr_match->str; + else /* back to what has been typed */ + ptr = original_text; + + if (curr_match == NULL || curr_match->original) + { + edit_submode_extra = (char_u *)"Back at original"; + edit_submode_highl = TRUE; + } + else if (first_match != NULL && first_match->next != NULL && + (first_match->next == first_match || + first_match->next->original)) + { + edit_submode_extra = (char_u *)"(the only match)"; + edit_submode_highl = FALSE; + } + + /* + * Use ins_char() to insert the text, it is a bit slower than + * ins_str(), but it takes care of replace mode. + */ + if (ptr != NULL) + while (*ptr) + ins_char(*ptr++); + + started_completion = TRUE; + need_redraw = TRUE; + (void)set_highlight('r'); + msg_highlight = TRUE; + if (mesg != NULL) + { + msg(mesg); + mch_delay(1000L, FALSE); + } + else if (quick_m != NULL) + msg(quick_m); + else if (edit_submode_extra != NULL) + showmode(); + edit_submode_extra = NULL; + msg_highlight = FALSE; + + /* + * If there is a file name for the match, overwrite any + * previous message, it's more interesting to know where the + * match comes from, except when using the dictionary. + * Truncate the file name to avoid a wait for return. + */ + if (curr_match != NULL && curr_match->fname != NULL && + (ctrl_x_mode != CTRL_X_DICTIONARY || + (mesg == NULL && quick_m == NULL))) + { + STRCPY(IObuff, "match in file "); + i = (strsize(curr_match->fname) + 16) - sc_col; + if (i <= 0) + i = 0; + else + STRCAT(IObuff, "<"); + STRCAT(IObuff, curr_match->fname + i); + msg(IObuff); + } + break; +#endif /* INSERT_EXPAND */ + + case Ctrl('Y'): /* copy from previous line */ +#ifdef INSERT_EXPAND + if (ctrl_x_mode == CTRL_X_SCROLL) + { + scrolldown_clamp(); + updateScreen(VALID); + break; + } +#endif /* INSERT_EXPAND */ + lnum = curwin->w_cursor.lnum - 1; + goto copychar; + + case Ctrl('E'): /* copy from next line */ +#ifdef INSERT_EXPAND + if (ctrl_x_mode == CTRL_X_SCROLL) + { + scrollup_clamp(); + updateScreen(VALID); + break; + } +#endif /* INSERT_EXPAND */ + lnum = curwin->w_cursor.lnum + 1; +copychar: + if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) + { + beep_flush(); + break; + } + + /* try to advance to the cursor column */ + temp = 0; + ptr = ml_get(lnum); + while ((colnr_t)temp < curwin->w_virtcol && *ptr) + temp += lbr_chartabsize(ptr++, (colnr_t)temp); + + if ((colnr_t)temp > curwin->w_virtcol) + --ptr; + if ((c = *ptr) == NUL) + { + beep_flush(); + break; + } + + /*FALLTHROUGH*/ + default: +normalchar: + /* + * do some very smart indenting when entering '{' or '}' + */ + if (((did_si || can_si_back) && c == '{') || + (can_si && c == '}')) + { + FPOS *pos, old_pos; + + /* for '}' set indent equal to indent of line + * containing matching '{' + */ + if (c == '}' && (pos = findmatch('{')) != NULL) + { + old_pos = curwin->w_cursor; + /* + * If the matching '{' has a ')' immediately before it + * (ignoring white-space), then line up with the start + * of the line containing the matching '(' if there is + * one. This handles the case where an + * "if (..\n..) {" statement continues over multiple + * lines -- webb + */ + ptr = ml_get(pos->lnum); + i = pos->col; + if (i > 0) /* skip blanks before '{' */ + while (--i > 0 && vim_iswhite(ptr[i])) + ; + curwin->w_cursor.lnum = pos->lnum; + curwin->w_cursor.col = i; + if (ptr[i] == ')' && (pos = findmatch('(')) != NULL) + curwin->w_cursor = *pos; + i = get_indent(); + curwin->w_cursor = old_pos; + set_indent(i, TRUE); + } + else if (curwin->w_cursor.col > 0) + { + /* + * when inserting '{' after "O" reduce indent, but not + * more than indent of previous line + */ + temp = TRUE; + if (c == '{' && can_si_back && + curwin->w_cursor.lnum > 1) + { + old_pos = curwin->w_cursor; + i = get_indent(); + while (curwin->w_cursor.lnum > 1) + { + ptr = skipwhite( + ml_get(--(curwin->w_cursor.lnum))); + /* ignore empty lines and lines starting with + * '#'. + */ + if (*ptr != '#' && *ptr != NUL) + break; + } + if (get_indent() >= i) + temp = FALSE; + curwin->w_cursor = old_pos; + } + if (temp) + shift_line(TRUE, FALSE, 1); + } + } + /* set indent of '#' always to 0 */ + if (curwin->w_cursor.col > 0 && can_si && c == '#') + { + /* remember current indent for next line */ + old_indent = get_indent(); + set_indent(0, TRUE); + } + + if (c == ' ') + { +#ifdef CINDENT + if (inindent(0)) + can_cindent = FALSE; +#endif + if (Insstart_blank_vcol == MAXCOL && + curwin->w_cursor.lnum == Insstart.lnum) + Insstart_blank_vcol = curwin->w_virtcol; + } + + if (iswordchar(c) || !echeck_abbr(c)) + { + insert_special(c, FALSE); + need_redraw = TRUE; +#ifdef RIGHTLEFT + revinslegal++; + revinschars++; +#endif + } + break; + } /* end of switch (c) */ + +#ifdef CINDENT + if (curbuf->b_p_cin && can_cindent +# ifdef INSERT_EXPAND + && !ctrl_x_mode +# endif + ) + { +force_cindent: + /* + * Indent now if a key was typed that is in 'cinkeys'. + */ + if (in_cinkeys(c, ' ', line_is_white)) + { + stop_arrow(); + + /* re-indent the current line */ + fixthisline(get_c_indent); + + /* draw the changes on the screen later */ + need_redraw = TRUE; + } + } +#endif /* CINDENT */ + + } /* for (;;) */ +} + +/* + * 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). + */ + static void +change_indent(type, amount, round) + int type; + int amount; + int round; +{ + 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; + + /* for the following tricks we don't want list mode */ + save_p_list = curwin->w_p_list; + if (save_p_list) + { + curwin->w_p_list = FALSE; + curs_columns(FALSE); /* recompute w_virtcol */ + } + vcol = curwin->w_virtcol; + + /* determine offset from first non-blank */ + new_cursor_col = curwin->w_cursor.col; + beginline(TRUE); + 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; + + /* + * Set the new indent. The cursor will be put on the first non-blank. + */ + if (type == INDENT_SET) + set_indent(amount, TRUE); + else + shift_line(type == INDENT_DEC, round, 1); + 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) + 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 = (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; + ++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 = new_cursor_col; + i = (int)curwin->w_virtcol - vcol; + ptr = alloc(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 = Insstart.col; + } + + curwin->w_p_list = save_p_list; + + if (new_cursor_col <= 0) + curwin->w_cursor.col = 0; + else + curwin->w_cursor.col = new_cursor_col; + curwin->w_set_curswant = TRUE; + + /* + * May have to adjust the start of the insert. + */ + if ((State & INSERT) && curwin->w_cursor.lnum == Insstart.lnum && + Insstart.col != 0) + { + if ((int)Insstart.col <= insstart_less) + Insstart.col = 0; + else + Insstart.col -= insstart_less; + } +} + +#ifdef INSERT_EXPAND +/* + * Is the character 'c' a valid key to keep us in the current ctrl-x mode? + * -- webb + */ + int +is_ctrl_x_key(c) + int c; +{ + switch (ctrl_x_mode) + { + case 0: /* Not in any ctrl-x mode */ + break; + case CTRL_X_NOT_DEFINED_YET: + if (c == Ctrl('X') || c == Ctrl('Y') || c == Ctrl('E') || + c == Ctrl('L') || c == Ctrl('F') || c == Ctrl(']') || + c == Ctrl('I') || c == Ctrl('D') || c == Ctrl('P') || + c == Ctrl('N')) + return TRUE; + break; + case CTRL_X_SCROLL: + if (c == Ctrl('Y') || c == Ctrl('E')) + return TRUE; + break; + case CTRL_X_WHOLE_LINE: + if (c == Ctrl('L') || c == Ctrl('P') || c == Ctrl('N')) + return TRUE; + break; + case CTRL_X_FILES: + if (c == Ctrl('F') || c == Ctrl('P') || c == Ctrl('N')) + return TRUE; + break; + case CTRL_X_DICTIONARY: + if (c == Ctrl('K') || c == Ctrl('P') || c == Ctrl('N')) + return TRUE; + break; + case CTRL_X_TAGS: + if (c == Ctrl(']') || c == Ctrl('P') || c == Ctrl('N')) + return TRUE; + break; + case CTRL_X_PATH_PATTERNS: + if (c == Ctrl('P') || c == Ctrl('N')) + return TRUE; + break; + case CTRL_X_PATH_DEFINES: + if (c == Ctrl('D') || c == Ctrl('P') || c == Ctrl('N')) + return TRUE; + break; + default: + emsg(e_internal); + break; + } + return FALSE; +} + +/* + * This is like add_completion(), 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 infered, ie this tries to work out what case you probably wanted + * the rest of the word to be in -- webb + */ + int +add_completion_and_infercase(str, len, fname, dir) + char_u *str; + int len; + char_u *fname; + int dir; +{ + int has_lower = FALSE; + int was_letter = FALSE; + int orig_len; + int idx; + + if (p_ic && curbuf->b_p_inf && len < IOSIZE) + { + /* Infer case of completed part -- webb */ + orig_len = STRLEN(original_text); + + /* Use IObuff, str would change text in buffer! */ + STRNCPY(IObuff, str, len); + IObuff[len] = NUL; + + /* Rule 1: Were any chars converted to lower? */ + for (idx = 0; idx < orig_len; ++idx) + { + if (islower(original_text[idx])) + { + has_lower = TRUE; + if (isupper(IObuff[idx])) + { + /* Rule 1 is satisfied */ + for (idx = orig_len; idx < len; ++idx) + IObuff[idx] = TO_LOWER(IObuff[idx]); + break; + } + } + } + + /* + * Rule 2: No lower case, 2nd consecutive letter converted to + * upper case. + */ + if (!has_lower) + { + for (idx = 0; idx < orig_len; ++idx) + { + if (was_letter && isupper(original_text[idx]) && + islower(IObuff[idx])) + { + /* Rule 2 is satisfied */ + for (idx = orig_len; idx < len; ++idx) + IObuff[idx] = TO_UPPER(IObuff[idx]); + break; + } + was_letter = isalpha(original_text[idx]); + } + } + + /* Copy the original case of the part we typed */ + STRNCPY(IObuff, original_text, orig_len); + + return add_completion(IObuff, len, fname, dir); + } + return add_completion(str, len, fname, dir); +} + +/* + * Add a match to the list of matches. + * If the given string is already in the list of completions, then return + * FAIL, otherwise add it to the list and return OK. If there is an error, + * maybe because alloc returns NULL, then RET_ERROR is returned -- webb. + */ + static int +add_completion(str, len, fname, dir) + char_u *str; + int len; + char_u *fname; + int dir; +{ + struct Completion *match; + + mch_breakcheck(); + if (got_int) + return RET_ERROR; + if (len < 0) + len = STRLEN(str); + + /* + * If the same match is already present, don't add it. + */ + if (first_match != NULL) + { + match = first_match; + do + { + if (STRNCMP(match->str, str, (size_t)len) == 0 && + match->str[len] == NUL) + return FAIL; + match = match->next; + } while (match != NULL && match != first_match); + } + + /* + * Allocate a new match structure. + * Copy the values to the new match structure. + */ + match = (struct Completion *)alloc((unsigned)sizeof(struct Completion)); + if (match == NULL) + return RET_ERROR; + if ((match->str = strnsave(str, len)) == NULL) + { + vim_free(match); + return RET_ERROR; + } + if (fname != NULL) + match->fname = strsave(fname); /* ignore errors */ + else + match->fname = NULL; + match->original = FALSE; + + /* + * Link the new match structure in the list of matches. + */ + if (first_match == NULL) + { + first_match = curr_match = match; + curr_match->next = curr_match->prev = NULL; + } + else + { + if (dir == FORWARD) + { + match->next = NULL; + match->prev = curr_match; + curr_match->next = match; + curr_match = match; + } + else /* BACKWARD */ + { + match->prev = NULL; + match->next = curr_match; + curr_match->prev = match; + first_match = curr_match = match; + } + } + + return OK; +} + +/* + * Make the completion list cyclic. Add the original text at the end. + * Return the number of matches (excluding the original). + */ + static int +make_cyclic() +{ + struct Completion *match, *orig; + int count = 0; + + if (first_match != NULL) + { + /* + * Find the end of the list. + */ + match = first_match; + count = 1; + while (match->next != NULL) + { + match = match->next; + ++count; + } + + if (original_text != NULL) + { + /* + * Allocate a new structure for the original text. + * Copy the original text to the new structure. + * Link it in the list at the end. + */ + orig = (struct Completion *)alloc((unsigned)sizeof( + struct Completion)); + if (orig != NULL) + { + if ((orig->str = strsave(original_text)) == NULL) + vim_free(orig); + else + { + orig->fname = NULL; + orig->original = TRUE; + orig->prev = match; + match->next = orig; + match = orig; + curr_match = orig; + } + } + } + match->next = first_match; + first_match->prev = match; + } + return count; +} + +/* + * Add any identifiers that match the given pattern to the list of + * completions. + */ + static void +complete_dictionaries(pat, dir) + char_u *pat; + int dir; +{ + struct Completion *save_curr_match = curr_match; + char_u *dict = p_dict; + char_u *ptr; + char_u *buf; + char_u *fname; + int at_start; + FILE *fp; + struct regexp *prog = NULL; + + if ((buf = alloc(LSIZE)) == NULL) + return; + if (curr_match != NULL) + { + while (curr_match->next != NULL) + curr_match = curr_match->next; + } + if (*dict != NUL) + { + (void)set_highlight('r'); + msg_highlight = TRUE; + MSG("Please wait, searching dictionaries"); + set_reg_ic(pat); /* set reg_ic according to p_ic, p_scs and pat */ + reg_magic = p_magic; + prog = vim_regcomp(pat); + } + while (*dict != NUL && prog != NULL && !got_int) + { + /* copy one dictionary file name into buf */ + (void)copy_option_part(&dict, buf, LSIZE, ","); + + fp = fopen((char *)buf, "r"); /* open dictionary file */ + + if (fp != NULL) + { + fname = strsave(buf); /* keep name of file */ + /* + * Read dictionary file line by line. + * Check each line for a match. + */ + while (!got_int && !vim_fgets(buf, LSIZE, fp)) + { + ptr = buf; + at_start = TRUE; + while (vim_regexec(prog, ptr, at_start)) + { + at_start = FALSE; + ptr = prog->startp[0]; + while (iswordchar(*ptr)) + ++ptr; + if (add_completion_and_infercase(prog->startp[0], + (int)(ptr - prog->startp[0]), fname, FORWARD) + == RET_ERROR) + break; + } + line_breakcheck(); + } + fclose(fp); + vim_free(fname); + } + } + vim_free(prog); + if (save_curr_match != NULL) + curr_match = save_curr_match; + else if (dir == BACKWARD) + curr_match = first_match; + vim_free(buf); +} + +/* + * Free the list of completions + */ + static void +free_completions() +{ + struct Completion *match; + + if (first_match == NULL) + return; + curr_match = first_match; + do + { + match = curr_match; + curr_match = curr_match->next; + vim_free(match->str); + vim_free(match->fname); + vim_free(match); + } while (curr_match != NULL && curr_match != first_match); + first_match = curr_match = NULL; +} + +/* + * Return the number of items in the Completion list + */ + static int +count_completions() +{ + struct Completion *match; + int num = 0; + + if (first_match == NULL) + return 0; + match = first_match; + do + { + if (!match->original) /* original string doesn't count */ + num++; + match = match->next; + } while (match != NULL && match != first_match); + return num; +} +#endif /* INSERT_EXPAND */ + +/* + * Next character is interpreted literally. + * A one, two or three digit decimal number is interpreted as its byte value. + * If one or two digits are entered, the next character is given to vungetc(). + */ + int +get_literal() +{ + int cc; + int nc; + int i; + + if (got_int) + return Ctrl('C'); + +#ifdef USE_GUI + /* + * In GUI there is no point inserting the internal code for a special key. + * It is more useful to insert the string "<KEY>" instead. This would + * probably be useful in a text window too, but it would not be + * vi-compatible (maybe there should be an option for it?) -- webb + */ + if (gui.in_use) + ++allow_keys; +#endif + ++no_mapping; /* don't map the next key hits */ + cc = 0; + for (i = 0; i < 3; ++i) + { + do + nc = vgetc(); + while (nc == K_IGNORE || nc == K_SCROLLBAR || nc == K_HORIZ_SCROLLBAR); + if (!(State & CMDLINE)) + add_to_showcmd(nc, FALSE); + if (IS_SPECIAL(nc) || !isdigit(nc)) + break; + cc = cc * 10 + nc - '0'; + if (cc > 255) + cc = 255; /* limit range to 0-255 */ + nc = 0; + } + if (i == 0) /* no number entered */ + { + if (nc == K_ZERO) /* NUL is stored as NL */ + { + cc = '\n'; + nc = 0; + } + else + { + cc = nc; + nc = 0; + } + } + + if (cc == 0) /* NUL is stored as NL */ + cc = '\n'; + + --no_mapping; +#ifdef USE_GUI + if (gui.in_use) + --allow_keys; +#endif + if (nc) + vungetc(nc); + got_int = FALSE; /* CTRL-C typed after CTRL-V is not an interrupt */ + return cc; +} + +/* + * Insert character, taking care of special keys and mod_mask + */ + static void +insert_special(c, allow_modmask) + int c; + int allow_modmask; +{ + char_u *p; + int len; + + /* + * Special function key, translate into "<Key>". Up to the last '>' is + * inserted with ins_str(), so as not to replace characters in replace + * mode. + * Only use mod_mask for special keys, to avoid things like <S-Space>, + * unless 'allow_modmask' is TRUE. + */ + if (IS_SPECIAL(c) || (mod_mask && allow_modmask)) + { + p = get_special_key_name(c, mod_mask); + len = STRLEN(p); + c = p[len - 1]; + if (len > 2) + { + p[len - 1] = NUL; + ins_str(p); + AppendToRedobuff(p); + } + } + insertchar(c, FALSE, -1); +} + +/* + * Special characters in this context are those that need processing other + * than the simple insertion that can be performed here. This includes ESC + * which terminates the insert, and CR/NL which need special processing to + * open up a new line. This routine tries to optimize insertions performed by + * the "redo", "undo" or "put" commands, so it needs to know when it should + * stop and defer processing to the "normal" mechanism. + */ +#define ISSPECIAL(c) ((c) < ' ' || (c) >= DEL) + + void +insertchar(c, force_formatting, second_indent) + unsigned c; + int force_formatting; /* format line regardless of p_fo */ + int second_indent; /* indent for second line if >= 0 */ +{ + int haveto_redraw = FALSE; + int textwidth; + colnr_t leader_len; + int first_line = TRUE; + int fo_ins_blank; + int save_char = NUL; + + stop_arrow(); + + /* + * find out textwidth to be used: + * if 'textwidth' option is set, use it + * else if 'wrapmargin' option is set, use Columns - 'wrapmargin' + * if invalid value, use 0. + * Set default to window width (maximum 79) for "Q" command. + */ + textwidth = curbuf->b_p_tw; + if (textwidth == 0 && curbuf->b_p_wm) + textwidth = Columns - curbuf->b_p_wm; + if (textwidth < 0) + textwidth = 0; + if (force_formatting && textwidth == 0) + { + textwidth = Columns - 1; + if (textwidth > 79) + textwidth = 79; + } + + fo_ins_blank = has_format_option(FO_INS_BLANK); + + /* + * Try to break the line in two or more pieces when: + * - Always do this if we have been called to do formatting only. + * - Otherwise: + * - Don't do this if inserting a blank + * - Don't do this if an existing character is being replaced. + * - Do this if the cursor is not on the line where insert started + * or - 'formatoptions' doesn't have 'l' or the line was not too long + * before the insert. + * - 'formatoptions' doesn't have 'b' or a blank was inserted at or + * before 'textwidth' + */ + if (force_formatting || (!vim_iswhite(c) && + !(State == REPLACE && *ml_get_cursor() != NUL) && + (curwin->w_cursor.lnum != Insstart.lnum || + ((!has_format_option(FO_INS_LONG) || + Insstart_textlen <= (colnr_t)textwidth) && + (!fo_ins_blank || Insstart_blank_vcol <= (colnr_t)textwidth))))) + { + /* + * When 'ai' is off we don't want a space under the cursor to be + * deleted. Replace it with an 'x' temporarily. + */ + if (!curbuf->b_p_ai && vim_iswhite(gchar_cursor())) + { + save_char = gchar_cursor(); + pchar_cursor('x'); + } + while (textwidth && curwin->w_virtcol >= (colnr_t)textwidth) + { + int startcol; /* Cursor column at entry */ + int wantcol; /* column at textwidth border */ + int foundcol; /* column for start of spaces */ + int end_foundcol = 0; /* column for start of word */ + colnr_t len; + + if (!force_formatting && has_format_option(FO_WRAP_COMS)) + fo_do_comments = TRUE; + + /* Don't break until after the comment leader */ + leader_len = get_leader_len(ml_get_curline(), NULL); + if (!force_formatting && leader_len == 0 && + !has_format_option(FO_WRAP)) + + { + textwidth = 0; + break; + } + if ((startcol = curwin->w_cursor.col) == 0) + break; + /* find column of textwidth border */ + coladvance((colnr_t)textwidth); + wantcol = curwin->w_cursor.col; + + curwin->w_cursor.col = startcol - 1; + foundcol = 0; + /* + * Find position to break at. + * Stop at start of line. + * Stop at first entered white when 'formatoptions' has 'v' + */ + while (curwin->w_cursor.col > 0 && + ((!fo_ins_blank && !has_format_option(FO_INS_VI)) || + curwin->w_cursor.lnum != Insstart.lnum || + curwin->w_cursor.col >= Insstart.col)) + { + if (vim_iswhite(gchar_cursor())) + { + /* remember position of blank just before text */ + end_foundcol = curwin->w_cursor.col; + while (curwin->w_cursor.col > 0 && + vim_iswhite(gchar_cursor())) + --curwin->w_cursor.col; + if (curwin->w_cursor.col == 0 && + vim_iswhite(gchar_cursor())) + break; /* only spaces in front of text */ + /* Don't break until after the comment leader */ + if (curwin->w_cursor.col < leader_len) + break; + foundcol = curwin->w_cursor.col + 1; + if (curwin->w_cursor.col < (colnr_t)wantcol) + break; + } + --curwin->w_cursor.col; + } + + if (foundcol == 0) /* no spaces, cannot break line */ + { + curwin->w_cursor.col = startcol; + break; + } + + /* + * offset between cursor position and line break is used by + * replace stack functions + */ + replace_offset = startcol - end_foundcol - 1; + + /* + * adjust startcol for spaces that will be deleted and + * characters that will remain on top line + */ + curwin->w_cursor.col = foundcol; + while (vim_iswhite(gchar_cursor())) + { + ++curwin->w_cursor.col; + --startcol; + } + startcol -= foundcol; + if (startcol < 0) + startcol = 0; + + /* put cursor after pos. to break line */ + curwin->w_cursor.col = foundcol; + + Opencmd(FORWARD, FALSE, TRUE); + + replace_offset = 0; + if (second_indent >= 0 && first_line) + set_indent(second_indent, TRUE); + first_line = FALSE; + + /* + * check if cursor is not past the NUL off the line, cindent may + * have added or removed indent. + */ + curwin->w_cursor.col += startcol; + len = STRLEN(ml_get_curline()); + if (curwin->w_cursor.col > len) + curwin->w_cursor.col = len; + + curs_columns(FALSE); /* update curwin->w_virtcol */ + haveto_redraw = TRUE; +#ifdef CINDENT + can_cindent = TRUE; +#endif + } + + if (save_char) /* put back space after cursor */ + pchar_cursor(save_char); + + if (c == NUL) /* formatting only */ + return; + fo_do_comments = FALSE; + if (haveto_redraw) + { + /* + * If the cursor ended up just below the screen we scroll up here + * to avoid a redraw of the whole screen in the most common cases. + */ + if (curwin->w_cursor.lnum == curwin->w_botline && + !curwin->w_empty_rows) + win_del_lines(curwin, 0, 1, TRUE, TRUE); + updateScreen(CURSUPD); + } + } + if (c == NUL) /* only formatting was wanted */ + return; + + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + + /* + * If there's any pending input, grab up to INPUT_BUFLEN at once. + * This speeds up normal text input considerably. + */ +#define INPUT_BUFLEN 100 + if (!ISSPECIAL(c) && vpeekc() != NUL && State != REPLACE +#ifdef RIGHTLEFT + && !p_ri +#endif + ) + { + char_u p[INPUT_BUFLEN + 1]; + int i; + + p[0] = c; + i = 1; + while ((c = vpeekc()) != NUL && !ISSPECIAL(c) && i < INPUT_BUFLEN && + (textwidth == 0 || + (curwin->w_virtcol += charsize(p[i - 1])) < (colnr_t)textwidth) && + !(!no_abbr && !iswordchar(c) && iswordchar(p[i - 1]))) + { +#ifdef RIGHTLEFT + c = vgetc(); + if (p_hkmap && KeyTyped) + c = hkmap(c); /* Hebrew mode mapping */ + p[i++] = c; +#else + p[i++] = vgetc(); +#endif + } + +#ifdef DIGRAPHS + do_digraph(-1); /* clear digraphs */ + do_digraph(p[i-1]); /* may be the start of a digraph */ +#endif + p[i] = '\0'; + ins_str(p); + AppendToRedobuff(p); + } + else + { + ins_char(c); + AppendCharToRedobuff(c); + } +} + +/* + * start_arrow() is called when an arrow key is used in insert mode. + * It resembles hitting the <ESC> key. + */ + static void +start_arrow(end_insert_pos) + FPOS *end_insert_pos; +{ + if (!arrow_used) /* something has been inserted */ + { + AppendToRedobuff(ESC_STR); + arrow_used = TRUE; /* this means we stopped the current insert */ + stop_insert(end_insert_pos); + } +} + +/* + * stop_arrow() is called before a change is made in insert mode. + * If an arrow key has been used, start a new insertion. + */ + static void +stop_arrow() +{ + if (arrow_used) + { + (void)u_save_cursor(); /* errors are ignored! */ + Insstart = curwin->w_cursor; /* new insertion starts here */ + Insstart_textlen = linetabsize(ml_get_curline()); + ResetRedobuff(); + AppendToRedobuff((char_u *)"1i"); /* pretend we start an insertion */ + arrow_used = FALSE; + } +} + +/* + * do a few things to stop inserting + */ + static void +stop_insert(end_insert_pos) + FPOS *end_insert_pos; /* where insert ended */ +{ + stop_redo_ins(); + + /* + * save the inserted text for later redo with ^@ + */ + vim_free(last_insert); + last_insert = get_inserted(); + last_insert_skip = new_insert_skip; + + /* + * If we just did an auto-indent, remove the white space from the end of + * the line, and put the cursor back. + */ + if (did_ai && !arrow_used) + { + if (gchar_cursor() == NUL && curwin->w_cursor.col > 0) + --curwin->w_cursor.col; + while (vim_iswhite(gchar_cursor())) + delchar(TRUE); + if (gchar_cursor() != NUL) + ++curwin->w_cursor.col; /* put cursor back on the NUL */ + if (curwin->w_p_list) /* the deletion is only seen in list + * mode */ + updateline(); + } + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + + /* set '[ and '] to the inserted text */ + curbuf->b_op_start = Insstart; + curbuf->b_op_end = *end_insert_pos; +} + +/* + * Set the last inserted text to a single character. + * Used for the replace command. + */ + void +set_last_insert(c) + int c; +{ + vim_free(last_insert); + last_insert = alloc(4); + if (last_insert != NULL) + { + last_insert[0] = Ctrl('V'); + last_insert[1] = c; + last_insert[2] = ESC; + last_insert[3] = NUL; + /* Use the CTRL-V only when not entering a digit */ + last_insert_skip = isdigit(c) ? 1 : 0; + } +} + +/* + * move cursor to start of line + * if flag == TRUE move to first non-white + * if flag == MAYBE then move to first non-white if startofline is set, + * otherwise don't move at all. + */ + void +beginline(flag) + int flag; +{ + if (flag == MAYBE && !p_sol) + coladvance(curwin->w_curswant); + else + { + curwin->w_cursor.col = 0; + if (flag) + { + register char_u *ptr; + + for (ptr = ml_get_curline(); vim_iswhite(*ptr); ++ptr) + ++curwin->w_cursor.col; + } + curwin->w_set_curswant = TRUE; + } +} + +/* + * oneright oneleft cursor_down cursor_up + * + * Move one char {right,left,down,up}. + * Return OK when sucessful, FAIL when we hit a line of file boundary. + */ + + int +oneright() +{ + char_u *ptr; + + ptr = ml_get_cursor(); + if (*ptr++ == NUL || *ptr == NUL) + return FAIL; + curwin->w_set_curswant = TRUE; + ++curwin->w_cursor.col; + return OK; +} + + int +oneleft() +{ + if (curwin->w_cursor.col == 0) + return FAIL; + curwin->w_set_curswant = TRUE; + --curwin->w_cursor.col; + return OK; +} + + int +cursor_up(n) + long n; +{ + if (n != 0 && curwin->w_cursor.lnum == 1) + return FAIL; + if (n >= curwin->w_cursor.lnum) + curwin->w_cursor.lnum = 1; + else + curwin->w_cursor.lnum -= n; + + /* try to advance to the column we want to be at */ + coladvance(curwin->w_curswant); + + if (op_type == NOP) + cursupdate(); /* make sure curwin->w_topline is valid */ + + return OK; +} + + int +cursor_down(n) + long n; +{ + if (n != 0 && curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) + return FAIL; + curwin->w_cursor.lnum += n; + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + + /* try to advance to the column we want to be at */ + coladvance(curwin->w_curswant); + + if (op_type == NOP) + cursupdate(); /* make sure curwin->w_topline is valid */ + + return OK; +} + +/* + * screengo() -- + * + * move 'dist' lines in direction 'dir', counting lines by *screen* + * lines rather than lines in the file + * 'dist' must be positive. + * + * return OK if able to move cursor, FAIL otherwise. + */ + + int +screengo(dir, dist) + int dir; + long dist; +{ + int linelen = linetabsize(ml_get_curline()); + int retval = OK; + int atend = FALSE; + int n; + + op_motion_type = MCHAR; + op_inclusive = FALSE; + + /* + * Instead of sticking at the last character of the line in the file we + * try to stick in the last column of the screen + */ + if (curwin->w_curswant == MAXCOL) + { + atend = TRUE; + curwin->w_curswant = ((curwin->w_virtcol + + (curwin->w_p_nu ? 8 : 0)) / Columns + 1) * Columns - 1; + if (curwin->w_p_nu && curwin->w_curswant > 8) + curwin->w_curswant -= 8; + } + else + while (curwin->w_curswant >= (colnr_t)(linelen + Columns)) + curwin->w_curswant -= Columns; + + while (dist--) + { + if (dir == BACKWARD) + { + /* move back within line */ + if ((long)curwin->w_curswant >= Columns) + curwin->w_curswant -= Columns; + else /* to previous line */ + { + if (curwin->w_cursor.lnum == 1) + { + retval = FAIL; + break; + } + --curwin->w_cursor.lnum; + linelen = linetabsize(ml_get_curline()); + n = ((linelen + (curwin->w_p_nu ? 8 : 0) - 1) / Columns) + * Columns; + if (curwin->w_p_nu && + (long)curwin->w_curswant >= Columns - 8 && n) + n -= Columns; + curwin->w_curswant += n; + } + } + else /* dir == FORWARD */ + { + n = ((linelen + (curwin->w_p_nu ? 8 : 0) - 1) / Columns) * Columns; + if (curwin->w_p_nu && n > 8) + n -= 8; + /* move forward within line */ + if (curwin->w_curswant < (colnr_t)n) + curwin->w_curswant += Columns; + else /* to next line */ + { + if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) + { + retval = FAIL; + break; + } + curwin->w_cursor.lnum++; + linelen = linetabsize(ml_get_curline()); + curwin->w_curswant %= Columns; + } + } + } + coladvance(curwin->w_curswant); + if (atend) + curwin->w_curswant = MAXCOL; /* stick in the last column */ + if (op_type == NOP) + cursupdate(); + return retval; +} + +/* + * move screen 'count' pages up or down and update screen + * + * return FAIL for failure, OK otherwise + */ + int +onepage(dir, count) + int dir; + long count; +{ + linenr_t lp; + long n; + int off; + + if (curbuf->b_ml.ml_line_count == 1) /* nothing to do */ + return FAIL; + for ( ; count > 0; --count) + { + if (dir == FORWARD ? (curwin->w_topline >= + curbuf->b_ml.ml_line_count - 1) : (curwin->w_topline == 1)) + { + beep_flush(); + return FAIL; + } + if (dir == FORWARD) + { + /* at end of file */ + if (curwin->w_botline > curbuf->b_ml.ml_line_count) + curwin->w_topline = curbuf->b_ml.ml_line_count; + /* next line is big */ + /* or just three lines on screen */ + else + { + if (plines(curwin->w_botline) >= curwin->w_height - 2 || + curwin->w_botline - curwin->w_topline <= 3) + off = 0; + else + off = 2; + curwin->w_topline = curwin->w_botline - off; + curwin->w_cursor.lnum = curwin->w_topline; + } + comp_Botline(curwin); + } + else /* dir == BACKWARDS */ + { + lp = curwin->w_topline; + /* + * If the first two lines on the screen are not too big, we keep + * them on the screen. + */ + if ((n = plines(lp)) > curwin->w_height / 2) + --lp; + else if (lp < curbuf->b_ml.ml_line_count && + n + plines(lp + 1) < curwin->w_height / 2) + ++lp; + curwin->w_cursor.lnum = lp; + n = 0; + while (n <= curwin->w_height && lp >= 1) + { + n += plines(lp); + --lp; + } + if (n <= curwin->w_height) /* at begin of file */ + { + curwin->w_topline = 1; + comp_Botline(curwin); + } + else if (lp >= curwin->w_topline - 2) /* very long lines */ + { + --curwin->w_topline; + comp_Botline(curwin); + curwin->w_cursor.lnum = curwin->w_botline - 1; + } + else + { + curwin->w_topline = lp + 2; + comp_Botline(curwin); + } + } + } + cursor_correct(); + beginline(MAYBE); + updateScreen(VALID); + return OK; +} + +/* #define KEEP_SCREEN_LINE */ + + void +halfpage(flag, Prenum) + int flag; + linenr_t Prenum; +{ + long scrolled = 0; + int i; + int n; + + if (Prenum) + curwin->w_p_scroll = (Prenum > curwin->w_height) ? + curwin->w_height : Prenum; + n = (curwin->w_p_scroll <= curwin->w_height) ? + curwin->w_p_scroll : curwin->w_height; + + if (flag) /* scroll down */ + { + while (n > 0 && curwin->w_botline <= curbuf->b_ml.ml_line_count) + { + i = plines(curwin->w_topline); + n -= i; + if (n < 0 && scrolled) + break; + scrolled += i; + ++curwin->w_topline; + comp_Botline(curwin); /* compute curwin->w_botline */ +#ifndef KEEP_SCREEN_LINE + if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) + ++curwin->w_cursor.lnum; +#endif + } +#ifndef KEEP_SCREEN_LINE + /* + * When hit bottom of the file: move cursor down. + */ + if (n > 0) + { + curwin->w_cursor.lnum += n; + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + } +#else + /* try to put the cursor in the same screen line */ + while ((curwin->w_cursor.lnum < curwin->w_topline || scrolled > 0) + && curwin->w_cursor.lnum < curwin->w_botline - 1) + { + scrolled -= plines(curwin->w_cursor.lnum); + if (scrolled < 0 && curwin->w_cursor.lnum >= curwin->w_topline) + break; + ++curwin->w_cursor.lnum; + } +#endif + } + else /* scroll up */ + { + while (n > 0 && curwin->w_topline > 1) + { + i = plines(curwin->w_topline - 1); + n -= i; + if (n < 0 && scrolled) + break; + scrolled += i; + --curwin->w_topline; +#ifndef KEEP_SCREEN_LINE + if (curwin->w_cursor.lnum > 1) + --curwin->w_cursor.lnum; +#endif + } + comp_Botline(curwin); /* compute curwin->w_botline */ +#ifndef KEEP_SCREEN_LINE + /* + * When hit top of the file: move cursor up. + */ + if (n > 0) + { + if (curwin->w_cursor.lnum > (linenr_t)n) + curwin->w_cursor.lnum -= n; + else + curwin->w_cursor.lnum = 1; + } +#else + /* try to put the cursor in the same screen line */ + scrolled += n; /* move cursor when topline is 1 */ + while (curwin->w_cursor.lnum > curwin->w_topline && + (scrolled > 0 || curwin->w_cursor.lnum >= curwin->w_botline)) + { + scrolled -= plines(curwin->w_cursor.lnum - 1); + if (scrolled < 0 && curwin->w_cursor.lnum < curwin->w_botline) + break; + --curwin->w_cursor.lnum; + } +#endif + } + cursor_correct(); + beginline(MAYBE); + updateScreen(VALID); +} + +/* + * Stuff the last inserted text in the read buffer. + * Last_insert actually is a copy of the redo buffer, so we + * first have to remove the command. + */ + int +stuff_inserted(c, count, no_esc) + int c; + long count; + int no_esc; +{ + char_u *esc_ptr = NULL; + char_u *ptr; + + ptr = get_last_insert(); + if (ptr == NULL) + { + EMSG(e_noinstext); + return FAIL; + } + + if (c) + stuffcharReadbuff(c); + if (no_esc && (esc_ptr = (char_u *)vim_strrchr(ptr, 27)) != NULL) + *esc_ptr = NUL; /* remove the ESC */ + + do + stuffReadbuff(ptr); + while (--count > 0); + + if (esc_ptr != NULL) + *esc_ptr = 27; /* put the ESC back */ + + return OK; +} + + char_u * +get_last_insert() +{ + if (last_insert == NULL) + return NULL; + return last_insert + last_insert_skip; +} + +/* + * Check the word in front of the cursor for an abbreviation. + * Called when the non-id character "c" has been entered. + * When an abbreviation is recognized it is removed from the text and + * the replacement string is inserted in typebuf[], followed by "c". + */ + static int +echeck_abbr(c) + int c; +{ + if (p_paste || no_abbr) /* no abbreviations or in paste mode */ + return FALSE; + + return check_abbr(c, ml_get_curline(), curwin->w_cursor.col, + curwin->w_cursor.lnum == Insstart.lnum ? Insstart.col : 0); +} + +/* + * replace-stack functions + * + * When replacing characters the replaced character is remembered + * for each new character. This is used to re-insert the old text + * when backspacing. + * + * replace_offset is normally 0, in which case replace_push will add a new + * character at the end of the stack. If replace_offset is not 0, that many + * characters will be left on the stack above the newly inserted character. + */ + +char_u *replace_stack = NULL; +long replace_stack_nr = 0; /* next entry in replace stack */ +long replace_stack_len = 0; /* max. number of entries */ + + void +replace_push(c) + int c; /* character that is replaced (NUL is none) */ +{ + char_u *p; + + if (replace_stack_nr < replace_offset) /* nothing to do */ + return; + if (replace_stack_len <= replace_stack_nr) + { + replace_stack_len += 50; + p = lalloc(sizeof(char_u) * replace_stack_len, TRUE); + if (p == NULL) /* out of memory */ + { + replace_stack_len -= 50; + return; + } + if (replace_stack != NULL) + { + vim_memmove(p, replace_stack, + (size_t)(replace_stack_nr * sizeof(char_u))); + vim_free(replace_stack); + } + replace_stack = p; + } + p = replace_stack + replace_stack_nr - replace_offset; + if (replace_offset) + vim_memmove(p + 1, p, (size_t)(replace_offset * sizeof(char_u))); + *p = c; + ++replace_stack_nr; +} + +/* + * pop one item from the replace stack + * return -1 if stack empty + * return 0 if no character was replaced + * return replaced character otherwise + */ + int +replace_pop() +{ + if (replace_stack_nr == 0) + return -1; + return (int)replace_stack[--replace_stack_nr]; +} + +/* + * make the replace stack empty + * (called when exiting replace mode) + */ + void +replace_flush() +{ + vim_free(replace_stack); + replace_stack = NULL; + replace_stack_len = 0; + replace_stack_nr = 0; +} + +#if defined(LISPINDENT) || defined(CINDENT) +/* + * Re-indent the current line, based on the current contents of it and the + * surrounding lines. Fixing the cursor position seems really easy -- I'm very + * confused what all the part that handles Control-T is doing that I'm not. + * "get_the_indent" should be get_c_indent or get_lisp_indent. + */ + + void +fixthisline(get_the_indent) + int (*get_the_indent) __ARGS((void)); +{ + change_indent(INDENT_SET, get_the_indent(), FALSE); + if (linewhite(curwin->w_cursor.lnum)) + did_ai = TRUE; /* delete the indent if the line stays empty */ +} +#endif /* defined(LISPINDENT) || defined(CINDENT) */ + +#ifdef CINDENT +/* + * return TRUE if 'cinkeys' contains the key "keytyped", + * when == '*': Only if key is preceded with '*' (indent before insert) + * when == '!': Only if key is prededed with '!' (don't insert) + * when == ' ': Only if key is not preceded with '*'(indent afterwards) + * + * If line_is_empty is TRUE accept keys with '0' before them. + */ + int +in_cinkeys(keytyped, when, line_is_empty) + int keytyped; + int when; + int line_is_empty; +{ + char_u *look; + int try_match; + char_u *p; + + for (look = curbuf->b_p_cink; *look; ) + { + /* + * Find out if we want to try a match with this key, depending on + * 'when' and a '*' or '!' before the key. + */ + switch (when) + { + case '*': try_match = (*look == '*'); break; + case '!': try_match = (*look == '!'); break; + default: try_match = (*look != '*'); break; + } + if (*look == '*' || *look == '!') + ++look; + + /* + * If there is a '0', only accept a match if the line is empty. + */ + if (*look == '0') + { + if (!line_is_empty) + try_match = FALSE; + ++look; + } + + /* + * does it look like a control character? + */ + if (*look == '^' && look[1] >= '@' && look[1] <= '_') + { + if (try_match && keytyped == Ctrl(look[1])) + return TRUE; + look += 2; + } + /* + * 'o' means "o" command, open forward. + * 'O' means "O" command, open backward. + */ + else if (*look == 'o') + { + if (try_match && keytyped == KEY_OPEN_FORW) + return TRUE; + ++look; + } + else if (*look == 'O') + { + if (try_match && keytyped == KEY_OPEN_BACK) + return TRUE; + ++look; + } + + /* + * 'e' means to check for "else" at start of line and just before the + * cursor. + */ + else if (*look == 'e') + { + if (try_match && keytyped == 'e' && curwin->w_cursor.col >= 4) + { + p = ml_get_curline(); + if (skipwhite(p) == p + curwin->w_cursor.col - 4 && + STRNCMP(p + curwin->w_cursor.col - 4, "else", 4) == 0) + return TRUE; + } + ++look; + } + + /* + * ':' only causes an indent if it is at the end of a label or case + * statement. + */ + else if (*look == ':') + { + if (try_match && keytyped == ':') + { + p = ml_get_curline(); + if (iscase(p) || islabel(30)) + return TRUE; + } + ++look; + } + + + /* + * Is it a key in <>, maybe? + */ + else if (*look == '<') + { + if (try_match) + { + /* + * make up some named keys <o>, <O>, <e>, <0>, <>>, <<>, <*> + * and <!> so that people can re-indent on o, O, e, 0, <, >, * + * and ! keys if they really really want to. + */ + if (vim_strchr((char_u *)"<>!*oOe0", look[1]) != NULL && + keytyped == look[1]) + return TRUE; + + if (keytyped == get_special_key_code(look + 1)) + return TRUE; + } + while (*look && *look != '>') + look++; + while (*look == '>') + look++; + } + + /* + * ok, it's a boring generic character. + */ + else + { + if (try_match && *look == keytyped) + return TRUE; + ++look; + } + + /* + * Skip over ", ". + */ + look = skip_to_option_part(look); + } + return FALSE; +} +#endif /* CINDENT */ + +#if defined(RIGHTLEFT) || defined(PROTO) +/* + * Map Hebrew keyboard when in hkmap mode. + */ + int +hkmap(c) + int c; +{ + switch(c) + { + case '`': return ';'; + case '/': return '.'; + case '\'': return ','; + case 'q': return '/'; + case 'w': return '\''; + + /* Hebrew letters - set offset from 'a' */ + case ',': c = '{'; break; + case '.': c = 'v'; break; + case ';': c = 't'; break; + default: { + static char str[] = "zqbcxlsjphmkwonu ydafe rig"; + + if (c < 'a' || c > 'z') + return c; + c = str[c - 'a']; + break; + } + } + + return c - 'a' + p_aleph; +} +#endif |