📄 ex.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 lintstatic char sccsid[] = "@(#)ex.c 8.117 (Berkeley) 4/13/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 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 *));#define DEFCOM ".+1"/* * ex -- * Read an ex command and execute it. */intex(sp, ep) SCR *sp; EXF *ep;{ enum input irval; TEXT *tp; u_int flags, saved_mode; int eval; char defcom[sizeof(DEFCOM)]; 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); if (O_ISSET(sp, O_BEAUTIFY)) LF_SET(TXT_BEAUTIFY); if (O_ISSET(sp, O_PROMPT)) LF_SET(TXT_PROMPT); for (eval = 0;; ++sp->if_lno) { /* * Get the next command. Interrupt flag manipulation is safe * because ex_icmd clears them all. */ F_CLR(sp, S_INTERRUPTED); F_SET(sp, S_INTERRUPTIBLE); irval = sp->s_get(sp, ep, sp->tiqp, ':', flags); if (F_ISSET(sp, S_INTERRUPTED)) { (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); goto ret; } saved_mode = F_ISSET(sp, S_SCREENS | S_MAJOR_CHANGE); tp = sp->tiqp->cqh_first; if (tp->len == 0) { if (F_ISSET(sp->gp, G_STDIN_TTY)) { /* Special case \r command. */ (void)fputc('\r', stdout); (void)fflush(stdout); } memmove(defcom, DEFCOM, sizeof(DEFCOM)); if (ex_icmd(sp, ep, defcom, sizeof(DEFCOM) - 1) && !F_ISSET(sp->gp, G_STDIN_TTY)) F_SET(sp, S_EXIT_FORCE); } else { if (F_ISSET(sp->gp, G_STDIN_TTY)) /* Special case ^D command. */ if (tp->len == 1 && tp->lb[0] == '\004') { (void)fputc('\r', stdout); (void)fflush(stdout); } else (void)fputc('\n', stdout); if (ex_icmd(sp, ep, tp->lb, tp->len) && !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. */intex_cfile(sp, ep, filename) SCR *sp; EXF *ep; char *filename;{ 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); 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. */intex_icmd(sp, ep, cmd, len) SCR *sp; EXF *ep; char *cmd; size_t len;{ /* * 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. */ F_CLR(sp, S_INTERRUPTED | S_INTERRUPTIBLE); return (ex_cmd(sp, ep, cmd, len));}/* 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"};/* * ex_cmd -- * Parse and execute a string containing ex commands. */intex_cmd(sp, ep, cmd, cmdlen) SCR *sp; EXF *ep; char *cmd; size_t cmdlen;{ 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 ch, cnt, delim, flags, namelen, nl, uselastcmd, tmp; char *arg1, *save_cmd, *p, *t; /* Init. */ nl = 0;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 (F_ISSET(sp, S_INTERRUPTED)) return (0); /* Skip whitespace, separators, newlines. */ for (; cmdlen > 0; ++cmd, --cmdlen) if ((ch = *cmd) == '\n') ++sp->if_lno; else if (!isblank(ch)) break; if (cmdlen == 0) return (0); /* Command lines that start with a double-quote are comments. */ if (ch == '"') { while (--cmdlen > 0 && *++cmd != '\n'); if (*cmd == '\n') { ++cmd; --cmdlen; ++sp->if_lno; } goto loop; } /* * !!! * 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 (ch == ':') while (--cmdlen > 0 && *++cmd == ':'); /* Skip whitespace. */ for (; cmdlen > 0; ++cmd, --cmdlen) { ch = *cmd; if (!isblank(ch)) break; } /* The last point at which an empty line means do nothing. */ if (cmdlen == 0) 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; /* 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. */#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; } } /* * 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) if (p[0] == 'k' && p[1] && !p[2]) { cmd -= namelen - 1; cmdlen += namelen - 1; cp = &cmds[C_K]; } else if (p[0] == 's') { cmd -= namelen - 1; cmdlen += namelen - 1; cp = &cmd_subagain; } else { msgq(sp, M_ERR, "The %.*s command is unknown.", namelen, p); goto err; } /* Some commands are either not implemented or turned off. */ 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]; uselastcmd = 0; } else { cp = exp->lastcmd; uselastcmd = 1; } /* 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 and visual in vi mode commands 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, * and substitute commands did not 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_VISUAL_VI]) { /* * Move to the next non-whitespace character. * The first '!' is eaten as a force flag. */ for (tmp = 0; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (!isblank(ch)) { if (!tmp && ch == '!') { tmp = 1; F_SET(&exc, E_FORCE); /* Reset, don't reparse. */ save_cmd = cmd + 1; continue; } break; } }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -