This is edit.c in view mode; [Download] [Up]
/* 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" #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) /* * Structure used to store one match for insert completion. */ struct Completion { struct Completion *next; struct Completion *prev; char_u *str; /* matched text */ char_u *fname; /* file containing the match */ int original; /* TRUE if this is the original text */ }; /* * All the current matches are stored in a list. * "first_match" points to the start of the list. * "curr_match" points to the currently selected entry. */ static struct Completion *first_match = NULL; static struct Completion *curr_match = NULL; static int started_completion; static char_u *complete_pat; static int save_sm; static char_u *original_text = NULL; /* text before completion */ 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)); static void clear_insexp __ARGS((void)); static int ins_expand_pre __ARGS((int c)); static int ins_complete __ARGS((int c)); #endif /* INSERT_EXPAND */ #define BACKSPACE_CHAR 1 #define BACKSPACE_WORD 2 #define BACKSPACE_WORD_NOT_SPACE 3 #define BACKSPACE_LINE 4 static void edit_putchar __ARGS((int c, int highlight)); static void undisplay_dollar __ARGS((void)); 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 int ins_reg __ARGS((void)); static int ins_esc __ARGS((long *count, int need_redraw, int cmdchar)); #ifdef RIGHTLEFT static void ins_ctrlb __ARGS((void)); static void ins_ctrl_ __ARGS((void)); #endif static void ins_shift __ARGS((int c, int lastc)); static void ins_del __ARGS((void)); static int ins_bs __ARGS((int c, int mode)); #ifdef USE_MOUSE static void ins_mouse __ARGS((int c)); #endif static void ins_left __ARGS((void)); static void ins_home __ARGS((void)); static void ins_end __ARGS((void)); 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((void)); static void ins_pageup __ARGS((void)); static void ins_down __ARGS((void)); static void ins_pagedown __ARGS((void)); static int ins_tab __ARGS((void)); static int ins_eol __ARGS((int c)); #ifdef DIGRAPHS static int ins_digraph __ARGS((void)); #endif static int ins_copychar __ARGS((linenr_t lnum)); #ifdef SMARTINDENT static void ins_try_si __ARGS((int c)); #endif 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; /* nr of chars in front of previous insert */ static int new_insert_skip; /* nr of chars in front of current insert */ #ifdef CINDENT static int can_cindent; /* may do cindenting on this line */ #endif static int old_indent = 0; /* for ^^D command in insert mode */ #ifdef RIGHTLEFT int revins_on; /* reverse insert mode on */ int revins_chars; /* how much to skip after edit */ int revins_legal; /* was the last char 'legal'? */ int revins_scol; /* start column of revins session */ #endif /* * edit(): Start insering 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. * * 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; char_u *ptr; 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 CINDENT int line_is_white = FALSE; /* line is empty before insert */ #endif linenr_t old_topline = 0; /* topline before insertion */ /* sleep before redrawing, needed for "CTRL-O :" that results in an * error message */ check_for_delay(TRUE); #ifdef INSERT_EXPAND clear_insexp(); /* clear stuff for ctrl-x mode */ #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 (cmdchar != NUL && !restart_edit) { ResetRedobuff(); AppendNumberToRedobuff(count); 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') State = REPLACE; else State = INSERT; /* * Need to recompute the cursor position, it might move when the cursor is * on a TAB or special character. */ curs_columns(TRUE); #ifdef USE_MOUSE setmouse(); #endif clear_showcmd(); #ifdef 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 /* * 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|". */ validate_virtcol(); 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 '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) i = showmode(); if (!p_im) change_warning(i + 1); #ifdef USE_GUI if (gui.in_use) gui_update_cursor(TRUE); /* may show different cursor shape */ #endif #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 (!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; /* set curwin->w_curswant for next K_DOWN or K_UP */ if (!arrow_used) curwin->w_set_curswant = TRUE; /* * 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. * Don't do this when the topline changed already, it has * already been adjusted (by insertchar() calling open_line())). */ if (need_redraw && curwin->w_p_wrap && !did_backspace && curwin->w_topline == old_topline) { mincol = curwin->w_wcol; validate_cursor_col(); if ((int)curwin->w_wcol < (int)mincol - curbuf->b_p_ts && curwin->w_wrow == curwin->w_winpos + curwin->w_height - 1 - p_so && curwin->w_cursor.lnum != curwin->w_topline) { set_topline(curwin, curwin->w_topline + 1); update_topline(); update_screen(VALID_TO_CURSCHAR); need_redraw = FALSE; } else update_topline(); } else update_topline(); did_backspace = FALSE; /* * redraw is postponed until here to make 'dollar' option work * correctly. */ validate_cursor(); /* may set must_redraw */ if (must_redraw) /* must redraw whole screen */ { update_screen(must_redraw); need_redraw = FALSE; } else if (need_redraw) { update_screenline(); if (curwin->w_redr_status == TRUE) win_redr_status(curwin); /* display [+] if required */ need_redraw = FALSE; } else if (clear_cmdline || redraw_cmdline) showmode(); /* clear cmdline, show mode and ruler */ showruler(0); setcursor(); update_curswant(); emsg_on_display = FALSE; /* may remove error message now */ old_topline = curwin->w_topline; c = vgetc(); #ifdef RIGHTLEFT if (p_hkmap && KeyTyped) c = hkmap(c); /* Hebrew mode mapping */ #endif #ifdef INSERT_EXPAND /* * Prepare for or stop ctrl-x mode. */ need_redraw |= ins_expand_pre(c); #endif if (c != Ctrl('D')) /* remember to detect ^^D and 0^D */ lastc = c; #ifdef DIGRAPHS c = do_digraph(c); #endif if (c == Ctrl('V') || c == Ctrl('Q')) { if (redrawing() && !char_avail()) edit_putchar('^', TRUE); AppendToRedobuff((char_u *)"\026"); /* CTRL-V */ if (!add_to_showcmd(c, FALSE)) setcursor(); c = get_literal(); clear_showcmd(); insert_special(c, TRUE); need_redraw = TRUE; #ifdef RIGHTLEFT revins_chars++; revins_legal++; #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 /* * The big switch to handle a character in insert mode. */ switch (c) { case K_INS: /* toggle insert/replace mode */ if (State == REPLACE) State = INSERT; else State = REPLACE; AppendCharToRedobuff(K_INS); showmode(); #ifdef USE_GUI if (gui.in_use) gui_update_cursor(TRUE); /* change shape of cursor */ #endif 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; edit_submode = (char_u *)"^X mode (^E/^Y/^L/^]/^F/^I/^K/^D)"; showmode(); break; #endif 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: if (ins_esc(&count, need_redraw, cmdchar)) return (c == Ctrl('O')); continue; /* * 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'): need_redraw |= ins_reg(); break; #ifdef RIGHTLEFT /* * toggle reverse insert mode */ case Ctrl('B'): ins_ctrlb(); break; /* * toggle language: khmap and revins.on * Move to end of reverse inserted text. */ case Ctrl('_'): ins_ctrl_(); break; #endif /* Make indent one shiftwidth smaller. */ case Ctrl('D'): #ifdef INSERT_EXPAND if (ctrl_x_mode == CTRL_X_PATH_DEFINES) goto docomplete; #endif /* FALLTHROUGH */ /* Make indent one shiftwidth greater. */ case Ctrl('T'): ins_shift(c, lastc); need_redraw = TRUE; break; /* delete character under the cursor */ case K_DEL: ins_del(); need_redraw = TRUE; break; /* delete character before the cursor */ case K_BS: case Ctrl('H'): did_backspace = ins_bs(c, BACKSPACE_CHAR); need_redraw = TRUE; break; /* delete word before the cursor */ case Ctrl('W'): did_backspace = ins_bs(c, BACKSPACE_WORD); need_redraw = TRUE; break; /* delete all inserted text in current line */ case Ctrl('U'): did_backspace = ins_bs(c, BACKSPACE_LINE); need_redraw = TRUE; break; #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: ins_mouse(c); break; case K_IGNORE: break; #endif #ifdef USE_GUI case K_SCROLLBAR: ins_scroll(); break; case K_HORIZ_SCROLLBAR: ins_horscroll(); break; #endif case K_LEFT: ins_left(); break; case K_HOME: case K_KHOME: ins_home(); break; case K_END: case K_KEND: ins_end(); break; case K_S_LEFT: ins_s_left(); break; case K_RIGHT: ins_right(); break; case K_S_RIGHT: ins_s_right(); break; case K_UP: ins_up(); break; case K_S_UP: case K_PAGEUP: case K_KPAGEUP: ins_pageup(); break; case K_DOWN: ins_down(); break; case K_S_DOWN: case K_PAGEDOWN: case K_KPAGEDOWN: ins_pagedown(); break; /* TAB or Complete patterns along path */ case TAB: #ifdef INSERT_EXPAND if (ctrl_x_mode == CTRL_X_PATH_PATTERNS) goto docomplete; #endif if (ins_tab()) goto normalchar; /* insert TAB as a normal char */ need_redraw = TRUE; break; case CR: case NL: if (ins_eol(c)) goto doESCkey; /* out of memory */ break; #ifdef DIGRAPHS case Ctrl('K'): # ifdef INSERT_EXPAND if (ctrl_x_mode == CTRL_X_DICTIONARY) goto docomplete; # endif c = ins_digraph(); if (c != NUL) goto normalchar; need_redraw = TRUE; break; #else /* DIGRAPHS */ # ifdef INSERT_EXPAND case Ctrl('K'): if (ctrl_x_mode != CTRL_X_DICTIONARY) goto normalchar; goto docomplete; # endif #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: need_redraw |= ins_complete(c); break; #endif /* INSERT_EXPAND */ case Ctrl('Y'): /* copy from previous line */ #ifdef INSERT_EXPAND if (ctrl_x_mode == CTRL_X_SCROLL) { scrolldown_clamp(); update_screen(VALID); break; } #endif c = ins_copychar(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(); update_screen(VALID); break; } #endif c = ins_copychar(curwin->w_cursor.lnum + 1); copychar: if (c == NUL) break; /*FALLTHROUGH*/ default: normalchar: #ifdef SMARTINDENT /* * Try to perform smart-indenting. */ ins_try_si(c); #endif if (c == ' ') { #ifdef CINDENT if (inindent(0)) can_cindent = FALSE; #endif if (Insstart_blank_vcol == MAXCOL && curwin->w_cursor.lnum == Insstart.lnum) { validate_virtcol(); Insstart_blank_vcol = curwin->w_virtcol; } } if (iswordchar(c) || !echeck_abbr(c)) { insert_special(c, FALSE); need_redraw = TRUE; #ifdef RIGHTLEFT revins_legal++; revins_chars++; #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 (;;) */ } /* * 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 void edit_putchar(c, highlight) int c; int highlight; { int attr; if (NextScreen != NULL) { update_topline(); /* just in case w_topline isn't valid */ validate_cursor(); if (highlight) attr = highlight_attr[HLF_8]; else attr = 0; screen_putchar(c, curwin->w_winpos + curwin->w_wrow, #ifdef RIGHTLEFT curwin->w_p_rl ? (int)Columns - 1 - curwin->w_wcol : #endif curwin->w_wcol, 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; curs_columns(FALSE); /* recompute w_wrow and w_wcol */ if (curwin->w_wcol < Columns) { 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) { dollar_vcol = 0; update_screenline(); } } /* * 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; int start_col; /* 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; validate_virtcol(); /* recompute w_virtcol */ vcol = curwin->w_virtcol; /* * 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(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; 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) 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; changed_cline_bef_curs(); /* * 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; } /* * 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 (State == REPLACE && start_col >= 0) { while (start_col > (int)curwin->w_cursor.col) { (void)replace_pop(); --start_col; } while (start_col < (int)curwin->w_cursor.col) { replace_push(NUL); ++start_col; } } } #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; ui_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 = vim_strnsave(str, len)) == NULL) { vim_free(match); return RET_ERROR; } if (fname != NULL) match->fname = vim_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 = vim_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; vim_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) { MSG_ATTR("Please wait, searching dictionaries", highlight_attr[HLF_R]); set_reg_ic(pat); /* set reg_ic according to p_ic, p_scs and pat */ prog = vim_regcomp(pat, (int)p_magic); } 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 = vim_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; } static void clear_insexp() { started_completion = FALSE; complete_pat = NULL; save_sm = -1; } /* * Prepare for insert-expand, or stop it. */ static int ins_expand_pre(c) int c; { char_u *ptr; char_u *tmp_ptr; int temp; linenr_t lnum; int need_redraw = FALSE; 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) { update_topline(); update_screen(NOT_VALID); } 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(); } } } return need_redraw; } static int ins_complete(c) int c; { int complete_direction; char_u *mesg; /* Message about completion */ static int done_dir = 0; /* Found all matches in this * direction */ static FPOS first_match_pos; static FPOS last_match_pos; char_u *ptr; char_u *tmp_ptr; static colnr_t complete_col = 0; /* init for gcc */ int temp = 0; vim_regexp *prog; int num_matches; char_u **matches; int i; FPOS *complete_pos; int save_p_scs; int cc; int need_redraw = FALSE; if (c == Ctrl('P') || c == Ctrl('L')) complete_direction = BACKWARD; else complete_direction = FORWARD; 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; #ifdef SMARTINDENT did_si = FALSE; can_si = FALSE; can_si_back = FALSE; #endif 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 = vim_strsave((char_u *)"\\<\\k\\k"); if (complete_pat == NULL) return FALSE; 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 = vim_strsave(IObuff); if (complete_pat == NULL) return FALSE; } else { complete_pat = alloc(temp + 3); if (complete_pat == NULL) return FALSE; 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 = vim_strnsave(tmp_ptr, temp); if (complete_pat == NULL) return FALSE; } 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) return FALSE; } original_text = vim_strnsave(tmp_ptr, temp); if (original_text == NULL) { vim_free(complete_pat); complete_pat = NULL; return FALSE; } 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, (int)p_magic); if (prog != NULL && find_tags(NULL, prog, &num_matches, &matches, FALSE, 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) { /* * May change home directory back to "~". */ tilde_replace(complete_pat, num_matches, matches); 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 (void)del_char(FALSE); } changed_cline_bef_curs(); complete_pos = NULL; if (started_completion && curr_match == NULL && (p_ws || done_dir == BOTH_DIRECTIONS)) { edit_submode_extra = e_patnotf; edit_submode_highl = HLF_E; } 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) { edit_submode_extra = e_patnotf; edit_submode_highl = HLF_E; } } /* eat the ESC to avoid leaving insert mode */ if (got_int && !global_busy) { (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 (edit_submode_extra == NULL) { if (curr_match == NULL || curr_match->original) { edit_submode_extra = (char_u *)"Back at original"; edit_submode_highl = HLF_W; } 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 = -1; } } /* * 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; if (mesg != NULL) { if (dollar_vcol) curs_columns(FALSE); update_screenline(); need_redraw = FALSE; msg_attr(mesg, highlight_attr[HLF_R]); setcursor(); flushbuf(); ui_delay(2000L, FALSE); } if (edit_submode_extra != NULL) { if (!p_smd) msg_attr(edit_submode_extra, edit_submode_highl >= 0 ? highlight_attr[edit_submode_highl] : 0); else showmode(); edit_submode_extra = NULL; } /* * 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)) { 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); redraw_cmdline = FALSE; /* don't overwrite! */ } return need_redraw; } #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 (textwidth && (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'); } validate_virtcol(); 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; open_line(FORWARD, redrawing(), TRUE, old_indent); old_indent = 0; 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; validate_virtcol(); /* 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) { update_topline(); update_screen(NOT_VALID); } } if (c == NUL) /* only formatting was wanted */ return; did_ai = FALSE; #ifdef SMARTINDENT did_si = FALSE; can_si = FALSE; can_si_back = FALSE; #endif /* * If there's any pending input, grab up to INPUT_BUFLEN at once. * This speeds up normal text input considerably. * Don't do this when 'cindent' is set, because we might need to re-indent * at a ':', or any other character. */ #define INPUT_BUFLEN 100 if (!ISSPECIAL(c) && vpeekc() != NUL && State != REPLACE #ifdef CINDENT && !curbuf->b_p_cin #endif #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 || (validate_virtcol(), 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())) (void)del_char(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 */ update_screenline(); } did_ai = FALSE; #ifdef SMARTINDENT did_si = FALSE; can_si = FALSE; can_si_back = FALSE; #endif /* 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) { 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 successful, 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, op_pending) long n; int op_pending; /* operator is pending */ { if (n != 0) { if (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_pending) update_topline(); /* make sure curwin->w_topline is valid */ return OK; } int cursor_down(n, op_pending) long n; int op_pending; /* operator is pending */ { if (n != 0) { if (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_pending) update_topline(); /* make sure curwin->w_topline is valid */ return OK; } /* * 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 static int ins_reg() { int need_redraw = FALSE; int cc; /* * If we are going to wait for a character, show a '"'. */ if (redrawing() && !char_avail()) { edit_putchar('"', TRUE); if (!add_to_showcmd(Ctrl('R'), FALSE)) setcursor(); } /* * don't map the register name. This also prevents the mode message to be * deleted when ESC is hit */ ++no_mapping; cc = vgetc(); --no_mapping; /* * Don't call u_sync while getting the expression, * evaluating it or giving an error message for it! */ ++no_u_sync; if (cc == '=') cc = get_expr_register(); #ifdef HAVE_LANGMAP LANGMAP_ADJUST(cc, TRUE); #endif if (insert_reg(cc) == FAIL) { beep_flush(); need_redraw = TRUE; /* remove the '"' */ } --no_u_sync; clear_showcmd(); return need_redraw; } /* * Handle ESC in insert mode. * Returns TRUE when leaving insert mode, FALSE when going to repeat the * insert. */ static int ins_esc(count, need_redraw, cmdchar) long *count; int need_redraw; int cmdchar; { int temp; static int disabled_redraw = FALSE; temp = curwin->w_cursor.col; if (disabled_redraw) { --RedrawingDisabled; disabled_redraw = FALSE; } if (!arrow_used) { /* * Don't append the ESC for "r<CR>". */ if (cmdchar != 'r') AppendToRedobuff(ESC_STR); /* * Repeating insert may take a long time. Check for * interrupt now and then. */ if (*count) { line_breakcheck(); if (got_int) *count = 0; } if (--*count > 0) /* repeat what was typed */ { (void)start_redo_ins(); ++RedrawingDisabled; disabled_redraw = TRUE; return FALSE; /* repeat the insert */ } 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) update_screenline(); /* 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_on #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(""); return TRUE; /* exit Insert mode */ } #ifdef RIGHTLEFT static void ins_ctrlb() { p_ri = !p_ri; revins_on = (State == INSERT && p_ri); if (revins_on) undisplay_dollar(); showmode(); } static void ins_ctrl_() { if (revins_on && revins_chars && revins_scol >= 0) { while (gchar_cursor() != NUL && revins_chars--) ++curwin->w_cursor.col; } p_ri = !p_ri; revins_on = (State == INSERT && p_ri); if (revins_on) { revins_scol = curwin->w_cursor.col; revins_legal++; revins_chars = 0; undisplay_dollar(); } else revins_scol = -1; p_hkmap = curwin->w_p_rl ^ p_ri; /* be consistent! */ showmode(); } #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. */ static void ins_shift(c, lastc) int c; int lastc; { int i; stop_arrow(); AppendCharToRedobuff(c); /* * 0^D and ^^D: remove all indent. */ if ((lastc == '0' || lastc == '^') && curwin->w_cursor.col) { --curwin->w_cursor.col; (void)del_char(FALSE); /* delete the '^' or '0' */ /* * In Replace mode, restore the character that '^' or '0' * replaced. */ if (State == REPLACE && (i = replace_pop()) >= 0) { State = INSERT; ins_char(i); --curwin->w_cursor.col; State = REPLACE; } 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; #ifdef SMARTINDENT did_si = FALSE; can_si = FALSE; can_si_back = FALSE; #endif #ifdef CINDENT can_cindent = FALSE; /* no cindenting after ^D or ^T */ #endif } static void ins_del() { int temp; 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; redraw_later(VALID_TO_CURSCHAR); } } else if (del_char(FALSE) == FAIL)/* delete char under cursor */ beep_flush(); did_ai = FALSE; #ifdef SMARTINDENT did_si = FALSE; can_si = FALSE; can_si_back = FALSE; #endif AppendCharToRedobuff(K_DEL); } /* * Handle Backspace, delete-word and delete-line in Insert mode. * Return TRUE when backspace was actually used. */ static int ins_bs(c, mode) int c; int mode; { linenr_t lnum; int cc; int temp = 0; /* init for GCC */ colnr_t mincol; int did_backspace = FALSE; /* * 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_on && #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(); return FALSE; } stop_arrow(); #ifdef CINDENT if (inindent(0)) can_cindent = FALSE; #endif #ifdef RIGHTLEFT if (revins_on) /* put cursor after last inserted char */ inc_cursor(); #endif /* * delete newline! */ if (curwin->w_cursor.col <= 0) { lnum = Insstart.lnum; if (curwin->w_cursor.lnum == Insstart.lnum #ifdef RIGHTLEFT || revins_on #endif ) { if (u_save((linenr_t)(curwin->w_cursor.lnum - 2), (linenr_t)(curwin->w_cursor.lnum + 1)) == FAIL) return FALSE; --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, TRUE); redraw_later(VALID_TO_CURSCHAR); 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 { /* * Delete character(s) before the cursor. */ #ifdef RIGHTLEFT if (revins_on) /* put cursor on last inserted char */ dec_cursor(); #endif mincol = 0; /* keep indent */ if (mode == BACKSPACE_LINE && curbuf->b_p_ai #ifdef RIGHTLEFT && !revins_on #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_on) /* 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_on) #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)del_char(FALSE); } else /* State != REPLACE */ { (void)del_char(FALSE); #ifdef RIGHTLEFT if (revins_chars) { revins_chars--; revins_legal++; } if (revins_on && gchar_cursor() == NUL) break; #endif } /* Just a single backspace?: */ if (mode == BACKSPACE_CHAR) break; } while ( #ifdef RIGHTLEFT revins_on || #endif (curwin->w_cursor.col > mincol && (curwin->w_cursor.lnum != Insstart.lnum || curwin->w_cursor.col != Insstart.col))); did_backspace = TRUE; } #ifdef SMARTINDENT did_si = FALSE; can_si = FALSE; can_si_back = FALSE; #endif 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); return did_backspace; } #ifdef USE_MOUSE static void ins_mouse(c) int c; { FPOS tpos; # ifdef USE_GUI /* When GUI is active, also move/paste when 'mouse' is empty */ if (!gui.in_use) # endif if (!mouse_has(MOUSE_INSERT)) return; undisplay_dollar(); tpos = curwin->w_cursor; if (do_mouse(NULL, c, BACKWARD, 1L, FALSE)) { start_arrow(&tpos); # ifdef CINDENT can_cindent = TRUE; # endif } } #endif #ifdef USE_GUI void ins_scroll() { FPOS tpos; undisplay_dollar(); tpos = curwin->w_cursor; if (gui_do_scroll()) { start_arrow(&tpos); # ifdef CINDENT can_cindent = TRUE; # endif } } void ins_horscroll() { FPOS tpos; undisplay_dollar(); tpos = curwin->w_cursor; if (gui_do_horiz_scroll()) { start_arrow(&tpos); #ifdef CINDENT can_cindent = TRUE; #endif } } #endif static void ins_left() { FPOS tpos; undisplay_dollar(); tpos = curwin->w_cursor; if (oneleft() == OK) { start_arrow(&tpos); #ifdef RIGHTLEFT /* If exit reversed string, position is fixed */ if (revins_scol != -1 && (int)curwin->w_cursor.col >= revins_scol) revins_legal++; revins_chars++; #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(); } static void ins_home() { FPOS tpos; 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); } static void ins_end() { FPOS tpos; 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); } static void ins_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(); } static void ins_right() { undisplay_dollar(); if (gchar_cursor() != NUL) { start_arrow(&curwin->w_cursor); curwin->w_set_curswant = TRUE; ++curwin->w_cursor.col; #ifdef RIGHTLEFT revins_legal++; if (revins_chars) revins_chars--; #endif } /* if 'whichwrap' set for cursor in insert mode, may move the * cursor to the 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(); } static void ins_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(); } static void ins_up() { FPOS tpos; linenr_t old_topline; undisplay_dollar(); tpos = curwin->w_cursor; old_topline = curwin->w_topline; if (cursor_up(1L, FALSE) == OK) { if (old_topline != curwin->w_topline) update_screen(VALID); start_arrow(&tpos); #ifdef CINDENT can_cindent = TRUE; #endif } else beep_flush(); } static void ins_pageup() { FPOS tpos; undisplay_dollar(); tpos = curwin->w_cursor; if (onepage(BACKWARD, 1L) == OK) { start_arrow(&tpos); #ifdef CINDENT can_cindent = TRUE; #endif } else beep_flush(); } static void ins_down() { FPOS tpos; linenr_t old_topline = curwin->w_topline; undisplay_dollar(); tpos = curwin->w_cursor; if (cursor_down(1L, FALSE) == OK) { if (old_topline != curwin->w_topline) update_screen(VALID); start_arrow(&tpos); #ifdef CINDENT can_cindent = TRUE; #endif } else beep_flush(); } static void ins_pagedown() { FPOS tpos; undisplay_dollar(); tpos = curwin->w_cursor; if (onepage(FORWARD, 1L) == OK) { start_arrow(&tpos); #ifdef CINDENT can_cindent = TRUE; #endif } else beep_flush(); } /* * Handle TAB in insert mode. * Return TRUE when the TAB needs to be inserted like a normal character. */ static int ins_tab() { int i; int temp; if (Insstart_blank_vcol == MAXCOL && curwin->w_cursor.lnum == Insstart.lnum) { validate_virtcol(); Insstart_blank_vcol = curwin->w_virtcol; } if (echeck_abbr(TAB + ABBR_OFF)) return FALSE; i = inindent(0); #ifdef CINDENT if (i) can_cindent = FALSE; #endif /* * When nothing special, insert TAB like a normal character */ if (!curbuf->b_p_et && !(p_sta && i)) return TRUE; stop_arrow(); did_ai = FALSE; #ifdef SMARTINDENT did_si = FALSE; can_si = FALSE; can_si_back = FALSE; #endif AppendToRedobuff((char_u *)"\t"); if (p_sta && i) /* insert tab in indent */ { if (State == REPLACE) /* delete a char first */ { i = gchar_cursor(); if (i) { replace_push(i); del_char(FALSE); } } change_indent(INDENT_INC, 0, p_sr); return FALSE; } /* * p_et is set: expand a tab into spaces */ validate_virtcol(); 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); } return FALSE; } /* * Handle CR or NL in insert mode. * Return TRUE when out of memory. */ static int ins_eol(c) int c; { int i; if (echeck_abbr(c + ABBR_OFF)) return FALSE; 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_on) while (gchar_cursor() != NUL) ++curwin->w_cursor.col; #endif AppendToRedobuff(NL_STR); if (has_format_option(FO_RET_COMS)) fo_do_comments = TRUE; i = open_line(FORWARD, TRUE, FALSE, old_indent); old_indent = 0; fo_do_comments = FALSE; #ifdef CINDENT can_cindent = TRUE; #endif return (!i); } #ifdef DIGRAPHS /* * Handle digraph in insert mode. * Returns character still to be inserted, or NUL when nothing remaining to be * done. */ static int ins_digraph() { int c; int cc; if (redrawing() && !char_avail()) { edit_putchar('?', TRUE); if (!add_to_showcmd(Ctrl('K'), 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); return NUL; } if (c != ESC) { if (redrawing() && !char_avail()) { if (charsize(c) == 1) edit_putchar(c, TRUE); 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(); return c; } } clear_showcmd(); return NUL; } #endif /* * Handle CTRL-E and CTRL-Y in Insert mode: copy char from other line. * Returns the char to be inserted, or NUL if none found. */ static int ins_copychar(lnum) linenr_t lnum; { int c; int temp; char_u *ptr; if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { beep_flush(); return NUL; } /* try to advance to the cursor column */ temp = 0; ptr = ml_get(lnum); validate_virtcol(); 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(); return c; } #ifdef SMARTINDENT /* * Try to do some very smart auto-indenting. * Used when inserting a "normal" character. */ static void ins_try_si(c) int c; { FPOS *pos, old_pos; char_u *ptr; int i; int temp; /* * do some very smart indenting when entering '{' or '}' */ if (((did_si || can_si_back) && c == '{') || (can_si && c == '}')) { /* * for '}' set indent equal to indent of line containing matching '{' */ if (c == '}' && (pos = findmatch(NULL, '{')) != 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, '(')) != 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); } } #endif
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.