diff options
Diffstat (limited to 'ex')
-rw-r--r-- | ex/ex.awk | 6 | ||||
-rw-r--r-- | ex/ex.c | 2370 | ||||
-rw-r--r-- | ex/ex.h | 228 | ||||
-rw-r--r-- | ex/ex_abbrev.c | 117 | ||||
-rw-r--r-- | ex/ex_append.c | 277 | ||||
-rw-r--r-- | ex/ex_args.c | 327 | ||||
-rw-r--r-- | ex/ex_argv.c | 756 | ||||
-rw-r--r-- | ex/ex_at.c | 126 | ||||
-rw-r--r-- | ex/ex_bang.c | 186 | ||||
-rw-r--r-- | ex/ex_cd.c | 129 | ||||
-rw-r--r-- | ex/ex_cmd.c | 457 | ||||
-rw-r--r-- | ex/ex_cscope.c | 1057 | ||||
-rw-r--r-- | ex/ex_delete.c | 65 | ||||
-rw-r--r-- | ex/ex_display.c | 145 | ||||
-rw-r--r-- | ex/ex_edit.c | 153 | ||||
-rw-r--r-- | ex/ex_equal.c | 59 | ||||
-rw-r--r-- | ex/ex_file.c | 80 | ||||
-rw-r--r-- | ex/ex_filter.c | 316 | ||||
-rw-r--r-- | ex/ex_global.c | 328 | ||||
-rw-r--r-- | ex/ex_init.c | 417 | ||||
-rw-r--r-- | ex/ex_join.c | 177 | ||||
-rw-r--r-- | ex/ex_map.c | 121 | ||||
-rw-r--r-- | ex/ex_mark.c | 45 | ||||
-rw-r--r-- | ex/ex_mkexrc.c | 101 | ||||
-rw-r--r-- | ex/ex_move.c | 198 | ||||
-rw-r--r-- | ex/ex_open.c | 46 | ||||
-rw-r--r-- | ex/ex_perl.c | 69 | ||||
-rw-r--r-- | ex/ex_preserve.c | 103 | ||||
-rw-r--r-- | ex/ex_print.c | 352 | ||||
-rw-r--r-- | ex/ex_put.c | 51 | ||||
-rw-r--r-- | ex/ex_quit.c | 46 | ||||
-rw-r--r-- | ex/ex_read.c | 360 | ||||
-rw-r--r-- | ex/ex_screen.c | 138 | ||||
-rw-r--r-- | ex/ex_script.c | 798 | ||||
-rw-r--r-- | ex/ex_set.c | 46 | ||||
-rw-r--r-- | ex/ex_shell.c | 378 | ||||
-rw-r--r-- | ex/ex_shift.c | 191 | ||||
-rw-r--r-- | ex/ex_source.c | 85 | ||||
-rw-r--r-- | ex/ex_stop.c | 51 | ||||
-rw-r--r-- | ex/ex_subst.c | 1459 | ||||
-rw-r--r-- | ex/ex_tag.c | 1324 | ||||
-rw-r--r-- | ex/ex_tcl.c | 80 | ||||
-rw-r--r-- | ex/ex_txt.c | 430 | ||||
-rw-r--r-- | ex/ex_undo.c | 77 | ||||
-rw-r--r-- | ex/ex_usage.c | 196 | ||||
-rw-r--r-- | ex/ex_util.c | 234 | ||||
-rw-r--r-- | ex/ex_version.c | 39 | ||||
-rw-r--r-- | ex/ex_visual.c | 161 | ||||
-rw-r--r-- | ex/ex_write.c | 375 | ||||
-rw-r--r-- | ex/ex_yank.c | 46 | ||||
-rw-r--r-- | ex/ex_z.c | 150 | ||||
-rw-r--r-- | ex/script.h | 23 | ||||
-rw-r--r-- | ex/tag.h | 107 | ||||
-rw-r--r-- | ex/version.h | 2 |
54 files changed, 15658 insertions, 0 deletions
diff --git a/ex/ex.awk b/ex/ex.awk new file mode 100644 index 000000000000..3ee372e14aac --- /dev/null +++ b/ex/ex.awk @@ -0,0 +1,6 @@ +# @(#)ex.awk 10.1 (Berkeley) 6/8/95 + +/^\/\* C_[0-9A-Z_]* \*\/$/ { + printf("#define %s %d\n", $2, cnt++); + next; +} diff --git a/ex/ex.c b/ex/ex.c new file mode 100644 index 000000000000..f92d8f7c4f9a --- /dev/null +++ b/ex/ex.c @@ -0,0 +1,2370 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex.c 10.57 (Berkeley) 10/10/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" +#include "../vi/vi.h" + +#if defined(DEBUG) && defined(COMLOG) +static void ex_comlog __P((SCR *, EXCMD *)); +#endif +static EXCMDLIST const * + ex_comm_search __P((char *, size_t)); +static int ex_discard __P((SCR *)); +static int ex_line __P((SCR *, EXCMD *, MARK *, int *, int *)); +static int ex_load __P((SCR *)); +static void ex_unknown __P((SCR *, char *, size_t)); + +/* + * ex -- + * Main ex loop. + * + * PUBLIC: int ex __P((SCR **)); + */ +int +ex(spp) + SCR **spp; +{ + EX_PRIVATE *exp; + GS *gp; + MSGS *mp; + SCR *sp; + TEXT *tp; + u_int32_t flags; + + sp = *spp; + gp = sp->gp; + exp = EXP(sp); + + /* Start the ex screen. */ + if (ex_init(sp)) + return (1); + + /* Flush any saved messages. */ + while ((mp = gp->msgq.lh_first) != NULL) { + gp->scr_msg(sp, mp->mtype, mp->buf, mp->len); + LIST_REMOVE(mp, q); + free(mp->buf); + free(mp); + } + + /* If reading from a file, errors should have name and line info. */ + if (F_ISSET(gp, G_SCRIPTED)) { + gp->excmd.if_lno = 1; + gp->excmd.if_name = "script"; + } + + /* + * !!! + * Initialize the text flags. The beautify edit option historically + * applied to ex command input read from a file. In addition, the + * first time a ^H was discarded from the input, there was a message, + * "^H discarded", that was displayed. We don't bother. + */ + LF_INIT(TXT_BACKSLASH | TXT_CNTRLD | TXT_CR); + for (;; ++gp->excmd.if_lno) { + /* Display status line and flush. */ + if (F_ISSET(sp, SC_STATUS)) { + if (!F_ISSET(sp, SC_EX_SILENT)) + msgq_status(sp, sp->lno, 0); + F_CLR(sp, SC_STATUS); + } + (void)ex_fflush(sp); + + /* Set the flags the user can reset. */ + if (O_ISSET(sp, O_BEAUTIFY)) + LF_SET(TXT_BEAUTIFY); + if (O_ISSET(sp, O_PROMPT)) + LF_SET(TXT_PROMPT); + + /* Clear any current interrupts, and get a command. */ + CLR_INTERRUPT(sp); + if (ex_txt(sp, &sp->tiq, ':', flags)) + return (1); + if (INTERRUPTED(sp)) { + (void)ex_puts(sp, "\n"); + (void)ex_fflush(sp); + continue; + } + + /* Initialize the command structure. */ + CLEAR_EX_PARSER(&gp->excmd); + + /* + * If the user entered a single carriage return, send + * ex_cmd() a separator -- it discards single newlines. + */ + tp = sp->tiq.cqh_first; + if (tp->len == 0) { + gp->excmd.cp = " "; /* __TK__ why not |? */ + gp->excmd.clen = 1; + } else { + gp->excmd.cp = tp->lb; + gp->excmd.clen = tp->len; + } + F_INIT(&gp->excmd, E_NRSEP); + + if (ex_cmd(sp) && F_ISSET(gp, G_SCRIPTED)) + return (1); + + if (INTERRUPTED(sp)) { + CLR_INTERRUPT(sp); + msgq(sp, M_ERR, "170|Interrupted"); + } + + /* + * If the last command caused a restart, or switched screens + * or into vi, return. + */ + if (F_ISSET(gp, G_SRESTART) || F_ISSET(sp, SC_SSWITCH | SC_VI)) { + *spp = sp; + break; + } + + /* If the last command switched files, we don't care. */ + F_CLR(sp, SC_FSWITCH); + + /* + * If we're exiting this screen, move to the next one. By + * definition, this means returning into vi, so return to the + * main editor loop. The ordering is careful, don't discard + * the contents of sp until the end. + */ + if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) { + if (file_end(sp, NULL, F_ISSET(sp, SC_EXIT_FORCE))) + return (1); + *spp = screen_next(sp); + return (screen_end(sp)); + } + } + return (0); +} + +/* + * ex_cmd -- + * The guts of the ex parser: parse and execute a string containing + * ex commands. + * + * !!! + * This code MODIFIES the string that gets passed in, to delete quoting + * characters, etc. The string cannot be readonly/text space, nor should + * you expect to use it again after ex_cmd() returns. + * + * !!! + * 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 + * + * or: vi + * :set|file|append|set|file + * + * For extra credit, try them in a startup .exrc file. + * + * PUBLIC: int ex_cmd __P((SCR *)); + */ +int +ex_cmd(sp) + SCR *sp; +{ + enum nresult nret; + EX_PRIVATE *exp; + EXCMD *ecp; + GS *gp; + MARK cur; + recno_t lno; + size_t arg1_len, discard, len; + u_int32_t flags; + long ltmp; + int at_found, gv_found; + int ch, cnt, delim, isaddr, namelen; + int newscreen, notempty, tmp, vi_address; + char *arg1, *p, *s, *t; + + gp = sp->gp; + exp = EXP(sp); + + /* + * We always start running the command on the top of the stack. + * This means that *everything* must be resolved when we leave + * this function for any reason. + */ +loop: ecp = gp->ecq.lh_first; + + /* If we're reading a command from a file, set up error information. */ + if (ecp->if_name != NULL) { + gp->if_lno = ecp->if_lno; + gp->if_name = ecp->if_name; + } + + /* + * If a move to the end of the file is scheduled for this command, + * do it now. + */ + if (F_ISSET(ecp, E_MOVETOEND)) { + if (db_last(sp, &sp->lno)) + goto rfail; + sp->cno = 0; + F_CLR(ecp, E_MOVETOEND); + } + + /* If we found a newline, increment the count now. */ + if (F_ISSET(ecp, E_NEWLINE)) { + ++gp->if_lno; + ++ecp->if_lno; + F_CLR(ecp, E_NEWLINE); + } + + /* (Re)initialize the EXCMD structure, preserving some flags. */ + CLEAR_EX_CMD(ecp); + + /* Initialize the argument structures. */ + if (argv_init(sp, ecp)) + goto err; + + /* Initialize +cmd, saved command information. */ + arg1 = NULL; + ecp->save_cmdlen = 0; + + /* Skip <blank>s, empty lines. */ + for (notempty = 0; ecp->clen > 0; ++ecp->cp, --ecp->clen) + if ((ch = *ecp->cp) == '\n') { + ++gp->if_lno; + ++ecp->if_lno; + } else if (isblank(ch)) + notempty = 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 (ecp->clen != 0 && ch == ':') { + notempty = 1; + while (--ecp->clen > 0 && (ch = *++ecp->cp) == ':'); + } + + /* + * 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 <newline> characters into command lines, we + * have to check for that case. + */ + if (ecp->clen != 0 && ch == '"') { + while (--ecp->clen > 0 && *++ecp->cp != '\n'); + if (*ecp->cp == '\n') { + F_SET(ecp, E_NEWLINE); + ++ecp->cp; + --ecp->clen; + } + goto loop; + } + + /* Skip whitespace. */ + for (; ecp->clen > 0; ++ecp->cp, --ecp->clen) { + ch = *ecp->cp; + if (!isblank(ch)) + break; + } + + /* + * The last point at which an empty line can mean do nothing. + * + * !!! + * Historically, in ex mode, lines containing only <blank> characters + * were the same as a single <carriage-return>, 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, discarding lines that + * contain only <blank> characters from .exrc files. + * + * !!! + * This is where you end up when you're done a command, i.e. clen has + * gone to zero. Continue if there are more commands to run. + */ + if (ecp->clen == 0 && + (!notempty || F_ISSET(sp, SC_VI) || F_ISSET(ecp, E_BLIGNORE))) { + if (ex_load(sp)) + goto rfail; + ecp = gp->ecq.lh_first; + if (ecp->clen == 0) + goto rsuccess; + goto loop; + } + + /* + * Check to see if this is a command for which we may want to move + * the cursor back up to the previous line. (The command :1<CR> + * wants a <newline> separator, but the command :<CR> wants to erase + * the command line.) If the line is empty except for <blank>s, + * <carriage-return> or <eof>, we'll probably want to move up. I + * don't think there's any way to get <blank> characters *after* the + * command character, but this is the ex parser, and I've been wrong + * before. + */ + if (F_ISSET(ecp, E_NRSEP) && + ecp->clen != 0 && (ecp->clen != 1 || ecp->cp[0] != '\004')) + F_CLR(ecp, E_NRSEP); + + /* Parse command addresses. */ + if (ex_range(sp, ecp, &tmp)) + goto rfail; + if (tmp) + goto err; + + /* + * Skip <blank>s and any more colons (the command :3,5:print + * worked, historically). + */ + for (; ecp->clen > 0; ++ecp->cp, --ecp->clen) { + ch = *ecp->cp; + if (!isblank(ch) && 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 <blank> separated) + * command separators were very strange. For example, the command + * |||<carriage-return>, 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!#&*<=>@~" + newscreen = 0; + if (ecp->clen != 0 && ecp->cp[0] != '|' && ecp->cp[0] != '\n') { + if (strchr(SINGLE_CHAR_COMMANDS, *ecp->cp)) { + p = ecp->cp; + ++ecp->cp; + --ecp->clen; + namelen = 1; + } else { + for (p = ecp->cp; + ecp->clen > 0; --ecp->clen, ++ecp->cp) + if (!isalpha(*ecp->cp)) + break; + if ((namelen = ecp->cp - p) == 0) { + msgq(sp, M_ERR, "080|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. + * + * Adding commands that start with 'd', and match "delete" + * up to a l, p, +, - or # character can break this code. + * + * !!! + * Capital letters beginning the command names ex, edit, + * next, previous, tag and visual (in vi mode) indicate the + * command should happen in a new screen. + */ + switch (p[0]) { + case '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] == '^' || s[0] == '#') { + len = (ecp->cp - p) - (s - p); + ecp->cp -= len; + ecp->clen += len; + ecp->rcmd = cmds[C_DELETE]; + ecp->rcmd.syntax = "1bca1"; + ecp->cmd = &ecp->rcmd; + goto skip_srch; + } + break; + case 'E': case 'F': case 'N': case 'P': case 'T': case 'V': + newscreen = 1; + p[0] = tolower(p[0]); + break; + } + + /* + * 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 any flag to follow the s command, e.g. + * "s/e/E/|s|sgc3p" was legal. Make the command "sgc" work. + * Since the following characters all have to be flags, i.e. + * alphabetics, we can let the s command routine return errors + * if it was some illegal command string. This code will break + * if an "sg" or similar command is ever added. The substitute + * code doesn't care if it's a "cgr" flag or a "#lp" flag that + * follows the 's', but we limit the choices here to "cgr" so + * that we get unknown command messages for wrong combinations. + */ + if ((ecp->cmd = ex_comm_search(p, namelen)) == NULL) + switch (p[0]) { + case 'k': + if (namelen == 2) { + ecp->cp -= namelen - 1; + ecp->clen += namelen - 1; + ecp->cmd = &cmds[C_K]; + break; + } + goto unknown; + case 's': + for (s = p + 1, cnt = namelen; --cnt; ++s) + if (s[0] != 'c' && + s[0] != 'g' && s[0] != 'r') + break; + if (cnt == 0) { + ecp->cp -= namelen - 1; + ecp->clen += namelen - 1; + ecp->rcmd = cmds[C_SUBSTITUTE]; + ecp->rcmd.fn = ex_subagain; + ecp->cmd = &ecp->rcmd; + break; + } + /* FALLTHROUGH */ + default: +unknown: if (newscreen) + p[0] = toupper(p[0]); + ex_unknown(sp, p, namelen); + goto err; + } + + /* + * The visual command has a different syntax when called + * from ex than when called from a vi colon command. FMH. + * Make the change now, before we test for the newscreen + * semantic, so that we're testing the right one. + */ +skip_srch: if (ecp->cmd == &cmds[C_VISUAL_EX] && F_ISSET(sp, SC_VI)) + ecp->cmd = &cmds[C_VISUAL_VI]; + + /* + * !!! + * Historic vi permitted a capital 'P' at the beginning of + * any command that started with 'p'. Probably wanted the + * P[rint] command for backward compatibility, and the code + * just made Preserve and Put work by accident. Nvi uses + * Previous to mean previous-in-a-new-screen, so be careful. + */ + if (newscreen && !F_ISSET(ecp->cmd, E_NEWSCREEN) && + (ecp->cmd == &cmds[C_PRINT] || + ecp->cmd == &cmds[C_PRESERVE])) + newscreen = 0; + + /* Test for a newscreen associated with this command. */ + if (newscreen && !F_ISSET(ecp->cmd, E_NEWSCREEN)) + goto unknown; + + /* Secure means no shell access. */ + if (F_ISSET(ecp->cmd, E_SECURE) && O_ISSET(sp, O_SECURE)) { + ex_emsg(sp, ecp->cmd->name, EXM_SECURE); + goto err; + } + + /* + * Multiple < and > characters; another "feature". Note, + * The string passed to the underlying function may not be + * nul terminated in this case. + */ + if ((ecp->cmd == &cmds[C_SHIFTL] && *p == '<') || + (ecp->cmd == &cmds[C_SHIFTR] && *p == '>')) { + for (ch = *p; + ecp->clen > 0; --ecp->clen, ++ecp->cp) + if (*ecp->cp != ch) + break; + if (argv_exp0(sp, ecp, p, ecp->cp - p)) + goto err; + } + + /* Set the format style flags for the next command. */ + if (ecp->cmd == &cmds[C_HASH]) + exp->fdef = E_C_HASH; + else if (ecp->cmd == &cmds[C_LIST]) + exp->fdef = E_C_LIST; + else if (ecp->cmd == &cmds[C_PRINT]) + exp->fdef = E_C_PRINT; + F_CLR(ecp, E_USELASTCMD); + } else { + /* Print is the default command. */ + ecp->cmd = &cmds[C_PRINT]; + + /* Set the saved format flags. */ + F_SET(ecp, exp->fdef); + + /* + * !!! + * If no address was specified, and it's not a global command, + * we up the address by one. (I have no idea why globals are + * exempted, but it's (ahem) historic practice.) + */ + if (ecp->addrcnt == 0 && !F_ISSET(sp, SC_EX_GLOBAL)) { + ecp->addrcnt = 1; + ecp->addr1.lno = sp->lno + 1; + ecp->addr1.cno = sp->cno; + } + + F_SET(ecp, E_USELASTCMD); + } + + /* + * !!! + * 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. <CR>'s after the set didn't change to + * the new format, but :1p would. + */ + if (O_ISSET(sp, O_NUMBER)) { + F_SET(ecp, E_OPTNUM); + FL_SET(ecp->iflags, E_C_HASH); + } else + F_CLR(ecp, E_OPTNUM); + + /* Check for ex mode legality. */ + if (F_ISSET(sp, SC_EX) && (F_ISSET(ecp->cmd, E_VIONLY) || newscreen)) { + msgq(sp, M_ERR, + "082|%s: command not available in ex mode", ecp->cmd->name); + goto err; + } + + /* Add standard command flags. */ + F_SET(ecp, ecp->cmd->flags); + if (!newscreen) + F_CLR(ecp, E_NEWSCREEN); + + /* + * There are three normal termination cases for an ex command. They + * are the end of the string (ecp->clen), or unescaped (by <literal + * next> characters) <newline> or '|' characters. As we're now past + * possible addresses, we can determine how long the command is, so we + * don't have to look for all the possible terminations. Naturally, + * there are some exciting special cases: + * + * 1: The bang, global, v and the filter versions of the read and + * write commands are delimited by <newline>s (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 s 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 s commands didn't delimit the command. And, + * in the filter cases for read and write, and the bang, global and v + * commands, they did not delimit the command at all. + * + * For example, the following commands were legal: + * + * :edit +25|s/abc/ABC/ file.c + * :s/|/PIPE/ + * :read !spell % | columnate + * :global/pattern/p|l + * + * It's not quite as simple as it sounds, however. The command: + * + * :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 of syntax) delimited the RE's + * based on its delimiter and not anything so irretrievably vulgar as a + * command syntax. + * + * 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. + */ + discard = 0; /* Characters discarded from the command. */ + arg1_len = 0; + ecp->save_cmd = ecp->cp; + if (ecp->cmd == &cmds[C_EDIT] || ecp->cmd == &cmds[C_EX] || + ecp->cmd == &cmds[C_NEXT] || ecp->cmd == &cmds[C_VISUAL_VI]) { + /* + * Move to the next non-whitespace character. A '!' + * immediately following the command is eaten as a + * force flag. + */ + if (ecp->clen > 0 && *ecp->cp == '!') { + ++ecp->cp; + --ecp->clen; + FL_SET(ecp->iflags, E_C_FORCE); + + /* Reset, don't reparse. */ + ecp->save_cmd = ecp->cp; + } + for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) + if (!isblank(*ecp->cp)) + 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. The escaping + * characters are stripped as no longer useful. + */ + if (ecp->clen > 0 && *ecp->cp == '+') { + ++ecp->cp; + --ecp->clen; + for (arg1 = p = ecp->cp; + ecp->clen > 0; --ecp->clen, ++ecp->cp) { + ch = *ecp->cp; + if (IS_ESCAPE(sp, ecp, ch) && + ecp->clen > 1) { + ++discard; + --ecp->clen; + ch = *++ecp->cp; + } else if (isblank(ch)) + break; + *p++ = ch; + } + arg1_len = ecp->cp - arg1; + + /* Reset, so the first argument isn't reparsed. */ + ecp->save_cmd = ecp->cp; + } + } else if (ecp->cmd == &cmds[C_BANG] || + ecp->cmd == &cmds[C_GLOBAL] || ecp->cmd == &cmds[C_V]) { + /* + * QUOTING NOTE: + * + * We use backslashes to escape <newline> characters, although + * this wasn't historic practice for the bang command. It was + * for the global and v commands, and it's common usage when + * doing text insert during the command. Escaping characters + * are stripped as no longer useful. + */ + for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { + ch = *ecp->cp; + if (ch == '\\' && ecp->clen > 1 && ecp->cp[1] == '\n') { + ++discard; + --ecp->clen; + ch = *++ecp->cp; + + ++gp->if_lno; + ++ecp->if_lno; + } else if (ch == '\n') + break; + *p++ = ch; + } + } else if (ecp->cmd == &cmds[C_READ] || ecp->cmd == &cmds[C_WRITE]) { + /* + * For write commands, if the next character is a <blank>, and + * the next non-blank character is a '!', it's a filter command + * and we want to eat everything up to the <newline>. For read + * commands, if the next non-blank character is a '!', it's a + * filter command and we want to eat everything up to the next + * <newline>. Otherwise, we're done. + */ + for (tmp = 0; ecp->clen > 0; --ecp->clen, ++ecp->cp) { + ch = *ecp->cp; + if (isblank(ch)) + tmp = 1; + else + break; + } + if (ecp->clen > 0 && ch == '!' && + (ecp->cmd == &cmds[C_READ] || tmp)) + for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) + if (ecp->cp[0] == '\n') + break; + } else if (ecp->cmd == &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 s command. + */ + for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) + if (!isblank(ecp->cp[0])) + break; + + if (isalnum(ecp->cp[0]) || ecp->cp[0] == '|') { + ecp->rcmd = cmds[C_SUBSTITUTE]; + ecp->rcmd.fn = ex_subagain; + ecp->cmd = &ecp->rcmd; + } else if (ecp->clen > 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 = *ecp->cp; + ++ecp->cp; + --ecp->clen; + for (cnt = 2; ecp->clen > 0 && + cnt != 0; --ecp->clen, ++ecp->cp) + if (ecp->cp[0] == '\\' && + ecp->clen > 1) { + ++ecp->cp; + --ecp->clen; + } else if (ecp->cp[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 <newline>'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 they're + * no longer useful. + */ + vi_address = ecp->clen != 0 && ecp->cp[0] != '\n'; + for (p = ecp->cp; ecp->clen > 0; --ecp->clen, ++ecp->cp) { + ch = ecp->cp[0]; + if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { + tmp = ecp->cp[1]; + if (tmp == '\n' || tmp == '|') { + if (tmp == '\n') { + ++gp->if_lno; + ++ecp->if_lno; + } + ++discard; + --ecp->clen; + ++ecp->cp; + ch = tmp; + } + } else if (ch == '\n' || ch == '|') { + if (ch == '\n') + F_SET(ecp, E_NEWLINE); + --ecp->clen; + break; + } + *p++ = ch; + } + + /* + * Save off the next command information, go back to the + * original start of the command. + */ + p = ecp->cp + 1; + ecp->cp = ecp->save_cmd; + ecp->save_cmd = p; + ecp->save_cmdlen = ecp->clen; + ecp->clen = ((ecp->save_cmd - ecp->cp) - 1) - discard; + + /* + * QUOTING NOTE: + * + * 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 (ecp->cmd == &cmds[C_SET]) + for (p = ecp->cp, len = ecp->clen; 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_ADDR_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 or if we just used + * the current cursor. + */ + switch (F_ISSET(ecp, E_ADDR1 | E_ADDR2 | E_ADDR2_ALL | E_ADDR2_NONE)) { + case E_ADDR1: /* One address: */ + switch (ecp->addrcnt) { + case 0: /* Default cursor/empty file. */ + ecp->addrcnt = 1; + F_SET(ecp, E_ADDR_DEF); + if (F_ISSET(ecp, E_ADDR_ZERODEF)) { + if (db_last(sp, &lno)) + goto err; + if (lno == 0) { + ecp->addr1.lno = 0; + F_SET(ecp, E_ADDR_ZERO); + } else + ecp->addr1.lno = sp->lno; + } else + ecp->addr1.lno = sp->lno; + ecp->addr1.cno = sp->cno; + break; + case 1: + break; + case 2: /* Lose the first address. */ + ecp->addrcnt = 1; + ecp->addr1 = ecp->addr2; + } + break; + case E_ADDR2_NONE: /* Zero/two addresses: */ + if (ecp->addrcnt == 0) /* Default to nothing. */ + break; + goto two_addr; + case E_ADDR2_ALL: /* Zero/two addresses: */ + if (ecp->addrcnt == 0) { /* Default entire/empty file. */ + F_SET(ecp, E_ADDR_DEF); + ecp->addrcnt = 2; + if (sp->ep == NULL) + ecp->addr2.lno = 0; + else if (db_last(sp, &ecp->addr2.lno)) + goto err; + if (F_ISSET(ecp, E_ADDR_ZERODEF) && + ecp->addr2.lno == 0) { + ecp->addr1.lno = 0; + F_SET(ecp, E_ADDR_ZERO); + } else + ecp->addr1.lno = 1; + ecp->addr1.cno = ecp->addr2.cno = 0; + F_SET(ecp, E_ADDR2_ALL); + break; + } + /* FALLTHROUGH */ + case E_ADDR2: /* Two addresses: */ +two_addr: switch (ecp->addrcnt) { + case 0: /* Default cursor/empty file. */ + ecp->addrcnt = 2; + F_SET(ecp, E_ADDR_DEF); + if (sp->lno == 1 && + F_ISSET(ecp, E_ADDR_ZERODEF)) { + if (db_last(sp, &lno)) + goto err; + if (lno == 0) { + ecp->addr1.lno = ecp->addr2.lno = 0; + F_SET(ecp, E_ADDR_ZERO); + } else + ecp->addr1.lno = + ecp->addr2.lno = sp->lno; + } else + ecp->addr1.lno = ecp->addr2.lno = sp->lno; + ecp->addr1.cno = ecp->addr2.cno = sp->cno; + break; + case 1: /* Default to first address. */ + ecp->addrcnt = 2; + ecp->addr2 = ecp->addr1; + break; + case 2: + break; + } + break; + default: + if (ecp->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 (ecp->cmd == &cmds[C_SCROLL]) { + ecp->addrcnt = 2; + ecp->addr1.lno = sp->lno + 1; + ecp->addr2.lno = sp->lno + O_VAL(sp, O_SCROLL); + ecp->addr1.cno = ecp->addr2.cno = sp->cno; + if (db_last(sp, &lno)) + goto err; + if (lno != 0 && lno > sp->lno && ecp->addr2.lno > lno) + ecp->addr2.lno = lno; + } + + ecp->flagoff = 0; + for (p = ecp->cmd->syntax; *p != '\0'; ++p) { + /* + * The force flag is sensitive to leading whitespace, i.e. + * "next !" is different from "next!". Handle it before + * skipping leading <blank>s. + */ + if (*p == '!') { + if (ecp->clen > 0 && *ecp->cp == '!') { + ++ecp->cp; + --ecp->clen; + FL_SET(ecp->iflags, E_C_FORCE); + } + continue; + } + + /* Skip leading <blank>s. */ + for (; ecp->clen > 0; --ecp->clen, ++ecp->cp) + if (!isblank(*ecp->cp)) + break; + if (ecp->clen == 0) + 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 (; ecp->clen; --ecp->clen, ++ecp->cp) + switch (*ecp->cp) { + case '+': + ++ecp->flagoff; + break; + case '-': + case '^': + --ecp->flagoff; + break; + case '#': + F_CLR(ecp, E_OPTNUM); + FL_SET(ecp->iflags, E_C_HASH); + exp->fdef |= E_C_HASH; + break; + case 'l': + FL_SET(ecp->iflags, E_C_LIST); + exp->fdef |= E_C_LIST; + break; + case 'p': + FL_SET(ecp->iflags, E_C_PRINT); + exp->fdef |= E_C_PRINT; + break; + default: + goto end_case1; + } +end_case1: break; + case '2': /* -, ., +, ^ */ + case '3': /* -, ., +, ^, = */ + for (; ecp->clen; --ecp->clen, ++ecp->cp) + switch (*ecp->cp) { + case '-': + FL_SET(ecp->iflags, E_C_DASH); + break; + case '.': + FL_SET(ecp->iflags, E_C_DOT); + break; + case '+': + FL_SET(ecp->iflags, E_C_PLUS); + break; + case '^': + FL_SET(ecp->iflags, E_C_CARAT); + break; + case '=': + if (*p == '3') { + FL_SET(ecp->iflags, E_C_EQUAL); + break; + } + /* FALLTHROUGH */ + default: + goto end_case23; + } +end_case23: 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 ((ecp->cp[0] == '+' || ecp->cp[0] == '-' || + ecp->cp[0] == '^' || ecp->cp[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(ecp->cp[0])) { + ecp->buffer = *ecp->cp; + ++ecp->cp; + --ecp->clen; + FL_SET(ecp->iflags, E_C_BUFFER); + } + break; + case 'c': /* count [01+a] */ + ++p; + /* Validate any signed value. */ + if (!isdigit(*ecp->cp) && (*p != '+' || + (*ecp->cp != '+' && *ecp->cp != '-'))) + break; + /* If a signed value, set appropriate flags. */ + if (*ecp->cp == '-') + FL_SET(ecp->iflags, E_C_COUNT_NEG); + else if (*ecp->cp == '+') + FL_SET(ecp->iflags, E_C_COUNT_POS); + if ((nret = + nget_slong(<mp, ecp->cp, &t, 10)) != NUM_OK) { + ex_badaddr(sp, NULL, A_NOTSET, nret); + goto err; + } + if (ltmp == 0 && *p != '0') { + msgq(sp, M_ERR, "083|Count may not be zero"); + goto err; + } + ecp->clen -= (t - ecp->cp); + ecp->cp = t; + + /* + * Counts 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') { + ecp->addr1 = ecp->addr2; + ecp->addr2.lno = ecp->addr1.lno + ltmp - 1; + } else + ecp->count = ltmp; + FL_SET(ecp->iflags, E_C_COUNT); + break; + case 'f': /* file */ + if (argv_exp2(sp, ecp, ecp->cp, ecp->clen)) + goto err; + goto arg_cnt_chk; + case 'l': /* line */ + /* + * Get a line specification. + * + * If the line was a search expression, we may have + * changed state during the call, and we're now + * searching the file. Push ourselves onto the state + * stack. + */ + if (ex_line(sp, ecp, &cur, &isaddr, &tmp)) + goto rfail; + if (tmp) + goto err; + + /* Line specifications are always required. */ + if (!isaddr) { + msgq_str(sp, M_ERR, ecp->cp, + "084|%s: bad line specification"); + goto err; + } + /* + * The target line should exist for these commands, + * but 0 is legal for them as well. + */ + if (cur.lno != 0 && !db_exist(sp, cur.lno)) { + ex_badaddr(sp, NULL, A_EOF, NUM_OK); + goto err; + } + ecp->lineno = cur.lno; + break; + case 'S': /* string, file exp. */ + if (ecp->clen != 0) { + if (argv_exp1(sp, ecp, ecp->cp, + ecp->clen, ecp->cmd == &cmds[C_BANG])) + goto err; + goto addr_verify; + } + /* FALLTHROUGH */ + case 's': /* string */ + if (argv_exp0(sp, ecp, ecp->cp, ecp->clen)) + goto err; + goto addr_verify; + 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 = ecp->cp; + ecp->clen > 0; --ecp->clen, ++ecp->cp) { + ch = *ecp->cp; + if (IS_ESCAPE(sp, + ecp, ch) && ecp->clen > 1) { + --ecp->clen; + *p++ = *++ecp->cp; + } else if (isblank(ch)) { + ++ecp->cp; + --ecp->clen; + break; + } else + *p++ = ch; + } + if (argv_exp0(sp, ecp, t, p - t)) + goto err; + + /* Delete intervening whitespace. */ + for (; ecp->clen > 0; + --ecp->clen, ++ecp->cp) { + ch = *ecp->cp; + if (!isblank(ch)) + break; + } + if (ecp->clen == 0) + goto usage; + + /* Followed by the string. */ + for (p = t = ecp->cp; ecp->clen > 0; + --ecp->clen, ++ecp->cp, ++p) { + ch = *ecp->cp; + if (IS_ESCAPE(sp, + ecp, ch) && ecp->clen > 1) { + --ecp->clen; + *p = *++ecp->cp; + } else + *p = ch; + } + if (argv_exp0(sp, ecp, t, p - t)) + goto err; + goto addr_verify; + case 'w': /* word */ + if (argv_exp3(sp, ecp, ecp->cp, ecp->clen)) + goto err; +arg_cnt_chk: if (*++p != 'N') { /* N */ + /* + * If a number is specified, must either be + * 0 or that number, if optional, and that + * number, if required. + */ + tmp = *p - '0'; + if ((*++p != 'o' || exp->argsoff != 0) && + exp->argsoff != tmp) + goto usage; + } + goto addr_verify; + default: + msgq(sp, M_ERR, + "085|Internal syntax table error (%s: %s)", + ecp->cmd->name, KEY_NAME(sp, *p)); + } + } + + /* Skip trailing whitespace. */ + for (; ecp->clen > 0; --ecp->clen) { + ch = *ecp->cp++; + 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 (ecp->clen != 0 || strpbrk(p, "lr")) { +usage: msgq(sp, M_ERR, "086|Usage: %s", ecp->cmd->usage); + goto err; + } + + /* + * Verify that the addresses are legal. Check the addresses here, + * because this is a place where all ex addresses pass through. + * (They don't all pass through ex_line(), for instance.) We're + * assuming that any non-existent line doesn't exist because it's + * past the end-of-file. That's a pretty good guess. + * + * If it's a "default vi command", an address of zero is okay. + */ +addr_verify: + switch (ecp->addrcnt) { + case 2: + /* + * 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 (ecp->addr2.lno == 0) { + if (!F_ISSET(ecp, E_ADDR_ZERO) && + (F_ISSET(sp, SC_EX) || + !F_ISSET(ecp, E_USELASTCMD))) { + ex_badaddr(sp, ecp->cmd, A_ZERO, NUM_OK); + goto err; + } + } else if (!db_exist(sp, ecp->addr2.lno)) + if (FL_ISSET(ecp->iflags, E_C_COUNT)) { + if (db_last(sp, &lno)) + goto err; + ecp->addr2.lno = lno; + } else { + ex_badaddr(sp, NULL, A_EOF, NUM_OK); + goto err; + } + /* FALLTHROUGH */ + case 1: + if (ecp->addr1.lno == 0) { + if (!F_ISSET(ecp, E_ADDR_ZERO) && + (F_ISSET(sp, SC_EX) || + !F_ISSET(ecp, E_USELASTCMD))) { + ex_badaddr(sp, ecp->cmd, A_ZERO, NUM_OK); + goto err; + } + } else if (!db_exist(sp, ecp->addr1.lno)) { + ex_badaddr(sp, NULL, A_EOF, NUM_OK); + 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. + * + * !!! + * In addition, IF THE LINE CHANGES, move to the first nonblank of + * the line. + * + * !!! + * 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 ((F_ISSET(sp, SC_VI) || F_ISSET(ecp, E_NOPRDEF)) && + F_ISSET(ecp, E_USELASTCMD) && vi_address == 0) { + switch (ecp->addrcnt) { + case 2: + if (sp->lno != + (ecp->addr2.lno ? ecp->addr2.lno : 1)) { + sp->lno = + ecp->addr2.lno ? ecp->addr2.lno : 1; + sp->cno = 0; + (void)nonblank(sp, sp->lno, &sp->cno); + } + break; + case 1: + if (sp->lno != + (ecp->addr1.lno ? ecp->addr1.lno : 1)) { + sp->lno = + ecp->addr1.lno ? ecp->addr1.lno : 1; + sp->cno = 0; + (void)nonblank(sp, sp->lno, &sp->cno); + } + break; + } + ecp->cp = ecp->save_cmd; + ecp->clen = ecp->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(ecp, E_ABSMARK)) { + cur.lno = sp->lno; + cur.cno = sp->cno; + F_CLR(ecp, E_ABSMARK); + if (mark_set(sp, ABSMARK1, &cur, 1)) + goto err; + } + +#if defined(DEBUG) && defined(COMLOG) + ex_comlog(sp, ecp); +#endif + /* Increment the command count if not called from vi. */ + if (F_ISSET(sp, SC_EX)) + ++sp->ccnt; + + /* + * If file state available, and not doing a global command, + * log the start of an action. + */ + if (sp->ep != NULL && !F_ISSET(sp, SC_EX_GLOBAL)) + (void)log_cursor(sp); + + /* + * !!! + * There are two special commands for the purposes of this code: the + * default command (<carriage-return>) or the scrolling commands (^D + * and <EOF>) as the first non-<blank> 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 move back up to the previous line, and erase + * the prompt character with the output. Since ex runs in canonical + * mode, we don't have to do anything else, a <newline> has already + * been echoed by the tty driver. It's OK if vi calls us -- we won't + * be in ex mode so we'll do nothing. + */ + if (F_ISSET(ecp, E_NRSEP)) { + if (sp->ep != NULL && + F_ISSET(sp, SC_EX) && !F_ISSET(gp, G_SCRIPTED) && + (F_ISSET(ecp, E_USELASTCMD) || ecp->cmd == &cmds[C_SCROLL])) + gp->scr_ex_adjust(sp, EX_TERM_SCROLL); + F_CLR(ecp, E_NRSEP); + } + + /* + * Call the underlying function for the ex command. + * + * XXX + * Interrupts behave like errors, for now. + */ + if (ecp->cmd->fn(sp, ecp) || INTERRUPTED(sp)) { + if (F_ISSET(gp, G_SCRIPTED)) + F_SET(sp, SC_EXIT_FORCE); + goto err; + } + +#ifdef DEBUG + /* Make sure no function left global temporary space locked. */ + if (F_ISSET(gp, G_TMP_INUSE)) { + F_CLR(gp, G_TMP_INUSE); + msgq(sp, M_ERR, "087|%s: temporary buffer not released", + ecp->cmd->name); + } +#endif + /* + * Ex displayed the number of lines modified immediately after each + * command, so the command "1,10d|1,10d" would display: + * + * 10 lines deleted + * 10 lines deleted + * <autoprint line> + * + * Executing ex commands from vi only reported the final modified + * lines message -- that's wrong enough that we don't match it. + */ + if (F_ISSET(sp, SC_EX)) + mod_rpt(sp); + + /* + * Integrate any offset parsed by the underlying command, and make + * sure the referenced line exists. + * + * XXX + * May not match historic practice (which I've never been able to + * completely figure 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 might know. + */ + if (sp->ep != NULL && ecp->flagoff) { + if (ecp->flagoff < 0) { + if (sp->lno <= -ecp->flagoff) { + msgq(sp, M_ERR, + "088|Flag offset to before line 1"); + goto err; + } + } else { + if (!NPFITS(MAX_REC_NUMBER, sp->lno, ecp->flagoff)) { + ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); + goto err; + } + if (!db_exist(sp, sp->lno + ecp->flagoff)) { + msgq(sp, M_ERR, + "089|Flag offset past end-of-file"); + goto err; + } + } + sp->lno += ecp->flagoff; + } + + /* + * If the command executed successfully, we may want to display a line + * based on the autoprint option or an explicit print flag. (Make sure + * that there's a line to display.) Also, the autoprint edit option is + * turned off for the duration of global commands. + */ + if (F_ISSET(sp, SC_EX) && sp->ep != NULL && sp->lno != 0) { + /* + * The print commands have already handled the `print' flags. + * If so, clear them. + */ + if (FL_ISSET(ecp->iflags, E_CLRFLAG)) + FL_CLR(ecp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT); + + /* If hash set only because of the number option, discard it. */ + if (F_ISSET(ecp, E_OPTNUM)) + FL_CLR(ecp->iflags, E_C_HASH); + + /* + * If there was an explicit flag to display the new cursor line, + * or autoprint is set and a change was made, display the line. + * If any print flags were set use them, else default to print. + */ + LF_INIT(FL_ISSET(ecp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT)); + if (!LF_ISSET(E_C_HASH | E_C_LIST | E_C_PRINT | E_NOAUTO) && + !F_ISSET(sp, SC_EX_GLOBAL) && + O_ISSET(sp, O_AUTOPRINT) && F_ISSET(ecp, E_AUTOPRINT)) + LF_INIT(E_C_PRINT); + + if (LF_ISSET(E_C_HASH | E_C_LIST | E_C_PRINT)) { + cur.lno = sp->lno; + cur.cno = 0; + (void)ex_print(sp, ecp, &cur, &cur, flags); + } + } + + /* + * If the command had an associated "+cmd", it has to be executed + * before we finish executing any more of this ex command. For + * example, consider a .exrc file that contains the following lines: + * + * :set all + * :edit +25 file.c|s/abc/ABC/|1 + * :3,5 print + * + * This can happen more than once -- the historic vi simply hung or + * dropped core, of course. Prepend the + command back into the + * current command and continue. We may have to add an additional + * <literal next> character. We know that it will fit because we + * discarded at least one space and the + character. + */ + if (arg1_len != 0) { + /* + * If the last character of the + command was a <literal next> + * character, it would be treated differently because of the + * append. Quote it, if necessary. + */ + if (IS_ESCAPE(sp, ecp, arg1[arg1_len - 1])) { + *--ecp->save_cmd = CH_LITERAL; + ++ecp->save_cmdlen; + } + + ecp->save_cmd -= arg1_len; + ecp->save_cmdlen += arg1_len; + memcpy(ecp->save_cmd, arg1, arg1_len); + + /* + * Any commands executed from a +cmd are executed starting at + * the first column of the last line of the file -- NOT the + * first nonblank.) The main file startup code doesn't know + * that a +cmd was set, however, so it may have put us at the + * top of the file. (Note, this is safe because we must have + * switched files to get here.) + */ + F_SET(ecp, E_MOVETOEND); + } + + /* Update the current command. */ + ecp->cp = ecp->save_cmd; + ecp->clen = ecp->save_cmdlen; + + /* + * !!! + * If we've changed screens or underlying files, any pending global or + * v command, or @ buffer that has associated addresses, has to be + * discarded. This is historic practice for globals, and necessary for + * @ buffers that had associated addresses. + * + * Otherwise, if we've changed underlying files, it's not a problem, + * we continue with the rest of the ex command(s), operating on the + * new file. However, if we switch screens (either by exiting or by + * an explicit command), we have no way of knowing where to put output + * messages, and, since we don't control screens here, we could screw + * up the upper layers, (e.g. we could exit/reenter a screen multiple + * times). So, return and continue after we've got a new screen. + */ + if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_FSWITCH | SC_SSWITCH)) { + at_found = gv_found = 0; + for (ecp = sp->gp->ecq.lh_first; + ecp != NULL; ecp = ecp->q.le_next) + switch (ecp->agv_flags) { + case 0: + case AGV_AT_NORANGE: + break; + case AGV_AT: + if (!at_found) { + at_found = 1; + msgq(sp, M_ERR, + "090|@ with range running when the file/screen changed"); + } + break; + case AGV_GLOBAL: + case AGV_V: + if (!gv_found) { + gv_found = 1; + msgq(sp, M_ERR, + "091|Global/v command running when the file/screen changed"); + } + break; + default: + abort(); + } + if (at_found || gv_found) + goto discard; + if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE | SC_SSWITCH)) + goto rsuccess; + } + + goto loop; + /* NOTREACHED */ + +err: /* + * On command failure, we discard keys and pending commands remaining, + * as well as any keys that were mapped and waiting. The save_cmdlen + * test is not necessarily correct. If we fail early enough we don't + * know if the entire string was a single command or not. Guess, as + * it's useful to know if commands other than the current one are being + * discarded. + */ + if (ecp->save_cmdlen == 0) + for (; ecp->clen; --ecp->clen) { + ch = *ecp->cp++; + if (IS_ESCAPE(sp, ecp, ch) && ecp->clen > 1) { + --ecp->clen; + ++ecp->cp; + } else if (ch == '\n' || ch == '|') { + if (ecp->clen > 1) + ecp->save_cmdlen = 1; + break; + } + } + if (ecp->save_cmdlen != 0 || gp->ecq.lh_first != &gp->excmd) { +discard: msgq(sp, M_BERR, + "092|Ex command failed: pending commands discarded"); + ex_discard(sp); + } + if (v_event_flush(sp, CH_MAPPED)) + msgq(sp, M_BERR, + "093|Ex command failed: mapped keys discarded"); + +rfail: tmp = 1; + if (0) +rsuccess: tmp = 0; + + /* Turn off any file name error information. */ + gp->if_name = NULL; + + /* Turn off the global bit. */ + F_CLR(sp, SC_EX_GLOBAL); + + return (tmp); +} + +/* + * ex_range -- + * Get a line range for ex commands, or perform a vi ex address search. + * + * PUBLIC: int ex_range __P((SCR *, EXCMD *, int *)); + */ +int +ex_range(sp, ecp, errp) + SCR *sp; + EXCMD *ecp; + int *errp; +{ + enum { ADDR_FOUND, ADDR_NEED, ADDR_NONE } addr; + GS *gp; + EX_PRIVATE *exp; + MARK m; + int isaddr; + + *errp = 0; + + /* + * Parse comma or semi-colon delimited line specs. + * + * Semi-colon delimiters update the current address to be the last + * address. For example, the command + * + * :3;/pattern/ecp->cp + * + * will search for pattern from line 3. In addition, if ecp->cp + * is not a valid command, the current line will be left at 3, not + * at the original address. + * + * Extra addresses are discarded, starting with the first. + * + * !!! + * If any addresses are missing, they default to the current line. + * This was historically true for both leading and trailing comma + * delimited addresses as well as for trailing semicolon delimited + * addresses. For consistency, we make it true for leading semicolon + * addresses as well. + */ + gp = sp->gp; + exp = EXP(sp); + for (addr = ADDR_NONE, ecp->addrcnt = 0; ecp->clen > 0;) + switch (*ecp->cp) { + case '%': /* Entire file. */ + /* Vi ex address searches didn't permit % signs. */ + if (F_ISSET(ecp, E_VISEARCH)) + goto ret; + + /* It's an error if the file is empty. */ + if (sp->ep == NULL) { + ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); + *errp = 1; + return (0); + } + /* + * !!! + * A percent character addresses all of the lines in + * the file. Historically, it couldn't be followed by + * any other address. We do it as a text substitution + * for simplicity. POSIX 1003.2 is expected to follow + * this practice. + * + * If it's an empty file, the first line is 0, not 1. + */ + if (addr == ADDR_FOUND) { + ex_badaddr(sp, NULL, A_COMBO, NUM_OK); + *errp = 1; + return (0); + } + if (db_last(sp, &ecp->addr2.lno)) + return (1); + ecp->addr1.lno = ecp->addr2.lno == 0 ? 0 : 1; + ecp->addr1.cno = ecp->addr2.cno = 0; + ecp->addrcnt = 2; + addr = ADDR_FOUND; + ++ecp->cp; + --ecp->clen; + break; + case ',': /* Comma delimiter. */ + /* Vi ex address searches didn't permit commas. */ + if (F_ISSET(ecp, E_VISEARCH)) + goto ret; + /* FALLTHROUGH */ + case ';': /* Semi-colon delimiter. */ + if (sp->ep == NULL) { + ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); + *errp = 1; + return (0); + } + if (addr != ADDR_FOUND) + switch (ecp->addrcnt) { + case 0: + ecp->addr1.lno = sp->lno; + ecp->addr1.cno = sp->cno; + ecp->addrcnt = 1; + break; + case 2: + ecp->addr1 = ecp->addr2; + /* FALLTHROUGH */ + case 1: + ecp->addr2.lno = sp->lno; + ecp->addr2.cno = sp->cno; + ecp->addrcnt = 2; + break; + } + if (*ecp->cp == ';') + switch (ecp->addrcnt) { + case 0: + abort(); + /* NOTREACHED */ + case 1: + sp->lno = ecp->addr1.lno; + sp->cno = ecp->addr1.cno; + break; + case 2: + sp->lno = ecp->addr2.lno; + sp->cno = ecp->addr2.cno; + break; + } + addr = ADDR_NEED; + /* FALLTHROUGH */ + case ' ': /* Whitespace. */ + case '\t': /* Whitespace. */ + ++ecp->cp; + --ecp->clen; + break; + default: + /* Get a line specification. */ + if (ex_line(sp, ecp, &m, &isaddr, errp)) + return (1); + if (*errp) + return (0); + if (!isaddr) + goto ret; + if (addr == ADDR_FOUND) { + ex_badaddr(sp, NULL, A_COMBO, NUM_OK); + *errp = 1; + return (0); + } + switch (ecp->addrcnt) { + case 0: + ecp->addr1 = m; + ecp->addrcnt = 1; + break; + case 1: + ecp->addr2 = m; + ecp->addrcnt = 2; + break; + case 2: + ecp->addr1 = ecp->addr2; + ecp->addr2 = m; + break; + } + addr = ADDR_FOUND; + break; + } + + /* + * !!! + * Vi ex address searches are indifferent to order or trailing + * semi-colons. + */ +ret: if (F_ISSET(ecp, E_VISEARCH)) + return (0); + + if (addr == ADDR_NEED) + switch (ecp->addrcnt) { + case 0: + ecp->addr1.lno = sp->lno; + ecp->addr1.cno = sp->cno; + ecp->addrcnt = 1; + break; + case 2: + ecp->addr1 = ecp->addr2; + /* FALLTHROUGH */ + case 1: + ecp->addr2.lno = sp->lno; + ecp->addr2.cno = sp->cno; + ecp->addrcnt = 2; + break; + } + + if (ecp->addrcnt == 2 && ecp->addr2.lno < ecp->addr1.lno) { + msgq(sp, M_ERR, + "094|The second address is smaller than the first"); + *errp = 1; + } + return (0); +} + +/* + * ex_line -- + * 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 probably still not exactly historic practice, although I think + * it's fairly close. + */ +static int +ex_line(sp, ecp, mp, isaddrp, errp) + SCR *sp; + EXCMD *ecp; + MARK *mp; + int *isaddrp, *errp; +{ + enum nresult nret; + EX_PRIVATE *exp; + GS *gp; + long total, val; + int isneg; + int (*sf) __P((SCR *, MARK *, MARK *, char *, size_t, char **, u_int)); + char *endp; + + gp = sp->gp; + exp = EXP(sp); + + *isaddrp = *errp = 0; + F_CLR(ecp, E_DELTA); + + /* No addresses permitted until a file has been read in. */ + if (sp->ep == NULL && strchr("$0123456789'\\/?.+-^", *ecp->cp)) { + ex_badaddr(sp, NULL, A_EMPTY, NUM_OK); + *errp = 1; + return (0); + } + + switch (*ecp->cp) { + case '$': /* Last line in the file. */ + *isaddrp = 1; + F_SET(ecp, E_ABSMARK); + + mp->cno = 0; + if (db_last(sp, &mp->lno)) + return (1); + ++ecp->cp; + --ecp->clen; + break; /* Absolute line number. */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + *isaddrp = 1; + F_SET(ecp, E_ABSMARK); + + if ((nret = nget_slong(&val, ecp->cp, &endp, 10)) != NUM_OK) { + ex_badaddr(sp, NULL, A_NOTSET, nret); + *errp = 1; + return (0); + } + if (!NPFITS(MAX_REC_NUMBER, 0, val)) { + ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); + *errp = 1; + return (0); + } + mp->lno = val; + mp->cno = 0; + ecp->clen -= (endp - ecp->cp); + ecp->cp = endp; + break; + case '\'': /* Use a mark. */ + *isaddrp = 1; + F_SET(ecp, E_ABSMARK); + + if (ecp->clen == 1) { + msgq(sp, M_ERR, "095|No mark name supplied"); + *errp = 1; + return (0); + } + if (mark_get(sp, ecp->cp[1], mp, M_ERR)) { + *errp = 1; + return (0); + } + ecp->cp += 2; + ecp->clen -= 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 (ecp->clen < 2 || + ecp->cp[1] != '/' && ecp->cp[1] != '?') { + msgq(sp, M_ERR, "096|\\ not followed by / or ?"); + *errp = 1; + return (0); + } + ++ecp->cp; + --ecp->clen; + sf = ecp->cp[0] == '/' ? f_search : b_search; + goto search; + case '/': /* Search forward. */ + sf = f_search; + goto search; + case '?': /* Search backward. */ + sf = b_search; + +search: mp->lno = sp->lno; + mp->cno = sp->cno; + if (sf(sp, mp, mp, ecp->cp, ecp->clen, &endp, + SEARCH_MSG | SEARCH_PARSE | SEARCH_SET | + (F_ISSET(ecp, E_SEARCH_WMSG) ? SEARCH_WMSG : 0))) { + *errp = 1; + return (0); + } + + /* Fix up the command pointers. */ + ecp->clen -= (endp - ecp->cp); + ecp->cp = endp; + + *isaddrp = 1; + F_SET(ecp, E_ABSMARK); + break; + case '.': /* Current position. */ + *isaddrp = 1; + mp->cno = sp->cno; + + /* If an empty file, then '.' is 0, not 1. */ + if (sp->lno == 1) { + if (db_last(sp, &mp->lno)) + return (1); + if (mp->lno != 0) + mp->lno = 1; + } else + mp->lno = sp->lno; + + /* + * !!! + * Historically, .<number> was the same as .+<number>, i.e. + * the '+' could be omitted. (This feature is found in ed + * as well.) + */ + if (ecp->clen > 1 && isdigit(ecp->cp[1])) + *ecp->cp = '+'; + else { + ++ecp->cp; + --ecp->clen; + } + break; + } + + /* Skip trailing <blank>s. */ + for (; ecp->clen > 0 && + isblank(ecp->cp[0]); ++ecp->cp, --ecp->clen); + + /* + * Evaluate any offset. If no address yet found, the offset + * is relative to ".". + */ + total = 0; + if (ecp->clen != 0 && (isdigit(ecp->cp[0]) || + ecp->cp[0] == '+' || ecp->cp[0] == '-' || + ecp->cp[0] == '^')) { + if (!*isaddrp) { + *isaddrp = 1; + mp->lno = sp->lno; + mp->cno = sp->cno; + } + /* + * Evaluate an offset, defined as: + * + * [+-^<blank>]*[<blank>]*[0-9]* + * + * The rough translation is any number of signs, optionally + * followed by numbers, or a number by itself, all <blank> + * separated. + * + * !!! + * All address offsets were additive, e.g. "2 2 3p" was the + * same as "7p", or, "/ZZZ/ 2" was the same as "/ZZZ/+2". + * Note, however, "2 /ZZZ/" was an error. It was also legal + * to insert signs without numbers, so "3 - 2" was legal, and + * equal to 4. + * + * !!! + * Offsets were historically permitted for any line address, + * e.g. the command "1,2 copy 2 2 2 2" copied lines 1,2 after + * line 8. + * + * !!! + * Offsets were historically permitted for search commands, + * and handled as addresses: "/pattern/2 2 2" was legal, and + * referenced the 6th line after pattern. + */ + F_SET(ecp, E_DELTA); + for (;;) { + for (; ecp->clen > 0 && isblank(ecp->cp[0]); + ++ecp->cp, --ecp->clen); + if (ecp->clen == 0 || !isdigit(ecp->cp[0]) && + ecp->cp[0] != '+' && ecp->cp[0] != '-' && + ecp->cp[0] != '^') + break; + if (!isdigit(ecp->cp[0]) && + !isdigit(ecp->cp[1])) { + total += ecp->cp[0] == '+' ? 1 : -1; + --ecp->clen; + ++ecp->cp; + } else { + if (ecp->cp[0] == '-' || + ecp->cp[0] == '^') { + ++ecp->cp; + --ecp->clen; + isneg = 1; + } else + isneg = 0; + + /* Get a signed long, add it to the total. */ + if ((nret = nget_slong(&val, + ecp->cp, &endp, 10)) != NUM_OK || + (nret = NADD_SLONG(sp, + total, val)) != NUM_OK) { + ex_badaddr(sp, NULL, A_NOTSET, nret); + *errp = 1; + return (0); + } + total += isneg ? -val : val; + ecp->clen -= (endp - ecp->cp); + ecp->cp = endp; + } + } + } + + /* + * Any value less than 0 is an error. Make sure that the new value + * will fit into a recno_t. + */ + if (*isaddrp && total != 0) { + if (total < 0) { + if (-total > mp->lno) { + msgq(sp, M_ERR, + "097|Reference to a line number less than 0"); + *errp = 1; + return (0); + } + } else + if (!NPFITS(MAX_REC_NUMBER, mp->lno, total)) { + ex_badaddr(sp, NULL, A_NOTSET, NUM_OVER); + *errp = 1; + return (0); + } + mp->lno += total; + } + return (0); +} + + +/* + * ex_load -- + * Load up the next command, which may be an @ buffer or global command. + */ +static int +ex_load(sp) + SCR *sp; +{ + GS *gp; + EXCMD *ecp; + RANGE *rp; + + F_CLR(sp, SC_EX_GLOBAL); + + /* + * Lose any exhausted commands. We know that the first command + * can't be an AGV command, which makes things a bit easier. + */ + for (gp = sp->gp;;) { + /* + * If we're back to the original structure, leave it around, + * but discard any allocated source name, we've returned to + * the beginning of the command stack. + */ + if ((ecp = gp->ecq.lh_first) == &gp->excmd) { + if (F_ISSET(ecp, E_NAMEDISCARD)) { + free(ecp->if_name); + ecp->if_name = NULL; + } + return (0); + } + + /* + * ecp->clen will be 0 for the first discarded command, but + * may not be 0 for subsequent ones, e.g. if the original + * command was ":g/xx/@a|s/b/c/", then when we discard the + * command pushed on the stack by the @a, we have to resume + * the global command which included the substitute command. + */ + if (ecp->clen != 0) + return (0); + + /* + * If it's an @, global or v command, we may need to continue + * the command on a different line. + */ + if (FL_ISSET(ecp->agv_flags, AGV_ALL)) { + /* Discard any exhausted ranges. */ + while ((rp = ecp->rq.cqh_first) != (void *)&ecp->rq) + if (rp->start > rp->stop) { + CIRCLEQ_REMOVE(&ecp->rq, rp, q); + free(rp); + } else + break; + + /* If there's another range, continue with it. */ + if (rp != (void *)&ecp->rq) + break; + + /* If it's a global/v command, fix up the last line. */ + if (FL_ISSET(ecp->agv_flags, + AGV_GLOBAL | AGV_V) && ecp->range_lno != OOBLNO) + if (db_exist(sp, ecp->range_lno)) + sp->lno = ecp->range_lno; + else { + if (db_last(sp, &sp->lno)) + return (1); + if (sp->lno == 0) + sp->lno = 1; + } + free(ecp->o_cp); + } + + /* Discard the EXCMD. */ + LIST_REMOVE(ecp, q); + free(ecp); + } + + /* + * We only get here if it's an active @, global or v command. Set + * the current line number, and get a new copy of the command for + * the parser. Note, the original pointer almost certainly moved, + * so we have play games. + */ + ecp->cp = ecp->o_cp; + memcpy(ecp->cp, ecp->cp + ecp->o_clen, ecp->o_clen); + ecp->clen = ecp->o_clen; + ecp->range_lno = sp->lno = rp->start++; + + if (FL_ISSET(ecp->agv_flags, AGV_GLOBAL | AGV_V)) + F_SET(sp, SC_EX_GLOBAL); + return (0); +} + +/* + * ex_discard -- + * Discard any pending ex commands. + */ +static int +ex_discard(sp) + SCR *sp; +{ + GS *gp; + EXCMD *ecp; + RANGE *rp; + + /* + * We know the first command can't be an AGV command, so we don't + * process it specially. We do, however, nail the command itself. + */ + for (gp = sp->gp; (ecp = gp->ecq.lh_first) != &gp->excmd;) { + if (FL_ISSET(ecp->agv_flags, AGV_ALL)) { + while ((rp = ecp->rq.cqh_first) != (void *)&ecp->rq) { + CIRCLEQ_REMOVE(&ecp->rq, rp, q); + free(rp); + } + free(ecp->o_cp); + } + LIST_REMOVE(ecp, q); + free(ecp); + } + gp->ecq.lh_first->clen = 0; + return (0); +} + +/* + * ex_unknown -- + * Display an unknown command name. + */ +static void +ex_unknown(sp, cmd, len) + SCR *sp; + char *cmd; + size_t len; +{ + size_t blen; + char *bp; + + GET_SPACE_GOTO(sp, bp, blen, len + 1); + bp[len] = '\0'; + memcpy(bp, cmd, len); + msgq_str(sp, M_ERR, bp, "098|The %s command is unknown"); + FREE_SPACE(sp, bp, blen); + +alloc_err: + return; +} + +/* + * 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. See + * the usual ranting in the vi/v_txt_ev.c:txt_abbrev() routine. + * + * PUBLIC: int ex_is_abbrev __P((char *, size_t)); + */ +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. See the usual + * ranting in the vi/v_txt_ev.c:txt_unmap() routine. + * + * PUBLIC: int ex_is_unmap __P((char *, size_t)); + */ +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]); +} + +/* + * ex_comm_search -- + * Search for a command name. + */ +static 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); +} + +/* + * ex_badaddr -- + * Display a bad address message. + * + * PUBLIC: void ex_badaddr + * PUBLIC: __P((SCR *, EXCMDLIST const *, enum badaddr, enum nresult)); + */ +void +ex_badaddr(sp, cp, ba, nret) + SCR *sp; + EXCMDLIST const *cp; + enum badaddr ba; + enum nresult nret; +{ + recno_t lno; + + switch (nret) { + case NUM_OK: + break; + case NUM_ERR: + msgq(sp, M_SYSERR, NULL); + return; + case NUM_OVER: + msgq(sp, M_ERR, "099|Address value overflow"); + return; + case NUM_UNDER: + msgq(sp, M_ERR, "100|Address value underflow"); + return; + } + + /* + * When encountering an address error, tell the user if there's no + * underlying file, that's the real problem. + */ + if (sp->ep == NULL) { + ex_emsg(sp, cp->name, EXM_NOFILEYET); + return; + } + + switch (ba) { + case A_COMBO: + msgq(sp, M_ERR, "101|Illegal address combination"); + break; + case A_EOF: + if (db_last(sp, &lno)) + return; + if (lno != 0) { + msgq(sp, M_ERR, + "102|Illegal address: only %lu lines in the file", + lno); + break; + } + /* FALLTHROUGH */ + case A_EMPTY: + msgq(sp, M_ERR, "103|Illegal address: the file is empty"); + break; + case A_NOTSET: + abort(); + /* NOTREACHED */ + case A_ZERO: + msgq(sp, M_ERR, + "104|The %s command doesn't permit an address of 0", + cp->name); + break; + } + return; +} + +#if defined(DEBUG) && defined(COMLOG) +/* + * ex_comlog -- + * Log ex commands. + */ +static void +ex_comlog(sp, ecp) + SCR *sp; + EXCMD *ecp; +{ + TRACE(sp, "ecmd: %s", ecp->cmd->name); + if (ecp->addrcnt > 0) { + TRACE(sp, " a1 %d", ecp->addr1.lno); + if (ecp->addrcnt > 1) + TRACE(sp, " a2: %d", ecp->addr2.lno); + } + if (ecp->lineno) + TRACE(sp, " line %d", ecp->lineno); + if (ecp->flags) + TRACE(sp, " flags 0x%x", ecp->flags); + if (F_ISSET(&exc, E_BUFFER)) + TRACE(sp, " buffer %c", ecp->buffer); + if (ecp->argc) + for (cnt = 0; cnt < ecp->argc; ++cnt) + TRACE(sp, " arg %d: {%s}", cnt, ecp->argv[cnt]->bp); + TRACE(sp, "\n"); +} +#endif diff --git a/ex/ex.h b/ex/ex.h new file mode 100644 index 000000000000..5870990b744e --- /dev/null +++ b/ex/ex.h @@ -0,0 +1,228 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + * + * @(#)ex.h 10.24 (Berkeley) 8/12/96 + */ + +#define PROMPTCHAR ':' /* Prompt using a colon. */ + +typedef struct _excmdlist { /* Ex command table structure. */ + char *name; /* Command name, underlying function. */ + int (*fn) __P((SCR *, EXCMD *)); + +#define E_ADDR1 0x00000001 /* One address. */ +#define E_ADDR2 0x00000002 /* Two addresses. */ +#define E_ADDR2_ALL 0x00000004 /* Zero/two addresses; zero == all. */ +#define E_ADDR2_NONE 0x00000008 /* Zero/two addresses; zero == none. */ +#define E_ADDR_ZERO 0x00000010 /* 0 is a legal addr1. */ +#define E_ADDR_ZERODEF 0x00000020 /* 0 is default addr1 of empty files. */ +#define E_AUTOPRINT 0x00000040 /* Command always sets autoprint. */ +#define E_CLRFLAG 0x00000080 /* Clear the print (#, l, p) flags. */ +#define E_NEWSCREEN 0x00000100 /* Create a new screen. */ +#define E_SECURE 0x00000200 /* Permission denied if O_SECURE set. */ +#define E_VIONLY 0x00000400 /* Meaningful only in vi. */ +#define __INUSE1 0xfffff800 /* Same name space as EX_PRIVATE. */ + u_int16_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[]; /* Table of ex commands. */ + +/* + * !!! + * QUOTING NOTE: + * + * 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, cmdp, ch) \ + (F_ISSET(cmdp, E_VLITONLY) ? \ + (ch) == CH_LITERAL : KEY_VAL(sp, ch) == K_VLNEXT) + +/* + * File state must be checked for each command -- any ex command may be entered + * at any time, and most of them won't work well if a file hasn't yet been read + * in. Historic vi generally took the easy way out and dropped core. + */ +#define NEEDFILE(sp, cmdp) { \ + if ((sp)->ep == NULL) { \ + ex_emsg(sp, (cmdp)->cmd->name, EXM_NOFILEYET); \ + return (1); \ + } \ +} + +/* Range structures for global and @ commands. */ +typedef struct _range RANGE; +struct _range { /* Global command range. */ + CIRCLEQ_ENTRY(_range) q; /* Linked list of ranges. */ + recno_t start, stop; /* Start/stop of the range. */ +}; + +/* Ex command structure. */ +struct _excmd { + LIST_ENTRY(_excmd) q; /* Linked list of commands. */ + + char *if_name; /* Associated file. */ + recno_t if_lno; /* Associated line number. */ + + /* Clear the structure for the ex parser. */ +#define CLEAR_EX_PARSER(cmdp) \ + memset(&((cmdp)->cp), 0, ((char *)&(cmdp)->flags - \ + (char *)&((cmdp)->cp)) + sizeof((cmdp)->flags)) + + char *cp; /* Current command text. */ + size_t clen; /* Current command length. */ + + char *save_cmd; /* Remaining command. */ + size_t save_cmdlen; /* Remaining command length. */ + + EXCMDLIST const *cmd; /* Command: entry in command table. */ + EXCMDLIST rcmd; /* Command: table entry/replacement. */ + + CIRCLEQ_HEAD(_rh, _range) rq; /* @/global range: linked list. */ + recno_t range_lno; /* @/global range: set line number. */ + char *o_cp; /* Original @/global command. */ + size_t o_clen; /* Original @/global command length. */ +#define AGV_AT 0x01 /* @ buffer execution. */ +#define AGV_AT_NORANGE 0x02 /* @ buffer execution without range. */ +#define AGV_GLOBAL 0x04 /* global command. */ +#define AGV_V 0x08 /* v command. */ +#define AGV_ALL (AGV_AT | AGV_AT_NORANGE | AGV_GLOBAL | AGV_V) + u_int8_t agv_flags; + + /* Clear the structure before each ex command. */ +#define CLEAR_EX_CMD(cmdp) { \ + u_int32_t L__f = F_ISSET(cmdp, E_PRESERVE); \ + memset(&((cmdp)->buffer), 0, ((char *)&(cmdp)->flags - \ + (char *)&((cmdp)->buffer)) + sizeof((cmdp)->flags)); \ + F_SET(cmdp, L__f); \ +} + + CHAR_T buffer; /* Command: named buffer. */ + recno_t lineno; /* Command: line number. */ + long count; /* Command: signed count. */ + long flagoff; /* Command: signed flag offset. */ + int addrcnt; /* Command: addresses (0, 1 or 2). */ + MARK addr1; /* Command: 1st address. */ + MARK addr2; /* Command: 2nd address. */ + ARGS **argv; /* Command: array of arguments. */ + int argc; /* Command: count of arguments. */ + +#define E_C_BUFFER 0x00001 /* Buffer name specified. */ +#define E_C_CARAT 0x00002 /* ^ flag. */ +#define E_C_COUNT 0x00004 /* Count specified. */ +#define E_C_COUNT_NEG 0x00008 /* Count was signed negative. */ +#define E_C_COUNT_POS 0x00010 /* Count was signed positive. */ +#define E_C_DASH 0x00020 /* - flag. */ +#define E_C_DOT 0x00040 /* . flag. */ +#define E_C_EQUAL 0x00080 /* = flag. */ +#define E_C_FORCE 0x00100 /* ! flag. */ +#define E_C_HASH 0x00200 /* # flag. */ +#define E_C_LIST 0x00400 /* l flag. */ +#define E_C_PLUS 0x00800 /* + flag. */ +#define E_C_PRINT 0x01000 /* p flag. */ + u_int16_t iflags; /* User input information. */ + +#define __INUSE2 0x000004ff /* Same name space as EXCMDLIST. */ +#define E_BLIGNORE 0x00000800 /* Ignore blank lines. */ +#define E_NAMEDISCARD 0x00001000 /* Free/discard the name. */ +#define E_NOAUTO 0x00002000 /* Don't do autoprint output. */ +#define E_NOPRDEF 0x00004000 /* Don't print as default. */ +#define E_NRSEP 0x00008000 /* Need to line adjust ex output. */ +#define E_OPTNUM 0x00010000 /* Number edit option affected. */ +#define E_VLITONLY 0x00020000 /* Use ^V quoting only. */ +#define E_PRESERVE 0x0003f800 /* Bits to preserve across commands. */ + +#define E_ABSMARK 0x00040000 /* Set the absolute mark. */ +#define E_ADDR_DEF 0x00080000 /* Default addresses used. */ +#define E_DELTA 0x00100000 /* Search address with delta. */ +#define E_MODIFY 0x00200000 /* File name expansion modified arg. */ +#define E_MOVETOEND 0x00400000 /* Move to the end of the file first. */ +#define E_NEWLINE 0x00800000 /* Found ending <newline>. */ +#define E_SEARCH_WMSG 0x01000000 /* Display search-wrapped message. */ +#define E_USELASTCMD 0x02000000 /* Use the last command. */ +#define E_VISEARCH 0x04000000 /* It's really a vi search command. */ + u_int32_t flags; /* Current flags. */ +}; + +/* Ex private, per-screen memory. */ +typedef struct _ex_private { + CIRCLEQ_HEAD(_tqh, _tagq) tq; /* Tag queue. */ + TAILQ_HEAD(_tagfh, _tagf) tagfq;/* Tag file list. */ + LIST_HEAD(_csch, _csc) cscq; /* Cscope connection list. */ + char *tag_last; /* Saved last tag string. */ + + CHAR_T *lastbcomm; /* Last bang command. */ + + ARGS **args; /* Command: argument list. */ + int argscnt; /* Command: argument list count. */ + int argsoff; /* Command: offset into arguments. */ + + u_int32_t fdef; /* Saved E_C_* default command flags. */ + + char *ibp; /* File line input buffer. */ + size_t ibp_len; /* File line input buffer length. */ + + /* + * Buffers for the ex output. The screen/vi support doesn't do any + * character buffering of any kind. We do it here so that we're not + * calling the screen output routines on every character. + * + * XXX + * Change to grow dynamically. + */ + char obp[1024]; /* Ex output buffer. */ + size_t obp_len; /* Ex output buffer length. */ + +#define EXP_CSCINIT 0x01 /* Cscope initialized. */ + u_int8_t flags; +} EX_PRIVATE; +#define EXP(sp) ((EX_PRIVATE *)((sp)->ex_private)) + +/* + * Filter actions: + * + * FILTER_BANG !: filter text through the utility. + * FILTER_RBANG !: read from the utility (without stdin). + * FILTER_READ read: read from the utility (with stdin). + * FILTER_WRITE write: write to the utility, display its output. + */ +enum filtertype { FILTER_BANG, FILTER_RBANG, FILTER_READ, FILTER_WRITE }; + +/* Ex common error messages. */ +typedef enum { + EXM_EMPTYBUF, /* Empty buffer. */ + EXM_FILECOUNT, /* Too many file names. */ + EXM_NOCANON, /* No terminal interface. */ + EXM_NOCANON_F, /* EXM_NOCANO: filter version. */ + EXM_NOFILEYET, /* Illegal until a file read in. */ + EXM_NOPREVBUF, /* No previous buffer specified. */ + EXM_NOPREVRE, /* No previous RE specified. */ + EXM_NOSUSPEND, /* No suspension. */ + EXM_SECURE, /* Illegal if secure edit option set. */ + EXM_SECURE_F, /* EXM_SECURE: filter version */ + EXM_USAGE /* Standard usage message. */ +} exm_t; + +/* Ex address error types. */ +enum badaddr { A_COMBO, A_EMPTY, A_EOF, A_NOTSET, A_ZERO }; + +/* Ex common tag error messages. */ +typedef enum { + TAG_BADLNO, /* Tag line doesn't exist. */ + TAG_EMPTY, /* Tags stack is empty. */ + TAG_SEARCH /* Tags search pattern wasn't found. */ +} tagmsg_t; + +#include "ex_def.h" +#include "ex_extern.h" diff --git a/ex/ex_abbrev.c b/ex/ex_abbrev.c new file mode 100644 index 000000000000..231098ce7d25 --- /dev/null +++ b/ex/ex_abbrev.c @@ -0,0 +1,117 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_abbrev.c 10.7 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "../vi/vi.h" + +/* + * ex_abbr -- :abbreviate [key replacement] + * Create an abbreviation or display abbreviations. + * + * PUBLIC: int ex_abbr __P((SCR *, EXCMD *)); + */ +int +ex_abbr(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + CHAR_T *p; + size_t len; + + switch (cmdp->argc) { + case 0: + if (seq_dump(sp, SEQ_ABBREV, 0) == 0) + msgq(sp, M_INFO, "105|No abbreviations to display"); + return (0); + case 2: + break; + default: + abort(); + } + + /* + * Check for illegal characters. + * + * !!! + * Another fun one, historically. See vi/v_ntext.c:txt_abbrev() for + * details. The bottom line is that all abbreviations have to end + * with a "word" character, because it's the transition from word to + * non-word characters that triggers the test for an abbreviation. In + * addition, because of the way the test is done, there can't be any + * transitions from word to non-word character (or vice-versa) other + * than between the next-to-last and last characters of the string, + * and there can't be any <blank> characters. Warn the user. + */ + if (!inword(cmdp->argv[0]->bp[cmdp->argv[0]->len - 1])) { + msgq(sp, M_ERR, + "106|Abbreviations must end with a \"word\" character"); + return (1); + } + for (p = cmdp->argv[0]->bp; *p != '\0'; ++p) + if (isblank(p[0])) { + msgq(sp, M_ERR, + "107|Abbreviations may not contain tabs or spaces"); + return (1); + } + if (cmdp->argv[0]->len > 2) + for (p = cmdp->argv[0]->bp, + len = cmdp->argv[0]->len - 2; len; --len, ++p) + if (inword(p[0]) != inword(p[1])) { + msgq(sp, M_ERR, +"108|Abbreviations may not mix word/non-word characters, except at the end"); + 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. + * + * PUBLIC: int ex_unabbr __P((SCR *, EXCMD *)); + */ +int +ex_unabbr(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + ARGS *ap; + + ap = cmdp->argv[0]; + if (!F_ISSET(sp->gp, G_ABBREV) || + seq_delete(sp, ap->bp, ap->len, SEQ_ABBREV)) { + msgq_str(sp, M_ERR, ap->bp, + "109|\"%s\" is not an abbreviation"); + return (1); + } + return (0); +} diff --git a/ex/ex_append.c b/ex/ex_append.c new file mode 100644 index 000000000000..8d89e125f51f --- /dev/null +++ b/ex/ex_append.c @@ -0,0 +1,277 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_append.c 10.30 (Berkeley) 10/23/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" + +enum which {APPEND, CHANGE, INSERT}; + +static int ex_aci __P((SCR *, EXCMD *, 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. + * + * PUBLIC: int ex_append __P((SCR *, EXCMD *)); + */ +int +ex_append(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + return (ex_aci(sp, cmdp, APPEND)); +} + +/* + * ex_change -- :[line[,line]] c[hange][!] [count] + * Change one or more lines to the input text. + * + * PUBLIC: int ex_change __P((SCR *, EXCMD *)); + */ +int +ex_change(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + return (ex_aci(sp, 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. + * + * PUBLIC: int ex_insert __P((SCR *, EXCMD *)); + */ +int +ex_insert(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + return (ex_aci(sp, cmdp, INSERT)); +} + +/* + * ex_aci -- + * Append, change, insert in ex. + */ +static int +ex_aci(sp, cmdp, cmd) + SCR *sp; + EXCMD *cmdp; + enum which cmd; +{ + CHAR_T *p, *t; + GS *gp; + TEXT *tp; + TEXTH tiq; + recno_t cnt, lno; + size_t len; + u_int32_t flags; + int need_newline; + + gp = sp->gp; + NEEDFILE(sp, cmdp); + + /* + * 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 appends, inserts are the same as appends to the previous + * line. + * + * !!! + * Set the address to which we'll append. We set sp->lno to this + * address as well so that autoindent works correctly when get text + * from the user. + */ + lno = cmdp->addr1.lno; + sp->lno = lno; + if ((cmd == CHANGE || cmd == INSERT) && lno != 0) + --lno; + + /* + * !!! + * If the file isn't empty, cut changes into the unnamed buffer. + */ + if (cmd == CHANGE && cmdp->addr1.lno != 0 && + (cut(sp, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE) || + del(sp, &cmdp->addr1, &cmdp->addr2, 1))) + return (1); + + /* + * !!! + * Anything that was left after the command separator becomes part + * of the inserted text. Apparently, it was common usage to enter: + * + * :g/pattern/append|stuff1 + * + * and append the line of text "stuff1" to the lines containing the + * pattern. It was also historically legal to enter: + * + * :append|stuff1 + * stuff2 + * . + * + * and the text on the ex command line would be appended as well as + * the text inserted after it. There was an historic bug however, + * that the user had to enter *two* terminating lines (the '.' lines) + * to terminate text input mode, in this case. This whole thing + * could be taken too far, however. Entering: + * + * :append|stuff1\ + * stuff2 + * stuff3 + * . + * + * i.e. mixing and matching the forms confused the historic vi, and, + * not only did it take two terminating lines to terminate text input + * mode, but the trailing backslashes were retained on the input. We + * match historic practice except that we discard the backslashes. + * + * Input lines specified on the ex command line lines are separated by + * <newline>s. If there is a trailing delimiter an empty line was + * inserted. There may also be a leading delimiter, which is ignored + * unless it's also a trailing delimiter. It is possible to encounter + * a termination line, i.e. a single '.', in a global command, but not + * necessary if the text insert command was the last of the global + * commands. + */ + if (cmdp->save_cmdlen != 0) { + for (p = cmdp->save_cmd, + len = cmdp->save_cmdlen; len > 0; p = t) { + for (t = p; len > 0 && t[0] != '\n'; ++t, --len); + if (t != p || len == 0) { + if (F_ISSET(sp, SC_EX_GLOBAL) && + t - p == 1 && p[0] == '.') { + ++t; + if (len > 0) + --len; + break; + } + if (db_append(sp, 1, lno++, p, t - p)) + return (1); + } + if (len != 0) { + ++t; + if (--len == 0 && + db_append(sp, 1, lno++, "", 0)) + return (1); + } + } + /* + * If there's any remaining text, we're in a global, and + * there's more command to parse. + * + * !!! + * We depend on the fact that non-global commands will eat the + * rest of the command line as text input, and before getting + * any text input from the user. Otherwise, we'd have to save + * off the command text before or during the call to the text + * input function below. + */ + if (len != 0) + cmdp->save_cmd = t; + cmdp->save_cmdlen = len; + } + + if (F_ISSET(sp, SC_EX_GLOBAL)) { + if ((sp->lno = lno) == 0 && db_exist(sp, 1)) + sp->lno = 1; + return (0); + } + + /* + * If not in a global command, read from the terminal. + * + * If this code is called by vi, we want to reset the terminal and use + * ex's line get routine. It actually works fine if we use vi'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. + * However, depending on the screen that we're using, that may not + * be possible. + */ + if (F_ISSET(sp, SC_VI)) { + if (gp->scr_screen(sp, SC_EX)) { + ex_emsg(sp, cmdp->cmd->name, EXM_NOCANON); + return (1); + } + + /* If we're still in the vi screen, move out explicitly. */ + need_newline = !F_ISSET(sp, SC_SCR_EXWROTE); + F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE); + if (need_newline) + (void)ex_puts(sp, "\n"); + + /* + * !!! + * Users of historical versions of vi sometimes get confused + * when they enter append mode, and can't seem to get out of + * it. Give them an informational message. + */ + (void)ex_puts(sp, + msg_cat(sp, "273|Entering ex input mode.", NULL)); + (void)ex_puts(sp, "\n"); + (void)ex_fflush(sp); + } + + /* + * Set input flags; the ! flag turns off autoindent for append, + * change and insert. + */ + LF_INIT(TXT_DOTTERM | TXT_NUMBER); + if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && O_ISSET(sp, O_AUTOINDENT)) + LF_SET(TXT_AUTOINDENT); + if (O_ISSET(sp, O_BEAUTIFY)) + LF_SET(TXT_BEAUTIFY); + + /* + * This code can't use the common screen TEXTH structure (sp->tiq), + * as it may already be in use, e.g. ":append|s/abc/ABC/" would fail + * as we are only halfway through the text when the append code fires. + * Use a local structure instead. (The ex code would have to use a + * local structure except that we're guaranteed to finish remaining + * characters in the common TEXTH structure when they were inserted + * into the file, above.) + */ + memset(&tiq, 0, sizeof(TEXTH)); + CIRCLEQ_INIT(&tiq); + + if (ex_txt(sp, &tiq, 0, flags)) + return (1); + + for (cnt = 0, tp = tiq.cqh_first; + tp != (TEXT *)&tiq; ++cnt, tp = tp->q.cqe_next) + if (db_append(sp, 1, lno++, tp->lb, tp->len)) + return (1); + + /* + * Set sp->lno to the final line number value (correcting for a + * possible 0 value) as that's historically correct for the final + * line value, whether or not the user entered any text. + */ + if ((sp->lno = lno) == 0 && db_exist(sp, 1)) + sp->lno = 1; + + return (0); +} diff --git a/ex/ex_args.c b/ex/ex_args.c new file mode 100644 index 000000000000..bc37109fc130 --- /dev/null +++ b/ex/ex_args.c @@ -0,0 +1,327 @@ +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1991, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_args.c 10.16 (Berkeley) 7/13/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "../vi/vi.h" + +static int ex_N_next __P((SCR *, EXCMD *)); + +/* + * 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. + * + * PUBLIC: int ex_next __P((SCR *, EXCMD *)); + */ +int +ex_next(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + ARGS **argv; + FREF *frp; + int noargs; + char **ap; + + /* Check for file to move to. */ + if (cmdp->argc == 0 && (sp->cargv == NULL || sp->cargv[1] == NULL)) { + msgq(sp, M_ERR, "111|No more files to edit"); + return (1); + } + + if (F_ISSET(cmdp, E_NEWSCREEN)) { + /* By default, edit the next file in the old argument list. */ + if (cmdp->argc == 0) { + if (argv_exp0(sp, + cmdp, sp->cargv[1], strlen(sp->cargv[1]))) + return (1); + return (ex_edit(sp, cmdp)); + } + return (ex_N_next(sp, cmdp)); + } + + /* Check modification. */ + if (file_m1(sp, + FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE)) + return (1); + + /* Any arguments are a replacement file list. */ + if (cmdp->argc) { + /* Free the current list. */ + if (!F_ISSET(sp, SC_ARGNOFREE) && sp->argv != NULL) { + for (ap = sp->argv; *ap != NULL; ++ap) + free(*ap); + free(sp->argv); + } + F_CLR(sp, SC_ARGNOFREE | SC_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 file. */ + sp->cargv = sp->argv; + if ((frp = file_add(sp, *sp->cargv)) == NULL) + return (1); + noargs = 0; + + /* Display a file count with the welcome message. */ + F_SET(sp, SC_STATUS_CNT); + } else { + if ((frp = file_add(sp, sp->cargv[1])) == NULL) + return (1); + if (F_ISSET(sp, SC_ARGRECOVER)) + F_SET(frp, FR_RECOVER); + noargs = 1; + } + + if (file_init(sp, frp, NULL, FS_SETALT | + (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) + return (1); + if (noargs) + ++sp->cargv; + + F_SET(sp, SC_FSWITCH); + return (0); +} + +/* + * ex_N_next -- + * New screen version of ex_next. + */ +static int +ex_N_next(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + SCR *new; + FREF *frp; + + /* Get a new screen. */ + if (screen_init(sp->gp, sp, &new)) + return (1); + if (vs_split(sp, new, 0)) { + (void)screen_end(new); + return (1); + } + + /* Get a backing file. */ + if ((frp = file_add(new, cmdp->argv[0]->bp)) == NULL || + file_init(new, frp, NULL, + (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) { + (void)vs_discard(new, NULL); + (void)screen_end(new); + return (1); + } + + /* The arguments are a replacement file list. */ + new->cargv = new->argv = ex_buildargv(sp, cmdp, NULL); + + /* Display a file count with the welcome message. */ + F_SET(new, SC_STATUS_CNT); + + /* Set up the switch. */ + sp->nextdisp = new; + F_SET(sp, SC_SSWITCH); + + return (0); +} + +/* + * ex_prev -- :prev + * Edit the previous file. + * + * PUBLIC: int ex_prev __P((SCR *, EXCMD *)); + */ +int +ex_prev(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + FREF *frp; + + if (sp->cargv == sp->argv) { + msgq(sp, M_ERR, "112|No previous files to edit"); + return (1); + } + + if (F_ISSET(cmdp, E_NEWSCREEN)) { + if (argv_exp0(sp, cmdp, sp->cargv[-1], strlen(sp->cargv[-1]))) + return (1); + return (ex_edit(sp, cmdp)); + } + + if (file_m1(sp, + FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE)) + return (1); + + if ((frp = file_add(sp, sp->cargv[-1])) == NULL) + return (1); + + if (file_init(sp, frp, NULL, FS_SETALT | + (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) + return (1); + --sp->cargv; + + F_SET(sp, SC_FSWITCH); + return (0); +} + +/* + * ex_rew -- :rew + * Re-edit the list of files. + * + * !!! + * Historic practice was that all files would start editing at the beginning + * of the file. We don't get this right because we may have multiple screens + * and we can't clear the FR_CURSORSET bit for a single screen. I don't see + * anyone noticing, but if they do, we'll have to put information into the SCR + * structure so we can keep track of it. + * + * PUBLIC: int ex_rew __P((SCR *, EXCMD *)); + */ +int +ex_rew(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + FREF *frp; + + /* + * !!! + * Historic practice -- you can rewind to the current file. + */ + if (sp->argv == NULL) { + msgq(sp, M_ERR, "113|No previous files to rewind"); + return (1); + } + + if (file_m1(sp, + FL_ISSET(cmdp->iflags, E_C_FORCE), FS_ALL | FS_POSSIBLE)) + return (1); + + /* 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, FS_SETALT | + (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) + return (1); + + /* Switch and display a file count with the welcome message. */ + F_SET(sp, SC_FSWITCH | SC_STATUS_CNT); + + return (0); +} + +/* + * ex_args -- :args + * Display the list of files. + * + * PUBLIC: int ex_args __P((SCR *, EXCMD *)); + */ +int +ex_args(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + GS *gp; + int cnt, col, len, sep; + char **ap; + + if (sp->argv == NULL) { + (void)msgq(sp, M_ERR, "114|No file list to display"); + return (0); + } + + gp = sp->gp; + 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_puts(sp, "\n"); + } else if (cnt != 1) { + sep = 1; + (void)ex_puts(sp, " "); + } + ++cnt; + + (void)ex_printf(sp, "%s%s%s", ap == sp->cargv ? "[" : "", + *ap, ap == sp->cargv ? "]" : ""); + if (INTERRUPTED(sp)) + break; + } + (void)ex_puts(sp, "\n"); + return (0); +} + +/* + * ex_buildargv -- + * Build a new file argument list. + * + * PUBLIC: char **ex_buildargv __P((SCR *, EXCMD *, char *)); + */ +char ** +ex_buildargv(sp, cmdp, name) + SCR *sp; + EXCMD *cmdp; + char *name; +{ + ARGS **argv; + int argc; + char **ap, **s_argv; + + argc = cmdp == NULL ? 1 : cmdp->argc; + CALLOC(sp, s_argv, char **, argc + 1, sizeof(char *)); + if ((ap = s_argv) == NULL) + return (NULL); + + if (cmdp == NULL) { + if ((*ap = v_strdup(sp, name, strlen(name))) == NULL) + return (NULL); + ++ap; + } else + for (argv = cmdp->argv; argv[0]->len != 0; ++ap, ++argv) + if ((*ap = + v_strdup(sp, argv[0]->bp, argv[0]->len)) == NULL) + return (NULL); + *ap = NULL; + return (s_argv); +} diff --git a/ex/ex_argv.c b/ex/ex_argv.c new file mode 100644 index 000000000000..cc5a201bea26 --- /dev/null +++ b/ex/ex_argv.c @@ -0,0 +1,756 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_argv.c 10.26 (Berkeley) 9/20/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" + +static int argv_alloc __P((SCR *, size_t)); +static int argv_comp __P((const void *, const void *)); +static int argv_fexp __P((SCR *, EXCMD *, + char *, size_t, char *, size_t *, char **, size_t *, int)); +static int argv_lexp __P((SCR *, EXCMD *, char *)); +static int argv_sexp __P((SCR *, char **, size_t *, size_t *)); + +/* + * argv_init -- + * Build a prototype arguments list. + * + * PUBLIC: int argv_init __P((SCR *, EXCMD *)); + */ +int +argv_init(sp, excp) + SCR *sp; + EXCMD *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. + * + * PUBLIC: int argv_exp0 __P((SCR *, EXCMD *, char *, size_t)); + */ +int +argv_exp0(sp, excp, cmd, cmdlen) + SCR *sp; + EXCMD *excp; + char *cmd; + size_t cmdlen; +{ + EX_PRIVATE *exp; + + exp = EXP(sp); + argv_alloc(sp, cmdlen); + memcpy(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. + * + * PUBLIC: int argv_exp1 __P((SCR *, EXCMD *, char *, size_t, int)); + */ +int +argv_exp1(sp, excp, cmd, cmdlen, is_bang) + SCR *sp; + EXCMD *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, 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. + * + * PUBLIC: int argv_exp2 __P((SCR *, EXCMD *, char *, size_t)); + */ +int +argv_exp2(sp, excp, cmd, cmdlen) + SCR *sp; + EXCMD *excp; + char *cmd; + size_t cmdlen; +{ + 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) + memcpy(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, 0)) { + 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, and then we can ignore them in the user's argument. + */ + if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1)) + n = 0; + else { + for (p = mp = O_STR(sp, O_SHELLMETA); *p != '\0'; ++p) + if (isblank(*p) || isalnum(*p)) + break; + p = bp + SHELLOFFSET; + n = len - SHELLOFFSET; + if (*p != '\0') { + for (; n > 0; --n, ++p) + if (strchr(mp, *p) != NULL) + break; + } else + for (; n > 0; --n, ++p) + if (!isblank(*p) && + !isalnum(*p) && strchr(mp, *p) != NULL) + break; + } + + /* + * If we found a meta character in the string, fork a shell to expand + * it. Unfortunately, this is comparatively slow. Historically, it + * didn't matter much, since users don't enter meta characters as part + * of pathnames that frequently. The addition of filename completion + * broke that assumption because it's easy to use. As a result, lots + * folks have complained that the expansion code is too slow. So, we + * detect filename completion as a special case, and do it internally. + * Note that this code assumes that the <asterisk> character is the + * match-anything meta character. That feels safe -- if anyone writes + * a shell that doesn't follow that convention, I'd suggest giving them + * a festive hot-lead enema. + */ + switch (n) { + case 0: + p = bp + SHELLOFFSET; + len -= SHELLOFFSET; + rval = argv_exp3(sp, excp, p, len); + break; + case 1: + if (*p == '*') { + *p = '\0'; + rval = argv_lexp(sp, excp, bp + SHELLOFFSET); + break; + } + /* FALLTHROUGH */ + default: + if (argv_sexp(sp, &bp, &blen, &len)) { + rval = 1; + goto err; + } + p = bp; + rval = argv_exp3(sp, excp, p, len); + break; + } + +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. + * + * PUBLIC: int argv_exp3 __P((SCR *, EXCMD *, char *, size_t)); + */ +int +argv_exp3(sp, excp, cmd, cmdlen) + SCR *sp; + EXCMD *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, excp, 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, excp, *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; + EXCMD *excp; + char *cmd, *p, **bpp; + size_t cmdlen, *lenp, *blenp; + int is_bang; +{ + EX_PRIVATE *exp; + char *bp, *t; + size_t blen, len, off, 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, + "115|No previous command to replace \"!\""); + return (1); + } + len += tlen = strlen(exp->lastbcomm); + off = p - bp; + ADD_SPACE_RET(sp, bp, blen, len); + p = bp + off; + memcpy(p, exp->lastbcomm, tlen); + p += tlen; + F_SET(excp, E_MODIFY); + break; + case '%': + if ((t = sp->frp->name) == NULL) { + msgq(sp, M_ERR, + "116|No filename to substitute for %%"); + return (1); + } + tlen = strlen(t); + len += tlen; + off = p - bp; + ADD_SPACE_RET(sp, bp, blen, len); + p = bp + off; + memcpy(p, t, tlen); + p += tlen; + F_SET(excp, E_MODIFY); + break; + case '#': + if ((t = sp->alt_name) == NULL) { + msgq(sp, M_ERR, + "117|No filename to substitute for #"); + return (1); + } + len += tlen = strlen(t); + off = p - bp; + ADD_SPACE_RET(sp, bp, blen, len); + p = bp + off; + memcpy(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[1] == '!')) { + ++cmd; + --cmdlen; + } + /* FALLTHROUGH */ + default: +ins_ch: ++len; + off = p - bp; + ADD_SPACE_RET(sp, bp, blen, len); + p = bp + off; + *p++ = *cmd; + } + + /* Nul termination. */ + ++len; + off = p - bp; + ADD_SPACE_RET(sp, bp, blen, len); + p = bp + off; + *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[exp->argscnt], 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. + * + * PUBLIC: int argv_free __P((SCR *)); + */ +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]); + } + free(exp->args); + } + exp->args = NULL; + exp->argscnt = 0; + exp->argsoff = 0; + return (0); +} + +/* + * argv_lexp -- + * Find all file names matching the prefix and append them to the + * buffer. + */ +static int +argv_lexp(sp, excp, path) + SCR *sp; + EXCMD *excp; + char *path; +{ + struct dirent *dp; + DIR *dirp; + EX_PRIVATE *exp; + int off; + size_t dlen, len, nlen; + char *dname, *name, *p; + + exp = EXP(sp); + + /* Set up the name and length for comparison. */ + if ((p = strrchr(path, '/')) == NULL) { + dname = "."; + dlen = 0; + name = path; + } else { + if (p == path) { + dname = "/"; + dlen = 1; + } else { + *p = '\0'; + dname = path; + dlen = strlen(path); + } + name = p + 1; + } + nlen = strlen(name); + + /* + * XXX + * We don't use the d_namlen field, it's not portable enough; we + * assume that d_name is nul terminated, instead. + */ + if ((dirp = opendir(dname)) == NULL) { + msgq_str(sp, M_SYSERR, dname, "%s"); + return (1); + } + for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) { + if (nlen == 0) { + if (dp->d_name[0] == '.') + continue; + len = strlen(dp->d_name); + } else { + len = strlen(dp->d_name); + if (len < nlen || memcmp(dp->d_name, name, nlen)) + continue; + } + + /* Directory + name + slash + null. */ + argv_alloc(sp, dlen + len + 2); + p = exp->args[exp->argsoff]->bp; + if (dlen != 0) { + memcpy(p, dname, dlen); + p += dlen; + if (dlen > 1 || dname[0] != '/') + *p++ = '/'; + } + memcpy(p, dp->d_name, len + 1); + exp->args[exp->argsoff]->len = dlen + len + 1; + ++exp->argsoff; + excp->argv = exp->args; + excp->argc = exp->argsoff; + } + closedir(dirp); + + if (off == exp->argsoff) { + /* + * If we didn't find a match, complain that the expansion + * failed. We can't know for certain that's the error, but + * it's a good guess, and it matches historic practice. + */ + msgq(sp, M_ERR, "304|Shell expansion failed"); + return (1); + } + qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp); + return (0); +} + +/* + * argv_comp -- + * Alphabetic comparison. + */ +static int +argv_comp(a, b) + const void *a, *b; +{ + return (strcmp((char *)(*(ARGS **)a)->bp, (char *)(*(ARGS **)b)->bp)); +} + +/* + * 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; +{ + enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval; + FILE *ifp; + pid_t pid; + size_t blen, len; + int ch, std_output[2]; + char *bp, *p, *sh, *sh_path; + + /* Secure means no shell access. */ + if (O_ISSET(sp, O_SECURE)) { + msgq(sp, M_ERR, +"289|Shell expansions not supported when the secure edit option is set"); + return (1); + } + + sh_path = O_STR(sp, O_SHELL); + if ((sh = strrchr(sh_path, '/')) == NULL) + sh = sh_path; + else + ++sh; + + /* Local copies of the buffer variables. */ + bp = *bpp; + blen = *blenp; + + /* + * There are two different processes running through this code, named + * the utility (the shell) and the parent. The utility reads standard + * input and writes standard output and standard error output. The + * parent writes to the utility, reads its standard output and ignores + * its standard error output. Historically, the standard error output + * was discarded by vi, as it produces a lot of noise when file patterns + * don't match. + * + * The parent reads std_output[0], and the utility writes std_output[1]. + */ + ifp = NULL; + std_output[0] = std_output[1] = -1; + if (pipe(std_output) < 0) { + msgq(sp, M_SYSERR, "pipe"); + return (1); + } + if ((ifp = fdopen(std_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. We sincerely hope. + */ + switch (pid = vfork()) { + case -1: /* Error. */ + msgq(sp, M_SYSERR, "vfork"); +err: if (ifp != NULL) + (void)fclose(ifp); + else if (std_output[0] != -1) + close(std_output[0]); + if (std_output[1] != -1) + close(std_output[0]); + return (1); + case 0: /* Utility. */ + /* Redirect stdout to the write end of the pipe. */ + (void)dup2(std_output[1], STDOUT_FILENO); + + /* Close the utility's file descriptors. */ + (void)close(std_output[0]); + (void)close(std_output[1]); + (void)close(STDERR_FILENO); + + /* + * XXX + * Assume that all shells have -c. + */ + execl(sh_path, sh, "-c", bp, NULL); + msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s"); + _exit(127); + default: /* Parent. */ + /* Close the pipe ends the parent won't use. */ + (void)close(std_output[1]); + break; + } + + /* + * Copy process standard 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, *blenp, *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')) { + --p; + --len; + } + *p = '\0'; + *lenp = len; + *bpp = bp; /* *blenp is already updated. */ + + if (ferror(ifp)) + goto ioerr; + if (fclose(ifp)) { +ioerr: msgq_str(sp, M_ERR, sh, "119|I/O error: %s"); +alloc_err: rval = SEXP_ERR; + } else + rval = SEXP_OK; + + /* + * Wait for the process. If the shell process fails (e.g., "echo $q" + * where q wasn't a defined variable) or if the returned string has + * no characters or only blank characters, (e.g., "echo $5"), complain + * that the shell expansion failed. We can't know for certain that's + * the error, but it's a good guess, and it matches historic practice. + * This won't catch "echo foo_$5", but that's not a common error and + * historic vi didn't catch it either. + */ + if (proc_wait(sp, (long)pid, sh, 1, 0)) + rval = SEXP_EXPANSION_ERR; + + for (p = bp; len; ++p, --len) + if (!isblank(*p)) + break; + if (len == 0) + rval = SEXP_EXPANSION_ERR; + + if (rval == SEXP_EXPANSION_ERR) + msgq(sp, M_ERR, "304|Shell expansion failed"); + + return (rval == SEXP_OK ? 0 : 1); +} diff --git a/ex/ex_at.c b/ex/ex_at.c new file mode 100644 index 000000000000..e9c6c592d60b --- /dev/null +++ b/ex/ex_at.c @@ -0,0 +1,126 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_at.c 10.12 (Berkeley) 9/15/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" + +/* + * ex_at -- :@[@ | buffer] + * :*[* | buffer] + * + * Execute the contents of the buffer. + * + * PUBLIC: int ex_at __P((SCR *, EXCMD *)); + */ +int +ex_at(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + CB *cbp; + CHAR_T name; + EXCMD *ecp; + RANGE *rp; + TEXT *tp; + size_t len; + char *p; + + /* + * !!! + * Historically, [@*]<carriage-return> and [@*][@*] executed the most + * recently executed buffer in ex mode. + */ + name = FL_ISSET(cmdp->iflags, E_C_BUFFER) ? cmdp->buffer : '@'; + if (name == '@' || name == '*') { + if (!F_ISSET(sp, SC_AT_SET)) { + ex_emsg(sp, NULL, EXM_NOPREVBUF); + return (1); + } + name = sp->at_lbuf; + } + sp->at_lbuf = name; + F_SET(sp, SC_AT_SET); + + CBNAME(sp, cbp, name); + if (cbp == NULL) { + ex_emsg(sp, KEY_NAME(sp, name), EXM_EMPTYBUF); + return (1); + } + + /* + * !!! + * Historically the @ command took a range of lines, and the @ buffer + * was executed once per line. The historic vi could be trashed by + * this because it didn't notice if the underlying file changed, or, + * for that matter, if there were no more lines on which to operate. + * For example, take a 10 line file, load "%delete" into a buffer, + * and enter :8,10@<buffer>. + * + * The solution is a bit tricky. If the user specifies a range, take + * the same approach as for global commands, and discard the command + * if exit or switch to a new file/screen. If the user doesn't specify + * the range, continue to execute after a file/screen switch, which + * means @ buffers are still useful in a multi-screen environment. + */ + CALLOC_RET(sp, ecp, EXCMD *, 1, sizeof(EXCMD)); + CIRCLEQ_INIT(&ecp->rq); + CALLOC_RET(sp, rp, RANGE *, 1, sizeof(RANGE)); + rp->start = cmdp->addr1.lno; + if (F_ISSET(cmdp, E_ADDR_DEF)) { + rp->stop = rp->start; + FL_SET(ecp->agv_flags, AGV_AT_NORANGE); + } else { + rp->stop = cmdp->addr2.lno; + FL_SET(ecp->agv_flags, AGV_AT); + } + CIRCLEQ_INSERT_HEAD(&ecp->rq, rp, q); + + /* + * Buffers executed in ex mode or from the colon command line in vi + * were ex commands. We can't push it on the terminal queue, since + * it has to be executed immediately, and we may be in the middle of + * an ex command already. Push the command on the ex command stack. + * Build two copies of the command. We need two copies because the + * ex parser may step on the command string when it's parsing it. + */ + for (len = 0, tp = cbp->textq.cqh_last; + tp != (void *)&cbp->textq; tp = tp->q.cqe_prev) + len += tp->len + 1; + + MALLOC_RET(sp, ecp->cp, char *, len * 2); + ecp->o_cp = ecp->cp; + ecp->o_clen = len; + ecp->cp[len] = '\0'; + + /* Copy the buffer into the command space. */ + for (p = ecp->cp + len, tp = cbp->textq.cqh_last; + tp != (void *)&cbp->textq; tp = tp->q.cqe_prev) { + memcpy(p, tp->lb, tp->len); + p += tp->len; + *p++ = '\n'; + } + + LIST_INSERT_HEAD(&sp->gp->ecq, ecp, q); + return (0); +} diff --git a/ex/ex_bang.c b/ex/ex_bang.c new file mode 100644 index 000000000000..25f3f7732ab0 --- /dev/null +++ b/ex/ex_bang.c @@ -0,0 +1,186 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_bang.c 10.33 (Berkeley) 9/23/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" +#include "../vi/vi.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. + * + * PUBLIC: int ex_bang __P((SCR *, EXCMD *)); + */ +int +ex_bang(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + enum filtertype ftype; + ARGS *ap; + EX_PRIVATE *exp; + MARK rm; + recno_t lno; + int rval; + const char *msg; + + ap = cmdp->argv[0]; + if (ap->len == 0) { + ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); + return (1); + } + + /* Set the "last bang command" remembered value. */ + 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, it was historically + * redisplayed. + */ + if (F_ISSET(cmdp, E_MODIFY) && !F_ISSET(sp, SC_EX_SILENT)) { + /* + * Display the command if modified. Historic ex/vi displayed + * the command if it was modified due to file name and/or bang + * expansion. If piping lines in vi, it would be immediately + * overwritten by any error or line change reporting. + */ + if (F_ISSET(sp, SC_VI)) + vs_update(sp, "!", ap->bp); + else { + (void)ex_printf(sp, "!%s\n", ap->bp); + (void)ex_fflush(sp); + } + } + + /* + * If no addresses were specified, run the command. If there's an + * underlying file, it's 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. + */ + if (cmdp->addrcnt == 0) { + msg = NULL; + if (sp->ep != NULL && F_ISSET(sp->ep, F_MODIFIED)) + if (O_ISSET(sp, O_AUTOWRITE)) { + if (file_aw(sp, FS_ALL)) + return (0); + } else if (O_ISSET(sp, O_WARN) && + !F_ISSET(sp, SC_EX_SILENT)) + msg = msg_cat(sp, + "303|File modified since last write.", + NULL); + + /* If we're still in a vi screen, move out explicitly. */ + (void)ex_exec_proc(sp, + cmdp, ap->bp, msg, !F_ISSET(sp, SC_EX | SC_SCR_EXWROTE)); + } + + /* + * 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 lines by only the stdout lines. This + * makes no sense to me, so nvi makes it consistent for both, and + * matches vi's historic behavior. + */ + else { + NEEDFILE(sp, cmdp); + + /* Autoprint is set historically, even if the command fails. */ + F_SET(cmdp, E_AUTOPRINT); + + /* + * !!! + * Historical vi permitted "!!" in an empty file. When this + * happens, we arrive here with two addresses of 1,1 and a + * bad attitude. The simple solution is to turn it into a + * FILTER_READ operation, with the exception that stdin isn't + * opened for the utility, and the cursor position isn't the + * same. The only historic glitch (I think) is that we don't + * put an empty line into the default cut buffer, as historic + * vi did. Imagine, if you can, my disappointment. + */ + ftype = FILTER_BANG; + if (cmdp->addr1.lno == 1 && cmdp->addr2.lno == 1) { + if (db_last(sp, &lno)) + return (1); + if (lno == 0) { + cmdp->addr1.lno = cmdp->addr2.lno = 0; + ftype = FILTER_RBANG; + } + } + rval = ex_filter(sp, cmdp, + &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 and consistent, so we do it. + */ + sp->lno = rm.lno; + if (F_ISSET(sp, SC_VI)) { + sp->cno = 0; + (void)nonblank(sp, sp->lno, &sp->cno); + } else + sp->cno = rm.cno; + } + + /* Ex terminates with a bang, even if the command fails. */ + if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT)) + (void)ex_puts(sp, "!\n"); + + /* + * XXX + * The ! commands never return an error, so that autoprint always + * happens in the ex parser. + */ + return (0); +} diff --git a/ex/ex_cd.c b/ex/ex_cd.c new file mode 100644 index 000000000000..3307c7b6306b --- /dev/null +++ b/ex/ex_cd.c @@ -0,0 +1,129 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_cd.c 10.10 (Berkeley) 8/12/96"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" + +/* + * ex_cd -- :cd[!] [directory] + * Change directories. + * + * PUBLIC: int ex_cd __P((SCR *, EXCMD *)); + */ +int +ex_cd(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + struct passwd *pw; + ARGS *ap; + CHAR_T savech; + char *dir, *p, *t; /* 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(sp->ep, F_MODIFIED) && + !FL_ISSET(cmdp->iflags, E_C_FORCE) && sp->frp->name[0] != '/') { + msgq(sp, M_ERR, + "120|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, + "121|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 this succeeds, don't display + * a message, vi didn't historically, and it should be obvious to the + * user where they are. + */ + if (!chdir(dir)) + return (0); + + /* + * 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; + + /* Try the O_CDPATH option values. */ + for (p = t = O_STR(sp, O_CDPATH);; ++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 length is 1 or less, then we're dealing with + * ":XXX", "XXX::XXXX" , "XXX:", or "". Since we've + * already tried dot, we ignore tham all. + */ + if (t < p - 1) { + savech = *p; + *p = '\0'; + (void)snprintf(buf, + sizeof(buf), "%s/%s", t, dir); + *p = savech; + if (!chdir(buf)) { + if (getcwd(buf, sizeof(buf)) != NULL) + msgq_str(sp, M_INFO, buf, "122|New current directory: %s"); + return (0); + } + } + t = p + 1; + if (*p == '\0') + break; + } + +err: msgq_str(sp, M_SYSERR, dir, "%s"); + return (1); +} diff --git a/ex/ex_cmd.c b/ex/ex_cmd.c new file mode 100644 index 000000000000..8f7fc8da7a13 --- /dev/null +++ b/ex/ex_cmd.c @@ -0,0 +1,457 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_cmd.c 10.20 (Berkeley) 10/10/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.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, + "", + "^D", + "scroll lines"}, +/* C_BANG */ + {"!", ex_bang, E_ADDR2_NONE | E_SECURE, + "S", + "[line [,line]] ! command", + "filter lines through commands or run commands"}, +/* C_HASH */ + {"#", ex_number, E_ADDR2|E_CLRFLAG, + "ca1", + "[line [,line]] # [count] [l]", + "display numbered lines"}, +/* C_SUBAGAIN */ + {"&", ex_subagain, E_ADDR2, + "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, + "ca1", + "[line [,line]] <[<...] [count] [flags]", + "shift lines left"}, +/* C_EQUAL */ + {"=", ex_equal, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF, + "1", + "[line] = [flags]", + "display line number"}, +/* C_SHIFTR */ + {">", ex_shiftr, E_ADDR2|E_AUTOPRINT, + "ca1", + "[line [,line]] >[>...] [count] [flags]", + "shift lines right"}, +/* C_AT */ + {"@", ex_at, E_ADDR2, + "b", + "@ [buffer]", + "execute a buffer"}, +/* C_APPEND */ + {"append", ex_append, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF, + "!", + "[line] a[ppend][!]", + "append input to a line"}, +/* C_ABBR */ + {"abbreviate", ex_abbr, 0, + "W", + "ab[brev] [word replace]", + "specify an input abbreviation"}, +/* C_ARGS */ + {"args", ex_args, 0, + "", + "ar[gs]", + "display file argument list"}, +/* C_BG */ + {"bg", ex_bg, E_VIONLY, + "", + "bg", + "put a foreground screen into the background"}, +/* C_CHANGE */ + {"change", ex_change, E_ADDR2|E_ADDR_ZERODEF, + "!ca", + "[line [,line]] c[hange][!] [count]", + "change lines to input"}, +/* C_CD */ + {"cd", ex_cd, 0, + "!f1o", + "cd[!] [directory]", + "change the current directory"}, +/* C_CHDIR */ + {"chdir", ex_cd, 0, + "!f1o", + "chd[ir][!] [directory]", + "change the current directory"}, +/* C_COPY */ + {"copy", ex_copy, E_ADDR2|E_AUTOPRINT, + "l1", + "[line [,line]] co[py] line [flags]", + "copy lines elsewhere in the file"}, +/* C_CSCOPE */ + {"cscope", ex_cscope, 0, + "!s", + "cs[cope] command [args]", + "create a set of tags using a cscope command"}, +/* + * !!! + * 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, + "bca1", + "[line [,line]] d[elete][flags] [buffer] [count] [flags]", + "delete lines from the file"}, +/* C_DISPLAY */ + {"display", ex_display, 0, + "w1r", + "display b[uffers] | c[onnections] | s[creens] | t[ags]", + "display buffers, connections, screens or tags"}, +/* C_EDIT */ + {"edit", ex_edit, E_NEWSCREEN, + "f1o", + "[Ee][dit][!] [+cmd] [file]", + "begin editing another file"}, +/* C_EX */ + {"ex", ex_edit, E_NEWSCREEN, + "f1o", + "[Ee]x[!] [+cmd] [file]", + "begin editing another file"}, +/* C_EXUSAGE */ + {"exusage", ex_usage, 0, + "w1o", + "[exu]sage [command]", + "display ex command usage statement"}, +/* C_FILE */ + {"file", ex_file, 0, + "f1o", + "f[ile] [name]", + "display (and optionally set) file name"}, +/* C_FG */ + {"fg", ex_fg, E_NEWSCREEN|E_VIONLY, + "f1o", + "[Ff]g [file]", + "bring a backgrounded screen into the foreground"}, +/* C_GLOBAL */ + {"global", ex_global, E_ADDR2_ALL, + "!s", + "[line [,line]] g[lobal][!] [;/]RE[;/] [commands]", + "execute a global command on lines matching an RE"}, +/* C_HELP */ + {"help", ex_help, 0, + "", + "he[lp]", + "display help statement"}, +/* C_INSERT */ + {"insert", ex_insert, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF, + "!", + "[line] i[nsert][!]", + "insert input before a line"}, +/* C_JOIN */ + {"join", ex_join, E_ADDR2|E_AUTOPRINT, + "!ca1", + "[line [,line]] j[oin][!] [count] [flags]", + "join lines into a single line"}, +/* C_K */ + {"k", ex_mark, E_ADDR1, + "w1r", + "[line] k key", + "mark a line position"}, +/* C_LIST */ + {"list", ex_list, E_ADDR2|E_CLRFLAG, + "ca1", + "[line [,line]] l[ist] [count] [#]", + "display lines in an unambiguous form"}, +/* C_MOVE */ + {"move", ex_move, E_ADDR2|E_AUTOPRINT, + "l", + "[line [,line]] m[ove] line", + "move lines elsewhere in the file"}, +/* C_MARK */ + {"mark", ex_mark, E_ADDR1, + "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, 0, + "!f1r", + "mkexrc[!] file", + "write a .exrc file"}, +/* C_NEXT */ + {"next", ex_next, E_NEWSCREEN, + "!fN", + "[Nn][ext][!] [+cmd] [file ...]", + "edit (and optionally specify) the next file"}, +/* C_NUMBER */ + {"number", ex_number, E_ADDR2|E_CLRFLAG, + "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_CLRFLAG, + "ca1", + "[line [,line]] p[rint] [count] [#l]", + "display lines"}, +/* C_PERLCMD */ + {"perl", ex_perl, E_ADDR2_ALL|E_ADDR_ZERO| + E_ADDR_ZERODEF|E_SECURE, + "s", + "pe[rl] cmd", + "run the perl interpreter with the command"}, +/* C_PERLDOCMD */ + {"perldo", ex_perl, E_ADDR2_ALL|E_ADDR_ZERO| + E_ADDR_ZERODEF|E_SECURE, + "s", + "perld[o] cmd", + "run the perl interpreter with the command, on each line"}, +/* C_PRESERVE */ + {"preserve", ex_preserve, 0, + "", + "pre[serve]", + "preserve an edit session for recovery"}, +/* C_PREVIOUS */ + {"previous", ex_prev, E_NEWSCREEN, + "!", + "[Pp]rev[ious][!]", + "edit the previous file in the file argument list"}, +/* C_PUT */ + {"put", ex_put, + E_ADDR1|E_AUTOPRINT|E_ADDR_ZERO|E_ADDR_ZERODEF, + "b", + "[line] pu[t] [buffer]", + "append a cut buffer to the line"}, +/* C_QUIT */ + {"quit", ex_quit, 0, + "!", + "q[uit][!]", + "exit ex/vi"}, +/* C_READ */ + {"read", ex_read, E_ADDR1|E_ADDR_ZERO|E_ADDR_ZERODEF, + "s", + "[line] r[ead] [!cmd | [file]]", + "append input from a command or file to the line"}, +/* C_RECOVER */ + {"recover", ex_recover, 0, + "!f1r", + "recover[!] file", + "recover a saved file"}, +/* C_RESIZE */ + {"resize", ex_resize, E_VIONLY, + "c+", + "resize [+-]rows", + "grow or shrink the current screen"}, +/* C_REWIND */ + {"rewind", ex_rew, 0, + "!", + "rew[ind][!]", + "re-edit all the files in the file argument list"}, +/* + * !!! + * Adding new commands starting with 's' may break the substitute command code + * in ex_cmd() (the ex parser). Read through the comments there, first. + */ +/* C_SUBSTITUTE */ + {"s", ex_s, E_ADDR2, + "s", + "[line [,line]] s [[/;]RE[/;]repl[/;] [cgr] [count] [#lp]]", + "substitute on lines matching an RE"}, +/* C_SCRIPT */ + {"script", ex_script, E_SECURE, + "!f1o", + "sc[ript][!] [file]", + "run a shell in a screen"}, +/* C_SET */ + {"set", ex_set, 0, + "wN", + "se[t] [option[=[value]]...] [nooption ...] [option? ...] [all]", + "set options (use \":set all\" to see all options)"}, +/* C_SHELL */ + {"shell", ex_shell, E_SECURE, + "", + "sh[ell]", + "suspend editing and run a shell"}, +/* C_SOURCE */ + {"source", ex_source, 0, + "f1r", + "so[urce] file", + "read a file of ex commands"}, +/* C_STOP */ + {"stop", ex_stop, E_SECURE, + "!", + "st[op][!]", + "suspend the edit session"}, +/* C_SUSPEND */ + {"suspend", ex_stop, E_SECURE, + "!", + "su[spend][!]", + "suspend the edit session"}, +/* C_T */ + {"t", ex_copy, E_ADDR2|E_AUTOPRINT, + "l1", + "[line [,line]] t line [flags]", + "copy lines elsewhere in the file"}, +/* C_TAG */ + {"tag", ex_tag_push, E_NEWSCREEN, + "!w1o", + "[Tt]a[g][!] [string]", + "edit the file containing the tag"}, +/* C_TAGNEXT */ + {"tagnext", ex_tag_next, 0, + "!", + "tagn[ext][!]", + "move to the next tag"}, +/* C_TAGPOP */ + {"tagpop", ex_tag_pop, 0, + "!w1o", + "tagp[op][!] [number | file]", + "return to the previous group of tags"}, +/* C_TAGPREV */ + {"tagprev", ex_tag_prev, 0, + "!", + "tagpr[ev][!]", + "move to the previous tag"}, +/* C_TAGTOP */ + {"tagtop", ex_tag_top, 0, + "!", + "tagt[op][!]", + "discard all tags"}, +/* C_TCLCMD */ + {"tcl", ex_tcl, E_ADDR2_ALL|E_ADDR_ZERO| + E_ADDR_ZERODEF|E_SECURE, + "s", + "tc[l] cmd", + "run the tcl interpreter with the command"}, +/* C_UNDO */ + {"undo", ex_undo, E_AUTOPRINT, + "", + "u[ndo]", + "undo the most recent change"}, +/* C_UNABBREVIATE */ + {"unabbreviate",ex_unabbr, 0, + "w1r", + "una[bbrev] word", + "delete an abbreviation"}, +/* C_UNMAP */ + {"unmap", ex_unmap, 0, + "!w1r", + "unm[ap][!] word", + "delete an input or command map"}, +/* C_V */ + {"v", ex_v, E_ADDR2_ALL, + "s", + "[line [,line]] v [;/]RE[;/] [commands]", + "execute a global command on lines NOT matching an RE"}, +/* C_VERSION */ + {"version", ex_version, 0, + "", + "version", + "display the program version information"}, +/* C_VISUAL_EX */ + {"visual", ex_visual, E_ADDR1|E_ADDR_ZERODEF, + "2c11", + "[line] vi[sual] [-|.|+|^] [window_size] [flags]", + "enter visual (vi) mode from ex mode"}, +/* C_VISUAL_VI */ + {"visual", ex_edit, E_NEWSCREEN, + "f1o", + "[Vv]i[sual][!] [+cmd] [file]", + "edit another file (from vi mode only)"}, +/* C_VIUSAGE */ + {"viusage", ex_viusage, 0, + "w1o", + "[viu]sage [key]", + "display vi key usage statement"}, +/* C_WRITE */ + {"write", ex_write, E_ADDR2_ALL|E_ADDR_ZERODEF, + "!s", + "[line [,line]] w[rite][!] [ !cmd | [>>] [file]]", + "write the file"}, +/* C_WN */ + {"wn", ex_wn, E_ADDR2_ALL|E_ADDR_ZERODEF, + "!s", + "[line [,line]] wn[!] [>>] [file]", + "write the file and switch to the next file"}, +/* C_WQ */ + {"wq", ex_wq, E_ADDR2_ALL|E_ADDR_ZERODEF, + "!s", + "[line [,line]] wq[!] [>>] [file]", + "write the file and exit"}, +/* C_XIT */ + {"xit", ex_xit, E_ADDR2_ALL|E_ADDR_ZERODEF, + "!f1o", + "[line [,line]] x[it][!] [file]", + "exit"}, +/* C_YANK */ + {"yank", ex_yank, E_ADDR2, + "bca", + "[line [,line]] ya[nk] [buffer] [count]", + "copy lines to a cut buffer"}, +/* C_Z */ + {"z", ex_z, E_ADDR1, + "3c01", + "[line] z [-|.|+|^|=] [count] [flags]", + "display different screens of the file"}, +/* C_SUBTILDE */ + {"~", ex_subtilde, E_ADDR2, + "s", + "[line [,line]] ~ [cgr] [count] [#lp]", + "replace previous RE with previous replacement string,"}, + {NULL}, +}; diff --git a/ex/ex_cscope.c b/ex/ex_cscope.c new file mode 100644 index 000000000000..c2fa0a5c454b --- /dev/null +++ b/ex/ex_cscope.c @@ -0,0 +1,1057 @@ +/*- + * Copyright (c) 1994, 1996 + * Rob Mayoff. All rights reserved. + * Copyright (c) 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_cscope.c 10.13 (Berkeley) 9/15/96"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/types.h> /* XXX: param.h may not have included types.h */ +#include <sys/queue.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> + +#include <bitstring.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "../common/common.h" +#include "pathnames.h" +#include "tag.h" + +#define CSCOPE_DBFILE "cscope.out" +#define CSCOPE_PATHS "cscope.tpath" + +/* + * 0name find all uses of name + * 1name find definition of name + * 2name find all function calls made from name + * 3name find callers of name + * 4string find text string (cscope 12.9) + * 4name find assignments to name (cscope 13.3) + * 5pattern change pattern -- NOT USED + * 6pattern find pattern + * 7name find files with name as substring + * 8name find files #including name + */ +#define FINDHELP "\ +find c|d|e|f|g|i|s|t buffer|pattern\n\ + c: find callers of name\n\ + d: find all function calls made from name\n\ + e: find pattern\n\ + f: find files with name as substring\n\ + g: find definition of name\n\ + i: find files #including name\n\ + s: find all uses of name\n\ + t: find assignments to name" + +static int cscope_add __P((SCR *, EXCMD *, char *)); +static int cscope_find __P((SCR *, EXCMD*, char *)); +static int cscope_help __P((SCR *, EXCMD *, char *)); +static int cscope_kill __P((SCR *, EXCMD *, char *)); +static int cscope_reset __P((SCR *, EXCMD *, char *)); + +typedef struct _cc { + char *name; + int (*function) __P((SCR *, EXCMD *, char *)); + char *help_msg; + char *usage_msg; +} CC; + +static CC const cscope_cmds[] = { + { "add", cscope_add, + "Add a new cscope database", "add file | directory" }, + { "find", cscope_find, + "Query the databases for a pattern", FINDHELP }, + { "help", cscope_help, + "Show help for cscope commands", "help [command]" }, + { "kill", cscope_kill, + "Kill a cscope connection", "kill number" }, + { "reset", cscope_reset, + "Discard all current cscope connections", "reset" }, + { NULL } +}; + +static TAGQ *create_cs_cmd __P((SCR *, char *, size_t *)); +static int csc_help __P((SCR *, char *)); +static void csc_file __P((SCR *, + CSC *, char *, char **, size_t *, int *)); +static int get_paths __P((SCR *, CSC *)); +static CC const *lookup_ccmd __P((char *)); +static int parse __P((SCR *, CSC *, TAGQ *, int *)); +static int read_prompt __P((SCR *, CSC *)); +static int run_cscope __P((SCR *, CSC *, char *)); +static int start_cscopes __P((SCR *, EXCMD *)); +static int terminate __P((SCR *, CSC *, int)); + +/* + * ex_cscope -- + * Perform an ex cscope. + * + * PUBLIC: int ex_cscope __P((SCR *, EXCMD *)); + */ +int +ex_cscope(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + CC const *ccp; + EX_PRIVATE *exp; + int i; + char *cmd, *p; + + /* Initialize the default cscope directories. */ + exp = EXP(sp); + if (!F_ISSET(exp, EXP_CSCINIT) && start_cscopes(sp, cmdp)) + return (1); + F_SET(exp, EXP_CSCINIT); + + /* Skip leading whitespace. */ + for (p = cmdp->argv[0]->bp, i = cmdp->argv[0]->len; i > 0; --i, ++p) + if (!isspace(*p)) + break; + if (i == 0) + goto usage; + + /* Skip the command to any arguments. */ + for (cmd = p; i > 0; --i, ++p) + if (isspace(*p)) + break; + if (*p != '\0') { + *p++ = '\0'; + for (; *p && isspace(*p); ++p); + } + + if ((ccp = lookup_ccmd(cmd)) == NULL) { +usage: msgq(sp, M_ERR, "309|Use \"cscope help\" for help"); + return (1); + } + + /* Call the underlying function. */ + return (ccp->function(sp, cmdp, p)); +} + +/* + * start_cscopes -- + * Initialize the cscope package. + */ +static int +start_cscopes(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + size_t blen, len; + char *bp, *cscopes, *p, *t; + + /* + * EXTENSION #1: + * + * If the CSCOPE_DIRS environment variable is set, we treat it as a + * list of cscope directories that we're using, similar to the tags + * edit option. + * + * XXX + * This should probably be an edit option, although that implies that + * we start/stop cscope processes periodically, instead of once when + * the editor starts. + */ + if ((cscopes = getenv("CSCOPE_DIRS")) == NULL) + return (0); + len = strlen(cscopes); + GET_SPACE_RET(sp, bp, blen, len); + memcpy(bp, cscopes, len + 1); + + for (cscopes = t = bp; (p = strsep(&t, "\t :")) != NULL;) + if (*p != '\0') + (void)cscope_add(sp, cmdp, p); + + FREE_SPACE(sp, bp, blen); + return (0); +} + +/* + * cscope_add -- + * The cscope add command. + */ +static int +cscope_add(sp, cmdp, dname) + SCR *sp; + EXCMD *cmdp; + char *dname; +{ + struct stat sb; + EX_PRIVATE *exp; + CSC *csc; + size_t len; + int cur_argc; + char *dbname, path[MAXPATHLEN]; + + exp = EXP(sp); + + /* + * 0 additional args: usage. + * 1 additional args: matched a file. + * >1 additional args: object, too many args. + */ + cur_argc = cmdp->argc; + if (argv_exp2(sp, cmdp, dname, strlen(dname))) + return (1); + if (cmdp->argc == cur_argc) { + (void)csc_help(sp, "add"); + return (1); + } + if (cmdp->argc == cur_argc + 1) + dname = cmdp->argv[cur_argc]->bp; + else { + ex_emsg(sp, dname, EXM_FILECOUNT); + return (1); + } + + /* + * The user can specify a specific file (so they can have multiple + * Cscope databases in a single directory) or a directory. If the + * file doesn't exist, we're done. If it's a directory, append the + * standard database file name and try again. Store the directory + * name regardless so that we can use it as a base for searches. + */ + if (stat(dname, &sb)) { + msgq(sp, M_SYSERR, dname); + return (1); + } + if (S_ISDIR(sb.st_mode)) { + (void)snprintf(path, sizeof(path), + "%s/%s", dname, CSCOPE_DBFILE); + if (stat(path, &sb)) { + msgq(sp, M_SYSERR, path); + return (1); + } + dbname = CSCOPE_DBFILE; + } else if ((dbname = strrchr(dname, '/')) != NULL) + *dbname++ = '\0'; + + /* Allocate a cscope connection structure and initialize its fields. */ + len = strlen(dname); + CALLOC_RET(sp, csc, CSC *, 1, sizeof(CSC) + len); + csc->dname = csc->buf; + csc->dlen = len; + memcpy(csc->dname, dname, len); + csc->mtime = sb.st_mtime; + + /* Get the search paths for the cscope. */ + if (get_paths(sp, csc)) + goto err; + + /* Start the cscope process. */ + if (run_cscope(sp, csc, dbname)) + goto err; + + /* + * Add the cscope connection to the screen's list. From now on, + * on error, we have to call terminate, which expects the csc to + * be on the chain. + */ + LIST_INSERT_HEAD(&exp->cscq, csc, q); + + /* Read the initial prompt from the cscope to make sure it's okay. */ + if (read_prompt(sp, csc)) { + terminate(sp, csc, 0); + return (1); + } + + return (0); + +err: free(csc); + return (1); +} + +/* + * get_paths -- + * Get the directories to search for the files associated with this + * cscope database. + */ +static int +get_paths(sp, csc) + SCR *sp; + CSC *csc; +{ + struct stat sb; + int fd, nentries; + size_t len; + char *p, **pathp, buf[MAXPATHLEN * 2]; + + /* + * EXTENSION #2: + * + * If there's a cscope directory with a file named CSCOPE_PATHS, it + * contains a colon-separated list of paths in which to search for + * files returned by cscope. + * + * XXX + * These paths are absolute paths, and not relative to the cscope + * directory. To fix this, rewrite the each path using the cscope + * directory as a prefix. + */ + (void)snprintf(buf, sizeof(buf), "%s/%s", csc->dname, CSCOPE_PATHS); + if (stat(buf, &sb) == 0) { + /* Read in the CSCOPE_PATHS file. */ + len = sb.st_size; + MALLOC_RET(sp, csc->pbuf, char *, len + 1); + if ((fd = open(buf, O_RDONLY, 0)) < 0 || + read(fd, csc->pbuf, len) != len) { + msgq_str(sp, M_SYSERR, buf, "%s"); + if (fd >= 0) + (void)close(fd); + return (1); + } + (void)close(fd); + csc->pbuf[len] = '\0'; + + /* Count up the entries. */ + for (nentries = 0, p = csc->pbuf; *p != '\0'; ++p) + if (p[0] == ':' && p[1] != '\0') + ++nentries; + + /* Build an array of pointers to the paths. */ + CALLOC_GOTO(sp, + csc->paths, char **, nentries + 1, sizeof(char **)); + for (pathp = csc->paths, p = strtok(csc->pbuf, ":"); + p != NULL; p = strtok(NULL, ":")) + *pathp++ = p; + return (0); + } + + /* + * If the CSCOPE_PATHS file doesn't exist, we look for files + * relative to the cscope directory. + */ + if ((csc->pbuf = strdup(csc->dname)) == NULL) { + msgq(sp, M_SYSERR, NULL); + return (1); + } + CALLOC_GOTO(sp, csc->paths, char **, 2, sizeof(char *)); + csc->paths[0] = csc->pbuf; + return (0); + +alloc_err: + if (csc->pbuf != NULL) { + free(csc->pbuf); + csc->pbuf = NULL; + } + return (1); +} + +/* + * run_cscope -- + * Fork off the cscope process. + */ +static int +run_cscope(sp, csc, dbname) + SCR *sp; + CSC *csc; + char *dbname; +{ + int to_cs[2], from_cs[2]; + char cmd[MAXPATHLEN * 2]; + + /* + * Cscope reads from to_cs[0] and writes to from_cs[1]; vi reads from + * from_cs[0] and writes to to_cs[1]. + */ + to_cs[0] = to_cs[1] = from_cs[0] = from_cs[0] = -1; + if (pipe(to_cs) < 0 || pipe(from_cs) < 0) { + msgq(sp, M_SYSERR, "pipe"); + goto err; + } + switch (csc->pid = vfork()) { + case -1: + msgq(sp, M_SYSERR, "vfork"); +err: if (to_cs[0] != -1) + (void)close(to_cs[0]); + if (to_cs[1] != -1) + (void)close(to_cs[1]); + if (from_cs[0] != -1) + (void)close(from_cs[0]); + if (from_cs[1] != -1) + (void)close(from_cs[1]); + return (1); + case 0: /* child: run cscope. */ + (void)dup2(to_cs[0], STDIN_FILENO); + (void)dup2(from_cs[1], STDOUT_FILENO); + (void)dup2(from_cs[1], STDERR_FILENO); + + /* Close unused file descriptors. */ + (void)close(to_cs[1]); + (void)close(from_cs[0]); + + /* Run the cscope command. */ +#define CSCOPE_CMD_FMT "cd '%s' && exec cscope -dl -f %s" + (void)snprintf(cmd, sizeof(cmd), + CSCOPE_CMD_FMT, csc->dname, dbname); + (void)execl(_PATH_BSHELL, "sh", "-c", cmd, NULL); + msgq_str(sp, M_SYSERR, cmd, "execl: %s"); + _exit (127); + /* NOTREACHED */ + default: /* parent. */ + /* Close unused file descriptors. */ + (void)close(to_cs[0]); + (void)close(from_cs[1]); + + /* + * Save the file descriptors for later duplication, and + * reopen as streams. + */ + csc->to_fd = to_cs[1]; + csc->to_fp = fdopen(to_cs[1], "w"); + csc->from_fd = from_cs[0]; + csc->from_fp = fdopen(from_cs[0], "r"); + break; + } + return (0); +} + +/* + * cscope_find -- + * The cscope find command. + */ +static int +cscope_find(sp, cmdp, pattern) + SCR *sp; + EXCMD *cmdp; + char *pattern; +{ + CSC *csc, *csc_next; + EX_PRIVATE *exp; + FREF *frp; + TAGQ *rtqp, *tqp; + TAG *rtp; + recno_t lno; + size_t cno, search; + int force, istmp, matches; + + exp = EXP(sp); + + /* Check for connections. */ + if (exp->cscq.lh_first == NULL) { + msgq(sp, M_ERR, "310|No cscope connections running"); + return (1); + } + + /* + * Allocate all necessary memory before doing anything hard. If the + * tags stack is empty, we'll need the `local context' TAGQ structure + * later. + */ + rtp = NULL; + rtqp = NULL; + if (exp->tq.cqh_first == (void *)&exp->tq) { + /* Initialize the `local context' tag queue structure. */ + CALLOC_GOTO(sp, rtqp, TAGQ *, 1, sizeof(TAGQ)); + CIRCLEQ_INIT(&rtqp->tagq); + + /* Initialize and link in its tag structure. */ + CALLOC_GOTO(sp, rtp, TAG *, 1, sizeof(TAG)); + CIRCLEQ_INSERT_HEAD(&rtqp->tagq, rtp, q); + rtqp->current = rtp; + } + + /* Create the cscope command. */ + if ((tqp = create_cs_cmd(sp, pattern, &search)) == NULL) + goto err; + + /* + * Stick the current context in a convenient place, we'll lose it + * when we switch files. + */ + frp = sp->frp; + lno = sp->lno; + cno = sp->cno; + istmp = F_ISSET(sp->frp, FR_TMPFILE) && !F_ISSET(cmdp, E_NEWSCREEN); + + /* Search all open connections for a match. */ + matches = 0; + for (csc = exp->cscq.lh_first; csc != NULL; csc = csc_next) { + /* Copy csc->q.lh_next here in case csc is killed. */ + csc_next = csc->q.le_next; + + /* + * Send the command to the cscope program. (We skip the + * first two bytes of the command, because we stored the + * search cscope command character and a leading space + * there.) + */ + (void)fprintf(csc->to_fp, "%d%s\n", search, tqp->tag + 2); + (void)fflush(csc->to_fp); + + /* Read the output. */ + if (parse(sp, csc, tqp, &matches)) { + if (rtqp != NULL) + free(rtqp); + tagq_free(sp, tqp); + return (1); + } + } + + if (matches == 0) { + msgq(sp, M_INFO, "278|No matches for query"); + return (0); + } + + tqp->current = tqp->tagq.cqh_first; + + /* Try to switch to the first tag. */ + force = FL_ISSET(cmdp->iflags, E_C_FORCE); + if (F_ISSET(cmdp, E_NEWSCREEN)) { + if (ex_tag_Nswitch(sp, tqp->current, force)) + goto err; + + /* Everything else gets done in the new screen. */ + sp = sp->nextdisp; + exp = EXP(sp); + } else + if (ex_tag_nswitch(sp, tqp->current, force)) + goto err; + + /* + * If this is the first tag, put a `current location' queue entry + * in place, so we can pop all the way back to the current mark. + * Note, it doesn't point to much of anything, it's a placeholder. + */ + if (exp->tq.cqh_first == (void *)&exp->tq) { + CIRCLEQ_INSERT_HEAD(&exp->tq, rtqp, q); + } else + rtqp = exp->tq.cqh_first; + + /* Link the current TAGQ structure into place. */ + CIRCLEQ_INSERT_HEAD(&exp->tq, tqp, q); + + (void)cscope_search(sp, tqp, tqp->current); + + /* + * Move the current context from the temporary save area into the + * right structure. + * + * If we were in a temporary file, we don't have a context to which + * we can return, so just make it be the same as what we're moving + * to. It will be a little odd that ^T doesn't change anything, but + * I don't think it's a big deal. + */ + if (istmp) { + rtqp->current->frp = sp->frp; + rtqp->current->lno = sp->lno; + rtqp->current->cno = sp->cno; + } else { + rtqp->current->frp = frp; + rtqp->current->lno = lno; + rtqp->current->cno = cno; + } + + return (0); + +err: +alloc_err: + if (rtqp != NULL) + free(rtqp); + if (rtp != NULL) + free(rtp); + return (1); +} + +/* + * create_cs_cmd -- + * Build a cscope command, creating and initializing the base TAGQ. + */ +static TAGQ * +create_cs_cmd(sp, pattern, searchp) + SCR *sp; + char *pattern; + size_t *searchp; +{ + CB *cbp; + TAGQ *tqp; + size_t tlen; + char *p; + + /* + * Cscope supports a "change pattern" command which we never use, + * cscope command 5. Set CSCOPE_QUERIES[5] to " " since the user + * can't pass " " as the first character of pattern. That way the + * user can't ask for pattern 5 so we don't need any special-case + * code. + */ +#define CSCOPE_QUERIES "sgdct efi" + + if (pattern == NULL) + goto usage; + + /* Skip leading blanks, check for command character. */ + for (; isblank(pattern[0]); ++pattern); + if (pattern[0] == '\0' || !isblank(pattern[1])) + goto usage; + for (*searchp = 0, p = CSCOPE_QUERIES; + *p != '\0' && *p != pattern[0]; ++*searchp, ++p); + if (*p == '\0') { + msgq(sp, M_ERR, + "311|%s: unknown search type: use one of %s", + KEY_NAME(sp, pattern[0]), CSCOPE_QUERIES); + return (NULL); + } + + /* Skip <blank> characters to the pattern. */ + for (p = pattern + 1; *p != '\0' && isblank(*p); ++p); + if (*p == '\0') { +usage: (void)csc_help(sp, "find"); + return (NULL); + } + + /* The user can specify the contents of a buffer as the pattern. */ + cbp = NULL; + if (p[0] == '"' && p[1] != '\0' && p[2] == '\0') + CBNAME(sp, cbp, p[1]); + if (cbp != NULL) { + p = cbp->textq.cqh_first->lb; + tlen = cbp->textq.cqh_first->len; + } else + tlen = strlen(p); + + /* Allocate and initialize the TAGQ structure. */ + CALLOC(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + tlen + 3); + if (tqp == NULL) + return (NULL); + CIRCLEQ_INIT(&tqp->tagq); + tqp->tag = tqp->buf; + tqp->tag[0] = pattern[0]; + tqp->tag[1] = ' '; + tqp->tlen = tlen + 2; + memcpy(tqp->tag + 2, p, tlen); + tqp->tag[tlen + 2] = '\0'; + F_SET(tqp, TAG_CSCOPE); + + return (tqp); +} + +/* + * parse -- + * Parse the cscope output. + */ +static int +parse(sp, csc, tqp, matchesp) + SCR *sp; + CSC *csc; + TAGQ *tqp; + int *matchesp; +{ + TAG *tp; + recno_t slno; + size_t dlen, nlen, slen; + int ch, i, isolder, nlines; + char *dname, *name, *search, *p, *t, dummy[2], buf[2048]; + + for (;;) { + if (!fgets(buf, sizeof(buf), csc->from_fp)) + goto io_err; + + /* + * If the database is out of date, or there's some other + * problem, cscope will output error messages before the + * number-of-lines output. Display/discard any output + * that doesn't match what we want. + */ +#define CSCOPE_NLINES_FMT "cscope: %d lines%1[\n]" + if (sscanf(buf, CSCOPE_NLINES_FMT, &nlines, dummy) == 2) + break; + if ((p = strchr(buf, '\n')) != NULL) + *p = '\0'; + msgq(sp, M_ERR, "%s: \"%s\"", csc->dname, buf); + } + + while (nlines--) { + if (fgets(buf, sizeof(buf), csc->from_fp) == NULL) + goto io_err; + + /* If the line's too long for the buffer, discard it. */ + if ((p = strchr(buf, '\n')) == NULL) { + while ((ch = getc(csc->from_fp)) != EOF && ch != '\n'); + continue; + } + *p = '\0'; + + /* + * The cscope output is in the following format: + * + * <filename> <context> <line number> <pattern> + * + * Figure out how long everything is so we can allocate in one + * swell foop, but discard anything that looks wrong. + */ + for (p = buf, i = 0; + i < 3 && (t = strsep(&p, "\t ")) != NULL; ++i) + switch (i) { + case 0: /* Filename. */ + name = t; + nlen = strlen(name); + break; + case 1: /* Context. */ + break; + case 2: /* Line number. */ + slno = (recno_t)atol(t); + break; + } + if (i != 3 || p == NULL || t == NULL) + continue; + + /* The rest of the string is the search pattern. */ + search = p; + slen = strlen(p); + + /* Resolve the file name. */ + csc_file(sp, csc, name, &dname, &dlen, &isolder); + + /* + * If the file is older than the cscope database, that is, + * the database was built since the file was last modified, + * or there wasn't a search string, use the line number. + */ + if (isolder || strcmp(search, "<unknown>") == 0) { + search = NULL; + slen = 0; + } + + /* + * Allocate and initialize a tag structure plus the variable + * length cscope information that follows it. + */ + CALLOC_RET(sp, tp, + TAG *, 1, sizeof(TAG) + dlen + 2 + nlen + 1 + slen + 1); + tp->fname = tp->buf; + if (dlen != 0) { + memcpy(tp->fname, dname, dlen); + tp->fname[dlen] = '/'; + ++dlen; + } + memcpy(tp->fname + dlen, name, nlen + 1); + tp->fnlen = dlen + nlen; + tp->slno = slno; + if (slen != 0) { + tp->search = tp->fname + tp->fnlen + 1; + memcpy(tp->search, search, (tp->slen = slen) + 1); + } + CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q); + + ++*matchesp; + } + + (void)read_prompt(sp, csc); + return (0); + +io_err: if (feof(csc->from_fp)) + errno = EIO; + msgq_str(sp, M_SYSERR, "%s", csc->dname); + terminate(sp, csc, 0); + return (1); +} + +/* + * csc_file -- + * Search for the right path to this file. + */ +static void +csc_file(sp, csc, name, dirp, dlenp, isolderp) + SCR *sp; + CSC *csc; + char *name, **dirp; + size_t *dlenp; + int *isolderp; +{ + struct stat sb; + char **pp, buf[MAXPATHLEN]; + + /* + * Check for the file in all of the listed paths. If we don't + * find it, we simply return it unchanged. We have to do this + * now, even though it's expensive, because if the user changes + * directories, we can't change our minds as to where the file + * lives. + */ + for (pp = csc->paths; *pp != NULL; ++pp) { + (void)snprintf(buf, sizeof(buf), "%s/%s", *pp, name); + if (stat(buf, &sb) == 0) { + *dirp = *pp; + *dlenp = strlen(*pp); + *isolderp = sb.st_mtime < csc->mtime; + return; + } + } + *dlenp = 0; +} + +/* + * cscope_help -- + * The cscope help command. + */ +static int +cscope_help(sp, cmdp, subcmd) + SCR *sp; + EXCMD *cmdp; + char *subcmd; +{ + return (csc_help(sp, subcmd)); +} + +/* + * csc_help -- + * Display help/usage messages. + */ +static int +csc_help(sp, cmd) + SCR *sp; + char *cmd; +{ + CC const *ccp; + + if (cmd != NULL && *cmd != '\0') + if ((ccp = lookup_ccmd(cmd)) == NULL) { + ex_printf(sp, + "%s doesn't match any cscope command\n", cmd); + return (1); + } else { + ex_printf(sp, + "Command: %s (%s)\n", ccp->name, ccp->help_msg); + ex_printf(sp, " Usage: %s\n", ccp->usage_msg); + return (0); + } + + ex_printf(sp, "cscope commands:\n"); + for (ccp = cscope_cmds; ccp->name != NULL; ++ccp) + ex_printf(sp, " %*s: %s\n", 5, ccp->name, ccp->help_msg); + return (0); +} + +/* + * cscope_kill -- + * The cscope kill command. + */ +static int +cscope_kill(sp, cmdp, cn) + SCR *sp; + EXCMD *cmdp; + char *cn; +{ + return (terminate(sp, NULL, atoi(cn))); +} + +/* + * terminate -- + * Detach from a cscope process. + */ +static int +terminate(sp, csc, n) + SCR *sp; + CSC *csc; + int n; +{ + EX_PRIVATE *exp; + int i, pstat; + + exp = EXP(sp); + + /* + * We either get a csc structure or a number. If not provided a + * csc structure, find the right one. + */ + if (csc == NULL) { + if (n < 1) + goto badno; + for (i = 1, csc = exp->cscq.lh_first; + csc != NULL; csc = csc->q.le_next, i++) + if (i == n) + break; + if (csc == NULL) { +badno: msgq(sp, M_ERR, "312|%d: no such cscope session", n); + return (1); + } + } + + /* + * XXX + * Theoretically, we have the only file descriptors to the process, + * so closing them should let it exit gracefully, deleting temporary + * files, etc. The original vi cscope integration sent the cscope + * connection a SIGTERM signal, so I'm not sure if closing the file + * descriptors is sufficient. + */ + if (csc->from_fp != NULL) + (void)fclose(csc->from_fp); + if (csc->to_fp != NULL) + (void)fclose(csc->to_fp); + (void)waitpid(csc->pid, &pstat, 0); + + /* Discard cscope connection information. */ + LIST_REMOVE(csc, q); + if (csc->pbuf != NULL) + free(csc->pbuf); + if (csc->paths != NULL) + free(csc->paths); + free(csc); + return (0); +} + +/* + * cscope_reset -- + * The cscope reset command. + */ +static int +cscope_reset(sp, cmdp, notusedp) + SCR *sp; + EXCMD *cmdp; + char *notusedp; +{ + EX_PRIVATE *exp; + + for (exp = EXP(sp); exp->cscq.lh_first != NULL;) + if (cscope_kill(sp, cmdp, "1")) + return (1); + return (0); +} + +/* + * cscope_display -- + * Display current connections. + * + * PUBLIC: int cscope_display __P((SCR *)); + */ +int +cscope_display(sp) + SCR *sp; +{ + EX_PRIVATE *exp; + CSC *csc; + int i; + + exp = EXP(sp); + if (exp->cscq.lh_first == NULL) { + ex_printf(sp, "No cscope connections.\n"); + return (0); + } + for (i = 1, + csc = exp->cscq.lh_first; csc != NULL; ++i, csc = csc->q.le_next) + ex_printf(sp, + "%2d %s (process %lu)\n", i, csc->dname, (u_long)csc->pid); + return (0); +} + +/* + * cscope_search -- + * Search a file for a cscope entry. + * + * PUBLIC: int cscope_search __P((SCR *, TAGQ *, TAG *)); + */ +int +cscope_search(sp, tqp, tp) + SCR *sp; + TAGQ *tqp; + TAG *tp; +{ + MARK m; + + /* If we don't have a search pattern, use the line number. */ + if (tp->search == NULL) { + if (!db_exist(sp, tp->slno)) { + tag_msg(sp, TAG_BADLNO, tqp->tag); + return (1); + } + m.lno = tp->slno; + } 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; + if (f_search(sp, &m, &m, + tp->search, tp->slen, NULL, SEARCH_CSCOPE | SEARCH_FILE)) { + tag_msg(sp, TAG_SEARCH, tqp->tag); + return (1); + } + + /* + * !!! + * Historically, tags set the search direction if it wasn't + * already set. + */ + if (sp->searchdir == NOTSET) + sp->searchdir = FORWARD; + } + + /* + * !!! + * Tags move to the first non-blank, NOT the search pattern start. + */ + sp->lno = m.lno; + sp->cno = 0; + (void)nonblank(sp, sp->lno, &sp->cno); + return (0); +} + + +/* + * lookup_ccmd -- + * Return a pointer to the command structure. + */ +static CC const * +lookup_ccmd(name) + char *name; +{ + CC const *ccp; + size_t len; + + len = strlen(name); + for (ccp = cscope_cmds; ccp->name != NULL; ++ccp) + if (strncmp(name, ccp->name, len) == 0) + return (ccp); + return (NULL); +} + +/* + * read_prompt -- + * Read a prompt from cscope. + */ +static int +read_prompt(sp, csc) + SCR *sp; + CSC *csc; +{ + int ch; + +#define CSCOPE_PROMPT ">> " + for (;;) { + while ((ch = + getc(csc->from_fp)) != EOF && ch != CSCOPE_PROMPT[0]); + if (ch == EOF) { + terminate(sp, csc, 0); + return (1); + } + if (getc(csc->from_fp) != CSCOPE_PROMPT[1]) + continue; + if (getc(csc->from_fp) != CSCOPE_PROMPT[2]) + continue; + break; + } + return (0); +} diff --git a/ex/ex_delete.c b/ex/ex_delete.c new file mode 100644 index 000000000000..58734bbcfce4 --- /dev/null +++ b/ex/ex_delete.c @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_delete.c 10.9 (Berkeley) 10/23/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" + +/* + * ex_delete: [line [,line]] d[elete] [buffer] [count] [flags] + * + * Delete lines from the file. + * + * PUBLIC: int ex_delete __P((SCR *, EXCMD *)); + */ +int +ex_delete(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + recno_t lno; + + NEEDFILE(sp, cmdp); + + /* + * !!! + * 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, + FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL, + &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE)) + return (1); + + /* Delete the lines. */ + if (del(sp, &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 (db_last(sp, &lno)) + return (1); + if (sp->lno > lno) + sp->lno = lno; + return (0); +} diff --git a/ex/ex_display.c b/ex/ex_display.c new file mode 100644 index 000000000000..75315170f95e --- /dev/null +++ b/ex/ex_display.c @@ -0,0 +1,145 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_display.c 10.12 (Berkeley) 4/10/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> + +#include "../common/common.h" +#include "tag.h" + +static int bdisplay __P((SCR *)); +static void db __P((SCR *, CB *, CHAR_T *)); + +/* + * ex_display -- :display b[uffers] | c[onnections] | s[creens] | t[ags] + * + * Display cscope connections, buffers, tags or screens. + * + * PUBLIC: int ex_display __P((SCR *, EXCMD *)); + */ +int +ex_display(sp, cmdp) + SCR *sp; + EXCMD *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)); + case 'c': +#undef ARG +#define ARG "connections" + if (cmdp->argv[0]->len >= sizeof(ARG) || + memcmp(cmdp->argv[0]->bp, ARG, cmdp->argv[0]->len)) + break; + return (cscope_display(sp)); + 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)); + 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_tag_display(sp)); + } + ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); + return (1); +} + +/* + * bdisplay -- + * + * Display buffers. + */ +static int +bdisplay(sp) + SCR *sp; +{ + CB *cbp; + + if (sp->gp->cutq.lh_first == NULL && sp->gp->dcbp == NULL) { + msgq(sp, M_INFO, "123|No cut buffers to display"); + return (0); + } + + /* 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; + GS *gp; + TEXT *tp; + size_t len; + + gp = sp->gp; + (void)ex_printf(sp, "********** %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_puts(sp, KEY_NAME(sp, *p)); + if (INTERRUPTED(sp)) + return; + } + (void)ex_puts(sp, "\n"); + } +} diff --git a/ex/ex_edit.c b/ex/ex_edit.c new file mode 100644 index 000000000000..8b18e0f0bbca --- /dev/null +++ b/ex/ex_edit.c @@ -0,0 +1,153 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_edit.c 10.10 (Berkeley) 4/27/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "../vi/vi.h" + +static int ex_N_edit __P((SCR *, EXCMD *, FREF *, int)); + +/* + * ex_edit -- :e[dit][!] [+cmd] [file] + * :ex[!] [+cmd] [file] + * :vi[sual][!] [+cmd] [file] + * + * Edit a file; if none specified, re-edit the current file. The third + * 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. This seems unreasonable, so we support it + * regardless. + * + * PUBLIC: int ex_edit __P((SCR *, EXCMD *)); + */ +int +ex_edit(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + FREF *frp; + int attach, setalt; + + 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 + * (or wasn't editing any file), create another one. The + * reason for not reusing temporary files is that there is + * special exit processing of them, and reuse is tricky. + */ + frp = sp->frp; + if (sp->ep == NULL || F_ISSET(frp, FR_TMPFILE)) { + if ((frp = file_add(sp, NULL)) == NULL) + return (1); + attach = 0; + } else + attach = 1; + setalt = 0; + break; + case 1: + if ((frp = file_add(sp, cmdp->argv[0]->bp)) == NULL) + return (1); + attach = 0; + setalt = 1; + set_alt_name(sp, cmdp->argv[0]->bp); + break; + default: + abort(); + } + + if (F_ISSET(cmdp, E_NEWSCREEN)) + return (ex_N_edit(sp, cmdp, frp, attach)); + + /* + * Check for modifications. + * + * !!! + * Contrary to POSIX 1003.2-1992, autowrite did not affect :edit. + */ + if (file_m2(sp, FL_ISSET(cmdp->iflags, E_C_FORCE))) + return (1); + + /* Switch files. */ + if (file_init(sp, frp, NULL, (setalt ? FS_SETALT : 0) | + (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) + return (1); + + F_SET(sp, SC_FSWITCH); + return (0); +} + +/* + * ex_N_edit -- + * New screen version of ex_edit. + */ +static int +ex_N_edit(sp, cmdp, frp, attach) + SCR *sp; + EXCMD *cmdp; + FREF *frp; + int attach; +{ + SCR *new; + + /* Get a new screen. */ + if (screen_init(sp->gp, sp, &new)) + return (1); + if (vs_split(sp, new, 0)) { + (void)screen_end(new); + return (1); + } + + /* Get a backing file. */ + if (attach) { + /* Copy file state, keep the screen and cursor the same. */ + new->ep = sp->ep; + ++new->ep->refcnt; + + new->frp = frp; + new->frp->flags = sp->frp->flags; + + new->lno = sp->lno; + new->cno = sp->cno; + } else if (file_init(new, frp, NULL, + (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) { + (void)vs_discard(new, NULL); + (void)screen_end(new); + return (1); + } + + /* Create the argument list. */ + new->cargv = new->argv = ex_buildargv(sp, NULL, frp->name); + + /* Set up the switch. */ + sp->nextdisp = new; + F_SET(sp, SC_SSWITCH); + + return (0); +} diff --git a/ex/ex_equal.c b/ex/ex_equal.c new file mode 100644 index 000000000000..565df66bd0a9 --- /dev/null +++ b/ex/ex_equal.c @@ -0,0 +1,59 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_equal.c 10.10 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" + +/* + * ex_equal -- :address = + * + * PUBLIC: int ex_equal __P((SCR *, EXCMD *)); + */ +int +ex_equal(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + recno_t lno; + + NEEDFILE(sp, cmdp); + + /* + * 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_ADDR_DEF)) { + if (db_last(sp, &lno)) + return (1); + } else + lno = cmdp->addr1.lno; + + (void)ex_printf(sp, "%ld\n", lno); + return (0); +} diff --git a/ex/ex_file.c b/ex/ex_file.c new file mode 100644 index 000000000000..3492f9c7bf0d --- /dev/null +++ b/ex/ex_file.c @@ -0,0 +1,80 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_file.c 10.12 (Berkeley) 7/12/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" + +/* + * ex_file -- :f[ile] [name] + * Change the file's name and display the status line. + * + * PUBLIC: int ex_file __P((SCR *, EXCMD *)); + */ +int +ex_file(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + CHAR_T *p; + FREF *frp; + + NEEDFILE(sp, cmdp); + + 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 file has a real name, it's no longer a temporary, + * clear the temporary file flags. + */ + F_CLR(frp, FR_TMPEXIT | FR_TMPFILE); + + /* Have to force a write if the file exists, next time. */ + F_SET(frp, FR_NAMECHANGE); + + /* Notify the screen. */ + (void)sp->gp->scr_rename(sp, sp->frp->name, 1); + break; + default: + abort(); + } + msgq_status(sp, sp->lno, MSTAT_SHOWLAST); + return (0); +} diff --git a/ex/ex_filter.c b/ex/ex_filter.c new file mode 100644 index 000000000000..2e86e58b3ba2 --- /dev/null +++ b/ex/ex_filter.c @@ -0,0 +1,316 @@ +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1991, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_filter.c 10.34 (Berkeley) 10/23/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" + +static int filter_ldisplay __P((SCR *, FILE *)); + +/* + * ex_filter -- + * Run a range of lines through a filter utility and optionally + * replace the original text with the stdout/stderr output of + * the utility. + * + * PUBLIC: int ex_filter __P((SCR *, + * PUBLIC: EXCMD *, MARK *, MARK *, MARK *, char *, enum filtertype)); + */ +int +ex_filter(sp, cmdp, fm, tm, rp, cmd, ftype) + SCR *sp; + EXCMD *cmdp; + 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; + char *name; + + rval = 0; + + /* Set return cursor position, which is never less than line 1. */ + *rp = *fm; + if (rp->lno == 0) + rp->lno = 1; + + /* We're going to need a shell. */ + if (opts_empty(sp, O_SHELL, 0)) + return (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]. + * + * !!! + * Historically, in the FILTER_READ case, the utility reads from + * the terminal (e.g. :r! cat works). Otherwise open up utility + * input pipe. + */ + ofp = NULL; + input[0] = input[1] = output[0] = output[1] = -1; + if (ftype != FILTER_READ && 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; + } + + /* Fork off the utility process. */ + switch (utility_pid = vfork()) { + case -1: /* Error. */ + 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]); + return (1); + case 0: /* Utility. */ + /* + * 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 in both ex and vi for consistency. + */ + if (input[0] != -1) + (void)dup2(input[0], STDIN_FILENO); + (void)dup2(output[1], STDOUT_FILENO); + (void)dup2(output[1], STDERR_FILENO); + + /* Close the utility's file descriptors. */ + if (input[0] != -1) + (void)close(input[0]); + if (input[1] != -1) + (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_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s"); + _exit (127); + /* NOTREACHED */ + default: /* Parent-reader, parent-writer. */ + /* Close the pipe ends neither parent will use. */ + if (input[0] != -1) + (void)close(input[0]); + (void)close(output[1]); + break; + } + + /* + * FILTER_RBANG, 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. + * + * For FILTER_RBANG, there is nothing to write to the utility. + * Make sure it doesn't wait forever by closing its standard + * input. + * + * !!! + * Set the return cursor to the last line read in for FILTER_READ. + * 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_RBANG || ftype == FILTER_READ) { + if (ftype == FILTER_RBANG) + (void)close(input[1]); + + if (ex_readfp(sp, "filter", ofp, fm, &nread, 1)) + rval = 1; + sp->rptlines[L_ADDED] += nread; + if (ftype == FILTER_READ) + if (fm->lno == 0) + rp->lno = nread; + else + rp->lno += nread; + goto uwait; + } + + /* + * FILTER_BANG, 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_BANG: + * 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:db_get() code, based on the flag set + * here. + */ + F_SET(sp->ep, F_MULTILOCK); + switch (parent_writer_pid = fork()) { + case -1: /* Error. */ + 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, "filter", ifp, fm, tm, NULL, NULL, 1)); + + /* NOTREACHED */ + default: /* Parent-reader. */ + (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. + */ + if (filter_ldisplay(sp, ofp)) + rval = 1; + } else { + /* + * Read the output from the read end of the output + * pipe. Ex_readfp appends to the MARK and closes + * ofp. + */ + if (ex_readfp(sp, "filter", ofp, tm, &nread, 1)) + rval = 1; + sp->rptlines[L_ADDED] += nread; + } + + /* Wait for the parent-writer. */ + if (proc_wait(sp, + (long)parent_writer_pid, "parent-writer", 0, 1)) + rval = 1; + + /* Delete any lines written to the utility. */ + if (rval == 0 && ftype == FILTER_BANG && + (cut(sp, NULL, fm, tm, CUT_LINEMODE) || + del(sp, 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 && !db_exist(sp, rp->lno)) + --rp->lno; + break; + } + F_CLR(sp->ep, F_MULTILOCK); + + /* + * !!! + * Ignore errors on vi file reads, to make reads prettier. It's + * completely inconsistent, and historic practice. + */ +uwait: return (proc_wait(sp, (long)utility_pid, cmd, + ftype == FILTER_READ && F_ISSET(sp, SC_VI) ? 1 : 0, 0) || rval); +} + +/* + * 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; + + for (exp = EXP(sp); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp);) + if (ex_ldisplay(sp, exp->ibp, len, 0, 0)) + break; + if (ferror(fp)) + msgq(sp, M_SYSERR, "filter read"); + (void)fclose(fp); + return (0); +} diff --git a/ex/ex_global.c b/ex/ex_global.c new file mode 100644 index 000000000000..aba9dc5fabe3 --- /dev/null +++ b/ex/ex_global.c @@ -0,0 +1,328 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_global.c 10.22 (Berkeley) 10/10/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" + +enum which {GLOBAL, V}; + +static int ex_g_setup __P((SCR *, EXCMD *, enum which)); + +/* + * ex_global -- [line [,line]] g[lobal][!] /pattern/ [commands] + * Exec on lines matching a pattern. + * + * PUBLIC: int ex_global __P((SCR *, EXCMD *)); + */ +int +ex_global(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + return (ex_g_setup(sp, + cmdp, FL_ISSET(cmdp->iflags, E_C_FORCE) ? V : GLOBAL)); +} + +/* + * ex_v -- [line [,line]] v /pattern/ [commands] + * Exec on lines not matching a pattern. + * + * PUBLIC: int ex_v __P((SCR *, EXCMD *)); + */ +int +ex_v(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + return (ex_g_setup(sp, cmdp, V)); +} + +/* + * ex_g_setup -- + * Ex global and v commands. + */ +static int +ex_g_setup(sp, cmdp, cmd) + SCR *sp; + EXCMD *cmdp; + enum which cmd; +{ + CHAR_T *ptrn, *p, *t; + EXCMD *ecp; + MARK abs; + RANGE *rp; + busy_t btype; + recno_t start, end; + regex_t *re; + regmatch_t match[1]; + size_t len; + int cnt, delim, eval; + char *dbp; + + NEEDFILE(sp, cmdp); + + if (F_ISSET(sp, SC_EX_GLOBAL)) { + msgq(sp, M_ERR, + "124|The %s command can't be used as part of a global or v command", + cmdp->cmd->name); + return (1); + } + + /* + * Skip leading white space. Historic vi allowed any non-alphanumeric + * to serve as the global command delimiter. + */ + if (cmdp->argc == 0) + goto usage; + for (p = cmdp->argv[0]->bp; isblank(*p); ++p); + if (*p == '\0' || isalnum(*p) || + *p == '\\' || *p == '|' || *p == '\n') { +usage: ex_emsg(sp, cmdp->cmd->usage, EXM_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] == '\\') + if (p[1] == delim) + ++p; + else if (p[1] == '\\') + *t++ = *p++; + *t++ = *p++; + } + + /* If the pattern string is empty, use the last one. */ + if (*ptrn == '\0') { + if (sp->re == NULL) { + ex_emsg(sp, NULL, EXM_NOPREVRE); + return (1); + } + + /* Re-compile the RE if necessary. */ + if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, + sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) + return (1); + } else { + /* Compile the RE. */ + if (re_compile(sp, ptrn, t - ptrn, + &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH)) + return (1); + + /* + * Set saved RE. Historic practice is that globals set + * direction as well as the RE. + */ + sp->searchdir = FORWARD; + } + re = &sp->re_c; + + /* The global commands always set the previous context mark. */ + abs.lno = sp->lno; + abs.cno = sp->cno; + if (mark_set(sp, ABSMARK1, &abs, 1)) + return (1); + + /* Get an EXCMD structure. */ + CALLOC_RET(sp, ecp, EXCMD *, 1, sizeof(EXCMD)); + CIRCLEQ_INIT(&ecp->rq); + + /* + * Get a copy of the command string; the default command is print. + * Don't worry about a set of <blank>s with no command, that will + * default to print in the ex parser. We need to have two copies + * because the ex parser may step on the command string when it's + * parsing it. + */ + if ((len = cmdp->argv[0]->len - (p - cmdp->argv[0]->bp)) == 0) { + p = "pp"; + len = 1; + } + + MALLOC_RET(sp, ecp->cp, char *, len * 2); + ecp->o_cp = ecp->cp; + ecp->o_clen = len; + memcpy(ecp->cp + len, p, len); + ecp->range_lno = OOBLNO; + FL_SET(ecp->agv_flags, cmd == GLOBAL ? AGV_GLOBAL : AGV_V); + LIST_INSERT_HEAD(&sp->gp->ecq, ecp, q); + + /* + * 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. + */ + btype = BUSY_ON; + cnt = INTERRUPT_CHECK; + for (start = cmdp->addr1.lno, + end = cmdp->addr2.lno; start <= end; ++start) { + if (cnt-- == 0) { + if (INTERRUPTED(sp)) { + LIST_REMOVE(ecp, q); + free(ecp->cp); + free(ecp); + break; + } + search_busy(sp, btype); + btype = BUSY_UPDATE; + cnt = INTERRUPT_CHECK; + } + if (db_get(sp, start, DBG_FATAL, &dbp, &len)) + return (1); + match[0].rm_so = 0; + match[0].rm_eo = len; + switch (eval = + regexec(&sp->re_c, dbp, 0, match, REG_STARTEND)) { + case 0: + if (cmd == V) + continue; + break; + case REG_NOMATCH: + if (cmd == GLOBAL) + continue; + break; + default: + re_error(sp, eval, &sp->re_c); + break; + } + + /* If follows the last entry, extend the last entry's range. */ + if ((rp = ecp->rq.cqh_last) != (void *)&ecp->rq && + rp->stop == start - 1) { + ++rp->stop; + continue; + } + + /* Allocate a new range, and append it to the list. */ + CALLOC(sp, rp, RANGE *, 1, sizeof(RANGE)); + if (rp == NULL) + return (1); + rp->start = rp->stop = start; + CIRCLEQ_INSERT_TAIL(&ecp->rq, rp, q); + } + search_busy(sp, BUSY_OFF); + return (0); +} + +/* + * ex_g_insdel -- + * Update the ranges based on an insertion or deletion. + * + * PUBLIC: int ex_g_insdel __P((SCR *, lnop_t, recno_t)); + */ +int +ex_g_insdel(sp, op, lno) + SCR *sp; + lnop_t op; + recno_t lno; +{ + EXCMD *ecp; + RANGE *nrp, *rp; + + /* All insert/append operations are done as inserts. */ + if (op == LINE_APPEND) + abort(); + + if (op == LINE_RESET) + return (0); + + for (ecp = sp->gp->ecq.lh_first; ecp != NULL; ecp = ecp->q.le_next) { + if (!FL_ISSET(ecp->agv_flags, AGV_AT | AGV_GLOBAL | AGV_V)) + continue; + for (rp = ecp->rq.cqh_first; rp != (void *)&ecp->rq; 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 or + * increment the range. + */ + if (rp->start > lno) { + if (op == LINE_DELETE) { + --rp->start; + --rp->stop; + } else { + ++rp->start; + ++rp->stop; + } + continue; + } + + /* + * Lno is inside the range, decrement the end point + * for deletion, and split the range for insertion. + * In the latter case, since we're inserting a new + * element, neither range can be exhausted. + */ + if (op == LINE_DELETE) { + if (rp->start > --rp->stop) { + CIRCLEQ_REMOVE(&ecp->rq, rp, q); + free(rp); + } + } else { + CALLOC_RET(sp, nrp, RANGE *, 1, sizeof(RANGE)); + nrp->start = lno + 1; + nrp->stop = rp->stop + 1; + rp->stop = lno - 1; + CIRCLEQ_INSERT_AFTER(&ecp->rq, rp, nrp, q); + rp = nrp; + } + } + + /* + * If the command deleted/inserted lines, the cursor moves to + * the line after the deleted/inserted line. + */ + ecp->range_lno = lno; + } + return (0); +} diff --git a/ex/ex_init.c b/ex/ex_init.c new file mode 100644 index 000000000000..6a78416a3344 --- /dev/null +++ b/ex/ex_init.c @@ -0,0 +1,417 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_init.c 10.26 (Berkeley) 8/12/96"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/types.h> /* XXX: param.h may not have included types.h */ +#include <sys/queue.h> +#include <sys/stat.h> + +#include <bitstring.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" +#include "tag.h" +#include "pathnames.h" + +enum rc { NOEXIST, NOPERM, RCOK }; +static enum rc exrc_isok __P((SCR *, struct stat *, char *, int, int)); + +static int ex_run_file __P((SCR *, char *)); + +/* + * ex_screen_copy -- + * Copy ex screen. + * + * PUBLIC: int ex_screen_copy __P((SCR *, SCR *)); + */ +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. */ + CIRCLEQ_INIT(&nexp->tq); + TAILQ_INIT(&nexp->tagfq); + LIST_INIT(&nexp->cscq); + + if (orig == NULL) { + } else { + oexp = EXP(orig); + + if (oexp->lastbcomm != NULL && + (nexp->lastbcomm = strdup(oexp->lastbcomm)) == NULL) { + msgq(sp, M_SYSERR, NULL); + return(1); + } + if (ex_tag_copy(orig, sp)) + return (1); + } + return (0); +} + +/* + * ex_screen_end -- + * End a vi screen. + * + * PUBLIC: int ex_screen_end __P((SCR *)); + */ +int +ex_screen_end(sp) + SCR *sp; +{ + EX_PRIVATE *exp; + int rval; + + if ((exp = EXP(sp)) == NULL) + return (0); + + rval = 0; + + /* Close down script connections. */ + if (F_ISSET(sp, SC_SCRIPT) && sscr_end(sp)) + rval = 1; + + if (argv_free(sp)) + rval = 1; + + if (exp->ibp != NULL) + free(exp->ibp); + + if (exp->lastbcomm != NULL) + free(exp->lastbcomm); + + if (ex_tag_free(sp)) + rval = 1; + + /* Free private memory. */ + free(exp); + sp->ex_private = NULL; + + return (rval); +} + +/* + * ex_optchange -- + * Handle change of options for ex. + * + * PUBLIC: int ex_optchange __P((SCR *, int, char *, u_long *)); + */ +int +ex_optchange(sp, offset, str, valp) + SCR *sp; + int offset; + char *str; + u_long *valp; +{ + switch (offset) { + case O_TAGS: + return (ex_tagf_alloc(sp, str)); + } + return (0); +} + +/* + * ex_exrc -- + * Read the EXINIT environment variable and the startup exrc files, + * and execute their commands. + * + * PUBLIC: int ex_exrc __P((SCR *)); + */ +int +ex_exrc(sp) + SCR *sp; +{ + struct stat hsb, lsb; + char *p, path[MAXPATHLEN]; + + /* + * Source the system, environment, $HOME and local .exrc values. + * Vi historically didn't check $HOME/.exrc if the environment + * variable EXINIT was set. This is all done before the file is + * read in, because things in the .exrc information can set, for + * example, the recovery directory. + * + * !!! + * While nvi can handle any of the options settings of historic vi, + * the converse is not true. Since users are going to have to have + * files and environmental variables that work with both, we use nvi + * versions of both the $HOME and local startup files if they exist, + * otherwise the historic ones. + * + * !!! + * For a discussion of permissions and when what .exrc files are + * read, see the comment above the exrc_isok() function below. + * + * !!! + * If the user started the historic of vi in $HOME, vi read the user's + * .exrc file twice, as $HOME/.exrc and as ./.exrc. We avoid this, as + * it's going to make some commands behave oddly, and I can't imagine + * anyone depending on it. + */ + switch (exrc_isok(sp, &hsb, _PATH_SYSEXRC, 1, 0)) { + case NOEXIST: + case NOPERM: + break; + case RCOK: + if (ex_run_file(sp, _PATH_SYSEXRC)) + return (1); + break; + } + + /* Run the commands. */ + if (EXCMD_RUNNING(sp->gp)) + (void)ex_cmd(sp); + if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) + return (0); + + if ((p = getenv("NEXINIT")) != NULL) { + if (ex_run_str(sp, "NEXINIT", p, strlen(p), 1, 0)) + return (1); + } else if ((p = getenv("EXINIT")) != NULL) { + if (ex_run_str(sp, "EXINIT", p, strlen(p), 1, 0)) + return (1); + } else if ((p = getenv("HOME")) != NULL && *p) { + (void)snprintf(path, sizeof(path), "%s/%s", p, _PATH_NEXRC); + switch (exrc_isok(sp, &hsb, path, 0, 1)) { + case NOEXIST: + (void)snprintf(path, + sizeof(path), "%s/%s", p, _PATH_EXRC); + if (exrc_isok(sp, + &hsb, path, 0, 1) == RCOK && ex_run_file(sp, path)) + return (1); + break; + case NOPERM: + break; + case RCOK: + if (ex_run_file(sp, path)) + return (1); + break; + } + } + + /* Run the commands. */ + if (EXCMD_RUNNING(sp->gp)) + (void)ex_cmd(sp); + if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) + return (0); + + /* Previous commands may have set the exrc option. */ + if (O_ISSET(sp, O_EXRC)) { + switch (exrc_isok(sp, &lsb, _PATH_NEXRC, 0, 0)) { + case NOEXIST: + if (exrc_isok(sp, &lsb, _PATH_EXRC, 0, 0) == RCOK && + (lsb.st_dev != hsb.st_dev || + lsb.st_ino != hsb.st_ino) && + ex_run_file(sp, _PATH_EXRC)) + return (1); + break; + case NOPERM: + break; + case RCOK: + if ((lsb.st_dev != hsb.st_dev || + lsb.st_ino != hsb.st_ino) && + ex_run_file(sp, _PATH_NEXRC)) + return (1); + break; + } + /* Run the commands. */ + if (EXCMD_RUNNING(sp->gp)) + (void)ex_cmd(sp); + if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) + return (0); + } + + return (0); +} + +/* + * ex_run_file -- + * Set up a file of ex commands to run. + */ +static int +ex_run_file(sp, name) + SCR *sp; + char *name; +{ + ARGS *ap[2], a; + EXCMD cmd; + + ex_cinit(&cmd, C_SOURCE, 0, OOBLNO, OOBLNO, 0, ap); + ex_cadd(&cmd, &a, name, strlen(name)); + return (ex_source(sp, &cmd)); +} + +/* + * ex_run_str -- + * Set up a string of ex commands to run. + * + * PUBLIC: int ex_run_str __P((SCR *, char *, char *, size_t, int, int)); + */ +int +ex_run_str(sp, name, str, len, ex_flags, nocopy) + SCR *sp; + char *name, *str; + size_t len; + int ex_flags, nocopy; +{ + GS *gp; + EXCMD *ecp; + + gp = sp->gp; + if (EXCMD_RUNNING(gp)) { + CALLOC_RET(sp, ecp, EXCMD *, 1, sizeof(EXCMD)); + LIST_INSERT_HEAD(&gp->ecq, ecp, q); + } else + ecp = &gp->excmd; + + F_INIT(ecp, + ex_flags ? E_BLIGNORE | E_NOAUTO | E_NOPRDEF | E_VLITONLY : 0); + + if (nocopy) + ecp->cp = str; + else + if ((ecp->cp = v_strdup(sp, str, len)) == NULL) + return (1); + ecp->clen = len; + + if (name == NULL) + ecp->if_name = NULL; + else { + if ((ecp->if_name = v_strdup(sp, name, strlen(name))) == NULL) + return (1); + ecp->if_lno = 1; + F_SET(ecp, E_NAMEDISCARD); + } + + return (0); +} + +/* + * exrc_isok -- + * Check a .exrc file for source-ability. + * + * !!! + * Historically, vi read the $HOME and local .exrc files if they were owned + * by the user's real ID, or the "sourceany" option was set, regardless of + * any other considerations. We no longer support the sourceany option as + * it's a security problem of mammoth proportions. We require the system + * .exrc file to be owned by root, the $HOME .exrc file to be owned by the + * user's effective ID (or that the user's effective ID be root) and the + * local .exrc files to be owned by the user's effective ID. In all cases, + * the file cannot be writeable by anyone other than its owner. + * + * In O'Reilly ("Learning the VI Editor", Fifth Ed., May 1992, page 106), + * it notes that System V release 3.2 and later has an option "[no]exrc". + * The behavior is that local .exrc files are read only if the exrc option + * is set. The default for the exrc option was off, so, by default, local + * .exrc files were not read. The problem this was intended to solve was + * that System V permitted users to give away files, so there's no possible + * ownership or writeability test to ensure that the file is safe. + * + * POSIX 1003.2-1992 standardized exrc as an option. It required the exrc + * option to be off by default, thus local .exrc files are not to be read + * by default. The Rationale noted (incorrectly) that this was a change + * to historic practice, but correctly noted that a default of off improves + * system security. POSIX also required that vi check the effective user + * ID instead of the real user ID, which is why we've switched from historic + * practice. + * + * We initialize the exrc variable to off. If it's turned on by the system + * or $HOME .exrc files, and the local .exrc file passes the ownership and + * writeability tests, then we read it. This breaks historic 4BSD practice, + * but it gives us a measure of security on systems where users can give away + * files. + */ +static enum rc +exrc_isok(sp, sbp, path, rootown, rootid) + SCR *sp; + struct stat *sbp; + char *path; + int rootown, rootid; +{ + enum { ROOTOWN, OWN, WRITER } etype; + uid_t euid; + int nf1, nf2; + char *a, *b, buf[MAXPATHLEN]; + + /* Check for the file's existence. */ + if (stat(path, sbp)) + return (NOEXIST); + + /* Check ownership permissions. */ + euid = geteuid(); + if (!(rootown && sbp->st_uid == 0) && + !(rootid && euid == 0) && sbp->st_uid != euid) { + etype = rootown ? ROOTOWN : OWN; + goto denied; + } + + /* Check writeability. */ + if (sbp->st_mode & (S_IWGRP | S_IWOTH)) { + etype = WRITER; + goto denied; + } + return (RCOK); + +denied: a = msg_print(sp, path, &nf1); + if (strchr(path, '/') == NULL && getcwd(buf, sizeof(buf)) != NULL) { + b = msg_print(sp, buf, &nf2); + switch (etype) { + case ROOTOWN: + msgq(sp, M_ERR, + "125|%s/%s: not sourced: not owned by you or root", + b, a); + break; + case OWN: + msgq(sp, M_ERR, + "126|%s/%s: not sourced: not owned by you", b, a); + break; + case WRITER: + msgq(sp, M_ERR, + "127|%s/%s: not sourced: writeable by a user other than the owner", b, a); + break; + } + if (nf2) + FREE_SPACE(sp, b, 0); + } else + switch (etype) { + case ROOTOWN: + msgq(sp, M_ERR, + "128|%s: not sourced: not owned by you or root", a); + break; + case OWN: + msgq(sp, M_ERR, + "129|%s: not sourced: not owned by you", a); + break; + case WRITER: + msgq(sp, M_ERR, + "130|%s: not sourced: writeable by a user other than the owner", a); + break; + } + + if (nf1) + FREE_SPACE(sp, a, 0); + return (NOPERM); +} diff --git a/ex/ex_join.c b/ex/ex_join.c new file mode 100644 index 000000000000..c26c4243178a --- /dev/null +++ b/ex/ex_join.c @@ -0,0 +1,177 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_join.c 10.10 (Berkeley) 9/15/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" + +/* + * ex_join -- :[line [,line]] j[oin][!] [count] [flags] + * Join lines. + * + * PUBLIC: int ex_join __P((SCR *, EXCMD *)); + */ +int +ex_join(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + recno_t from, to; + size_t blen, clen, len, tlen; + int echar, extra, first; + char *bp, *p, *tbp; + + NEEDFILE(sp, cmdp); + + from = cmdp->addr1.lno; + to = cmdp->addr2.lno; + + /* Check for no lines to join. */ + if (!db_exist(sp, from + 1)) { + msgq(sp, M_ERR, "131|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 (FL_ISSET(cmdp->iflags, E_C_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 (db_get(sp, from, 0, &p, &len)) { + 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 ., insert two spaces. + * Else, insert one space. + * + * One change -- add ? and ! to the list of characters for + * which we insert two spaces. I expect that POSIX 1003.2 + * will require this as well. + * + * Echar is the last character in the last line joined. + */ + extra = 0; + if (!first && !FL_ISSET(cmdp->iflags, E_C_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) { + memcpy(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 (db_delete(sp, to)) + goto err; + + /* If the original line changed, reset it. */ + if (!first && db_set(sp, 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/ex/ex_map.c b/ex/ex_map.c new file mode 100644 index 000000000000..bc2cf0855919 --- /dev/null +++ b/ex/ex_map.c @@ -0,0 +1,121 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_map.c 10.9 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.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. + * + * PUBLIC: int ex_map __P((SCR *, EXCMD *)); + */ +int +ex_map(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + seq_t stype; + CHAR_T *input, *p; + + stype = FL_ISSET(cmdp->iflags, E_C_FORCE) ? SEQ_INPUT : SEQ_COMMAND; + + switch (cmdp->argc) { + case 0: + if (seq_dump(sp, stype, 1) == 0) + msgq(sp, M_INFO, stype == SEQ_INPUT ? + "132|No input map entries" : + "133|No command map entries"); + 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. If the screen specific routine has been set, + * call it as well. Note, the SEQ_FUNCMAP type is persistent across + * screen types, maybe the next screen type will get it right. + */ + if (input[0] == '#' && isdigit(input[1])) { + for (p = input + 2; 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 | SEQ_USERDEF)) + return (1); + return (sp->gp->scr_fmap == NULL ? 0 : + sp->gp->scr_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, + "134|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. + * + * PUBLIC: int ex_unmap __P((SCR *, EXCMD *)); + */ +int +ex_unmap(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + if (seq_delete(sp, cmdp->argv[0]->bp, cmdp->argv[0]->len, + FL_ISSET(cmdp->iflags, E_C_FORCE) ? SEQ_INPUT : SEQ_COMMAND)) { + msgq_str(sp, M_INFO, + cmdp->argv[0]->bp, "135|\"%s\" isn't currently mapped"); + return (1); + } + return (0); +} diff --git a/ex/ex_mark.c b/ex/ex_mark.c new file mode 100644 index 000000000000..08ad8c2aa30b --- /dev/null +++ b/ex/ex_mark.c @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_mark.c 10.8 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" + +/* + * ex_mark -- :mark char + * :k char + * Mark lines. + * + * + * PUBLIC: int ex_mark __P((SCR *, EXCMD *)); + */ +int +ex_mark(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + NEEDFILE(sp, cmdp); + + if (cmdp->argv[0]->len != 1) { + msgq(sp, M_ERR, "136|Mark names must be a single character"); + return (1); + } + return (mark_set(sp, cmdp->argv[0]->bp[0], &cmdp->addr1, 1)); +} diff --git a/ex/ex_mkexrc.c b/ex/ex_mkexrc.c new file mode 100644 index 000000000000..0eb15d408cf7 --- /dev/null +++ b/ex/ex_mkexrc.c @@ -0,0 +1,101 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_mkexrc.c 10.11 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/stat.h> + +#include <bitstring.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" +#include "pathnames.h" + +/* + * ex_mkexrc -- :mkexrc[!] [file] + * + * Create (or overwrite) a .exrc file with the current info. + * + * PUBLIC: int ex_mkexrc __P((SCR *, EXCMD *)); + */ +int +ex_mkexrc(sp, cmdp) + SCR *sp; + EXCMD *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 (!FL_ISSET(cmdp->iflags, E_C_FORCE) && !stat(fname, &sb)) { + msgq_str(sp, M_ERR, fname, + "137|%s exists, not written; use ! to override"); + 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_str(sp, M_SYSERR, fname, "%s"); + return (1); + } + + if ((fp = fdopen(fd, "w")) == NULL) { + sverrno = errno; + (void)close(fd); + goto e2; + } + + if (seq_save(sp, fp, "abbreviate ", SEQ_ABBREV) || ferror(fp)) + goto e1; + if (seq_save(sp, fp, "map ", SEQ_COMMAND) || ferror(fp)) + goto e1; + if (seq_save(sp, fp, "map! ", SEQ_INPUT) || ferror(fp)) + goto e1; + if (opts_save(sp, fp) || ferror(fp)) + goto e1; + if (fclose(fp)) { + sverrno = errno; + goto e2; + } + + msgq_str(sp, M_INFO, fname, "138|New exrc file: %s"); + return (0); + +e1: sverrno = errno; + (void)fclose(fp); +e2: errno = sverrno; + msgq_str(sp, M_SYSERR, fname, "%s"); + return (1); +} diff --git a/ex/ex_move.c b/ex/ex_move.c new file mode 100644 index 000000000000..d6e45c373964 --- /dev/null +++ b/ex/ex_move.c @@ -0,0 +1,198 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_move.c 10.10 (Berkeley) 9/15/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" + +/* + * ex_copy -- :[line [,line]] co[py] line [flags] + * Copy selected lines. + * + * PUBLIC: int ex_copy __P((SCR *, EXCMD *)); + */ +int +ex_copy(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + CB cb; + MARK fm1, fm2, m, tm; + recno_t cnt; + int rval; + + rval = 0; + + NEEDFILE(sp, cmdp); + + /* + * 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, 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, &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. + * + * PUBLIC: int ex_move __P((SCR *, EXCMD *)); + */ +int +ex_move(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + LMARK *lmp; + MARK fm1, fm2; + recno_t cnt, diff, fl, tl, mfl, mtl; + size_t blen, len; + int mark_reset; + char *bp, *p; + + NEEDFILE(sp, cmdp); + + /* + * 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, "139|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 = sp->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, 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 (db_get(sp, fl, DBG_FATAL, &p, &len)) + return (1); + BINC_RET(sp, bp, blen, len); + memcpy(bp, p, len); + if (db_append(sp, 1, tl, bp, len)) + return (1); + if (mark_reset) + for (lmp = sp->ep->marks.lh_first; + lmp != NULL; lmp = lmp->q.le_next) + if (lmp->name != ABSMARK1 && + lmp->lno == fl) + lmp->lno = tl + 1; + if (db_delete(sp, fl)) + return (1); + } + } else { /* Destination < source. */ + mfl = tl; + mtl = tl + diff; + for (cnt = diff; cnt--;) { + if (db_get(sp, fl, DBG_FATAL, &p, &len)) + return (1); + BINC_RET(sp, bp, blen, len); + memcpy(bp, p, len); + if (db_append(sp, 1, tl++, bp, len)) + return (1); + if (mark_reset) + for (lmp = sp->ep->marks.lh_first; + lmp != NULL; lmp = lmp->q.le_next) + if (lmp->name != ABSMARK1 && + lmp->lno == fl) + lmp->lno = tl; + ++fl; + if (db_delete(sp, 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 = sp->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, lmp); + + + sp->rptlines[L_MOVED] += diff; + return (0); +} diff --git a/ex/ex_open.c b/ex/ex_open.c new file mode 100644 index 000000000000..afffaeb2202d --- /dev/null +++ b/ex/ex_open.c @@ -0,0 +1,46 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_open.c 10.7 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" + +/* + * ex_open -- :[line] o[pen] [/pattern/] [flags] + * + * Switch to single line "open" mode. + * + * PUBLIC: int ex_open __P((SCR *, EXCMD *)); + */ +int +ex_open(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + /* If open option off, disallow open command. */ + if (!O_ISSET(sp, O_OPEN)) { + msgq(sp, M_ERR, + "140|The open command requires that the open option be set"); + return (1); + } + + msgq(sp, M_ERR, "141|The open command is not yet implemented"); + return (1); +} diff --git a/ex/ex_perl.c b/ex/ex_perl.c new file mode 100644 index 000000000000..e620352ab518 --- /dev/null +++ b/ex/ex_perl.c @@ -0,0 +1,69 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * Copyright (c) 1995 + * George V. Neville-Neil. All rights reserved. + * Copyright (c) 1996 + * Sven Verdoolaege. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_perl.c 8.10 (Berkeley) 9/15/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "../common/common.h" + +/* + * ex_perl -- :[line [,line]] perl [command] + * Run a command through the perl interpreter. + * + * ex_perldo -- :[line [,line]] perldo [command] + * Run a set of lines through the perl interpreter. + * + * PUBLIC: int ex_perl __P((SCR*, EXCMD *)); + */ +int +ex_perl(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ +#ifdef HAVE_PERL_INTERP + CHAR_T *p; + size_t len; + + /* Skip leading white space. */ + if (cmdp->argc != 0) + for (p = cmdp->argv[0]->bp, + len = cmdp->argv[0]->len; len > 0; --len, ++p) + if (!isblank(*p)) + break; + if (cmdp->argc == 0 || len == 0) { + ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); + return (1); + } + return (cmdp->cmd == &cmds[C_PERLCMD] ? + perl_ex_perl(sp, p, len, cmdp->addr1.lno, cmdp->addr2.lno) : + perl_ex_perldo(sp, p, len, cmdp->addr1.lno, cmdp->addr2.lno)); +#else + msgq(sp, M_ERR, "306|Vi was not loaded with a Perl interpreter"); + return (1); +#endif +} diff --git a/ex/ex_preserve.c b/ex/ex_preserve.c new file mode 100644 index 000000000000..5614c88d753a --- /dev/null +++ b/ex/ex_preserve.c @@ -0,0 +1,103 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_preserve.c 10.12 (Berkeley) 4/27/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> + +#include "../common/common.h" + +/* + * ex_preserve -- :pre[serve] + * Push the file to recovery. + * + * PUBLIC: int ex_preserve __P((SCR *, EXCMD *)); + */ +int +ex_preserve(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + recno_t lno; + + NEEDFILE(sp, cmdp); + + if (!F_ISSET(sp->ep, F_RCV_ON)) { + msgq(sp, M_ERR, "142|Preservation of this file not possible"); + return (1); + } + + /* If recovery not initialized, do so. */ + if (F_ISSET(sp->ep, F_FIRSTMODIFY) && rcv_init(sp)) + return (1); + + /* Force the file to be read in, in case it hasn't yet. */ + if (db_last(sp, &lno)) + return (1); + + /* Sync to disk. */ + if (rcv_sync(sp, RCV_SNAPSHOT)) + return (1); + + msgq(sp, M_INFO, "143|File preserved"); + return (0); +} + +/* + * ex_recover -- :rec[over][!] file + * Recover the file. + * + * PUBLIC: int ex_recover __P((SCR *, EXCMD *)); + */ +int +ex_recover(sp, cmdp) + SCR *sp; + EXCMD *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, FL_ISSET(cmdp->iflags, E_C_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, FS_SETALT | + (FL_ISSET(cmdp->iflags, E_C_FORCE) ? FS_FORCE : 0))) + return (1); + + F_SET(sp, SC_FSWITCH); + return (0); +} diff --git a/ex/ex_print.c b/ex/ex_print.c new file mode 100644 index 000000000000..4218e08e0978 --- /dev/null +++ b/ex/ex_print.c @@ -0,0 +1,352 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_print.c 10.18 (Berkeley) 5/12/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> + +#ifdef __STDC__ +#include <stdarg.h> +#else +#include <varargs.h> +#endif + +#include "../common/common.h" + +static int ex_prchars __P((SCR *, const char *, size_t *, size_t, u_int, int)); + +/* + * ex_list -- :[line [,line]] l[ist] [count] [flags] + * + * Display the addressed lines such that the output is unambiguous. + * + * PUBLIC: int ex_list __P((SCR *, EXCMD *)); + */ +int +ex_list(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + if (ex_print(sp, cmdp, + &cmdp->addr1, &cmdp->addr2, cmdp->iflags | E_C_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. + * + * PUBLIC: int ex_number __P((SCR *, EXCMD *)); + */ +int +ex_number(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + if (ex_print(sp, cmdp, + &cmdp->addr1, &cmdp->addr2, cmdp->iflags | E_C_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. + * + * PUBLIC: int ex_pr __P((SCR *, EXCMD *)); + */ +int +ex_pr(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + if (ex_print(sp, cmdp, &cmdp->addr1, &cmdp->addr2, cmdp->iflags)) + return (1); + sp->lno = cmdp->addr2.lno; + sp->cno = cmdp->addr2.cno; + return (0); +} + +/* + * ex_print -- + * Print the selected lines. + * + * PUBLIC: int ex_print __P((SCR *, EXCMD *, MARK *, MARK *, u_int32_t)); + */ +int +ex_print(sp, cmdp, fp, tp, flags) + SCR *sp; + EXCMD *cmdp; + MARK *fp, *tp; + u_int32_t flags; +{ + GS *gp; + recno_t from, to; + size_t col, len; + char *p, buf[10]; + + NEEDFILE(sp, cmdp); + + gp = sp->gp; + for (from = fp->lno, to = tp->lno; from <= to; ++from) { + col = 0; + + /* + * 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_C_HASH)) { + if (from <= 999999) { + snprintf(buf, sizeof(buf), "%6ld ", from); + p = buf; + } else + p = "TOOBIG "; + if (ex_prchars(sp, p, &col, 8, 0, 0)) + return (1); + } + + /* + * Display the line. The format for E_C_PRINT isn't very good, + * especially in handling end-of-line tabs, but they're almost + * backward compatible. + */ + if (db_get(sp, from, DBG_FATAL, &p, &len)) + return (1); + + if (len == 0 && !LF_ISSET(E_C_LIST)) + (void)ex_puts(sp, "\n"); + else if (ex_ldisplay(sp, p, len, col, flags)) + return (1); + + if (INTERRUPTED(sp)) + break; + } + return (0); +} + +/* + * ex_ldisplay -- + * Display a line without any preceding number. + * + * PUBLIC: int ex_ldisplay __P((SCR *, const char *, size_t, size_t, u_int)); + */ +int +ex_ldisplay(sp, p, len, col, flags) + SCR *sp; + const char *p; + size_t len, col; + u_int flags; +{ + if (len > 0 && ex_prchars(sp, p, &col, len, LF_ISSET(E_C_LIST), 0)) + return (1); + if (!INTERRUPTED(sp) && LF_ISSET(E_C_LIST)) { + p = "$"; + if (ex_prchars(sp, p, &col, 1, LF_ISSET(E_C_LIST), 0)) + return (1); + } + if (!INTERRUPTED(sp)) + (void)ex_puts(sp, "\n"); + return (0); +} + +/* + * ex_scprint -- + * Display a line for the substitute with confirmation routine. + * + * PUBLIC: int ex_scprint __P((SCR *, MARK *, MARK *)); + */ +int +ex_scprint(sp, fp, tp) + SCR *sp; + MARK *fp, *tp; +{ + const char *p; + size_t col, len; + + col = 0; + if (O_ISSET(sp, O_NUMBER)) { + p = " "; + if (ex_prchars(sp, p, &col, 8, 0, 0)) + return (1); + } + + if (db_get(sp, fp->lno, DBG_FATAL, (char **)&p, &len)) + return (1); + + if (ex_prchars(sp, p, &col, fp->cno, 0, ' ')) + return (1); + p += fp->cno; + if (ex_prchars(sp, + p, &col, tp->cno == fp->cno ? 1 : tp->cno - fp->cno, 0, '^')) + return (1); + if (INTERRUPTED(sp)) + return (1); + p = "[ynq]"; /* XXX: should be msg_cat. */ + if (ex_prchars(sp, p, &col, 5, 0, 0)) + return (1); + (void)ex_fflush(sp); + return (0); +} + +/* + * ex_prchars -- + * Local routine to dump characters to the screen. + */ +static int +ex_prchars(sp, p, colp, len, flags, repeatc) + SCR *sp; + const char *p; + size_t *colp, len; + u_int flags; + int repeatc; +{ + CHAR_T ch, *kp; + GS *gp; + size_t col, tlen, ts; + + if (O_ISSET(sp, O_LIST)) + LF_SET(E_C_LIST); + gp = sp->gp; + ts = O_VAL(sp, O_TABSTOP); + for (col = *colp; len--;) + if ((ch = *p++) == '\t' && !LF_ISSET(E_C_LIST)) + for (tlen = ts - col % ts; + col < sp->cols && tlen--; ++col) { + (void)ex_printf(sp, + "%c", repeatc ? repeatc : ' '); + if (INTERRUPTED(sp)) + goto intr; + } + else { + kp = KEY_NAME(sp, ch); + tlen = KEY_LEN(sp, ch); + if (!repeatc && col + tlen < sp->cols) { + (void)ex_puts(sp, kp); + col += tlen; + } else + for (; tlen--; ++kp, ++col) { + if (col == sp->cols) { + col = 0; + (void)ex_puts(sp, "\n"); + } + (void)ex_printf(sp, + "%c", repeatc ? repeatc : *kp); + if (INTERRUPTED(sp)) + goto intr; + } + } +intr: *colp = col; + return (0); +} + +/* + * ex_printf -- + * Ex's version of printf. + * + * PUBLIC: int ex_printf __P((SCR *, const char *, ...)); + */ +int +#ifdef __STDC__ +ex_printf(SCR *sp, const char *fmt, ...) +#else +ex_printf(sp, fmt, va_alist) + SCR *sp; + const char *fmt; + va_dcl +#endif +{ + EX_PRIVATE *exp; + va_list ap; + size_t n; + + exp = EXP(sp); + +#ifdef __STDC__ + va_start(ap, fmt); +#else + va_start(ap); +#endif + exp->obp_len += n = vsnprintf(exp->obp + exp->obp_len, + sizeof(exp->obp) - exp->obp_len, fmt, ap); + va_end(ap); + + /* Flush when reach a <newline> or half the buffer. */ + if (exp->obp[exp->obp_len - 1] == '\n' || + exp->obp_len > sizeof(exp->obp) / 2) + (void)ex_fflush(sp); + return (n); +} + +/* + * ex_puts -- + * Ex's version of puts. + * + * PUBLIC: int ex_puts __P((SCR *, const char *)); + */ +int +ex_puts(sp, str) + SCR *sp; + const char *str; +{ + EX_PRIVATE *exp; + int doflush, n; + + exp = EXP(sp); + + /* Flush when reach a <newline> or the end of the buffer. */ + for (doflush = n = 0; *str != '\0'; ++n) { + if (exp->obp_len > sizeof(exp->obp)) + (void)ex_fflush(sp); + if ((exp->obp[exp->obp_len++] = *str++) == '\n') + doflush = 1; + } + if (doflush) + (void)ex_fflush(sp); + return (n); +} + +/* + * ex_fflush -- + * Ex's version of fflush. + * + * PUBLIC: int ex_fflush __P((SCR *sp)); + */ +int +ex_fflush(sp) + SCR *sp; +{ + EX_PRIVATE *exp; + + exp = EXP(sp); + + if (exp->obp_len != 0) { + sp->gp->scr_msg(sp, M_NONE, exp->obp, exp->obp_len); + exp->obp_len = 0; + } + return (0); +} diff --git a/ex/ex_put.c b/ex/ex_put.c new file mode 100644 index 000000000000..2facb03f0fb5 --- /dev/null +++ b/ex/ex_put.c @@ -0,0 +1,51 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_put.c 10.7 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> + +#include "../common/common.h" + +/* + * ex_put -- [line] pu[t] [buffer] + * Append a cut buffer into the file. + * + * PUBLIC: int ex_put __P((SCR *, EXCMD *)); + */ +int +ex_put(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + MARK m; + + NEEDFILE(sp, cmdp); + + m.lno = sp->lno; + m.cno = sp->cno; + if (put(sp, NULL, + FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL, + &cmdp->addr1, &m, 1)) + return (1); + sp->lno = m.lno; + sp->cno = m.cno; + return (0); +} diff --git a/ex/ex_quit.c b/ex/ex_quit.c new file mode 100644 index 000000000000..705fa1a64ade --- /dev/null +++ b/ex/ex_quit.c @@ -0,0 +1,46 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_quit.c 10.7 (Berkeley) 4/27/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" + +/* + * ex_quit -- :quit[!] + * Quit. + * + * PUBLIC: int ex_quit __P((SCR *, EXCMD *)); + */ +int +ex_quit(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + int force; + + force = FL_ISSET(cmdp->iflags, E_C_FORCE); + + /* Check for file modifications, or more files to edit. */ + if (file_m2(sp, force) || ex_ncheck(sp, force)) + return (1); + + F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT); + return (0); +} diff --git a/ex/ex_read.c b/ex/ex_read.c new file mode 100644 index 000000000000..78296ff28ca6 --- /dev/null +++ b/ex/ex_read.c @@ -0,0 +1,360 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_read.c 10.38 (Berkeley) 8/12/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "../vi/vi.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. + * + * PUBLIC: int ex_read __P((SCR *, EXCMD *)); + */ +int +ex_read(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + enum { R_ARG, R_EXPANDARG, R_FILTER } which; + struct stat sb; + CHAR_T *arg, *name; + EX_PRIVATE *exp; + FILE *fp; + FREF *frp; + GS *gp; + MARK rm; + recno_t nlines; + size_t arglen; + int argc, rval; + char *p; + + gp = sp->gp; + + /* + * 0 args: read the current pathname. + * 1 args: check for "read !arg". + */ + switch (cmdp->argc) { + case 0: + which = R_ARG; + break; + case 1: + arg = cmdp->argv[0]->bp; + arglen = cmdp->argv[0]->len; + if (*arg == '!') { + ++arg; + --arglen; + which = R_FILTER; + + /* Secure means no shell access. */ + if (O_ISSET(sp, O_SECURE)) { + ex_emsg(sp, cmdp->cmd->name, EXM_SECURE_F); + return (1); + } + } else + which = R_EXPANDARG; + break; + default: + abort(); + /* NOTREACHED */ + } + + /* Load a temporary file if no file being edited. */ + if (sp->ep == NULL) { + if ((frp = file_add(sp, NULL)) == NULL) + return (1); + if (file_init(sp, frp, NULL, 0)) + return (1); + } + + switch (which) { + case R_FILTER: + /* + * File name and bang expand the user's argument. If + * we don't get an additional argument, it's illegal. + */ + argc = cmdp->argc; + if (argv_exp1(sp, cmdp, arg, arglen, 1)) + return (1); + if (argc == cmdp->argc) { + ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); + return (1); + } + argc = cmdp->argc - 1; + + /* Set the last bang command. */ + exp = EXP(sp); + if (exp->lastbcomm != NULL) + free(exp->lastbcomm); + if ((exp->lastbcomm = + strdup(cmdp->argv[argc]->bp)) == NULL) { + msgq(sp, M_SYSERR, NULL); + return (1); + } + + /* + * Vi redisplayed the user's argument if it changed, ex + * always displayed a !, plus the user's argument if it + * changed. + */ + if (F_ISSET(sp, SC_VI)) { + if (F_ISSET(cmdp, E_MODIFY)) + (void)vs_update(sp, "!", cmdp->argv[argc]->bp); + } else { + if (F_ISSET(cmdp, E_MODIFY)) + (void)ex_printf(sp, + "!%s\n", cmdp->argv[argc]->bp); + else + (void)ex_puts(sp, "!\n"); + (void)ex_fflush(sp); + } + + /* + * Historically, filter reads as the first ex command didn't + * wait for the user. If SC_SCR_EXWROTE not already set, set + * the don't-wait flag. + */ + if (!F_ISSET(sp, SC_SCR_EXWROTE)) + F_SET(sp, SC_EX_WAIT_NO); + + /* + * Switch into ex canonical mode. The reason to restore the + * original terminal modes for read filters is so that users + * can do things like ":r! cat /dev/tty". + * + * !!! + * We do not output an extra <newline>, so that we don't touch + * the screen on a normal read. + */ + if (F_ISSET(sp, SC_VI)) { + if (gp->scr_screen(sp, SC_EX)) { + ex_emsg(sp, cmdp->cmd->name, EXM_NOCANON_F); + return (1); + } + /* + * !!! + * Historically, the read command doesn't switch to + * the alternate X11 xterm screen, if doing a filter + * read -- don't set SA_ALTERNATE. + */ + F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE); + } + + if (ex_filter(sp, cmdp, &cmdp->addr1, + NULL, &rm, cmdp->argv[argc]->bp, FILTER_READ)) + return (1); + + /* The filter version of read set the autoprint flag. */ + F_SET(cmdp, E_AUTOPRINT); + + /* + * If in vi mode, move to the first nonblank. Might have + * switched into ex mode, so saved the original SC_VI value. + */ + sp->lno = rm.lno; + if (F_ISSET(sp, SC_VI)) { + sp->cno = 0; + (void)nonblank(sp, sp->lno, &sp->cno); + } + return (0); + case R_ARG: + name = sp->frp->name; + break; + case R_EXPANDARG: + if (argv_exp2(sp, cmdp, arg, arglen)) + return (1); + /* + * 0 args: impossible. + * 1 args: impossible (I hope). + * 2 args: read it. + * >2 args: object, too many args. + * + * The 1 args case depends on the argv_sexp() function refusing + * to return success without at least one non-blank character. + */ + switch (cmdp->argc) { + case 0: + case 1: + abort(); + /* NOTREACHED */ + case 2: + name = cmdp->argv[1]->bp; + /* + * !!! + * Historically, the read and write commands renamed + * "unnamed" files, or, if the file had a name, set + * the alternate file name. + */ + if (F_ISSET(sp->frp, FR_TMPFILE) && + !F_ISSET(sp->frp, FR_EXNAMED)) { + if ((p = v_strdup(sp, cmdp->argv[1]->bp, + cmdp->argv[1]->len)) != NULL) { + free(sp->frp->name); + sp->frp->name = p; + } + /* + * The file has a real name, it's no longer a + * temporary, clear the temporary file flags. + */ + F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE); + F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED); + + /* Notify the screen. */ + (void)sp->gp->scr_rename(sp, sp->frp->name, 1); + } else + set_alt_name(sp, name); + break; + default: + ex_emsg(sp, cmdp->argv[0]->bp, EXM_FILECOUNT); + return (1); + + } + break; + } + + /* + * !!! + * 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. We permit reading from named pipes too, since + * they didn't exist when the original implementation of vi was done + * and they seem a reasonable addition. + */ + if ((fp = fopen(name, "r")) == NULL || fstat(fileno(fp), &sb)) { + msgq_str(sp, M_SYSERR, name, "%s"); + return (1); + } + if (!S_ISFIFO(sb.st_mode) && !S_ISREG(sb.st_mode)) { + (void)fclose(fp); + msgq(sp, M_ERR, + "145|Only regular files and named pipes may be read"); + return (1); + } + + /* Try and get a lock. */ + if (file_lock(sp, NULL, NULL, fileno(fp), 0) == LOCK_UNAVAIL) + msgq(sp, M_ERR, "146|%s: read lock was unavailable", name); + + rval = ex_readfp(sp, name, fp, &cmdp->addr1, &nlines, 0); + + /* + * In vi, 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.) + * + * In ex, set the cursor to the last line read in, if anything read in, + * otherwise, the address. + */ + if (F_ISSET(sp, SC_VI)) { + sp->lno = cmdp->addr1.lno; + if (nlines) + ++sp->lno; + } else + sp->lno = cmdp->addr1.lno + nlines; + return (rval); +} + +/* + * ex_readfp -- + * Read lines into the file. + * + * PUBLIC: int ex_readfp __P((SCR *, char *, FILE *, MARK *, recno_t *, int)); + */ +int +ex_readfp(sp, name, fp, fm, nlinesp, silent) + SCR *sp; + char *name; + FILE *fp; + MARK *fm; + recno_t *nlinesp; + int silent; +{ + EX_PRIVATE *exp; + GS *gp; + recno_t lcnt, lno; + size_t len; + u_long ccnt; /* XXX: can't print off_t portably. */ + int nf, rval; + char *p; + + gp = sp->gp; + exp = EXP(sp); + + /* + * Add in the lines from the output. Insertion starts at the line + * following the address. + */ + ccnt = 0; + lcnt = 0; + p = "147|Reading..."; + for (lno = fm->lno; !ex_getline(sp, fp, &len); ++lno, ++lcnt) { + if ((lcnt + 1) % INTERRUPT_CHECK == 0) { + if (INTERRUPTED(sp)) + break; + if (!silent) { + gp->scr_busy(sp, p, + p == NULL ? BUSY_UPDATE : BUSY_ON); + p = NULL; + } + } + if (db_append(sp, 1, lno, exp->ibp, len)) + goto err; + ccnt += len; + } + + if (ferror(fp) || fclose(fp)) + goto err; + + /* Return the number of lines read in. */ + if (nlinesp != NULL) + *nlinesp = lcnt; + + if (!silent) { + p = msg_print(sp, name, &nf); + msgq(sp, M_INFO, + "148|%s: %lu lines, %lu characters", p, lcnt, ccnt); + if (nf) + FREE_SPACE(sp, p, 0); + } + + rval = 0; + if (0) { +err: msgq_str(sp, M_SYSERR, name, "%s"); + (void)fclose(fp); + rval = 1; + } + + if (!silent) + gp->scr_busy(sp, NULL, BUSY_OFF); + return (rval); +} diff --git a/ex/ex_screen.c b/ex/ex_screen.c new file mode 100644 index 000000000000..9bc5bf04b06a --- /dev/null +++ b/ex/ex_screen.c @@ -0,0 +1,138 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_screen.c 10.11 (Berkeley) 6/29/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "../vi/vi.h" + +/* + * ex_bg -- :bg + * Hide the screen. + * + * PUBLIC: int ex_bg __P((SCR *, EXCMD *)); + */ +int +ex_bg(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + return (vs_bg(sp)); +} + +/* + * ex_fg -- :fg [file] + * Show the screen. + * + * PUBLIC: int ex_fg __P((SCR *, EXCMD *)); + */ +int +ex_fg(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + SCR *nsp; + int newscreen; + + newscreen = F_ISSET(cmdp, E_NEWSCREEN); + if (vs_fg(sp, &nsp, cmdp->argc ? cmdp->argv[0]->bp : NULL, newscreen)) + return (1); + + /* Set up the switch. */ + if (newscreen) { + sp->nextdisp = nsp; + F_SET(sp, SC_SSWITCH); + } + return (0); +} + +/* + * ex_resize -- :resize [+-]rows + * Change the screen size. + * + * PUBLIC: int ex_resize __P((SCR *, EXCMD *)); + */ +int +ex_resize(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + adj_t adj; + + switch (FL_ISSET(cmdp->iflags, + E_C_COUNT | E_C_COUNT_NEG | E_C_COUNT_POS)) { + case E_C_COUNT: + adj = A_SET; + break; + case E_C_COUNT | E_C_COUNT_NEG: + adj = A_DECREASE; + break; + case E_C_COUNT | E_C_COUNT_POS: + adj = A_INCREASE; + break; + default: + ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); + return (1); + } + return (vs_resize(sp, cmdp->count, adj)); +} + +/* + * ex_sdisplay -- + * Display the list of screens. + * + * PUBLIC: int ex_sdisplay __P((SCR *)); + */ +int +ex_sdisplay(sp) + SCR *sp; +{ + GS *gp; + SCR *tsp; + int cnt, col, len, sep; + + gp = sp->gp; + if ((tsp = gp->hq.cqh_first) == (void *)&gp->hq) { + msgq(sp, M_INFO, "149|No background screens to display"); + return (0); + } + + col = len = sep = 0; + for (cnt = 1; tsp != (void *)&gp->hq && !INTERRUPTED(sp); + tsp = tsp->q.cqe_next) { + col += len = strlen(tsp->frp->name) + sep; + if (col >= sp->cols - 1) { + col = len; + sep = 0; + (void)ex_puts(sp, "\n"); + } else if (cnt != 1) { + sep = 1; + (void)ex_puts(sp, " "); + } + (void)ex_puts(sp, tsp->frp->name); + ++cnt; + } + if (!INTERRUPTED(sp)) + (void)ex_puts(sp, "\n"); + return (0); +} diff --git a/ex/ex_script.c b/ex/ex_script.c new file mode 100644 index 000000000000..9ca6d60060d9 --- /dev/null +++ b/ex/ex_script.c @@ -0,0 +1,798 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Brian Hirt. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_script.c 10.30 (Berkeley) 9/24/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/queue.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#include <sys/stat.h> +#ifdef HAVE_SYS5_PTY +#include <sys/stropts.h> +#endif +#include <sys/time.h> +#include <sys/wait.h> + +#include <bitstring.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> /* XXX: OSF/1 bug: include before <grp.h> */ +#include <grp.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "../common/common.h" +#include "../vi/vi.h" +#include "script.h" +#include "pathnames.h" + +static void sscr_check __P((SCR *)); +static int sscr_getprompt __P((SCR *)); +static int sscr_init __P((SCR *)); +static int sscr_insert __P((SCR *)); +static int sscr_matchprompt __P((SCR *, char *, size_t, size_t *)); +static int sscr_pty __P((int *, int *, char *, struct termios *, void *)); +static int sscr_setprompt __P((SCR *, char *, size_t)); + +/* + * ex_script -- : sc[ript][!] [file] + * Switch to script mode. + * + * PUBLIC: int ex_script __P((SCR *, EXCMD *)); + */ +int +ex_script(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + /* Vi only command. */ + if (!F_ISSET(sp, SC_VI)) { + msgq(sp, M_ERR, + "150|The script command is only available in vi mode"); + return (1); + } + + /* Switch to the new file. */ + if (cmdp->argc != 0 && ex_edit(sp, cmdp)) + return (1); + + /* Create the shell, figure out the prompt. */ + if (sscr_init(sp)) + return (1); + + return (0); +} + +/* + * sscr_init -- + * Create a pty setup for a shell. + */ +static int +sscr_init(sp) + SCR *sp; +{ + SCRIPT *sc; + char *sh, *sh_path; + + /* We're going to need a shell. */ + if (opts_empty(sp, O_SHELL, 0)) + return (1); + + 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); + +#ifdef TIOCGWINSZ + if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) { + msgq(sp, M_SYSERR, "tcgetattr"); + goto err; + } + + if (sscr_pty(&sc->sh_master, + &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) { + msgq(sp, M_SYSERR, "pty"); + goto err; + } +#else + if (sscr_pty(&sc->sh_master, + &sc->sh_slave, sc->sh_name, &sc->sh_term, NULL) == -1) { + msgq(sp, M_SYSERR, "pty"); + goto err; + } +#endif + + /* + * __TK__ huh? + * Don't use vfork() here, because the signal semantics differ from + * implementation to implementation. + */ + switch (sc->sh_pid = fork()) { + case -1: /* Error. */ + 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. */ + /* + * XXX + * So that shells that do command line editing turn it off. + */ + (void)setenv("TERM", "emacs", 1); + (void)setenv("TERMCAP", "emacs:", 1); + (void)setenv("EMACS", "t", 1); + + (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_str(sp, M_SYSERR, sh_path, "execl: %s"); + _exit(127); + default: /* Parent. */ + break; + } + + if (sscr_getprompt(sp)) + return (1); + + F_SET(sp, SC_SCRIPT); + F_SET(sp->gp, G_SCRWIN); + 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) + SCR *sp; +{ + 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 (db_last(sp, &lline) || + db_append(sp, 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 (db_last(sp, &lline) || db_append(sp, 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. + * + * PUBLIC: int sscr_exec __P((SCR *, recno_t)); + */ +int +sscr_exec(sp, lno) + SCR *sp; + recno_t lno; +{ + SCRIPT *sc; + recno_t last_lno; + size_t blen, len, last_len, tlen; + int isempty, matchprompt, nw, rval; + char *bp, *p; + + /* If there's a prompt on the last line, append the command. */ + if (db_last(sp, &last_lno)) + return (1); + if (db_get(sp, last_lno, DBG_FATAL, &p, &last_len)) + 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 (db_eget(sp, lno, &p, &len, &isempty)) { + if (isempty) + goto empty; + 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, "151|No command 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 (db_set(sp, last_lno, bp, last_len + len)) +err1: rval = 1; + } + if (matchprompt) + FREE_SPACE(sp, bp, blen); + return (rval); +} + +/* + * sscr_input -- + * Read any waiting shell input. + * + * PUBLIC: int sscr_input __P((SCR *)); + */ +int +sscr_input(sp) + SCR *sp; +{ + GS *gp; + struct timeval poll; + fd_set rdfd; + int maxfd; + + gp = sp->gp; + +loop: maxfd = 0; + FD_ZERO(&rdfd); + poll.tv_sec = 0; + poll.tv_usec = 0; + + /* Set up the input mask. */ + for (sp = gp->dq.cqh_first; sp != (void *)&gp->dq; sp = sp->q.cqe_next) + if (F_ISSET(sp, SC_SCRIPT)) { + FD_SET(sp->script->sh_master, &rdfd); + if (sp->script->sh_master > maxfd) + maxfd = sp->script->sh_master; + } + + /* Check for input. */ + switch (select(maxfd + 1, &rdfd, NULL, NULL, &poll)) { + case -1: + msgq(sp, M_SYSERR, "select"); + return (1); + case 0: + return (0); + default: + break; + } + + /* Read the input. */ + for (sp = gp->dq.cqh_first; sp != (void *)&gp->dq; sp = sp->q.cqe_next) + if (F_ISSET(sp, SC_SCRIPT) && + FD_ISSET(sp->script->sh_master, &rdfd) && sscr_insert(sp)) + return (1); + goto loop; +} + +/* + * sscr_insert -- + * Take a line from the shell and insert it into the file. + */ +static int +sscr_insert(sp) + SCR *sp; +{ + struct timeval tv; + CHAR_T *endp, *p, *t; + SCRIPT *sc; + fd_set rdfd; + 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. */ + if (db_last(sp, &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); + 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 (db_append(sp, 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_ZERO(&rdfd); + FD_SET(sc->sh_master, &rdfd); + if (select(sc->sh_master + 1, + &rdfd, NULL, NULL, &tv) == 1) { + memmove(bp, t, len); + endp = bp + len; + goto more; + } + } + if (sscr_setprompt(sp, t, len)) + return (1); + if (db_append(sp, 1, lno++, t, len)) + goto ret; + } + + /* The cursor moves to EOF. */ + sp->lno = lno; + sp->cno = len ? len - 1 : 0; + rval = vs_refresh(sp, 1); + +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); + 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. + * + * PUBLIC: int sscr_end __P((SCR *)); + */ +int +sscr_end(sp) + SCR *sp; +{ + SCRIPT *sc; + + if ((sc = sp->script) == NULL) + return (0); + + /* Turn off the script flags. */ + F_CLR(sp, SC_SCRIPT); + sscr_check(sp); + + /* 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. */ + (void)proc_wait(sp, (long)sc->sh_pid, "script-shell", 0, 0); + + /* Free memory. */ + free(sc->sh_prompt); + free(sc); + sp->script = NULL; + + return (0); +} + +/* + * sscr_check -- + * Set/clear the global scripting bit. + */ +static void +sscr_check(sp) + SCR *sp; +{ + GS *gp; + + gp = sp->gp; + for (sp = gp->dq.cqh_first; sp != (void *)&gp->dq; sp = sp->q.cqe_next) + if (F_ISSET(sp, SC_SCRIPT)) { + F_SET(gp, G_SCRWIN); + return; + } + F_CLR(gp, G_SCRWIN); +} + +#ifdef HAVE_SYS5_PTY +static int ptys_open __P((int, char *)); +static int ptym_open __P((char *)); + +static int +sscr_pty(amaster, aslave, name, termp, winp) + int *amaster, *aslave; + char *name; + struct termios *termp; + void *winp; +{ + int master, slave, ttygid; + + /* open master terminal */ + if ((master = ptym_open(name)) < 0) { + errno = ENOENT; /* out of ptys */ + return (-1); + } + + /* open slave terminal */ + if ((slave = ptys_open(master, name)) >= 0) { + *amaster = master; + *aslave = slave; + } else { + errno = ENOENT; /* out of ptys */ + return (-1); + } + + if (termp) + (void) tcsetattr(slave, TCSAFLUSH, termp); +#ifdef TIOCSWINSZ + if (winp != NULL) + (void) ioctl(slave, TIOCSWINSZ, (struct winsize *)winp); +#endif + return (0); +} + +/* + * ptym_open -- + * This function opens a master pty and returns the file descriptor + * to it. pts_name is also returned which is the name of the slave. + */ +static int +ptym_open(pts_name) + char *pts_name; +{ + int fdm; + char *ptr, *ptsname(); + + strcpy(pts_name, _PATH_SYSV_PTY); + if ((fdm = open(pts_name, O_RDWR)) < 0 ) + return (-1); + + if (grantpt(fdm) < 0) { + close(fdm); + return (-2); + } + + if (unlockpt(fdm) < 0) { + close(fdm); + return (-3); + } + + if (unlockpt(fdm) < 0) { + close(fdm); + return (-3); + } + + /* get slave's name */ + if ((ptr = ptsname(fdm)) == NULL) { + close(fdm); + return (-3); + } + strcpy(pts_name, ptr); + return (fdm); +} + +/* + * ptys_open -- + * This function opens the slave pty. + */ +static int +ptys_open(fdm, pts_name) + int fdm; + char *pts_name; +{ + int fds; + + if ((fds = open(pts_name, O_RDWR)) < 0) { + close(fdm); + return (-5); + } + + if (ioctl(fds, I_PUSH, "ptem") < 0) { + close(fds); + close(fdm); + return (-6); + } + + if (ioctl(fds, I_PUSH, "ldterm") < 0) { + close(fds); + close(fdm); + return (-7); + } + + if (ioctl(fds, I_PUSH, "ttcompat") < 0) { + close(fds); + close(fdm); + return (-8); + } + + return (fds); +} + +#else /* !HAVE_SYS5_PTY */ + +static int +sscr_pty(amaster, aslave, name, termp, winp) + int *amaster, *aslave; + char *name; + struct termios *termp; + void *winp; +{ + static char line[] = "/dev/ptyXX"; + register char *cp1, *cp2; + register int master, slave, ttygid; + struct group *gr; + + if ((gr = getgrnam("tty")) != NULL) + ttygid = gr->gr_gid; + else + ttygid = -1; + + for (cp1 = "pqrs"; *cp1; cp1++) { + line[8] = *cp1; + for (cp2 = "0123456789abcdef"; *cp2; cp2++) { + line[5] = 'p'; + line[9] = *cp2; + if ((master = open(line, O_RDWR, 0)) == -1) { + if (errno == ENOENT) + return (-1); /* out of ptys */ + } else { + line[5] = 't'; + (void) chown(line, getuid(), ttygid); + (void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP); +#ifdef HAVE_REVOKE + (void) revoke(line); +#endif + if ((slave = open(line, O_RDWR, 0)) != -1) { + *amaster = master; + *aslave = slave; + if (name) + strcpy(name, line); + if (termp) + (void) tcsetattr(slave, + TCSAFLUSH, termp); +#ifdef TIOCSWINSZ + if (winp) + (void) ioctl(slave, TIOCSWINSZ, + (char *)winp); +#endif + return (0); + } + (void) close(master); + } + } + } + errno = ENOENT; /* out of ptys */ + return (-1); +} +#endif /* HAVE_SYS5_PTY */ diff --git a/ex/ex_set.c b/ex/ex_set.c new file mode 100644 index 000000000000..11e929764242 --- /dev/null +++ b/ex/ex_set.c @@ -0,0 +1,46 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_set.c 10.7 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" + +/* + * ex_set -- :set + * Ex set option. + * + * PUBLIC: int ex_set __P((SCR *, EXCMD *)); + */ +int +ex_set(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + switch(cmdp->argc) { + case 0: + opts_dump(sp, CHANGED_DISPLAY); + break; + default: + if (opts_set(sp, cmdp->argv, cmdp->cmd->usage)) + return (1); + break; + } + return (0); +} diff --git a/ex/ex_shell.c b/ex/ex_shell.c new file mode 100644 index 000000000000..95168033a13f --- /dev/null +++ b/ex/ex_shell.c @@ -0,0 +1,378 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_shell.c 10.38 (Berkeley) 8/19/96"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/wait.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" + +static const char *sigmsg __P((int)); + +/* + * ex_shell -- :sh[ell] + * Invoke the program named in the SHELL environment variable + * with the argument -i. + * + * PUBLIC: int ex_shell __P((SCR *, EXCMD *)); + */ +int +ex_shell(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + int rval; + char buf[MAXPATHLEN]; + + /* We'll need a shell. */ + if (opts_empty(sp, O_SHELL, 0)) + return (1); + + /* + * XXX + * Assumes all shells use -i. + */ + (void)snprintf(buf, sizeof(buf), "%s -i", O_STR(sp, O_SHELL)); + + /* Restore the window name. */ + (void)sp->gp->scr_rename(sp, NULL, 0); + + /* If we're still in a vi screen, move out explicitly. */ + rval = ex_exec_proc(sp, cmdp, buf, NULL, !F_ISSET(sp, SC_SCR_EXWROTE)); + + /* Set the window name. */ + (void)sp->gp->scr_rename(sp, sp->frp->name, 1); + + /* + * !!! + * Historically, vi didn't require a continue message after the + * return of the shell. Match it. + */ + F_SET(sp, SC_EX_WAIT_NO); + + return (rval); +} + +/* + * ex_exec_proc -- + * Run a separate process. + * + * PUBLIC: int ex_exec_proc __P((SCR *, EXCMD *, char *, const char *, int)); + */ +int +ex_exec_proc(sp, cmdp, cmd, msg, need_newline) + SCR *sp; + EXCMD *cmdp; + char *cmd; + const char *msg; + int need_newline; +{ + GS *gp; + const char *name; + pid_t pid; + + gp = sp->gp; + + /* We'll need a shell. */ + if (opts_empty(sp, O_SHELL, 0)) + return (1); + + /* Enter ex mode. */ + if (F_ISSET(sp, SC_VI)) { + if (gp->scr_screen(sp, SC_EX)) { + ex_emsg(sp, cmdp->cmd->name, EXM_NOCANON); + return (1); + } + (void)gp->scr_attr(sp, SA_ALTERNATE, 0); + F_SET(sp, SC_SCR_EX | SC_SCR_EXWROTE); + } + + /* Put out additional newline, message. */ + if (need_newline) + (void)ex_puts(sp, "\n"); + if (msg != NULL) { + (void)ex_puts(sp, msg); + (void)ex_puts(sp, "\n"); + } + (void)ex_fflush(sp); + + switch (pid = vfork()) { + case -1: /* Error. */ + msgq(sp, M_SYSERR, "vfork"); + return (1); + case 0: /* Utility. */ + 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_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s"); + _exit(127); + /* NOTREACHED */ + default: /* Parent. */ + return (proc_wait(sp, (long)pid, cmd, 0, 0)); + } + /* NOTREACHED */ +} + +/* + * 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. + * + * PUBLIC: int proc_wait __P((SCR *, long, const char *, int, int)); + */ +int +proc_wait(sp, pid, cmd, silent, okpipe) + SCR *sp; + long pid; + const char *cmd; + int silent, okpipe; +{ + size_t len; + int nf, pstat; + char *p; + + /* Wait for the utility, ignoring interruptions. */ + for (;;) { + errno = 0; + if (waitpid((pid_t)pid, &pstat, 0) != -1) + break; + if (errno != EINTR) { + msgq(sp, M_SYSERR, "waitpid"); + 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); + p = msg_print(sp, cmd, &nf); + len = strlen(p); + msgq(sp, M_ERR, "%.*s%s: received signal: %s%s", + MIN(len, 20), p, len > 20 ? " ..." : "", + sigmsg(WTERMSIG(pstat)), + WCOREDUMP(pstat) ? "; core dumped" : ""); + if (nf) + FREE_SPACE(sp, p, 0); + return (1); + } + + if (WIFEXITED(pstat) && WEXITSTATUS(pstat)) { + /* + * Remain silent for "normal" errors when doing shell file + * name expansions, they almost certainly indicate nothing + * more than a failure to match. + * + * Remain silent for vi read filter errors. It's historic + * practice. + */ + if (!silent) { + for (; isblank(*cmd); ++cmd); + p = msg_print(sp, cmd, &nf); + len = strlen(p); + msgq(sp, M_ERR, "%.*s%s: exited with status %d", + MIN(len, 20), p, len > 20 ? " ..." : "", + WEXITSTATUS(pstat)); + if (nf) + FREE_SPACE(sp, p, 0); + } + return (1); + } + return (0); +} + +/* + * XXX + * The sys_siglist[] table in the C library has this information, but there's + * no portable way to get to it. (Believe me, I tried.) + */ +typedef struct _sigs { + int number; /* signal number */ + char *message; /* related message */ +} SIGS; + +SIGS const sigs[] = { +#ifdef SIGABRT + SIGABRT, "Abort trap", +#endif +#ifdef SIGALRM + SIGALRM, "Alarm clock", +#endif +#ifdef SIGBUS + SIGBUS, "Bus error", +#endif +#ifdef SIGCLD + SIGCLD, "Child exited or stopped", +#endif +#ifdef SIGCHLD + SIGCHLD, "Child exited", +#endif +#ifdef SIGCONT + SIGCONT, "Continued", +#endif +#ifdef SIGDANGER + SIGDANGER, "System crash imminent", +#endif +#ifdef SIGEMT + SIGEMT, "EMT trap", +#endif +#ifdef SIGFPE + SIGFPE, "Floating point exception", +#endif +#ifdef SIGGRANT + SIGGRANT, "HFT monitor mode granted", +#endif +#ifdef SIGHUP + SIGHUP, "Hangup", +#endif +#ifdef SIGILL + SIGILL, "Illegal instruction", +#endif +#ifdef SIGINFO + SIGINFO, "Information request", +#endif +#ifdef SIGINT + SIGINT, "Interrupt", +#endif +#ifdef SIGIO + SIGIO, "I/O possible", +#endif +#ifdef SIGIOT + SIGIOT, "IOT trap", +#endif +#ifdef SIGKILL + SIGKILL, "Killed", +#endif +#ifdef SIGLOST + SIGLOST, "Record lock", +#endif +#ifdef SIGMIGRATE + SIGMIGRATE, "Migrate process to another CPU", +#endif +#ifdef SIGMSG + SIGMSG, "HFT input data pending", +#endif +#ifdef SIGPIPE + SIGPIPE, "Broken pipe", +#endif +#ifdef SIGPOLL + SIGPOLL, "I/O possible", +#endif +#ifdef SIGPRE + SIGPRE, "Programming error", +#endif +#ifdef SIGPROF + SIGPROF, "Profiling timer expired", +#endif +#ifdef SIGPWR + SIGPWR, "Power failure imminent", +#endif +#ifdef SIGRETRACT + SIGRETRACT, "HFT monitor mode retracted", +#endif +#ifdef SIGQUIT + SIGQUIT, "Quit", +#endif +#ifdef SIGSAK + SIGSAK, "Secure Attention Key", +#endif +#ifdef SIGSEGV + SIGSEGV, "Segmentation fault", +#endif +#ifdef SIGSOUND + SIGSOUND, "HFT sound sequence completed", +#endif +#ifdef SIGSTOP + SIGSTOP, "Suspended (signal)", +#endif +#ifdef SIGSYS + SIGSYS, "Bad system call", +#endif +#ifdef SIGTERM + SIGTERM, "Terminated", +#endif +#ifdef SIGTRAP + SIGTRAP, "Trace/BPT trap", +#endif +#ifdef SIGTSTP + SIGTSTP, "Suspended", +#endif +#ifdef SIGTTIN + SIGTTIN, "Stopped (tty input)", +#endif +#ifdef SIGTTOU + SIGTTOU, "Stopped (tty output)", +#endif +#ifdef SIGURG + SIGURG, "Urgent I/O condition", +#endif +#ifdef SIGUSR1 + SIGUSR1, "User defined signal 1", +#endif +#ifdef SIGUSR2 + SIGUSR2, "User defined signal 2", +#endif +#ifdef SIGVTALRM + SIGVTALRM, "Virtual timer expired", +#endif +#ifdef SIGWINCH + SIGWINCH, "Window size changes", +#endif +#ifdef SIGXCPU + SIGXCPU, "Cputime limit exceeded", +#endif +#ifdef SIGXFSZ + SIGXFSZ, "Filesize limit exceeded", +#endif +}; + +/* + * sigmsg -- + * Return a pointer to a message describing a signal. + */ +static const char * +sigmsg(signo) + int signo; +{ + static char buf[40]; + const SIGS *sigp; + int n; + + for (n = 0, + sigp = &sigs[0]; n < sizeof(sigs) / sizeof(sigs[0]); ++n, ++sigp) + if (sigp->number == signo) + return (sigp->message); + (void)snprintf(buf, sizeof(buf), "Unknown signal: %d", signo); + return (buf); +} diff --git a/ex/ex_shift.c b/ex/ex_shift.c new file mode 100644 index 000000000000..83bd36d12a7d --- /dev/null +++ b/ex/ex_shift.c @@ -0,0 +1,191 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_shift.c 10.11 (Berkeley) 9/15/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" + +enum which {LEFT, RIGHT}; +static int shift __P((SCR *, EXCMD *, enum which)); + +/* + * ex_shiftl -- :<[<...] + * + * + * PUBLIC: int ex_shiftl __P((SCR *, EXCMD *)); + */ +int +ex_shiftl(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + return (shift(sp, cmdp, LEFT)); +} + +/* + * ex_shiftr -- :>[>...] + * + * PUBLIC: int ex_shiftr __P((SCR *, EXCMD *)); + */ +int +ex_shiftr(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + return (shift(sp, cmdp, RIGHT)); +} + +/* + * shift -- + * Ex shift support. + */ +static int +shift(sp, cmdp, rl) + SCR *sp; + EXCMD *cmdp; + enum which rl; +{ + recno_t from, to; + size_t blen, len, newcol, newidx, oldcol, oldidx, sw; + int curset; + char *p, *bp, *tbp; + + NEEDFILE(sp, cmdp); + + if (O_VAL(sp, O_SHIFTWIDTH) == 0) { + msgq(sp, M_INFO, "152|shiftwidth option set to 0"); + return (0); + } + + /* Copy the lines being shifted into the unnamed buffer. */ + if (cut(sp, NULL, &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE)) + return (1); + + /* + * 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 (db_get(sp, from, DBG_FATAL, &p, &len)) + 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. */ + memcpy(tbp, p + oldidx, len - oldidx); + + /* Set the replacement line. */ + if (db_set(sp, 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, to, &sp->cno); + } + + FREE_SPACE(sp, bp, blen); + + sp->rptlines[L_SHIFT] += cmdp->addr2.lno - cmdp->addr1.lno + 1; + return (0); +} diff --git a/ex/ex_source.c b/ex/ex_source.c new file mode 100644 index 000000000000..b52c527716fd --- /dev/null +++ b/ex/ex_source.c @@ -0,0 +1,85 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_source.c 10.12 (Berkeley) 8/10/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/stat.h> + +#include <bitstring.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" + +/* + * ex_source -- :source file + * Execute ex commands from a file. + * + * PUBLIC: int ex_source __P((SCR *, EXCMD *)); + */ +int +ex_source(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + struct stat sb; + int fd, len; + char *bp, *name; + + name = cmdp->argv[0]->bp; + if ((fd = open(name, O_RDONLY, 0)) < 0 || fstat(fd, &sb)) + goto err; + + /* + * XXX + * I'd like to test to see if the file is too large 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. So, put an fairly unreasonable limit on it, + * I don't want to be dropping core here. + */ +#define MEGABYTE 1048576 + if (sb.st_size > MEGABYTE) { + errno = ENOMEM; + goto err; + } + + MALLOC(sp, bp, char *, (size_t)sb.st_size + 1); + if (bp == NULL) { + (void)close(fd); + return (1); + } + bp[sb.st_size] = '\0'; + + /* Read the file into memory. */ + len = read(fd, bp, (int)sb.st_size); + (void)close(fd); + if (len == -1 || len != sb.st_size) { + if (len != sb.st_size) + errno = EIO; + free(bp); +err: msgq_str(sp, M_SYSERR, name, "%s"); + return (1); + } + + /* Put it on the ex queue. */ + return (ex_run_str(sp, name, bp, (size_t)sb.st_size, 1, 1)); +} diff --git a/ex/ex_stop.c b/ex/ex_stop.c new file mode 100644 index 000000000000..bc55fd24ccb7 --- /dev/null +++ b/ex/ex_stop.c @@ -0,0 +1,51 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_stop.c 10.10 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" + +/* + * ex_stop -- :stop[!] + * :suspend[!] + * Suspend execution. + * + * PUBLIC: int ex_stop __P((SCR *, EXCMD *)); + */ +int +ex_stop(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + int allowed; + + /* For some strange reason, the force flag turns off autowrite. */ + if (!FL_ISSET(cmdp->iflags, E_C_FORCE) && file_aw(sp, FS_ALL)) + return (1); + + if (sp->gp->scr_suspend(sp, &allowed)) + return (1); + if (!allowed) + ex_emsg(sp, NULL, EXM_NOSUSPEND); + return (0); +} diff --git a/ex/ex_subst.c b/ex/ex_subst.c new file mode 100644 index 000000000000..0ebb81dd58e7 --- /dev/null +++ b/ex/ex_subst.c @@ -0,0 +1,1459 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_subst.c 10.37 (Berkeley) 9/15/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" +#include "../vi/vi.h" + +#define SUB_FIRST 0x01 /* The 'r' flag isn't reasonable. */ +#define SUB_MUSTSETR 0x02 /* The 'r' flag is required. */ + +static int re_conv __P((SCR *, char **, size_t *, int *)); +static int re_cscope_conv __P((SCR *, char **, size_t *, int *)); +static int re_sub __P((SCR *, + char *, char **, size_t *, size_t *, regmatch_t [10])); +static int re_tag_conv __P((SCR *, char **, size_t *, int *)); +static int s __P((SCR *, EXCMD *, char *, regex_t *, u_int)); + +/* + * ex_s -- + * [line [,line]] s[ubstitute] [[/;]pat[/;]/repl[/;] [cgr] [count] [#lp]] + * + * Substitute on lines matching a pattern. + * + * PUBLIC: int ex_s __P((SCR *, EXCMD *)); + */ +int +ex_s(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + regex_t *re; + size_t blen, len; + u_int flags; + int delim; + 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. + */ + if (cmdp->argc == 0) + goto subagain; + for (p = cmdp->argv[0]->bp, + len = cmdp->argv[0]->len; len > 0; --len, ++p) { + if (!isblank(*p)) + break; + } + if (len == 0) +subagain: return (ex_subagain(sp, cmdp)); + + delim = *p++; + if (isalnum(delim) || delim == '\\') + return (s(sp, cmdp, p, &sp->subre_c, 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 escaping 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 escaping 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 escaping 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 (sp->re == NULL) { + ex_emsg(sp, NULL, EXM_NOPREVRE); + return (1); + } + + /* Re-compile the RE if necessary. */ + if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, + sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) + return (1); + flags = 0; + } else { + /* + * !!! + * Compile the RE. Historic practice is that substitutes set + * the search direction as well as both substitute and search + * RE's. We compile the RE twice, as we don't want to bother + * ref counting the pattern string and (opaque) structure. + */ + if (re_compile(sp, ptrn, t - ptrn, + &sp->re, &sp->re_len, &sp->re_c, RE_C_SEARCH)) + return (1); + if (re_compile(sp, ptrn, t - ptrn, + &sp->subre, &sp->subre_len, &sp->subre_c, RE_C_SUBST)) + return (1); + + flags = SUB_FIRST; + sp->searchdir = FORWARD; + } + re = &sp->re_c; + + /* + * 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 + * re_sub(). + * + * 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 escaping 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 = 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; + memcpy(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); + } + memcpy(sp->repl, bp, len); + } + FREE_SPACE(sp, bp, blen); + } + return (s(sp, cmdp, p, re, flags)); +} + +/* + * ex_subagain -- + * [line [,line]] & [cgr] [count] [#lp]] + * + * Substitute using the last substitute RE and replacement pattern. + * + * PUBLIC: int ex_subagain __P((SCR *, EXCMD *)); + */ +int +ex_subagain(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + if (sp->subre == NULL) { + ex_emsg(sp, NULL, EXM_NOPREVRE); + return (1); + } + if (!F_ISSET(sp, SC_RE_SUBST) && re_compile(sp, + sp->subre, sp->subre_len, NULL, NULL, &sp->subre_c, RE_C_SUBST)) + return (1); + return (s(sp, + cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->subre_c, 0)); +} + +/* + * ex_subtilde -- + * [line [,line]] ~ [cgr] [count] [#lp]] + * + * Substitute using the last RE and last substitute replacement pattern. + * + * PUBLIC: int ex_subtilde __P((SCR *, EXCMD *)); + */ +int +ex_subtilde(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + if (sp->re == NULL) { + ex_emsg(sp, NULL, EXM_NOPREVRE); + return (1); + } + if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp, + sp->re, sp->re_len, NULL, NULL, &sp->re_c, RE_C_SEARCH)) + return (1); + return (s(sp, + cmdp, cmdp->argc ? cmdp->argv[0]->bp : NULL, &sp->re_c, 0)); +} + +/* + * s -- + * 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. + * + * 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 *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); \ + } \ + } \ + memcpy(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; \ + } \ +} + +static int +s(sp, cmdp, s, re, flags) + SCR *sp; + EXCMD *cmdp; + char *s; + regex_t *re; + u_int flags; +{ + EVENT ev; + MARK from, to; + TEXTH tiq; + recno_t elno, lno, slno; + regmatch_t match[10]; + size_t blen, cnt, last, lbclen, lblen, len, llen; + size_t offset, saved_offset, scno; + int cflag, lflag, nflag, pflag, rflag; + int didsub, do_eol_match, eflags, empty_ok, eval; + int linechanged, matched, quit, rval; + char *bp, *lb; + + NEEDFILE(sp, cmdp); + + slno = sp->lno; + scno = sp->cno; + + /* + * !!! + * 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_s()). + */ + 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; + if (s == NULL) + goto noargs; + 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, "153|Count overflow"); + else if (lno == LONG_MIN) + msgq(sp, M_ERR, "154|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; + if (!db_exist(sp, cmdp->addr2.lno) && + db_last(sp, &cmdp->addr2.lno)) + return (1); + break; + case '#': + nflag = 1; + break; + case 'c': + sp->c_suffix = !sp->c_suffix; + + /* Ex text structure initialization. */ + if (F_ISSET(sp, SC_EX)) { + memset(&tiq, 0, sizeof(TEXTH)); + CIRCLEQ_INIT(&tiq); + } + 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, + "155|Regular expression specified; r flag meaningless"); + return (1); + } + if (!F_ISSET(sp, SC_RE_SEARCH)) { + ex_emsg(sp, NULL, EXM_NOPREVRE); + return (1); + } + rflag = 1; + re = &sp->re_c; + break; + default: + goto usage; + } + + if (*s != '\0' || !rflag && LF_ISSET(SUB_MUSTSETR)) { +usage: ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); + return (1); + } + +noargs: if (F_ISSET(sp, SC_VI) && sp->c_suffix && (lflag || nflag || pflag)) { + msgq(sp, M_ERR, +"156|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)) + break; + + /* Get the line. */ + if (db_get(sp, lno, DBG_FATAL, &s, &llen)) + goto err; + + /* + * 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); + memcpy(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 err; + } + 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. + */ + from.lno = to.lno = lno; + from.cno = match[0].rm_so + offset; + to.cno = match[0].rm_eo + offset; + /* + * Both ex and vi have to correct for a change before + * the first character in the line. + */ + if (llen == 0) + from.cno = to.cno = 0; + if (F_ISSET(sp, SC_VI)) { + /* + * Only vi has to correct for a change after + * the last character in the line. + * + * XXX + * It would be nice to change the vi code so + * that we could display a cursor past EOL. + */ + if (to.cno >= llen) + to.cno = llen - 1; + if (from.cno >= llen) + from.cno = llen - 1; + + sp->lno = from.lno; + sp->cno = from.cno; + if (vs_refresh(sp, 1)) + goto err; + + vs_update(sp, msg_cat(sp, + "169|Confirm change? [n]", NULL), NULL); + + if (v_event_get(sp, &ev, 0, 0)) + goto err; + switch (ev.e_event) { + case E_CHARACTER: + break; + case E_EOF: + case E_ERR: + case E_INTERRUPT: + goto lquit; + default: + v_event_err(sp, &ev); + goto lquit; + } + } else { + if (ex_print(sp, cmdp, &from, &to, 0) || + ex_scprint(sp, &from, &to)) + goto lquit; + if (ex_txt(sp, &tiq, 0, TXT_CR)) + goto err; + ev.e_c = tiq.cqh_first->lb[0]; + } + + switch (ev.e_c) { + case CH_YES: + break; + default: + case CH_NO: + didsub = 0; + BUILD(sp, s +offset, match[0].rm_eo); + goto skip; + case CH_QUIT: + /* Set the quit/interrupted flags. */ +lquit: quit = 1; + F_SET(sp->gp, G_INTERRUPTED); + + /* + * Resolve any changes, then return to (and + * exit from) the main loop. + */ + goto endmatch; + } + } + + /* + * Set the cursor to the last position changed, converting + * from 1-based to 0-based. + */ + sp->lno = lno; + sp->cno = match[0].rm_so; + + /* 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 (re_sub(sp, s + offset, &lb, &lbclen, &lblen, match)) + goto err; + + /* 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 (db_insert(sp, lno, + lb + last, sp->newl[cnt] - last)) + goto err; + last = sp->newl[cnt] + 1; + ++sp->rptlines[L_ADDED]; + } + lbclen -= last; + offset -= last; + sp->newl_cnt = 0; + } + + /* Store and retrieve the line. */ + if (db_set(sp, lno, lb + last, lbclen)) + goto err; + if (db_get(sp, lno, DBG_FATAL, &s, &llen)) + goto err; + ADD_SPACE_RET(sp, bp, blen, llen) + memcpy(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 (db_insert(sp, + lno, lb + last, sp->newl[cnt] - last)) + goto err; + last = sp->newl[cnt] + 1; + ++sp->rptlines[L_ADDED]; + } + lbclen -= last; + sp->newl_cnt = 0; + } + + /* Store the changed line. */ + if (db_set(sp, lno, lb + last, lbclen)) + goto err; + + /* 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) + (void)ex_print(sp, cmdp, &from, &to, E_C_LIST); + if (nflag) + (void)ex_print(sp, cmdp, &from, &to, E_C_HASH); + if (pflag) + (void)ex_print(sp, cmdp, &from, &to, E_C_PRINT); + } + } + + /* + * !!! + * Historically, vi attempted to leave the cursor at the same place if + * the substitution was done at the current cursor position. Otherwise + * it moved it to the first non-blank of the last line changed. There + * were some problems: for example, :s/$/foo/ with the cursor on the + * last character of the line left the cursor on the last character, or + * the & command with multiple occurrences of the matching string in the + * line usually left the cursor in a fairly random position. + * + * We try to do the same thing, with the exception that if the user is + * doing substitution with confirmation, we move to the last line about + * which the user was consulted, as opposed to the last line that they + * actually changed. This prevents a screen flash if the user doesn't + * change many of the possible lines. + */ + if (!sp->c_suffix && (sp->lno != slno || sp->cno != scno)) { + sp->cno = 0; + (void)nonblank(sp, 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. + */ + rval = 0; + if (!matched) { + if (!F_ISSET(sp, SC_EX_GLOBAL)) { + msgq(sp, M_ERR, "157|No match found"); + goto err; + } + } else if (!lflag && !nflag && !pflag) + F_SET(cmdp, E_AUTOPRINT); + + if (0) { +err: rval = 1; + } + + if (bp != NULL) + FREE_SPACE(sp, bp, blen); + if (lb != NULL) + free(lb); + return (rval); +} + +/* + * re_compile -- + * Compile the RE. + * + * PUBLIC: int re_compile __P((SCR *, + * PUBLIC: char *, size_t, char **, size_t *, regex_t *, u_int)); + */ +int +re_compile(sp, ptrn, plen, ptrnp, lenp, rep, flags) + SCR *sp; + char *ptrn, **ptrnp; + size_t plen, *lenp; + regex_t *rep; + u_int flags; +{ + size_t len; + int reflags, replaced, rval; + char *p; + + /* Set RE flags. */ + reflags = 0; + if (!LF_ISSET(RE_C_CSCOPE | RE_C_TAG)) { + if (O_ISSET(sp, O_EXTENDED)) + reflags |= REG_EXTENDED; + if (O_ISSET(sp, O_IGNORECASE)) + reflags |= REG_ICASE; + if (O_ISSET(sp, O_ICLOWER)) { + for (p = ptrn, len = plen; len > 0; ++p, --len) + if (isupper(*p)) + break; + if (len == 0) + reflags |= REG_ICASE; + } + } + + /* If we're replacing a saved value, clear the old one. */ + if (LF_ISSET(RE_C_SEARCH) && F_ISSET(sp, SC_RE_SEARCH)) { + regfree(&sp->re_c); + F_CLR(sp, SC_RE_SEARCH); + } + if (LF_ISSET(RE_C_SUBST) && F_ISSET(sp, SC_RE_SUBST)) { + regfree(&sp->subre_c); + F_CLR(sp, SC_RE_SUBST); + } + + /* + * If we're saving the string, it's a pattern we haven't seen before, + * so convert the vi-style RE's to POSIX 1003.2 RE's. Save a copy for + * later recompilation. Free any previously saved value. + */ + if (ptrnp != NULL) { + if (LF_ISSET(RE_C_CSCOPE)) { + if (re_cscope_conv(sp, &ptrn, &plen, &replaced)) + return (1); + /* + * XXX + * Currently, the match-any-<blank> expression used in + * re_cscope_conv() requires extended RE's. This may + * not be right or safe. + */ + reflags |= REG_EXTENDED; + } else if (LF_ISSET(RE_C_TAG)) { + if (re_tag_conv(sp, &ptrn, &plen, &replaced)) + return (1); + } else + if (re_conv(sp, &ptrn, &plen, &replaced)) + return (1); + + /* Discard previous pattern. */ + if (*ptrnp != NULL) { + free(*ptrnp); + *ptrnp = NULL; + } + if (lenp != NULL) + *lenp = plen; + + /* + * Copy the string into allocated memory. + * + * XXX + * Regcomp isn't 8-bit clean, so the pattern is nul-terminated + * for now. There's just no other solution. + */ + MALLOC(sp, *ptrnp, char *, plen + 1); + if (*ptrnp != NULL) { + memcpy(*ptrnp, ptrn, plen); + (*ptrnp)[plen] = '\0'; + } + + /* Free up conversion-routine-allocated memory. */ + if (replaced) + FREE_SPACE(sp, ptrn, 0); + + if (*ptrnp == NULL) + return (1); + + ptrn = *ptrnp; + } + + /* + * XXX + * Regcomp isn't 8-bit clean, so we just lost if the pattern + * contained a nul. Bummer! + */ + if ((rval = regcomp(rep, ptrn, /* plen, */ reflags)) != 0) { + if (!LF_ISSET(RE_C_SILENT)) + re_error(sp, rval, rep); + return (1); + } + + if (LF_ISSET(RE_C_SEARCH)) + F_SET(sp, SC_RE_SEARCH); + if (LF_ISSET(RE_C_SUBST)) + F_SET(sp, SC_RE_SUBST); + + return (0); +} + +/* + * re_conv -- + * Convert vi's regular expressions into something that the + * the POSIX 1003.2 RE functions can handle. + * + * There are three conversions we make to make vi's RE's (specifically + * the global, search, and substitute patterns) work with POSIX RE's. + * + * 1: If O_MAGIC is not set, strip backslashes from the magic character + * set (.[*~) that have them, and add them to the ones that don't. + * 2: If O_MAGIC is not set, the string "\~" is replaced with the text + * from the last substitute command's replacement string. If O_MAGIC + * is set, it's the string "~". + * 3: The pattern \<ptrn\> does "word" searches, convert it to use the + * new RE escapes. + * + * !!!/XXX + * This doesn't exactly match the historic behavior of vi because we do + * the ~ substitution before calling the RE engine, so magic characters + * in the replacement string will be expanded by the RE engine, and they + * weren't historically. It's a bug. + */ +static int +re_conv(sp, ptrnp, plenp, replacedp) + SCR *sp; + char **ptrnp; + size_t *plenp; + int *replacedp; +{ + size_t blen, len, needlen; + int magic; + char *bp, *p, *t; + + /* + * First pass through, we figure out how much space we'll need. + * We do it in two passes, on the grounds that most of the time + * the user is doing a search and won't have magic characters. + * That way we can skip most of the memory allocation and copies. + */ + magic = 0; + for (p = *ptrnp, len = *plenp, needlen = 0; len > 0; ++p, --len) + switch (*p) { + case '\\': + if (len > 1) { + --len; + switch (*++p) { + case '<': + magic = 1; + needlen += sizeof(RE_WSTART); + break; + case '>': + magic = 1; + needlen += sizeof(RE_WSTOP); + break; + case '~': + if (!O_ISSET(sp, O_MAGIC)) { + magic = 1; + needlen += sp->repl_len; + } + break; + case '.': + case '[': + case '*': + if (!O_ISSET(sp, O_MAGIC)) { + magic = 1; + needlen += 1; + } + break; + default: + needlen += 2; + } + } else + needlen += 1; + break; + case '~': + if (O_ISSET(sp, O_MAGIC)) { + magic = 1; + needlen += sp->repl_len; + } + break; + case '.': + case '[': + case '*': + if (!O_ISSET(sp, O_MAGIC)) { + magic = 1; + needlen += 2; + } + break; + default: + needlen += 1; + break; + } + + if (!magic) { + *replacedp = 0; + return (0); + } + + /* Get enough memory to hold the final pattern. */ + *replacedp = 1; + GET_SPACE_RET(sp, bp, blen, needlen); + + for (p = *ptrnp, len = *plenp, t = bp; len > 0; ++p, --len) + switch (*p) { + case '\\': + if (len > 1) { + --len; + switch (*++p) { + case '<': + memcpy(t, + RE_WSTART, sizeof(RE_WSTART) - 1); + t += sizeof(RE_WSTART) - 1; + break; + case '>': + memcpy(t, + RE_WSTOP, sizeof(RE_WSTOP) - 1); + t += sizeof(RE_WSTOP) - 1; + break; + case '~': + if (O_ISSET(sp, O_MAGIC)) + *t++ = '~'; + else { + memcpy(t, + sp->repl, sp->repl_len); + t += sp->repl_len; + } + break; + case '.': + case '[': + case '*': + if (O_ISSET(sp, O_MAGIC)) + *t++ = '\\'; + *t++ = *p; + break; + default: + *t++ = '\\'; + *t++ = *p; + } + } else + *t++ = '\\'; + break; + case '~': + if (O_ISSET(sp, O_MAGIC)) { + memcpy(t, sp->repl, sp->repl_len); + t += sp->repl_len; + } else + *t++ = '~'; + break; + case '.': + case '[': + case '*': + if (!O_ISSET(sp, O_MAGIC)) + *t++ = '\\'; + *t++ = *p; + break; + default: + *t++ = *p; + break; + } + + *ptrnp = bp; + *plenp = t - bp; + return (0); +} + +/* + * re_tag_conv -- + * Convert a tags search path into something that the POSIX + * 1003.2 RE functions can handle. + */ +static int +re_tag_conv(sp, ptrnp, plenp, replacedp) + SCR *sp; + char **ptrnp; + size_t *plenp; + int *replacedp; +{ + size_t blen, len; + int lastdollar; + char *bp, *p, *t; + + len = *plenp; + + /* Max memory usage is 2 times the length of the string. */ + *replacedp = 1; + GET_SPACE_RET(sp, bp, blen, len * 2); + + p = *ptrnp; + t = bp; + + /* If the last character is a '/' or '?', we just strip it. */ + if (len > 0 && (p[len - 1] == '/' || p[len - 1] == '?')) + --len; + + /* If the next-to-last or last character is a '$', it's magic. */ + if (len > 0 && p[len - 1] == '$') { + --len; + lastdollar = 1; + } else + lastdollar = 0; + + /* If the first character is a '/' or '?', we just strip it. */ + if (len > 0 && (p[0] == '/' || p[0] == '?')) { + ++p; + --len; + } + + /* If the first or second character is a '^', it's magic. */ + if (p[0] == '^') { + *t++ = *p++; + --len; + } + + /* + * Escape every other magic character we can find, meanwhile stripping + * the backslashes ctags inserts when escaping the search delimiter + * characters. + */ + for (; len > 0; --len) { + if (p[0] == '\\' && (p[1] == '/' || p[1] == '?')) { + ++p; + --len; + } else if (strchr("^.[]$*", p[0])) + *t++ = '\\'; + *t++ = *p++; + } + if (lastdollar) + *t++ = '$'; + + *ptrnp = bp; + *plenp = t - bp; + return (0); +} + +/* + * re_cscope_conv -- + * Convert a cscope search path into something that the POSIX + * 1003.2 RE functions can handle. + */ +static int +re_cscope_conv(sp, ptrnp, plenp, replacedp) + SCR *sp; + char **ptrnp; + size_t *plenp; + int *replacedp; +{ + size_t blen, len, nspaces; + char *bp, *p, *t; + + /* + * Each space in the source line printed by cscope represents an + * arbitrary sequence of spaces, tabs, and comments. + */ +#define CSCOPE_RE_SPACE "([ \t]|/\\*([^*]|\\*/)*\\*/)*" + for (nspaces = 0, p = *ptrnp, len = *plenp; len > 0; ++p, --len) + if (*p == ' ') + ++nspaces; + + /* + * Allocate plenty of space: + * the string, plus potential escaping characters; + * nspaces + 2 copies of CSCOPE_RE_SPACE; + * ^, $, nul terminator characters. + */ + *replacedp = 1; + len = (p - *ptrnp) * 2 + (nspaces + 2) * sizeof(CSCOPE_RE_SPACE) + 3; + GET_SPACE_RET(sp, bp, blen, len); + + p = *ptrnp; + t = bp; + + *t++ = '^'; + memcpy(t, CSCOPE_RE_SPACE, sizeof(CSCOPE_RE_SPACE) - 1); + t += sizeof(CSCOPE_RE_SPACE) - 1; + + for (len = *plenp; len > 0; ++p, --len) + if (*p == ' ') { + memcpy(t, CSCOPE_RE_SPACE, sizeof(CSCOPE_RE_SPACE) - 1); + t += sizeof(CSCOPE_RE_SPACE) - 1; + } else { + if (strchr("\\^.[]$*+?()|{}", *p)) + *t++ = '\\'; + *t++ = *p; + } + + memcpy(t, CSCOPE_RE_SPACE, sizeof(CSCOPE_RE_SPACE) - 1); + t += sizeof(CSCOPE_RE_SPACE) - 1; + *t++ = '$'; + + *ptrnp = bp; + *plenp = t - bp; + return (0); +} + +/* + * re_error -- + * Report a regular expression error. + * + * PUBLIC: void re_error __P((SCR *, int, regex_t *)); + */ +void +re_error(sp, errcode, preg) + SCR *sp; + int errcode; + regex_t *preg; +{ + size_t s; + char *oe; + + s = regerror(errcode, preg, "", 0); + if ((oe = malloc(s)) == NULL) + msgq(sp, M_SYSERR, NULL); + else { + (void)regerror(errcode, preg, oe, s); + msgq(sp, M_ERR, "RE error: %s", oe); + free(oe); + } +} + +/* + * re_sub -- + * Do the substitution for a regular expression. + */ +static int +re_sub(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 escaping characters. This (hopefully) matches historic practice. + */ +#define OUTCH(ch, nltrans) { \ + CHAR_T __ch = (ch); \ + u_int __value = KEY_VAL(sp, __ch); \ + if (nltrans && (__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) + OUTCH(*t, 0); + 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; + } + } + OUTCH(ch, 1); + } + + *lbp = lb; /* Update caller's information. */ + *lbclenp = lbclen; + *lblenp = lblen; + return (0); +} diff --git a/ex/ex_tag.c b/ex/ex_tag.c new file mode 100644 index 000000000000..461b1526ef00 --- /dev/null +++ b/ex/ex_tag.c @@ -0,0 +1,1324 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * David Hitz of Auspex Systems, Inc. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_tag.c 10.36 (Berkeley) 9/15/96"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/types.h> /* XXX: param.h may not have included types.h */ + +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + +#include <sys/queue.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" +#include "../vi/vi.h" +#include "tag.h" + +static char *binary_search __P((char *, char *, char *)); +static int compare __P((char *, char *, char *)); +static void ctag_file __P((SCR *, TAGF *, char *, char **, size_t *)); +static int ctag_search __P((SCR *, char *, size_t, char *)); +static int ctag_sfile __P((SCR *, TAGF *, TAGQ *, char *)); +static TAGQ *ctag_slist __P((SCR *, char *)); +static char *linear_search __P((char *, char *, char *)); +static int tag_copy __P((SCR *, TAG *, TAG **)); +static int tag_pop __P((SCR *, TAGQ *, int)); +static int tagf_copy __P((SCR *, TAGF *, TAGF **)); +static int tagf_free __P((SCR *, TAGF *)); +static int tagq_copy __P((SCR *, TAGQ *, TAGQ **)); + +/* + * ex_tag_first -- + * The tag code can be entered from main, e.g., "vi -t tag". + * + * PUBLIC: int ex_tag_first __P((SCR *, char *)); + */ +int +ex_tag_first(sp, tagarg) + SCR *sp; + char *tagarg; +{ + ARGS *ap[2], a; + EXCMD cmd; + + /* Build an argument for the ex :tag command. */ + ex_cinit(&cmd, C_TAG, 0, OOBLNO, OOBLNO, 0, ap); + ex_cadd(&cmd, &a, tagarg, strlen(tagarg)); + + /* + * XXX + * Historic vi went ahead and created a temporary file when it failed + * to find the tag. We match historic practice, but don't distinguish + * between real error and failure to find the tag. + */ + if (ex_tag_push(sp, &cmd)) + return (0); + + /* Display tags in the center of the screen. */ + F_CLR(sp, SC_SCR_TOP); + F_SET(sp, SC_SCR_CENTER); + + return (0); +} + +/* + * ex_tag_push -- ^] + * :tag[!] [string] + * + * Enter a new TAGQ context based on a ctag string. + * + * PUBLIC: int ex_tag_push __P((SCR *, EXCMD *)); + */ +int +ex_tag_push(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + EX_PRIVATE *exp; + FREF *frp; + TAG *rtp; + TAGQ *rtqp, *tqp; + recno_t lno; + size_t cno; + long tl; + int force, istmp; + + exp = EXP(sp); + switch (cmdp->argc) { + case 1: + if (exp->tag_last != NULL) + free(exp->tag_last); + + if ((exp->tag_last = strdup(cmdp->argv[0]->bp)) == NULL) { + msgq(sp, M_SYSERR, NULL); + return (1); + } + + /* Taglength may limit the number of characters. */ + if ((tl = + O_VAL(sp, O_TAGLENGTH)) != 0 && strlen(exp->tag_last) > tl) + exp->tag_last[tl] = '\0'; + break; + case 0: + if (exp->tag_last == NULL) { + msgq(sp, M_ERR, "158|No previous tag entered"); + return (1); + } + break; + default: + abort(); + } + + /* Get the tag information. */ + if ((tqp = ctag_slist(sp, exp->tag_last)) == NULL) + return (1); + + /* + * Allocate all necessary memory before swapping screens. Initialize + * flags so we know what to free. + */ + rtp = NULL; + rtqp = NULL; + if (exp->tq.cqh_first == (void *)&exp->tq) { + /* Initialize the `local context' tag queue structure. */ + CALLOC_GOTO(sp, rtqp, TAGQ *, 1, sizeof(TAGQ)); + CIRCLEQ_INIT(&rtqp->tagq); + + /* Initialize and link in its tag structure. */ + CALLOC_GOTO(sp, rtp, TAG *, 1, sizeof(TAG)); + CIRCLEQ_INSERT_HEAD(&rtqp->tagq, rtp, q); + rtqp->current = rtp; + } + + /* + * Stick the current context information in a convenient place, we're + * about to lose it. Note, if we're called on editor startup, there + * will be no FREF structure. + */ + frp = sp->frp; + lno = sp->lno; + cno = sp->cno; + istmp = frp == NULL || + F_ISSET(frp, FR_TMPFILE) && !F_ISSET(cmdp, E_NEWSCREEN); + + /* Try to switch to the tag. */ + force = FL_ISSET(cmdp->iflags, E_C_FORCE); + if (F_ISSET(cmdp, E_NEWSCREEN)) { + if (ex_tag_Nswitch(sp, tqp->tagq.cqh_first, force)) + goto err; + + /* Everything else gets done in the new screen. */ + sp = sp->nextdisp; + exp = EXP(sp); + } else + if (ex_tag_nswitch(sp, tqp->tagq.cqh_first, force)) + goto err; + + /* + * If this is the first tag, put a `current location' queue entry + * in place, so we can pop all the way back to the current mark. + * Note, it doesn't point to much of anything, it's a placeholder. + */ + if (exp->tq.cqh_first == (void *)&exp->tq) { + CIRCLEQ_INSERT_HEAD(&exp->tq, rtqp, q); + } else + rtqp = exp->tq.cqh_first; + + /* Link the new TAGQ structure into place. */ + CIRCLEQ_INSERT_HEAD(&exp->tq, tqp, q); + + (void)ctag_search(sp, + tqp->current->search, tqp->current->slen, tqp->tag); + + /* + * Move the current context from the temporary save area into the + * right structure. + * + * If we were in a temporary file, we don't have a context to which + * we can return, so just make it be the same as what we're moving + * to. It will be a little odd that ^T doesn't change anything, but + * I don't think it's a big deal. + */ + if (istmp) { + rtqp->current->frp = sp->frp; + rtqp->current->lno = sp->lno; + rtqp->current->cno = sp->cno; + } else { + rtqp->current->frp = frp; + rtqp->current->lno = lno; + rtqp->current->cno = cno; + } + return (0); + +err: +alloc_err: + if (rtqp != NULL) + free(rtqp); + if (rtp != NULL) + free(rtp); + tagq_free(sp, tqp); + return (1); +} + +/* + * ex_tag_next -- + * Switch context to the next TAG. + * + * PUBLIC: int ex_tag_next __P((SCR *, EXCMD *)); + */ +int +ex_tag_next(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + EX_PRIVATE *exp; + TAG *tp; + TAGQ *tqp; + + exp = EXP(sp); + if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) { + tag_msg(sp, TAG_EMPTY, NULL); + return (1); + } + if ((tp = tqp->current->q.cqe_next) == (void *)&tqp->tagq) { + msgq(sp, M_ERR, "282|Already at the last tag of this group"); + return (1); + } + if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE))) + return (1); + tqp->current = tp; + + if (F_ISSET(tqp, TAG_CSCOPE)) + (void)cscope_search(sp, tqp, tp); + else + (void)ctag_search(sp, tp->search, tp->slen, tqp->tag); + return (0); +} + +/* + * ex_tag_prev -- + * Switch context to the next TAG. + * + * PUBLIC: int ex_tag_prev __P((SCR *, EXCMD *)); + */ +int +ex_tag_prev(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + EX_PRIVATE *exp; + TAG *tp; + TAGQ *tqp; + + exp = EXP(sp); + if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) { + tag_msg(sp, TAG_EMPTY, NULL); + return (0); + } + if ((tp = tqp->current->q.cqe_prev) == (void *)&tqp->tagq) { + msgq(sp, M_ERR, "255|Already at the first tag of this group"); + return (1); + } + if (ex_tag_nswitch(sp, tp, FL_ISSET(cmdp->iflags, E_C_FORCE))) + return (1); + tqp->current = tp; + + if (F_ISSET(tqp, TAG_CSCOPE)) + (void)cscope_search(sp, tqp, tp); + else + (void)ctag_search(sp, tp->search, tp->slen, tqp->tag); + return (0); +} + +/* + * ex_tag_nswitch -- + * Switch context to the specified TAG. + * + * PUBLIC: int ex_tag_nswitch __P((SCR *, TAG *, int)); + */ +int +ex_tag_nswitch(sp, tp, force) + SCR *sp; + TAG *tp; + int force; +{ + /* Get a file structure. */ + if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL) + return (1); + + /* If not changing files, return, we're done. */ + if (tp->frp == sp->frp) + return (0); + + /* Check for permission to leave. */ + if (file_m1(sp, force, FS_ALL | FS_POSSIBLE)) + return (1); + + /* Initialize the new file. */ + if (file_init(sp, tp->frp, NULL, FS_SETALT)) + return (1); + + /* Display tags in the center of the screen. */ + F_CLR(sp, SC_SCR_TOP); + F_SET(sp, SC_SCR_CENTER); + + /* Switch. */ + F_SET(sp, SC_FSWITCH); + return (0); +} + +/* + * ex_tag_Nswitch -- + * Switch context to the specified TAG in a new screen. + * + * PUBLIC: int ex_tag_Nswitch __P((SCR *, TAG *, int)); + */ +int +ex_tag_Nswitch(sp, tp, force) + SCR *sp; + TAG *tp; + int force; +{ + SCR *new; + + /* Get a file structure. */ + if (tp->frp == NULL && (tp->frp = file_add(sp, tp->fname)) == NULL) + return (1); + + /* Get a new screen. */ + if (screen_init(sp->gp, sp, &new)) + return (1); + if (vs_split(sp, new, 0)) { + (void)file_end(new, new->ep, 1); + (void)screen_end(new); + return (1); + } + + /* Get a backing file. */ + if (tp->frp == sp->frp) { + /* Copy file state. */ + new->ep = sp->ep; + ++new->ep->refcnt; + + new->frp = tp->frp; + new->frp->flags = sp->frp->flags; + } else if (file_init(new, tp->frp, NULL, force)) { + (void)vs_discard(new, NULL); + (void)screen_end(new); + return (1); + } + + /* Create the argument list. */ + new->cargv = new->argv = ex_buildargv(sp, NULL, tp->frp->name); + + /* Display tags in the center of the screen. */ + F_CLR(new, SC_SCR_TOP); + F_SET(new, SC_SCR_CENTER); + + /* Switch. */ + sp->nextdisp = new; + F_SET(sp, SC_SSWITCH); + + return (0); +} + +/* + * ex_tag_pop -- ^T + * :tagp[op][!] [number | file] + * + * Pop to a previous TAGQ context. + * + * PUBLIC: int ex_tag_pop __P((SCR *, EXCMD *)); + */ +int +ex_tag_pop(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + EX_PRIVATE *exp; + TAGQ *tqp, *dtqp; + size_t arglen; + long off; + char *arg, *p, *t; + + /* Check for an empty stack. */ + exp = EXP(sp); + if (exp->tq.cqh_first == (void *)&exp->tq) { + tag_msg(sp, TAG_EMPTY, NULL); + return (1); + } + + /* Find the last TAG structure that we're going to DISCARD! */ + switch (cmdp->argc) { + case 0: /* Pop one tag. */ + dtqp = exp->tq.cqh_first; + break; + case 1: /* Name or number. */ + arg = cmdp->argv[0]->bp; + off = strtol(arg, &p, 10); + if (*p != '\0') + goto filearg; + + /* Number: pop that many queue entries. */ + if (off < 1) + return (0); + for (tqp = exp->tq.cqh_first; + tqp != (void *)&exp->tq && --off > 1; + tqp = tqp->q.cqe_next); + if (tqp == (void *)&exp->tq) { + msgq(sp, M_ERR, + "159|Less than %s entries on the tags stack; use :display t[ags]", + arg); + return (1); + } + dtqp = tqp; + break; + + /* File argument: pop to that queue entry. */ +filearg: arglen = strlen(arg); + for (tqp = exp->tq.cqh_first; + tqp != (void *)&exp->tq; + dtqp = tqp, tqp = tqp->q.cqe_next) { + /* Don't pop to the current file. */ + if (tqp == exp->tq.cqh_first) + continue; + p = tqp->current->frp->name; + if ((t = strrchr(p, '/')) == NULL) + t = p; + else + ++t; + if (!strncmp(arg, t, arglen)) + break; + } + if (tqp == (void *)&exp->tq) { + msgq_str(sp, M_ERR, arg, + "160|No file %s on the tags stack to return to; use :display t[ags]"); + return (1); + } + if (tqp == exp->tq.cqh_first) + return (0); + break; + default: + abort(); + } + + return (tag_pop(sp, dtqp, FL_ISSET(cmdp->iflags, E_C_FORCE))); +} + +/* + * ex_tag_top -- :tagt[op][!] + * Clear the tag stack. + * + * PUBLIC: int ex_tag_top __P((SCR *, EXCMD *)); + */ +int +ex_tag_top(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + EX_PRIVATE *exp; + + exp = EXP(sp); + + /* Check for an empty stack. */ + if (exp->tq.cqh_first == (void *)&exp->tq) { + tag_msg(sp, TAG_EMPTY, NULL); + return (1); + } + + /* Return to the oldest information. */ + return (tag_pop(sp, + exp->tq.cqh_last->q.cqe_prev, FL_ISSET(cmdp->iflags, E_C_FORCE))); +} + +/* + * tag_pop -- + * Pop up to and including the specified TAGQ context. + */ +static int +tag_pop(sp, dtqp, force) + SCR *sp; + TAGQ *dtqp; + int force; +{ + EX_PRIVATE *exp; + TAG *tp; + TAGQ *tqp; + + exp = EXP(sp); + + /* + * Update the cursor from the saved TAG information of the TAG + * structure we're moving to. + */ + tp = dtqp->q.cqe_next->current; + if (tp->frp == sp->frp) { + sp->lno = tp->lno; + sp->cno = tp->cno; + } else { + if (file_m1(sp, force, FS_ALL | FS_POSSIBLE)) + return (1); + + tp->frp->lno = tp->lno; + tp->frp->cno = tp->cno; + F_SET(sp->frp, FR_CURSORSET); + if (file_init(sp, tp->frp, NULL, FS_SETALT)) + return (1); + + F_SET(sp, SC_FSWITCH); + } + + /* Pop entries off the queue up to and including dtqp. */ + do { + tqp = exp->tq.cqh_first; + if (tagq_free(sp, tqp)) + return (0); + } while (tqp != dtqp); + + /* + * If only a single tag left, we've returned to the first tag point, + * and the stack is now empty. + */ + if (exp->tq.cqh_first->q.cqe_next == (void *)&exp->tq) + tagq_free(sp, exp->tq.cqh_first); + + return (0); +} + +/* + * ex_tag_display -- + * Display the list of tags. + * + * PUBLIC: int ex_tag_display __P((SCR *)); + */ +int +ex_tag_display(sp) + SCR *sp; +{ + EX_PRIVATE *exp; + TAG *tp; + TAGQ *tqp; + int cnt; + size_t len; + char *p, *sep; + + exp = EXP(sp); + if ((tqp = exp->tq.cqh_first) == (void *)&exp->tq) { + tag_msg(sp, TAG_EMPTY, NULL); + return (0); + } + + /* + * We give the file name 20 columns and the search string the rest. + * If there's not enough room, we don't do anything special, it's + * not worth the effort, it just makes the display more confusing. + * + * We also assume that characters in file names map 1-1 to printing + * characters. This might not be true, but I don't think it's worth + * fixing. (The obvious fix is to pass the filenames through the + * msg_print function.) + */ +#define L_NAME 30 /* Name. */ +#define L_SLOP 4 /* Leading number plus trailing *. */ +#define L_SPACE 5 /* Spaces after name, before tag. */ +#define L_TAG 20 /* Tag. */ + if (sp->cols <= L_NAME + L_SLOP) { + msgq(sp, M_ERR, "292|Display too small."); + return (0); + } + + /* + * Display the list of tags for each queue entry. The first entry + * is numbered, and the current tag entry has an asterisk appended. + */ + for (cnt = 1, tqp = exp->tq.cqh_first; !INTERRUPTED(sp) && + tqp != (void *)&exp->tq; ++cnt, tqp = tqp->q.cqe_next) + for (tp = tqp->tagq.cqh_first; + tp != (void *)&tqp->tagq; tp = tp->q.cqe_next) { + if (tp == tqp->tagq.cqh_first) + (void)ex_printf(sp, "%2d ", cnt); + else + (void)ex_printf(sp, " "); + p = tp->frp == NULL ? tp->fname : tp->frp->name; + if ((len = strlen(p)) > L_NAME) { + len = len - (L_NAME - 4); + (void)ex_printf(sp, " ... %*.*s", + L_NAME - 4, L_NAME - 4, p + len); + } else + (void)ex_printf(sp, + " %*.*s", L_NAME, L_NAME, p); + if (tqp->current == tp) + (void)ex_printf(sp, "*"); + + if (tp == tqp->tagq.cqh_first && tqp->tag != NULL && + (sp->cols - L_NAME) >= L_TAG + L_SPACE) { + len = strlen(tqp->tag); + if (len > sp->cols - (L_NAME + L_SPACE)) + len = sp->cols - (L_NAME + L_SPACE); + (void)ex_printf(sp, "%s%.*s", + tqp->current == tp ? " " : " ", + (int)len, tqp->tag); + } + (void)ex_printf(sp, "\n"); + } + return (0); +} + +/* + * ex_tag_copy -- + * Copy a screen's tag structures. + * + * PUBLIC: int ex_tag_copy __P((SCR *, SCR *)); + */ +int +ex_tag_copy(orig, sp) + SCR *orig, *sp; +{ + EX_PRIVATE *oexp, *nexp; + TAGQ *aqp, *tqp; + TAG *ap, *tp; + TAGF *atfp, *tfp; + + oexp = EXP(orig); + nexp = EXP(sp); + + /* Copy tag queue and tags stack. */ + for (aqp = oexp->tq.cqh_first; + aqp != (void *)&oexp->tq; aqp = aqp->q.cqe_next) { + if (tagq_copy(sp, aqp, &tqp)) + return (1); + for (ap = aqp->tagq.cqh_first; + ap != (void *)&aqp->tagq; ap = ap->q.cqe_next) { + if (tag_copy(sp, ap, &tp)) + return (1); + /* Set the current pointer. */ + if (aqp->current == ap) + tqp->current = tp; + CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q); + } + CIRCLEQ_INSERT_TAIL(&nexp->tq, tqp, q); + } + + /* Copy list of tag files. */ + for (atfp = oexp->tagfq.tqh_first; + atfp != NULL; atfp = atfp->q.tqe_next) { + if (tagf_copy(sp, atfp, &tfp)) + return (1); + TAILQ_INSERT_TAIL(&nexp->tagfq, tfp, q); + } + + /* Copy the last tag. */ + if (oexp->tag_last != NULL && + (nexp->tag_last = strdup(oexp->tag_last)) == NULL) { + msgq(sp, M_SYSERR, NULL); + return (1); + } + return (0); +} + +/* + * tagf_copy -- + * Copy a TAGF structure and return it in new memory. + */ +static int +tagf_copy(sp, otfp, tfpp) + SCR *sp; + TAGF *otfp, **tfpp; +{ + TAGF *tfp; + + MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF)); + *tfp = *otfp; + + /* XXX: Allocate as part of the TAGF structure!!! */ + if ((tfp->name = strdup(otfp->name)) == NULL) + return (1); + + *tfpp = tfp; + return (0); +} + +/* + * tagq_copy -- + * Copy a TAGQ structure and return it in new memory. + */ +static int +tagq_copy(sp, otqp, tqpp) + SCR *sp; + TAGQ *otqp, **tqpp; +{ + TAGQ *tqp; + size_t len; + + len = sizeof(TAGQ); + if (otqp->tag != NULL) + len += otqp->tlen + 1; + MALLOC_RET(sp, tqp, TAGQ *, len); + memcpy(tqp, otqp, len); + + CIRCLEQ_INIT(&tqp->tagq); + tqp->current = NULL; + if (otqp->tag != NULL) + tqp->tag = tqp->buf; + + *tqpp = tqp; + return (0); +} + +/* + * tag_copy -- + * Copy a TAG structure and return it in new memory. + */ +static int +tag_copy(sp, otp, tpp) + SCR *sp; + TAG *otp, **tpp; +{ + TAG *tp; + size_t len; + + len = sizeof(TAG); + if (otp->fname != NULL) + len += otp->fnlen + 1; + if (otp->search != NULL) + len += otp->slen + 1; + MALLOC_RET(sp, tp, TAG *, len); + memcpy(tp, otp, len); + + if (otp->fname != NULL) + tp->fname = tp->buf; + if (otp->search != NULL) + tp->search = tp->fname + otp->fnlen + 1; + + *tpp = tp; + return (0); +} + +/* + * tagf_free -- + * Free a TAGF structure. + */ +static int +tagf_free(sp, tfp) + SCR *sp; + TAGF *tfp; +{ + EX_PRIVATE *exp; + + exp = EXP(sp); + TAILQ_REMOVE(&exp->tagfq, tfp, q); + free(tfp->name); + free(tfp); + return (0); +} + +/* + * tagq_free -- + * Free a TAGQ structure (and associated TAG structures). + * + * PUBLIC: int tagq_free __P((SCR *, TAGQ *)); + */ +int +tagq_free(sp, tqp) + SCR *sp; + TAGQ *tqp; +{ + EX_PRIVATE *exp; + TAG *tp; + + exp = EXP(sp); + while ((tp = tqp->tagq.cqh_first) != (void *)&tqp->tagq) { + CIRCLEQ_REMOVE(&tqp->tagq, tp, q); + free(tp); + } + /* + * !!! + * If allocated and then the user failed to switch files, the TAGQ + * structure was never attached to any list. + */ + if (tqp->q.cqe_next != NULL) + CIRCLEQ_REMOVE(&exp->tq, tqp, q); + free(tqp); + return (0); +} + +/* + * tag_msg + * A few common messages. + * + * PUBLIC: void tag_msg __P((SCR *, tagmsg_t, char *)); + */ +void +tag_msg(sp, msg, tag) + SCR *sp; + tagmsg_t msg; + char *tag; +{ + switch (msg) { + case TAG_BADLNO: + msgq_str(sp, M_ERR, tag, + "164|%s: the tag's line number is past the end of the file"); + break; + case TAG_EMPTY: + msgq(sp, M_INFO, "165|The tags stack is empty"); + break; + case TAG_SEARCH: + msgq_str(sp, M_ERR, tag, "166|%s: search pattern not found"); + break; + default: + abort(); + } +} + +/* + * ex_tagf_alloc -- + * Create a new list of ctag files. + * + * PUBLIC: int ex_tagf_alloc __P((SCR *, char *)); + */ +int +ex_tagf_alloc(sp, str) + SCR *sp; + char *str; +{ + EX_PRIVATE *exp; + TAGF *tfp; + size_t len; + char *p, *t; + + /* Free current queue. */ + exp = EXP(sp); + while ((tfp = exp->tagfq.tqh_first) != NULL) + tagf_free(sp, tfp); + + /* Create new queue. */ + for (p = t = str;; ++p) { + if (*p == '\0' || isblank(*p)) { + if ((len = p - t) > 1) { + MALLOC_RET(sp, tfp, TAGF *, sizeof(TAGF)); + MALLOC(sp, tfp->name, char *, len + 1); + if (tfp->name == NULL) { + free(tfp); + return (1); + } + memcpy(tfp->name, t, len); + tfp->name[len] = '\0'; + tfp->flags = 0; + TAILQ_INSERT_TAIL(&exp->tagfq, tfp, q); + } + t = p + 1; + } + if (*p == '\0') + break; + } + return (0); +} + /* Free previous queue. */ +/* + * ex_tag_free -- + * Free the ex tag information. + * + * PUBLIC: int ex_tag_free __P((SCR *)); + */ +int +ex_tag_free(sp) + SCR *sp; +{ + EX_PRIVATE *exp; + TAGF *tfp; + TAGQ *tqp; + + /* Free up tag information. */ + exp = EXP(sp); + while ((tqp = exp->tq.cqh_first) != (void *)&exp->tq) + tagq_free(sp, tqp); + while ((tfp = exp->tagfq.tqh_first) != NULL) + tagf_free(sp, tfp); + if (exp->tag_last != NULL) + free(exp->tag_last); + return (0); +} + +/* + * ctag_search -- + * Search a file for a tag. + */ +static int +ctag_search(sp, search, slen, tag) + SCR *sp; + char *search, *tag; + size_t slen; +{ + MARK m; + char *p; + + /* + * !!! + * 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. POSIX 1003.2 permits it. + */ + if (isdigit(search[0])) { + m.lno = atoi(search); + if (!db_exist(sp, m.lno)) { + tag_msg(sp, TAG_BADLNO, tag); + return (1); + } + } 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; + if (f_search(sp, &m, &m, + search, slen, NULL, SEARCH_FILE | SEARCH_TAG)) + if ((p = strrchr(search, '(')) != NULL) { + slen = p - search; + if (f_search(sp, &m, &m, search, slen, + NULL, SEARCH_FILE | SEARCH_TAG)) + goto notfound; + } else { +notfound: tag_msg(sp, TAG_SEARCH, tag); + return (1); + } + /* + * !!! + * Historically, tags set the search direction if it wasn't + * already set. + */ + if (sp->searchdir == NOTSET) + sp->searchdir = FORWARD; + } + + /* + * !!! + * Tags move to the first non-blank, NOT the search pattern start. + */ + sp->lno = m.lno; + sp->cno = 0; + (void)nonblank(sp, sp->lno, &sp->cno); + return (0); +} + +/* + * ctag_slist -- + * Search the list of tags files for a tag, and return tag queue. + */ +static TAGQ * +ctag_slist(sp, tag) + SCR *sp; + char *tag; +{ + EX_PRIVATE *exp; + TAGF *tfp; + TAGQ *tqp; + size_t len; + int echk; + + exp = EXP(sp); + + /* Allocate and initialize the tag queue structure. */ + len = strlen(tag); + CALLOC_GOTO(sp, tqp, TAGQ *, 1, sizeof(TAGQ) + len + 1); + CIRCLEQ_INIT(&tqp->tagq); + tqp->tag = tqp->buf; + memcpy(tqp->tag, tag, (tqp->tlen = len) + 1); + + /* + * Find the tag, only display missing file messages once, and + * then only if we didn't find the tag. + */ + for (echk = 0, + tfp = exp->tagfq.tqh_first; tfp != NULL; tfp = tfp->q.tqe_next) + if (ctag_sfile(sp, tfp, tqp, tag)) { + echk = 1; + F_SET(tfp, TAGF_ERR); + } else + F_CLR(tfp, TAGF_ERR | TAGF_ERR_WARN); + + /* Check to see if we found anything. */ + if (tqp->tagq.cqh_first == (void *)&tqp->tagq) { + msgq_str(sp, M_ERR, tag, "162|%s: tag not found"); + if (echk) + for (tfp = exp->tagfq.tqh_first; + tfp != NULL; tfp = tfp->q.tqe_next) + if (F_ISSET(tfp, TAGF_ERR) && + !F_ISSET(tfp, TAGF_ERR_WARN)) { + errno = tfp->errnum; + msgq_str(sp, M_SYSERR, tfp->name, "%s"); + F_SET(tfp, TAGF_ERR_WARN); + } + free(tqp); + return (NULL); + } + + tqp->current = tqp->tagq.cqh_first; + return (tqp); + +alloc_err: + return (NULL); +} + +/* + * ctag_sfile -- + * Search a tags file for a tag, adding any found to the tag queue. + */ +static int +ctag_sfile(sp, tfp, tqp, tname) + SCR *sp; + TAGF *tfp; + TAGQ *tqp; + char *tname; +{ + struct stat sb; + TAG *tp; + size_t dlen, nlen, slen; + int fd, i, nf1, nf2; + char *back, *cname, *dname, *front, *map, *name, *p, *search, *t; + + if ((fd = open(tfp->name, O_RDONLY, 0)) < 0) { + tfp->errnum = errno; + return (1); + } + + /* + * XXX + * Some old BSD systems require MAP_FILE as an argument when mapping + * regular files. + */ +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif + /* + * 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 mmap fails if the file is too large. + */ + if (fstat(fd, &sb) != 0 || + (map = mmap(NULL, (size_t)sb.st_size, PROT_READ | PROT_WRITE, + MAP_FILE | MAP_PRIVATE, fd, (off_t)0)) == (caddr_t)-1) { + tfp->errnum = errno; + (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) + goto done; + + /* + * Initialize and link in the tag structure(s). The historic ctags + * file format only permitted a single tag location per tag. The + * obvious extension to permit multiple tags locations per tag is to + * output multiple records in the standard format. Unfortunately, + * this won't work correctly with historic ex/vi implementations, + * because their binary search assumes that there's only one record + * per tag, and so will use a random tag entry if there si more than + * one. This code handles either format. + * + * The tags file is in the following format: + * + * <tag> <filename> <line number> | <pattern> + * + * Figure out how long everything is so we can allocate in one swell + * foop, but discard anything that looks wrong. + */ + for (;;) { + /* Nul-terminate the end of the line. */ + for (p = front; p < back && *p != '\n'; ++p); + if (p == back || *p != '\n') + break; + *p = '\0'; + + /* Update the pointers for the next time. */ + t = p + 1; + p = front; + front = t; + + /* Break the line into tokens. */ + for (i = 0; i < 2 && (t = strsep(&p, "\t ")) != NULL; ++i) + switch (i) { + case 0: /* Tag. */ + cname = t; + break; + case 1: /* Filename. */ + name = t; + nlen = strlen(name); + break; + } + + /* Check for corruption. */ + if (i != 2 || p == NULL || t == NULL) + goto corrupt; + + /* The rest of the string is the search pattern. */ + search = p; + if ((slen = strlen(p)) == 0) { +corrupt: p = msg_print(sp, tname, &nf1); + t = msg_print(sp, tfp->name, &nf2); + msgq(sp, M_ERR, "163|%s: corrupted tag in %s", p, t); + if (nf1) + FREE_SPACE(sp, p, 0); + if (nf2) + FREE_SPACE(sp, t, 0); + continue; + } + + /* Check for passing the last entry. */ + if (strcmp(tname, cname)) + break; + + /* Resolve the file name. */ + ctag_file(sp, tfp, name, &dname, &dlen); + + CALLOC_GOTO(sp, tp, + TAG *, 1, sizeof(TAG) + dlen + 2 + nlen + 1 + slen + 1); + tp->fname = tp->buf; + if (dlen != 0) { + memcpy(tp->fname, dname, dlen); + tp->fname[dlen] = '/'; + ++dlen; + } + memcpy(tp->fname + dlen, name, nlen + 1); + tp->fnlen = dlen + nlen; + tp->search = tp->fname + tp->fnlen + 1; + memcpy(tp->search, search, (tp->slen = slen) + 1); + CIRCLEQ_INSERT_TAIL(&tqp->tagq, tp, q); + } + +alloc_err: +done: if (munmap(map, (size_t)sb.st_size)) + msgq(sp, M_SYSERR, "munmap"); + if (close(fd)) + msgq(sp, M_SYSERR, "close"); + return (0); +} + +/* + * ctag_file -- + * Search for the right path to this file. + */ +static void +ctag_file(sp, tfp, name, dirp, dlenp) + SCR *sp; + TAGF *tfp; + char *name, **dirp; + size_t *dlenp; +{ + struct stat sb; + size_t len; + char *p, buf[MAXPATHLEN]; + + /* + * !!! + * 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 get it right. + */ + *dlenp = 0; + if (name[0] != '/' && + stat(name, &sb) && (p = strrchr(tfp->name, '/')) != NULL) { + *p = '\0'; + len = snprintf(buf, sizeof(buf), "%s/%s", tfp->name, name); + *p = '/'; + if (stat(buf, &sb) == 0) { + *dirp = tfp->name; + *dlenp = strlen(*dirp); + } + } +} + +/* + * 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 EQUAL 0 +#define GREATER 1 +#define LESS (-1) + +#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/ex/ex_tcl.c b/ex/ex_tcl.c new file mode 100644 index 000000000000..06736a781006 --- /dev/null +++ b/ex/ex_tcl.c @@ -0,0 +1,80 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * Copyright (c) 1995 + * George V. Neville-Neil. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_tcl.c 8.10 (Berkeley) 9/15/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "../common/common.h" + +#ifdef HAVE_TCL_INTERP +#include <tcl.h> +#endif + +/* + * ex_tcl -- :[line [,line]] tcl [command] + * Run a command through the tcl interpreter. + * + * PUBLIC: int ex_tcl __P((SCR*, EXCMD *)); + */ +int +ex_tcl(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ +#ifdef HAVE_TCL_INTERP + CHAR_T *p; + GS *gp; + size_t len; + char buf[128]; + + /* Initialize the interpreter. */ + gp = sp->gp; + if (gp->tcl_interp == NULL && tcl_init(gp)) + return (1); + + /* Skip leading white space. */ + if (cmdp->argc != 0) + for (p = cmdp->argv[0]->bp, + len = cmdp->argv[0]->len; len > 0; --len, ++p) + if (!isblank(*p)) + break; + if (cmdp->argc == 0 || len == 0) { + ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); + return (1); + } + + (void)snprintf(buf, sizeof(buf), + "set viScreenId %d\nset viStartLine %lu\nset viStopLine %lu", + sp->id, cmdp->addr1.lno, cmdp->addr2.lno); + if (Tcl_Eval(gp->tcl_interp, buf) == TCL_OK && + Tcl_Eval(gp->tcl_interp, cmdp->argv[0]->bp) == TCL_OK) + return (0); + + msgq(sp, M_ERR, "Tcl: %s", ((Tcl_Interp *)gp->tcl_interp)->result); + return (1); +#else + msgq(sp, M_ERR, "302|Vi was not loaded with a Tcl interpreter"); + return (1); +#endif /* HAVE_TCL_INTERP */ +} diff --git a/ex/ex_txt.c b/ex/ex_txt.c new file mode 100644 index 000000000000..2f62ff51a47c --- /dev/null +++ b/ex/ex_txt.c @@ -0,0 +1,430 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_txt.c 10.17 (Berkeley) 10/10/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" + +/* + * !!! + * The backslash characters was special when it preceded a newline as part of + * a substitution replacement pattern. For example, the input ":a\<cr>" would + * failed immediately with an error, as the <cr> wasn't part of a substitution + * replacement pattern. This implies a frightening integration of the editor + * and the parser and/or the RE engine. There's no way I'm going to reproduce + * those semantics. + * + * So, if backslashes are special, this code inserts the backslash and the next + * character into the string, without regard for the character or the command + * being entered. Since "\<cr>" was illegal historically (except for the one + * special case), and the command will fail eventually, no historical scripts + * should break (presuming they didn't depend on the failure mode itself or the + * characters remaining when failure occurred. + */ + +static int txt_dent __P((SCR *, TEXT *)); +static void txt_prompt __P((SCR *, TEXT *, ARG_CHAR_T, u_int32_t)); + +/* + * ex_txt -- + * Get lines from the terminal for ex. + * + * PUBLIC: int ex_txt __P((SCR *, TEXTH *, ARG_CHAR_T, u_int32_t)); + */ +int +ex_txt(sp, tiqh, prompt, flags) + SCR *sp; + TEXTH *tiqh; + ARG_CHAR_T prompt; + u_int32_t flags; +{ + EVENT ev; + GS *gp; + TEXT ait, *ntp, *tp; + carat_t carat_st; + size_t cnt; + int rval; + + rval = 0; + + /* + * Get a TEXT structure with some initial buffer space, reusing the + * last one if it's big enough. (All TEXT bookkeeping fields default + * to 0 -- text_init() handles this.) + */ + if (tiqh->cqh_first != (void *)tiqh) { + tp = tiqh->cqh_first; + if (tp->q.cqe_next != (void *)tiqh || tp->lb_len < 32) { + text_lfree(tiqh); + goto newtp; + } + tp->len = 0; + } else { +newtp: if ((tp = text_init(sp, NULL, 0, 32)) == NULL) + goto err; + CIRCLEQ_INSERT_HEAD(tiqh, tp, q); + } + + /* Set the starting line number. */ + tp->lno = sp->lno + 1; + + /* + * If it's a terminal, set up autoindent, put out the prompt, and + * set it up so we know we were suspended. Otherwise, turn off + * the autoindent flag, as that requires less special casing below. + * + * XXX + * Historic practice is that ^Z suspended command mode (but, because + * it ran in cooked mode, it was unaffected by the autowrite option.) + * On restart, any "current" input was discarded, whether in insert + * mode or not, and ex was in command mode. This code matches historic + * practice, but not 'cause it's easier. + */ + gp = sp->gp; + if (F_ISSET(gp, G_SCRIPTED)) + LF_CLR(TXT_AUTOINDENT); + else { + if (LF_ISSET(TXT_AUTOINDENT)) { + LF_SET(TXT_EOFCHAR); + if (v_txt_auto(sp, sp->lno, NULL, 0, tp)) + goto err; + } + txt_prompt(sp, tp, prompt, flags); + } + + for (carat_st = C_NOTSET;;) { + if (v_event_get(sp, &ev, 0, 0)) + goto err; + + /* Deal with all non-character events. */ + switch (ev.e_event) { + case E_CHARACTER: + break; + case E_ERR: + goto err; + case E_REPAINT: + case E_WRESIZE: + continue; + case E_EOF: + rval = 1; + /* FALLTHROUGH */ + case E_INTERRUPT: + /* + * Handle EOF/SIGINT events by discarding partially + * entered text and returning. EOF returns failure, + * E_INTERRUPT returns success. + */ + goto notlast; + default: + v_event_err(sp, &ev); + goto notlast; + } + + /* + * Deal with character events. + * + * Check to see if the character fits into the input buffer. + * (Use tp->len, ignore overwrite and non-printable chars.) + */ + BINC_GOTO(sp, tp->lb, tp->lb_len, tp->len + 1); + + switch (ev.e_value) { + case K_CR: + /* + * !!! + * Historically, <carriage-return>'s in the command + * weren't special, so the ex parser would return an + * unknown command error message. However, if they + * terminated the command if they were in a map. I'm + * pretty sure this still isn't right, but it handles + * what I've seen so far. + */ + if (!F_ISSET(&ev.e_ch, CH_MAPPED)) + goto ins_ch; + /* FALLTHROUGH */ + case K_NL: + /* + * '\' can escape <carriage-return>/<newline>. We + * don't discard the backslash because we need it + * to get the <newline> through the ex parser. + */ + if (LF_ISSET(TXT_BACKSLASH) && + tp->len != 0 && tp->lb[tp->len - 1] == '\\') + goto ins_ch; + + /* + * CR returns from the ex command line. + * + * XXX + * Terminate with a nul, needed by filter. + */ + if (LF_ISSET(TXT_CR)) { + tp->lb[tp->len] = '\0'; + goto done; + } + + /* + * '.' may terminate text input mode; free the current + * TEXT. + */ + if (LF_ISSET(TXT_DOTTERM) && tp->len == tp->ai + 1 && + tp->lb[tp->len - 1] == '.') { +notlast: CIRCLEQ_REMOVE(tiqh, tp, q); + text_free(tp); + goto done; + } + + /* Set up bookkeeping for the new line. */ + if ((ntp = text_init(sp, NULL, 0, 32)) == NULL) + goto err; + ntp->lno = tp->lno + 1; + + /* + * Reset the autoindent line value. 0^D keeps the ai + * line from changing, ^D changes the level, even if + * there were no characters in the old line. Note, if + * using the current tp structure, use the cursor as + * the length, the autoindent characters may have been + * erased. + */ + if (LF_ISSET(TXT_AUTOINDENT)) { + if (carat_st == C_NOCHANGE) { + if (v_txt_auto(sp, + OOBLNO, &ait, ait.ai, ntp)) + goto err; + free(ait.lb); + } else + if (v_txt_auto(sp, + OOBLNO, tp, tp->len, ntp)) + goto err; + carat_st = C_NOTSET; + } + txt_prompt(sp, ntp, prompt, flags); + + /* + * Swap old and new TEXT's, and insert the new TEXT + * into the queue. + */ + tp = ntp; + CIRCLEQ_INSERT_TAIL(tiqh, tp, q); + break; + case K_CARAT: /* Delete autoindent chars. */ + if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) + carat_st = C_CARATSET; + goto ins_ch; + case K_ZERO: /* Delete autoindent chars. */ + if (tp->len <= tp->ai && LF_ISSET(TXT_AUTOINDENT)) + carat_st = C_ZEROSET; + goto ins_ch; + case K_CNTRLD: /* Delete autoindent char. */ + /* + * !!! + * Historically, the ^D command took (but then ignored) + * a count. For simplicity, we don't return it unless + * it's the first character entered. The check for len + * equal to 0 is okay, TXT_AUTOINDENT won't be set. + */ + if (LF_ISSET(TXT_CNTRLD)) { + for (cnt = 0; cnt < tp->len; ++cnt) + if (!isblank(tp->lb[cnt])) + break; + if (cnt == tp->len) { + tp->len = 1; + tp->lb[0] = ev.e_c; + tp->lb[1] = '\0'; + + /* + * Put out a line separator, in case + * the command fails. + */ + (void)putchar('\n'); + goto done; + } + } + + /* + * POSIX 1003.1b-1993, paragraph 7.1.1.9, states that + * the EOF characters are discarded if there are other + * characters to process in the line, i.e. if the EOF + * is not the first character in the line. For this + * reason, historic ex discarded the EOF characters, + * even if occurring in the middle of the input line. + * We match that historic practice. + * + * !!! + * The test for discarding in the middle of the line is + * done in the switch, because the CARAT forms are N+1, + * not N. + * + * !!! + * There's considerable magic to make the terminal code + * return the EOF character at all. See that code for + * details. + */ + if (!LF_ISSET(TXT_AUTOINDENT) || tp->len == 0) + continue; + switch (carat_st) { + case C_CARATSET: /* ^^D */ + if (tp->len > tp->ai + 1) + continue; + + /* Save the ai string for later. */ + ait.lb = NULL; + ait.lb_len = 0; + BINC_GOTO(sp, ait.lb, ait.lb_len, tp->ai); + memcpy(ait.lb, tp->lb, tp->ai); + ait.ai = ait.len = tp->ai; + + carat_st = C_NOCHANGE; + goto leftmargin; + case C_ZEROSET: /* 0^D */ + if (tp->len > tp->ai + 1) + continue; + + carat_st = C_NOTSET; +leftmargin: (void)gp->scr_ex_adjust(sp, EX_TERM_CE); + tp->ai = tp->len = 0; + break; + case C_NOTSET: /* ^D */ + if (tp->len > tp->ai) + continue; + + if (txt_dent(sp, tp)) + goto err; + break; + default: + abort(); + } + + /* Clear and redisplay the line. */ + (void)gp->scr_ex_adjust(sp, EX_TERM_CE); + txt_prompt(sp, tp, prompt, flags); + break; + default: + /* + * See the TXT_BEAUTIFY comment in vi/v_txt_ev.c. + * + * Silently eliminate any iscntrl() character that was + * not already handled specially, except for <tab> and + * <ff>. + */ +ins_ch: if (LF_ISSET(TXT_BEAUTIFY) && iscntrl(ev.e_c) && + ev.e_value != K_FORMFEED && ev.e_value != K_TAB) + break; + + tp->lb[tp->len++] = ev.e_c; + break; + } + } + /* NOTREACHED */ + +done: return (rval); + +err: +alloc_err: + return (1); +} + +/* + * txt_prompt -- + * Display the ex prompt, line number, ai characters. Characters had + * better be printable by the terminal driver, but that's its problem, + * not ours. + */ +static void +txt_prompt(sp, tp, prompt, flags) + SCR *sp; + TEXT *tp; + ARG_CHAR_T prompt; + u_int32_t flags; +{ + /* Display the prompt. */ + if (LF_ISSET(TXT_PROMPT)) + (void)printf("%c", prompt); + + /* Display the line number. */ + if (LF_ISSET(TXT_NUMBER) && O_ISSET(sp, O_NUMBER)) + (void)printf("%6lu ", (u_long)tp->lno); + + /* Print out autoindent string. */ + if (LF_ISSET(TXT_AUTOINDENT)) + (void)printf("%.*s", (int)tp->ai, tp->lb); + (void)fflush(stdout); +} + +/* + * txt_dent -- + * Handle ^D outdents. + * + * Ex version of vi/v_ntext.c:txt_dent(). See that code for the (usual) + * ranting and raving. This is a fair bit simpler as ^T isn't special. + */ +static int +txt_dent(sp, tp) + SCR *sp; + TEXT *tp; +{ + u_long sw, ts; + size_t cno, off, scno, spaces, tabs; + + ts = O_VAL(sp, O_TABSTOP); + sw = O_VAL(sp, O_SHIFTWIDTH); + + /* Get the current screen column. */ + for (off = scno = 0; off < tp->len; ++off) + if (tp->lb[off] == '\t') + scno += COL_OFF(scno, ts); + else + ++scno; + + /* Get the previous shiftwidth column. */ + cno = scno; + scno -= --scno % sw; + + /* + * Since we don't know what comes before the character(s) being + * deleted, we have to resolve the autoindent characters . The + * example is a <tab>, which doesn't take up a full shiftwidth + * number of columns because it's preceded by <space>s. This is + * easy to get if the user sets shiftwidth to a value less than + * tabstop, and then uses ^T to indent, and ^D to outdent. + * + * Count up spaces/tabs needed to get to the target. + */ + for (cno = 0, tabs = 0; cno + COL_OFF(cno, ts) <= scno; ++tabs) + cno += COL_OFF(cno, ts); + spaces = scno - cno; + + /* Make sure there's enough room. */ + BINC_RET(sp, tp->lb, tp->lb_len, tabs + spaces + 1); + + /* Adjust the final ai character count. */ + tp->ai = tabs + spaces; + + /* Enter the replacement characters. */ + for (tp->len = 0; tabs > 0; --tabs) + tp->lb[tp->len++] = '\t'; + for (; spaces > 0; --spaces) + tp->lb[tp->len++] = ' '; + return (0); +} diff --git a/ex/ex_undo.c b/ex/ex_undo.c new file mode 100644 index 000000000000..0b0b5b26be18 --- /dev/null +++ b/ex/ex_undo.c @@ -0,0 +1,77 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_undo.c 10.6 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../common/common.h" + +/* + * ex_undo -- u + * Undo the last change. + * + * PUBLIC: int ex_undo __P((SCR *, EXCMD *)); + */ +int +ex_undo(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + EXF *ep; + MARK m; + + /* + * !!! + * Historic undo always set the previous context mark. + */ + m.lno = sp->lno; + m.cno = sp->cno; + if (mark_set(sp, 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. + */ + ep = sp->ep; + if (!F_ISSET(ep, F_UNDO)) { + F_SET(ep, F_UNDO); + ep->lundo = FORWARD; + } + switch (ep->lundo) { + case BACKWARD: + if (log_forward(sp, &m)) + return (1); + ep->lundo = FORWARD; + break; + case FORWARD: + if (log_backward(sp, &m)) + return (1); + ep->lundo = BACKWARD; + break; + case NOTSET: + abort(); + } + sp->lno = m.lno; + sp->cno = m.cno; + return (0); +} diff --git a/ex/ex_usage.c b/ex/ex_usage.c new file mode 100644 index 000000000000..cddf7a6643c7 --- /dev/null +++ b/ex/ex_usage.c @@ -0,0 +1,196 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_usage.c 10.13 (Berkeley) 5/3/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <ctype.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" +#include "../vi/vi.h" + +/* + * ex_help -- :help + * Display help message. + * + * PUBLIC: int ex_help __P((SCR *, EXCMD *)); + */ +int +ex_help(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + (void)ex_puts(sp, + "To see the list of vi commands, enter \":viusage<CR>\"\n"); + (void)ex_puts(sp, + "To see the list of ex commands, enter \":exusage<CR>\"\n"); + (void)ex_puts(sp, + "For an ex command usage statement enter \":exusage [cmd]<CR>\"\n"); + (void)ex_puts(sp, + "For a vi key usage statement enter \":viusage [key]<CR>\"\n"); + (void)ex_puts(sp, "To exit, enter \":q!\"\n"); + return (0); +} + +/* + * ex_usage -- :exusage [cmd] + * Display ex usage strings. + * + * PUBLIC: int ex_usage __P((SCR *, EXCMD *)); + */ +int +ex_usage(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + ARGS *ap; + EXCMDLIST const *cp; + int newscreen; + char *name, *p, nb[MAXCMDNAMELEN + 5]; + + switch (cmdp->argc) { + case 1: + ap = cmdp->argv[0]; + if (isupper(ap->bp[0])) { + newscreen = 1; + ap->bp[0] = tolower(ap->bp[0]); + } else + newscreen = 0; + for (cp = cmds; cp->name != NULL && + memcmp(ap->bp, cp->name, ap->len); ++cp); + if (cp->name == NULL || + newscreen && !F_ISSET(cp, E_NEWSCREEN)) { + if (newscreen) + ap->bp[0] = toupper(ap->bp[0]); + (void)ex_printf(sp, "The %.*s command is unknown\n", + (int)ap->len, ap->bp); + } else { + (void)ex_printf(sp, + "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(sp, + "Command: %s\n Usage: %s\n", cp->help, cp->usage); + } + break; + case 0: + for (cp = cmds; cp->name != NULL && !INTERRUPTED(sp); ++cp) { + /* + * The ^D command has an unprintable name. + * + * XXX + * We display both capital and lower-case versions of + * the appropriate commands -- no need to add in extra + * room, they're all short names. + */ + if (cp == &cmds[C_SCROLL]) + name = "^D"; + else if (F_ISSET(cp, E_NEWSCREEN)) { + nb[0] = '['; + nb[1] = toupper(cp->name[0]); + nb[2] = cp->name[0]; + nb[3] = ']'; + for (name = cp->name + 1, + p = nb + 4; (*p++ = *name++) != '\0';); + name = nb; + } else + name = cp->name; + (void)ex_printf(sp, + "%*s: %s\n", MAXCMDNAMELEN, name, cp->help); + } + break; + default: + abort(); + } + return (0); +} + +/* + * ex_viusage -- :viusage [key] + * Display vi usage strings. + * + * PUBLIC: int ex_viusage __P((SCR *, EXCMD *)); + */ +int +ex_viusage(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + GS *gp; + VIKEYS const *kp; + int key; + + gp = sp->gp; + switch (cmdp->argc) { + case 1: + if (cmdp->argv[0]->len != 1) { + ex_emsg(sp, cmdp->cmd->usage, EXM_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->usage == NULL) +nokey: (void)ex_printf(sp, + "The %s key has no current meaning\n", + KEY_NAME(sp, key)); + else + (void)ex_printf(sp, + " Key:%s%s\nUsage: %s\n", + isblank(*kp->help) ? "" : " ", kp->help, kp->usage); + break; + case 0: + for (key = 0; key <= MAXVIKEY && !INTERRUPTED(sp); ++key) { + /* Special case: ~ command. */ + if (key == '~' && O_ISSET(sp, O_TILDEOP)) + kp = &tmotion; + else + kp = &vikeys[key]; + if (kp->help != NULL) + (void)ex_printf(sp, "%s\n", kp->help); + } + break; + default: + abort(); + } + return (0); +} diff --git a/ex/ex_util.c b/ex/ex_util.c new file mode 100644 index 000000000000..6c4772e61540 --- /dev/null +++ b/ex/ex_util.c @@ -0,0 +1,234 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_util.c 10.23 (Berkeley) 6/19/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/stat.h> + +#include <bitstring.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" + +/* + * ex_cinit -- + * Create an EX command structure. + * + * PUBLIC: void ex_cinit __P((EXCMD *, + * PUBLIC: int, int, recno_t, recno_t, int, ARGS **)); + */ +void +ex_cinit(cmdp, cmd_id, naddr, lno1, lno2, force, ap) + EXCMD *cmdp; + int cmd_id, force, naddr; + recno_t lno1, lno2; + ARGS **ap; +{ + memset(cmdp, 0, sizeof(EXCMD)); + cmdp->cmd = &cmds[cmd_id]; + cmdp->addrcnt = naddr; + cmdp->addr1.lno = lno1; + cmdp->addr2.lno = lno2; + cmdp->addr1.cno = cmdp->addr2.cno = 1; + if (force) + cmdp->iflags |= E_C_FORCE; + cmdp->argc = 0; + if ((cmdp->argv = ap) != NULL) + cmdp->argv[0] = NULL; +} + +/* + * ex_cadd -- + * Add an argument to an EX command structure. + * + * PUBLIC: void ex_cadd __P((EXCMD *, ARGS *, char *, size_t)); + */ +void +ex_cadd(cmdp, ap, arg, len) + EXCMD *cmdp; + ARGS *ap; + char *arg; + size_t len; +{ + cmdp->argv[cmdp->argc] = ap; + ap->bp = arg; + ap->len = len; + cmdp->argv[++cmdp->argc] = NULL; +} + +/* + * ex_getline -- + * Return a line from the file. + * + * PUBLIC: int ex_getline __P((SCR *, FILE *, size_t *)); + */ +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_ncheck -- + * Check for more files to edit. + * + * PUBLIC: int ex_ncheck __P((SCR *, int)); + */ +int +ex_ncheck(sp, force) + SCR *sp; + int force; +{ + char **ap; + + /* + * !!! + * 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; + + for (ap = sp->cargv + 1; *ap != NULL; ++ap); + msgq(sp, M_ERR, + "167|%d more files to edit", (ap - sp->cargv) - 1); + + return (1); + } + return (0); +} + +/* + * ex_init -- + * Init the screen for ex. + * + * PUBLIC: int ex_init __P((SCR *)); + */ +int +ex_init(sp) + SCR *sp; +{ + GS *gp; + + gp = sp->gp; + + if (gp->scr_screen(sp, SC_EX)) + return (1); + (void)gp->scr_attr(sp, SA_ALTERNATE, 0); + + sp->rows = O_VAL(sp, O_LINES); + sp->cols = O_VAL(sp, O_COLUMNS); + + F_CLR(sp, SC_VI); + F_SET(sp, SC_EX | SC_SCR_EX); + return (0); +} + +/* + * ex_emsg -- + * Display a few common ex and vi error messages. + * + * PUBLIC: void ex_emsg __P((SCR *, char *, exm_t)); + */ +void +ex_emsg(sp, p, which) + SCR *sp; + char *p; + exm_t which; +{ + switch (which) { + case EXM_EMPTYBUF: + msgq(sp, M_ERR, "168|Buffer %s is empty", p); + break; + case EXM_FILECOUNT: + msgq_str(sp, M_ERR, p, + "144|%s: expanded into too many file names"); + break; + case EXM_NOCANON: + msgq(sp, M_ERR, + "283|The %s command requires the ex terminal interface", p); + break; + case EXM_NOCANON_F: + msgq(sp, M_ERR, + "272|That form of %s requires the ex terminal interface", + p); + break; + case EXM_NOFILEYET: + if (p == NULL) + msgq(sp, M_ERR, + "274|Command failed, no file read in yet."); + else + msgq(sp, M_ERR, + "173|The %s command requires that a file have already been read in", p); + break; + case EXM_NOPREVBUF: + msgq(sp, M_ERR, "171|No previous buffer to execute"); + break; + case EXM_NOPREVRE: + msgq(sp, M_ERR, "172|No previous regular expression"); + break; + case EXM_NOSUSPEND: + msgq(sp, M_ERR, "230|This screen may not be suspended"); + break; + case EXM_SECURE: + msgq(sp, M_ERR, +"290|The %s command is not supported when the secure edit option is set", p); + break; + case EXM_SECURE_F: + msgq(sp, M_ERR, +"284|That form of %s is not supported when the secure edit option is set", p); + break; + case EXM_USAGE: + msgq(sp, M_ERR, "174|Usage: %s", p); + break; + } +} diff --git a/ex/ex_version.c b/ex/ex_version.c new file mode 100644 index 000000000000..d7363c8bd1ac --- /dev/null +++ b/ex/ex_version.c @@ -0,0 +1,39 @@ +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1991, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_version.c 10.31 (Berkeley) 8/22/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" +#include "version.h" + +/* + * ex_version -- :version + * Display the program version. + * + * PUBLIC: int ex_version __P((SCR *, EXCMD *)); + */ +int +ex_version(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + msgq(sp, M_INFO, VI_VERSION); + return (0); +} diff --git a/ex/ex_visual.c b/ex/ex_visual.c new file mode 100644 index 000000000000..82e503d44658 --- /dev/null +++ b/ex/ex_visual.c @@ -0,0 +1,161 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_visual.c 10.13 (Berkeley) 6/28/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/time.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" +#include "../vi/vi.h" + +/* + * ex_visual -- :[line] vi[sual] [^-.+] [window_size] [flags] + * Switch to visual mode. + * + * PUBLIC: int ex_visual __P((SCR *, EXCMD *)); + */ +int +ex_visual(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + SCR *tsp; + 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, + "175|The visual command requires that the open option be set"); + return (1); + } + + /* Move to the address. */ + sp->lno = cmdp->addr1.lno == 0 ? 1 : 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 (FL_ISSET(cmdp->iflags, + E_C_CARAT | E_C_DASH | E_C_DOT | E_C_PLUS)) { + case E_C_CARAT: + pos = '^'; + break; + case E_C_DASH: + pos = '-'; + break; + case E_C_DOT: + pos = '.'; + break; + case E_C_PLUS: + pos = '+'; + break; + default: + sp->frp->lno = sp->lno; + sp->frp->cno = 0; + (void)nonblank(sp, sp->lno, &sp->cno); + F_SET(sp->frp, FR_CURSORSET); + goto nopush; + } + + if (FL_ISSET(cmdp->iflags, E_C_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)v_event_push(sp, NULL, 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 (FL_ISSET(cmdp->iflags, E_C_HASH | E_C_LIST | E_C_PRINT)) { + case E_C_HASH: + O_SET(sp, O_NUMBER); + break; + case E_C_LIST: + O_SET(sp, O_LIST); + break; + case E_C_PRINT: + break; + } + +nopush: /* + * !!! + * You can call the visual part of the editor from within an ex + * global command. + * + * XXX + * Historically, undoing a visual session was a single undo command, + * i.e. you could undo all of the changes you made in visual mode. + * We don't get this right; I'm waiting for the new logging code to + * be available. + * + * It's explicit, don't have to wait for the user, unless there's + * already a reason to wait. + */ + if (!F_ISSET(sp, SC_SCR_EXWROTE)) + F_SET(sp, SC_EX_WAIT_NO); + + if (F_ISSET(sp, SC_EX_GLOBAL)) { + /* + * When the vi screen(s) exit, we don't want to lose our hold + * on this screen or this file, otherwise we're going to fail + * fairly spectacularly. + */ + ++sp->refcnt; + ++sp->ep->refcnt; + + /* + * Fake up a screen pointer -- vi doesn't get to change our + * underlying file, regardless. + */ + tsp = sp; + if (vi(&tsp)) + return (1); + + /* + * !!! + * Historically, if the user exited the vi screen(s) using an + * ex quit command (e.g. :wq, :q) ex/vi exited, it was only if + * they exited vi using the Q command that ex continued. Some + * early versions of nvi continued in ex regardless, but users + * didn't like the semantic. + * + * Reset the screen. + */ + if (ex_init(sp)) + return (1); + + /* Move out of the vi screen. */ + (void)ex_puts(sp, "\n"); + } else { + F_CLR(sp, SC_EX | SC_SCR_EX); + F_SET(sp, SC_VI); + } + return (0); +} diff --git a/ex/ex_write.c b/ex/ex_write.c new file mode 100644 index 000000000000..b3122e353563 --- /dev/null +++ b/ex/ex_write.c @@ -0,0 +1,375 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_write.c 10.30 (Berkeley) 7/12/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> +#include <sys/stat.h> + +#include <bitstring.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "../common/common.h" + +enum which {WN, WQ, WRITE, XIT}; +static int exwr __P((SCR *, EXCMD *, enum which)); + +/* + * ex_wn -- :wn[!] [>>] [file] + * Write to a file and switch to the next one. + * + * PUBLIC: int ex_wn __P((SCR *, EXCMD *)); + */ +int +ex_wn(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + if (exwr(sp, cmdp, WN)) + return (1); + if (file_m3(sp, 0)) + return (1); + + /* The file name isn't a new file to edit. */ + cmdp->argc = 0; + + return (ex_next(sp, cmdp)); +} + +/* + * ex_wq -- :wq[!] [>>] [file] + * Write to a file and quit. + * + * PUBLIC: int ex_wq __P((SCR *, EXCMD *)); + */ +int +ex_wq(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + int force; + + if (exwr(sp, cmdp, WQ)) + return (1); + if (file_m3(sp, 0)) + return (1); + + force = FL_ISSET(cmdp->iflags, E_C_FORCE); + + if (ex_ncheck(sp, force)) + return (1); + + F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT); + return (0); +} + +/* + * ex_write -- :write[!] [>>] [file] + * :write [!] [cmd] + * Write to a file. + * + * PUBLIC: int ex_write __P((SCR *, EXCMD *)); + */ +int +ex_write(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + return (exwr(sp, cmdp, WRITE)); +} + + +/* + * ex_xit -- :x[it]! [file] + * Write out any modifications and quit. + * + * PUBLIC: int ex_xit __P((SCR *, EXCMD *)); + */ +int +ex_xit(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + int force; + + NEEDFILE(sp, cmdp); + + if (F_ISSET(sp->ep, F_MODIFIED) && exwr(sp, cmdp, XIT)) + return (1); + if (file_m3(sp, 0)) + return (1); + + force = FL_ISSET(cmdp->iflags, E_C_FORCE); + + if (ex_ncheck(sp, force)) + return (1); + + F_SET(sp, force ? SC_EXIT_FORCE : SC_EXIT); + return (0); +} + +/* + * exwr -- + * The guts of the ex write commands. + */ +static int +exwr(sp, cmdp, cmd) + SCR *sp; + EXCMD *cmdp; + enum which cmd; +{ + MARK rm; + int flags; + char *name, *p; + + NEEDFILE(sp, cmdp); + + /* All write commands can have an associated '!'. */ + LF_INIT(FS_POSSIBLE); + if (FL_ISSET(cmdp->iflags, E_C_FORCE)) + LF_SET(FS_FORCE); + + /* Skip any leading whitespace. */ + if (cmdp->argc != 0) + for (p = cmdp->argv[0]->bp; *p != '\0' && isblank(*p); ++p); + + /* If "write !" it's a pipe to a utility. */ + if (cmdp->argc != 0 && cmd == WRITE && *p == '!') { + /* Secure means no shell access. */ + if (O_ISSET(sp, O_SECURE)) { + ex_emsg(sp, cmdp->cmd->name, EXM_SECURE_F); + return (1); + } + + /* Expand the argument. */ + for (++p; *p && isblank(*p); ++p); + if (*p == '\0') { + ex_emsg(sp, cmdp->cmd->usage, EXM_USAGE); + return (1); + } + if (argv_exp1(sp, cmdp, p, strlen(p), 1)) + return (1); + + /* + * Historically, vi waited after a write filter even if there + * wasn't any output from the command. People complained when + * nvi waited only if there was output, wanting the visual cue + * that the program hadn't written anything. + */ + F_SET(sp, SC_EX_WAIT_YES); + + /* + * !!! + * Ignore the return cursor position, the cursor doesn't + * move. + */ + if (ex_filter(sp, cmdp, &cmdp->addr1, + &cmdp->addr2, &rm, cmdp->argv[1]->bp, FILTER_WRITE)) + return (1); + + /* Ex terminates with a bang, even if the command fails. */ + if (!F_ISSET(sp, SC_VI) && !F_ISSET(sp, SC_EX_SILENT)) + (void)ex_puts(sp, "!\n"); + + return (0); + } + + /* Set the FS_ALL flag if we're writing the entire file. */ + if (cmdp->addr1.lno <= 1 && !db_exist(sp, cmdp->addr2.lno + 1)) + LF_SET(FS_ALL); + + /* If "write >>" it's an append to a file. */ + if (cmdp->argc != 0 && cmd != XIT && p[0] == '>' && p[1] == '>') { + LF_SET(FS_APPEND); + + /* Skip ">>" and whitespace. */ + for (p += 2; *p && isblank(*p); ++p); + } + + /* If no other arguments, just write the file back. */ + if (cmdp->argc == 0 || *p == '\0') + return (file_write(sp, + &cmdp->addr1, &cmdp->addr2, NULL, flags)); + + /* Build an argv so we get an argument count and file expansion. */ + if (argv_exp2(sp, cmdp, p, strlen(p))) + return (1); + + /* + * 0 args: impossible. + * 1 args: impossible (I hope). + * 2 args: read it. + * >2 args: object, too many args. + * + * The 1 args case depends on the argv_sexp() function refusing + * to return success without at least one non-blank character. + */ + switch (cmdp->argc) { + case 0: + case 1: + abort(); + /* NOTREACHED */ + case 2: + name = cmdp->argv[1]->bp; + + /* + * !!! + * Historically, the read and write commands renamed + * "unnamed" files, or, if the file had a name, set + * the alternate file name. + */ + if (F_ISSET(sp->frp, FR_TMPFILE) && + !F_ISSET(sp->frp, FR_EXNAMED)) { + if ((p = v_strdup(sp, + cmdp->argv[1]->bp, cmdp->argv[1]->len)) != NULL) { + free(sp->frp->name); + sp->frp->name = p; + } + /* + * The file has a real name, it's no longer a + * temporary, clear the temporary file flags. + * + * !!! + * If we're writing the whole file, FR_NAMECHANGE + * will be cleared by the write routine -- this is + * historic practice. + */ + F_CLR(sp->frp, FR_TMPEXIT | FR_TMPFILE); + F_SET(sp->frp, FR_NAMECHANGE | FR_EXNAMED); + + /* Notify the screen. */ + (void)sp->gp->scr_rename(sp, sp->frp->name, 1); + } else + set_alt_name(sp, name); + break; + default: + ex_emsg(sp, p, EXM_FILECOUNT); + return (1); + } + + return (file_write(sp, &cmdp->addr1, &cmdp->addr2, name, flags)); +} + +/* + * ex_writefp -- + * Write a range of lines to a FILE *. + * + * PUBLIC: int ex_writefp __P((SCR *, + * PUBLIC: char *, FILE *, MARK *, MARK *, u_long *, u_long *, int)); + */ +int +ex_writefp(sp, name, fp, fm, tm, nlno, nch, silent) + SCR *sp; + char *name; + FILE *fp; + MARK *fm, *tm; + u_long *nlno, *nch; + int silent; +{ + struct stat sb; + GS *gp; + u_long ccnt; /* XXX: can't print off_t portably. */ + recno_t fline, tline, lcnt; + size_t len; + int rval; + char *msg, *p; + + gp = sp->gp; + 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 db_get() and msgq(). Db_get() is safe, see + * the comment in ex_filter.c:ex_filter() 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; + msg = "253|Writing..."; + if (tline != 0) + for (; fline <= tline; ++fline, ++lcnt) { + /* Caller has to provide any interrupt message. */ + if ((lcnt + 1) % INTERRUPT_CHECK == 0) { + if (INTERRUPTED(sp)) + break; + if (!silent) { + gp->scr_busy(sp, msg, msg == NULL ? + BUSY_UPDATE : BUSY_ON); + msg = NULL; + } + } + if (db_get(sp, fline, DBG_FATAL, &p, &len)) + goto err; + if (fwrite(p, 1, len, fp) != len) + goto err; + ccnt += len; + if (putc('\n', fp) != '\n') + break; + ++ccnt; + } + + if (fflush(fp)) + goto err; + /* + * XXX + * I don't trust NFS -- check to make sure that we're talking to + * a regular file and sync so that NFS is forced to flush. + */ + if (!fstat(fileno(fp), &sb) && + S_ISREG(sb.st_mode) && fsync(fileno(fp))) + goto err; + + if (fclose(fp)) + goto err; + + rval = 0; + if (0) { +err: if (!F_ISSET(sp->ep, F_MULTILOCK)) + msgq_str(sp, M_SYSERR, name, "%s"); + (void)fclose(fp); + rval = 1; + } + + if (!silent) + gp->scr_busy(sp, NULL, BUSY_OFF); + + /* Report the possibly partial transfer. */ + if (nlno != NULL) { + *nch = ccnt; + *nlno = lcnt; + } + return (rval); +} diff --git a/ex/ex_yank.c b/ex/ex_yank.c new file mode 100644 index 000000000000..778dc7dee62c --- /dev/null +++ b/ex/ex_yank.c @@ -0,0 +1,46 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_yank.c 10.7 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> + +#include "../common/common.h" + +/* + * ex_yank -- :[line [,line]] ya[nk] [buffer] [count] + * Yank the lines into a buffer. + * + * PUBLIC: int ex_yank __P((SCR *, EXCMD *)); + */ +int +ex_yank(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + NEEDFILE(sp, cmdp); + + /* + * !!! + * Historically, yanking lines in ex didn't count toward the + * number-of-lines-yanked report. + */ + return (cut(sp, + FL_ISSET(cmdp->iflags, E_C_BUFFER) ? &cmdp->buffer : NULL, + &cmdp->addr1, &cmdp->addr2, CUT_LINEMODE)); +} diff --git a/ex/ex_z.c b/ex/ex_z.c new file mode 100644 index 000000000000..41b72ad1fd42 --- /dev/null +++ b/ex/ex_z.c @@ -0,0 +1,150 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + */ + +#include "config.h" + +#ifndef lint +static const char sccsid[] = "@(#)ex_z.c 10.10 (Berkeley) 3/6/96"; +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/queue.h> + +#include <bitstring.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../common/common.h" + +/* + * ex_z -- :[line] z [^-.+=] [count] [flags] + * Adjust window. + * + * PUBLIC: int ex_z __P((SCR *, EXCMD *)); + */ +int +ex_z(sp, cmdp) + SCR *sp; + EXCMD *cmdp; +{ + MARK abs; + recno_t cnt, equals, lno; + int eofcheck; + + NEEDFILE(sp, cmdp); + + /* + * !!! + * 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 (FL_ISSET(cmdp->iflags, E_C_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 (FL_ISSET(cmdp->iflags, + E_C_CARAT | E_C_DASH | E_C_DOT | E_C_EQUAL | E_C_PLUS)) { + case E_C_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_C_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_C_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, ABSMARK1, &abs, 1); + break; + case E_C_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, cmdp)) + return (1); + (void)ex_puts(sp, "----------------------------------------\n"); + cmdp->addr2.lno = cmdp->addr1.lno = equals = lno; + if (ex_pr(sp, cmdp)) + return (1); + (void)ex_puts(sp, "----------------------------------------\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_ADDR_DEF)) + ++lno; + /* FALLTHROUGH */ + case E_C_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 (db_last(sp, &lno)) + return (1); + if (cmdp->addr2.lno > lno) + cmdp->addr2.lno = lno; + } + + if (ex_pr(sp, cmdp)) + return (1); + if (equals) + sp->lno = equals; + return (0); +} diff --git a/ex/script.h b/ex/script.h new file mode 100644 index 000000000000..e29f63347961 --- /dev/null +++ b/ex/script.h @@ -0,0 +1,23 @@ +/*- + * Copyright (c) 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * + * See the LICENSE file for redistribution information. + * + * @(#)script.h 10.2 (Berkeley) 3/6/96 + */ + +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 */ +#ifdef TIOCGWINSZ + struct winsize sh_win; /* Window size. */ +#endif + struct termios sh_term; /* Terminal information. */ +}; diff --git a/ex/tag.h b/ex/tag.h new file mode 100644 index 000000000000..aee3dd27471d --- /dev/null +++ b/ex/tag.h @@ -0,0 +1,107 @@ +/*- + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1992, 1993, 1994, 1995, 1996 + * Keith Bostic. All rights reserved. + * Copyright (c) 1994, 1996 + * Rob Mayoff. All rights reserved. + * + * See the LICENSE file for redistribution information. + * + * @(#)tag.h 10.5 (Berkeley) 5/15/96 + */ + +/* + * Cscope connection information. One of these is maintained per cscope + * connection, linked from the EX_PRIVATE structure. + */ +struct _csc { + LIST_ENTRY(_csc) q; /* Linked list of cscope connections. */ + + char *dname; /* Base directory of this cscope connection. */ + size_t dlen; /* Length of base directory. */ + pid_t pid; /* PID of the connected cscope process. */ + time_t mtime; /* Last modification time of cscope database. */ + + FILE *from_fp; /* from cscope: FILE. */ + int from_fd; /* from cscope: file descriptor. */ + FILE *to_fp; /* to cscope: FILE. */ + int to_fd; /* to cscope: file descriptor. */ + + char **paths; /* Array of search paths for this cscope. */ + char *pbuf; /* Search path buffer. */ + size_t pblen; /* Search path buffer length. */ + + char buf[1]; /* Variable length buffer. */ +}; + +/* + * Tag file information. One of these is maintained per tag file, linked + * from the EXPRIVATE structure. + */ +struct _tagf { /* Tag files. */ + TAILQ_ENTRY(_tagf) q; /* Linked list of tag files. */ + char *name; /* Tag file name. */ + int errnum; /* Errno. */ + +#define TAGF_ERR 0x01 /* Error occurred. */ +#define TAGF_ERR_WARN 0x02 /* Error reported. */ + u_int8_t flags; +}; + +/* + * Tags are structured internally as follows: + * + * +----+ +----+ +----+ +----+ + * | EP | -> | Q1 | <-- | T1 | <-- | T2 | + * +----+ +----+ --> +----+ --> +----+ + * | + * +----+ +----+ + * | Q2 | <-- | T1 | + * +----+ --> +----+ + * | + * +----+ +----+ + * | Q3 | <-- | T1 | + * +----+ --> +----+ + * + * Each Q is a TAGQ, or tag "query", which is the result of one tag or cscope + * command. Each Q references one or more TAG's, or tagged file locations. + * + * tag: put a new Q at the head (^]) + * tagnext: T1 -> T2 inside Q (^N) + * tagprev: T2 -> T1 inside Q (^P) + * tagpop: discard Q (^T) + * tagtop: discard all Q + */ +struct _tag { /* Tag list. */ + CIRCLEQ_ENTRY(_tag) q; /* Linked list of tags. */ + + /* Tag pop/return information. */ + FREF *frp; /* Saved file. */ + recno_t lno; /* Saved line number. */ + size_t cno; /* Saved column number. */ + + char *fname; /* Filename. */ + size_t fnlen; /* Filename length. */ + recno_t slno; /* Search line number. */ + char *search; /* Search string. */ + size_t slen; /* Search string length. */ + + char buf[1]; /* Variable length buffer. */ +}; + +struct _tagq { /* Tag queue. */ + CIRCLEQ_ENTRY(_tagq) q; /* Linked list of tag queues. */ + /* This queue's tag list. */ + CIRCLEQ_HEAD(_tagqh, _tag) tagq; + + TAG *current; /* Current TAG within the queue. */ + + char *tag; /* Tag string. */ + size_t tlen; /* Tag string length. */ + +#define TAG_CSCOPE 0x01 /* Cscope tag. */ + u_int8_t flags; + + char buf[1]; /* Variable length buffer. */ +}; diff --git a/ex/version.h b/ex/version.h new file mode 100644 index 000000000000..7d657b635fc2 --- /dev/null +++ b/ex/version.h @@ -0,0 +1,2 @@ +#define VI_VERSION \ + "Version 1.79 (10/23/96) The CSRG, University of California, Berkeley." |