From de69775838eed0acd02f40de5e988d80611557ab Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 1 Apr 2019 14:08:00 +0200 Subject: vi: rearrange functions, no logic changes Signed-off-by: Denys Vlasenko --- editors/vi.c | 1574 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 787 insertions(+), 787 deletions(-) diff --git a/editors/vi.c b/editors/vi.c index e960afc..9db763c 100644 --- a/editors/vi.c +++ b/editors/vi.c @@ -693,424 +693,700 @@ static ALWAYS_INLINE int query_screen_dimensions(void) } #endif -//----- The Colon commands ------------------------------------- -#if ENABLE_FEATURE_VI_COLON -static char *get_one_address(char *p, int *addr) // get colon addr, if present +// sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready) +static int mysleep(int hund) { - int st; - char *q; - IF_FEATURE_VI_YANKMARK(char c;) - IF_FEATURE_VI_SEARCH(char *pat;) + struct pollfd pfd[1]; - *addr = -1; // assume no addr - if (*p == '.') { // the current line - p++; - q = begin_line(dot); - *addr = count_lines(text, q); - } -#if ENABLE_FEATURE_VI_YANKMARK - else if (*p == '\'') { // is this a mark addr - p++; - c = tolower(*p); - p++; - if (c >= 'a' && c <= 'z') { - // we have a mark - c = c - 'a'; - q = mark[(unsigned char) c]; - if (q != NULL) { // is mark valid - *addr = count_lines(text, q); - } - } - } -#endif -#if ENABLE_FEATURE_VI_SEARCH - else if (*p == '/') { // a search pattern - q = strchrnul(++p, '/'); - pat = xstrndup(p, q - p); // save copy of pattern - p = q; - if (*p == '/') - p++; - q = char_search(dot, pat, (FORWARD << 1) | FULL); - if (q != NULL) { - *addr = count_lines(text, q); - } - free(pat); - } -#endif - else if (*p == '$') { // the last line in file - p++; - q = begin_line(end - 1); - *addr = count_lines(text, q); - } else if (isdigit(*p)) { // specific line number - sscanf(p, "%d%n", addr, &st); - p += st; - } else { - // unrecognized address - assume -1 - *addr = -1; + if (hund != 0) + fflush_all(); + + pfd[0].fd = STDIN_FILENO; + pfd[0].events = POLLIN; + return safe_poll(pfd, 1, hund*10) > 0; +} + +//----- Set terminal attributes -------------------------------- +static void rawmode(void) +{ + // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals + set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL); + erase_char = term_orig.c_cc[VERASE]; +} + +static void cookmode(void) +{ + fflush_all(); + tcsetattr_stdin_TCSANOW(&term_orig); +} + +//----- Terminal Drawing --------------------------------------- +// The terminal is made up of 'rows' line of 'columns' columns. +// classically this would be 24 x 80. +// screen coordinates +// 0,0 ... 0,79 +// 1,0 ... 1,79 +// . ... . +// . ... . +// 22,0 ... 22,79 +// 23,0 ... 23,79 <- status line + +//----- Move the cursor to row x col (count from 0, not 1) ------- +static void place_cursor(int row, int col) +{ + char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2]; + + if (row < 0) row = 0; + if (row >= rows) row = rows - 1; + if (col < 0) col = 0; + if (col >= columns) col = columns - 1; + + sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1); + write1(cm1); +} + +//----- Erase from cursor to end of line ----------------------- +static void clear_to_eol(void) +{ + write1(ESC_CLEAR2EOL); +} + +static void go_bottom_and_clear_to_eol(void) +{ + place_cursor(rows - 1, 0); + clear_to_eol(); +} + +//----- Start standout mode ------------------------------------ +static void standout_start(void) +{ + write1(ESC_BOLD_TEXT); +} + +//----- End standout mode -------------------------------------- +static void standout_end(void) +{ + write1(ESC_NORM_TEXT); +} + +//----- Text Movement Routines --------------------------------- +static char *begin_line(char *p) // return pointer to first char cur line +{ + if (p > text) { + p = memrchr(text, '\n', p - text); + if (!p) + return text; + return p + 1; } return p; } -static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present +static char *end_line(char *p) // return pointer to NL of cur line { - //----- get the address' i.e., 1,3 'a,'b ----- - // get FIRST addr, if present - while (isblank(*p)) - p++; // skip over leading spaces - if (*p == '%') { // alias for 1,$ - p++; - *b = 1; - *e = count_lines(text, end-1); - goto ga0; - } - p = get_one_address(p, b); - while (isblank(*p)) - p++; - if (*p == ',') { // is there a address separator - p++; - while (isblank(*p)) - p++; - // get SECOND addr, if present - p = get_one_address(p, e); + if (p < end - 1) { + p = memchr(p, '\n', end - p - 1); + if (!p) + return end - 1; } - ga0: - while (isblank(*p)) - p++; // skip over trailing spaces return p; } -#if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS -static void setops(const char *args, const char *opname, int flg_no, - const char *short_opname, int opt) +static char *dollar_line(char *p) // return pointer to just before NL line { - const char *a = args + flg_no; - int l = strlen(opname) - 1; // opname have + ' ' + p = end_line(p); + // Try to stay off of the Newline + if (*p == '\n' && (p - begin_line(p)) > 0) + p--; + return p; +} - // maybe strncmp? we had tons of erroneous strncasecmp's... - if (strncasecmp(a, opname, l) == 0 - || strncasecmp(a, short_opname, 2) == 0 - ) { - if (flg_no) - vi_setops &= ~opt; - else - vi_setops |= opt; - } +static char *prev_line(char *p) // return pointer first char prev line +{ + p = begin_line(p); // goto beginning of cur line + if (p > text && p[-1] == '\n') + p--; // step to prev line + p = begin_line(p); // goto beginning of prev line + return p; } -#endif -#endif /* FEATURE_VI_COLON */ +static char *next_line(char *p) // return pointer first char next line +{ + p = end_line(p); + if (p < end - 1 && *p == '\n') + p++; // step to next line + return p; +} -// buf must be no longer than MAX_INPUT_LEN! -static void colon(char *buf) +//----- Text Information Routines ------------------------------ +static char *end_screen(void) { -#if !ENABLE_FEATURE_VI_COLON - // Simple ":cmd" handler with minimal set of commands - char *p = buf; + char *q; int cnt; - if (*p == ':') - p++; - cnt = strlen(p); - if (cnt == 0) - return; - if (strncmp(p, "quit", cnt) == 0 - || strncmp(p, "q!", cnt) == 0 - ) { - if (modified_count && p[1] != '!') { - status_line_bold("No write since last change (:%s! overrides)", p); - } else { - editing = 0; - } - return; + // find new bottom line + q = screenbegin; + for (cnt = 0; cnt < rows - 2; cnt++) + q = next_line(q); + q = end_line(q); + return q; +} + +// count line from start to stop +static int count_lines(char *start, char *stop) +{ + char *q; + int cnt; + + if (stop < start) { // start and stop are backwards- reverse them + q = start; + start = stop; + stop = q; } - if (strncmp(p, "write", cnt) == 0 - || strncmp(p, "wq", cnt) == 0 - || strncmp(p, "wn", cnt) == 0 - || (p[0] == 'x' && !p[1]) - ) { - if (modified_count != 0 || p[0] != 'x') { - cnt = file_write(current_filename, text, end - 1); - } - if (cnt < 0) { - if (cnt == -1) - status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO); - } else { - modified_count = 0; - last_modified_count = -1; - status_line("'%s' %dL, %dC", - current_filename, - count_lines(text, end - 1), cnt - ); - if (p[0] == 'x' - || p[1] == 'q' || p[1] == 'n' - || p[1] == 'Q' || p[1] == 'N' - ) { - editing = 0; - } - } - return; - } - if (strncmp(p, "file", cnt) == 0) { - last_status_cksum = 0; // force status update - return; - } - if (sscanf(p, "%d", &cnt) > 0) { - dot = find_line(cnt); - dot_skip_over_ws(); - return; + cnt = 0; + stop = end_line(stop); + while (start <= stop && start <= end - 1) { + start = end_line(start); + if (*start == '\n') + cnt++; + start++; } - not_implemented(p); -#else + return cnt; +} - char c, *buf1, *q, *r; - char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN]; - int i, l, li, b, e; - int useforce; -# if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC - char *orig_buf; -# endif +static char *find_line(int li) // find beginning of line #li +{ + char *q; - // :3154 // if (-e line 3154) goto it else stay put - // :4,33w! foo // write a portion of buffer to file "foo" - // :w // write all of buffer to current file - // :q // quit - // :q! // quit- dont care about modified file - // :'a,'z!sort -u // filter block through sort - // :'f // goto mark "f" - // :'fl // list literal the mark "f" line - // :.r bar // read file "bar" into buffer before dot - // :/123/,/abc/d // delete lines from "123" line to "abc" line - // :/xyz/ // goto the "xyz" line - // :s/find/replace/ // substitute pattern "find" with "replace" - // :! // run then return - // + for (q = text; li > 1; li--) { + q = next_line(q); + } + return q; +} - if (!buf[0]) - goto ret; - if (*buf == ':') - buf++; // move past the ':' +static int next_tabstop(int col) +{ + return col + ((tabstop - 1) - (col % tabstop)); +} - li = i = 0; - b = e = -1; - q = text; // assume 1,$ for the range - r = end - 1; - li = count_lines(text, end - 1); - fn = current_filename; +//----- Erase the Screen[] memory ------------------------------ +static void screen_erase(void) +{ + memset(screen, ' ', screensize); // clear new screen +} - // look for optional address(es) :. :1 :1,9 :'q,'a :% - buf = get_address(buf, &b, &e); +//----- Synchronize the cursor to Dot -------------------------- +static NOINLINE void sync_cursor(char *d, int *row, int *col) +{ + char *beg_cur; // begin and end of "d" line + char *tp; + int cnt, ro, co; -# if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC - // remember orig command line - orig_buf = buf; -# endif + beg_cur = begin_line(d); // first char of cur line - // get the COMMAND into cmd[] - buf1 = cmd; - while (*buf != '\0') { - if (isspace(*buf)) - break; - *buf1++ = *buf++; + if (beg_cur < screenbegin) { + // "d" is before top line on screen + // how many lines do we have to move + cnt = count_lines(beg_cur, screenbegin); + sc1: + screenbegin = beg_cur; + if (cnt > (rows - 1) / 2) { + // we moved too many lines. put "dot" in middle of screen + for (cnt = 0; cnt < (rows - 1) / 2; cnt++) { + screenbegin = prev_line(screenbegin); + } + } + } else { + char *end_scr; // begin and end of screen + end_scr = end_screen(); // last char of screen + if (beg_cur > end_scr) { + // "d" is after bottom line on screen + // how many lines do we have to move + cnt = count_lines(end_scr, beg_cur); + if (cnt > (rows - 1) / 2) + goto sc1; // too many lines + for (ro = 0; ro < cnt - 1; ro++) { + // move screen begin the same amount + screenbegin = next_line(screenbegin); + // now, move the end of screen + end_scr = next_line(end_scr); + end_scr = end_line(end_scr); + } + } } - *buf1 = '\0'; - // get any ARGuments - while (isblank(*buf)) - buf++; - strcpy(args, buf); - useforce = FALSE; - buf1 = last_char_is(cmd, '!'); - if (buf1) { - useforce = TRUE; - *buf1 = '\0'; // get rid of ! + // "d" is on screen- find out which row + tp = screenbegin; + for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row + if (tp == beg_cur) + break; + tp = next_line(tp); } - if (b >= 0) { - // if there is only one addr, then the addr - // is the line number of the single line the - // user wants. So, reset the end - // pointer to point at end of the "b" line - q = find_line(b); // what line is #b - r = end_line(q); - li = 1; + + // find out what col "d" is on + co = 0; + while (tp < d) { // drive "co" to correct column + if (*tp == '\n') //vda || *tp == '\0') + break; + if (*tp == '\t') { + // handle tabs like real vi + if (d == tp && cmd_mode) { + break; + } + co = next_tabstop(co); + } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) { + co++; // display as ^X, use 2 columns + } + co++; + tp++; } - if (e >= 0) { - // we were given two addrs. change the - // end pointer to the addr given by user. - r = find_line(e); // what line is #e - r = end_line(r); - li = e - b + 1; + + // "co" is the column where "dot" is. + // The screen has "columns" columns. + // The currently displayed columns are 0+offset -- columns+ofset + // |-------------------------------------------------------------| + // ^ ^ ^ + // offset | |------- columns ----------------| + // + // If "co" is already in this range then we do not have to adjust offset + // but, we do have to subtract the "offset" bias from "co". + // If "co" is outside this range then we have to change "offset". + // If the first char of a line is a tab the cursor will try to stay + // in column 7, but we have to set offset to 0. + + if (co < 0 + offset) { + offset = co; } - // ------------ now look for the command ------------ - i = strlen(cmd); - if (i == 0) { // :123CR goto line #123 - if (b >= 0) { - dot = find_line(b); // what line is #b - dot_skip_over_ws(); - } + if (co >= columns + offset) { + offset = co - columns + 1; } -# if ENABLE_FEATURE_ALLOW_EXEC - else if (cmd[0] == '!') { // run a cmd - int retcode; - // :!ls run the - go_bottom_and_clear_to_eol(); - cookmode(); - retcode = system(orig_buf + 1); // run the cmd - if (retcode) - printf("\nshell returned %i\n\n", retcode); - rawmode(); - Hit_Return(); // let user see results + // if the first char of the line is a tab, and "dot" is sitting on it + // force offset to 0. + if (d == beg_cur && *d == '\t') { + offset = 0; } -# endif - else if (cmd[0] == '=' && !cmd[1]) { // where is the address - if (b < 0) { // no addr given- use defaults - b = e = count_lines(text, dot); - } - status_line("%d", b); - } else if (strncmp(cmd, "delete", i) == 0) { // delete lines - if (b < 0) { // no addr given- use defaults - q = begin_line(dot); // assume .,. for the range - r = end_line(dot); - } - dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines - dot_skip_over_ws(); - } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file - int size; + co -= offset; - // don't edit, if the current file has been modified - if (modified_count && !useforce) { - status_line_bold("No write since last change (:%s! overrides)", cmd); - goto ret; - } - if (args[0]) { - // the user supplied a file name - fn = args; - } else if (current_filename && current_filename[0]) { - // no user supplied name- use the current filename - // fn = current_filename; was set by default - } else { - // no user file name, no current name- punt - status_line_bold("No current filename"); - goto ret; - } + *row = ro; + *col = co; +} - size = init_text_buffer(fn); +//----- The Colon commands ------------------------------------- +#if ENABLE_FEATURE_VI_COLON +static char *get_one_address(char *p, int *addr) // get colon addr, if present +{ + int st; + char *q; + IF_FEATURE_VI_YANKMARK(char c;) + IF_FEATURE_VI_SEARCH(char *pat;) -# if ENABLE_FEATURE_VI_YANKMARK - if (Ureg >= 0 && Ureg < 28) { - free(reg[Ureg]); // free orig line reg- for 'U' - reg[Ureg] = NULL; - } - if (YDreg >= 0 && YDreg < 28) { - free(reg[YDreg]); // free default yank/delete register - reg[YDreg] = NULL; - } -# endif - // how many lines in text[]? - li = count_lines(text, end - 1); - status_line("'%s'%s" - IF_FEATURE_VI_READONLY("%s") - " %dL, %dC", - current_filename, - (size < 0 ? " [New file]" : ""), - IF_FEATURE_VI_READONLY( - ((readonly_mode) ? " [Readonly]" : ""), - ) - li, (int)(end - text) - ); - } else if (strncmp(cmd, "file", i) == 0) { // what File is this - if (b != -1 || e != -1) { - status_line_bold("No address allowed on this command"); - goto ret; - } - if (args[0]) { - // user wants a new filename - free(current_filename); - current_filename = xstrdup(args); - } else { - // user wants file status info - last_status_cksum = 0; // force status update + *addr = -1; // assume no addr + if (*p == '.') { // the current line + p++; + q = begin_line(dot); + *addr = count_lines(text, q); + } +#if ENABLE_FEATURE_VI_YANKMARK + else if (*p == '\'') { // is this a mark addr + p++; + c = tolower(*p); + p++; + if (c >= 'a' && c <= 'z') { + // we have a mark + c = c - 'a'; + q = mark[(unsigned char) c]; + if (q != NULL) { // is mark valid + *addr = count_lines(text, q); + } } - } else if (strncmp(cmd, "features", i) == 0) { // what features are available - // print out values of all features - go_bottom_and_clear_to_eol(); - cookmode(); - show_help(); - rawmode(); - Hit_Return(); - } else if (strncmp(cmd, "list", i) == 0) { // literal print line - if (b < 0) { // no addr given- use defaults - q = begin_line(dot); // assume .,. for the range - r = end_line(dot); + } +#endif +#if ENABLE_FEATURE_VI_SEARCH + else if (*p == '/') { // a search pattern + q = strchrnul(++p, '/'); + pat = xstrndup(p, q - p); // save copy of pattern + p = q; + if (*p == '/') + p++; + q = char_search(dot, pat, (FORWARD << 1) | FULL); + if (q != NULL) { + *addr = count_lines(text, q); } - go_bottom_and_clear_to_eol(); - puts("\r"); - for (; q <= r; q++) { - int c_is_no_print; + free(pat); + } +#endif + else if (*p == '$') { // the last line in file + p++; + q = begin_line(end - 1); + *addr = count_lines(text, q); + } else if (isdigit(*p)) { // specific line number + sscanf(p, "%d%n", addr, &st); + p += st; + } else { + // unrecognized address - assume -1 + *addr = -1; + } + return p; +} - c = *q; - c_is_no_print = (c & 0x80) && !Isprint(c); - if (c_is_no_print) { - c = '.'; - standout_start(); - } - if (c == '\n') { - write1("$\r"); - } else if (c < ' ' || c == 127) { - bb_putchar('^'); - if (c == 127) - c = '?'; - else - c += '@'; - } - bb_putchar(c); - if (c_is_no_print) - standout_end(); - } - Hit_Return(); - } else if (strncmp(cmd, "quit", i) == 0 // quit - || strncmp(cmd, "next", i) == 0 // edit next file - || strncmp(cmd, "prev", i) == 0 // edit previous file +static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present +{ + //----- get the address' i.e., 1,3 'a,'b ----- + // get FIRST addr, if present + while (isblank(*p)) + p++; // skip over leading spaces + if (*p == '%') { // alias for 1,$ + p++; + *b = 1; + *e = count_lines(text, end-1); + goto ga0; + } + p = get_one_address(p, b); + while (isblank(*p)) + p++; + if (*p == ',') { // is there a address separator + p++; + while (isblank(*p)) + p++; + // get SECOND addr, if present + p = get_one_address(p, e); + } + ga0: + while (isblank(*p)) + p++; // skip over trailing spaces + return p; +} + +#if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS +static void setops(const char *args, const char *opname, int flg_no, + const char *short_opname, int opt) +{ + const char *a = args + flg_no; + int l = strlen(opname) - 1; // opname have + ' ' + + // maybe strncmp? we had tons of erroneous strncasecmp's... + if (strncasecmp(a, opname, l) == 0 + || strncasecmp(a, short_opname, 2) == 0 ) { - int n; - if (useforce) { - if (*cmd == 'q') { - // force end of argv list - optind = save_argc; - } + if (flg_no) + vi_setops &= ~opt; + else + vi_setops |= opt; + } +} +#endif + +#endif /* FEATURE_VI_COLON */ + +// buf must be no longer than MAX_INPUT_LEN! +static void colon(char *buf) +{ +#if !ENABLE_FEATURE_VI_COLON + // Simple ":cmd" handler with minimal set of commands + char *p = buf; + int cnt; + + if (*p == ':') + p++; + cnt = strlen(p); + if (cnt == 0) + return; + if (strncmp(p, "quit", cnt) == 0 + || strncmp(p, "q!", cnt) == 0 + ) { + if (modified_count && p[1] != '!') { + status_line_bold("No write since last change (:%s! overrides)", p); + } else { editing = 0; - goto ret; - } - // don't exit if the file been modified - if (modified_count) { - status_line_bold("No write since last change (:%s! overrides)", cmd); - goto ret; - } - // are there other file to edit - n = save_argc - optind - 1; - if (*cmd == 'q' && n > 0) { - status_line_bold("%d more file(s) to edit", n); - goto ret; } - if (*cmd == 'n' && n <= 0) { - status_line_bold("No more files to edit"); - goto ret; + return; + } + if (strncmp(p, "write", cnt) == 0 + || strncmp(p, "wq", cnt) == 0 + || strncmp(p, "wn", cnt) == 0 + || (p[0] == 'x' && !p[1]) + ) { + if (modified_count != 0 || p[0] != 'x') { + cnt = file_write(current_filename, text, end - 1); } - if (*cmd == 'p') { - // are there previous files to edit - if (optind < 1) { - status_line_bold("No previous files to edit"); - goto ret; + if (cnt < 0) { + if (cnt == -1) + status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO); + } else { + modified_count = 0; + last_modified_count = -1; + status_line("'%s' %dL, %dC", + current_filename, + count_lines(text, end - 1), cnt + ); + if (p[0] == 'x' + || p[1] == 'q' || p[1] == 'n' + || p[1] == 'Q' || p[1] == 'N' + ) { + editing = 0; } - optind -= 2; } - editing = 0; - } else if (strncmp(cmd, "read", i) == 0) { // read file into text[] - int size; + return; + } + if (strncmp(p, "file", cnt) == 0) { + last_status_cksum = 0; // force status update + return; + } + if (sscanf(p, "%d", &cnt) > 0) { + dot = find_line(cnt); + dot_skip_over_ws(); + return; + } + not_implemented(p); +#else - fn = args; - if (!fn[0]) { - status_line_bold("No filename given"); - goto ret; - } + char c, *buf1, *q, *r; + char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN]; + int i, l, li, b, e; + int useforce; +# if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC + char *orig_buf; +# endif + + // :3154 // if (-e line 3154) goto it else stay put + // :4,33w! foo // write a portion of buffer to file "foo" + // :w // write all of buffer to current file + // :q // quit + // :q! // quit- dont care about modified file + // :'a,'z!sort -u // filter block through sort + // :'f // goto mark "f" + // :'fl // list literal the mark "f" line + // :.r bar // read file "bar" into buffer before dot + // :/123/,/abc/d // delete lines from "123" line to "abc" line + // :/xyz/ // goto the "xyz" line + // :s/find/replace/ // substitute pattern "find" with "replace" + // :! // run then return + // + + if (!buf[0]) + goto ret; + if (*buf == ':') + buf++; // move past the ':' + + li = i = 0; + b = e = -1; + q = text; // assume 1,$ for the range + r = end - 1; + li = count_lines(text, end - 1); + fn = current_filename; + + // look for optional address(es) :. :1 :1,9 :'q,'a :% + buf = get_address(buf, &b, &e); + +# if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC + // remember orig command line + orig_buf = buf; +# endif + + // get the COMMAND into cmd[] + buf1 = cmd; + while (*buf != '\0') { + if (isspace(*buf)) + break; + *buf1++ = *buf++; + } + *buf1 = '\0'; + // get any ARGuments + while (isblank(*buf)) + buf++; + strcpy(args, buf); + useforce = FALSE; + buf1 = last_char_is(cmd, '!'); + if (buf1) { + useforce = TRUE; + *buf1 = '\0'; // get rid of ! + } + if (b >= 0) { + // if there is only one addr, then the addr + // is the line number of the single line the + // user wants. So, reset the end + // pointer to point at end of the "b" line + q = find_line(b); // what line is #b + r = end_line(q); + li = 1; + } + if (e >= 0) { + // we were given two addrs. change the + // end pointer to the addr given by user. + r = find_line(e); // what line is #e + r = end_line(r); + li = e - b + 1; + } + // ------------ now look for the command ------------ + i = strlen(cmd); + if (i == 0) { // :123CR goto line #123 + if (b >= 0) { + dot = find_line(b); // what line is #b + dot_skip_over_ws(); + } + } +# if ENABLE_FEATURE_ALLOW_EXEC + else if (cmd[0] == '!') { // run a cmd + int retcode; + // :!ls run the + go_bottom_and_clear_to_eol(); + cookmode(); + retcode = system(orig_buf + 1); // run the cmd + if (retcode) + printf("\nshell returned %i\n\n", retcode); + rawmode(); + Hit_Return(); // let user see results + } +# endif + else if (cmd[0] == '=' && !cmd[1]) { // where is the address + if (b < 0) { // no addr given- use defaults + b = e = count_lines(text, dot); + } + status_line("%d", b); + } else if (strncmp(cmd, "delete", i) == 0) { // delete lines + if (b < 0) { // no addr given- use defaults + q = begin_line(dot); // assume .,. for the range + r = end_line(dot); + } + dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines + dot_skip_over_ws(); + } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file + int size; + + // don't edit, if the current file has been modified + if (modified_count && !useforce) { + status_line_bold("No write since last change (:%s! overrides)", cmd); + goto ret; + } + if (args[0]) { + // the user supplied a file name + fn = args; + } else if (current_filename && current_filename[0]) { + // no user supplied name- use the current filename + // fn = current_filename; was set by default + } else { + // no user file name, no current name- punt + status_line_bold("No current filename"); + goto ret; + } + + size = init_text_buffer(fn); + +# if ENABLE_FEATURE_VI_YANKMARK + if (Ureg >= 0 && Ureg < 28) { + free(reg[Ureg]); // free orig line reg- for 'U' + reg[Ureg] = NULL; + } + if (YDreg >= 0 && YDreg < 28) { + free(reg[YDreg]); // free default yank/delete register + reg[YDreg] = NULL; + } +# endif + // how many lines in text[]? + li = count_lines(text, end - 1); + status_line("'%s'%s" + IF_FEATURE_VI_READONLY("%s") + " %dL, %dC", + current_filename, + (size < 0 ? " [New file]" : ""), + IF_FEATURE_VI_READONLY( + ((readonly_mode) ? " [Readonly]" : ""), + ) + li, (int)(end - text) + ); + } else if (strncmp(cmd, "file", i) == 0) { // what File is this + if (b != -1 || e != -1) { + status_line_bold("No address allowed on this command"); + goto ret; + } + if (args[0]) { + // user wants a new filename + free(current_filename); + current_filename = xstrdup(args); + } else { + // user wants file status info + last_status_cksum = 0; // force status update + } + } else if (strncmp(cmd, "features", i) == 0) { // what features are available + // print out values of all features + go_bottom_and_clear_to_eol(); + cookmode(); + show_help(); + rawmode(); + Hit_Return(); + } else if (strncmp(cmd, "list", i) == 0) { // literal print line + if (b < 0) { // no addr given- use defaults + q = begin_line(dot); // assume .,. for the range + r = end_line(dot); + } + go_bottom_and_clear_to_eol(); + puts("\r"); + for (; q <= r; q++) { + int c_is_no_print; + + c = *q; + c_is_no_print = (c & 0x80) && !Isprint(c); + if (c_is_no_print) { + c = '.'; + standout_start(); + } + if (c == '\n') { + write1("$\r"); + } else if (c < ' ' || c == 127) { + bb_putchar('^'); + if (c == 127) + c = '?'; + else + c += '@'; + } + bb_putchar(c); + if (c_is_no_print) + standout_end(); + } + Hit_Return(); + } else if (strncmp(cmd, "quit", i) == 0 // quit + || strncmp(cmd, "next", i) == 0 // edit next file + || strncmp(cmd, "prev", i) == 0 // edit previous file + ) { + int n; + if (useforce) { + if (*cmd == 'q') { + // force end of argv list + optind = save_argc; + } + editing = 0; + goto ret; + } + // don't exit if the file been modified + if (modified_count) { + status_line_bold("No write since last change (:%s! overrides)", cmd); + goto ret; + } + // are there other file to edit + n = save_argc - optind - 1; + if (*cmd == 'q' && n > 0) { + status_line_bold("%d more file(s) to edit", n); + goto ret; + } + if (*cmd == 'n' && n <= 0) { + status_line_bold("No more files to edit"); + goto ret; + } + if (*cmd == 'p') { + // are there previous files to edit + if (optind < 1) { + status_line_bold("No previous files to edit"); + goto ret; + } + optind -= 2; + } + editing = 0; + } else if (strncmp(cmd, "read", i) == 0) { // read file into text[] + int size; + + fn = args; + if (!fn[0]) { + status_line_bold("No filename given"); + goto ret; + } if (b < 0) { // no addr given- use defaults q = begin_line(dot); // assume "dot" } @@ -1205,348 +1481,154 @@ static void colon(char *buf) int dont_chain_first_item = ALLOW_UNDO; # endif - // F points to the "find" pattern - // R points to the "replace" pattern - // replace the cmd line delimiters "/" with NULs - c = orig_buf[1]; // what is the delimiter - F = orig_buf + 2; // start of "find" - R = strchr(F, c); // middle delimiter - if (!R) - goto colon_s_fail; - len_F = R - F; - *R++ = '\0'; // terminate "find" - flags = strchr(R, c); - if (!flags) - goto colon_s_fail; - len_R = flags - R; - *flags++ = '\0'; // terminate "replace" - gflag = *flags; - - q = begin_line(q); - if (b < 0) { // maybe :s/foo/bar/ - q = begin_line(dot); // start with cur line - b = count_lines(text, q); // cur line number - } - if (e < 0) - e = b; // maybe :.s/foo/bar/ - - for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0 - char *ls = q; // orig line start - char *found; - vc4: - found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find" - if (found) { - uintptr_t bias; - // we found the "find" pattern - delete it - // For undo support, the first item should not be chained - text_hole_delete(found, found + len_F - 1, dont_chain_first_item); -# if ENABLE_FEATURE_VI_UNDO - dont_chain_first_item = ALLOW_UNDO_CHAIN; -# endif - // insert the "replace" patern - bias = string_insert(found, R, ALLOW_UNDO_CHAIN); - found += bias; - ls += bias; - /*q += bias; - recalculated anyway */ - // check for "global" :s/foo/bar/g - if (gflag == 'g') { - if ((found + len_R) < end_line(ls)) { - q = found + len_R; - goto vc4; // don't let q move past cur line - } - } - } - q = next_line(ls); - } -# endif /* FEATURE_VI_SEARCH */ - } else if (strncmp(cmd, "version", i) == 0) { // show software version - status_line(BB_VER); - } else if (strncmp(cmd, "write", i) == 0 // write text to file - || strncmp(cmd, "wq", i) == 0 - || strncmp(cmd, "wn", i) == 0 - || (cmd[0] == 'x' && !cmd[1]) - ) { - int size; - //int forced = FALSE; - - // is there a file name to write to? - if (args[0]) { - fn = args; - } -# if ENABLE_FEATURE_VI_READONLY - if (readonly_mode && !useforce) { - status_line_bold("'%s' is read only", fn); - goto ret; - } -# endif - //if (useforce) { - // if "fn" is not write-able, chmod u+w - // sprintf(syscmd, "chmod u+w %s", fn); - // system(syscmd); - // forced = TRUE; - //} - if (modified_count != 0 || cmd[0] != 'x') { - size = r - q + 1; - l = file_write(fn, q, r); - } else { - size = 0; - l = 0; - } - //if (useforce && forced) { - // chmod u-w - // sprintf(syscmd, "chmod u-w %s", fn); - // system(syscmd); - // forced = FALSE; - //} - if (l < 0) { - if (l == -1) - status_line_bold_errno(fn); - } else { - // how many lines written - li = count_lines(q, q + l - 1); - status_line("'%s' %dL, %dC", fn, li, l); - if (l == size) { - if (q == text && q + l == end) { - modified_count = 0; - last_modified_count = -1; - } - if (cmd[0] == 'x' - || cmd[1] == 'q' || cmd[1] == 'n' - || cmd[1] == 'Q' || cmd[1] == 'N' - ) { - editing = 0; - } - } - } -# if ENABLE_FEATURE_VI_YANKMARK - } else if (strncmp(cmd, "yank", i) == 0) { // yank lines - if (b < 0) { // no addr given- use defaults - q = begin_line(dot); // assume .,. for the range - r = end_line(dot); - } - text_yank(q, r, YDreg); - li = count_lines(q, r); - status_line("Yank %d lines (%d chars) into [%c]", - li, strlen(reg[YDreg]), what_reg()); -# endif - } else { - // cmd unknown - not_implemented(cmd); - } - ret: - dot = bound_dot(dot); // make sure "dot" is valid - return; -# if ENABLE_FEATURE_VI_SEARCH - colon_s_fail: - status_line(":s expression missing delimiters"); -# endif -#endif /* FEATURE_VI_COLON */ -} - -static void Hit_Return(void) -{ - int c; - - standout_start(); - write1("[Hit return to continue]"); - standout_end(); - while ((c = get_one_char()) != '\n' && c != '\r') - continue; - redraw(TRUE); // force redraw all -} - -static int next_tabstop(int col) -{ - return col + ((tabstop - 1) - (col % tabstop)); -} - -//----- Synchronize the cursor to Dot -------------------------- -static NOINLINE void sync_cursor(char *d, int *row, int *col) -{ - char *beg_cur; // begin and end of "d" line - char *tp; - int cnt, ro, co; - - beg_cur = begin_line(d); // first char of cur line - - if (beg_cur < screenbegin) { - // "d" is before top line on screen - // how many lines do we have to move - cnt = count_lines(beg_cur, screenbegin); - sc1: - screenbegin = beg_cur; - if (cnt > (rows - 1) / 2) { - // we moved too many lines. put "dot" in middle of screen - for (cnt = 0; cnt < (rows - 1) / 2; cnt++) { - screenbegin = prev_line(screenbegin); - } - } - } else { - char *end_scr; // begin and end of screen - end_scr = end_screen(); // last char of screen - if (beg_cur > end_scr) { - // "d" is after bottom line on screen - // how many lines do we have to move - cnt = count_lines(end_scr, beg_cur); - if (cnt > (rows - 1) / 2) - goto sc1; // too many lines - for (ro = 0; ro < cnt - 1; ro++) { - // move screen begin the same amount - screenbegin = next_line(screenbegin); - // now, move the end of screen - end_scr = next_line(end_scr); - end_scr = end_line(end_scr); - } - } - } - // "d" is on screen- find out which row - tp = screenbegin; - for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row - if (tp == beg_cur) - break; - tp = next_line(tp); - } - - // find out what col "d" is on - co = 0; - while (tp < d) { // drive "co" to correct column - if (*tp == '\n') //vda || *tp == '\0') - break; - if (*tp == '\t') { - // handle tabs like real vi - if (d == tp && cmd_mode) { - break; - } - co = next_tabstop(co); - } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) { - co++; // display as ^X, use 2 columns - } - co++; - tp++; - } - - // "co" is the column where "dot" is. - // The screen has "columns" columns. - // The currently displayed columns are 0+offset -- columns+ofset - // |-------------------------------------------------------------| - // ^ ^ ^ - // offset | |------- columns ----------------| - // - // If "co" is already in this range then we do not have to adjust offset - // but, we do have to subtract the "offset" bias from "co". - // If "co" is outside this range then we have to change "offset". - // If the first char of a line is a tab the cursor will try to stay - // in column 7, but we have to set offset to 0. - - if (co < 0 + offset) { - offset = co; - } - if (co >= columns + offset) { - offset = co - columns + 1; - } - // if the first char of the line is a tab, and "dot" is sitting on it - // force offset to 0. - if (d == beg_cur && *d == '\t') { - offset = 0; - } - co -= offset; - - *row = ro; - *col = co; -} - -//----- Text Movement Routines --------------------------------- -static char *begin_line(char *p) // return pointer to first char cur line -{ - if (p > text) { - p = memrchr(text, '\n', p - text); - if (!p) - return text; - return p + 1; - } - return p; -} - -static char *end_line(char *p) // return pointer to NL of cur line -{ - if (p < end - 1) { - p = memchr(p, '\n', end - p - 1); - if (!p) - return end - 1; - } - return p; -} - -static char *dollar_line(char *p) // return pointer to just before NL line -{ - p = end_line(p); - // Try to stay off of the Newline - if (*p == '\n' && (p - begin_line(p)) > 0) - p--; - return p; -} - -static char *prev_line(char *p) // return pointer first char prev line -{ - p = begin_line(p); // goto beginning of cur line - if (p > text && p[-1] == '\n') - p--; // step to prev line - p = begin_line(p); // goto beginning of prev line - return p; -} - -static char *next_line(char *p) // return pointer first char next line -{ - p = end_line(p); - if (p < end - 1 && *p == '\n') - p++; // step to next line - return p; -} - -//----- Text Information Routines ------------------------------ -static char *end_screen(void) -{ - char *q; - int cnt; + // F points to the "find" pattern + // R points to the "replace" pattern + // replace the cmd line delimiters "/" with NULs + c = orig_buf[1]; // what is the delimiter + F = orig_buf + 2; // start of "find" + R = strchr(F, c); // middle delimiter + if (!R) + goto colon_s_fail; + len_F = R - F; + *R++ = '\0'; // terminate "find" + flags = strchr(R, c); + if (!flags) + goto colon_s_fail; + len_R = flags - R; + *flags++ = '\0'; // terminate "replace" + gflag = *flags; - // find new bottom line - q = screenbegin; - for (cnt = 0; cnt < rows - 2; cnt++) - q = next_line(q); - q = end_line(q); - return q; -} + q = begin_line(q); + if (b < 0) { // maybe :s/foo/bar/ + q = begin_line(dot); // start with cur line + b = count_lines(text, q); // cur line number + } + if (e < 0) + e = b; // maybe :.s/foo/bar/ -// count line from start to stop -static int count_lines(char *start, char *stop) -{ - char *q; - int cnt; + for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0 + char *ls = q; // orig line start + char *found; + vc4: + found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find" + if (found) { + uintptr_t bias; + // we found the "find" pattern - delete it + // For undo support, the first item should not be chained + text_hole_delete(found, found + len_F - 1, dont_chain_first_item); +# if ENABLE_FEATURE_VI_UNDO + dont_chain_first_item = ALLOW_UNDO_CHAIN; +# endif + // insert the "replace" patern + bias = string_insert(found, R, ALLOW_UNDO_CHAIN); + found += bias; + ls += bias; + /*q += bias; - recalculated anyway */ + // check for "global" :s/foo/bar/g + if (gflag == 'g') { + if ((found + len_R) < end_line(ls)) { + q = found + len_R; + goto vc4; // don't let q move past cur line + } + } + } + q = next_line(ls); + } +# endif /* FEATURE_VI_SEARCH */ + } else if (strncmp(cmd, "version", i) == 0) { // show software version + status_line(BB_VER); + } else if (strncmp(cmd, "write", i) == 0 // write text to file + || strncmp(cmd, "wq", i) == 0 + || strncmp(cmd, "wn", i) == 0 + || (cmd[0] == 'x' && !cmd[1]) + ) { + int size; + //int forced = FALSE; - if (stop < start) { // start and stop are backwards- reverse them - q = start; - start = stop; - stop = q; - } - cnt = 0; - stop = end_line(stop); - while (start <= stop && start <= end - 1) { - start = end_line(start); - if (*start == '\n') - cnt++; - start++; + // is there a file name to write to? + if (args[0]) { + fn = args; + } +# if ENABLE_FEATURE_VI_READONLY + if (readonly_mode && !useforce) { + status_line_bold("'%s' is read only", fn); + goto ret; + } +# endif + //if (useforce) { + // if "fn" is not write-able, chmod u+w + // sprintf(syscmd, "chmod u+w %s", fn); + // system(syscmd); + // forced = TRUE; + //} + if (modified_count != 0 || cmd[0] != 'x') { + size = r - q + 1; + l = file_write(fn, q, r); + } else { + size = 0; + l = 0; + } + //if (useforce && forced) { + // chmod u-w + // sprintf(syscmd, "chmod u-w %s", fn); + // system(syscmd); + // forced = FALSE; + //} + if (l < 0) { + if (l == -1) + status_line_bold_errno(fn); + } else { + // how many lines written + li = count_lines(q, q + l - 1); + status_line("'%s' %dL, %dC", fn, li, l); + if (l == size) { + if (q == text && q + l == end) { + modified_count = 0; + last_modified_count = -1; + } + if (cmd[0] == 'x' + || cmd[1] == 'q' || cmd[1] == 'n' + || cmd[1] == 'Q' || cmd[1] == 'N' + ) { + editing = 0; + } + } + } +# if ENABLE_FEATURE_VI_YANKMARK + } else if (strncmp(cmd, "yank", i) == 0) { // yank lines + if (b < 0) { // no addr given- use defaults + q = begin_line(dot); // assume .,. for the range + r = end_line(dot); + } + text_yank(q, r, YDreg); + li = count_lines(q, r); + status_line("Yank %d lines (%d chars) into [%c]", + li, strlen(reg[YDreg]), what_reg()); +# endif + } else { + // cmd unknown + not_implemented(cmd); } - return cnt; + ret: + dot = bound_dot(dot); // make sure "dot" is valid + return; +# if ENABLE_FEATURE_VI_SEARCH + colon_s_fail: + status_line(":s expression missing delimiters"); +# endif +#endif /* FEATURE_VI_COLON */ } -static char *find_line(int li) // find beginning of line #li +static void Hit_Return(void) { - char *q; + int c; - for (q = text; li > 1; li--) { - q = next_line(q); - } - return q; + standout_start(); + write1("[Hit return to continue]"); + standout_end(); + while ((c = get_one_char()) != '\n' && c != '\r') + continue; + redraw(TRUE); // force redraw all } //----- Dot Movement Routines ---------------------------------- @@ -2430,32 +2512,6 @@ static char *swap_context(char *p) // goto new context for '' command make this } #endif /* FEATURE_VI_YANKMARK */ -//----- Set terminal attributes -------------------------------- -static void rawmode(void) -{ - // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals - set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL); - erase_char = term_orig.c_cc[VERASE]; -} - -static void cookmode(void) -{ - fflush_all(); - tcsetattr_stdin_TCSANOW(&term_orig); -} - -static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready -{ - struct pollfd pfd[1]; - - if (hund != 0) - fflush_all(); - - pfd[0].fd = STDIN_FILENO; - pfd[0].events = POLLIN; - return safe_poll(pfd, 1, hund*10) > 0; -} - //----- IO Routines -------------------------------------------- static int readit(void) // read (maybe cursor) key from stdin { @@ -2637,55 +2693,6 @@ static int file_write(char *fn, char *first, char *last) return charcnt; } -//----- Terminal Drawing --------------------------------------- -// The terminal is made up of 'rows' line of 'columns' columns. -// classically this would be 24 x 80. -// screen coordinates -// 0,0 ... 0,79 -// 1,0 ... 1,79 -// . ... . -// . ... . -// 22,0 ... 22,79 -// 23,0 ... 23,79 <- status line - -//----- Move the cursor to row x col (count from 0, not 1) ------- -static void place_cursor(int row, int col) -{ - char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2]; - - if (row < 0) row = 0; - if (row >= rows) row = rows - 1; - if (col < 0) col = 0; - if (col >= columns) col = columns - 1; - - sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1); - write1(cm1); -} - -//----- Erase from cursor to end of line ----------------------- -static void clear_to_eol(void) -{ - write1(ESC_CLEAR2EOL); -} - -static void go_bottom_and_clear_to_eol(void) -{ - place_cursor(rows - 1, 0); - clear_to_eol(); -} - -//----- Start standout mode ------------------------------------ -static void standout_start(void) -{ - write1(ESC_BOLD_TEXT); -} - -//----- End standout mode -------------------------------------- -static void standout_end(void) -{ - write1(ESC_NORM_TEXT); -} - //----- Flash the screen -------------------------------------- static void flash(int h) { @@ -2709,13 +2716,6 @@ static void indicate_error(void) } } -//----- Screen[] Routines -------------------------------------- -//----- Erase the Screen[] memory ------------------------------ -static void screen_erase(void) -{ - memset(screen, ' ', screensize); // clear new screen -} - static int bufsum(char *buf, int count) { int sum = 0; -- cgit v1.1