1867 lines
49 KiB
C
1867 lines
49 KiB
C
/*-
|
|
* Copyright (c) 1992, 1993, 1994
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the University of
|
|
* California, Berkeley and its contributors.
|
|
* 4. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifndef lint
|
|
static char sccsid[] = "@(#)ex.c 8.157 (Berkeley) 8/17/94";
|
|
#endif /* not lint */
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/time.h>
|
|
|
|
#include <bitstring.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
#include "compat.h"
|
|
#include <db.h>
|
|
#include <regex.h>
|
|
|
|
#include "vi.h"
|
|
#include "excmd.h"
|
|
|
|
static void badlno __P((SCR *, recno_t));
|
|
static __inline EXCMDLIST const *
|
|
ex_comm_search __P((char *, size_t));
|
|
static int ep_line __P((SCR *, EXF *, MARK *, char **, size_t *, int *));
|
|
static int ep_range __P((SCR *, EXF *, EXCMDARG *, char **, size_t *));
|
|
|
|
/*
|
|
* ex --
|
|
* Read an ex command and execute it.
|
|
*/
|
|
int
|
|
ex(sp, ep)
|
|
SCR *sp;
|
|
EXF *ep;
|
|
{
|
|
enum input irval;
|
|
TEXT *tp;
|
|
u_int flags, saved_mode;
|
|
int eval;
|
|
|
|
if (ex_init(sp, ep))
|
|
return (1);
|
|
|
|
if (sp->s_refresh(sp, ep))
|
|
return (ex_end(sp));
|
|
|
|
/* If reading from a file, messages should have line info. */
|
|
if (!F_ISSET(sp->gp, G_STDIN_TTY)) {
|
|
sp->if_lno = 1;
|
|
sp->if_name = strdup("input");
|
|
}
|
|
|
|
/*
|
|
* !!!
|
|
* Historically, the beautify option applies to ex command input read
|
|
* from a file. In addition, the first time a ^H was discarded from
|
|
* the input, a message "^H discarded" was displayed. We don't bother.
|
|
*/
|
|
LF_INIT(TXT_BACKSLASH | TXT_CNTRLD | TXT_CR | TXT_EXSUSPEND);
|
|
|
|
for (eval = 0;; ++sp->if_lno) {
|
|
/* Set the flags that the user can change. */
|
|
if (O_ISSET(sp, O_BEAUTIFY))
|
|
LF_SET(TXT_BEAUTIFY);
|
|
else
|
|
LF_CLR(TXT_BEAUTIFY);
|
|
if (O_ISSET(sp, O_PROMPT))
|
|
LF_SET(TXT_PROMPT);
|
|
else
|
|
LF_CLR(TXT_PROMPT);
|
|
|
|
/*
|
|
* Get the next command. Interrupt flag manipulation is
|
|
* safe because ex_icmd clears them all.
|
|
*/
|
|
CLR_INTERRUPT(sp);
|
|
F_SET(sp, S_INTERRUPTIBLE);
|
|
irval = sp->s_get(sp, ep, sp->tiqp, ':', flags);
|
|
if (INTERRUPTED(sp)) {
|
|
(void)fputc('\n', stdout);
|
|
(void)fflush(stdout);
|
|
goto refresh;
|
|
}
|
|
switch (irval) {
|
|
case INP_OK:
|
|
break;
|
|
case INP_EOF:
|
|
case INP_ERR:
|
|
F_SET(sp, S_EXIT_FORCE);
|
|
/* FALLTHROUGH */
|
|
case INP_INTR:
|
|
goto ret;
|
|
}
|
|
|
|
/*
|
|
* If the user entered a carriage return, send ex_cmd()
|
|
* a separator -- it discards single newlines.
|
|
*/
|
|
tp = sp->tiqp->cqh_first;
|
|
if (tp->len == 0) {
|
|
tp->len = 1;
|
|
tp->lb[0] = ' ';
|
|
}
|
|
|
|
saved_mode = F_ISSET(sp, S_SCREENS | S_MAJOR_CHANGE);
|
|
if (ex_icmd(sp, ep,
|
|
tp->lb, tp->len, 1) && !F_ISSET(sp->gp, G_STDIN_TTY))
|
|
F_SET(sp, S_EXIT_FORCE);
|
|
(void)msg_rpt(sp, 0);
|
|
if (saved_mode != F_ISSET(sp, S_SCREENS | S_MAJOR_CHANGE))
|
|
break;
|
|
|
|
refresh: if (sp->s_refresh(sp, ep)) {
|
|
eval = 1;
|
|
break;
|
|
}
|
|
}
|
|
ret: if (sp->if_name != NULL) {
|
|
FREE(sp->if_name, strlen(sp->if_name) + 1);
|
|
sp->if_name = NULL;
|
|
}
|
|
return (ex_end(sp) || eval);
|
|
}
|
|
|
|
/*
|
|
* ex_cfile --
|
|
* Execute ex commands from a file.
|
|
*/
|
|
int
|
|
ex_cfile(sp, ep, filename, needsep)
|
|
SCR *sp;
|
|
EXF *ep;
|
|
char *filename;
|
|
int needsep;
|
|
{
|
|
struct stat sb;
|
|
int fd, len, rval;
|
|
char *bp;
|
|
|
|
bp = NULL;
|
|
if ((fd = open(filename, O_RDONLY, 0)) < 0 || fstat(fd, &sb))
|
|
goto err;
|
|
|
|
/*
|
|
* XXX
|
|
* We'd like to test if the file is too big to malloc. Since we don't
|
|
* know what size or type off_t's or size_t's are, what the largest
|
|
* unsigned integral type is, or what random insanity the local C
|
|
* compiler will perpetrate, doing the comparison in a portable way
|
|
* is flatly impossible. Hope that malloc fails if the file is too
|
|
* large.
|
|
*/
|
|
MALLOC(sp, bp, char *, (size_t)sb.st_size + 1);
|
|
if (bp == NULL)
|
|
goto err;
|
|
|
|
len = read(fd, bp, (int)sb.st_size);
|
|
if (len == -1 || len != sb.st_size) {
|
|
if (len != sb.st_size)
|
|
errno = EIO;
|
|
err: rval = 1;
|
|
msgq(sp, M_SYSERR, filename);
|
|
} else {
|
|
bp[sb.st_size] = '\0'; /* XXX */
|
|
|
|
/*
|
|
* Run the command. Messages include file/line information,
|
|
* but we don't care if we can't get space.
|
|
*/
|
|
sp->if_lno = 1;
|
|
sp->if_name = strdup(filename);
|
|
F_SET(sp, S_VLITONLY);
|
|
rval = ex_icmd(sp, ep, bp, len, needsep);
|
|
F_CLR(sp, S_VLITONLY);
|
|
free(sp->if_name);
|
|
sp->if_name = NULL;
|
|
}
|
|
|
|
/*
|
|
* !!!
|
|
* THE UNDERLYING EXF MAY HAVE CHANGED.
|
|
*/
|
|
if (bp != NULL)
|
|
FREE(bp, sb.st_size);
|
|
if (fd >= 0)
|
|
(void)close(fd);
|
|
return (rval);
|
|
}
|
|
|
|
/*
|
|
* ex_icmd --
|
|
* Call ex_cmd() after turning off interruptible bits.
|
|
*/
|
|
int
|
|
ex_icmd(sp, ep, cmd, len, needsep)
|
|
SCR *sp;
|
|
EXF *ep;
|
|
char *cmd;
|
|
size_t len;
|
|
int needsep;
|
|
{
|
|
/*
|
|
* Ex goes through here for each vi :colon command and for each ex
|
|
* command, however, globally executed commands don't go through
|
|
* here, instead, they call ex_cmd directly. So, reset all of the
|
|
* interruptible flags now.
|
|
*
|
|
* !!!
|
|
* Previous versions of nvi cleared mapped characters on error. This
|
|
* feature was removed when users complained that it wasn't historic
|
|
* practice.
|
|
*/
|
|
CLR_INTERRUPT(sp);
|
|
return (ex_cmd(sp, ep, cmd, len, needsep));
|
|
}
|
|
|
|
/* Special command structure for :s as a repeat substitution command. */
|
|
static EXCMDLIST const cmd_subagain =
|
|
{"s", ex_subagain, E_ADDR2|E_NORC,
|
|
"s",
|
|
"[line [,line]] s [cgr] [count] [#lp]",
|
|
"repeat the last subsitution"};
|
|
|
|
/* Special command structure for :d[flags]. */
|
|
static EXCMDLIST const cmd_del2 =
|
|
{"delete", ex_delete, E_ADDR2|E_AUTOPRINT|E_NORC,
|
|
"1bca1",
|
|
"[line [,line]] d[elete][flags] [buffer] [count] [flags]",
|
|
"delete lines from the file"};
|
|
|
|
/*
|
|
* ex_cmd --
|
|
* Parse and execute a string containing ex commands.
|
|
*/
|
|
int
|
|
ex_cmd(sp, ep, cmd, cmdlen, needsep)
|
|
SCR *sp;
|
|
EXF *ep;
|
|
char *cmd;
|
|
size_t cmdlen;
|
|
int needsep;
|
|
{
|
|
enum { NOTSET, NEEDSEP_N, NEEDSEP_NR, NONE } sep;
|
|
EX_PRIVATE *exp;
|
|
EXCMDARG exc;
|
|
EXCMDLIST const *cp;
|
|
MARK cur;
|
|
recno_t lno, num;
|
|
size_t arg1_len, len, save_cmdlen;
|
|
long flagoff;
|
|
u_int saved_mode;
|
|
int blank, ch, cnt, delim, flags, namelen, nl;
|
|
int optnum, uselastcmd, tmp, vi_address;
|
|
char *arg1, *save_cmd, *p, *s, *t;
|
|
|
|
/* Init. */
|
|
nl = 0;
|
|
sep = needsep ? NOTSET : NONE;
|
|
loop: if (nl) {
|
|
nl = 0;
|
|
++sp->if_lno;
|
|
}
|
|
arg1 = NULL;
|
|
save_cmdlen = 0;
|
|
|
|
/* It's possible that we've been interrupted during a command. */
|
|
if (INTERRUPTED(sp))
|
|
return (0);
|
|
|
|
/* Skip <blank>s, empty lines. */
|
|
for (blank = 0; cmdlen > 0; ++cmd, --cmdlen)
|
|
if ((ch = *cmd) == '\n')
|
|
++sp->if_lno;
|
|
else if (isblank(ch))
|
|
blank = 1;
|
|
else
|
|
break;
|
|
|
|
/*
|
|
* !!!
|
|
* Permit extra colons at the start of the line. Historically,
|
|
* ex/vi allowed a single extra one. It's simpler not to count.
|
|
* The stripping is done here because, historically, any command
|
|
* could have preceding colons, e.g. ":g/pattern/:p" worked.
|
|
*/
|
|
if (cmdlen != 0 && ch == ':') {
|
|
if (sep == NOTSET)
|
|
sep = NEEDSEP_N;
|
|
while (--cmdlen > 0 && (ch = *++cmd) == ':');
|
|
}
|
|
|
|
/*
|
|
* Command lines that start with a double-quote are comments.
|
|
*
|
|
* !!!
|
|
* Historically, there was no escape or delimiter for a comment,
|
|
* e.g. :"foo|set was a single comment and nothing was output.
|
|
* Since nvi permits users to escape <newline> characters into
|
|
* command lines, we have to check for that case.
|
|
*/
|
|
if (cmdlen != 0 && ch == '"') {
|
|
while (--cmdlen > 0 && *++cmd != '\n');
|
|
if (*cmd == '\n') {
|
|
nl = 1;
|
|
++cmd;
|
|
--cmdlen;
|
|
}
|
|
goto loop;
|
|
}
|
|
|
|
/* Skip whitespace. */
|
|
for (; cmdlen > 0; ++cmd, --cmdlen) {
|
|
ch = *cmd;
|
|
if (!isblank(ch))
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* The last point at which an empty line can mean do nothing.
|
|
*
|
|
* !!!
|
|
* Historically, in ex mode, lines containing only <blank> characters
|
|
* were the same as a single <carriage-return>, i.e. a default command.
|
|
* In vi mode, they were ignored.
|
|
*
|
|
* In .exrc files this was a serious annoyance, as vi kept trying to
|
|
* treat them as print commands. We ignore backward compatibility in
|
|
* this case, and discard lines containing only <blank> characters from
|
|
* .exrc files.
|
|
*/
|
|
if (cmdlen == 0 && (!IN_EX_MODE(sp) || ep == NULL || !blank))
|
|
return (0);
|
|
|
|
/* Initialize the structure passed to underlying functions. */
|
|
memset(&exc, 0, sizeof(EXCMDARG));
|
|
exp = EXP(sp);
|
|
if (argv_init(sp, ep, &exc))
|
|
goto err;
|
|
|
|
/*
|
|
* Check to see if this is a command for which we may want to output
|
|
* a \r separator instead of a \n. (The command :1<CR> puts out a \n,
|
|
* but the command :<CR> puts out a \r.) If the line is empty except
|
|
* for <blank>s, <carriage-return> or <eof>, we'll probably want to
|
|
* output \r. I don't think there's any way to get <blank> characters
|
|
* *after* the command character, but this is the ex parser, and I've
|
|
* been wrong before.
|
|
*/
|
|
if (sep == NOTSET)
|
|
sep = cmdlen == 0 || cmdlen == 1 && cmd[0] == '\004' ?
|
|
NEEDSEP_NR : NEEDSEP_N;
|
|
|
|
/* Parse command addresses. */
|
|
if (ep_range(sp, ep, &exc, &cmd, &cmdlen))
|
|
goto err;
|
|
|
|
/* Skip whitespace. */
|
|
for (; cmdlen > 0; ++cmd, --cmdlen) {
|
|
ch = *cmd;
|
|
if (!isblank(ch))
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If no command, ex does the last specified of p, l, or #, and vi
|
|
* moves to the line. Otherwise, determine the length of the command
|
|
* name by looking for the first non-alphabetic character. (There
|
|
* are a few non-alphabetic characters in command names, but they're
|
|
* all single character commands.) This isn't a great test, because
|
|
* it means that, for the command ":e +cut.c file", we'll report that
|
|
* the command "cut" wasn't known. However, it makes ":e+35 file" work
|
|
* correctly.
|
|
*
|
|
* !!!
|
|
* Historically, lines with multiple adjacent (or <blank> separated)
|
|
* command separators were very strange. For example, the command
|
|
* |||<carriage-return>, when the cursor was on line 1, displayed
|
|
* lines 2, 3 and 5 of the file. In addition, the command " | "
|
|
* would only display the line after the next line, instead of the
|
|
* next two lines. No ideas why. It worked reasonably when executed
|
|
* from vi mode, and displayed lines 2, 3, and 4, so we do a default
|
|
* command for each separator.
|
|
*/
|
|
#define SINGLE_CHAR_COMMANDS "\004!#&*<=>@~"
|
|
if (cmdlen != 0 && cmd[0] != '|' && cmd[0] != '\n') {
|
|
if (strchr(SINGLE_CHAR_COMMANDS, *cmd)) {
|
|
p = cmd;
|
|
++cmd;
|
|
--cmdlen;
|
|
namelen = 1;
|
|
} else {
|
|
for (p = cmd; cmdlen > 0; --cmdlen, ++cmd)
|
|
if (!isalpha(*cmd))
|
|
break;
|
|
if ((namelen = cmd - p) == 0) {
|
|
msgq(sp, M_ERR, "Unknown command name");
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* !!!
|
|
* Historic vi permitted flags to immediately follow any
|
|
* subset of the 'delete' command, but then did not permit
|
|
* further arguments (flag, buffer, count). Make it work.
|
|
* Permit further arguments for the few shreds of dignity
|
|
* it offers.
|
|
*
|
|
* !!!
|
|
* Note, adding commands that start with 'd', and match
|
|
* "delete" up to a l, p, +, - or # character can break
|
|
* this code.
|
|
*/
|
|
if (p[0] == 'd') {
|
|
for (s = p,
|
|
t = cmds[C_DELETE].name; *s == *t; ++s, ++t);
|
|
if (s[0] == 'l' || s[0] == 'p' ||
|
|
s[0] == '+' || s[0] == '-' || s[0] == '#') {
|
|
len = (cmd - p) - (s - p);
|
|
cmd -= len;
|
|
cmdlen += len;
|
|
cp = &cmd_del2;
|
|
goto skip;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Search the table for the command.
|
|
*
|
|
* !!!
|
|
* Historic vi permitted the mark to immediately follow the
|
|
* 'k' in the 'k' command. Make it work.
|
|
*
|
|
* !!!
|
|
* Historic vi permitted pretty much anything to follow the
|
|
* substitute command, e.g. "s/e/E/|s|sgc3p" was fine. Make
|
|
* the command "sgc" work.
|
|
*/
|
|
if ((cp = ex_comm_search(p, namelen)) == NULL)
|
|
switch (p[0]) {
|
|
case 's':
|
|
cmd -= namelen - 1;
|
|
cmdlen += namelen - 1;
|
|
cp = &cmd_subagain;
|
|
break;
|
|
case 'k':
|
|
if (p[1] && !p[2]) {
|
|
cmd -= namelen - 1;
|
|
cmdlen += namelen - 1;
|
|
cp = &cmds[C_K];
|
|
break;
|
|
}
|
|
/* FALLTHROUGH */
|
|
default:
|
|
msgq(sp, M_ERR,
|
|
"The %.*s command is unknown", namelen, p);
|
|
goto err;
|
|
}
|
|
|
|
/* Some commands are either not implemented or turned off. */
|
|
skip: if (F_ISSET(cp, E_NOPERM)) {
|
|
msgq(sp, M_ERR,
|
|
"The %s command is not currently supported",
|
|
cp->name);
|
|
goto err;
|
|
}
|
|
|
|
/* Some commands aren't okay in globals. */
|
|
if (F_ISSET(sp, S_GLOBAL) && F_ISSET(cp, E_NOGLOBAL)) {
|
|
msgq(sp, M_ERR,
|
|
"The %s command can't be used as part of a global command",
|
|
cp->name);
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* Multiple < and > characters; another "feature". Note,
|
|
* The string passed to the underlying function may not be
|
|
* nul terminated in this case.
|
|
*/
|
|
if ((cp == &cmds[C_SHIFTL] && *p == '<') ||
|
|
(cp == &cmds[C_SHIFTR] && *p == '>')) {
|
|
for (ch = *p; cmdlen > 0; --cmdlen, ++cmd)
|
|
if (*cmd != ch)
|
|
break;
|
|
if (argv_exp0(sp, ep, &exc, p, cmd - p))
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* The visual command has a different syntax when called
|
|
* from ex than when called from a vi colon command. FMH.
|
|
*/
|
|
if (cp == &cmds[C_VISUAL_EX] && IN_VI_MODE(sp))
|
|
cp = &cmds[C_VISUAL_VI];
|
|
|
|
/* Set the format style flags for the next command. */
|
|
if (cp == &cmds[C_HASH])
|
|
exp->fdef = E_F_HASH;
|
|
else if (cp == &cmds[C_LIST])
|
|
exp->fdef = E_F_LIST;
|
|
else if (cp == &cmds[C_PRINT])
|
|
exp->fdef = E_F_PRINT;
|
|
uselastcmd = 0;
|
|
} else {
|
|
/* Print is the default command. */
|
|
cp = &cmds[C_PRINT];
|
|
|
|
/* Set the saved format flags. */
|
|
F_SET(&exc, exp->fdef);
|
|
|
|
/*
|
|
* !!!
|
|
* If no address was specified, and it's not a global command,
|
|
* we up the address by one. (I have not an idea why global
|
|
* commands are exempted, but it's (ahem) historic practice.
|
|
*/
|
|
if (exc.addrcnt == 0 && !F_ISSET(sp, S_GLOBAL)) {
|
|
exc.addrcnt = 1;
|
|
exc.addr1.lno = sp->lno + 1;
|
|
exc.addr1.cno = sp->cno;
|
|
}
|
|
|
|
uselastcmd = 1;
|
|
}
|
|
|
|
/*
|
|
* !!!
|
|
* Historically, the number option applied to both ex and vi. One
|
|
* strangeness was that ex didn't switch display formats until a
|
|
* command was entered, e.g. <CR>'s after the set didn't change to
|
|
* the new format, but :1p would.
|
|
*/
|
|
if (O_ISSET(sp, O_NUMBER)) {
|
|
optnum = 1;
|
|
F_SET(&exc, E_F_HASH);
|
|
} else
|
|
optnum = 0;
|
|
|
|
/* Initialize local flags to the command flags. */
|
|
LF_INIT(cp->flags);
|
|
|
|
/*
|
|
* File state must be checked throughout this code, because it is
|
|
* called when reading the .exrc file and similar things. There's
|
|
* this little chicken and egg problem -- if we read the file first,
|
|
* we won't know how to display it. If we read/set the exrc stuff
|
|
* first, we can't allow any command that requires file state. We
|
|
* don't have a "reading an rc" bit, because we want the commands
|
|
* to work when the user source's the rc file later. Historic vi
|
|
* generally took the easy way out and dropped core.
|
|
*/
|
|
if (LF_ISSET(E_NORC) && ep == NULL) {
|
|
msgq(sp, M_ERR,
|
|
"The %s command requires that a file have already been read in",
|
|
cp->name);
|
|
goto err;
|
|
}
|
|
|
|
/*
|
|
* There are three normal termination cases for an ex command. They
|
|
* are the end of the string (cmdlen), or unescaped (by literal next
|
|
* characters) newline or '|' characters. As we're past any addresses,
|
|
* we can now determine how long the command is, so we don't have to
|
|
* look for all the possible terminations. There are three exciting
|
|
* special cases:
|
|
*
|
|
* 1: The bang, global, vglobal and the filter versions of the read and
|
|
* write commands are delimited by newlines (they can contain shell
|
|
* pipes).
|
|
* 2: The ex, edit, next and visual in vi mode commands all take ex
|
|
* commands as their first arguments.
|
|
* 3: The substitute command takes an RE as its first argument, and
|
|
* wants it to be specially delimited.
|
|
*
|
|
* Historically, '|' characters in the first argument of the ex, edit,
|
|
* next, vi visual, and substitute commands didn't delimit the command.
|
|
* And, in the filter cases for read and write, and the bang, global
|
|
* and vglobal commands, they did not delimit the command at all.
|
|
*
|
|
* For example, the following commands were legal:
|
|
*
|
|
* :edit +25|s/abc/ABC/ file.c
|
|
* :substitute s/|/PIPE/
|
|
* :read !spell % | columnate
|
|
* :global/pattern/p|l
|
|
*
|
|
* It's not quite as simple as it sounds, however. The command:
|
|
*
|
|
* :substitute s/a/b/|s/c/d|set
|
|
*
|
|
* was also legal, i.e. the historic ex parser (using the word loosely,
|
|
* since "parser" implies some regularity) delimited the RE's based on
|
|
* its delimiter and not anything so irretrievably vulgar as a command
|
|
* syntax.
|
|
*
|
|
* One thing that makes this easier is that we can ignore most of the
|
|
* command termination conditions for the commands that want to take
|
|
* the command up to the next newline. None of them are legal in .exrc
|
|
* files, so if we're here, we only dealing with a single line, and we
|
|
* can just eat it.
|
|
*
|
|
* Anyhow, the following code makes this all work. First, for the
|
|
* special cases we move past their special argument(s). Then, we
|
|
* do normal command processing on whatever is left. Barf-O-Rama.
|
|
*/
|
|
arg1_len = 0;
|
|
save_cmd = cmd;
|
|
if (cp == &cmds[C_EDIT] || cp == &cmds[C_EX] ||
|
|
cp == &cmds[C_NEXT] || cp == &cmds[C_VISUAL_VI]) {
|
|
/*
|
|
* Move to the next non-whitespace character. A '!'
|
|
* immediately following the command is eaten as a
|
|
* force flag.
|
|
*/
|
|
if (cmdlen > 0 && *cmd == '!') {
|
|
++cmd;
|
|
--cmdlen;
|
|
F_SET(&exc, E_FORCE);
|
|
|
|
/* Reset, don't reparse. */
|
|
save_cmd = cmd;
|
|
}
|
|
for (tmp = 0; cmdlen > 0; --cmdlen, ++cmd)
|
|
if (!isblank(*cmd))
|
|
break;
|
|
/*
|
|
* QUOTING NOTE:
|
|
*
|
|
* The historic implementation ignored all escape characters
|
|
* so there was no way to put a space or newline into the +cmd
|
|
* field. We do a simplistic job of fixing it by moving to the
|
|
* first whitespace character that isn't escaped by a literal
|
|
* next character. The literal next characters are stripped
|
|
* as they're no longer useful.
|
|
*/
|
|
if (cmdlen > 0 && *cmd == '+') {
|
|
++cmd;
|
|
--cmdlen;
|
|
for (arg1 = p = cmd; cmdlen > 0; --cmdlen, ++cmd) {
|
|
ch = *cmd;
|
|
if (IS_ESCAPE(sp, ch) && cmdlen > 1) {
|
|
--cmdlen;
|
|
ch = *++cmd;
|
|
} else if (isblank(ch))
|
|
break;
|
|
*p++ = ch;
|
|
}
|
|
arg1_len = cmd - arg1;
|
|
|
|
/* Reset, so the first argument isn't reparsed. */
|
|
save_cmd = cmd;
|
|
}
|
|
} else if (cp == &cmds[C_BANG] ||
|
|
cp == &cmds[C_GLOBAL] || cp == &cmds[C_VGLOBAL]) {
|
|
cmd += cmdlen;
|
|
cmdlen = 0;
|
|
} else if (cp == &cmds[C_READ] || cp == &cmds[C_WRITE]) {
|
|
/*
|
|
* Move to the next character. If it's a '!', it's a filter
|
|
* command and we want to eat it all, otherwise, we're done.
|
|
*/
|
|
for (; cmdlen > 0; --cmdlen, ++cmd) {
|
|
ch = *cmd;
|
|
if (!isblank(ch))
|
|
break;
|
|
}
|
|
if (cmdlen > 0 && ch == '!') {
|
|
cmd += cmdlen;
|
|
cmdlen = 0;
|
|
}
|
|
} else if (cp == &cmds[C_SUBSTITUTE]) {
|
|
/*
|
|
* Move to the next non-whitespace character, we'll use it as
|
|
* the delimiter. If the character isn't an alphanumeric or
|
|
* a '|', it's the delimiter, so parse it. Otherwise, we're
|
|
* into something like ":s g", so use the special substitute
|
|
* command.
|
|
*/
|
|
for (; cmdlen > 0; --cmdlen, ++cmd)
|
|
if (!isblank(cmd[0]))
|
|
break;
|
|
|
|
if (isalnum(cmd[0]) || cmd[0] == '|')
|
|
cp = &cmd_subagain;
|
|
else if (cmdlen > 0) {
|
|
/*
|
|
* QUOTING NOTE:
|
|
*
|
|
* Backslashes quote delimiter characters for RE's.
|
|
* The backslashes are NOT removed since they'll be
|
|
* used by the RE code. Move to the third delimiter
|
|
* that's not escaped (or the end of the command).
|
|
*/
|
|
delim = *cmd;
|
|
++cmd;
|
|
--cmdlen;
|
|
for (cnt = 2; cmdlen > 0 && cnt; --cmdlen, ++cmd)
|
|
if (cmd[0] == '\\' && cmdlen > 1) {
|
|
++cmd;
|
|
--cmdlen;
|
|
} else if (cmd[0] == delim)
|
|
--cnt;
|
|
}
|
|
}
|
|
/*
|
|
* Use normal quoting and termination rules to find the end of this
|
|
* command.
|
|
*
|
|
* QUOTING NOTE:
|
|
*
|
|
* Historically, vi permitted ^V's to escape <newline>'s in the .exrc
|
|
* file. It was almost certainly a bug, but that's what bug-for-bug
|
|
* compatibility means, Grasshopper. Also, ^V's escape the command
|
|
* delimiters. Literal next quote characters in front of the newlines,
|
|
* '|' characters or literal next characters are stripped as as they're
|
|
* no longer useful.
|
|
*/
|
|
vi_address = cmdlen != 0 && cmd[0] != '\n';
|
|
for (p = cmd, cnt = 0; cmdlen > 0; --cmdlen, ++cmd) {
|
|
ch = cmd[0];
|
|
if (IS_ESCAPE(sp, ch) && cmdlen > 1) {
|
|
tmp = cmd[1];
|
|
if (tmp == '\n' || tmp == '|') {
|
|
if (tmp == '\n')
|
|
++sp->if_lno;
|
|
--cmdlen;
|
|
++cmd;
|
|
++cnt;
|
|
ch = tmp;
|
|
}
|
|
} else if (ch == '\n' || ch == '|') {
|
|
if (ch == '\n')
|
|
nl = 1;
|
|
--cmdlen;
|
|
break;
|
|
}
|
|
*p++ = ch;
|
|
}
|
|
|
|
/*
|
|
* Save off the next command information, go back to the
|
|
* original start of the command.
|
|
*/
|
|
p = cmd + 1;
|
|
cmd = save_cmd;
|
|
save_cmd = p;
|
|
save_cmdlen = cmdlen;
|
|
cmdlen = ((save_cmd - cmd) - 1) - cnt;
|
|
|
|
/*
|
|
* !!!
|
|
* The "set tags" command historically used a backslash, not the
|
|
* user's literal next character, to escape whitespace. Handle
|
|
* it here instead of complicating the argv_exp3() code. Note,
|
|
* this isn't a particularly complex trap, and if backslashes were
|
|
* legal in set commands, this would have to be much more complicated.
|
|
*/
|
|
if (cp == &cmds[C_SET])
|
|
for (p = cmd, len = cmdlen; len > 0; --len, ++p)
|
|
if (*p == '\\')
|
|
*p = CH_LITERAL;
|
|
|
|
/*
|
|
* Set the default addresses. It's an error to specify an address for
|
|
* a command that doesn't take them. If two addresses are specified
|
|
* for a command that only takes one, lose the first one. Two special
|
|
* cases here, some commands take 0 or 2 addresses. For most of them
|
|
* (the E_ADDR2_ALL flag), 0 defaults to the entire file. For one
|
|
* (the `!' command, the E_ADDR2_NONE flag), 0 defaults to no lines.
|
|
*
|
|
* Also, if the file is empty, some commands want to use an address of
|
|
* 0, i.e. the entire file is 0 to 0, and the default first address is
|
|
* 0. Otherwise, an entire file is 1 to N and the default line is 1.
|
|
* Note, we also add the E_ZERO flag to the command flags, for the case
|
|
* where the 0 address is only valid if it's a default address.
|
|
*
|
|
* Also, set a flag if we set the default addresses. Some commands
|
|
* (ex: z) care if the user specified an address of if we just used
|
|
* the current cursor.
|
|
*/
|
|
switch (LF_ISSET(E_ADDR1|E_ADDR2|E_ADDR2_ALL|E_ADDR2_NONE)) {
|
|
case E_ADDR1: /* One address: */
|
|
switch (exc.addrcnt) {
|
|
case 0: /* Default cursor/empty file. */
|
|
exc.addrcnt = 1;
|
|
F_SET(&exc, E_ADDRDEF);
|
|
if (LF_ISSET(E_ZERODEF)) {
|
|
if (file_lline(sp, ep, &lno))
|
|
goto err;
|
|
if (lno == 0) {
|
|
exc.addr1.lno = 0;
|
|
LF_SET(E_ZERO);
|
|
} else
|
|
exc.addr1.lno = sp->lno;
|
|
} else
|
|
exc.addr1.lno = sp->lno;
|
|
exc.addr1.cno = sp->cno;
|
|
break;
|
|
case 1:
|
|
break;
|
|
case 2: /* Lose the first address. */
|
|
exc.addrcnt = 1;
|
|
exc.addr1 = exc.addr2;
|
|
}
|
|
break;
|
|
case E_ADDR2_NONE: /* Zero/two addresses: */
|
|
if (exc.addrcnt == 0) /* Default to nothing. */
|
|
break;
|
|
goto two;
|
|
case E_ADDR2_ALL: /* Zero/two addresses: */
|
|
if (exc.addrcnt == 0) { /* Default entire/empty file. */
|
|
exc.addrcnt = 2;
|
|
F_SET(&exc, E_ADDRDEF);
|
|
if (file_lline(sp, ep, &exc.addr2.lno))
|
|
goto err;
|
|
if (LF_ISSET(E_ZERODEF) && exc.addr2.lno == 0) {
|
|
exc.addr1.lno = 0;
|
|
LF_SET(E_ZERO);
|
|
} else
|
|
exc.addr1.lno = 1;
|
|
exc.addr1.cno = exc.addr2.cno = 0;
|
|
F_SET(&exc, E_ADDR2_ALL);
|
|
break;
|
|
}
|
|
/* FALLTHROUGH */
|
|
case E_ADDR2: /* Two addresses: */
|
|
two: switch (exc.addrcnt) {
|
|
case 0: /* Default cursor/empty file. */
|
|
exc.addrcnt = 2;
|
|
F_SET(&exc, E_ADDRDEF);
|
|
if (LF_ISSET(E_ZERODEF) && sp->lno == 1) {
|
|
if (file_lline(sp, ep, &lno))
|
|
goto err;
|
|
if (lno == 0) {
|
|
exc.addr1.lno = exc.addr2.lno = 0;
|
|
LF_SET(E_ZERO);
|
|
} else
|
|
exc.addr1.lno = exc.addr2.lno = sp->lno;
|
|
} else
|
|
exc.addr1.lno = exc.addr2.lno = sp->lno;
|
|
exc.addr1.cno = exc.addr2.cno = sp->cno;
|
|
break;
|
|
case 1: /* Default to first address. */
|
|
exc.addrcnt = 2;
|
|
exc.addr2 = exc.addr1;
|
|
break;
|
|
case 2:
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
if (exc.addrcnt) /* Error. */
|
|
goto usage;
|
|
}
|
|
|
|
/*
|
|
* !!!
|
|
* The ^D scroll command historically scrolled the value of the scroll
|
|
* option or to EOF. It was an error if the cursor was already at EOF.
|
|
* (Leading addresses were permitted, but were then ignored.)
|
|
*/
|
|
if (cp == &cmds[C_SCROLL]) {
|
|
exc.addrcnt = 2;
|
|
exc.addr1.lno = sp->lno + 1;
|
|
exc.addr2.lno = sp->lno + O_VAL(sp, O_SCROLL);
|
|
exc.addr1.cno = exc.addr2.cno = sp->cno;
|
|
if (file_lline(sp, ep, &lno))
|
|
goto err;
|
|
if (lno != 0 && lno > sp->lno && exc.addr2.lno > lno)
|
|
exc.addr2.lno = lno;
|
|
}
|
|
|
|
flagoff = 0;
|
|
for (p = cp->syntax; *p != '\0'; ++p) {
|
|
/*
|
|
* The force flag is sensitive to leading whitespace, i.e.
|
|
* "next !" is different from "next!". Handle it before
|
|
* skipping leading <blank>s.
|
|
*/
|
|
if (*p == '!') {
|
|
if (cmdlen > 0 && *cmd == '!') {
|
|
++cmd;
|
|
--cmdlen;
|
|
F_SET(&exc, E_FORCE);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
/* Skip leading <blank>s. */
|
|
for (; cmdlen > 0; --cmdlen, ++cmd)
|
|
if (!isblank(*cmd))
|
|
break;
|
|
|
|
/*
|
|
* Quit when reach the end of the command, unless it's a
|
|
* command that does its own parsing, in which case we want
|
|
* to build a reasonable argv for it. This code guarantees
|
|
* that there will be an argv when the function gets called,
|
|
* so the correct test is for a length of 0, not for the
|
|
* argc > 0. Since '!' can precede commands that do their
|
|
* own parsing, we have to have already handled it.
|
|
*/
|
|
if (cmdlen == 0 && *p != 'S' && *p != 's')
|
|
break;
|
|
|
|
switch (*p) {
|
|
case '1': /* +, -, #, l, p */
|
|
/*
|
|
* !!!
|
|
* Historically, some flags were ignored depending
|
|
* on where they occurred in the command line. For
|
|
* example, in the command, ":3+++p--#", historic vi
|
|
* acted on the '#' flag, but ignored the '-' flags.
|
|
* It's unambiguous what the flags mean, so we just
|
|
* handle them regardless of the stupidity of their
|
|
* location.
|
|
*/
|
|
for (; cmdlen; --cmdlen, ++cmd)
|
|
switch (*cmd) {
|
|
case '+':
|
|
++flagoff;
|
|
break;
|
|
case '-':
|
|
--flagoff;
|
|
break;
|
|
case '#':
|
|
optnum = 0;
|
|
F_SET(&exc, E_F_HASH);
|
|
exp->fdef |= E_F_HASH;
|
|
break;
|
|
case 'l':
|
|
F_SET(&exc, E_F_LIST);
|
|
exp->fdef |= E_F_LIST;
|
|
break;
|
|
case 'p':
|
|
F_SET(&exc, E_F_PRINT);
|
|
exp->fdef |= E_F_PRINT;
|
|
break;
|
|
default:
|
|
goto end1;
|
|
}
|
|
end1: break;
|
|
case '2': /* -, ., +, ^ */
|
|
case '3': /* -, ., +, ^, = */
|
|
for (; cmdlen; --cmdlen, ++cmd)
|
|
switch (*cmd) {
|
|
case '-':
|
|
F_SET(&exc, E_F_DASH);
|
|
break;
|
|
case '.':
|
|
F_SET(&exc, E_F_DOT);
|
|
break;
|
|
case '+':
|
|
F_SET(&exc, E_F_PLUS);
|
|
break;
|
|
case '^':
|
|
F_SET(&exc, E_F_CARAT);
|
|
break;
|
|
case '=':
|
|
if (*p == '3') {
|
|
F_SET(&exc, E_F_EQUAL);
|
|
break;
|
|
}
|
|
/* FALLTHROUGH */
|
|
default:
|
|
goto end2;
|
|
}
|
|
end2: break;
|
|
case 'b': /* buffer */
|
|
/*
|
|
* !!!
|
|
* Historically, "d #" was a delete with a flag, not a
|
|
* delete into the '#' buffer. If the current command
|
|
* permits a flag, don't use one as a buffer. However,
|
|
* the 'l' and 'p' flags were legal buffer names in the
|
|
* historic ex, and were used as buffers, not flags.
|
|
*/
|
|
if ((cmd[0] == '+' || cmd[0] == '-' || cmd[0] == '#') &&
|
|
strchr(p, '1') != NULL)
|
|
break;
|
|
/*
|
|
* !!!
|
|
* Digits can't be buffer names in ex commands, or the
|
|
* command "d2" would be a delete into buffer '2', and
|
|
* not a two-line deletion.
|
|
*/
|
|
if (!isdigit(cmd[0])) {
|
|
exc.buffer = *cmd;
|
|
++cmd;
|
|
--cmdlen;
|
|
F_SET(&exc, E_BUFFER);
|
|
}
|
|
break;
|
|
case 'c': /* count [01+a] */
|
|
++p;
|
|
/* Validate any signed value. */
|
|
if (!isdigit(*cmd) &&
|
|
(*p != '+' || (*cmd != '+' && *cmd != '-')))
|
|
break;
|
|
/* If a signed value, set appropriate flags. */
|
|
if (*cmd == '-')
|
|
F_SET(&exc, E_COUNT_NEG);
|
|
else if (*cmd == '+')
|
|
F_SET(&exc, E_COUNT_POS);
|
|
/* 8-bit XXX */ if ((lno = strtol(cmd, &t, 10)) == 0 && *p != '0') {
|
|
msgq(sp, M_ERR, "Count may not be zero");
|
|
goto err;
|
|
}
|
|
cmdlen -= (t - cmd);
|
|
cmd = t;
|
|
/*
|
|
* Count as address offsets occur in commands taking
|
|
* two addresses. Historic vi practice was to use
|
|
* the count as an offset from the *second* address.
|
|
*
|
|
* Set a count flag; some underlying commands (see
|
|
* join) do different things with counts than with
|
|
* line addresses.
|
|
*/
|
|
if (*p == 'a') {
|
|
exc.addr1 = exc.addr2;
|
|
exc.addr2.lno = exc.addr1.lno + lno - 1;
|
|
} else
|
|
exc.count = lno;
|
|
F_SET(&exc, E_COUNT);
|
|
break;
|
|
case 'f': /* file */
|
|
if (argv_exp2(sp, ep,
|
|
&exc, cmd, cmdlen, cp == &cmds[C_BANG]))
|
|
goto err;
|
|
goto countchk;
|
|
case 'l': /* line */
|
|
if (ep_line(sp, ep, &cur, &cmd, &cmdlen, &tmp))
|
|
goto err;
|
|
/* Line specifications are always required. */
|
|
if (!tmp) {
|
|
msgq(sp, M_ERR,
|
|
"%s: bad line specification", cmd);
|
|
goto err;
|
|
}
|
|
/* The line must exist for these commands. */
|
|
if (file_lline(sp, ep, &lno))
|
|
goto err;
|
|
if (cur.lno > lno) {
|
|
badlno(sp, lno);
|
|
goto err;
|
|
}
|
|
exc.lineno = cur.lno;
|
|
break;
|
|
case 'S': /* string, file exp. */
|
|
if (argv_exp1(sp, ep,
|
|
&exc, cmd, cmdlen, cp == &cmds[C_BANG]))
|
|
goto err;
|
|
goto addr2;
|
|
case 's': /* string */
|
|
if (argv_exp0(sp, ep, &exc, cmd, cmdlen))
|
|
goto err;
|
|
goto addr2;
|
|
case 'W': /* word string */
|
|
/*
|
|
* QUOTING NOTE:
|
|
*
|
|
* Literal next characters escape the following
|
|
* character. Quoting characters are stripped
|
|
* here since they are no longer useful.
|
|
*
|
|
* First there was the word.
|
|
*/
|
|
for (p = t = cmd; cmdlen > 0; --cmdlen, ++cmd) {
|
|
ch = *cmd;
|
|
if (IS_ESCAPE(sp, ch) && cmdlen > 1) {
|
|
--cmdlen;
|
|
*p++ = *++cmd;
|
|
} else if (isblank(ch)) {
|
|
++cmd;
|
|
--cmdlen;
|
|
break;
|
|
} else
|
|
*p++ = ch;
|
|
}
|
|
if (argv_exp0(sp, ep, &exc, t, p - t))
|
|
goto err;
|
|
|
|
/* Delete intervening whitespace. */
|
|
for (; cmdlen > 0; --cmdlen, ++cmd) {
|
|
ch = *cmd;
|
|
if (!isblank(ch))
|
|
break;
|
|
}
|
|
if (cmdlen == 0)
|
|
goto usage;
|
|
|
|
/* Followed by the string. */
|
|
for (p = t = cmd; cmdlen > 0; --cmdlen, ++cmd, ++p) {
|
|
ch = *cmd;
|
|
if (IS_ESCAPE(sp, ch) && cmdlen > 1) {
|
|
--cmdlen;
|
|
*p = *++cmd;
|
|
} else
|
|
*p = ch;
|
|
}
|
|
if (argv_exp0(sp, ep, &exc, t, p - t))
|
|
goto err;
|
|
goto addr2;
|
|
case 'w': /* word */
|
|
if (argv_exp3(sp, ep, &exc, cmd, cmdlen))
|
|
goto err;
|
|
countchk: if (*++p != 'N') { /* N */
|
|
/*
|
|
* If a number is specified, must either be
|
|
* 0 or that number, if optional, and that
|
|
* number, if required.
|
|
*/
|
|
num = *p - '0';
|
|
if ((*++p != 'o' || exp->argsoff != 0) &&
|
|
exp->argsoff != num)
|
|
goto usage;
|
|
}
|
|
goto addr2;
|
|
default:
|
|
msgq(sp, M_ERR,
|
|
"Internal syntax table error (%s: %c)",
|
|
cp->name, *p);
|
|
}
|
|
}
|
|
|
|
/* Skip trailing whitespace. */
|
|
for (; cmdlen; --cmdlen) {
|
|
ch = *cmd++;
|
|
if (!isblank(ch))
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* There shouldn't be anything left, and no more required
|
|
* fields, i.e neither 'l' or 'r' in the syntax string.
|
|
*/
|
|
if (cmdlen || strpbrk(p, "lr")) {
|
|
usage: msgq(sp, M_ERR, "Usage: %s", cp->usage);
|
|
goto err;
|
|
}
|
|
|
|
/* Verify that the addresses are legal. */
|
|
addr2: switch (exc.addrcnt) {
|
|
case 2:
|
|
if (file_lline(sp, ep, &lno))
|
|
goto err;
|
|
/*
|
|
* Historic ex/vi permitted commands with counts to go past
|
|
* EOF. So, for example, if the file only had 5 lines, the
|
|
* ex command "1,6>" would fail, but the command ">300"
|
|
* would succeed. Since we don't want to have to make all
|
|
* of the underlying commands handle random line numbers,
|
|
* fix it here.
|
|
*/
|
|
if (exc.addr2.lno > lno)
|
|
if (F_ISSET(&exc, E_COUNT))
|
|
exc.addr2.lno = lno;
|
|
else {
|
|
badlno(sp, lno);
|
|
goto err;
|
|
}
|
|
/* FALLTHROUGH */
|
|
case 1:
|
|
num = exc.addr1.lno;
|
|
/*
|
|
* If it's a "default vi command", zero is okay. Historic
|
|
* vi allowed this, note, it's also the hack that allows
|
|
* "vi +100 nonexistent_file" to work.
|
|
*/
|
|
if (num == 0 && (IN_EX_MODE(sp) || uselastcmd != 1) &&
|
|
!LF_ISSET(E_ZERO)) {
|
|
msgq(sp, M_ERR,
|
|
"The %s command doesn't permit an address of 0",
|
|
cp->name);
|
|
goto err;
|
|
}
|
|
if (file_lline(sp, ep, &lno))
|
|
goto err;
|
|
if (num > lno) {
|
|
badlno(sp, lno);
|
|
goto err;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If doing a default command and there's nothing left on the line,
|
|
* vi just moves to the line. For example, ":3" and ":'a,'b" just
|
|
* move to line 3 and line 'b, respectively, but ":3|" prints line 3.
|
|
*
|
|
* !!!
|
|
* This is done before the absolute mark gets set; historically,
|
|
* "/a/,/b/" did NOT set vi's absolute mark, but "/a/,/b/d" did.
|
|
*/
|
|
if (IN_VI_MODE(sp) && uselastcmd && vi_address == 0) {
|
|
switch (exc.addrcnt) {
|
|
case 2:
|
|
sp->lno = exc.addr2.lno ? exc.addr2.lno : 1;
|
|
sp->cno = exc.addr2.cno;
|
|
break;
|
|
case 1:
|
|
sp->lno = exc.addr1.lno ? exc.addr1.lno : 1;
|
|
sp->cno = exc.addr1.cno;
|
|
break;
|
|
}
|
|
cmd = save_cmd;
|
|
cmdlen = save_cmdlen;
|
|
goto loop;
|
|
}
|
|
|
|
/*
|
|
* Set the absolute mark -- we have to set it for vi here, in case
|
|
* it's a compound command, e.g. ":5p|6" should set the absolute
|
|
* mark for vi.
|
|
*/
|
|
if (F_ISSET(exp, EX_ABSMARK)) {
|
|
cur.lno = sp->lno;
|
|
cur.cno = sp->cno;
|
|
F_CLR(exp, EX_ABSMARK);
|
|
if (mark_set(sp, ep, ABSMARK1, &cur, 1))
|
|
goto err;
|
|
}
|
|
|
|
/* Final setup for the command. */
|
|
exc.cmd = cp;
|
|
|
|
#if defined(DEBUG) && 0
|
|
TRACE(sp, "ex_cmd: %s", exc.cmd->name);
|
|
if (exc.addrcnt > 0) {
|
|
TRACE(sp, "\taddr1 %d", exc.addr1.lno);
|
|
if (exc.addrcnt > 1)
|
|
TRACE(sp, " addr2: %d", exc.addr2.lno);
|
|
TRACE(sp, "\n");
|
|
}
|
|
if (exc.lineno)
|
|
TRACE(sp, "\tlineno %d", exc.lineno);
|
|
if (exc.flags)
|
|
TRACE(sp, "\tflags %0x", exc.flags);
|
|
if (F_ISSET(&exc, E_BUFFER))
|
|
TRACE(sp, "\tbuffer %c", exc.buffer);
|
|
TRACE(sp, "\n");
|
|
if (exc.argc) {
|
|
for (cnt = 0; cnt < exc.argc; ++cnt)
|
|
TRACE(sp, "\targ %d: {%s}", cnt, exc.argv[cnt]);
|
|
TRACE(sp, "\n");
|
|
}
|
|
#endif
|
|
/* Clear autoprint flag. */
|
|
F_CLR(exp, EX_AUTOPRINT);
|
|
|
|
/* Increment the command count if not called from vi. */
|
|
if (IN_EX_MODE(sp))
|
|
++sp->ccnt;
|
|
|
|
/*
|
|
* If file state available, and not doing a global command,
|
|
* log the start of an action.
|
|
*/
|
|
if (ep != NULL && !F_ISSET(sp, S_GLOBAL))
|
|
(void)log_cursor(sp, ep);
|
|
|
|
/*
|
|
* !!!
|
|
* There are two special commands for the purposes of this code: the
|
|
* default command (<carriage-return>) or the scrolling commands (^D
|
|
* and <EOF>) as the first non-<blank> characters in the line.
|
|
*
|
|
* If this is the first command in the command line, we received the
|
|
* command from the ex command loop and we're talking to a tty, and
|
|
* and there's nothing else on the command line, and it's one of the
|
|
* special commands, we erase the prompt character with a '\r'. Else,
|
|
* we put out a newline character to separate the command from the
|
|
* output from the command. It's OK if vi calls us -- we won't be in
|
|
* ex mode so we'll do nothing.
|
|
*
|
|
* !!!
|
|
* Historically, ex only put out a \r, so, if the displayed line was
|
|
* only a single character long, and <eof> was represented as ^D, the
|
|
* output wouldn't overwrite the user's input. Sex currently doesn't
|
|
* display the <eof> character if it's going to be the scroll command,
|
|
* i.e. if it's the first non-<blank> character in the line. If sex
|
|
* is changed to run in cooked mode, i.e. <eof> is displayed, this code
|
|
* will have to overwrite it. We also don't treat lines with extra
|
|
* prompt characters as empty -- it's not worth the effort since we'd
|
|
* have to overwrite some indeterminate number of columns with spaces
|
|
* to clean up. For now, put out enough spaces to overwrite the prompt.
|
|
*/
|
|
if (sep != NONE) {
|
|
if (ep != NULL &&
|
|
IN_EX_MODE(sp) && F_ISSET(sp->gp, G_STDIN_TTY))
|
|
if (sep == NEEDSEP_NR &&
|
|
(uselastcmd || cp == &cmds[C_SCROLL])) {
|
|
(void)putchar('\r');
|
|
for (len = KEY_LEN(sp, PROMPTCHAR); len--;)
|
|
(void)putchar(' ');
|
|
(void)putchar('\r');
|
|
} else
|
|
(void)putchar('\n');
|
|
sep = NONE;
|
|
}
|
|
|
|
/* Save the current mode. */
|
|
saved_mode = F_ISSET(sp, S_SCREENS | S_MAJOR_CHANGE);
|
|
|
|
/* Do the command. */
|
|
if (cp->fn(sp, ep, &exc))
|
|
goto err;
|
|
|
|
#ifdef DEBUG
|
|
/* Make sure no function left the temporary space locked. */
|
|
if (F_ISSET(sp->gp, G_TMP_INUSE)) {
|
|
F_CLR(sp->gp, G_TMP_INUSE);
|
|
msgq(sp, M_ERR, "Error: ex: temporary buffer not released");
|
|
goto err;
|
|
}
|
|
#endif
|
|
if (saved_mode != F_ISSET(sp, S_SCREENS | S_MAJOR_CHANGE)) {
|
|
/*
|
|
* Only here if the mode of the underlying file changed, e.g.
|
|
* the user switched files or is exiting. Two things that we
|
|
* might have to save: first, any "+cmd" field set up for an
|
|
* ex/edit command will have to be saved for later, also, any
|
|
* part of the current ex command that hasn't been executed
|
|
* yet. For example:
|
|
*
|
|
* :edit +25 file.c|s/abc/ABC/|1
|
|
*
|
|
* !!!
|
|
* The historic vi just hung, of course; nvi handles it by
|
|
* pushing the keys onto the tty queue. Since the commands
|
|
* are intended as ex commands, add additional characters
|
|
* to make it all work if we're switching modes to vi. Also,
|
|
* + commands were oriented to the last line in the file,
|
|
* historically, make the cursor start out there.
|
|
*
|
|
* For the fun of it, if you want to see if a vi clone got the
|
|
* ex argument parsing right, try:
|
|
*
|
|
* echo 'foo|bar' > file1; echo 'foo/bar' > file2;
|
|
* vi
|
|
* :edit +1|s/|/PIPE/|w file1| e file2|1 | s/\//SLASH/|wq
|
|
*/
|
|
if (arg1_len == 0 && save_cmdlen == 0)
|
|
return (0);
|
|
if (term_push(sp, "\n", 1, 0))
|
|
goto err;
|
|
if (save_cmdlen != 0)
|
|
if (term_push(sp, save_cmd, save_cmdlen, 0))
|
|
goto err;
|
|
if (arg1 != NULL) {
|
|
if (IN_VI_MODE(sp) && save_cmdlen != 0 &&
|
|
term_push(sp, "|", 1, 0))
|
|
goto err;
|
|
if (term_push(sp, arg1, arg1_len, 0))
|
|
goto err;
|
|
if (file_lline(sp, ep, &sp->frp->lno))
|
|
goto err;
|
|
F_SET(sp->frp, FR_CURSORSET);
|
|
}
|
|
if (IN_VI_MODE(sp) && term_push(sp, ":", 1, 0))
|
|
goto err;
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Integrate any offset parsed by the underlying command, and make
|
|
* sure the referenced line exists.
|
|
*
|
|
* XXX
|
|
* May not match historic practice (I've never been able to completely
|
|
* figure it out.) For example, the '=' command from vi mode often
|
|
* got the offset wrong, and complained it was too large, but didn't
|
|
* seem to have a problem with the cursor. If anyone complains, ask
|
|
* them how it's supposed to work, they probably know.
|
|
*/
|
|
if (ep != NULL && (flagoff += exc.flagoff)) {
|
|
if (flagoff < 0) {
|
|
if (sp->lno <= -flagoff) {
|
|
msgq(sp, M_ERR, "Flag offset before line 1");
|
|
goto err;
|
|
}
|
|
} else {
|
|
if (file_lline(sp, ep, &lno))
|
|
goto err;
|
|
if (sp->lno + flagoff > lno) {
|
|
msgq(sp, M_ERR, "Flag offset past end-of-file");
|
|
goto err;
|
|
}
|
|
}
|
|
sp->lno += flagoff;
|
|
}
|
|
|
|
/*
|
|
* If the command was successful and we're in ex command mode, we
|
|
* may want to display a line. Make sure there's a line to display.
|
|
*/
|
|
if (ep != NULL &&
|
|
IN_EX_MODE(sp) && !F_ISSET(sp, S_GLOBAL) && sp->lno != 0) {
|
|
/*
|
|
* The print commands have already handled the `print' flags.
|
|
* If so, clear them.
|
|
*/
|
|
if (LF_ISSET(E_F_PRCLEAR))
|
|
F_CLR(&exc, E_F_HASH | E_F_LIST | E_F_PRINT);
|
|
|
|
/* If hash only set because of the number option, discard it. */
|
|
if (optnum)
|
|
F_CLR(&exc, E_F_HASH);
|
|
|
|
/*
|
|
* If there was an explicit flag to display the new cursor
|
|
* line, or we're in ex mode, autoprint is set, and a change
|
|
* was made, display the line. If any print flags set use
|
|
* them, otherwise default to print.
|
|
*/
|
|
LF_INIT(F_ISSET(&exc, E_F_HASH | E_F_LIST | E_F_PRINT));
|
|
if (!LF_ISSET(E_F_HASH | E_F_LIST | E_F_PRINT) &&
|
|
O_ISSET(sp, O_AUTOPRINT) &&
|
|
(F_ISSET(exp, EX_AUTOPRINT) || F_ISSET(cp, E_AUTOPRINT)))
|
|
LF_INIT(E_F_PRINT);
|
|
|
|
if (LF_ISSET(E_F_HASH | E_F_LIST | E_F_PRINT)) {
|
|
memset(&exc, 0, sizeof(EXCMDARG));
|
|
exc.addrcnt = 2;
|
|
exc.addr1.lno = exc.addr2.lno = sp->lno;
|
|
exc.addr1.cno = exc.addr2.cno = sp->cno;
|
|
(void)ex_print(sp, ep, &exc.addr1, &exc.addr2, flags);
|
|
}
|
|
}
|
|
|
|
cmd = save_cmd;
|
|
cmdlen = save_cmdlen;
|
|
goto loop;
|
|
/* NOTREACHED */
|
|
|
|
/*
|
|
* If we haven't put out a separator line, do it now. For more
|
|
* detailed comments, see above.
|
|
*/
|
|
err: if (sep != NONE &&
|
|
ep != NULL && IN_EX_MODE(sp) && F_ISSET(sp->gp, G_STDIN_TTY))
|
|
(void)fputc('\n', stdout);
|
|
/*
|
|
* On error, we discard any keys we have left, as well as any keys
|
|
* that were mapped. The test of save_cmdlen isn't necessarily
|
|
* correct. If we fail early enough we don't know if the entire
|
|
* string was a single command or not. Try and guess, it's useful
|
|
* to know if part of the command was discarded.
|
|
*/
|
|
if (save_cmdlen == 0)
|
|
for (; cmdlen; --cmdlen) {
|
|
ch = *cmd++;
|
|
if (IS_ESCAPE(sp, ch) && cmdlen > 1) {
|
|
--cmdlen;
|
|
++cmd;
|
|
} else if (ch == '\n' || ch == '|') {
|
|
if (cmdlen > 1)
|
|
save_cmdlen = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (save_cmdlen != 0)
|
|
msgq(sp, M_ERR,
|
|
"Ex command failed: remaining command input discarded");
|
|
/*
|
|
* !!!
|
|
* Previous versions of nvi cleared mapped characters on error. This
|
|
* feature was removed when users complained that it wasn't historic
|
|
* practice.
|
|
*/
|
|
return (1);
|
|
}
|
|
|
|
/*
|
|
* ep_range --
|
|
* Get a line range for ex commands.
|
|
*/
|
|
static int
|
|
ep_range(sp, ep, excp, cmdp, cmdlenp)
|
|
SCR *sp;
|
|
EXF *ep;
|
|
EXCMDARG *excp;
|
|
char **cmdp;
|
|
size_t *cmdlenp;
|
|
{
|
|
MARK cur, savecursor;
|
|
size_t cmdlen;
|
|
int savecursor_set, tmp;
|
|
char *cmd;
|
|
|
|
/* Percent character is all lines in the file. */
|
|
cmd = *cmdp;
|
|
cmdlen = *cmdlenp;
|
|
if (*cmd == '%') {
|
|
excp->addr1.lno = 1;
|
|
if (file_lline(sp, ep, &excp->addr2.lno))
|
|
return (1);
|
|
|
|
/* If an empty file, then the first line is 0, not 1. */
|
|
if (excp->addr2.lno == 0)
|
|
excp->addr1.lno = 0;
|
|
excp->addr1.cno = excp->addr2.cno = 0;
|
|
excp->addrcnt = 2;
|
|
|
|
++*cmdp;
|
|
--*cmdlenp;
|
|
return (0);
|
|
}
|
|
|
|
/* Parse comma or semi-colon delimited line specs. */
|
|
for (savecursor_set = 0, excp->addrcnt = 0; cmdlen > 0;)
|
|
switch (*cmd) {
|
|
case ';': /* Semi-colon delimiter. */
|
|
/*
|
|
* Comma delimiters delimit; semi-colon delimiters
|
|
* change the current address for the 2nd address
|
|
* to be the first address. Trailing or multiple
|
|
* delimiters are discarded.
|
|
*/
|
|
if (excp->addrcnt == 0)
|
|
goto done;
|
|
if (!savecursor_set) {
|
|
savecursor.lno = sp->lno;
|
|
savecursor.cno = sp->cno;
|
|
sp->lno = excp->addr1.lno;
|
|
sp->cno = excp->addr1.cno;
|
|
savecursor_set = 1;
|
|
}
|
|
++cmd;
|
|
--cmdlen;
|
|
break;
|
|
case ',': /* Comma delimiter. */
|
|
/* If no addresses yet, defaults to ".". */
|
|
if (excp->addrcnt == 0) {
|
|
excp->addr1.lno = sp->lno;
|
|
excp->addr1.cno = sp->cno;
|
|
excp->addrcnt = 1;
|
|
}
|
|
/* FALLTHROUGH */
|
|
case ' ': /* Whitespace. */
|
|
case '\t': /* Whitespace. */
|
|
++cmd;
|
|
--cmdlen;
|
|
break;
|
|
default:
|
|
if (ep_line(sp, ep, &cur, &cmd, &cmdlen, &tmp))
|
|
return (1);
|
|
if (!tmp)
|
|
goto done;
|
|
|
|
/*
|
|
* Extra addresses are discarded, starting with
|
|
* the first.
|
|
*/
|
|
switch (excp->addrcnt) {
|
|
case 0:
|
|
excp->addr1 = cur;
|
|
excp->addrcnt = 1;
|
|
break;
|
|
case 1:
|
|
excp->addr2 = cur;
|
|
excp->addrcnt = 2;
|
|
break;
|
|
case 2:
|
|
excp->addr1 = excp->addr2;
|
|
excp->addr2 = cur;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* XXX
|
|
* This is probably not the right behavior for savecursor --
|
|
* need to figure out what the historical ex did for ";,;,;5p"
|
|
* or similar stupidity.
|
|
*/
|
|
done: if (savecursor_set) {
|
|
sp->lno = savecursor.lno;
|
|
sp->cno = savecursor.cno;
|
|
}
|
|
if (excp->addrcnt == 2 && excp->addr2.lno < excp->addr1.lno) {
|
|
msgq(sp, M_ERR,
|
|
"The second address is smaller than the first");
|
|
return (1);
|
|
}
|
|
*cmdp = cmd;
|
|
*cmdlenp = cmdlen;
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Get a single line address specifier.
|
|
*
|
|
* The way the "previous context" mark worked was that any "non-relative"
|
|
* motion set it. While ex/vi wasn't totally consistent about this, ANY
|
|
* numeric address, search pattern, '$', or mark reference in an address
|
|
* was considered non-relative, and set the value. Which should explain
|
|
* why we're hacking marks down here. The problem was that the mark was
|
|
* only set if the command was called, i.e. we have to set a flag and test
|
|
* it later.
|
|
*
|
|
* XXX
|
|
* This is not exactly historic practice, although it's fairly close.
|
|
*/
|
|
static int
|
|
ep_line(sp, ep, cur, cmdp, cmdlenp, addr_found)
|
|
SCR *sp;
|
|
EXF *ep;
|
|
MARK *cur;
|
|
char **cmdp;
|
|
size_t *cmdlenp;
|
|
int *addr_found;
|
|
{
|
|
EX_PRIVATE *exp;
|
|
MARK m;
|
|
long total;
|
|
u_int flags;
|
|
size_t cmdlen;
|
|
int (*sf) __P((SCR *, EXF *, MARK *, MARK *, char *, char **, u_int *));
|
|
char *cmd, *endp;
|
|
|
|
exp = EXP(sp);
|
|
*addr_found = 0;
|
|
|
|
cmd = *cmdp;
|
|
cmdlen = *cmdlenp;
|
|
switch (*cmd) {
|
|
case '$': /* Last line in the file. */
|
|
*addr_found = 1;
|
|
F_SET(exp, EX_ABSMARK);
|
|
|
|
cur->cno = 0;
|
|
if (file_lline(sp, ep, &cur->lno))
|
|
return (1);
|
|
++cmd;
|
|
--cmdlen;
|
|
break; /* Absolute line number. */
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
*addr_found = 1;
|
|
F_SET(exp, EX_ABSMARK);
|
|
|
|
cur->cno = 0;
|
|
/* 8-bit XXX */ cur->lno = strtol(cmd, &endp, 10);
|
|
cmdlen -= (endp - cmd);
|
|
cmd = endp;
|
|
break;
|
|
case '\'': /* Use a mark. */
|
|
*addr_found = 1;
|
|
F_SET(exp, EX_ABSMARK);
|
|
|
|
if (cmdlen == 1) {
|
|
msgq(sp, M_ERR, "No mark name supplied");
|
|
return (1);
|
|
}
|
|
if (mark_get(sp, ep, cmd[1], cur))
|
|
return (1);
|
|
cmd += 2;
|
|
cmdlen -= 2;
|
|
break;
|
|
case '\\': /* Search: forward/backward. */
|
|
/*
|
|
* !!!
|
|
* I can't find any difference between // and \/ or between
|
|
* ?? and \?. Mark Horton doesn't remember there being any
|
|
* difference. C'est la vie.
|
|
*/
|
|
if (cmdlen < 2 || cmd[1] != '/' && cmd[1] != '?') {
|
|
msgq(sp, M_ERR, "\\ not followed by / or ?");
|
|
return (1);
|
|
}
|
|
++cmd;
|
|
--cmdlen;
|
|
sf = cmd[0] == '/' ? f_search : b_search;
|
|
goto search;
|
|
case '/': /* Search forward. */
|
|
sf = f_search;
|
|
goto search;
|
|
case '?': /* Search backward. */
|
|
sf = b_search;
|
|
search: F_SET(exp, EX_ABSMARK);
|
|
|
|
if (ep == NULL) {
|
|
msgq(sp, M_ERR,
|
|
"A search address requires that a file have already been read in");
|
|
return (1);
|
|
}
|
|
*addr_found = 1;
|
|
m.lno = sp->lno;
|
|
m.cno = sp->cno;
|
|
flags = SEARCH_MSG | SEARCH_PARSE | SEARCH_SET;
|
|
if (sf(sp, ep, &m, &m, cmd, &endp, &flags))
|
|
return (1);
|
|
cur->lno = m.lno;
|
|
cur->cno = m.cno;
|
|
cmdlen -= (endp - cmd);
|
|
cmd = endp;
|
|
break;
|
|
case '.': /* Current position. */
|
|
*addr_found = 1;
|
|
cur->cno = sp->cno;
|
|
|
|
/* If an empty file, then '.' is 0, not 1. */
|
|
if (sp->lno == 1) {
|
|
if (file_lline(sp, ep, &cur->lno))
|
|
return (1);
|
|
if (cur->lno != 0)
|
|
cur->lno = 1;
|
|
} else
|
|
cur->lno = sp->lno;
|
|
++cmd;
|
|
--cmdlen;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Evaluate any offset. Offsets are +/- any number, or any number
|
|
* of +/- signs, or any combination thereof. If no address found
|
|
* yet, offset is relative to ".".
|
|
*/
|
|
for (total = 0; cmdlen > 0 && (cmd[0] == '-' || cmd[0] == '+');) {
|
|
if (!*addr_found) {
|
|
cur->lno = sp->lno;
|
|
cur->cno = sp->cno;
|
|
*addr_found = 1;
|
|
}
|
|
|
|
if (cmdlen > 1 && isdigit(cmd[1])) {
|
|
/* 8-bit XXX */ total += strtol(cmd, &endp, 10);
|
|
cmdlen -= (endp - cmd);
|
|
cmd = endp;
|
|
} else {
|
|
total += cmd[0] == '-' ? -1 : 1;
|
|
--cmdlen;
|
|
++cmd;
|
|
}
|
|
}
|
|
|
|
if (*addr_found) {
|
|
if (total < 0 && -total > cur->lno) {
|
|
msgq(sp, M_ERR,
|
|
"Reference to a line number less than 0");
|
|
return (1);
|
|
}
|
|
cur->lno += total;
|
|
|
|
*cmdp = cmd;
|
|
*cmdlenp = cmdlen;
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* ex_is_abbrev -
|
|
* The vi text input routine needs to know if ex thinks this is
|
|
* an [un]abbreviate command, so it can turn off abbreviations.
|
|
* Usual ranting in the vi/v_ntext:txt_abbrev() routine.
|
|
*/
|
|
int
|
|
ex_is_abbrev(name, len)
|
|
char *name;
|
|
size_t len;
|
|
{
|
|
EXCMDLIST const *cp;
|
|
|
|
return ((cp = ex_comm_search(name, len)) != NULL &&
|
|
(cp == &cmds[C_ABBR] || cp == &cmds[C_UNABBREVIATE]));
|
|
}
|
|
|
|
/*
|
|
* ex_is_unmap -
|
|
* The vi text input routine needs to know if ex thinks this is
|
|
* an unmap command, so it can turn off input mapping. Usual
|
|
* ranting in the vi/v_ntext:txt_unmap() routine.
|
|
*/
|
|
int
|
|
ex_is_unmap(name, len)
|
|
char *name;
|
|
size_t len;
|
|
{
|
|
EXCMDLIST const *cp;
|
|
|
|
/*
|
|
* The command the vi input routines are really interested in
|
|
* is "unmap!", not just unmap.
|
|
*/
|
|
if (name[len - 1] != '!')
|
|
return (0);
|
|
--len;
|
|
return ((cp = ex_comm_search(name, len)) != NULL &&
|
|
cp == &cmds[C_UNMAP]);
|
|
}
|
|
|
|
static __inline EXCMDLIST const *
|
|
ex_comm_search(name, len)
|
|
char *name;
|
|
size_t len;
|
|
{
|
|
EXCMDLIST const *cp;
|
|
|
|
for (cp = cmds; cp->name != NULL; ++cp) {
|
|
if (cp->name[0] > name[0])
|
|
return (NULL);
|
|
if (cp->name[0] != name[0])
|
|
continue;
|
|
if (!memcmp(name, cp->name, len))
|
|
return (cp);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
static void
|
|
badlno(sp, lno)
|
|
SCR *sp;
|
|
recno_t lno;
|
|
{
|
|
if (lno == 0)
|
|
msgq(sp, M_ERR, "Illegal address: the file is empty");
|
|
else
|
|
msgq(sp, M_ERR, "Illegal address: only %lu line%s in the file",
|
|
lno, lno > 1 ? "s" : "");
|
|
}
|