This opens an OpenBSD-mirabile (aka MirBSD) repository. ### MirBSD is: # Copyright (c) 1982-2003 by Thorsten "mirabile" Glaser <x86@ePost.de> # Copyright © 1968-2003 The authors of And contributors to UNIX®, the # C Language, BSD/Berkeley Unix; 386BSD, NetBSD 1.1 and OpenBSD. # # Anyone who obtained a copy of this work is hereby permitted to freely use, # distribute, modify, merge, sublicence, give away or sell it as long as the # authors are given due credit and the following notice is retained: # # This work is provided "as is", with no explicit or implicit warranty what- # soever. Use it only at your own risk. In no event may an author or contri- # butor be held liable for any damage, directly or indirectly, that origina- # ted through or is caused by creation or modification of this work. MirBSD is my private tree. MirBSD does not differ very much from OpenBSD and intentionally tracks OpenBSD. That's why it _is_ OpenBSD, just not the official one. It's like with DarrenBSD. At time of this writing, no advertising for MirBSD must be done, because the advertising clause has not yet been sorted out. http://templeofhate.com/tglaser/MirBSD/index.php
2191 lines
44 KiB
C
2191 lines
44 KiB
C
/* $OpenBSD: vi.c,v 1.11 2003/03/13 09:03:07 deraadt Exp $ */
|
|
|
|
/*
|
|
* 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));
|
|
static int x_vi_putbuf ARGS((const char *s, size_t len));
|
|
|
|
#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 */
|
|
|
|
int
|
|
x_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 int
|
|
vi_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) strlcpy(srchpat, locpat, sizeof srchpat);
|
|
}
|
|
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 void
|
|
vi_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 int
|
|
nextstate(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 int
|
|
vi_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 int
|
|
vi_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--;
|
|
es->cursor = cur;
|
|
} else
|
|
return -1;
|
|
} else {
|
|
/* Don't save state in middle of macro.. */
|
|
if (is_undoable(*cmd) && !macro.p) {
|
|
undo->winleft = es->winleft;
|
|
memmove(undo->cbuf, es->cbuf, es->linelen);
|
|
undo->linelen = es->linelen;
|
|
undo->cursor = es->cursor;
|
|
lastac = argcnt;
|
|
memmove(lastcmd, cmd, MAXVICMD);
|
|
}
|
|
switch (*cmd) {
|
|
|
|
case Ctrl('l'):
|
|
case Ctrl('r'):
|
|
redraw_line(1);
|
|
break;
|
|
|
|
case '@':
|
|
{
|
|
static char alias[] = "_\0";
|
|
struct tbl *ap;
|
|
int olen, nlen;
|
|
char *p, *nbuf;
|
|
|
|
/* lookup letter in alias list... */
|
|
alias[1] = cmd[1];
|
|
ap = tsearch(&aliases, alias, hash(alias));
|
|
if (!cmd[1] || !ap || !(ap->flag & ISSET))
|
|
return -1;
|
|
/* check if this is a recursive call... */
|
|
if ((p = (char *) macro.p))
|
|
while ((p = strchr(p, '\0')) && p[1])
|
|
if (*++p == cmd[1])
|
|
return -1;
|
|
/* insert alias into macro buffer */
|
|
nlen = strlen(ap->val.s) + 1;
|
|
olen = !macro.p ? 2
|
|
: macro.len - (macro.p - macro.buf);
|
|
nbuf = alloc(nlen + 1 + olen, APERM);
|
|
memcpy(nbuf, ap->val.s, nlen);
|
|
nbuf[nlen++] = cmd[1];
|
|
if (macro.p) {
|
|
memcpy(nbuf + nlen, macro.p, olen);
|
|
afree(macro.buf, APERM);
|
|
nlen += olen;
|
|
} else {
|
|
nbuf[nlen++] = '\0';
|
|
nbuf[nlen++] = '\0';
|
|
}
|
|
macro.p = macro.buf = (unsigned char *) nbuf;
|
|
macro.len = nlen;
|
|
}
|
|
break;
|
|
|
|
case 'a':
|
|
modified = 1; hnum = hlast;
|
|
if (es->linelen != 0)
|
|
es->cursor++;
|
|
insert = INSERT;
|
|
break;
|
|
|
|
case 'A':
|
|
modified = 1; hnum = hlast;
|
|
del_range(0, 0);
|
|
es->cursor = es->linelen;
|
|
insert = INSERT;
|
|
break;
|
|
|
|
case 'S':
|
|
es->cursor = domove(1, "^", 1);
|
|
del_range(es->cursor, es->linelen);
|
|
modified = 1; hnum = hlast;
|
|
insert = INSERT;
|
|
break;
|
|
|
|
case 'Y':
|
|
cmd = "y$";
|
|
/* ahhhhhh... */
|
|
case 'c':
|
|
case 'd':
|
|
case 'y':
|
|
if (*cmd == cmd[1]) {
|
|
c1 = *cmd == 'c' ? domove(1, "^", 1) : 0;
|
|
c2 = es->linelen;
|
|
} else if (!is_move(cmd[1]))
|
|
return -1;
|
|
else {
|
|
if ((ncursor = domove(argcnt, &cmd[1], 1)) < 0)
|
|
return -1;
|
|
if (*cmd == 'c' &&
|
|
(cmd[1]=='w' || cmd[1]=='W') &&
|
|
!isspace(es->cbuf[es->cursor])) {
|
|
while (isspace(es->cbuf[--ncursor]))
|
|
;
|
|
ncursor++;
|
|
}
|
|
if (ncursor > es->cursor) {
|
|
c1 = es->cursor;
|
|
c2 = ncursor;
|
|
} else {
|
|
c1 = ncursor;
|
|
c2 = es->cursor;
|
|
if (cmd[1] == '%')
|
|
c2++;
|
|
}
|
|
}
|
|
if (*cmd != 'c' && c1 != c2)
|
|
yank_range(c1, c2);
|
|
if (*cmd != 'y') {
|
|
del_range(c1, c2);
|
|
es->cursor = c1;
|
|
}
|
|
if (*cmd == 'c') {
|
|
modified = 1; hnum = hlast;
|
|
insert = INSERT;
|
|
}
|
|
break;
|
|
|
|
case 'p':
|
|
modified = 1; hnum = hlast;
|
|
if (es->linelen != 0)
|
|
es->cursor++;
|
|
while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
|
|
;
|
|
if (es->cursor != 0)
|
|
es->cursor--;
|
|
if (argcnt != 0)
|
|
return -1;
|
|
break;
|
|
|
|
case 'P':
|
|
modified = 1; hnum = hlast;
|
|
any = 0;
|
|
while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
|
|
any = 1;
|
|
if (any && es->cursor != 0)
|
|
es->cursor--;
|
|
if (argcnt != 0)
|
|
return -1;
|
|
break;
|
|
|
|
case 'C':
|
|
modified = 1; hnum = hlast;
|
|
del_range(es->cursor, es->linelen);
|
|
insert = INSERT;
|
|
break;
|
|
|
|
case 'D':
|
|
yank_range(es->cursor, es->linelen);
|
|
del_range(es->cursor, es->linelen);
|
|
if (es->cursor != 0)
|
|
es->cursor--;
|
|
break;
|
|
|
|
case 'g':
|
|
if (!argcnt)
|
|
argcnt = hlast + 1;
|
|
/* fall through */
|
|
case 'G':
|
|
if (!argcnt)
|
|
argcnt = 1;
|
|
else
|
|
argcnt = hlast - (source->line - argcnt);
|
|
if (grabhist(modified, argcnt - 1) < 0)
|
|
return -1;
|
|
else {
|
|
modified = 0;
|
|
hnum = argcnt - 1;
|
|
}
|
|
break;
|
|
|
|
case 'i':
|
|
modified = 1; hnum = hlast;
|
|
insert = INSERT;
|
|
break;
|
|
|
|
case 'I':
|
|
modified = 1; hnum = hlast;
|
|
es->cursor = domove(1, "^", 1);
|
|
insert = INSERT;
|
|
break;
|
|
|
|
case 'j':
|
|
case '+':
|
|
case Ctrl('n'):
|
|
if (grabhist(modified, hnum + argcnt) < 0)
|
|
return -1;
|
|
else {
|
|
modified = 0;
|
|
hnum += argcnt;
|
|
}
|
|
break;
|
|
|
|
case 'k':
|
|
case '-':
|
|
case Ctrl('p'):
|
|
if (grabhist(modified, hnum - argcnt) < 0)
|
|
return -1;
|
|
else {
|
|
modified = 0;
|
|
hnum -= argcnt;
|
|
}
|
|
break;
|
|
|
|
case 'r':
|
|
if (es->linelen == 0)
|
|
return -1;
|
|
modified = 1; hnum = hlast;
|
|
if (cmd[1] == 0)
|
|
vi_error();
|
|
else
|
|
es->cbuf[es->cursor] = cmd[1];
|
|
break;
|
|
|
|
case 'R':
|
|
modified = 1; hnum = hlast;
|
|
insert = REPLACE;
|
|
break;
|
|
|
|
case 's':
|
|
if (es->linelen == 0)
|
|
return -1;
|
|
modified = 1; hnum = hlast;
|
|
if (es->cursor + argcnt > es->linelen)
|
|
argcnt = es->linelen - es->cursor;
|
|
del_range(es->cursor, es->cursor + argcnt);
|
|
insert = INSERT;
|
|
break;
|
|
|
|
case 'v':
|
|
if (es->linelen == 0)
|
|
return -1;
|
|
if (!argcnt) {
|
|
if (modified) {
|
|
es->cbuf[es->linelen] = '\0';
|
|
source->line++;
|
|
histsave(source->line, es->cbuf, 1);
|
|
} else
|
|
argcnt = source->line + 1
|
|
- (hlast - hnum);
|
|
}
|
|
shf_snprintf(es->cbuf, es->cbufsize,
|
|
argcnt ? "%s %d" : "%s",
|
|
"fc -e ${VISUAL:-${EDITOR:-vi}} --",
|
|
argcnt);
|
|
es->linelen = strlen(es->cbuf);
|
|
return 2;
|
|
|
|
case 'x':
|
|
if (es->linelen == 0)
|
|
return -1;
|
|
modified = 1; hnum = hlast;
|
|
if (es->cursor + argcnt > es->linelen)
|
|
argcnt = es->linelen - es->cursor;
|
|
yank_range(es->cursor, es->cursor + argcnt);
|
|
del_range(es->cursor, es->cursor + argcnt);
|
|
break;
|
|
|
|
case 'X':
|
|
if (es->cursor > 0) {
|
|
modified = 1; hnum = hlast;
|
|
if (es->cursor < argcnt)
|
|
argcnt = es->cursor;
|
|
yank_range(es->cursor - argcnt, es->cursor);
|
|
del_range(es->cursor - argcnt, es->cursor);
|
|
es->cursor -= argcnt;
|
|
} else
|
|
return -1;
|
|
break;
|
|
|
|
case 'u':
|
|
t = es;
|
|
es = undo;
|
|
undo = t;
|
|
break;
|
|
|
|
case 'U':
|
|
if (!modified)
|
|
return -1;
|
|
if (grabhist(modified, ohnum) < 0)
|
|
return -1;
|
|
modified = 0;
|
|
hnum = ohnum;
|
|
break;
|
|
|
|
case '?':
|
|
if (hnum == hlast)
|
|
hnum = -1;
|
|
/* ahhh */
|
|
case '/':
|
|
c3 = 1;
|
|
srchlen = 0;
|
|
lastsearch = *cmd;
|
|
/* fall through */
|
|
case 'n':
|
|
case 'N':
|
|
if (lastsearch == ' ')
|
|
return -1;
|
|
if (lastsearch == '?')
|
|
c1 = 1;
|
|
else
|
|
c1 = 0;
|
|
if (*cmd == 'N')
|
|
c1 = !c1;
|
|
if ((c2 = grabsearch(modified, hnum,
|
|
c1, srchpat)) < 0) {
|
|
if (c3) {
|
|
restore_cbuf();
|
|
refresh(0);
|
|
}
|
|
return -1;
|
|
} else {
|
|
modified = 0;
|
|
hnum = c2;
|
|
ohnum = hnum;
|
|
}
|
|
break;
|
|
case '_': {
|
|
int inspace;
|
|
char *p, *sp;
|
|
|
|
if (histnum(-1) < 0)
|
|
return -1;
|
|
p = *histpos();
|
|
#define issp(c) (isspace((c)) || (c) == '\n')
|
|
if (argcnt) {
|
|
while (*p && issp(*p))
|
|
p++;
|
|
while (*p && --argcnt) {
|
|
while (*p && !issp(*p))
|
|
p++;
|
|
while (*p && issp(*p))
|
|
p++;
|
|
}
|
|
if (!*p)
|
|
return -1;
|
|
sp = p;
|
|
} else {
|
|
sp = p;
|
|
inspace = 0;
|
|
while (*p) {
|
|
if (issp(*p))
|
|
inspace = 1;
|
|
else if (inspace) {
|
|
inspace = 0;
|
|
sp = p;
|
|
}
|
|
p++;
|
|
}
|
|
p = sp;
|
|
}
|
|
modified = 1; hnum = hlast;
|
|
if (es->cursor != es->linelen)
|
|
es->cursor++;
|
|
while (*p && !issp(*p)) {
|
|
argcnt++;
|
|
p++;
|
|
}
|
|
if (putbuf(space, 1, 0) != 0)
|
|
argcnt = -1;
|
|
else if (putbuf(sp, argcnt, 0) != 0)
|
|
argcnt = -1;
|
|
if (argcnt < 0) {
|
|
if (es->cursor != 0)
|
|
es->cursor--;
|
|
return -1;
|
|
}
|
|
insert = INSERT;
|
|
}
|
|
break;
|
|
|
|
case '~': {
|
|
char *p;
|
|
int i;
|
|
|
|
if (es->linelen == 0)
|
|
return -1;
|
|
for (i = 0; i < argcnt; i++) {
|
|
p = &es->cbuf[es->cursor];
|
|
if (islower(*p)) {
|
|
modified = 1; hnum = hlast;
|
|
*p = toupper(*p);
|
|
} else if (isupper(*p)) {
|
|
modified = 1; hnum = hlast;
|
|
*p = tolower(*p);
|
|
}
|
|
if (es->cursor < es->linelen - 1)
|
|
es->cursor++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case '#':
|
|
{
|
|
int ret = x_do_comment(es->cbuf, es->cbufsize,
|
|
&es->linelen);
|
|
if (ret >= 0)
|
|
es->cursor = 0;
|
|
return ret;
|
|
}
|
|
|
|
case '=': /* at&t ksh */
|
|
case Ctrl('e'): /* Nonstandard vi/ksh */
|
|
print_expansions(es, 1);
|
|
break;
|
|
|
|
|
|
case Ctrl('i'): /* Nonstandard vi/ksh */
|
|
if (!Flag(FVITABCOMPLETE))
|
|
return -1;
|
|
complete_word(1, argcnt);
|
|
break;
|
|
|
|
case Ctrl('['): /* some annoying at&t ksh's */
|
|
if (!Flag(FVIESCCOMPLETE))
|
|
return -1;
|
|
case '\\': /* at&t ksh */
|
|
case Ctrl('f'): /* Nonstandard vi/ksh */
|
|
complete_word(1, argcnt);
|
|
break;
|
|
|
|
|
|
case '*': /* at&t ksh */
|
|
case Ctrl('x'): /* Nonstandard vi/ksh */
|
|
expand_word(1);
|
|
break;
|
|
}
|
|
if (insert == 0 && es->cursor != 0 && es->cursor >= es->linelen)
|
|
es->cursor--;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
domove(argcnt, cmd, sub)
|
|
int argcnt;
|
|
const char *cmd;
|
|
int sub;
|
|
{
|
|
int bcount, UNINITIALIZED(i), t;
|
|
int UNINITIALIZED(ncursor);
|
|
|
|
switch (*cmd) {
|
|
|
|
case 'b':
|
|
if (!sub && es->cursor == 0)
|
|
return -1;
|
|
ncursor = backword(argcnt);
|
|
break;
|
|
|
|
case 'B':
|
|
if (!sub && es->cursor == 0)
|
|
return -1;
|
|
ncursor = Backword(argcnt);
|
|
break;
|
|
|
|
case 'e':
|
|
if (!sub && es->cursor + 1 >= es->linelen)
|
|
return -1;
|
|
ncursor = endword(argcnt);
|
|
if (sub && ncursor < es->linelen)
|
|
ncursor++;
|
|
break;
|
|
|
|
case 'E':
|
|
if (!sub && es->cursor + 1 >= es->linelen)
|
|
return -1;
|
|
ncursor = Endword(argcnt);
|
|
if (sub && ncursor < es->linelen)
|
|
ncursor++;
|
|
break;
|
|
|
|
case 'f':
|
|
case 'F':
|
|
case 't':
|
|
case 'T':
|
|
fsavecmd = *cmd;
|
|
fsavech = cmd[1];
|
|
/* drop through */
|
|
|
|
case ',':
|
|
case ';':
|
|
if (fsavecmd == ' ')
|
|
return -1;
|
|
i = fsavecmd == 'f' || fsavecmd == 'F';
|
|
t = fsavecmd > 'a';
|
|
if (*cmd == ',')
|
|
t = !t;
|
|
if ((ncursor = findch(fsavech, argcnt, t, i)) < 0)
|
|
return -1;
|
|
if (sub && t)
|
|
ncursor++;
|
|
break;
|
|
|
|
case 'h':
|
|
case Ctrl('h'):
|
|
if (!sub && es->cursor == 0)
|
|
return -1;
|
|
ncursor = es->cursor - argcnt;
|
|
if (ncursor < 0)
|
|
ncursor = 0;
|
|
break;
|
|
|
|
case ' ':
|
|
case 'l':
|
|
if (!sub && es->cursor + 1 >= es->linelen)
|
|
return -1;
|
|
if (es->linelen != 0) {
|
|
ncursor = es->cursor + argcnt;
|
|
if (ncursor > es->linelen)
|
|
ncursor = es->linelen;
|
|
}
|
|
break;
|
|
|
|
case 'w':
|
|
if (!sub && es->cursor + 1 >= es->linelen)
|
|
return -1;
|
|
ncursor = forwword(argcnt);
|
|
break;
|
|
|
|
case 'W':
|
|
if (!sub && es->cursor + 1 >= es->linelen)
|
|
return -1;
|
|
ncursor = Forwword(argcnt);
|
|
break;
|
|
|
|
case '0':
|
|
ncursor = 0;
|
|
break;
|
|
|
|
case '^':
|
|
ncursor = 0;
|
|
while (ncursor < es->linelen - 1 && isspace(es->cbuf[ncursor]))
|
|
ncursor++;
|
|
break;
|
|
|
|
case '|':
|
|
ncursor = argcnt;
|
|
if (ncursor > es->linelen)
|
|
ncursor = es->linelen;
|
|
if (ncursor)
|
|
ncursor--;
|
|
break;
|
|
|
|
case '$':
|
|
if (es->linelen != 0)
|
|
ncursor = es->linelen;
|
|
else
|
|
ncursor = 0;
|
|
break;
|
|
|
|
case '%':
|
|
ncursor = es->cursor;
|
|
while (ncursor < es->linelen &&
|
|
(i = bracktype(es->cbuf[ncursor])) == 0)
|
|
ncursor++;
|
|
if (ncursor == es->linelen)
|
|
return -1;
|
|
bcount = 1;
|
|
do {
|
|
if (i > 0) {
|
|
if (++ncursor >= es->linelen)
|
|
return -1;
|
|
} else {
|
|
if (--ncursor < 0)
|
|
return -1;
|
|
}
|
|
t = bracktype(es->cbuf[ncursor]);
|
|
if (t == i)
|
|
bcount++;
|
|
else if (t == -i)
|
|
bcount--;
|
|
} while (bcount != 0);
|
|
if (sub && i > 0)
|
|
ncursor++;
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
return ncursor;
|
|
}
|
|
|
|
static int
|
|
redo_insert(count)
|
|
int count;
|
|
{
|
|
while (count-- > 0)
|
|
if (putbuf(ibuf, inslen, insert==REPLACE) != 0)
|
|
return -1;
|
|
if (es->cursor > 0)
|
|
es->cursor--;
|
|
insert = 0;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
yank_range(a, b)
|
|
int a, b;
|
|
{
|
|
yanklen = b - a;
|
|
if (yanklen != 0)
|
|
memmove(ybuf, &es->cbuf[a], yanklen);
|
|
}
|
|
|
|
static int
|
|
bracktype(ch)
|
|
int ch;
|
|
{
|
|
switch (ch) {
|
|
|
|
case '(':
|
|
return 1;
|
|
|
|
case '[':
|
|
return 2;
|
|
|
|
case '{':
|
|
return 3;
|
|
|
|
case ')':
|
|
return -1;
|
|
|
|
case ']':
|
|
return -2;
|
|
|
|
case '}':
|
|
return -3;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Non user interface editor routines below here
|
|
*/
|
|
|
|
static int cur_col; /* current column on line */
|
|
static int pwidth; /* width of prompt */
|
|
static int prompt_trunc; /* how much of prompt to truncate */
|
|
static int prompt_skip; /* how much of prompt to skip */
|
|
static int winwidth; /* width of window */
|
|
static char *wbuf[2]; /* window buffers */
|
|
static int wbuf_len; /* length of window buffers (x_cols-3)*/
|
|
static int win; /* window buffer in use */
|
|
static char morec; /* more character at right of window */
|
|
static int lastref; /* argument to last refresh() */
|
|
static char holdbuf[CMDLEN]; /* place to hold last edit buffer */
|
|
static int holdlen; /* length of holdbuf */
|
|
|
|
static void
|
|
save_cbuf()
|
|
{
|
|
memmove(holdbuf, es->cbuf, es->linelen);
|
|
holdlen = es->linelen;
|
|
holdbuf[holdlen] = '\0';
|
|
}
|
|
|
|
static void
|
|
restore_cbuf()
|
|
{
|
|
es->cursor = 0;
|
|
es->linelen = holdlen;
|
|
memmove(es->cbuf, holdbuf, holdlen);
|
|
}
|
|
|
|
/* return a new edstate */
|
|
static struct edstate *
|
|
save_edstate(old)
|
|
struct edstate *old;
|
|
{
|
|
struct edstate *new;
|
|
|
|
new = (struct edstate *)alloc(sizeof(struct edstate), APERM);
|
|
new->cbuf = alloc(old->cbufsize, APERM);
|
|
new->cbufsize = old->cbufsize;
|
|
strlcpy(new->cbuf, old->cbuf, new->cbufsize);
|
|
new->linelen = old->linelen;
|
|
new->cursor = old->cursor;
|
|
new->winleft = old->winleft;
|
|
return new;
|
|
}
|
|
|
|
static void
|
|
restore_edstate(new, old)
|
|
struct edstate *old, *new;
|
|
{
|
|
strncpy(new->cbuf, old->cbuf, old->linelen);
|
|
new->linelen = old->linelen;
|
|
new->cursor = old->cursor;
|
|
new->winleft = old->winleft;
|
|
free_edstate(old);
|
|
}
|
|
|
|
static void
|
|
free_edstate(old)
|
|
struct edstate *old;
|
|
{
|
|
afree(old->cbuf, APERM);
|
|
afree((char *)old, APERM);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
edit_reset(buf, len)
|
|
char *buf;
|
|
size_t len;
|
|
{
|
|
const char *p;
|
|
|
|
es = &ebuf;
|
|
es->cbuf = buf;
|
|
es->cbufsize = len;
|
|
undo = &undobuf;
|
|
undo->cbufsize = len;
|
|
|
|
es->linelen = undo->linelen = 0;
|
|
es->cursor = undo->cursor = 0;
|
|
es->winleft = undo->winleft = 0;
|
|
|
|
cur_col = pwidth = promptlen(prompt, &p);
|
|
prompt_skip = p - prompt;
|
|
if (pwidth > x_cols - 3 - MIN_EDIT_SPACE) {
|
|
cur_col = x_cols - 3 - MIN_EDIT_SPACE;
|
|
prompt_trunc = pwidth - cur_col;
|
|
pwidth -= prompt_trunc;
|
|
} else
|
|
prompt_trunc = 0;
|
|
if (!wbuf_len || wbuf_len != x_cols - 3) {
|
|
wbuf_len = x_cols - 3;
|
|
wbuf[0] = aresize(wbuf[0], wbuf_len, APERM);
|
|
wbuf[1] = aresize(wbuf[1], wbuf_len, APERM);
|
|
}
|
|
(void) memset(wbuf[0], ' ', wbuf_len);
|
|
(void) memset(wbuf[1], ' ', wbuf_len);
|
|
winwidth = x_cols - pwidth - 3;
|
|
win = 0;
|
|
morec = ' ';
|
|
lastref = 1;
|
|
holdlen = 0;
|
|
}
|
|
|
|
/*
|
|
* this is used for calling x_escape() in complete_word()
|
|
*/
|
|
static int
|
|
x_vi_putbuf(s, len)
|
|
const char *s;
|
|
size_t len;
|
|
{
|
|
return putbuf(s, len, 0);
|
|
}
|
|
|
|
static int
|
|
putbuf(buf, len, repl)
|
|
const char *buf;
|
|
int len;
|
|
int repl;
|
|
{
|
|
if (len == 0)
|
|
return 0;
|
|
if (repl) {
|
|
if (es->cursor + len >= es->cbufsize)
|
|
return -1;
|
|
if (es->cursor + len > es->linelen)
|
|
es->linelen = es->cursor + len;
|
|
} else {
|
|
if (es->linelen + len >= es->cbufsize)
|
|
return -1;
|
|
memmove(&es->cbuf[es->cursor + len], &es->cbuf[es->cursor],
|
|
es->linelen - es->cursor);
|
|
es->linelen += len;
|
|
}
|
|
memmove(&es->cbuf[es->cursor], buf, len);
|
|
es->cursor += len;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
del_range(a, b)
|
|
int a, b;
|
|
{
|
|
if (es->linelen != b)
|
|
memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b);
|
|
es->linelen -= b - a;
|
|
}
|
|
|
|
static int
|
|
findch(ch, cnt, forw, incl)
|
|
int ch;
|
|
int cnt;
|
|
int forw;
|
|
int incl;
|
|
{
|
|
int ncursor;
|
|
|
|
if (es->linelen == 0)
|
|
return -1;
|
|
ncursor = es->cursor;
|
|
while (cnt--) {
|
|
do {
|
|
if (forw) {
|
|
if (++ncursor == es->linelen)
|
|
return -1;
|
|
} else {
|
|
if (--ncursor < 0)
|
|
return -1;
|
|
}
|
|
} while (es->cbuf[ncursor] != ch);
|
|
}
|
|
if (!incl) {
|
|
if (forw)
|
|
ncursor--;
|
|
else
|
|
ncursor++;
|
|
}
|
|
return ncursor;
|
|
}
|
|
|
|
static int
|
|
forwword(argcnt)
|
|
int argcnt;
|
|
{
|
|
int ncursor;
|
|
|
|
ncursor = es->cursor;
|
|
while (ncursor < es->linelen && argcnt--) {
|
|
if (is_wordch(es->cbuf[ncursor]))
|
|
while (is_wordch(es->cbuf[ncursor]) &&
|
|
ncursor < es->linelen)
|
|
ncursor++;
|
|
else if (!isspace(es->cbuf[ncursor]))
|
|
while (!is_wordch(es->cbuf[ncursor]) &&
|
|
!isspace(es->cbuf[ncursor]) &&
|
|
ncursor < es->linelen)
|
|
ncursor++;
|
|
while (isspace(es->cbuf[ncursor]) && ncursor < es->linelen)
|
|
ncursor++;
|
|
}
|
|
return ncursor;
|
|
}
|
|
|
|
static int
|
|
backword(argcnt)
|
|
int argcnt;
|
|
{
|
|
int ncursor;
|
|
|
|
ncursor = es->cursor;
|
|
while (ncursor > 0 && argcnt--) {
|
|
while (--ncursor > 0 && isspace(es->cbuf[ncursor]))
|
|
;
|
|
if (ncursor > 0) {
|
|
if (is_wordch(es->cbuf[ncursor]))
|
|
while (--ncursor >= 0 &&
|
|
is_wordch(es->cbuf[ncursor]))
|
|
;
|
|
else
|
|
while (--ncursor >= 0 &&
|
|
!is_wordch(es->cbuf[ncursor]) &&
|
|
!isspace(es->cbuf[ncursor]))
|
|
;
|
|
ncursor++;
|
|
}
|
|
}
|
|
return ncursor;
|
|
}
|
|
|
|
static int
|
|
endword(argcnt)
|
|
int argcnt;
|
|
{
|
|
int ncursor;
|
|
|
|
ncursor = es->cursor;
|
|
while (ncursor < es->linelen && argcnt--) {
|
|
while (++ncursor < es->linelen - 1 &&
|
|
isspace(es->cbuf[ncursor]))
|
|
;
|
|
if (ncursor < es->linelen - 1) {
|
|
if (is_wordch(es->cbuf[ncursor]))
|
|
while (++ncursor < es->linelen &&
|
|
is_wordch(es->cbuf[ncursor]))
|
|
;
|
|
else
|
|
while (++ncursor < es->linelen &&
|
|
!is_wordch(es->cbuf[ncursor]) &&
|
|
!isspace(es->cbuf[ncursor]))
|
|
;
|
|
ncursor--;
|
|
}
|
|
}
|
|
return ncursor;
|
|
}
|
|
|
|
static int
|
|
Forwword(argcnt)
|
|
int argcnt;
|
|
{
|
|
int ncursor;
|
|
|
|
ncursor = es->cursor;
|
|
while (ncursor < es->linelen && argcnt--) {
|
|
while (!isspace(es->cbuf[ncursor]) && ncursor < es->linelen)
|
|
ncursor++;
|
|
while (isspace(es->cbuf[ncursor]) && ncursor < es->linelen)
|
|
ncursor++;
|
|
}
|
|
return ncursor;
|
|
}
|
|
|
|
static int
|
|
Backword(argcnt)
|
|
int argcnt;
|
|
{
|
|
int ncursor;
|
|
|
|
ncursor = es->cursor;
|
|
while (ncursor > 0 && argcnt--) {
|
|
while (--ncursor >= 0 && isspace(es->cbuf[ncursor]))
|
|
;
|
|
while (ncursor >= 0 && !isspace(es->cbuf[ncursor]))
|
|
ncursor--;
|
|
ncursor++;
|
|
}
|
|
return ncursor;
|
|
}
|
|
|
|
static int
|
|
Endword(argcnt)
|
|
int argcnt;
|
|
{
|
|
int ncursor;
|
|
|
|
ncursor = es->cursor;
|
|
while (ncursor < es->linelen - 1 && argcnt--) {
|
|
while (++ncursor < es->linelen - 1 &&
|
|
isspace(es->cbuf[ncursor]))
|
|
;
|
|
if (ncursor < es->linelen - 1) {
|
|
while (++ncursor < es->linelen &&
|
|
!isspace(es->cbuf[ncursor]))
|
|
;
|
|
ncursor--;
|
|
}
|
|
}
|
|
return ncursor;
|
|
}
|
|
|
|
static int
|
|
grabhist(save, n)
|
|
int save;
|
|
int n;
|
|
{
|
|
char *hptr;
|
|
|
|
if (n < 0 || n > hlast)
|
|
return -1;
|
|
if (n == hlast) {
|
|
restore_cbuf();
|
|
ohnum = n;
|
|
return 0;
|
|
}
|
|
(void) histnum(n);
|
|
if ((hptr = *histpos()) == NULL) {
|
|
internal_errorf(0, "grabhist: bad history array");
|
|
return -1;
|
|
}
|
|
if (save)
|
|
save_cbuf();
|
|
if ((es->linelen = strlen(hptr)) >= es->cbufsize)
|
|
es->linelen = es->cbufsize - 1;
|
|
memmove(es->cbuf, hptr, es->linelen);
|
|
es->cursor = 0;
|
|
ohnum = n;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
grabsearch(save, start, fwd, pat)
|
|
int save, start, fwd;
|
|
char *pat;
|
|
{
|
|
char *hptr;
|
|
int hist;
|
|
int anchored;
|
|
|
|
if ((start == 0 && fwd == 0) || (start >= hlast-1 && fwd == 1))
|
|
return -1;
|
|
if (fwd)
|
|
start++;
|
|
else
|
|
start--;
|
|
anchored = *pat == '^' ? (++pat, 1) : 0;
|
|
if ((hist = findhist(start, fwd, pat, anchored)) < 0) {
|
|
/* if (start != 0 && fwd && match(holdbuf, pat) >= 0) { */
|
|
/* XXX should FILECMP be strncmp? */
|
|
if (start != 0 && fwd && FILECMP(holdbuf, pat) >= 0) {
|
|
restore_cbuf();
|
|
return 0;
|
|
} else
|
|
return -1;
|
|
}
|
|
if (save)
|
|
save_cbuf();
|
|
histnum(hist);
|
|
hptr = *histpos();
|
|
if ((es->linelen = strlen(hptr)) >= es->cbufsize)
|
|
es->linelen = es->cbufsize - 1;
|
|
memmove(es->cbuf, hptr, es->linelen);
|
|
es->cursor = 0;
|
|
return hist;
|
|
}
|
|
|
|
static void
|
|
redraw_line(newline)
|
|
int newline;
|
|
{
|
|
(void) memset(wbuf[win], ' ', wbuf_len);
|
|
if (newline) {
|
|
x_putc('\r');
|
|
x_putc('\n');
|
|
}
|
|
vi_pprompt(0);
|
|
cur_col = pwidth;
|
|
morec = ' ';
|
|
}
|
|
|
|
static void
|
|
refresh(leftside)
|
|
int leftside;
|
|
{
|
|
if (leftside < 0)
|
|
leftside = lastref;
|
|
else
|
|
lastref = leftside;
|
|
if (outofwin())
|
|
rewindow();
|
|
display(wbuf[1 - win], wbuf[win], leftside);
|
|
win = 1 - win;
|
|
}
|
|
|
|
static int
|
|
outofwin()
|
|
{
|
|
int cur, col;
|
|
|
|
if (es->cursor < es->winleft)
|
|
return 1;
|
|
col = 0;
|
|
cur = es->winleft;
|
|
while (cur < es->cursor)
|
|
col = newcol((unsigned char) es->cbuf[cur++], col);
|
|
if (col >= winwidth)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
rewindow()
|
|
{
|
|
register int tcur, tcol;
|
|
int holdcur1, holdcol1;
|
|
int holdcur2, holdcol2;
|
|
|
|
holdcur1 = holdcur2 = tcur = 0;
|
|
holdcol1 = holdcol2 = tcol = 0;
|
|
while (tcur < es->cursor) {
|
|
if (tcol - holdcol2 > winwidth / 2) {
|
|
holdcur1 = holdcur2;
|
|
holdcol1 = holdcol2;
|
|
holdcur2 = tcur;
|
|
holdcol2 = tcol;
|
|
}
|
|
tcol = newcol((unsigned char) es->cbuf[tcur++], tcol);
|
|
}
|
|
while (tcol - holdcol1 > winwidth / 2)
|
|
holdcol1 = newcol((unsigned char) es->cbuf[holdcur1++],
|
|
holdcol1);
|
|
es->winleft = holdcur1;
|
|
}
|
|
|
|
static int
|
|
newcol(ch, col)
|
|
int ch, col;
|
|
{
|
|
if (ch == '\t')
|
|
return (col | 7) + 1;
|
|
return col + char_len(ch);
|
|
}
|
|
|
|
static void
|
|
display(wb1, wb2, leftside)
|
|
char *wb1, *wb2;
|
|
int leftside;
|
|
{
|
|
unsigned char ch;
|
|
char *twb1, *twb2, mc;
|
|
int cur, col, cnt;
|
|
int UNINITIALIZED(ncol);
|
|
int moreright;
|
|
|
|
col = 0;
|
|
cur = es->winleft;
|
|
moreright = 0;
|
|
twb1 = wb1;
|
|
while (col < winwidth && cur < es->linelen) {
|
|
if (cur == es->cursor && leftside)
|
|
ncol = col + pwidth;
|
|
if ((ch = es->cbuf[cur]) == '\t') {
|
|
do {
|
|
*twb1++ = ' ';
|
|
} while (++col < winwidth && (col & 7) != 0);
|
|
} else {
|
|
if ((ch & 0x80) && Flag(FVISHOW8)) {
|
|
*twb1++ = 'M';
|
|
if (++col < winwidth) {
|
|
*twb1++ = '-';
|
|
col++;
|
|
}
|
|
ch &= 0x7f;
|
|
}
|
|
if (col < winwidth) {
|
|
if (ch < ' ' || ch == 0x7f) {
|
|
*twb1++ = '^';
|
|
if (++col < winwidth) {
|
|
*twb1++ = ch ^ '@';
|
|
col++;
|
|
}
|
|
} else {
|
|
*twb1++ = ch;
|
|
col++;
|
|
}
|
|
}
|
|
}
|
|
if (cur == es->cursor && !leftside)
|
|
ncol = col + pwidth - 1;
|
|
cur++;
|
|
}
|
|
if (cur == es->cursor)
|
|
ncol = col + pwidth;
|
|
if (col < winwidth) {
|
|
while (col < winwidth) {
|
|
*twb1++ = ' ';
|
|
col++;
|
|
}
|
|
} else
|
|
moreright++;
|
|
*twb1 = ' ';
|
|
|
|
col = pwidth;
|
|
cnt = winwidth;
|
|
twb1 = wb1;
|
|
twb2 = wb2;
|
|
while (cnt--) {
|
|
if (*twb1 != *twb2) {
|
|
if (cur_col != col)
|
|
ed_mov_opt(col, wb1);
|
|
x_putc(*twb1);
|
|
cur_col++;
|
|
}
|
|
twb1++;
|
|
twb2++;
|
|
col++;
|
|
}
|
|
if (es->winleft > 0 && moreright)
|
|
/* POSIX says to use * for this but that is a globbing
|
|
* character and may confuse people; + is more innocuous
|
|
*/
|
|
mc = '+';
|
|
else if (es->winleft > 0)
|
|
mc = '<';
|
|
else if (moreright)
|
|
mc = '>';
|
|
else
|
|
mc = ' ';
|
|
if (mc != morec) {
|
|
ed_mov_opt(pwidth + winwidth + 1, wb1);
|
|
x_putc(mc);
|
|
cur_col++;
|
|
morec = mc;
|
|
}
|
|
if (cur_col != ncol)
|
|
ed_mov_opt(ncol, wb1);
|
|
}
|
|
|
|
static void
|
|
ed_mov_opt(col, wb)
|
|
int col;
|
|
char *wb;
|
|
{
|
|
if (col < cur_col) {
|
|
if (col + 1 < cur_col - col) {
|
|
x_putc('\r');
|
|
vi_pprompt(0);
|
|
cur_col = pwidth;
|
|
while (cur_col++ < col)
|
|
x_putc(*wb++);
|
|
} else {
|
|
while (cur_col-- > col)
|
|
x_putc('\b');
|
|
}
|
|
} else {
|
|
wb = &wb[cur_col - pwidth];
|
|
while (cur_col++ < col)
|
|
x_putc(*wb++);
|
|
}
|
|
cur_col = col;
|
|
}
|
|
|
|
|
|
/* replace word with all expansions (ie, expand word*) */
|
|
static int
|
|
expand_word(command)
|
|
int command;
|
|
{
|
|
static struct edstate *buf;
|
|
int rval = 0;
|
|
int nwords;
|
|
int start, end;
|
|
char **words;
|
|
int i;
|
|
|
|
/* Undo previous expansion */
|
|
if (command == 0 && expanded == EXPAND && buf) {
|
|
restore_edstate(es, buf);
|
|
buf = 0;
|
|
expanded = NONE;
|
|
return 0;
|
|
}
|
|
if (buf) {
|
|
free_edstate(buf);
|
|
buf = 0;
|
|
}
|
|
|
|
nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
|
|
es->cbuf, es->linelen, es->cursor,
|
|
&start, &end, &words, (int *) 0);
|
|
if (nwords == 0) {
|
|
vi_error();
|
|
return -1;
|
|
}
|
|
|
|
buf = save_edstate(es);
|
|
expanded = EXPAND;
|
|
del_range(start, end);
|
|
es->cursor = start;
|
|
for (i = 0; i < nwords; ) {
|
|
if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
|
|
rval = -1;
|
|
break;
|
|
}
|
|
if (++i < nwords && putbuf(space, 1, 0) != 0) {
|
|
rval = -1;
|
|
break;
|
|
}
|
|
}
|
|
i = buf->cursor - end;
|
|
if (rval == 0 && i > 0)
|
|
es->cursor += i;
|
|
modified = 1; hnum = hlast;
|
|
insert = INSERT;
|
|
lastac = 0;
|
|
refresh(0);
|
|
return rval;
|
|
}
|
|
|
|
static int
|
|
complete_word(command, count)
|
|
int command;
|
|
int count;
|
|
{
|
|
static struct edstate *buf;
|
|
int rval = 0;
|
|
int nwords;
|
|
int start, end;
|
|
char **words;
|
|
char *match;
|
|
int match_len;
|
|
int is_unique;
|
|
int is_command;
|
|
|
|
/* Undo previous completion */
|
|
if (command == 0 && expanded == COMPLETE && buf) {
|
|
print_expansions(buf, 0);
|
|
expanded = PRINT;
|
|
return 0;
|
|
}
|
|
if (command == 0 && expanded == PRINT && buf) {
|
|
restore_edstate(es, buf);
|
|
buf = 0;
|
|
expanded = NONE;
|
|
return 0;
|
|
}
|
|
if (buf) {
|
|
free_edstate(buf);
|
|
buf = 0;
|
|
}
|
|
|
|
/* XCF_FULLPATH for count 'cause the menu printed by print_expansions()
|
|
* was done this way.
|
|
*/
|
|
nwords = x_cf_glob(XCF_COMMAND_FILE | (count ? XCF_FULLPATH : 0),
|
|
es->cbuf, es->linelen, es->cursor,
|
|
&start, &end, &words, &is_command);
|
|
if (nwords == 0) {
|
|
vi_error();
|
|
return -1;
|
|
}
|
|
if (count) {
|
|
int i;
|
|
|
|
count--;
|
|
if (count >= nwords) {
|
|
vi_error();
|
|
x_print_expansions(nwords, words, is_command);
|
|
x_free_words(nwords, words);
|
|
redraw_line(0);
|
|
return -1;
|
|
}
|
|
/*
|
|
* Expand the count'th word to its basename
|
|
*/
|
|
if (is_command) {
|
|
match = words[count]
|
|
+ x_basename(words[count], (char *) 0);
|
|
/* If more than one possible match, use full path */
|
|
for (i = 0; i < nwords; i++)
|
|
if (i != count &&
|
|
FILECMP(words[i]
|
|
+ x_basename(words[i], (char *) 0),
|
|
match) == 0)
|
|
{
|
|
match = words[count];
|
|
break;
|
|
}
|
|
} else
|
|
match = words[count];
|
|
match_len = strlen(match);
|
|
is_unique = 1;
|
|
/* expanded = PRINT; next call undo */
|
|
} else {
|
|
match = words[0];
|
|
match_len = x_longest_prefix(nwords, words);
|
|
expanded = COMPLETE; /* next call will list completions */
|
|
is_unique = nwords == 1;
|
|
}
|
|
|
|
buf = save_edstate(es);
|
|
del_range(start, end);
|
|
es->cursor = start;
|
|
|
|
/* escape all shell-sensitive characters and put the result into
|
|
* command buffer */
|
|
rval = x_escape(match, match_len, x_vi_putbuf);
|
|
|
|
if (rval == 0 && is_unique) {
|
|
/* If exact match, don't undo. Allows directory completions
|
|
* to be used (ie, complete the next portion of the path).
|
|
*/
|
|
expanded = NONE;
|
|
|
|
/* If not a directory, add a space to the end... */
|
|
if (match_len > 0 && !ISDIRSEP(match[match_len - 1]))
|
|
rval = putbuf(space, 1, 0);
|
|
}
|
|
x_free_words(nwords, words);
|
|
|
|
modified = 1; hnum = hlast;
|
|
insert = INSERT;
|
|
lastac = 0; /* prevent this from being redone... */
|
|
refresh(0);
|
|
|
|
return rval;
|
|
}
|
|
|
|
static int
|
|
print_expansions(e, command)
|
|
struct edstate *e;
|
|
int command;
|
|
{
|
|
int nwords;
|
|
int start, end;
|
|
char **words;
|
|
int is_command;
|
|
|
|
nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
|
|
e->cbuf, e->linelen, e->cursor,
|
|
&start, &end, &words, &is_command);
|
|
if (nwords == 0) {
|
|
vi_error();
|
|
return -1;
|
|
}
|
|
x_print_expansions(nwords, words, is_command);
|
|
x_free_words(nwords, words);
|
|
redraw_line(0);
|
|
return 0;
|
|
}
|
|
|
|
/* How long is char when displayed (not counting tabs) */
|
|
static int
|
|
char_len(c)
|
|
int c;
|
|
{
|
|
int len = 1;
|
|
|
|
if ((c & 0x80) && Flag(FVISHOW8)) {
|
|
len += 2;
|
|
c &= 0x7f;
|
|
}
|
|
if (c < ' ' || c == 0x7f)
|
|
len++;
|
|
return len;
|
|
}
|
|
|
|
/* Similar to x_zotc(emacs.c), but no tab weirdness */
|
|
static void
|
|
x_vi_zotc(c)
|
|
int c;
|
|
{
|
|
if (Flag(FVISHOW8) && (c & 0x80)) {
|
|
x_puts("M-");
|
|
c &= 0x7f;
|
|
}
|
|
if (c < ' ' || c == 0x7f) {
|
|
x_putc('^');
|
|
c ^= '@';
|
|
}
|
|
x_putc(c);
|
|
}
|
|
|
|
static void
|
|
vi_pprompt(full)
|
|
int full;
|
|
{
|
|
pprompt(prompt + (full ? 0 : prompt_skip), prompt_trunc);
|
|
}
|
|
|
|
static void
|
|
vi_error()
|
|
{
|
|
/* Beem out of any macros as soon as an error occurs */
|
|
vi_macro_reset();
|
|
x_putc(BEL);
|
|
x_flush();
|
|
}
|
|
|
|
static void
|
|
vi_macro_reset()
|
|
{
|
|
if (macro.p) {
|
|
afree(macro.buf, APERM);
|
|
memset((char *) ¯o, 0, sizeof(macro));
|
|
}
|
|
}
|
|
|
|
#endif /* VI */
|