/* ex.c */ /* Author: * Steve Kirkendall * 14407 SW Teal Blvd. #C * Beaverton, OR 97005 * kirkenda@cs.pdx.edu */ /* This file contains the code for reading ex commands. */ #include "config.h" #include "ctype.h" #include "vi.h" /* This data type is used to describe the possible argument combinations */ typedef short ARGT; #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") */ static struct { 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 */ } cmdnames[] = { /* cmd name cmd code function arguments */ {"append", CMD_APPEND, cmd_append, FROM+ZERO+BANG }, #ifdef DEBUG {"bug", CMD_DEBUG, cmd_debug, RANGE+BANG+EXTRA+NL}, #endif {"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 }, {"print", CMD_PRINT, cmd_print, RANGE+NL }, {"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 }, #ifndef NO_AT {"@", CMD_AT, cmd_at, EXTRA }, #endif #ifndef NO_ABBR {"abbreviate", CMD_ABBR, cmd_map, EXRCOK+BANG+EXTRA}, #endif {"args", CMD_ARGS, cmd_args, EXRCOK+NAMEDFS }, #ifndef NO_ERRLIST {"cc", CMD_CC, cmd_make, BANG+FILES }, #endif {"cd", CMD_CD, cmd_cd, EXRCOK+BANG+NAMEDF}, {"copy", CMD_COPY, cmd_move, RANGE+EXTRA }, #ifndef NO_DIGRAPH {"digraph", CMD_DIGRAPH, cmd_digraph, EXRCOK+BANG+EXTRA}, #endif #ifndef NO_ERRLIST {"errlist", CMD_ERRLIST, cmd_errlist, BANG+NAMEDF }, #endif {"ex", CMD_EDIT, cmd_edit, BANG+FILE1 }, {"mark", CMD_MARK, cmd_mark, FROM+WORD1 }, #ifndef NO_MKEXRC {"mkexrc", CMD_MKEXRC, cmd_mkexrc, NAMEDF }, #endif {"number", CMD_NUMBER, cmd_print, RANGE+NL }, {"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 }, #ifdef SIGTSTP {"stop", CMD_STOP, cmd_suspend, NONE }, #endif {"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 }, #ifdef DEBUG {"debug", CMD_DEBUG, cmd_debug, RANGE+BANG+EXTRA+NL}, {"validate", CMD_VALIDATE, cmd_validate, BANG+NL }, #endif {"chdir", CMD_CD, cmd_cd, EXRCOK+BANG+NAMEDF}, #ifndef NO_COLOR {"color", CMD_COLOR, cmd_color, EXRCOK+EXTRA }, #endif #ifndef NO_ERRLIST {"make", CMD_MAKE, cmd_make, BANG+NAMEDFS }, #endif {"map", CMD_MAP, cmd_map, EXRCOK+BANG+EXTRA}, {"previous", CMD_PREVIOUS, cmd_next, BANG }, {"rewind", CMD_REWIND, cmd_next, BANG }, #ifdef SIGTSTP {"suspend", CMD_SUSPEND, cmd_suspend, NONE }, #endif {"unmap", CMD_UNMAP, cmd_map, EXRCOK+BANG+EXTRA}, #ifndef NO_ABBR {"unabbreviate",CMD_UNABBR, cmd_map, EXRCOK+WORD1 }, #endif {(char *)0} }; /* 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. */ char *parseptrn(ptrn) REG char *ptrn; { REG char *scan; for (scan = ptrn + 1; *scan && *scan != *ptrn; scan++) { /* allow backslashed versions of / and ? in the pattern */ if (*scan == '\\' && scan[1] != '\0') { scan++; } } if (*scan) { *scan++ = '\0'; } return scan; } /* 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 */ { long num; REG char *t; /* parse each ;-delimited clause of this linespec */ do { /* skip an initial ';', if any */ if (*s == ';') { s++; } /* skip leading spaces */ while (isspace(*s)) { s++; } /* dot means current position */ if (*s == '.') { s++; *markptr = cursor; } /* '$' means the last line */ else if (*s == '$') { s++; *markptr = MARK_LAST; } /* digit means an absolute line number */ else if (isdigit(*s)) { for (num = 0; isdigit(*s); s++) { num = num * 10 + *s - '0'; } *markptr = MARK_AT_LINE(num); } /* appostrophe means go to a set mark */ else if (*s == '\'') { s++; *markptr = m_tomark(cursor, 1L, (int)*s); s++; } /* slash means do a search */ else if (*s == '/' || *s == '?') { /* put a '\0' at the end of the search pattern */ t = parseptrn(s); /* search for the pattern */ *markptr &= ~(BLKSIZE - 1); if (*s == '/') { pfetch(markline(*markptr)); if (plen > 0) *markptr += plen - 1; *markptr = m_fsrch(*markptr, s); } else { *markptr = m_bsrch(*markptr, s); } /* adjust command string pointer */ s = t; } /* if linespec was faulty, quit now */ if (!*markptr) { return s; } /* maybe add an offset */ t = s; if (*t == '-' || *t == '+') { s++; for (num = 0; isdigit(*s); s++) { num = num * 10 + *s - '0'; } if (num == 0) { num = 1; } *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); *markptr = MARK_UNSET; } return s; } /* This function reads an ex command and executes it. */ void ex() { char cmdbuf[150]; REG int cmdlen; static long oldline; significant = FALSE; oldline = markline(cursor); while (mode == MODE_EX) { /* read a line */ #ifdef CRUNCH cmdlen = vgets(':', cmdbuf, sizeof(cmdbuf)); #else cmdlen = vgets(*o_prompt ? ':' : '\0', cmdbuf, sizeof(cmdbuf)); #endif if (cmdlen < 0) { return; } /* if empty line, assume ".+1" */ if (cmdlen == 0) { strcpy(cmdbuf, ".+1"); qaddch('\r'); clrtoeol(); } else { addch('\n'); } refresh(); /* parse & execute the command */ doexcmd(cmdbuf); /* handle autoprint */ if (significant || markline(cursor) != oldline) { significant = FALSE; oldline = markline(cursor); if (*o_autoprint && mode == MODE_EX) { cmd_print(cursor, cursor, CMD_PRINT, FALSE, ""); } } } } void doexcmd(cmdbuf) 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 */ U_line = 0L; /* permit extra colons at the start of the line */ for (; *cmdbuf == ':'; cmdbuf++) { } /* ignore command lines that start with a double-quote */ if (*cmdbuf == '"') { return; } scan = cmdbuf; /* parse the line specifier */ if (nlines < 1) { /* no file, so don't allow addresses */ } else if (*scan == '%') { /* '%' means all lines */ frommark = MARK_FIRST; tomark = MARK_LAST; scan++; } else if (*scan == '0') { frommark = tomark = MARK_UNSET; scan++; } else { frommark = cursor; scan = linespec(scan, &frommark); tomark = frommark; if (frommark && *scan == ',') { scan++; scan = linespec(scan, &tomark); } if (!tomark) { /* faulty line spec -- fault already described */ return; } if (frommark > tomark) { msg("first address exceeds the second"); return; } } isdfl = (scan == cmdbuf); /* skip whitespace */ while (isspace(*scan)) { scan++; } /* if no command, then just move the cursor to the mark */ if (!*scan) { if (tomark != MARK_UNSET) cursor = tomark; return; } /* figure out how long the command name is */ if (!isalpha(*scan)) { cmdlen = 1; } else { for (cmdlen = 1; isalpha(scan[cmdlen]); cmdlen++) { } } /* lookup the command code */ for (cmdidx = 0; cmdnames[cmdidx].name && strncmp(scan, cmdnames[cmdidx].name, cmdlen); cmdidx++) { } argt = cmdnames[cmdidx].argt; cmd = cmdnames[cmdidx].code; if (cmd == CMD_NULL) { msg("Unknown command \"%.*s\"", cmdlen, scan); return; } /* !!! if the command doesn't have NOBAR set, then replace | with \0 */ /* if the command ended with a bang, set the forceit flag */ scan += cmdlen; if ((argt & BANG) && *scan == '!') { scan++; forceit = 1; } else { forceit = 0; } /* skip any more whitespace, to leave scan pointing to arguments */ while (isspace(*scan)) { scan++; } /* a couple of special cases for filenames */ if (argt & XFILE) { /* if names were given, process them */ if (*scan) { for (build = tmpblk.c, iswild = didsub = FALSE; *scan; scan++) { switch (*scan) { case '\\': if (scan[1] == '\\' || scan[1] == '%' || scan[1] == '#') { *build++ = *++scan; } else { *build++ = '\\'; } break; case '%': if (!*origname) { msg("No filename to substitute for %%"); return; } strcpy(build, origname); while (*build) { build++; } didsub = TRUE; break; case '#': if (!*prevorig) { msg("No filename to substitute for #"); return; } strcpy(build, prevorig); while (*build) { build++; } didsub = TRUE; break; case '*': case '?': #if !(MSDOS || TOS) case '[': case '`': case '{': /* } */ case '$': case '~': #endif *build++ = *scan; iswild = TRUE; break; default: *build++ = *scan; } } *build = '\0'; if (cmd == CMD_BANG || cmd == CMD_READ && tmpblk.c[0] == '!' || cmd == CMD_WRITE && tmpblk.c[0] == '!') { if (didsub) { if (mode != MODE_EX) { addch('\n'); } addstr(tmpblk.c); addch('\n'); exrefresh(); } } else { if (iswild && tmpblk.c[0] != '>') { scan = wildcard(tmpblk.c); } } } else /* no names given, maybe assume origname */ { if (!(argt & NODFL)) { strcpy(tmpblk.c, origname); } else { *tmpblk.c = '\0'; } } scan = tmpblk.c; } /* bad arguments? */ if (!(argt & EXRCOK) && nlines < 1L) { msg("Can't use the \"%s\" command in a %s file", cmdnames[cmdidx].name, EXRC); return; } if (!(argt & (ZERO | EXRCOK)) && frommark == MARK_UNSET) { msg("Can't use address 0 with \"%s\" command.", cmdnames[cmdidx].name); return; } if (!(argt & FROM) && frommark != cursor && nlines >= 1L) { msg("Can't use address with \"%s\" command.", cmdnames[cmdidx].name); return; } if (!(argt & TO) && tomark != frommark && nlines >= 1L) { msg("Can't use a range with \"%s\" command.", cmdnames[cmdidx].name); return; } if (!(argt & EXTRA) && *scan) { msg("Extra characters after \"%s\" command.", cmdnames[cmdidx].name); return; } if ((argt & NOSPC) && !(cmd == CMD_READ && (forceit || *scan == '!'))) { build = scan; #ifndef CRUNCH if ((argt & PLUS) && *build == '+') { while (*build && !isspace(*build)) { build++; } while (*build && isspace(*build)) { build++; } } #endif /* not CRUNCH */ for (; *build; build++) { if (isspace(*build)) { msg("Too many %s to \"%s\" command.", (argt & XFILE) ? "filenames" : "arguments", cmdnames[cmdidx].name); return; } } } /* some commands have special default ranges */ if (isdfl && (argt & DFLALL)) { frommark = MARK_FIRST; tomark = MARK_LAST; } else if (isdfl && (argt & DFLNONE)) { frommark = tomark = 0L; } /* write a newline if called from visual mode */ if ((argt & NL) && mode != MODE_EX && !exwrote) { addch('\n'); exrefresh(); } /* act on the command */ (*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. */ int doexrc(filename) char *filename; /* name of a ".exrc" file */ { int fd; /* file descriptor */ int len; /* length of the ".exrc" file */ /* !!! kludge: we use U_text as the buffer. This has the side-effect * of interfering with the shift-U visual command. Disable shift-U. */ U_line = 0L; /* open the file, read it, and close */ fd = open(filename, O_RDONLY); if (fd < 0) { return 0; } len = tread(fd, U_text, BLKSIZE); close(fd); /* execute the string */ exstring(U_text, len, ctrl('V')); return 1; } /* 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 * may be longer. */ 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 */ char *src, *dest; int i; /* 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 != '|'; src++, i++) { if (src[0] == qchar && src[1] == '|') { src++; } *dest++ = *src; } *dest = '\0'; /* do it */ doexcmd(single); } }