vi: rearrange functions, no logic changes
No binary (build failed)
—
(log yok)
| @@ -483,123 +483,8 @@ struct globals { | |
| } while (0) | |
| - | static void do_cmd(int); // execute a command |
| - | static int next_tabstop(int); |
| - | static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot |
| - | static char *begin_line(char *); // return pointer to cur line B-o-l |
| - | static char *end_line(char *); // return pointer to cur line E-o-l |
| - | static char *prev_line(char *); // return pointer to prev line B-o-l |
| - | static char *next_line(char *); // return pointer to next line B-o-l |
| - | static char *end_screen(void); // get pointer to last char on screen |
| - | static int count_lines(char *, char *); // count line from start to stop |
| - | static char *find_line(int); // find beginning of line #li |
| - | static char *move_to_col(char *, int); // move "p" to column l |
| - | static void dot_left(void); // move dot left- dont leave line |
| - | static void dot_right(void); // move dot right- dont leave line |
| - | static void dot_begin(void); // move dot to B-o-l |
| - | static void dot_end(void); // move dot to E-o-l |
| - | static void dot_next(void); // move dot to next line B-o-l |
| - | static void dot_prev(void); // move dot to prev line B-o-l |
| - | static void dot_scroll(int, int); // move the screen up or down |
| - | static void dot_skip_over_ws(void); // move dot pat WS |
| - | static char *bound_dot(char *); // make sure text[0] <= P < "end" |
| - | static char *new_screen(int, int); // malloc virtual screen memory |
| - | #if !ENABLE_FEATURE_VI_UNDO |
| - | #define char_insert(a,b,c) char_insert(a,b) |
| - | #endif |
| - | static char *char_insert(char *, char, int); // insert the char c at 'p' |
| - | // might reallocate text[]! use p += stupid_insert(p, ...), |
| - | // and be careful to not use pointers into potentially freed text[]! |
| - | static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p' |
| - | static int st_test(char *, int, int, char *); // helper for skip_thing() |
| - | static char *skip_thing(char *, int, int, int); // skip some object |
| - | static char *find_pair(char *, char); // find matching pair () [] {} |
| - | #if !ENABLE_FEATURE_VI_UNDO |
| - | #define text_hole_delete(a,b,c) text_hole_delete(a,b) |
| - | #endif |
| - | static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole |
| - | // might reallocate text[]! use p += text_hole_make(p, ...), |
| - | // and be careful to not use pointers into potentially freed text[]! |
| - | static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole |
| - | #if !ENABLE_FEATURE_VI_UNDO |
| - | #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d) |
| - | #endif |
| - | static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete |
| - | static void rawmode(void); // set "raw" mode on tty |
| - | static void cookmode(void); // return to "cooked" mode on tty |
| - | // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready) |
| - | static int mysleep(int); |
| - | static int get_one_char(void); // read 1 char from stdin |
| - | // file_insert might reallocate text[]! |
| - | static int file_insert(const char *, char *, int); |
| - | static int file_write(char *, char *, char *); |
| - | static void screen_erase(void); |
| - | static void go_bottom_and_clear_to_eol(void); |
| - | static void standout_start(void); // send "start reverse video" sequence |
| - | static void standout_end(void); // send "end reverse video" sequence |
| - | static void flash(int); // flash the terminal screen |
| static void show_status_line(void); // put a message on the bottom line | |
| - | static void status_line(const char *, ...); // print to status buf |
| static void status_line_bold(const char *, ...); | |
| - | static void status_line_bold_errno(const char *fn); |
| - | static void not_implemented(const char *); // display "Not implemented" message |
| - | static int format_edit_status(void); // format file status on status line |
| - | static void redraw(int); // force a full screen refresh |
| - | static char* format_line(char* /*, int*/); |
| - | static void refresh(int); // update the terminal from screen[] |
| - | |
| - | static void indicate_error(void); // use flash or beep to indicate error |
| - | static void Hit_Return(void); |
| - | |
| - | #if ENABLE_FEATURE_VI_SEARCH |
| - | static char *char_search(char *, const char *, int); // search for pattern starting at p |
| - | #endif |
| - | #if ENABLE_FEATURE_VI_COLON |
| - | static char *get_one_address(char *, int *); // get colon addr, if present |
| - | static char *get_address(char *, int *, int *); // get two colon addrs, if present |
| - | #endif |
| - | static void colon(char *); // execute the "colon" mode cmds |
| - | #if ENABLE_FEATURE_VI_USE_SIGNALS |
| - | static void winch_handler(int); // catch window size changes |
| - | static void tstp_handler(int); // catch ctrl-Z |
| - | static void int_handler(int); // catch ctrl-C |
| - | #endif |
| - | #if ENABLE_FEATURE_VI_DOT_CMD |
| - | static void start_new_cmd_q(char); // new queue for command |
| - | static void end_cmd_q(void); // stop saving input chars |
| - | #else |
| - | #define end_cmd_q() ((void)0) |
| - | #endif |
| - | #if ENABLE_FEATURE_VI_SETOPTS |
| - | static void showmatching(char *); // show the matching pair () [] {} |
| - | #endif |
| - | #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME |
| - | // might reallocate text[]! use p += string_insert(p, ...), |
| - | // and be careful to not use pointers into potentially freed text[]! |
| - | # if !ENABLE_FEATURE_VI_UNDO |
| - | #define string_insert(a,b,c) string_insert(a,b) |
| - | # endif |
| - | static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p' |
| - | #endif |
| - | #if ENABLE_FEATURE_VI_YANKMARK |
| - | static char *text_yank(char *, char *, int); // save copy of "p" into a register |
| - | static char what_reg(void); // what is letter of current YDreg |
| - | static void check_context(char); // remember context for '' command |
| - | #endif |
| - | #if ENABLE_FEATURE_VI_UNDO |
| - | static void flush_undo_data(void); |
| - | static void undo_push(char *, unsigned, unsigned char); // push an operation on the undo stack |
| - | static void undo_push_insert(char *, int, int); // convenience function |
| - | static void undo_pop(void); // undo the last operation |
| - | # if ENABLE_FEATURE_VI_UNDO_QUEUE |
| - | static void undo_queue_commit(void); // flush any queued objects to the undo stack |
| - | # else |
| - | # define undo_queue_commit() ((void)0) |
| - | # endif |
| - | #else |
| - | #define flush_undo_data() ((void)0) |
| - | #define undo_queue_commit() ((void)0) |
| - | #endif |
| #if ENABLE_FEATURE_VI_CRASHME | |
| static void crash_dummy(); | |
| @@ -645,37 +530,6 @@ static void write1(const char *out) | |
| fputs(out, stdout); | |
| } | |
| - | /* read text from file or create an empty buf */ |
| - | /* will also update current_filename */ |
| - | static int init_text_buffer(char *fn) |
| - | { |
| - | int rc; |
| - | |
| - | /* allocate/reallocate text buffer */ |
| - | free(text); |
| - | text_size = 10240; |
| - | screenbegin = dot = end = text = xzalloc(text_size); |
| - | |
| - | if (fn != current_filename) { |
| - | free(current_filename); |
| - | current_filename = xstrdup(fn); |
| - | } |
| - | rc = file_insert(fn, text, 1); |
| - | if (rc < 0) { |
| - | // file doesnt exist. Start empty buf with dummy line |
| - | char_insert(text, '\n', NO_UNDO); |
| - | } |
| - | |
| - | flush_undo_data(); |
| - | modified_count = 0; |
| - | last_modified_count = -1; |
| - | #if ENABLE_FEATURE_VI_YANKMARK |
| - | /* init the marks */ |
| - | memset(mark, 0, sizeof(mark)); |
| - | #endif |
| - | return rc; |
| - | } |
| - | |
| #if ENABLE_FEATURE_VI_WIN_RESIZE | |
| static int query_screen_dimensions(void) | |
| { | |
| @@ -969,654 +823,288 @@ static NOINLINE void sync_cursor(char *d, int *row, int *col) | |
| *col = co; | |
| } | |
| - | //----- The Colon commands ------------------------------------- |
| - | #if ENABLE_FEATURE_VI_COLON |
| - | static char *get_one_address(char *p, int *addr) // get colon addr, if present |
| + | //----- Format a text[] line into a buffer --------------------- |
| + | static char* format_line(char *src /*, int li*/) |
| { | |
| - | int st; |
| - | char *q; |
| - | IF_FEATURE_VI_YANKMARK(char c;) |
| - | IF_FEATURE_VI_SEARCH(char *pat;) |
| + | unsigned char c; |
| + | int co; |
| + | int ofs = offset; |
| + | char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2] |
| - | *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); |
| + | c = '~'; // char in col 0 in non-existent lines is '~' |
| + | co = 0; |
| + | while (co < columns + tabstop) { |
| + | // have we gone past the end? |
| + | if (src < end) { |
| + | c = *src++; |
| + | if (c == '\n') |
| + | break; |
| + | if ((c & 0x80) && !Isprint(c)) { |
| + | c = '.'; |
| + | } |
| + | if (c < ' ' || c == 0x7f) { |
| + | if (c == '\t') { |
| + | c = ' '; |
| + | // co % 8 != 7 |
| + | while ((co % tabstop) != (tabstop - 1)) { |
| + | dest[co++] = c; |
| + | } |
| + | } else { |
| + | dest[co++] = '^'; |
| + | if (c == 0x7f) |
| + | c = '?'; |
| + | else |
| + | c += '@'; // Ctrl-X -> 'X' |
| + | } |
| } | |
| } | |
| - | } |
| - | #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); |
| + | dest[co++] = c; |
| + | // discard scrolled-off-to-the-left portion, |
| + | // in tabstop-sized pieces |
| + | if (ofs >= tabstop && co >= tabstop) { |
| + | memmove(dest, dest + tabstop, co); |
| + | co -= tabstop; |
| + | ofs -= tabstop; |
| } | |
| - | 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 (src >= end) |
| + | break; |
| } | |
| - | return p; |
| + | // check "short line, gigantic offset" case |
| + | if (co < ofs) |
| + | ofs = co; |
| + | // discard last scrolled off part |
| + | co -= ofs; |
| + | dest += ofs; |
| + | // fill the rest with spaces |
| + | if (co < columns) |
| + | memset(&dest[co], ' ', columns - co); |
| + | return dest; |
| } | |
| - | static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present |
| + | //----- Refresh the changed screen lines ----------------------- |
| + | // Copy the source line from text[] into the buffer and note |
| + | // if the current screenline is different from the new buffer. |
| + | // If they differ then that line needs redrawing on the terminal. |
| + | // |
| + | static void refresh(int full_screen) |
| { | |
| - | //----- 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; |
| - | } |
| + | #define old_offset refresh__old_offset |
| - | #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 + ' ' |
| + | int li, changed; |
| + | char *tp, *sp; // pointer into text[] and screen[] |
| - | // 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; |
| - | } |
| - | } |
| + | if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) { |
| + | unsigned c = columns, r = rows; |
| + | query_screen_dimensions(); |
| + | #if ENABLE_FEATURE_VI_USE_SIGNALS |
| + | full_screen |= (c - columns) | (r - rows); |
| + | #else |
| + | if (c != columns || r != rows) { |
| + | full_screen = TRUE; |
| + | // update screen memory since SIGWINCH won't have done it |
| + | new_screen(rows, columns); |
| + | } |
| #endif | |
| + | } |
| + | sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot") |
| + | tp = screenbegin; // index into text[] of top line |
| - | #endif /* FEATURE_VI_COLON */ |
| + | // compare text[] to screen[] and mark screen[] lines that need updating |
| + | for (li = 0; li < rows - 1; li++) { |
| + | int cs, ce; // column start & end |
| + | char *out_buf; |
| + | // format current text line |
| + | out_buf = format_line(tp /*, li*/); |
| - | // 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; |
| + | // skip to the end of the current text[] line |
| + | if (tp < end) { |
| + | char *t = memchr(tp, '\n', end - tp); |
| + | if (!t) t = end - 1; |
| + | tp = t + 1; |
| + | } |
| - | 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; |
| + | // see if there are any changes between virtual screen and out_buf |
| + | changed = FALSE; // assume no change |
| + | cs = 0; |
| + | ce = columns - 1; |
| + | sp = &screen[li * columns]; // start of screen line |
| + | if (full_screen) { |
| + | // force re-draw of every single column from 0 - columns-1 |
| + | goto re0; |
| } | |
| - | 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); |
| + | // compare newly formatted buffer with virtual screen |
| + | // look forward for first difference between buf and screen |
| + | for (; cs <= ce; cs++) { |
| + | if (out_buf[cs] != sp[cs]) { |
| + | changed = TRUE; // mark for redraw |
| + | break; |
| + | } |
| } | |
| - | 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; |
| + | |
| + | // look backward for last difference between out_buf and screen |
| + | for (; ce >= cs; ce--) { |
| + | if (out_buf[ce] != sp[ce]) { |
| + | changed = TRUE; // mark for redraw |
| + | break; |
| } | |
| } | |
| - | 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 |
| + | // now, cs is index of first diff, and ce is index of last diff |
| - | 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 |
| + | // if horz offset has changed, force a redraw |
| + | if (offset != old_offset) { |
| + | re0: |
| + | changed = TRUE; |
| + | } |
| - | // :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" |
| - | // :!<cmd> // run <cmd> then return |
| - | // |
| + | // make a sanity check of columns indexes |
| + | if (cs < 0) cs = 0; |
| + | if (ce > columns - 1) ce = columns - 1; |
| + | if (cs > ce) { cs = 0; ce = columns - 1; } |
| + | // is there a change between virtual screen and out_buf |
| + | if (changed) { |
| + | // copy changed part of buffer to virtual screen |
| + | memcpy(sp+cs, out_buf+cs, ce-cs+1); |
| + | place_cursor(li, cs); |
| + | // write line out to terminal |
| + | fwrite(&sp[cs], ce - cs + 1, 1, stdout); |
| + | } |
| + | } |
| - | if (!buf[0]) |
| - | goto ret; |
| - | if (*buf == ':') |
| - | buf++; // move past the ':' |
| + | place_cursor(crow, ccol); |
| - | 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; |
| + | old_offset = offset; |
| + | #undef old_offset |
| + | } |
| - | // look for optional address(es) :. :1 :1,9 :'q,'a :% |
| - | buf = get_address(buf, &b, &e); |
| + | //----- Force refresh of all Lines ----------------------------- |
| + | static void redraw(int full_screen) |
| + | { |
| + | // cursor to top,left; clear to the end of screen |
| + | write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS); |
| + | screen_erase(); // erase the internal screen buffer |
| + | last_status_cksum = 0; // force status update |
| + | refresh(full_screen); // this will redraw the entire display |
| + | show_status_line(); |
| + | } |
| - | # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC |
| - | // remember orig command line |
| - | orig_buf = buf; |
| - | # endif |
| + | //----- Flash the screen -------------------------------------- |
| + | static void flash(int h) |
| + | { |
| + | standout_start(); |
| + | redraw(TRUE); |
| + | mysleep(h); |
| + | standout_end(); |
| + | redraw(TRUE); |
| + | } |
| - | // 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 <cmd> |
| - | 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 |
| + | static void indicate_error(void) |
| + | { |
| + | #if ENABLE_FEATURE_VI_CRASHME |
| + | if (crashme > 0) |
| + | return; |
| + | #endif |
| + | if (!err_method) { |
| + | write1(ESC_BELL); |
| + | } else { |
| + | flash(10); |
| } | |
| - | # 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; |
| - | } |
| + | //----- IO Routines -------------------------------------------- |
| + | static int readit(void) // read (maybe cursor) key from stdin |
| + | { |
| + | int c; |
| - | size = init_text_buffer(fn); |
| + | fflush_all(); |
| - | # 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); |
| - | } |
| + | // Wait for input. TIMEOUT = -1 makes read_key wait even |
| + | // on nonblocking stdin. |
| + | // Note: read_key sets errno to 0 on success. |
| + | again: |
| + | c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1); |
| + | if (c == -1) { // EOF/error |
| + | if (errno == EAGAIN) // paranoia |
| + | goto again; |
| go_bottom_and_clear_to_eol(); | |
| - | puts("\r"); |
| - | for (; q <= r; q++) { |
| - | int c_is_no_print; |
| + | cookmode(); // terminal to "cooked" |
| + | bb_error_msg_and_die("can't read user input"); |
| + | } |
| + | return c; |
| + | } |
| - | 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; |
| + | static int get_one_char(void) |
| + | { |
| + | int c; |
| - | 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" |
| - | } |
| - | // read after current line- unless user said ":0r foo" |
| - | if (b != 0) { |
| - | q = next_line(q); |
| - | // read after last line |
| - | if (q == end-1) |
| - | ++q; |
| - | } |
| - | { // dance around potentially-reallocated text[] |
| - | uintptr_t ofs = q - text; |
| - | size = file_insert(fn, q, 0); |
| - | q = text + ofs; |
| - | } |
| - | if (size < 0) |
| - | goto ret; // nothing was inserted |
| - | // how many lines in text[]? |
| - | li = count_lines(q, q + size - 1); |
| - | status_line("'%s'" |
| - | IF_FEATURE_VI_READONLY("%s") |
| - | " %dL, %dC", |
| - | fn, |
| - | IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),) |
| - | li, size |
| - | ); |
| - | if (size > 0) { |
| - | // if the insert is before "dot" then we need to update |
| - | if (q <= dot) |
| - | dot += size; |
| - | } |
| - | } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args |
| - | if (modified_count && !useforce) { |
| - | status_line_bold("No write since last change (:%s! overrides)", cmd); |
| + | #if ENABLE_FEATURE_VI_DOT_CMD |
| + | if (!adding2q) { |
| + | // we are not adding to the q. |
| + | // but, we may be reading from a q |
| + | if (ioq == 0) { |
| + | // there is no current q, read from STDIN |
| + | c = readit(); // get the users input |
| } else { | |
| - | // reset the filenames to edit |
| - | optind = -1; // start from 0th file |
| - | editing = 0; |
| - | } |
| - | # if ENABLE_FEATURE_VI_SET |
| - | } else if (strncmp(cmd, "set", i) == 0) { // set or clear features |
| - | # if ENABLE_FEATURE_VI_SETOPTS |
| - | char *argp; |
| - | # endif |
| - | i = 0; // offset into args |
| - | // only blank is regarded as args delimiter. What about tab '\t'? |
| - | if (!args[0] || strcasecmp(args, "all") == 0) { |
| - | // print out values of all options |
| - | # if ENABLE_FEATURE_VI_SETOPTS |
| - | status_line_bold( |
| - | "%sautoindent " |
| - | "%sflash " |
| - | "%signorecase " |
| - | "%sshowmatch " |
| - | "tabstop=%u", |
| - | autoindent ? "" : "no", |
| - | err_method ? "" : "no", |
| - | ignorecase ? "" : "no", |
| - | showmatch ? "" : "no", |
| - | tabstop |
| - | ); |
| - | # endif |
| - | goto ret; |
| - | } |
| - | # if ENABLE_FEATURE_VI_SETOPTS |
| - | argp = args; |
| - | while (*argp) { |
| - | if (strncmp(argp, "no", 2) == 0) |
| - | i = 2; // ":set noautoindent" |
| - | setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT); |
| - | setops(argp, "flash " , i, "fl", VI_ERR_METHOD); |
| - | setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE); |
| - | setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH ); |
| - | if (strncmp(argp + i, "tabstop=", 8) == 0) { |
| - | int t = 0; |
| - | sscanf(argp + i+8, "%u", &t); |
| - | if (t > 0 && t <= MAX_TABSTOP) |
| - | tabstop = t; |
| + | // there is a queue to get chars from first |
| + | // careful with correct sign expansion! |
| + | c = (unsigned char)*ioq++; |
| + | if (c == '\0') { |
| + | // the end of the q, read from STDIN |
| + | free(ioq_start); |
| + | ioq_start = ioq = 0; |
| + | c = readit(); // get the users input |
| } | |
| - | argp = skip_non_whitespace(argp); |
| - | argp = skip_whitespace(argp); |
| } | |
| - | # endif /* FEATURE_VI_SETOPTS */ |
| - | # endif /* FEATURE_VI_SET */ |
| + | } else { |
| + | // adding STDIN chars to q |
| + | c = readit(); // get the users input |
| + | if (lmc_len >= MAX_INPUT_LEN - 1) { |
| + | status_line_bold("last_modifying_cmd overrun"); |
| + | } else { |
| + | // add new char to q |
| + | last_modifying_cmd[lmc_len++] = c; |
| + | } |
| + | } |
| + | #else |
| + | c = readit(); // get the users input |
| + | #endif /* FEATURE_VI_DOT_CMD */ |
| + | return c; |
| + | } |
| - | # if ENABLE_FEATURE_VI_SEARCH |
| - | } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern |
| - | char *F, *R, *flags; |
| - | size_t len_F, len_R; |
| - | int gflag; // global replace flag |
| - | # if ENABLE_FEATURE_VI_UNDO |
| - | int dont_chain_first_item = ALLOW_UNDO; |
| - | # endif |
| + | // Get input line (uses "status line" area) |
| + | static char *get_input_line(const char *prompt) |
| + | { |
| + | // char [MAX_INPUT_LEN] |
| + | #define buf get_input_line__buf |
| - | // 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/ |
| + | int c; |
| + | int i; |
| - | 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; |
| + | strcpy(buf, prompt); |
| + | last_status_cksum = 0; // force status update |
| + | go_bottom_and_clear_to_eol(); |
| + | write1(prompt); // write out the :, /, or ? prompt |
| - | // 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); |
| + | i = strlen(buf); |
| + | while (i < MAX_INPUT_LEN) { |
| + | c = get_one_char(); |
| + | if (c == '\n' || c == '\r' || c == 27) |
| + | break; // this is end of input |
| + | if (c == erase_char || c == 8 || c == 127) { |
| + | // user wants to erase prev char |
| + | buf[--i] = '\0'; |
| + | write1("\b \b"); // erase char on screen |
| + | if (i <= 0) // user backs up before b-o-l, exit |
| + | break; |
| + | } else if (c > 0 && c < 256) { // exclude Unicode |
| + | // (TODO: need to handle Unicode) |
| + | buf[i] = c; |
| + | buf[++i] = '\0'; |
| + | bb_putchar(c); |
| } | |
| - | 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 */ |
| + | refresh(FALSE); |
| + | return buf; |
| + | #undef buf |
| } | |
| static void Hit_Return(void) | |
| @@ -1631,436 +1119,366 @@ static void Hit_Return(void) | |
| redraw(TRUE); // force redraw all | |
| } | |
| - | //----- Dot Movement Routines ---------------------------------- |
| - | static void dot_left(void) |
| + | //----- Draw the status line at bottom of the screen ------------- |
| + | // show file status on status line |
| + | static int format_edit_status(void) |
| { | |
| - | undo_queue_commit(); |
| - | if (dot > text && dot[-1] != '\n') |
| - | dot--; |
| - | } |
| + | static const char cmd_mode_indicator[] ALIGN1 = "-IR-"; |
| - | static void dot_right(void) |
| - | { |
| - | undo_queue_commit(); |
| - | if (dot < end - 1 && *dot != '\n') |
| - | dot++; |
| - | } |
| + | #define tot format_edit_status__tot |
| - | static void dot_begin(void) |
| - | { |
| - | undo_queue_commit(); |
| - | dot = begin_line(dot); // return pointer to first char cur line |
| - | } |
| + | int cur, percent, ret, trunc_at; |
| - | static void dot_end(void) |
| - | { |
| - | undo_queue_commit(); |
| - | dot = end_line(dot); // return pointer to last char cur line |
| - | } |
| + | // modified_count is now a counter rather than a flag. this |
| + | // helps reduce the amount of line counting we need to do. |
| + | // (this will cause a mis-reporting of modified status |
| + | // once every MAXINT editing operations.) |
| - | static char *move_to_col(char *p, int l) |
| - | { |
| - | int co; |
| + | // it would be nice to do a similar optimization here -- if |
| + | // we haven't done a motion that could have changed which line |
| + | // we're on, then we shouldn't have to do this count_lines() |
| + | cur = count_lines(text, dot); |
| - | p = begin_line(p); |
| - | co = 0; |
| - | while (co < l && p < end) { |
| - | if (*p == '\n') //vda || *p == '\0') |
| - | break; |
| - | if (*p == '\t') { |
| - | co = next_tabstop(co); |
| - | } else if (*p < ' ' || *p == 127) { |
| - | co++; // display as ^X, use 2 columns |
| - | } |
| - | co++; |
| - | p++; |
| + | // count_lines() is expensive. |
| + | // Call it only if something was changed since last time |
| + | // we were here: |
| + | if (modified_count != last_modified_count) { |
| + | tot = cur + count_lines(dot, end - 1) - 1; |
| + | last_modified_count = modified_count; |
| } | |
| - | return p; |
| - | } |
| - | static void dot_next(void) |
| - | { |
| - | undo_queue_commit(); |
| - | dot = next_line(dot); |
| + | // current line percent |
| + | // ------------- ~~ ---------- |
| + | // total lines 100 |
| + | if (tot > 0) { |
| + | percent = (100 * cur) / tot; |
| + | } else { |
| + | cur = tot = 0; |
| + | percent = 100; |
| + | } |
| + | |
| + | trunc_at = columns < STATUS_BUFFER_LEN-1 ? |
| + | columns : STATUS_BUFFER_LEN-1; |
| + | |
| + | ret = snprintf(status_buffer, trunc_at+1, |
| + | #if ENABLE_FEATURE_VI_READONLY |
| + | "%c %s%s%s %d/%d %d%%", |
| + | #else |
| + | "%c %s%s %d/%d %d%%", |
| + | #endif |
| + | cmd_mode_indicator[cmd_mode & 3], |
| + | (current_filename != NULL ? current_filename : "No file"), |
| + | #if ENABLE_FEATURE_VI_READONLY |
| + | (readonly_mode ? " [Readonly]" : ""), |
| + | #endif |
| + | (modified_count ? " [Modified]" : ""), |
| + | cur, tot, percent); |
| + | |
| + | if (ret >= 0 && ret < trunc_at) |
| + | return ret; // it all fit |
| + | |
| + | return trunc_at; // had to truncate |
| + | #undef tot |
| } | |
| - | static void dot_prev(void) |
| + | static int bufsum(char *buf, int count) |
| { | |
| - | undo_queue_commit(); |
| - | dot = prev_line(dot); |
| + | int sum = 0; |
| + | char *e = buf + count; |
| + | while (buf < e) |
| + | sum += (unsigned char) *buf++; |
| + | return sum; |
| } | |
| - | static void dot_scroll(int cnt, int dir) |
| + | static void show_status_line(void) |
| { | |
| - | char *q; |
| + | int cnt = 0, cksum = 0; |
| - | undo_queue_commit(); |
| - | for (; cnt > 0; cnt--) { |
| - | if (dir < 0) { |
| - | // scroll Backwards |
| - | // ctrl-Y scroll up one line |
| - | screenbegin = prev_line(screenbegin); |
| - | } else { |
| - | // scroll Forwards |
| - | // ctrl-E scroll down one line |
| - | screenbegin = next_line(screenbegin); |
| + | // either we already have an error or status message, or we |
| + | // create one. |
| + | if (!have_status_msg) { |
| + | cnt = format_edit_status(); |
| + | cksum = bufsum(status_buffer, cnt); |
| + | } |
| + | if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) { |
| + | last_status_cksum = cksum; // remember if we have seen this line |
| + | go_bottom_and_clear_to_eol(); |
| + | write1(status_buffer); |
| + | if (have_status_msg) { |
| + | if (((int)strlen(status_buffer) - (have_status_msg - 1)) > |
| + | (columns - 1) ) { |
| + | have_status_msg = 0; |
| + | Hit_Return(); |
| + | } |
| + | have_status_msg = 0; |
| } | |
| + | place_cursor(crow, ccol); // put cursor back in correct place |
| } | |
| - | // make sure "dot" stays on the screen so we dont scroll off |
| - | if (dot < screenbegin) |
| - | dot = screenbegin; |
| - | q = end_screen(); // find new bottom line |
| - | if (dot > q) |
| - | dot = begin_line(q); // is dot is below bottom line? |
| - | dot_skip_over_ws(); |
| + | fflush_all(); |
| } | |
| - | static void dot_skip_over_ws(void) |
| + | //----- format the status buffer, the bottom line of screen ------ |
| + | // format status buffer, with STANDOUT mode |
| + | static void status_line_bold(const char *format, ...) |
| { | |
| - | // skip WS |
| - | while (isspace(*dot) && *dot != '\n' && dot < end - 1) |
| - | dot++; |
| + | va_list args; |
| + | |
| + | va_start(args, format); |
| + | strcpy(status_buffer, ESC_BOLD_TEXT); |
| + | vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args); |
| + | strcat(status_buffer, ESC_NORM_TEXT); |
| + | va_end(args); |
| + | |
| + | have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2; |
| } | |
| - | static char *bound_dot(char *p) // make sure text[0] <= P < "end" |
| + | static void status_line_bold_errno(const char *fn) |
| { | |
| - | if (p >= end && end > text) { |
| - | p = end - 1; |
| - | indicate_error(); |
| - | } |
| - | if (p < text) { |
| - | p = text; |
| - | indicate_error(); |
| - | } |
| - | return p; |
| + | status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO); |
| } | |
| - | //----- Helper Utility Routines -------------------------------- |
| - | |
| - | //---------------------------------------------------------------- |
| - | //----- Char Routines -------------------------------------------- |
| - | /* Chars that are part of a word- |
| - | * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz |
| - | * Chars that are Not part of a word (stoppers) |
| - | * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~ |
| - | * Chars that are WhiteSpace |
| - | * TAB NEWLINE VT FF RETURN SPACE |
| - | * DO NOT COUNT NEWLINE AS WHITESPACE |
| - | */ |
| - | |
| - | static char *new_screen(int ro, int co) |
| + | // format status buffer |
| + | static void status_line(const char *format, ...) |
| { | |
| - | int li; |
| - | |
| - | free(screen); |
| - | screensize = ro * co + 8; |
| - | screen = xmalloc(screensize); |
| - | // initialize the new screen. assume this will be a empty file. |
| - | screen_erase(); |
| - | // non-existent text[] lines start with a tilde (~). |
| - | for (li = 1; li < ro - 1; li++) { |
| - | screen[(li * co) + 0] = '~'; |
| - | } |
| - | return screen; |
| - | } |
| + | va_list args; |
| - | #if ENABLE_FEATURE_VI_SEARCH |
| + | va_start(args, format); |
| + | vsprintf(status_buffer, format, args); |
| + | va_end(args); |
| - | # if ENABLE_FEATURE_VI_REGEX_SEARCH |
| + | have_status_msg = 1; |
| + | } |
| - | // search for pattern starting at p |
| - | static char *char_search(char *p, const char *pat, int dir_and_range) |
| + | // copy s to buf, convert unprintable |
| + | static void print_literal(char *buf, const char *s) |
| { | |
| - | struct re_pattern_buffer preg; |
| - | const char *err; |
| - | char *q; |
| - | int i; |
| - | int size; |
| - | int range; |
| - | |
| - | re_syntax_options = RE_SYNTAX_POSIX_EXTENDED; |
| - | if (ignorecase) |
| - | re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE; |
| + | char *d; |
| + | unsigned char c; |
| - | memset(&preg, 0, sizeof(preg)); |
| - | err = re_compile_pattern(pat, strlen(pat), &preg); |
| - | if (err != NULL) { |
| - | status_line_bold("bad search pattern '%s': %s", pat, err); |
| - | return p; |
| - | } |
| + | buf[0] = '\0'; |
| + | if (!s[0]) |
| + | s = "(NULL)"; |
| - | range = (dir_and_range & 1); |
| - | q = end - 1; // if FULL |
| - | if (range == LIMITED) |
| - | q = next_line(p); |
| - | if (dir_and_range < 0) { // BACK? |
| - | q = text; |
| - | if (range == LIMITED) |
| - | q = prev_line(p); |
| - | } |
| + | d = buf; |
| + | for (; *s; s++) { |
| + | int c_is_no_print; |
| - | // RANGE could be negative if we are searching backwards |
| - | range = q - p; |
| - | q = p; |
| - | size = range; |
| - | if (range < 0) { |
| - | size = -size; |
| - | q = p - size; |
| - | if (q < text) |
| - | q = text; |
| + | c = *s; |
| + | c_is_no_print = (c & 0x80) && !Isprint(c); |
| + | if (c_is_no_print) { |
| + | strcpy(d, ESC_NORM_TEXT); |
| + | d += sizeof(ESC_NORM_TEXT)-1; |
| + | c = '.'; |
| + | } |
| + | if (c < ' ' || c == 0x7f) { |
| + | *d++ = '^'; |
| + | c |= '@'; // 0x40 |
| + | if (c == 0x7f) |
| + | c = '?'; |
| + | } |
| + | *d++ = c; |
| + | *d = '\0'; |
| + | if (c_is_no_print) { |
| + | strcpy(d, ESC_BOLD_TEXT); |
| + | d += sizeof(ESC_BOLD_TEXT)-1; |
| + | } |
| + | if (*s == '\n') { |
| + | *d++ = '$'; |
| + | *d = '\0'; |
| + | } |
| + | if (d - buf > MAX_INPUT_LEN - 10) // paranoia |
| + | break; |
| } | |
| - | // search for the compiled pattern, preg, in p[] |
| - | // range < 0: search backward |
| - | // range > 0: search forward |
| - | // 0 < start < size |
| - | // re_search() < 0: not found or error |
| - | // re_search() >= 0: index of found pattern |
| - | // struct pattern char int int int struct reg |
| - | // re_search(*pattern_buffer, *string, size, start, range, *regs) |
| - | i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL); |
| - | regfree(&preg); |
| - | if (i < 0) |
| - | return NULL; |
| - | if (dir_and_range > 0) // FORWARD? |
| - | p = p + i; |
| - | else |
| - | p = p - i; |
| - | return p; |
| } | |
| - | # else |
| - | |
| - | # if ENABLE_FEATURE_VI_SETOPTS |
| - | static int mycmp(const char *s1, const char *s2, int len) |
| + | static void not_implemented(const char *s) |
| { | |
| - | if (ignorecase) { |
| - | return strncasecmp(s1, s2, len); |
| - | } |
| - | return strncmp(s1, s2, len); |
| + | char buf[MAX_INPUT_LEN]; |
| + | |
| + | print_literal(buf, s); |
| + | status_line_bold("\'%s\' is not implemented", buf); |
| } | |
| - | # else |
| - | # define mycmp strncmp |
| - | # endif |
| - | static char *char_search(char *p, const char *pat, int dir_and_range) |
| + | #if ENABLE_FEATURE_VI_YANKMARK |
| + | static char *text_yank(char *p, char *q, int dest) // copy text into a register |
| { | |
| - | char *start, *stop; |
| - | int len; |
| - | int range; |
| - | |
| - | len = strlen(pat); |
| - | range = (dir_and_range & 1); |
| - | if (dir_and_range > 0) { //FORWARD? |
| - | stop = end - 1; // assume range is p..end-1 |
| - | if (range == LIMITED) |
| - | stop = next_line(p); // range is to next line |
| - | for (start = p; start < stop; start++) { |
| - | if (mycmp(start, pat, len) == 0) { |
| - | return start; |
| - | } |
| - | } |
| - | } else { //BACK |
| - | stop = text; // assume range is text..p |
| - | if (range == LIMITED) |
| - | stop = prev_line(p); // range is to prev line |
| - | for (start = p - len; start >= stop; start--) { |
| - | if (mycmp(start, pat, len) == 0) { |
| - | return start; |
| - | } |
| - | } |
| + | int cnt = q - p; |
| + | if (cnt < 0) { // they are backwards- reverse them |
| + | p = q; |
| + | cnt = -cnt; |
| } | |
| - | // pattern not found |
| - | return NULL; |
| + | free(reg[dest]); // if already a yank register, free it |
| + | reg[dest] = xstrndup(p, cnt + 1); |
| + | return p; |
| } | |
| - | # endif |
| + | static char what_reg(void) |
| + | { |
| + | char c; |
| - | #endif /* FEATURE_VI_SEARCH */ |
| + | c = 'D'; // default to D-reg |
| + | if (0 <= YDreg && YDreg <= 25) |
| + | c = 'a' + (char) YDreg; |
| + | if (YDreg == 26) |
| + | c = 'D'; |
| + | if (YDreg == 27) |
| + | c = 'U'; |
| + | return c; |
| + | } |
| - | static char *char_insert(char *p, char c, int undo) // insert the char c at 'p' |
| + | static void check_context(char cmd) |
| { | |
| - | if (c == 22) { // Is this an ctrl-V? |
| - | p += stupid_insert(p, '^'); // use ^ to indicate literal next |
| - | refresh(FALSE); // show the ^ |
| - | c = get_one_char(); |
| - | *p = c; |
| - | #if ENABLE_FEATURE_VI_UNDO |
| - | undo_push_insert(p, 1, undo); |
| - | #else |
| - | modified_count++; |
| - | #endif /* ENABLE_FEATURE_VI_UNDO */ |
| - | p++; |
| - | } else if (c == 27) { // Is this an ESC? |
| - | cmd_mode = 0; |
| - | undo_queue_commit(); |
| - | cmdcnt = 0; |
| - | end_cmd_q(); // stop adding to q |
| - | last_status_cksum = 0; // force status update |
| - | if ((p[-1] != '\n') && (dot > text)) { |
| - | p--; |
| - | } |
| - | } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS |
| - | if (p > text) { |
| - | p--; |
| - | p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char |
| - | } |
| - | } else { |
| - | // insert a char into text[] |
| - | if (c == 13) |
| - | c = '\n'; // translate \r to \n |
| - | #if ENABLE_FEATURE_VI_UNDO |
| - | # if ENABLE_FEATURE_VI_UNDO_QUEUE |
| - | if (c == '\n') |
| - | undo_queue_commit(); |
| - | # endif |
| - | undo_push_insert(p, 1, undo); |
| - | #else |
| - | modified_count++; |
| - | #endif /* ENABLE_FEATURE_VI_UNDO */ |
| - | p += 1 + stupid_insert(p, c); // insert the char |
| - | #if ENABLE_FEATURE_VI_SETOPTS |
| - | if (showmatch && strchr(")]}", c) != NULL) { |
| - | showmatching(p - 1); |
| - | } |
| - | if (autoindent && c == '\n') { // auto indent the new line |
| - | char *q; |
| - | size_t len; |
| - | q = prev_line(p); // use prev line as template |
| - | len = strspn(q, " \t"); // space or tab |
| - | if (len) { |
| - | uintptr_t bias; |
| - | bias = text_hole_make(p, len); |
| - | p += bias; |
| - | q += bias; |
| - | #if ENABLE_FEATURE_VI_UNDO |
| - | undo_push_insert(p, len, undo); |
| - | #endif |
| - | memcpy(p, q, len); |
| - | p += len; |
| - | } |
| + | // A context is defined to be "modifying text" |
| + | // Any modifying command establishes a new context. |
| + | |
| + | if (dot < context_start || dot > context_end) { |
| + | if (strchr(modifying_cmds, cmd) != NULL) { |
| + | // we are trying to modify text[]- make this the current context |
| + | mark[27] = mark[26]; // move cur to prev |
| + | mark[26] = dot; // move local to cur |
| + | context_start = prev_line(prev_line(dot)); |
| + | context_end = next_line(next_line(dot)); |
| + | //loiter= start_loiter= now; |
| } | |
| - | #endif |
| } | |
| - | return p; |
| } | |
| - | // might reallocate text[]! use p += stupid_insert(p, ...), |
| - | // and be careful to not use pointers into potentially freed text[]! |
| - | static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p' |
| + | static char *swap_context(char *p) // goto new context for '' command make this the current context |
| { | |
| - | uintptr_t bias; |
| - | bias = text_hole_make(p, 1); |
| - | p += bias; |
| - | *p = c; |
| - | return bias; |
| + | char *tmp; |
| + | |
| + | // the current context is in mark[26] |
| + | // the previous context is in mark[27] |
| + | // only swap context if other context is valid |
| + | if (text <= mark[27] && mark[27] <= end - 1) { |
| + | tmp = mark[27]; |
| + | mark[27] = p; |
| + | mark[26] = p = tmp; |
| + | context_start = prev_line(prev_line(prev_line(p))); |
| + | context_end = next_line(next_line(next_line(p))); |
| + | } |
| + | return p; |
| } | |
| + | #endif /* FEATURE_VI_YANKMARK */ |
| - | static int st_test(char *p, int type, int dir, char *tested) |
| - | { |
| - | char c, c0, ci; |
| - | int test, inc; |
| + | #if ENABLE_FEATURE_VI_UNDO |
| + | static void undo_push(char *, unsigned, unsigned char); |
| + | #endif |
| - | inc = dir; |
| - | c = c0 = p[0]; |
| - | ci = p[inc]; |
| - | test = 0; |
| + | // open a hole in text[] |
| + | // might reallocate text[]! use p += text_hole_make(p, ...), |
| + | // and be careful to not use pointers into potentially freed text[]! |
| + | static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole |
| + | { |
| + | uintptr_t bias = 0; |
| - | if (type == S_BEFORE_WS) { |
| - | c = ci; |
| - | test = (!isspace(c) || c == '\n'); |
| - | } |
| - | if (type == S_TO_WS) { |
| - | c = c0; |
| - | test = (!isspace(c) || c == '\n'); |
| - | } |
| - | if (type == S_OVER_WS) { |
| - | c = c0; |
| - | test = isspace(c); |
| - | } |
| - | if (type == S_END_PUNCT) { |
| - | c = ci; |
| - | test = ispunct(c); |
| - | } |
| - | if (type == S_END_ALNUM) { |
| - | c = ci; |
| - | test = (isalnum(c) || c == '_'); |
| + | if (size <= 0) |
| + | return bias; |
| + | end += size; // adjust the new END |
| + | if (end >= (text + text_size)) { |
| + | char *new_text; |
| + | text_size += end - (text + text_size) + 10240; |
| + | new_text = xrealloc(text, text_size); |
| + | bias = (new_text - text); |
| + | screenbegin += bias; |
| + | dot += bias; |
| + | end += bias; |
| + | p += bias; |
| + | #if ENABLE_FEATURE_VI_YANKMARK |
| + | { |
| + | int i; |
| + | for (i = 0; i < ARRAY_SIZE(mark); i++) |
| + | if (mark[i]) |
| + | mark[i] += bias; |
| + | } |
| + | #endif |
| + | text = new_text; |
| } | |
| - | *tested = c; |
| - | return test; |
| + | memmove(p + size, p, end - size - p); |
| + | memset(p, ' ', size); // clear new hole |
| + | return bias; |
| } | |
| - | static char *skip_thing(char *p, int linecnt, int dir, int type) |
| + | // close a hole in text[] - delete "p" through "q", inclusive |
| + | // "undo" value indicates if this operation should be undo-able |
| + | #if !ENABLE_FEATURE_VI_UNDO |
| + | #define text_hole_delete(a,b,c) text_hole_delete(a,b) |
| + | #endif |
| + | static char *text_hole_delete(char *p, char *q, int undo) |
| { | |
| - | char c; |
| + | char *src, *dest; |
| + | int cnt, hole_size; |
| - | while (st_test(p, type, dir, &c)) { |
| - | // make sure we limit search to correct number of lines |
| - | if (c == '\n' && --linecnt < 1) |
| + | // move forwards, from beginning |
| + | // assume p <= q |
| + | src = q + 1; |
| + | dest = p; |
| + | if (q < p) { // they are backward- swap them |
| + | src = p + 1; |
| + | dest = q; |
| + | } |
| + | hole_size = q - p + 1; |
| + | cnt = end - src; |
| + | #if ENABLE_FEATURE_VI_UNDO |
| + | switch (undo) { |
| + | case NO_UNDO: |
| break; | |
| - | if (dir >= 0 && p >= end - 1) |
| + | case ALLOW_UNDO: |
| + | undo_push(p, hole_size, UNDO_DEL); |
| break; | |
| - | if (dir < 0 && p <= text) |
| + | case ALLOW_UNDO_CHAIN: |
| + | undo_push(p, hole_size, UNDO_DEL_CHAIN); |
| break; | |
| - | p += dir; // move to next char |
| + | # if ENABLE_FEATURE_VI_UNDO_QUEUE |
| + | case ALLOW_UNDO_QUEUED: |
| + | undo_push(p, hole_size, UNDO_DEL_QUEUED); |
| + | break; |
| + | # endif |
| } | |
| - | return p; |
| + | modified_count--; |
| + | #endif |
| + | if (src < text || src > end) |
| + | goto thd0; |
| + | if (dest < text || dest >= end) |
| + | goto thd0; |
| + | modified_count++; |
| + | if (src >= end) |
| + | goto thd_atend; // just delete the end of the buffer |
| + | memmove(dest, src, cnt); |
| + | thd_atend: |
| + | end = end - hole_size; // adjust the new END |
| + | if (dest >= end) |
| + | dest = end - 1; // make sure dest in below end-1 |
| + | if (end <= text) |
| + | dest = end = text; // keep pointers valid |
| + | thd0: |
| + | return dest; |
| } | |
| - | // find matching char of pair () [] {} |
| - | // will crash if c is not one of these |
| - | static char *find_pair(char *p, const char c) |
| - | { |
| - | const char *braces = "()[]{}"; |
| - | char match; |
| - | int dir, level; |
| - | |
| - | dir = strchr(braces, c) - braces; |
| - | dir ^= 1; |
| - | match = braces[dir]; |
| - | dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\} |
| + | #if ENABLE_FEATURE_VI_UNDO |
| - | // look for match, count levels of pairs (( )) |
| - | level = 1; |
| - | for (;;) { |
| - | p += dir; |
| - | if (p < text || p >= end) |
| - | return NULL; |
| - | if (*p == c) |
| - | level++; // increase pair levels |
| - | if (*p == match) { |
| - | level--; // reduce pair level |
| - | if (level == 0) |
| - | return p; // found matching pair |
| - | } |
| + | # if ENABLE_FEATURE_VI_UNDO_QUEUE |
| + | // Flush any queued objects to the undo stack |
| + | static void undo_queue_commit(void) |
| + | { |
| + | // Pushes the queue object onto the undo stack |
| + | if (undo_q > 0) { |
| + | // Deleted character undo events grow from the end |
| + | undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q, |
| + | undo_q, |
| + | (undo_queue_state | UNDO_USE_SPOS) |
| + | ); |
| + | undo_queue_state = UNDO_EMPTY; |
| + | undo_q = 0; |
| } | |
| } | |
| + | # else |
| + | # define undo_queue_commit() ((void)0) |
| + | # endif |
| - | #if ENABLE_FEATURE_VI_SETOPTS |
| - | // show the matching char of a pair, () [] {} |
| - | static void showmatching(char *p) |
| + | static void flush_undo_data(void) |
| { | |
| - | char *q, *save_dot; |
| - | |
| - | // we found half of a pair |
| - | q = find_pair(p, *p); // get loc of matching char |
| - | if (q == NULL) { |
| - | indicate_error(); // no matching char |
| - | } else { |
| - | // "q" now points to matching pair |
| - | save_dot = dot; // remember where we are |
| - | dot = q; // go to new loc |
| - | refresh(FALSE); // let the user see it |
| - | mysleep(40); // give user some time |
| - | dot = save_dot; // go back to old loc |
| - | refresh(FALSE); |
| - | } |
| - | } |
| - | #endif /* FEATURE_VI_SETOPTS */ |
| - | |
| - | #if ENABLE_FEATURE_VI_UNDO |
| - | static void flush_undo_data(void) |
| - | { |
| - | struct undo_object *undo_entry; |
| + | struct undo_object *undo_entry; |
| while (undo_stack_tail) { | |
| undo_entry = undo_stack_tail; | |
| @@ -2085,7 +1503,7 @@ static void undo_push(char *src, unsigned length, uint8_t u_type) | |
| // for the INS/DEL operation. The raw values should be equal to the values of | |
| // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG | |
| - | #if ENABLE_FEATURE_VI_UNDO_QUEUE |
| + | # if ENABLE_FEATURE_VI_UNDO_QUEUE |
| // This undo queuing functionality groups multiple character typing or backspaces | |
| // into a single large undo object. This greatly reduces calls to malloc() for | |
| // single-character operations while typing and has the side benefit of letting | |
| @@ -2136,10 +1554,10 @@ static void undo_push(char *src, unsigned length, uint8_t u_type) | |
| } | |
| break; | |
| } | |
| - | #else |
| + | # else |
| // If undo queuing is disabled, ignore the queuing flag entirely | |
| u_type = u_type & ~UNDO_QUEUED_FLAG; | |
| - | #endif |
| + | # endif |
| // Allocate a new undo object | |
| if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) { | |
| @@ -2154,16 +1572,16 @@ static void undo_push(char *src, unsigned length, uint8_t u_type) | |
| undo_entry = xzalloc(sizeof(*undo_entry)); | |
| } | |
| undo_entry->length = length; | |
| - | #if ENABLE_FEATURE_VI_UNDO_QUEUE |
| + | # if ENABLE_FEATURE_VI_UNDO_QUEUE |
| if ((u_type & UNDO_USE_SPOS) != 0) { | |
| undo_entry->start = undo_queue_spos - text; // use start position from queue | |
| } else { | |
| undo_entry->start = src - text; // use offset from start of text buffer | |
| } | |
| u_type = (u_type & ~UNDO_USE_SPOS); | |
| - | #else |
| + | # else |
| undo_entry->start = src - text; | |
| - | #endif |
| + | # endif |
| undo_entry->u_type = u_type; | |
| // Push it on undo stack | |
| @@ -2253,115 +1671,145 @@ static void undo_pop(void) | |
| } | |
| } | |
| - | #if ENABLE_FEATURE_VI_UNDO_QUEUE |
| - | // Flush any queued objects to the undo stack |
| - | static void undo_queue_commit(void) |
| + | #else |
| + | # define flush_undo_data() ((void)0) |
| + | # define undo_queue_commit() ((void)0) |
| + | #endif /* ENABLE_FEATURE_VI_UNDO */ |
| + | |
| + | //----- Dot Movement Routines ---------------------------------- |
| + | static void dot_left(void) |
| { | |
| - | // Pushes the queue object onto the undo stack |
| - | if (undo_q > 0) { |
| - | // Deleted character undo events grow from the end |
| - | undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q, |
| - | undo_q, |
| - | (undo_queue_state | UNDO_USE_SPOS) |
| - | ); |
| - | undo_queue_state = UNDO_EMPTY; |
| - | undo_q = 0; |
| + | undo_queue_commit(); |
| + | if (dot > text && dot[-1] != '\n') |
| + | dot--; |
| + | } |
| + | |
| + | static void dot_right(void) |
| + | { |
| + | undo_queue_commit(); |
| + | if (dot < end - 1 && *dot != '\n') |
| + | dot++; |
| + | } |
| + | |
| + | static void dot_begin(void) |
| + | { |
| + | undo_queue_commit(); |
| + | dot = begin_line(dot); // return pointer to first char cur line |
| + | } |
| + | |
| + | static void dot_end(void) |
| + | { |
| + | undo_queue_commit(); |
| + | dot = end_line(dot); // return pointer to last char cur line |
| + | } |
| + | |
| + | static char *move_to_col(char *p, int l) |
| + | { |
| + | int co; |
| + | |
| + | p = begin_line(p); |
| + | co = 0; |
| + | while (co < l && p < end) { |
| + | if (*p == '\n') //vda || *p == '\0') |
| + | break; |
| + | if (*p == '\t') { |
| + | co = next_tabstop(co); |
| + | } else if (*p < ' ' || *p == 127) { |
| + | co++; // display as ^X, use 2 columns |
| + | } |
| + | co++; |
| + | p++; |
| } | |
| + | return p; |
| } | |
| - | #endif |
| - | #endif /* ENABLE_FEATURE_VI_UNDO */ |
| + | static void dot_next(void) |
| + | { |
| + | undo_queue_commit(); |
| + | dot = next_line(dot); |
| + | } |
| - | // open a hole in text[] |
| - | // might reallocate text[]! use p += text_hole_make(p, ...), |
| - | // and be careful to not use pointers into potentially freed text[]! |
| - | static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole |
| + | static void dot_prev(void) |
| { | |
| - | uintptr_t bias = 0; |
| + | undo_queue_commit(); |
| + | dot = prev_line(dot); |
| + | } |
| - | if (size <= 0) |
| - | return bias; |
| - | end += size; // adjust the new END |
| - | if (end >= (text + text_size)) { |
| - | char *new_text; |
| - | text_size += end - (text + text_size) + 10240; |
| - | new_text = xrealloc(text, text_size); |
| - | bias = (new_text - text); |
| - | screenbegin += bias; |
| - | dot += bias; |
| - | end += bias; |
| - | p += bias; |
| - | #if ENABLE_FEATURE_VI_YANKMARK |
| - | { |
| - | int i; |
| - | for (i = 0; i < ARRAY_SIZE(mark); i++) |
| - | if (mark[i]) |
| - | mark[i] += bias; |
| + | static void dot_skip_over_ws(void) |
| + | { |
| + | // skip WS |
| + | while (isspace(*dot) && *dot != '\n' && dot < end - 1) |
| + | dot++; |
| + | } |
| + | |
| + | static void dot_scroll(int cnt, int dir) |
| + | { |
| + | char *q; |
| + | |
| + | undo_queue_commit(); |
| + | for (; cnt > 0; cnt--) { |
| + | if (dir < 0) { |
| + | // scroll Backwards |
| + | // ctrl-Y scroll up one line |
| + | screenbegin = prev_line(screenbegin); |
| + | } else { |
| + | // scroll Forwards |
| + | // ctrl-E scroll down one line |
| + | screenbegin = next_line(screenbegin); |
| } | |
| - | #endif |
| - | text = new_text; |
| } | |
| - | memmove(p + size, p, end - size - p); |
| - | memset(p, ' ', size); // clear new hole |
| - | return bias; |
| + | // make sure "dot" stays on the screen so we dont scroll off |
| + | if (dot < screenbegin) |
| + | dot = screenbegin; |
| + | q = end_screen(); // find new bottom line |
| + | if (dot > q) |
| + | dot = begin_line(q); // is dot is below bottom line? |
| + | dot_skip_over_ws(); |
| } | |
| - | // close a hole in text[] |
| - | // "undo" value indicates if this operation should be undo-able |
| - | static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive |
| + | static char *bound_dot(char *p) // make sure text[0] <= P < "end" |
| { | |
| - | char *src, *dest; |
| - | int cnt, hole_size; |
| + | if (p >= end && end > text) { |
| + | p = end - 1; |
| + | indicate_error(); |
| + | } |
| + | if (p < text) { |
| + | p = text; |
| + | indicate_error(); |
| + | } |
| + | return p; |
| + | } |
| - | // move forwards, from beginning |
| - | // assume p <= q |
| - | src = q + 1; |
| - | dest = p; |
| - | if (q < p) { // they are backward- swap them |
| - | src = p + 1; |
| - | dest = q; |
| + | #if ENABLE_FEATURE_VI_DOT_CMD |
| + | static void start_new_cmd_q(char c) |
| + | { |
| + | // get buffer for new cmd |
| + | // if there is a current cmd count put it in the buffer first |
| + | if (cmdcnt > 0) { |
| + | lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c); |
| + | } else { // just save char c onto queue |
| + | last_modifying_cmd[0] = c; |
| + | lmc_len = 1; |
| } | |
| - | hole_size = q - p + 1; |
| - | cnt = end - src; |
| - | #if ENABLE_FEATURE_VI_UNDO |
| - | switch (undo) { |
| - | case NO_UNDO: |
| - | break; |
| - | case ALLOW_UNDO: |
| - | undo_push(p, hole_size, UNDO_DEL); |
| - | break; |
| - | case ALLOW_UNDO_CHAIN: |
| - | undo_push(p, hole_size, UNDO_DEL_CHAIN); |
| - | break; |
| - | # if ENABLE_FEATURE_VI_UNDO_QUEUE |
| - | case ALLOW_UNDO_QUEUED: |
| - | undo_push(p, hole_size, UNDO_DEL_QUEUED); |
| - | break; |
| + | adding2q = 1; |
| + | } |
| + | static void end_cmd_q(void) |
| + | { |
| + | # if ENABLE_FEATURE_VI_YANKMARK |
| + | YDreg = 26; // go back to default Yank/Delete reg |
| # endif | |
| - | } |
| - | modified_count--; |
| - | #endif |
| - | if (src < text || src > end) |
| - | goto thd0; |
| - | if (dest < text || dest >= end) |
| - | goto thd0; |
| - | modified_count++; |
| - | if (src >= end) |
| - | goto thd_atend; // just delete the end of the buffer |
| - | memmove(dest, src, cnt); |
| - | thd_atend: |
| - | end = end - hole_size; // adjust the new END |
| - | if (dest >= end) |
| - | dest = end - 1; // make sure dest in below end-1 |
| - | if (end <= text) |
| - | dest = end = text; // keep pointers valid |
| - | thd0: |
| - | return dest; |
| + | adding2q = 0; |
| } | |
| + | #else |
| + | # define end_cmd_q() ((void)0) |
| + | #endif /* FEATURE_VI_DOT_CMD */ |
| // copy text into register, then delete text. | |
| // if dist <= 0, do not include, or go past, a NewLine | |
| // | |
| + | #if !ENABLE_FEATURE_VI_UNDO |
| + | #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d) |
| + | #endif |
| static char *yank_delete(char *start, char *stop, int dist, int yf, int undo) | |
| { | |
| char *p; | |
| @@ -2396,226 +1844,12 @@ static char *yank_delete(char *start, char *stop, int dist, int yf, int undo) | |
| return p; | |
| } | |
| - | #if ENABLE_FEATURE_VI_DOT_CMD |
| - | static void start_new_cmd_q(char c) |
| + | // might reallocate text[]! |
| + | static int file_insert(const char *fn, char *p, int initial) |
| { | |
| - | // get buffer for new cmd |
| - | // if there is a current cmd count put it in the buffer first |
| - | if (cmdcnt > 0) { |
| - | lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c); |
| - | } else { // just save char c onto queue |
| - | last_modifying_cmd[0] = c; |
| - | lmc_len = 1; |
| - | } |
| - | adding2q = 1; |
| - | } |
| - | |
| - | static void end_cmd_q(void) |
| - | { |
| - | #if ENABLE_FEATURE_VI_YANKMARK |
| - | YDreg = 26; // go back to default Yank/Delete reg |
| - | #endif |
| - | adding2q = 0; |
| - | } |
| - | #endif /* FEATURE_VI_DOT_CMD */ |
| - | |
| - | #if ENABLE_FEATURE_VI_YANKMARK \ |
| - | || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \ |
| - | || ENABLE_FEATURE_VI_CRASHME |
| - | // might reallocate text[]! use p += string_insert(p, ...), |
| - | // and be careful to not use pointers into potentially freed text[]! |
| - | static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p' |
| - | { |
| - | uintptr_t bias; |
| - | int i; |
| - | |
| - | i = strlen(s); |
| - | #if ENABLE_FEATURE_VI_UNDO |
| - | undo_push_insert(p, i, undo); |
| - | #endif |
| - | bias = text_hole_make(p, i); |
| - | p += bias; |
| - | memcpy(p, s, i); |
| - | #if ENABLE_FEATURE_VI_YANKMARK |
| - | { |
| - | int cnt; |
| - | for (cnt = 0; *s != '\0'; s++) { |
| - | if (*s == '\n') |
| - | cnt++; |
| - | } |
| - | status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg()); |
| - | } |
| - | #endif |
| - | return bias; |
| - | } |
| - | #endif |
| - | |
| - | #if ENABLE_FEATURE_VI_YANKMARK |
| - | static char *text_yank(char *p, char *q, int dest) // copy text into a register |
| - | { |
| - | int cnt = q - p; |
| - | if (cnt < 0) { // they are backwards- reverse them |
| - | p = q; |
| - | cnt = -cnt; |
| - | } |
| - | free(reg[dest]); // if already a yank register, free it |
| - | reg[dest] = xstrndup(p, cnt + 1); |
| - | return p; |
| - | } |
| - | |
| - | static char what_reg(void) |
| - | { |
| - | char c; |
| - | |
| - | c = 'D'; // default to D-reg |
| - | if (0 <= YDreg && YDreg <= 25) |
| - | c = 'a' + (char) YDreg; |
| - | if (YDreg == 26) |
| - | c = 'D'; |
| - | if (YDreg == 27) |
| - | c = 'U'; |
| - | return c; |
| - | } |
| - | |
| - | static void check_context(char cmd) |
| - | { |
| - | // A context is defined to be "modifying text" |
| - | // Any modifying command establishes a new context. |
| - | |
| - | if (dot < context_start || dot > context_end) { |
| - | if (strchr(modifying_cmds, cmd) != NULL) { |
| - | // we are trying to modify text[]- make this the current context |
| - | mark[27] = mark[26]; // move cur to prev |
| - | mark[26] = dot; // move local to cur |
| - | context_start = prev_line(prev_line(dot)); |
| - | context_end = next_line(next_line(dot)); |
| - | //loiter= start_loiter= now; |
| - | } |
| - | } |
| - | } |
| - | |
| - | static char *swap_context(char *p) // goto new context for '' command make this the current context |
| - | { |
| - | char *tmp; |
| - | |
| - | // the current context is in mark[26] |
| - | // the previous context is in mark[27] |
| - | // only swap context if other context is valid |
| - | if (text <= mark[27] && mark[27] <= end - 1) { |
| - | tmp = mark[27]; |
| - | mark[27] = p; |
| - | mark[26] = p = tmp; |
| - | context_start = prev_line(prev_line(prev_line(p))); |
| - | context_end = next_line(next_line(next_line(p))); |
| - | } |
| - | return p; |
| - | } |
| - | #endif /* FEATURE_VI_YANKMARK */ |
| - | |
| - | //----- IO Routines -------------------------------------------- |
| - | static int readit(void) // read (maybe cursor) key from stdin |
| - | { |
| - | int c; |
| - | |
| - | fflush_all(); |
| - | |
| - | // Wait for input. TIMEOUT = -1 makes read_key wait even |
| - | // on nonblocking stdin. |
| - | // Note: read_key sets errno to 0 on success. |
| - | again: |
| - | c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1); |
| - | if (c == -1) { // EOF/error |
| - | if (errno == EAGAIN) // paranoia |
| - | goto again; |
| - | go_bottom_and_clear_to_eol(); |
| - | cookmode(); // terminal to "cooked" |
| - | bb_error_msg_and_die("can't read user input"); |
| - | } |
| - | return c; |
| - | } |
| - | |
| - | //----- IO Routines -------------------------------------------- |
| - | static int get_one_char(void) |
| - | { |
| - | int c; |
| - | |
| - | #if ENABLE_FEATURE_VI_DOT_CMD |
| - | if (!adding2q) { |
| - | // we are not adding to the q. |
| - | // but, we may be reading from a q |
| - | if (ioq == 0) { |
| - | // there is no current q, read from STDIN |
| - | c = readit(); // get the users input |
| - | } else { |
| - | // there is a queue to get chars from first |
| - | // careful with correct sign expansion! |
| - | c = (unsigned char)*ioq++; |
| - | if (c == '\0') { |
| - | // the end of the q, read from STDIN |
| - | free(ioq_start); |
| - | ioq_start = ioq = 0; |
| - | c = readit(); // get the users input |
| - | } |
| - | } |
| - | } else { |
| - | // adding STDIN chars to q |
| - | c = readit(); // get the users input |
| - | if (lmc_len >= MAX_INPUT_LEN - 1) { |
| - | status_line_bold("last_modifying_cmd overrun"); |
| - | } else { |
| - | // add new char to q |
| - | last_modifying_cmd[lmc_len++] = c; |
| - | } |
| - | } |
| - | #else |
| - | c = readit(); // get the users input |
| - | #endif /* FEATURE_VI_DOT_CMD */ |
| - | return c; |
| - | } |
| - | |
| - | // Get input line (uses "status line" area) |
| - | static char *get_input_line(const char *prompt) |
| - | { |
| - | // char [MAX_INPUT_LEN] |
| - | #define buf get_input_line__buf |
| - | |
| - | int c; |
| - | int i; |
| - | |
| - | strcpy(buf, prompt); |
| - | last_status_cksum = 0; // force status update |
| - | go_bottom_and_clear_to_eol(); |
| - | write1(prompt); // write out the :, /, or ? prompt |
| - | |
| - | i = strlen(buf); |
| - | while (i < MAX_INPUT_LEN) { |
| - | c = get_one_char(); |
| - | if (c == '\n' || c == '\r' || c == 27) |
| - | break; // this is end of input |
| - | if (c == erase_char || c == 8 || c == 127) { |
| - | // user wants to erase prev char |
| - | buf[--i] = '\0'; |
| - | write1("\b \b"); // erase char on screen |
| - | if (i <= 0) // user backs up before b-o-l, exit |
| - | break; |
| - | } else if (c > 0 && c < 256) { // exclude Unicode |
| - | // (TODO: need to handle Unicode) |
| - | buf[i] = c; |
| - | buf[++i] = '\0'; |
| - | bb_putchar(c); |
| - | } |
| - | } |
| - | refresh(FALSE); |
| - | return buf; |
| - | #undef buf |
| - | } |
| - | |
| - | // might reallocate text[]! |
| - | static int file_insert(const char *fn, char *p, int initial) |
| - | { |
| - | int cnt = -1; |
| - | int fd, size; |
| - | struct stat statbuf; |
| + | int cnt = -1; |
| + | int fd, size; |
| + | struct stat statbuf; |
| if (p < text) | |
| p = text; | |
| @@ -2666,399 +1900,1064 @@ static int file_insert(const char *fn, char *p, int initial) | |
| return cnt; | |
| } | |
| - | static int file_write(char *fn, char *first, char *last) |
| + | // find matching char of pair () [] {} |
| + | // will crash if c is not one of these |
| + | static char *find_pair(char *p, const char c) |
| { | |
| - | int fd, cnt, charcnt; |
| + | const char *braces = "()[]{}"; |
| + | char match; |
| + | int dir, level; |
| - | if (fn == 0) { |
| - | status_line_bold("No current filename"); |
| - | return -2; |
| - | } |
| - | // By popular request we do not open file with O_TRUNC, |
| - | // but instead ftruncate() it _after_ successful write. |
| - | // Might reduce amount of data lost on power fail etc. |
| - | fd = open(fn, (O_WRONLY | O_CREAT), 0666); |
| - | if (fd < 0) |
| - | return -1; |
| - | cnt = last - first + 1; |
| - | charcnt = full_write(fd, first, cnt); |
| - | ftruncate(fd, charcnt); |
| - | if (charcnt == cnt) { |
| - | // good write |
| - | //modified_count = FALSE; |
| - | } else { |
| - | charcnt = 0; |
| + | dir = strchr(braces, c) - braces; |
| + | dir ^= 1; |
| + | match = braces[dir]; |
| + | dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\} |
| + | |
| + | // look for match, count levels of pairs (( )) |
| + | level = 1; |
| + | for (;;) { |
| + | p += dir; |
| + | if (p < text || p >= end) |
| + | return NULL; |
| + | if (*p == c) |
| + | level++; // increase pair levels |
| + | if (*p == match) { |
| + | level--; // reduce pair level |
| + | if (level == 0) |
| + | return p; // found matching pair |
| + | } |
| } | |
| - | close(fd); |
| - | return charcnt; |
| } | |
| - | //----- Flash the screen -------------------------------------- |
| - | static void flash(int h) |
| + | #if ENABLE_FEATURE_VI_SETOPTS |
| + | // show the matching char of a pair, () [] {} |
| + | static void showmatching(char *p) |
| { | |
| - | standout_start(); |
| - | redraw(TRUE); |
| - | mysleep(h); |
| - | standout_end(); |
| - | redraw(TRUE); |
| + | char *q, *save_dot; |
| + | |
| + | // we found half of a pair |
| + | q = find_pair(p, *p); // get loc of matching char |
| + | if (q == NULL) { |
| + | indicate_error(); // no matching char |
| + | } else { |
| + | // "q" now points to matching pair |
| + | save_dot = dot; // remember where we are |
| + | dot = q; // go to new loc |
| + | refresh(FALSE); // let the user see it |
| + | mysleep(40); // give user some time |
| + | dot = save_dot; // go back to old loc |
| + | refresh(FALSE); |
| + | } |
| } | |
| + | #endif /* FEATURE_VI_SETOPTS */ |
| - | static void indicate_error(void) |
| + | // might reallocate text[]! use p += stupid_insert(p, ...), |
| + | // and be careful to not use pointers into potentially freed text[]! |
| + | static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p' |
| { | |
| - | #if ENABLE_FEATURE_VI_CRASHME |
| - | if (crashme > 0) |
| - | return; |
| + | uintptr_t bias; |
| + | bias = text_hole_make(p, 1); |
| + | p += bias; |
| + | *p = c; |
| + | return bias; |
| + | } |
| + | |
| + | #if !ENABLE_FEATURE_VI_UNDO |
| + | #define char_insert(a,b,c) char_insert(a,b) |
| #endif | |
| - | if (!err_method) { |
| - | write1(ESC_BELL); |
| + | static char *char_insert(char *p, char c, int undo) // insert the char c at 'p' |
| + | { |
| + | if (c == 22) { // Is this an ctrl-V? |
| + | p += stupid_insert(p, '^'); // use ^ to indicate literal next |
| + | refresh(FALSE); // show the ^ |
| + | c = get_one_char(); |
| + | *p = c; |
| + | #if ENABLE_FEATURE_VI_UNDO |
| + | undo_push_insert(p, 1, undo); |
| + | #else |
| + | modified_count++; |
| + | #endif /* ENABLE_FEATURE_VI_UNDO */ |
| + | p++; |
| + | } else if (c == 27) { // Is this an ESC? |
| + | cmd_mode = 0; |
| + | undo_queue_commit(); |
| + | cmdcnt = 0; |
| + | end_cmd_q(); // stop adding to q |
| + | last_status_cksum = 0; // force status update |
| + | if ((p[-1] != '\n') && (dot > text)) { |
| + | p--; |
| + | } |
| + | } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS |
| + | if (p > text) { |
| + | p--; |
| + | p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char |
| + | } |
| } else { | |
| - | flash(10); |
| + | // insert a char into text[] |
| + | if (c == 13) |
| + | c = '\n'; // translate \r to \n |
| + | #if ENABLE_FEATURE_VI_UNDO |
| + | # if ENABLE_FEATURE_VI_UNDO_QUEUE |
| + | if (c == '\n') |
| + | undo_queue_commit(); |
| + | # endif |
| + | undo_push_insert(p, 1, undo); |
| + | #else |
| + | modified_count++; |
| + | #endif /* ENABLE_FEATURE_VI_UNDO */ |
| + | p += 1 + stupid_insert(p, c); // insert the char |
| + | #if ENABLE_FEATURE_VI_SETOPTS |
| + | if (showmatch && strchr(")]}", c) != NULL) { |
| + | showmatching(p - 1); |
| + | } |
| + | if (autoindent && c == '\n') { // auto indent the new line |
| + | char *q; |
| + | size_t len; |
| + | q = prev_line(p); // use prev line as template |
| + | len = strspn(q, " \t"); // space or tab |
| + | if (len) { |
| + | uintptr_t bias; |
| + | bias = text_hole_make(p, len); |
| + | p += bias; |
| + | q += bias; |
| + | #if ENABLE_FEATURE_VI_UNDO |
| + | undo_push_insert(p, len, undo); |
| + | #endif |
| + | memcpy(p, q, len); |
| + | p += len; |
| + | } |
| + | } |
| + | #endif |
| } | |
| + | return p; |
| } | |
| - | static int bufsum(char *buf, int count) |
| + | // read text from file or create an empty buf |
| + | // will also update current_filename |
| + | static int init_text_buffer(char *fn) |
| { | |
| - | int sum = 0; |
| - | char *e = buf + count; |
| + | int rc; |
| - | while (buf < e) |
| - | sum += (unsigned char) *buf++; |
| - | return sum; |
| + | // allocate/reallocate text buffer |
| + | free(text); |
| + | text_size = 10240; |
| + | screenbegin = dot = end = text = xzalloc(text_size); |
| + | |
| + | if (fn != current_filename) { |
| + | free(current_filename); |
| + | current_filename = xstrdup(fn); |
| + | } |
| + | rc = file_insert(fn, text, 1); |
| + | if (rc < 0) { |
| + | // file doesnt exist. Start empty buf with dummy line |
| + | char_insert(text, '\n', NO_UNDO); |
| + | } |
| + | |
| + | flush_undo_data(); |
| + | modified_count = 0; |
| + | last_modified_count = -1; |
| + | #if ENABLE_FEATURE_VI_YANKMARK |
| + | // init the marks |
| + | memset(mark, 0, sizeof(mark)); |
| + | #endif |
| + | return rc; |
| } | |
| - | //----- Draw the status line at bottom of the screen ------------- |
| - | static void show_status_line(void) |
| + | #if ENABLE_FEATURE_VI_YANKMARK \ |
| + | || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \ |
| + | || ENABLE_FEATURE_VI_CRASHME |
| + | // might reallocate text[]! use p += string_insert(p, ...), |
| + | // and be careful to not use pointers into potentially freed text[]! |
| + | # if !ENABLE_FEATURE_VI_UNDO |
| + | # define string_insert(a,b,c) string_insert(a,b) |
| + | # endif |
| + | static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p' |
| { | |
| - | int cnt = 0, cksum = 0; |
| + | uintptr_t bias; |
| + | int i; |
| - | // either we already have an error or status message, or we |
| - | // create one. |
| - | if (!have_status_msg) { |
| - | cnt = format_edit_status(); |
| - | cksum = bufsum(status_buffer, cnt); |
| + | i = strlen(s); |
| + | #if ENABLE_FEATURE_VI_UNDO |
| + | undo_push_insert(p, i, undo); |
| + | #endif |
| + | bias = text_hole_make(p, i); |
| + | p += bias; |
| + | memcpy(p, s, i); |
| + | #if ENABLE_FEATURE_VI_YANKMARK |
| + | { |
| + | int cnt; |
| + | for (cnt = 0; *s != '\0'; s++) { |
| + | if (*s == '\n') |
| + | cnt++; |
| + | } |
| + | status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg()); |
| } | |
| - | if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) { |
| - | last_status_cksum = cksum; // remember if we have seen this line |
| - | go_bottom_and_clear_to_eol(); |
| - | write1(status_buffer); |
| - | if (have_status_msg) { |
| - | if (((int)strlen(status_buffer) - (have_status_msg - 1)) > |
| - | (columns - 1) ) { |
| - | have_status_msg = 0; |
| - | Hit_Return(); |
| + | #endif |
| + | return bias; |
| + | } |
| + | #endif |
| + | |
| + | static int file_write(char *fn, char *first, char *last) |
| + | { |
| + | int fd, cnt, charcnt; |
| + | |
| + | if (fn == 0) { |
| + | status_line_bold("No current filename"); |
| + | return -2; |
| + | } |
| + | // By popular request we do not open file with O_TRUNC, |
| + | // but instead ftruncate() it _after_ successful write. |
| + | // Might reduce amount of data lost on power fail etc. |
| + | fd = open(fn, (O_WRONLY | O_CREAT), 0666); |
| + | if (fd < 0) |
| + | return -1; |
| + | cnt = last - first + 1; |
| + | charcnt = full_write(fd, first, cnt); |
| + | ftruncate(fd, charcnt); |
| + | if (charcnt == cnt) { |
| + | // good write |
| + | //modified_count = FALSE; |
| + | } else { |
| + | charcnt = 0; |
| + | } |
| + | close(fd); |
| + | return charcnt; |
| + | } |
| + | |
| + | #if ENABLE_FEATURE_VI_SEARCH |
| + | # if ENABLE_FEATURE_VI_REGEX_SEARCH |
| + | // search for pattern starting at p |
| + | static char *char_search(char *p, const char *pat, int dir_and_range) |
| + | { |
| + | struct re_pattern_buffer preg; |
| + | const char *err; |
| + | char *q; |
| + | int i; |
| + | int size; |
| + | int range; |
| + | |
| + | re_syntax_options = RE_SYNTAX_POSIX_EXTENDED; |
| + | if (ignorecase) |
| + | re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE; |
| + | |
| + | memset(&preg, 0, sizeof(preg)); |
| + | err = re_compile_pattern(pat, strlen(pat), &preg); |
| + | if (err != NULL) { |
| + | status_line_bold("bad search pattern '%s': %s", pat, err); |
| + | return p; |
| + | } |
| + | |
| + | range = (dir_and_range & 1); |
| + | q = end - 1; // if FULL |
| + | if (range == LIMITED) |
| + | q = next_line(p); |
| + | if (dir_and_range < 0) { // BACK? |
| + | q = text; |
| + | if (range == LIMITED) |
| + | q = prev_line(p); |
| + | } |
| + | |
| + | // RANGE could be negative if we are searching backwards |
| + | range = q - p; |
| + | q = p; |
| + | size = range; |
| + | if (range < 0) { |
| + | size = -size; |
| + | q = p - size; |
| + | if (q < text) |
| + | q = text; |
| + | } |
| + | // search for the compiled pattern, preg, in p[] |
| + | // range < 0: search backward |
| + | // range > 0: search forward |
| + | // 0 < start < size |
| + | // re_search() < 0: not found or error |
| + | // re_search() >= 0: index of found pattern |
| + | // struct pattern char int int int struct reg |
| + | // re_search(*pattern_buffer, *string, size, start, range, *regs) |
| + | i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL); |
| + | regfree(&preg); |
| + | if (i < 0) |
| + | return NULL; |
| + | if (dir_and_range > 0) // FORWARD? |
| + | p = p + i; |
| + | else |
| + | p = p - i; |
| + | return p; |
| + | } |
| + | # else |
| + | # if ENABLE_FEATURE_VI_SETOPTS |
| + | static int mycmp(const char *s1, const char *s2, int len) |
| + | { |
| + | if (ignorecase) { |
| + | return strncasecmp(s1, s2, len); |
| + | } |
| + | return strncmp(s1, s2, len); |
| + | } |
| + | # else |
| + | # define mycmp strncmp |
| + | # endif |
| + | static char *char_search(char *p, const char *pat, int dir_and_range) |
| + | { |
| + | char *start, *stop; |
| + | int len; |
| + | int range; |
| + | |
| + | len = strlen(pat); |
| + | range = (dir_and_range & 1); |
| + | if (dir_and_range > 0) { //FORWARD? |
| + | stop = end - 1; // assume range is p..end-1 |
| + | if (range == LIMITED) |
| + | stop = next_line(p); // range is to next line |
| + | for (start = p; start < stop; start++) { |
| + | if (mycmp(start, pat, len) == 0) { |
| + | return start; |
| + | } |
| + | } |
| + | } else { //BACK |
| + | stop = text; // assume range is text..p |
| + | if (range == LIMITED) |
| + | stop = prev_line(p); // range is to prev line |
| + | for (start = p - len; start >= stop; start--) { |
| + | if (mycmp(start, pat, len) == 0) { |
| + | return start; |
| + | } |
| + | } |
| + | } |
| + | // pattern not found |
| + | return NULL; |
| + | } |
| + | # endif |
| + | #endif /* FEATURE_VI_SEARCH */ |
| + | |
| + | //----- 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;) |
| + | |
| + | *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; |
| + | } |
| + | return p; |
| + | } |
| + | |
| + | 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 |
| + | ) { |
| + | 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; |
| + | } |
| + | 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 (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; |
| + | } |
| + | not_implemented(p); |
| + | #else |
| + | |
| + | 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" |
| + | // :!<cmd> // run <cmd> 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 <cmd> |
| + | 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" |
| + | } |
| + | // read after current line- unless user said ":0r foo" |
| + | if (b != 0) { |
| + | q = next_line(q); |
| + | // read after last line |
| + | if (q == end-1) |
| + | ++q; |
| + | } |
| + | { // dance around potentially-reallocated text[] |
| + | uintptr_t ofs = q - text; |
| + | size = file_insert(fn, q, 0); |
| + | q = text + ofs; |
| + | } |
| + | if (size < 0) |
| + | goto ret; // nothing was inserted |
| + | // how many lines in text[]? |
| + | li = count_lines(q, q + size - 1); |
| + | status_line("'%s'" |
| + | IF_FEATURE_VI_READONLY("%s") |
| + | " %dL, %dC", |
| + | fn, |
| + | IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),) |
| + | li, size |
| + | ); |
| + | if (size > 0) { |
| + | // if the insert is before "dot" then we need to update |
| + | if (q <= dot) |
| + | dot += size; |
| + | } |
| + | } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args |
| + | if (modified_count && !useforce) { |
| + | status_line_bold("No write since last change (:%s! overrides)", cmd); |
| + | } else { |
| + | // reset the filenames to edit |
| + | optind = -1; // start from 0th file |
| + | editing = 0; |
| + | } |
| + | # if ENABLE_FEATURE_VI_SET |
| + | } else if (strncmp(cmd, "set", i) == 0) { // set or clear features |
| + | # if ENABLE_FEATURE_VI_SETOPTS |
| + | char *argp; |
| + | # endif |
| + | i = 0; // offset into args |
| + | // only blank is regarded as args delimiter. What about tab '\t'? |
| + | if (!args[0] || strcasecmp(args, "all") == 0) { |
| + | // print out values of all options |
| + | # if ENABLE_FEATURE_VI_SETOPTS |
| + | status_line_bold( |
| + | "%sautoindent " |
| + | "%sflash " |
| + | "%signorecase " |
| + | "%sshowmatch " |
| + | "tabstop=%u", |
| + | autoindent ? "" : "no", |
| + | err_method ? "" : "no", |
| + | ignorecase ? "" : "no", |
| + | showmatch ? "" : "no", |
| + | tabstop |
| + | ); |
| + | # endif |
| + | goto ret; |
| + | } |
| + | # if ENABLE_FEATURE_VI_SETOPTS |
| + | argp = args; |
| + | while (*argp) { |
| + | if (strncmp(argp, "no", 2) == 0) |
| + | i = 2; // ":set noautoindent" |
| + | setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT); |
| + | setops(argp, "flash " , i, "fl", VI_ERR_METHOD); |
| + | setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE); |
| + | setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH ); |
| + | if (strncmp(argp + i, "tabstop=", 8) == 0) { |
| + | int t = 0; |
| + | sscanf(argp + i+8, "%u", &t); |
| + | if (t > 0 && t <= MAX_TABSTOP) |
| + | tabstop = t; |
| } | |
| - | have_status_msg = 0; |
| + | argp = skip_non_whitespace(argp); |
| + | argp = skip_whitespace(argp); |
| } | |
| - | place_cursor(crow, ccol); // put cursor back in correct place |
| - | } |
| - | fflush_all(); |
| - | } |
| - | |
| - | //----- format the status buffer, the bottom line of screen ------ |
| - | // format status buffer, with STANDOUT mode |
| - | static void status_line_bold(const char *format, ...) |
| - | { |
| - | va_list args; |
| - | |
| - | va_start(args, format); |
| - | strcpy(status_buffer, ESC_BOLD_TEXT); |
| - | vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args); |
| - | strcat(status_buffer, ESC_NORM_TEXT); |
| - | va_end(args); |
| - | |
| - | have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2; |
| - | } |
| - | |
| - | static void status_line_bold_errno(const char *fn) |
| - | { |
| - | status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO); |
| - | } |
| - | |
| - | // format status buffer |
| - | static void status_line(const char *format, ...) |
| - | { |
| - | va_list args; |
| - | |
| - | va_start(args, format); |
| - | vsprintf(status_buffer, format, args); |
| - | va_end(args); |
| + | # endif /* FEATURE_VI_SETOPTS */ |
| + | # endif /* FEATURE_VI_SET */ |
| - | have_status_msg = 1; |
| - | } |
| + | # if ENABLE_FEATURE_VI_SEARCH |
| + | } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern |
| + | char *F, *R, *flags; |
| + | size_t len_F, len_R; |
| + | int gflag; // global replace flag |
| + | # if ENABLE_FEATURE_VI_UNDO |
| + | int dont_chain_first_item = ALLOW_UNDO; |
| + | # endif |
| - | // copy s to buf, convert unprintable |
| - | static void print_literal(char *buf, const char *s) |
| - | { |
| - | char *d; |
| - | unsigned char c; |
| + | // 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; |
| - | buf[0] = '\0'; |
| - | if (!s[0]) |
| - | s = "(NULL)"; |
| + | 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/ |
| - | d = buf; |
| - | for (; *s; s++) { |
| - | int c_is_no_print; |
| + | 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; |
| - | c = *s; |
| - | c_is_no_print = (c & 0x80) && !Isprint(c); |
| - | if (c_is_no_print) { |
| - | strcpy(d, ESC_NORM_TEXT); |
| - | d += sizeof(ESC_NORM_TEXT)-1; |
| - | c = '.'; |
| + | // is there a file name to write to? |
| + | if (args[0]) { |
| + | fn = args; |
| } | |
| - | if (c < ' ' || c == 0x7f) { |
| - | *d++ = '^'; |
| - | c |= '@'; // 0x40 |
| - | if (c == 0x7f) |
| - | c = '?'; |
| + | # if ENABLE_FEATURE_VI_READONLY |
| + | if (readonly_mode && !useforce) { |
| + | status_line_bold("'%s' is read only", fn); |
| + | goto ret; |
| } | |
| - | *d++ = c; |
| - | *d = '\0'; |
| - | if (c_is_no_print) { |
| - | strcpy(d, ESC_BOLD_TEXT); |
| - | d += sizeof(ESC_BOLD_TEXT)-1; |
| + | # 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 (*s == '\n') { |
| - | *d++ = '$'; |
| - | *d = '\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 (d - buf > MAX_INPUT_LEN - 10) // paranoia |
| - | break; |
| - | } |
| - | } |
| - | |
| - | static void not_implemented(const char *s) |
| - | { |
| - | char buf[MAX_INPUT_LEN]; |
| - | |
| - | print_literal(buf, s); |
| - | status_line_bold("\'%s\' is not implemented", buf); |
| - | } |
| - | |
| - | // show file status on status line |
| - | static int format_edit_status(void) |
| - | { |
| - | static const char cmd_mode_indicator[] ALIGN1 = "-IR-"; |
| - | |
| - | #define tot format_edit_status__tot |
| - | |
| - | int cur, percent, ret, trunc_at; |
| - | |
| - | // modified_count is now a counter rather than a flag. this |
| - | // helps reduce the amount of line counting we need to do. |
| - | // (this will cause a mis-reporting of modified status |
| - | // once every MAXINT editing operations.) |
| - | |
| - | // it would be nice to do a similar optimization here -- if |
| - | // we haven't done a motion that could have changed which line |
| - | // we're on, then we shouldn't have to do this count_lines() |
| - | cur = count_lines(text, dot); |
| - | |
| - | // count_lines() is expensive. |
| - | // Call it only if something was changed since last time |
| - | // we were here: |
| - | if (modified_count != last_modified_count) { |
| - | tot = cur + count_lines(dot, end - 1) - 1; |
| - | last_modified_count = modified_count; |
| - | } |
| - | |
| - | // current line percent |
| - | // ------------- ~~ ---------- |
| - | // total lines 100 |
| - | if (tot > 0) { |
| - | percent = (100 * cur) / tot; |
| + | # 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 { | |
| - | cur = tot = 0; |
| - | percent = 100; |
| + | // cmd unknown |
| + | not_implemented(cmd); |
| } | |
| - | |
| - | trunc_at = columns < STATUS_BUFFER_LEN-1 ? |
| - | columns : STATUS_BUFFER_LEN-1; |
| - | |
| - | ret = snprintf(status_buffer, trunc_at+1, |
| - | #if ENABLE_FEATURE_VI_READONLY |
| - | "%c %s%s%s %d/%d %d%%", |
| - | #else |
| - | "%c %s%s %d/%d %d%%", |
| - | #endif |
| - | cmd_mode_indicator[cmd_mode & 3], |
| - | (current_filename != NULL ? current_filename : "No file"), |
| - | #if ENABLE_FEATURE_VI_READONLY |
| - | (readonly_mode ? " [Readonly]" : ""), |
| - | #endif |
| - | (modified_count ? " [Modified]" : ""), |
| - | cur, tot, percent); |
| - | |
| - | if (ret >= 0 && ret < trunc_at) |
| - | return ret; // it all fit |
| - | |
| - | return trunc_at; // had to truncate |
| - | #undef tot |
| + | 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 */ |
| } | |
| - | //----- Force refresh of all Lines ----------------------------- |
| - | static void redraw(int full_screen) |
| - | { |
| - | // cursor to top,left; clear to the end of screen |
| - | write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS); |
| - | screen_erase(); // erase the internal screen buffer |
| - | last_status_cksum = 0; // force status update |
| - | refresh(full_screen); // this will redraw the entire display |
| - | show_status_line(); |
| - | } |
| + | //----- Helper Utility Routines -------------------------------- |
| - | //----- Format a text[] line into a buffer --------------------- |
| - | static char* format_line(char *src /*, int li*/) |
| + | //---------------------------------------------------------------- |
| + | //----- Char Routines -------------------------------------------- |
| + | /* Chars that are part of a word- |
| + | * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz |
| + | * Chars that are Not part of a word (stoppers) |
| + | * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~ |
| + | * Chars that are WhiteSpace |
| + | * TAB NEWLINE VT FF RETURN SPACE |
| + | * DO NOT COUNT NEWLINE AS WHITESPACE |
| + | */ |
| + | |
| + | static char *new_screen(int ro, int co) |
| { | |
| - | unsigned char c; |
| - | int co; |
| - | int ofs = offset; |
| - | char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2] |
| + | int li; |
| - | c = '~'; // char in col 0 in non-existent lines is '~' |
| - | co = 0; |
| - | while (co < columns + tabstop) { |
| - | // have we gone past the end? |
| - | if (src < end) { |
| - | c = *src++; |
| - | if (c == '\n') |
| - | break; |
| - | if ((c & 0x80) && !Isprint(c)) { |
| - | c = '.'; |
| - | } |
| - | if (c < ' ' || c == 0x7f) { |
| - | if (c == '\t') { |
| - | c = ' '; |
| - | // co % 8 != 7 |
| - | while ((co % tabstop) != (tabstop - 1)) { |
| - | dest[co++] = c; |
| - | } |
| - | } else { |
| - | dest[co++] = '^'; |
| - | if (c == 0x7f) |
| - | c = '?'; |
| - | else |
| - | c += '@'; // Ctrl-X -> 'X' |
| - | } |
| - | } |
| - | } |
| - | dest[co++] = c; |
| - | // discard scrolled-off-to-the-left portion, |
| - | // in tabstop-sized pieces |
| - | if (ofs >= tabstop && co >= tabstop) { |
| - | memmove(dest, dest + tabstop, co); |
| - | co -= tabstop; |
| - | ofs -= tabstop; |
| - | } |
| - | if (src >= end) |
| - | break; |
| + | free(screen); |
| + | screensize = ro * co + 8; |
| + | screen = xmalloc(screensize); |
| + | // initialize the new screen. assume this will be a empty file. |
| + | screen_erase(); |
| + | // non-existent text[] lines start with a tilde (~). |
| + | for (li = 1; li < ro - 1; li++) { |
| + | screen[(li * co) + 0] = '~'; |
| } | |
| - | // check "short line, gigantic offset" case |
| - | if (co < ofs) |
| - | ofs = co; |
| - | // discard last scrolled off part |
| - | co -= ofs; |
| - | dest += ofs; |
| - | // fill the rest with spaces |
| - | if (co < columns) |
| - | memset(&dest[co], ' ', columns - co); |
| - | return dest; |
| + | return screen; |
| } | |
| - | //----- Refresh the changed screen lines ----------------------- |
| - | // Copy the source line from text[] into the buffer and note |
| - | // if the current screenline is different from the new buffer. |
| - | // If they differ then that line needs redrawing on the terminal. |
| - | // |
| - | static void refresh(int full_screen) |
| + | static int st_test(char *p, int type, int dir, char *tested) |
| { | |
| - | #define old_offset refresh__old_offset |
| + | char c, c0, ci; |
| + | int test, inc; |
| - | int li, changed; |
| - | char *tp, *sp; // pointer into text[] and screen[] |
| + | inc = dir; |
| + | c = c0 = p[0]; |
| + | ci = p[inc]; |
| + | test = 0; |
| - | if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) { |
| - | unsigned c = columns, r = rows; |
| - | query_screen_dimensions(); |
| - | #if ENABLE_FEATURE_VI_USE_SIGNALS |
| - | full_screen |= (c - columns) | (r - rows); |
| - | #else |
| - | if (c != columns || r != rows) { |
| - | full_screen = TRUE; |
| - | // update screen memory since SIGWINCH won't have done it |
| - | new_screen(rows, columns); |
| - | } |
| - | #endif |
| + | if (type == S_BEFORE_WS) { |
| + | c = ci; |
| + | test = (!isspace(c) || c == '\n'); |
| } | |
| - | sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot") |
| - | tp = screenbegin; // index into text[] of top line |
| - | |
| - | // compare text[] to screen[] and mark screen[] lines that need updating |
| - | for (li = 0; li < rows - 1; li++) { |
| - | int cs, ce; // column start & end |
| - | char *out_buf; |
| - | // format current text line |
| - | out_buf = format_line(tp /*, li*/); |
| - | |
| - | // skip to the end of the current text[] line |
| - | if (tp < end) { |
| - | char *t = memchr(tp, '\n', end - tp); |
| - | if (!t) t = end - 1; |
| - | tp = t + 1; |
| - | } |
| - | |
| - | // see if there are any changes between virtual screen and out_buf |
| - | changed = FALSE; // assume no change |
| - | cs = 0; |
| - | ce = columns - 1; |
| - | sp = &screen[li * columns]; // start of screen line |
| - | if (full_screen) { |
| - | // force re-draw of every single column from 0 - columns-1 |
| - | goto re0; |
| - | } |
| - | // compare newly formatted buffer with virtual screen |
| - | // look forward for first difference between buf and screen |
| - | for (; cs <= ce; cs++) { |
| - | if (out_buf[cs] != sp[cs]) { |
| - | changed = TRUE; // mark for redraw |
| - | break; |
| - | } |
| - | } |
| - | |
| - | // look backward for last difference between out_buf and screen |
| - | for (; ce >= cs; ce--) { |
| - | if (out_buf[ce] != sp[ce]) { |
| - | changed = TRUE; // mark for redraw |
| - | break; |
| - | } |
| - | } |
| - | // now, cs is index of first diff, and ce is index of last diff |
| - | |
| - | // if horz offset has changed, force a redraw |
| - | if (offset != old_offset) { |
| - | re0: |
| - | changed = TRUE; |
| - | } |
| - | |
| - | // make a sanity check of columns indexes |
| - | if (cs < 0) cs = 0; |
| - | if (ce > columns - 1) ce = columns - 1; |
| - | if (cs > ce) { cs = 0; ce = columns - 1; } |
| - | // is there a change between virtual screen and out_buf |
| - | if (changed) { |
| - | // copy changed part of buffer to virtual screen |
| - | memcpy(sp+cs, out_buf+cs, ce-cs+1); |
| - | place_cursor(li, cs); |
| - | // write line out to terminal |
| - | fwrite(&sp[cs], ce - cs + 1, 1, stdout); |
| - | } |
| + | if (type == S_TO_WS) { |
| + | c = c0; |
| + | test = (!isspace(c) || c == '\n'); |
| } | |
| + | if (type == S_OVER_WS) { |
| + | c = c0; |
| + | test = isspace(c); |
| + | } |
| + | if (type == S_END_PUNCT) { |
| + | c = ci; |
| + | test = ispunct(c); |
| + | } |
| + | if (type == S_END_ALNUM) { |
| + | c = ci; |
| + | test = (isalnum(c) || c == '_'); |
| + | } |
| + | *tested = c; |
| + | return test; |
| + | } |
| - | place_cursor(crow, ccol); |
| + | static char *skip_thing(char *p, int linecnt, int dir, int type) |
| + | { |
| + | char c; |
| - | old_offset = offset; |
| - | #undef old_offset |
| + | while (st_test(p, type, dir, &c)) { |
| + | // make sure we limit search to correct number of lines |
| + | if (c == '\n' && --linecnt < 1) |
| + | break; |
| + | if (dir >= 0 && p >= end - 1) |
| + | break; |
| + | if (dir < 0 && p <= text) |
| + | break; |
| + | p += dir; // move to next char |
| + | } |
| + | return p; |
| } | |
| #if ENABLE_FEATURE_VI_USE_SIGNALS | |