/* * ex_sdisplay -- * Display the list of screens. * * PUBLIC: int ex_sdisplay __P((SCR *)); */ int ex_sdisplay(SCR *sp) { GS *gp; SCR *tsp; int cnt, sep; size_t col, len; gp = sp->gp; if ((tsp = TAILQ_FIRST(&gp->hq)) == NULL) { msgq(sp, M_INFO, "149|No background screens to display"); return (0); } col = len = sep = 0; for (cnt = 1; tsp != NULL && !INTERRUPTED(sp); tsp = TAILQ_NEXT(tsp, q)) { 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); }
/* * ex_prchars -- * Local routine to dump characters to the screen. */ static int ex_prchars(SCR *sp, const CHAR_T *p, size_t *colp, size_t len, u_int flags, int repeatc) { CHAR_T ch; char *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++) == L('\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_COL(sp, ch); /* * Start a new line if the last character does not fit * into the current line. The implicit new lines are * not interruptible. */ if (col + tlen > sp->cols) { col = 0; (void)ex_puts(sp, "\n"); } col += tlen; if (!repeatc) { (void)ex_puts(sp, kp); if (INTERRUPTED(sp)) goto intr; } else while (tlen--) { (void)ex_printf(sp, "%c", repeatc); if (INTERRUPTED(sp)) goto intr; } if (col == sp->cols) { col = 0; (void)ex_puts(sp, "\n"); } } intr: *colp = col; return (0); }
/* * ex_exec_proc -- * Run a separate process. * * PUBLIC: int ex_exec_proc __P((SCR *, EXCMD *, const char *, const char *, int)); */ int ex_exec_proc(SCR *sp, EXCMD *cmdp, const 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_wemsg(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 (gp->scr_child) gp->scr_child(sp); 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, (char *)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 */ }
/* * ex_help -- :help * Display help message. * * PUBLIC: int ex_help __P((SCR *, EXCMD *)); */ int ex_help(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_prchars -- * Local routine to dump characters to the screen. */ static int ex_prchars(SCR *sp, const CHAR_T *p, size_t *colp, size_t len, u_int flags, int repeatc) { CHAR_T ch; const char *kp; size_t col, tlen, ts; if (O_ISSET(sp, O_LIST)) LF_SET(E_C_LIST); ts = O_VAL(sp, O_TABSTOP); for (col = *colp; len--;) if ((ch = *p++) == L('\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 { /* XXXX */ if (INTISWIDE(ch)) { CHAR_T str[2] = {0, 0}; str[0] = ch; INT2CHAR(sp, str, 2, kp, tlen); } else { kp = (char *)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); }
/* * db -- * Display a buffer. */ static void db(SCR *sp, CB *cbp, const char *np) { CHAR_T *p; GS *gp; TEXT *tp; size_t len; const unsigned char *name = (const void *)np; 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, (char *)KEY_NAME(sp, *p)); if (INTERRUPTED(sp)) return; } (void)ex_puts(sp, "\n"); } }
/* * ex_ldisplay -- * Display a line without any preceding number. * * PUBLIC: int ex_ldisplay(SCR *, const CHAR_T *, size_t, size_t, u_int); */ int ex_ldisplay(SCR *sp, const CHAR_T *p, size_t len, size_t 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 = L("$"); 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_print -- * Print the selected lines. * * PUBLIC: int ex_print(SCR *, EXCMD *, MARK *, MARK *, u_int32_t); */ int ex_print(SCR *sp, EXCMD *cmdp, MARK *fp, MARK *tp, u_int32_t flags) { GS *gp; recno_t from, to; size_t col, len; CHAR_T *p; CHAR_T 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) { SPRINTF(buf, SIZE(buf), L("%6u "), from); p = buf; } else p = L("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_z -- :[line] z [^-.+=] [count] [flags] * Adjust window. * * PUBLIC: int ex_z __P((SCR *, EXCMD *)); */ int ex_z(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 HISTORICAL_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); }
/* * 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(SCR *sp, EXCMD *cmdp) { enum { R_ARG, R_EXPANDARG, R_FILTER } which; struct stat sb; CHAR_T *arg; char *name; size_t nlen; EX_PRIVATE *exp; FILE *fp; FREF *frp; GS *gp; MARK rm; db_recno_t nlines; size_t arglen; int argc, rval; char *p; char *np; 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_wemsg(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 = v_wstrdup(sp, cmdp->argv[argc]->bp, cmdp->argv[argc]->len)) == 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_wemsg(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: INT2CHAR(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len + 1, name, nlen); /* * !!! * 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 = strdup(name)) != 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); name = sp->frp->name; } else { set_alt_name(sp, name); name = sp->alt_name; } break; default: ex_wemsg(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_aci -- * Append, change, insert in ex. */ static int ex_aci(SCR *sp, EXCMD *cmdp, enum which cmd) { CHAR_T *p, *t; GS *gp; TEXT *tp; TEXTH tiq; db_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++, NULL, 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_wemsg(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)); TAILQ_INIT(&tiq); if (ex_txt(sp, &tiq, 0, flags)) return (1); for (cnt = 0, tp = TAILQ_FIRST(&tiq); tp != NULL; ++cnt, tp = TAILQ_NEXT(tp, q)) 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); }
/* * exwr -- * The guts of the ex write commands. */ static int exwr(SCR *sp, EXCMD *cmdp, enum which cmd) { MARK rm; int flags; char *name; CHAR_T *p = NULL; size_t nlen; char *n; int rc; EX_PRIVATE *exp; 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' && cmdskip(*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_wemsg(sp, cmdp->cmd->name, EXM_SECURE_F); return (1); } /* Expand the argument. */ for (++p; *p && cmdskip(*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); /* Set the last bang command */ exp = EXP(sp); free(exp->lastbcomm); exp->lastbcomm = v_wstrdup(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len); /* * 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 && cmdskip(*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: INT2CHAR(sp, cmdp->argv[1]->bp, cmdp->argv[1]->len+1, n, nlen); name = v_strdup(sp, n, nlen - 1); /* * !!! * 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 ((n = v_strdup(sp, name, nlen - 1)) != NULL) { free(sp->frp->name); sp->frp->name = n; } /* * 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: INT2CHAR(sp, p, STRLEN(p) + 1, n, nlen); ex_emsg(sp, n, EXM_FILECOUNT); return (1); } rc = file_write(sp, &cmdp->addr1, &cmdp->addr2, name, flags); free(name); return rc; }
/* * 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(SCR *, EXCMD *); */ int ex_bang(SCR *sp, EXCMD *cmdp) { enum filtertype ftype; ARGS *ap; EX_PRIVATE *exp; MARK rm; recno_t lno; int rval; const char *msg; char *np; size_t nlen; 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); free(exp->lastbcomm); if ((exp->lastbcomm = v_wstrdup(sp, ap->bp, ap->len)) == 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, "!"WS"\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. */ INT2CHAR(sp, ap->bp, ap->len+1, np, nlen); (void)ex_exec_proc(sp, cmdp, np, 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); }
/* * ex_visual -- :[line] vi[sual] [^-.+] [window_size] [flags] * Switch to visual mode. * * PUBLIC: int ex_visual(SCR *, EXCMD *); */ int ex_visual(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, "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", (ulong)sp->lno, pos, cmdp->count); else len = snprintf(buf, sizeof(buf), "%luz%c", (ulong)sp->lno, pos); if (len >= sizeof(buf)) len = sizeof(buf) - 1; (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); }