#include "config.h"
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/time.h>
#include <bitstring.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "../common/common.h"
#include "vi.h"
typedef enum {
GC_ERR, GC_ERR_NOFLUSH, GC_EVENT, GC_FATAL, GC_INTERRUPT, GC_OK
} gcret_t;
static VIKEYS const
*v_alias(SCR *, VICMD *, VIKEYS const *);
static gcret_t v_cmd(SCR *, VICMD *, VICMD *, VICMD *, int *, int *);
static int v_count(SCR *, CHAR_T, u_long *);
static void v_dtoh(SCR *);
static int v_init(SCR *);
static gcret_t v_key(SCR *, int, EVENT *, u_int32_t);
static int v_keyword(SCR *);
static int v_motion(SCR *, VICMD *, VICMD *, int *);
#if defined(DEBUG) && defined(COMLOG)
static void v_comlog(SCR *, VICMD *);
#endif
#define DOT (&VIP(sp)->sdot)
#define DOTMOTION (&VIP(sp)->sdotmotion)
int
vi(SCR **spp)
{
GS *gp;
MARK abs;
SCR *next, *sp;
VICMD cmd, *vp;
VI_PRIVATE *vip;
int comcount, mapped, rval;
sp = *spp;
gp = sp->gp;
vp = &cmd;
memset(vp, 0, sizeof(VICMD));
F_SET(vp, VM_RCM_SET);
if (v_init(sp))
return (1);
(void)sp->gp->scr_rename(sp, sp->frp->name, 1);
for (vip = VIP(sp), rval = 0;;) {
if (!MAPPED_KEYS_WAITING(sp) && vs_resolve(sp, NULL, 0))
goto ret;
if (F_ISSET(vip, VIP_S_REFRESH))
F_CLR(vip, VIP_S_REFRESH);
else {
sp->showmode = SM_COMMAND;
if (vs_refresh(sp, 0))
goto ret;
}
if (F_ISSET(vp, VM_RCM_SET | VM_RCM_SETFNB | VM_RCM_SETNNB)) {
F_CLR(vip, VIP_RCM_LAST);
(void)vs_column(sp, &sp->rcm);
}
if (MAPPED_KEYS_WAITING(sp))
mapped = 1;
else {
if (log_cursor(sp))
goto err;
mapped = 0;
}
if (EXCMD_RUNNING(gp)) {
vp->kp = &vikeys[':'];
goto ex_continue;
}
memset(vp, 0, sizeof(VICMD));
switch (v_cmd(sp, DOT, vp, NULL, &comcount, &mapped)) {
case GC_ERR:
goto err;
case GC_ERR_NOFLUSH:
goto gc_err_noflush;
case GC_EVENT:
if (v_event_exec(sp, vp))
goto err;
goto gc_event;
case GC_FATAL:
goto ret;
case GC_INTERRUPT:
goto intr;
case GC_OK:
break;
}
if (F_ISSET(vp->kp, V_SECURE) && O_ISSET(sp, O_SECURE)) {
ex_emsg(sp, KEY_NAME(sp, vp->key), EXM_SECURE);
goto err;
}
if (F_ISSET(vp, VC_ISDOT) && comcount)
DOTMOTION->count = 1;
F_SET(vp, vp->kp->flags);
if (F_ISSET(vp, V_ABS | V_ABS_C | V_ABS_L)) {
abs.lno = sp->lno;
abs.cno = sp->cno;
}
vp->m_start.lno = vp->m_stop.lno = vp->m_final.lno = sp->lno;
vp->m_start.cno = vp->m_stop.cno = vp->m_final.cno = sp->cno;
if (F_ISSET(vp, V_MOTION) &&
v_motion(sp, DOTMOTION, vp, &mapped)) {
if (INTERRUPTED(sp))
goto intr;
goto err;
}
if (F_ISSET(vp, VC_C1SET) && F_ISSET(vp, VM_LMODE))
vp->m_stop.lno += vp->count - 1;
++sp->ccnt;
#if defined(DEBUG) && defined(COMLOG)
v_comlog(sp, vp);
#endif
ex_continue: if (vp->kp->func(sp, vp))
goto err;
gc_event:
#ifdef DEBUG
if (F_ISSET(gp, G_TMP_INUSE)) {
F_CLR(gp, G_TMP_INUSE);
msgq(sp, M_ERR,
"vi: temporary buffer not released");
}
#endif
if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) {
if (file_end(sp, NULL, F_ISSET(sp, SC_EXIT_FORCE)))
goto ret;
if (vs_discard(sp, &next))
goto ret;
if (next == NULL && vs_swap(sp, &next, NULL))
goto ret;
*spp = next;
if (screen_end(sp))
goto ret;
if (next == NULL)
break;
sp = next;
vip = VIP(sp);
(void)sp->gp->scr_rename(sp, sp->frp->name, 1);
F_SET(vip, VIP_CUR_INVALID);
continue;
}
if (F_ISSET(vp, V_DOT) && !mapped) {
*DOT = cmd;
F_SET(DOT, VC_ISDOT);
if (F_ISSET(vp, VC_C1RESET))
F_SET(DOT, VC_C1SET);
F_CLR(DOT, VM_COMMASK | VM_RCM_MASK);
}
switch (F_ISSET(vp, VM_RCM_MASK)) {
case 0:
case VM_RCM_SET:
break;
case VM_RCM:
vp->m_final.cno = vs_rcm(sp,
vp->m_final.lno, F_ISSET(vip, VIP_RCM_LAST));
break;
case VM_RCM_SETLAST:
F_SET(vip, VIP_RCM_LAST);
break;
case VM_RCM_SETFNB:
vp->m_final.cno = 0;
case VM_RCM_SETNNB:
if (nonblank(sp, vp->m_final.lno, &vp->m_final.cno))
goto err;
break;
default:
abort();
}
sp->lno = vp->m_final.lno;
sp->cno = vp->m_final.cno;
if ((F_ISSET(vp, V_ABS) ||
(F_ISSET(vp, V_ABS_L) && sp->lno != abs.lno) ||
(F_ISSET(vp, V_ABS_C) &&
(sp->lno != abs.lno || sp->cno != abs.cno))) &&
mark_set(sp, ABSMARK1, &abs, 1))
goto err;
if (0) {
err: if (v_event_flush(sp, CH_MAPPED))
msgq(sp, M_BERR,
"Vi command failed: mapped keys discarded");
}
gc_err_noflush: if (INTERRUPTED(sp)) {
intr: CLR_INTERRUPT(sp);
if (v_event_flush(sp, CH_MAPPED))
msgq(sp, M_ERR,
"Interrupted: mapped keys discarded");
else
msgq(sp, M_ERR, "Interrupted");
}
if (F_ISSET(sp, SC_SSWITCH)) {
F_CLR(sp, SC_SSWITCH);
F_SET(sp, SC_STATUS);
sp = sp->nextdisp;
vip = VIP(sp);
(void)sp->gp->scr_rename(sp, sp->frp->name, 1);
F_SET(vip, VIP_CUR_INVALID);
if (vs_refresh(sp, 1))
return (1);
}
if (F_ISSET(sp, SC_FSWITCH)) {
F_CLR(sp, SC_FSWITCH);
F_CLR(sp, SC_SCR_TOP);
F_SET(sp, SC_SCR_CENTER);
(void)sp->gp->scr_rename(sp, sp->frp->name, 1);
}
if (F_ISSET(sp->ep, F_RCV_SYNC))
rcv_sync(sp, 0);
if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_EX)) {
*spp = sp;
v_dtoh(sp);
break;
}
}
if (0)
ret: rval = 1;
return (rval);
}
#define KEY(key, ec_flags) { \
if ((gcret = v_key(sp, 0, &ev, (ec_flags))) != GC_OK) \
return (gcret); \
if (ev.e_value == K_ESCAPE) \
goto esc; \
if (F_ISSET(&ev.e_ch, CH_MAPPED)) \
*mappedp = 1; \
(key) = ev.e_c; \
}
VIKEYS const tmotion = {
v_mulcase, V_CNT|V_DOT|V_MOTION|VM_RCM_SET,
"[count]~[count]motion",
" ~ change case to motion"
};
static gcret_t
v_cmd(SCR *sp, VICMD *dp, VICMD *vp, VICMD *ismotion, int *comcountp,
int *mappedp)
{
enum { COMMANDMODE, ISPARTIAL, NOTPARTIAL } cpart;
EVENT ev;
VIKEYS const *kp;
gcret_t gcret;
u_int flags;
CHAR_T key;
char *s;
cpart = ismotion == NULL ? COMMANDMODE : ISPARTIAL;
if ((gcret =
v_key(sp, ismotion == NULL, &ev, EC_MAPCOMMAND)) != GC_OK) {
if (gcret == GC_EVENT)
vp->ev = ev;
return (gcret);
}
if (ev.e_value == K_ESCAPE)
goto esc;
if (F_ISSET(&ev.e_ch, CH_MAPPED))
*mappedp = 1;
key = ev.e_c;
if (ismotion == NULL)
cpart = NOTPARTIAL;
if (key == '"') {
cpart = ISPARTIAL;
if (ismotion != NULL) {
v_emsg(sp, NULL, VIM_COMBUF);
return (GC_ERR);
}
KEY(vp->buffer, 0);
F_SET(vp, VC_BUFFER);
KEY(key, EC_MAPCOMMAND);
}
if (isdigit(key) && key != '0') {
if (v_count(sp, key, &vp->count))
return (GC_ERR);
F_SET(vp, VC_C1SET);
*comcountp = 1;
KEY(key, EC_MAPCOMMAND);
} else
*comcountp = 0;
if (key == '"') {
cpart = ISPARTIAL;
if (F_ISSET(vp, VC_BUFFER)) {
msgq(sp, M_ERR, "Only one buffer may be specified");
return (GC_ERR);
}
if (ismotion != NULL) {
v_emsg(sp, NULL, VIM_COMBUF);
return (GC_ERR);
}
KEY(vp->buffer, 0);
F_SET(vp, VC_BUFFER);
KEY(key, EC_MAPCOMMAND);
}
cpart = ISPARTIAL;
if (key > MAXVIKEY) {
v_emsg(sp, KEY_NAME(sp, key), VIM_NOCOM);
return (GC_ERR);
}
kp = &vikeys[vp->key = key];
if (vp->key == 'D' && F_ISSET(vp, VC_C1SET)) {
*comcountp = 0;
vp->count = 0;
F_CLR(vp, VC_C1SET);
}
if (kp->func == NULL && (kp = v_alias(sp, vp, kp)) == NULL)
return (GC_ERR);
if (key == '~' && O_ISSET(sp, O_TILDEOP))
kp = &tmotion;
vp->kp = kp;
if (kp->func == NULL) {
if (key != '.') {
v_emsg(sp, KEY_NAME(sp, key),
ev.e_value == K_ESCAPE ? VIM_NOCOM_B : VIM_NOCOM);
return (GC_ERR);
}
if (dp == NULL)
goto usage;
if (VIP(sp)->u_ccnt == sp->ccnt) {
vp->kp = &vikeys['u'];
F_SET(vp, VC_ISDOT);
return (GC_OK);
}
if (!F_ISSET(dp, VC_ISDOT)) {
msgq(sp, M_ERR, "No command to repeat");
return (GC_ERR);
}
if (F_ISSET(vp, VC_C1SET)) {
F_SET(dp, VC_C1SET);
dp->count = vp->count;
}
if (F_ISSET(vp, VC_BUFFER))
dp->buffer = vp->buffer;
*vp = *dp;
return (GC_OK);
}
flags = kp->flags;
if (F_ISSET(vp, VC_C1SET) && !LF_ISSET(V_CNT))
goto usage;
if (ismotion == NULL) {
if (!LF_ISSET(V_OBUF) && F_ISSET(vp, VC_BUFFER))
goto usage;
if (LF_ISSET(V_RBUF)) {
KEY(vp->buffer, 0);
F_SET(vp, VC_BUFFER);
}
}
if (vp->key == '[' || vp->key == ']' || vp->key == 'Z') {
KEY(key, 0);
if (vp->key != key) {
usage: if (ismotion == NULL)
s = kp->usage;
else if (ismotion->key == '~' && O_ISSET(sp, O_TILDEOP))
s = tmotion.usage;
else
s = vikeys[ismotion->key].usage;
v_emsg(sp, s, VIM_USAGE);
return (GC_ERR);
}
}
if (vp->key == 'z') {
KEY(vp->character, 0);
if (isdigit(vp->character)) {
if (v_count(sp, vp->character, &vp->count2))
return (GC_ERR);
F_SET(vp, VC_C2SET);
KEY(vp->character, 0);
}
}
if (ismotion != NULL && ismotion->key != key && !LF_ISSET(V_MOVE)) {
msgq(sp, M_ERR, "%s may not be used as a motion command",
KEY_NAME(sp, key));
return (GC_ERR);
}
if (LF_ISSET(V_CHAR))
KEY(vp->character, 0);
if (F_ISSET(kp, V_KEYW) && v_keyword(sp))
return (GC_ERR);
return (GC_OK);
esc: switch (cpart) {
case COMMANDMODE:
msgq(sp, M_BERR, "Already in command mode");
return (GC_ERR_NOFLUSH);
case ISPARTIAL:
break;
case NOTPARTIAL:
(void)sp->gp->scr_bell(sp);
break;
}
return (GC_ERR);
}
static int
v_motion(SCR *sp, VICMD *dm, VICMD *vp, int *mappedp)
{
VICMD motion;
size_t len;
u_long cnt;
u_int flags;
int tilde_reset, notused;
if (F_ISSET(vp, VC_ISDOT)) {
motion = *dm;
F_SET(&motion, VC_ISDOT);
F_CLR(&motion, VM_COMMASK);
} else {
memset(&motion, 0, sizeof(VICMD));
if (v_cmd(sp, NULL, &motion, vp, ¬used, mappedp) != GC_OK)
return (1);
}
cnt = motion.count = F_ISSET(&motion, VC_C1SET) ? motion.count : 1;
if (F_ISSET(vp, VC_C1SET)) {
motion.count *= vp->count;
F_SET(&motion, VC_C1SET);
F_CLR(vp, VC_C1SET);
F_SET(vp, VC_C1RESET);
}
if (vp->key == motion.key) {
F_SET(vp, VM_LDOUBLE | VM_LMODE);
vp->m_start.lno = sp->lno;
vp->m_start.cno = 0;
vp->m_stop.lno = sp->lno + motion.count - 1;
if (db_get(sp, vp->m_stop.lno, 0, NULL, &len)) {
if (vp->m_stop.lno != 1 ||
(vp->key != 'c' && vp->key != '!')) {
v_emsg(sp, NULL, VIM_EMPTY);
return (1);
}
vp->m_stop.cno = 0;
} else
vp->m_stop.cno = len ? len - 1 : 0;
} else {
motion.rkp = vp->kp;
if (vp->kp == &tmotion) {
tilde_reset = 1;
vp->kp = &vikeys['y'];
} else
tilde_reset = 0;
flags = F_ISSET(vp, VM_RCM_MASK);
if (LF_ISSET(VM_RCM_SET)) {
LF_SET(VM_RCM);
LF_CLR(VM_RCM_SET);
}
F_CLR(vp, VM_RCM_MASK);
F_SET(&motion, motion.kp->flags & ~VM_RCM_MASK);
motion.m_final.lno =
motion.m_stop.lno = motion.m_start.lno = sp->lno;
motion.m_final.cno =
motion.m_stop.cno = motion.m_start.cno = sp->cno;
if ((motion.kp->func)(sp, &motion))
return (1);
if (!db_exist(sp, vp->m_stop.lno)) {
if (vp->m_stop.lno != 1 ||
(vp->key != 'c' && vp->key != '!')) {
v_emsg(sp, NULL, VIM_EMPTY);
return (1);
}
vp->m_stop.cno = 0;
}
if (tilde_reset)
vp->kp = &tmotion;
F_SET(vp, F_ISSET(&motion, VM_COMMASK | VM_RCM_MASK));
if (!F_ISSET(vp, VM_RCM_MASK))
F_SET(vp, flags);
vp->rkp = motion.kp;
if (motion.m_start.lno > motion.m_stop.lno ||
(motion.m_start.lno == motion.m_stop.lno &&
motion.m_start.cno > motion.m_stop.cno)) {
vp->m_start = motion.m_stop;
vp->m_stop = motion.m_start;
} else {
vp->m_start = motion.m_start;
vp->m_stop = motion.m_stop;
}
vp->m_final = motion.m_final;
}
if (F_ISSET(vp->kp, V_DOT)) {
*dm = motion;
dm->count = cnt;
}
return (0);
}
static int
v_init(SCR *sp)
{
GS *gp;
VI_PRIVATE *vip;
gp = sp->gp;
vip = VIP(sp);
if (gp->scr_screen(sp, SC_VI))
return (1);
(void)gp->scr_attr(sp, SA_ALTERNATE, 1);
F_CLR(sp, SC_EX | SC_SCR_EX);
F_SET(sp, SC_VI);
sp->rows = vip->srows = O_VAL(sp, O_LINES);
sp->cols = O_VAL(sp, O_COLUMNS);
sp->t_rows = sp->t_minrows = O_VAL(sp, O_WINDOW);
if (sp->rows != 1) {
if (sp->t_rows > sp->rows - 1) {
sp->t_minrows = sp->t_rows = sp->rows - 1;
msgq(sp, M_INFO,
"Windows option value is too large, max is %u",
sp->t_rows);
}
sp->t_maxrows = sp->rows - 1;
} else
sp->t_maxrows = 1;
sp->woff = 0;
CALLOC_RET(sp, HMAP, SIZE_HMAP(sp), sizeof(SMAP));
TMAP = HMAP + (sp->t_rows - 1);
HMAP->lno = sp->lno;
HMAP->coff = 0;
HMAP->soff = 1;
F_CLR(sp, SC_SCR_TOP);
F_SET(sp, SC_SCR_REFORMAT | SC_SCR_CENTER);
F_SET(vip, VIP_CUR_INVALID);
F_SET(vip, VIP_N_EX_PAINT);
return (0);
}
static void
v_dtoh(SCR *sp)
{
GS *gp;
SCR *tsp;
int hidden;
hidden = 0;
gp = sp->gp;
while ((tsp = TAILQ_FIRST(&gp->dq))) {
free(_HMAP(tsp));
_HMAP(tsp) = NULL;
TAILQ_REMOVE(&gp->dq, tsp, q);
TAILQ_INSERT_TAIL(&gp->hq, tsp, q);
++hidden;
}
TAILQ_REMOVE(&gp->hq, sp, q);
TAILQ_INSERT_TAIL(&gp->dq, sp, q);
if (hidden > 1)
msgq(sp, M_INFO,
"%d screens backgrounded; use :display to list them",
hidden - 1);
}
static int
v_keyword(SCR *sp)
{
VI_PRIVATE *vip;
size_t beg, end, len;
int moved, state;
char *p;
if (db_get(sp, sp->lno, DBG_FATAL, &p, &len))
return (1);
for (moved = 0,
beg = sp->cno; beg < len && isspace(p[beg]); moved = 1, ++beg);
if (beg >= len) {
msgq(sp, M_BERR, "Cursor not in a word");
return (1);
}
if (moved) {
sp->cno = beg;
(void)vs_refresh(sp, 0);
}
for (state = inword(p[beg]),
end = beg; ++end < len && state == inword(p[end]););
vip = VIP(sp);
len = (end - beg);
BINC_RET(sp, vip->keyw, vip->klen, len);
memmove(vip->keyw, p + beg, len);
vip->keyw[len] = '\0';
return (0);
}
static VIKEYS const *
v_alias(SCR *sp, VICMD *vp, VIKEYS const *kp)
{
CHAR_T push;
switch (vp->key) {
case 'C':
push = '$';
vp->key = 'c';
break;
case 'D':
push = '$';
vp->key = 'd';
break;
case 'S':
push = '_';
vp->key = 'c';
break;
case 'Y':
push = '_';
vp->key = 'y';
break;
default:
return (kp);
}
return (v_event_push(sp,
NULL, &push, 1, CH_NOMAP | CH_QUOTED) ? NULL : &vikeys[vp->key]);
}
static int
v_count(SCR *sp, CHAR_T fkey, u_long *countp)
{
EVENT ev;
u_long count, tc;
ev.e_c = fkey;
count = tc = 0;
do {
tc = count * 10 + ev.e_c - '0';
if (count > tc) {
do {
if (v_key(sp, 0, &ev,
EC_MAPCOMMAND | EC_MAPNODIGIT) != GC_OK)
return (1);
} while (isdigit(ev.e_c));
msgq(sp, M_ERR,
"Number larger than %lu", ULONG_MAX);
return (1);
}
count = tc;
if (v_key(sp, 0, &ev, EC_MAPCOMMAND | EC_MAPNODIGIT) != GC_OK)
return (1);
} while (isdigit(ev.e_c));
*countp = count;
return (0);
}
static gcret_t
v_key(SCR *sp, int command_events, EVENT *evp, u_int32_t ec_flags)
{
u_int32_t quote;
for (quote = 0;;) {
if (v_event_get(sp, evp, 0, ec_flags | quote))
return (GC_FATAL);
quote = 0;
switch (evp->e_event) {
case E_CHARACTER:
if (evp->e_value == K_VLNEXT) {
quote = EC_QUOTED;
break;
}
return (GC_OK);
case E_ERR:
case E_EOF:
return (GC_FATAL);
case E_INTERRUPT:
(void)sp->gp->scr_bell(sp);
return (GC_INTERRUPT);
case E_REPAINT:
if (vs_repaint(sp, evp))
return (GC_FATAL);
break;
case E_WRESIZE:
return (GC_ERR);
case E_QUIT:
case E_WRITE:
if (command_events)
return (GC_EVENT);
default:
v_event_err(sp, evp);
return (GC_ERR);
}
}
}
#if defined(DEBUG) && defined(COMLOG)
static void
v_comlog(SCR *sp, VICMD *vp)
{
TRACE(sp, "vcmd: %c", vp->key);
if (F_ISSET(vp, VC_BUFFER))
TRACE(sp, " buffer: %c", vp->buffer);
if (F_ISSET(vp, VC_C1SET))
TRACE(sp, " c1: %lu", vp->count);
if (F_ISSET(vp, VC_C2SET))
TRACE(sp, " c2: %lu", vp->count2);
TRACE(sp, " flags: 0x%x\n", vp->flags);
}
#endif