From a51d953b95a7cc6b40a6b3a5bfd95f3154acf5e2 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Sun, 29 Aug 2021 14:56:02 +0100 Subject: [PATCH] vi: further changes to colon addresses Improved error messages: - specify when a search fails or a mark isn't set; - warn when line addresses are out of range or when a range of lines is reversed. Addresses are limited to the number of lines in the file so a command like ':2000000000' (go to the two billionth line) no longer causes a long pause. Improved vi compatibility of '+' and '-' operators that aren't followed immediately by a number: :4+++= 7 :3-2= 1 :3 - 2= 4 (yes, really!) In a command like ':,$' the empty address before the separator now correctly refers to the current line. (The similar case ':1,' was already being handled.) And all with a tidy reduction in bloat (32-bit build): function old new delta colon 4029 4069 +40 .rodata 99348 99253 -95 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/1 up/down: 40/-95) Total: -55 bytes Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- editors/vi.c | 132 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 49 deletions(-) diff --git a/editors/vi.c b/editors/vi.c index e4ba2b2b0..3dbe5b471 100644 --- a/editors/vi.c +++ b/editors/vi.c @@ -2458,26 +2458,38 @@ static char *char_search(char *p, const char *pat, int dir_and_range) //----- The Colon commands ------------------------------------- #if ENABLE_FEATURE_VI_COLON -static char *get_one_address(char *p, int *result) // get colon addr, if present +// Evaluate colon address expression. Returns a pointer to the +// next character or NULL on error. If 'result' contains a valid +// address 'valid' is TRUE. +static char *get_one_address(char *p, int *result, int *valid) { - int st, num, sign, addr, new_addr; + int num, sign, addr, got_addr; # if ENABLE_FEATURE_VI_YANKMARK || ENABLE_FEATURE_VI_SEARCH char *q, c; # endif IF_FEATURE_VI_SEARCH(int dir;) - addr = -1; // assume no addr + got_addr = FALSE; + addr = count_lines(text, dot); // default to current line sign = 0; for (;;) { - new_addr = -1; if (isblank(*p)) { + if (got_addr) { + addr += sign; + sign = 0; + } p++; - } else if (*p == '.') { // the current line + } else if (!got_addr && *p == '.') { // the current line p++; - new_addr = count_lines(text, dot); + //addr = count_lines(text, dot); + got_addr = TRUE; + } else if (!got_addr && *p == '$') { // the last line in file + p++; + addr = count_lines(text, end - 1); + got_addr = TRUE; } # if ENABLE_FEATURE_VI_YANKMARK - else if (*p == '\'') { // is this a mark addr + else if (!got_addr && *p == '\'') { // is this a mark addr p++; c = tolower(*p); p++; @@ -2487,13 +2499,16 @@ static char *get_one_address(char *p, int *result) // get colon addr, if present c = c - 'a'; q = mark[(unsigned char) c]; } - if (q == NULL) // is mark valid + if (q == NULL) { // is mark valid + status_line_bold("Mark not set"); return NULL; - new_addr = count_lines(text, q); + } + addr = count_lines(text, q); + got_addr = TRUE; } # endif # if ENABLE_FEATURE_VI_SEARCH - else if (*p == '/' || *p == '?') { // a search pattern + else if (!got_addr && (*p == '/' || *p == '?')) { // a search pattern c = *p; q = strchrnul(p + 1, c); if (p + 1 != q) { @@ -2516,40 +2531,41 @@ static char *get_one_address(char *p, int *result) // get colon addr, if present // no match, continue from other end of file q = char_search(dir > 0 ? text : end - 1, last_search_pattern + 1, dir); - if (q == NULL) + if (q == NULL) { + status_line_bold("Pattern not found"); return NULL; + } } - new_addr = count_lines(text, q); + addr = count_lines(text, q); + got_addr = TRUE; } # endif - else if (*p == '$') { // the last line in file - p++; - new_addr = count_lines(text, end - 1); - } else if (isdigit(*p)) { - sscanf(p, "%d%n", &num, &st); - p += st; - if (addr < 0) { // specific line number + else if (isdigit(*p)) { + num = 0; + while (isdigit(*p)) + num = num * 10 + *p++ -'0'; + if (!got_addr) { // specific line number addr = num; + got_addr = TRUE; } else { // offset from current addr addr += sign >= 0 ? num : -num; } sign = 0; } else if (*p == '-' || *p == '+') { - sign = *p++ == '-' ? -1 : 1; - if (addr < 0) { // default address is dot - addr = count_lines(text, dot); + if (!got_addr) { // default address is dot + //addr = count_lines(text, dot); + got_addr = TRUE; + } else { + addr += sign; } + sign = *p++ == '-' ? -1 : 1; } else { addr += sign; // consume unused trailing sign break; } - if (new_addr >= 0) { - if (addr >= 0) // only one new address per expression - return NULL; - addr = new_addr; - } } *result = addr; + *valid = got_addr; return p; } @@ -2558,34 +2574,40 @@ static char *get_one_address(char *p, int *result) // get colon addr, if present // Read line addresses for a colon command. The user can enter as // many as they like but only the last two will be used. -static char *get_address(char *p, int *b, int *e) +static char *get_address(char *p, int *b, int *e, unsigned int *got) { int state = GET_ADDRESS; + int valid; + int addr; char *save_dot = dot; //----- get the address' i.e., 1,3 'a,'b ----- for (;;) { if (isblank(*p)) { p++; - } else if (*p == '%' && state == GET_ADDRESS) { // alias for 1,$ + } else if (state == GET_ADDRESS && *p == '%') { // alias for 1,$ p++; *b = 1; *e = count_lines(text, end-1); + *got = 3; + state = GET_SEPARATOR; + } else if (state == GET_ADDRESS) { + valid = FALSE; + p = get_one_address(p, &addr, &valid); + // Quit on error or if the address is invalid and isn't of + // the form ',$' or '1,' (in which case it defaults to dot). + if (p == NULL || !(valid || *p == ',' || *p == ';' || *got & 1)) + break; + *b = *e; + *e = addr; + *got = (*got << 1) | 1; state = GET_SEPARATOR; } else if (state == GET_SEPARATOR && (*p == ',' || *p == ';')) { if (*p == ';') dot = find_line(*e); p++; - *b = *e; state = GET_ADDRESS; - } else if (state == GET_ADDRESS) { - p = get_one_address(p, e); - if (p == NULL) - break; - state = GET_SEPARATOR; } else { - if (state == GET_SEPARATOR && *b >= 0 && *e < 0) - *e = count_lines(text, dot); break; } } @@ -2799,9 +2821,14 @@ static void colon(char *buf) not_implemented(p); #else +// check how many addresses we got +# define GOT_ADDRESS (got & 1) +# define GOT_RANGE ((got & 3) == 3) + char c, *buf1, *q, *r; char *fn, cmd[MAX_INPUT_LEN], *cmdend, *args, *exp = NULL; int i, l, li, b, e; + unsigned int got; int useforce; // :3154 // if (-e line 3154) goto it else stay put @@ -2828,14 +2855,13 @@ static void colon(char *buf) li = i = 0; b = e = -1; + got = 0; li = count_lines(text, end - 1); fn = current_filename; // look for optional address(es) :. :1 :1,9 :'q,'a :% - buf1 = buf; - buf = get_address(buf, &b, &e); + buf = get_address(buf, &b, &e, &got); if (buf == NULL) { - status_line_bold("Bad address: %s", buf1); goto ret; } @@ -2858,13 +2884,17 @@ static void colon(char *buf) } // assume the command will want a range, certain commands // (read, substitute) need to adjust these assumptions - if (e < 0) { + if (!GOT_ADDRESS) { q = text; // no addr, use 1,$ for the range r = end - 1; } else { // at least one addr was given, get its details + if (e < 0 || e > li) { + status_line_bold("Invalid range"); + goto ret; + } q = r = find_line(e); - if (b < 0) { + if (!GOT_RANGE) { // if there is only one addr, then it's the line // number of the single line the user wants. // Reset the end pointer to the end of that line. @@ -2873,6 +2903,10 @@ static void colon(char *buf) } else { // we were given two addrs. change the // start pointer to the addr given by user. + if (b < 0 || b > li || b > e) { + status_line_bold("Invalid range"); + goto ret; + } q = find_line(b); // what line is #b r = end_line(r); li = e - b + 1; @@ -2903,12 +2937,12 @@ static void colon(char *buf) } # endif else if (cmd[0] == '=' && !cmd[1]) { // where is the address - if (e < 0) { // no addr given- use defaults + if (!GOT_ADDRESS) { // no addr given- use defaults e = count_lines(text, dot); } status_line("%d", e); } else if (strncmp(cmd, "delete", i) == 0) { // delete lines - if (e < 0) { // no addr given- use defaults + if (!GOT_ADDRESS) { // no addr given- use defaults q = begin_line(dot); // assume .,. for the range r = end_line(dot); } @@ -2980,7 +3014,7 @@ static void colon(char *buf) rawmode(); Hit_Return(); } else if (strncmp(cmd, "list", i) == 0) { // literal print line - if (e < 0) { // no addr given- use defaults + if (!GOT_ADDRESS) { // no addr given- use defaults q = begin_line(dot); // assume .,. for the range r = end_line(dot); } @@ -3063,7 +3097,7 @@ static void colon(char *buf) if (e == 0) { // user said ":0r foo" q = text; } else { // read after given line or current line if none given - q = next_line(e > 0 ? find_line(e) : dot); + q = next_line(GOT_ADDRESS ? find_line(e) : dot); // read after last line if (q == end-1) ++q; @@ -3184,11 +3218,11 @@ static void colon(char *buf) len_F = strlen(F); } - if (e < 0) { // no addr given + if (!GOT_ADDRESS) { // no addr given q = begin_line(dot); // start with cur line r = end_line(dot); b = e = count_lines(text, q); // cur line number - } else if (b < 0) { // one addr given + } else if (!GOT_RANGE) { // one addr given b = e; } @@ -3359,7 +3393,7 @@ static void colon(char *buf) } # if ENABLE_FEATURE_VI_YANKMARK } else if (strncmp(cmd, "yank", i) == 0) { // yank lines - if (b < 0) { // no addr given- use defaults + if (!GOT_ADDRESS) { // no addr given- use defaults q = begin_line(dot); // assume .,. for the range r = end_line(dot); }