Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
tree: d8725da8ac
Fetching contributors…

Cannot retrieve contributors at this time

1980 lines (1804 sloc) 61.443 kB
/* vi.c */
/* Copyright 1995 by Steve Kirkendall */
#include "elvis.h"
#ifdef FEATURE_RCSID
char id_vi[] = "$Id: vi.c,v 2.96 2004/03/07 21:38:21 steve Exp $";
#endif
#if USE_PROTOTYPES
static RESULT parse(_CHAR_ key, void *info);
static ELVCURSOR shape(WINDOW win);
# ifdef FEATURE_TEXTOBJ
static long indentation(WINDOW win, long line);
static RESULT vixmlobj(WINDOW win, VIINFO *vinf, MARKBUF *from, MARKBUF *to);
# endif
#endif
/* This is a list of operator commands. Note that 'g' commands are denoted by
* setting the 8th bit of the character, so \365 is gu, \325 is gU, \376 is g~,
* and \275 is g=.
*/
#define OPERATORS "cdy<=>!\275\365\325\376"
/* This is a list of letters and control characters that can be used as the
* 2nd key in a text object's name. In addition to the characters listed here,
* elvis always allows any punctuation character to be used -- as a field
* delimiter if nothing else.
*/
#define TEXTOBJECTS "BLSWXblpstwx\n"
/* These variables store the command which <.> is supposed to repeat.
* If the command is supposed to switch to input mode, then those
* commands will be smart enough to behave differently when executed
* for the <.> command. They'll paste from the "Elvis previous input"
* buffer.
*/
VIINFO dotcmd; /* the command itself, already parsed */
ELVBOOL dotviz; /* was the dotcmd used as a visible operator? */
long dotlines;/* number of lines for visible operator, or 0L if char/rect */
/* These are the "when" conditions in the array below */
#define WHEN_SEL 0x0001 /* legal while text is selected */
#define WHEN_ONCE 0x0002 /* legal during ^O */
#define WHEN_OPEN 0x0004 /* legal in "open" mode */
#define WHEN_HIST 0x0008 /* legal in "history" buffers */
#define WHEN_MOVE 0x0010 /* legal as target of an operator */
#define WHEN_ANY 0x001f /* all of the above */
#define WHEN_NEVER 0x0000 /* none of the above */
#define WHEN_MORE 0x0020 /* legal as part of a "more" invocation */
#define WHEN_EMPTY 0x0040 /* legal when buffer is empty */
#define WHEN_SEL_ONCE (WHEN_SEL|WHEN_ONCE)
#define WHEN_ONCE_OPEN (WHEN_ONCE|WHEN_OPEN)
#define WHEN_ONCE_OPEN_HIST (WHEN_ONCE|WHEN_OPEN|WHEN_HIST)
#define WHEN_SEL_ONCE_OPEN_HIST (WHEN_SEL|WHEN_ONCE|WHEN_OPEN|WHEN_HIST)
#define WHEN_SEL_ONCE_OPEN (WHEN_SEL|WHEN_ONCE|WHEN_OPEN)
#define WHEN_SEL_ONCE_MOVE (WHEN_SEL|WHEN_ONCE|WHEN_MOVE)
#define WHEN_SEL_ONCE_OPEN_MOVE (WHEN_SEL|WHEN_ONCE|WHEN_OPEN|WHEN_MOVE)
#define WHEN_SEL_ONCE_HIST_MOVE (WHEN_SEL|WHEN_ONCE|WHEN_HIST|WHEN_MOVE)
#define WHEN_OPEN_HIST (WHEN_OPEN|WHEN_HIST)
/* This array describes each command */
static struct
{
RESULT (*func) P_((WINDOW win, VIINFO *vinf));
unsigned short when; /* when command is legal */
unsigned short tweak; /* misc features */
char *helptopic; /* label in "elvisvi.html" */
}
vikeys[] =
{
/* NUL not defined */ { NULL, WHEN_NEVER, TWEAK_NONE, "nul" },
/* ^A search for word */ { m_search, WHEN_ANY, TWEAK_MARK },
/* ^B page backward */ { m_scroll, WHEN_SEL_ONCE, TWEAK_FRONT },
/* ^C not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* ^D scroll dn 1/2page */ { m_scroll, WHEN_SEL_ONCE_OPEN, TWEAK_FIXCOL },
/* ^E scroll up */ { m_scroll, WHEN_SEL_ONCE, TWEAK_FIXCOL },
/* ^F page forward */ { m_scroll, WHEN_SEL_ONCE, TWEAK_FRONT },
/* ^G show file status */ { m_absolute, WHEN_SEL_ONCE_OPEN|WHEN_EMPTY, TWEAK_NONE },
/* ^H move left, like h */ { m_left, WHEN_ANY, TWEAK_NONE },
/* ^I move to next tag */ { m_tag, WHEN_ONCE_OPEN, TWEAK_NONE },
/* ^J move down */ { m_updown, WHEN_ANY, TWEAK_IGNCOL_INCL_LINE },
/* ^K find misspelled */ { m_spell, WHEN_NEVER, TWEAK_NONE },
/* ^L redraw screen */ { v_expose, WHEN_SEL_ONCE|WHEN_EMPTY, TWEAK_NONE },
/* ^M mv front next ln */ { m_updown, WHEN_ANY, TWEAK_FRONT_INCL_LINE },
/* ^N move down */ { m_updown, WHEN_ANY, TWEAK_IGNCOL_INCL_LINE },
/* ^O ignored */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* ^P move up */ { m_updown, WHEN_ANY, TWEAK_IGNCOL_INCL_LINE },
/* ^Q not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* ^R redo change */ { v_undo, WHEN_ONCE_OPEN|WHEN_EMPTY, TWEAK_NONE },
/* ^S not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* ^T pop tagstack */ { v_tag, WHEN_OPEN|WHEN_EMPTY, TWEAK_NONE },
/* ^U scroll up 1/2page */ { m_scroll, WHEN_SEL_ONCE_OPEN, TWEAK_FIXCOL },
/* ^V start visible */ { v_visible, WHEN_SEL_ONCE, TWEAK_LINE },
/* ^W window operations */ { v_window, WHEN_SEL_ONCE_OPEN_HIST|WHEN_EMPTY,TWEAK_NONE },
/* ^X move to phys col */ { m_column, WHEN_SEL_ONCE_OPEN_HIST|WHEN_MOVE,TWEAK_IGNCOL_MARK },
/* ^Y scroll down */ { m_scroll, WHEN_SEL_ONCE, TWEAK_FIXCOL },
/* ^Z stop process */ { v_notex, WHEN_ANY, TWEAK_NONE },
/* ESC end visible mark */ { v_visible, WHEN_SEL, TWEAK_NONE, "^obra" },
/* ^\ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE, "^bksl" },
/* ^] keyword is tag */ { v_tag, WHEN_ONCE_OPEN, TWEAK_NONE, "^cbra" },
/* ^^ previous file */ { v_notex, WHEN_ONCE_OPEN|WHEN_EMPTY, TWEAK_FRONT },
/* ^_ move to row */ { NULL, WHEN_SEL_ONCE, TWEAK_FIXCOL_INCL },
/* SPC move right,like l */ { m_right, WHEN_ANY, TWEAK_NONE, "l" },
/* ! run thru filter */ { NULL, WHEN_NEVER, TWEAK_FRONT_LINE_DOT_OPER_UNDO, "bang" },
/* " select cut buffer */ { NULL, 0,/* This is handled by the command parser */0, "quote" },
/* # increment number */ { v_number, WHEN_ONCE_OPEN_HIST, TWEAK_DOT_UNDO, "hash" },
/* $ move to rear */ { m_column, WHEN_ANY, TWEAK_IGNCOL_INCL, "dollar"},
/* % move to match */ { m_absolute, WHEN_ANY, TWEAK_INCL, "pct" },
/* & repeat subst */ { v_notex, WHEN_NEVER, TWEAK_FIXCOL_INCL_LINE_DOT_UNDO, "amp"},
/* ' move to a mark */ { m_mark, WHEN_SEL_ONCE_OPEN_MOVE, TWEAK_FRONT_INCL_MARK_LINE, "apost"},
/* ( mv back sentence */ { m_bsentence, WHEN_ANY, TWEAK_NONE, "open" },
/* ) mv fwd sentence */ { m_fsentence, WHEN_ANY, TWEAK_NONE, "close" },
/* * errlist */ { v_notex, WHEN_OPEN|WHEN_EMPTY, TWEAK_FRONT, },
/* + mv front next ln */ { m_updown, WHEN_ANY, TWEAK_FRONT_INCL_LINE, },
/* , reverse [fFtT] cmd*/ { m_csearch, WHEN_ANY, TWEAK_INCL, "comma" },
/* - mv front prev ln */ { m_updown, WHEN_ANY, TWEAK_FRONT_INCL_LINE },
/* . special... */ { NULL, 0,/* This is handled by the command parser */0, "stop" },
/* / forward search */ { m_search, WHEN_ANY|WHEN_MORE, TWEAK_MARK, "slash" },
/* 0 part of count? */ { m_column, WHEN_ANY, TWEAK_IGNCOL_MARK },
/* 1 part of count */ { NULL /* This is handled by the command parser */ },
/* 2 part of count */ { NULL /* This is handled by the command parser */ },
/* 3 part of count */ { NULL /* This is handled by the command parser */ },
/* 4 part of count */ { NULL /* This is handled by the command parser */ },
/* 5 part of count */ { NULL /* This is handled by the command parser */ },
/* 6 part of count */ { NULL /* This is handled by the command parser */ },
/* 7 part of count */ { NULL /* This is handled by the command parser */ },
/* 8 part of count */ { NULL /* This is handled by the command parser */ },
/* 9 part of count */ { NULL /* This is handled by the command parser */ },
/* : run single EX cmd */ { v_ex, WHEN_SEL_ONCE_OPEN_HIST|WHEN_EMPTY, TWEAK_NONE, "colon" },
/* ; repeat [fFtT] cmd */ { m_csearch, WHEN_ANY, TWEAK_INCL, "semi" },
/* < shift text left */ { NULL, WHEN_OPEN, TWEAK_FRONT_LINE_DOT_OPER_UNDO,"lt"},
/* = preset filter */ { NULL, WHEN_OPEN, TWEAK_DOT_OPER_UNDO },
/* > shift text right */ { NULL, WHEN_OPEN, TWEAK_FRONT_LINE_DOT_OPER_UNDO,"gt"},
/* ? backward search */ { m_search, WHEN_ANY|WHEN_MORE, TWEAK_MARK, "quest" },
/* @ execute a cutbuf */ { v_at, WHEN_SEL_ONCE_OPEN_HIST|WHEN_EMPTY,TWEAK_NONE, "at" },
/* A append at EOL */ { v_input, WHEN_ONCE_OPEN_HIST|WHEN_EMPTY|WHEN_MORE, TWEAK_DOT_UNDO },
/* B move back Word */ { m_bigword, WHEN_ANY, TWEAK_NONE },
/* C change to EOL */ { v_notop, WHEN_OPEN, TWEAK_DOT_UNDO },
/* D delete to EOL */ { v_notop, WHEN_OPEN, TWEAK_DOT_UNDO },
/* E move end of Word */ { m_bigword, WHEN_ANY, TWEAK_INCL },
/* F move bk to char */ { m_csearch, WHEN_ANY, TWEAK_INCL },
/* G move to line # */ { m_absolute, WHEN_ANY, TWEAK_FRONT_INCL_MARK_LINE },
/* H move to row */ { m_scrnrel, WHEN_SEL_ONCE_MOVE, TWEAK_FRONT_INCL_LINE },
/* I insert at front */ { v_input, WHEN_ONCE_OPEN_HIST|WHEN_EMPTY|WHEN_MORE, TWEAK_DOT_UNDO },
/* J join lines */ { v_notex, WHEN_ONCE_OPEN, TWEAK_DOT_UNDO },
/* K look up keyword */ { v_notex, WHEN_OPEN, TWEAK_NONE },
/* L move to last row */ { m_scrnrel, WHEN_SEL_ONCE_MOVE, TWEAK_FRONT_INCL_LINE },
/* M move to mid row */ { m_scrnrel, WHEN_SEL_ONCE_MOVE, TWEAK_FRONT_INCL_LINE },
/* N reverse prev srch */ { m_search, WHEN_ANY, TWEAK_MARK },
/* O insert above line */ { v_input, WHEN_ONCE_OPEN_HIST|WHEN_EMPTY|WHEN_MORE, TWEAK_DOT_UNDO },
/* P paste before */ { v_paste, WHEN_ONCE_OPEN_HIST|WHEN_EMPTY, TWEAK_DOT_UNDO },
/* Q quit to EX mode */ { v_ex, WHEN_OPEN|WHEN_EMPTY, TWEAK_NONE },
/* R overtype */ { v_input, WHEN_ONCE_OPEN_HIST|WHEN_MORE, TWEAK_DOT_UNDO },
/* S change line */ { v_notop, WHEN_OPEN, TWEAK_DOT_UNDO },
/* T move bk to char */ { m_csearch, WHEN_ANY, TWEAK_INCL },
/* U undo whole line */ { v_undo, WHEN_OPEN_HIST|WHEN_EMPTY, TWEAK_FRONT_UNDO },
/* V start visible */ { v_visible, WHEN_SEL_ONCE_OPEN, TWEAK_LINE },
/* W move forward Word */ { m_bigword, WHEN_ANY, TWEAK_NONE },
/* X delete to left */ { v_delchar, WHEN_ONCE_OPEN_HIST, TWEAK_DOT_UNDO },
/* Y yank text */ { v_notop, WHEN_OPEN, TWEAK_NONE },
/* Z save file & exit */ { v_quit, WHEN_ONCE_OPEN|WHEN_EMPTY, TWEAK_NONE },
/* [ move back section */ { m_bsection, WHEN_ANY, TWEAK_MARK, "obra" },
/* \ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE, "bksl" },
/* ] move fwd section */ { m_fsection, WHEN_ANY, TWEAK_MARK, "cbra" },
/* ^ move to front */ { m_front, WHEN_ANY, TWEAK_NONE },
/* _ current line */ { m_updown, WHEN_ANY, TWEAK_FRONT_INCL_LINE },
/* ` move to mark */ { m_mark, WHEN_SEL_ONCE_OPEN_MOVE, TWEAK_MARK, "grave" },
/* a append at cursor */ { v_input, WHEN_OPEN_HIST|WHEN_EMPTY|WHEN_MORE, TWEAK_DOT_UNDO },
/* b move back word */ { m_word, WHEN_ANY, TWEAK_NONE },
/* c change text */ { NULL, WHEN_OPEN, TWEAK_DOT_OPER_UNDO },
/* d delete op */ { NULL, WHEN_OPEN, TWEAK_DOT_OPER_UNDO },
/* e move end word */ { m_word, WHEN_ANY, TWEAK_INCL },
/* f move fwd for char */ { m_csearch, WHEN_ANY, TWEAK_INCL },
/* g command prefix */ { NULL /* This is handled by the command parser */ },
/* h move left */ { m_left, WHEN_ANY, TWEAK_NONE },
/* i insert at cursor */ { v_input, WHEN_ONCE_OPEN_HIST|WHEN_EMPTY|WHEN_MORE, TWEAK_DOT_UNDO|TWEAK_IGNCOL },
/* j move down */ { m_updown, WHEN_ANY, TWEAK_IGNCOL_INCL_LINE },
/* k move up */ { m_updown, WHEN_ANY, TWEAK_IGNCOL_INCL_LINE },
/* l move right */ { m_right, WHEN_ANY, TWEAK_NONE },
/* m define a mark */ { v_setmark, WHEN_ONCE_OPEN|WHEN_OPEN, TWEAK_NONE },
/* n repeat prev srch */ { m_search, WHEN_ANY, TWEAK_MARK },
/* o insert below line */ { v_input, WHEN_OPEN_HIST|WHEN_EMPTY|WHEN_MORE, TWEAK_DOT_UNDO },
/* p paste after */ { v_paste, WHEN_ONCE_OPEN_HIST, TWEAK_DOT_UNDO },
/* q not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* r replace chars */ { v_delchar, WHEN_ONCE_OPEN_HIST, TWEAK_DOT_UNDO },
/* s subst N chars */ { v_notop, WHEN_OPEN, TWEAK_DOT_UNDO },
/* t move fwd to char */ { m_csearch, WHEN_ANY, TWEAK_INCL },
/* u undo */ { v_undo, WHEN_ONCE_OPEN|WHEN_EMPTY, TWEAK_NONE },
/* v start visible */ { v_visible, WHEN_SEL_ONCE_OPEN, TWEAK_NONE },
/* w move fwd word */ { m_word, WHEN_ANY, TWEAK_NONE },
/* x delete character */ { v_delchar, WHEN_ONCE_OPEN_HIST, TWEAK_DOT_UNDO },
/* y yank text */ { NULL, WHEN_OPEN, TWEAK_OPER },
/* z adjust scrn row */ { m_z, WHEN_SEL_ONCE, TWEAK_FIXCOL },
/* { back paragraph */ { m_bsection, WHEN_ANY, TWEAK_NONE, "ocur" },
/* | move to column */ { m_column, WHEN_ANY, TWEAK_IGNCOL_MARK, "bar" },
/* } fwd paragraph */ { m_fsection, WHEN_ANY, TWEAK_NONE, "ccur" },
/* ~ upper/lowercase */ { v_delchar, WHEN_ONCE_OPEN_HIST, TWEAK_DOT_UNDO },
/* DEL not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/****************************************************************************************************************/
/* gNUL not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^A not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^B not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^C not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^D not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^E not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^F not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^G not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^H not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^I move to prev tag */ { m_tag, WHEN_ONCE_OPEN, TWEAK_NONE },
/* g^J not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^K not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^L not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^M not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^N not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^O not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^P not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^Q not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^R not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^S not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^T not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^U not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^V opposite sel end */ { v_visible, WHEN_SEL, TWEAK_FIXCOL },
/* g^W not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^X not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^Y not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^Z not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gESC not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^\ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^] not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^^ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^_ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gSPC not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g! not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g" not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g# not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g$ move to row rear */ { m_g, WHEN_ANY, TWEAK_IGNCOL_INCL },
/* g% opposite sel end */ { v_visible, WHEN_SEL, TWEAK_FIXCOL },
/* g& not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g' not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g( not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g) not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g* not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g+ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g, not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g- not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g. not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g/ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g0 not defined */ { m_g, WHEN_ANY, TWEAK_IGNCOL_MARK },
/* g1 not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g2 not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g3 not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g4 not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g5 not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g6 not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g7 not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g8 not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g9 not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g: not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g; not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g< not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g= replace char op */ { NULL, WHEN_OPEN, TWEAK_DOT_OPER_UNDO },
/* g> not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g? not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g@ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gA not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gB not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gC not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gD find global def */ { m_search, WHEN_ANY, TWEAK_MARK },
/* gE not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gF not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gG not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gH not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gI insert at front */ { v_input, WHEN_ONCE_OPEN_HIST|WHEN_EMPTY|WHEN_MORE, TWEAK_DOT_UNDO },
/* gJ join lines */ { v_notex, WHEN_ONCE_OPEN, TWEAK_DOT_UNDO },
/* gK not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gL not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gM not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gN not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gO not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gP not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gQ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gR not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gS not defined */ { m_endspell, WHEN_ANY, TWEAK_INCL },
/* gT not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gU make uppercase op */ { NULL, WHEN_OPEN, TWEAK_DOT_OPER_UNDO },
/* gV not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gW not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gX not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gY not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gZ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g[ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g\ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g] not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g^ move to row front */ { m_g, WHEN_ANY, TWEAK_NONE },
/* g_ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g` not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* ga not defined */ { v_ascii, WHEN_ONCE_OPEN, TWEAK_NONE },
/* gb not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gc not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gd find local def */ { m_search, WHEN_ANY, TWEAK_MARK },
/* ge not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gf not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gg not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gh not defined */ { m_g, WHEN_ANY, TWEAK_NONE },
/* gi not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gj not defined */ { m_gupdown, WHEN_ANY, TWEAK_IGNCOL_INCL_LINE },
/* gk not defined */ { m_gupdown, WHEN_ANY, TWEAK_IGNCOL_INCL_LINE },
/* gl not defined */ { m_g, WHEN_ANY, TWEAK_NONE },
/* gm not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gn not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* go not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gp not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gq not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gr not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gs find misspelled */ { m_spell, WHEN_NEVER, TWEAK_NONE },
/* gt not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gu make lowercase op */ { NULL, WHEN_OPEN, TWEAK_DOT_OPER_UNDO },
/* gv not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gw not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gx not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gy not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* gz not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g{ not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g| not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g} not defined */ { NULL, WHEN_NEVER, TWEAK_NONE },
/* g~ toggle case op */ { NULL, WHEN_OPEN, TWEAK_DOT_OPER_UNDO },
/* gDEL not defined */ { NULL, WHEN_NEVER, TWEAK_NONE }
};
#ifdef FEATURE_TEXTOBJ
/* Return the indentation width (number of columns, after expanding tabs) for
* a given line number. For empty lines, return -1. This is used for the 'L'
* indentation blocks.
*/
static long indentation(win, line)
WINDOW win; /* buffer */
long line; /* line number */
{
long width;
BUFFER buf = markbuffer(win->cursor);
MARKBUF m;
CHAR *cp;
/* start counting from the beginning of the line */
scanalloc(&cp, marktmp(m, buf, lowline(bufbufinfo(buf), line)));
width = 0;
/* count the spaces and tabs */
while (*cp && *cp != '\n' && elvspace(*cp))
{
if (*cp == ' ')
width++;
else if (*cp == '\t')
width += opt_totab(o_tabstop(buf), width);
scannext(&cp);
}
/* if empty line, then width = -1 */
if (!cp || *cp == '\n'
#ifdef DISPLAY_SYNTAX
|| *cp == dmspreprocessor(win)
#endif
)
width = -1;
/* clean up, and return the width */
scanfree(&cp);
return width;
}
/* Find the endpoints for an XML object, delimited by <tag>...</tag> pairs.
* This function should only be called from the vitextobj() function.
*/
static RESULT vixmlobj(win, vinf, from, to)
WINDOW win; /* window where the command is to be run */
VIINFO *vinf; /* identifies the type of text object */
MARKBUF *from; /* output: move this mark to top of affected text */
MARKBUF *to; /* output: move this mark to bottom of affected text */
{
CHAR *scan, *intag;
CHAR prev;
CHAR *name = NULL, *inname;
int nest;
ELVBOOL slash;
MARKBUF middle;
static struct xmlstack_s
{
struct xmlstack_s *next;
CHAR *name;
} *stack = NULL, *st;
DEFAULT(1);
/* start scanning at cursor */
scanalloc(&scan, win->cursor);
/* Adjust the scanning point. If in a </tag> then we want to start
* at its "<". If in a <tag> then we want to start after the "<".
* Otherwise it doesn't matter much, so go back to the cursor position.
*/
while (scan && *scan != '<' && scanprev(&scan) && *scan != '>')
{
}
if (!scan || *scan != '<')
scanseek(&scan, win->cursor);
else if (scannext(&scan) && *scan == '/')
scanprev(&scan);
/* scan forward for the count'th unmatched "</". This is tricky!
* Not all XML tags come in pairs, so we can't simply count tags with
* and without slashes. We must push <tag> names onto a stack, and
* pop them when we get a </tag>. To handle unpaired tags, </tag>
* should always pop back to the matching name; or, if there is no
* name, then the match must be before the cursor so WE'RE IN THAT
* TAG PAIR! THAT'S ONE OF THE TAGS WE SHOULD COUNT!
*/
for (prev = *scan, nest = vinf->count; nest > 0; prev = *scan)
{
/* we scan pairs of characters -- one in "prev" and one at
* "*scan". We already have "prev", but now we need to
* increment the scan pointer.
*/
if (!scannext(&scan))
goto Fail;
/* Is this the start of a tag? */
if (prev == '<')
{
/* Remember the location of the "<". If this turns out
* to be the ending tag for an ix object, we'll need it.
*/
*to = *scanmark(&scan);
to->offset--;
middle = *to;
/* detect a slash */
slash = (ELVBOOL)(*scan == '/');
if (slash)
if (!scannext(&scan)) goto Fail ;
/* collect chars of the name */
assert(name == NULL);
for (;
scan && (elvalnum(*scan) ||
*scan == ':' ||
(*scan == '-' && prev != '-'));
scannext(&scan))
{
buildCHAR(&name, vinf->key2 == 'X' ? elvtolower(*scan) : *scan);
prev = *scan;
}
if (scan && *scan == '-')
{
scanprev(&scan);
name[CHARlen(name) - 1] = '\0';
}
if (!scan || !name)
goto Fail;
/* if slash, pop to match. If no match decrement nest*/
if (slash)
{
/* pop any tags that don't nest properly */
while (stack && CHARcmp(stack->name, name))
{
st = stack;
stack = st->next;
safefree(st->name);
safefree(st);
}
/* did we find a match? */
if (stack)
{
/* pop the match too. */
st = stack;
stack = st->next;
safefree(st->name);
safefree(st);
/* free the name */
safefree(name);
name = NULL;
}
else /* must extend back past the cursor */
{
nest--;
/* free the name, unless this is
* the tag we want.
*/
if (nest > 0)
{
safefree(name);
name = NULL;
}
}
}
else /* push onto stack */
{
st = (struct xmlstack_s *)safealloc(1, sizeof *st);
st->name = name;
st->next = stack;
stack = st;
name = NULL;
}
}
}
/* if "ax" object, remember where it ends to help find the right end */
if (vinf->command == 'a')
*to = *scanmark(&scan);
/* Scan backward for the starting tag, skipping over nested pairs.
* This is easier than the search for the ending tag, because now we
* only need to worry about nested pairs of THAT PARTICULAR TAG NAME.
*/
scanseek(&scan, &middle);
for (nest = 1; scan && nest > 0; )
{
if (!scanprev(&scan))
goto Fail;
if (*scan != '<')
continue;
/* we're at the start of a tag. See if it's a starter or not */
scandup(&intag, &scan);
scannext(&intag);
slash = (ELVBOOL)(*intag == '/');
if (slash)
scannext(&intag);
/* does the name match? */
inname = name;
if (vinf->key2 == 'X')
while (*inname && *inname++ == elvtolower(*intag))
scannext(&intag);
else
while (*inname && *inname++ == *intag)
scannext(&intag);
/* !!! Note: the following if() statement isn't quite right.
* It doesn't handle - correctly. Consequently </tag> may
* be paired up with <tag-long-name>, provided there is no
* </tag-long-name> between them.
*/
if (!*inname && (!intag || !(elvalnum(*intag)||*intag == ':')))
{
/* yes, the name matches -- adjust nest */
if (slash)
nest++;
else
nest--;
}
scanfree(&intag);
}
/* now we can compute the endpoints */
if (vinf->command == 'i')
{
/* skip to the end of the starting tag */
while (*scan != '>')
if (!scannext(&scan)) goto Fail;
/* this is probably the start of the inner tag */
*from = *scanmark(&scan);
from->offset++;
/* skip any spaces and tabs */
do
{
if (!scannext(&scan)) goto Fail;
} while (*scan == ' ' || *scan == '\t');
/* if we hit a newline, then starting point is after newline */
if (*scan == '\n')
{
scannext(&scan);
*from = *scanmark(&scan);
}
/* Go back to the "<" of the ending tag. This is probably the
* end of the inner tag.
*/
scanseek(&scan, to);
/* skip backward past any spaces and tabs */
while (scanprev(&scan) && (*scan == ' ' || *scan == '\t'))
{
}
/* if we hit a newline, then that's the real end of the object*/
if (scan && *scan == '\n')
{
*to = *scanmark(&scan);
to->offset++;
}
}
else /* vinf->command == 'a' */
{
/* The "<" of the start tag is the start of the object */
*from = *scanmark(&scan);
/* go back to the ending tag */
scanseek(&scan, to);
/* scan forward for the ">" at the end of that tag */
while (*scan != '>')
if (!scannext(&scan)) goto Fail;
/* This is the end of the object */
*to = *scanmark(&scan);
to->offset++;
}
/* if the endpoints would be the same, then fail */
if (from->offset >= to->offset)
goto Fail;
/* success! */
safefree(name);
scanfree(&scan);
return RESULT_COMPLETE;
Fail:
if (name)
safefree(name);
scanfree(&scan);
while (stack)
{
st = stack;
stack = st->next;
safefree(st->name);
safefree(st);
}
return RESULT_ERROR;
}
/* Find the endpoints of a text object. The "from" and "to" parameters should
* pointers to MARKBUF structures -- not dynamically allocated MARKs! Returns
* RESULT_COMPLETE normally, RESULT_MORE if only the "from" end could be found,
* or RESULT_ERROR if neither end could be found or the text object is not
* understood.
*/
RESULT vitextobj(win, vinf, from, to)
WINDOW win; /* window where the command is to be run */
VIINFO *vinf; /* identifies the type of text object */
MARKBUF *from; /* output: move this mark to top of affected text */
MARKBUF *to; /* output: move this mark to bottom of affected text */
{
CHAR *oldmatchchar;
RESULT result = RESULT_COMPLETE;
BUFFER buf;
CHAR *scan;
long count;
VIINFO cmd; /* simulated command, to find characters */
CHAR fromkey;/* keystroke used to find top of object */
CHAR tokey; /* keystroke used to find top of next object */
CHAR opposite;/* for block delimiters, this is the other delimiter */
CHAR prev; /* previously scanned character */
MARKBUF tmp;
long cursoff;
long ln, fromln, in, nextin;
/* if the buffer is empty, then there can't be any text object */
buf = markbuffer(win->cursor);
if (o_buflines(buf) == 0L)
return RESULT_ERROR;
/* temporarily change matchchar to a hardcoded value so we can handle
* blocks in a consistent way.
*/
oldmatchchar = o_matchchar;
o_matchchar = toCHAR("{}[]()<>");
/* change b, B, or l to the equivelent block or field delimiter */
if (vinf->key2 == 'b')
vinf->key2 = '(';
else if (vinf->key2 == 'B')
vinf->key2 = '{';
else if (vinf->key2 == 'l')
vinf->key2 = '\n';
else if (vinf->key2 == 't')
vinf->key2 = '\t';
/* if block delimiter, then find its opposite */
for (scan = o_matchchar, opposite = '\0'; !opposite && *scan; scan += 2)
if (scan[0] == vinf->key2)
opposite = scan[1];
else if (scan[1] == vinf->key2)
opposite = scan[0];
/* for now, assume we won't need fromkey/tokey */
fromkey = tokey = '\0';
/* "from" and "to" buffers are in the window's buffer */
from->buffer = to->buffer = buf;
/* the default count is always 1, for all types of text object */
DEFAULT(1);
/* handle each type of object separately */
switch (vinf->key2)
{
case '{':
case '[':
case '(':
case '<':
case '%':
/* search backward for nth character */
scanalloc(&scan, win->cursor);
if (vinf->key2 != '%')
{
for (count = vinf->count, prev = ' '; ; )
{
if (*scan == vinf->key2)
count--;
else if (*scan == '\n' && prev == vinf->key2)
break;
prev = *scan;
if (count == 0 || !scanprev(&scan))
break;
if (*scan == opposite)
count++;
}
}
else /* % */
{
for (count = vinf->count, prev = ' '; ; )
{
if (*scan=='(' || *scan=='{' || *scan=='[')
count--;
else if (*scan == '\n' &&
(prev=='(' || prev=='{' || prev=='['))
break;
prev = *scan;
if (count == 0 || !scanprev(&scan))
break;
if (*scan==')' || *scan=='}' || *scan==']')
count++;
}
}
if (count > 0L)
{
/* oops, there was no such character */
result = RESULT_ERROR;
scanfree(&scan);
goto Error;
}
/* This is the top of the object */
from->offset = markoffset(scanmark(&scan));
scanfree(&scan);
cursoff = markoffset(win->cursor);
marksetoffset(win->cursor, from->offset);
/* find the matching character by simulating a % command */
cmd.command = '%';
cmd.count = 0L;
result = m_absolute(win, &cmd);
if (result != RESULT_COMPLETE)
{
result = RESULT_MORE;
marksetoffset(win->cursor, cursoff);
goto Error;
}
/* the cursor is now at the bottom of the object */
to->offset = markoffset(win->cursor);
/* include delimiter chars if 'a', exclude if 'i' */
if (vinf->command == 'a')
to->offset++;
else
{
markaddoffset(from, 1L);
if (scanchar(from) == '\n')
markaddoffset(from, 1L);
do
{
markaddoffset(to, -1L);
} while (elvspace(scanchar(to)));
markaddoffset(to, 1L);
}
break;
case '}':
case ']':
case ')':
case '>':
/* search forward for nth character */
scanalloc(&scan, win->cursor);
scanprev(&scan);
for (count = vinf->count; count > 0L && scannext(&scan); )
if (*scan == vinf->key2)
count--;
else if (*scan == opposite)
count++;
if (count > 0L)
{
/* oops, there was no such character */
result = RESULT_ERROR;
scanfree(&scan);
goto Error;
}
/* This is the bottom of the object */
to->offset = markoffset(scanmark(&scan));
scanfree(&scan);
marksetoffset(win->cursor, to->offset);
/* find the matching character by simulating a % command */
cmd.command = '%';
cmd.count = 0L;
result = m_absolute(win, &cmd);
if (result != RESULT_COMPLETE)
goto Error;
/* the cursor is now at the bottom of the object */
from->offset = markoffset(win->cursor);
/* include delimiter chars if 'a', exclude if 'i' */
if (vinf->command == 'a')
to->offset++;
else
{
markaddoffset(from, 1L);
if (scanchar(from) == '\n')
markaddoffset(from, 1L);
do
{
markaddoffset(to, -1L);
} while (elvspace(scanchar(to)));
markaddoffset(to, 1L);
}
break;
case 'L':
fromln = markline(win->cursor);
in = indentation(win, fromln);
count = vinf->count;
/* if this line is blank, then scan forward and backward for
* the nearest nonblank lines. Use the one with more
* indentation.
*/
if (in < 0L)
{
/* scan backward for a non-blank line */
for (ln = fromln, nextin = -1L; nextin < 0 && --ln > 0;)
nextin = indentation(win, ln);
/* scan forward for a non-blank line */
for (; in < 0 && ++fromln < o_buflines(buf); )
in = indentation(win, fromln);
/* choose the one with more indentation. If the entire
* file consists of blank lines, then this will leave
* fromln=0, in=-1L, which will cause the next loop to
* detect a failure condition.
*/
if (nextin > in)
fromln = ln, in = nextin;
}
/* search backward for n'th line with a smaller indent */
for (ln = fromln - 1; ln >= 1 && in > 0 && count > 0; ln--)
{
nextin = indentation(win, ln);
if (nextin >= 0 && nextin < in)
{
in = nextin;
count--;
if (in == 0 || count == 0)
break;
}
}
if (count > 1 || (count == 1 && in == 0))
{
result = RESULT_ERROR;
goto Error;
}
/* we went one line too far -- range starts one line down */
marksetline(from, ln + 1);
/* search forward past lines with more indentation */
for (ln = fromln + 1; ln < o_buflines(buf); ln++)
{
nextin = indentation(win, ln);
if (nextin >= 0 && nextin <= in)
break;
}
/* ln is now either past the end of the buffer, or on the line
* after the end of the indentation block. Either way, that's
* the end of the block.
*/
marksetline(to, ln);
/* the 'i' version leaves off the whitespace */
if (vinf->command == 'i')
{
/* trim the leading space */
for (scanalloc(&scan, from);
*scan && elvspace(*scan);
scannext(&scan))
{
}
if (scan)
marksetoffset(from, markoffset(scanmark(&scan)));
scanfree(&scan);
/* trim the trailing space */
markaddoffset(to, -1L);
for (scanalloc(&scan, to);
*scan && elvspace(*scan);
scanprev(&scan))
{
}
if (scan)
marksetoffset(to, markoffset(scanmark(&scan)) + 1);
scanfree(&scan);
}
break;
case 'w':
fromkey = 'b';
tokey = 'w';
break;
case 'W':
fromkey = 'B';
tokey = 'W';
break;
case 's':
fromkey = '(';
tokey = ')';
break;
case 'S':
fromkey = '[';
tokey = ']';
break;
case 'p':
fromkey = '{';
tokey = '}';
break;
case 'x':
case 'X':
o_matchchar = oldmatchchar;
return vixmlobj(win, vinf, from, to);
default:
if (elvalnum(vinf->key2))
{
/* bogus letter */
result = RESULT_ERROR;
goto Error;
}
/* Other characters can be used as delimiters though... */
/* search backward for character */
scanalloc(&scan, win->cursor);
for (count = vinf->count; count && scanprev(&scan); )
{
if (*scan == vinf->key2 || *scan == '\n')
count--;
}
if (!scan && count > 1)
{
/* oops, there was no such character */
result = RESULT_ERROR;
scanfree(&scan);
goto Error;
}
/* This is the top of the object */
if (scan)
{
opposite = *scan;
marksetoffset(from, markoffset(scanmark(&scan)));
}
else
{
marksetoffset(from, 0L);
scanseek(&scan, from);
opposite = '\0';
}
/* search forward the matching character */
for (count = vinf->count * 2 - 1; count > 0 && scannext(&scan);)
if (*scan == vinf->key2 || *scan == '\n')
count--;
if (!scan)
{
/* oops, there was no such character */
result = RESULT_ERROR;
scanfree(&scan);
goto Error;
}
/* this is the bottom of the object */
to->offset = markoffset(scanmark(&scan));
/* 'a' includes the trailing delimiter, unless there was no
* trailing delimiter in which case it includes the leading
* delimiter. 'i' excludes both delimiters, and also leading
* whitespace.
*/
if (vinf->command == 'a')
{
if (*scan == vinf->key2)
{
to->offset++;
if (opposite)
from->offset++;
}
else if (opposite && opposite != vinf->key2)
from->offset++;
}
else
{
if (opposite)
from->offset++;
scanseek(&scan, from);
while (elvspace(*scan) && scannext(&scan))
{
}
if (scan && markoffset(scanmark(&scan)) < markoffset(to))
marksetoffset(from, markoffset(scanmark(&scan)));
}
scanfree(&scan);
/* If ends are on different lines, and delimiter isn't '\n',
* then treat that as an error.
*/
if (vinf->key2 != '\n' && markline(from) != markline(to))
{
result = RESULT_ERROR;
goto Error;
}
}
/* maybe use a simulated command to find the top of the object */
if (fromkey && tokey)
{
/* To find the start of the object, add 1 (unless already at
* end of buffer) and then simulate a `fromkey' command.
*/
if (markoffset(win->cursor) + 1L < o_bufchars(buf))
markaddoffset(win->cursor, 1L);
cmd.command = cmd.key2 = fromkey;
cmd.count = 1L;
result = (*vikeys[cmd.command].func)(win, &cmd);
if (result != RESULT_COMPLETE)
goto Error;
from->offset = markoffset(win->cursor);
/* To find the end of the object, simulate a `tokey' command. */
cmd.command = cmd.key2 = tokey;
cmd.count = vinf->count;
result = (*vikeys[cmd.command].func)(win, &cmd);
if (result != RESULT_COMPLETE)
goto Error;
to->offset = markoffset(win->cursor);
/* adjust whitespace */
if (vinf->command == 'a')
{
/* We want to include whitespace. If there is no
* trailing whitespace, then try to use leading.
*/
tmp = *to;
tmp.offset--;
if (!elvspace(scanchar(&tmp)))
{
tmp.offset = from->offset - 1;
while (tmp.offset > 0 && elvspace(scanchar(&tmp)))
tmp.offset--;
from->offset = tmp.offset + 1;
}
}
else
{
/* if leading/trailing whitespace, then exclude it */
tmp = *to;
do
tmp.offset--;
while (tmp.offset > from->offset && elvspace(scanchar(&tmp)));
to->offset = tmp.offset + 1;
while (elvspace(scanchar(from)))
from->offset++;
}
}
/* text objects never include the last *CHARACTER*, but they always
* include the last *LINE*.
*/
if (vinf->tweak & TWEAK_LINE)
vinf->tweak |= TWEAK_INCL;
else
vinf->tweak &= ~TWEAK_INCL;
Error:
/* restore the matchchar option */
o_matchchar = oldmatchchar;
assert(result != RESULT_COMPLETE || from->offset < to->offset);
return result;
}
#endif /* FEATURE_TEXTOBJ */
/* This initializes the "info" field to represent the start of a new command */
void viinitcmd(info)
VIINFO *info; /* vi command to be blanked */
{
info->phase = VI_START;
info->count = info->count2 = 0;
info->cutbuf = info->oper = info->command = info->key2 = '\0';
}
/* This performs a single vi command for a given window. The vi command
* is stored in the current state.
*/
RESULT _viperform(win)
WINDOW win; /* window whose command is to be executed */
{
return viperform(win, (VIINFO *)win->state->info);
}
/* This performs a single vi command, and returns RESULT_COMPLETE if
* successful, RESULT_ERROR if not error, or RESULT_MORE if it has pushed
* a new state and can't be resolved until that mode is popped.
*/
RESULT viperform(win, vinf)
WINDOW win; /* window where the command should be performed */
VIINFO *vinf; /* the command itself */
{
STATE *state = win->state;
RESULT result = RESULT_COMPLETE;
MARKBUF from, to;
unsigned short flags;
long tmp;
/* Remember the cursor position */
bufwilldo(win->cursor, ElvFalse);
/* If a vi command runs an ex command, and that ex command generates
* an error message, then we don't want it to look like a script.
*/
msgscriptline(NULL, EX_BUF);
/* The next map will be in "command" context, unless we alter the state
* stack. Might as well set the mapflags now.
*/
win->state->mapflags = MAP_COMMAND;
/* if the command is '.' then recall the previous repeatable command */
vinf->tweak = vikeys[vinf->command].tweak;
if (vinf->command == '.')
{
/* change the count, if a new count was supplied */
if (vinf->count)
{
dotcmd.count = vinf->count;
}
/* recall the command */
*vinf = dotcmd;
/* If it was a visible operator the first time, then it must
* be visible now too. Exception: if the earlier command was
* applied to whole lines, then reapply it now to the same
* quantity of whole lines.
*/
if (dotviz && !win->seltop)
{
if (dotlines)
{
vinf->count = dotlines;
}
else
{
return RESULT_ERROR;
}
}
}
/* see if the command is legal in this context */
flags = vikeys[vinf->command].when;
if ((win->seltop && !(flags & WHEN_SEL)
#ifdef FEATURE_TEXTOBJ
&& vinf->command != 'i' && vinf->command != 'a'
#endif
)
|| ((state->flags & (ELVIS_ONCE | ELVIS_POP)) && !(flags & WHEN_ONCE))
|| ((state->flags & ELVIS_BOTTOM) && !(flags & WHEN_OPEN))
|| (state->acton && !(flags & WHEN_HIST))
|| (vinf->oper && !(flags & WHEN_MOVE)
#ifdef FEATURE_TEXTOBJ
&& vinf->command != 'i' && vinf->command != 'a'
#endif
)
|| (o_locked(markbuffer(state->cursor))
&& ((vikeys[vinf->command].tweak & TWEAK_UNDO) || (vinf->oper && vinf->oper != 'y')))
|| vikeys[vinf->command].func == NULL)
{
viinitcmd(vinf);
return RESULT_ERROR;
}
/* if the buffer is empty, and this command doesn't work on an empty
* buffer, then stuff a newline into the buffer.
*/
if (o_bufchars(markbuffer(state->cursor)) == 0 && !(flags & WHEN_EMPTY))
{
bufreplace(marktmp(from, markbuffer(state->cursor), 0L), &from, toCHAR("\n"), 1L);
o_modified(markbuffer(state->cursor)) = ElvFalse;
}
/* If we're using an operator, then combine the operator's flags
* with the movement command's flags.
*/
if (vinf->oper)
{
vinf->tweak |= vikeys[vinf->oper].tweak;
}
/* If the command doesn't accept two separate counts, and we got two,
* then multiply them and store the result as the count.
*/
if ((vinf->tweak & TWEAK_TWONUM) == 0 && vinf->count2 != 0)
{
if (vinf->count)
{
vinf->count *= vinf->count2;
}
else
{
vinf->count = vinf->count2;
}
}
/* if TWEAK_UNDO, then set the buffer's "willdo" flag. This will cause
* an "undo" version of the buffer to be created if the command does
* indeed modify the buffer. Exception: If this is the second phase
* of a complex command, then we don't want to set the flag because
* we already set it in the first phase.
*/
if ((vinf->tweak & TWEAK_UNDO) != 0 && (win->state->flags & ELVIS_MORE) == 0)
{
bufwilldo(state->cursor, ElvTrue);
}
#ifdef FEATURE_TEXTOBJ
/* if text object used in during visual selection, adjust endpoints */
if (win->seltop && (vinf->command == 'a' || vinf->command == 'i'))
{
/* Find the object's endpoints */
if (vitextobj(win, vinf, &from, &to) != RESULT_COMPLETE)
{
viinitcmd(vinf);
return RESULT_ERROR;
}
/* Set the highlighted region's endpoints to the object's
* endpoints. Leave the cursor at the bottom of the text
* object. Note that we must subtract 1 from the endpoint
* since 'v' includes the cursor but text objects assume the
* end is excluded.
*/
assert(from.buffer == markbuffer(win->seltop));
marksetoffset(win->seltop, from.offset);
marksetoffset(win->selbottom, to.offset - 1L);
marksetoffset(win->cursor, to.offset - 1L);
win->selattop = ElvFalse;
win->wantcol = (*win->md->mark2col)(win, win->cursor, ElvTrue);
/* The end at the cursor will be adjusted for line/rectangle
* modes automatically, elsewhere. But the other end isn't
* expected to move, so it isn't adjusted automatically; we
* must adjust it here explicitly.
*/
switch (win->seltype)
{
case 'l': /* line mode */
marksetoffset(win->seltop, markoffset((*win->md->move)(win, win->seltop, 0L, 0, ElvTrue)));
break;
case 'r': /* rectangle mode */
win->selleft = (*win->md->mark2col)(win, win->seltop, ElvTrue);
if (win->selleft < win->wantcol)
{
win->selright = win->wantcol;
win->selorigcol = win->selleft;
}
else
{
win->selright = win->selorigcol = win->selleft;
win->selleft = win->wantcol;
}
marksetoffset(win->seltop, markoffset((*win->md->move)(win, win->seltop, 0L, win->selleft, ElvTrue)));
break;
}
/* prepare for next command */
viinitcmd(vinf);
return RESULT_COMPLETE;
}
/* if text object used as target of operator, then compute endpoints */
if (vinf->oper && (vinf->command == 'a' || vinf->command == 'i'))
{
if (vitextobj(win, vinf, &from, &to) != RESULT_COMPLETE)
{
viinitcmd(vinf);
return RESULT_ERROR;
}
/* if TWEAK_DOT, on the main buffer, then remember command. */
if ((vinf->tweak & TWEAK_DOT) != 0 && state->acton == NULL)
{
dotcmd = *vinf;
dotcmd.tweak |= TWEAK_DOTTING;
dotviz = ElvFalse;
dotlines = 0L;
}
}
else
#endif
/* unless we're applying an operator to a visible selection... */
if (!vinf->oper || !win->seltop)
{
/* save the cursor position in a local buffer, in case we need
* to do a TWEAK_MARK after calling the function, or if this
* command will be used as the target of an operator.
*/
from = *state->cursor;
/* if TWEAK_DOT, on the main buffer, then remember command. */
if ((vinf->tweak & TWEAK_DOT) != 0 && state->acton == NULL)
{
dotcmd = *vinf;
dotcmd.tweak |= TWEAK_DOTTING;
dotviz = ElvFalse;
dotlines = 0L;
}
/* call the function */
switch ((*vikeys[vinf->command].func)(win, vinf))
{
case RESULT_ERROR:
viinitcmd(vinf);
return RESULT_ERROR;
case RESULT_MORE:
assert(vikeys[vinf->command].when & WHEN_MORE);
return RESULT_MORE;
case RESULT_COMPLETE:
/* do nothing -- just fall through */;
}
/* apply the tweaks */
flags = vinf->tweak;
if (flags & TWEAK_FRONT)
{
(void)m_front(win, vinf);
}
if (flags & TWEAK_FIXCOL)
{
marksetoffset(state->cursor, markoffset((*win->md->move)(win, state->cursor, 0, win->wantcol, viiscmd(win))));
}
else if (!vinf->oper && !(flags & TWEAK_IGNCOL))
{
win->wantcol = (*win->md->mark2col)(win, state->cursor, viiscmd(win));
}
/* use the final location as the target of an operator, maybe */
to = *state->cursor;
}
else /* visible operator */
{
/* Remember this command, and the fact that it is being
* applied to a visible selection.
*/
if (state->acton == NULL)
{
dotcmd = *vinf;
dotcmd.tweak |= TWEAK_DOTTING;
dotviz = ElvTrue;
if (win->seltype == 'l' || win->seltype == 'L')
{
dotlines = markline(win->selbottom) - markline(win->seltop) + 1;
}
else
{
dotlines = 0L;
}
}
from = *win->seltop;
to = *win->selbottom;
}
/* if TWEAK_MARK, on main buffer, then remember the cursor position */
if ((vinf->tweak & TWEAK_MARK) != 0 && state->acton == NULL)
{
if (win->prevcursor)
{
markfree(win->prevcursor);
}
win->prevcursor = markdup(&from);
}
/* if we have an operator, apply it */
if (vinf->oper)
{
/* make sure both endpoints are in the same buffer */
if (to.buffer != from.buffer)
{
msg(MSG_ERROR, "would span buffers");
viinitcmd(vinf);
return RESULT_ERROR;
}
/* swap endpoints, if necessary, to make from <= to */
if (to.offset < from.offset)
{
tmp = to.offset;
to.offset = from.offset;
from.offset = tmp;
}
/* move the cursor to the top of the affected region */
marksetoffset(state->cursor, from.offset);
/* The = operator needs special treatment. If a multi-line
* region is selected, then = needs to work in line mode;
* otherwise it should work in character mode.
*/
if (vinf->oper == '='
&& markoffset(dispmove(win, 0L, INFINITY)) < to.offset)
{
vinf->tweak |= TWEAK_LINE|TWEAK_FRONT;
}
/* do we need to adjust for line-mode or inclusion? */
if (vinf->tweak & TWEAK_LINE)
{
from.offset = markoffset((*win->md->move)(win, &from, 0, 0, ElvTrue));
if (vinf->tweak & TWEAK_INCL)
{
to.offset = markoffset((*win->md->move)(win, &to, 0, INFINITY, ElvFalse)) + 1;
}
else
{
to.offset = markoffset((*win->md->move)(win, &to, 0, 0, ElvTrue));
}
}
else
{
if ((vinf->tweak & TWEAK_INCL) != 0 && scanchar(&to) != '\n')
{
to.offset++;
}
else if (--to.offset < from.offset
|| from.offset == markoffset((*win->md->move)(win, &from, 0, 0, ElvTrue))
|| scanchar(&to) != '\n')
{
to.offset++;
}
}
/* guard against the possibility of overflowing the buffer */
if (markoffset(&to) > o_bufchars(markbuffer(&to)))
marksetoffset(&to, o_bufchars(markbuffer(&to)));
/* do the operator */
result = oper(win, vinf, &from, &to);
#ifdef FEATURE_V
/* if we were doing visible marking before, end it now */
if (win->seltop)
{
vinf->command = ELVCTRL('[');
(void)v_visible(win, vinf);
}
#endif
/* set the desired cursor column to its current column */
if (!state->acton)
{
win->wantcol = (*win->md->mark2col)(win, state->cursor, viiscmd(win));
}
}
/* done! */
if (result != RESULT_MORE)
viinitcmd(vinf);
return result;
}
/* This parses a keystroke as part of a vi command. */
static RESULT parse(key, info)
_CHAR_ key; /* keystroke from user */
void *info; /* current vi command parsing state */
{
VIINFO *vinf = (VIINFO *)info;
CHAR digit;
/* if ^O, and we aren't already in a ^O, then this ^O does nothing */
if (key == ELVCTRL('O') && !vinf->control_o)
{
vinf->control_o = ElvTrue;
windefault->state->mapflags |= MAP_DISABLE;
return RESULT_MORE;
}
else
{
vinf->control_o = ElvFalse;
}
/* g is special for commands -- it causes the following keystroke to
* be interpreted as a command beyond the normal ascii range.
*/
if (vinf->phase == VI_START && key == 'g')
{
vinf->phase = VI_AFTERG;
windefault->state->mapflags |= MAP_DISABLE;
return RESULT_MORE;
}
else if (vinf->phase == VI_AFTERG
|| (vinf->phase == VI_START && vinf->oper == ELVG(key)))
{
key = ELVG(key);
vinf->phase = VI_START;
}
if (vinf->phase == VI_CUTBUF)
{
if (key == '\033')
{
viinitcmd(vinf);
windefault->state->mapflags = MAP_COMMAND;
return RESULT_ERROR;
}
vinf->cutbuf = key;
if (windefault->seltop && vinf->oper)
vinf->phase = VI_COMPLETE;
else if (vinf->count == 0)
vinf->phase = VI_START;
else
vinf->phase = VI_COUNT2;
}
else if (vinf->phase == VI_COUNT2 && elvdigit(key))
{
vinf->count2 = vinf->count2 * 10 + key - '0';
}
else if (vinf->phase == VI_START && vinf->count == 0 && key == '0')
{
vinf->command = key;
vinf->phase = VI_COMPLETE;
}
else if (vinf->phase == VI_START && elvdigit(key))
{
vinf->count = vinf->count * 10 + key - '0';
}
else if (vinf->phase == VI_COUNT2 || vinf->phase == VI_KEY2)
{
switch (key)
{
case '\033':
viinitcmd(vinf);
windefault->state->mapflags = MAP_COMMAND;
return RESULT_ERROR;
case ELVCTRL('V'):
vinf->phase = VI_QUOTE;
windefault->state->mapflags |= MAP_DISABLE;
break;
case ELVCTRL('X'):
vinf->phase = VI_HEX1;
windefault->state->mapflags |= MAP_DISABLE;
break;
default:
vinf->key2 = key;
vinf->phase = VI_COMPLETE;
windefault->state->mapflags |= MAP_DISABLE;
}
}
else if (vinf->phase == VI_QUOTE)
{
vinf->key2 = key;
vinf->phase = VI_COMPLETE;
windefault->state->mapflags |= MAP_DISABLE;
}
else if (vinf->phase == VI_HEX1 || vinf->phase == VI_HEX2)
{
/* convert digit from ASCII to binary */
if (key >= '0' && key <= '9')
{
digit = key - '0';
}
else if (key >= 'a' && key <= 'f')
{
digit = key - 'a' + 10;
}
else if (key >= 'A' && key <= 'F')
{
digit = key - 'A' + 10;
}
else
{
viinitcmd(vinf);
windefault->state->mapflags = MAP_COMMAND;
return RESULT_ERROR;
}
/* merge it into the key */
if (vinf->phase == VI_HEX1)
{
vinf->key2 = digit << 4;
vinf->phase = VI_HEX2;
windefault->state->mapflags |= MAP_DISABLE;
}
else
{
vinf->key2 |= digit;
vinf->phase = VI_COMPLETE;
}
}
else if (strchr(OPERATORS, (char)key))
{
if (windefault && windefault->seltop)
{
vinf->oper = key;
vinf->command = (windefault->seltype == 'c' ? 'e' : '_');
#ifdef FEATURE_G
/* The g= operator needs a second keystroke, which is
* stored as though it was a cutbuffer name. It can't
* be stored as key2, because that would prevent things
* like g=xfy -- "change everything from here to the
* next y into an x".
*/
if (vinf->oper == ELVG('='))
{
vinf->phase = VI_CUTBUF;
windefault->state->mapflags |= MAP_DISABLE;
}
else
#endif
vinf->phase = VI_COMPLETE;
}
else if (vinf->oper == key)
{
vinf->command = '_';
vinf->phase = VI_COMPLETE;
}
else if (vinf->oper == '\0')
{
vinf->oper = key;
assert(vinf->phase == VI_START);
#ifdef FEATURE_G
/* The g= operator needs a second keystroke, which is
* stored as though it was a cutbuffer name. See the
* above comment for a description of why.
*/
if (vinf->oper == ELVG('='))
{
vinf->phase = VI_CUTBUF;
windefault->state->mapflags |= MAP_DISABLE;
}
#endif
}
else
{
viinitcmd(vinf);
windefault->state->mapflags = MAP_COMMAND;
return RESULT_ERROR;
}
}
else if (key == '"')
{
vinf->phase = VI_CUTBUF;
windefault->state->mapflags |= MAP_DISABLE;
}
else if (key == '#' || key == 'z')
{
vinf->command = key;
vinf->phase = VI_COUNT2;
windefault->state->mapflags |= MAP_DISABLE;
}
else if (strchr("[]@#`'rtfmTF\"Z\027", (char)key)) /* \027 is ^W */
{
vinf->command = key;
vinf->phase = VI_KEY2;
windefault->state->mapflags |= MAP_DISABLE;
}
#ifdef FEATURE_TEXTOBJ
else if ((windefault->seltop || vinf->oper) && (key == 'i' || key == 'a'))
{
vinf->command = key;
vinf->phase = VI_KEY2;
windefault->state->mapflags |= MAP_DISABLE;
}
#endif
else
{
vinf->command = key & 0xff;
vinf->phase = VI_COMPLETE;
}
/* set the map context to either MAP_COMMAND or MAP_MOTION */
windefault->state->mapflags &= ~(MAP_COMMAND|MAP_MOTION);
if (vinf->oper == '\0')
windefault->state->mapflags |= MAP_COMMAND;
else
windefault->state->mapflags |= MAP_MOTION;
return (vinf->phase == VI_COMPLETE ? RESULT_COMPLETE : RESULT_MORE);
}
/* This function decides what shape the cursor should be */
static ELVCURSOR shape(win)
WINDOW win; /* the window whose shape should be fetched */
{
return CURSOR_COMMAND;
}
/* Push a vi command state onto the stack */
void vipush(win, flags, cursor)
WINDOW win; /* the window to receive the new state */
ELVISSTATE flags; /* flags of the new state */
MARK cursor; /* the cursor to use in the new state */
{
/* push a state */
statepush(win, flags);
/* initialize it to look like a vi state */
win->state->parse = parse;
win->state->perform = _viperform;
win->state->shape = shape;
win->state->info = safealloc(1, sizeof (VIINFO));
win->state->modename = viiscmd(win) ? "Command" : "One Cmd";
win->state->mapflags |= MAP_COMMAND;
viinitcmd((VIINFO *)win->state->info);
/* if a specific cursor was given, use it */
if (cursor)
{
win->state->cursor = cursor;
win->state->top = markdup(cursor);
win->state->bottom = markdup(cursor);
}
/* If this is the main buffer for this window, then make the cursor's
* current column the default.
*/
if (!win->state->acton && o_bufchars(markbuffer(win->state->cursor)) > 0)
{
win->wantcol = (*win->md->mark2col)(win, win->state->cursor, ElvTrue);
}
}
/* This function translates a potentially-abbreviated vi key name into
* standardized name which can be used as a help topic. If the name can't
* be standardized, then it returns NULL instead.
*/
CHAR *viname(name)
CHAR *name;
{
CHAR key, key2;
static CHAR stdname[12];
/* Get the first character. gX is seen as ELVG(X), and ^X is seen as
* ELVCTRL(X). Other characters are taken literally.
*/
if (name[0] == 'g' && name[1])
{
key = *++name;
if (key == '^' && name[1])
{
name++;
key = ELVCTRL(*name);
}
key = ELVG(key);
name++;
}
else if (name[0] == '^' && name[1])
{
key = *++name;
key = ELVCTRL(key);
name++;
}
else
{
key = *name++;
}
/* Get the second character, if there is one. */
if (name[0] == '^' && name[1])
{
key2 = *++name;
key2 = ELVCTRL(key2);
name++;
}
else if (name[0])
key2 = *name++;
else
key2 = '\0';
/* if any extra chars, then not a vi command */
if (*name)
return NULL;
/* some commands require two characters */
if (!key2 && key == 'z')
return NULL;
/* some commands ignore a second character */
if ((key == '[' || key == ']') && key2 == key)
{
key2 = '\0';
}
/* some commands allow a second character, but don't require it */
else if (key2 && (key == ELVCTRL('W') || key == 'i' || key == 'a'))
{
/* do nothing */
}
else if (key2)
return NULL;
/* now we have what appears to be a well-formed key name, with the main
* keystroke in "key", and an optional second char in "key2". Is the
* main key defined?
*/
if (!vikeys[key].func && (!key || !strchr(OPERATORS, key)) && key != '.')
return NULL;
/* format the name */
if ((key == 'i' || key == 'a') && key2)
{
if (!strchr(TEXTOBJECTS, key2) && !elvpunct(key2))
return NULL;
CHARcpy(stdname, toCHAR("textobject"));
}
else
{
memset(stdname, 0, sizeof stdname);
if (key >= 0x80)
{
CHARcpy(stdname, toCHAR("g"));
key = ELVUNG(key);
}
if (vikeys[key].helptopic)
CHARcat(stdname, toCHAR(vikeys[key].helptopic));
else if (key < ' ')
{
stdname[CHARlen(stdname)] = '^';
stdname[CHARlen(stdname)] = ELVCTRL(key);
}
else
{
stdname[CHARlen(stdname)] = key;
}
/* format the 2nd character, if any */
if (key2)
{
if (key2 < ' ')
{
stdname[CHARlen(stdname)] = '^';
stdname[CHARlen(stdname)] = ELVCTRL(key2);
}
else
{
stdname[CHARlen(stdname)] = key2;
}
}
}
return stdname;
}
#ifdef FEATURE_NORMAL
/* Interpret vi keystokes in a window */
RESULT vinormal(win, nkeys, keys)
WINDOW win; /* window where keystrokes are to be run */
int nkeys; /* number of keystrokes in keys[] array */
CHAR *keys; /* the keystrokes to run */
{
STATE *ex; /* ex input state, or NULL if none */
STATE *top; /* the state that ex acted on */
STATE *vi; /* the newly-pushed vi command interpreter used here */
int i;
RESULT result;
/* This is ordinarily executed from an ex command line. We want to
* suspend the ex input state, so the vi commands will run on the
* window's main buffer instead of the (Elvis ex history) buffer.
*/
if (win->state->acton)
{
ex = win->state;
win->state = win->state->acton;
}
else
ex = NULL;
/* remember the top of the stack, so we can restore the stack later */
top = win->state;
/* push a vi command interpreter onto the input state stack */
vipush(win, 0, NULL);
vi = win->state;
/* interpret each keystroke */
for (result = RESULT_COMPLETE, i = 0; i < nkeys; i++)
{
/* don't allow it to switch windows */
if (windefault != win)
{
if (o_verbose >= 1)
msg(MSG_INFO, ":normal switched windows");
result = RESULT_ERROR;
break;
}
/* run the keystroke -- catch errors */
result = statekey(keys[i]);
if (result == RESULT_ERROR)
{
if (o_verbose >= 1)
msg(MSG_INFO, ":normal vi command failed");
result = RESULT_ERROR;
break;
}
}
/* arrange for "vi" to be popped immediately, or after the completion
* of the next command. If other states have been pushed during the
* :normal command, then leave those unchanged.
*/
vi->flags |= (result == RESULT_MORE ? ELVIS_ONCE : ELVIS_POP);
/* patch the ex input state back onto the stack */
if (ex)
{
/*!!! Are there any intermediate states I should worry about?
* I don't think so. This code assumes there are none.
*/
ex->pop = win->state;
win->state = ex;
}
/* return the result */
return result;
}
#endif /* defined(FEATURE_NORMAL) */
Jump to Line
Something went wrong with that request. Please try again.