📄 ex.c
字号:
} /* If doing a default command, vi just moves to the line. */ if (IN_VI_MODE(sp) && uselastcmd) { switch (exc.addrcnt) { case 2: sp->lno = exc.addr2.lno ? exc.addr2.lno : 1; sp->cno = exc.addr2.cno; break; case 1: sp->lno = exc.addr1.lno ? exc.addr1.lno : 1; sp->cno = exc.addr1.cno; break; } cmd = save_cmd; cmdlen = save_cmdlen; goto loop; } /* Reset "last" command. */ if (LF_ISSET(E_SETLAST)) exp->lastcmd = cp; /* Final setup for the command. */ exc.cmd = cp;#if defined(DEBUG) && 0 TRACE(sp, "ex_cmd: %s", exc.cmd->name); if (exc.addrcnt > 0) { TRACE(sp, "\taddr1 %d", exc.addr1.lno); if (exc.addrcnt > 1) TRACE(sp, " addr2: %d", exc.addr2.lno); TRACE(sp, "\n"); } if (exc.lineno) TRACE(sp, "\tlineno %d", exc.lineno); if (exc.flags) TRACE(sp, "\tflags %0x", exc.flags); if (F_ISSET(&exc, E_BUFFER)) TRACE(sp, "\tbuffer %c", exc.buffer); TRACE(sp, "\n"); if (exc.argc) { for (cnt = 0; cnt < exc.argc; ++cnt) TRACE(sp, "\targ %d: {%s}", cnt, exc.argv[cnt]); TRACE(sp, "\n"); }#endif /* Clear autoprint flag. */ F_CLR(exp, EX_AUTOPRINT); /* Increment the command count if not called from vi. */ if (!IN_VI_MODE(sp)) ++sp->ccnt; /* * If file state and not doing a global command, log the start of * an action. */ if (ep != NULL && !F_ISSET(sp, S_GLOBAL)) (void)log_cursor(sp, ep); /* Save the current mode. */ saved_mode = F_ISSET(sp, S_SCREENS | S_MAJOR_CHANGE); /* Do the command. */ if ((cp->fn)(sp, ep, &exc)) goto err;#ifdef DEBUG /* Make sure no function left the temporary space locked. */ if (F_ISSET(sp->gp, G_TMP_INUSE)) { F_CLR(sp->gp, G_TMP_INUSE); msgq(sp, M_ERR, "Error: ex: temporary buffer not released."); goto err; }#endif if (saved_mode != F_ISSET(sp, S_SCREENS | S_MAJOR_CHANGE)) { /* * Only here if the mode of the underlying file changed, e.g. * the user switched files or is exiting. Two things that we * might have to save: first, any "+cmd" field set up for an * ex/edit command will have to be saved for later, also, any * part of the current ex command that hasn't been executed * yet. For example: * * :edit +25 file.c|s/abc/ABC/|1 * * The historic vi just hung, of course; nvi handle's it by * pushing the keys onto the tty queue. If we're switching * modes to vi, since the commands are intended as ex commands, * add the extra characters to make it work. * * For the fun of it, if you want to see if a vi clone got the * ex argument parsing right, try: * * echo 'foo|bar' > file1; echo 'foo/bar' > file2; * vi * :edit +1|s/|/PIPE/|w file1| e file2|1 | s/\//SLASH/|wq */ if (arg1_len == NULL && save_cmdlen == 0) return (0); if (IN_VI_MODE(sp) && term_push(sp, "\n", 1, 0, 0)) goto err; if (save_cmdlen != 0) if (term_push(sp, save_cmd, save_cmdlen, 0, 0)) goto err; if (arg1 != NULL) { if (IN_VI_MODE(sp) && save_cmdlen != 0 && term_push(sp, "|", 1, 0, 0)) goto err; if (term_push(sp, arg1, arg1_len, 0, 0)) goto err; } if (IN_VI_MODE(sp) && term_push(sp, ":", 1, 0, 0)) goto err; return (0); } if (IN_EX_MODE(sp) && ep != NULL) { /* * The print commands have already handled the `print' flags. * If so, clear them. Don't return, autoprint may still have * stuff to print out. */ if (LF_ISSET(E_F_PRCLEAR)) F_CLR(&exc, E_F_HASH | E_F_LIST | E_F_PRINT); /* * If the command was successful, and there was an explicit * flag to display the new cursor line, or we're in ex mode, * autoprint is set, and a change was made, display the line. */ if (flagoff) { if (flagoff < 0) { if (sp->lno < -flagoff) { msgq(sp, M_ERR, "Flag offset before line 1."); goto err; } } else { if (file_lline(sp, ep, &lno)) goto err; if (sp->lno + flagoff > lno) { msgq(sp, M_ERR, "Flag offset past end-of-file."); goto err; } } sp->lno += flagoff; } if (O_ISSET(sp, O_AUTOPRINT) && (F_ISSET(exp, EX_AUTOPRINT) || F_ISSET(cp, E_AUTOPRINT))) LF_INIT(E_F_PRINT); else LF_INIT(F_ISSET(&exc, E_F_HASH | E_F_LIST | E_F_PRINT)); memset(&exc, 0, sizeof(EXCMDARG)); exc.addrcnt = 2; exc.addr1.lno = exc.addr2.lno = sp->lno; exc.addr1.cno = exc.addr2.cno = sp->cno; switch (LF_ISSET(E_F_HASH | E_F_LIST | E_F_PRINT)) { case E_F_HASH: exc.cmd = &cmds[C_HASH]; ex_number(sp, ep, &exc); break; case E_F_LIST: exc.cmd = &cmds[C_LIST]; ex_list(sp, ep, &exc); break; case E_F_PRINT: exc.cmd = &cmds[C_PRINT]; ex_pr(sp, ep, &exc); break; } } cmd = save_cmd; cmdlen = save_cmdlen; goto loop; /* NOTREACHED */ /* * On error, we discard any keys we have left, as well as any keys * that were mapped. The test of save_cmdlen isn't necessarily * correct. If we fail early enough we don't know if the entire * string was a single command or not. Try and guess, it's useful * to know if part of the command was discarded. */err: if (save_cmdlen == 0) for (; cmdlen; --cmdlen) { ch = *cmd++; if (IS_ESCAPE(sp, ch) && cmdlen > 1) { --cmdlen; ++cmd; } else if (ch == '\n' || ch == '|') { if (cmdlen > 1) save_cmdlen = 1; break; } } if (save_cmdlen != 0) msgq(sp, M_ERR, "Ex command failed: remaining command input discarded."); term_map_flush(sp, "Ex command failed"); return (1);}/* * ep_range -- * Get a line range for ex commands. */static intep_range(sp, ep, excp, cmdp, cmdlenp) SCR *sp; EXF *ep; EXCMDARG *excp; char **cmdp; size_t *cmdlenp;{ MARK cur, savecursor; size_t cmdlen; int savecursor_set, tmp; char *cmd; /* Percent character is all lines in the file. */ cmd = *cmdp; cmdlen = *cmdlenp; if (*cmd == '%') { excp->addr1.lno = 1; if (file_lline(sp, ep, &excp->addr2.lno)) return (1); /* If an empty file, then the first line is 0, not 1. */ if (excp->addr2.lno == 0) excp->addr1.lno = 0; excp->addr1.cno = excp->addr2.cno = 0; excp->addrcnt = 2; ++*cmdp; --*cmdlenp; return (0); } /* Parse comma or semi-colon delimited line specs. */ for (savecursor_set = 0, excp->addrcnt = 0; cmdlen > 0;) switch (*cmd) { case ';': /* Semi-colon delimiter. */ /* * Comma delimiters delimit; semi-colon delimiters * change the current address for the 2nd address * to be the first address. Trailing or multiple * delimiters are discarded. */ if (excp->addrcnt == 0) goto done; if (!savecursor_set) { savecursor.lno = sp->lno; savecursor.cno = sp->cno; sp->lno = excp->addr1.lno; sp->cno = excp->addr1.cno; savecursor_set = 1; } ++cmd; --cmdlen; break; case ',': /* Comma delimiter. */ /* If no addresses yet, defaults to ".". */ if (excp->addrcnt == 0) { excp->addr1.lno = sp->lno; excp->addr1.cno = sp->cno; excp->addrcnt = 1; } /* FALLTHROUGH */ case ' ': /* Whitespace. */ case '\t': /* Whitespace. */ ++cmd; --cmdlen; break; default: if (ep_line(sp, ep, &cur, &cmd, &cmdlen, &tmp)) return (1); if (!tmp) goto done; /* * Extra addresses are discarded, starting with * the first. */ switch (excp->addrcnt) { case 0: excp->addr1 = cur; excp->addrcnt = 1; break; case 1: excp->addr2 = cur; excp->addrcnt = 2; break; case 2: excp->addr1 = excp->addr2; excp->addr2 = cur; break; } break; } /* * XXX * This is probably not right behavior for savecursor -- need * to figure out what the historical ex did for ";,;,;5p" or * similar stupidity. */done: if (savecursor_set) { sp->lno = savecursor.lno; sp->cno = savecursor.cno; } if (excp->addrcnt == 2 && (excp->addr2.lno < excp->addr1.lno || excp->addr2.lno == excp->addr1.lno && excp->addr2.cno < excp->addr1.cno)) { msgq(sp, M_ERR, "The second address is smaller than the first."); return (1); } *cmdp = cmd; *cmdlenp = cmdlen; return (0);}/* * Get a single line address specifier. */static intep_line(sp, ep, cur, cmdp, cmdlenp, addr_found) SCR *sp; EXF *ep; MARK *cur; char **cmdp; size_t *cmdlenp; int *addr_found;{ MARK m; long total; u_int flags; size_t cmdlen; int (*sf) __P((SCR *, EXF *, MARK *, MARK *, char *, char **, u_int *)); char *cmd, *endp; *addr_found = 0; cmd = *cmdp; cmdlen = *cmdlenp; switch (*cmd) { case '$': /* Last line in the file. */ *addr_found = 1; cur->cno = 0; if (file_lline(sp, ep, &cur->lno)) return (1); ++cmd; --cmdlen; break; /* Absolute line number. */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': *addr_found = 1; /* * The way the vi "previous context" mark worked was that * "non-relative" motions set it. While vi wasn't totally * consistent about this, ANY numeric address was considered * non-relative, and set the value. Which is why we're * hacking marks down here. */ if (IN_VI_MODE(sp)) { m.lno = sp->lno; m.cno = sp->cno; if (mark_set(sp, ep, ABSMARK1, &m, 1)) return (1); } cur->cno = 0;/* 8-bit XXX */ cur->lno = strtol(cmd, &endp, 10); cmdlen -= (endp - cmd); cmd = endp; break; case '\'': /* Use a mark. */ *addr_found = 1; if (cmdlen == 1) { msgq(sp, M_ERR, "No mark name supplied."); return (1); } if (mark_get(sp, ep, cmd[1], cur)) return (1); cmd += 2; cmdlen -= 2; break; case '\\': /* Search: forward/backward. */ /* * !!! * I can't find any difference between // and \/ or * between ?? and \?. Mark Horton doesn't remember * there being any difference. C'est la vie. */ if (cmdlen < 2 || cmd[1] != '/' && cmd[1] != '?') { msgq(sp, M_ERR, "\\ not followed by / or ?."); return (1); } ++cmd; --cmdlen; sf = cmd[0] == '/' ? f_search : b_search; goto search; case '/': /* Search forward. */ sf = f_search; goto search; case '?': /* Search backward. */ sf = b_search;search: if (ep == NULL) { msgq(sp, M_ERR, "A search address requires that a file have already been read in."); return (1); } *addr_found = 1; m.lno = sp->lno; m.cno = sp->cno; flags = SEARCH_MSG | SEARCH_PARSE | SEARCH_SET; if (sf(sp, ep, &m, &m, cmd, &endp, &flags)) return (1); cur->lno = m.lno; cur->cno = m.cno; cmdlen -= (endp - cmd); cmd = endp; break; case '.': /* Current position. */ *addr_found = 1; cur->cno = sp->cno; /* If an empty file, then '.' is 0, not 1. */ if (sp->lno == 1) { if (file_lline(sp, ep, &cur->lno)) return (1); if (cur->lno != 0) cur->lno = 1; } else cur->lno = sp->lno; ++cmd; --cmdlen; break; } /* * Evaluate any offset. Offsets are +/- any number, or any number * of +/- signs, or any combination thereof. If no address found * yet, offset is relative to ".". */ for (total = 0; cmdlen > 0 && (cmd[0] == '-' || cmd[0] == '+');) { if (!*addr_found) { cur->lno = sp->lno; cur->cno = sp->cno; *addr_found = 1; } if (cmdlen > 1 && isdigit(cmd[1])) {/* 8-bit XXX */ total += strtol(cmd, &endp, 10); cmdlen -= (endp - cmd); cmd = endp; } else { total += cmd[0] == '-' ? -1 : 1; --cmdlen; ++cmd; } } if (*addr_found) { if (total < 0 && -total > cur->lno) { msgq(sp, M_ERR, "Reference to a line number less than 0."); return (1); } cur->lno += total; *cmdp = cmd; *cmdlenp = cmdlen; } return (0);}/* * ex_is_abbrev - * The vi text input routine needs to know if ex thinks this is * an [un]abbreviate command, so it can turn off abbreviations. * Usual ranting in the vi/v_ntext:txt_abbrev() routine. */intex_is_abbrev(name, len) char *name; size_t len;{ EXCMDLIST const *cp; return ((cp = ex_comm_search(name, len)) != NULL && (cp == &cmds[C_ABBR] || cp == &cmds[C_UNABBREVIATE]));}/* * ex_is_unmap - * The vi text input routine needs to know if ex thinks this is * an unmap command, so it can turn off input mapping. Usual * ranting in the vi/v_ntext:txt_unmap() routine. */intex_is_unmap(name, len) char *name; size_t len;{ EXCMDLIST const *cp; /* * The command the vi input routines are really interested in * is "unmap!", not just unmap. */ if (name[len - 1] != '!') return (0); --len; return ((cp = ex_comm_search(name, len)) != NULL && cp == &cmds[C_UNMAP]);}static inline EXCMDLIST const *ex_comm_search(name, len) char *name; size_t len;{ EXCMDLIST const *cp; for (cp = cmds; cp->name != NULL; ++cp) { if (cp->name[0] > name[0]) return (NULL); if (cp->name[0] != name[0]) continue; if (!memcmp(name, cp->name, len)) return (cp); } return (NULL);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -