2203 lines
44 KiB
C
2203 lines
44 KiB
C
/* $NetBSD: vi.c,v 1.12 2011/06/22 03:56:17 mrg Exp $ */
|
|
|
|
/*
|
|
* vi command editing
|
|
* written by John Rochester (initially for nsh)
|
|
* bludgeoned to fit pdksh by Larry Bouzane, Jeff Sparkes & Eric Gisin
|
|
*
|
|
*/
|
|
#include <sys/cdefs.h>
|
|
|
|
#ifndef lint
|
|
__RCSID("$NetBSD: vi.c,v 1.12 2011/06/22 03:56:17 mrg Exp $");
|
|
#endif
|
|
|
|
#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));
|
|
static void vi_reset ARGS((char *, size_t));
|
|
static int nextstate ARGS((int));
|
|
static int vi_insert ARGS((int));
|
|
static int vi_cmd ARGS((int, const char *));
|
|
static int domove ARGS((int, const char *, int));
|
|
static int redo_insert ARGS((int));
|
|
static void yank_range ARGS((int, int));
|
|
static int bracktype ARGS((int));
|
|
static void save_cbuf ARGS((void));
|
|
static void restore_cbuf ARGS((void));
|
|
static void edit_reset ARGS((char *, size_t));
|
|
static int putbuf ARGS((const char *, int, int));
|
|
static void del_range ARGS((int, int));
|
|
static int findch ARGS((int, int, int, int));
|
|
static int forwword ARGS((int));
|
|
static int backword ARGS((int));
|
|
static int endword ARGS((int));
|
|
static int Forwword ARGS((int));
|
|
static int Backword ARGS((int));
|
|
static int Endword ARGS((int));
|
|
static int grabhist ARGS((int, int));
|
|
static int grabsearch ARGS((int, int, int, char *));
|
|
static void redraw_line ARGS((int));
|
|
static void refresh ARGS((int));
|
|
static int outofwin ARGS((void));
|
|
static void rewindow ARGS((void));
|
|
static int newcol ARGS((int, int));
|
|
static void display ARGS((char *, char *, int));
|
|
static void ed_mov_opt ARGS((int, char *));
|
|
static int expand_word ARGS((int));
|
|
static int complete_word ARGS((int, int));
|
|
static int print_expansions ARGS((struct edstate *, int));
|
|
static int char_len ARGS((int));
|
|
static void x_vi_zotc ARGS((int));
|
|
static void vi_pprompt ARGS((int));
|
|
static void vi_error ARGS((void));
|
|
static void vi_macro_reset ARGS((void));
|
|
static int x_vi_putbuf ARGS((const char *, size_t));
|
|
|
|
#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 || len <= (size_t)es->linelen)
|
|
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((unsigned char)locpat[n - 1]))
|
|
n--;
|
|
while (n > 0 && !isspace((unsigned char)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)) {
|
|
if (es->linelen + 2 > es->cbufsize)
|
|
vi_error();
|
|
es->cbuf[es->linelen++] = 'M';
|
|
es->cbuf[es->linelen++] = '-';
|
|
ch &= 0x7f;
|
|
}
|
|
if (ch < ' ' || ch == 0x7f) {
|
|
if (es->linelen + 2 > es->cbufsize)
|
|
vi_error();
|
|
es->cbuf[es->linelen++] = '^';
|
|
es->cbuf[es->linelen++] = ch ^ '@';
|
|
} else {
|
|
if (es->linelen >= es->cbufsize)
|
|
vi_error();
|
|
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((unsigned char)es->cbuf[es->cursor])) {
|
|
while (isspace((unsigned char)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((unsigned char)(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((unsigned char)*p)) {
|
|
modified = 1; hnum = hlast;
|
|
*p = toupper((unsigned char)*p);
|
|
} else if (isupper((unsigned char)*p)) {
|
|
modified = 1; hnum = hlast;
|
|
*p = tolower((unsigned char)*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((unsigned char)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);
|
|
memcpy(new->cbuf, old->cbuf, old->linelen);
|
|
new->cbufsize = old->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;
|
|
{
|
|
memcpy(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((unsigned char)es->cbuf[ncursor]))
|
|
while (!is_wordch(es->cbuf[ncursor]) &&
|
|
!isspace((unsigned char)es->cbuf[ncursor]) &&
|
|
ncursor < es->linelen)
|
|
ncursor++;
|
|
while (isspace((unsigned char)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((unsigned char)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((unsigned char)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((unsigned char)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((unsigned char)es->cbuf[ncursor]))
|
|
;
|
|
ncursor--;
|
|
}
|
|
}
|
|
return ncursor;
|
|
}
|
|
|
|
static int
|
|
Forwword(argcnt)
|
|
int argcnt;
|
|
{
|
|
int ncursor;
|
|
|
|
ncursor = es->cursor;
|
|
while (ncursor < es->linelen && argcnt--) {
|
|
while (!isspace((unsigned char)es->cbuf[ncursor]) && ncursor < es->linelen)
|
|
ncursor++;
|
|
while (isspace((unsigned char)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((unsigned char)es->cbuf[ncursor]))
|
|
;
|
|
while (ncursor >= 0 && !isspace((unsigned char)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((unsigned char)es->cbuf[ncursor]))
|
|
;
|
|
if (ncursor < es->linelen - 1) {
|
|
while (++ncursor < es->linelen &&
|
|
!isspace((unsigned char)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(newlinex)
|
|
int newlinex;
|
|
{
|
|
(void) memset(wbuf[win], ' ', wbuf_len);
|
|
if (newlinex) {
|
|
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(commandx)
|
|
int commandx;
|
|
{
|
|
static struct edstate *buf;
|
|
int rval = 0;
|
|
int nwords;
|
|
int start, end;
|
|
char **words;
|
|
int i;
|
|
|
|
/* Undo previous expansion */
|
|
if (commandx == 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(commandx, count)
|
|
int commandx;
|
|
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 (commandx == 0 && expanded == COMPLETE && buf) {
|
|
print_expansions(buf, 0);
|
|
expanded = PRINT;
|
|
return 0;
|
|
}
|
|
if (commandx == 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(ex, commandx)
|
|
struct edstate *ex;
|
|
int commandx;
|
|
{
|
|
int nwords;
|
|
int start, end;
|
|
char **words;
|
|
int is_command;
|
|
|
|
nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
|
|
ex->cbuf, ex->linelen, ex->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 */
|