/* Copyright (c) 1990,1991,1992 Chris and John Downey */ #ifndef lint static char *sccsid = "@(#)edit.c 2.4 (Chris & John Downey) 8/26/92"; #endif /*** * program name: xvi * function: PD version of UNIX "vi" editor, with extensions. * module name: edit.c * module function: Insert and replace mode handling. * history: STEVIE - ST Editor for VI Enthusiasts, Version 3.10 Originally by Tim Thompson (twitch!tjt) Extensive modifications by Tony Andrews (onecom!wldrdg!tony) Heavily modified by Chris & John Downey ***/ #include "xvi.h" static void end_replace P((int)); /* * Position of start of insert. This is used * to prevent backing up past the starting point. */ static Posn Insertloc; /* * This flexbuf is used to hold the current insertion text. */ static Flexbuf Insbuff; /* * Replace-mode stuff. */ static enum { replace_one, /* replace command was an 'r' */ got_one, /* normal ending state for replace_one */ overwrite /* replace command was an 'R' */ } repstate; static char *saved_line; /* * record of old line before replace * started; note that, if * (repstate != overwrite), this * should never be referenced. */ static int nchars; /* no of chars in saved_line */ static int start_index; /* index into line where we entered replace */ static int start_column; /* virtual col corresponding to start_index */ /* * Process the given character, in insert mode. */ bool_t i_proc(c) int c; { register Posn *curpos; static bool_t literal_next = FALSE; static bool_t wait_buffer = FALSE; curpos = curwin->w_cursor; if (wait_buffer || (!literal_next && c == CTRL('A'))) { /* * Add contents of named buffer, or the last * insert buffer if CTRL('A') was typed. */ if (!wait_buffer) { c = '<'; } yp_stuff_input(curwin, c, TRUE); wait_buffer = FALSE; return(FALSE); } else if (!literal_next) { /* * This switch is for special characters; we skip over * it for normal characters, or for literal-next mode. */ switch (c) { case ESC: /* an escape ends input mode */ { char *cltext; cltext = curpos->p_line->l_text; curwin->w_set_want_col = TRUE; /* * If there is only auto-indentation * on the current line, delete it. */ if (curpos->p_index == indentchars && cltext[indentchars] == '\0') { replchars(curwin, curpos->p_line, 0, indentchars, ""); begin_line(curwin, FALSE); } indentchars = 0; /* * The cursor should end up on the last inserted * character. This is an attempt to match the real * 'vi', but it may not be quite right yet. */ while (one_left(curwin, FALSE) && gchar(curwin->w_cursor) == '\0') { ; } State = NORMAL; end_command(curwin); (void) yank_str('<', flexgetstr(&Insbuff), FALSE); flexclear(&Insbuff); if (!(echo & e_CHARUPDATE)) { echo |= e_CHARUPDATE; move_window_to_cursor(curwin); cursupdate(curwin); } update_buffer(curbuf); return(TRUE); } case CTRL('T'): case CTRL('D'): /* * If we're at the beginning of the line, or just * after autoindent characters, move one shiftwidth * left (CTRL('D')) or right (CTRL('T')). */ if (curpos->p_index <= indentchars) { Line *lp; int ind; lp = curpos->p_line; ind = get_indent(curpos->p_line); ind += (c == CTRL('D') ? (ind < Pn(P_shiftwidth) ? -ind : -Pn(P_shiftwidth)) : Pn(P_shiftwidth)); indentchars = set_indent(lp, ind); move_cursor(curwin, curpos->p_line, indentchars); cursupdate(curwin); updateline(curwin); (void) flexaddch(&Insbuff, c); } else { beep(curwin); } return(TRUE); case '\b': case DEL: /* * Can't backup past starting point. */ if (curpos->p_line == Insertloc.p_line && curpos->p_index <= Insertloc.p_index) { beep(curwin); return(TRUE); } /* * Can't backup to a previous line. */ if (curpos->p_line != Insertloc.p_line && curpos->p_index <= 0) { beep(curwin); return(TRUE); } (void) one_left(curwin, FALSE); if (curpos->p_index < indentchars) indentchars--; replchars(curwin, curpos->p_line, curpos->p_index, 1, ""); (void) flexaddch(&Insbuff, '\b'); cursupdate(curwin); if (curwin->w_col == 0) { /* * Make sure backspacing over a physical line * break updates the screen correctly. */ update_buffer(curbuf); } else { updateline(curwin); } return(TRUE); case '\r': case '\n': { int i; int previndex; Line *prevline; (void) flexaddch(&Insbuff, '\n'); i = indentchars; prevline = curpos->p_line; previndex = curpos->p_index; if (openfwd(TRUE) == FALSE) { stuff("%c", ESC); show_error(curwin, "No buffer space - returning to command mode"); return(TRUE); } /* * If the previous line only had * auto-indent on it, delete it. */ if (i == previndex) { replchars(curwin, prevline, 0, i, ""); } move_window_to_cursor(curwin); cursupdate(curwin); update_buffer(curbuf); } return(TRUE); case CTRL('B'): wait_buffer = TRUE; return(FALSE); break; case CTRL('V'): (void) flexaddch(&Insbuff, CTRL('V')); literal_next = TRUE; return(FALSE); } } /* * If we get here, we want to insert the character into the buffer. */ /* * We may already have been in literal-next mode. * Careful not to reset this until after we need it. */ literal_next = FALSE; /* * Deal with wrapmargin. * * OK, so it isn't really right yet. */ if ((c == ' ' || c == '\t') && Pn(P_wrapmargin) != 0 && curwin->w_cursor->p_index >= curwin->w_ncols - Pn(P_wrapmargin)) { (void) i_proc('\n'); /* * We shouldn't really be putting the newline * in the redo buffer, so we change it to the * character we actually got. */ flexrmchar(&Insbuff); (void) flexaddch(&Insbuff, c); return(TRUE); } /* * Do the actual insertion of the new character. */ replchars(curwin, curpos->p_line, curpos->p_index, 0, mkstr(c)); /* * Update the screen. */ s_inschar(curwin, c); updateline(curwin); /* * Put the character into the insert buffer. */ (void) flexaddch(&Insbuff, c); /* * If showmatch mode is set, check for right parens * and braces. If there isn't a match, then beep. * If there is a match AND it's on the screen, * flash to it briefly. * * These characters included to make this * source file work okay with showmatch: [ { ( */ if (Pb(P_showmatch) && (c == ')' || c == '}' || c == ']')) { Posn *lpos, csave; lpos = showmatch(); if (lpos == NULL) { beep(curwin); } else if (!earlier(lpos->p_line, curwin->w_topline) && earlier(lpos->p_line, curwin->w_botline)) { /* * Show the match if it's on screen. */ update_buffer(curbuf); csave = *curpos; move_cursor(curwin, lpos->p_line, lpos->p_index); cursupdate(curwin); wind_goto(curwin); delay(); move_cursor(curwin, csave.p_line, csave.p_index); cursupdate(curwin); } } /* * Finally, move the cursor right one space. */ (void) one_right(curwin, TRUE); return(TRUE); } /* * This function is the interface provided for functions in * normal mode to go into insert mode. We only come out of * insert mode when the user presses escape. * * The parameter is a flag to say whether we should start * at the start of the line. * * Note that we do not have to call start_command() as the * caller does that for us - this is so the caller can include * any other stuff (e.g. an initial delete) into the command. */ void startinsert(startln) int startln; /* if set, insert point really at start of line */ { Insertloc = *curwin->w_cursor; if (startln) Insertloc.p_index = 0; flexclear(&Insbuff); State = INSERT; } /* * Process the given character, in replace mode. */ bool_t r_proc(c) int c; { Posn *curpos; static bool_t literal_next = FALSE; static bool_t wait_buffer = FALSE; curpos = curwin->w_cursor; if (wait_buffer || (!literal_next && c == CTRL('A'))) { /* * Add contents of named buffer, or the last * insert buffer if CTRL('A') was typed. */ if (!wait_buffer) { c = '<'; } yp_stuff_input(curwin, c, TRUE); wait_buffer = FALSE; return(FALSE); } else if (!literal_next) { switch (c) { case ESC: /* an escape ends input mode */ end_replace(c); return(TRUE); case '\b': /* back space */ case DEL: if (repstate == overwrite && curwin->w_virtcol > start_column) { (void) one_left(curwin, FALSE); replchars(curwin, curpos->p_line, curpos->p_index, 1, (curpos->p_index < nchars) ? mkstr(saved_line[curpos->p_index]) : ""); updateline(curwin); (void) flexaddch(&Insbuff, '\b'); } else { beep(curwin); if (repstate == replace_one) { end_replace(c); } } return(TRUE); case K_LARROW: /* left arrow */ if (repstate == overwrite && curwin->w_virtcol > start_column && one_left(curwin, FALSE)) { (void) flexaddch(&Insbuff, c); return(TRUE); } else { beep(curwin); if (repstate == replace_one) { end_replace(c); } return(FALSE); } case K_RARROW: /* right arrow */ if (repstate == overwrite && one_right(curwin, FALSE)) { (void) flexaddch(&Insbuff, c); return(TRUE); } else { beep(curwin); if (repstate == replace_one) { end_replace(c); } return(FALSE); } case '\r': /* new line */ case '\n': if (curpos->p_line->l_next == curbuf->b_lastline && repstate == overwrite) { /* * Don't allow splitting of last line of * buffer in overwrite mode. Why not? */ beep(curwin); return(TRUE); } if (repstate == replace_one) { echo &= ~e_CHARUPDATE; if (openfwd(TRUE) == FALSE) { show_error(curwin, "No buffer space!"); return(TRUE); } (void) flexaddch(&Insbuff, c); /* * Having split the line, we must * delete the character which was * supposed to be replaced with * the newline. */ replchars(curwin, curpos->p_line, curpos->p_index, 1, ""); repstate = got_one; end_replace('\n'); } else { (void) flexaddch(&Insbuff, '\n'); (void) onedown(curwin, 1L); /* * This is wrong, but it's difficult * to get it right. */ coladvance(curwin, start_column); free(saved_line); saved_line = strsave(curpos->p_line->l_text); if (saved_line == NULL) { State = NORMAL; return(TRUE); } nchars = strlen(saved_line); } return(TRUE); case CTRL('B'): wait_buffer = TRUE; return(FALSE); break; case CTRL('V'): (void) flexaddch(&Insbuff, CTRL('V')); literal_next = TRUE; return(TRUE); } } /* * If we get here, we want to insert the character into the buffer. */ /* * We may already have been in literal-next mode. * Careful not to reset this until after we need it. */ literal_next = FALSE; /* * Put the character into the insert buffer. */ (void) flexaddch(&Insbuff, c); if (repstate == overwrite || repstate == replace_one) { replchars(curwin, curpos->p_line, curpos->p_index, 1, mkstr(c)); updateline(curwin); (void) one_right(curwin, TRUE); } /* * If command was an 'r', leave replace mode after one character. */ if (repstate == replace_one) { repstate = got_one; end_replace(c); } return(TRUE); } /* * This function is the interface provided for functions in * normal mode to go into replace mode. We only come out of * replace mode when the user presses escape, or when they * used the 'r' command and typed a single character. * * The parameter is the command character which took us into replace mode. */ void startreplace(c) int c; { if (!start_command(curwin)) { return; } start_index = curwin->w_cursor->p_index; start_column = curwin->w_virtcol; if (c == 'r') { repstate = replace_one; saved_line = NULL; } else { repstate = overwrite; saved_line = strsave(curwin->w_cursor->p_line->l_text); if (saved_line == NULL) { beep(curwin); end_command(curwin); return; } nchars = strlen(saved_line); /* * Initialize Insbuff. Note that we don't do this if the * command was 'r', because they might type ESC to abort * the command, in which case we shouldn't change Insbuff. */ flexclear(&Insbuff); } State = REPLACE; } static void end_replace(c) int c; { Posn *curpos; char *cltext; curpos = curwin->w_cursor; State = NORMAL; end_command(curwin); /* * If (repstate == replace_one), they must have typed 'r', then * thought better of it & typed ESC; so we shouldn't complain or * change the buffer, the cursor position, or Insbuff. */ if (repstate != replace_one) { (void) yank_str('<', flexgetstr(&Insbuff), FALSE); flexclear(&Insbuff); /* * Free the saved line if necessary. */ if (repstate == overwrite) { free(saved_line); } /* * The cursor should end up on the * last replaced character. */ cltext = curpos->p_line->l_text; while (one_left(curwin, FALSE) && gchar(curwin->w_cursor) == '\0') { ; } if (!(echo & e_CHARUPDATE)) { echo |= e_CHARUPDATE; move_window_to_cursor(curwin); cursupdate(curwin); } update_buffer(curbuf); } } char * mkstr(c) int c; { static char s[2]; s[0] = c; s[1] = '\0'; return(s); }