Skip to content

HTTPS clone URL

Subversion checkout URL

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

Cannot retrieve contributors at this time

2629 lines (2370 sloc) 61.719 kb
/* exconfig.c */
/* Copyright 1995 by Steve Kirkendall */
#include "elvis.h"
#ifdef FEATURE_RCSID
char id_exconfig[] = "$Id: exconfig.c,v 2.147 2003/10/19 23:13:33 steve Exp $";
#endif
/* This variable stores the current state of ex's control structures. */
EXCTLSTATE exctlstate;
static MAPFLAGS maphelp2 P_((CHAR **refcp, char *word, MAPFLAGS flag));
static MAPFLAGS maphelp P_((CHAR **refcp, CHAR **mode));
static CHAR *equaltilde P_((CHAR *value, int len, CHAR *cmd));
#ifdef FEATURE_ALIAS
/* These are used for storing aliases */
typedef struct alias_s
{
struct alias_s *next; /* some other alias */
char *name; /* name of this alias */
CHAR *command; /* commands for this alias */
ELVBOOL inuse; /* is this alias already being run? */
ELVBOOL internal; /* save in .exrc file? */
} alias_t;
BEGIN_EXTERNC
static void listalias P_((WINDOW win, alias_t *alias, ELVBOOL shortformat));
static void buildarg P_((CHAR **cmd, CHAR *arg, long len, CHAR *defarg, long deflen, ELVBOOL quote));
END_EXTERNC
static alias_t *aliases; /* This is the head of a list of aliases */
# ifdef FEATURE_COMPLETE
/* Return the name of an alias, by number. If there is no such alias then
* return NULL. This is used only by the name completion code in ex.c.
*/
char *exaliasname(i)
int i;
{
alias_t *alias;
for (alias = aliases; --i >= 0 && alias; alias = alias->next)
{
}
return alias ? alias->name : NULL;
}
# endif /* FEATURE_COMPLETE */
/* look up a name in the alias list. The name can be terminated with any
* non-alphanumeric character, not just '\0'. Return its name if alias,
* or NULL otherwise. Optionally ignore if already in use.
*/
char *exisalias(name, inuse)
char *name; /* name of a command, maybe an alias */
ELVBOOL inuse; /* find even if in use? (else hide in-use aliases) */
{
alias_t *alias;
/* look for alias */
for (alias = aliases; alias; alias = alias->next)
if (!strcmp(name, alias->name))
return (alias->inuse && !inuse) ? NULL : alias->name;
return NULL;
}
/* list a single alias */
static void listalias(win, alias, shortformat)
WINDOW win;
alias_t *alias;
ELVBOOL shortformat;
{
CHAR ch[4];
CHAR *start;
int len;
int indent;
drawexlist(win, toCHAR(alias->name), strlen(alias->name));
if (CHARchr(alias->command, '\n') == alias->command + CHARlen(alias->command) - 1)
{
/* single-line command simply follows the alias name */
drawexlist(win, blanks, 10 - (CHARlen(alias->name) % 10));
drawexlist(win, alias->command, CHARlen(alias->command));
}
else if (shortformat)
{
/* multi-line command, but only show first line */
drawexlist(win, blanks, 10 - (CHARlen(alias->name) % 10));
ch[0] = '{';
ch[1] = ' ';
drawexlist(win, ch, 2);
for (start = alias->command, len = 0;
*start++ != '\n' && len < o_columns(win) - 16;
len++)
{
}
drawexlist(win, alias->command, len);
ch[0] = ch[1] = ch[2] = '.';
ch[3] = '\n';
drawexlist(win, ch, 4);
}
else
{
/* multi-line command is output in a fancy way */
ch[0] = ' ';
ch[1] = '{';
ch[2] = '\n';
drawexlist(win, ch, 3);
indent = 4;
for (start = alias->command, len = 0; *start; len++)
{
if (start[len] == '\n')
{
drawexlist(win, blanks, indent);
drawexlist(win, start, len);
ch[0] = '\n';
drawexlist(win, ch, 1);
start += len + 1;
if (len > 1 && indent + 2 < QTY(blanks) && start[-2] == '{')
indent += 2;
if (indent > 4 && *start == '}')
indent -= 2;
len = -1; /* will be incremented to 0 by for()*/
}
}
ch[0] = '}';
ch[1] = '\n';
drawexlist(win, ch, 2);
}
}
/* maintain the alias list */
RESULT ex_alias(xinf)
EXINFO *xinf;
{
alias_t *newalias, *alias, *lag;
int i;
/* if no aliases named, then list all */
if (!xinf->lhs)
{
for (alias = aliases; alias; alias = alias->next)
{
if (xinf->bang == alias->internal)
listalias(xinf->window, alias, ElvTrue);
}
return RESULT_COMPLETE;
}
/* Try to find the named alias */
for (lag = NULL, alias = aliases;
alias && CHARcmp(xinf->lhs, toCHAR(alias->name));
lag = alias, alias = alias->next)
{
}
/* Unaliasing? */
if (xinf->command == EX_UNALIAS)
{
if (alias)
{
/* safety check */
if (alias->inuse)
{
msg(MSG_ERROR, "[s]can't unalias $1 because it is in use", alias->name);
return RESULT_ERROR;
}
/* remove it from the list, and free it */
if (lag)
lag->next = alias->next;
else
aliases = alias->next;
safefree(alias->name);
safefree(alias->command);
safefree(alias);
}
return RESULT_COMPLETE;
}
/* listing one specific alias? */
if (!xinf->rhs)
{
if (!alias)
msg(MSG_WARNING, "[S]no alias named $1", xinf->lhs);
else
listalias(xinf->window, alias, ElvFalse);
return RESULT_COMPLETE;
}
/* safety check */
if (alias && alias->inuse)
{
msg(MSG_ERROR, "[s]can't redefine $1 because it is in use", alias->name);
return RESULT_ERROR;
}
/* verify that the name contains only alphanumeric characters */
for (i = 0; xinf->lhs[i]; i++)
{
if (!elvalnum(xinf->lhs[i]))
{
msg(MSG_ERROR, "alias names must be alphanumeric");
return RESULT_ERROR;
}
}
/* create or alter an alias */
if (alias)
safefree(alias->command);
else
{
/* find the aliases before & after it, in ASCII order */
for (lag = NULL, alias = aliases;
alias && CHARcmp(xinf->lhs, toCHAR(alias->name)) > 0;
lag = alias, alias = alias->next)
{
}
/* allocate the new alias, and insert it into the list */
newalias = (alias_t *)safekept(1, sizeof *alias);
newalias->next = alias;
if (lag)
lag->next = newalias;
else
aliases = newalias;
newalias->name = safekdup(tochar8(xinf->lhs));
alias = newalias;
}
#ifdef DEBUG_ALLOC
alias->command = CHARkdup(xinf->rhs);
#else
alias->command = xinf->rhs;
xinf->rhs = NULL;
#endif
alias->internal = xinf->bang;
return RESULT_COMPLETE;
}
/* Add an argument to cmd, by calling buildCHAR() repeatedly. */
static void buildarg(cmd, arg, len, defarg, deflen, quote)
CHAR **cmd; /* the resulting string */
CHAR *arg; /* the arg to add */
long len; /* length of arg */
CHAR *defarg;/* default, used if len == 0 */
long deflen; /* length of defarg */
ELVBOOL quote; /* should backslashes be inserted? */
{
long i;
/* if normal arg is empty, then use defarg without quoting */
if (len == 0)
{
arg = defarg;
len = deflen;
quote = ElvFalse;
}
/* copy characters, with optional quoting */
for (i = 0; i < len; i++, arg++)
{
if (quote && (*arg == '\\' || (o_magicchar && CHARchr(o_magicchar, *arg))))
buildCHAR(cmd, '\\');
buildCHAR(cmd, *arg);
}
}
/* Execute an alias */
RESULT ex_doalias(xinf)
EXINFO *xinf;
{
alias_t *alias;
CHAR *cmd, *str, *defarg, *name;
CHAR *mustfree;
CHAR *args[11];
long lens[11];
long deflen;
long namelen;
int i, j;
char num[24];
ELVBOOL inword;
ELVBOOL anyargs, anyaddr, anybang;
ELVBOOL multiline;
ELVBOOL quote;
RESULT result = RESULT_ERROR;
#ifdef FEATURE_AUTOCMD
void *popopt;
#endif
/* Find the alias. It *will* exist, and use the same name pointer */
for (alias = aliases; alias->name != xinf->cmdname; alias = alias->next)
{
}
/* parse the args, if any. args[0] is the whole argument string, and
* args[1] through args[9] are the first 9 words from that string.
*/
memset(lens, 0, sizeof lens);
mustfree = NULL;
if (xinf->rhs)
{
args[0] = xinf->rhs;
lens[0] = CHARlen(args[0]);
#ifdef FEATURE_PROTO
if (xinf->command == EX_DOPROTO)
{
/* parsing a URL -- skip the protocol, if given */
str = xinf->rhs;
for (i = 0; elvalpha(str[i]); i++)
{
}
if (i > 0 && str[i] == ':')
str += i + 1;
/* parsing a URL -- first part is machine name */
if (str[0] == '/' && str[1] == '/')
{
args[1] = str += 2;
while (*str && *str != ':' && *str != '/' && *str != '?')
str++;
lens[1] = (int)(str - args[1]);
if (*str == ':')
do
{
str++;
} while (elvdigit(*str));
}
/* next is the resource name */
args[2] = str;
while (*str && *str != '#' && *str != '?')
str++;
lens[2] = (int)(str - args[2]);
/* next is the anchor name */
if (*str == '#')
{
str++;
args[3] = str;
lens[3] = CHARlen(str);
str += lens[3];
}
/* remainder is '&'-delimited, URL-encoded params */
if (*str == '?' && str[1])
{
/* URL-decode the params, remembering lengths */
for (str++, i = 4; i < 9 && *str; str++)
{
switch (*str)
{
case '&':
if (lens[i] > 0)
i++;
break;
case '+':
buildCHAR(&mustfree, ' ');
lens[i]++;
break;
case '%':
if (elvxdigit(str[1]) && elvxdigit(str[2]))
{
str++;
if (elvdigit(*str))
j = *str - '0';
else
j = (*str - 'A' + 10) & 0x0f;
j <<= 4;
str++;
if (elvdigit(*str))
j = *str - '0';
else
j = (*str - 'A' + 10) & 0x0f;
buildCHAR(&mustfree, j);
lens[i]++;
break;
}
/* else fall through... */
default:
buildCHAR(&mustfree, *str);
lens[i]++;
}
}
/* use lengths to find start of each param */
for (j = 4, str = mustfree; j <= i; j++)
{
args[j] = str;
str += lens[j];
}
}
}
else /* EX_DOALIAS */
#endif /* FEATURE_PROTO */
{
args[1] = args[0];
for (i = 1, inword = ElvTrue, str = args[0]; *str && i < 10; str++)
{
if (inword)
{
if (elvspace(*str))
inword = ElvFalse;
else
lens[i]++;
}
else if (!elvspace(*str))
{
args[++i] = str;
lens[i] = 1;
inword = ElvTrue;
}
}
}
}
/* Any missing args are zero-length */
for (i = 1; i < QTY(args); i++)
if (lens[i] == 0)
args[i] = args[0] + lens[0];
/* Build a copy of the command string, with !0-!9 replaced by args[0]
* through args[9].
*/
anyargs = anyaddr = anybang = multiline = ElvFalse;
for (cmd = NULL, str = alias->command; *str; str++)
{
/* if not '!' then it can't be an arg substitution */
if (*str != '!')
{
buildCHAR(&cmd, *str);
if (*str == '\n' && str[1])
multiline = ElvTrue;
continue;
}
/* Allow an optional ':' after the '!'. Also allow an optional
* \ which causes backslashes to be inserted before certain
* characters in the expansion.
*/
quote = ElvFalse;
deflen = 0;
defarg = args[0]; /* anything but NULL, really */
str++;
name = NULL;
namelen = 0;
while (*str == ':' || *str == '\\' || *str == '(' || elvalpha(*str))
{
if (*str == ':')
str++;
else if (*str == '\\')
str++, quote = ElvTrue;
else if (*str == '(')
{
str++;
defarg = str;
for (deflen = 0; *str != ')'; deflen++)
{
if (!*str || *str == '\n')
{
msg(MSG_ERROR, "malformed !() in alias $1", alias->name);
if (cmd)
safefree(cmd);
if (mustfree)
safefree(mustfree);
return RESULT_ERROR;
}
str++;
}
str++;
}
else /* elvalpha(*str) -- only works for !name= and !name& */
{
name = str;
for (namelen = 1; elvalnum(name[namelen]); namelen++)
{
}
if (name[namelen]=='=' || name[namelen]=='&')
str += namelen;
else
{
name = NULL;
break;
}
}
}
/* which substitution is being requested? */
switch (*str)
{
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
/* insert an argument */
i = *str - '0';
if (str[1] == '*')
{
/* !n* includes all items from !n to !$ */
str++;
buildarg(&cmd, args[i], lens[0] - (int)(args[i] - args[0]), defarg, deflen, quote);
}
else
buildarg(&cmd, args[i], lens[i], defarg, deflen, quote);
anyargs = ElvTrue;
break;
case '*':
/* insert the whole argument string */
buildarg(&cmd, args[0], lens[0], defarg, deflen, quote);
anyargs = ElvTrue;
break;
case '^':
/* insert the first argument string */
buildarg(&cmd, args[1], lens[1], defarg, deflen, quote);
anyargs = ElvTrue;
break;
case '$':
/* insert the last argument string */
for (i = 1; i < QTY(lens) - 1 && lens[i + 1] > 0; i++)
{
}
if (str[1] == '*')
{
/* !$* includes all items from !1 to !$-1 */
str++;
if (i != 1)
buildarg(&cmd, args[0], lens[i - 1] + (int)(args[i - 1] - args[0]), defarg, deflen, quote);
}
else
buildarg(&cmd, args[i], lens[i], defarg, deflen, quote);
anyargs = ElvTrue;
break;
case '!':
buildCHAR(&cmd, '!');
break;
case '?':
if (xinf->bang)
buildCHAR(&cmd, '!');
anybang = ElvTrue;
break;
case '<':
if (xinf->anyaddr)
{
sprintf(num, "%ld", xinf->from);
buildstr(&cmd, num);
}
else
buildarg(&cmd, NULL, 0, defarg, deflen, quote);
anyaddr = ElvTrue;
break;
case '>':
if (xinf->anyaddr)
{
sprintf(num, "%ld", xinf->to);
buildstr(&cmd, num);
}
else
buildarg(&cmd, NULL, 0, defarg, deflen, quote);
anyaddr = ElvTrue;
break;
case '%':
if (xinf->anyaddr)
{
sprintf(num, "%ld,%ld", xinf->from, xinf->to);
buildstr(&cmd, num);
}
else
buildarg(&cmd, NULL, 0, defarg, deflen, quote);
anyaddr = ElvTrue;
break;
case '=':
case '&':
/* If just "!=" with no name, then insert "!=" */
if (!name)
{
buildCHAR(&cmd, '!');
buildCHAR(&cmd, *str);
break;
}
/* find a named arg & insert it (else use default) */
for (i = 1;
i <= 9 && (lens[i] <= namelen
|| CHARncmp(args[i], name, (int)namelen)
|| args[i][namelen] != '=');
i++)
{
}
if (i <= 9)
{
if (*str == '=')
{
buildarg(&cmd, args[i] + namelen+1,
lens[i] - (namelen+1),
defarg, deflen, quote);
}
else
{
for (name = args[i] + (namelen+1),
j = lens[i] - (namelen+1);
--j >= 0;
name++)
{
switch (*name)
{
case '\t': buildstr(&cmd, "%09"); break;
case '+': buildstr(&cmd, "%2B"); break;
case '"': buildstr(&cmd, "%22"); break;
case '%': buildstr(&cmd, "%25"); break;
case '<': buildstr(&cmd, "%3C"); break;
case '>': buildstr(&cmd, "%3E"); break;
case ' ': buildCHAR(&cmd, '+'); break;
default: buildCHAR(&cmd, *name);
}
}
}
}
else
buildarg(&cmd, NULL, 0, defarg, deflen, quote);
anyargs = ElvTrue;
break;
default:
/* no substitution -- use a literal ! character */
if (str[-1] == ':')
buildCHAR(&cmd, '!');
buildCHAR(&cmd, str[-1]);
buildCHAR(&cmd, *str);
}
}
/* don't need the mustfree strings anymore */
if (mustfree)
safefree(mustfree);
/* If command contained no !n strings, but the alias was invoked with
* arguments, then append the arguments to the last command line.
*/
if (xinf->command == EX_DOALIAS && !anyargs && !multiline && lens[0] > 0)
{
cmd[CHARlen(cmd) - 1] = ' '; /* convert newline to space */
buildarg(&cmd, args[0], lens[0], args[0], lens[0], ElvFalse);
buildCHAR(&cmd, '\n');
anyargs = ElvTrue;
}
/* Detect usage errors */
if (xinf->command == EX_DOALIAS && xinf->bang && !anybang)
msg(MSG_ERROR, "[s]the $1 alias doesn't use a ! suffix", alias->name);
else if (xinf->command == EX_DOALIAS && xinf->anyaddr && !anyaddr)
msg(MSG_ERROR, "[s]the $1 alias doesn't use addresses", alias->name);
else if (xinf->command == EX_DOALIAS && lens[0] > 0 && !anyargs)
msg(MSG_ERROR, "[s]the $1 alias doesn't use arguments", alias->name);
else
{
/* No errors - Run the command. Mark it as being "in use"
* while it is running, to prevent recursion. If autocmd is
* supported, then do an AliasEnter event immediately after
* starting a :local context, and AliasLeave before restoring
* options to their non-local values. This allws AliasEnter
* to use :local for settings that only affect the alias. Note
* that exstring() also has its own internal :local logic, but
* the duplication doesn't cost much and keeps exstring()'s
* interface simple.
*/
alias->inuse = ElvTrue;
# ifdef FEATURE_AUTOCMD
# ifdef FEATURE_MISC
popopt = optlocal(NULL);
# endif
(void)auperform(xinf->window, ElvFalse, NULL, AU_ALIASENTER, toCHAR(alias->name));
# endif
result = exstring(xinf->window, cmd, alias->name);
# ifdef FEATURE_AUTOCMD
(void)auperform(xinf->window, ElvFalse, NULL, AU_ALIASENTER, toCHAR(alias->name));
# ifdef FEATURE_MISC
(void)optlocal(popopt);
# endif
# endif
alias->inuse = ElvFalse;
}
/* Free the copy of the command string */
safefree(cmd);
return result;
}
# ifdef FEATURE_MKEXRC
/* add user aliases to end of buffer */
void exaliassave(custom)
BUFFER custom; /* the buffer to which the :au commands are added */
{
ELVBOOL anyout; /* any groups output yet? */
MARKBUF end;
alias_t *alias;
CHAR *cmd, *word;
/* for each alias... */
anyout = ElvFalse;
for (alias = aliases; alias; alias = alias->next)
{
/* skip if standard - we don't need to save it since the
* standard initialization scripts will recreate it each time
* elvis is run anyway.
*/
if (alias->internal)
continue;
/* if first alias, output "try {" */
if (!anyout)
{
anyout = ElvTrue;
(void)marktmp(end, custom, o_bufchars(custom));
bufreplace(&end, &end,
toCHAR("try {\n"), 6L);
}
/* construct an alias command string */
cmd = NULL;
buildstr(&cmd, " alias ");
buildstr(&cmd, alias->name);
buildCHAR(&cmd, ' ');
word = alias->command;
if (CHARchr(word, '\n') == word + CHARlen(word) - 1)
{
/* single line - output it including final \n */
for ( ; *word; word++)
{
if (*word == ELVCTRL('V') || *word == '\033')
buildCHAR(&cmd, ELVCTRL('V'));
buildCHAR(&cmd, *word);
}
}
else
{
/* multi-line - add explicit {} around it */
buildstr(&cmd, "{\n ");
for ( ; *word; word++)
{
if (*word == ELVCTRL('V') || *word == '\033')
buildCHAR(&cmd, ELVCTRL('V'));
buildCHAR(&cmd, *word);
if (*word == '\n' && word[1])
buildstr(&cmd, " ");
}
buildstr(&cmd, " }\n");
}
/* add the command to the buffer */
end.offset = o_bufchars(custom);
bufreplace(&end, &end, cmd, CHARlen(cmd));
/* free the string form of the command */
safefree(cmd);
} /* for alias */
/* if any groups were output, then we need to end "try {" */
if (anyout)
{
end.offset = o_bufchars(custom);
bufreplace(&end, &end, toCHAR("}\n"), 2L);
}
}
# endif /* FEATURE_MKEXRC */
#endif /* FEATURE_ALIAS */
RESULT ex_args(xinf)
EXINFO *xinf;
{
int i, col, len;
char **tmp;
/* were we given a new args list? */
if (xinf->nfiles > 0)
{
/* yes, use it */
tmp = arglist;
arglist = xinf->file;
xinf->file = tmp;
for (xinf->nfiles = 0; xinf->file[xinf->nfiles]; xinf->nfiles++)
{
}
argnext = 0;
}
else
{
/* show current args list */
for (i = col = 0; arglist[i]; i++)
{
len = strlen(arglist[i]);
/* whitespace between args */
if (i == 0)
; /* no space is needed */
else if (col + len + 4 > o_columns(xinf->window))
{
drawexlist(xinf->window, toCHAR("\n"), 1);
col = 0;
}
else
{
drawexlist(xinf->window, toCHAR(" "), 1);
col++;
}
/* Output the arg. If current, enclose in '[' */
if (i == argnext - 1)
drawexlist(xinf->window, toCHAR("["), 1);
drawexlist(xinf->window, toCHAR(arglist[i]), len);
if (i == argnext - 1)
drawexlist(xinf->window, toCHAR("]"), 1);
col += len;
}
/* the final newline */
drawexlist(xinf->window, toCHAR("\n"), 1);
}
return RESULT_COMPLETE;
}
/* This function implements the :color command. */
RESULT ex_color(xinf)
EXINFO *xinf;
{
int i;
WINDOW win;
CHAR *lhs;
/* If this GUI doesn't support colors, then do nothing */
if (!gui->color)
{
if (xinf->bang)
return RESULT_COMPLETE;
msg(MSG_ERROR, "the (gui) user interface doesn't support colors");
return RESULT_ERROR;
}
/* If "gui.role", where "gui" is the current GUI, then strip that off */
lhs = xinf->lhs;
i = CHARlen(o_gui);
if (lhs && !CHARncmp(lhs, o_gui, i) && lhs[i] == '.' && lhs[i + 1])
lhs += i + 1;
if (!xinf->rhs)
{
/* no args, or just a role name - list the colors */
colorlist(xinf->window, lhs, xinf->bang);
}
else if (CHARchr(lhs, '.'))
{
/* setting some other GUI's colors */
if (!xinf->bang)
colorforeign(lhs, xinf->rhs);
}
else
{
/* both a role and a new color definition - set the colors */
i = colorfind(lhs);
if (i == 0)
return RESULT_ERROR;
colorset(i, xinf->rhs, (ELVBOOL)!xinf->bang);
/* reset the GUI, to bypass any optimizations */
guireset();
/* Cause all windows to be redrawn from scratch */
for (win = windows; win; win = win->next)
win->di->logic = DRAW_SCRATCH;
}
return RESULT_COMPLETE;
}
RESULT ex_comment(xinf)
EXINFO *xinf;
{
#ifdef FEATURE_CALC
CHAR *result;
#endif
assert(xinf->command == EX_COMMENT || xinf->command == EX_ECHO
|| xinf->command == EX_CALC || xinf->command == EX_GOTO);
if (xinf->command == EX_ECHO && xinf->rhs)
{
drawextext(xinf->window, xinf->rhs, (int)CHARlen(xinf->rhs));
drawextext(xinf->window, toCHAR("\n"), 1);
}
#ifdef FEATURE_CALC
else if (xinf->command == EX_CALC && xinf->rhs)
{
result = calculate(xinf->rhs, NULL, CALC_ALL);
if (!result)
{
return RESULT_ERROR;
}
drawextext(xinf->window, result, (int)CHARlen(result));
drawextext(xinf->window, toCHAR("\n"), 1);
}
#endif
else if (xinf->command == EX_GOTO && xinf->fromaddr)
{
if (xinf->fromoffset > markoffset(xinf->fromaddr)
&& xinf->fromoffset <= markoffset(xinf->toaddr))
xinf->newcurs = markalloc(markbuffer(xinf->fromaddr), xinf->fromoffset);
else
xinf->newcurs = markdup(xinf->fromaddr);
}
return RESULT_COMPLETE;
}
RESULT ex_message(xinf)
EXINFO *xinf;
{
MSGIMP imp;
RESULT result;
/* choose an importance level for this message */
switch (xinf->command)
{
case EX_MESSAGE: imp = MSG_INFO; result = RESULT_COMPLETE; break;
case EX_WARNING: imp = MSG_WARNING; result = RESULT_COMPLETE; break;
case EX_ERROR: imp = MSG_ERROR; result = RESULT_ERROR; break;
default:
/* this should never happen. */
#ifndef NDEBUG
abort();
#endif
return RESULT_ERROR;
}
/* do we have a message? */
if (!xinf->rhs)
{
/* no - fake it for :error, else just return */
if (xinf->command == MSG_ERROR)
xinf->rhs = CHARdup(toCHAR("error"));
else
return result;
}
/* don't allow bracket at the beginning -- would look like args */
if (*xinf->rhs == '[')
*xinf->rhs = '{';
/* output the message, or queue it */
msg(imp, tochar8(xinf->rhs));
/* return the result code */
return result;
}
RESULT ex_digraph(xinf)
EXINFO *xinf;
{
digaction(xinf->window, xinf->bang, xinf->rhs);
return RESULT_COMPLETE;
}
RESULT ex_display(xinf)
EXINFO *xinf;
{
int len;
#ifdef FEATURE_NORMAL
long lback, last;
BUFFER buf;
RESULT result;
MARK cursor, orig;
/* As a special case, ":normal vicmds" interprets "vicmds" as a series
* of vi commands.
*/
if (xinf->command == EX_NORMAL && xinf->rhs)
{
/* Count the chars in rhs. Skip the last character, since it
* is just a newline marking the end of the string.
*/
len = CHARlen(xinf->rhs) - 1;
/* remember the cursor position, we can restore it and return
* the new position the usual way (via xinf->newcurs).
*/
if (xinf->window->state->acton)
cursor = xinf->window->state->acton->cursor;
else
cursor = xinf->window->cursor;
orig = markdup(cursor);
/* If no address was given, then run the command once */
if (!xinf->anyaddr)
{
/* no addresses, run the command once, here */
result = vinormal(xinf->window, len, xinf->rhs);
}
else
{
/* Otherwise we need to repeat the command on each
* line. We want to do this in a top-to-bottom
* sequence, but the command may insert or delete
* lines so simply stepping by line number won't
* work. Instead, we count relative to the end of
* the buffer.
*/
buf = xinf->defaddr.buffer;
lback = o_buflines(buf) - xinf->from;
last = o_buflines(buf) - xinf->to;
result = RESULT_COMPLETE;
for ( ;
lback >= last
&& marksetline(cursor, o_buflines(buf) - lback);
lback--)
{
result = vinormal(xinf->window, len, xinf->rhs);
if (result != RESULT_COMPLETE)
break;
}
}
/* if success, then stuff the new position into xinf->newcurs */
if (result == RESULT_COMPLETE)
xinf->newcurs = markdup(cursor);
/* move the cursor back to its original position, so
* xinf->newcurs can change it gracefully
*/
marksetbuffer(cursor, markbuffer(orig));
marksetoffset(cursor, markoffset(orig));
return result;
}
#endif
/* List or change the display mode */
if (xinf->command == EX_DISPLAY && !xinf->rhs)
{
displist(xinf->window);
}
else
{
/* trim trailing whitespace from the mode name */
if (xinf->rhs)
{
for (len = CHARlen(xinf->rhs); xinf->rhs[--len] == ' ';)
xinf->rhs[len] = '\0';
}
/* set the mode */
if (!dispset(xinf->window, tochar8(xinf->rhs)))
return RESULT_ERROR;
}
xinf->window->di->logic = DRAW_CHANGED;
return RESULT_COMPLETE;
}
RESULT ex_gui(xinf)
EXINFO *xinf;
{
if (!gui->guicmd)
{
msg(MSG_ERROR, "gui-specific commands not supported");
return RESULT_ERROR;
}
return (*gui->guicmd)(xinf->window ? xinf->window->gw : NULL, tochar8(xinf->rhs))
? RESULT_COMPLETE
: RESULT_ERROR;
}
#ifdef FEATURE_MISC
RESULT ex_help(xinf)
EXINFO *xinf;
{
# ifndef DISPLAY_HTML
msg(MSG_ERROR, "help unavailable; html mode is disabled");
return RESULT_ERROR;
# else
CHAR *topic; /* topic to search for; a tag name */
char *section;/* name of help file containing topic */
CHAR *tag; /* name of tag to search for -- section#topic */
BUFFER buf; /* buffer containing help text */
MARK tagdefn;/* result of search; where cursor should move to */
OPTDESC *od; /* description struct of an option */
int len;
int i;
#ifdef FEATURE_ALIAS
alias_t *alias;
#endif
/* remove trailing whitespace from args */
if (xinf->lhs)
for (topic = &xinf->lhs[CHARlen(xinf->lhs)];
topic-- != xinf->lhs && elvspace(*topic);
)
*topic = '\0';
if (xinf->rhs)
for (topic = &xinf->rhs[CHARlen(xinf->rhs)];
topic-- != xinf->rhs && elvspace(*topic);
)
*topic = '\0';
/* construct a tag name for the requested topic */
topic = NULL;
len = xinf->lhs ? CHARlen(xinf->lhs) : 0;
if (!xinf->lhs)
{
/* :help */
topic = toCHAR("CONTENTS");
section = "elvis.html";
}
else if (!CHARcmp(xinf->lhs, toCHAR("ex")))
{
/* :help ex */
section = "elvisex.html";
}
else if (!CHARcmp(xinf->lhs, toCHAR("vi")))
{
/* :help vi */
section = "elvisvi.html";
}
else if ((!CHARncmp(xinf->lhs, toCHAR("se"), 2)
|| !CHARncmp(xinf->lhs, toCHAR(":se"), 3))
&& xinf->rhs)
{
/* :help set optionname */
/* strip off trailing =, if any */
topic = xinf->rhs + CHARlen(xinf->rhs);
while (*--topic == ' ' || *topic == '=')
{
}
topic[1] = '\0';
/* topic is option name */
if (optgetstr(xinf->rhs, &od))
topic = toCHAR(od->longname);
else if (xinf->rhs[0] == 'n' && xinf->rhs[1] == 'o'
&& (optgetstr(xinf->rhs + 2, &od)))
topic = toCHAR(od->longname);
else
topic = xinf->rhs;
section = "elvisopt.html";
}
else if ((!CHARncmp(xinf->lhs, toCHAR("di"), 2)
|| !CHARncmp(xinf->lhs, toCHAR(":di"), 3))
&& xinf->rhs)
{
/* :help display mode */
/* topic is option name */
topic = xinf->rhs;
section = "elvisdm.html";
}
# ifdef FEATURE_AUTOCMD
else if ((!CHARncmp(xinf->lhs, toCHAR("au"), 2)
|| !CHARncmp(xinf->lhs, toCHAR(":au"), 3))
&& xinf->rhs)
{
/* :help autocmd event */
/* topic is event name */
topic = auname(xinf->rhs);
if (!topic)
topic = toCHAR("AUTOCMD");
section = "elvistip.html";
}
# endif
else if (len > 1 && xinf->lhs[0] == ':')
{
/* :help :exname */
section = "elvisex.html";
topic = exname(xinf->lhs + 1);
#ifdef FEATURE_ALIAS
if (!topic)
{
/* Try to find the named alias */
for (alias = aliases;
alias && CHARcmp(xinf->lhs+1, toCHAR(alias->name));
alias = alias->next)
{
}
/* if it's a ! alias then look in elvistip.html;
* else just tell the user it's an alias.
*/
if (alias)
{
if (!alias->internal)
{
msg(MSG_INFO, "[S]$1 is an alias.", xinf->lhs + 1);
return RESULT_COMPLETE;
}
section = "elvistip.html";
topic = xinf->lhs + 1;
}
}
#endif
if (!topic)
topic = toCHAR("GROUP");
}
else if (len > 1 && xinf->lhs[0] == '<' && xinf->lhs[1])
{
/* :help <name */
/* strip off trailing > and leading </ */
topic = xinf->lhs + CHARlen(xinf->lhs) - 1;
if (*topic == '>')
*topic = '\0';
topic = xinf->lhs;
if (topic[1] == '/')
*++topic = '<';
if (!elvalnum(topic[1]))
topic = toCHAR("html");
section = "elvisdm.html";
}
else if (len > 1 && elvalpha(xinf->lhs[0])
&& (xinf->lhs[len - 1] == '(' || xinf->lhs[len - 1] == ')'))
{
/* :help function() (where function is a name) */
section = "elvisexp.html";
do
{
len--;
} while (!elvalpha(xinf->lhs[len - 1]));
xinf->lhs[len] = '\0';
topic = xinf->lhs;
}
else if ((topic = viname(xinf->lhs)) != NULL)
{
/* :help c (where c is a vi command, usually single-char) */
section = "elvisvi.html";
}
else if (optgetstr(xinf->lhs, &od) != NULL)
{
/* :help optionname */
topic = toCHAR(od->longname);
section = "elvisopt.html";
}
else if ((topic = exname(xinf->lhs)) != NULL)
{
/* :help exname */
section = "elvisex.html";
}
else
{
#ifdef FEATURE_ALIAS
/* Try to find the named alias */
for (alias = aliases;
alias && CHARcmp(xinf->lhs, toCHAR(alias->name));
alias = alias->next)
{
}
/* if it's a ! alias then look in elvistip.html;
* else just tell the user it's an alias.
*/
if (alias)
{
if (!alias->internal)
{
msg(MSG_INFO, "[S]$1 is an alias.", xinf->lhs);
return RESULT_COMPLETE;
}
section = "elvistip.html";
topic = xinf->lhs;
}
else
#endif
{
/* Can't tell what user is looking for; perhaps the user
* doesn't know the syntax of :help ? Teach them!
*/
topic = toCHAR("help");
section = "elvisex.html";
}
}
/* if help text not found, then give up */
buf = bufpath(o_elvispath, section, toCHAR(section));
if (!buf)
{
msg(MSG_ERROR, "[s]help not available; couldn't load $1", section);
return RESULT_ERROR;
}
/* help text uses "html" display mode */
if (optflags(o_bufdisplay(buf)) & OPT_FREE)
{
safefree(o_bufdisplay(buf));
optflags(o_bufdisplay(buf)) &= ~OPT_FREE;
}
o_bufdisplay(buf) = toCHAR("html");
o_initialsyntax(buf) = ElvFalse;
/* combine section name and topic name to form a tag */
if (topic)
{
tag = (CHAR *)safealloc((int)(CHARlen(o_filename(buf)) + CHARlen(topic) + 2), sizeof(CHAR));
CHARcpy(tag, o_filename(buf));
CHARcat(tag, toCHAR("#"));
CHARcat(tag, topic);
}
else
{
tag = CHARdup(o_filename(buf));
}
/* perform tag lookup to find the the topic in the help file */
tagdefn = (*dmhtml.tagload)(tag, NULL);
if (!tagdefn)
{
msg(MSG_ERROR, "[S]no help available for $1", topic);
safefree(tag);
return RESULT_ERROR;
}
safefree(tag);
/* Try to create a new window for the help text. If that doesn't
* work, then use the original window and push the old cursor onto
* the tag stack.
*/
bufwilldo(tagdefn, ElvFalse);
if ((*gui->creategw)(tochar8(o_bufname(markbuffer(tagdefn))), ""))
{
return RESULT_COMPLETE;
}
/* push the current cursor position and display mode onto tag stack */
if (o_tagstack &&
!o_internal(markbuffer(xinf->window->cursor)) &&
o_filename(markbuffer(xinf->window->cursor)))
{
if (xinf->window->tagstack[TAGSTK - 1].prevtag)
safefree(xinf->window->tagstack[TAGSTK - 1].prevtag);
if (xinf->window->tagstack[TAGSTK - 1].origin)
markfree(xinf->window->tagstack[TAGSTK - 1].origin);
for (i = TAGSTK - 1; i > 0; i--)
{
xinf->window->tagstack[i] = xinf->window->tagstack[i - 1];
}
xinf->window->tagstack[0].prevtag = (o_previoustag ? CHARdup(o_previoustag) : NULL);
xinf->window->tagstack[0].origin = markdup(xinf->window->cursor);
xinf->window->tagstack[0].display = xinf->window->md->name;
}
/* arrange for the cursor to move to the tag position */
xinf->newcurs = markdup(tagdefn);
return RESULT_COMPLETE;
# endif /* DISPLAY_HTML */
}
#endif /* FEATURE_MISC */
#ifdef FEATURE_CALC
/* Evaluate an expression, and set the "then" flag according to result */
RESULT ex_if(xinf)
EXINFO *xinf;
{
CHAR *result;
/* expression is required */
if (!xinf->rhs)
{
msg(MSG_ERROR, "missing rhs");
return RESULT_ERROR;
}
/* evaluate expression */
result = calculate(xinf->rhs, NULL, xinf->command == EX_EVAL ? CALC_MSG : CALC_ALL);
if (!result)
{
return RESULT_ERROR;
}
if (xinf->command == EX_IF)
{
/* set "exthenflag" based on result of evaluation */
exctlstate.thenflag = calctrue(result);
return RESULT_COMPLETE;
}
else /* command == EX_EVAL */
{
/* execute the result as an ex command */
return exstring(xinf->window, result, NULL);
}
}
#endif /* FEATURE_CALC */
/* implements the :try, :then, and :else commands */
RESULT ex_then(xinf)
EXINFO *xinf;
{
RESULT result = RESULT_COMPLETE;
ELVBOOL origmsg;
CHAR origsecurity;
assert(xinf->command == EX_THEN || xinf->command == EX_ELSE
|| xinf->command == EX_TRY || xinf->command == EX_SAFELY);
/* If no commands, then do nothing */
if (!xinf->rhs)
return result;
/* For :try, execute the commands unconditionally and then set the
* "exthenflag" to indicate whether the command succeeded. For :safely,
* execute the commands unconditionally with security=safer temporarily.
* Otherwise (for :then and :else) execute the commands if "exthenflag"
* is set appropriately.
*/
if (xinf->command == EX_TRY)
{
origmsg = msghide(ElvTrue);
exctlstate.thenflag = (ELVBOOL)(exstring(xinf->window, xinf->rhs, NULL) == RESULT_COMPLETE);
(void)msghide(origmsg);
}
else if (xinf->command == EX_SAFELY)
{
origsecurity = o_security;
if (o_security == 'n' /* normal */)
o_security = 's' /* safer */;
result = exstring(xinf->window, xinf->rhs, NULL);
o_security = origsecurity;
}
else if (xinf->command == EX_THEN ? exctlstate.thenflag : !exctlstate.thenflag)
{
result = exstring(xinf->window, xinf->rhs, NULL);
}
return result;
}
#ifdef FEATURE_CALC
RESULT ex_while(xinf)
EXINFO *xinf;
{
CHAR *vals;
int thisfile;
assert(xinf->command == EX_WHILE || xinf->command == EX_FOR);
/* If there was some other, unused test lying around, then free it. */
if (exctlstate.dotest)
safefree(exctlstate.dotest);
exctlstate.dotest = NULL;
if (exctlstate.list)
{
safefree(exctlstate.list);
exctlstate.list = NULL;
}
if (exctlstate.nfiles > 0)
{
for (thisfile = 0; thisfile < exctlstate.nfiles; thisfile++)
safefree(exctlstate.file[thisfile]);
safefree(exctlstate.file);
exctlstate.nfiles = 0;
}
/* :for requires a variable name, which must be an option */
if (xinf->command == EX_FOR)
{
if (!xinf->lhs)
{
msg(MSG_ERROR, "missing lhs");
return RESULT_ERROR;
}
if (!optval(tochar8(xinf->lhs)))
{
msg(MSG_ERROR, "[S]bad option $1", xinf->lhs);
return RESULT_ERROR;
}
}
/* expression is required for :while, and the non-file version of :for*/
if (!xinf->rhs && !(xinf->command == EX_FOR && xinf->nfiles > 0))
{
msg(MSG_ERROR, "missing rhs");
return RESULT_ERROR;
}
if (xinf->command == EX_WHILE)
{
/* store the new test */
exctlstate.dotest = xinf->rhs;
xinf->rhs = NULL;
}
else if (xinf->nfiles > 0)
{
/* remember the files list */
exctlstate.file = xinf->file;
exctlstate.nfiles = xinf->nfiles;
/* don't let the main ex code free this array */
xinf->nfiles = 0;
xinf->file = NULL;
/* remember the name of the variable */
exctlstate.dotest = xinf->lhs;
xinf->lhs = NULL;
}
else
{
/* evaluate the for-list expression */
vals = calculate(xinf->rhs, NULL, CALC_ALL);
if (!vals)
/* error message was already output by calculate() */
return RESULT_ERROR;
exctlstate.list = CHARdup(vals);
/* remember the name of the variable */
exctlstate.dotest = xinf->lhs;
xinf->lhs = NULL;
}
return RESULT_COMPLETE;
}
RESULT ex_do(xinf)
EXINFO *xinf;
{
CHAR *value;
CHAR *end, atend;
RESULT result = RESULT_COMPLETE;
int thisfile;
/* if no :while was executed before this, then fail */
if (!exctlstate.dotest)
{
msg(MSG_ERROR, "missing :while or :for");
return RESULT_ERROR;
}
/* while the expression is true and valid... */
value = exctlstate.list;
end = NULL;
atend = '\0';
thisfile = 0;
while (result == RESULT_COMPLETE)
{
/* distinguish between "for" and "while" */
if (exctlstate.list)
{
/* :for i (expr) */
/* restore the character at the end of previous word */
if (end)
{
*end = atend;
value = end;
}
/* skip ahead to next word. Break if none */
while (*value && elvspace(*value))
value++;
if (!*value)
break;
/* locate the end of the word, and mark it */
for (end = value; *end && !elvspace(*end); end++)
{
}
atend = *end;
*end = '\0';
/* store the value in the variable */
if (!optputstr(exctlstate.dotest, value, ElvFalse))
{
value = NULL;
break;
}
}
else if (exctlstate.nfiles > 0)
{
/* for i in files... */
/* if no more files, then break */
if (thisfile >= exctlstate.nfiles)
{
/* make sure this doesn't look like error */
if (!value)
value = toCHAR(exctlstate.file[0]);
break;
}
/* copy the next file name into the variable */
value = toCHAR(exctlstate.file[thisfile++]);
if (!optputstr(exctlstate.dotest, value, ElvFalse))
{
value = NULL;
break;
}
}
else
{
/* :while */
/* evaluate the expression. Break if failed or false */
value = calculate(exctlstate.dotest, NULL, CALC_ALL);
if (!value || !calctrue(value))
break;
}
/* Run the command. If no command, then display result */
if (xinf->rhs)
result = exstring(xinf->window, xinf->rhs, NULL);
else
{
drawextext(xinf->window, value, CHARlen(value));
drawextext(xinf->window, toCHAR("\n"), 1);
}
/* is the user getting bored? */
if (guipoll(ElvFalse))
break;
}
/* free the dotest expression, and (if used) the list */
safefree(exctlstate.dotest);
exctlstate.dotest = NULL;
if (exctlstate.list)
{
safefree(exctlstate.list);
exctlstate.list = NULL;
}
if (exctlstate.nfiles > 0)
{
for (thisfile = 0; thisfile < exctlstate.nfiles; thisfile++)
safefree(exctlstate.file[thisfile]);
safefree(exctlstate.file);
exctlstate.file = NULL;
exctlstate.nfiles = 0;
}
/* if test could not be evaluated, then this command fails */
if (!value)
return RESULT_ERROR;
return result;
}
/* Implements the :switch command -- evaluate an expression, and store the
* result for use in later :case statements.
*/
RESULT ex_switch(xinf)
EXINFO *xinf;
{
/* free the old switch value, if any */
if (exctlstate.switchvalue)
safefree(exctlstate.switchvalue);
exctlstate.switchvalue = NULL;
exctlstate.switchcarry = ElvFalse;
/* verify that we were given an expression */
if (!xinf->rhs)
{
msg(MSG_ERROR, "missing rhs");
return RESULT_ERROR;
}
/* compute a new value */
exctlstate.switchvalue = calculate(xinf->rhs, NULL, CALC_ALL);
if (exctlstate.switchvalue)
{
exctlstate.switchvalue = CHARdup(exctlstate.switchvalue);
return RESULT_COMPLETE;
}
return RESULT_ERROR;
}
/* This implements the :case and :default commands. It tests the value from
* a previous :switch command, and conditionally executes a command.
*/
RESULT ex_case(xinf)
EXINFO *xinf;
{
/* check syntax */
if (xinf->command == EX_CASE && !xinf->lhs)
{
msg(MSG_ERROR, "missing lhs");
return RESULT_ERROR;
}
if (xinf->command == EX_DEFAULT && !xinf->rhs)
{
msg(MSG_ERROR, "missing ex command");
return RESULT_ERROR;
}
/* if a previous case matched, then do nothing here... unless the
* previous match had no ex command line, and is therefor trying to
* execute this case's command line.
*/
if (!exctlstate.switchvalue && !exctlstate.switchcarry)
return RESULT_COMPLETE;
/* The :default command doesn't care about values, and we don't care
* when a previous match had no ex command line eithe. Otherwise we
* need to check the :switch value against this case's value.
*/
if (!exctlstate.switchcarry && xinf->command != EX_DEFAULT)
{
if (CHARcmp(exctlstate.switchvalue, xinf->lhs))
/* no match, so skip this case */
return RESULT_COMPLETE;
}
/* It *DOES* match. Clobber the exswitchvalue so each :switch will
* only match (at most) one case. This also allows is to detect
* the default condition later.
*/
if (exctlstate.switchvalue)
{
safefree(exctlstate.switchvalue);
exctlstate.switchvalue = NULL;
}
exctlstate.switchcarry = ElvFalse;
/* Execute the command for this case. If there is no command, then
* set a flag to indicate that the next case should match.
*/
if (xinf->rhs)
return exstring(xinf->window, xinf->rhs, NULL);
exctlstate.switchcarry = ElvTrue;
return RESULT_COMPLETE;
}
#endif /* FEATURE_CALC */
/* Test for a given word at the front of the map text. If present, then advance
* past it and return its flag bit.
*/
static MAPFLAGS maphelp2(refcp, word, flag)
CHAR **refcp; /* front of the map text */
char *word; /* a word to test for */
MAPFLAGS flag; /* flag to return if word is found */
{
int len = strlen(word);
CHAR *bigword = toCHAR(word);
if (!CHARncmp(*refcp, bigword, len)
&& (!(*refcp)[len] || elvspace((*refcp)[len])))
{
*refcp += len;
while (elvspace(**refcp))
(*refcp)++;
return flag;
}
return (MAPFLAGS)0;
}
/* parse any number of map flags. Return the MAPFLAGS value, and advance
* *refcp past the flags. If "mode=..." is seen, then set *refmode to point
* to a static string giving the mode name.
*/
static MAPFLAGS maphelp(refcp, refmode)
CHAR **refcp;
CHAR **refmode;
{
MAPFLAGS flags = (MAPFLAGS)0;
CHAR *start;
static CHAR modebuf[30];
/* if no arg, then return no flags */
if (!*refcp)
return flags;
/* parse any flags */
do
{
/* skip extra whitespace */
while (elvspace(**refcp))
(*refcp)++;
/* check for a word */
start = *refcp;
flags |= maphelp2(refcp, "input", MAP_INPUT);
flags |= maphelp2(refcp, "history", MAP_HISTORY);
flags |= maphelp2(refcp, "command", MAP_COMMAND);
flags |= maphelp2(refcp, "motion", MAP_MOTION);
flags |= maphelp2(refcp, "select", MAP_SELECT);
flags |= maphelp2(refcp, "visual", MAP_ASCMD);
flags |= maphelp2(refcp, "noremap", MAP_NOREMAP);
flags |= maphelp2(refcp, "nosave", MAP_NOSAVE);
if (!CHARncmp(*refcp, "mode=", 5))
{
for (start = modebuf, *refcp += 5;
start < &modebuf[sizeof modebuf -1]
&& !elvspace(**refcp);
)
*start++ = *(*refcp)++;
*start = '\0';
*refmode = modebuf;
}
} while (start != *refcp);
return flags;
}
/* This implemented :map, :unmap, :abbr, :unabbr, :break, and :unbreak */
RESULT ex_map(xinf)
EXINFO *xinf;
{
CHAR *line, *build;
MAPFLAGS flags;
int len;
CHAR *mode, *lhs, *rhs;
ELVBOOL all;
assert(xinf->command == EX_MAP || xinf->command == EX_ABBR
|| xinf->command == EX_UNMAP || xinf->command == EX_UNABBR
|| xinf->command == EX_BREAK || xinf->command == EX_UNBREAK);
/* the lhs and rhs of maps are complicated by the fact that they may
* be prefixed by context flags. This doesn't affect abbreviations.
*/
flags = 0;
lhs = rhs = mode = NULL;
if (xinf->command == EX_ABBR || xinf->command == EX_UNABBR)
{
/* standard parser is smart enough for :abbr */
lhs = xinf->lhs;
rhs = xinf->rhs;
}
else if (xinf->rhs)
{
/* for :map, everything is passed as part of the rhs, and we
* parse it out here.
*/
lhs = xinf->rhs;
flags = maphelp(&lhs, &mode);
for (build = rhs = lhs; *rhs && !elvspace(*rhs); )
{
if (*rhs == ELVCTRL('V') && rhs[1])
rhs++;
*build++ = *rhs++;
}
if (*rhs)
*rhs++ = '\0';
*build = '\0';
flags |= maphelp(&rhs, &mode);
for (build = line = rhs; *line; )
{
if (*line == ELVCTRL('V') && line[1])
line++;
*build++ = *line++;
}
*build = '\0';
if (!*rhs)
rhs = NULL;
if (!*lhs)
lhs = NULL;
}
/* detect "map all" */
all = ElvFalse;
if (xinf->command == EX_MAP && lhs && !rhs && !CHARcmp(lhs, toCHAR("all")))
{
lhs = NULL;
all = ElvTrue;
}
/* check for missing mandatory arguments */
if ((xinf->command == EX_MAP || xinf->command == EX_ABBR)
&& lhs && !rhs)
{
msg(MSG_ERROR, "missing rhs");
return RESULT_ERROR;
}
if ((xinf->command == EX_UNMAP || xinf->command == EX_UNABBR
|| xinf->command == EX_BREAK || xinf->command == EX_UNBREAK)
&& !lhs)
{
msg(MSG_ERROR, "missing lhs");
return RESULT_ERROR;
}
/* choose which flags to map */
if (xinf->command == EX_ABBR || xinf->command == EX_UNABBR)
{
flags = MAP_ABBR|(xinf->bang ? MAP_HISTORY : MAP_INPUT);
}
else
{
/* using :map! implies "input history" */
if (xinf->bang)
flags |= MAP_INPUT|MAP_HISTORY;
/* "visual" implies at least one of "input history" */
if ((flags & (MAP_INPUT|MAP_HISTORY|MAP_ASCMD)) == MAP_ASCMD)
flags |= MAP_INPUT|MAP_HISTORY;
/* if no other context specified, assume "command motion select" */
if ((flags & MAP_WHEN) == 0)
flags |= MAP_COMMAND|MAP_MOTION|MAP_SELECT;
}
/* either list, unmap, or map */
if (!lhs)
{
/* can't list maps before the first window is created */
if (xinf->window)
while ((line = maplist(flags & (MAP_WHEN|MAP_ABBR), mode, &len)) != (CHAR *)0)
{
if (*line == ' ' || all)
drawextext(xinf->window, line, len);
}
}
else if (!rhs)
{
(void)mapdelete(lhs, (int)CHARlen(lhs), flags, mode,
(ELVBOOL)(xinf->command == EX_UNMAP || xinf->command == EX_UNABBR),
(ELVBOOL)(xinf->command == EX_BREAK));
}
else
{
mapinsert(lhs, (int)CHARlen(lhs), rhs, (int)CHARlen(rhs), (CHAR *)0, flags, mode);
}
return RESULT_COMPLETE;
}
#ifdef FEATURE_EQUALTILDE
static CHAR *equaltilde(value, len, cmd)
CHAR *value; /*value to act on */
int len; /* length of value (which might not be NUL terminated)*/
CHAR *cmd; /* ex command to apply to the value */
{
BUFFER buf, prevdef;
MARKBUF top, bottom;
RESULT result;
/* create a temporary buffer */
buf = bufalloc(toCHAR(EQUALTILDE_BUF), 0, ElvTrue);
/* store the value into the buffer */
bufreplace(marktmp(top, buf, 0L), marktmp(bottom, buf, o_bufchars(buf)),
value, len);
bufreplace(marktmp(bottom, buf, o_bufchars(buf)), &bottom, toCHAR("\n"), 1);
#if 0
/* temporarily move the cursor to this buffer */
top = *windefault->cursor;
marksetoffset(windefault->cursor, 0L);
marksetbuffer(windefault->cursor, buf);
#else
/* temporarily make this the default buffer */
prevdef = bufdefault;
bufoptions(buf);
#endif
/* apply the ex command line to the buffer */
result = exstring(windefault, cmd, "equaltilde");
#if 0
/* restore the cursor */
marksetbuffer(windefault->cursor, top.buffer);
marksetoffset(windefault->cursor, top.offset);
#else
/* restore the buffer */
bufoptions(prevdef);
#endif
/* if the command failed or has no characters, return NULL */
if (result != RESULT_COMPLETE || o_bufchars(buf) == 0L)
return NULL;
/* else return the buffer's contents as a string */
return bufmemory(marktmp(top, buf, 0L), marktmp(bottom, buf, o_bufchars(buf) - 1L));
}
#endif
RESULT ex_set(xinf)
EXINFO *xinf;
{
CHAR outbuf[10000];
static CHAR empty[1];
#ifdef FEATURE_CALC
CHAR *value;
CHAR *opereq = NULL;
int i;
int j;
# ifdef FEATURE_ARRAY
CHUNK chunks[3];
CHAR *build;
CHAR *element;
CHAR *oldval = NULL;
# endif
#ifdef FEATURE_EQUALTILDE
ELVBOOL doequaltilde = ElvFalse;
CHAR *mustfree = NULL;
#endif
if (xinf->command == EX_LET)
{
i = 0;
if (!xinf->rhs)
{
goto MissingRHS;
}
/* copy name into outbuf[], so we can nul-terminate it */
for ( ; xinf->rhs && elvalnum(xinf->rhs[i]); i++)
{
outbuf[i] = xinf->rhs[i];
}
outbuf[i] = '\0';
# ifdef FEATURE_ARRAY
/* if "[" then get the array subscript */
if (xinf->rhs[i] == '[' || xinf->rhs[i] == '.')
{
/* get the option's value, as a string. We need to
* save it in a private buffer since optgetstr()
* returns a static buffer, and calculate() also uses
* optgetstr. So we copy it into outbuf.
*/
value = optgetstr(outbuf, NULL);
if (!value)
{
msg(MSG_ERROR, "[S]bad option $1", outbuf);
return RESULT_ERROR;
}
oldval = outbuf + i + 1;
CHARncpy(oldval, value, QTY(outbuf) - 100);
oldval[QTY(oldval) - 100] = '\0';
/* parse the subscript -- it will be alphanumeric */
if (xinf->rhs[i] == '.')
{
/* collect the chars */
for (build = NULL;
xinf->rhs[++i] && elvalnum(xinf->rhs[i]);
buildCHAR(&build, xinf->rhs[i]))
{
}
/* if the string is a set with a valueless
* element of that name, then delete the name.
*/
element = calcelement(oldval, build);
if (element && *element != ':')
{
j = CHARlen(build);
if (*element == ',')
element++, j++;
else if (oldval + j != element)
j++;
memmove(element - j, element, (CHARlen(element) + 1) * sizeof(CHAR));
}
/* find the chunks in the option's value */
(void)calcsubscript(oldval, build, QTY(chunks), chunks);
/* if no chunks, then append the name and try
* again.
*/
if (chunks[0].ptr == NULL)
{
if (oldval[0])
CHARcat(oldval, toCHAR(","));
CHARcat(oldval, build);
CHARcat(oldval, toCHAR(":"));
(void)calcsubscript(oldval, build, QTY(chunks), chunks);
}
safefree(build);
}
else /* it will end with "] = " */
{
for (build = NULL; xinf->rhs[++i]; buildCHAR(&build, xinf->rhs[i]))
{
/* if "] = " then stop */
if (xinf->rhs[i] == ']')
{
j = i + 1;
while (xinf->rhs[j] && elvspace(xinf->rhs[j]))
j++;
if (xinf->rhs[j] == '=' && xinf->rhs[j + 1] != '=')
break;
}
}
i++;
/* evaluate it */
if (build)
{
value = calculate(build, NULL, CALC_ALL);
safefree(build);
if (!value)
return RESULT_ERROR;
/* find the chunks in the option's value */
(void)calcsubscript(oldval, value, QTY(chunks), chunks);
}
else
value = NULL;
}
/* can't assign to element 0 (the length), or to
* scattered chunks.
*/
if (!value
|| chunks[0].ptr == NULL
|| chunks[0].ptr < oldval
|| chunks[0].ptr + chunks[0].len > oldval + CHARlen(oldval)
|| chunks[1].ptr)
{
msg(MSG_ERROR, "invalid subscript for assignment");
return RESULT_ERROR;
}
}
# endif /* FEATURE_ARRAY */
/* skip whitespace */
while (elvspace(xinf->rhs[i]))
{
i++;
}
/* if we're using an <oper>= then adjust the expression */
if (xinf->rhs[i] != '=' && elvpunct(xinf->rhs[i]))
{
/* convert ":let option oper= expr" to
* ":let option = option oper (expr)"
*/
for (j = 0; j < i; j++)
buildCHAR(&opereq, xinf->rhs[j]);
while (xinf->rhs[i] != '=' && xinf->rhs[i])
buildCHAR(&opereq, xinf->rhs[i++]);
buildCHAR(&opereq, ' ');
buildCHAR(&opereq, '(');
for (j = i + 1; xinf->rhs[j]; j++)
buildCHAR(&opereq, xinf->rhs[j]);
buildCHAR(&opereq, ')');
}
/* skip '=' */
if (xinf->rhs[i] != '=' || !outbuf[0])
{
goto MissingRHS;
}
i++;
# ifdef FEATURE_EQUALTILDE
/* detect if we're using "=~" */
if (!opereq && xinf->rhs[i] == '~')
{
doequaltilde = ElvTrue;
i++;
}
# endif
/* skip whitespace after the '=' */
while (elvspace(xinf->rhs[i]))
{
i++;
}
if (!xinf->rhs[i])
{
MissingRHS:
if (opereq)
safefree(opereq);
msg(MSG_ERROR, "missing rhs");
return RESULT_ERROR;
}
/* evaluate the expression */
# ifdef FEATURE_EQUALTILDE
if (doequaltilde)
{
# ifdef FEATURE_ARRAY
if (oldval)
{
mustfree = value = equaltilde(chunks[0].ptr, chunks[0].len, &xinf->rhs[i]);
}
else
# endif
{
value = optgetstr(outbuf, NULL);
mustfree = value = equaltilde(value, CHARlen(value), &xinf->rhs[i]);
}
}
else
# endif
value = calculate(opereq ? opereq : &xinf->rhs[i], NULL, CALC_ALL);
if (!value)
{
/* error message already given */
if (opereq)
safefree(opereq);
#ifdef FEATURE_EQUALTILDE
if (mustfree)
safefree(mustfree);
#endif
return RESULT_ERROR;
}
/* store the result */
# ifdef FEATURE_ARRAY
if (oldval)
{
/* the subscripted portion of the value is always one
* chunk, and it excludes any required delimiters. So
* all we need to do is copy text before the chunk,
* copy the new text, and copy the text after the chunk.
*/
build = NULL;
for (i = 0; i < (int)(chunks[0].ptr - oldval); i++)
buildCHAR(&build, oldval[i]);
while (*value)
buildCHAR(&build, *value++);
for (i += chunks[0].len; oldval[i]; i++)
buildCHAR(&build, oldval[i]);
if (!optputstr(outbuf, build, xinf->bang))
{
safefree(build);
if (opereq)
safefree(opereq);
# ifdef FEATURE_EQUALTILDE
if (mustfree)
safefree(mustfree);
# endif
return RESULT_ERROR;
}
safefree(build);
}
else /* no subscripts */
# endif /* FEATURE_ARRAY */
{
if (!optputstr(outbuf, value, xinf->bang))
{
/* error message already given */
if (opereq)
safefree(opereq);
# ifdef FEATURE_EQUALTILDE
if (mustfree)
safefree(mustfree);
# endif
return RESULT_ERROR;
}
}
}
else
#endif /* FEATURE_CALC */
#ifdef FEATURE_MISC
if (xinf->command == EX_LOCAL)
{
if (!xinf->rhs)
{
msg(MSG_ERROR, "missing rhs");
return RESULT_ERROR;
}
if (!optset(xinf->bang, xinf->rhs, NULL, 0))
{
return RESULT_ERROR;
}
}
else
#endif /* FEATURE_MISC */
/* command == EX_SET */
{
if (!optset(xinf->bang, xinf->rhs ? xinf->rhs : empty, outbuf, QTY(outbuf)))
{
return RESULT_ERROR;
}
if (*outbuf)
{
drawexlist(windefault, outbuf, (int)CHARlen(outbuf));
}
}
return RESULT_COMPLETE;
}
RESULT ex_version(xinf)
EXINFO *xinf;
{
msg(MSG_INFO, "[s]elvis $1", VERSION);
#ifdef COPY1
msg(MSG_INFO, "[s]$1", COPY1);
#endif
#ifdef COPY2
msg(MSG_INFO, "[s]$1", COPY2);
#endif
#ifdef COPY3
msg(MSG_INFO, "[s]$1", COPY3);
#endif
#ifdef COPY4
msg(MSG_INFO, "[s]$1", COPY4);
#endif
#ifdef COPY5
msg(MSG_INFO, "[s]$1", COPY5);
#endif
#ifdef PORTEDBY
msg(MSG_INFO, "[s]Ported to (os) by $1", PORTEDBY);
#endif
return RESULT_COMPLETE;
}
RESULT ex_qall(xinf)
EXINFO *xinf;
{
WINDOW win, except;
WINDOW orig;
RESULT result;
ELVBOOL didorig;
assert(xinf->command == EX_QALL || xinf->command == EX_PRESERVE
|| xinf->command == EX_ONLY);
/* If :preserve, then turn off the tempsession flag */
if (xinf->command == EX_PRESERVE)
{
o_tempsession = ElvFalse;
}
/* if :only, then use EX_CLOSE but don't close this window */
if (xinf->command == EX_ONLY)
{
xinf->command = EX_CLOSE;
except = xinf->window;
}
else
{
xinf->command = EX_QUIT;
except = NULL;
}
/* run the command on each window, except possibly this one */
orig = xinf->window;
for (win = winofbuf(NULL, NULL), result = RESULT_COMPLETE, didorig = ElvFalse;
win;
win = winofbuf(win, NULL))
{
/* maybe skip the current window */
if (win == except)
{
didorig = ElvTrue;
continue;
}
xinf->window = win;
if (ex_xit(xinf) != RESULT_COMPLETE)
{
result = RESULT_ERROR;
}
else if (win != orig)
{
/* Need to explicitly delete all windows except the
* current one. The current one will go away
* automatically when the ex_qall() function exits.
*/
(*gui->destroygw)(win->gw, ElvFalse);
win = didorig ? orig : NULL;
}
else
{
didorig = ElvTrue;
}
}
return result;
}
RESULT ex_xit(xinf)
EXINFO *xinf;
{
BUFFER buf; /* the buffer to be saved */
MARKBUF top, bottom;
STATE *state;
BUFFER b; /* other buffers */
static long morechgs; /* change counter at last "more files" warning */
static BUFFER morebuf; /* buffer which morechgs value refers to */
assert(xinf->command == EX_CLOSE || xinf->command == EX_QUIT
|| xinf->command == EX_XIT || xinf->command == EX_WQUIT);
/* Save the buffer, if :wquit or modified and :xit */
buf = markbuffer(xinf->window->cursor);
if (xinf->command == EX_WQUIT ||
(o_modified(buf) && xinf->command == EX_XIT))
{
/* Write to named file or the buffer's original file.
* If can't write, then fail.
*/
if (xinf->nfiles == 1
? !bufwrite(marktmp(top, buf, 0), marktmp(bottom, buf, o_bufchars(buf)), xinf->file[0], xinf->bang)
: !bufsave(buf, xinf->bang, ElvTrue))
{
/* an error message has already been output */
return RESULT_ERROR;
}
}
/* if :q on a modified buffer, and no other window is showing this
* buffer, then either (without !) complain or (with !) turn off the
* modified flag.
*/
if (xinf->command == EX_QUIT
&& o_modified(buf)
&& winofbuf(NULL, buf) == xinf->window
&& winofbuf(xinf->window, buf) == NULL)
{
if (xinf->bang)
{
o_modified(buf) = ElvFalse;
}
else
{
msg(MSG_ERROR, "[S]$1 modified, not saved", o_filename(buf) ? o_filename(buf) : o_bufname(buf));
return RESULT_ERROR;
}
}
/* If this is the last window, then make sure *ALL* user buffers
* have been saved. Exception: If :close! and this session isn't
* temporary, then we don't need to check buffers.
*/
if ((morebuf != markbuffer(xinf->window->cursor) || morebuf->changes != morechgs || xinf->command == EX_CLOSE)
&& !(xinf->command == EX_CLOSE && xinf->bang && o_tempsession)
&& winofbuf(NULL, NULL) == xinf->window && !winofbuf(xinf->window, NULL))
{
/* remember some stuff which should prevent us from warning
* the user more than once.
*/
morebuf = markbuffer(xinf->window->cursor);
morechgs = morebuf->changes;
/* check all buffers */
for (b = elvis_buffers; b; b = buflist(b))
{
if (!o_internal(b) && o_modified(b)
&& (xinf->command == EX_CLOSE || b != buf))
{
/* We've found a modified, unsaved user buffer.
* If the command is :q! then we want to
* discard all buffers; otherwise we want to
* warn the user that other buffers need to
* be checked.
*/
if (xinf->command == EX_QUIT && xinf->bang)
o_modified(b) = ElvFalse;
else
{
msg(MSG_ERROR, "check other buffers");
return RESULT_ERROR;
}
}
}
/* also check for more files to edit */
if (arglist && (argnext < 0 || arglist[argnext])
&& (xinf->command != EX_QUIT || !xinf->bang))
{
msg(MSG_WARNING, "more files");
return RESULT_ERROR;
}
}
/* If :close, then set the "retain" flag on the window's main buffer. */
if (xinf->command == EX_CLOSE)
{
o_retain(markbuffer(xinf->window->cursor)) = ElvTrue;
}
/* Arrange for the state stack to pop everything. This will cause
* the window to be closed, eventually.
*/
for (state = xinf->window->state; state; state = state->pop)
{
state->flags |= ELVIS_POP;
}
return RESULT_COMPLETE;
}
#ifdef FEATURE_SPELL
RESULT ex_check(xinf)
EXINFO *xinf;
{
long flags = 0;
CHAR *word, *end, atend;
int i;
ELVBOOL casesensitive;
assert(xinf->command == EX_CHECK || xinf->command == EX_WORDS);
/* if no words then dump current personal words */
if (!xinf->rhs)
{
if (xinf->command == EX_CHECK)
spellcheckfont(NULL, xinf->bang ? SPELL_CHECK_TAGONLY : SPELL_CHECK_ALL, xinf->bang);
else
spellsave(NULL);
return RESULT_COMPLETE;
}
/* find the start of each word. Also notice "+" or "-" */
for (word = xinf->rhs; *word; word++)
{
if (*word == '+')
flags = 0;
else if (*word == '-')
flags = SPELL_FLAG_BAD;
else if (*word == '*' && xinf->command == EX_CHECK)
flags = -1;
else if (!elvspace(*word))
{
/* found a word! now see how long it is */
casesensitive = (ELVBOOL)elvupper(*word);
for (end = word + 1;
elvalnum(*end) || *end == '_' || *end == '\'';
end++)
{
casesensitive |= elvupper(*word);
}
/* temporarily mark the end with a NUL character */
atend = *end;
*end = '\0';
/* add font or word, depending on the command */
if (xinf->command == EX_CHECK)
{
/* add the font */
spellcheckfont(word,
flags == -1 ? SPELL_CHECK_ALL :
(flags & SPELL_FLAG_BAD) ? SPELL_CHECK_NONE :
SPELL_CHECK_TAGONLY, xinf->bang);
}
else if (CHARlen(word) < 100) /* personal word */
{
/* add the word */
if (!xinf->bang)
flags |= SPELL_FLAG_PERSONAL;
spellwords = spelladdword(spellwords, word,
flags);
/* if case sensitive, then make the
* wrong-case version be a "wrong" word.
* There's no need to make this be treated
* as a personal word.
*/
if (casesensitive)
{
for (i = 0; &word[i] != end; i++)
word[i] = elvtolower(word[i]);
spellwords = spelladdword(spellwords,
word, SPELL_FLAG_BAD);
}
}
/* prepare for next word */
*end = atend;
word = end - 1; /* advances to end in for(;;) stmt */
}
}
return RESULT_COMPLETE;
}
RESULT ex_wordfile(xinf)
EXINFO *xinf;
{
int i;
assert(xinf->command == EX_WORDFILE);
/* check arguments */
if (xinf->nfiles == 0)
{
if (!o_spelldict || !*o_spelldict)
{
msg(MSG_ERROR, "missing file name");
return RESULT_ERROR;
}
spellload(tochar8(o_spelldict), ElvFalse);
}
else
{
for (i = 0; i < xinf->nfiles; i++)
{
spellload(xinf->file[i], (ELVBOOL)!xinf->bang);
}
}
return RESULT_COMPLETE;
}
#endif /* FEATURE_SPELL */
Jump to Line
Something went wrong with that request. Please try again.