Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 9f1c6f0f11
Fetching contributors…

Cannot retrieve contributors at this time

1287 lines (1169 sloc) 34.985 kb
/* input.c */
/* Copyright 1995 by Steve Kirkendall */
#include "elvis.h"
#ifdef FEATURE_RCSID
char id_input[] = "$Id: input.c,v 2.83 2003/10/18 04:47:18 steve Exp $";
#endif
/* These data types are used to store the parsing state for input mode
* commands. This is very simple, since most commands are only one keystroke
* long. (The only exceptions are that INP_QUOTE is two keystrokes long, and
* INP_HEX1/INP_HEX2 is three keystrokes long.)
*/
typedef enum {
INP_NORMAL, /* a normal character to insert/replace */
INP_NEWLINE, /* insert a newline */
INP_QUOTE, /* we're in the middle of a ^V sequence */
INP_HEX1, /* we're expecting the first of two hex digits */
INP_HEX2, /* we're expecting the second of two hex digits */
INP_DIG1, /* we're expecting the first of two digraph chars */
INP_DIG2, /* we're expecting the second of two digraph chars */
INP_TAB, /* ^I - insert a bunch of spaces to look like a tab */
INP_ONEVI, /* ^O - execute one vi command */
INP_MULTIVI, /* ESC - execute many vi commands */
INP_BACKSPACE, /* ^H - backspace one character */
INP_BACKWORD, /* ^W - backspace one word */
INP_BACKLINE, /* ^U - backspace one line */
INP_SHIFTL, /* ^D - reduce indentation */
INP_SHIFTR, /* ^T - increase indentation */
INP_EXPOSE, /* ^R/^L - redraw the screen */
INP_PREVIOUS, /* ^@ - insert a copy of previous text, then exit */
INP_AGAIN, /* ^A - insert a copy of previous text, continue */
INP_PUT, /* ^P - insert a copy of anonymous cut buffer */
INP_KEEP /* ^Z - move rightward keeping chars -- undo a BS */
} INPCMD;
typedef struct
{
ELVBOOL setbottom; /* Set bottom = cursor before drawing cursor? */
ELVBOOL replacing; /* ElvTrue if we're in "replace" mode */
ELVBOOL willdo; /* ElvTrue if we're starting another change */
ELVBOOL hexlock; /* ElvTrue if we're in hex lock mode */
INPCMD cmd; /* the command to perform */
CHAR arg; /* argument -- usually a key to insert */
CHAR backsp; /* char backspaced over, or '\0' */
CHAR prev; /* previously input char, or '\0' */
} INPUTINFO;
#if USE_PROTOTYPES
static void cleanup(WINDOW win, ELVBOOL del, ELVBOOL backsp, ELVBOOL yank);
static void addchar(MARK cursor, MARK top, MARK bottom, INPUTINFO *info);
static ELVBOOL tryabbr(WINDOW win, _CHAR_ nextc);
static RESULT perform(WINDOW win);
static RESULT parse(_CHAR_ key, void *info);
static ELVCURSOR shape(WINDOW win);
#endif
/* This function deletes everything between "cursor" and "bottom" of the
* current state. This is used, for example, when the user hits <Esc>
* after using "cw" to change a long word into a short one. It should be
* called for the INP_ONEVI command with backsp=ElvFalse, and before INP_MULTIVI
* backup=ElvTrue.
*/
static void cleanup(win, del, backsp, yank)
WINDOW win; /* window where input took place */
ELVBOOL del; /* if ElvTrue, delete text after the cursor */
ELVBOOL backsp; /* if ElvTrue, move to the left */
ELVBOOL yank; /* if ElvTrue, yank input text into ". buffer */
{
/* delete the excess in the edited region */
if (del && markoffset(win->state->cursor) < markoffset(win->state->bottom)
&& markoffset(win->state->top) < markoffset(win->state->bottom))
{
bufreplace(win->state->cursor, win->state->bottom, NULL, 0);
}
else
{
marksetoffset(win->state->bottom, markoffset(win->state->cursor));
if (markoffset(win->state->top) > markoffset(win->state->cursor))
marksetoffset(win->state->top, markoffset(win->state->cursor));
}
assert(markoffset(win->state->cursor) == markoffset(win->state->bottom));
/* save the newly input text in the "previous input" buffer */
if (yank)
{
cutyank('.', win->state->top, win->state->bottom, 'c', 'y');
}
/* move the cursor back one character, unless it is already at the
* start of a line.
*/
if (backsp && markoffset(win->state->cursor) > 0)
{
markaddoffset(win->state->cursor, -1);
if (scanchar(win->state->cursor) == '\n')
{
markaddoffset(win->state->cursor, 1);
}
marksetoffset(win->state->top, markoffset(win->state->cursor));
marksetoffset(win->state->bottom, markoffset(win->state->cursor));
}
/* Force the screen to be regenerated */
if (win->di->logic == DRAW_NORMAL)
win->di->logic = DRAW_CHANGED;
}
/* This function inserts/replaces a single character in a buffer, and
* advances the cursor and (if necessary) bottom mark.
*/
static void addchar(cursor, top, bottom, info)
MARK cursor; /* where to add a character */
MARK top; /* start of edit bounds */
MARK bottom; /* end of edit bounds */
INPUTINFO *info; /* other info, including char to be inserted */
{
MARKBUF replaced;
/* decide whether to insert or replace */
replaced = *cursor;
if (markoffset(cursor) < markoffset(bottom))
{
replaced.offset++;
}
else if (info->replacing && markoffset(cursor) < o_bufchars(markbuffer(cursor)))
{
if (scanchar(cursor) != '\n')
{
replaced.offset++;
}
}
/* do it */
bufreplace(cursor, &replaced, &info->arg, 1);
/* we need to advance the cursor, and maybe bottom */
markaddoffset(cursor, 1);
if (markoffset(cursor) > markoffset(bottom))
{
marksetoffset(bottom, markoffset(cursor));
}
}
/* This function attempts to expand an abbreviation at the cursor location,
* if there is one. If so, it deletes the short form, and pushes the long
* form and following character back onto the type-ahead buffer. Else it
* returns ElvFalse.
*/
static ELVBOOL tryabbr(win, nextc)
WINDOW win; /* window where abbreviation may need expanding */
_CHAR_ nextc; /* character after the word */
{
MARKBUF from, to;
CHAR *cp, *build;
long slen, llen, tlen;
CHAR cbuf[1];
#if 0 /* this would prevent abbrs from being expanded on command lines */
if((win->state->flags & (ELVIS_POP|ELVIS_ONCE|ELVIS_1LINE)) ==
(ELVIS_POP|ELVIS_ONCE|ELVIS_1LINE)) { /* nishi */
return ElvFalse;
}
#endif
/* Try to do an abbreviation. To do this, we collect
* characters backward to the preceding whitespace. We
* go to the preceding whitespace because abbreviations
* can't contain whitespace; we know we'll never need
* more characters to recognize an abbreviation. We
* collect the characters backwards just because it is
* easier.
*/
for (scanalloc(&cp, win->state->cursor), build = NULL;
cp && scanprev(&cp) && !elvspace(*cp);
)
{
buildCHAR(&build, *cp);
}
scanfree(&cp);
if (build)
{
cp = mapabbr(build, &slen, &llen, (ELVBOOL)(win->state->acton != NULL));
if (cp)
{
/* yes, we have an abbreviation! */
/* count the characters in the long form which appear
* before the first control character.
*/
for (tlen = 0; tlen < llen && cp[tlen] >= ' '; tlen++)
{
}
/* replace the short form with the plain text part of
* the long form.
*/
cleanup(win, ElvTrue, ElvFalse, ElvFalse);
(void)marktmp(from, markbuffer(win->state->cursor), markoffset(win->state->cursor) - slen);
(void)marktmp(to, markbuffer(win->state->cursor), markoffset(win->state->cursor));
bufreplace(&from, &to, cp, tlen);
cp += tlen;
llen -= tlen;
markaddoffset(&from, tlen);
/* handle the final character (space or tab typed by
* the user) and any remaining characters from the
* long form of the abbreviation.
*/
if (nextc)
{
cbuf[0] = nextc;
if (llen > 0)
{
/* stuff the long form, and the user's
* non-alnum character, into the queue
*/
mapunget(cbuf, 1, ElvFalse);
mapunget(cp, (int)llen, ElvFalse);
}
else
{
/* insert the final character into the
* text
*/
bufreplace(&from, &from, cbuf, 1L);
markaddoffset(&from, 1L);
}
}
/* move cursor to where word goes */
marksetoffset(win->state->bottom, markoffset(&from));
marksetoffset(win->state->cursor, markoffset(&from));
safefree(build);
return ElvTrue;
}
safefree(build);
}
return ElvFalse;
}
/* This function performs an input-mode command. Usually, this will be a
* character to insert/replace in the text.
*/
static RESULT perform(win)
WINDOW win; /* window where inputting should be done */
{
STATE *state = win->state;
INPUTINFO *ii = (INPUTINFO *)state->info;
MARK cursor = state->cursor;
MARK tmp;
MARKBUF from, to;
CHAR *cp;
EXINFO xinfb;
VIINFO vinfb;
long col, len;
ELVBOOL oldcb;
#ifdef FEATURE_COMPLETE
#ifdef FEATURE_TAGS
CHAR *end;
#endif
ELVBOOL usetab;
union
{
char partial[256];
CHAR full[256];
} name;
char *littlecp, ch, peek;
#endif
safeinspect();
/* initially assume there is no matching parenthesis */
#ifdef FEATURE_TEXTOBJ
win->matchend =
#endif
win->match = -4;
/* if cursor has been moved outside the top & bottom, then reset
* the top & bottom to match cursor
*/
if (markbuffer(state->top) != markbuffer(state->cursor))
{
marksetbuffer(state->top, markbuffer(state->cursor));
marksetoffset(state->top, markoffset(state->cursor));
marksetbuffer(state->bottom, markbuffer(state->cursor));
marksetoffset(state->bottom, markoffset(state->cursor));
}
else if (markoffset(state->top) > markoffset(state->cursor)
|| markoffset(state->cursor) > markoffset(state->bottom))
{
marksetoffset(state->top, markoffset(state->cursor));
marksetoffset(state->bottom, markoffset(state->cursor));
}
/* if the "smarttab" option is set, and the cursor is in the line's
* indentation area, then INP_TAB is treated like INP_SHIFTR.
*/
if (o_smarttab && ii->cmd == INP_TAB)
{
scanalloc(&cp, state->cursor);
while (scanprev(&cp) && (*cp == '\t' || *cp == ' '))
{
}
if (!cp || *cp == '\n')
ii->cmd = INP_SHIFTR;
scanfree(&cp);
}
/* process the keystroke */
switch (ii->cmd)
{
case INP_NORMAL:
/* maybe try to do a digraph */
if (ii->backsp && o_digraph)
{
ii->arg = digraph(ii->backsp, ii->arg);
}
/* If next character is non-alphanumeric, check for abbrev.
* (Note: Since we never expand abbreviations except in the
* main buffer or the ex history buffer, we can skip it if
* we're editing some other buffer such as regexp history.
* Assume only the ex history buffer has inputtab=ex.)
*/
if (!elvalnum(ii->arg)
&& ii->arg != '_'
&& (state->acton == NULL || o_inputtab(markbuffer(cursor)) == 'e'))
{
if (tryabbr(win, ii->arg))
break;
}
/* fall through... */
case INP_HEX1: /* can't happen */
case INP_HEX2:
case INP_DIG1: /* can't happen */
case INP_DIG2:
case INP_QUOTE:
/* add the character */
addchar(cursor, state->top, state->bottom, ii);
/* if it wasn't whitespace, then maybe do wordwrap */
if (!elvspace(ii->arg)
&& cursor == win->cursor
&& win->md->wordwrap
&& o_textwidth(markbuffer(cursor)) > 0
&& dispmark2col(win) >= o_textwidth(markbuffer(cursor)))
{
/* Figure out where the current word started */
for (scanalloc(&cp, cursor);
cp && scanprev(&cp) && !elvspace(*cp);
)
{
}
if (cp)
{
to = *scanmark(&cp);
markaddoffset(&to, 1L);
}
else
{
to = *dispmove(win, 0L, 0L);
}
/* Locate the front of this line. We won't look past
* back before this character.
*/
tmp = dispmove(win, 0L, 0L);
/* scan backward over any whitespace */
for (len = markoffset(&to) - markoffset(tmp);
len > 0 && scanprev(&cp) && elvspace(*cp);
len--)
{
}
if (cp)
{
from = *scanmark(&cp);
markaddoffset(&from, 1L);
}
assert(cp || len <= 0);
scanfree(&cp);
/* We can only do the word wrap stuff if the current
* word isn't the line's first word.
*/
if (len > 0)
{
/* replace the whitespace with a newline */
bufreplace(&from, &to, toCHAR("\n"), 1L);
marksetoffset(&to, markoffset(&from) + 1);
/* handle autoindent */
dispindent(win, &to, -1L);
}
}
/* remember digraph hints */
ii->backsp = '\0';
ii->prev = ii->arg;
/* maybe delete old text */
if (calcelement(o_cleantext, toCHAR("input")))
cleanup(win, ElvTrue, ElvFalse, ElvFalse);
#ifdef DISPLAY_SYNTAX
# ifdef FEATURE_SMARTARGS
/* Give smartargs a chance */
dmssmartargs(win);
# endif
#endif
/* are we supposed to highlight matching chars? */
if (o_showmatch(win) && !win->state->acton)
{
#ifdef FEATURE_TEXTOBJ
/* before doing the usual match, maybe we should make
* a special effort for XML tag pairs.
*/
if (ii->arg == '>' && o_matchchar
&& (CHARchr(o_matchchar, 'x') || CHARchr(o_matchchar, 'X')))
{
MARKBUF start, end;
/* look for a tag pair ending at the cursor */
from = *cursor;
assert(markoffset(cursor) > 0);
markaddoffset(cursor, -1);
memset((char *)&vinfb, 0, sizeof vinfb);
vinfb.command = 'a';
vinfb.key2 = CHARchr(o_matchchar, 'x') ? 'x' : 'X';
if (vitextobj(win, &vinfb, &start, &end) == RESULT_COMPLETE
&& markoffset(&end) == markoffset(&from))
{
/* the match starts at <tag> */
win->match = markoffset(&start);
win->matchend = win->match;
/* scan for the end of the <tag> */
scanalloc(&cp, &start);
while (cp && *cp != '>')
scannext(&cp);
if (cp)
{
win->matchend = markoffset(scanmark(&cp));
/* if followed by \n, include that */
scannext(&cp);
while (cp && (*cp == ' ' || *cp == '\t'))
scannext(&cp);
if (cp && *cp == '\n')
win->matchend = markoffset(scanmark(&cp));
}
scanfree(&cp);
/* match really ends after that char */
win->matchend++;
}
assert(markbuffer(cursor) == markbuffer(&from));
*cursor = from;
}
else
#endif /* FEATURE_TEXTOBJ */
/* If the character was a parenthesis then try to
* locate its match. [{(
*/
if (CHARchr(toCHAR(")}]"), ii->arg))
{
from = *cursor;
assert(markoffset(cursor) > 0);
markaddoffset(cursor, -1);
memset((char *)&vinfb, 0, sizeof vinfb);
vinfb.command = '%';
if (m_absolute(win, &vinfb) == RESULT_COMPLETE)
{
win->match = markoffset(cursor);
#ifdef FEATURE_TEXTOBJ
win->matchend = win->match + 1;
#endif
}
assert(markbuffer(cursor) == markbuffer(&from));
*cursor = from;
}
}
break;
case INP_NEWLINE:
cleanup(win, ElvTrue, ElvFalse, ElvFalse);
if (!tryabbr(win, '\n'))
{
ii->arg = '\n';
ii->cmd = INP_NORMAL;
perform(win);
dispindent(win, cursor, -1);
}
ii->backsp = ii->prev = '\0';
break;
case INP_TAB:
oldcb = o_completebinary;
if (!tryabbr(win, '\t'))
{
switch (o_inputtab(markbuffer(cursor)))
{
#ifndef FEATURE_COMPLETE
case 'e':
case 'f':
case 'i':
#endif /* not FEATURE_COMPLETE */
case 't':
/* insert a tab */
addchar(cursor, state->top, state->bottom, ii);
break;
case 's':
/* insert enough spaces to look like a tab */
col = dispmark2col(win);
ii->arg = ' ';
do
{
addchar(cursor, state->top, state->bottom, ii);
col++;
} while (!opt_istab(o_tabstop(markbuffer(cursor)), col));
break;
#ifdef FEATURE_COMPLETE
case 'i':
#ifdef FEATURE_TAGS
/* try to find a matching tag name */
cp = tagcomplete(win, cursor);
/* If no matching tag, or already at end of
* complete tag, then insert a literal tab
* character, not a space.
*/
if (*cp == ' ')
*cp = '\t';
/* For other values, delete the trailing
* whitespace which would normally be appended.
*/
end = CHARchr(cp, ' ');
if (end)
*end = '\0';
#else
cp = toCHAR("\t");
#endif
/* Now... if we have anything to add, add it */
if (*cp)
{
len = CHARlen(cp);
col = markoffset(cursor) + len;
bufreplace(cursor, win->state->bottom, cp, len);
marksetoffset(cursor, col);
marksetoffset(win->state->bottom, col);
}
break;
case 'e':
/* Assume this is an ex command line, and try
* to do something useful.
*/
tmp = markdup(cursor);
(void)marksetline(tmp, markline(cursor));
cp = excomplete(win, tmp, cursor);
markfree(tmp);
if (cp)
{
/* excomplete() found something useful,
* or returned "\t" to make the <Tab>
* be a literal tab character. Copy
* it into the buffer.
*/
len = CHARlen(cp);
col = markoffset(cursor) + len;
bufreplace(cursor, win->state->bottom, cp, len);
marksetoffset(cursor, col);
marksetoffset(win->state->bottom, col);
break;
}
/* else fall through to filename completion */
case 'f':
/* filename completion */
/* if at start of input, then fail */
if (markoffset(cursor) == markoffset(state->top))
{
guibeep(win);
break;
}
/* locate the previous character */
tmp = markdup(cursor);
markaddoffset(tmp, -1);
/* if previous char can't be in name, fail */
if (CHARchr(toCHAR("\t\n()<>"), scanchar(tmp)))
{
markfree(tmp);
guibeep(win);
break;
}
/* collect the partial name into char array */
usetab = (ELVBOOL)!calcelement(o_filenamerules, toCHAR("space"));
littlecp = &name.partial[QTY(name.partial)];
*--littlecp = '\0';
ch = scanchar(tmp);
markaddoffset(tmp, -1L);
peek = scanchar(tmp);
while (markoffset(tmp) > markoffset(state->top)
&& ch != '\n' && ch && ch != '\t' &&
(ch != ' ' || usetab || peek == '\\') &&
(!(ch == '<' || ch == '>' || ch == '(' || ch == ')')
|| peek == '\\'))
{
*--littlecp = ch;
ch = peek;
markaddoffset(tmp, -1);
if (markoffset(tmp) >= 0)
peek = scanchar(tmp);
}
markaddoffset(tmp, 2);
/* try to expand the filename */
littlecp = iofilename(littlecp,
(ch == '(') ? ')' : '\t');
if (!littlecp)
{
markfree(tmp);
guibeep(win);
break;
}
/* name found -- replace old word with expanded
* name. Note that we need to convert from
* char[] to CHAR[], and add backslashes before
* certain characters.
*/
for (cp = name.full, col = 0;
*littlecp != '\0';
littlecp++, col++)
{
if (usetab)
{
if (strchr("<>#%", *littlecp) != NULL
|| (littlecp[0] == '\\' && !elvalnum(littlecp[1]) && littlecp[1] != '\0'))
*cp++ = '\\', col++;
*cp++ = *littlecp;
}
else
{
if (strchr(" <>#%", *littlecp) != NULL
|| (littlecp[0] == '\\' && !elvalnum(littlecp[1]) && littlecp[1] != '\0'))
*cp++ = '\\', col++;
if (*littlecp == '\t')
*cp++ = ' ';
else
*cp++ = *littlecp;
}
}
*cp = '\0';
bufreplace(tmp, win->state->bottom, name.full, col);
marksetoffset(cursor, markoffset(tmp) + col);
marksetoffset(win->state->bottom, markoffset(cursor));
markfree(tmp);
#endif /* FEATURE_COMPLETE */
}
}
o_completebinary = oldcb;
ii->backsp = ii->prev = '\0';
break;
case INP_ONEVI:
cleanup(win, ElvTrue, ElvFalse, ElvTrue);
vipush(win, ELVIS_ONCE, NULL);
ii->backsp = ii->prev = '\0';
ii->setbottom = ii->willdo = ElvTrue;
break;
case INP_MULTIVI:
(void)tryabbr(win, '\0');
cleanup(win, ElvTrue, ElvTrue, ElvTrue);
win->state->flags |= ELVIS_POP;
ii->backsp = ii->prev = '\0';
/* if blank line in autoindent mode, then delete whitespace */
if (o_autoindent(markbuffer(cursor)))
{
for (from = *dispmove(win,0L,0L), scanalloc(&cp, &from);
cp && (*cp == ' ' || *cp == '\t');
scannext(&cp))
{
}
if (cp && *cp == '\n')
{
to = *scanmark(&cp);
scanfree(&cp);
if (markoffset(&to) > markoffset(&from) &&
markoffset(&to) - 1 == markoffset(cursor))
{
bufreplace(&from, &to, NULL, 0L);
}
}
else
{
scanfree(&cp);
}
}
break;
case INP_BACKSPACE:
ii->backsp = '\0';
if (markoffset(win->state->top) < markoffset(cursor))
{
/* backspace within the newly typed text */
markaddoffset(cursor, -1);
ii->backsp = ii->prev;
/* maybe clean up */
if (calcelement(o_cleantext, win->state->acton ? toCHAR("ex") : toCHAR("bs")))
cleanup(win, ElvTrue, ElvFalse, ElvFalse);
}
else if (win->state->acton != NULL
&& (win->state->flags & ELVIS_1LINE) != 0)
{
/* backspace out of an ex command line or regexp line */
cleanup(win, ElvTrue, ElvTrue, ElvTrue);
win->state->flags |= ELVIS_POP;
win->state->pop->flags &= ~(ELVIS_MORE | ELVIS_ONCE);
if (win->state->pop->perform == _viperform)
{
viinitcmd((VIINFO *)win->state->pop->info);
}
ii->backsp = '\0';
}
else
{
/* bump into left edge of new text */
guibeep(win);
}
ii->prev = '\0';
break;
case INP_BACKWORD:
if (markoffset(win->state->top) < markoffset(cursor))
{
/* expect to back up at least one character */
markaddoffset(cursor, -1);
scanalloc(&cp, cursor);
/* if on whitespace, then back up to non-whitespace */
while (markoffset(win->state->top) < markoffset(win->state->cursor)
&& elvspace(*cp))
{
markaddoffset(cursor, -1);
scanprev(&cp);
}
/* back up to whitespace */
while (markoffset(win->state->top) < markoffset(win->state->cursor)
&& !elvspace(*cp))
{
markaddoffset(cursor, -1);
scanprev(&cp);
}
/* if we hit whitespace, then leave cursor after it */
if (elvspace(*cp))
{
markaddoffset(cursor, 1);
}
scanfree(&cp);
/* maybe clean up */
if (calcelement(o_cleantext, win->state->acton ? toCHAR("ex") : toCHAR("bs")))
cleanup(win, ElvTrue, ElvFalse, ElvFalse);
}
else
{
guibeep(win);
}
ii->backsp = ii->prev = '\0';
break;
case INP_BACKLINE:
/* find the start of this line, or if the cursor is already
* there, then the start of the preceding line.
*/
tmp = (*win->md->move)(win, cursor, 0, 0, ElvFalse);
if (markoffset(tmp) == markoffset(cursor))
{
tmp = (*win->md->move)(win, cursor, -1, 0, ElvFalse);
}
/* move to either the start of the line or the top of the
* edited region, whichever is closer.
*/
if (markoffset(tmp) > markoffset(win->state->top))
{
marksetoffset(cursor, markoffset(tmp));
/* maybe clean up */
if (calcelement(o_cleantext, win->state->acton ? toCHAR("ex") : toCHAR("bs")))
cleanup(win, ElvTrue, ElvFalse, ElvFalse);
}
else if (markoffset(state->top) < markoffset(cursor))
{
marksetoffset(cursor, markoffset(state->top));
/* maybe clean up */
if (calcelement(o_cleantext, win->state->acton ? toCHAR("ex") : toCHAR("bs")))
cleanup(win, ElvTrue, ElvFalse, ElvFalse);
}
else
{
guibeep(win);
}
ii->backsp = ii->prev = '\0';
break;
case INP_SHIFTL:
case INP_SHIFTR:
/* delete any text which has been backspaced -- if it happened
* to be whitespace then it could confuse the shift commands.
*/
cleanup(win, ElvTrue, ElvFalse, ElvFalse);
/* remember the cursor position, relative to end of buffer */
len = o_bufchars(markbuffer(cursor)) - markoffset(cursor);
assert(len >= 0);
/* build a :<! or :>! ex command */
memset((char *)&xinfb, 0, sizeof xinfb);
xinfb.defaddr = *cursor;
xinfb.from = xinfb.to = markline(cursor);
xinfb.fromaddr = marktmp(from, markbuffer(cursor),
lowline(bufbufinfo(markbuffer(cursor)), xinfb.from));
xinfb.toaddr = marktmp(to, markbuffer(cursor),
lowline(bufbufinfo(markbuffer(cursor)), xinfb.to + 1));
xinfb.command = (ii->cmd == INP_SHIFTL) ? EX_SHIFTL : EX_SHIFTR;
xinfb.multi = 1;
xinfb.bang = ElvTrue;
/* execute the command */
(void)ex_shift(&xinfb);
ii->backsp = ii->prev = '\0';
/* restore the cursor position, relative to end of buffer */
assert(o_bufchars(markbuffer(cursor)) >= len);
marksetoffset(cursor, o_bufchars(markbuffer(cursor)) - len);
break;
case INP_EXPOSE:
drawexpose(win, 0, 0, (int)(o_lines(win) - 1), (int)(o_columns(win) - 1));
break;
case INP_PREVIOUS:
case INP_AGAIN:
case INP_PUT:
cleanup(win, ElvTrue, ElvFalse, ElvFalse);
/* Copy the text. Be careful not to change the "top" mark. */
from = *state->top;
tmp = cutput((ii->cmd == INP_PUT ? '1' : '.'),
win, cursor, ElvFalse, ElvTrue, ElvTrue);
marksetoffset(state->top, markoffset(&from));
/* if successful, tweak the "cursor" and "bottom" marks. */
if (tmp)
{
marksetoffset(cursor, markoffset(tmp) + 1);
marksetoffset(state->bottom, markoffset(cursor));
if (ii->cmd == INP_PREVIOUS)
{
cleanup(win, ElvTrue, ElvTrue, ElvTrue);
state->flags |= ELVIS_POP;
}
}
ii->backsp = ii->prev = '\0';
break;
case INP_KEEP:
if (markoffset(cursor) < markoffset(state->bottom))
markaddoffset(cursor, 1L);
else
guibeep(win);
break;
}
/* set wantcol to the cursor's current column */
win->wantcol = dispmark2col(win);
/* prepare for next command */
ii->cmd = ii->hexlock ? INP_HEX1 : INP_NORMAL;
return RESULT_COMPLETE;
}
/* This function parses a command. This involves remembering whether we're
* in the middle of a ^V quoted character, and also recognizing some special
* characters.
*/
static RESULT parse(key, info)
_CHAR_ key; /* next keystroke */
void *info; /* current parsing state */
{
INPUTINFO *ii = (INPUTINFO *)info;
CHAR sym; /* visible symbol, e.g. '^' after a ^V */
/* maybe adjust the edit bounds */
if (ii->setbottom)
{
marksetoffset(windefault->state->bottom,
markoffset(windefault->state->cursor));
ii->setbottom = ElvFalse;
}
/* maybe start a new "undo" level for the next input key */
if (ii->willdo)
bufwilldo(windefault->state->cursor, ElvTrue);
/* parse the input command */
if (ii->cmd == INP_HEX1 || ii->cmd == INP_HEX2)
{
/* convert hex digit from ASCII to binary */
if (key >= '0' && key <= '9')
{
key -= '0';
}
else if (key >= 'a' && key <= 'f')
{
key -= 'a' - 10;
}
else if (key >= 'A' && key <= 'F')
{
key -= 'A' - 10;
}
else if (key == ELVCTRL('X'))
{
/* toggle hexlock */
if (ii->hexlock)
{
ii->hexlock = ElvFalse;
ii->cmd = INP_NORMAL;
}
else
{
ii->hexlock = ElvTrue;
ii->cmd = INP_HEX1;
}
return RESULT_MORE;
}
else if (key == '\b')
{
/* backspace, but don't alter hexlock */
ii->arg = key;
ii->cmd = INP_BACKSPACE;
return RESULT_COMPLETE;
}
else if (ii->hexlock)
{
/* any other non-hex character terminates hexlock,
* and then gets processed normally.
*/
ii->hexlock = ElvFalse;
ii->cmd = INP_NORMAL;
return parse(key, info);
}
else
{
/* this command is invalid; prepare for next command */
ii->cmd = INP_NORMAL;
return RESULT_ERROR;
}
/* merge into arg */
if (ii->cmd == INP_HEX1)
{
ii->arg = (key << 4);
ii->cmd = INP_HEX2;
windefault->state->mapflags |= MAP_DISABLE;
return RESULT_MORE;
}
else
{
ii->arg |= key;
return RESULT_COMPLETE;
}
}
else if (ii->cmd == INP_DIG1 || ii->cmd == INP_DIG2)
{
if (ii->cmd == INP_DIG1)
{
ii->arg = key;
ii->cmd = INP_DIG2;
windefault->state->mapflags |= MAP_DISABLE;
return RESULT_MORE;
}
else
{
ii->arg = digraph(ii->arg, key);
return RESULT_COMPLETE;
}
}
else if (ii->cmd == INP_QUOTE)
{
ii->arg = key;
}
else
{
ii->arg = key;
sym = '\0';
switch (key)
{
case ELVCTRL('@'): ii->cmd = INP_PREVIOUS; break;
case ELVCTRL('A'): ii->cmd = INP_AGAIN; break;
case ELVCTRL('D'): ii->cmd = INP_SHIFTL; break;
case '\177': /* usually mapped to "visual x", else... */
case ELVCTRL('H'): ii->cmd = INP_BACKSPACE; break;
case ELVCTRL('I'): ii->cmd = INP_TAB; break;
case ELVCTRL('J'): ii->cmd = INP_NEWLINE; break;
case ELVCTRL('K'): ii->cmd = INP_DIG1, sym = '+'; break;
case ELVCTRL('M'): ii->cmd = INP_NEWLINE; break;
case ELVCTRL('O'): ii->cmd = INP_ONEVI; break;
case ELVCTRL('P'): ii->cmd = INP_PUT; break;
case ELVCTRL('R'): ii->cmd = INP_EXPOSE; break;
case ELVCTRL('T'): ii->cmd = INP_SHIFTR; break;
case ELVCTRL('U'): ii->cmd = INP_BACKLINE; break;
case ELVCTRL('V'): ii->cmd = INP_QUOTE, sym = '^'; break;
case ELVCTRL('W'): ii->cmd = INP_BACKWORD; break;
case ELVCTRL('X'): ii->cmd = INP_HEX1, sym = '$'; break;
case ELVCTRL('Z'): ii->cmd = INP_KEEP; break;
case ELVCTRL('['): ii->cmd = INP_MULTIVI; break;
default: ii->cmd = INP_NORMAL;
}
/* ^V, ^X, and ^K require more keystrokes... */
if (sym)
{
/* display a symbol on the screen */
ii->arg = sym;
addchar(windefault->state->cursor,
windefault->state->top,
windefault->state->bottom, ii);
markaddoffset(windefault->state->cursor, -1);
/* disable maps during extra character[s] */
windefault->state->mapflags |= MAP_DISABLE;
return RESULT_MORE;
}
}
/* the command is complete */
return RESULT_COMPLETE;
}
/* This function decides on a cursor shape */
static ELVCURSOR shape(win)
WINDOW win; /* window whose shape should be returned */
{
STATE *state = win->state;
INPUTINFO *info = (INPUTINFO *)state->info;
MARK cursor = state->cursor;
/* if in the middle of ^V, then always CURSOR_QUOTE */
if (info->cmd == INP_QUOTE)
{
state->mapflags |= MAP_DISABLE;
return CURSOR_QUOTE;
}
/* decide whether to insert or replace */
if (markoffset(state->top) <= markoffset(cursor)
&& markoffset(cursor) < markoffset(state->bottom))
{
if (info->setbottom)
{
marksetoffset(state->bottom, markoffset(cursor));
info->setbottom = ElvFalse;
bufwilldo(cursor, ElvTrue);
return CURSOR_INSERT;
}
return CURSOR_REPLACE;
}
else if (info->replacing && markoffset(cursor) < o_bufchars(markbuffer(cursor)))
{
if (scanchar(cursor) != '\n')
{
info->setbottom = ElvFalse;
return CURSOR_REPLACE;
}
}
info->setbottom = ElvFalse;
return CURSOR_INSERT;
}
/* This function pushes a state onto the state stack, and then initializes it
* to be either an input or replace state, with the cursor at a given location.
* The "mode" argument can be 'R' for replace, or anything else for input.
* The input cursor is "cursor", which should generally the result of a
* markalloc() or markdup() function call. Ditto for the top and bottom of
* the edit region.
*/
void inputpush(win, flags, mode)
WINDOW win; /* window that should be switched to input mode */
ELVISSTATE flags; /* flags describing this state */
_char_ mode; /* 'R' for replace, or anything else for insert */
{
/* push the state */
flags |= ELVIS_REGION;
statepush(win, flags);
/* initialize the state */
win->state->parse = parse;
win->state->perform = perform;
win->state->shape = shape;
win->state->info = safealloc(1, sizeof (INPUTINFO));
win->state->mapflags |= MAP_INPUT;
if (mode == 'R')
{
((INPUTINFO *)win->state->info)->replacing = ElvTrue;
win->state->modename = "Replace";
}
else
{
((INPUTINFO *)win->state->info)->replacing = ElvFalse;
win->state->modename = " Input ";
}
}
/* This function tweaks the most recent "input" or "replace" state.
* The "mode" can be 't' to toggle "input" to "replace" or vice verse,
* 'R' to force the mode to be "replace", or anything else to force the
* mode to be "input". This function is called by vi mode's perform()
* function.
*/
void inputtoggle(win, mode)
WINDOW win; /* window to be toggled */
_char_ mode; /* 'R' for replace, 't' to toggle, else insert */
{
STATE *state;
/* find the most recent "input" or "replace" state */
for (state = win->state; state && state->perform != perform; state = state->pop)
{
}
assert(state != NULL);
/* change the mode */
switch (mode)
{
case 't':
((INPUTINFO *)state->info)->replacing = (ELVBOOL)!((INPUTINFO *)state->info)->replacing;
break;
case 'R':
((INPUTINFO *)state->info)->replacing = ElvTrue;
break;
default:
((INPUTINFO *)state->info)->replacing = ElvFalse;
}
if (((INPUTINFO *)state->info)->replacing)
{
state->modename = "Replace";
}
else
{
state->modename = " Input";
}
}
/* This function sets the edit boundaries of an "input" state. If there
* is no input state on the state stack, then this function will push one.
* This function is used to implement the <c> operator, among other things.
*/
void inputchange(win, from, to, linemd)
WINDOW win; /* window to be affected */
MARK from; /* new start of edit bounds */
MARK to; /* new end of edit bounds */
ELVBOOL linemd; /* replace old text with a new line? */
{
MARKBUF tmp;
CHAR ch;
ELVBOOL ctrl_o = ElvFalse; /* JohnW 19/06/96 */
assert(markbuffer(from) == markbuffer(win->state->cursor));
assert(markbuffer(from) == markbuffer(to));
assert(markoffset(from) <= markoffset(to));
/* Was this command issued via <Control-O> from input mode?
* If not, then we'll need to push one.
*/
if (!win->state->pop)
{
inputpush(win, 0, 'i');
}
else
{
ctrl_o = ElvTrue;
}
/* replace the last char with '$', if there is a last char
* and it is on the same line. If it is on a different line,
* then delete the old text. If from==to, then do nothing.
*/
if (markoffset(from) == markoffset(to))
{
/* do nothing */
}
else if (ctrl_o || /* JohnW 19/06/96 */
calcelement(o_cleantext, markoffset(to) > markoffset(dispmove(win, 0, INFINITY)) ? toCHAR("long") : toCHAR("short")))
{
/* delete the old text */
if (linemd)
{
if (o_autoindent(markbuffer(from)))
{
for (ch = scanchar(from);
markoffset(from) < markoffset(to) && (ch == ' ' || ch == '\t');
markaddoffset(from, 1), ch = scanchar(from))
{
}
}
bufreplace(from, to, toCHAR("\n"), 1);
}
else
{
bufreplace(from, to, NULL, 0);
}
marksetoffset(to, markoffset(from));
}
else
{
/* replace the last character with a '$' */
tmp = *to;
tmp.offset--;
bufreplace(&tmp, to, toCHAR("$"), 1);
}
/* set the edit boundaries and the cursor */
marksetbuffer(win->state->top, markbuffer(from));
marksetbuffer(win->state->bottom, markbuffer(to));
marksetoffset(win->state->top, markoffset(from));
marksetoffset(win->state->bottom, markoffset(to));
marksetoffset(win->state->cursor, markoffset(from));
}
/* This function is called by statekey() when the user hits <Enter>, before
* calling the stratum's enter() function. This function deletes extra
* characters after the cursor, and adjusts the endpoints of the edited
* region to make them be whole lines.
*/
void inputbeforeenter(win)
WINDOW win; /* window where <Enter> was just pressed */
{
/* Make sure "win->state->bottom" includes the cursor position */
if (markoffset(win->state->bottom) < markoffset(win->state->cursor))
{
marksetoffset(win->state->bottom, markoffset(win->state->cursor));
}
/* Delete stuff from after the cursor */
cleanup(win, ElvTrue, ElvFalse, ElvFalse);
/* Attempt to expand an abbreviation at the end of the line */
(void)tryabbr(win, '\0');
/* adjust the endpoints of the edited area to be whole lines */
marksetoffset(win->state->top,
markoffset((*dmnormal.move)(win, win->state->top, 0, 0, ElvFalse)));
marksetoffset(win->state->bottom,
markoffset((*dmnormal.move)(win, win->state->bottom, 0, INFINITY, ElvFalse)));
}
Jump to Line
Something went wrong with that request. Please try again.