From a6a4f07746b676691400c41fa4f116251050ba7f Mon Sep 17 00:00:00 2001 From: sef Date: Tue, 16 Aug 1994 23:36:45 +0000 Subject: Latest public release of nvi, from Keith Bostic. I hope I got this right ;). Reviewed by: Sean Eric Fagan --- usr.bin/vi/ex/ex.c | 1866 +++++++++++++++++++++++++++++++++++++++++++ usr.bin/vi/ex/ex_abbrev.c | 129 +++ usr.bin/vi/ex/ex_append.c | 220 +++++ usr.bin/vi/ex/ex_args.c | 263 ++++++ usr.bin/vi/ex/ex_argv.c | 609 ++++++++++++++ usr.bin/vi/ex/ex_at.c | 118 +++ usr.bin/vi/ex/ex_bang.c | 242 ++++++ usr.bin/vi/ex/ex_cd.c | 223 ++++++ usr.bin/vi/ex/ex_delete.c | 92 +++ usr.bin/vi/ex/ex_digraph.c | 324 ++++++++ usr.bin/vi/ex/ex_display.c | 169 ++++ usr.bin/vi/ex/ex_edit.c | 122 +++ usr.bin/vi/ex/ex_equal.c | 86 ++ usr.bin/vi/ex/ex_exit.c | 79 ++ usr.bin/vi/ex/ex_file.c | 103 +++ usr.bin/vi/ex/ex_global.c | 400 ++++++++++ usr.bin/vi/ex/ex_init.c | 202 +++++ usr.bin/vi/ex/ex_join.c | 200 +++++ usr.bin/vi/ex/ex_map.c | 160 ++++ usr.bin/vi/ex/ex_mark.c | 66 ++ usr.bin/vi/ex/ex_mkexrc.c | 130 +++ usr.bin/vi/ex/ex_move.c | 222 +++++ usr.bin/vi/ex/ex_open.c | 75 ++ usr.bin/vi/ex/ex_preserve.c | 128 +++ usr.bin/vi/ex/ex_print.c | 212 +++++ usr.bin/vi/ex/ex_put.c | 78 ++ usr.bin/vi/ex/ex_read.c | 300 +++++++ usr.bin/vi/ex/ex_screen.c | 155 ++++ usr.bin/vi/ex/ex_script.c | 582 ++++++++++++++ usr.bin/vi/ex/ex_set.c | 70 ++ usr.bin/vi/ex/ex_shell.c | 150 ++++ usr.bin/vi/ex/ex_shift.c | 204 +++++ usr.bin/vi/ex/ex_source.c | 66 ++ usr.bin/vi/ex/ex_stop.c | 76 ++ usr.bin/vi/ex/ex_subst.c | 1001 +++++++++++++++++++++++ usr.bin/vi/ex/ex_tag.c | 905 +++++++++++++++++++++ usr.bin/vi/ex/ex_undo.c | 103 +++ usr.bin/vi/ex/ex_usage.c | 197 +++++ usr.bin/vi/ex/ex_util.c | 189 +++++ usr.bin/vi/ex/ex_version.c | 71 ++ usr.bin/vi/ex/ex_visual.c | 137 ++++ usr.bin/vi/ex/ex_write.c | 327 ++++++++ usr.bin/vi/ex/ex_yank.c | 69 ++ usr.bin/vi/ex/ex_z.c | 180 +++++ usr.bin/vi/ex/excmd.awk | 6 + usr.bin/vi/ex/excmd.c | 458 +++++++++++ usr.bin/vi/ex/excmd.h.stub | 285 +++++++ usr.bin/vi/ex/filter.c | 414 ++++++++++ usr.bin/vi/ex/script.h | 45 ++ usr.bin/vi/ex/tag.h | 58 ++ 50 files changed, 12566 insertions(+) create mode 100644 usr.bin/vi/ex/ex.c create mode 100644 usr.bin/vi/ex/ex_abbrev.c create mode 100644 usr.bin/vi/ex/ex_append.c create mode 100644 usr.bin/vi/ex/ex_args.c create mode 100644 usr.bin/vi/ex/ex_argv.c create mode 100644 usr.bin/vi/ex/ex_at.c create mode 100644 usr.bin/vi/ex/ex_bang.c create mode 100644 usr.bin/vi/ex/ex_cd.c create mode 100644 usr.bin/vi/ex/ex_delete.c create mode 100644 usr.bin/vi/ex/ex_digraph.c create mode 100644 usr.bin/vi/ex/ex_display.c create mode 100644 usr.bin/vi/ex/ex_edit.c create mode 100644 usr.bin/vi/ex/ex_equal.c create mode 100644 usr.bin/vi/ex/ex_exit.c create mode 100644 usr.bin/vi/ex/ex_file.c create mode 100644 usr.bin/vi/ex/ex_global.c create mode 100644 usr.bin/vi/ex/ex_init.c create mode 100644 usr.bin/vi/ex/ex_join.c create mode 100644 usr.bin/vi/ex/ex_map.c create mode 100644 usr.bin/vi/ex/ex_mark.c create mode 100644 usr.bin/vi/ex/ex_mkexrc.c create mode 100644 usr.bin/vi/ex/ex_move.c create mode 100644 usr.bin/vi/ex/ex_open.c create mode 100644 usr.bin/vi/ex/ex_preserve.c create mode 100644 usr.bin/vi/ex/ex_print.c create mode 100644 usr.bin/vi/ex/ex_put.c create mode 100644 usr.bin/vi/ex/ex_read.c create mode 100644 usr.bin/vi/ex/ex_screen.c create mode 100644 usr.bin/vi/ex/ex_script.c create mode 100644 usr.bin/vi/ex/ex_set.c create mode 100644 usr.bin/vi/ex/ex_shell.c create mode 100644 usr.bin/vi/ex/ex_shift.c create mode 100644 usr.bin/vi/ex/ex_source.c create mode 100644 usr.bin/vi/ex/ex_stop.c create mode 100644 usr.bin/vi/ex/ex_subst.c create mode 100644 usr.bin/vi/ex/ex_tag.c create mode 100644 usr.bin/vi/ex/ex_undo.c create mode 100644 usr.bin/vi/ex/ex_usage.c create mode 100644 usr.bin/vi/ex/ex_util.c create mode 100644 usr.bin/vi/ex/ex_version.c create mode 100644 usr.bin/vi/ex/ex_visual.c create mode 100644 usr.bin/vi/ex/ex_write.c create mode 100644 usr.bin/vi/ex/ex_yank.c create mode 100644 usr.bin/vi/ex/ex_z.c create mode 100644 usr.bin/vi/ex/excmd.awk create mode 100644 usr.bin/vi/ex/excmd.c create mode 100644 usr.bin/vi/ex/excmd.h.stub create mode 100644 usr.bin/vi/ex/filter.c create mode 100644 usr.bin/vi/ex/script.h create mode 100644 usr.bin/vi/ex/tag.h (limited to 'usr.bin/vi/ex') diff --git a/usr.bin/vi/ex/ex.c b/usr.bin/vi/ex/ex.c new file mode 100644 index 0000000..c0455a1 --- /dev/null +++ b/usr.bin/vi/ex/ex.c @@ -0,0 +1,1866 @@ +/*- + * 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.155 (Berkeley) 8/14/94"; +#endif /* not lint */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#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 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 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 characters + * were the same as a single , 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 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 puts out a \n, + * but the command : puts out a \r.) If the line is empty except + * for s, or , we'll probably want to + * output \r. I don't think there's any way to get 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 separated) + * command separators were very strange. For example, the command + * |||, 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. '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 '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 s. + */ + if (*p == '!') { + if (cmdlen > 0 && *cmd == '!') { + ++cmd; + --cmdlen; + F_SET(&exc, E_FORCE); + } + continue; + } + + /* Skip leading 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 () or the scrolling commands (^D + * and ) as the first non- 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 was represented as ^D, the + * output wouldn't overwrite the user's input. Sex currently doesn't + * display the character if it's going to be the scroll command, + * i.e. if it's the first non- character in the line. If sex + * is changed to run in cooked mode, i.e. 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" : ""); +} diff --git a/usr.bin/vi/ex/ex_abbrev.c b/usr.bin/vi/ex/ex_abbrev.c new file mode 100644 index 0000000..a0af8cd --- /dev/null +++ b/usr.bin/vi/ex/ex_abbrev.c @@ -0,0 +1,129 @@ +/*- + * 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_abbrev.c 8.12 (Berkeley) 8/14/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" +#include "../vi/vcmd.h" + +/* + * ex_abbr -- :abbreviate [key replacement] + * Create an abbreviation or display abbreviations. + */ +int +ex_abbr(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + CHAR_T *p; + size_t len; + + switch (cmdp->argc) { + case 0: + if (seq_dump(sp, SEQ_ABBREV, 0) == 0) + msgq(sp, M_INFO, "No abbreviations to display"); + return (0); + case 2: + break; + default: + abort(); + } + + /* Check for illegal characters. */ + for (p = cmdp->argv[0]->bp, len = cmdp->argv[0]->len; len--; ++p) + if (!inword(*p)) { + msgq(sp, M_ERR, + "%s may not be part of an abbreviated word", + KEY_NAME(sp, *p)); + return (1); + } + + if (seq_set(sp, NULL, 0, cmdp->argv[0]->bp, cmdp->argv[0]->len, + cmdp->argv[1]->bp, cmdp->argv[1]->len, SEQ_ABBREV, SEQ_USERDEF)) + return (1); + + F_SET(sp->gp, G_ABBREV); + return (0); +} + +/* + * ex_unabbr -- :unabbreviate key + * Delete an abbreviation. + */ +int +ex_unabbr(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + ARGS *ap; + + ap = cmdp->argv[0]; + if (!F_ISSET(sp->gp, G_ABBREV) || + seq_delete(sp, ap->bp, ap->len, SEQ_ABBREV)) { + msgq(sp, M_ERR, "\"%s\" is not an abbreviation", ap->bp); + return (1); + } + return (0); +} + +/* + * abbr_save -- + * Save the abbreviation sequences to a file. + */ +int +abbr_save(sp, fp) + SCR *sp; + FILE *fp; +{ + return (seq_save(sp, fp, "abbreviate ", SEQ_ABBREV)); +} diff --git a/usr.bin/vi/ex/ex_append.c b/usr.bin/vi/ex/ex_append.c new file mode 100644 index 0000000..97172dc --- /dev/null +++ b/usr.bin/vi/ex/ex_append.c @@ -0,0 +1,220 @@ +/*- + * 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_append.c 8.22 (Berkeley) 8/7/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" +#include "../sex/sex_screen.h" + +enum which {APPEND, CHANGE, INSERT}; + +static int aci __P((SCR *, EXF *, EXCMDARG *, enum which)); + +/* + * ex_append -- :[line] a[ppend][!] + * Append one or more lines of new text after the specified line, + * or the current line if no address is specified. + */ +int +ex_append(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + return (aci(sp, ep, cmdp, APPEND)); +} + +/* + * ex_change -- :[line[,line]] c[hange][!] [count] + * Change one or more lines to the input text. + */ +int +ex_change(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + return (aci(sp, ep, cmdp, CHANGE)); +} + +/* + * ex_insert -- :[line] i[nsert][!] + * Insert one or more lines of new text before the specified line, + * or the current line if no address is specified. + */ +int +ex_insert(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + return (aci(sp, ep, cmdp, INSERT)); +} + +static int +aci(sp, ep, cmdp, cmd) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; + enum which cmd; +{ + MARK m; + TEXTH *sv_tiqp, tiq; + TEXT *tp; + struct termios t; + u_int flags; + int rval; + + rval = 0; + + /* + * Set input flags; the ! flag turns off autoindent for append, + * change and insert. + */ + LF_INIT(TXT_DOTTERM | TXT_NLECHO); + if (!F_ISSET(cmdp, E_FORCE) && O_ISSET(sp, O_AUTOINDENT)) + LF_SET(TXT_AUTOINDENT); + if (O_ISSET(sp, O_BEAUTIFY)) + LF_SET(TXT_BEAUTIFY); + + /* Input is interruptible. */ + F_SET(sp, S_INTERRUPTIBLE); + + /* + * If this code is called by vi, the screen TEXTH structure (sp->tiqp) + * may already be in use, e.g. ":append|s/abc/ABC/" would fail as we're + * only halfway through the line when the append code fires. Use the + * local structure instead. + * + * If this code is called by vi, we want to reset the terminal and use + * ex's s_get() routine. It actually works fine if we use vi's s_get() + * routine, but it doesn't look as nice. Maybe if we had a separate + * window or something, but getting a line at a time looks awkward. + */ + if (IN_VI_MODE(sp)) { + memset(&tiq, 0, sizeof(TEXTH)); + CIRCLEQ_INIT(&tiq); + sv_tiqp = sp->tiqp; + sp->tiqp = &tiq; + + if (F_ISSET(sp->gp, G_STDIN_TTY)) + SEX_RAW(t); + (void)write(STDOUT_FILENO, "\n", 1); + LF_SET(TXT_NLECHO); + + } + + /* Set the line number, so that autoindent works correctly. */ + sp->lno = cmdp->addr1.lno; + + if (sex_get(sp, ep, sp->tiqp, 0, flags) != INP_OK) + goto err; + + /* + * If doing a change, replace lines for as long as possible. Then, + * append more lines or delete remaining lines. Changes to an empty + * file are just appends, and inserts are the same as appends to the + * previous line. + * + * !!! + * Adjust the current line number for the commands to match historic + * practice if the user doesn't enter anything, and set the address + * to which we'll append. This is safe because an address of 0 is + * illegal for change and insert. + */ + m = cmdp->addr1; + switch (cmd) { + case INSERT: + --m.lno; + /* FALLTHROUGH */ + case APPEND: + if (sp->lno == 0) + sp->lno = 1; + break; + case CHANGE: + --m.lno; + if (sp->lno != 1) + --sp->lno; + break; + } + + /* + * !!! + * Cut into the unnamed buffer. + */ + if (cmd == CHANGE && + (cut(sp, ep, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE) || + delete(sp, ep, &cmdp->addr1, &cmdp->addr2, 1))) + goto err; + + for (tp = sp->tiqp->cqh_first; + tp != (TEXT *)sp->tiqp; tp = tp->q.cqe_next) { + if (file_aline(sp, ep, 1, m.lno, tp->lb, tp->len)) { +err: rval = 1; + break; + } + sp->lno = ++m.lno; + } + + if (IN_VI_MODE(sp)) { + sp->tiqp = sv_tiqp; + text_lfree(&tiq); + + /* Reset the terminal state. */ + if (F_ISSET(sp->gp, G_STDIN_TTY)) { + if (SEX_NORAW(t)) + rval = 1; + F_SET(sp, S_REFRESH); + } + } + return (rval); +} diff --git a/usr.bin/vi/ex/ex_args.c b/usr.bin/vi/ex/ex_args.c new file mode 100644 index 0000000..9213e84 --- /dev/null +++ b/usr.bin/vi/ex/ex_args.c @@ -0,0 +1,263 @@ +/*- + * Copyright (c) 1991, 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_args.c 8.27 (Berkeley) 8/4/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_next -- :next [+cmd] [files] + * Edit the next file, optionally setting the list of files. + * + * !!! + * The :next command behaved differently from the :rewind command in + * historic vi. See nvi/docs/autowrite for details, but the basic + * idea was that it ignored the force flag if the autowrite flag was + * set. This implementation handles them all identically. + */ +int +ex_next(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + ARGS **argv, **pc; + FREF *frp; + int noargs; + char **ap; + + if (file_m1(sp, ep, F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE)) + return (1); + + /* + * If the first argument is a plus sign, '+', it's an initial + * ex command. + */ + argv = cmdp->argv; + if (cmdp->argc && argv[0]->bp[0] == '+') { + --cmdp->argc; + pc = argv++; + } else + pc = NULL; + + /* Any other arguments are a replacement file list. */ + if (cmdp->argc) { + /* Free the current list. */ + if (!F_ISSET(sp, S_ARGNOFREE) && sp->argv != NULL) { + for (ap = sp->argv; *ap != NULL; ++ap) + free(*ap); + free(sp->argv); + } + F_CLR(sp, S_ARGNOFREE | S_ARGRECOVER); + sp->cargv = NULL; + + /* Create a new list. */ + CALLOC_RET(sp, + sp->argv, char **, cmdp->argc + 1, sizeof(char *)); + for (ap = sp->argv, + argv = cmdp->argv; argv[0]->len != 0; ++ap, ++argv) + if ((*ap = + v_strdup(sp, argv[0]->bp, argv[0]->len)) == NULL) + return (1); + *ap = NULL; + + /* Switch to the first one. */ + sp->cargv = sp->argv; + if ((frp = file_add(sp, *sp->cargv)) == NULL) + return (1); + noargs = 0; + } else { + if (sp->cargv == NULL || sp->cargv[1] == NULL) { + msgq(sp, M_ERR, "No more files to edit"); + return (1); + } + if ((frp = file_add(sp, sp->cargv[1])) == NULL) + return (1); + if (F_ISSET(sp, S_ARGRECOVER)) + F_SET(frp, FR_RECOVER); + noargs = 1; + } + + if (file_init(sp, frp, NULL, F_ISSET(cmdp, E_FORCE))) + return (1); + if (noargs) + ++sp->cargv; + + /* Push the initial command onto the stack. */ + if (pc != NULL) + if (IN_EX_MODE(sp)) + (void)term_push(sp, pc[0]->bp, pc[0]->len, 0); + else if (IN_VI_MODE(sp)) { + (void)term_push(sp, "\n", 1, 0); + (void)term_push(sp, pc[0]->bp, pc[0]->len, 0); + (void)term_push(sp, ":", 1, 0); + (void)file_lline(sp, sp->ep, &sp->frp->lno); + F_SET(sp->frp, FR_CURSORSET); + } + + F_SET(sp, S_FSWITCH); + return (0); +} + +/* + * ex_prev -- :prev + * Edit the previous file. + */ +int +ex_prev(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + FREF *frp; + + if (file_m1(sp, ep, F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE)) + return (1); + + if (sp->cargv == sp->argv) { + msgq(sp, M_ERR, "No previous files to edit"); + return (1); + } + if ((frp = file_add(sp, sp->cargv[-1])) == NULL) + return (1); + + if (file_init(sp, frp, NULL, F_ISSET(cmdp, E_FORCE))) + return (1); + + --sp->cargv; + F_SET(sp, S_FSWITCH); + return (0); +} + +/* + * ex_rew -- :rew + * Re-edit the list of files. + */ +int +ex_rew(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + FREF *frp; + + /* + * !!! + * Historic practice -- you can rewind to the current file. + */ + if (sp->argv == NULL) { + msgq(sp, M_ERR, "No previous files to rewind"); + return (1); + } + + if (file_m1(sp, ep, F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE)) + return (1); + + /* + * !!! + * Historic practice, start at the beginning of the file. + */ + for (frp = sp->frefq.cqh_first; + frp != (FREF *)&sp->frefq; frp = frp->q.cqe_next) + F_CLR(frp, FR_CURSORSET | FR_FNONBLANK); + + /* Switch to the first one. */ + sp->cargv = sp->argv; + if ((frp = file_add(sp, *sp->cargv)) == NULL) + return (1); + if (file_init(sp, frp, NULL, F_ISSET(cmdp, E_FORCE))) + return (1); + + F_SET(sp, S_FSWITCH); + return (0); +} + +/* + * ex_args -- :args + * Display the list of files. + */ +int +ex_args(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + int cnt, col, len, sep; + char **ap; + + if (sp->argv == NULL) { + (void)ex_printf(EXCOOKIE, "No file list to display.\n"); + return (0); + } + + col = len = sep = 0; + for (cnt = 1, ap = sp->argv; *ap != NULL; ++ap) { + col += len = strlen(*ap) + sep + (ap == sp->cargv ? 2 : 0); + if (col >= sp->cols - 1) { + col = len; + sep = 0; + (void)ex_printf(EXCOOKIE, "\n"); + } else if (cnt != 1) { + sep = 1; + (void)ex_printf(EXCOOKIE, " "); + } + ++cnt; + + if (ap == sp->cargv) + (void)ex_printf(EXCOOKIE, "[%s]", *ap); + else + (void)ex_printf(EXCOOKIE, "%s", *ap); + } + (void)ex_printf(EXCOOKIE, "\n"); + return (0); +} diff --git a/usr.bin/vi/ex/ex_argv.c b/usr.bin/vi/ex/ex_argv.c new file mode 100644 index 0000000..f93aa76 --- /dev/null +++ b/usr.bin/vi/ex/ex_argv.c @@ -0,0 +1,609 @@ +/*- + * Copyright (c) 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_argv.c 8.36 (Berkeley) 8/4/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +static int argv_alloc __P((SCR *, size_t)); +static int argv_fexp __P((SCR *, EXCMDARG *, + char *, size_t, char *, size_t *, char **, size_t *, int)); +static int argv_sexp __P((SCR *, char **, size_t *, size_t *)); + +/* + * argv_init -- + * Build a prototype arguments list. + */ +int +argv_init(sp, ep, excp) + SCR *sp; + EXF *ep; + EXCMDARG *excp; +{ + EX_PRIVATE *exp; + + exp = EXP(sp); + exp->argsoff = 0; + argv_alloc(sp, 1); + + excp->argv = exp->args; + excp->argc = exp->argsoff; + return (0); +} + +/* + * argv_exp0 -- + * Append a string to the argument list. + */ +int +argv_exp0(sp, ep, excp, cmd, cmdlen) + SCR *sp; + EXF *ep; + EXCMDARG *excp; + char *cmd; + size_t cmdlen; +{ + EX_PRIVATE *exp; + + exp = EXP(sp); + argv_alloc(sp, cmdlen); + memmove(exp->args[exp->argsoff]->bp, cmd, cmdlen); + exp->args[exp->argsoff]->bp[cmdlen] = '\0'; + exp->args[exp->argsoff]->len = cmdlen; + ++exp->argsoff; + excp->argv = exp->args; + excp->argc = exp->argsoff; + return (0); +} + +/* + * argv_exp1 -- + * Do file name expansion on a string, and append it to the + * argument list. + */ +int +argv_exp1(sp, ep, excp, cmd, cmdlen, is_bang) + SCR *sp; + EXF *ep; + EXCMDARG *excp; + char *cmd; + size_t cmdlen; + int is_bang; +{ + EX_PRIVATE *exp; + size_t blen, len; + char *bp, *p, *t; + + GET_SPACE_RET(sp, bp, blen, 512); + + len = 0; + exp = EXP(sp); + if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) { + FREE_SPACE(sp, bp, blen); + return (1); + } + + /* If it's empty, we're done. */ + if (len != 0) { + for (p = bp, t = bp + len; p < t; ++p) + if (!isblank(*p)) + break; + if (p == t) + goto ret; + } else + goto ret; + + (void)argv_exp0(sp, ep, excp, bp, len); + +ret: FREE_SPACE(sp, bp, blen); + return (0); +} + +/* + * argv_exp2 -- + * Do file name and shell expansion on a string, and append it to + * the argument list. + */ +int +argv_exp2(sp, ep, excp, cmd, cmdlen, is_bang) + SCR *sp; + EXF *ep; + EXCMDARG *excp; + char *cmd; + size_t cmdlen; + int is_bang; +{ + size_t blen, len, n; + int rval; + char *bp, *mp, *p; + + GET_SPACE_RET(sp, bp, blen, 512); + +#define SHELLECHO "echo " +#define SHELLOFFSET (sizeof(SHELLECHO) - 1) + memmove(bp, SHELLECHO, SHELLOFFSET); + p = bp + SHELLOFFSET; + len = SHELLOFFSET; + +#if defined(DEBUG) && 0 + TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd); +#endif + + if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, is_bang)) { + rval = 1; + goto err; + } + +#if defined(DEBUG) && 0 + TRACE(sp, "before shell: %d: {%s}\n", len, bp); +#endif + + /* + * Do shell word expansion -- it's very, very hard to figure out what + * magic characters the user's shell expects. Historically, it was a + * union of v7 shell and csh meta characters. We match that practice + * by default, so ":read \%" tries to read a file named '%'. It would + * make more sense to pass any special characters through the shell, + * but then, if your shell was csh, the above example will behave + * differently in nvi than in vi. If you want to get other characters + * passed through to your shell, change the "meta" option. + * + * To avoid a function call per character, we do a first pass through + * the meta characters looking for characters that aren't expected + * to be there. + */ + for (p = mp = O_STR(sp, O_META); *p != '\0'; ++p) + if (isblank(*p) || isalnum(*p)) + break; + if (*p != '\0') { + for (p = bp, n = len; n > 0; --n, ++p) + if (strchr(mp, *p) != NULL) + break; + } else + for (p = bp, n = len; n > 0; --n, ++p) + if (!isblank(*p) && + !isalnum(*p) && strchr(mp, *p) != NULL) + break; + if (n > 0) { + if (argv_sexp(sp, &bp, &blen, &len)) { + rval = 1; + goto err; + } + p = bp; + } else { + p = bp + SHELLOFFSET; + len -= SHELLOFFSET; + } + +#if defined(DEBUG) && 0 + TRACE(sp, "after shell: %d: {%s}\n", len, bp); +#endif + + rval = argv_exp3(sp, ep, excp, p, len); + +err: FREE_SPACE(sp, bp, blen); + return (rval); +} + +/* + * argv_exp3 -- + * Take a string and break it up into an argv, which is appended + * to the argument list. + */ +int +argv_exp3(sp, ep, excp, cmd, cmdlen) + SCR *sp; + EXF *ep; + EXCMDARG *excp; + char *cmd; + size_t cmdlen; +{ + EX_PRIVATE *exp; + size_t len; + int ch, off; + char *ap, *p; + + for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) { + /* Skip any leading whitespace. */ + for (; cmdlen > 0; --cmdlen, ++cmd) { + ch = *cmd; + if (!isblank(ch)) + break; + } + if (cmdlen == 0) + break; + + /* + * Determine the length of this whitespace delimited + * argument. + * + * QUOTING NOTE: + * + * Skip any character preceded by the user's quoting + * character. + */ + for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) { + ch = *cmd; + if (IS_ESCAPE(sp, ch) && cmdlen > 1) { + ++cmd; + --cmdlen; + } else if (isblank(ch)) + break; + } + + /* + * Copy the argument into place. + * + * QUOTING NOTE: + * + * Lose quote chars. + */ + argv_alloc(sp, len); + off = exp->argsoff; + exp->args[off]->len = len; + for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++) + if (IS_ESCAPE(sp, *ap)) + ++ap; + *p = '\0'; + } + excp->argv = exp->args; + excp->argc = exp->argsoff; + +#if defined(DEBUG) && 0 + for (cnt = 0; cnt < exp->argsoff; ++cnt) + TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]); +#endif + return (0); +} + +/* + * argv_fexp -- + * Do file name and bang command expansion. + */ +static int +argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang) + SCR *sp; + EXCMDARG *excp; + char *cmd, *p, **bpp; + size_t cmdlen, *lenp, *blenp; + int is_bang; +{ + EX_PRIVATE *exp; + char *bp, *t; + size_t blen, len, tlen; + + /* Replace file name characters. */ + for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd) + switch (*cmd) { + case '!': + if (!is_bang) + goto ins_ch; + exp = EXP(sp); + if (exp->lastbcomm == NULL) { + msgq(sp, M_ERR, + "No previous command to replace \"!\""); + return (1); + } + len += tlen = strlen(exp->lastbcomm); + ADD_SPACE_RET(sp, bp, blen, len); + memmove(p, exp->lastbcomm, tlen); + p += tlen; + F_SET(excp, E_MODIFY); + break; + case '%': + if ((t = sp->frp->name) == NULL) { + msgq(sp, M_ERR, + "No filename to substitute for %%"); + return (1); + } + tlen = strlen(t); + len += tlen; + ADD_SPACE_RET(sp, bp, blen, len); + memmove(p, t, tlen); + p += tlen; + F_SET(excp, E_MODIFY); + break; + case '#': + if ((t = sp->alt_name) == NULL) { + msgq(sp, M_ERR, + "No filename to substitute for #"); + return (1); + } + len += tlen = strlen(t); + ADD_SPACE_RET(sp, bp, blen, len); + memmove(p, t, tlen); + p += tlen; + F_SET(excp, E_MODIFY); + break; + case '\\': + /* + * QUOTING NOTE: + * + * Strip any backslashes that protected the file + * expansion characters. + */ + if (cmdlen > 1 && (cmd[1] == '%' || cmd[1] == '#')) { + ++cmd; + --cmdlen; + } + /* FALLTHROUGH */ + default: +ins_ch: ++len; + ADD_SPACE_RET(sp, bp, blen, len); + *p++ = *cmd; + } + + /* Nul termination. */ + ++len; + ADD_SPACE_RET(sp, bp, blen, len); + *p = '\0'; + + /* Return the new string length, buffer, buffer length. */ + *lenp = len - 1; + *bpp = bp; + *blenp = blen; + return (0); +} + +/* + * argv_alloc -- + * Make more space for arguments. + */ +static int +argv_alloc(sp, len) + SCR *sp; + size_t len; +{ + ARGS *ap; + EX_PRIVATE *exp; + int cnt, off; + + /* + * Allocate room for another argument, always leaving + * enough room for an ARGS structure with a length of 0. + */ +#define INCREMENT 20 + exp = EXP(sp); + off = exp->argsoff; + if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) { + cnt = exp->argscnt + INCREMENT; + REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *)); + if (exp->args == NULL) { + (void)argv_free(sp); + goto mem; + } + memset(&exp->args[off], 0, INCREMENT * sizeof(ARGS *)); + exp->argscnt = cnt; + } + + /* First argument. */ + if (exp->args[off] == NULL) { + CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS)); + if (exp->args[off] == NULL) + goto mem; + } + + /* First argument buffer. */ + ap = exp->args[off]; + ap->len = 0; + if (ap->blen < len + 1) { + ap->blen = len + 1; + REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T)); + if (ap->bp == NULL) { + ap->bp = NULL; + ap->blen = 0; + F_CLR(ap, A_ALLOCATED); +mem: msgq(sp, M_SYSERR, NULL); + return (1); + } + F_SET(ap, A_ALLOCATED); + } + + /* Second argument. */ + if (exp->args[++off] == NULL) { + CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS)); + if (exp->args[off] == NULL) + goto mem; + } + /* 0 length serves as end-of-argument marker. */ + exp->args[off]->len = 0; + return (0); +} + +/* + * argv_free -- + * Free up argument structures. + */ +int +argv_free(sp) + SCR *sp; +{ + EX_PRIVATE *exp; + int off; + + exp = EXP(sp); + if (exp->args != NULL) { + for (off = 0; off < exp->argscnt; ++off) { + if (exp->args[off] == NULL) + continue; + if (F_ISSET(exp->args[off], A_ALLOCATED)) + free(exp->args[off]->bp); + FREE(exp->args[off], sizeof(ARGS)); + } + FREE(exp->args, exp->argscnt * sizeof(ARGS *)); + } + exp->args = NULL; + exp->argscnt = 0; + exp->argsoff = 0; + return (0); +} + +/* + * argv_sexp -- + * Fork a shell, pipe a command through it, and read the output into + * a buffer. + */ +static int +argv_sexp(sp, bpp, blenp, lenp) + SCR *sp; + char **bpp; + size_t *blenp, *lenp; +{ + FILE *ifp; + pid_t pid; + size_t blen, len; + int ch, rval, output[2]; + char *bp, *p, *sh, *sh_path; + + bp = *bpp; + blen = *blenp; + + sh_path = O_STR(sp, O_SHELL); + if ((sh = strrchr(sh_path, '/')) == NULL) + sh = sh_path; + else + ++sh; + + /* + * There are two different processes running through this code. + * They are named the utility and the parent. The utility reads + * from standard input and writes to the parent. The parent reads + * from the utility and writes into the buffer. The parent reads + * from output[0], and the utility writes to output[1]. + */ + if (pipe(output) < 0) { + msgq(sp, M_SYSERR, "pipe"); + return (1); + } + if ((ifp = fdopen(output[0], "r")) == NULL) { + msgq(sp, M_SYSERR, "fdopen"); + goto err; + } + + /* + * Do the minimal amount of work possible, the shell is going + * to run briefly and then exit. Hopefully. + */ + SIGBLOCK(sp->gp); + switch (pid = vfork()) { + case -1: /* Error. */ + SIGUNBLOCK(sp->gp); + + msgq(sp, M_SYSERR, "vfork"); +err: (void)close(output[0]); + (void)close(output[1]); + return (1); + case 0: /* Utility. */ + /* The utility has default signal behavior. */ + sig_end(); + + /* Redirect stdout/stderr to the write end of the pipe. */ + (void)dup2(output[1], STDOUT_FILENO); + (void)dup2(output[1], STDERR_FILENO); + + /* Close the utility's file descriptors. */ + (void)close(output[0]); + (void)close(output[1]); + + /* Assumes that all shells have -c. */ + execl(sh_path, sh, "-c", bp, NULL); + msgq(sp, M_ERR, + "Error: execl: %s: %s", sh_path, strerror(errno)); + _exit(127); + default: /* Parent. */ + SIGUNBLOCK(sp->gp); + + /* Close the pipe end the parent won't use. */ + (void)close(output[1]); + break; + } + + rval = 0; + + /* + * Copy process output into a buffer. + * + * !!! + * Historic vi apparently discarded leading \n and \r's from + * the shell output stream. We don't on the grounds that any + * shell that does that is broken. + */ + for (p = bp, len = 0, ch = EOF; + (ch = getc(ifp)) != EOF; *p++ = ch, --blen, ++len) + if (blen < 5) { + ADD_SPACE_GOTO(sp, bp, blen, *blenp * 2); + p = bp + len; + blen = *blenp - len; + } + + /* Delete the final newline, nul terminate the string. */ + if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) { + --len; + *--p = '\0'; + } else + *p = '\0'; + *lenp = len; + + if (ferror(ifp)) { + msgq(sp, M_ERR, "I/O error: %s", sh); +binc_err: rval = 1; + } + (void)fclose(ifp); + + *bpp = bp; /* *blenp is already updated. */ + + /* Wait for the process. */ + return (proc_wait(sp, (long)pid, sh, 0) || rval); +} diff --git a/usr.bin/vi/ex/ex_at.c b/usr.bin/vi/ex/ex_at.c new file mode 100644 index 0000000..fc5922f --- /dev/null +++ b/usr.bin/vi/ex/ex_at.c @@ -0,0 +1,118 @@ +/*- + * 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_at.c 8.25 (Berkeley) 8/1/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_at -- :@[@ | buffer] + * :*[* | buffer] + * + * Execute the contents of the buffer. + */ +int +ex_at(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + CB *cbp; + EX_PRIVATE *exp; + TEXT *tp; + int name; + + exp = EXP(sp); + + /* + * !!! + * Historically, [@*] and [@*][@*] executed the most + * recently executed buffer in ex mode. In vi mode, only @@ repeated + * the last buffer. We change historic practice and make @* work from + * vi mode as well, it's simpler and more consistent. + */ + name = F_ISSET(cmdp, E_BUFFER) ? cmdp->buffer : '@'; + if (name == '@' || name == '*') { + if (!exp->at_lbuf_set) { + msgq(sp, M_ERR, "No previous buffer to execute"); + return (1); + } + name = exp->at_lbuf; + } + + CBNAME(sp, cbp, name); + if (cbp == NULL) { + msgq(sp, M_ERR, "Buffer %s is empty", KEY_NAME(sp, name)); + return (1); + } + + /* Save for reuse. */ + exp->at_lbuf = name; + exp->at_lbuf_set = 1; + + /* + * !!! + * Historic practice is that if the buffer was cut in line mode, + * were appended to each line as it was pushed onto + * the stack. If the buffer was cut in character mode, + * were appended to all lines but the last one. + */ + for (tp = cbp->textq.cqh_last; + tp != (void *)&cbp->textq; tp = tp->q.cqe_prev) + if ((F_ISSET(cbp, CB_LMODE) || + tp->q.cqe_next != (void *)&cbp->textq) && + term_push(sp, "\n", 1, 0) || + term_push(sp, tp->lb, tp->len, 0)) + return (1); + return (0); +} diff --git a/usr.bin/vi/ex/ex_bang.c b/usr.bin/vi/ex/ex_bang.c new file mode 100644 index 0000000..70bbb57 --- /dev/null +++ b/usr.bin/vi/ex/ex_bang.c @@ -0,0 +1,242 @@ +/*- + * 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_bang.c 8.33 (Berkeley) 8/14/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" +#include "../sex/sex_screen.h" + +/* + * ex_bang -- :[line [,line]] ! command + * + * Pass the rest of the line after the ! character to the program named by + * the O_SHELL option. + * + * Historical vi did NOT do shell expansion on the arguments before passing + * them, only file name expansion. This means that the O_SHELL program got + * "$t" as an argument if that is what the user entered. Also, there's a + * special expansion done for the bang command. Any exclamation points in + * the user's argument are replaced by the last, expanded ! command. + * + * There's some fairly amazing slop in this routine to make the different + * ways of getting here display the right things. It took a long time to + * get it right (wrong?), so be careful. + */ +int +ex_bang(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + enum filtertype ftype; + ARGS *ap; + EX_PRIVATE *exp; + MARK rm; + recno_t lno; + size_t blen; + int rval; + char *bp, *msg; + + ap = cmdp->argv[0]; + if (ap->len == 0) { + msgq(sp, M_ERR, "Usage: %s", cmdp->cmd->usage); + return (1); + } + + /* Set the last bang command. */ + exp = EXP(sp); + if (exp->lastbcomm != NULL) + free(exp->lastbcomm); + if ((exp->lastbcomm = strdup(ap->bp)) == NULL) { + msgq(sp, M_SYSERR, NULL); + return (1); + } + + /* + * If the command was modified by the expansion, we redisplay it. + * Redisplaying it in vi mode is tricky, and handled separately + * in each case below. If we're in ex mode, it's easy, so we just + * do it here. + */ + bp = NULL; + if (F_ISSET(cmdp, E_MODIFY) && !F_ISSET(sp, S_EXSILENT)) { + if (IN_EX_MODE(sp)) { + (void)ex_printf(EXCOOKIE, "!%s\n", ap->bp); + (void)ex_fflush(EXCOOKIE); + } + /* + * Vi: Display the command if modified. Historic vi displayed + * the command if it was modified due to file name and/or bang + * expansion. If piping lines, it was immediately overwritten + * by any error or line change reporting. We don't the user to + * have to page through the responses, so we only post it until + * it's erased by something else. Otherwise, pass it on to the + * ex_exec_proc routine to display after the screen has been + * cleaned up. + */ + if (IN_VI_MODE(sp)) { + GET_SPACE_RET(sp, bp, blen, ap->len + 3); + bp[0] = '!'; + memmove(bp + 1, ap->bp, ap->len); + bp[ap->len + 1] = '\n'; + bp[ap->len + 2] = '\0'; + } + } + + /* + * If addresses were specified, pipe lines from the file through the + * command. + * + * Historically, vi lines were replaced by both the stdout and stderr + * lines of the command, but ex by only the stdout lines. This makes + * no sense to me, so nvi makes it consistent for both, and matches + * vi's historic behavior. + */ + if (cmdp->addrcnt != 0) { + /* Autoprint is set historically, even if the command fails. */ + F_SET(exp, EX_AUTOPRINT); + + /* Vi gets a busy message. */ + if (bp != NULL) + (void)sp->s_busy(sp, bp); + + /* + * !!! + * Historical vi permitted "!!" in an empty file. When it + * happens, we get called with two addresses of 1,1 and a + * bad attitude. The simple solution is to turn it into a + * FILTER_READ operation, but that means that we don't put + * an empty line into the default cut buffer as did historic + * vi. Tough. + */ + ftype = FILTER; + if (cmdp->addr1.lno == 1 && cmdp->addr2.lno == 1) { + if (file_lline(sp, ep, &lno)) + return (1); + if (lno == 0) { + cmdp->addr1.lno = cmdp->addr2.lno = 0; + ftype = FILTER_READ; + } + } + rval = filtercmd(sp, ep, + &cmdp->addr1, &cmdp->addr2, &rm, ap->bp, ftype); + + /* + * If in vi mode, move to the first nonblank. + * + * !!! + * Historic vi wasn't consistent in this area -- if you used + * a forward motion it moved to the first nonblank, but if you + * did a backward motion it didn't. And, if you followed a + * backward motion with a forward motion, it wouldn't move to + * the nonblank for either. Going to the nonblank generally + * seems more useful, so we do it. + */ + if (rval == 0) { + sp->lno = rm.lno; + if (IN_VI_MODE(sp)) { + sp->cno = 0; + (void)nonblank(sp, ep, sp->lno, &sp->cno); + } + } + goto ret2; + } + + /* + * If no addresses were specified, run the command. If the file + * has been modified and autowrite is set, write the file back. + * If the file has been modified, autowrite is not set and the + * warn option is set, tell the user about the file. + */ + msg = NULL; + if (F_ISSET(ep, F_MODIFIED)) + if (O_ISSET(sp, O_AUTOWRITE)) { + if (file_write(sp, ep, NULL, NULL, NULL, FS_ALL)) { + rval = 1; + goto ret1; + } + } else if (O_ISSET(sp, O_WARN) && !F_ISSET(sp, S_EXSILENT)) + msg = "File modified since last write.\n"; + + /* Run the command. */ + rval = ex_exec_proc(sp, ap->bp, bp, msg); + + /* Vi requires user permission to continue. */ + if (IN_VI_MODE(sp)) + F_SET(sp, S_CONTINUE); + +ret2: if (IN_EX_MODE(sp)) { + /* + * Put ex error messages out so they aren't confused with + * the autoprint output. + */ + if (rval) + (void)sex_refresh(sp, sp->ep); + + /* Ex terminates with a bang, even if the command fails. */ + if (!F_ISSET(sp, S_EXSILENT)) + (void)write(STDOUT_FILENO, "!\n", 2); + } + + /* Free the extra space. */ +ret1: if (bp != NULL) + FREE_SPACE(sp, bp, blen); + + /* + * XXX + * The ! commands never return an error, so that autoprint always + * happens in the ex parser. + */ + return (0); +} diff --git a/usr.bin/vi/ex/ex_cd.c b/usr.bin/vi/ex/ex_cd.c new file mode 100644 index 0000000..3732f7b --- /dev/null +++ b/usr.bin/vi/ex/ex_cd.c @@ -0,0 +1,223 @@ +/*- + * 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_cd.c 8.16 (Berkeley) 8/8/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_cd -- :cd[!] [directory] + * Change directories. + */ +int +ex_cd(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + struct passwd *pw; + ARGS *ap; + CDPATH *cdp; + char *dir; /* XXX END OF THE STACK, DON'T TRUST GETCWD. */ + char buf[MAXPATHLEN * 2]; + + /* + * !!! + * Historic practice is that the cd isn't attempted if the file has + * been modified, unless its name begins with a leading '/' or the + * force flag is set. + */ + if (F_ISSET(ep, F_MODIFIED) && + !F_ISSET(cmdp, E_FORCE) && sp->frp->name[0] != '/') { + msgq(sp, M_ERR, + "File modified since last complete write; write or use ! to override"); + return (1); + } + + switch (cmdp->argc) { + case 0: + /* If no argument, change to the user's home directory. */ + if ((dir = getenv("HOME")) == NULL) { + if ((pw = getpwuid(getuid())) == NULL || + pw->pw_dir == NULL || pw->pw_dir[0] == '\0') { + msgq(sp, M_ERR, + "Unable to find home directory location"); + return (1); + } + dir = pw->pw_dir; + } + break; + case 1: + dir = cmdp->argv[0]->bp; + break; + default: + abort(); + } + + /* Try the current directory first. */ + if (!chdir(dir)) + goto ret; + + /* + * If moving to the user's home directory, or, the path begins with + * "/", "./" or "../", it's the only place we try. + */ + if (cmdp->argc == 0 || + (ap = cmdp->argv[0])->bp[0] == '/' || + ap->len == 1 && ap->bp[0] == '.' || + ap->len >= 2 && ap->bp[0] == '.' && ap->bp[1] == '.' && + (ap->bp[2] == '/' || ap->bp[2] == '\0')) + goto err; + + /* If the user has a CDPATH variable, try its elements. */ + for (cdp = EXP(sp)->cdq.tqh_first; cdp != NULL; cdp = cdp->q.tqe_next) { + (void)snprintf(buf, sizeof(buf), "%s/%s", cdp->path, dir); + if (!chdir(buf)) { +ret: if (getcwd(buf, sizeof(buf)) != NULL) + msgq(sp, M_INFO, "New directory: %s", buf); + return (0); + } + } +err: msgq(sp, M_SYSERR, "%s", dir); + return (1); +} + +#define FREE_CDPATH(cdp) { \ + TAILQ_REMOVE(&exp->cdq, (cdp), q); \ + free((cdp)->path); \ + FREE((cdp), sizeof(CDPATH)); \ +} +/* + * ex_cdalloc -- + * Create a new list of cd paths. + */ +int +ex_cdalloc(sp, str) + SCR *sp; + char *str; +{ + EX_PRIVATE *exp; + CDPATH *cdp; + size_t len; + int founddot; + char *p, *t; + + /* Free current queue. */ + exp = EXP(sp); + while ((cdp = exp->cdq.tqh_first) != NULL) + FREE_CDPATH(cdp); + + /* + * Create new queue. The CDPATH environmental variable (and the + * user's manual entry) are delimited by colon characters. + */ + for (p = t = str, founddot = 0;; ++p) { + if (*p == '\0' || *p == ':') { + /* + * Empty strings specify ".". The only way to get an + * empty string is a leading colon, colons in a row, + * or a trailing colon. Or, to put it the other way, + * if the the length is zero, then it's either ":XXX", + * "XXX::XXXX" , "XXX:", or "", and the only failure + * mode is the last one. Note, the string ":" gives + * us two entries of '.', so we only include one of + * them. + */ + if ((len = p - t) == 0) { + if (p == str && *p == '\0') + break; + if (founddot) { + if (*p == '\0') + break; + continue; + } + len = 1; + t = "."; + founddot = 1; + } + MALLOC_RET(sp, cdp, CDPATH *, sizeof(CDPATH)); + MALLOC(sp, cdp->path, char *, len + 1); + if (cdp->path == NULL) { + free(cdp); + return (1); + } + memmove(cdp->path, t, len); + cdp->path[len] = '\0'; + TAILQ_INSERT_TAIL(&exp->cdq, cdp, q); + t = p + 1; + } + if (*p == '\0') + break; + } + return (0); +} + /* Free previous queue. */ +/* + * ex_cdfree -- + * Free the cd path list. + */ +int +ex_cdfree(sp) + SCR *sp; +{ + EX_PRIVATE *exp; + CDPATH *cdp; + + /* Free up cd path information. */ + exp = EXP(sp); + while ((cdp = exp->cdq.tqh_first) != NULL) + FREE_CDPATH(cdp); + return (0); +} diff --git a/usr.bin/vi/ex/ex_delete.c b/usr.bin/vi/ex/ex_delete.c new file mode 100644 index 0000000..08c4250 --- /dev/null +++ b/usr.bin/vi/ex/ex_delete.c @@ -0,0 +1,92 @@ +/*- + * 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_delete.c 8.12 (Berkeley) 8/5/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_delete: [line [,line]] d[elete] [buffer] [count] [flags] + * + * Delete lines from the file. + */ +int +ex_delete(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + recno_t lno; + + /* + * !!! + * Historically, lines deleted in ex were not placed in the numeric + * buffers. We follow historic practice so that we don't overwrite + * vi buffers accidentally. + */ + if (cut(sp, ep, + F_ISSET(cmdp, E_BUFFER) ? &cmdp->buffer : NULL, + &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE)) + return (1); + + /* Delete the lines. */ + if (delete(sp, ep, &cmdp->addr1, &cmdp->addr2, 1)) + return (1); + + /* Set the cursor to the line after the last line deleted. */ + sp->lno = cmdp->addr1.lno; + + /* Or the last line in the file if deleted to the end of the file. */ + if (file_lline(sp, ep, &lno)) + return (1); + if (sp->lno > lno) + sp->lno = lno; + return (0); +} diff --git a/usr.bin/vi/ex/ex_digraph.c b/usr.bin/vi/ex/ex_digraph.c new file mode 100644 index 0000000..0739923 --- /dev/null +++ b/usr.bin/vi/ex/ex_digraph.c @@ -0,0 +1,324 @@ +/*- + * 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_digraph.c 8.6 (Berkeley) 3/25/94"; +#endif /* not lint */ + +#ifndef NO_DIGRAPH +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include +#include + +#include "vi.h" +#include "excmd.h" + +static void do_digraph __P((SCR *, EXF *, int, u_char *)); + +/* This stuff is used to build the default digraphs table. */ +static u_char digtable[][4] = { +# ifdef CS_IBMPC + "C,\200", "u\"\1", "e'\2", "a^\3", + "a\"\4", "a`\5", "a@\6", "c,\7", + "e^\10", "e\"\211", "e`\12", "i\"\13", + "i^\14", "i`\15", "A\"\16", "A@\17", + "E'\20", "ae\21", "AE\22", "o^\23", + "o\"\24", "o`\25", "u^\26", "u`\27", + "y\"\30", "O\"\31", "U\"\32", "a'\240", + "i'!", "o'\"", "u'#", "n~$", + "N~%", "a-&", "o-'", "~?(", + "~!-", "\"<.", "\">/", +# ifdef CS_SPECIAL + "2/+", "4/,", "^+;", "^q<", + "^c=", "^r>", "^t?", "pp]", + "^^^", "oo_", "*a`", "*ba", + "*pc", "*Sd", "*se", "*uf", + "*tg", "*Ph", "*Ti", "*Oj", + "*dk", "*Hl", "*hm", "*En", + "*No", "eqp", "pmq", "ger", + "les", "*It", "*iu", "*/v", + "*=w", "sq{", "^n|", "^2}", + "^3~", "^_\377", +# endif /* CS_SPECIAL */ +# endif /* CS_IBMPC */ +# ifdef CS_LATIN1 + "~!!", "a-*", "\">+", "o-:", + "\"<>", "~??", + + "A`@", "A'A", "A^B", "A~C", + "A\"D", "A@E", "AEF", "C,G", + "E`H", "E'I", "E^J", "E\"K", + "I`L", "I'M", "I^N", "I\"O", + "-DP", "N~Q", "O`R", "O'S", + "O^T", "O~U", "O\"V", "O/X", + "U`Y", "U'Z", "U^[", "U\"\\", + "Y'_", + + "a``", "a'a", "a^b", "a~c", + "a\"d", "a@e", "aef", "c,g", + "e`h", "e'i", "e^j", "e\"k", + "i`l", "i'm", "i^n", "i\"o", + "-dp", "n~q", "o`r", "o's", + "o^t", "o~u", "o\"v", "o/x", + "u`y", "u'z", "u^{", "u\"|", + "y'~", +# endif /* CS_LATIN1 */ + "" +}; + +int +digraph_init(sp) + SCR *sp; +{ + int i; + + for (i = 0; *digtable[i]; i++) + do_digraph(sp, NULL, 0, digtable[i]); + do_digraph(sp, NULL, 0, NULL); + return (0); +} + +int +ex_digraph(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + do_digraph(sp, ep, F_ISSET(cmdp, E_FORCE), cmdp->argv[0]->bp); + return (0); +} + +static struct _DIG +{ + struct _DIG *next; + char key1; + char key2; + char dig; + char save; +} *digs; + +int +digraph(sp, key1, key2) + SCR *sp; + char key1; /* the underlying character */ + char key2; /* the second character */ +{ + int new_key; + register struct _DIG *dp; + + /* if digraphs are disabled, then just return the new char */ + if (O_ISSET(sp, O_DIGRAPH)) + { + return key2; + } + + /* remember the new key, so we can return it if this isn't a digraph */ + new_key = key2; + + /* sort key1 and key2, so that their original order won't matter */ + if (key1 > key2) + { + key2 = key1; + key1 = new_key; + } + + /* scan through the digraph chart */ + for (dp = digs; + dp && (dp->key1 != key1 || dp->key2 != key2); + dp = dp->next) + { + } + + /* if this combination isn't in there, just use the new key */ + if (!dp) + { + return new_key; + } + + /* else use the digraph key */ + return dp->dig; +} + +/* this function lists or defines digraphs */ +static void +do_digraph(sp, ep, bang, extra) + SCR *sp; + EXF *ep; + int bang; + u_char *extra; +{ + int dig; + register struct _DIG *dp; + struct _DIG *prev; + static int user_defined = 0; /* boolean: are all later digraphs user-defined? */ + char listbuf[8]; + + /* if "extra" is NULL, then we've reached the end of the built-ins */ + if (!extra) + { + user_defined = 1; + return; + } + + /* if no args, then display the existing digraphs */ + if (*extra < ' ') + { + listbuf[0] = listbuf[1] = listbuf[2] = listbuf[5] = ' '; + listbuf[7] = '\0'; + for (dig = 0, dp = digs; dp; dp = dp->next) + { + if (dp->save || bang) + { + dig += 7; + if (dig >= sp->cno) + { + addch('\n'); + refresh(); + dig = 7; + } + listbuf[3] = dp->key1; + listbuf[4] = dp->key2; + listbuf[6] = dp->dig; + addstr(listbuf); + } + } + addch('\n'); + refresh(); + return; + } + + /* make sure we have at least two characters */ + if (!extra[1]) + { + msgq(sp, M_ERR, + "Digraphs must be composed of two characters"); + return; + } + + /* sort key1 and key2, so that their original order won't matter */ + if (extra[0] > extra[1]) + { + dig = extra[0]; + extra[0] = extra[1]; + extra[1] = dig; + } + + /* locate the new digraph character */ + for (dig = 2; extra[dig] == ' ' || extra[dig] == '\t'; dig++) + { + } + dig = extra[dig]; + if (!bang && dig) + { + dig |= 0x80; + } + + /* search for the digraph */ + for (prev = (struct _DIG *)0, dp = digs; + dp && (dp->key1 != extra[0] || dp->key2 != extra[1]); + prev = dp, dp = dp->next) + { + } + + /* deleting the digraph? */ + if (!dig) + { + if (!dp) + { +#ifndef CRUNCH + msgq(sp, M_ERR, + "%c%c not a digraph", extra[0], extra[1]); +#endif + return; + } + if (prev) + prev->next = dp->next; + else + digs = dp->next; + free(dp); + return; + } + + /* if necessary, create a new digraph struct for the new digraph */ + if (dig && !dp) + { + MALLOC(sp, dp, struct _DIG *, sizeof(struct _DIG)); + if (dp == NULL) + return; + if (prev) + prev->next = dp; + else + digs = dp; + dp->next = (struct _DIG *)0; + } + + /* assign it the new digraph value */ + dp->key1 = extra[0]; + dp->key2 = extra[1]; + dp->dig = dig; + dp->save = user_defined; +} + +void +digraph_save(sp, fd) + SCR *sp; + int fd; +{ + static char buf[] = "digraph! XX Y\n"; + register struct _DIG *dp; + + for (dp = digs; dp; dp = dp->next) + { + if (dp->save) + { + buf[9] = dp->key1; + buf[10] = dp->key2; + buf[12] = dp->dig; + write(fd, buf, (unsigned)14); + } + } +} +#endif diff --git a/usr.bin/vi/ex/ex_display.c b/usr.bin/vi/ex/ex_display.c new file mode 100644 index 0000000..139127e --- /dev/null +++ b/usr.bin/vi/ex/ex_display.c @@ -0,0 +1,169 @@ +/*- + * 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_display.c 8.21 (Berkeley) 8/3/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "tag.h" +#include "excmd.h" + +static int bdisplay __P((SCR *, EXF *)); +static void db __P((SCR *, CB *, CHAR_T *)); + +/* + * ex_display -- :display b[uffers] | s[creens] | t[ags] + * + * Display buffers, tags or screens. + */ +int +ex_display(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + switch (cmdp->argv[0]->bp[0]) { + case 'b': +#undef ARG +#define ARG "buffers" + if (cmdp->argv[0]->len >= sizeof(ARG) || + memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len)) + break; + return (bdisplay(sp, ep)); + case 's': +#undef ARG +#define ARG "screens" + if (cmdp->argv[0]->len >= sizeof(ARG) || + memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len)) + break; + return (ex_sdisplay(sp, ep)); + case 't': +#undef ARG +#define ARG "tags" + if (cmdp->argv[0]->len >= sizeof(ARG) || + memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len)) + break; + return (ex_tagdisplay(sp, ep)); + } + msgq(sp, M_ERR, "Usage: %s", cmdp->cmd->usage); + return (1); +} + +/* + * bdisplay -- + * + * Display buffers. + */ +static int +bdisplay(sp, ep) + SCR *sp; + EXF *ep; +{ + CB *cbp; + + if (sp->gp->cutq.lh_first == NULL && sp->gp->dcbp == NULL) { + (void)ex_printf(EXCOOKIE, "No cut buffers to display.\n"); + return (0); + } + + /* Buffers can be infinitely long, make it interruptible. */ + F_SET(sp, S_INTERRUPTIBLE); + + /* Display regular cut buffers. */ + for (cbp = sp->gp->cutq.lh_first; cbp != NULL; cbp = cbp->q.le_next) { + if (isdigit(cbp->name)) + continue; + if (cbp->textq.cqh_first != (void *)&cbp->textq) + db(sp, cbp, NULL); + if (INTERRUPTED(sp)) + return (0); + } + /* Display numbered buffers. */ + for (cbp = sp->gp->cutq.lh_first; cbp != NULL; cbp = cbp->q.le_next) { + if (!isdigit(cbp->name)) + continue; + if (cbp->textq.cqh_first != (void *)&cbp->textq) + db(sp, cbp, NULL); + if (INTERRUPTED(sp)) + return (0); + } + /* Display default buffer. */ + if ((cbp = sp->gp->dcbp) != NULL) + db(sp, cbp, "default buffer"); + return (0); +} + +/* + * db -- + * Display a buffer. + */ +static void +db(sp, cbp, name) + SCR *sp; + CB *cbp; + CHAR_T *name; +{ + CHAR_T *p; + TEXT *tp; + size_t len; + + (void)ex_printf(EXCOOKIE, "********** %s%s\n", + name == NULL ? KEY_NAME(sp, cbp->name) : name, + F_ISSET(cbp, CB_LMODE) ? " (line mode)" : " (character mode)"); + for (tp = cbp->textq.cqh_first; + tp != (void *)&cbp->textq; tp = tp->q.cqe_next) { + for (len = tp->len, p = tp->lb; len--; ++p) { + (void)ex_printf(EXCOOKIE, "%s", KEY_NAME(sp, *p)); + if (INTERRUPTED(sp)) + return; + } + (void)ex_printf(EXCOOKIE, "\n"); + } +} diff --git a/usr.bin/vi/ex/ex_edit.c b/usr.bin/vi/ex/ex_edit.c new file mode 100644 index 0000000..bfe0a2a --- /dev/null +++ b/usr.bin/vi/ex/ex_edit.c @@ -0,0 +1,122 @@ +/*- + * 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_edit.c 8.18 (Berkeley) 8/4/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_edit -- :e[dit][!] [+cmd] [file] + * :vi[sual][!] [+cmd] [file] + * + * Edit a file; if none specified, re-edit the current file. The second + * form of the command can only be executed while in vi mode. See the + * hack in ex.c:ex_cmd(). + * + * !!! + * Historic vi didn't permit the '+' command form without specifying + * a file name as well. + */ +int +ex_edit(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + ARGS *ap; + FREF *frp; + + frp = sp->frp; + switch (cmdp->argc) { + case 0: + /* + * If the name has been changed, we edit that file, not the + * original name. If the user was editing a temporary file, + * create another one. The reason for this is that we do + * special exit processing of temporary files, and reusing + * them is tricky. + */ + if (F_ISSET(frp, FR_TMPFILE)) { + if ((frp = file_add(sp, NULL)) == NULL) + return (1); + } else { + if ((frp = file_add(sp, frp->name)) == NULL) + return (1); + set_alt_name(sp, sp->frp->name); + } + break; + case 1: + ap = cmdp->argv[0]; + if ((frp = file_add(sp, ap->bp)) == NULL) + return (1); + set_alt_name(sp, ap->bp); + break; + default: + abort(); + } + + /* + * Check for modifications. + * + * !!! + * Contrary to POSIX 1003.2-1992, autowrite did not affect :edit. + */ + if (file_m2(sp, ep, F_ISSET(cmdp, E_FORCE))) + return (1); + + /* Switch files. */ + if (file_init(sp, frp, NULL, F_ISSET(cmdp, E_FORCE))) + return (1); + F_SET(sp, S_FSWITCH); + return (0); +} diff --git a/usr.bin/vi/ex/ex_equal.c b/usr.bin/vi/ex/ex_equal.c new file mode 100644 index 0000000..a80d723 --- /dev/null +++ b/usr.bin/vi/ex/ex_equal.c @@ -0,0 +1,86 @@ +/*- + * 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_equal.c 8.6 (Berkeley) 4/26/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_equal -- :address = + */ +int +ex_equal(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + recno_t lno; + + /* + * Print out the line number matching the specified address, + * or the number of the last line in the file if no address + * specified. + * + * !!! + * Historically, ":0=" displayed 0, and ":=" or ":1=" in an + * empty file displayed 1. Until somebody complains loudly, + * we're going to do it right. The tables in excmd.c permit + * lno to get away with any address from 0 to the end of the + * file, which, in an empty file, is 0. + */ + if (F_ISSET(cmdp, E_ADDRDEF)) { + if (file_lline(sp, ep, &lno)) + return (1); + } else + lno = cmdp->addr1.lno; + + (void)ex_printf(EXCOOKIE, "%ld\n", lno); + return (0); +} diff --git a/usr.bin/vi/ex/ex_exit.c b/usr.bin/vi/ex/ex_exit.c new file mode 100644 index 0000000..3f0a362 --- /dev/null +++ b/usr.bin/vi/ex/ex_exit.c @@ -0,0 +1,79 @@ +/*- + * 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_exit.c 8.13 (Berkeley) 8/4/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_quit -- :quit[!] + * Quit. + */ +int +ex_quit(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + int force; + + force = F_ISSET(cmdp, E_FORCE); + + /* Check for modifications. */ + if (file_m2(sp, ep, force)) + return (1); + + /* Check for more files to edit. */ + if (ex_ncheck(sp, force)) + return (1); + + F_SET(sp, force ? S_EXIT_FORCE : S_EXIT); + return (0); +} diff --git a/usr.bin/vi/ex/ex_file.c b/usr.bin/vi/ex/ex_file.c new file mode 100644 index 0000000..b2c68e7 --- /dev/null +++ b/usr.bin/vi/ex/ex_file.c @@ -0,0 +1,103 @@ +/*- + * 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_file.c 8.10 (Berkeley) 8/8/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_file -- :f[ile] [name] + * Change the file's name and display the status line. + */ +int +ex_file(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + CHAR_T *p; + FREF *frp; + + switch (cmdp->argc) { + case 0: + break; + case 1: + frp = sp->frp; + + /* Make sure can allocate enough space. */ + if ((p = v_strdup(sp, + cmdp->argv[0]->bp, cmdp->argv[0]->len)) == NULL) + return (1); + + /* If already have a file name, it becomes the alternate. */ + if (!F_ISSET(frp, FR_TMPFILE)) + set_alt_name(sp, frp->name); + + /* Free the previous name. */ + free(frp->name); + frp->name = p; + + /* + * The read-only bit follows the file name; clear it. + * The file has a real name, it's no longer a temporary. + */ + F_CLR(frp, FR_RDONLY | FR_TMPFILE); + + /* Have to force a write if the file exists, next time. */ + F_SET(frp, FR_NAMECHANGE); + break; + default: + abort(); + } + return (msg_status(sp, ep, sp->lno, 1)); +} diff --git a/usr.bin/vi/ex/ex_global.c b/usr.bin/vi/ex/ex_global.c new file mode 100644 index 0000000..624bf8e --- /dev/null +++ b/usr.bin/vi/ex/ex_global.c @@ -0,0 +1,400 @@ +/*- + * 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_global.c 8.41 (Berkeley) 8/9/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +enum which {GLOBAL, VGLOBAL}; + +static int global __P((SCR *, EXF *, EXCMDARG *, enum which)); + +/* + * ex_global -- [line [,line]] g[lobal][!] /pattern/ [commands] + * Exec on lines matching a pattern. + */ +int +ex_global(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + return (global(sp, ep, + cmdp, F_ISSET(cmdp, E_FORCE) ? VGLOBAL : GLOBAL)); +} + +/* + * ex_vglobal -- [line [,line]] v[global] /pattern/ [commands] + * Exec on lines not matching a pattern. + */ +int +ex_vglobal(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + return (global(sp, ep, cmdp, VGLOBAL)); +} + +static int +global(sp, ep, cmdp, cmd) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; + enum which cmd; +{ + MARK abs; + RANGE *rp; + EX_PRIVATE *exp; + recno_t elno, lno; + regmatch_t match[1]; + regex_t *re, lre; + size_t clen, len; + int delim, eval, reflags, replaced, rval; + char *cb, *ptrn, *p, *t; + + /* + * Skip leading white space. Historic vi allowed any non- + * alphanumeric to serve as the global command delimiter. + */ + for (p = cmdp->argv[0]->bp; isblank(*p); ++p); + if (*p == '\0' || isalnum(*p)) { + msgq(sp, M_ERR, "Usage: %s", cmdp->cmd->usage); + return (1); + } + delim = *p++; + + /* + * Get the pattern string, toss escaped characters. + * + * QUOTING NOTE: + * Only toss an escaped character if it escapes a delimiter. + */ + for (ptrn = t = p;;) { + if (p[0] == '\0' || p[0] == delim) { + if (p[0] == delim) + ++p; + /* + * !!! + * Nul terminate the pattern string -- it's passed + * to regcomp which doesn't understand anything else. + */ + *t = '\0'; + break; + } + if (p[0] == '\\' && p[1] == delim) + ++p; + *t++ = *p++; + } + + /* If the pattern string is empty, use the last one. */ + if (*ptrn == '\0') { + if (!F_ISSET(sp, S_SRE_SET)) { + msgq(sp, M_ERR, "No previous regular expression"); + return (1); + } + re = &sp->sre; + } else { + /* Set RE flags. */ + reflags = 0; + if (O_ISSET(sp, O_EXTENDED)) + reflags |= REG_EXTENDED; + if (O_ISSET(sp, O_IGNORECASE)) + reflags |= REG_ICASE; + + /* Convert vi-style RE's to POSIX 1003.2 RE's. */ + if (re_conv(sp, &ptrn, &replaced)) + return (1); + + /* Compile the RE. */ + re = &lre; + eval = regcomp(re, ptrn, reflags); + + /* Free up any allocated memory. */ + if (replaced) + FREE_SPACE(sp, ptrn, 0); + + if (eval) { + re_error(sp, eval, re); + return (1); + } + + /* + * Set saved RE. Historic practice is that + * globals set direction as well as the RE. + */ + sp->sre = lre; + sp->searchdir = FORWARD; + F_SET(sp, S_SRE_SET); + } + + /* + * Get a copy of the command string; the default command is print. + * Don't worry about a set of s with no command, that will + * default to print in the ex parser. + */ + if ((clen = strlen(p)) == 0) { + p = "p"; + clen = 1; + } + MALLOC_RET(sp, cb, char *, clen); + memmove(cb, p, clen); + + /* + * The global commands sets the substitute RE as well as + * the everything-else RE. + */ + sp->subre = sp->sre; + F_SET(sp, S_SUBRE_SET); + + /* Set the global flag. */ + F_SET(sp, S_GLOBAL); + + /* The global commands always set the previous context mark. */ + abs.lno = sp->lno; + abs.cno = sp->cno; + if (mark_set(sp, ep, ABSMARK1, &abs, 1)) + goto err; + + /* + * For each line... The semantics of global matching are that we first + * have to decide which lines are going to get passed to the command, + * and then pass them to the command, ignoring other changes. There's + * really no way to do this in a single pass, since arbitrary line + * creation, deletion and movement can be done in the ex command. For + * example, a good vi clone test is ":g/X/mo.-3", or "g/X/.,.+1d". + * What we do is create linked list of lines that are tracked through + * each ex command. There's a callback routine which the DB interface + * routines call when a line is created or deleted. This doesn't help + * the layering much. + */ + exp = EXP(sp); + for (rval = 0, lno = cmdp->addr1.lno, + elno = cmdp->addr2.lno; lno <= elno; ++lno) { + /* Someone's unhappy, time to stop. */ + if (INTERRUPTED(sp)) + goto interrupted; + + /* Get the line and search for a match. */ + if ((t = file_gline(sp, ep, lno, &len)) == NULL) { + GETLINE_ERR(sp, lno); + goto err; + } + match[0].rm_so = 0; + match[0].rm_eo = len; + switch(eval = regexec(re, t, 1, match, REG_STARTEND)) { + case 0: + if (cmd == VGLOBAL) + continue; + break; + case REG_NOMATCH: + if (cmd == GLOBAL) + continue; + break; + default: + re_error(sp, eval, re); + goto err; + } + + /* If follows the last entry, extend the last entry's range. */ + if ((rp = exp->rangeq.cqh_last) != (void *)&exp->rangeq && + rp->stop == lno - 1) { + ++rp->stop; + continue; + } + + /* Allocate a new range, and append it to the list. */ + CALLOC(sp, rp, RANGE *, 1, sizeof(RANGE)); + if (rp == NULL) + goto err; + rp->start = rp->stop = lno; + CIRCLEQ_INSERT_TAIL(&exp->rangeq, rp, q); + } + + exp = EXP(sp); + exp->range_lno = OOBLNO; + for (;;) { + /* + * Start at the beginning of the range each time, it may have + * been changed (or exhausted) if lines were inserted/deleted. + */ + if ((rp = exp->rangeq.cqh_first) == (void *)&exp->rangeq) + break; + if (rp->start > rp->stop) { + CIRCLEQ_REMOVE(&exp->rangeq, exp->rangeq.cqh_first, q); + free(rp); + continue; + } + + /* + * Execute the command, setting the cursor to the line so that + * relative addressing works. This means that the cursor moves + * to the last line sent to the command, by default, even if + * the command fails. + */ + exp->range_lno = sp->lno = rp->start++; + if (ex_cmd(sp, ep, cb, clen, 0)) + goto err; + + /* Someone's unhappy, time to stop. */ + if (INTERRUPTED(sp)) { +interrupted: msgq(sp, M_INFO, "Interrupted"); + break; + } + } + + /* Set the cursor to the new value, making sure it exists. */ + if (exp->range_lno != OOBLNO) { + if (file_lline(sp, ep, &lno)) + return (1); + sp->lno = + lno < exp->range_lno ? (lno ? lno : 1) : exp->range_lno; + } + if (0) { +err: rval = 1; + } + + /* Command we ran may have set the autoprint flag, clear it. */ + F_CLR(exp, EX_AUTOPRINT); + + /* Clear the global flag. */ + F_CLR(sp, S_GLOBAL); + + /* Free any remaining ranges and the command buffer. */ + while ((rp = exp->rangeq.cqh_first) != (void *)&exp->rangeq) { + CIRCLEQ_REMOVE(&exp->rangeq, exp->rangeq.cqh_first, q); + free(rp); + } + free(cb); + return (rval); +} + +/* + * global_insdel -- + * Update the ranges based on an insertion or deletion. + */ +void +global_insdel(sp, ep, op, lno) + SCR *sp; + EXF *ep; + enum operation op; + recno_t lno; +{ + EX_PRIVATE *exp; + RANGE *nrp, *rp; + + exp = EXP(sp); + + switch (op) { + case LINE_APPEND: + return; + case LINE_DELETE: + for (rp = exp->rangeq.cqh_first; + rp != (void *)&exp->rangeq; rp = nrp) { + nrp = rp->q.cqe_next; + /* If range less than the line, ignore it. */ + if (rp->stop < lno) + continue; + /* If range greater than the line, decrement range. */ + if (rp->start > lno) { + --rp->start; + --rp->stop; + continue; + } + /* Lno is inside the range, decrement the end point. */ + if (rp->start > --rp->stop) { + CIRCLEQ_REMOVE(&exp->rangeq, rp, q); + free(rp); + } + } + break; + case LINE_INSERT: + for (rp = exp->rangeq.cqh_first; + rp != (void *)&exp->rangeq; rp = rp->q.cqe_next) { + /* If range less than the line, ignore it. */ + if (rp->stop < lno) + continue; + /* If range greater than the line, increment range. */ + if (rp->start >= lno) { + ++rp->start; + ++rp->stop; + continue; + } + /* + * Lno is inside the range, so the range must be split. + * Since we're inserting a new element, neither range + * can be exhausted. + */ + CALLOC(sp, nrp, RANGE *, 1, sizeof(RANGE)); + if (nrp == NULL) { + F_SET(sp, S_INTERRUPTED); + return; + } + nrp->start = lno + 1; + nrp->stop = rp->stop + 1; + rp->stop = lno - 1; + CIRCLEQ_INSERT_AFTER(&exp->rangeq, rp, nrp, q); + rp = nrp; + } + break; + case LINE_RESET: + return; + } + /* + * If the command deleted/inserted lines, the cursor moves to + * the line after the deleted/inserted line. + */ + exp->range_lno = lno; +} diff --git a/usr.bin/vi/ex/ex_init.c b/usr.bin/vi/ex/ex_init.c new file mode 100644 index 0000000..79b0e4b --- /dev/null +++ b/usr.bin/vi/ex/ex_init.c @@ -0,0 +1,202 @@ +/*- + * 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_init.c 8.16 (Berkeley) 8/8/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" +#include "tag.h" + +/* + * ex_screen_copy -- + * Copy ex screen. + */ +int +ex_screen_copy(orig, sp) + SCR *orig, *sp; +{ + EX_PRIVATE *oexp, *nexp; + + /* Create the private ex structure. */ + CALLOC_RET(orig, nexp, EX_PRIVATE *, 1, sizeof(EX_PRIVATE)); + sp->ex_private = nexp; + + /* Initialize queues. */ + TAILQ_INIT(&nexp->tagq); + TAILQ_INIT(&nexp->tagfq); + TAILQ_INIT(&nexp->cdq); + CIRCLEQ_INIT(&nexp->rangeq); + + if (orig == NULL) { + nexp->at_lbuf_set = 0; + } else { + oexp = EXP(orig); + + nexp->at_lbuf = oexp->at_lbuf; + nexp->at_lbuf_set = oexp->at_lbuf_set; + + if (oexp->lastbcomm != NULL && + (nexp->lastbcomm = strdup(oexp->lastbcomm)) == NULL) { + msgq(sp, M_SYSERR, NULL); + return(1); + } + + if (ex_tagcopy(orig, sp)) + return (1); + } + return (0); +} + +/* + * ex_screen_end -- + * End a vi screen. + */ +int +ex_screen_end(sp) + SCR *sp; +{ + EX_PRIVATE *exp; + int rval; + + rval = 0; + exp = EXP(sp); + + if (argv_free(sp)) + rval = 1; + + if (exp->ibp != NULL) + FREE(exp->ibp, exp->ibp_len); + + if (exp->lastbcomm != NULL) + FREE(exp->lastbcomm, strlen(exp->lastbcomm) + 1); + + if (ex_tagfree(sp)) + rval = 1; + + if (ex_cdfree(sp)) + rval = 1; + + /* Free private memory. */ + FREE(exp, sizeof(EX_PRIVATE)); + sp->ex_private = NULL; + + return (rval); +} + +/* + * ex_init -- + * Initialize ex. + */ +int +ex_init(sp, ep) + SCR *sp; + EXF *ep; +{ + size_t len; + + /* + * The default address is the last line of the file. If the address + * set bit is on for this file, load the address, ensuring that it + * exists. + */ + if (F_ISSET(sp->frp, FR_CURSORSET)) { + sp->lno = sp->frp->lno; + sp->cno = sp->frp->cno; + + if (file_gline(sp, ep, sp->lno, &len) == NULL) { + if (file_lline(sp, ep, &sp->lno)) + return (1); + if (sp->lno == 0) + sp->lno = 1; + sp->cno = 0; + } else if (sp->cno >= len) + sp->cno = 0; + } else { + if (file_lline(sp, ep, &sp->lno)) + return (1); + if (sp->lno == 0) + sp->lno = 1; + sp->cno = 0; + } + + /* Display the status line. */ + return (msg_status(sp, ep, sp->lno, 0)); +} + +/* + * ex_end -- + * End ex session. + */ +int +ex_end(sp) + SCR *sp; +{ + return (0); +} + +/* + * ex_optchange -- + * Handle change of options for vi. + */ +int +ex_optchange(sp, opt) + SCR *sp; + int opt; +{ + switch (opt) { + case O_CDPATH: + return (ex_cdalloc(sp, O_STR(sp, O_CDPATH))); + case O_TAGS: + return (ex_tagalloc(sp, O_STR(sp, O_TAGS))); + } + return (0); +} diff --git a/usr.bin/vi/ex/ex_join.c b/usr.bin/vi/ex/ex_join.c new file mode 100644 index 0000000..d561f0a --- /dev/null +++ b/usr.bin/vi/ex/ex_join.c @@ -0,0 +1,200 @@ +/*- + * 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_join.c 8.12 (Berkeley) 5/21/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_join -- :[line [,line]] j[oin][!] [count] [flags] + * Join lines. + */ +int +ex_join(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + recno_t from, to; + size_t blen, clen, len, tlen; + int echar, extra, first; + char *bp, *p, *tbp; + + from = cmdp->addr1.lno; + to = cmdp->addr2.lno; + + /* Check for no lines to join. */ + if ((p = file_gline(sp, ep, from + 1, &len)) == NULL) { + msgq(sp, M_ERR, "No following lines to join"); + return (1); + } + + GET_SPACE_RET(sp, bp, blen, 256); + + /* + * The count for the join command was off-by-one, + * historically, to other counts for other commands. + */ + if (F_ISSET(cmdp, E_COUNT)) + ++cmdp->addr2.lno; + + /* + * If only a single address specified, or, the same address + * specified twice, the from/two addresses will be the same. + */ + if (cmdp->addr1.lno == cmdp->addr2.lno) + ++cmdp->addr2.lno; + + clen = tlen = 0; + for (first = 1, + from = cmdp->addr1.lno, to = cmdp->addr2.lno; from <= to; ++from) { + /* + * Get next line. Historic versions of vi allowed "10J" while + * less than 10 lines from the end-of-file, so we do too. + */ + if ((p = file_gline(sp, ep, from, &len)) == NULL) { + cmdp->addr2.lno = from - 1; + break; + } + + /* Empty lines just go away. */ + if (len == 0) + continue; + + /* + * Get more space if necessary. Note, tlen isn't the length + * of the new line, it's roughly the amount of space needed. + * tbp - bp is the length of the new line. + */ + tlen += len + 2; + ADD_SPACE_RET(sp, bp, blen, tlen); + tbp = bp + clen; + + /* + * Historic practice: + * + * If force specified, join without modification. + * If the current line ends with whitespace, strip leading + * whitespace from the joined line. + * If the next line starts with a ), do nothing. + * If the current line ends with ., ? or !, insert two spaces. + * Else, insert one space. + * + * Echar is the last character in the last line joined. + */ + extra = 0; + if (!first && !F_ISSET(cmdp, E_FORCE)) { + if (isblank(echar)) + for (; len && isblank(*p); --len, ++p); + else if (p[0] != ')') { + if (strchr(".?!", echar)) { + *tbp++ = ' '; + ++clen; + extra = 1; + } + *tbp++ = ' '; + ++clen; + for (; len && isblank(*p); --len, ++p); + } + } + + if (len != 0) { + memmove(tbp, p, len); + tbp += len; + clen += len; + echar = p[len - 1]; + } else + echar = ' '; + + /* + * Historic practice for vi was to put the cursor at the first + * inserted whitespace character, if there was one, or the + * first character of the joined line, if there wasn't, or the + * last character of the line if joined to an empty line. If + * a count was specified, the cursor was moved as described + * for the first line joined, ignoring subsequent lines. If + * the join was a ':' command, the cursor was placed at the + * first non-blank character of the line unless the cursor was + * "attracted" to the end of line when the command was executed + * in which case it moved to the new end of line. There are + * probably several more special cases, but frankly, my dear, + * I don't give a damn. This implementation puts the cursor + * on the first inserted whitespace character, the first + * character of the joined line, or the last character of the + * line regardless. Note, if the cursor isn't on the joined + * line (possible with : commands), it is reset to the starting + * line. + */ + if (first) { + sp->cno = (tbp - bp) - (1 + extra); + first = 0; + } else + sp->cno = (tbp - bp) - len - (1 + extra); + } + sp->lno = cmdp->addr1.lno; + + /* Delete the joined lines. */ + for (from = cmdp->addr1.lno, to = cmdp->addr2.lno; to > from; --to) + if (file_dline(sp, ep, to)) + goto err; + + /* If the original line changed, reset it. */ + if (!first && file_sline(sp, ep, from, bp, tbp - bp)) { +err: FREE_SPACE(sp, bp, blen); + return (1); + } + FREE_SPACE(sp, bp, blen); + + sp->rptlines[L_JOINED] += (cmdp->addr2.lno - cmdp->addr1.lno) + 1; + return (0); +} diff --git a/usr.bin/vi/ex/ex_map.c b/usr.bin/vi/ex/ex_map.c new file mode 100644 index 0000000..a1f1915 --- /dev/null +++ b/usr.bin/vi/ex/ex_map.c @@ -0,0 +1,160 @@ +/*- + * 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_map.c 8.17 (Berkeley) 7/16/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_map -- :map[!] [input] [replacement] + * Map a key/string or display mapped keys. + * + * Historical note: + * Historic vi maps were fairly bizarre, and likely to differ in + * very subtle and strange ways from this implementation. Two + * things worth noting are that vi would often hang or drop core + * if the map was strange enough (ex: map X "xy$@x^V), or, simply + * not work. One trick worth remembering is that if you put a + * mark at the start of the map, e.g. map X mx"xy ...), or if you + * put the map in a .exrc file, things would often work much better. + * No clue why. + */ +int +ex_map(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + enum seqtype stype; + CHAR_T *input, *p; + + stype = F_ISSET(cmdp, E_FORCE) ? SEQ_INPUT : SEQ_COMMAND; + + switch (cmdp->argc) { + case 0: + if (seq_dump(sp, stype, 1) == 0) + msgq(sp, M_INFO, "No %s map entries", + stype == SEQ_INPUT ? "input" : "command"); + return (0); + case 2: + input = cmdp->argv[0]->bp; + break; + default: + abort(); + } + + /* + * If the mapped string is #[0-9]* (and wasn't quoted) then store + * the function key mapping, and call the screen specific routine. + * Note, if the screen specific routine is able to create the + * mapping, the SEQ_FUNCMAP type stays around, maybe the next screen + * type can get it right. + */ + if (input[0] == '#') { + for (p = input + 1; isdigit(*p); ++p); + if (p[0] != '\0') + goto nofunc; + + if (seq_set(sp, NULL, 0, input, cmdp->argv[0]->len, + cmdp->argv[1]->bp, cmdp->argv[1]->len, stype, SEQ_FUNCMAP)) + return (1); + return (sp->s_fmap(sp, stype, input, cmdp->argv[0]->len, + cmdp->argv[1]->bp, cmdp->argv[1]->len)); + } + + /* Some single keys may not be remapped in command mode. */ +nofunc: if (stype == SEQ_COMMAND && input[1] == '\0') + switch (KEY_VAL(sp, input[0])) { + case K_COLON: + case K_ESCAPE: + case K_NL: + msgq(sp, M_ERR, "The %s character may not be remapped", + KEY_NAME(sp, input[0])); + return (1); + } + return (seq_set(sp, NULL, 0, input, cmdp->argv[0]->len, + cmdp->argv[1]->bp, cmdp->argv[1]->len, stype, SEQ_USERDEF)); +} + +/* + * ex_unmap -- (:unmap[!] key) + * Unmap a key. + */ +int +ex_unmap(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + if (seq_delete(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len, + F_ISSET(cmdp, E_FORCE) ? SEQ_INPUT : SEQ_COMMAND)) { + msgq(sp, M_INFO, "\"%s\" isn't mapped", cmdp->argv[0]->bp); + return (1); + } + return (0); +} + +/* + * map_save -- + * Save the mapped sequences to a file. + */ +int +map_save(sp, fp) + SCR *sp; + FILE *fp; +{ + if (seq_save(sp, fp, "map ", SEQ_COMMAND)) + return (1); + return (seq_save(sp, fp, "map! ", SEQ_INPUT)); +} diff --git a/usr.bin/vi/ex/ex_mark.c b/usr.bin/vi/ex/ex_mark.c new file mode 100644 index 0000000..bd1e0fd --- /dev/null +++ b/usr.bin/vi/ex/ex_mark.c @@ -0,0 +1,66 @@ +/*- + * 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_mark.c 8.6 (Berkeley) 5/21/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +int +ex_mark(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + if (cmdp->argv[0]->len != 1) { + msgq(sp, M_ERR, "Mark names must be a single character"); + return (1); + } + return (mark_set(sp, ep, cmdp->argv[0]->bp[0], &cmdp->addr1, 1)); +} diff --git a/usr.bin/vi/ex/ex_mkexrc.c b/usr.bin/vi/ex/ex_mkexrc.c new file mode 100644 index 0000000..095d51e --- /dev/null +++ b/usr.bin/vi/ex/ex_mkexrc.c @@ -0,0 +1,130 @@ +/*- + * 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_mkexrc.c 8.12 (Berkeley) 7/15/94"; +#endif /* not lint */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_mkexrc -- :mkexrc[!] [file] + * + * Create (or overwrite) a .exrc file with the current info. + */ +int +ex_mkexrc(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + struct stat sb; + FILE *fp; + int fd, sverrno; + char *fname; + + switch (cmdp->argc) { + case 0: + fname = _PATH_EXRC; + break; + case 1: + fname = cmdp->argv[0]->bp; + set_alt_name(sp, fname); + break; + default: + abort(); + } + + if (!F_ISSET(cmdp, E_FORCE) && !stat(fname, &sb)) { + msgq(sp, M_ERR, + "%s exists, not written; use ! to override", fname); + return (1); + } + + /* Create with max permissions of rw-r--r--. */ + if ((fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) < 0) { + msgq(sp, M_SYSERR, fname); + return (1); + } + + if ((fp = fdopen(fd, "w")) == NULL) { + sverrno = errno; + (void)close(fd); + errno = sverrno; + goto e2; + } + + if (abbr_save(sp, fp) || ferror(fp)) + goto e1; + if (map_save(sp, fp) || ferror(fp)) + goto e1; + if (opts_save(sp, fp) || ferror(fp)) + goto e1; +#ifndef NO_DIGRAPH + digraph_save(sp, fd); +#endif + if (fclose(fp)) + goto e2; + + msgq(sp, M_INFO, "New .exrc file: %s. ", fname); + return (0); + +e1: sverrno = errno; + (void)fclose(fp); + errno = sverrno; +e2: msgq(sp, M_ERR, "%s: incomplete: %s", fname, strerror(errno)); + return (1); +} diff --git a/usr.bin/vi/ex/ex_move.c b/usr.bin/vi/ex/ex_move.c new file mode 100644 index 0000000..9822508 --- /dev/null +++ b/usr.bin/vi/ex/ex_move.c @@ -0,0 +1,222 @@ +/*- + * 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_move.c 8.17 (Berkeley) 8/8/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_copy -- :[line [,line]] co[py] line [flags] + * Copy selected lines. + */ +int +ex_copy(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + CB cb; + MARK fm1, fm2, m, tm; + recno_t cnt; + int rval; + + rval = 0; + + /* + * It's possible to copy things into the area that's being + * copied, e.g. "2,5copy3" is legitimate. Save the text to + * a cut buffer. + */ + fm1 = cmdp->addr1; + fm2 = cmdp->addr2; + memset(&cb, 0, sizeof(cb)); + CIRCLEQ_INIT(&cb.textq); + for (cnt = fm1.lno; cnt <= fm2.lno; ++cnt) + if (cut_line(sp, ep, cnt, 0, 0, &cb)) { + rval = 1; + goto err; + } + cb.flags |= CB_LMODE; + + /* Put the text into place. */ + tm.lno = cmdp->lineno; + tm.cno = 0; + if (put(sp, ep, &cb, NULL, &tm, &m, 1)) + rval = 1; + else { + /* + * Copy puts the cursor on the last line copied. The cursor + * returned by the put routine is the first line put, not the + * last, because that's the historic semantic of vi. + */ + cnt = (fm2.lno - fm1.lno) + 1; + sp->lno = m.lno + (cnt - 1); + sp->cno = 0; + } +err: text_lfree(&cb.textq); + return (rval); +} + +/* + * ex_move -- :[line [,line]] mo[ve] line + * Move selected lines. + */ +int +ex_move(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + LMARK *lmp; + MARK fm1, fm2; + recno_t cnt, diff, fl, tl, mfl, mtl; + size_t blen, len; + int mark_reset; + char *bp, *p; + + /* + * It's not possible to move things into the area that's being + * moved. + */ + fm1 = cmdp->addr1; + fm2 = cmdp->addr2; + if (cmdp->lineno >= fm1.lno && cmdp->lineno <= fm2.lno) { + msgq(sp, M_ERR, "Destination line is inside move range"); + return (1); + } + + /* + * Log the positions of any marks in the to-be-deleted lines. This + * has to work with the logging code. What happens is that we log + * the old mark positions, make the changes, then log the new mark + * positions. Then the marks end up in the right positions no matter + * which way the log is traversed. + * + * XXX + * Reset the MARK_USERSET flag so that the log can undo the mark. + * This isn't very clean, and should probably be fixed. + */ + fl = fm1.lno; + tl = cmdp->lineno; + + /* Log the old positions of the marks. */ + mark_reset = 0; + for (lmp = ep->marks.lh_first; lmp != NULL; lmp = lmp->q.le_next) + if (lmp->name != ABSMARK1 && + lmp->lno >= fl && lmp->lno <= tl) { + mark_reset = 1; + F_CLR(lmp, MARK_USERSET); + (void)log_mark(sp, ep, lmp); + } + + /* Get memory for the copy. */ + GET_SPACE_RET(sp, bp, blen, 256); + + /* Move the lines. */ + diff = (fm2.lno - fm1.lno) + 1; + if (tl > fl) { /* Destination > source. */ + mfl = tl - diff; + mtl = tl; + for (cnt = diff; cnt--;) { + if ((p = file_gline(sp, ep, fl, &len)) == NULL) + return (1); + BINC_RET(sp, bp, blen, len); + memmove(bp, p, len); + if (file_aline(sp, ep, 1, tl, bp, len)) + return (1); + if (mark_reset) + for (lmp = ep->marks.lh_first; + lmp != NULL; lmp = lmp->q.le_next) + if (lmp->name != ABSMARK1 && + lmp->lno == fl) + lmp->lno = tl + 1; + if (file_dline(sp, ep, fl)) + return (1); + } + } else { /* Destination < source. */ + mfl = tl; + mtl = tl + diff; + for (cnt = diff; cnt--;) { + if ((p = file_gline(sp, ep, fl, &len)) == NULL) + return (1); + BINC_RET(sp, bp, blen, len); + memmove(bp, p, len); + if (file_aline(sp, ep, 1, tl++, bp, len)) + return (1); + if (mark_reset) + for (lmp = ep->marks.lh_first; + lmp != NULL; lmp = lmp->q.le_next) + if (lmp->name != ABSMARK1 && + lmp->lno == fl) + lmp->lno = tl; + ++fl; + if (file_dline(sp, ep, fl)) + return (1); + } + } + FREE_SPACE(sp, bp, blen); + + sp->lno = tl; /* Last line moved. */ + sp->cno = 0; + + /* Log the new positions of the marks. */ + if (mark_reset) + for (lmp = ep->marks.lh_first; + lmp != NULL; lmp = lmp->q.le_next) + if (lmp->name != ABSMARK1 && + lmp->lno >= mfl && lmp->lno <= mtl) + (void)log_mark(sp, ep, lmp); + + + sp->rptlines[L_MOVED] += diff; + return (0); +} diff --git a/usr.bin/vi/ex/ex_open.c b/usr.bin/vi/ex/ex_open.c new file mode 100644 index 0000000..385fdd3 --- /dev/null +++ b/usr.bin/vi/ex/ex_open.c @@ -0,0 +1,75 @@ +/*- + * Copyright (c) 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_open.c 8.4 (Berkeley) 5/21/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_open -- :[line] o[pen] [/pattern/] [flags] + * + * Switch to single line "open" mode. + */ +int +ex_open(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + /* If open option off, disallow open command. */ + if (!O_ISSET(sp, O_OPEN)) { + msgq(sp, M_ERR, + "The open command requires that the open option be set"); + return (1); + } + + msgq(sp, M_ERR, "The open command is not yet implemented"); + return (1); +} diff --git a/usr.bin/vi/ex/ex_preserve.c b/usr.bin/vi/ex/ex_preserve.c new file mode 100644 index 0000000..8960f98 --- /dev/null +++ b/usr.bin/vi/ex/ex_preserve.c @@ -0,0 +1,128 @@ +/*- + * Copyright (c) 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_preserve.c 8.12 (Berkeley) 8/4/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_preserve -- :pre[serve] + * Push the file to recovery. + */ +int +ex_preserve(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + recno_t lno; + + if (!F_ISSET(ep, F_RCV_ON)) { + msgq(sp, M_ERR, "Preservation of this file not possible"); + return (1); + } + + /* If recovery not initialized, do so. */ + if (F_ISSET(ep, F_FIRSTMODIFY) && rcv_init(sp, ep)) + return (1); + + /* Force the file to be read in, in case it hasn't yet. */ + if (file_lline(sp, ep, &lno)) + return (1); + + /* Sync to disk. */ + if (rcv_sync(sp, ep, RCV_SNAPSHOT)) + return (1); + + msgq(sp, M_INFO, "File preserved"); + return (0); +} + +/* + * ex_recover -- :rec[over][!] file + * + * Recover the file. + */ +int +ex_recover(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + ARGS *ap; + FREF *frp; + + ap = cmdp->argv[0]; + + /* Set the alternate file name. */ + set_alt_name(sp, ap->bp); + + /* + * Check for modifications. Autowrite did not historically + * affect :recover. + */ + if (file_m2(sp, ep, F_ISSET(cmdp, E_FORCE))) + return (1); + + /* Get a file structure for the file. */ + if ((frp = file_add(sp, ap->bp)) == NULL) + return (1); + + /* Set the recover bit. */ + F_SET(frp, FR_RECOVER); + + /* Switch files. */ + if (file_init(sp, frp, NULL, F_ISSET(cmdp, E_FORCE))) + return (1); + F_SET(sp, S_FSWITCH); + return (0); +} diff --git a/usr.bin/vi/ex/ex_print.c b/usr.bin/vi/ex/ex_print.c new file mode 100644 index 0000000..c3aa22f --- /dev/null +++ b/usr.bin/vi/ex/ex_print.c @@ -0,0 +1,212 @@ +/*- + * 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_print.c 8.14 (Berkeley) 8/7/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_list -- :[line [,line]] l[ist] [count] [flags] + * + * Display the addressed lines such that the output is unambiguous. + */ +int +ex_list(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + if (ex_print(sp, ep, + &cmdp->addr1, &cmdp->addr2, cmdp->flags | E_F_LIST)) + return (1); + sp->lno = cmdp->addr2.lno; + sp->cno = cmdp->addr2.cno; + return (0); +} + +/* + * ex_number -- :[line [,line]] nu[mber] [count] [flags] + * + * Display the addressed lines with a leading line number. + */ +int +ex_number(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + if (ex_print(sp, ep, + &cmdp->addr1, &cmdp->addr2, cmdp->flags | E_F_HASH)) + return (1); + sp->lno = cmdp->addr2.lno; + sp->cno = cmdp->addr2.cno; + return (0); +} + +/* + * ex_pr -- :[line [,line]] p[rint] [count] [flags] + * + * Display the addressed lines. + */ +int +ex_pr(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + if (ex_print(sp, ep, &cmdp->addr1, &cmdp->addr2, cmdp->flags)) + return (1); + sp->lno = cmdp->addr2.lno; + sp->cno = cmdp->addr2.cno; + return (0); +} + +/* + * ex_print -- + * Print the selected lines. + */ +int +ex_print(sp, ep, fp, tp, flags) + SCR *sp; + EXF *ep; + MARK *fp, *tp; + register int flags; +{ + recno_t from, to; + size_t col, len; + char *p; + + F_SET(sp, S_INTERRUPTIBLE); + for (from = fp->lno, to = tp->lno; from <= to; ++from) { + /* + * Display the line number. The %6 format is specified + * by POSIX 1003.2, and is almost certainly large enough. + * Check, though, just in case. + */ + if (LF_ISSET(E_F_HASH)) + if (from <= 999999) + col = ex_printf(EXCOOKIE, "%6ld ", from); + else + col = ex_printf(EXCOOKIE, "TOOBIG "); + else + col = 0; + + /* + * Display the line. The format for E_F_PRINT isn't very good, + * especially in handling end-of-line tabs, but they're almost + * backward compatible. + */ + if ((p = file_gline(sp, ep, from, &len)) == NULL) { + GETLINE_ERR(sp, from); + return (1); + } + + if (len == 0 && !LF_ISSET(E_F_LIST)) + (void)ex_printf(EXCOOKIE, "\n"); + else if (ex_ldisplay(sp, p, len, col, flags)) + return (1); + + if (INTERRUPTED(sp)) + break; + } + + return (0); +} + +/* + * ex_ldisplay -- + * Display a line. + */ +int +ex_ldisplay(sp, lp, len, col, flags) + SCR *sp; + CHAR_T *lp; + size_t len, col; + u_int flags; +{ + CHAR_T ch, *kp; + u_long ts; + size_t tlen; + + ts = O_VAL(sp, O_TABSTOP); + for (;; --len) { + if (len > 0) + ch = *lp++; + else if (LF_ISSET(E_F_LIST)) + ch = '$'; + else + break; + if (ch == '\t' && !LF_ISSET(E_F_LIST)) + for (tlen = ts - col % ts; + col < sp->cols && tlen--; ++col) + (void)ex_printf(EXCOOKIE, " "); + else { + kp = KEY_NAME(sp, ch); + tlen = KEY_LEN(sp, ch); + if (col + tlen < sp->cols) { + (void)ex_printf(EXCOOKIE, "%s", kp); + col += tlen; + } else + for (; tlen--; ++kp, ++col) { + if (col == sp->cols) { + col = 0; + (void)ex_printf(EXCOOKIE, "\n"); + } + (void)ex_printf(EXCOOKIE, "%c", *kp); + } + } + if (len == 0) + break; + } + (void)ex_printf(EXCOOKIE, "\n"); + return (0); +} diff --git a/usr.bin/vi/ex/ex_put.c b/usr.bin/vi/ex/ex_put.c new file mode 100644 index 0000000..ce44b5f --- /dev/null +++ b/usr.bin/vi/ex/ex_put.c @@ -0,0 +1,78 @@ +/*- + * 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_put.c 8.6 (Berkeley) 7/23/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_put -- [line] pu[t] [buffer] + * + * Append a cut buffer into the file. + */ +int +ex_put(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + MARK m; + + m.lno = sp->lno; + m.cno = sp->cno; + if (put(sp, ep, NULL, F_ISSET(cmdp, E_BUFFER) ? &cmdp->buffer : NULL, + &cmdp->addr1, &m, 1)) + return (1); + sp->lno = m.lno; + sp->cno = m.cno; + return (0); +} diff --git a/usr.bin/vi/ex/ex_read.c b/usr.bin/vi/ex/ex_read.c new file mode 100644 index 0000000..dafc4ad --- /dev/null +++ b/usr.bin/vi/ex/ex_read.c @@ -0,0 +1,300 @@ +/*- + * 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_read.c 8.39 (Berkeley) 8/9/94"; +#endif /* not lint */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_read -- :read [file] + * :read [!cmd] + * Read from a file or utility. + * + * !!! + * Historical vi wouldn't undo a filter read, for no apparent reason. + */ +int +ex_read(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + struct stat sb; + CHAR_T *arg, *name; + EX_PRIVATE *exp; + FILE *fp; + MARK rm; + recno_t nlines; + size_t arglen, blen, len; + int btear, farg, rval; + char *p; + + /* + * 0 args: we're done. + * 1 args: check for "read !arg". + * 2 args: check for "read ! arg". + * >2 args: object, too many args. + */ + farg = 0; + switch (cmdp->argc) { + case 0: + break; + case 1: + arg = cmdp->argv[0]->bp; + arglen = cmdp->argv[0]->len; + if (*arg == '!') { + ++arg; + --arglen; + farg = 1; + } + break; + case 2: + if (cmdp->argv[0]->len == 1 && cmdp->argv[0]->bp[0] == '!') { + arg = cmdp->argv[1]->bp; + arglen = cmdp->argv[1]->len; + farg = 2; + break; + } + /* FALLTHROUGH */ + default: + goto badarg; + } + + if (farg != 0) { + /* File name and bang expand the user's argument. */ + if (argv_exp1(sp, ep, cmdp, arg, arglen, 1)) + return (1); + + /* If argc unchanged, there wasn't anything to expand. */ + if (cmdp->argc == farg) + goto usage; + + /* Set the last bang command. */ + exp = EXP(sp); + if (exp->lastbcomm != NULL) + free(exp->lastbcomm); + if ((exp->lastbcomm = strdup(cmdp->argv[farg]->bp)) == NULL) { + msgq(sp, M_SYSERR, NULL); + return (1); + } + + /* Redisplay the user's argument if it's changed. */ + if (F_ISSET(cmdp, E_MODIFY) && IN_VI_MODE(sp)) { + len = cmdp->argv[farg]->len; + GET_SPACE_RET(sp, p, blen, len + 2); + p[0] = '!'; + memmove(p + 1, + cmdp->argv[farg]->bp, cmdp->argv[farg]->len + 1); + (void)sp->s_busy(sp, p); + FREE_SPACE(sp, p, blen); + } + + if (filtercmd(sp, ep, &cmdp->addr1, + NULL, &rm, cmdp->argv[farg]->bp, FILTER_READ)) + return (1); + + /* The filter version of read set the autoprint flag. */ + F_SET(EXP(sp), EX_AUTOPRINT); + + /* If in vi mode, move to the first nonblank. */ + sp->lno = rm.lno; + if (IN_VI_MODE(sp)) { + sp->cno = 0; + (void)nonblank(sp, ep, sp->lno, &sp->cno); + } + return (0); + } + + /* Shell and file name expand the user's argument. */ + if (argv_exp2(sp, ep, cmdp, arg, arglen, 0)) + return (1); + + /* + * 0 args: no arguments, read the current file, don't set the + * alternate file name. + * 1 args: read it, switching to it or settgin the alternate file + * name. + * >1 args: object, too many args. + */ + switch (cmdp->argc) { + case 1: + name = sp->frp->name; + break; + case 2: + name = cmdp->argv[1]->bp; + /* + * !!! + * Historically, if you had an "unnamed" file, the read command + * renamed the file. + */ + if (F_ISSET(sp->frp, FR_TMPFILE) && + !F_ISSET(sp->frp, FR_READNAMED)) { + if ((p = v_strdup(sp, + cmdp->argv[1]->bp, cmdp->argv[1]->len)) != NULL) { + free(sp->frp->name); + sp->frp->name = p; + } + F_SET(sp->frp, FR_NAMECHANGE | FR_READNAMED); + } else + set_alt_name(sp, name); + break; + default: +badarg: msgq(sp, M_ERR, + "%s expanded into too many file names", cmdp->argv[0]->bp); +usage: msgq(sp, M_ERR, "Usage: %s", cmdp->cmd->usage); + return (1); + } + + /* + * !!! + * Historically, vi did not permit reads from non-regular files, + * nor did it distinguish between "read !" and "read!", so there + * was no way to "force" it. + */ + if ((fp = fopen(name, "r")) == NULL || fstat(fileno(fp), &sb)) { + msgq(sp, M_SYSERR, "%s", name); + return (1); + } + if (!S_ISREG(sb.st_mode)) { + (void)fclose(fp); + msgq(sp, M_ERR, "Only regular files may be read"); + return (1); + } + + /* Turn on busy message. */ + btear = F_ISSET(sp, S_EXSILENT) ? 0 : !busy_on(sp, "Reading..."); + rval = ex_readfp(sp, ep, name, fp, &cmdp->addr1, &nlines, 1); + if (btear) + busy_off(sp); + + /* + * Set the cursor to the first line read in, if anything read + * in, otherwise, the address. (Historic vi set it to the + * line after the address regardless, but since that line may + * not exist we don't bother.) + */ + sp->lno = cmdp->addr1.lno; + if (nlines) + ++sp->lno; + + return (rval); +} + +/* + * ex_readfp -- + * Read lines into the file. + */ +int +ex_readfp(sp, ep, name, fp, fm, nlinesp, success_msg) + SCR *sp; + EXF *ep; + char *name; + FILE *fp; + MARK *fm; + recno_t *nlinesp; + int success_msg; +{ + EX_PRIVATE *exp; + recno_t lcnt, lno; + size_t len; + u_long ccnt; /* XXX: can't print off_t portably. */ + int rval; + + rval = 0; + exp = EXP(sp); + + /* + * Add in the lines from the output. Insertion starts at the line + * following the address. + */ + ccnt = 0; + lcnt = 0; + for (lno = fm->lno; !ex_getline(sp, fp, &len); ++lno, ++lcnt) { + if (INTERRUPTED(sp)) { + if (!success_msg) + msgq(sp, M_INFO, "Interrupted"); + break; + } + if (file_aline(sp, ep, 1, lno, exp->ibp, len)) { + rval = 1; + break; + } + ccnt += len; + } + + if (ferror(fp)) { + msgq(sp, M_SYSERR, "%s", name); + rval = 1; + } + + if (fclose(fp)) { + msgq(sp, M_SYSERR, "%s", name); + return (1); + } + + if (rval) + return (1); + + /* Return the number of lines read in. */ + if (nlinesp != NULL) + *nlinesp = lcnt; + + if (success_msg) + msgq(sp, M_INFO, "%s%s: %lu line%s, %lu characters", + INTERRUPTED(sp) ? "Interrupted read: " : "", + name, lcnt, lcnt == 1 ? "" : "s", ccnt); + + return (0); +} diff --git a/usr.bin/vi/ex/ex_screen.c b/usr.bin/vi/ex/ex_screen.c new file mode 100644 index 0000000..179ed07 --- /dev/null +++ b/usr.bin/vi/ex/ex_screen.c @@ -0,0 +1,155 @@ +/*- + * Copyright (c) 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_screen.c 8.13 (Berkeley) 6/27/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_split -- :s[plit] [file ...] + * Split the screen, optionally setting the file list. + */ +int +ex_split(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + return (sp->s_split(sp, cmdp->argc ? cmdp->argv : NULL, cmdp->argc)); +} + +/* + * ex_bg -- :bg + * Hide the screen. + */ +int +ex_bg(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + return (sp->s_bg(sp)); +} + +/* + * ex_fg -- :fg [file] + * Show the screen. + */ +int +ex_fg(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + return (sp->s_fg(sp, cmdp->argc ? cmdp->argv[0]->bp : NULL)); +} + +/* + * ex_resize -- :resize [+-]rows + * Change the screen size. + */ +int +ex_resize(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + enum adjust adj; + + if (!F_ISSET(cmdp, E_COUNT)) { + msgq(sp, M_ERR, "Usage: %s", cmdp->cmd->usage); + return (1); + } + if (F_ISSET(cmdp, E_COUNT_NEG)) + adj = A_DECREASE; + else if (F_ISSET(cmdp, E_COUNT_POS)) + adj = A_INCREASE; + else + adj = A_SET; + return (sp->s_rabs(sp, cmdp->count, adj)); +} + +/* + * ex_sdisplay -- + * Display the list of screens. + */ +int +ex_sdisplay(sp, ep) + SCR *sp; + EXF *ep; +{ + SCR *tsp; + int cnt, col, len, sep; + + if ((tsp = sp->gp->hq.cqh_first) == (void *)&sp->gp->hq) { + (void)ex_printf(EXCOOKIE, + "No backgrounded screens to display.\n"); + return (0); + } + + col = len = sep = 0; + for (cnt = 1; tsp != (void *)&sp->gp->hq; tsp = tsp->q.cqe_next) { + col += len = strlen(tsp->frp->name) + sep; + if (col >= sp->cols - 1) { + col = len; + sep = 0; + (void)ex_printf(EXCOOKIE, "\n"); + } else if (cnt != 1) { + sep = 1; + (void)ex_printf(EXCOOKIE, " "); + } + (void)ex_printf(EXCOOKIE, "%s", tsp->frp->name); + ++cnt; + } + (void)ex_printf(EXCOOKIE, "\n"); + return (0); +} diff --git a/usr.bin/vi/ex/ex_script.c b/usr.bin/vi/ex/ex_script.c new file mode 100644 index 0000000..59586f1 --- /dev/null +++ b/usr.bin/vi/ex/ex_script.c @@ -0,0 +1,582 @@ +/*- + * 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_script.c 8.17 (Berkeley) 7/23/94"; +#endif /* not lint */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" +#include "script.h" + +/* + * XXX + */ +int openpty __P((int *, int *, char *, struct termios *, struct winsize *)); + +static int sscr_getprompt __P((SCR *, EXF *)); +static int sscr_init __P((SCR *, EXF *)); +static int sscr_matchprompt __P((SCR *, char *, size_t, size_t *)); +static int sscr_setprompt __P((SCR *, char *, size_t)); + +/* + * ex_script -- : sc[ript][!] [file] + * + * Switch to script mode. + */ +int +ex_script(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + /* Vi only command. */ + if (!IN_VI_MODE(sp)) { + msgq(sp, M_ERR, + "The script command is only available in vi mode"); + return (1); + } + + /* Switch to the new file. */ + if (cmdp->argc != 0 && ex_edit(sp, ep, cmdp)) + return (1); + + /* + * Create the shell, figure out the prompt. + * + * !!! + * The files just switched, use sp->ep. + */ + if (sscr_init(sp, sp->ep)) + return (1); + + return (0); +} + +/* + * sscr_init -- + * Create a pty setup for a shell. + */ +static int +sscr_init(sp, ep) + SCR *sp; + EXF *ep; +{ + SCRIPT *sc; + char *sh, *sh_path; + + MALLOC_RET(sp, sc, SCRIPT *, sizeof(SCRIPT)); + sp->script = sc; + sc->sh_prompt = NULL; + sc->sh_prompt_len = 0; + + /* + * There are two different processes running through this code. + * They are the shell and the parent. + */ + sc->sh_master = sc->sh_slave = -1; + + if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) { + msgq(sp, M_SYSERR, "tcgetattr"); + goto err; + } + + /* + * Turn off output postprocessing and echo. + */ + sc->sh_term.c_oflag &= ~OPOST; + sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK); + + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) { + msgq(sp, M_SYSERR, "tcgetattr"); + goto err; + } + + if (openpty(&sc->sh_master, + &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) { + msgq(sp, M_SYSERR, "openpty"); + goto err; + } + + /* + * Don't use vfork() here, because the signal semantics differ from + * implementation to implementation. + */ + SIGBLOCK(sp->gp); + switch (sc->sh_pid = fork()) { + case -1: /* Error. */ + SIGUNBLOCK(sp->gp); + + msgq(sp, M_SYSERR, "fork"); +err: if (sc->sh_master != -1) + (void)close(sc->sh_master); + if (sc->sh_slave != -1) + (void)close(sc->sh_slave); + return (1); + case 0: /* Utility. */ + /* The utility has default signal behavior. */ + sig_end(); + + /* + * XXX + * So that shells that do command line editing turn it off. + */ + (void)putenv("TERM=emacs"); + (void)putenv("TERMCAP=emacs:"); + (void)putenv("EMACS=t"); + + (void)setsid(); +#ifdef TIOCSCTTY + /* + * 4.4BSD allocates a controlling terminal using the TIOCSCTTY + * ioctl, not by opening a terminal device file. POSIX 1003.1 + * doesn't define a portable way to do this. If TIOCSCTTY is + * not available, hope that the open does it. + */ + (void)ioctl(sc->sh_slave, TIOCSCTTY, 0); +#endif + (void)close(sc->sh_master); + (void)dup2(sc->sh_slave, STDIN_FILENO); + (void)dup2(sc->sh_slave, STDOUT_FILENO); + (void)dup2(sc->sh_slave, STDERR_FILENO); + (void)close(sc->sh_slave); + + /* Assumes that all shells have -i. */ + sh_path = O_STR(sp, O_SHELL); + if ((sh = strrchr(sh_path, '/')) == NULL) + sh = sh_path; + else + ++sh; + execl(sh_path, sh, "-i", NULL); + msgq(sp, M_ERR, + "Error: execl: %s: %s", sh_path, strerror(errno)); + _exit(127); + default: /* Parent. */ + SIGUNBLOCK(sp->gp); + break; + } + + if (sscr_getprompt(sp, ep)) + return (1); + + F_SET(sp, S_REDRAW | S_SCRIPT); + return (0); + +} + +/* + * sscr_getprompt -- + * Eat lines printed by the shell until a line with no trailing + * carriage return comes; set the prompt from that line. + */ +static int +sscr_getprompt(sp, ep) + SCR *sp; + EXF *ep; +{ + struct timeval tv; + CHAR_T *endp, *p, *t, buf[1024]; + SCRIPT *sc; + fd_set fdset; + recno_t lline; + size_t llen, len; + u_int value; + int nr; + + FD_ZERO(&fdset); + endp = buf; + len = sizeof(buf); + + /* Wait up to a second for characters to read. */ + tv.tv_sec = 5; + tv.tv_usec = 0; + sc = sp->script; + FD_SET(sc->sh_master, &fdset); + switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) { + case -1: /* Error or interrupt. */ + msgq(sp, M_SYSERR, "select"); + goto prompterr; + case 0: /* Timeout */ + msgq(sp, M_ERR, "Error: timed out"); + goto prompterr; + case 1: /* Characters to read. */ + break; + } + + /* Read the characters. */ +more: len = sizeof(buf) - (endp - buf); + switch (nr = read(sc->sh_master, endp, len)) { + case 0: /* EOF. */ + msgq(sp, M_ERR, "Error: shell: EOF"); + goto prompterr; + case -1: /* Error or interrupt. */ + msgq(sp, M_SYSERR, "shell"); + goto prompterr; + default: + endp += nr; + break; + } + + /* If any complete lines, push them into the file. */ + for (p = t = buf; p < endp; ++p) { + value = KEY_VAL(sp, *p); + if (value == K_CR || value == K_NL) { + if (file_lline(sp, ep, &lline) || + file_aline(sp, ep, 0, lline, t, p - t)) + goto prompterr; + t = p + 1; + } + } + if (p > buf) { + memmove(buf, t, endp - t); + endp = buf + (endp - t); + } + if (endp == buf) + goto more; + + /* Wait up 1/10 of a second to make sure that we got it all. */ + tv.tv_sec = 0; + tv.tv_usec = 100000; + switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) { + case -1: /* Error or interrupt. */ + msgq(sp, M_SYSERR, "select"); + goto prompterr; + case 0: /* Timeout */ + break; + case 1: /* Characters to read. */ + goto more; + } + + /* Timed out, so theoretically we have a prompt. */ + llen = endp - buf; + endp = buf; + + /* Append the line into the file. */ + if (file_lline(sp, ep, &lline) || + file_aline(sp, ep, 0, lline, buf, llen)) { +prompterr: sscr_end(sp); + return (1); + } + + return (sscr_setprompt(sp, buf, llen)); +} + +/* + * sscr_exec -- + * Take a line and hand it off to the shell. + */ +int +sscr_exec(sp, ep, lno) + SCR *sp; + EXF *ep; + recno_t lno; +{ + SCRIPT *sc; + recno_t last_lno; + size_t blen, len, last_len, tlen; + int matchprompt, nw, rval; + char *bp, *p; + + /* If there's a prompt on the last line, append the command. */ + if (file_lline(sp, ep, &last_lno)) + return (1); + if ((p = file_gline(sp, ep, last_lno, &last_len)) == NULL) { + GETLINE_ERR(sp, last_lno); + return (1); + } + if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) { + matchprompt = 1; + GET_SPACE_RET(sp, bp, blen, last_len + 128); + memmove(bp, p, last_len); + } else + matchprompt = 0; + + /* Get something to execute. */ + if ((p = file_gline(sp, ep, lno, &len)) == NULL) { + if (file_lline(sp, ep, &lno)) + goto err1; + if (lno == 0) + goto empty; + else + GETLINE_ERR(sp, lno); + goto err1; + } + + /* Empty lines aren't interesting. */ + if (len == 0) + goto empty; + + /* Delete any prompt. */ + if (sscr_matchprompt(sp, p, len, &tlen)) { + if (tlen == len) { +empty: msgq(sp, M_BERR, "Nothing to execute"); + goto err1; + } + p += (len - tlen); + len = tlen; + } + + /* Push the line to the shell. */ + sc = sp->script; + if ((nw = write(sc->sh_master, p, len)) != len) + goto err2; + rval = 0; + if (write(sc->sh_master, "\n", 1) != 1) { +err2: if (nw == 0) + errno = EIO; + msgq(sp, M_SYSERR, "shell"); + goto err1; + } + + if (matchprompt) { + ADD_SPACE_RET(sp, bp, blen, last_len + len); + memmove(bp + last_len, p, len); + if (file_sline(sp, ep, last_lno, bp, last_len + len)) +err1: rval = 1; + } + if (matchprompt) + FREE_SPACE(sp, bp, blen); + return (rval); +} + +/* + * sscr_input -- + * Take a line from the shell and insert it into the file. + */ +int +sscr_input(sp) + SCR *sp; +{ + struct timeval tv; + CHAR_T *endp, *p, *t; + EXF *ep; + SCRIPT *sc; + recno_t lno; + size_t blen, len, tlen; + u_int value; + int nr, rval; + char *bp; + + /* Find out where the end of the file is. */ + ep = sp->ep; + if (file_lline(sp, ep, &lno)) + return (1); + +#define MINREAD 1024 + GET_SPACE_RET(sp, bp, blen, MINREAD); + endp = bp; + + /* Read the characters. */ + rval = 1; + sc = sp->script; +more: switch (nr = read(sc->sh_master, endp, MINREAD)) { + case 0: /* EOF; shell just exited. */ + sscr_end(sp); + F_CLR(sp, S_SCRIPT); + rval = 0; + goto ret; + case -1: /* Error or interrupt. */ + msgq(sp, M_SYSERR, "shell"); + goto ret; + default: + endp += nr; + break; + } + + /* Append the lines into the file. */ + for (p = t = bp; p < endp; ++p) { + value = KEY_VAL(sp, *p); + if (value == K_CR || value == K_NL) { + len = p - t; + if (file_aline(sp, ep, 1, lno++, t, len)) + goto ret; + t = p + 1; + } + } + if (p > t) { + len = p - t; + /* + * If the last thing from the shell isn't another prompt, wait + * up to 1/10 of a second for more stuff to show up, so that + * we don't break the output into two separate lines. Don't + * want to hang indefinitely because some program is hanging, + * confused the shell, or whatever. + */ + if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) { + tv.tv_sec = 0; + tv.tv_usec = 100000; + FD_SET(sc->sh_master, &sp->rdfd); + FD_CLR(STDIN_FILENO, &sp->rdfd); + if (select(sc->sh_master + 1, + &sp->rdfd, NULL, NULL, &tv) == 1) { + memmove(bp, t, len); + endp = bp + len; + goto more; + } + } + if (sscr_setprompt(sp, t, len)) + return (1); + if (file_aline(sp, ep, 1, lno++, t, len)) + goto ret; + } + + /* The cursor moves to EOF. */ + sp->lno = lno; + sp->cno = len ? len - 1 : 0; + rval = sp->s_refresh(sp, ep); + +ret: FREE_SPACE(sp, bp, blen); + return (rval); +} + +/* + * sscr_setprompt -- + * + * Set the prompt to the last line we got from the shell. + * + */ +static int +sscr_setprompt(sp, buf, len) + SCR *sp; + char* buf; + size_t len; +{ + SCRIPT *sc; + + sc = sp->script; + if (sc->sh_prompt) + FREE(sc->sh_prompt, sc->sh_prompt_len); + MALLOC(sp, sc->sh_prompt, char *, len + 1); + if (sc->sh_prompt == NULL) { + sscr_end(sp); + return (1); + } + memmove(sc->sh_prompt, buf, len); + sc->sh_prompt_len = len; + sc->sh_prompt[len] = '\0'; + return (0); +} + +/* + * sscr_matchprompt -- + * Check to see if a line matches the prompt. Nul's indicate + * parts that can change, in both content and size. + */ +static int +sscr_matchprompt(sp, lp, line_len, lenp) + SCR *sp; + char *lp; + size_t line_len, *lenp; +{ + SCRIPT *sc; + size_t prompt_len; + char *pp; + + sc = sp->script; + if (line_len < (prompt_len = sc->sh_prompt_len)) + return (0); + + for (pp = sc->sh_prompt; + prompt_len && line_len; --prompt_len, --line_len) { + if (*pp == '\0') { + for (; prompt_len && *pp == '\0'; --prompt_len, ++pp); + if (!prompt_len) + return (0); + for (; line_len && *lp != *pp; --line_len, ++lp); + if (!line_len) + return (0); + } + if (*pp++ != *lp++) + break; + } + + if (prompt_len) + return (0); + if (lenp != NULL) + *lenp = line_len; + return (1); +} + +/* + * sscr_end -- + * End the pipe to a shell. + */ +int +sscr_end(sp) + SCR *sp; +{ + SCRIPT *sc; + int rval; + + if ((sc = sp->script) == NULL) + return (0); + + /* Turn off the script flag. */ + F_CLR(sp, S_SCRIPT); + + /* Close down the parent's file descriptors. */ + if (sc->sh_master != -1) + (void)close(sc->sh_master); + if (sc->sh_slave != -1) + (void)close(sc->sh_slave); + + /* This should have killed the child. */ + rval = proc_wait(sp, (long)sc->sh_pid, "script-shell", 0); + + /* Free memory. */ + FREE(sc->sh_prompt, sc->sh_prompt_len); + FREE(sc, sizeof(SCRIPT)); + sp->script = NULL; + + return (rval); +} diff --git a/usr.bin/vi/ex/ex_set.c b/usr.bin/vi/ex/ex_set.c new file mode 100644 index 0000000..1ad19f7 --- /dev/null +++ b/usr.bin/vi/ex/ex_set.c @@ -0,0 +1,70 @@ +/*- + * 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_set.c 8.4 (Berkeley) 7/22/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +int +ex_set(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + switch(cmdp->argc) { + case 0: + opts_dump(sp, CHANGED_DISPLAY); + break; + default: + opts_set(sp, cmdp->cmd->usage, cmdp->argv); + break; + } + return (0); +} diff --git a/usr.bin/vi/ex/ex_shell.c b/usr.bin/vi/ex/ex_shell.c new file mode 100644 index 0000000..bcbb97d --- /dev/null +++ b/usr.bin/vi/ex/ex_shell.c @@ -0,0 +1,150 @@ +/*- + * 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_shell.c 8.24 (Berkeley) 8/5/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" +#include "../svi/svi_screen.h" + +/* + * ex_shell -- :sh[ell] + * Invoke the program named in the SHELL environment variable + * with the argument -i. + */ +int +ex_shell(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + char buf[MAXPATHLEN]; + + (void)snprintf(buf, sizeof(buf), "%s -i", O_STR(sp, O_SHELL)); + return (ex_exec_proc(sp, buf, "\n", NULL)); +} + +/* + * ex_exec_proc -- + * Run a separate process. + */ +int +ex_exec_proc(sp, cmd, p1, p2) + SCR *sp; + char *cmd, *p1, *p2; +{ + const char *name; + pid_t pid; + int rval, teardown; + + /* Clear the rest of the screen. */ + if (sp->s_clear(sp)) + return (1); + + /* Save ex/vi terminal settings, and restore the original ones. */ + teardown = !ex_sleave(sp); + + /* + * Flush waiting messages (autowrite, for example) so the output + * matches historic practice. + */ + (void)sex_refresh(sp, sp->ep); + + /* Put out various messages. */ + if (p1 != NULL) + (void)write(STDOUT_FILENO, p1, strlen(p1)); + if (p2 != NULL) + (void)write(STDOUT_FILENO, p2, strlen(p2)); + + SIGBLOCK(sp->gp); + switch (pid = vfork()) { + case -1: /* Error. */ + SIGUNBLOCK(sp->gp); + + msgq(sp, M_SYSERR, "vfork"); + rval = 1; + break; + case 0: /* Utility. */ + /* The utility has default signal behavior. */ + sig_end(); + + if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL) + name = O_STR(sp, O_SHELL); + else + ++name; + execl(O_STR(sp, O_SHELL), name, "-c", cmd, NULL); + msgq(sp, M_ERR, "Error: execl: %s: %s", + O_STR(sp, O_SHELL), strerror(errno)); + _exit(127); + /* NOTREACHED */ + default: /* Parent. */ + SIGUNBLOCK(sp->gp); + + rval = proc_wait(sp, (long)pid, cmd, 0); + break; + } + + /* Restore ex/vi terminal settings. */ + if (teardown) + ex_rleave(sp); + + /* + * XXX + * Stat of the tty structures (see ex_sleave, ex_rleave) only give + * us 1-second resolution on the tty changes. A fast '!' command, + * e.g. ":!pwd" can beat us to the refresh. When there's better + * resolution from the stat(2) timers, this can go away. + */ + F_SET(sp, S_REFRESH); + + return (rval); +} diff --git a/usr.bin/vi/ex/ex_shift.c b/usr.bin/vi/ex/ex_shift.c new file mode 100644 index 0000000..41cb557 --- /dev/null +++ b/usr.bin/vi/ex/ex_shift.c @@ -0,0 +1,204 @@ +/*- + * 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_shift.c 8.14 (Berkeley) 3/14/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +enum which {LEFT, RIGHT}; +static int shift __P((SCR *, EXF *, EXCMDARG *, enum which)); + +int +ex_shiftl(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + return (shift(sp, ep, cmdp, LEFT)); +} + +int +ex_shiftr(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + return (shift(sp, ep, cmdp, RIGHT)); +} + +static int +shift(sp, ep, cmdp, rl) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; + enum which rl; +{ + recno_t from, to; + size_t blen, len, newcol, newidx, oldcol, oldidx, sw; + int curset; + char *p, *bp, *tbp; + + if (O_VAL(sp, O_SHIFTWIDTH) == 0) { + msgq(sp, M_INFO, "shiftwidth option set to 0"); + return (0); + } + + /* + * The historic version of vi permitted the user to string any number + * of '>' or '<' characters together, resulting in an indent of the + * appropriate levels. There's a special hack in ex_cmd() so that + * cmdp->argv[0] points to the string of '>' or '<' characters. + * + * Q: What's the difference between the people adding features + * to vi and the Girl Scouts? + * A: The Girl Scouts have mint cookies and adult supervision. + */ + for (p = cmdp->argv[0]->bp, sw = 0; *p == '>' || *p == '<'; ++p) + sw += O_VAL(sp, O_SHIFTWIDTH); + + GET_SPACE_RET(sp, bp, blen, 256); + + curset = 0; + for (from = cmdp->addr1.lno, to = cmdp->addr2.lno; from <= to; ++from) { + if ((p = file_gline(sp, ep, from, &len)) == NULL) + goto err; + if (!len) { + if (sp->lno == from) + curset = 1; + continue; + } + + /* + * Calculate the old indent amount and the number of + * characters it used. + */ + for (oldidx = 0, oldcol = 0; oldidx < len; ++oldidx) + if (p[oldidx] == ' ') + ++oldcol; + else if (p[oldidx] == '\t') + oldcol += O_VAL(sp, O_TABSTOP) - + oldcol % O_VAL(sp, O_TABSTOP); + else + break; + + /* Calculate the new indent amount. */ + if (rl == RIGHT) + newcol = oldcol + sw; + else { + newcol = oldcol < sw ? 0 : oldcol - sw; + if (newcol == oldcol) { + if (sp->lno == from) + curset = 1; + continue; + } + } + + /* Get a buffer that will hold the new line. */ + ADD_SPACE_RET(sp, bp, blen, newcol + len); + + /* + * Build a new indent string and count the number of + * characters it uses. + */ + for (tbp = bp, newidx = 0; + newcol >= O_VAL(sp, O_TABSTOP); ++newidx) { + *tbp++ = '\t'; + newcol -= O_VAL(sp, O_TABSTOP); + } + for (; newcol > 0; --newcol, ++newidx) + *tbp++ = ' '; + + /* Add the original line. */ + memmove(tbp, p + oldidx, len - oldidx); + + /* Set the replacement line. */ + if (file_sline(sp, ep, from, bp, (tbp + (len - oldidx)) - bp)) { +err: FREE_SPACE(sp, bp, blen); + return (1); + } + + /* + * !!! + * The shift command in historic vi had the usual bizarre + * collection of cursor semantics. If called from vi, the + * cursor was repositioned to the first non-blank character + * of the lowest numbered line shifted. If called from ex, + * the cursor was repositioned to the first non-blank of the + * highest numbered line shifted. Here, if the cursor isn't + * part of the set of lines that are moved, move it to the + * first non-blank of the last line shifted. (This makes + * ":3>>" in vi work reasonably.) If the cursor is part of + * the shifted lines, it doesn't get moved at all. This + * permits shifting of marked areas, i.e. ">'a." shifts the + * marked area twice, something that couldn't be done with + * historic vi. + */ + if (sp->lno == from) { + curset = 1; + if (newidx > oldidx) + sp->cno += newidx - oldidx; + else if (sp->cno >= oldidx - newidx) + sp->cno -= oldidx - newidx; + } + } + if (!curset) { + sp->lno = to; + sp->cno = 0; + (void)nonblank(sp, ep, to, &sp->cno); + } + + FREE_SPACE(sp, bp, blen); + + sp->rptlines[rl == RIGHT ? L_RSHIFT : L_LSHIFT] += + cmdp->addr2.lno - cmdp->addr1.lno + 1; + return (0); +} diff --git a/usr.bin/vi/ex/ex_source.c b/usr.bin/vi/ex/ex_source.c new file mode 100644 index 0000000..70b85b9 --- /dev/null +++ b/usr.bin/vi/ex/ex_source.c @@ -0,0 +1,66 @@ +/*- + * 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_source.c 8.5 (Berkeley) 8/8/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_source -- :source file + * Execute ex commands from a file. + */ +int +ex_source(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + return (ex_cfile(sp, ep, cmdp->argv[0]->bp, 0)); +} diff --git a/usr.bin/vi/ex/ex_stop.c b/usr.bin/vi/ex/ex_stop.c new file mode 100644 index 0000000..3487bbb --- /dev/null +++ b/usr.bin/vi/ex/ex_stop.c @@ -0,0 +1,76 @@ +/*- + * Copyright (c) 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_stop.c 8.7 (Berkeley) 8/14/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" +#include "../sex/sex_screen.h" + +/* + * ex_stop -- :stop[!] + * :suspend[!] + * Suspend execution. + */ +int +ex_stop(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + /* For some strange reason, the force flag turns off autowrite. */ + if (!F_ISSET(cmdp, E_FORCE) && + F_ISSET(ep, F_MODIFIED) && O_ISSET(sp, O_AUTOWRITE) && + file_write(sp, ep, NULL, NULL, NULL, FS_ALL)) + return (1); + return (sp->s_suspend(sp)); +} diff --git a/usr.bin/vi/ex/ex_subst.c b/usr.bin/vi/ex/ex_subst.c new file mode 100644 index 0000000..54b6ee8 --- /dev/null +++ b/usr.bin/vi/ex/ex_subst.c @@ -0,0 +1,1001 @@ +/*- + * 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_subst.c 8.57 (Berkeley) 8/7/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +#define SUB_FIRST 0x01 /* The 'r' flag isn't reasonable. */ +#define SUB_MUSTSETR 0x02 /* The 'r' flag is required. */ + +static __inline int regsub __P((SCR *, char *, + char **, size_t *, size_t *, regmatch_t [10])); +static int substitute __P((SCR *, EXF *, + EXCMDARG *, char *, regex_t *, u_int)); + +/* + * ex_substitute -- + * [line [,line]] s[ubstitute] [[/;]pat[/;]/repl[/;] [cgr] [count] [#lp]] + * + * Substitute on lines matching a pattern. + */ +int +ex_substitute(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + regex_t *re, lre; + size_t blen, len; + u_int flags; + int delim, eval, reflags, replaced; + char *bp, *ptrn, *rep, *p, *t; + + /* + * Skip leading white space. + * + * !!! + * Historic vi allowed any non-alphanumeric to serve as the + * substitution command delimiter. + * + * !!! + * If the arguments are empty, it's the same as &, i.e. we + * repeat the last substitution. + */ + for (p = cmdp->argv[0]->bp, + len = cmdp->argv[0]->len; len > 0; --len, ++p) { + if (!isblank(*p)) + break; + } + if (len == 0) + return (ex_subagain(sp, ep, cmdp)); + delim = *p++; + if (isalnum(delim)) + return (substitute(sp, ep, + cmdp, p, &sp->subre, SUB_MUSTSETR)); + + /* + * !!! + * The full-blown substitute command reset the remembered + * state of the 'c' and 'g' suffices. + */ + sp->c_suffix = sp->g_suffix = 0; + + /* + * Get the pattern string, toss escaped characters. + * + * !!! + * Historic vi accepted any of the following forms: + * + * :s/abc/def/ change "abc" to "def" + * :s/abc/def change "abc" to "def" + * :s/abc/ delete "abc" + * :s/abc delete "abc" + * + * QUOTING NOTE: + * + * Only toss an escape character if it escapes a delimiter. + * This means that "s/A/\\\\f" replaces "A" with "\\f". It + * would be nice to be more regular, i.e. for each layer of + * escaping a single escape character is removed, but that's + * not how the historic vi worked. + */ + for (ptrn = t = p;;) { + if (p[0] == '\0' || p[0] == delim) { + if (p[0] == delim) + ++p; + /* + * !!! + * Nul terminate the pattern string -- it's passed + * to regcomp which doesn't understand anything else. + */ + *t = '\0'; + break; + } + if (p[0] == '\\') + if (p[1] == delim) + ++p; + else if (p[1] == '\\') + *t++ = *p++; + *t++ = *p++; + } + + /* + * If the pattern string is empty, use the last RE (not just the + * last substitution RE). + */ + if (*ptrn == '\0') { + if (!F_ISSET(sp, S_SRE_SET)) { + msgq(sp, M_ERR, "No previous regular expression"); + return (1); + } + re = &sp->sre; + flags = 0; + } else { + /* Set RE flags. */ + reflags = 0; + if (O_ISSET(sp, O_EXTENDED)) + reflags |= REG_EXTENDED; + if (O_ISSET(sp, O_IGNORECASE)) + reflags |= REG_ICASE; + + /* Convert vi-style RE's to POSIX 1003.2 RE's. */ + if (re_conv(sp, &ptrn, &replaced)) + return (1); + + /* Compile the RE. */ + eval = regcomp(&lre, (char *)ptrn, reflags); + + /* Free up any allocated memory. */ + if (replaced) + FREE_SPACE(sp, ptrn, 0); + + if (eval) { + re_error(sp, eval, &lre); + return (1); + } + + /* + * Set saved RE. + * + * !!! + * Historic practice is that substitutes set the search + * direction as well as both substitute and search RE's. + */ + sp->searchdir = FORWARD; + sp->sre = lre; + F_SET(sp, S_SRE_SET); + sp->subre = lre; + F_SET(sp, S_SUBRE_SET); + + re = &lre; + flags = SUB_FIRST; + } + + /* + * Get the replacement string. + * + * The special character & (\& if O_MAGIC not set) matches the + * entire RE. No handling of & is required here, it's done by + * regsub(). + * + * The special character ~ (\~ if O_MAGIC not set) inserts the + * previous replacement string into this replacement string. + * Count ~'s to figure out how much space we need. We could + * special case nonexistent last patterns or whether or not + * O_MAGIC is set, but it's probably not worth the effort. + * + * QUOTING NOTE: + * + * Only toss an escape character if it escapes a delimiter or + * if O_MAGIC is set and it escapes a tilde. + * + * !!! + * If the entire replacement pattern is "%", then use the last + * replacement pattern. This semantic was added to vi in System + * V and then percolated elsewhere, presumably around the time + * that it was added to their version of ed(1). + */ + if (p[0] == '\0' || p[0] == delim) { + if (p[0] == delim) + ++p; + if (sp->repl != NULL) + FREE(sp->repl, sp->repl_len); + sp->repl = NULL; + sp->repl_len = 0; + } else if (p[0] == '%' && (p[1] == '\0' || p[1] == delim)) + p += p[1] == delim ? 2 : 1; + else { + for (rep = p, len = 0; + p[0] != '\0' && p[0] != delim; ++p, ++len) + if (p[0] == '~') + len += sp->repl_len; + GET_SPACE_RET(sp, bp, blen, len); + for (t = bp, len = 0, p = rep;;) { + if (p[0] == '\0' || p[0] == delim) { + if (p[0] == delim) + ++p; + break; + } + if (p[0] == '\\') { + if (p[1] == delim) + ++p; + else if (p[1] == '\\') { + *t++ = *p++; + ++len; + } else if (p[1] == '~') { + ++p; + if (!O_ISSET(sp, O_MAGIC)) + goto tilde; + } + } else if (p[0] == '~' && O_ISSET(sp, O_MAGIC)) { +tilde: ++p; + memmove(t, sp->repl, sp->repl_len); + t += sp->repl_len; + len += sp->repl_len; + continue; + } + *t++ = *p++; + ++len; + } + if ((sp->repl_len = len) != 0) { + if (sp->repl != NULL) + free(sp->repl); + if ((sp->repl = malloc(len)) == NULL) { + msgq(sp, M_SYSERR, NULL); + FREE_SPACE(sp, bp, blen); + return (1); + } + memmove(sp->repl, bp, len); + } + FREE_SPACE(sp, bp, blen); + } + return (substitute(sp, ep, cmdp, p, re, flags)); +} + +/* + * ex_subagain -- + * [line [,line]] & [cgr] [count] [#lp]] + * + * Substitute using the last substitute RE and replacement pattern. + */ +int +ex_subagain(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + if (!F_ISSET(sp, S_SUBRE_SET)) { + msgq(sp, M_ERR, "No previous regular expression"); + return (1); + } + return (substitute(sp, ep, cmdp, cmdp->argv[0]->bp, &sp->subre, 0)); +} + +/* + * ex_subtilde -- + * [line [,line]] ~ [cgr] [count] [#lp]] + * + * Substitute using the last RE and last substitute replacement pattern. + */ +int +ex_subtilde(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + if (!F_ISSET(sp, S_SRE_SET)) { + msgq(sp, M_ERR, "No previous regular expression"); + return (1); + } + return (substitute(sp, ep, cmdp, cmdp->argv[0]->bp, &sp->sre, 0)); +} + +/* + * The nasty part of the substitution is what happens when the replacement + * string contains newlines. It's a bit tricky -- consider the information + * that has to be retained for "s/f\(o\)o/^M\1^M\1/". The solution here is + * to build a set of newline offsets which we use to break the line up later, + * when the replacement is done. Don't change it unless you're pretty damned + * confident. + */ +#define NEEDNEWLINE(sp) { \ + if (sp->newl_len == sp->newl_cnt) { \ + sp->newl_len += 25; \ + REALLOC(sp, sp->newl, size_t *, \ + sp->newl_len * sizeof(size_t)); \ + if (sp->newl == NULL) { \ + sp->newl_len = 0; \ + return (1); \ + } \ + } \ +} + +#define BUILD(sp, l, len) { \ + if (lbclen + (len) > lblen) { \ + lblen += MAX(lbclen + (len), 256); \ + REALLOC(sp, lb, char *, lblen); \ + if (lb == NULL) { \ + lbclen = 0; \ + return (1); \ + } \ + } \ + memmove(lb + lbclen, l, len); \ + lbclen += len; \ +} + +#define NEEDSP(sp, len, pnt) { \ + if (lbclen + (len) > lblen) { \ + lblen += MAX(lbclen + (len), 256); \ + REALLOC(sp, lb, char *, lblen); \ + if (lb == NULL) { \ + lbclen = 0; \ + return (1); \ + } \ + pnt = lb + lbclen; \ + } \ +} + +/* + * substitute -- + * Do the substitution. This stuff is *really* tricky. There are + * lots of special cases, and general nastiness. Don't mess with it + * unless you're pretty confident. + */ +static int +substitute(sp, ep, cmdp, s, re, flags) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; + char *s; + regex_t *re; + u_int flags; +{ + MARK from, to; + recno_t elno, lno; + regmatch_t match[10]; + size_t blen, cnt, last, lbclen, lblen, len, llen, offset, saved_offset; + int cflag, lflag, nflag, pflag, rflag; + int didsub, do_eol_match, eflags, empty_ok, eval; + int linechanged, matched, quit, rval; + char *bp, *lb; + + /* + * !!! + * Historically, the 'g' and 'c' suffices were always toggled as flags, + * so ":s/A/B/" was the same as ":s/A/B/ccgg". If O_EDCOMPATIBLE was + * not set, they were initialized to 0 for all substitute commands. If + * O_EDCOMPATIBLE was set, they were initialized to 0 only if the user + * specified substitute/replacement patterns (see ex_substitute()). + */ + if (!O_ISSET(sp, O_EDCOMPATIBLE)) + sp->c_suffix = sp->g_suffix = 0; + + /* + * Historic vi permitted the '#', 'l' and 'p' options in vi mode, but + * it only displayed the last change. I'd disallow them, but they are + * useful in combination with the [v]global commands. In the current + * model the problem is combining them with the 'c' flag -- the screen + * would have to flip back and forth between the confirm screen and the + * ex print screen, which would be pretty awful. We do display all + * changes, though, for what that's worth. + * + * !!! + * Historic vi was fairly strict about the order of "options", the + * count, and "flags". I'm somewhat fuzzy on the difference between + * options and flags, anyway, so this is a simpler approach, and we + * just take it them in whatever order the user gives them. (The ex + * usage statement doesn't reflect this.) + */ + cflag = lflag = nflag = pflag = rflag = 0; + for (lno = OOBLNO; *s != '\0'; ++s) + switch (*s) { + case ' ': + case '\t': + continue; + case '+': + ++cmdp->flagoff; + break; + case '-': + --cmdp->flagoff; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (lno != OOBLNO) + goto usage; + errno = 0; + lno = strtoul(s, &s, 10); + if (*s == '\0') /* Loop increment correction. */ + --s; + if (errno == ERANGE) { + if (lno == LONG_MAX) + msgq(sp, M_ERR, "Count overflow"); + else if (lno == LONG_MIN) + msgq(sp, M_ERR, "Count underflow"); + else + msgq(sp, M_SYSERR, NULL); + return (1); + } + /* + * In historic vi, the count was inclusive from the + * second address. + */ + cmdp->addr1.lno = cmdp->addr2.lno; + cmdp->addr2.lno += lno - 1; + break; + case '#': + nflag = 1; + break; + case 'c': + sp->c_suffix = !sp->c_suffix; + break; + case 'g': + sp->g_suffix = !sp->g_suffix; + break; + case 'l': + lflag = 1; + break; + case 'p': + pflag = 1; + break; + case 'r': + if (LF_ISSET(SUB_FIRST)) { + msgq(sp, M_ERR, + "Regular expression specified; r flag meaningless"); + return (1); + } + if (!F_ISSET(sp, S_SRE_SET)) { + msgq(sp, M_ERR, + "No previous regular expression"); + return (1); + } + rflag = 1; + re = &sp->sre; + break; + default: + goto usage; + } + + if (*s != '\0' || !rflag && LF_ISSET(SUB_MUSTSETR)) { +usage: msgq(sp, M_ERR, "Usage: %s", cmdp->cmd->usage); + return (1); + } + + if (IN_VI_MODE(sp) && sp->c_suffix && (lflag || nflag || pflag)) { + msgq(sp, M_ERR, + "The #, l and p flags may not be combined with the c flag in vi mode"); + return (1); + } + + /* + * bp: if interactive, line cache + * blen: if interactive, line cache length + * lb: build buffer pointer. + * lbclen: current length of built buffer. + * lblen; length of build buffer. + */ + bp = lb = NULL; + blen = lbclen = lblen = 0; + + /* For each line... */ + for (matched = quit = 0, lno = cmdp->addr1.lno, + elno = cmdp->addr2.lno; !quit && lno <= elno; ++lno) { + + /* Someone's unhappy, time to stop. */ + if (INTERRUPTED(sp)) { + if (!F_ISSET(sp, S_GLOBAL)) + msgq(sp, M_INFO, "Interrupted"); + break; + } + + /* Get the line. */ + if ((s = file_gline(sp, ep, lno, &llen)) == NULL) { + GETLINE_ERR(sp, lno); + goto ret1; + } + + /* + * Make a local copy if doing confirmation -- when calling + * the confirm routine we're likely to lose the cached copy. + */ + if (sp->c_suffix) { + if (bp == NULL) { + GET_SPACE_RET(sp, bp, blen, llen); + } else + ADD_SPACE_RET(sp, bp, blen, llen); + memmove(bp, s, llen); + s = bp; + } + + /* Start searching from the beginning. */ + offset = 0; + len = llen; + + /* Reset the build buffer offset. */ + lbclen = 0; + + /* Reset empty match flag. */ + empty_ok = 1; + + /* + * We don't want to have to do a setline if the line didn't + * change -- keep track of whether or not this line changed. + * If doing confirmations, don't want to keep setting the + * line if change is refused -- keep track of substitutions. + */ + didsub = linechanged = 0; + + /* New line, do an EOL match. */ + do_eol_match = 1; + + /* It's not nul terminated, but we pretend it is. */ + eflags = REG_STARTEND; + + /* + * The search area is from s + offset to the EOL. + * + * Generally, match[0].rm_so is the offset of the start + * of the match from the start of the search, and offset + * is the offset of the start of the last search. + */ +nextmatch: match[0].rm_so = 0; + match[0].rm_eo = len; + + /* Get the next match. */ + eval = regexec(re, (char *)s + offset, 10, match, eflags); + + /* + * There wasn't a match or if there was an error, deal with + * it. If there was a previous match in this line, resolve + * the changes into the database. Otherwise, just move on. + */ + if (eval == REG_NOMATCH) + goto endmatch; + if (eval != 0) { + re_error(sp, eval, re); + goto ret1; + } + matched = 1; + + /* Only the first search can match an anchored expression. */ + eflags |= REG_NOTBOL; + + /* + * !!! + * It's possible to match 0-length strings -- for example, the + * command s;a*;X;, when matched against the string "aabb" will + * result in "XbXbX", i.e. the matches are "aa", the space + * between the b's and the space between the b's and the end of + * the string. There is a similar space between the beginning + * of the string and the a's. The rule that we use (because vi + * historically used it) is that any 0-length match, occurring + * immediately after a match, is ignored. Otherwise, the above + * example would have resulted in "XXbXbX". Another example is + * incorrectly using " *" to replace groups of spaces with one + * space. + * + * The way we do this is that if we just had a successful match, + * the starting offset does not skip characters, and the match + * is empty, ignore the match and move forward. If there's no + * more characters in the string, we were attempting to match + * after the last character, so quit. + */ + if (!empty_ok && match[0].rm_so == 0 && match[0].rm_eo == 0) { + empty_ok = 1; + if (len == 0) + goto endmatch; + BUILD(sp, s + offset, 1) + ++offset; + --len; + goto nextmatch; + } + + /* Confirm change. */ + if (sp->c_suffix) { + /* + * Set the cursor position for confirmation. Note, + * if we matched on a '$', the cursor may be past + * the end of line. + * + * XXX + * We may want to "fix" this in the confirm routine, + * if the confirm routine should be able to display + * a cursor past EOL. + */ + from.lno = to.lno = lno; + from.cno = match[0].rm_so + offset; + to.cno = match[0].rm_eo; + if (llen == 0) + from.cno = to.cno = 0; + else { + if (to.cno >= llen) + to.cno = llen - 1; + if (from.cno >= llen) + from.cno = llen - 1; + } + switch (sp->s_confirm(sp, ep, &from, &to)) { + case CONF_YES: + break; + case CONF_NO: + didsub = 0; + BUILD(sp, s +offset, match[0].rm_eo); + goto skip; + case CONF_QUIT: + /* Set the quit flag. */ + quit = 1; + + /* If interruptible, pass the info back. */ + if (F_ISSET(sp, S_INTERRUPTIBLE)) + F_SET(sp, S_INTERRUPTED); + + /* + * If any changes, resolve them, otherwise + * return to the main loop. + */ + goto endmatch; + } + } + + /* Copy the bytes before the match into the build buffer. */ + BUILD(sp, s + offset, match[0].rm_so); + + /* Substitute the matching bytes. */ + didsub = 1; + if (regsub(sp, s + offset, &lb, &lbclen, &lblen, match)) + goto ret1; + + /* Set the change flag so we know this line was modified. */ + linechanged = 1; + + /* Move past the matched bytes. */ +skip: offset += match[0].rm_eo; + len -= match[0].rm_eo; + + /* A match cannot be followed by an empty pattern. */ + empty_ok = 0; + + /* + * If doing a global change with confirmation, we have to + * update the screen. The basic idea is to store the line + * so the screen update routines can find it, and restart. + */ + if (didsub && sp->c_suffix && sp->g_suffix) { + /* + * The new search offset will be the end of the + * modified line. + */ + saved_offset = lbclen; + + /* Copy the rest of the line. */ + if (len) + BUILD(sp, s + offset, len) + + /* Set the new offset. */ + offset = saved_offset; + + /* Store inserted lines, adjusting the build buffer. */ + last = 0; + if (sp->newl_cnt) { + for (cnt = 0; + cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { + if (file_iline(sp, ep, lno, + lb + last, sp->newl[cnt] - last)) + goto ret1; + last = sp->newl[cnt] + 1; + ++sp->rptlines[L_ADDED]; + } + lbclen -= last; + offset -= last; + sp->newl_cnt = 0; + } + + /* Store and retrieve the line. */ + if (file_sline(sp, ep, lno, lb + last, lbclen)) + goto ret1; + if ((s = file_gline(sp, ep, lno, &llen)) == NULL) { + GETLINE_ERR(sp, lno); + goto ret1; + } + ADD_SPACE_RET(sp, bp, blen, llen) + memmove(bp, s, llen); + s = bp; + len = llen - offset; + + /* Restart the build. */ + lbclen = 0; + BUILD(sp, s, offset); + + /* + * If we haven't already done the after-the-string + * match, do one. Set REG_NOTEOL so the '$' pattern + * only matches once. + */ + if (!do_eol_match) + goto endmatch; + if (offset == len) { + do_eol_match = 0; + eflags |= REG_NOTEOL; + } + goto nextmatch; + } + + /* + * If it's a global: + * + * If at the end of the string, do a test for the after + * the string match. Set REG_NOTEOL so the '$' pattern + * only matches once. + */ + if (sp->g_suffix && do_eol_match) { + if (len == 0) { + do_eol_match = 0; + eflags |= REG_NOTEOL; + } + goto nextmatch; + } + +endmatch: if (!linechanged) + continue; + + /* Copy any remaining bytes into the build buffer. */ + if (len) + BUILD(sp, s + offset, len) + + /* Store inserted lines, adjusting the build buffer. */ + last = 0; + if (sp->newl_cnt) { + for (cnt = 0; + cnt < sp->newl_cnt; ++cnt, ++lno, ++elno) { + if (file_iline(sp, ep, + lno, lb + last, sp->newl[cnt] - last)) + goto ret1; + last = sp->newl[cnt] + 1; + ++sp->rptlines[L_ADDED]; + } + lbclen -= last; + sp->newl_cnt = 0; + } + + /* Store the changed line. */ + if (file_sline(sp, ep, lno, lb + last, lbclen)) + goto ret1; + + /* Update changed line counter. */ + if (sp->rptlchange != lno) { + sp->rptlchange = lno; + ++sp->rptlines[L_CHANGED]; + } + + /* + * !!! + * Display as necessary. Historic practice is to only + * display the last line of a line split into multiple + * lines. + */ + if (lflag || nflag || pflag) { + from.lno = to.lno = lno; + from.cno = to.cno = 0; + if (lflag) + ex_print(sp, ep, &from, &to, E_F_LIST); + if (nflag) + ex_print(sp, ep, &from, &to, E_F_HASH); + if (pflag) + ex_print(sp, ep, &from, &to, E_F_PRINT); + } + + if (!sp->c_suffix) + sp->lno = lno; + + /* + * !!! + * Move the cursor to the last line changed. + */ + if (!sp->c_suffix) + sp->lno = lno; + } + + /* + * !!! + * Move the cursor to the first non-blank of the last line change. + * + * XXX + * This is NOT backward compatible with historic vi, which always + * moved to the last line actually changed. + */ + if (!sp->c_suffix) { + sp->cno = 0; + (void)nonblank(sp, ep, sp->lno, &sp->cno); + } + + /* + * If not in a global command, and nothing matched, say so. + * Else, if none of the lines displayed, put something up. + */ + if (!matched) { + if (!F_ISSET(sp, S_GLOBAL)) + msgq(sp, M_INFO, "No match found"); + } else if (!lflag && !nflag && !pflag) + F_SET(EXP(sp), EX_AUTOPRINT); + + rval = 0; + if (0) { +ret1: rval = 1; + } + + if (bp != NULL) + FREE_SPACE(sp, bp, blen); + if (lb != NULL) + free(lb); + return (rval); +} + +/* + * regsub -- + * Do the substitution for a regular expression. + */ +static __inline int +regsub(sp, ip, lbp, lbclenp, lblenp, match) + SCR *sp; + char *ip; /* Input line. */ + char **lbp; + size_t *lbclenp, *lblenp; + regmatch_t match[10]; +{ + enum { C_NOTSET, C_LOWER, C_ONELOWER, C_ONEUPPER, C_UPPER } conv; + size_t lbclen, lblen; /* Local copies. */ + size_t mlen; /* Match length. */ + size_t rpl; /* Remaining replacement length. */ + char *rp; /* Replacement pointer. */ + int ch; + int no; /* Match replacement offset. */ + char *p, *t; /* Buffer pointers. */ + char *lb; /* Local copies. */ + + lb = *lbp; /* Get local copies. */ + lbclen = *lbclenp; + lblen = *lblenp; + + /* + * QUOTING NOTE: + * + * There are some special sequences that vi provides in the + * replacement patterns. + * & string the RE matched (\& if nomagic set) + * \# n-th regular subexpression + * \E end \U, \L conversion + * \e end \U, \L conversion + * \l convert the next character to lower-case + * \L convert to lower-case, until \E, \e, or end of replacement + * \u convert the next character to upper-case + * \U convert to upper-case, until \E, \e, or end of replacement + * + * Otherwise, since this is the lowest level of replacement, discard + * all escape characters. This (hopefully) follows historic practice. + */ +#define ADDCH(ch) { \ + CHAR_T __ch = (ch); \ + u_int __value = KEY_VAL(sp, __ch); \ + if (__value == K_CR || __value == K_NL) { \ + NEEDNEWLINE(sp); \ + sp->newl[sp->newl_cnt++] = lbclen; \ + } else if (conv != C_NOTSET) { \ + switch (conv) { \ + case C_ONELOWER: \ + conv = C_NOTSET; \ + /* FALLTHROUGH */ \ + case C_LOWER: \ + if (isupper(__ch)) \ + __ch = tolower(__ch); \ + break; \ + case C_ONEUPPER: \ + conv = C_NOTSET; \ + /* FALLTHROUGH */ \ + case C_UPPER: \ + if (islower(__ch)) \ + __ch = toupper(__ch); \ + break; \ + default: \ + abort(); \ + } \ + } \ + NEEDSP(sp, 1, p); \ + *p++ = __ch; \ + ++lbclen; \ +} + conv = C_NOTSET; + for (rp = sp->repl, rpl = sp->repl_len, p = lb + lbclen; rpl--;) { + switch (ch = *rp++) { + case '&': + if (O_ISSET(sp, O_MAGIC)) { + no = 0; + goto subzero; + } + break; + case '\\': + if (rpl == 0) + break; + --rpl; + switch (ch = *rp) { + case '&': + ++rp; + if (!O_ISSET(sp, O_MAGIC)) { + no = 0; + goto subzero; + } + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + no = *rp++ - '0'; +subzero: if (match[no].rm_so == -1 || + match[no].rm_eo == -1) + break; + mlen = match[no].rm_eo - match[no].rm_so; + for (t = ip + match[no].rm_so; mlen--; ++t) + ADDCH(*t); + continue; + case 'e': + case 'E': + ++rp; + conv = C_NOTSET; + continue; + case 'l': + ++rp; + conv = C_ONELOWER; + continue; + case 'L': + ++rp; + conv = C_LOWER; + continue; + case 'u': + ++rp; + conv = C_ONEUPPER; + continue; + case 'U': + ++rp; + conv = C_UPPER; + continue; + default: + ++rp; + break; + } + } + ADDCH(ch); + } + + *lbp = lb; /* Update caller's information. */ + *lbclenp = lbclen; + *lblenp = lblen; + return (0); +} diff --git a/usr.bin/vi/ex/ex_tag.c b/usr.bin/vi/ex/ex_tag.c new file mode 100644 index 0000000..0bb8c3e --- /dev/null +++ b/usr.bin/vi/ex/ex_tag.c @@ -0,0 +1,905 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * David Hitz of Auspex Systems, Inc. + * + * 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_tag.c 8.43 (Berkeley) 8/4/94"; +#endif /* not lint */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" +#include "tag.h" + +static char *binary_search __P((char *, char *, char *)); +static int compare __P((char *, char *, char *)); +static char *linear_search __P((char *, char *, char *)); +static int search __P((SCR *, char *, char *, char **)); +static int tag_get __P((SCR *, char *, char **, char **, char **)); + +/* + * ex_tagfirst -- + * The tag code can be entered from main, i.e. "vi -t tag". + */ +int +ex_tagfirst(sp, tagarg) + SCR *sp; + char *tagarg; +{ + FREF *frp; + MARK m; + long tl; + u_int flags; + int sval; + char *p, *tag, *name, *search; + + /* Taglength may limit the number of characters. */ + if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(tagarg) > tl) + tagarg[tl] = '\0'; + + /* Get the tag information. */ + if (tag_get(sp, tagarg, &tag, &name, &search)) + return (1); + + /* Create the file entry. */ + if ((frp = file_add(sp, name)) == NULL) + return (1); + if (file_init(sp, frp, NULL, 0)) + return (1); + + /* + * !!! + * The historic tags file format (from a long, long time ago...) + * used a line number, not a search string. I got complaints, so + * people are still using the format. + */ + if (isdigit(search[0])) { + m.lno = atoi(search); + m.cno = 0; + } else { + /* + * Search for the tag; cheap fallback for C functions if + * the name is the same but the arguments have changed. + */ + m.lno = 1; + m.cno = 0; + flags = SEARCH_FILE | SEARCH_TAG | SEARCH_TERM; + sval = f_search(sp, sp->ep, &m, &m, search, NULL, &flags); + if (sval && (p = strrchr(search, '(')) != NULL) { + p[1] = '\0'; + sval = f_search(sp, sp->ep, + &m, &m, search, NULL, &flags); + } + if (sval) + msgq(sp, M_ERR, "%s: search pattern not found", tag); + } + + /* Set up the screen. */ + frp->lno = m.lno; + frp->cno = m.cno; + F_SET(frp, FR_CURSORSET); + + /* Might as well make this the default tag. */ + if ((EXP(sp)->tlast = strdup(tagarg)) == NULL) { + msgq(sp, M_SYSERR, NULL); + return (1); + } + return (0); +} + +/* Free a tag or tagf structure from a queue. */ +#define FREETAG(tp) { \ + TAILQ_REMOVE(&exp->tagq, (tp), q); \ + if ((tp)->search != NULL) \ + free((tp)->search); \ + FREE((tp), sizeof(TAGF)); \ +} +#define FREETAGF(tfp) { \ + TAILQ_REMOVE(&exp->tagfq, (tfp), q); \ + free((tfp)->name); \ + FREE((tfp), sizeof(TAGF)); \ +} + +/* + * ex_tagpush -- :tag [file] + * Move to a new tag. + * + * The tags stacks in nvi are a bit tricky. Each tag contains a file name, + * search string, and line/column numbers. The search string is only used + * for the first access and for user display. The first record on the stack + * is the place where we first did a tag, so it has no search string. The + * second record is the first tag, and so on. Note, this means that the + * "current" tag is always on the stack. Each tag has a line/column which is + * the location from which the user tagged the following TAG entry, and which + * is used as the return location. + */ +int +ex_tagpush(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + enum {TC_CHANGE, TC_CURRENT} which; + EX_PRIVATE *exp; + FREF *frp; + MARK m; + TAG *tp; + u_int flags; + int sval; + long tl; + char *name, *p, *search, *tag; + + exp = EXP(sp); + switch (cmdp->argc) { + case 1: + if (exp->tlast != NULL) + FREE(exp->tlast, strlen(exp->tlast) + 1); + if ((exp->tlast = strdup(cmdp->argv[0]->bp)) == NULL) { + msgq(sp, M_SYSERR, NULL); + return (1); + } + break; + case 0: + if (exp->tlast == NULL) { + msgq(sp, M_ERR, "No previous tag entered"); + return (1); + } + break; + default: + abort(); + } + + /* Taglength may limit the number of characters. */ + if ((tl = O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(exp->tlast) > tl) + exp->tlast[tl] = '\0'; + + /* Get the tag information. */ + if (tag_get(sp, exp->tlast, &tag, &name, &search)) + return (1); + + /* Get the (possibly new) FREF structure. */ + if ((frp = file_add(sp, name)) == NULL) + goto err; + + if (sp->frp == frp) + which = TC_CURRENT; + else { + if (file_m1(sp, sp->ep, + F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE)) + goto err; + which = TC_CHANGE; + } + + /* + * Get a tag structure -- if this is the first tag, push it on the + * stack as a placeholder and get another tag structure. Set the + * line/column of the most recent element on the stack to be the + * current values, including the file pointer. Then push the new + * TAG onto the stack with the new file and search string for user + * display. + */ + CALLOC(sp, tp, TAG *, 1, sizeof(TAG)); + if (tp != NULL && exp->tagq.tqh_first == NULL) { + TAILQ_INSERT_HEAD(&exp->tagq, tp, q); + CALLOC(sp, tp, TAG *, 1, sizeof(TAG)); + } + if (exp->tagq.tqh_first != NULL) { + exp->tagq.tqh_first->frp = sp->frp; + exp->tagq.tqh_first->lno = sp->lno; + exp->tagq.tqh_first->cno = sp->cno; + } + if (tp != NULL) { + if ((tp->search = strdup(search)) == NULL) + msgq(sp, M_SYSERR, NULL); + else + tp->slen = strlen(search); + tp->frp = frp; + TAILQ_INSERT_HEAD(&exp->tagq, tp, q); + } + + /* Switch files. */ + if (which == TC_CHANGE && file_init(sp, frp, NULL, 0)) { + if (tp != NULL) + FREETAG(tp); + /* Handle special, first-tag case. */ + if (exp->tagq.tqh_first->q.tqe_next == NULL) + TAILQ_REMOVE(&exp->tagq, exp->tagq.tqh_first, q); +err: free(tag); + return (1); + } + + /* + * !!! + * Historic vi accepted a line number as well as a search + * string, and people are apparently still using the format. + */ + if (isdigit(search[0])) { + m.lno = atoi(search); + m.cno = 0; + sval = 0; + } else { + /* + * Search for the tag; cheap fallback for C functions + * if the name is the same but the arguments have changed. + */ + m.lno = 1; + m.cno = 0; + flags = SEARCH_FILE | SEARCH_TAG | SEARCH_TERM; + sval = f_search(sp, sp->ep, &m, &m, search, NULL, &flags); + if (sval && (p = strrchr(search, '(')) != NULL) { + p[1] = '\0'; + sval = f_search(sp, sp->ep, + &m, &m, search, NULL, &flags); + p[1] = '('; + } + if (sval) + msgq(sp, M_ERR, "%s: search pattern not found", tag); + } + free(tag); + + switch (which) { + case TC_CHANGE: + frp->lno = m.lno; + frp->cno = m.cno; + F_SET(frp, FR_CURSORSET); + F_SET(sp, S_FSWITCH); + break; + case TC_CURRENT: + if (sval) + return (1); + sp->lno = m.lno; + sp->cno = m.cno; + break; + } + return (0); +} + +/* + * ex_tagpop -- :tagp[op][!] [number | file] + * Pop the tag stack. + */ +int +ex_tagpop(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + EX_PRIVATE *exp; + TAG *ntp, *tp; + long off; + size_t arglen; + char *arg, *p, *t; + + /* Check for an empty stack. */ + exp = EXP(sp); + if (exp->tagq.tqh_first == NULL) { + msgq(sp, M_INFO, "The tags stack is empty"); + return (1); + } + + switch (cmdp->argc) { + case 0: /* Pop one tag. */ + ntp = exp->tagq.tqh_first; + break; + case 1: /* Name or number. */ + arg = cmdp->argv[0]->bp; + off = strtol(arg, &p, 10); + if (*p == '\0') { + if (off < 1) + return (0); + for (tp = exp->tagq.tqh_first; + tp != NULL && --off > 1; tp = tp->q.tqe_next); + if (tp == NULL) { + msgq(sp, M_ERR, +"Less than %s entries on the tags stack; use :display to see the tags stack", + arg); + return (1); + } + ntp = tp; + } else { + arglen = strlen(arg); + for (tp = exp->tagq.tqh_first; + tp != NULL; ntp = tp, tp = tp->q.tqe_next) { + /* Use the user's original file name. */ + p = tp->frp->name; + if ((t = strrchr(p, '/')) == NULL) + t = p; + else + ++t; + if (!strncmp(arg, t, arglen)) + break; + } + if (tp == NULL) { + msgq(sp, M_ERR, +"No file named %s on the tags stack; use :display to see the tags stack", + arg); + return (1); + } + } + break; + default: + abort(); + } + + /* Update the cursor from the saved TAG information. */ + tp = ntp->q.tqe_next; + if (tp->frp == sp->frp) { + sp->lno = tp->lno; + sp->cno = tp->cno; + } else { + if (file_m1(sp, ep, + F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE)) + return (1); + if (file_init(sp, tp->frp, NULL, 0)) + return (1); + + tp->frp->lno = tp->lno; + tp->frp->cno = tp->cno; + F_SET(sp->frp, FR_CURSORSET); + + F_SET(sp, S_FSWITCH); + } + + /* Pop entries off the queue up to ntp. */ + for (;;) { + tp = exp->tagq.tqh_first; + FREETAG(tp); + if (tp == ntp) + break; + } + + /* If returning to the first tag, the stack is now empty. */ + if (exp->tagq.tqh_first->q.tqe_next == NULL) + TAILQ_REMOVE(&exp->tagq, exp->tagq.tqh_first, q); + return (0); +} + +/* + * ex_tagtop -- :tagt[op][!] + * Clear the tag stack. + */ +int +ex_tagtop(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + EX_PRIVATE *exp; + TAG *tp; + + /* Find oldest saved information. */ + exp = EXP(sp); + for (tp = exp->tagq.tqh_first; + tp != NULL && tp->q.tqe_next != NULL; tp = tp->q.tqe_next); + if (tp == NULL) { + msgq(sp, M_INFO, "The tags stack is empty"); + return (1); + } + + /* If not switching files, it's easy; else do the work. */ + if (tp->frp == sp->frp) { + sp->lno = tp->lno; + sp->cno = tp->cno; + } else { + if (file_m1(sp, sp->ep, + F_ISSET(cmdp, E_FORCE), FS_ALL | FS_POSSIBLE)) + return (1); + if (file_init(sp, tp->frp, NULL, 0)) + return (1); + + tp->frp->lno = tp->lno; + tp->frp->cno = tp->cno; + + F_SET(sp->frp, FR_CURSORSET); + F_SET(sp, S_FSWITCH); + } + + /* Empty out the queue. */ + while ((tp = exp->tagq.tqh_first) != NULL) + FREETAG(tp); + return (0); +} + +/* + * ex_tagdisplay -- + * Display the list of tags. + */ +int +ex_tagdisplay(sp, ep) + SCR *sp; + EXF *ep; +{ + EX_PRIVATE *exp; + TAG *tp; + size_t len, maxlen; + int cnt; + char *name; + + exp = EXP(sp); + if ((tp = exp->tagq.tqh_first) == NULL) { + (void)ex_printf(EXCOOKIE, "No tags to display.\n"); + return (0); + } + + /* + * Figure out the formatting. MNOC is the maximum + * number of file name columns before we split the line. + */ +#define MNOC 15 + for (maxlen = 0, + tp = exp->tagq.tqh_first; tp != NULL; tp = tp->q.tqe_next) { + len = strlen(name = tp->frp->name); /* The original name. */ + if (maxlen < len && len < MNOC) + maxlen = len; + } + + for (cnt = 1, tp = exp->tagq.tqh_first; tp != NULL; + ++cnt, tp = tp->q.tqe_next) { + len = strlen(name = tp->frp->name); /* The original name. */ + if (len > maxlen || len + tp->slen > sp->cols) + if (tp == NULL || tp->search == NULL) + (void)ex_printf(EXCOOKIE, + "%2d %s\n", cnt, name); + else + (void)ex_printf(EXCOOKIE, + "%2d %s\n** %*.*s %s\n", cnt, name, + (int)maxlen, (int)maxlen, "", tp->search); + else + if (tp == NULL || tp->search == NULL) + (void)ex_printf(EXCOOKIE, "%2d %*.*s\n", + cnt, (int)maxlen, (int)len, name); + else + (void)ex_printf(EXCOOKIE, "%2d %*.*s %s\n", + cnt, (int)maxlen, (int)len, name, + tp->search); + } + return (0); +} + +/* + * ex_tagalloc -- + * Create a new list of tag files. + */ +int +ex_tagalloc(sp, str) + SCR *sp; + char *str; +{ + EX_PRIVATE *exp; + TAGF *tp; + size_t len; + char *p, *t; + + /* Free current queue. */ + exp = EXP(sp); + while ((tp = exp->tagfq.tqh_first) != NULL) + FREETAGF(tp); + + /* Create new queue. */ + for (p = t = str;; ++p) { + if (*p == '\0' || isblank(*p)) { + if ((len = p - t) > 1) { + MALLOC_RET(sp, tp, TAGF *, sizeof(TAGF)); + MALLOC(sp, tp->name, char *, len + 1); + if (tp->name == NULL) { + FREE(tp, sizeof(TAGF)); + return (1); + } + memmove(tp->name, t, len); + tp->name[len] = '\0'; + tp->flags = 0; + TAILQ_INSERT_TAIL(&exp->tagfq, tp, q); + } + t = p + 1; + } + if (*p == '\0') + break; + } + return (0); +} + /* Free previous queue. */ +/* + * ex_tagfree -- + * Free the tags file list. + */ +int +ex_tagfree(sp) + SCR *sp; +{ + EX_PRIVATE *exp; + TAG *tp; + TAGF *tfp; + + /* Free up tag information. */ + exp = EXP(sp); + while ((tp = exp->tagq.tqh_first) != NULL) + FREETAG(tp); + while ((tfp = exp->tagfq.tqh_first) != NULL) + FREETAGF(tfp); + if (exp->tlast != NULL) + free(exp->tlast); + return (0); +} + +/* + * ex_tagcopy -- + * Copy a screen's tag structures. + */ +int +ex_tagcopy(orig, sp) + SCR *orig, *sp; +{ + EX_PRIVATE *oexp, *nexp; + TAG *ap, *tp; + TAGF *atfp, *tfp; + + /* Copy tag stack. */ + oexp = EXP(orig); + nexp = EXP(sp); + for (ap = oexp->tagq.tqh_first; ap != NULL; ap = ap->q.tqe_next) { + MALLOC(sp, tp, TAG *, sizeof(TAG)); + if (tp == NULL) + goto nomem; + *tp = *ap; + if (ap->search != NULL && + (tp->search = strdup(ap->search)) == NULL) + goto nomem; + TAILQ_INSERT_TAIL(&nexp->tagq, tp, q); + } + + /* Copy list of tag files. */ + for (atfp = oexp->tagfq.tqh_first; + atfp != NULL; atfp = atfp->q.tqe_next) { + MALLOC(sp, tfp, TAGF *, sizeof(TAGF)); + if (tfp == NULL) + goto nomem; + *tfp = *atfp; + if ((tfp->name = strdup(atfp->name)) == NULL) + goto nomem; + TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q); + } + + /* Copy the last tag. */ + if (oexp->tlast != NULL && + (nexp->tlast = strdup(oexp->tlast)) == NULL) { +nomem: msgq(sp, M_SYSERR, NULL); + return (1); + } + return (0); +} + +/* + * tag_get -- + * Get a tag from the tags files. + */ +static int +tag_get(sp, tag, tagp, filep, searchp) + SCR *sp; + char *tag, **tagp, **filep, **searchp; +{ + struct stat sb; + EX_PRIVATE *exp; + TAGF *tfp; + size_t plen, slen, tlen; + int dne; + char *p, pbuf[MAXPATHLEN]; + + /* + * Find the tag, only display missing file messages once, and + * then only if we didn't find the tag. + */ + dne = 0; + exp = EXP(sp); + for (p = NULL, tfp = exp->tagfq.tqh_first; + tfp != NULL && p == NULL; tfp = tfp->q.tqe_next) { + errno = 0; + F_CLR(tfp, TAGF_DNE); + if (search(sp, tfp->name, tag, &p)) + if (errno == ENOENT) { + if (!F_ISSET(tfp, TAGF_DNE_WARN)) { + dne = 1; + F_SET(tfp, TAGF_DNE); + } + } else + msgq(sp, M_SYSERR, tfp->name); + else + if (p != NULL) + break; + } + + if (p == NULL) { + msgq(sp, M_ERR, "%s: tag not found", tag); + if (dne) + for (tfp = exp->tagfq.tqh_first; + tfp != NULL; tfp = tfp->q.tqe_next) + if (F_ISSET(tfp, TAGF_DNE)) { + errno = ENOENT; + msgq(sp, M_SYSERR, tfp->name); + F_SET(tfp, TAGF_DNE_WARN); + } + return (1); + } + + /* + * Set the return pointers; tagp points to the tag, and, incidentally + * the allocated string, filep points to the file name, and searchp + * points to the search string. All three are nul-terminated. + */ + for (*tagp = p; *p && !isblank(*p); ++p); + if (*p == '\0') + goto malformed; + for (*p++ = '\0'; isblank(*p); ++p); + for (*filep = p; *p && !isblank(*p); ++p); + if (*p == '\0') + goto malformed; + for (*p++ = '\0'; isblank(*p); ++p); + *searchp = p; + if (*p == '\0') { +malformed: free(*tagp); + msgq(sp, M_ERR, "%s: corrupted tag in %s", tag, tfp->name); + return (1); + } + + /* + * !!! + * If the tag file path is a relative path, see if it exists. If it + * doesn't, look relative to the tags file path. It's okay for a tag + * file to not exist, and, historically, vi simply displayed a "new" + * file. However, if the path exists relative to the tag file, it's + * pretty clear what's happening, so we may as well do it right. + */ + if ((*filep)[0] != '/' + && stat(*filep, &sb) && (p = strrchr(tfp->name, '/')) != NULL) { + *p = '\0'; + plen = snprintf(pbuf, sizeof(pbuf), "%s/%s", tfp->name, *filep); + *p = '/'; + if (stat(pbuf, &sb) == 0) { + slen = strlen(*searchp); + tlen = strlen(*tagp); + MALLOC(sp, p, char *, plen + slen + tlen + 5); + if (p != NULL) { + memmove(p, *tagp, tlen); + free(*tagp); + *tagp = p; + *(p += tlen) = '\0'; + memmove(++p, pbuf, plen); + *filep = p; + *(p += plen) = '\0'; + memmove(++p, *searchp, slen); + *searchp = p; + *(p += slen) = '\0'; + } + } + } + return (0); +} + +#define EQUAL 0 +#define GREATER 1 +#define LESS (-1) + +/* + * search -- + * Search a file for a tag. + */ +static int +search(sp, name, tname, tag) + SCR *sp; + char *name, *tname, **tag; +{ + struct stat sb; + int fd, len; + char *endp, *back, *front, *map, *p; + + if ((fd = open(name, O_RDONLY, 0)) < 0) + return (1); + + /* + * XXX + * We'd like to test if the file is too big to mmap. 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. + */ + if (fstat(fd, &sb) || (map = mmap(NULL, (size_t)sb.st_size, + PROT_READ, MAP_PRIVATE, fd, (off_t)0)) == (caddr_t)-1) { + (void)close(fd); + return (1); + } + front = map; + back = front + sb.st_size; + + front = binary_search(tname, front, back); + front = linear_search(tname, front, back); + + if (front == NULL || (endp = strchr(front, '\n')) == NULL) { + *tag = NULL; + goto done; + } + + len = endp - front; + MALLOC(sp, p, char *, len + 1); + if (p == NULL) { + *tag = NULL; + goto done; + } + memmove(p, front, len); + p[len] = '\0'; + *tag = p; + +done: if (munmap(map, (size_t)sb.st_size)) + msgq(sp, M_SYSERR, "munmap"); + if (close(fd)) + msgq(sp, M_SYSERR, "close"); + return (0); +} + +/* + * Binary search for "string" in memory between "front" and "back". + * + * This routine is expected to return a pointer to the start of a line at + * *or before* the first word matching "string". Relaxing the constraint + * this way simplifies the algorithm. + * + * Invariants: + * front points to the beginning of a line at or before the first + * matching string. + * + * back points to the beginning of a line at or after the first + * matching line. + * + * Base of the Invariants. + * front = NULL; + * back = EOF; + * + * Advancing the Invariants: + * + * p = first newline after halfway point from front to back. + * + * If the string at "p" is not greater than the string to match, + * p is the new front. Otherwise it is the new back. + * + * Termination: + * + * The definition of the routine allows it return at any point, + * since front is always at or before the line to print. + * + * In fact, it returns when the chosen "p" equals "back". This + * implies that there exists a string is least half as long as + * (back - front), which in turn implies that a linear search will + * be no more expensive than the cost of simply printing a string or two. + * + * Trying to continue with binary search at this point would be + * more trouble than it's worth. + */ +#define SKIP_PAST_NEWLINE(p, back) while (p < back && *p++ != '\n'); + +static char * +binary_search(string, front, back) + register char *string, *front, *back; +{ + register char *p; + + p = front + (back - front) / 2; + SKIP_PAST_NEWLINE(p, back); + + while (p != back) { + if (compare(string, p, back) == GREATER) + front = p; + else + back = p; + p = front + (back - front) / 2; + SKIP_PAST_NEWLINE(p, back); + } + return (front); +} + +/* + * Find the first line that starts with string, linearly searching from front + * to back. + * + * Return NULL for no such line. + * + * This routine assumes: + * + * o front points at the first character in a line. + * o front is before or at the first line to be printed. + */ +static char * +linear_search(string, front, back) + char *string, *front, *back; +{ + while (front < back) { + switch (compare(string, front, back)) { + case EQUAL: /* Found it. */ + return (front); + case LESS: /* No such string. */ + return (NULL); + case GREATER: /* Keep going. */ + break; + } + SKIP_PAST_NEWLINE(front, back); + } + return (NULL); +} + +/* + * Return LESS, GREATER, or EQUAL depending on how the string1 compares + * with string2 (s1 ??? s2). + * + * o Matches up to len(s1) are EQUAL. + * o Matches up to len(s2) are GREATER. + * + * The string "s1" is null terminated. The string s2 is '\t', space, (or + * "back") terminated. + * + * !!! + * Reasonably modern ctags programs use tabs as separators, not spaces. + * However, historic programs did use spaces, and, I got complaints. + */ +static int +compare(s1, s2, back) + register char *s1, *s2, *back; +{ + for (; *s1 && s2 < back && (*s2 != '\t' && *s2 != ' '); ++s1, ++s2) + if (*s1 != *s2) + return (*s1 < *s2 ? LESS : GREATER); + return (*s1 ? GREATER : s2 < back && + (*s2 != '\t' && *s2 != ' ') ? LESS : EQUAL); +} diff --git a/usr.bin/vi/ex/ex_undo.c b/usr.bin/vi/ex/ex_undo.c new file mode 100644 index 0000000..4331696 --- /dev/null +++ b/usr.bin/vi/ex/ex_undo.c @@ -0,0 +1,103 @@ +/*- + * 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_undo.c 8.7 (Berkeley) 8/9/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_undo -- u + * Undo the last change. + */ +int +ex_undo(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + MARK m; + + /* + * !!! + * Historic undo always set the previous context mark. + */ + m.lno = sp->lno; + m.cno = sp->cno; + if (mark_set(sp, ep, ABSMARK1, &m, 1)) + return (1); + + /* + * !!! + * Multiple undo isn't available in ex, as there's no '.' command. + * Whether 'u' is undo or redo is toggled each time, unless there + * was a change since the last undo, in which case it's an undo. + */ + if (!F_ISSET(ep, F_UNDO)) { + F_SET(ep, F_UNDO); + ep->lundo = FORWARD; + } + switch (ep->lundo) { + case BACKWARD: + if (log_forward(sp, ep, &m)) + return (1); + ep->lundo = FORWARD; + break; + case FORWARD: + if (log_backward(sp, ep, &m)) + return (1); + ep->lundo = BACKWARD; + break; + case NOTSET: + abort(); + } + sp->lno = m.lno; + sp->cno = m.cno; + return (0); +} diff --git a/usr.bin/vi/ex/ex_usage.c b/usr.bin/vi/ex/ex_usage.c new file mode 100644 index 0000000..b6031b5 --- /dev/null +++ b/usr.bin/vi/ex/ex_usage.c @@ -0,0 +1,197 @@ +/*- + * 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_usage.c 8.19 (Berkeley) 8/14/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" +#include "../vi/vcmd.h" + +/* + * ex_help -- :help + * Display help message. + */ +int +ex_help(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + (void)ex_printf(EXCOOKIE, + "To see the list of vi commands, enter \":viusage\"\n"); + (void)ex_printf(EXCOOKIE, + "To see the list of ex commands, enter \":exusage\"\n"); + (void)ex_printf(EXCOOKIE, + "For an ex command usage statement enter \":exusage [cmd]\"\n"); + (void)ex_printf(EXCOOKIE, + "For a vi key usage statement enter \":viusage [key]\"\n"); + (void)ex_printf(EXCOOKIE, "To exit, enter \":q!\"\n"); + return (0); +} + +/* + * ex_usage -- :exusage [cmd] + * Display ex usage strings. + */ +int +ex_usage(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + ARGS *ap; + EXCMDLIST const *cp; + char *name; + + switch (cmdp->argc) { + case 1: + ap = cmdp->argv[0]; + for (cp = cmds; cp->name != NULL && + memcmp(ap->bp, cp->name, ap->len); ++cp); + if (cp->name == NULL) + (void)ex_printf(EXCOOKIE, + "The %.*s command is unknown", + (int)ap->len, ap->bp); + else { + (void)ex_printf(EXCOOKIE, + "Command: %s\n Usage: %s\n", cp->help, cp->usage); + /* + * !!! + * The "visual" command has two modes, one from ex, + * one from the vi colon line. Don't ask. + */ + if (cp != &cmds[C_VISUAL_EX] && + cp != &cmds[C_VISUAL_VI]) + break; + if (cp == &cmds[C_VISUAL_EX]) + cp = &cmds[C_VISUAL_VI]; + else + cp = &cmds[C_VISUAL_EX]; + (void)ex_printf(EXCOOKIE, + "Command: %s\n Usage: %s\n", cp->help, cp->usage); + } + break; + case 0: + F_SET(sp, S_INTERRUPTIBLE); + for (cp = cmds; cp->name != NULL; ++cp) { + /* The ^D command has an unprintable name. */ + if (cp == &cmds[C_SCROLL]) + name = "^D"; + else + name = cp->name; + (void)ex_printf(EXCOOKIE, + "%*s: %s\n", MAXCMDNAMELEN, name, cp->help); + } + break; + default: + abort(); + } + return (0); +} + +/* + * ex_viusage -- :viusage [key] + * Display vi usage strings. + */ +int +ex_viusage(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + VIKEYS const *kp; + int key; + + switch (cmdp->argc) { + case 1: + if (cmdp->argv[0]->len != 1) { + msgq(sp, M_ERR, "Usage: %s", cmdp->cmd->usage); + return (1); + } + key = cmdp->argv[0]->bp[0]; + if (key > MAXVIKEY) + goto nokey; + + /* Special case: '[' and ']' commands. */ + if ((key == '[' || key == ']') && cmdp->argv[0]->bp[1] != key) + goto nokey; + + /* Special case: ~ command. */ + if (key == '~' && O_ISSET(sp, O_TILDEOP)) + kp = &tmotion; + else + kp = &vikeys[key]; + + if (kp->func == NULL) +nokey: (void)ex_printf(EXCOOKIE, + "The %s key has no current meaning", + KEY_NAME(sp, key)); + else + (void)ex_printf(EXCOOKIE, + " Key:%s%s\nUsage: %s\n", + isblank(*kp->help) ? "" : " ", kp->help, kp->usage); + break; + case 0: + F_SET(sp, S_INTERRUPTIBLE); + for (key = 0; key <= MAXVIKEY; ++key) { + /* Special case: ~ command. */ + if (key == '~' && O_ISSET(sp, O_TILDEOP)) + kp = &tmotion; + else + kp = &vikeys[key]; + if (kp->help != NULL) + (void)ex_printf(EXCOOKIE, "%s\n", kp->help); + } + break; + default: + abort(); + } + return (0); +} diff --git a/usr.bin/vi/ex/ex_util.c b/usr.bin/vi/ex/ex_util.c new file mode 100644 index 0000000..e88c28f --- /dev/null +++ b/usr.bin/vi/ex/ex_util.c @@ -0,0 +1,189 @@ +/*- + * Copyright (c) 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_util.c 8.12 (Berkeley) 8/4/94"; +#endif /* not lint */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_getline -- + * Return a line from the terminal. + */ +int +ex_getline(sp, fp, lenp) + SCR *sp; + FILE *fp; + size_t *lenp; +{ + EX_PRIVATE *exp; + size_t off; + int ch; + char *p; + + exp = EXP(sp); + for (errno = 0, off = 0, p = exp->ibp;;) { + if (off >= exp->ibp_len) { + BINC_RET(sp, exp->ibp, exp->ibp_len, off + 1); + p = exp->ibp + off; + } + if ((ch = getc(fp)) == EOF && !feof(fp)) { + if (errno == EINTR) { + errno = 0; + clearerr(fp); + continue; + } + return (1); + } + if (ch == EOF || ch == '\n') { + if (ch == EOF && !off) + return (1); + *lenp = off; + return (0); + } + *p++ = ch; + ++off; + } + /* NOTREACHED */ +} + +/* + * ex_sleave -- + * Save the terminal/signal state, screen modification time. + * Specific to ex/filter.c and ex/ex_shell.c. + */ +int +ex_sleave(sp) + SCR *sp; +{ + struct stat sb; + EX_PRIVATE *exp; + + /* Ignore sessions not using tty's. */ + if (!F_ISSET(sp->gp, G_STDIN_TTY)) + return (1); + + exp = EXP(sp); + if (tcgetattr(STDIN_FILENO, &exp->leave_term)) { + msgq(sp, M_SYSERR, "tcgetattr"); + return (1); + } + if (tcsetattr(STDIN_FILENO, + TCSANOW | TCSASOFT, &sp->gp->original_termios)) { + msgq(sp, M_SYSERR, "tcsetattr"); + return (1); + } + /* + * The process may write to the terminal. Save the access time + * (read) and modification time (write) of the tty; if they have + * changed when we restore the modes, will have to refresh the + * screen. + */ + if (fstat(STDIN_FILENO, &sb)) { + msgq(sp, M_SYSERR, "stat: stdin"); + exp->leave_atime = exp->leave_mtime = 0; + } else { + exp->leave_atime = sb.st_atime; + exp->leave_mtime = sb.st_mtime; + } + return (0); +} + +/* + * ex_rleave -- + * Return the terminal/signal state, not screen modification time. + * Specific to ex/filter.c and ex/ex_shell.c. + */ +void +ex_rleave(sp) + SCR *sp; +{ + EX_PRIVATE *exp; + struct stat sb; + + exp = EXP(sp); + + /* Restore the terminal modes. */ + if (tcsetattr(STDIN_FILENO, TCSANOW | TCSASOFT, &exp->leave_term)) + msgq(sp, M_SYSERR, "tcsetattr"); + + /* If the terminal was used, refresh the screen. */ + if (fstat(STDIN_FILENO, &sb) || exp->leave_atime == 0 || + exp->leave_atime != sb.st_atime || exp->leave_mtime != sb.st_mtime) + F_SET(sp, S_REFRESH); +} + +/* + * ex_ncheck -- + * Check for more files to edit. + */ +int +ex_ncheck(sp, force) + SCR *sp; + int force; +{ + /* + * !!! + * Historic practice: quit! or two quit's done in succession + * (where ZZ counts as a quit) didn't check for other files. + */ + if (!force && sp->ccnt != sp->q_ccnt + 1 && + sp->cargv != NULL && sp->cargv[1] != NULL) { + sp->q_ccnt = sp->ccnt; + msgq(sp, M_ERR, + "More files to edit; use n[ext] to go to the next file, q[uit]! to quit"); + return (1); + } + return (0); +} diff --git a/usr.bin/vi/ex/ex_version.c b/usr.bin/vi/ex/ex_version.c new file mode 100644 index 0000000..31c2cb7 --- /dev/null +++ b/usr.bin/vi/ex/ex_version.c @@ -0,0 +1,71 @@ +/*- + * Copyright (c) 1991, 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_version.c 8.62 (Berkeley) 8/15/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_version -- :version + * Display the program version. + */ +int +ex_version(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + static time_t then = 776975285; + + (void)ex_printf(EXCOOKIE, +"Version 1.32, %sThe CSRG, University of California, Berkeley.\n", + ctime(&then)); + return (0); +} diff --git a/usr.bin/vi/ex/ex_visual.c b/usr.bin/vi/ex/ex_visual.c new file mode 100644 index 0000000..cfb0c13 --- /dev/null +++ b/usr.bin/vi/ex/ex_visual.c @@ -0,0 +1,137 @@ +/*- + * 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_visual.c 8.13 (Berkeley) 7/18/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_visual -- :[line] vi[sual] [^-.+] [window_size] [flags] + * + * Switch to visual mode. + */ +int +ex_visual(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + size_t len; + int pos; + char buf[256]; + + /* If open option off, disallow visual command. */ + if (!O_ISSET(sp, O_OPEN)) { + msgq(sp, M_ERR, + "The visual command requires that the open option be set"); + return (1); + } + + /* If a line specified, move to that line. */ + if (cmdp->addrcnt) + sp->lno = cmdp->addr1.lno; + + /* + * Push a command based on the line position flags. If no + * flag specified, the line goes at the top of the screen. + */ + switch (F_ISSET(cmdp, E_F_CARAT | E_F_DASH | E_F_DOT | E_F_PLUS)) { + case E_F_CARAT: + pos = '^'; + break; + case E_F_DASH: + pos = '-'; + break; + case E_F_DOT: + pos = '.'; + break; + case E_F_PLUS: + pos = '+'; + break; + default: + sp->frp->lno = sp->lno; + sp->frp->cno = 0; + F_SET(sp->frp, FR_CURSORSET | FR_FNONBLANK); + goto nopush; + } + + if (F_ISSET(cmdp, E_COUNT)) + len = snprintf(buf, sizeof(buf), + "%luz%c%lu", sp->lno, pos, cmdp->count); + else + len = snprintf(buf, sizeof(buf), "%luz%c", sp->lno, pos); + (void)term_push(sp, buf, len, CH_NOMAP | CH_QUOTED); + + /* + * !!! + * Historically, if no line address was specified, the [p#l] flags + * caused the cursor to be moved to the last line of the file, which + * was then positioned as described above. This seems useless, so + * I haven't implemented it. + */ + switch (F_ISSET(cmdp, E_F_HASH | E_F_LIST | E_F_PRINT)) { + case E_F_HASH: + O_SET(sp, O_NUMBER); + break; + case E_F_LIST: + O_SET(sp, O_LIST); + break; + case E_F_PRINT: + break; + } + + /* Switch modes. */ +nopush: F_CLR(sp, S_SCREENS); + F_SET(sp, sp->saved_vi_mode); + + return (0); +} diff --git a/usr.bin/vi/ex/ex_write.c b/usr.bin/vi/ex/ex_write.c new file mode 100644 index 0000000..3dbb6e8 --- /dev/null +++ b/usr.bin/vi/ex/ex_write.c @@ -0,0 +1,327 @@ +/*- + * 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_write.c 8.36 (Berkeley) 8/4/94"; +#endif /* not lint */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +enum which {WN, WQ, WRITE, XIT}; + +static int exwr __P((SCR *, EXF *, EXCMDARG *, enum which)); + +/* + * ex_wn -- :wn[!] [>>] [file] + * Write to a file and switch to the next one. + */ +int +ex_wn(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + if (exwr(sp, ep, cmdp, WN)) + return (1); + if (file_m3(sp, ep, 0)) + return (1); + + /* The file name isn't a new file to edit. */ + cmdp->argc = 0; + + return (ex_next(sp, ep, cmdp)); +} + +/* + * ex_wq -- :wq[!] [>>] [file] + * Write to a file and quit. + */ +int +ex_wq(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + int force; + + if (exwr(sp, ep, cmdp, WQ)) + return (1); + if (file_m3(sp, ep, 0)) + return (1); + + force = F_ISSET(cmdp, E_FORCE); + + if (ex_ncheck(sp, force)) + return (1); + + F_SET(sp, force ? S_EXIT_FORCE : S_EXIT); + return (0); +} + +/* + * ex_write -- :write[!] [>>] [file] + * :write [!] [cmd] + * Write to a file. + */ +int +ex_write(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + return (exwr(sp, ep, cmdp, WRITE)); +} + + +/* + * ex_xit -- :x[it]! [file] + * + * Write out any modifications and quit. + */ +int +ex_xit(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + int force; + + if (F_ISSET((ep), F_MODIFIED) && exwr(sp, ep, cmdp, XIT)) + return (1); + if (file_m3(sp, ep, 0)) + return (1); + + force = F_ISSET(cmdp, E_FORCE); + + if (ex_ncheck(sp, force)) + return (1); + + F_SET(sp, force ? S_EXIT_FORCE : S_EXIT); + return (0); +} + +/* + * exwr -- + * The guts of the ex write commands. + */ +static int +exwr(sp, ep, cmdp, cmd) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; + enum which cmd; +{ + EX_PRIVATE *exp; + MARK rm; + int flags; + char *name, *p; + + /* All write commands can have an associated '!'. */ + LF_INIT(FS_POSSIBLE); + if (F_ISSET(cmdp, E_FORCE)) + LF_SET(FS_FORCE); + + /* Skip any leading whitespace. */ + if (cmdp->argc != 0) + for (p = cmdp->argv[0]->bp; *p && isblank(*p); ++p); + + /* If no arguments, just write the file back. */ + if (cmdp->argc == 0 || *p == '\0') { + if (F_ISSET(cmdp, E_ADDR2_ALL)) + LF_SET(FS_ALL); + return (file_write(sp, ep, + &cmdp->addr1, &cmdp->addr2, NULL, flags)); + } + + /* If "write !" it's a pipe to a utility. */ + exp = EXP(sp); + if (cmd == WRITE && *p == '!') { + for (++p; *p && isblank(*p); ++p); + if (*p == '\0') { + msgq(sp, M_ERR, "Usage: %s", cmdp->cmd->usage); + return (1); + } + /* Expand the argument. */ + if (argv_exp1(sp, ep, cmdp, p, strlen(p), 0)) + return (1); + if (filtercmd(sp, ep, &cmdp->addr1, &cmdp->addr2, + &rm, cmdp->argv[1]->bp, FILTER_WRITE)) + return (1); + sp->lno = rm.lno; + return (0); + } + + /* If "write >>" it's an append to a file. */ + if (cmd != XIT && p[0] == '>' && p[1] == '>') { + LF_SET(FS_APPEND); + + /* Skip ">>" and whitespace. */ + for (p += 2; *p && isblank(*p); ++p); + } + + /* Build an argv so we get an argument count and file expansion. */ + if (argv_exp2(sp, ep, cmdp, p, strlen(p), 0)) + return (1); + + switch (cmdp->argc) { + case 1: + /* + * Nothing to expand, write the current file. + * XXX + * Should never happen, already checked this case. + */ + name = NULL; + break; + case 2: + /* One new argument, write it. */ + name = cmdp->argv[exp->argsoff - 1]->bp; + set_alt_name(sp, name); + break; + default: + /* If expanded to more than one argument, object. */ + msgq(sp, M_ERR, "%s expanded into too many file names", + cmdp->argv[0]->bp); + msgq(sp, M_ERR, "Usage: %s", cmdp->cmd->usage); + return (1); + } + + if (F_ISSET(cmdp, E_ADDR2_ALL)) + LF_SET(FS_ALL); + return (file_write(sp, ep, &cmdp->addr1, &cmdp->addr2, name, flags)); +} + +/* + * ex_writefp -- + * Write a range of lines to a FILE *. + */ +int +ex_writefp(sp, ep, name, fp, fm, tm, nlno, nch) + SCR *sp; + EXF *ep; + char *name; + FILE *fp; + MARK *fm, *tm; + u_long *nlno, *nch; +{ + struct stat sb; + u_long ccnt; /* XXX: can't print off_t portably. */ + recno_t fline, tline, lcnt; + size_t len; + int sv_errno; + char *p; + + fline = fm->lno; + tline = tm->lno; + + if (nlno != NULL) { + *nch = 0; + *nlno = 0; + } + + /* + * The vi filter code has multiple processes running simultaneously, + * and one of them calls ex_writefp(). The "unsafe" function calls + * in this code are to file_gline() and msgq(). File_gline() is safe, + * see the comment in filter.c:filtercmd() for details. We don't call + * msgq if the multiple process bit in the EXF is set. + * + * !!! + * Historic vi permitted files of 0 length to be written. However, + * since the way vi got around dealing with "empty" files was to + * always have a line in the file no matter what, it wrote them as + * files of a single, empty line. We write empty files. + * + * "Alex, I'll take vi trivia for $1000." + */ + ccnt = 0; + lcnt = 0; + if (tline != 0) { + for (; fline <= tline; ++fline, ++lcnt) { + /* Caller has to provide any interrupt message. */ + if (INTERRUPTED(sp)) + break; + if ((p = file_gline(sp, ep, fline, &len)) == NULL) + break; + if (fwrite(p, 1, len, fp) != len) { + msgq(sp, M_SYSERR, name); + (void)fclose(fp); + return (1); + } + ccnt += len; + if (putc('\n', fp) != '\n') + break; + ++ccnt; + } + } + + /* If it's a regular file, sync it so that NFS is forced to flush. */ + if (!fstat(fileno(fp), &sb) && + S_ISREG(sb.st_mode) && fsync(fileno(fp))) { + sv_errno = errno; + (void)fclose(fp); + errno = sv_errno; + goto err; + } + if (fclose(fp)) + goto err; + if (nlno != NULL) { + *nch = ccnt; + *nlno = lcnt; + } + return (0); + +err: if (!F_ISSET(ep, F_MULTILOCK)) + msgq(sp, M_SYSERR, name); + return (1); +} diff --git a/usr.bin/vi/ex/ex_yank.c b/usr.bin/vi/ex/ex_yank.c new file mode 100644 index 0000000..7f5d280 --- /dev/null +++ b/usr.bin/vi/ex/ex_yank.c @@ -0,0 +1,69 @@ +/*- + * 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_yank.c 8.5 (Berkeley) 5/17/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_yank -- :[line [,line]] ya[nk] [buffer] [count] + * + * Yank the lines into a buffer. + */ +int +ex_yank(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + return (cut(sp, ep, + F_ISSET(cmdp, E_BUFFER) ? &cmdp->buffer : NULL, + &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE)); +} diff --git a/usr.bin/vi/ex/ex_z.c b/usr.bin/vi/ex/ex_z.c new file mode 100644 index 0000000..a5917b3 --- /dev/null +++ b/usr.bin/vi/ex/ex_z.c @@ -0,0 +1,180 @@ +/*- + * Copyright (c) 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_z.c 8.6 (Berkeley) 7/23/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * ex_z -- :[line] z [^-.+=] [count] [flags] + * + * Adjust window. + */ +int +ex_z(sp, ep, cmdp) + SCR *sp; + EXF *ep; + EXCMDARG *cmdp; +{ + MARK abs; + recno_t cnt, equals, lno; + int eofcheck; + + /* + * !!! + * If no count specified, use either two times the size of the + * scrolling region, or the size of the window option. POSIX + * 1003.2 claims that the latter is correct, but historic ex/vi + * documentation and practice appear to use the scrolling region. + * I'm using the window size as it means that the entire screen + * is used instead of losing a line to roundoff. Note, we drop + * a line from the cnt if using the window size to leave room for + * the next ex prompt. + */ + if (F_ISSET(cmdp, E_COUNT)) + cnt = cmdp->count; + else +#ifdef HISTORIC_PRACTICE + cnt = O_VAL(sp, O_SCROLL) * 2; +#else + cnt = O_VAL(sp, O_WINDOW) - 1; +#endif + + equals = 0; + eofcheck = 0; + lno = cmdp->addr1.lno; + + switch (F_ISSET(cmdp, + E_F_CARAT | E_F_DASH | E_F_DOT | E_F_EQUAL | E_F_PLUS)) { + case E_F_CARAT: /* Display cnt * 2 before the line. */ + eofcheck = 1; + if (lno > cnt * 2) + cmdp->addr1.lno = (lno - cnt * 2) + 1; + else + cmdp->addr1.lno = 1; + cmdp->addr2.lno = (cmdp->addr1.lno + cnt) - 1; + break; + case E_F_DASH: /* Line goes at the bottom of the screen. */ + cmdp->addr1.lno = lno > cnt ? (lno - cnt) + 1 : 1; + cmdp->addr2.lno = lno; + break; + case E_F_DOT: /* Line goes in the middle of the screen. */ + /* + * !!! + * Historically, the "middleness" of the line overrode the + * count, so that "3z.19" or "3z.20" would display the first + * 12 lines of the file, i.e. (N - 1) / 2 lines before and + * after the specified line. + */ + eofcheck = 1; + cnt = (cnt - 1) / 2; + cmdp->addr1.lno = lno > cnt ? lno - cnt : 1; + cmdp->addr2.lno = lno + cnt; + + /* + * !!! + * Historically, z. set the absolute cursor mark. + */ + abs.lno = sp->lno; + abs.cno = sp->cno; + (void)mark_set(sp, ep, ABSMARK1, &abs, 1); + break; + case E_F_EQUAL: /* Center with hyphens. */ + /* + * !!! + * Strangeness. The '=' flag is like the '.' flag (see the + * above comment, it applies here as well) but with a special + * little hack. Print out lines of hyphens before and after + * the specified line. Additionally, the cursor remains set + * on that line. + */ + eofcheck = 1; + cnt = (cnt - 1) / 2; + cmdp->addr1.lno = lno > cnt ? lno - cnt : 1; + cmdp->addr2.lno = lno - 1; + if (ex_pr(sp, ep, cmdp)) + return (1); + (void)ex_printf(EXCOOKIE, + "%s", "----------------------------------------\n"); + cmdp->addr2.lno = cmdp->addr1.lno = equals = lno; + if (ex_pr(sp, ep, cmdp)) + return (1); + (void)ex_printf(EXCOOKIE, + "%s", "----------------------------------------\n"); + cmdp->addr1.lno = lno + 1; + cmdp->addr2.lno = (lno + cnt) - 1; + break; + default: + /* If no line specified, move to the next one. */ + if (F_ISSET(cmdp, E_ADDRDEF)) + ++lno; + /* FALLTHROUGH */ + case E_F_PLUS: /* Line goes at the top of the screen. */ + eofcheck = 1; + cmdp->addr1.lno = lno; + cmdp->addr2.lno = (lno + cnt) - 1; + break; + } + + if (eofcheck) { + if (file_lline(sp, ep, &lno)) + return (1); + if (cmdp->addr2.lno > lno) + cmdp->addr2.lno = lno; + } + + if (ex_pr(sp, ep, cmdp)) + return (1); + if (equals) + sp->lno = equals; + return (0); +} diff --git a/usr.bin/vi/ex/excmd.awk b/usr.bin/vi/ex/excmd.awk new file mode 100644 index 0000000..2890220 --- /dev/null +++ b/usr.bin/vi/ex/excmd.awk @@ -0,0 +1,6 @@ +# @(#)excmd.awk 8.1 (Berkeley) 4/17/94 + +/^\/\* C_[0-9A-Z_]* \*\/$/ { + printf("#define %s %d\n", $2, cnt++); + next; +} diff --git a/usr.bin/vi/ex/excmd.c b/usr.bin/vi/ex/excmd.c new file mode 100644 index 0000000..3845768 --- /dev/null +++ b/usr.bin/vi/ex/excmd.c @@ -0,0 +1,458 @@ +/*- + * 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[] = "@(#)excmd.c 8.58 (Berkeley) 8/9/94"; +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include + +#include "vi.h" +#include "excmd.h" + +/* + * This array maps ex command names to command functions. + * + * The order in which command names are listed below is important -- + * ambiguous abbreviations are resolved to be the first possible match, + * e.g. "r" means "read", not "rewind", because "read" is listed before + * "rewind". + * + * The syntax of the ex commands is unbelievably irregular, and a special + * case from beginning to end. Each command has an associated "syntax + * script" which describes the "arguments" that are possible. The script + * syntax is as follows: + * + * ! -- ! flag + * 1 -- flags: [+-]*[pl#][+-]* + * 2 -- flags: [-.+^] + * 3 -- flags: [-.+^=] + * b -- buffer + * c[01+a] -- count (0-N, 1-N, signed 1-N, address offset) + * f[N#][or] -- file (a number or N, optional or required) + * l -- line + * S -- string with file name expansion + * s -- string + * W -- word string + * w[N#][or] -- word (a number or N, optional or required) + */ +EXCMDLIST const cmds[] = { +/* C_SCROLL */ + {"\004", ex_pr, E_ADDR2|E_NORC, + "", + "^D", + "scroll lines"}, +/* C_BANG */ + {"!", ex_bang, E_ADDR2_NONE|E_NORC, + "S", + "[line [,line]] ! command", + "filter lines through commands or run commands"}, +/* C_HASH */ + {"#", ex_number, E_ADDR2|E_F_PRCLEAR|E_NORC, + "ca1", + "[line [,line]] # [count] [l]", + "display numbered lines"}, +/* C_SUBAGAIN */ + {"&", ex_subagain, E_ADDR2|E_NORC, + "s", + "[line [,line]] & [cgr] [count] [#lp]", + "repeat the last subsitution"}, +/* C_STAR */ + {"*", ex_at, 0, + "b", + "* [buffer]", + "execute a buffer"}, +/* C_SHIFTL */ + {"<", ex_shiftl, E_ADDR2|E_AUTOPRINT|E_NORC, + "ca1", + "[line [,line]] <[<...] [count] [flags]", + "shift lines left"}, +/* C_EQUAL */ + {"=", ex_equal, E_ADDR1|E_NORC|E_ZERO|E_ZERODEF, + "1", + "[line] = [flags]", + "display line number"}, +/* C_SHIFTR */ + {">", ex_shiftr, E_ADDR2|E_AUTOPRINT|E_NORC, + "ca1", + "[line [,line]] >[>...] [count] [flags]", + "shift lines right"}, +/* C_AT */ + {"@", ex_at, 0, + "b", + "@ [buffer]", + "execute a buffer"}, +/* C_APPEND */ + {"append", ex_append, E_ADDR1|E_NORC|E_ZERO|E_ZERODEF, + "!", + "[line] a[ppend][!]", + "append input to a line"}, +/* C_ABBR */ + {"abbreviate", ex_abbr, E_NOGLOBAL, + "W", + "ab[brev] [word replace]", + "specify an input abbreviation"}, +/* C_ARGS */ + {"args", ex_args, E_NOGLOBAL|E_NORC, + "", + "ar[gs]", + "display file argument list"}, +/* C_BG */ + {"bg", ex_bg, E_NOGLOBAL|E_NORC, + "", + "bg", + "background the current screen"}, +/* C_CHANGE */ + {"change", ex_change, E_ADDR2|E_NORC|E_ZERODEF, + "!ca", + "[line [,line]] c[hange][!] [count]", + "change lines to input"}, +/* C_CD */ + {"cd", ex_cd, E_NOGLOBAL, + "!f1o", + "cd[!] [directory]", + "change the current directory"}, +/* C_CHDIR */ + {"chdir", ex_cd, E_NOGLOBAL, + "!f1o", + "chd[ir][!] [directory]", + "change the current directory"}, +/* C_COPY */ + {"copy", ex_copy, E_ADDR2|E_AUTOPRINT|E_NORC, + "l1", + "[line [,line]] co[py] line [flags]", + "copy lines elsewhere in the file"}, +/* + * !!! + * Adding new commands starting with 'd' may break the delete command code + * in ex_cmd() (the ex parser). Read through the comments there, first. + */ +/* C_DELETE */ + {"delete", ex_delete, E_ADDR2|E_AUTOPRINT|E_NORC, + "bca1", + "[line [,line]] d[elete][flags] [buffer] [count] [flags]", + "delete lines from the file"}, +/* C_DISPLAY */ + {"display", ex_display, E_NOGLOBAL|E_NORC, + "w1r", + "display b[uffers] | s[creens] | t[ags]", + "display buffers, screens or tags"}, +/* C_DIGRAPH */ + {"digraph", ex_digraph, E_NOGLOBAL|E_NOPERM|E_NORC, + "", + "digraph", + "specify digraphs (not implemented)"}, +/* C_EDIT */ + {"edit", ex_edit, E_NOGLOBAL|E_NORC, + "f1o", + "e[dit][!] [+cmd] [file]", + "begin editing another file"}, +/* C_EX */ + {"ex", ex_edit, E_NOGLOBAL|E_NORC, + "f1o", + "ex[!] [+cmd] [file]", + "begin editing another file"}, +/* C_EXUSAGE */ + {"exusage", ex_usage, E_NOGLOBAL|E_NORC, + "w1o", + "[exu]sage [command]", + "display ex command usage statement"}, +/* C_FILE */ + {"file", ex_file, E_NOGLOBAL|E_NORC, + "f1o", + "f[ile] [name]", + "display (and optionally set) file name"}, +/* C_FG */ + {"fg", ex_fg, E_NOGLOBAL|E_NORC, + "f1o", + "fg [file]", + "switch the current screen and a backgrounded screen"}, +/* C_GLOBAL */ + {"global", ex_global, E_ADDR2_ALL|E_NOGLOBAL|E_NORC, + "!s", + "[line [,line]] g[lobal][!] [;/]RE[;/] [commands]", + "execute a global command on lines matching an RE"}, +/* C_HELP */ + {"help", ex_help, E_NOGLOBAL|E_NORC, + "", + "he[lp]", + "display help statement"}, +/* C_INSERT */ + {"insert", ex_insert, E_ADDR1|E_NORC, + "!", + "[line] i[nsert][!]", + "insert input before a line"}, +/* C_JOIN */ + {"join", ex_join, E_ADDR2|E_AUTOPRINT|E_NORC, + "!ca1", + "[line [,line]] j[oin][!] [count] [flags]", + "join lines into a single line"}, +/* C_K */ + {"k", ex_mark, E_ADDR1|E_NORC, + "w1r", + "[line] k key", + "mark a line position"}, +/* C_LIST */ + {"list", ex_list, E_ADDR2|E_F_PRCLEAR|E_NORC, + "ca1", + "[line [,line]] l[ist] [count] [#]", + "display lines in an unambiguous form"}, +/* C_MOVE */ + {"move", ex_move, E_ADDR2|E_AUTOPRINT|E_NORC, + "l", + "[line [,line]] m[ove] line", + "move lines elsewhere in the file"}, +/* C_MARK */ + {"mark", ex_mark, E_ADDR1|E_NORC, + "w1r", + "[line] ma[rk] key", + "mark a line position"}, +/* C_MAP */ + {"map", ex_map, 0, + "!W", + "map[!] [keys replace]", + "map input or commands to one or more keys"}, +/* C_MKEXRC */ + {"mkexrc", ex_mkexrc, E_NOGLOBAL|E_NORC, + "!f1r", + "mkexrc[!] file", + "write a .exrc file"}, +/* C_NEXT */ + {"next", ex_next, E_NOGLOBAL|E_NORC, + "!fN", + "n[ext][!] [+cmd] [file ...]", + "edit (and optionally specify) the next file"}, +/* C_NUMBER */ + {"number", ex_number, E_ADDR2|E_F_PRCLEAR|E_NORC, + "ca1", + "[line [,line]] nu[mber] [count] [l]", + "change display to number lines"}, +/* C_OPEN */ + {"open", ex_open, E_ADDR1, + "s", + "[line] o[pen] [/RE/] [flags]", + "enter \"open\" mode (not implemented)"}, +/* C_PRINT */ + {"print", ex_pr, E_ADDR2|E_F_PRCLEAR|E_NORC, + "ca1", + "[line [,line]] p[rint] [count] [#l]", + "display lines"}, +/* C_PRESERVE */ + {"preserve", ex_preserve, E_NOGLOBAL|E_NORC, + "", + "pre[serve]", + "preserve an edit session for recovery"}, +/* C_PREVIOUS */ + {"previous", ex_prev, E_NOGLOBAL|E_NORC, + "!", + "prev[ious][!]", + "edit the previous file in the file argument list"}, +/* C_PUT */ + {"put", ex_put, E_ADDR1|E_AUTOPRINT|E_NORC|E_ZERO, + "b", + "[line] pu[t] [buffer]", + "append a cut buffer to the line"}, +/* C_QUIT */ + {"quit", ex_quit, E_NOGLOBAL|E_NORC, + "!", + "q[uit][!]", + "exit ex/vi"}, +/* C_READ */ + {"read", ex_read, E_ADDR1|E_NORC|E_ZERO|E_ZERODEF, + "s", + "[line] r[ead] [!cmd | [file]]", + "append input from a command or file to the line"}, +/* C_RECOVER */ + {"recover", ex_recover, E_NOGLOBAL|E_NORC, + "!f1r", + "recover[!] file", + "recover a saved file"}, +/* C_RESIZE */ + {"resize", ex_resize, E_NOGLOBAL|E_NORC, + "c+", + "resize [+-]rows", + "grow or shrink the current screen"}, +/* C_REWIND */ + {"rewind", ex_rew, E_NOGLOBAL|E_NORC, + "!", + "rew[ind][!]", + "re-edit all the files in the file argument list"}, +/* C_SUBSTITUTE */ + {"substitute", ex_substitute, E_ADDR2|E_NORC, + "s", +"[line [,line]] s[ubstitute] [[/;]RE[/;]/repl[/;] [cgr] [count] [#lp]]", + "substitute on lines matching an RE"}, +/* C_SCRIPT */ + {"script", ex_script, E_NOGLOBAL|E_NORC, + "!f1o", + "sc[ript][!] [file]", + "run a shell in a screen"}, +/* C_SET */ + {"set", ex_set, E_NOGLOBAL, + "wN", + "se[t] [option[=[value]]...] [nooption ...] [option? ...] [all]", + "set options (use \":set all\" to see all options)"}, +/* C_SHELL */ + {"shell", ex_shell, E_NOGLOBAL|E_NORC, + "", + "sh[ell]", + "suspend editing and run a shell"}, +/* C_SOURCE */ + {"source", ex_source, E_NOGLOBAL, + "f1r", + "so[urce] file", + "read a file of ex commands"}, +/* C_SPLIT */ + {"split", ex_split, E_NOGLOBAL|E_NORC, + "fNo", + "sp[lit] [file ...]", + "split the current screen into two screens"}, +/* C_STOP */ + {"stop", ex_stop, E_NOGLOBAL|E_NORC, + "!", + "st[op][!]", + "suspend the edit session"}, +/* C_SUSPEND */ + {"suspend", ex_stop, E_NOGLOBAL|E_NORC, + "!", + "su[spend][!]", + "suspend the edit session"}, +/* C_T */ + {"t", ex_copy, E_ADDR2|E_AUTOPRINT|E_NORC, + "l1", + "[line [,line]] t line [flags]", + "copy lines elsewhere in the file"}, +/* C_TAG */ + {"tag", ex_tagpush, E_NOGLOBAL, + "!w1o", + "ta[g][!] [string]", + "edit the file containing the tag"}, +/* C_TAGPOP */ + {"tagpop", ex_tagpop, E_NOGLOBAL|E_NORC, + "!w1o", + "tagp[op][!] [number | file]", + "return to a previous tag"}, +/* C_TAGTOP */ + {"tagtop", ex_tagtop, E_NOGLOBAL|E_NORC, + "!", + "tagt[op][!]", + "return to the first tag"}, +/* C_UNDO */ + {"undo", ex_undo, E_AUTOPRINT|E_NOGLOBAL|E_NORC, + "", + "u[ndo]", + "undo the most recent change"}, +/* C_UNABBREVIATE */ + {"unabbreviate",ex_unabbr, E_NOGLOBAL, + "w1r", + "una[bbrev] word", + "delete an abbreviation"}, +/* C_UNMAP */ + {"unmap", ex_unmap, E_NOGLOBAL, + "!w1r", + "unm[ap][!] word", + "delete an input or command map"}, +/* C_VGLOBAL */ + {"vglobal", ex_vglobal, E_ADDR2_ALL|E_NOGLOBAL|E_NORC, + "s", + "[line [,line]] v[global] [;/]RE[;/] [commands]", + "execute a global command on lines NOT matching an RE"}, +/* C_VERSION */ + {"version", ex_version, E_NOGLOBAL|E_NORC, + "", + "version", + "display the program version information"}, +/* C_VISUAL_EX */ + {"visual", ex_visual, E_ADDR1|E_NOGLOBAL|E_NORC|E_ZERODEF, + "2c11", + "[line] vi[sual] [-|.|+|^] [window_size] [flags]", + "enter visual (vi) mode from ex mode"}, +/* C_VISUAL_VI */ + {"visual", ex_edit, E_NOGLOBAL|E_NORC, + "f1o", + "vi[sual][!] [+cmd] [file]", + "edit another file (from vi mode only)"}, +/* C_VIUSAGE */ + {"viusage", ex_viusage, E_NOGLOBAL|E_NORC, + "w1o", + "[viu]sage [key]", + "display vi key usage statement"}, +/* C_WRITE */ + {"write", ex_write, E_ADDR2_ALL|E_NOGLOBAL|E_NORC|E_ZERODEF, + "!s", + "[line [,line]] w[rite][!] [!cmd | [>>] [file]]", + "write the file"}, +/* C_WN */ + {"wn", ex_wn, E_ADDR2_ALL|E_NOGLOBAL|E_NORC|E_ZERODEF, + "!s", + "[line [,line]] wn[!] [>>] [file]", + "write the file and switch to the next file"}, +/* C_WQ */ + {"wq", ex_wq, E_ADDR2_ALL|E_NOGLOBAL|E_NORC|E_ZERODEF, + "!s", + "[line [,line]] wq[!] [>>] [file]", + "write the file and exit"}, +/* C_XIT */ + {"xit", ex_xit, E_ADDR2_ALL|E_NOGLOBAL|E_NORC|E_ZERODEF, + "!f1o", + "[line [,line]] x[it][!] [file]", + "exit"}, +/* C_YANK */ + {"yank", ex_yank, E_ADDR2|E_NORC, + "bca", + "[line [,line]] ya[nk] [buffer] [count]", + "copy lines to a cut buffer"}, +/* C_Z */ + {"z", ex_z, E_ADDR1|E_NOGLOBAL|E_NORC, + "3c01", + "[line] z [-|.|+|^|=] [count] [flags]", + "display different screens of the file"}, +/* C_SUBTILDE */ + {"~", ex_subtilde, E_ADDR2|E_NORC, + "s", + "[line [,line]] ~ [cgr] [count] [#lp]", + "replace previous RE with previous replacement string,"}, + {NULL}, +}; diff --git a/usr.bin/vi/ex/excmd.h.stub b/usr.bin/vi/ex/excmd.h.stub new file mode 100644 index 0000000..3b4051b --- /dev/null +++ b/usr.bin/vi/ex/excmd.h.stub @@ -0,0 +1,285 @@ +/*- + * 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. + * + * @(#)excmd.h.stub 8.73 (Berkeley) 8/9/94 + */ + +#define PROMPTCHAR ':' /* Prompt character. */ + +/* Ex command structure. */ +typedef struct _excmdlist { + char *name; /* Command name. */ + /* Underlying function. */ + int (*fn) __P((SCR *, EXF *, EXCMDARG *)); + +#define E_ADDR1 0x0000001 /* One address. */ +#define E_ADDR2 0x0000002 /* Two address. */ +#define E_ADDR2_ALL 0x0000004 /* Zero/two addresses; zero == all. */ +#define E_ADDR2_NONE 0x0000008 /* Zero/two addresses; zero == none. */ +#define E_ADDRDEF 0x0000010 /* Default addresses used. */ +#define E_AUTOPRINT 0x0000020 /* Command always sets autoprint. */ +#define E_BUFFER 0x0000040 /* Buffer name supplied. */ +#define E_COUNT 0x0000080 /* Count supplied. */ +#define E_COUNT_NEG 0x0000100 /* Count was signed negative. */ +#define E_COUNT_POS 0x0000200 /* Count was signed positive. */ +#define E_FORCE 0x0000400 /* ! */ + +#define E_F_CARAT 0x0000800 /* ^ flag. */ +#define E_F_DASH 0x0001000 /* - flag. */ +#define E_F_DOT 0x0002000 /* . flag. */ +#define E_F_EQUAL 0x0004000 /* = flag. */ +#define E_F_HASH 0x0008000 /* # flag. */ +#define E_F_LIST 0x0010000 /* l flag. */ +#define E_F_PLUS 0x0020000 /* + flag. */ +#define E_F_PRINT 0x0040000 /* p flag. */ + +#define E_F_PRCLEAR 0x0080000 /* Clear the print (#, l, p) flags. */ +#define E_MODIFY 0x0100000 /* File name expansion modified arg. */ +#define E_NOGLOBAL 0x0200000 /* Not in a global. */ +#define E_NOPERM 0x0400000 /* Permission denied for now. */ +#define E_NORC 0x0800000 /* Not from a .exrc or EXINIT. */ +#define E_ZERO 0x1000000 /* 0 is a legal addr1. */ +#define E_ZERODEF 0x2000000 /* 0 is default addr1 of empty files. */ + u_int32_t flags; + char *syntax; /* Syntax script. */ + char *usage; /* Usage line. */ + char *help; /* Help line. */ +} EXCMDLIST; +#define MAXCMDNAMELEN 12 /* Longest command name. */ +extern EXCMDLIST const cmds[]; /* List of ex commands. */ + +/* + * Structure passed around to functions implementing ex commands. + * There are several commands in vi that build one of these and + * call ex directly. See vi/v_ex.c for details. + */ +struct _excmdarg { + EXCMDLIST const *cmd; /* Command entry in command table. */ + CHAR_T buffer; /* Named buffer. */ + recno_t lineno; /* Line number. */ + long count; /* Signed, specified count. */ + long flagoff; /* Signed, flag offset parsed by command. */ + int addrcnt; /* Number of addresses (0, 1 or 2). */ + MARK addr1; /* 1st address. */ + MARK addr2; /* 2nd address. */ + ARGS **argv; /* Array of arguments. */ + int argc; /* Count of arguments. */ + u_int32_t flags; /* Selected flags from EXCMDLIST. */ +}; + +/* Global ranges. */ +typedef struct _range RANGE; +struct _range { + CIRCLEQ_ENTRY(_range) q; /* Linked list of ranges. */ + recno_t start, stop; /* Start/stop of the range. */ +}; + +/* Cd paths. */ +typedef struct _cdpath CDPATH; +struct _cdpath { + TAILQ_ENTRY(_cdpath) q; /* Linked list of cd paths. */ + char *path; /* Path. */ +}; + +/* Ex private, per-screen memory. */ +typedef struct _ex_private { + ARGS **args; /* Arguments. */ + int argscnt; /* Argument count. */ + int argsoff; /* Offset into arguments. */ + + CHAR_T at_lbuf; /* Last executed at buffer's name. */ + int at_lbuf_set; /* If at_lbuf is set. */ + + char *ibp; /* Line input buffer. */ + size_t ibp_len; /* Line input buffer length. */ + + u_int32_t fdef; /* Default command flags. */ + + CHAR_T *lastbcomm; /* Last bang command. */ + + struct termios leave_term; /* ex_[sr]leave tty state. */ + /* XXX: Should be struct timespec's, but time_t is more portable. */ + time_t leave_atime; /* ex_[sr]leave old access time. */ + time_t leave_mtime; /* ex_[sr]leave old mod time. */ + + TAILQ_HEAD(_tagh, _tag) tagq; /* Tag list (stack). */ + TAILQ_HEAD(_tagfh, _tagf) tagfq;/* Tag file list. */ + char *tlast; /* Saved last tag. */ + + TAILQ_HEAD(_cdh, _cdpath) cdq; /* Cd path list. */ + + /* Linked list of ranges. */ + CIRCLEQ_HEAD(_rangeh, _range) rangeq; + recno_t range_lno; /* Range set line number. */ + +#define EX_ABSMARK 0x01 /* Set the absolute mark. */ +#define EX_AUTOPRINT 0x02 /* Autoprint flag. */ + u_int8_t flags; +} EX_PRIVATE; +#define EXP(sp) ((EX_PRIVATE *)((sp)->ex_private)) + +/* + * !!! + * Historically, .exrc files and EXINIT variables could only use ^V + * as an escape character, neither ^Q or a user specified character + * worked. We enforce that here, just in case someone depends on it. + */ +#define IS_ESCAPE(sp, ch) \ + (F_ISSET(sp, S_VLITONLY) ? \ + (ch) == CH_LITERAL : KEY_VAL(sp, ch) == K_VLNEXT) + +/* + * Filter actions: + * + * FILTER Filter text through the utility. + * FILTER_READ Read from the utility into the file. + * FILTER_WRITE Write to the utility, display its output. + */ +enum filtertype { FILTER, FILTER_READ, FILTER_WRITE }; +int filtercmd __P((SCR *, EXF *, + MARK *, MARK *, MARK *, char *, enum filtertype)); + +/* Argument expansion routines. */ +int argv_init __P((SCR *, EXF *, EXCMDARG *)); +int argv_exp0 __P((SCR *, EXF *, EXCMDARG *, char *, size_t)); +int argv_exp1 __P((SCR *, EXF *, EXCMDARG *, char *, size_t, int)); +int argv_exp2 __P((SCR *, EXF *, EXCMDARG *, char *, size_t, int)); +int argv_exp3 __P((SCR *, EXF *, EXCMDARG *, char *, size_t)); +int argv_free __P((SCR *)); + +/* Ex function prototypes. */ +int ex __P((SCR *, EXF *)); +int ex_cfile __P((SCR *, EXF *, char *, int)); +int ex_cmd __P((SCR *, EXF *, char *, size_t, int)); +int ex_cdalloc __P((SCR *, char *)); +int ex_cdfree __P((SCR *)); +int ex_end __P((SCR *)); +int ex_exec_proc __P((SCR *, char *, char *, char *)); +int ex_gb __P((SCR *, EXF *, TEXTH *, int, u_int)); +int ex_getline __P((SCR *, FILE *, size_t *)); +int ex_icmd __P((SCR *, EXF *, char *, size_t, int)); +int ex_init __P((SCR *, EXF *)); +int ex_is_abbrev __P((char *, size_t)); +int ex_is_unmap __P((char *, size_t)); +int ex_ldisplay __P((SCR *, CHAR_T *, size_t, size_t, u_int)); +int ex_ncheck __P((SCR *, int)); +int ex_optchange __P((SCR *, int)); +int ex_print __P((SCR *, EXF *, MARK *, MARK *, int)); +int ex_readfp __P((SCR *, EXF *, char *, FILE *, MARK *, recno_t *, int)); +void ex_refresh __P((SCR *, EXF *)); +void ex_rleave __P((SCR *)); +int ex_screen_copy __P((SCR *, SCR *)); +int ex_screen_end __P((SCR *)); +int ex_sdisplay __P((SCR *, EXF *)); +int ex_sleave __P((SCR *)); +int ex_suspend __P((SCR *)); +int ex_tdisplay __P((SCR *, EXF *)); +int ex_writefp __P((SCR *, EXF *, + char *, FILE *, MARK *, MARK *, u_long *, u_long *)); +void global_insdel __P((SCR *, EXF *, enum operation, recno_t)); +int proc_wait __P((SCR *, long, const char *, int)); +int sscr_end __P((SCR *)); +int sscr_exec __P((SCR *, EXF *, recno_t)); +int sscr_input __P((SCR *)); + +int abbr_save __P((SCR *, FILE *)); +int map_save __P((SCR *, FILE *)); + +#define EXPROTO(name) int name __P((SCR *, EXF *, EXCMDARG *)) +EXPROTO(ex_abbr); +EXPROTO(ex_append); +EXPROTO(ex_args); +EXPROTO(ex_at); +EXPROTO(ex_bang); +EXPROTO(ex_bg); +EXPROTO(ex_cd); +EXPROTO(ex_change); +EXPROTO(ex_color); +EXPROTO(ex_copy); +EXPROTO(ex_debug); +EXPROTO(ex_delete); +EXPROTO(ex_digraph); +EXPROTO(ex_display); +EXPROTO(ex_edit); +EXPROTO(ex_equal); +EXPROTO(ex_fg); +EXPROTO(ex_file); +EXPROTO(ex_global); +EXPROTO(ex_help); +EXPROTO(ex_insert); +EXPROTO(ex_join); +EXPROTO(ex_list); +EXPROTO(ex_map); +EXPROTO(ex_mark); +EXPROTO(ex_mkexrc); +EXPROTO(ex_move); +EXPROTO(ex_next); +EXPROTO(ex_number); +EXPROTO(ex_open); +EXPROTO(ex_pr); +EXPROTO(ex_preserve); +EXPROTO(ex_prev); +EXPROTO(ex_put); +EXPROTO(ex_quit); +EXPROTO(ex_read); +EXPROTO(ex_recover); +EXPROTO(ex_resize); +EXPROTO(ex_rew); +EXPROTO(ex_script); +EXPROTO(ex_set); +EXPROTO(ex_shell); +EXPROTO(ex_shiftl); +EXPROTO(ex_shiftr); +EXPROTO(ex_source); +EXPROTO(ex_split); +EXPROTO(ex_stop); +EXPROTO(ex_subagain); +EXPROTO(ex_substitute); +EXPROTO(ex_subtilde); +EXPROTO(ex_tagpop); +EXPROTO(ex_tagpush); +EXPROTO(ex_tagtop); +EXPROTO(ex_unabbr); +EXPROTO(ex_undo); +EXPROTO(ex_unmap); +EXPROTO(ex_usage); +EXPROTO(ex_validate); +EXPROTO(ex_version); +EXPROTO(ex_vglobal); +EXPROTO(ex_visual); +EXPROTO(ex_viusage); +EXPROTO(ex_wn); +EXPROTO(ex_wq); +EXPROTO(ex_write); +EXPROTO(ex_xit); +EXPROTO(ex_yank); +EXPROTO(ex_z); diff --git a/usr.bin/vi/ex/filter.c b/usr.bin/vi/ex/filter.c new file mode 100644 index 0000000..59a221c --- /dev/null +++ b/usr.bin/vi/ex/filter.c @@ -0,0 +1,414 @@ +/*- + * Copyright (c) 1991, 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[] = "@(#)filter.c 8.43 (Berkeley) 8/7/94"; +#endif /* not lint */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "compat.h" +#include +#include +#include + +#include "vi.h" +#include "excmd.h" + +static int filter_ldisplay __P((SCR *, FILE *)); + +/* + * filtercmd -- + * Run a range of lines through a filter utility and optionally + * replace the original text with the stdout/stderr output of + * the utility. + */ +int +filtercmd(sp, ep, fm, tm, rp, cmd, ftype) + SCR *sp; + EXF *ep; + MARK *fm, *tm, *rp; + char *cmd; + enum filtertype ftype; +{ + FILE *ifp, *ofp; + pid_t parent_writer_pid, utility_pid; + recno_t nread; + int input[2], output[2], rval, teardown; + char *name; + + /* Set return cursor position; guard against a line number of zero. */ + *rp = *fm; + if (fm->lno == 0) + rp->lno = 1; + + /* + * There are three different processes running through this code. + * They are the utility, the parent-writer and the parent-reader. + * The parent-writer is the process that writes from the file to + * the utility, the parent reader is the process that reads from + * the utility. + * + * Input and output are named from the utility's point of view. + * The utility reads from input[0] and the parent(s) write to + * input[1]. The parent(s) read from output[0] and the utility + * writes to output[1]. + * + * In the FILTER_READ case, the utility isn't expected to want + * input. Redirect its input from /dev/null. Otherwise open + * up utility input pipe. + */ + teardown = 0; + ofp = NULL; + input[0] = input[1] = output[0] = output[1] = -1; + if (ftype == FILTER_READ) { + if ((input[0] = open(_PATH_DEVNULL, O_RDONLY, 0)) < 0) { + msgq(sp, M_ERR, + "filter: %s: %s", _PATH_DEVNULL, strerror(errno)); + return (1); + } + } else + if (pipe(input) < 0) { + msgq(sp, M_SYSERR, "pipe"); + goto err; + } + + /* Open up utility output pipe. */ + if (pipe(output) < 0) { + msgq(sp, M_SYSERR, "pipe"); + goto err; + } + if ((ofp = fdopen(output[0], "r")) == NULL) { + msgq(sp, M_SYSERR, "fdopen"); + goto err; + } + + /* + * Save ex/vi terminal settings, and restore the original ones. + * Restoration so that users can do things like ":r! cat /dev/tty". + */ + teardown = ftype != FILTER_WRITE && !ex_sleave(sp); + + /* Fork off the utility process. */ + SIGBLOCK(sp->gp); + switch (utility_pid = vfork()) { + case -1: /* Error. */ + SIGUNBLOCK(sp->gp); + + msgq(sp, M_SYSERR, "vfork"); +err: if (input[0] != -1) + (void)close(input[0]); + if (input[1] != -1) + (void)close(input[1]); + if (ofp != NULL) + (void)fclose(ofp); + else if (output[0] != -1) + (void)close(output[0]); + if (output[1] != -1) + (void)close(output[1]); + rval = 1; + goto ret; + case 0: /* Utility. */ + /* The utility has default signal behavior. */ + sig_end(); + + /* + * Redirect stdin from the read end of the input pipe, and + * redirect stdout/stderr to the write end of the output pipe. + * + * !!! + * Historically, ex only directed stdout into the input pipe, + * letting stderr come out on the terminal as usual. Vi did + * not, directing both stdout and stderr into the input pipe. + * We match that practice for both ex and vi for consistency. + */ + (void)dup2(input[0], STDIN_FILENO); + (void)dup2(output[1], STDOUT_FILENO); + (void)dup2(output[1], STDERR_FILENO); + + /* Close the utility's file descriptors. */ + (void)close(input[0]); + (void)close(input[1]); + (void)close(output[0]); + (void)close(output[1]); + + if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL) + name = O_STR(sp, O_SHELL); + else + ++name; + + execl(O_STR(sp, O_SHELL), name, "-c", cmd, NULL); + msgq(sp, M_ERR, "Error: execl: %s: %s", + O_STR(sp, O_SHELL), strerror(errno)); + _exit (127); + /* NOTREACHED */ + default: /* Parent-reader, parent-writer. */ + SIGUNBLOCK(sp->gp); + + /* Close the pipe ends neither parent will use. */ + (void)close(input[0]); + (void)close(output[1]); + break; + } + + /* + * FILTER_READ: + * + * Reading is the simple case -- we don't need a parent writer, + * so the parent reads the output from the read end of the output + * pipe until it finishes, then waits for the child. Ex_readfp + * appends to the MARK, and closes ofp. + * + * !!! + * Set the return cursor to the last line read in. Historically, + * this behaves differently from ":r file" command, which leaves + * the cursor at the first line read in. Check to make sure that + * it's not past EOF because we were reading into an empty file. + */ + if (ftype == FILTER_READ) { + rval = ex_readfp(sp, ep, "filter", ofp, fm, &nread, 0); + sp->rptlines[L_ADDED] += nread; + if (fm->lno == 0) + rp->lno = nread; + else + rp->lno += nread; + goto uwait; + } + + /* + * FILTER, FILTER_WRITE + * + * Here we need both a reader and a writer. Temporary files are + * expensive and we'd like to avoid disk I/O. Using pipes has the + * obvious starvation conditions. It's done as follows: + * + * fork + * child + * write lines out + * exit + * parent + * FILTER: + * read lines into the file + * delete old lines + * FILTER_WRITE + * read and display lines + * wait for child + * + * XXX + * We get away without locking the underlying database because we know + * that none of the records that we're reading will be modified until + * after we've read them. This depends on the fact that the current + * B+tree implementation doesn't balance pages or similar things when + * it inserts new records. When the DB code has locking, we should + * treat vi as if it were multiple applications sharing a database, and + * do the required locking. If necessary a work-around would be to do + * explicit locking in the line.c:file_gline() code, based on the flag + * set here. + */ + rval = 0; + F_SET(ep, F_MULTILOCK); + + SIGBLOCK(sp->gp); + switch (parent_writer_pid = fork()) { + case -1: /* Error. */ + SIGUNBLOCK(sp->gp); + + msgq(sp, M_SYSERR, "fork"); + (void)close(input[1]); + (void)close(output[0]); + rval = 1; + break; + case 0: /* Parent-writer. */ + /* + * Write the selected lines to the write end of the input + * pipe. This instance of ifp is closed by ex_writefp. + */ + (void)close(output[0]); + if ((ifp = fdopen(input[1], "w")) == NULL) + _exit (1); + _exit(ex_writefp(sp, ep, "filter", ifp, fm, tm, NULL, NULL)); + + /* NOTREACHED */ + default: /* Parent-reader. */ + SIGUNBLOCK(sp->gp); + + (void)close(input[1]); + if (ftype == FILTER_WRITE) + /* + * Read the output from the read end of the output + * pipe and display it. Filter_ldisplay closes ofp. + */ + rval = filter_ldisplay(sp, ofp); + else { + /* + * Read the output from the read end of the output + * pipe. Ex_readfp appends to the MARK and closes + * ofp. + */ + rval = ex_readfp(sp, ep, "filter", ofp, tm, &nread, 0); + sp->rptlines[L_ADDED] += nread; + } + + /* Wait for the parent-writer. */ + rval |= proc_wait(sp, + (long)parent_writer_pid, "parent-writer", 1); + + /* Delete any lines written to the utility. */ + if (rval == 0 && ftype == FILTER && + (cut(sp, ep, NULL, fm, tm, CUT_LINEMODE) || + delete(sp, ep, fm, tm, 1))) { + rval = 1; + break; + } + + /* + * If the filter had no output, we may have just deleted + * the cursor. Don't do any real error correction, we'll + * try and recover later. + */ + if (rp->lno > 1 && file_gline(sp, ep, rp->lno, NULL) == NULL) + --rp->lno; + break; + } + F_CLR(ep, F_MULTILOCK); + +uwait: rval |= proc_wait(sp, (long)utility_pid, cmd, 0); + + /* Restore ex/vi terminal settings. */ +ret: if (teardown) + ex_rleave(sp); + return (rval); +} + +/* + * proc_wait -- + * Wait for one of the processes. + * + * !!! + * The pid_t type varies in size from a short to a long depending on the + * system. It has to be cast into something or the standard promotion + * rules get you. I'm using a long based on the belief that nobody is + * going to make it unsigned and it's unlikely to be a quad. + */ +int +proc_wait(sp, pid, cmd, okpipe) + SCR *sp; + long pid; + const char *cmd; + int okpipe; +{ + extern const char *const sys_siglist[]; + size_t len; + int pstat; + + /* + * Wait for the utility to finish. We can get interrupted + * by SIGALRM, just ignore it. + */ + for (;;) { + errno = 0; + if (waitpid((pid_t)pid, &pstat, 0) != -1) + break; + if (errno != EINTR) { + msgq(sp, M_SYSERR, "wait error"); + return (1); + } + } + + /* + * Display the utility's exit status. Ignore SIGPIPE from the + * parent-writer, as that only means that the utility chose to + * exit before reading all of its input. + */ + if (WIFSIGNALED(pstat) && (!okpipe || WTERMSIG(pstat) != SIGPIPE)) { + for (; isblank(*cmd); ++cmd); + len = strlen(cmd); + msgq(sp, M_ERR, "%.*s%s: received signal: %s%s", + MIN(len, 20), cmd, len > 20 ? "..." : "", + sys_siglist[WTERMSIG(pstat)], + WCOREDUMP(pstat) ? "; core dumped" : ""); + return (1); + } + if (WIFEXITED(pstat) && WEXITSTATUS(pstat)) { + for (; isblank(*cmd); ++cmd); + len = strlen(cmd); + msgq(sp, M_ERR, "%.*s%s: exited with status %d", + MIN(len, 20), cmd, len > 20 ? "..." : "", + WEXITSTATUS(pstat)); + return (1); + } + return (0); +} + +/* + * filter_ldisplay -- + * Display output from a utility. + * + * !!! + * Historically, the characters were passed unmodified to the terminal. + * We use the ex print routines to make sure they're printable. + */ +static int +filter_ldisplay(sp, fp) + SCR *sp; + FILE *fp; +{ + size_t len; + + EX_PRIVATE *exp; + + F_SET(sp, S_INTERRUPTIBLE); + for (exp = EXP(sp); !ex_getline(sp, fp, &len);) { + if (ex_ldisplay(sp, exp->ibp, len, 0, 0)) + break; + if (INTERRUPTED(sp)) + break; + } + if (ferror(fp)) + msgq(sp, M_SYSERR, "filter input"); + (void)fclose(fp); + return (0); +} diff --git a/usr.bin/vi/ex/script.h b/usr.bin/vi/ex/script.h new file mode 100644 index 0000000..b21c63a --- /dev/null +++ b/usr.bin/vi/ex/script.h @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 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. + * + * @(#)script.h 8.2 (Berkeley) 4/17/94 + */ + +struct _script { + pid_t sh_pid; /* Shell pid. */ + int sh_master; /* Master pty fd. */ + int sh_slave; /* Slave pty fd. */ + char *sh_prompt; /* Prompt. */ + size_t sh_prompt_len; /* Prompt length. */ + char sh_name[64]; /* Pty name */ + struct winsize sh_win; /* Window size. */ + struct termios sh_term; /* Terminal information. */ +}; diff --git a/usr.bin/vi/ex/tag.h b/usr.bin/vi/ex/tag.h new file mode 100644 index 0000000..a9fd59d --- /dev/null +++ b/usr.bin/vi/ex/tag.h @@ -0,0 +1,58 @@ +/*- + * 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. + * + * @(#)tag.h 8.13 (Berkeley) 7/17/94 + */ + +struct _tagf { /* Tag file. */ + TAILQ_ENTRY(_tagf) q; /* Linked list of tag files. */ + char *name; /* Tag file name. */ + +#define TAGF_DNE 0x01 /* Didn't exist. */ +#define TAGF_DNE_WARN 0x02 /* DNE error reported. */ + u_int8_t flags; +}; + +struct _tag { /* Tag stack. */ + TAILQ_ENTRY(_tag) q; /* Linked list of tags. */ + FREF *frp; /* Saved file name. */ + recno_t lno; /* Saved line number. */ + size_t cno; /* Saved column number. */ + char *search; /* Search string. */ + size_t slen; /* Search string length. */ +}; + +int ex_tagalloc __P((SCR *, char *)); +int ex_tagcopy __P((SCR *, SCR *)); +int ex_tagdisplay __P((SCR *, EXF *)); +int ex_tagfirst __P((SCR *, char *)); +int ex_tagfree __P((SCR *)); -- cgit v1.1