windows-nt/Source/XPSP1/NT/sdktools/vi/cmdline.c
2020-09-26 16:20:57 +08:00

898 lines
25 KiB
C

/* $Header: /nw/tony/src/stevie/src/RCS/cmdline.c,v 1.20 89/08/13 11:41:23 tony Exp $
*
* Routines to parse and execute "command line" commands, such as searches
* or colon commands.
*/
#include "stevie.h"
static char *altfile = NULL; /* alternate file */
static int altline; /* line # in alternate file */
static char *nowrtmsg = "No write since last change (use ! to override)";
static char *nooutfile = "No output file";
static char *morefiles = "more files to edit";
extern char **files; /* used for "n" and "rew" */
extern int numfiles, curfile;
#define CMDSZ 100 /* size of the command buffer */
bool_t rangeerr;
static bool_t doecmd(char*arg, bool_t force);
static void badcmd(void);
static void get_range(char**cp, LNPTR*lower, LNPTR*upper);
static LNPTR *get_line(char**cp);
void ex_delete(LINE *l,LINE *u);
void dolist(LINE *l,LINE *u);
extern char *lastcmd; /* in dofilter */
/*
* getcmdln() - read a command line from the terminal
*
* Reads a command line started by typing '/', '?', '!', or ':'. Returns a
* pointer to the string that was read. For searches, an optional trailing
* '/' or '?' is removed.
*/
char *
getcmdln(firstc)
char firstc;
{
static char buff[CMDSZ];
register char *p = buff;
register int c;
register char *q;
gotocmd(TRUE, firstc);
/* collect the command string, handling '\b' and @ */
do {
switch (c = vgetc()) {
default: /* a normal character */
outchar(c);
*p++ = (char)c;
break;
case BS:
if (p > buff) {
/*
* this is gross, but it relies
* only on 'gotocmd'
*/
p--;
gotocmd(TRUE, firstc);
for (q = buff; q < p ;q++)
outchar(*q);
} else {
msg("");
return NULL; /* back to cmd mode */
}
break;
#if 0
case '@': /* line kill */
p = buff;
gotocmd(TRUE, firstc);
break;
#endif
case NL: /* done reading the line */
case CR:
break;
}
} while (c != NL && c != CR);
*p = '\0';
if (firstc == '/' || firstc == '?') { /* did we do a search? */
/*
* Look for a terminating '/' or '?'. This will be the first
* one that isn't quoted. Truncate the search string there.
*/
for (p = buff; *p ;) {
if (*p == firstc) { /* we're done */
*p = '\0';
break;
} else if (*p == '\\') /* next char quoted */
p += 2;
else
p++; /* normal char */
}
}
return buff;
}
/*
* docmdln() - handle a colon command
*
* Handles a colon command received interactively by getcmdln() or from
* the environment variable "EXINIT" (or eventually .virc).
*/
void
docmdln(cmdline)
char *cmdline;
{
char buff[CMDSZ];
char cmdbuf[CMDSZ];
char argbuf[CMDSZ];
char *cmd, *arg;
register char *p;
/*
* The next two variables contain the bounds of any range given in a
* command. If no range was given, both contain null line pointers.
* If only a single line was given, u_pos will contain a null line
* pointer.
*/
LNPTR l_pos, u_pos;
/*
* Clear the range variables.
*/
l_pos.linep = (struct line *) NULL;
u_pos.linep = (struct line *) NULL;
if (cmdline == NULL)
return;
if (strlen(cmdline) > CMDSZ-2) {
msg("Error: command line too long");
return;
}
strcpy(buff, cmdline);
/* skip any initial white space */
for (cmd = buff; *cmd != NUL && isspace(*cmd) ;cmd++)
;
if (*cmd == '%') { /* change '%' to "1,$" */
strcpy(cmdbuf, "1,$"); /* kind of gross... */
strcat(cmdbuf, cmd+1);
strcpy(cmd, cmdbuf);
}
while ((p=strchr(cmd, '%')) != NULL && *(p-1) != '\\') {
/* change '%' to Filename */
if (Filename == NULL) {
emsg("No filename");
return;
}
*p= NUL;
strcpy (cmdbuf, cmd);
strcat (cmdbuf, Filename);
strcat (cmdbuf, p+1);
strcpy(cmd, cmdbuf);
msg(cmd); /*repeat */
}
while ((p=strchr(cmd, '#')) != NULL && *(p-1) != '\\') {
/* change '#' to Altname */
if (altfile == NULL) {
emsg("No alternate file");
return;
}
*p= NUL;
strcpy (cmdbuf, cmd);
strcat (cmdbuf, altfile);
strcat (cmdbuf, p+1);
strcpy(cmd, cmdbuf);
msg(cmd); /*repeat */
}
/*
* Parse a range, if present (and update the cmd pointer).
*/
rangeerr = FALSE;
get_range(&cmd, &l_pos, &u_pos);
if(rangeerr) {
return;
}
if (l_pos.linep != NULL) {
if (LINEOF(&l_pos) > LINEOF(&u_pos)) {
emsg("Invalid range");
return;
}
}
strcpy(cmdbuf, cmd); /* save the unmodified command */
/* isolate the command and find any argument */
for ( p=cmd; *p != NUL && ! isspace(*p); p++ )
;
if ( *p == NUL )
arg = NULL;
else {
*p = NUL;
for (p++; *p != NUL && isspace(*p) ;p++)
;
if (*p == NUL)
arg = NULL;
else {
strcpy(argbuf, p);
arg = argbuf;
}
}
if (strcmp(cmd,"q!") == 0)
getout();
if (strcmp(cmd,"q") == 0) {
if (Changed)
emsg(nowrtmsg);
else {
if ((curfile + 1) < numfiles)
emsg(morefiles);
else
getout();
}
return;
}
if ((strcmp(cmd,"w") == 0) ||
(strcmp(cmd,"w!") == 0)) {
if (arg == NULL) {
if (Filename != NULL) {
writeit(Filename, &l_pos, &u_pos);
} else
emsg(nooutfile);
}
else {
if (altfile)
free(altfile);
altfile = strsave(arg);
writeit(arg, &l_pos, &u_pos);
}
return;
}
if (strcmp(cmd,"wq") == 0) {
if (Filename != NULL) {
if (writeit(Filename, (LNPTR *)NULL, (LNPTR *)NULL))
getout();
} else
emsg(nooutfile);
return;
}
if (strcmp(cmd, "x") == 0) {
doxit();
return;
}
if (strcmp(cmd,"f") == 0 && arg == NULL) {
fileinfo();
return;
}
if (*cmd == 'n') {
if ((curfile + 1) < numfiles) {
/*
* stuff ":e[!] FILE\n"
*/
stuffin(":e");
if (cmd[1] == '!')
stuffin("!");
stuffin(" ");
stuffin(files[++curfile]);
stuffin("\n");
} else
emsg("No more files!");
return;
}
if (*cmd == 'N') {
if (curfile > 0) {
/*
* stuff ":e[!] FILE\n"
*/
stuffin(":e");
if (cmd[1] == '!')
stuffin("!");
stuffin(" ");
stuffin(files[--curfile]);
stuffin("\n");
} else
emsg("No more files!");
return;
}
if(*cmd == 'l' || !strncmp(cmd,"li",2)) {
if(arg != NULL) {
msg("extra characters at end of \"list\" command");
} else {
dolist(l_pos.linep,u_pos.linep);
}
return;
}
if (strncmp(cmd, "rew", 3) == 0) {
if (numfiles <= 1) /* nothing to rewind */
return;
curfile = 0;
/*
* stuff ":e[!] FILE\n"
*/
stuffin(":e");
if (cmd[3] == '!')
stuffin("!");
stuffin(" ");
stuffin(files[0]);
stuffin("\n");
return;
}
if (strcmp(cmd,"e") == 0 || strcmp(cmd,"e!") == 0) {
(void) doecmd(arg, cmd[1] == '!');
return;
}
/*
* The command ":e#" gets expanded to something like ":efile", so
* detect that case here.
*/
if (*cmd == 'e' && arg == NULL) {
if (cmd[1] == '!')
(void) doecmd(&cmd[2], TRUE);
else
(void) doecmd(&cmd[1], FALSE);
return;
}
if (strcmp(cmd,"f") == 0) {
Filename = strsave(arg);
setviconsoletitle();
filemess("");
return;
}
if (strcmp(cmd,"r") == 0) {
if (arg == NULL) {
badcmd();
return;
}
if (readfile(arg, Curschar, 1)) {
emsg("Can't open file");
return;
}
updatescreen();
CHANGED;
return;
}
if (*cmd == 'd') {
if(arg != NULL) {
msg("extra characters at end of \"delete\" command");
} else {
ex_delete(l_pos.linep,u_pos.linep);
}
return;
}
if (strcmp(cmd,"=") == 0) {
smsg("%d", cntllines(Filemem, &l_pos));
return;
}
if (strncmp(cmd,"ta", 2) == 0) {
dotag(arg, cmd[2] == '!');
return;
}
if (strncmp(cmd,"set", 2) == 0) {
doset(arg);
return;
}
if (strcmp(cmd,"help") == 0) {
if (help()) {
screenclear();
updatescreen();
}
return;
}
if (strncmp(cmd, "ve", 2) == 0) {
extern char *Version;
msg(Version);
return;
}
if (strcmp(cmd, "sh") == 0) {
doshell(NULL, FALSE);
return;
}
if (strcmp(cmd, "source") == 0 ||
strcmp(cmd, "so") == 0) {
if(l_pos.linep != NULL) {
emsg("No range allowed on this command");
} else {
dosource(arg,TRUE);
}
return;
}
if (*cmd == '!' || *cmd == '@') {
if (*(cmd+1) == *cmd) {
if (lastcmd == (char*)NULL) {
emsg("No previous command");
return;
}
msg(lastcmd);
doshell(lastcmd, *cmd == '@');
}
else {
doshell(cmdbuf+1, *cmd == '@');
if (lastcmd == (char*)NULL)
lastcmd = (char*)alloc(CMDSZ);
strcpy(lastcmd, cmdbuf+1);
}
return;
}
if (strncmp(cmd, "s/", 2) == 0) {
dosub(&l_pos, &u_pos, cmdbuf+1);
return;
}
if (strncmp(cmd, "g/", 2) == 0) {
doglob(&l_pos, &u_pos, cmdbuf+1);
return;
}
if (strcmp(cmd, "cd") == 0) {
dochdir(arg);
return;
}
/*
* If we got a line, but no command, then go to the line.
*/
if (*cmd == NUL && l_pos.linep != NULL) {
*Curschar = l_pos;
return;
}
badcmd();
}
void doxit()
{
if (Changed) {
if (Filename != NULL) {
if (!writeit(Filename, (LNPTR *)NULL, (LNPTR *)NULL))
return;
} else {
emsg(nooutfile);
return;
}
}
if ((curfile + 1) < numfiles)
emsg(morefiles);
else
getout();
}
void dosource(char *arg,bool_t giveerror)
{
FILE *f;
char string[256];
if(arg == NULL) {
emsg("No filename given");
return;
}
if((f = fopen(arg,"r")) == NULL) {
if(giveerror) {
emsg("No such file or error opening file");
}
} else {
while(fgets(string,sizeof(string),f) != NULL) {
docmdln(string);
}
}
}
void ex_delete(LINE *l,LINE *u)
{
int ndone = 0;
LINE *cp;
LINE *np;
LNPTR savep;
if (l == NULL) { // no address? use current line.
l = u = Curschar->linep;
}
u_save(l->prev,u->next); // save for undo
for(cp = l; cp != NULL && !got_int; cp = np) {
np = cp->next; // set next before we delete the line
if(Curschar->linep != cp) {
savep = *Curschar;
Curschar->linep = cp;
Curschar->index = 0;
delline(1,FALSE);
*Curschar = savep;
} else {
delline(1,FALSE);
}
ndone++;
if(cp == u) {
break;
}
}
updatescreen();
if((ndone >= P(P_RP)) || got_int) {
smsg("%s%d fewer line%c",
got_int ? "Interrupt: " : "",
ndone,
ndone == 1 ? ' ' : 's');
}
}
void dolist(LINE *l,LINE *u)
{
LINE *cp;
char ch;
char *txt;
if(l == NULL) {
l = u = Curschar->linep;
}
puts(""); // scroll one line
for(cp = l; cp != NULL && !got_int; cp = cp->next) {
for(txt = cp->s,ch = *txt; ch; ch = *(++txt)) {
if(chars[ch].ch_size > 1) {
outstr(chars[ch].ch_str);
} else {
outchar(ch);
}
}
outstr("$\n");
if(cp == u) {
break;
}
}
if(got_int) {
puts("Interrupt");
}
wait_return();
}
/*
* get_range - parse a range specifier
*
* Ranges are of the form:
*
* addr[,addr]
*
* where 'addr' is:
*
* $ [+- NUM]
* 'x [+- NUM] (where x denotes a currently defined mark)
* . [+- NUM]
* NUM
*
* The pointer *cp is updated to point to the first character following
* the range spec. If an initial address is found, but no second, the
* upper bound is equal to the lower.
*/
static void
get_range(cp, lower, upper)
register char **cp;
LNPTR *lower, *upper;
{
register LNPTR *l;
register char *p;
if ((l = get_line(cp)) == NULL)
return;
*lower = *l;
for (p = *cp; *p != NUL && isspace(*p) ;p++)
;
*cp = p;
if (*p != ',') { /* is there another line spec ? */
*upper = *lower;
return;
}
*cp = ++p;
if ((l = get_line(cp)) == NULL) {
*upper = *lower;
return;
}
*upper = *l;
}
static LNPTR *
get_line(cp)
char **cp;
{
static LNPTR pos;
LNPTR *lp;
register char *p, c;
register int lnum;
pos.index = 0; /* shouldn't matter... check back later */
p = *cp;
/*
* Determine the basic form, if present.
*/
switch (c = *p++) {
case '$':
pos.linep = Fileend->linep->prev;
break;
case '.':
pos.linep = Curschar->linep;
break;
case '\'':
if ((lp = getmark(*p++)) == NULL) {
emsg("Unknown mark");
rangeerr = TRUE;
return (LNPTR *) NULL;
}
pos = *lp;
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
for (lnum = c - '0'; isdigit(*p) ;p++)
lnum = (lnum * 10) + (*p - '0');
pos = *gotoline(lnum);
break;
default:
return (LNPTR *) NULL;
}
while (*p != NUL && isspace(*p))
p++;
if (*p == '-' || *p == '+') {
bool_t neg = (*p++ == '-');
for (lnum = 0; isdigit(*p) ;p++)
lnum = (lnum * 10) + (*p - '0');
if (neg)
lnum = -lnum;
pos = *gotoline( cntllines(Filemem, &pos) + lnum );
}
*cp = p;
return &pos;
}
static void
badcmd()
{
emsg("Unrecognized command");
}
#define LSIZE 256 /* max. size of a line in the tags file */
/*
* dotag(tag, force) - goto tag
*/
void
dotag(tag, force)
char *tag;
bool_t force;
{
FILE *tp;
char lbuf[LSIZE]; /* line buffer */
char pbuf[LSIZE]; /* search pattern buffer */
bool_t match;
register char *fname, *str;
register char *p;
if ((tp = fopen("tags", "r")) == NULL) {
emsg("Can't open tags file");
return;
}
while (fgets(lbuf, LSIZE, tp) != NULL) {
if (lbuf[0] == ';') {
/* Allow comment line. */
continue;
}
if ((fname = strchr(lbuf, TAB)) == NULL) {
emsg("Format error in tags file");
return;
}
*fname++ = '\0';
if ((str = strchr(fname, TAB)) == NULL) {
emsg("Format error in tags file");
return;
}
*str++ = '\0';
if (P(P_IC)) {
match = _stricmp(lbuf, tag) == 0;
} else {
match = strcmp(lbuf, tag) == 0;
}
if (match) {
/*
* Scan through the search string. If we see a magic
* char, we have to quote it. This lets us use "real"
* implementations of ctags.
*/
p = pbuf;
*p++ = *str++; /* copy the '/' or '?' */
*p++ = *str++; /* copy the '^' */
for (; *str != NUL ;str++) {
if (*str == '\\') {
*p++ = *str++;
*p++ = *str;
} else if (strchr("/?", *str) != NULL) {
if (str[1] != '\n') {
*p++ = '\\';
*p++ = *str;
} else
*p++ = *str;
} else if (strchr("^()*.", *str) != NULL) {
*p++ = '\\';
*p++ = *str;
} else
*p++ = *str;
}
*p = NUL;
/*
* This looks out of order, but by calling stuffin()
* before doecmd() we keep an extra screen update
* from occuring. This stuffins() have no effect
* until we get back to the main loop, anyway.
*/
stuffin(pbuf); /* str has \n at end */
stuffin("\007"); /* CTRL('g') */
if (doecmd(fname, force)) {
fclose(tp);
return;
} else
stuffin(NULL); /* clear the input */
}
}
emsg("tag not found");
fclose(tp);
}
static bool_t
doecmd(arg, force)
char *arg;
bool_t force;
{
int line = 1; /* line # to go to in new file */
if (!force && Changed) {
emsg(nowrtmsg);
if ( arg != NULL ) {
if (altfile)
free(altfile);
altfile = strsave(arg);
}
return FALSE;
}
if (arg != NULL) {
/*
* First detect a ":e" on the current file. This is mainly
* for ":ta" commands where the destination is within the
* current file.
*/
if (Filename != NULL && strcmp(arg, Filename) == 0) {
if (!Changed || (Changed && !force))
return TRUE;
}
if (altfile) {
if (strcmp (arg, altfile) == 0)
line = altline;
free(altfile);
}
altfile = Filename;
altline = cntllines(Filemem, Curschar);
Filename = strsave(arg);
}
if (Filename == NULL) {
emsg("No filename");
return FALSE;
}
/* clear mem and read file */
freeall();
filealloc();
UNCHANGED;
if (readfile(Filename, Filemem, 0))
filemess("[New File]");
setviconsoletitle();
*Topchar = *Curschar;
if (line != 1) {
stuffnum(line);
stuffin("G");
}
do_mlines();
setpcmark();
updatescreen();
return TRUE;
}
void
gotocmd(clr, firstc)
bool_t clr;
char firstc;
{
windgoto(Rows-1,0);
if (clr)
EraseLine(); /* clear the bottom line */
if (firstc)
outchar(firstc);
}
/*
* msg(s) - displays the string 's' on the status line
*/
void
msg(s)
char *s;
{
gotocmd(TRUE, 0);
outstr(s);
flushbuf();
}
/*VARARGS1*/
void
smsg(s, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16)
char *s;
int a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16;
{
char sbuf[256]; /* Status line, > 80 chars to allow wrap. */
sprintf(sbuf, s,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16);
msg(sbuf);
}
/*
* emsg() - display an error message
*
* Rings the bell, if appropriate, and calls message() to do the real work
*/
void
emsg(s)
char *s;
{
if (P(P_EB))
beep();
msg(s);
}
int
wait_return0()
{
register char c;
if (got_int)
outstr("Interrupt: ");
outstr("Press RETURN to continue");
do {
c = (char)vgetc();
} while (c != CR && c != NL && c != ' ' && c != ':');
return c;
}
void
wait_return()
{
char c = (char)wait_return0();
if (c == ':') {
outchar(NL);
docmdln(getcmdln(c));
} else
screenclear();
updatescreen();
}