vi: rearrange functions, no logic changes
authorDenys Vlasenko <vda.linux@googlemail.com>
Mon, 1 Apr 2019 12:18:02 +0000 (14:18 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Mon, 1 Apr 2019 12:18:02 +0000 (14:18 +0200)
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
editors/vi.c

index 9db763ccdb1c0c8261fe950ad54ff96548a81f28..a0a2b7a826efbd925972eec64afb61af2fd86bfe 100644 (file)
@@ -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