📄 vi.c
字号:
/* * vi command editing * written by John Rochester (initially for nsh) * bludgeoned to fit pdksh by Larry Bouzane, Jeff Sparkes & Eric Gisin * */#include "config.h"#ifdef VI#include "sh.h"#include <ctype.h>#include "ksh_stat.h" /* completion */#include "edit.h"#define CMDLEN 1024#define Ctrl(c) (c&0x1f)#define is_wordch(c) (letnum(c))struct edstate { int winleft; char *cbuf; int cbufsize; int linelen; int cursor;};static int vi_hook ARGS((int ch));static void vi_reset ARGS((char *buf, size_t len));static int nextstate ARGS((int ch));static int vi_insert ARGS((int ch));static int vi_cmd ARGS((int argcnt, const char *cmd));static int domove ARGS((int argcnt, const char *cmd, int sub));static int redo_insert ARGS((int count));static void yank_range ARGS((int a, int b));static int bracktype ARGS((int ch));static void save_cbuf ARGS((void));static void restore_cbuf ARGS((void));static void edit_reset ARGS((char *buf, size_t len));static int putbuf ARGS((const char *buf, int len, int repl));static void del_range ARGS((int a, int b));static int findch ARGS((int ch, int cnt, int forw, int incl));static int forwword ARGS((int argcnt));static int backword ARGS((int argcnt));static int endword ARGS((int argcnt));static int Forwword ARGS((int argcnt));static int Backword ARGS((int argcnt));static int Endword ARGS((int argcnt));static int grabhist ARGS((int save, int n));static int grabsearch ARGS((int save, int start, int fwd, char *pat));static void redraw_line ARGS((int newline));static void refresh ARGS((int leftside));static int outofwin ARGS((void));static void rewindow ARGS((void));static int newcol ARGS((int ch, int col));static void display ARGS((char *wb1, char *wb2, int leftside));static void ed_mov_opt ARGS((int col, char *wb));static int expand_word ARGS((int command));static int complete_word ARGS((int command, int count));static int print_expansions ARGS((struct edstate *e, int command));static int char_len ARGS((int c));static void x_vi_zotc ARGS((int c));static void vi_pprompt ARGS((int full));static void vi_error ARGS((void));static void vi_macro_reset ARGS((void));#define C_ 0x1 /* a valid command that isn't a M_, E_, U_ */#define M_ 0x2 /* movement command (h, l, etc.) */#define E_ 0x4 /* extended command (c, d, y) */#define X_ 0x8 /* long command (@, f, F, t, T, etc.) */#define U_ 0x10 /* an UN-undoable command (that isn't a M_) */#define B_ 0x20 /* bad command (^@) */#define Z_ 0x40 /* repeat count defaults to 0 (not 1) */#define S_ 0x80 /* search (/, ?) */#define is_bad(c) (classify[(c)&0x7f]&B_)#define is_cmd(c) (classify[(c)&0x7f]&(M_|E_|C_|U_))#define is_move(c) (classify[(c)&0x7f]&M_)#define is_extend(c) (classify[(c)&0x7f]&E_)#define is_long(c) (classify[(c)&0x7f]&X_)#define is_undoable(c) (!(classify[(c)&0x7f]&U_))#define is_srch(c) (classify[(c)&0x7f]&S_)#define is_zerocount(c) (classify[(c)&0x7f]&Z_)const unsigned char classify[128] = { /* 0 1 2 3 4 5 6 7 */ /* 0 ^@ ^A ^B ^C ^D ^E ^F ^G */ B_, 0, 0, 0, 0, C_|U_, C_|Z_, 0, /* 01 ^H ^I ^J ^K ^L ^M ^N ^O */ M_, C_|Z_, 0, 0, C_|U_, 0, C_, 0, /* 02 ^P ^Q ^R ^S ^T ^U ^V ^W */ C_, 0, C_|U_, 0, 0, 0, C_, 0, /* 03 ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ */ C_, 0, 0, C_|Z_, 0, 0, 0, 0, /* 04 <space> ! " # $ % & ' */ M_, 0, 0, C_, M_, M_, 0, 0, /* 05 ( ) * + , - . / */ 0, 0, C_, C_, M_, C_, 0, C_|S_, /* 06 0 1 2 3 4 5 6 7 */ M_, 0, 0, 0, 0, 0, 0, 0, /* 07 8 9 : ; < = > ? */ 0, 0, 0, M_, 0, C_, 0, C_|S_, /* 010 @ A B C D E F G */ C_|X_, C_, M_, C_, C_, M_, M_|X_, C_|U_|Z_, /* 011 H I J K L M N O */ 0, C_, 0, 0, 0, 0, C_|U_, 0, /* 012 P Q R S T U V W */ C_, 0, C_, C_, M_|X_, C_, 0, M_, /* 013 X Y Z [ \ ] ^ _ */ C_, C_|U_, 0, 0, C_|Z_, 0, M_, C_|Z_, /* 014 ` a b c d e f g */ 0, C_, M_, E_, E_, M_, M_|X_, C_|Z_, /* 015 h i j k l m n o */ M_, C_, C_|U_, C_|U_, M_, 0, C_|U_, 0, /* 016 p q r s t u v w */ C_, 0, X_, C_, M_|X_, C_|U_, C_|U_|Z_,M_, /* 017 x y z { | } ~ ^? */ C_, E_|U_, 0, 0, M_|Z_, 0, C_, 0};#define MAXVICMD 3#define SRCHLEN 40#define INSERT 1#define REPLACE 2#define VNORMAL 0 /* command, insert or replace mode */#define VARG1 1 /* digit prefix (first, eg, 5l) */#define VEXTCMD 2 /* cmd + movement (eg, cl) */#define VARG2 3 /* digit prefix (second, eg, 2c3l) */#define VXCH 4 /* f, F, t, T, @ */#define VFAIL 5 /* bad command */#define VCMD 6 /* single char command (eg, X) */#define VREDO 7 /* . */#define VLIT 8 /* ^V */#define VSEARCH 9 /* /, ? */#define VVERSION 10 /* <ESC> ^V */static char undocbuf[CMDLEN];static struct edstate *save_edstate ARGS((struct edstate *old));static void restore_edstate ARGS((struct edstate *old, struct edstate *new));static void free_edstate ARGS((struct edstate *old));static struct edstate ebuf;static struct edstate undobuf = { 0, undocbuf, CMDLEN, 0, 0 };static struct edstate *es; /* current editor state */static struct edstate *undo;static char ibuf[CMDLEN]; /* input buffer */static int first_insert; /* set when starting in insert mode */static int saved_inslen; /* saved inslen for first insert */static int inslen; /* length of input buffer */static int srchlen; /* length of current search pattern */static char ybuf[CMDLEN]; /* yank buffer */static int yanklen; /* length of yank buffer */static int fsavecmd = ' '; /* last find command */static int fsavech; /* character to find */static char lastcmd[MAXVICMD]; /* last non-move command */static int lastac; /* argcnt for lastcmd */static int lastsearch = ' '; /* last search command */static char srchpat[SRCHLEN]; /* last search pattern */static int insert; /* non-zero in insert mode */static int hnum; /* position in history */static int ohnum; /* history line copied (after mod) */static int hlast; /* 1 past last position in history */static int modified; /* buffer has been "modified" */static int state;/* Information for keeping track of macros that are being expanded. * The format of buf is the alias contents followed by a null byte followed * by the name (letter) of the alias. The end of the buffer is marked by * a double null. The name of the alias is stored so recursive macros can * be detected. */struct macro_state { unsigned char *p; /* current position in buf */ unsigned char *buf; /* pointer to macro(s) being expanded */ int len; /* how much data in buffer */};static struct macro_state macro;enum expand_mode { NONE, EXPAND, COMPLETE, PRINT };static enum expand_mode expanded = NONE;/* last input was expanded */intx_vi(buf, len) char *buf; size_t len;{ int c; vi_reset(buf, len > CMDLEN ? CMDLEN : len); vi_pprompt(1); x_flush(); while (1) { if (macro.p) { c = *macro.p++; /* end of current macro? */ if (!c) { /* more macros left to finish? */ if (*macro.p++) continue; /* must be the end of all the macros */ vi_macro_reset(); c = x_getc(); } } else { c = x_getc(); } if (c == -1) break; if (state != VLIT) { if (c == edchars.intr || c == edchars.quit) { /* pretend we got an interrupt */ x_vi_zotc(c); x_flush(); trapsig(c == edchars.intr ? SIGINT : SIGQUIT); x_mode(FALSE); unwind(LSHELL); } else if (c == edchars.eof && state != VVERSION) { if (es->linelen == 0) { x_vi_zotc(edchars.eof); c = -1; break; } continue; } } if (vi_hook(c)) break; x_flush(); } x_putc('\r'); x_putc('\n'); x_flush(); if (c == -1) return -1; if (es->cbuf != buf) memmove(buf, es->cbuf, es->linelen); buf[es->linelen++] = '\n'; return es->linelen;}static intvi_hook(ch) int ch;{ static char curcmd[MAXVICMD]; static char locpat[SRCHLEN]; static int cmdlen; static int argc1, argc2; switch (state) { case VNORMAL: if (insert != 0) { if (ch == Ctrl('v')) { state = VLIT; ch = '^'; } switch (vi_insert(ch)) { case -1:#ifdef OS2 /* Arrow keys generate 0xe0X, where X is H.. */ state = VCMD; argc1 = 1; switch (x_getc()) { case 'H': *curcmd='k'; break; case 'K': *curcmd='h'; break; case 'P': *curcmd='j'; break; case 'M': *curcmd='l'; break; default: vi_error(); state = VNORMAL; } break;#else /* OS2 */ vi_error(); state = VNORMAL;#endif /* OS2 */ break; case 0: if (state == VLIT) { es->cursor--; refresh(0); } else refresh(insert != 0); break; case 1: return 1; } } else { if (ch == '\r' || ch == '\n') return 1; cmdlen = 0; argc1 = 0; if (ch >= '1' && ch <= '9') { argc1 = ch - '0'; state = VARG1; } else { curcmd[cmdlen++] = ch; state = nextstate(ch); if (state == VSEARCH) { save_cbuf(); es->cursor = 0; es->linelen = 0; if (ch == '/') { if (putbuf("/", 1, 0) != 0) { return -1; } } else if (putbuf("?", 1, 0) != 0) return -1; refresh(0); } if (state == VVERSION) { save_cbuf(); es->cursor = 0; es->linelen = 0; putbuf(ksh_version + 4, strlen(ksh_version + 4), 0); refresh(0); } } } break; case VLIT: if (is_bad(ch)) { del_range(es->cursor, es->cursor + 1); vi_error(); } else es->cbuf[es->cursor++] = ch; refresh(1); state = VNORMAL; break; case VVERSION: restore_cbuf(); state = VNORMAL; refresh(0); break; case VARG1: if (isdigit(ch)) argc1 = argc1 * 10 + ch - '0'; else { curcmd[cmdlen++] = ch; state = nextstate(ch); } break; case VEXTCMD: argc2 = 0; if (ch >= '1' && ch <= '9') { argc2 = ch - '0'; state = VARG2; return 0; } else { curcmd[cmdlen++] = ch; if (ch == curcmd[0]) state = VCMD; else if (is_move(ch)) state = nextstate(ch); else state = VFAIL; } break; case VARG2: if (isdigit(ch)) argc2 = argc2 * 10 + ch - '0'; else { if (argc1 == 0) argc1 = argc2; else argc1 *= argc2; curcmd[cmdlen++] = ch; if (ch == curcmd[0]) state = VCMD; else if (is_move(ch)) state = nextstate(ch); else state = VFAIL; } break; case VXCH: if (ch == Ctrl('[')) state = VNORMAL; else { curcmd[cmdlen++] = ch; state = VCMD; } break; case VSEARCH: if (ch == '\r' || ch == '\n' /*|| ch == Ctrl('[')*/ ) { restore_cbuf(); /* Repeat last search? */ if (srchlen == 0) { if (!srchpat[0]) { vi_error(); state = VNORMAL; refresh(0); return 0; } } else { locpat[srchlen] = '\0'; (void) strcpy(srchpat, locpat); } state = VCMD; } else if (ch == edchars.erase || ch == Ctrl('h')) { if (srchlen != 0) { srchlen--; es->linelen -= char_len((unsigned char) locpat[srchlen]); es->cursor = es->linelen; refresh(0); return 0; } restore_cbuf(); state = VNORMAL; refresh(0); } else if (ch == edchars.kill) { srchlen = 0; es->linelen = 1; es->cursor = 1; refresh(0); return 0; } else if (ch == edchars.werase) { int i; int n = srchlen; while (n > 0 && isspace(locpat[n - 1])) n--; while (n > 0 && !isspace(locpat[n - 1])) n--; for (i = srchlen; --i >= n; ) es->linelen -= char_len((unsigned char) locpat[i]); srchlen = n; es->cursor = es->linelen; refresh(0); return 0; } else { if (srchlen == SRCHLEN - 1) vi_error(); else { locpat[srchlen++] = ch; if ((ch & 0x80) && Flag(FVISHOW8)) { es->cbuf[es->linelen++] = 'M'; es->cbuf[es->linelen++] = '-'; ch &= 0x7f; } if (ch < ' ' || ch == 0x7f) { es->cbuf[es->linelen++] = '^'; es->cbuf[es->linelen++] = ch ^ '@'; } else es->cbuf[es->linelen++] = ch; es->cursor = es->linelen; refresh(0); } return 0; } break; } switch (state) { case VCMD: state = VNORMAL; switch (vi_cmd(argc1, curcmd)) { case -1: vi_error(); refresh(0); break; case 0: if (insert != 0) inslen = 0; refresh(insert != 0); break; case 1: refresh(0); return 1; case 2: /* back from a 'v' command - don't redraw the screen */ return 1; } break; case VREDO: state = VNORMAL; if (argc1 != 0) lastac = argc1; switch (vi_cmd(lastac, lastcmd)) { case -1: vi_error(); refresh(0); break; case 0: if (insert != 0) { if (lastcmd[0] == 's' || lastcmd[0] == 'c' || lastcmd[0] == 'C') { if (redo_insert(1) != 0) vi_error(); } else { if (redo_insert(lastac) != 0) vi_error(); } } refresh(0); break; case 1: refresh(0); return 1; case 2: /* back from a 'v' command - can't happen */ break; } break; case VFAIL: state = VNORMAL; vi_error(); break; } return 0;}static voidvi_reset(buf, len) char *buf; size_t len;{ state = VNORMAL; ohnum = hnum = hlast = histnum(-1) + 1; insert = INSERT; saved_inslen = inslen; first_insert = 1; inslen = 0; modified = 1; vi_macro_reset(); edit_reset(buf, len);}static intnextstate(ch) int ch;{ if (is_extend(ch)) return VEXTCMD; else if (is_srch(ch)) return VSEARCH; else if (is_long(ch)) return VXCH; else if (ch == '.') return VREDO; else if (ch == Ctrl('v')) return VVERSION; else if (is_cmd(ch)) return VCMD; else return VFAIL;}static intvi_insert(ch) int ch;{ int tcursor; if (ch == edchars.erase || ch == Ctrl('h')) { if (insert == REPLACE) { if (es->cursor == undo->cursor) { vi_error(); return 0; } if (inslen > 0) inslen--; es->cursor--; if (es->cursor >= undo->linelen) es->linelen--; else es->cbuf[es->cursor] = undo->cbuf[es->cursor]; } else { if (es->cursor == 0) { /* x_putc(BEL); no annoying bell here */ return 0; } if (inslen > 0) inslen--; es->cursor--; es->linelen--; memmove(&es->cbuf[es->cursor], &es->cbuf[es->cursor+1], es->linelen - es->cursor + 1); } expanded = NONE; return 0; } if (ch == edchars.kill) { if (es->cursor != 0) { inslen = 0; memmove(es->cbuf, &es->cbuf[es->cursor], es->linelen - es->cursor); es->linelen -= es->cursor; es->cursor = 0; } expanded = NONE; return 0; } if (ch == edchars.werase) { if (es->cursor != 0) { tcursor = Backword(1); memmove(&es->cbuf[tcursor], &es->cbuf[es->cursor], es->linelen - es->cursor); es->linelen -= es->cursor - tcursor; if (inslen < es->cursor - tcursor) inslen = 0; else inslen -= es->cursor - tcursor; es->cursor = tcursor; } expanded = NONE; return 0; } /* If any chars are entered before escape, trash the saved insert * buffer (if user inserts & deletes char, ibuf gets trashed and * we don't want to use it) */ if (first_insert && ch != Ctrl('[')) saved_inslen = 0; switch (ch) {#ifdef OS2 case 224: /* function key prefix */#endif /* OS2 */ case '\0': return -1; case '\r': case '\n': return 1; case Ctrl('['): expanded = NONE; if (first_insert) { first_insert = 0; if (inslen == 0) { inslen = saved_inslen; return redo_insert(0); } lastcmd[0] = 'a'; lastac = 1; } if (lastcmd[0] == 's' || lastcmd[0] == 'c' || lastcmd[0] == 'C') return redo_insert(0); else return redo_insert(lastac - 1); /* { Begin nonstandard vi commands */ case Ctrl('x'): expand_word(0); break; case Ctrl('f'): complete_word(0, 0); break; case Ctrl('e'): print_expansions(es, 0); break; case Ctrl('i'): if (Flag(FVITABCOMPLETE)) { complete_word(0, 0); break; } /* FALLTHROUGH */ /* End nonstandard vi commands } */ default: if (es->linelen == es->cbufsize - 1) return -1; ibuf[inslen++] = ch; if (insert == INSERT) { memmove(&es->cbuf[es->cursor+1], &es->cbuf[es->cursor], es->linelen - es->cursor); es->linelen++; } es->cbuf[es->cursor++] = ch; if (insert == REPLACE && es->cursor > es->linelen) es->linelen++; expanded = NONE; } return 0;}static intvi_cmd(argcnt, cmd) int argcnt; const char *cmd;{ int ncursor; int cur, c1, c2, c3 = 0; int any; struct edstate *t; if (argcnt == 0 && !is_zerocount(*cmd)) argcnt = 1; if (is_move(*cmd)) { if ((cur = domove(argcnt, cmd, 0)) >= 0) { if (cur == es->linelen && cur != 0) cur--;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -