/* This file contains the code for reading ex commands. */
/* This data type is used to describe the possible argument combinations */
#define FROM 1 /* allow a linespec */
#define TO 2 /* allow a second linespec */
#define BANG 4 /* allow a ! after the command name */
#define EXTRA 8 /* allow extra args after command name */
#define XFILE 16 /* expand wildcards in extra part */
#define NOSPC 32 /* no spaces allowed in the extra part */
#define DFLALL 64 /* default file range is 1,$ */
#define DFLNONE 128 /* no default file range */
#define NODFL 256 /* do not default to the current file name */
#define EXRCOK 512 /* can be in a .exrc file */
#define NL 1024 /* if mode!=MODE_EX, then write a newline first */
#define PLUS 2048 /* allow a line number, as in ":e +32 foo" */
#define ZERO 4096 /* allow 0 to be given as a line number */
#define NOBAR 8192 /* treat following '|' chars as normal */
#define FILES (XFILE + EXTRA) /* multiple extra files allowed */
#define WORD1 (EXTRA + NOSPC) /* one extra word allowed */
#define FILE1 (FILES + NOSPC) /* 1 file allowed, defaults to current file */
#define NAMEDF (FILE1 + NODFL) /* 1 file allowed, defaults to "" */
#define NAMEDFS (FILES + NODFL) /* multiple files allowed, default is "" */
#define RANGE (FROM + TO) /* range of linespecs allowed */
#define NONE 0 /* no args allowed at all */
/* This array maps ex command names to command codes. The order in which
* command names are listed below is significant -- ambiguous abbreviations
* are always resolved to be the first possible match. (e.g. "r" is taken
* to mean "read", not "rewind", because "read" comes before "rewind")
char *name
; /* name of the command */
CMD code
; /* enum code of the command */
void (*fn
)();/* function which executes the command */
ARGT argt
; /* command line arguments permitted/needed/used */
{ /* cmd name cmd code function arguments */
{"print", CMD_PRINT
, cmd_print
, RANGE
+NL
},
{"append", CMD_APPEND
, cmd_append
, FROM
+ZERO
+BANG
},
{"bug", CMD_DEBUG
, cmd_debug
, RANGE
+BANG
+EXTRA
+NL
},
{"change", CMD_CHANGE
, cmd_append
, RANGE
+BANG
},
{"delete", CMD_DELETE
, cmd_delete
, RANGE
+WORD1
},
{"edit", CMD_EDIT
, cmd_edit
, BANG
+FILE1
+PLUS
},
{"file", CMD_FILE
, cmd_file
, NAMEDF
},
{"global", CMD_GLOBAL
, cmd_global
, RANGE
+BANG
+EXTRA
+DFLALL
+NOBAR
},
{"insert", CMD_INSERT
, cmd_append
, FROM
+BANG
},
{"join", CMD_INSERT
, cmd_join
, RANGE
+BANG
},
{"k", CMD_MARK
, cmd_mark
, FROM
+WORD1
},
{"list", CMD_LIST
, cmd_print
, RANGE
+NL
},
{"move", CMD_MOVE
, cmd_move
, RANGE
+EXTRA
},
{"next", CMD_NEXT
, cmd_next
, BANG
+NAMEDFS
},
{"Next", CMD_PREVIOUS
, cmd_next
, BANG
},
{"quit", CMD_QUIT
, cmd_xit
, BANG
},
{"read", CMD_READ
, cmd_read
, FROM
+ZERO
+NAMEDF
},
{"substitute", CMD_SUBSTITUTE
, cmd_substitute
, RANGE
+EXTRA
},
{"to", CMD_COPY
, cmd_move
, RANGE
+EXTRA
},
{"undo", CMD_UNDO
, cmd_undo
, NONE
},
{"vglobal", CMD_VGLOBAL
, cmd_global
, RANGE
+EXTRA
+DFLALL
+NOBAR
},
{"write", CMD_WRITE
, cmd_write
, RANGE
+BANG
+FILE1
+DFLALL
},
{"xit", CMD_XIT
, cmd_xit
, BANG
+NL
},
{"yank", CMD_YANK
, cmd_delete
, RANGE
+WORD1
},
{"!", CMD_BANG
, cmd_shell
, EXRCOK
+RANGE
+NAMEDFS
+DFLNONE
+NL
+NOBAR
},
{"#", CMD_NUMBER
, cmd_print
, RANGE
+NL
},
{"<", CMD_SHIFTL
, cmd_shift
, RANGE
},
{">", CMD_SHIFTR
, cmd_shift
, RANGE
},
{"=", CMD_EQUAL
, cmd_file
, RANGE
},
{"&", CMD_SUBAGAIN
, cmd_substitute
, RANGE
},
{"@", CMD_AT
, cmd_at
, EXTRA
},
{"abbreviate", CMD_ABBR
, cmd_map
, EXRCOK
+BANG
+EXTRA
},
{"args", CMD_ARGS
, cmd_args
, EXRCOK
+NAMEDFS
},
{"cc", CMD_CC
, cmd_make
, BANG
+FILES
},
{"cd", CMD_CD
, cmd_cd
, EXRCOK
+BANG
+NAMEDF
},
{"copy", CMD_COPY
, cmd_move
, RANGE
+EXTRA
},
{"digraph", CMD_DIGRAPH
, cmd_digraph
, EXRCOK
+BANG
+EXTRA
},
{"errlist", CMD_ERRLIST
, cmd_errlist
, BANG
+NAMEDF
},
{"ex", CMD_EDIT
, cmd_edit
, BANG
+FILE1
},
{"mark", CMD_MARK
, cmd_mark
, FROM
+WORD1
},
{"mkexrc", CMD_MKEXRC
, cmd_mkexrc
, NAMEDF
},
{"number", CMD_NUMBER
, cmd_print
, RANGE
+NL
},
{"pop", CMD_POP
, cmd_pop
, BANG
+WORD1
},
{"put", CMD_PUT
, cmd_put
, FROM
+ZERO
+WORD1
},
{"set", CMD_SET
, cmd_set
, EXRCOK
+EXTRA
},
{"shell", CMD_SHELL
, cmd_shell
, NL
},
{"source", CMD_SOURCE
, cmd_source
, EXRCOK
+NAMEDF
},
{"stop", CMD_STOP
, cmd_suspend
, NONE
},
{"tag", CMD_TAG
, cmd_tag
, BANG
+WORD1
},
{"version", CMD_VERSION
, cmd_version
, EXRCOK
+NONE
},
{"visual", CMD_VISUAL
, cmd_edit
, BANG
+NAMEDF
},
{"wq", CMD_WQUIT
, cmd_xit
, NL
},
{"debug", CMD_DEBUG
, cmd_debug
, RANGE
+BANG
+EXTRA
+NL
},
{"validate", CMD_VALIDATE
, cmd_validate
, BANG
+NL
},
{"chdir", CMD_CD
, cmd_cd
, EXRCOK
+BANG
+NAMEDF
},
{"color", CMD_COLOR
, cmd_color
, EXRCOK
+EXTRA
},
{"make", CMD_MAKE
, cmd_make
, BANG
+NAMEDFS
},
{"map", CMD_MAP
, cmd_map
, EXRCOK
+BANG
+EXTRA
},
{"previous", CMD_PREVIOUS
, cmd_next
, BANG
},
{"rewind", CMD_REWIND
, cmd_next
, BANG
},
{"suspend", CMD_SUSPEND
, cmd_suspend
, NONE
},
{"unmap", CMD_UNMAP
, cmd_map
, EXRCOK
+BANG
+EXTRA
},
{"unabbreviate",CMD_UNABBR
, cmd_map
, EXRCOK
+WORD1
},
/* This function parses a search pattern - given a pointer to a / or ?,
* it replaces the ending / or ? with a \0, and returns a pointer to the
* stuff that came after the pattern.
/* allow backslashed versions of / and ? in the pattern */
if (*scan
== '\\' && scan
[1] != '\0')
/* This function parses a line specifier for ex commands */
char *linespec(s
, markptr
)
REG
char *s
; /* start of the line specifier */
MARK
*markptr
; /* where to store the mark's value */
/* parse each ;-delimited clause of this linespec */
/* skip an initial ';', if any */
/* skip leading spaces */
/* dot means current position */
/* '$' means the last line */
/* digit means an absolute line number */
for (num
= 0; isdigit(*s
); s
++)
num
= num
* 10 + *s
- '0';
*markptr
= MARK_AT_LINE(num
);
/* appostrophe means go to a set mark */
*markptr
= m_tomark(cursor
, 1L, (int)*s
);
/* slash means do a search */
else if (*s
== '/' || *s
== '?')
/* put a '\0' at the end of the search pattern */
/* search for the pattern */
*markptr
&= ~(BLKSIZE
- 1);
pfetch(markline(*markptr
));
*markptr
= m_fsrch(*markptr
, s
);
*markptr
= m_bsrch(*markptr
, s
);
/* adjust command string pointer */
/* if linespec was faulty, quit now */
/* maybe add an offset */
if (*t
== '-' || *t
== '+')
for (num
= 0; isdigit(*s
); s
++)
num
= num
* 10 + *s
- '0';
*markptr
= m_updnto(*markptr
, num
, *t
);
} while (*s
== ';' || *s
== '+' || *s
== '-');
/* protect against invalid line numbers */
num
= markline(*markptr
);
if (num
< 1L || num
> nlines
)
msg("Invalid line number -- must be from 1 to %ld", nlines
);
/* This function reads an ex command and executes it. */
oldline
= markline(cursor
);
cmdlen
= vgets(':', cmdbuf
, sizeof(cmdbuf
));
cmdlen
= vgets(*o_prompt
? ':' : '\0', cmdbuf
, sizeof(cmdbuf
));
/* if empty line, assume ".+1" */
/* parse & execute the command */
if (significant
|| markline(cursor
) != oldline
)
oldline
= markline(cursor
);
if (*o_autoprint
&& mode
== MODE_EX
)
cmd_print(cursor
, cursor
, CMD_PRINT
, FALSE
, "");
char *cmdbuf
; /* string containing an ex command */
REG
char *scan
; /* used to scan thru cmdbuf */
MARK frommark
; /* first linespec */
MARK tomark
; /* second linespec */
REG
int cmdlen
; /* length of the command name given */
CMD cmd
; /* what command is this? */
ARGT argt
; /* argument types for this command */
short forceit
; /* bang version of a command? */
REG
int cmdidx
; /* index of command */
REG
char *build
; /* used while copying filenames */
int iswild
; /* boolean: filenames use wildcards? */
int isdfl
; /* using default line ranges? */
int didsub
; /* did we substitute file names for % or # */
/* ex commands can't be undone via the shift-U command */
/* permit extra colons at the start of the line */
for (; *cmdbuf
== ':'; cmdbuf
++)
/* ignore command lines that start with a double-quote */
/* parse the line specifier */
/* no file, so don't allow addresses */
/* '%' means all lines */
frommark
= tomark
= (*scan
? MARK_UNSET
: MARK_FIRST
);
scan
= linespec(scan
, &frommark
);
if (frommark
&& *scan
== ',')
scan
= linespec(scan
, &tomark
);
/* faulty line spec -- fault already described */
msg("first address exceeds the second");
isdfl
= (scan
== cmdbuf
);
/* Figure out how long the command name is. If no command, then the
* length is 0, which will match the "print" command.
/* if not in ex mode, and both endpoints are at the line,
* then just move to the start of that line without printing
if (mode
!= MODE_EX
&& frommark
== tomark
)
if (tomark
!= MARK_UNSET
)
else if (!isalpha(*scan
))
/* lookup the command code */
cmdnames
[cmdidx
].name
&& strncmp(scan
, cmdnames
[cmdidx
].name
, cmdlen
);
argt
= cmdnames
[cmdidx
].argt
;
cmd
= cmdnames
[cmdidx
].code
;
msg("Unknown command \"%.*s\"", cmdlen
, scan
);
/* !!! if the command doesn't have NOBAR set, then replace | with \0 */
/* if the command ended with a bang, set the forceit flag */
if ((argt
& BANG
) && *scan
== '!')
/* skip any more whitespace, to leave scan pointing to arguments */
/* a couple of special cases for filenames */
/* if names were given, process them */
for (build
= tmpblk
.c
, iswild
= didsub
= FALSE
; *scan
; scan
++)
if (scan
[1] == '\\' || scan
[1] == '%' || scan
[1] == '#')
msg("No filename to substitute for %%");
msg("No filename to substitute for #");
|| cmd
== CMD_READ
&& tmpblk
.c
[0] == '!'
|| cmd
== CMD_WRITE
&& tmpblk
.c
[0] == '!')
if (iswild
&& tmpblk
.c
[0] != '>')
scan
= wildcard(tmpblk
.c
);
else /* no names given, maybe assume origname */
strcpy(tmpblk
.c
, origname
);
if (!(argt
& EXRCOK
) && nlines
< 1L)
msg("Can't use the \"%s\" command in a %s file", cmdnames
[cmdidx
].name
, EXRC
);
if (!(argt
& (ZERO
| EXRCOK
)) && frommark
== MARK_UNSET
)
msg("Can't use address 0 with \"%s\" command.", cmdnames
[cmdidx
].name
);
if (!(argt
& FROM
) && frommark
!= cursor
&& nlines
>= 1L)
msg("Can't use address with \"%s\" command.", cmdnames
[cmdidx
].name
);
if (!(argt
& TO
) && tomark
!= frommark
&& nlines
>= 1L)
msg("Can't use a range with \"%s\" command.", cmdnames
[cmdidx
].name
);
if (!(argt
& EXTRA
) && *scan
)
msg("Extra characters after \"%s\" command.", cmdnames
[cmdidx
].name
);
if ((argt
& NOSPC
) && !(cmd
== CMD_READ
&& (forceit
|| *scan
== '!')))
if ((argt
& PLUS
) && *build
== '+')
while (*build
&& !isspace(*build
))
while (*build
&& isspace(*build
))
msg("Too many %s to \"%s\" command.",
(argt
& XFILE
) ? "filenames" : "arguments",
/* some commands have special default ranges */
if (isdfl
&& (argt
& DFLALL
))
else if (isdfl
&& (argt
& DFLNONE
))
/* write a newline if called from visual mode */
if ((argt
& NL
) && mode
!= MODE_EX
&& !exwrote
)
(*cmdnames
[cmdidx
].fn
)(frommark
, tomark
, cmd
, forceit
, scan
);
/* This function executes EX commands from a file. It returns 1 normally, or
* 0 if the file could not be opened for reading.
char *filename
; /* name of a ".exrc" file */
int fd
; /* file descriptor */
int len
; /* length of the ".exrc" file */
/* small address space - we need to conserve space */
/* !!! kludge: we use U_text as the buffer. This has the side-effect
* of interfering with the shift-U visual command. Disable shift-U.
/* small stack, but big heap. Allocate buffer from heap */
char *U_text
= (char *)malloc(4096);
/* small stack - we need to conserve space */
/* !!! kludge: we use U_text as the buffer. This has the side-effect
* of interfering with the shift-U visual command. Disable shift-U.
/* This is how we would *like* to do it -- with a large buffer on the
* stack, so we can handle large .exrc files and also recursion.
/* open the file, read it, and close */
fd
= open(filename
, O_RDONLY
);
#if TINYSTACK && (TOS || MINT)
#if TINYSTACK && (TOS || MINT)
len
= tread(fd
, U_text
, 4096);
len
= tread(fd
, U_text
, sizeof U_text
);
exstring(U_text
, len
, ctrl('V'));
#if TINYSTACK && (TOS || MINT)
/* This function executes EX commands from a string. The commands may be
* separated by newlines or by | characters. It also handles quoting.
* Each individual command is limited to 132 bytes, but the total string
void exstring(buf
, len
, qchar
)
char *buf
; /* the commands to execute */
int len
; /* the length of the string */
int qchar
; /* the quote character -- ^V for file, or \ for kbd */
char single
[133]; /* a single command */
/* find & do each command */
for (src
= buf
; src
< &buf
[len
]; src
++)
/* Copy a single command into single[]. Convert any quoted |
* into a normal |, and stop at a newline or unquoted |.
for (dest
= single
, i
= 0;
i
< 132 && src
< &buf
[len
] && *src
!= '\n' && *src
!= '|';
if (src
[0] == qchar
&& src
[1] == '|')