/* Copyright (c) 1990,1991,1992 Chris and John Downey */ #ifndef lint static char *sccsid = "@(#)normal.c 2.7 (Chris & John Downey) 8/24/92"; #endif /*** * program name: xvi * function: PD version of UNIX "vi" editor, with extensions. * module name: normal.c * module function: Main routine for processing characters in command mode as well as routines for handling the operators. * 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 bool_t do_target P((int, int)); static bool_t do_cmd P((int, int)); static bool_t do_badcmd P((int, int)); static bool_t do_page P((int, int)); static bool_t do_scroll P((int, int)); static bool_t do_word P((int, int)); static bool_t do_csearch P((int, int)); static bool_t do_z P((int, int)); static bool_t do_x P((int, int)); static bool_t do_HLM P((int, int)); static bool_t do_rchar P((int, int)); static bool_t do_ins P((int, int)); static void op_shift P((int, int, int, long, Posn *, Posn *)); static void op_delete P((int, int, long, Posn *, Posn *)); static void op_change P((int, int, long, Posn *, Posn *)); static void op_yank P((Posn *, Posn *)); static bool_t dojoin P((void)); /* * Command type table. This is used for certain operations which are * the same for "classes" of commands, e.g. for disallowing their use * as targets of operators. * * Entries in this table having value 0 are unimplemented commands. * * If TARGET is set, the command may be used as the target for one of * the operators (e.g. 'c'); the default is that targets are character- * based unless TGT_LINE is set in which case they are line-based. * Similarly, the default is that targets are exclusive, unless the * TGT_INCLUSIVE flag is set. * * Q: WHAT DO WE DO ABOUT RETURN and LINEFEED??? * A: The ascii_map() macro (see ascii.h) handles this for QNX (I think). */ #define COMMAND 0x1 /* is implemented */ #define TARGET 0x2 /* can serve as a target */ #define TGT_LINE 0x4 /* a line-based target */ #define TGT_CHAR 0 /* a char-based target */ #define TGT_INCLUSIVE 0x8 /* an inclusive target */ #define TGT_EXCLUSIVE 0 /* an exclusive target */ #define TWO_CHAR 0x10 /* a two-character command */ static struct { bool_t (*c_func) P((int, int)); unsigned char c_flags; } cmd_types[256] = { /* ^@ */ do_badcmd, 0, /* ^A */ do_badcmd, 0, /* ^B */ do_page, COMMAND, /* ^C */ do_badcmd, 0, /* ^D */ do_scroll, COMMAND, /* ^E */ do_scroll, COMMAND, /* ^F */ do_page, COMMAND, /* ^G */ do_cmd, COMMAND, /* ^H */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* ^I */ do_badcmd, 0, /* ^J */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* ^K */ do_badcmd, 0, /* ^L */ do_cmd, COMMAND, /* ^M */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* ^N */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* ^O */ do_cmd, COMMAND, /* ^P */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* ^Q */ do_badcmd, 0, /* ^R */ do_cmd, COMMAND, /* ^S */ do_badcmd, 0, /* ^T */ do_cmd, COMMAND, /* ^U */ do_scroll, COMMAND, /* ^V */ do_badcmd, 0, /* ^W */ do_cmd, COMMAND, /* ^X */ do_badcmd, 0, /* ^Y */ do_scroll, COMMAND, /* ^Z */ do_cmd, COMMAND, /* ESCAPE */ do_cmd, COMMAND, /* ^\ */ do_badcmd, 0, /* ^] */ do_cmd, COMMAND, /* ^^ */ do_cmd, COMMAND, /* ^_ */ do_rchar, COMMAND, /* space */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* ! */ do_cmd, COMMAND, /* " */ do_cmd, COMMAND | TWO_CHAR, /* # */ do_badcmd, 0, /* $ */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_INCLUSIVE, /* % */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_INCLUSIVE, /* & */ do_cmd, COMMAND, /* ' */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE | TWO_CHAR, /* ( */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* ) */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* * */ do_badcmd, 0, /* + */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* , */ do_csearch, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* - */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* . */ do_cmd, COMMAND, /* / */ do_cmd, COMMAND, /* 0 */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* 1 */ do_badcmd, 0, /* 2 */ do_badcmd, 0, /* 3 */ do_badcmd, 0, /* 4 */ do_badcmd, 0, /* 5 */ do_badcmd, 0, /* 6 */ do_badcmd, 0, /* 7 */ do_badcmd, 0, /* 8 */ do_badcmd, 0, /* 9 */ do_badcmd, 0, /* : */ do_cmd, COMMAND, /* ; */ do_csearch, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* < */ do_cmd, COMMAND, /* = */ do_badcmd, 0, /* > */ do_cmd, COMMAND, /* ? */ do_cmd, COMMAND, /* @ */ do_cmd, COMMAND | TWO_CHAR, /* A */ do_ins, COMMAND, /* B */ do_word, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* C */ do_cmd, COMMAND, /* D */ do_cmd, COMMAND, /* E */ do_word, COMMAND | TARGET | TGT_CHAR | TGT_INCLUSIVE, /* F */ do_csearch, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE | TWO_CHAR, /* G */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* H */ do_HLM, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* I */ do_ins, COMMAND, /* J */ do_cmd, COMMAND, /* K */ do_badcmd, 0, /* L */ do_HLM, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* M */ do_HLM, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* N */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* O */ do_ins, COMMAND, /* P */ do_cmd, COMMAND, /* Q */ do_badcmd, 0, /* R */ do_cmd, COMMAND, /* S */ do_cmd, COMMAND, /* T */ do_csearch, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE | TWO_CHAR, /* U */ do_badcmd, 0, /* V */ do_badcmd, 0, /* W */ do_word, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* X */ do_x, COMMAND, /* Y */ do_cmd, COMMAND, /* Z */ do_cmd, COMMAND | TWO_CHAR, /* [ */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE | TWO_CHAR, /* \ */ do_badcmd, 0, /* ] */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE | TWO_CHAR, /* ^ */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* _ */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* ` */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE | TWO_CHAR, /* a */ do_ins, COMMAND, /* b */ do_word, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* c */ do_cmd, COMMAND, /* d */ do_cmd, COMMAND, /* e */ do_word, COMMAND | TARGET | TGT_CHAR | TGT_INCLUSIVE, /* f */ do_csearch, COMMAND | TARGET | TGT_CHAR | TGT_INCLUSIVE | TWO_CHAR, /* g */ do_cmd, COMMAND, /* h */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* i */ do_ins, COMMAND, /* j */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* k */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* l */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* m */ do_cmd, COMMAND | TWO_CHAR, /* n */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* o */ do_ins, COMMAND, /* p */ do_cmd, COMMAND, /* q */ do_badcmd, 0, /* r */ do_cmd, COMMAND, /* s */ do_cmd, COMMAND, /* t */ do_csearch, COMMAND | TARGET | TGT_CHAR | TGT_INCLUSIVE | TWO_CHAR, /* u */ do_cmd, COMMAND, /* v */ do_badcmd, 0, /* w */ do_word, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* x */ do_x, COMMAND, /* y */ do_cmd, COMMAND, /* z */ do_z, COMMAND | TWO_CHAR, /* { */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* | */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* } */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* ~ */ do_rchar, COMMAND, /* DEL */ do_badcmd, 0, /* K_HELP */ do_cmd, COMMAND, /* K_UNDO */ do_cmd, COMMAND, /* K_INSERT */ do_ins, COMMAND, /* K_HOME */ do_HLM, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* K_UARROW */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* K_DARROW */ do_target, COMMAND | TARGET | TGT_LINE | TGT_EXCLUSIVE, /* K_LARROW */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* K_RARROW */ do_target, COMMAND | TARGET | TGT_CHAR | TGT_EXCLUSIVE, /* K_CGRAVE */ do_cmd, COMMAND, }; #define NOP '\0' /* no pending operation */ static int operator = NOP; /* current pending operator */ /* * When a cursor motion command is made, it is marked as being a character * or line oriented motion. Then, if an operator is in effect, the operation * becomes character or line oriented accordingly. * * Character motions are marked as being inclusive or not. Most char. * motions are inclusive, but some (e.g. 'w') are not. */ static enum { m_bad, /* 'bad' motion type marks unusable yank buf */ m_nonincl, /* non-inclusive character motion */ m_incl, /* inclusive character motion */ m_line /* line-based motion */ } mtype; /* type of the current cursor motion */ /* * Cursor position at start of operator. */ static Posn startop; /* * Operators can have counts either before the operator, or between the * operator and the following cursor motion as in: * * d3w or 3dw * * The number is initially stored in Prenum as it is processed. * * If a count is given before an operator, it is saved in opnum when the * initial recognition of the operator takes place. If normal() is called * with a pending operator, the count in opnum (if present) overrides * any count that comes later. */ static long Prenum; static long opnum; /* * This variable contains the name of the yank/put buffer we are * currently using. It is set by the " command. The default value * is always '@'; other values are a-z and possibly ':'. */ static int cur_yp_name = '@'; /* * This state variable is TRUE if we got a preceding buffer * name: if set, the buffer_name variable contains the letter * which was given as the buffer name. */ static bool_t got_name = FALSE; static char buffer_name; /* * Return value of Prenum unless it is 0, in which case return the * default value of 1. */ #define LDEF1PRENUM ((Prenum == 0 ? 1L : Prenum)) /* * Return value of Prenum as an int, unless it is 0, in which case * return the default value of 1. * * Note that this assumes that Prenum will never be negative. */ #define IDEF1PRENUM (Prenum == 0 ? \ 1 : \ sizeof (int) == sizeof (long) || \ Prenum <= INT_MAX ? \ (int) Prenum : \ INT_MAX) /* * Redo buffer. */ struct redo { enum { r_insert, r_replace1, r_normal } r_mode; Flexbuf r_fb; } Redo; /* * Execute a command in "normal" (i.e. command) mode. */ bool_t normal(c) register int c; { /* * This variable is used to recall whether we got * an operator last time, to decide whether we * should apply it this time. */ register bool_t finish_op; /* * TRUE if we are awaiting a second character to finish the * current command - this is for two-character commands like * ZZ, and for commands taking a single character argument. * The "first_char" and "second_char" variables are for the * first character (stored between calls), and the second one. */ static bool_t two_char = FALSE; static int first_char; int second_char; unsigned char cflags; bool_t (*cfunc) P((int, int)) = NULL; /* * If the character is a digit, and it is not a leading '0', * compute Prenum instead of doing a command. Leading zeroes * are treated specially because '0' is a valid command. * * If two_char is set, don't treat digits this way; they are * passed in as the second character. This is because none of * the two-character commands are allowed to take prenums in * the middle; you want a prenum, you have to type it before * the command. So "t3" works as you might expect it to. */ if (!two_char && is_digit(c) && (c != '0' || Prenum > 0)) { Prenum = Prenum * 10 + (c - '0'); return(FALSE); } /* * If there is an operator pending, then the command we take * this time will terminate it. Finish_op tells us to finish * the operation before returning this time (unless it was * cancelled). */ finish_op = (operator != NOP); /* * If we're in the middle of an operator AND we had a count before * the operator, then that count overrides the current value of * Prenum. What this means effectively, is that commands like * "3dw" get turned into "d3w" which makes things fall into place * pretty neatly. */ if (finish_op || two_char) { if (opnum != 0) { Prenum = opnum; } } else { opnum = 0; } /* * If we got given a buffer name last time around, it is only * good for one operation; so at the start of each new command * we set or clear the yankput module's idea of the buffer name. * We don't do this if finish_op is set because it is not the * start of a new command. */ if (!finish_op) { cur_yp_name = got_name ? buffer_name : '@'; got_name = FALSE; } if (c > (sizeof(cmd_types) / sizeof(cmd_types[0]) - 1)) { operator = NOP; Prenum = 0; beep(curwin); return(FALSE); } /* * If two_char is set, it means we got the first character of * a two-character command last time. So check the second char, * and set cflags appropriately. */ if (two_char) { second_char = c; two_char = FALSE; cflags = cmd_types[ascii_map(first_char)].c_flags; cfunc = cmd_types[ascii_map(first_char)].c_func; /* * This seems to be a universal rule - if a two-character * command has ESC as the second character, it means "abort". */ if (second_char == ESC) { operator = NOP; finish_op = FALSE; Prenum = 0; return(FALSE); } } else { /* * Received a command. Find out its characteristics ... */ first_char = c; second_char = '\0'; cflags = cmd_types[ascii_map(first_char)].c_flags; cfunc = cmd_types[ascii_map(first_char)].c_func; /* * It's a two-character command. So wait until we get * the second character before proceeding. */ if (cflags & TWO_CHAR) { two_char = TRUE; first_char = c; if (Prenum != 0) { opnum = Prenum; Prenum = 0; } return(FALSE); } /* * If we got an operator last time, and the user * typed the same character again, we fake out the * default "apply to this line" rule by changing * the input character to a '_' which means "the * current line." */ if (finish_op) { if (operator == c) { first_char = c = '_'; cflags = cmd_types[ascii_map(c)].c_flags; cfunc = cmd_types[ascii_map(c)].c_func; } else if (!(cflags & TARGET)) { beep(curwin); operator = NOP; Prenum = 0; return(FALSE); } } } /* * At this point, cfunc must be set - if not, the entry in the * command table is zero, so disallow the input character. */ if (cfunc == NULL) { operator = NOP; Prenum = 0; beep(curwin); return(FALSE); } if (cflags & TARGET) { /* * A cursor movement command. */ if (cflags & TGT_LINE) { mtype = m_line; } else { if (cflags & TGT_INCLUSIVE) { mtype = m_incl; } else { mtype = m_nonincl; } } if (!(*cfunc)(first_char, second_char)) { beep(curwin); operator = NOP; Prenum = 0; return(FALSE); } /* * If an operation is pending, handle it... */ if (finish_op) { Posn top, bot; top = startop; bot = *curwin->w_cursor; /* * Put the cursor back to its starting position. */ move_cursor(curwin, startop.p_line, startop.p_index); if (lt(&bot, &top)) { pswap(&top, &bot); } switch (operator) { case '<': case '>': op_shift(operator, first_char, second_char, Prenum, &top, &bot); break; case 'd': op_delete(first_char, second_char, Prenum, &top, &bot); break; case 'y': op_yank(&top, &bot); break; case 'c': op_change(first_char, second_char, Prenum, &top, &bot); break; case '!': specify_pipe_range(curwin, top.p_line, bot.p_line); cmd_init(curwin, '!'); break; default: beep(curwin); } operator = NOP; } } else { /* * A command that does something. * Since it isn't a target, no operators need apply. */ if (finish_op) { beep(curwin); operator = NOP; } (void) (*cfunc)(first_char, second_char); } Prenum = 0; return(TRUE); } /* * Handle cursor movement commands. * These are used simply to move the cursor somewhere, * and also as targets for the various operators. * * If the return value is FALSE, the caller will complain * loudly to the user and cancel any outstanding operator. * The cursor should hopefully not have moved. * * Arguments are the first and second characters (where appropriate). */ static bool_t do_target(c1, c2) int c1, c2; { bool_t skip_spaces = FALSE; bool_t retval = TRUE; switch (c1) { case 'G': setpcmark(curwin); do_goto((Prenum > 0) ? Prenum : MAX_LINENO); skip_spaces = TRUE; break; case 'l': case ' ': c1 = K_RARROW; /* fall through ... */ case K_RARROW: case 'h': case K_LARROW: case CTRL('H'): { register bool_t (*mvfunc) P((Xviwin *, bool_t)); register long n; register long i; if (c1 == K_RARROW) { mvfunc = one_right; } else { mvfunc = one_left; } n = LDEF1PRENUM; for (i = 0; i < n; i++) { if (!(*mvfunc)(curwin, FALSE)) { break; } } if (i == 0) { retval = FALSE; } else { curwin->w_set_want_col = TRUE; } break; } case '-': skip_spaces = TRUE; /* FALL THROUGH */ case 'k': case K_UARROW: case CTRL('P'): if (!oneup(curwin, LDEF1PRENUM)) { retval = FALSE; } break; case '+': case '\r': skip_spaces = TRUE; /* FALL THROUGH */ case '\n': case 'j': case K_DARROW: case CTRL('N'): if (!onedown(curwin, LDEF1PRENUM)) { retval = FALSE; } break; /* * This is a strange motion command that helps make * operators more logical. It is actually implemented, * but not documented in the real 'vi'. This motion * command actually refers to "the current line". * Commands like "dd" and "yy" are really an alternate * form of "d_" and "y_". It does accept a count, so * "d3_" works to delete 3 lines. */ case '_': (void) onedown(curwin, LDEF1PRENUM - 1); break; case '|': begin_line(curwin, FALSE); if (Prenum > 0) { coladvance(curwin, LONG2INT(Prenum - 1)); } curwin->w_curswant = Prenum - 1; break; case '%': { Posn *pos = showmatch(); if (pos == NULL) { retval = FALSE; } else { setpcmark(curwin); move_cursor(curwin, pos->p_line, pos->p_index); curwin->w_set_want_col = TRUE; } } break; case '$': while (one_right(curwin, FALSE)) ; curwin->w_curswant = INT_MAX; /* so we stay at the end ... */ curwin->w_set_want_col = FALSE; break; case '^': case '0': begin_line(curwin, c1 == '^'); break; case 'n': case 'N': curwin->w_set_want_col = TRUE; (void) dosearch(curwin, "", c1); break; case '(': case ')': case '{': case '}': case '[': case ']': { int dir = FORWARD; char *pattern; Posn *newpos; switch (c1) { case '(': dir = BACKWARD; /*FALLTHROUGH*/ case ')': pattern = Ps(P_sentences); break; case '{': dir = BACKWARD; /*FALLTHROUGH*/ case '}': pattern = Ps(P_paragraphs); break; case '[': dir = BACKWARD; /*FALLTHROUGH*/ case ']': if (c1 != c2) { retval = FALSE; } else { pattern = Ps(P_sections); } } if (retval) { curwin->w_set_want_col = TRUE; newpos = find_pattern(pattern, dir, IDEF1PRENUM); if (newpos != NULL) { setpcmark(curwin); move_cursor(curwin, newpos->p_line, newpos->p_index); } else { retval = FALSE; } } break; } case '\'': case '`': { Posn *mark; mark = getmark(c2, curbuf); if (mark == NULL) { retval = FALSE; } else { Posn dest; /* * Record posn before re-setting the mark - * so that we don't accidentally side-effect * the place we are moving to! What a hack. */ dest = *mark; setpcmark(curwin); move_cursor(curwin, dest.p_line, c1 == '`' ? dest.p_index : 0); if (c1 == '`') { mtype = m_nonincl; } else { skip_spaces = TRUE; } } break; } } if (retval && skip_spaces) { begin_line(curwin, TRUE); } return(retval); } static bool_t do_cmd(c1, c2) int c1, c2; { switch (c1) { case K_HELP: do_help(curwin); break; case CTRL('R'): case CTRL('L'): redraw_screen(); break; case CTRL('G'): show_file_info(curwin); break; case CTRL(']'): /* :ta to current identifier */ tagword(); break; /* * Some convenient abbreviations... */ case 'D': stuff("\"%cd$", cur_yp_name); break; case 'Y': stuff("\"%c%dyy", cur_yp_name, IDEF1PRENUM); break; case 'C': stuff("\"%cc$", cur_yp_name); break; case 'S': stuff("\"%c%dcc", cur_yp_name, IDEF1PRENUM); break; /* * Operators. */ case 'd': case 'c': case 'y': case '>': case '<': case '!': if (Prenum != 0) opnum = Prenum; startop = *curwin->w_cursor; operator = c1; break; case 'p': case 'P': Redo.r_mode = r_normal; do_put(curwin, curwin->w_cursor, (c1 == 'p') ? FORWARD : BACKWARD, cur_yp_name); if (is_digit(cur_yp_name) && cur_yp_name != '0' && cur_yp_name != '9') { cur_yp_name++; } flexclear(&Redo.r_fb); (void) lformat(&Redo.r_fb, "\"%c%d%c", cur_yp_name, IDEF1PRENUM, c1); break; case 's': /* substitute characters */ start_command(curwin); replchars(curwin, curwin->w_cursor->p_line, curwin->w_cursor->p_index, IDEF1PRENUM, ""); updateline(curwin); Redo.r_mode = r_insert; flexclear(&Redo.r_fb); (void) lformat(&Redo.r_fb, "%lds", IDEF1PRENUM); startinsert(FALSE); break; case ':': case '?': case '/': cmd_init(curwin, c1); break; case '&': (void) do_ampersand(curwin, curwin->w_cursor->p_line, curwin->w_cursor->p_line, ""); begin_line(curwin, TRUE); updateline(curwin); break; case 'R': case 'r': Redo.r_mode = (c1 == 'r') ? r_replace1 : r_insert; flexclear(&Redo.r_fb); flexaddch(&Redo.r_fb, c1); startreplace(c1); break; case 'J': if (!dojoin()) beep(curwin); Redo.r_mode = r_normal; flexclear(&Redo.r_fb); flexaddch(&Redo.r_fb, c1); update_buffer(curbuf); break; case K_CGRAVE: /* shorthand command */ #ifndef QNX /* * We can't use this key on QNX. */ case CTRL('^'): #endif do_alt_edit(curwin); break; case 'u': case K_UNDO: undo(curwin); break; case CTRL('Z'): /* suspend editor */ do_suspend(curwin); break; /* * Buffer handling. */ case CTRL('T'): /* shrink window */ resize_window(curwin, - IDEF1PRENUM); move_cursor_to_window(curwin); break; case CTRL('W'): /* grow window */ resize_window(curwin, IDEF1PRENUM); break; case CTRL('O'): /* make window as large as possible */ resize_window(curwin, INT_MAX); break; case 'g': /* * Find the next window that the cursor * can be displayed in; i.e. at least one * text row is displayed. */ do { curwin = next_window(curwin); } while (curwin->w_nrows < 2); curbuf = curwin->w_buffer; move_cursor_to_window(curwin); wind_goto(curwin); break; case '"': got_name = TRUE; buffer_name = c2; break; case '@': yp_stuff_input(curwin, c2, TRUE); break; /* * Marks */ case 'm': if (!setmark(c2, curbuf, curwin->w_cursor)) beep(curwin); break; case 'Z': /* write, if changed, and exit */ if (c2 != 'Z') { beep(curwin); break; } /* * Make like a ":x" command. */ do_xit(curwin); break; case '.': /* * '.', meaning redo. As opposed to '.' as a target. */ stuff("%s", flexgetstr(&Redo.r_fb)); if (Redo.r_mode != r_normal) { yp_stuff_input(curwin, '<', TRUE); if (Redo.r_mode == r_insert) { stuff("%c", ESC); } } break; default: beep(curwin); break; } return(FALSE); } static bool_t do_badcmd(c1, c2) int c1, c2; { beep(curwin); return(FALSE); } /* * Handle page motion (control-F or control-B). */ static bool_t do_page(c1, c2) register int c1, c2; { long overlap; long n; /* * First move the cursor to the top of the screen * (for ^B), or to the top of the next screen (for ^F). */ move_cursor(curwin, (c1 == CTRL('B')) ? curwin->w_topline : curwin->w_botline, 0); /* * Cursor could have moved to the lastline of the buffer, * if the window is at the end of the buffer. Disallow * the cursor from being outside the buffer's bounds. */ if (curwin->w_cursor->p_line == curbuf->b_lastline) { move_cursor(curwin, curbuf->b_lastline->l_prev, 0); } /* * Decide on the amount of overlap to use. */ if (curwin->w_nrows > 10) { /* * At least 10 text lines in window. */ overlap = 2; } else if (curwin->w_nrows > 3) { /* * Between 3 and 9 text lines in window. */ overlap = 1; } else { /* * 1 or 2 text lines in window. */ overlap = 0; } /* * Given the overlap, decide where to move the cursor; * this will determine the new top line of the screen. */ if (c1 == CTRL('F')) { n = - overlap; n += (LDEF1PRENUM - 1) * (curwin->w_nrows - overlap - 1); } else { n = (- LDEF1PRENUM) * (curwin->w_nrows - overlap - 1); } if (n > 0) { (void) onedown(curwin, n); } else { (void) oneup(curwin, -n); } /* * Redraw the screen with the cursor at the top. */ begin_line(curwin, TRUE); curwin->w_topline = curwin->w_cursor->p_line; update_window(curwin); if (c1 == CTRL('B')) { /* * And move it to the bottom. */ move_window_to_cursor(curwin); cursupdate(curwin); move_cursor(curwin, curwin->w_botline->l_prev, 0); begin_line(curwin, TRUE); } /* * Finally, show where we are in the file. */ show_file_info(curwin); return(FALSE); } static bool_t do_scroll(c1, c2) int c1, c2; { switch (c1) { case CTRL('D'): scrollup(curwin, curwin->w_nrows / 2); (void) onedown(curwin, (long) (curwin->w_nrows / 2)); break; case CTRL('U'): scrolldown(curwin, curwin->w_nrows / 2); (void) oneup(curwin, (long) (curwin->w_nrows / 2)); break; case CTRL('E'): scrollup(curwin, (unsigned) IDEF1PRENUM); break; case CTRL('Y'): scrolldown(curwin, (unsigned) IDEF1PRENUM); break; } update_window(curwin); move_cursor_to_window(curwin); return(FALSE); } /* * Handle word motion ('w', 'W', 'b', 'B', 'e' or 'E'). */ static bool_t do_word(c1, c2) register int c1, c2; { register Posn *(*func) P((Posn *, int, bool_t)); register long n; register int lc; register int type; Posn pos; if (is_upper(c1)) { type = 1; lc = to_lower(c1); } else { type = 0; lc = c1; } curwin->w_set_want_col = TRUE; switch (lc) { case 'b': func = bck_word; break; case 'w': func = fwd_word; break; case 'e': func = end_word; break; } pos = *curwin->w_cursor; for (n = LDEF1PRENUM; n > 0; n--) { Posn *newpos; bool_t skip_whites; /* * "cw" is a special case; the whitespace after * the end of the last word involved in the change * does not get changed. The following code copes * with this strangeness. */ if (n == 1 && operator == 'c' && lc == 'w') { skip_whites = FALSE; mtype = m_incl; } else { skip_whites = TRUE; } newpos = (*func)(&pos, type, skip_whites); if (newpos == NULL) { return(FALSE); } if (n == 1 && lc == 'w' && operator != NOP && newpos->p_line != pos.p_line) { /* * We are on the last word to be operated * upon, and have crossed the line boundary. * This should not happen, so back up to * the end of the line the word is on. */ while (dec(newpos) == mv_SAMELINE) ; mtype = m_incl; } if (skip_whites == FALSE) { (void) dec(newpos); } pos = *newpos; } move_cursor(curwin, pos.p_line, pos.p_index); return(TRUE); } static bool_t do_csearch(c1, c2) int c1, c2; { bool_t retval = TRUE; Posn *pos; int dir; switch (c1) { case 'T': case 't': case 'F': case 'f': if (is_upper(c1)) { dir = BACKWARD; c1 = to_lower(c1); } else { dir = FORWARD; } curwin->w_set_want_col = TRUE; pos = searchc(c2, dir, (c1 == 't'), IDEF1PRENUM); break; case ',': case ';': /* * This should be FALSE for a backward motion. * How do we know it's a backward motion? * * Fix it later. */ mtype = m_incl; curwin->w_set_want_col = TRUE; pos = crepsearch(curbuf, c1 == ',', IDEF1PRENUM); break; } if (pos == NULL) { retval = FALSE; } else { move_cursor(curwin, pos->p_line, pos->p_index); } return(retval); } /* * Handle adjust window command ('z'). */ static bool_t do_z(c1, c2) int c1, c2; { Line *lp; int l; int znum; switch (c2) { case '\n': /* put cursor at top of screen */ case '\r': znum = 1; break; case '.': /* put cursor in middle of screen */ znum = curwin->w_nrows / 2; break; case '-': /* put cursor at bottom of screen */ znum = curwin->w_nrows - 1; break; default: return(FALSE); } if (Prenum > 0) { do_goto(Prenum); } lp = curwin->w_cursor->p_line; for (l = 0; l < znum && lp != curbuf->b_line0; ) { l += plines(curwin, lp); curwin->w_topline = lp; lp = lp->l_prev; } cursupdate(curwin); update_window(curwin); return(TRUE); } /* * Handle character delete commands ('x' or 'X'). */ static bool_t do_x(c1, c2) int c1, c2; { Posn *curp; Posn lastpos; int nchars; int i; nchars = IDEF1PRENUM; Redo.r_mode = r_normal; flexclear(&Redo.r_fb); (void) lformat(&Redo.r_fb, "%d%c", nchars, c1); curp = curwin->w_cursor; if (c1 == 'X') { for (i = 0; i < nchars && one_left(curwin, FALSE); i++) ; nchars = i; if (nchars == 0) { beep(curwin); return(TRUE); } } else /* c1 == 'x' */ { char *line; /* * Ensure that nchars is not too big. */ line = curp->p_line->l_text + curp->p_index; for (i = 0; i < nchars && line[i] != '\0'; i++) ; nchars = i; if (curp->p_line->l_text[0] == '\0') { /* * Can't do it on a blank line. */ beep(curwin); return(TRUE); } } lastpos.p_line = curp->p_line; lastpos.p_index = curp->p_index + nchars - 1; yp_push_deleted(); (void) do_yank(curbuf, curp, &lastpos, TRUE, cur_yp_name); replchars(curwin, curp->p_line, curp->p_index, nchars, ""); if (curp->p_line->l_text[curp->p_index] == '\0') { (void) one_left(curwin, FALSE); } updateline(curwin); return(TRUE); } /* * Handle home ('H') end of page ('L') and middle line ('M') motion commands. */ static bool_t do_HLM(c1, c2) int c1, c2; { register bool_t (*mvfunc) P((Xviwin *, long)); register long n; if (c1 == K_HOME) { c1 = 'H'; } /* * Silly to specify a number before 'H' or 'L' * which would move us off the screen. */ if (Prenum >= curwin->w_nrows) { return(FALSE); } move_cursor(curwin, (c1 == 'L') ? curwin->w_botline->l_prev : curwin->w_topline, 0); switch (c1) { case 'H': mvfunc = onedown; n = Prenum - 1; break; case 'L': mvfunc = oneup; n = Prenum - 1; break; case 'M': mvfunc = onedown; n = (long) (curwin->w_nrows - 1) / 2; } (void) (*mvfunc)(curwin, n); begin_line(curwin, TRUE); return(TRUE); } /* * Handle '~' and CTRL('_') commands. */ static bool_t do_rchar(c1, c2) int c1, c2; { Posn *cp; char *tp; int c; char newc[2]; Redo.r_mode = r_normal; flexclear(&Redo.r_fb); flexaddch(&Redo.r_fb, c1); cp = curwin->w_cursor; tp = cp->p_line->l_text; if (tp[0] == '\0') { /* * Can't do it on a blank line. */ beep(curwin); return(FALSE); } c = tp[cp->p_index]; switch (c1) { case '~': newc[0] = is_alpha(c) ? is_lower(c) ? to_upper(c) : to_lower(c) : c; break; case CTRL('_'): /* flip top bit */ #ifdef TOP_BIT newc[0] = c ^ TOP_BIT; if (newc[0] == '\0') { newc[0] = c; } #else /* not TOP_BIT */ beep(curwin); return(FALSE); #endif /* TOP_BIT */ } newc[1] = '\0'; replchars(curwin, cp->p_line, cp->p_index, 1, newc); updateline(curwin); (void) one_right(curwin, FALSE); return(FALSE); } /* * Handle commands which just go into insert mode * ('i', 'a', 'I', 'A', 'o', 'O'). */ static bool_t do_ins(c1, c2) int c1, c2; { bool_t startpos = TRUE; /* FALSE means start position moved */ if (!start_command(curwin)) { return(FALSE); } Redo.r_mode = r_insert; flexclear(&Redo.r_fb); flexaddch(&Redo.r_fb, c1); switch (c1) { case 'o': case 'O': if (((c1 == 'o') ? openfwd(FALSE) : openbwd()) == FALSE) { beep(curwin); end_command(curwin); return(FALSE); } break; case 'I': begin_line(curwin, TRUE); startpos = FALSE; break; case 'A': while (one_right(curwin, TRUE)) ; startpos = FALSE; break; case 'a': /* * 'a' works just like an 'i' on the next character. */ (void) one_right(curwin, TRUE); startpos = FALSE; } startinsert(startpos); return(FALSE); } /* * Handle a shift operation. The prenum and operator/operands are * passed, along with the first and last positions to be shifted. */ static void op_shift(op, c1, c2, num, top, bottom) int op; int c1, c2; long num; Posn *top; Posn *bottom; { /* * Do the shift. */ tabinout(op, top->p_line, bottom->p_line); /* * Put cursor on first non-white of line; this is good if the * cursor is in the range of lines specified, which is normally * will be. */ begin_line(curwin, TRUE); update_buffer(curbuf); /* * Construct redo buffer. */ Redo.r_mode = r_normal; flexclear(&Redo.r_fb); if (num != 0) { (void) lformat(&Redo.r_fb, "%c%ld%c%c", op, num, c1, c2); } else { (void) lformat(&Redo.r_fb, "%c%c%c", op, c1, c2); } } /* * op_delete - handle a delete operation * The characters "c1" and "c2" are the target character and * its argument (if it takes one) respectively. E.g. 'f', '/'. * The "num" argument is the numeric prefix. */ static void op_delete(c1, c2, num, top, bottom) int c1, c2; long num; Posn *top; Posn *bottom; { long nlines; int n; /* * If the target is non-inclusive, move back a character. * We assume it is okay to do this, and it seems to work, * so no checking is performed at the moment. */ if (mtype == m_nonincl) { (void) dec(bottom); } nlines = cntllines(top->p_line, bottom->p_line); /* * Do a yank of whatever we're about to delete. If there's too much * stuff to fit in the yank buffer, disallow the delete, since we * probably wouldn't have enough memory to do it anyway. */ yp_push_deleted(); if (!do_yank(curbuf, top, bottom, (mtype != m_line), cur_yp_name)) { show_error(curwin, "Not enough memory to perform delete"); return; } if (mtype == m_line) { /* * Put the cursor at the start of the section to be deleted * so that repllines will correctly update it and the screen * pointer, and update the screen. */ move_cursor(curwin, top->p_line, 0); repllines(curwin, top->p_line, nlines, (Line *) NULL); begin_line(curwin, TRUE); } else { /* * After a char-based delete, the cursor should always be * on the character following the last character of the * section being deleted. The easiest way to achieve this * is to put it on the character before the section to be * deleted (which will not be affected), and then move one * place right afterwards. */ move_cursor(curwin, top->p_line, top->p_index - ((top->p_index > 0) ? 1 : 0)); if (top->p_line == bottom->p_line) { /* * Delete characters within line. */ n = (bottom->p_index - top->p_index) + 1; replchars(curwin, top->p_line, top->p_index, n, ""); } else { /* * Character-based delete between lines. * So we actually have to do three deletes; * one to delete to the end of the top line, * one to delete the intervening lines, and * one to delete up to the target position. */ if (!start_command(curwin)) { return; } /* * First delete part of the last line. */ replchars(curwin, bottom->p_line, 0, bottom->p_index + 1, ""); /* * Now replace the rest of the top line with the * remainder of the bottom line. */ replchars(curwin, top->p_line, top->p_index, INT_MAX, bottom->p_line->l_text); /* * Finally, delete all lines from (top + 1) to bot, * inclusive. */ repllines(curwin, top->p_line->l_next, cntllines(top->p_line, bottom->p_line) - 1, (Line *) NULL); end_command(curwin); } if (top->p_index > 0) { (void) one_right(curwin, FALSE); } } /* * Construct redo buffer. */ Redo.r_mode = r_normal; flexclear(&Redo.r_fb); if (num != 0) { (void) lformat(&Redo.r_fb, "d%ld%c%c", num, c1, c2); } else { (void) lformat(&Redo.r_fb, "d%c%c", c1, c2); } if (mtype != m_line && nlines == 1) { updateline(curwin); } else { update_buffer(curbuf); } } /* * op_change - handle a change operation */ static void op_change(c1, c2, num, top, bottom) int c1, c2; long num; Posn *top; Posn *bottom; { bool_t doappend; /* true if we should do append, not insert */ /* * Start the command here so the initial delete gets * included in the meta-command and hence undo will * work properly. */ if (!start_command(curwin)) { return; } if (mtype == m_line) { long nlines; Line *lp; /* * This is a bit awkward ... for a line-based change, we don't * actually delete the whole range of lines, but instead leave * the first line in place and delete its text after the cursor * position. However, yanking the whole thing is probably okay. */ yp_push_deleted(); if (!do_yank(curbuf, top, bottom, FALSE, cur_yp_name)) { show_error(curwin, "Not enough memory to perform change"); return; } lp = top->p_line; nlines = cntllines(lp, bottom->p_line); if (nlines > 1) { repllines(curwin, lp->l_next, nlines - 1, (Line *) NULL); } move_cursor(curwin, lp, 0); /* * This is not right; it won't do the right thing when * the cursor is in the whitespace of an indented line. * However, it will do for the moment. */ begin_line(curwin, TRUE); replchars(curwin, lp, curwin->w_cursor->p_index, strlen(lp->l_text), ""); update_buffer(curbuf); } else { /* * A character-based change really is just a delete and an insert. * So use the deletion code to make things easier. */ doappend = (mtype == m_incl) && endofline(bottom); op_delete(c1, c2, num, top, bottom); if (doappend) { (void) one_right(curwin, TRUE); } } Redo.r_mode = r_insert; flexclear(&Redo.r_fb); if (num != 0) { (void) lformat(&Redo.r_fb, "c%ld%c%c", num, c1, c2); } else { (void) lformat(&Redo.r_fb, "c%c%c", c1, c2); } startinsert(FALSE); } static void op_yank(top, bottom) Posn *top; Posn *bottom; { long nlines; /* * Report on the number of lines yanked. */ nlines = cntllines(top->p_line, bottom->p_line); if (nlines > Pn(P_report)) { show_message(curwin, "%ld lines yanked", nlines); } /* * If the target is non-inclusive and character-based, * reduce the final position by one. */ if (mtype == m_nonincl) { (void) dec(bottom); } (void) do_yank(curbuf, top, bottom, (mtype != m_line), cur_yp_name); } static bool_t dojoin() { register Posn *curr_pos; /* cursor position (abbreviation) */ register Line *curr_line; /* line we started the join on */ char *nextline; /* text of subsequent line */ int join_index; /* index of start of new text section */ int size1; /* size of the first line */ int size2; /* size of the second line */ curr_pos = curwin->w_cursor; curr_line = curr_pos->p_line; /* * If we are on the last line, we can't join. */ if (curr_line->l_next == curbuf->b_lastline) return(FALSE); if (!start_command(curwin)) { return(FALSE); } /* * Move cursor to end of line, and find out * exactly where we will place the new text. */ while (one_right(curwin, FALSE)) ; join_index = curr_pos->p_index; if (curr_line->l_text[join_index] != '\0') join_index += 1; /* * Copy the text of the next line after the end of the * current line, but don't copy any initial whitespace. * Then delete the following line. */ nextline = curr_line->l_next->l_text; while (*nextline == ' ' || *nextline == '\t') nextline++; size1 = strlen(curr_line->l_text); size2 = strlen(nextline); replchars(curwin, curr_line, join_index, 0, nextline); repllines(curwin, curr_line->l_next, (long) 1, (Line *) NULL); if (size1 != 0 && size2 != 0) { /* * If there is no whitespace on this line, * insert a single space. */ if (gchar(curr_pos) != ' ' && gchar(curr_pos) != '\t') { replchars(curwin, curr_line, curr_pos->p_index + 1, 0, " "); } /* * Make sure the cursor sits on the right character. */ (void) one_right(curwin, FALSE); } end_command(curwin); update_buffer(curbuf); return(TRUE); }