📄 ex.c
字号:
/* * QUOTING NOTE: * * The historic implementation ignored all escape characters * so there was no way to put a space or newline into the +cmd * field. We do a simplistic job of fixing it by moving to the * first whitespace character that isn't escaped by a literal * next character. The literal next characters are stripped * as they're no longer useful. */ if (cmdlen > 0 && ch == '+') { ++cmd; --cmdlen; for (arg1 = p = cmd; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (IS_ESCAPE(sp, ch) && cmdlen > 1) { --cmdlen; ch = *++cmd; } else if (isblank(ch)) break; *p++ = ch; } arg1_len = cmd - arg1; /* Reset, so the first argument isn't reparsed. */ save_cmd = cmd; } } else if (cp == &cmds[C_BANG] || cp == &cmds[C_GLOBAL] || cp == &cmds[C_VGLOBAL]) { cmd += cmdlen; cmdlen = 0; } else if (cp == &cmds[C_READ] || cp == &cmds[C_WRITE]) { /* * Move to the next character. If it's a '!', it's a filter * command and we want to eat it all, otherwise, we're done. */ for (; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (!isblank(ch)) break; } if (cmdlen > 0 && ch == '!') { cmd += cmdlen; cmdlen = 0; } } else if (cp == &cmds[C_SUBSTITUTE]) { /* * Move to the next non-whitespace character, we'll use it as * the delimiter. If the character isn't an alphanumeric or * a '|', it's the delimiter, so parse it. Otherwise, we're * into something like ":s g", so use the special substitute * command. */ for (; cmdlen > 0; --cmdlen, ++cmd) if (!isblank(cmd[0])) break; if (isalnum(cmd[0]) || cmd[0] == '|') cp = &cmd_subagain; else if (cmdlen > 0) { /* * QUOTING NOTE: * * Backslashes quote delimiter characters for RE's. * The backslashes are NOT removed since they'll be * used by the RE code. Move to the third delimiter * that's not escaped (or the end of the command). */ delim = *cmd; ++cmd; --cmdlen; for (cnt = 2; cmdlen > 0 && cnt; --cmdlen, ++cmd) if (cmd[0] == '\\' && cmdlen > 1) { ++cmd; --cmdlen; } else if (cmd[0] == delim) --cnt; } } /* * Use normal quoting and termination rules to find the end * of this command. * * QUOTING NOTE: * * Historically, vi permitted ^V's to escape <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 as they're * no longer useful. */ for (p = cmd, cnt = 0; cmdlen > 0; --cmdlen, ++cmd) { ch = cmd[0]; if (IS_ESCAPE(sp, ch) && cmdlen > 1) { tmp = cmd[1]; if (tmp == '\n' || tmp == '|') { if (tmp == '\n') ++sp->if_lno; --cmdlen; ++cmd; ++cnt; ch = tmp; } } else if (ch == '\n' || ch == '|') { if (ch == '\n') nl = 1; --cmdlen; break; } *p++ = ch; } /* * Save off the next command information, go back to the * original start of the command. */ p = cmd + 1; cmd = save_cmd; save_cmd = p; save_cmdlen = cmdlen; cmdlen = ((save_cmd - cmd) - 1) - cnt; /* * !!! * The "set tags" command historically used a backslash, not the * user's literal next character, to escape whitespace. Handle * it here instead of complicating the argv_exp3() code. Note, * this isn't a particularly complex trap, and if backslashes were * legal in set commands, this would have to be much more complicated. */ if (cp == &cmds[C_SET]) for (p = cmd, len = cmdlen; len > 0; --len, ++p) if (*p == '\\') *p = CH_LITERAL; /* * Set the default addresses. It's an error to specify an address for * a command that doesn't take them. If two addresses are specified * for a command that only takes one, lose the first one. Two special * cases here, some commands take 0 or 2 addresses. For most of them * (the E_ADDR2_ALL flag), 0 defaults to the entire file. For one * (the `!' command, the E_ADDR2_NONE flag), 0 defaults to no lines. * * Also, if the file is empty, some commands want to use an address of * 0, i.e. the entire file is 0 to 0, and the default first address is * 0. Otherwise, an entire file is 1 to N and the default line is 1. * Note, we also add the E_ZERO flag to the command flags, for the case * where the 0 address is only valid if it's a default address. * * Also, set a flag if we set the default addresses. Some commands * (ex: z) care if the user specified an address of if we just used * the current cursor. */ switch (LF_ISSET(E_ADDR1|E_ADDR2|E_ADDR2_ALL|E_ADDR2_NONE)) { case E_ADDR1: /* One address: */ switch (exc.addrcnt) { case 0: /* Default cursor/empty file. */ exc.addrcnt = 1; F_SET(&exc, E_ADDRDEF); if (LF_ISSET(E_ZERODEF)) { if (file_lline(sp, ep, &lno)) goto err; if (lno == 0) { exc.addr1.lno = 0; LF_SET(E_ZERO); } else exc.addr1.lno = sp->lno; } else exc.addr1.lno = sp->lno; exc.addr1.cno = sp->cno; break; case 1: break; case 2: /* Lose the first address. */ exc.addrcnt = 1; exc.addr1 = exc.addr2; } break; case E_ADDR2_NONE: /* Zero/two addresses: */ if (exc.addrcnt == 0) /* Default to nothing. */ break; goto two; case E_ADDR2_ALL: /* Zero/two addresses: */ if (exc.addrcnt == 0) { /* Default entire/empty file. */ exc.addrcnt = 2; F_SET(&exc, E_ADDRDEF); if (file_lline(sp, ep, &exc.addr2.lno)) goto err; if (LF_ISSET(E_ZERODEF) && exc.addr2.lno == 0) { exc.addr1.lno = 0; LF_SET(E_ZERO); } else exc.addr1.lno = 1; exc.addr1.cno = exc.addr2.cno = 0; F_SET(&exc, E_ADDR2_ALL); break; } /* FALLTHROUGH */ case E_ADDR2: /* Two addresses: */two: switch (exc.addrcnt) { case 0: /* Default cursor/empty file. */ exc.addrcnt = 2; F_SET(&exc, E_ADDRDEF); if (LF_ISSET(E_ZERODEF) && sp->lno == 1) { if (file_lline(sp, ep, &lno)) goto err; if (lno == 0) { exc.addr1.lno = exc.addr2.lno = 0; LF_SET(E_ZERO); } else exc.addr1.lno = exc.addr2.lno = sp->lno; } else exc.addr1.lno = exc.addr2.lno = sp->lno; exc.addr1.cno = exc.addr2.cno = sp->cno; break; case 1: /* Default to first address. */ exc.addrcnt = 2; exc.addr2 = exc.addr1; break; case 2: break; } break; default: if (exc.addrcnt) /* Error. */ goto usage; } /* * !!! * The ^D scroll command historically scrolled half the screen size * rows down, rounded down, or to EOF. It was an error if the cursor * was already at EOF. (Leading addresses were permitted, but were * then ignored.) */ if (cp == &cmds[C_SCROLL]) { exc.addrcnt = 2; exc.addr1.lno = sp->lno + 1; exc.addr2.lno = sp->lno + 1 + (O_VAL(sp, O_LINES) + 1) / 2; exc.addr1.cno = exc.addr2.cno = sp->cno; if (file_lline(sp, ep, &lno)) goto err; if (lno != 0 && lno > sp->lno && exc.addr2.lno > lno) exc.addr2.lno = lno; } flagoff = 0; for (p = cp->syntax; *p != '\0'; ++p) { /* * The write command is sensitive to leading whitespace, e.g. * "write !" is different from "write!". If not the write * command, skip leading whitespace. */ if (cp != &cmds[C_WRITE]) for (; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (!isblank(ch)) break; } /* * Quit when reach the end of the command, unless it's a * command that does its own parsing, in which case we want * to build a reasonable argv for it. This code guarantees * that there will be an argv when the function gets called, * so the correct test is for a length of 0, not for the * argc > 0. */ if (cmdlen == 0 && *p != '!' && *p != 'S' && *p != 's') break; switch (*p) { case '!': /* ! */ if (*cmd == '!') { ++cmd; --cmdlen; F_SET(&exc, E_FORCE); } break; case '1': /* +, -, #, l, p */ /* * !!! * Historically, some flags were ignored depending * on where they occurred in the command line. For * example, in the command, ":3+++p--#", historic vi * acted on the '#' flag, but ignored the '-' flags. * It's unambiguous what the flags mean, so we just * handle them regardless of the stupidity of their * location. */ for (; cmdlen; --cmdlen, ++cmd) switch (*cmd) { case '+': ++flagoff; break; case '-': --flagoff; break; case '#': F_SET(&exc, E_F_HASH); break; case 'l': F_SET(&exc, E_F_LIST); break; case 'p': F_SET(&exc, E_F_PRINT); break; default: goto end1; }end1: break; case '2': /* -, ., +, ^ */ case '3': /* -, ., +, ^, = */ for (; cmdlen; --cmdlen, ++cmd) switch (*cmd) { case '-': F_SET(&exc, E_F_DASH); break; case '.': F_SET(&exc, E_F_DOT); break; case '+': F_SET(&exc, E_F_PLUS); break; case '^': F_SET(&exc, E_F_CARAT); break; case '=': if (*p == '3') { F_SET(&exc, E_F_EQUAL); break; } /* FALLTHROUGH */ default: goto end2; }end2: break; case 'b': /* buffer */ /* * Digits can't be buffer names in ex commands, or the * command "d2" would be a delete into buffer '2', and * not a two-line deletion. */ if (!isdigit(cmd[0])) { exc.buffer = *cmd; ++cmd; --cmdlen; F_SET(&exc, E_BUFFER); } break; case 'c': /* count [01+a] */ ++p; /* Validate any signed value. */ if (!isdigit(*cmd) && (*p != '+' || (*cmd != '+' && *cmd != '-'))) break; /* If a signed value, set appropriate flags. */ if (*cmd == '-') F_SET(&exc, E_COUNT_NEG); else if (*cmd == '+') F_SET(&exc, E_COUNT_POS);/* 8-bit XXX */ if ((lno = strtol(cmd, &t, 10)) == 0 && *p != '0') { msgq(sp, M_ERR, "Count may not be zero."); goto err; } cmdlen -= (t - cmd); cmd = t; /* * Count as address offsets occur in commands taking * two addresses. Historic vi practice was to use * the count as an offset from the *second* address. * * Set a count flag; some underlying commands (see * join) do different things with counts than with * line addresses. */ if (*p == 'a') { exc.addr1 = exc.addr2; exc.addr2.lno = exc.addr1.lno + lno - 1; } else exc.count = lno; F_SET(&exc, E_COUNT); break; case 'f': /* file */ if (argv_exp2(sp, ep, &exc, cmd, cmdlen, cp == &cmds[C_BANG])) goto err; goto countchk; case 'l': /* line */ if (ep_line(sp, ep, &cur, &cmd, &cmdlen, &tmp)) goto err; /* Line specifications are always required. */ if (!tmp) { msgq(sp, M_ERR, "%s: bad line specification", cmd); goto err; } exc.lineno = cur.lno; break; case 'S': /* string, file exp. */ if (argv_exp1(sp, ep, &exc, cmd, cmdlen, cp == &cmds[C_BANG])) goto err; goto addr2; case 's': /* string */ if (argv_exp0(sp, ep, &exc, cmd, cmdlen)) goto err; goto addr2; case 'W': /* word string */ /* * QUOTING NOTE: * * Literal next characters escape the following * character. Quoting characters are stripped * here since they are no longer useful. * * First there was the word. */ for (p = t = cmd; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (IS_ESCAPE(sp, ch) && cmdlen > 1) { --cmdlen; *p++ = *++cmd; } else if (isblank(ch)) { ++cmd; --cmdlen; break; } else *p++ = ch; } if (argv_exp0(sp, ep, &exc, t, p - t)) goto err; /* Delete intervening whitespace. */ for (; cmdlen > 0; --cmdlen, ++cmd) { ch = *cmd; if (!isblank(ch)) break; } if (cmdlen == 0) goto usage; /* Followed by the string. */ for (p = t = cmd; cmdlen > 0; --cmdlen, ++cmd, ++p) { ch = *cmd; if (IS_ESCAPE(sp, ch) && cmdlen > 1) { --cmdlen; *p = *++cmd; } else *p = ch; } if (argv_exp0(sp, ep, &exc, t, p - t)) goto err; goto addr2; case 'w': /* word */ if (argv_exp3(sp, ep, &exc, cmd, cmdlen)) goto err;countchk: if (*++p != 'N') { /* N */ /* * If a number is specified, must either be * 0 or that number, if optional, and that * number, if required. */ num = *p - '0'; if ((*++p != 'o' || exp->argsoff != 0) && exp->argsoff != num) goto usage; } goto addr2; default: msgq(sp, M_ERR, "Internal syntax table error (%s: %c).", cp->name, *p); } } /* Skip trailing whitespace. */ for (; cmdlen; --cmdlen) { ch = *cmd++; if (!isblank(ch)) break; } /* * There shouldn't be anything left, and no more required * fields, i.e neither 'l' or 'r' in the syntax string. */ if (cmdlen || strpbrk(p, "lr")) {usage: msgq(sp, M_ERR, "Usage: %s.", cp->usage); goto err; } /* Verify that the addresses are legal. */addr2: switch (exc.addrcnt) { case 2: if (file_lline(sp, ep, &lno)) goto err; /* * Historic ex/vi permitted commands with counts to go past * EOF. So, for example, if the file only had 5 lines, the * ex command "1,6>" would fail, but the command ">300" * would succeed. Since we don't want to have to make all * of the underlying commands handle random line numbers, * fix it here. */ if (exc.addr2.lno > lno) if (F_ISSET(&exc, E_COUNT)) exc.addr2.lno = lno; else { if (lno == 0) msgq(sp, M_ERR, "The file is empty."); else msgq(sp, M_ERR, "Only %lu line%s in the file.", lno, lno > 1 ? "s" : ""); goto err; } /* FALLTHROUGH */ case 1: num = exc.addr1.lno; /* * If it's a "default vi command", zero is okay. Historic * vi allowed this, note, it's also the hack that allows * "vi + nonexistent_file" to work. */ if (num == 0 && (!IN_VI_MODE(sp) || uselastcmd != 1) && !LF_ISSET(E_ZERO)) { msgq(sp, M_ERR, "The %s command doesn't permit an address of 0.", cp->name); goto err; } if (file_lline(sp, ep, &lno)) goto err; if (num > lno) { if (lno == 0) msgq(sp, M_ERR, "The file is empty."); else msgq(sp, M_ERR, "Only %lu line%s in the file.", lno, lno > 1 ? "s" : ""); goto err; } break;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -