I said no new features till after the 0.51 release. Well, I lied. This is a
authorEric Andersen <andersen@codepoet.org>
Wed, 4 Apr 2001 17:31:15 +0000 (17:31 -0000)
committerEric Andersen <andersen@codepoet.org>
Wed, 4 Apr 2001 17:31:15 +0000 (17:31 -0000)
vi editor for busybox, contributed by Sterling Huxley <sterling@europa.com>.
It adds 22k to the busybox binary when enabled.  Quite impressive!

Changelog
Config.h
applets.h
applets/usage.h
editors/vi.c [new file with mode: 0644]
include/applets.h
include/usage.h
usage.h
vi.c [new file with mode: 0644]

index 42dcfecae19cc262f4709dc0b768547407960e39..862f14eae148a5dd5b8a9dafe5523bedaac95f27 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -1,4 +1,6 @@
 0.51pre
+       * Sterling Huxley -- contributed a new vi applet!  This is a very
+           functional vi implementation in Only 22k.
        * Erik Andersen -- added env applet
        * Erik Andersen -- Split utility.c into libbb
        * Andreas Neuhaus <andy@fasta.fh-dortmund.de> -- fix for merging
index bd63665587e56f78c4b1cab100f10dc780ae530e..44fcbed297e5f6f6da2caec624495f851816c074 100644 (file)
--- a/Config.h
+++ b/Config.h
 //#define BB_UPDATE
 #define BB_UPTIME
 //#define BB_USLEEP
+//#define BB_VI
 //#define BB_WATCHDOG
 #define BB_WC
 //#define BB_WGET
 #define BB_FEATURE_TFTP_PUT
 #define BB_FEATURE_TFTP_GET
 //
+// features for vi
+#define BB_FEATURE_VI_COLON            // ":" colon commands, no "ex" mode
+#define BB_FEATURE_VI_YANKMARK         // Yank/Put commands and Mark cmds
+#define BB_FEATURE_VI_SEARCH           // search and replace cmds
+#define BB_FEATURE_VI_USE_SIGNALS      // catch signals
+#define BB_FEATURE_VI_DOT_CMD          // remember previous cmd and "." cmd
+#define BB_FEATURE_VI_READONLY         // vi -R and "view" mode
+#define BB_FEATURE_VI_SETOPTS          // set-able options,  ai ic showmatch
+#define BB_FEATURE_VI_SET              // :set
+#define BB_FEATURE_VI_WIN_RESIZE       // handle window resize
+//
 // End of Features List
 //
 //
index 255bd6b44f7f3220f88c85f575c546bdba1bb33c..c3037973d1598494fced9f36382bddd175d6a275 100644 (file)
--- a/applets.h
+++ b/applets.h
 #ifdef BB_UUENCODE
        APPLET(uuencode, uuencode_main, _BB_DIR_USR_BIN)
 #endif
+#ifdef BB_VI
+       APPLET(vi, vi_main, _BB_DIR_BIN)
+#endif
 #ifdef BB_WATCHDOG
        APPLET(watchdog, watchdog_main, _BB_DIR_SBIN)
 #endif
index aa7f0af22792cac176bb11d285ecbaf9d671aae9..abd0156adcbebed472965578104cd9ae88b334c3 100644 (file)
        "$ uudecode busybox busybox > busybox.uu\n" \
        "$\n"
 
+#define vi_trivial_usage \
+       "[OPTION] [FILE]..."
+#define vi_full_usage \
+       "edit FILE.\n\n" \
+       "Options:\n" \
+       "\t-R\tRead-only- do not write to the file." 
+
 #define watchdog_trivial_usage \
        "DEV"
 #define watchdog_full_usage \
diff --git a/editors/vi.c b/editors/vi.c
new file mode 100644 (file)
index 0000000..325ab34
--- /dev/null
@@ -0,0 +1,3683 @@
+/* vi: set sw=8 ts=8: */
+/*
+ * tiny vi.c: A small 'vi' clone
+ * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+char *vi_Version =
+       "$Id: vi.c,v 1.1 2001/04/04 17:31:15 andersen Exp $";
+
+/*
+ * To compile for standalone use:
+ *     gcc -Wall -Os -DSTANDALONE -o vi vi.c
+ *       or
+ *     gcc -Wall -Os -DSTANDALONE -DCRASHME -o vi vi.c         # include testing features
+ *     strip vi
+ */
+
+/*
+ * Things To Do:
+ *     EXINIT
+ *     $HOME/.exrc
+ *     add magic to search     /foo.*bar
+ *     add :help command
+ *     :map macros
+ *     how about mode lines:   vi: set sw=8 ts=8:
+ *     if mark[] values were line numbers rather than pointers
+ *        it would be easier to change the mark when add/delete lines
+ */
+
+//----  Feature --------------  Bytes to immplement
+#ifdef STANDALONE
+#define BB_FEATURE_VI_COLON    // 4288
+#define BB_FEATURE_VI_YANKMARK // 1408
+#define BB_FEATURE_VI_SEARCH   // 1088
+#define BB_FEATURE_VI_USE_SIGNALS      // 1056
+#define BB_FEATURE_VI_DOT_CMD  //  576
+#define BB_FEATURE_VI_READONLY //  128
+#define BB_FEATURE_VI_SETOPTS  //  576
+#define BB_FEATURE_VI_SET      //  224
+#define BB_FEATURE_VI_WIN_RESIZE       //  256  WIN_RESIZE
+// To test editor using CRASHME:
+//    vi -C filename
+// To stop testing, wait until all to text[] is deleted, or
+//    Ctrl-Z and kill -9 %1
+// while in the editor Ctrl-T will toggle the crashme function on and off.
+//#define BB_FEATURE_VI_CRASHME     // randomly pick commands to execute
+#endif                                                 /* STANDALONE */
+
+#ifndef STANDALONE
+#include "busybox.h"
+#endif                                                 /* STANDALONE */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <regex.h>
+#include <ctype.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#ifdef __linux__
+#include <stdint.h>            // INT32_MAX
+#else                                                  /* __linux__ */
+#include <values.h>
+extern int strncasecmp(const char *s1, const char *s2, size_t n);
+
+#define INT32_MAX      MAXINT
+#include <libgen.h>            //
+#endif                                                 /* __linux__ */
+
+#ifndef TRUE
+#define TRUE                   ((int)1)
+#define FALSE                  ((int)0)
+#endif                                                 /* TRUE */
+#define MAX_SCR_COLS           300
+
+// Misc. non-Ascii keys that report an escape sequence
+#define VI_K_UP                        128     // cursor key Up
+#define VI_K_DOWN              129     // cursor key Down
+#define VI_K_RIGHT             130     // Cursor Key Right
+#define VI_K_LEFT              131     // cursor key Left
+#define VI_K_HOME              132     // Cursor Key Home
+#define VI_K_END               133     // Cursor Key End
+#define VI_K_INSERT            134     // Cursor Key Insert
+#define VI_K_PAGEUP            135     // Cursor Key Page Up
+#define VI_K_PAGEDOWN          136     // Cursor Key Page Down
+#define VI_K_FUN1              137     // Function Key F1
+#define VI_K_FUN2              138     // Function Key F2
+#define VI_K_FUN3              139     // Function Key F3
+#define VI_K_FUN4              140     // Function Key F4
+#define VI_K_FUN5              141     // Function Key F5
+#define VI_K_FUN6              142     // Function Key F6
+#define VI_K_FUN7              143     // Function Key F7
+#define VI_K_FUN8              144     // Function Key F8
+#define VI_K_FUN9              145     // Function Key F9
+#define VI_K_FUN10             146     // Function Key F10
+#define VI_K_FUN11             147     // Function Key F11
+#define VI_K_FUN12             148     // Function Key F12
+
+static const int YANKONLY = FALSE;
+static const int YANKDEL = TRUE;
+static const int FORWARD = 1;  // code depends on "1"  for array index
+static const int BACK = -1;    // code depends on "-1" for array index
+static const int LIMITED = 0;  // how much of text[] in char_search
+static const int FULL = 1;     // how much of text[] in char_search
+
+static const int S_BEFORE_WS = 1;      // used in skip_thing() for moving "dot"
+static const int S_TO_WS = 2;          // used in skip_thing() for moving "dot"
+static const int S_OVER_WS = 3;                // used in skip_thing() for moving "dot"
+static const int S_END_PUNCT = 4;      // used in skip_thing() for moving "dot"
+static const int S_END_ALNUM = 5;      // used in skip_thing() for moving "dot"
+
+typedef unsigned char Byte;
+
+
+static int editing;            // >0 while we are editing a file
+static int cmd_mode;           // 0=command  1=insert
+static int file_modified;      // buffer contents changed
+static int err_method;         // indicate error with beep or flash
+static int fn_start;           // index of first cmd line file name
+static int save_argc;          // how many file names on cmd line
+static int cmdcnt;             // repetition count
+static fd_set rfds;            // use select() for small sleeps
+static struct timeval tv;      // use select() for small sleeps
+static char erase_char;                // the users erase character
+static int rows, columns;      // the terminal screen is this size
+static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
+static char *SOs, *SOn;
+static Byte *status_buffer;    // mesages to the user
+static Byte last_input_char;   // last char read from user
+static Byte last_forward_char; // last char searched for with 'f'
+static Byte *cfn;              // previous, current, and next file name
+static Byte *text, *end, *textend;     // pointers to the user data in memory
+static Byte *screen;           // pointer to the virtual screen buffer
+static int screensize;         //            and its size
+static Byte *screenbegin;      // index into text[], of top line on the screen
+static Byte *dot;              // where all the action takes place
+static int tabstop;
+static struct termios term_orig, term_vi;      // remember what the cooked mode was
+
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+static jmp_buf restart;                // catch_sig()
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+static struct winsize winsize; // remember the window size
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+#ifdef BB_FEATURE_VI_DOT_CMD
+static int adding2q;           // are we currently adding user input to q
+static Byte *last_modifying_cmd;       // last modifying cmd for "."
+static Byte *ioq, *ioq_start;  // pointer to string for get_one_char to "read"
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+#if    defined(BB_FEATURE_VI_DOT_CMD) || defined(BB_FEATURE_VI_YANKMARK)
+static Byte *modifying_cmds;   // cmds that modify text[]
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD || BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_READONLY
+static int readonly;
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+#ifdef BB_FEATURE_VI_SETOPTS
+static int autoindent;
+static int showmatch;
+static int ignorecase;
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+#ifdef BB_FEATURE_VI_YANKMARK
+static Byte *reg[28];          // named register a-z, "D", and "U" 0-25,26,27
+static int YDreg, Ureg;                // default delete register and orig line for "U"
+static Byte *mark[28];         // user marks points somewhere in text[]-  a-z and previous context ''
+static Byte *context_start, *context_end;
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_SEARCH
+static Byte *last_search_pattern;      // last pattern from a '/' or '?' search
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+
+
+static void edit_file(Byte *); // edit one file
+static void do_cmd(Byte);      // execute a command
+static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
+static Byte *begin_line(Byte *);       // return pointer to cur line B-o-l
+static Byte *end_line(Byte *); // return pointer to cur line E-o-l
+static Byte *dollar_line(Byte *);      // return pointer to just before NL
+static Byte *prev_line(Byte *);        // return pointer to prev line B-o-l
+static Byte *next_line(Byte *);        // return pointer to next line B-o-l
+static Byte *end_screen(void); // get pointer to last char on screen
+static int count_lines(Byte *, Byte *);        // count line from start to stop
+static Byte *find_line(int);   // find begining of line #li
+static Byte *move_to_col(Byte *, int); // move "p" to column l
+static int isblnk(Byte);       // is the char a blank or tab
+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 void dot_delete(void);  // delete the char at 'dot'
+static Byte *bound_dot(Byte *);        // make sure  text[0] <= P < "end"
+static Byte *new_screen(int, int);     // malloc virtual screen memory
+static Byte *new_text(int);    // malloc memory for text[] buffer
+static Byte *char_insert(Byte *, Byte);        // insert the char c at 'p'
+static Byte *stupid_insert(Byte *, Byte);      // stupidly insert the char c at 'p'
+static Byte find_range(Byte **, Byte **, Byte);        // return pointers for an object
+static int st_test(Byte *, int, int, Byte *);  // helper for skip_thing()
+static Byte *skip_thing(Byte *, int, int, int);        // skip some object
+static Byte *find_pair(Byte *, Byte);  // find matching pair ()  []  {}
+static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
+static Byte *text_hole_make(Byte *, int);      // at "p", make a 'size' byte hole
+static Byte *yank_delete(Byte *, Byte *, int, int);    // yank text[] into register then delete
+static void show_help(void);   // display some help info
+static void print_literal(Byte *, Byte *);     // copy s to buf, convert unprintable
+static void rawmode(void);     // set "raw" mode on tty
+static void cookmode(void);    // return to "cooked" mode on tty
+static int mysleep(int);       // sleep for 'h' 1/100 seconds
+static Byte readit(void);      // read (maybe cursor) key from stdin
+static Byte get_one_char(void);        // read 1 char from stdin
+static int file_size(Byte *);  // what is the byte size of "fn"
+static int file_insert(Byte *, Byte *, int);
+static int file_write(Byte *, Byte *, Byte *);
+static void place_cursor(int, int);
+static void screen_erase();
+static void clear_to_eol(void);
+static void clear_to_eos(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 beep(void);                // beep the terminal
+static void indicate_error(char);      // use flash or beep to indicate error
+static void show_status_line(void);    // put a message on the bottom line
+static void psb(char *, ...);  // Print Status Buf
+static void psbs(char *, ...); // Print Status Buf in standout mode
+static void ni(Byte *);                // display messages
+static void edit_status(void); // show file status on status line
+static void redraw(int);       // force a full screen refresh
+static void refresh(int);      // update the terminal from screen[]
+
+#ifdef BB_FEATURE_VI_SEARCH
+static Byte *char_search(Byte *, Byte *, int, int);    // search for pattern starting at p
+static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+#ifdef BB_FEATURE_VI_COLON
+static void Hit_Return(void);
+static Byte *get_address(Byte *, int *);       // get colon addr, if present
+static void colon(Byte *);     // execute the "colon" mode cmds
+#endif                                                 /* BB_FEATURE_VI_COLON */
+#if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
+static Byte *get_input_line(Byte *);   // get input line- use "status line"
+#endif                                                 /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+static void winch_sig(int);    // catch window size changes
+static void suspend_sig(int);  // catch ctrl-Z
+static void alarm_sig(int);    // catch alarm time-outs
+static void catch_sig(int);    // catch ctrl-C
+static void core_sig(int);     // catch a core dump signal
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+#ifdef BB_FEATURE_VI_DOT_CMD
+static void start_new_cmd_q(Byte);     // new queue for command
+static void end_cmd_q();       // stop saving input chars
+#else                                                  /* BB_FEATURE_VI_DOT_CMD */
+#define end_cmd_q()
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+static void window_size_get(int);      // find out what size the window is
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+#ifdef BB_FEATURE_VI_SETOPTS
+static void showmatching(Byte *);      // show the matching pair ()  []  {}
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+#if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
+static Byte *string_insert(Byte *, Byte *);    // insert the string at 'p'
+#endif                                                 /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
+#ifdef BB_FEATURE_VI_YANKMARK
+static Byte *text_yank(Byte *, Byte *, int);   // save copy of "p" into a register
+static Byte what_reg(void);            // what is letter of current YDreg
+static void check_context(Byte);       // remember context for '' command
+static Byte *swap_context(Byte *);     // goto new context for '' command
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_CRASHME
+static void crash_dummy();
+static void crash_test();
+static int crashme = 0;
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+
+
+#ifdef STANDALONE
+int main(int argc, char **argv)
+#else
+extern int vi_main(int argc, char **argv)
+#endif                                 /* STANDALONE */
+{
+       extern char *optarg;
+       char c;
+
+#ifdef BB_FEATURE_VI_YANKMARK
+       int i;
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+
+       SOs = "\033[7m";        // Terminal standout mode on
+       SOn = "\033[0m";        // Terminal standout mode off
+#ifdef BB_FEATURE_VI_CRASHME
+       (void) srand((long) getpid());
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+       status_buffer = (Byte *) malloc(200);   // hold messages to user
+#ifdef BB_FEATURE_VI_READONLY
+       readonly = FALSE;
+       if (strncmp(argv[0], "view", 4) == 0) {
+               readonly = TRUE;
+       }
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+#ifdef BB_FEATURE_VI_SETOPTS
+       autoindent = 1;
+       ignorecase = 1;
+       showmatch = 1;
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+#ifdef BB_FEATURE_VI_YANKMARK
+       for (i = 0; i < 28; i++) {
+               reg[i] = 0;
+       }                                       // init the yank regs
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_DOT_CMD
+       modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~";      // cmds modifying text[]
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+
+       //  1-  process $HOME/.exrc file
+       //  2-  process EXINIT variable from environment
+       //  3-  process command line args
+       while ((c = getopt(argc, argv, "hCR")) != -1) {
+               switch (c) {
+#ifdef BB_FEATURE_VI_CRASHME
+               case 'C':
+                       crashme = 1;
+                       break;
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+#ifdef BB_FEATURE_VI_READONLY
+               case 'R':               // Read-only flag
+                       readonly = TRUE;
+                       break;
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+                       //case 'r':     // recover flag-  ignore- we don't use tmp file
+                       //case 'x':     // encryption flag- ignore
+                       //case 'c':     // execute command first
+                       //case 'h':     // help -- just use default
+               default:
+                       show_help();
+                       break;
+               }
+       }
+
+       // The argv array can be used by the ":next"  and ":rewind" commands
+       // save optind.
+       fn_start = optind;      // remember first file name for :next and :rew
+       save_argc = argc;
+
+       //----- This is the main file handling loop --------------
+       if (optind >= argc) {
+               editing = 1;    // 0= exit,  1= one file,  2= multiple files
+               edit_file(0);
+       } else {
+               for (; optind < argc; optind++) {
+                       editing = 1;    // 0=exit, 1=one file, 2+ =many files
+                       if (cfn != 0)
+                               free(cfn);
+                       cfn = (Byte *) strdup(argv[optind]);
+                       edit_file(cfn);
+               }
+       }
+       //-----------------------------------------------------------
+
+       return (0);
+}
+
+static void edit_file(Byte * fn)
+{
+       char c;
+       int cnt, size;
+
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+       char *msg;
+       int sig;
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+#ifdef BB_FEATURE_VI_YANKMARK
+       static Byte *cur_line;
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+
+       rawmode();
+       rows = 24;
+       columns = 80;
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+       window_size_get(0);
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+       new_screen(rows, columns);      // get memory for virtual screen
+
+       cnt = file_size(fn);    // file size
+       size = 2 * cnt;         // 200% of file size
+       new_text(size);         // get a text[] buffer
+       screenbegin = dot = end = text;
+       if (fn != 0) {
+               file_insert(fn, text, cnt);
+       } else {
+               (void) char_insert(text, '\n'); // start empty buf with dummy line
+       }
+       file_modified = FALSE;
+#ifdef BB_FEATURE_VI_YANKMARK
+       YDreg = 26;                     // default Yank/Delete reg
+       Ureg = 27;                      // hold orig line for "U" cmd
+       for (cnt = 0; cnt < 28; cnt++) {
+               mark[cnt] = 0;
+       }                                       // init the marks
+       mark[26] = mark[27] = text;     // init "previous context"
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+
+       err_method = 1;         // flash
+       last_forward_char = last_input_char = '\0';
+       crow = 0;
+       ccol = 0;
+       edit_status();
+
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+       signal(SIGHUP, catch_sig);
+       signal(SIGINT, catch_sig);
+       signal(SIGALRM, alarm_sig);
+       signal(SIGTERM, catch_sig);
+       signal(SIGQUIT, core_sig);
+       signal(SIGILL, core_sig);
+       signal(SIGTRAP, core_sig);
+       signal(SIGIOT, core_sig);
+       signal(SIGABRT, core_sig);
+       signal(SIGFPE, core_sig);
+       signal(SIGBUS, core_sig);
+       signal(SIGSEGV, core_sig);
+       signal(SIGSYS, core_sig);
+       signal(SIGWINCH, winch_sig);
+       signal(SIGTSTP, suspend_sig);
+       sig = setjmp(restart);
+       if (sig != 0) {
+               msg = "";
+               if (sig == SIGWINCH)
+                       msg = "(window resize)";
+               if (sig == SIGHUP)
+                       msg = "(hangup)";
+               if (sig == SIGINT)
+                       msg = "(interrupt)";
+               if (sig == SIGTERM)
+                       msg = "(terminate)";
+               if (sig == SIGBUS)
+                       msg = "(bus error)";
+               if (sig == SIGSEGV)
+                       msg = "(I tried to touch invalid memory)";
+               if (sig == SIGALRM)
+                       msg = "(alarm)";
+
+               psbs("-- caught signal %d %s--", sig, msg);
+       }
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+
+       editing = 1;
+       cmd_mode = 0;           // 0=command  1=insert  2='R'eplace
+       cmdcnt = 0;
+       tabstop = 8;
+       offset = 0;                     // no horizontal offset
+       c = '\0';
+#ifdef BB_FEATURE_VI_DOT_CMD
+       if (last_modifying_cmd != 0)
+               free(last_modifying_cmd);
+       if (ioq_start != NULL)
+               free(ioq_start);
+       ioq = ioq_start = last_modifying_cmd = 0;
+       adding2q = 0;
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+       redraw(TRUE);
+       show_status_line();
+
+       //------This is the main Vi cmd handling loop -----------------------
+       while (editing > 0) {
+#ifdef BB_FEATURE_VI_CRASHME
+               if (crashme > 0) {
+                       if ((end - text) > 1) {
+                               crash_dummy();  // generate a random command
+                       } else {
+                               crashme = 0;
+                               dot =
+                                       string_insert(text, (Byte *) "\n\n#####  Ran out of text to work on.  #####\n\n");      // insert the string
+                               refresh(FALSE);
+                       }
+               }
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+               last_input_char = c = get_one_char();   // get a cmd from user
+#ifdef BB_FEATURE_VI_YANKMARK
+               // save a copy of the current line- for the 'U" command
+               if (begin_line(dot) != cur_line) {
+                       cur_line = begin_line(dot);
+                       text_yank(begin_line(dot), end_line(dot), Ureg);
+               }
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_DOT_CMD
+               // These are commands that change text[].
+               // Remember the input for the "." command
+               if (!adding2q && ioq_start == 0
+                       && strchr((char *) modifying_cmds, c) != NULL) {
+                       start_new_cmd_q(c);
+               }
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+               do_cmd(c);              // execute the user command
+               //
+               // poll to see if there is input already waiting. if we are
+               // not able to display output fast enough to keep up, skip
+               // the display update until we catch up with input.
+               if (mysleep(0) == 0) {
+                       // no input pending- so update output
+                       refresh(FALSE);
+                       show_status_line();
+               }
+#ifdef BB_FEATURE_VI_CRASHME
+               if (crashme > 0)
+                       crash_test();   // test editor variables
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+       }
+       //-------------------------------------------------------------------
+
+       place_cursor(rows, 0);  // go to bottom of screen
+       clear_to_eol();         // Erase to end of line
+       cookmode();
+}
+
+static Byte readbuffer[BUFSIZ];
+
+#ifdef BB_FEATURE_VI_CRASHME
+static int totalcmds = 0;
+static int Mp = 85;            // Movement command Probability
+static int Np = 90;            // Non-movement command Probability
+static int Dp = 96;            // Delete command Probability
+static int Ip = 97;            // Insert command Probability
+static int Yp = 98;            // Yank command Probability
+static int Pp = 99;            // Put command Probability
+static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
+char chars[20] = "\t012345 abcdABCD-=.$";
+char *words[20] = { "this", "is", "a", "test",
+       "broadcast", "the", "emergency", "of",
+       "system", "quick", "brown", "fox",
+       "jumped", "over", "lazy", "dogs",
+       "back", "January", "Febuary", "March"
+};
+char *lines[20] = {
+       "You should have received a copy of the GNU General Public License\n",
+       "char c, cm, *cmd, *cmd1;\n",
+       "generate a command by percentages\n",
+       "Numbers may be typed as a prefix to some commands.\n",
+       "Quit, discarding changes!\n",
+       "Forced write, if permission originally not valid.\n",
+       "In general, any ex or ed command (such as substitute or delete).\n",
+       "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
+       "Please get w/ me and I will go over it with you.\n",
+       "The following is a list of scheduled, committed changes.\n",
+       "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
+       "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
+       "Any question about transactions please contact Sterling Huxley.\n",
+       "I will try to get back to you by Friday, December 31.\n",
+       "This Change will be implemented on Friday.\n",
+       "Let me know if you have problems accessing this;\n",
+       "Sterling Huxley recently added you to the access list.\n",
+       "Would you like to go to lunch?\n",
+       "The last command will be automatically run.\n",
+       "This is too much english for a computer geek.\n",
+};
+char *multilines[20] = {
+       "You should have received a copy of the GNU General Public License\n",
+       "char c, cm, *cmd, *cmd1;\n",
+       "generate a command by percentages\n",
+       "Numbers may be typed as a prefix to some commands.\n",
+       "Quit, discarding changes!\n",
+       "Forced write, if permission originally not valid.\n",
+       "In general, any ex or ed command (such as substitute or delete).\n",
+       "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
+       "Please get w/ me and I will go over it with you.\n",
+       "The following is a list of scheduled, committed changes.\n",
+       "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
+       "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
+       "Any question about transactions please contact Sterling Huxley.\n",
+       "I will try to get back to you by Friday, December 31.\n",
+       "This Change will be implemented on Friday.\n",
+       "Let me know if you have problems accessing this;\n",
+       "Sterling Huxley recently added you to the access list.\n",
+       "Would you like to go to lunch?\n",
+       "The last command will be automatically run.\n",
+       "This is too much english for a computer geek.\n",
+};
+
+// create a random command to execute
+static void crash_dummy()
+{
+       static int sleeptime;   // how long to pause between commands
+       char c, cm, *cmd, *cmd1;
+       int i, cnt, thing, rbi, startrbi, percent;
+
+       // "dot" movement commands
+       cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
+
+       // is there already a command running?
+       if (strlen((char *) readbuffer) > 0)
+               goto cd1;
+  cd0:
+       startrbi = rbi = 0;
+       sleeptime = 0;          // how long to pause between commands
+       memset(readbuffer, '\0', BUFSIZ - 1);   // clear the read buffer
+       // generate a command by percentages
+       percent = (int) lrand48() % 100;        // get a number from 0-99
+       if (percent < Mp) {     //  Movement commands
+               // available commands
+               cmd = cmd1;
+               M++;
+       } else if (percent < Np) {      //  non-movement commands
+               cmd = "mz<>\'\"";       // available commands
+               N++;
+       } else if (percent < Dp) {      //  Delete commands
+               cmd = "dx";             // available commands
+               D++;
+       } else if (percent < Ip) {      //  Inset commands
+               cmd = "iIaAsrJ";        // available commands
+               I++;
+       } else if (percent < Yp) {      //  Yank commands
+               cmd = "yY";             // available commands
+               Y++;
+       } else if (percent < Pp) {      //  Put commands
+               cmd = "pP";             // available commands
+               P++;
+       } else {
+               // We do not know how to handle this command, try again
+               U++;
+               goto cd0;
+       }
+       // randomly pick one of the available cmds from "cmd[]"
+       i = (int) lrand48() % strlen(cmd);
+       cm = cmd[i];
+       if (strchr(":\024", cm))
+               goto cd0;               // dont allow these commands
+       readbuffer[rbi++] = cm; // put cmd into input buffer
+
+       // now we have the command-
+       // there are 1, 2, and multi char commands
+       // find out which and generate the rest of command as necessary
+       if (strchr("dmryz<>\'\"", cm)) {        // 2-char commands
+               cmd1 = " \n\r0$^-+wWeEbBhjklHL";
+               if (cm == 'm' || cm == '\'' || cm == '\"') {    // pick a reg[]
+                       cmd1 = "abcdefghijklmnopqrstuvwxyz";
+               }
+               thing = (int) lrand48() % strlen(cmd1); // pick a movement command
+               c = cmd1[thing];
+               readbuffer[rbi++] = c;  // add movement to input buffer
+       }
+       if (strchr("iIaAsc", cm)) {     // multi-char commands
+               if (cm == 'c') {
+                       // change some thing
+                       thing = (int) lrand48() % strlen(cmd1); // pick a movement command
+                       c = cmd1[thing];
+                       readbuffer[rbi++] = c;  // add movement to input buffer
+               }
+               thing = (int) lrand48() % 4;    // what thing to insert
+               cnt = (int) lrand48() % 10;     // how many to insert
+               for (i = 0; i < cnt; i++) {
+                       if (thing == 0) {       // insert chars
+                               readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
+                       } else if (thing == 1) {        // insert words
+                               strcat((char *) readbuffer, words[(int) lrand48() % 20]);
+                               strcat((char *) readbuffer, " ");
+                               sleeptime = 0;  // how fast to type
+                       } else if (thing == 2) {        // insert lines
+                               strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
+                               sleeptime = 0;  // how fast to type
+                       } else {        // insert multi-lines
+                               strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
+                               sleeptime = 0;  // how fast to type
+                       }
+               }
+               strcat((char *) readbuffer, "\033");
+       }
+  cd1:
+       totalcmds++;
+       if (sleeptime > 0)
+               (void) mysleep(sleeptime);      // sleep 1/100 sec
+}
+
+// test to see if there are any errors
+static void crash_test()
+{
+       static time_t oldtim;
+       time_t tim;
+       char d[2], buf[100], msg[BUFSIZ];
+
+       msg[0] = '\0';
+       if (end < text) {
+               strcat((char *) msg, "end<text ");
+       }
+       if (end > textend) {
+               strcat((char *) msg, "end>textend ");
+       }
+       if (dot < text) {
+               strcat((char *) msg, "dot<text ");
+       }
+       if (dot > end) {
+               strcat((char *) msg, "dot>end ");
+       }
+       if (screenbegin < text) {
+               strcat((char *) msg, "screenbegin<text ");
+       }
+       if (screenbegin > end - 1) {
+               strcat((char *) msg, "screenbegin>end-1 ");
+       }
+
+       if (strlen(msg) > 0) {
+               alarm(0);
+               sprintf(buf, "\n\n%d: \'%c\' ", totalcmds, last_input_char);
+               write(1, buf, strlen(buf));
+               write(1, msg, strlen(msg));
+               write(1, "\n\n\n", 3);
+               write(1, "\033[7m[Hit return to continue]\033[0m", 32);
+               while (read(0, d, 1) > 0) {
+                       if (d[0] == '\n' || d[0] == '\r')
+                               break;
+               }
+               alarm(3);
+       }
+       tim = (time_t) time((time_t *) 0);
+       if (tim >= (oldtim + 3)) {
+               sprintf((char *) status_buffer,
+                               "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
+                               totalcmds, M, N, I, D, Y, P, U, end - text + 1);
+               oldtim = tim;
+       }
+       return;
+}
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+
+//---------------------------------------------------------------------
+//----- the Ascii Chart -----------------------------------------------
+//
+//  00 nul   01 soh   02 stx   03 etx   04 eot   05 enq   06 ack   07 bel
+//  08 bs    09 ht    0a nl    0b vt    0c np    0d cr    0e so    0f si
+//  10 dle   11 dc1   12 dc2   13 dc3   14 dc4   15 nak   16 syn   17 etb
+//  18 can   19 em    1a sub   1b esc   1c fs    1d gs    1e rs    1f us
+//  20 sp    21 !     22 "     23 #     24 $     25 %     26 &     27 '
+//  28 (     29 )     2a *     2b +     2c ,     2d -     2e .     2f /
+//  30 0     31 1     32 2     33 3     34 4     35 5     36 6     37 7
+//  38 8     39 9     3a :     3b ;     3c <     3d =     3e >     3f ?
+//  40 @     41 A     42 B     43 C     44 D     45 E     46 F     47 G
+//  48 H     49 I     4a J     4b K     4c L     4d M     4e N     4f O
+//  50 P     51 Q     52 R     53 S     54 T     55 U     56 V     57 W
+//  58 X     59 Y     5a Z     5b [     5c \     5d ]     5e ^     5f _
+//  60 `     61 a     62 b     63 c     64 d     65 e     66 f     67 g
+//  68 h     69 i     6a j     6b k     6c l     6d m     6e n     6f o
+//  70 p     71 q     72 r     73 s     74 t     75 u     76 v     77 w
+//  78 x     79 y     7a z     7b {     7c |     7d }     7e ~     7f del
+//---------------------------------------------------------------------
+
+//----- Execute a Vi Command -----------------------------------
+static void do_cmd(Byte c)
+{
+       Byte c1, *p, *q, *msg, buf[9], *save_dot;
+       int cnt, i, j, dir, yf;
+
+       c1 = c;                         // quiet the compiler
+       cnt = yf = dir = 0;     // quiet the compiler
+       p = q = save_dot = msg = buf;   // quiet the compiler
+       memset(buf, '\0', 9);   // clear buf
+       if (cmd_mode == 2) {
+               // we are 'R'eplacing the current *dot with new char
+               if (*dot == '\n') {
+                       // don't Replace past E-o-l
+                       cmd_mode = 1;   // convert to insert
+               } else {
+                       if (1 <= c && c <= 127) {       // only ASCII chars
+                               if (c != 27)
+                                       dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
+                               dot = char_insert(dot, c);      // insert new char
+                       }
+                       goto dc1;
+               }
+       }
+       if (cmd_mode == 1) {
+               // insert the char c at "dot"
+               if (1 <= c && c <= 127) {
+                       dot = char_insert(dot, c);      // only ASCII chars
+               }
+               goto dc1;
+       }
+
+       switch (c) {
+               //case 0x01:        // soh
+               //case 0x09:        // ht
+               //case 0x0b:        // vt
+               //case 0x0e:        // so
+               //case 0x0f:        // si
+               //case 0x10:        // dle
+               //case 0x11:        // dc1
+               //case 0x12:        // dc2
+               //case 0x13:        // dc3
+       case 0x14:                      // dc4  ctrl-T
+#ifdef BB_FEATURE_VI_CRASHME
+               crashme = (crashme == 0) ? 1 : 0;
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+               break;
+               //case 0x16:        // syn
+               //case 0x17:        // etb
+               //case 0x18:        // can
+               //case 0x1c:        // fs
+               //case 0x1d:        // gs
+               //case 0x1e:        // rs
+               //case 0x1f:        // us
+               //case '!':     // !- 
+               //case '#':     // #- 
+               //case '&':     // &- 
+               //case '(':     // (- 
+               //case ')':     // )- 
+               //case '*':     // *- 
+               //case ',':     // ,- 
+               //case '=':     // =- 
+               //case '@':     // @- 
+               //case 'F':     // F- 
+               //case 'G':     // G- 
+               //case 'K':     // K- 
+               //case 'M':     // M- 
+               //case 'Q':     // Q- 
+               //case 'S':     // S- 
+               //case 'T':     // T- 
+               //case 'V':     // V- 
+               //case '[':     // [- 
+               //case '\\':        // \- 
+               //case ']':     // ]- 
+               //case '_':     // _- 
+               //case '`':     // `- 
+               //case 'g':     // g- 
+               //case 'm':     // m- 
+               //case 't':     // t- 
+               //case 'v':     // v- 
+       default:                        // unrecognised command
+               buf[0] = c;
+               buf[1] = '\0';
+               if (c <= ' ') {
+                       buf[0] = '^';
+                       buf[1] = c + '@';
+                       buf[2] = '\0';
+               }
+               ni((Byte *) buf);
+               end_cmd_q();    // stop adding to q
+       case 0x00:                      // nul- ignore
+               break;
+       case 2:                 // ctrl-B  scroll up   full screen
+       case VI_K_PAGEUP:       // Cursor Key Page Up
+               dot_scroll(rows - 2, -1);
+               break;
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+       case 0x03:                      // ctrl-C   interrupt
+               longjmp(restart, 1);
+               break;
+       case 26:                        // ctrl-Z suspend
+               suspend_sig(SIGTSTP);
+               break;
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+       case 4:                 // ctrl-D  scroll down half screen
+               dot_scroll((rows - 2) / 2, 1);
+               break;
+       case 5:                 // ctrl-E  scroll down one line
+               dot_scroll(1, 1);
+               break;
+       case 6:                 // ctrl-F  scroll down full screen
+       case VI_K_PAGEDOWN:     // Cursor Key Page Down
+               dot_scroll(rows - 2, 1);
+               break;
+       case 7:                 // ctrl-G  show current status
+               edit_status();
+               break;
+       case 'h':                       // h- move left
+       case VI_K_LEFT: // cursor key Left
+       case 8:                 // ^h- move left    (This may be ERASE char)
+       case 127:                       // DEL- move left   (This may be ERASE char)
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_left();
+               break;
+       case 10:                        // Newline ^J
+       case 'j':                       // j- goto next line, same col
+       case VI_K_DOWN: // cursor key Down
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_next();             // go to next B-o-l
+               dot = move_to_col(dot, ccol + offset);  // try stay in same col
+               break;
+       case 12:                        // ctrl-L  force redraw whole screen
+               place_cursor(0, 0);     // put cursor in correct place
+               clear_to_eos(); // tel terminal to erase display
+               (void) mysleep(10);
+               screen_erase(); // erase the internal screen buffer
+               refresh(TRUE);  // this will redraw the entire display
+               break;
+       case 13:                        // Carriage Return ^M
+       case '+':                       // +- goto next line
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_next();
+               dot_skip_over_ws();
+               break;
+       case 21:                        // ctrl-U  scroll up   half screen
+               dot_scroll((rows - 2) / 2, -1);
+               break;
+       case 25:                        // ctrl-Y  scroll up one line
+               dot_scroll(1, -1);
+               break;
+       case 0x1b:                      // esc
+               if (cmd_mode == 0)
+                       indicate_error(c);
+               cmd_mode = 0;   // stop insrting
+               end_cmd_q();
+               *status_buffer = '\0';  // clear status buffer
+               break;
+       case ' ':                       // move right
+       case 'l':                       // move right
+       case VI_K_RIGHT:        // Cursor Key Right
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_right();
+               break;
+#ifdef BB_FEATURE_VI_YANKMARK
+       case '"':                       // "- name a register to use for Delete/Yank
+               c1 = get_one_char();
+               c1 = tolower(c1);
+               if (islower(c1)) {
+                       YDreg = c1 - 'a';
+               } else {
+                       indicate_error(c);
+               }
+               break;
+       case '\'':                      // '- goto a specific mark
+               c1 = get_one_char();
+               c1 = tolower(c1);
+               if (islower(c1)) {
+                       c1 = c1 - 'a';
+                       // get the b-o-l
+                       q = mark[(int) c1];
+                       if (text <= q && q < end) {
+                               dot = q;
+                               dot_begin();    // go to B-o-l
+                               dot_skip_over_ws();
+                       }
+               } else if (c1 == '\'') {        // goto previous context
+                       dot = swap_context(dot);        // swap current and previous context
+                       dot_begin();    // go to B-o-l
+                       dot_skip_over_ws();
+               } else {
+                       indicate_error(c);
+               }
+               break;
+       case 'm':                       // m- Mark a line
+               // this is really stupid.  If there are any inserts or deletes
+               // between text[0] and dot then this mark will not point to the
+               // correct location! It could be off by many lines!
+               // Well..., at least its quick and dirty.
+               c1 = get_one_char();
+               c1 = tolower(c1);
+               if (islower(c1)) {
+                       c1 = c1 - 'a';
+                       // remember the line
+                       mark[(int) c1] = dot;
+               } else {
+                       indicate_error(c);
+               }
+               break;
+       case 'P':                       // P- Put register before
+       case 'p':                       // p- put register after
+               p = reg[YDreg];
+               if (p == 0) {
+                       psbs("Nothing in register %c", what_reg());
+                       break;
+               }
+               // are we putting whole lines or strings
+               if (strchr((char *) p, '\n') != NULL) {
+                       if (c == 'P') {
+                               dot_begin();    // putting lines- Put above
+                       }
+                       if (c == 'p') {
+                               // are we putting after very last line?
+                               if (end_line(dot) == (end - 1)) {
+                                       dot = end;      // force dot to end of text[]
+                               } else {
+                                       dot_next();     // next line, then put before
+                               }
+                       }
+               } else {
+                       if (c == 'p')
+                               dot_right();    // move to right, can move to NL
+               }
+               dot = string_insert(dot, p);    // insert the string
+               end_cmd_q();    // stop adding to q
+               break;
+       case 'u':                       // u- 
+       case 'U':                       // U- Undo; replace current line with original version
+               if (reg[Ureg] != 0) {
+                       p = begin_line(dot);
+                       q = end_line(dot);
+                       p = text_hole_delete(p, q);     // delete cur line
+                       p = string_insert(p, reg[Ureg]);        // insert orig line
+                       dot = p;
+                       dot_skip_over_ws();
+               }
+               break;
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+       case '$':                       // $- goto end of line
+       case VI_K_END:          // Cursor Key End
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot = end_line(dot + 1);
+               break;
+       case '%':                       // %- find matching char of pair () [] {}
+               for (q = dot; q < end && *q != '\n'; q++) {
+                       if (strchr("()[]{}", *q) != NULL) {
+                               // we found half of a pair
+                               p = find_pair(q, *q);
+                               if (p == NULL) {
+                                       indicate_error(c);
+                               } else {
+                                       dot = p;
+                               }
+                               break;
+                       }
+               }
+               if (*q == '\n')
+                       indicate_error(c);
+               break;
+       case 'f':                       // f- forward to a user specified char
+               last_forward_char = get_one_char();     // get the search char
+               //
+               // dont seperate these two commands. 'f' depends on ';'
+               //
+               //**** fall thru to ... 'i'
+       case ';':                       // ;- look at rest of line for last forward char
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               q = dot + 1;
+               while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
+                       q++;
+               }
+               if (*q == last_forward_char)
+                       dot = q;
+               break;
+       case '-':                       // -- goto prev line
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_prev();
+               dot_skip_over_ws();
+               break;
+#ifdef BB_FEATURE_VI_DOT_CMD
+       case '.':                       // .- repeat the last modifying command
+               // Stuff the last_modifying_cmd back into stdin
+               // and let it be re-executed.
+               if (last_modifying_cmd != 0) {
+                       ioq = ioq_start = (Byte *) strdup((char *) last_modifying_cmd);
+               }
+               break;
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+#ifdef BB_FEATURE_VI_SEARCH
+       case '?':                       // /- search for a pattern
+       case '/':                       // /- search for a pattern
+               buf[0] = c;
+               buf[1] = '\0';
+               q = get_input_line(buf);        // get input line- use "status line"
+               if (strlen((char *) q) == 1)
+                       goto dc3;       // if no pat re-use old pat
+               if (strlen((char *) q) > 1) {   // new pat- save it and find
+                       // there is a new pat
+                       if (last_search_pattern != 0) {
+                               free(last_search_pattern);
+                       }
+                       last_search_pattern = (Byte *) strdup((char *) q);
+                       goto dc3;       // now find the pattern
+               }
+               // user changed mind and erased the "/"-  do nothing
+               break;
+       case 'N':                       // N- backward search for last pattern
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dir = BACK;             // assume BACKWARD search
+               p = dot - 1;
+               if (last_search_pattern[0] == '?') {
+                       dir = FORWARD;
+                       p = dot + 1;
+               }
+               goto dc4;               // now search for pattern
+               break;
+       case 'n':                       // n- repeat search for last pattern
+               // search rest of text[] starting at next char
+               // if search fails return orignal "p" not the "p+1" address
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+         dc3:
+               if (last_search_pattern == 0) {
+                       msg = (Byte *) "No previous regular expression";
+                       goto dc2;
+               }
+               if (last_search_pattern[0] == '/') {
+                       dir = FORWARD;  // assume FORWARD search
+                       p = dot + 1;
+               }
+               if (last_search_pattern[0] == '?') {
+                       dir = BACK;
+                       p = dot - 1;
+               }
+         dc4:
+               q = char_search(p, last_search_pattern + 1, dir, FULL);
+               if (q != NULL) {
+                       dot = q;        // good search, update "dot"
+                       msg = (Byte *) "";
+                       goto dc2;
+               }
+               // no pattern found between "dot" and "end"- continue at top
+               p = text;
+               if (dir == BACK) {
+                       p = end - 1;
+               }
+               q = char_search(p, last_search_pattern + 1, dir, FULL);
+               if (q != NULL) {        // found something
+                       dot = q;        // found new pattern- goto it
+                       msg = (Byte *) "search hit BOTTOM, continuing at TOP";
+                       if (dir == BACK) {
+                               msg = (Byte *) "search hit TOP, continuing at BOTTOM";
+                       }
+               } else {
+                       msg = (Byte *) "Pattern not found";
+               }
+         dc2:
+               psbs("%s", msg);
+               break;
+       case '{':                       // {- move backward paragraph
+               q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
+               if (q != NULL) {        // found blank line
+                       dot = next_line(q);     // move to next blank line
+               }
+               break;
+       case '}':                       // }- move forward paragraph
+               q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
+               if (q != NULL) {        // found blank line
+                       dot = next_line(q);     // move to next blank line
+               }
+               break;
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+       case '0':                       // 0- goto begining of line
+       case '1':                       // 1- 
+       case '2':                       // 2- 
+       case '3':                       // 3- 
+       case '4':                       // 4- 
+       case '5':                       // 5- 
+       case '6':                       // 6- 
+       case '7':                       // 7- 
+       case '8':                       // 8- 
+       case '9':                       // 9- 
+               if (c == '0' && cmdcnt < 1) {
+                       dot_begin();    // this was a standalone zero
+               } else {
+                       cmdcnt = cmdcnt * 10 + (c - '0');       // this 0 is part of a number
+               }
+               break;
+       case ':':                       // :- the colon mode commands
+#ifdef BB_FEATURE_VI_COLON
+               p = get_input_line((Byte *) ":");       // get input line- use "status line"
+               colon(p);               // execute the command
+#else                                                  /* BB_FEATURE_VI_COLON */
+               *status_buffer = '\0';  // clear the status buffer
+               place_cursor(rows - 1, 0);      // go to Status line, bottom of screen
+               clear_to_eol(); // clear the line
+               write(1, ":", 1);       // write out the : prompt
+               for (cnt = 0; cnt < 8; cnt++) {
+                       c1 = get_one_char();
+                       if (c1 == '\n' || c1 == '\r') {
+                               break;
+                       }
+                       buf[cnt] = c1;
+                       buf[cnt + 1] = '\0';
+                       write(1, buf + cnt, 1); // echo the char
+               }
+               cnt = strlen((char *) buf);
+               if (strncasecmp((char *) buf, "quit", cnt) == 0 ||
+                       strncasecmp((char *) buf, "q!", cnt) == 0) {    // delete lines
+                       if (file_modified == TRUE && buf[1] != '!') {
+                               psbs("No write since last change (:quit! overrides)");
+                       } else {
+                               editing = 0;
+                       }
+               } else if (strncasecmp((char *) buf, "write", cnt) == 0 ||
+                                  strncasecmp((char *) buf, "wq", cnt) == 0) {
+                       cnt = file_write(cfn, text, end - 1);
+                       file_modified = FALSE;
+                       psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
+                       if (buf[1] == 'q') {
+                               editing = 0;
+                       }
+               } else {                // unrecognised cmd
+                       ni((Byte *) buf);
+               }
+#endif                                                 /* BB_FEATURE_VI_COLON */
+               break;
+       case '<':                       // <- Left  shift something
+       case '>':                       // >- Right shift something
+               cnt = count_lines(text, dot);   // remember what line we are on
+               c1 = get_one_char();    // get the type of thing to delete
+               find_range(&p, &q, c1);
+               (void) yank_delete(p, q, 1, YANKONLY);  // save copy before change
+               p = begin_line(p);
+               q = end_line(q);
+               i = count_lines(p, q);  // # of lines we are shifting
+               for ( ; i > 0; i--, p = next_line(p)) {
+                       if (c == '<') {
+                               // shift left- remove tab or 8 spaces
+                               if (*p == '\t') {
+                                       // shrink buffer 1 char
+                                       (void) text_hole_delete(p, p);
+                               } else if (*p == ' ') {
+                                       // we should be calculating columns, not just SPACE
+                                       for (j = 0; *p == ' ' && j < tabstop; j++) {
+                                               (void) text_hole_delete(p, p);
+                                       }
+                               }
+                       } else if (c == '>') {
+                               // shift right -- add tab or 8 spaces
+                               (void) char_insert(p, '\t');
+                       }
+               }
+               dot = find_line(cnt);   // what line were we on
+               dot_skip_over_ws();
+               end_cmd_q();    // stop adding to q
+               break;
+       case 'A':                       // A- append at e-o-l
+               dot_end();              // go to e-o-l
+               //**** fall thru to ... 'a'
+       case 'a':                       // a- append after current char
+               if (*dot != '\n')
+                       dot++;
+               goto dc_i;
+               break;
+       case 'B':                       // B- back a blank-delimited Word
+       case 'E':                       // E- end of a blank-delimited word
+       case 'W':                       // W- forward a blank-delimited word
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dir = FORWARD;
+               if (c == 'B')
+                       dir = BACK;
+               if (c == 'W' || isspace(dot[dir])) {
+                       dot = skip_thing(dot, 1, dir, S_TO_WS);
+                       dot = skip_thing(dot, 2, dir, S_OVER_WS);
+               }
+               if (c != 'W')
+                       dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
+               break;
+       case 'C':                       // C- Change to e-o-l
+       case 'D':                       // D- delete to e-o-l
+               save_dot = dot;
+               dot = dollar_line(dot); // move to before NL
+               // copy text into a register and delete
+               dot = yank_delete(save_dot, dot, 0, YANKDEL);   // delete to e-o-l
+               if (c == 'C')
+                       goto dc_i;      // start inserting
+#ifdef BB_FEATURE_VI_DOT_CMD
+               if (c == 'D')
+                       end_cmd_q();    // stop adding to q
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+               break;
+       case 'H':                       // H- goto top line on screen
+               dot = screenbegin;
+               if (cmdcnt > (rows - 1)) {
+                       cmdcnt = (rows - 1);
+               }
+               if (cmdcnt-- > 1) {
+                       do_cmd('+');
+               }                               // repeat cnt
+               dot_skip_over_ws();
+               break;
+       case 'I':                       // I- insert before first non-blank
+               dot_begin();    // 0
+               dot_skip_over_ws();
+               //**** fall thru to ... 'i'
+       case 'i':                       // i- insert before current char
+       case VI_K_INSERT:       // Cursor Key Insert
+         dc_i:
+               cmd_mode = 1;   // start insrting
+               psb("-- Insert --");
+               break;
+       case 'J':                       // J- join current and next lines together
+               if (cmdcnt-- > 2) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_end();              // move to NL
+               if (dot < end - 1) {    // make sure not last char in text[]
+                       *dot++ = ' ';   // replace NL with space
+                       while (isblnk(*dot)) {  // delete leading WS
+                               dot_delete();
+                       }
+               }
+               end_cmd_q();    // stop adding to q
+               break;
+       case 'L':                       // L- goto bottom line on screen
+               dot = end_screen();
+               if (cmdcnt > (rows - 1)) {
+                       cmdcnt = (rows - 1);
+               }
+               if (cmdcnt-- > 1) {
+                       do_cmd('-');
+               }                               // repeat cnt
+               dot_begin();
+               dot_skip_over_ws();
+               break;
+       case 'O':                       // O- open a empty line above
+               //    0i\n\033-i
+               p = begin_line(dot);
+               if (p[-1] == '\n') {
+                       dot_prev();
+       case 'o':                       // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
+                       dot_end();
+                       dot = char_insert(dot, '\n');
+               } else {
+                       dot_begin();    // 0
+                       dot = char_insert(dot, '\n');   // i\n\033
+                       dot_prev();     // -
+               }
+               goto dc_i;
+               break;
+       case 'R':                       // R- continuous Replace char
+               cmd_mode = 2;
+               psb("-- Replace --");
+               break;
+       case 'X':                       // X- delete char before dot
+       case 'x':                       // x- delete the current char
+       case 's':                       // s- substitute the current char
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dir = 0;
+               if (c == 'X')
+                       dir = -1;
+               if (dot[dir] != '\n') {
+                       if (c == 'X')
+                               dot--;  // delete prev char
+                       dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
+               }
+               if (c == 's')
+                       goto dc_i;      // start insrting
+               end_cmd_q();    // stop adding to q
+               break;
+       case 'Z':                       // Z- if modified, {write}; exit
+               // ZZ means to save file (if necessary), then exit
+               c1 = get_one_char();
+               if (c1 != 'Z') {
+                       indicate_error(c);
+                       break;
+               }
+               if (file_modified == TRUE
+#ifdef BB_FEATURE_VI_READONLY
+                       && readonly == FALSE
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+                       ) {
+                       cnt = file_write(cfn, text, end - 1);
+                       if (cnt == (end - 1 - text + 1)) {
+                               editing = 0;
+                       }
+               } else {
+                       editing = 0;
+               }
+               break;
+       case '^':                       // ^- move to first non-blank on line
+               dot_begin();
+               dot_skip_over_ws();
+               break;
+       case 'b':                       // b- back a word
+       case 'e':                       // e- end of word
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dir = FORWARD;
+               if (c == 'b')
+                       dir = BACK;
+               if ((dot + dir) < text || (dot + dir) > end - 1)
+                       break;
+               dot += dir;
+               if (isspace(*dot)) {
+                       dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
+               }
+               if (isalnum(*dot) || *dot == '_') {
+                       dot = skip_thing(dot, 1, dir, S_END_ALNUM);
+               } else if (ispunct(*dot)) {
+                       dot = skip_thing(dot, 1, dir, S_END_PUNCT);
+               }
+               break;
+       case 'c':                       // c- change something
+       case 'd':                       // d- delete something
+#ifdef BB_FEATURE_VI_YANKMARK
+       case 'y':                       // y- yank   something
+       case 'Y':                       // Y- Yank a line
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+               yf = YANKDEL;   // assume either "c" or "d"
+#ifdef BB_FEATURE_VI_YANKMARK
+               if (c == 'y' || c == 'Y')
+                       yf = YANKONLY;
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+               c1 = 'y';
+               if (c != 'Y')
+                       c1 = get_one_char();    // get the type of thing to delete
+               find_range(&p, &q, c1);
+               if (c1 == 27) { // ESC- user changed mind and wants out
+                       c = c1 = 27;    // Escape- do nothing
+               } else if (strchr("wW", c1)) {
+                       if (c == 'c') {
+                               // don't include trailing WS as part of word
+                               while (isblnk(*q)) {
+                                       if (q <= text || q[-1] == '\n')
+                                               break;
+                                       q--;
+                               }
+                       }
+                       dot = yank_delete(p, q, 0, yf); // delete word
+               } else if (strchr("0bBeE$", c1)) {
+                       // single line copy text into a register and delete
+                       dot = yank_delete(p, q, 0, yf); // delete word
+               } else if (strchr("cdykjHL+-{}\r\n", c1)) {
+                       // multiple line copy text into a register and delete
+                       dot = yank_delete(p, q, 1, yf); // delete lines
+                       if (c == 'd') {
+                               dot_begin();
+                               dot_skip_over_ws();
+                       }
+               } else {
+                       // could not recognize object
+                       c = c1 = 27;    // error-
+                       indicate_error(c);
+               }
+               if (c1 != 27) {
+                       // if CHANGING, not deleting, start inserting after the delete
+                       if (c == 'c') {
+                               strcpy((char *) buf, "Change");
+                               goto dc_i;      // start inserting
+                       }
+                       if (c == 'd') {
+                               strcpy((char *) buf, "Delete");
+                       }
+#ifdef BB_FEATURE_VI_YANKMARK
+                       if (c == 'y' || c == 'Y') {
+                               strcpy((char *) buf, "Yank");
+                       }
+                       p = reg[YDreg];
+                       q = p + strlen((char *) p);
+                       for (cnt = 0; p <= q; p++) {
+                               if (*p == '\n')
+                                       cnt++;
+                       }
+                       psb("%s %d lines (%d chars) using [%c]",
+                               buf, cnt, strlen((char *) reg[YDreg]), what_reg());
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+                       end_cmd_q();    // stop adding to q
+               }
+               break;
+       case 'k':                       // k- goto prev line, same col
+       case VI_K_UP:           // cursor key Up
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_prev();
+               dot = move_to_col(dot, ccol + offset);  // try stay in same col
+               break;
+       case 'r':                       // r- replace the current char with user input
+               c1 = get_one_char();    // get the replacement char
+               if (*dot != '\n') {
+                       *dot = c1;
+                       file_modified = TRUE;   // has the file been modified
+               }
+               end_cmd_q();    // stop adding to q
+               break;
+       case 'w':                       // w- forward a word
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               if (isalnum(*dot) || *dot == '_') {     // we are on ALNUM
+                       dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
+               } else if (ispunct(*dot)) {     // we are on PUNCT
+                       dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
+               }
+               if (dot < end - 1)
+                       dot++;          // move over word
+               if (isspace(*dot)) {
+                       dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
+               }
+               break;
+       case 'z':                       // z-
+               c1 = get_one_char();    // get the replacement char
+               cnt = 0;
+               if (c1 == '.')
+                       cnt = (rows - 2) / 2;   // put dot at center
+               if (c1 == '-')
+                       cnt = rows - 2; // put dot at bottom
+               screenbegin = begin_line(dot);  // start dot at top
+               dot_scroll(cnt, -1);
+               break;
+       case '|':                       // |- move to column "cmdcnt"
+               dot = move_to_col(dot, cmdcnt - 1);     // try to move to column
+               break;
+       case '~':                       // ~- flip the case of letters   a-z -> A-Z
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               if (islower(*dot)) {
+                       *dot = toupper(*dot);
+                       file_modified = TRUE;   // has the file been modified
+               } else if (isupper(*dot)) {
+                       *dot = tolower(*dot);
+                       file_modified = TRUE;   // has the file been modified
+               }
+               dot_right();
+               end_cmd_q();    // stop adding to q
+               break;
+               //----- The Cursor and Function Keys -----------------------------
+       case VI_K_HOME: // Cursor Key Home
+               dot_begin();
+               break;
+               // The Fn keys could point to do_macro which could translate them
+       case VI_K_FUN1: // Function Key F1
+       case VI_K_FUN2: // Function Key F2
+       case VI_K_FUN3: // Function Key F3
+       case VI_K_FUN4: // Function Key F4
+       case VI_K_FUN5: // Function Key F5
+       case VI_K_FUN6: // Function Key F6
+       case VI_K_FUN7: // Function Key F7
+       case VI_K_FUN8: // Function Key F8
+       case VI_K_FUN9: // Function Key F9
+       case VI_K_FUN10:        // Function Key F10
+       case VI_K_FUN11:        // Function Key F11
+       case VI_K_FUN12:        // Function Key F12
+               break;
+       }
+
+  dc1:
+       // if text[] just became empty, add back an empty line
+       if (end == text) {
+               (void) char_insert(text, '\n'); // start empty buf with dummy line
+               dot = text;
+       }
+       // it is OK for dot to exactly equal to end, otherwise check dot validity
+       if (dot != end) {
+               dot = bound_dot(dot);   // make sure "dot" is valid
+       }
+#ifdef BB_FEATURE_VI_YANKMARK
+       check_context(c);       // update the current context
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+
+       if (!isdigit(c))
+               cmdcnt = 0;             // cmd was not a number, reset cmdcnt
+       cnt = dot - begin_line(dot);
+       // Try to stay off of the Newline
+       if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
+               dot--;
+}
+
+//----- The Colon commands -------------------------------------
+#ifdef BB_FEATURE_VI_COLON
+static Byte *get_address(Byte * p, int *addr)  // get colon addr, if present
+{
+       int st;
+       Byte *q;
+
+#ifdef BB_FEATURE_VI_YANKMARK
+       Byte c;
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_SEARCH
+       Byte *pat, buf[1024];
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+
+       *addr = -1;                     // assume no addr
+       if (*p == '.') {        // the current line
+               p++;
+               q = begin_line(dot);
+               *addr = count_lines(text, q);
+#ifdef BB_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[(int) c];
+                       if (q != NULL) {        // is mark valid
+                               *addr = count_lines(text, q);   // count lines
+                       }
+               }
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_SEARCH
+       } else if (*p == '/') { // a search pattern
+               q = buf;
+               for (p++; *p; p++) {
+                       if (*p == '/')
+                               break;
+                       *q++ = *p;
+                       *q = '\0';
+               }
+               pat = (Byte *) strdup((char *) buf);    // save copy of pattern
+               if (*p == '/')
+                       p++;
+               q = char_search(dot, pat, FORWARD, FULL);
+               if (q != NULL) {
+                       *addr = count_lines(text, q);
+               }
+               free(pat);
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+       } 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((char *) p, "%d%n", addr, &st);
+               p += st;
+       } else {                        // I don't reconise this
+               // unrecognised address- assume -1
+               *addr = -1;
+       }
+       return (p);
+}
+
+static void colon(Byte * buf)
+{
+       Byte c, *orig_buf, *buf1, *q, *r;
+       Byte *fn, cmd[100], args[100];
+       int i, l, li, ch, st, b, e;
+       int useforce, forced;
+
+       // :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"
+       //
+       if (strlen((char *) buf) <= 0)
+               goto vc1;
+       if (*buf == ':')
+               buf++;                  // move past the ':'
+
+       forced = useforce = FALSE;
+       li = st = ch = i = 0;
+       b = e = -1;
+       q = text;                       // assume 1,$ for the range
+       r = end - 1;
+       li = count_lines(text, end - 1);
+       fn = cfn;                       // default to current file
+
+       // look for optional FIRST address(es)  :.  :1  :1,9   :'q,'a
+       while (isblnk(*buf))
+               buf++;
+
+       // get FIRST addr, if present
+       buf = get_address(buf, &b);
+       while (isblnk(*buf))
+               buf++;
+       if (*buf == ',') {
+               buf++;
+               while (isblnk(*buf))
+                       buf++;
+               // look for SECOND address
+               buf = get_address(buf, &e);
+       }
+       while (isblnk(*buf))
+               buf++;
+
+       // remember orig command line
+       orig_buf = buf;
+
+       // get the COMMAND into cmd[]
+       buf1 = cmd;
+       *buf1 = '\0';
+       while (*buf != '\0') {
+               if (isspace(*buf))
+                       break;
+               *buf1++ = *buf++;
+               *buf1 = '\0';
+       }
+       // get any ARGuments
+       while (isblnk(*buf))
+               buf++;
+       strcpy((char *) args, (char *) buf);
+       if (cmd[strlen((char *) cmd) - 1] == '!') {
+               useforce = TRUE;
+               cmd[strlen((char *) cmd) - 1] = '\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((char *) cmd);
+       if (i == 0) {           // :123CR goto line #123
+               if (b >= 0) {
+                       dot = find_line(b);     // what line is #b
+                       dot_skip_over_ws();
+               }
+       } else if (strncmp((char *) cmd, "=", i) == 0) {        // where is the address
+               if (b < 0) {    // no addr given- use defaults
+                       b = e = count_lines(text, dot);
+               }
+               psb("%d", b);
+       } else if (strncasecmp((char *) 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);    // save, then delete lines
+               dot_skip_over_ws();
+       } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
+               // don't exit if the file been modified
+               if (file_modified == TRUE && useforce != TRUE) {
+                       psbs("No write since last change (:edit! overrides)");
+                       goto vc1;
+               }
+               fn = args;
+               if (strlen((char *) fn) <= 0) {
+                       // no file name given, re-edit current file
+                       fn = cfn;
+               }
+               if (cfn != 0)
+                       free(cfn);
+               cfn = (Byte *) strdup((char *) fn);     // make this the current file
+               // delete all the contents of text[]
+               new_text(2 * file_size(fn));
+               screenbegin = dot = end = text;
+               // insert new file
+               if (fn != 0) {
+                       ch = file_insert(fn, text, file_size(fn));
+               }
+               file_modified = FALSE;
+#ifdef BB_FEATURE_VI_YANKMARK
+               if (reg[Ureg] != 0)
+                       free(reg[Ureg]);        //   free orig line reg- for 'U'
+               if (reg[YDreg] != 0)
+                       free(reg[YDreg]);       //   free default yank/delete register
+               for (li = 0; li < 28; li++) {
+                       mark[li] = 0;
+               }                               // init the marks
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+               // how many lines in text[]?
+               li = count_lines(text, end - 1);
+               psb("\"%s\" %dL, %dC", cfn, li, ch);
+       } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
+               if (b != -1 || e != -1) {
+                       ni((Byte *) "No address allowed on this command");
+                       goto vc1;
+               }
+               if (strlen((char *) args) > 0) {
+                       // user wants a new filename
+                       if (cfn != NULL)
+                               free(cfn);
+                       cfn = (Byte *) strdup((char *) args);
+               } else {
+                       // user wants file status info
+                       edit_status();
+               }
+       } else if (strncasecmp((char *) cmd, "features", i) == 0) {     // what features are available
+               // print out values of all features
+               place_cursor(rows - 1, 0);      // go to Status line, bottom of screen
+               clear_to_eol(); // clear the line
+               cookmode();
+               show_help();
+               rawmode();
+               Hit_Return();
+       } else if (strncasecmp((char *) 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);
+               }
+               place_cursor(rows - 1, 0);      // go to Status line, bottom of screen
+               clear_to_eol(); // clear the line
+               write(1, "\r\n", 2);
+               for (; q <= r; q++) {
+                       c = *q;
+                       if (c > '~')
+                               standout_start();
+                       if (c == '\n') {
+                               write(1, "$\r", 2);
+                       } else if (*q < ' ') {
+                               write(1, "^", 1);
+                               c += '@';
+                       }
+                       write(1, &c, 1);
+                       if (c > '~')
+                               standout_end();
+               }
+#ifdef BB_FEATURE_VI_SET
+         vc2:
+#endif                                                 /* BB_FEATURE_VI_SET */
+               Hit_Return();
+       } else if ((strncasecmp((char *) cmd, "quit", i) == 0) ||       // Quit
+                          (strncasecmp((char *) cmd, "next", i) == 0)) {       // edit next file
+               if (useforce == TRUE) {
+                       // force end of argv list
+                       if (*cmd == 'q') {
+                               optind = save_argc;
+                       }
+                       editing = 0;
+                       goto vc1;
+               }
+               // don't exit if the file been modified
+               if (file_modified == TRUE) {
+                       psbs("No write since last change (:%s! overrides)",
+                                (*cmd == 'q' ? "quit" : "next"));
+                       goto vc1;
+               }
+               // are there other file to edit
+               if (*cmd == 'q' && optind < save_argc - 1) {
+                       psbs("%d more file to edit", (save_argc - optind - 1));
+                       goto vc1;
+               }
+               if (*cmd == 'n' && optind >= save_argc - 1) {
+                       psbs("No more files to edit");
+                       goto vc1;
+               }
+               editing = 0;
+       } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
+               fn = args;
+               if (strlen((char *) fn) <= 0) {
+                       psbs("No filename given");
+                       goto vc1;
+               }
+               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);
+               ch = file_insert(fn, q, file_size(fn));
+               if (ch < 0)
+                       goto vc1;       // nothing was inserted
+               // how many lines in text[]?
+               li = count_lines(q, q + ch - 1);
+               psb("\"%s\" %dL, %dC", fn, li, ch);
+               if (ch > 0) {
+                       // if the insert is before "dot" then we need to update
+                       if (q <= dot)
+                               dot += ch;
+                       file_modified = TRUE;
+               }
+       } else if (strncasecmp((char *) cmd, "rewind", i) == 0) {       // rewind cmd line args
+               if (file_modified == TRUE && useforce != TRUE) {
+                       psbs("No write since last change (:rewind! overrides)");
+               } else {
+                       // reset the filenames to edit
+                       optind = fn_start - 1;
+                       editing = 0;
+               }
+#ifdef BB_FEATURE_VI_SET
+       } else if (strncasecmp((char *) cmd, "set", i) == 0) {  // set or clear features
+               i = 0;                  // offset into args
+               if (strlen((char *) args) == 0) {
+                       // print out values of all options
+                       place_cursor(rows - 1, 0);      // go to Status line, bottom of screen
+                       clear_to_eol(); // clear the line
+                       printf("----------------------------------------\r\n");
+#ifdef BB_FEATURE_VI_SETOPTS
+                       if (!autoindent)
+                               printf("no");
+                       printf("autoindent ");
+                       if (!ignorecase)
+                               printf("no");
+                       printf("ignorecase ");
+                       if (!showmatch)
+                               printf("no");
+                       printf("showmatch ");
+                       printf("tabstop=%d ", tabstop);
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+                       printf("\r\n");
+                       goto vc2;
+               }
+               if (strncasecmp((char *) args, "no", 2) == 0)
+                       i = 2;          // ":set noautoindent"
+#ifdef BB_FEATURE_VI_SETOPTS
+               if (strncasecmp((char *) args + i, "autoindent", 10) == 0 ||
+                       strncasecmp((char *) args + i, "ai", 2) == 0) {
+                       autoindent = (i == 2) ? 0 : 1;
+               }
+               if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 ||
+                       strncasecmp((char *) args + i, "ic", 2) == 0) {
+                       ignorecase = (i == 2) ? 0 : 1;
+               }
+               if (strncasecmp((char *) args + i, "showmatch", 9) == 0 ||
+                       strncasecmp((char *) args + i, "sm", 2) == 0) {
+                       showmatch = (i == 2) ? 0 : 1;
+               }
+               if (strncasecmp((char *) args + i, "tabstop", 7) == 0) {
+                       sscanf(strchr((char *) args + i, '='), "=%d", &ch);
+                       if (ch > 0 && ch < columns - 1)
+                               tabstop = ch;
+               }
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+#endif                                                 /* BB_FEATURE_VI_SET */
+#ifdef BB_FEATURE_VI_SEARCH
+       } else if (strncasecmp((char *) cmd, "s", 1) == 0) {    // substitute a pattern with a replacement pattern
+               Byte *ls, *F, *R;
+               int gflag;
+
+               // F points to the "find" pattern
+               // R points to the "replace" pattern
+               // replace the cmd line delimiters "/" with NULLs
+               gflag = 0;              // global replace flag
+               c = orig_buf[1];        // what is the delimiter
+               F = orig_buf + 2;       // start of "find"
+               R = (Byte *) strchr((char *) F, c);     // middle delimiter
+               *R++ = '\0';    // terminate "find"
+               buf1 = (Byte *) strchr((char *) R, c);
+               *buf1++ = '\0'; // terminate "replace"
+               if (*buf1 == 'g') {     // :s/foo/bar/g
+                       buf1++;
+                       gflag++;        // turn on gflag
+               }
+               q = begin_line(q);
+               if (b < 0) {    // maybe :s/foo/bar/
+                       q = begin_line(dot);    // start with cur line
+                       b = count_lines(text, q);       // cur line number
+               }
+               if (e < 0)
+                       e = b;          // maybe :.s/foo/bar/
+               for (i = b; i <= e; i++) {      // so, :20,23 s \0 find \0 replace \0
+                       ls = q;         // orig line start
+                 vc4:
+                       buf1 = char_search(q, F, FORWARD, LIMITED);     // search cur line only for "find"
+                       if (buf1 != NULL) {
+                               // we found the "find" pattern- delete it
+                               (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
+                               // inset the "replace" patern
+                               (void) string_insert(buf1, R);  // insert the string
+                               // check for "global"  :s/foo/bar/g
+                               if (gflag == 1) {
+                                       if ((buf1 + strlen((char *) R)) < end_line(ls)) {
+                                               q = buf1 + strlen((char *) R);
+                                               goto vc4;       // don't let q move past cur line
+                                       }
+                               }
+                       }
+                       q = next_line(ls);
+               }
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+       } else if (strncasecmp((char *) cmd, "version", i) == 0) {      // show software version
+               psb("%s", vi_Version);
+       } else if ((strncasecmp((char *) cmd, "write", i) == 0) ||      // write text to file
+                          (strncasecmp((char *) cmd, "wq", i) == 0)) { // write text to file
+               // is there a file name to write to?
+               if (strlen((char *) args) > 0) {
+                       fn = args;
+               }
+#ifdef BB_FEATURE_VI_READONLY
+               if (readonly == TRUE) {
+                       psbs("\"%s\" File is read only", fn);
+                       goto vc3;
+               }
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+               // how many lines in text[]?
+               li = count_lines(q, r);
+               ch = r - q + 1;
+               if (useforce == TRUE) {
+                       // if "fn" is not write-able, chmod u+w
+                       // sprintf(syscmd, "chmod u+w %s", fn);
+                       // system(syscmd);
+                       forced = TRUE;
+               }
+               l = file_write(fn, q, r);
+               if (useforce == TRUE && forced == TRUE) {
+                       // chmod u-w
+                       // sprintf(syscmd, "chmod u-w %s", fn);
+                       // system(syscmd);
+                       forced = FALSE;
+               }
+               psb("\"%s\" %dL, %dC", fn, li, l);
+               if (q == text && r == end - 1 && l == ch)
+                       file_modified = FALSE;
+               if (cmd[1] == 'q' && l == ch) {
+                       editing = 0;
+               }
+#ifdef BB_FEATURE_VI_READONLY
+         vc3:;
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+#ifdef BB_FEATURE_VI_YANKMARK
+       } else if (strncasecmp((char *) 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);
+               psb("Yank %d lines (%d chars) into [%c]",
+                       li, strlen((char *) reg[YDreg]), what_reg());
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+       } else {
+               // cmd unknown
+               ni((Byte *) cmd);
+       }
+  vc1:
+       dot = bound_dot(dot);   // make sure "dot" is valid
+       return;
+}
+
+static void Hit_Return(void)
+{
+       char c;
+
+       standout_start();       // start reverse video
+       write(1, "[Hit return to continue]", 24);
+       standout_end();         // end reverse video
+       while ((c = get_one_char()) != '\n' && c != '\r')       /*do nothing */
+               ;
+       redraw(TRUE);           // force redraw all
+}
+#endif                                                 /* BB_FEATURE_VI_COLON */
+
+//----- Synchronize the cursor to Dot --------------------------
+static void sync_cursor(Byte * d, int *row, int *col)
+{
+       Byte *beg_cur, *end_cur;        // begin and end of "d" line
+       Byte *beg_scr, *end_scr;        // begin and end of screen
+       Byte *tp;
+       int cnt, ro, co;
+
+       beg_cur = begin_line(d);        // first char of cur line
+       end_cur = end_line(d);  // last char of cur line
+
+       beg_scr = end_scr = screenbegin;        // first char of screen
+       end_scr = end_screen(); // last char of screen
+
+       if (beg_cur < screenbegin) {
+               // "d" is before  top line on screen
+               // how many lines do we have to move
+               cnt = count_lines(beg_cur, screenbegin);
+         sc1:
+               screenbegin = beg_cur;
+               if (cnt > (rows - 1) / 2) {
+                       // we moved too many lines. put "dot" in middle of screen
+                       for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
+                               screenbegin = prev_line(screenbegin);
+                       }
+               }
+       } else if (beg_cur > end_scr) {
+               // "d" is after bottom line on screen
+               // how many lines do we have to move
+               cnt = count_lines(end_scr, beg_cur);
+               if (cnt > (rows - 1) / 2)
+                       goto sc1;       // too many lines
+               for (ro = 0; ro < cnt - 1; ro++) {
+                       // move screen begin the same amount
+                       screenbegin = next_line(screenbegin);
+                       // now, move the end of screen
+                       end_scr = next_line(end_scr);
+                       end_scr = end_line(end_scr);
+               }
+       }
+       // "d" is on screen- find out which row
+       tp = screenbegin;
+       for (ro = 0; ro < rows - 1; ro++) {     // drive "ro" to correct row
+               if (tp == beg_cur)
+                       break;
+               tp = next_line(tp);
+       }
+
+       // find out what col "d" is on
+       co = 0;
+       do {                            // drive "co" to correct column
+               if (*tp == '\n' || *tp == '\0')
+                       break;
+               if (*tp == '\t') {
+                       //         7       - (co %    8  )
+                       co += ((tabstop - 1) - (co % tabstop));
+               } else if (*tp < ' ') {
+                       co++;           // display as ^X, use 2 columns
+               }
+       } while (tp++ < d && ++co);
+
+       // "co" is the column where "dot" is.
+       // The screen has "columns" columns.
+       // The currently displayed columns are  0+offset -- columns+ofset
+       // |-------------------------------------------------------------|
+       //               ^ ^                                ^
+       //        offset | |------- columns ----------------|
+       //
+       // If "co" is already in this range then we do not have to adjust offset
+       //      but, we do have to subtract the "offset" bias from "co".
+       // If "co" is outside this range then we have to change "offset".
+       // If the first char of a line is a tab the cursor will try to stay
+       //  in column 7, but we have to set offset to 0.
+
+       if (co < 0 + offset) {
+               offset = co;
+       }
+       if (co >= columns + offset) {
+               offset = co - columns + 1;
+       }
+       // if the first char of the line is a tab, and "dot" is sitting on it
+       //  force offset to 0.
+       if (d == beg_cur && *d == '\t') {
+               offset = 0;
+       }
+       co -= offset;
+
+       *row = ro;
+       *col = co;
+}
+
+//----- Text Movement Routines ---------------------------------
+static Byte *begin_line(Byte * p) // return pointer to first char cur line
+{
+       while (p > text && p[-1] != '\n')
+               p--;                    // go to cur line B-o-l
+       return (p);
+}
+
+static Byte *end_line(Byte * p) // return pointer to NL of cur line line
+{
+       while (p < end - 1 && *p != '\n')
+               p++;                    // go to cur line E-o-l
+       return (p);
+}
+
+static Byte *dollar_line(Byte * p) // return pointer to just before NL line
+{
+       while (p < end - 1 && *p != '\n')
+               p++;                    // go to cur line E-o-l
+       // Try to stay off of the Newline
+       if (*p == '\n' && (p - begin_line(p)) > 0)
+               p--;
+       return (p);
+}
+
+static Byte *prev_line(Byte * p) // return pointer first char prev line
+{
+       p = begin_line(p);      // goto begining of cur line
+       if (p[-1] == '\n' && p > text)
+               p--;                    // step to prev line
+       p = begin_line(p);      // goto begining of prev line
+       return (p);
+}
+
+static Byte *next_line(Byte * p) // return pointer first char next line
+{
+       p = end_line(p);
+       if (*p == '\n' && p < end - 1)
+               p++;                    // step to next line
+       return (p);
+}
+
+//----- Text Information Routines ------------------------------
+static Byte *end_screen(void)
+{
+       Byte *q;
+       int cnt;
+
+       // find new bottom line
+       q = screenbegin;
+       for (cnt = 0; cnt < rows - 2; cnt++)
+               q = next_line(q);
+       q = end_line(q);
+       return (q);
+}
+
+static int count_lines(Byte * start, Byte * stop) // count line from start to stop
+{
+       Byte *q;
+       int cnt;
+
+       if (stop < start) {     // start and stop are backwards- reverse them
+               q = start;
+               start = stop;
+               stop = q;
+       }
+       cnt = 0;
+       stop = end_line(stop);  // get to end of this line
+       for (q = start; q <= stop && q <= end - 1; q++) {
+               if (*q == '\n')
+                       cnt++;
+       }
+       return (cnt);
+}
+
+static Byte *find_line(int li) // find begining of line #li
+{
+       Byte *q;
+
+       for (q = text; li > 1; li--) {
+               q = next_line(q);
+       }
+       return (q);
+}
+
+//----- Dot Movement Routines ----------------------------------
+static void dot_left(void)
+{
+       if (dot > text && dot[-1] != '\n')
+               dot--;
+}
+
+static void dot_right(void)
+{
+       if (dot < end - 1 && *dot != '\n')
+               dot++;
+}
+
+static void dot_begin(void)
+{
+       dot = begin_line(dot);  // return pointer to first char cur line
+}
+
+static void dot_end(void)
+{
+       dot = end_line(dot);    // return pointer to last char cur line
+}
+
+static Byte *move_to_col(Byte * p, int l)
+{
+       int co;
+
+       p = begin_line(p);
+       co = 0;
+       do {
+               if (*p == '\n' || *p == '\0')
+                       break;
+               if (*p == '\t') {
+                       //         7       - (co %    8  )
+                       co += ((tabstop - 1) - (co % tabstop));
+               } else if (*p < ' ') {
+                       co++;           // display as ^X, use 2 columns
+               }
+       } while (++co <= l && p++ < end);
+       return (p);
+}
+
+static void dot_next(void)
+{
+       dot = next_line(dot);
+}
+
+static void dot_prev(void)
+{
+       dot = prev_line(dot);
+}
+
+static void dot_scroll(int cnt, int dir)
+{
+       Byte *q;
+
+       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);
+               }
+       }
+       // 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();
+}
+
+static void dot_skip_over_ws(void)
+{
+       // skip WS
+       while (isspace(*dot) && *dot != '\n' && dot < end - 1)
+               dot++;
+}
+
+static void dot_delete(void)   // delete the char at 'dot'
+{
+       (void) text_hole_delete(dot, dot);
+}
+
+static Byte *bound_dot(Byte * p) // make sure  text[0] <= P < "end"
+{
+       if (p >= end && end > text) {
+               p = end - 1;
+               indicate_error('1');
+       }
+       if (p < text) {
+               p = text;
+               indicate_error('2');
+       }
+       return (p);
+}
+
+//----- 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 Byte *new_screen(int ro, int co)
+{
+       if (screen != 0)
+               free(screen);
+       screensize = ro * co + 8;
+       screen = (Byte *) malloc(screensize);
+       return (screen);
+}
+
+static Byte *new_text(int size)
+{
+       if (size < 10240)
+               size = 10240;   // have a minimum size for new files
+       if (text != 0) {
+               //text -= 4;
+               free(text);
+       }
+       text = (Byte *) malloc(size + 8);
+       memset(text, '\0', size);       // clear new text[]
+       //text += 4;            // leave some room for "oops"
+       textend = text + size - 1;
+       //textend -= 4;         // leave some root for "oops"
+       return (text);
+}
+
+#ifdef BB_FEATURE_VI_SEARCH
+static int mycmp(Byte * s1, Byte * s2, int len)
+{
+       int i;
+
+       i = strncmp((char *) s1, (char *) s2, len);
+#ifdef BB_FEATURE_VI_SETOPTS
+       if (ignorecase) {
+               i = strncasecmp((char *) s1, (char *) s2, len);
+       }
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+       return (i);
+}
+
+static Byte *char_search(Byte * p, Byte * pat, int dir, int range)     // search for pattern starting at p
+{
+#ifndef REGEX_SEARCH
+       Byte *start, *stop;
+       int len;
+
+       len = strlen((char *) pat);
+       if (dir == 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 if (dir == 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);
+#else                                                  /*REGEX_SEARCH */
+       char *q;
+       struct re_pattern_buffer preg;
+       int i;
+       int size, range;
+
+       re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
+       preg.translate = 0;
+       preg.fastmap = 0;
+       preg.buffer = 0;
+       preg.allocated = 0;
+
+       // assume a LIMITED forward search
+       q = next_line(p);
+       q = end_line(q);
+       q = end - 1;
+       if (dir == BACK) {
+               q = prev_line(p);
+               q = text;
+       }
+       // count the number of chars to search over, forward or backward
+       size = q - p;
+       if (size < 0)
+               size = p - q;
+       // RANGE could be negative if we are searching backwards
+       range = q - p;
+
+       q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
+       if (q != 0) {
+               // The pattern was not compiled
+               psbs("bad search pattern: \"%s\": %s", pat, q);
+               i = 0;                  // return p if pattern not compiled
+               goto cs1;
+       }
+
+       q = p;
+       if (range < 0) {
+               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, 0, range, 0);
+       if (i == -1) {
+               p = 0;
+               i = 0;                  // return NULL if pattern not found
+       }
+  cs1:
+       if (dir == FORWARD) {
+               p = p + i;
+       } else {
+               p = p - i;
+       }
+       return (p);
+#endif                                                 /*REGEX_SEARCH */
+}
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+
+static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
+{
+       if (c == 22) {          // Is this an ctrl-V?
+               p = stupid_insert(p, '^');      // use ^ to indicate literal next
+               p--;                    // backup onto ^
+               refresh(FALSE); // show the ^
+               c = get_one_char();
+               *p = c;
+               p++;
+               file_modified = TRUE;   // has the file been modified
+       } else if (c == 27) {   // Is this an ESC?
+               cmd_mode = 0;
+               cmdcnt = 0;
+               end_cmd_q();    // stop adding to q
+               *status_buffer = '\0';  // clear the status buffer
+               if (p[-1] != '\n') {
+                       p--;
+               }
+       } else if (c == erase_char) {   // Is this a BS
+               //     123456789
+               if (p[-1] != '\n') {
+                       p--;
+                       p = text_hole_delete(p, p);     // shrink buffer 1 char
+#ifdef BB_FEATURE_VI_DOT_CMD
+                       // also rmove char from last_modifying_cmd
+                       if (strlen((char *) last_modifying_cmd) > 0) {
+                               Byte *q;
+
+                               q = last_modifying_cmd;
+                               q[strlen((char *) q) - 1] = '\0';       // erase BS
+                               q[strlen((char *) q) - 1] = '\0';       // erase prev char
+                       }
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+               }
+       } else {
+               // insert a char into text[]
+               Byte *sp;               // "save p"
+
+               if (c == 13)
+                       c = '\n';       // translate \r to \n
+               sp = p;                 // remember addr of insert
+               p = stupid_insert(p, c);        // insert the char
+#ifdef BB_FEATURE_VI_SETOPTS
+               if (showmatch && strchr(")]}", *sp) != NULL) {
+                       showmatching(sp);
+               }
+               if (autoindent && c == '\n') {  // auto indent the new line
+                       Byte *q;
+
+                       q = prev_line(p);       // use prev line as templet
+                       for (; isblnk(*q); q++) {
+                               p = stupid_insert(p, *q);       // insert the char
+                       }
+               }
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+       }
+       return (p);
+}
+
+static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
+{
+       p = text_hole_make(p, 1);
+       if (p != 0) {
+               *p = c;
+               file_modified = TRUE;   // has the file been modified
+               p++;
+       }
+       return (p);
+}
+
+static Byte find_range(Byte ** start, Byte ** stop, Byte c)
+{
+       Byte *save_dot, *p, *q;
+       int cnt;
+
+       save_dot = dot;
+       p = q = dot;
+
+       if (strchr("cdy><", c)) {
+               p = q = begin_line(p);
+               for (cnt = 1; cnt < cmdcnt; cnt++) {
+                       q = next_line(q);
+               }
+               q = end_line(q);
+       } else if (strchr("$0bBeE", c)) {
+               do_cmd(c);              // execute movement cmd
+               q = dot;
+       } else if (strchr("wW", c)) {
+               do_cmd(c);              // execute movement cmd
+               if (dot > text)
+                       dot--;          // move back off of next word
+               if (dot > text && *dot == '\n')
+                       dot--;          // stay off NL
+               q = dot;
+       } else if (strchr("H-k{", c)) {
+               q = end_line(dot);      // find NL
+               do_cmd(c);              // execute movement cmd
+               dot_begin();
+               p = dot;
+       } else if (strchr("L+j}\r\n", c)) {
+               p = begin_line(dot);
+               do_cmd(c);              // execute movement cmd
+               dot_end();              // find NL
+               q = dot;
+       } else {
+               c = 27;                 // error- return an ESC char
+               //break;
+       }
+       *start = p;
+       *stop = q;
+       if (q < p) {
+               *start = q;
+               *stop = p;
+       }
+       dot = save_dot;
+       return (c);
+}
+
+static int st_test(Byte * p, int type, int dir, Byte * tested)
+{
+       Byte c, c0, ci;
+       int test, inc;
+
+       inc = dir;
+       c = c0 = p[0];
+       ci = p[inc];
+       test = 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 == '_');
+       }
+       *tested = c;
+       return (test);
+}
+
+static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
+{
+       Byte c;
+
+       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);
+}
+
+// find matching char of pair  ()  []  {}
+static Byte *find_pair(Byte * p, Byte c)
+{
+       Byte match, *q;
+       int dir, level;
+
+       match = ')';
+       level = 1;
+       dir = 1;                        // assume forward
+       switch (c) {
+       case '(':
+               match = ')';
+               break;
+       case '[':
+               match = ']';
+               break;
+       case '{':
+               match = '}';
+               break;
+       case ')':
+               match = '(';
+               dir = -1;
+               break;
+       case ']':
+               match = '[';
+               dir = -1;
+               break;
+       case '}':
+               match = '{';
+               dir = -1;
+               break;
+       }
+       for (q = p + dir; text <= q && q < end; q += dir) {
+               // look for match, count levels of pairs  (( ))
+               if (*q == c)
+                       level++;        // increase pair levels
+               if (*q == match)
+                       level--;        // reduce pair level
+               if (level == 0)
+                       break;          // found matching pair
+       }
+       if (level != 0)
+               q = NULL;               // indicate no match
+       return (q);
+}
+
+#ifdef BB_FEATURE_VI_SETOPTS
+// show the matching char of a pair,  ()  []  {}
+static void showmatching(Byte * p)
+{
+       Byte *q, *save_dot;
+
+       // we found half of a pair
+       q = find_pair(p, *p);   // get loc of matching char
+       if (q == NULL) {
+               indicate_error('3');    // 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
+               (void) mysleep(40);     // give user some time
+               dot = save_dot; // go back to old loc
+               refresh(FALSE);
+       }
+}
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+
+//  open a hole in text[]
+static Byte *text_hole_make(Byte * p, int size)        // at "p", make a 'size' byte hole
+{
+       Byte *src, *dest;
+       int cnt;
+
+       if (size <= 0)
+               goto thm0;
+       src = p;
+       dest = p + size;
+       cnt = end - src;        // the rest of buffer
+       if (memmove(dest, src, cnt) != dest) {
+               psbs("can't create room for new characters");
+       }
+       memset(p, ' ', size);   // clear new hole
+       end = end + size;       // adjust the new END
+       file_modified = TRUE;   // has the file been modified
+  thm0:
+       return (p);
+}
+
+//  close a hole in text[]
+static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
+{
+       Byte *src, *dest;
+       int cnt, hole_size;
+
+       // 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 (src < text || src > end)
+               goto thd0;
+       if (dest < text || dest >= end)
+               goto thd0;
+       if (src >= end)
+               goto thd_atend; // just delete the end of the buffer
+       if (memmove(dest, src, cnt) != dest) {
+               psbs("can't delete the character");
+       }
+  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
+       file_modified = TRUE;   // has the file been modified
+  thd0:
+       return (dest);
+}
+
+// copy text into register, then delete text.
+// if dist <= 0, do not include, or go past, a NewLine
+//
+static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
+{
+       Byte *p;
+
+       // make sure start <= stop
+       if (start > stop) {
+               // they are backwards, reverse them
+               p = start;
+               start = stop;
+               stop = p;
+       }
+       if (dist <= 0) {
+               // we can not cross NL boundaries
+               p = start;
+               if (*p == '\n')
+                       return (p);
+               // dont go past a NewLine
+               for (; p + 1 <= stop; p++) {
+                       if (p[1] == '\n') {
+                               stop = p;       // "stop" just before NewLine
+                               break;
+                       }
+               }
+       }
+       p = start;
+#ifdef BB_FEATURE_VI_YANKMARK
+       text_yank(start, stop, YDreg);
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+       if (yf == YANKDEL) {
+               p = text_hole_delete(start, stop);
+       }                                       // delete lines
+       return (p);
+}
+
+static void show_help(void)
+{
+       printf("These features are available:\n");
+#ifdef BB_FEATURE_VI_SEARCH
+       printf("\tPattern searches with / and ?\n");
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+#ifdef BB_FEATURE_VI_DOT_CMD
+       printf("\tLast command repeat with \'.\'\n");
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+#ifdef BB_FEATURE_VI_YANKMARK
+       printf("\tLine marking with  'x\n");
+       printf("\tNamed buffers with  \"x\n");
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_READONLY
+       printf("\tReadonly with -R command line arg\n");
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+#ifdef BB_FEATURE_VI_SET
+       printf("\tSome colon mode commands with \':\'\n");
+#endif                                                 /* BB_FEATURE_VI_SET */
+#ifdef BB_FEATURE_VI_SETOPTS
+       printf("\tSettable options with \":set\"\n");
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+       printf("\tSignal catching- ^C\n");
+       printf("\tJob suspend and resume with ^Z\n");
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+       printf("\tAdapt to window re-sizes\n");
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+}
+
+static void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
+{
+       Byte c, b[2];
+
+       b[1] = '\0';
+       strcpy((char *) buf, "");       // init buf
+       if (strlen((char *) s) <= 0)
+               s = (Byte *) "(NULL)";
+       for (; *s > '\0'; s++) {
+               c = *s;
+               if (*s > '~') {
+                       strcat((char *) buf, SOs);
+                       c = *s - 128;
+               }
+               if (*s < ' ') {
+                       strcat((char *) buf, "^");
+                       c += '@';
+               }
+               b[0] = c;
+               strcat((char *) buf, (char *) b);
+               if (*s > '~')
+                       strcat((char *) buf, SOn);
+               if (*s == '\n') {
+                       strcat((char *) buf, "$");
+               }
+       }
+}
+
+#ifdef BB_FEATURE_VI_DOT_CMD
+static void start_new_cmd_q(Byte c)
+{
+       // release old cmd
+       if (last_modifying_cmd != 0)
+               free(last_modifying_cmd);
+       // get buffer for new cmd
+       last_modifying_cmd = (Byte *) malloc(BUFSIZ);
+       memset(last_modifying_cmd, '\0', BUFSIZ);       // clear new cmd queue
+       // if there is a current cmd count put it in the buffer first
+       if (cmdcnt > 0)
+               sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
+       // save char c onto queue
+       last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
+       adding2q = 1;
+       return;
+}
+
+static void end_cmd_q()
+{
+#ifdef BB_FEATURE_VI_YANKMARK
+       YDreg = 26;                     // go back to default Yank/Delete reg
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+       adding2q = 0;
+       return;
+}
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+
+#if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
+static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
+{
+       int cnt, i;
+
+       i = strlen((char *) s);
+       p = text_hole_make(p, i);
+       strncpy((char *) p, (char *) s, i);
+       for (cnt = 0; *s != '\0'; s++) {
+               if (*s == '\n')
+                       cnt++;
+       }
+#ifdef BB_FEATURE_VI_YANKMARK
+       psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+       return (p);
+}
+#endif                                                 /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
+
+#ifdef BB_FEATURE_VI_YANKMARK
+static Byte *text_yank(Byte * p, Byte * q, int dest)   // copy text into a register
+{
+       Byte *t;
+       int cnt;
+
+       if (q < p) {            // they are backwards- reverse them
+               t = q;
+               q = p;
+               p = t;
+       }
+       cnt = q - p + 1;
+       t = reg[dest];
+       if (t != 0) {           // if already a yank register
+               free(t);                //   free it
+       }
+       t = (Byte *) malloc(cnt + 1);   // get a new register
+       memset(t, '\0', cnt + 1);       // clear new text[]
+       strncpy((char *) t, (char *) p, cnt);   // copy text[] into bufer
+       reg[dest] = t;
+       return (p);
+}
+
+static Byte what_reg(void)
+{
+       Byte c;
+       int i;
+
+       i = 0;
+       c = 'D';                        // default to D-reg
+       if (0 <= YDreg && YDreg <= 25)
+               c = 'a' + (Byte) YDreg;
+       if (YDreg == 26)
+               c = 'D';
+       if (YDreg == 27)
+               c = 'U';
+       return (c);
+}
+
+static void check_context(Byte 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((char *) 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;
+               }
+       }
+       return;
+}
+
+static Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
+{
+       Byte *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] = mark[26];
+               mark[26] = tmp;
+               p = mark[26];   // where we are going- previous context
+               context_start = prev_line(prev_line(prev_line(p)));
+               context_end = next_line(next_line(next_line(p)));
+       }
+       return (p);
+}
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+
+static int isblnk(Byte c) // is the char a blank or tab
+{
+       return (c == ' ' || c == '\t');
+}
+
+//----- Set terminal attributes --------------------------------
+static void rawmode(void)
+{
+       tcgetattr(0, &term_orig);
+       term_vi = term_orig;
+       term_vi.c_lflag &= (~ICANON & ~ECHO);   // leave ISIG ON- allow intr's
+       term_vi.c_iflag &= (~IXON & ~ICRNL);
+       term_vi.c_cc[VMIN] = 1;
+       term_vi.c_cc[VTIME] = 0;
+       erase_char = term_vi.c_cc[VERASE];
+       tcsetattr(0, TCSANOW, &term_vi);
+}
+
+static void cookmode(void)
+{
+       tcsetattr(0, TCSANOW, &term_orig);
+}
+
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+//----- See what the window size currently is --------------------
+static void window_size_get(int sig)
+{
+       int i;
+
+       i = ioctl(0, TIOCGWINSZ, &winsize);
+       if (i != 0) {
+               // force 24x80
+               winsize.ws_row = 24;
+               winsize.ws_col = 80;
+       }
+       if (winsize.ws_row <= 1) {
+               winsize.ws_row = 24;
+       }
+       if (winsize.ws_col <= 1) {
+               winsize.ws_col = 80;
+       }
+       rows = (int) winsize.ws_row;
+       columns = (int) winsize.ws_col;
+}
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+
+//----- Come here when we get a window resize signal ---------
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+static void winch_sig(int sig)
+{
+       signal(SIGWINCH, winch_sig);
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+       window_size_get(0);
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+       new_screen(rows, columns);      // get memory for virtual screen
+       redraw(TRUE);           // re-draw the screen
+}
+
+//----- Come here when we get a continue signal -------------------
+static void cont_sig(int sig)
+{
+       rawmode();                      // terminal to "raw"
+       *status_buffer = '\0';  // clear the status buffer
+       redraw(TRUE);           // re-draw the screen
+
+       signal(SIGTSTP, suspend_sig);
+       signal(SIGCONT, SIG_DFL);
+       kill(getpid(), SIGCONT);
+}
+
+//----- Come here when we get a Suspend signal -------------------
+static void suspend_sig(int sig)
+{
+       place_cursor(rows, 0);  // go to bottom of screen
+       clear_to_eol();         // Erase to end of line
+       cookmode();                     // terminal to "cooked"
+
+       signal(SIGCONT, cont_sig);
+       signal(SIGTSTP, SIG_DFL);
+       kill(getpid(), SIGTSTP);
+}
+
+//----- Come here when we get a signal        --------------------
+static void catch_sig(int sig)
+{
+       signal(SIGHUP, catch_sig);
+       signal(SIGINT, catch_sig);
+       signal(SIGTERM, catch_sig);
+       longjmp(restart, sig);
+}
+
+static void alarm_sig(int sig)
+{
+       signal(SIGALRM, catch_sig);
+       longjmp(restart, sig);
+}
+
+//----- Come here when we get a core dump signal -----------------
+static void core_sig(int sig)
+{
+       signal(SIGQUIT, core_sig);
+       signal(SIGILL, core_sig);
+       signal(SIGTRAP, core_sig);
+       signal(SIGIOT, core_sig);
+       signal(SIGABRT, core_sig);
+       signal(SIGFPE, core_sig);
+       signal(SIGBUS, core_sig);
+       signal(SIGSEGV, core_sig);
+       signal(SIGSYS, core_sig);
+
+       dot = bound_dot(dot);   // make sure "dot" is valid
+
+       longjmp(restart, sig);
+}
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+
+static int mysleep(int hund)   // sleep for 'h' 1/100 seconds
+{
+       // Don't hang- Wait 5/100 seconds-  1 Sec= 1000000
+       FD_ZERO(&rfds);
+       FD_SET(0, &rfds);
+       tv.tv_sec = 0;
+       tv.tv_usec = hund * 10000;
+       select(1, &rfds, NULL, NULL, &tv);
+       return (FD_ISSET(0, &rfds));
+}
+
+//----- IO Routines --------------------------------------------
+static Byte readit(void)       // read (maybe cursor) key from stdin
+{
+       Byte c;
+       int i, bufsiz, cnt, cmdindex;
+       struct esc_cmds {
+               Byte *seq;
+               Byte val;
+       };
+
+       static struct esc_cmds esccmds[] = {
+               {(Byte *) "\eOA", (Byte) VI_K_UP},       // cursor key Up
+               {(Byte *) "\eOB", (Byte) VI_K_DOWN},     // cursor key Down
+               {(Byte *) "\eOC", (Byte) VI_K_RIGHT},    // Cursor Key Right
+               {(Byte *) "\eOD", (Byte) VI_K_LEFT},     // cursor key Left
+               {(Byte *) "\eOH", (Byte) VI_K_HOME},     // Cursor Key Home
+               {(Byte *) "\eOF", (Byte) VI_K_END},      // Cursor Key End
+               {(Byte *) "\e[A", (Byte) VI_K_UP},       // cursor key Up
+               {(Byte *) "\e[B", (Byte) VI_K_DOWN},     // cursor key Down
+               {(Byte *) "\e[C", (Byte) VI_K_RIGHT},    // Cursor Key Right
+               {(Byte *) "\e[D", (Byte) VI_K_LEFT},     // cursor key Left
+               {(Byte *) "\e[H", (Byte) VI_K_HOME},     // Cursor Key Home
+               {(Byte *) "\e[F", (Byte) VI_K_END},      // Cursor Key End
+               {(Byte *) "\e[2~", (Byte) VI_K_INSERT},  // Cursor Key Insert
+               {(Byte *) "\e[5~", (Byte) VI_K_PAGEUP},  // Cursor Key Page Up
+               {(Byte *) "\e[6~", (Byte) VI_K_PAGEDOWN},        // Cursor Key Page Down
+               {(Byte *) "\eOP", (Byte) VI_K_FUN1},     // Function Key F1
+               {(Byte *) "\eOQ", (Byte) VI_K_FUN2},     // Function Key F2
+               {(Byte *) "\eOR", (Byte) VI_K_FUN3},     // Function Key F3
+               {(Byte *) "\eOS", (Byte) VI_K_FUN4},     // Function Key F4
+               {(Byte *) "\e[15~", (Byte) VI_K_FUN5},   // Function Key F5
+               {(Byte *) "\e[17~", (Byte) VI_K_FUN6},   // Function Key F6
+               {(Byte *) "\e[18~", (Byte) VI_K_FUN7},   // Function Key F7
+               {(Byte *) "\e[19~", (Byte) VI_K_FUN8},   // Function Key F8
+               {(Byte *) "\e[20~", (Byte) VI_K_FUN9},   // Function Key F9
+               {(Byte *) "\e[21~", (Byte) VI_K_FUN10},  // Function Key F10
+               {(Byte *) "\e[23~", (Byte) VI_K_FUN11},  // Function Key F11
+               {(Byte *) "\e[24~", (Byte) VI_K_FUN12},  // Function Key F12
+               {(Byte *) "\e[11~", (Byte) VI_K_FUN1},   // Function Key F1
+               {(Byte *) "\e[12~", (Byte) VI_K_FUN2},   // Function Key F2
+               {(Byte *) "\e[13~", (Byte) VI_K_FUN3},   // Function Key F3
+               {(Byte *) "\e[14~", (Byte) VI_K_FUN4},   // Function Key F4
+       };
+
+#define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
+
+       (void) alarm(0);        // turn alarm OFF while we wait for input
+       // get input from User- are there already input chars in Q?
+       bufsiz = strlen((char *) readbuffer);
+       if (bufsiz <= 0) {
+         ri0:
+               // the Q is empty, wait for a typed char
+               bufsiz = read(0, readbuffer, BUFSIZ - 1);
+               if (bufsiz < 0) {
+                       if (errno == EINTR)
+                               goto ri0;       // interrupted sys call
+                       if (errno == EBADF)
+                               editing = 0;
+                       if (errno == EFAULT)
+                               editing = 0;
+                       if (errno == EINVAL)
+                               editing = 0;
+                       if (errno == EIO)
+                               editing = 0;
+                       errno = 0;
+                       bufsiz = 0;
+               }
+               readbuffer[bufsiz] = '\0';
+       }
+       // return char if it is not part of ESC sequence
+       if (readbuffer[0] != 27)
+               goto ri1;
+
+       // This is an ESC char. Is this Esc sequence?
+       // Could be bare Esc key. See if there are any
+       // more chars to read after the ESC. This would
+       // be a Function or Cursor Key sequence.
+       FD_ZERO(&rfds);
+       FD_SET(0, &rfds);
+       tv.tv_sec = 0;
+       tv.tv_usec = 10000;     // Wait 1/100 seconds- 1 Sec=1000000
+
+       // keep reading while there are input chars and room in buffer
+       while (select(1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5)) {
+               // read the rest of the ESC string
+               i = read(0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz);
+               if (i > 0) {
+                       bufsiz += i;
+                       readbuffer[bufsiz] = '\0';      // Terminate the string
+               }
+       }
+       // Maybe cursor or function key?
+       for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++) {
+               cnt = strlen((char *) esccmds[cmdindex].seq);
+               i = strncmp((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt);
+               if (i == 0) {
+                       // is a Cursor key- put derived value back into Q
+                       readbuffer[0] = esccmds[cmdindex].val;
+                       // squeeze out the ESC sequence
+                       for (i = 1; i < cnt; i++) {
+                               memmove(readbuffer + 1, readbuffer + 2, BUFSIZ - 2);
+                               readbuffer[BUFSIZ - 1] = '\0';
+                       }
+                       break;
+               }
+       }
+  ri1:
+       c = readbuffer[0];
+       // remove one char from Q
+       memmove(readbuffer, readbuffer + 1, BUFSIZ - 1);
+       readbuffer[BUFSIZ - 1] = '\0';
+       (void) alarm(3);        // we are done waiting for input, turn alarm ON
+       return (c);
+}
+
+//----- IO Routines --------------------------------------------
+static Byte get_one_char()
+{
+       static Byte c;
+
+#ifdef BB_FEATURE_VI_DOT_CMD
+       // ! adding2q  && ioq == 0  read()
+       // ! adding2q  && ioq != 0  *ioq
+       // adding2q         *last_modifying_cmd= read()
+       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
+                       c = *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 (last_modifying_cmd != 0) {
+                       // add new char to q
+                       last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
+               }
+       }
+#else                                                  /* BB_FEATURE_VI_DOT_CMD */
+       c = readit();           // get the users input
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+       return (c);                     // return the char, where ever it came from
+}
+
+#if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
+static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
+{
+       Byte buf[500];
+       Byte c;
+       int i;
+       static Byte *obufp = NULL;
+
+       strcpy((char *) buf, (char *) prompt);
+       *status_buffer = '\0';  // clear the status buffer
+       place_cursor(rows - 1, 0);      // go to Status line, bottom of screen
+       clear_to_eol();         // clear the line
+       write(1, prompt, strlen((char *) prompt));      // write out the :, /, or ? prompt
+
+       for (i = strlen((char *) buf); i < 500;) {
+               c = get_one_char();     // read user input
+               if (c == '\n' || c == '\r')
+                       break;          // is this end of input
+               if (c == erase_char) {  // user wants to erase prev char
+                       i--;            // backup to prev char
+                       buf[i] = '\0';  // erase the char
+                       buf[i + 1] = '\0';      // null terminate buffer
+                       write(1, "\b \b", 3);     // erase char on screen
+                       if (i <= 0) {   // user backs up before b-o-l, exit
+                               break;
+                       }
+               } else {
+                       buf[i] = c;     // save char in buffer
+                       buf[i + 1] = '\0';      // make sure buffer is null terminated
+                       write(1, buf + i, 1);   // echo the char back to user
+                       i++;
+               }
+       }
+       refresh(FALSE);
+       if (obufp != NULL)
+               free(obufp);
+       obufp = (Byte *) strdup((char *) buf);
+       return (obufp);
+}
+#endif                                                 /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
+
+static int file_size(Byte * fn) // what is the byte size of "fn"
+{
+       struct stat st_buf;
+       int cnt, sr;
+
+       if (fn == 0)
+               return (-1);
+       cnt = -1;
+       sr = stat((char *) fn, &st_buf);        // see if file exists
+       if (sr >= 0) {
+               cnt = (int) st_buf.st_size;
+       }
+       return (cnt);
+}
+
+static int file_insert(Byte * fn, Byte * p, int size)
+{
+       int fd, cnt, sr;
+       struct stat st_buf;
+
+       cnt = -1;
+       if (fn == 0) {
+               psbs("No filename given");
+               goto fi0;
+       }
+       sr = stat((char *) fn, &st_buf);        // see if file exists
+       if (sr < 0) {
+               psbs("\"%s\" %s", fn, "count not stat file");
+               goto fi0;
+       }
+       if (size <= 0 || (int) st_buf.st_size <= 0) {
+               psbs("The file size (%d) is too small", size);
+               if ((int) st_buf.st_size <= 0)
+                       psbs("\"%s\" is empty", fn);
+               goto fi0;
+       }
+       // There is a file with content
+       fd = open((char *) fn, O_RDWR);
+       if (fd < 0) {
+               psbs("\"%s\" %s", fn, "could not open file");
+               goto fi0;
+       }
+       p = text_hole_make(p, size);
+       cnt = read(fd, p, size);
+       close(fd);
+       if (cnt < 0) {
+               cnt = -1;
+               p = text_hole_delete(p, p + size - 1);  // un-do buffer insert
+               psbs("could not read file \"%s\"", fn);
+       } else if (cnt < size) {
+               // There was a partial read, shrink unused space text[]
+               p = text_hole_delete(p + cnt, p + (size - cnt) - 1);    // un-do buffer insert
+               psbs("could not read all of file \"%s\"", fn);
+       }
+       if (cnt >= size)
+               file_modified = TRUE;
+  fi0:
+       return (cnt);
+}
+
+static int file_write(Byte * fn, Byte * first, Byte * last)
+{
+       int fd, cnt, charcnt;
+
+       if (fn == 0) {
+               psbs("No current filename");
+               return (-1);
+       }
+       charcnt = 0;
+       // FIXIT- use the correct umask()
+       fd = open((char *) fn, (O_RDWR | O_CREAT | O_TRUNC), 0664);
+       if (fd < 0)
+               return (-1);
+       cnt = last - first + 1;
+       charcnt = write(fd, first, cnt);
+       if (charcnt == cnt) {
+               // good write
+               //file_modified= FALSE; // the file has not been modified
+       } else {
+               charcnt = 0;
+       }
+       close(fd);
+       return (charcnt);
+}
+
+//----- Terminal Drawing ---------------------------------------
+// The terminal is made up of 'rows' line of 'columns' columns.
+// classicly this would be 24 x 80.
+//  screen coordinates
+//  0,0     ...     0,79
+//  1,0     ...     1,79
+//  .       ...     .
+//  .       ...     .
+//  22,0        ...     22,79
+//  23,0        ...     23,79   status line
+//
+
+//----- Move the cursor to row x col (count from 0, not 1) -------
+static void place_cursor(int row, int col)
+{
+       Byte buf[30];
+       int l;
+
+       if (row < 0)
+               row = 0;
+       if (row >= rows)
+               row = rows - 1;
+       if (col < 0)
+               col = 0;
+       if (col >= columns)
+               col = columns - 1;
+       sprintf((char *) buf, "%c[%d;%dH", 0x1b, row + 1, col + 1);
+       l = strlen((char *) buf);
+       write(1, buf, l);
+}
+
+//----- Erase from cursor to end of line -----------------------
+static void clear_to_eol()
+{
+       write(1, "\033[0K", 4); // Erase from cursor to end of line
+}
+
+//----- Erase from cursor to end of screen -----------------------
+static void clear_to_eos()
+{
+       write(1, "\033[0J", 4); // Erase from cursor to end of screen
+}
+
+//----- Start standout mode ------------------------------------
+static void standout_start() // send "start reverse video" sequence
+{
+       write(1, "\033[7m", 4); // Start reverse video mode
+}
+
+//----- End standout mode --------------------------------------
+static void standout_end() // send "end reverse video" sequence
+{
+       write(1, "\033[0m", 4); // End reverse video mode
+}
+
+//----- Flash the screen  --------------------------------------
+static void flash(int h)
+{
+       standout_start();       // send "start reverse video" sequence
+       redraw(TRUE);
+       (void) mysleep(h);
+       standout_end();         // send "end reverse video" sequence
+       redraw(TRUE);
+}
+
+static void beep()
+{
+       write(1, "\007", 1);    // send out a bell character
+}
+
+static void indicate_error(char c)
+{
+#ifdef BB_FEATURE_VI_CRASHME
+       if (crashme > 0)
+               return;                 // generate a random command
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+       if (err_method == 0) {
+               beep();
+       } else {
+               flash(10);
+       }
+}
+
+//----- Screen[] Routines --------------------------------------
+//----- Erase the Screen[] memory ------------------------------
+static void screen_erase()
+{
+       int i;
+
+       for (i = 0; i < screensize; i++) {
+               screen[i] = '\0';
+       }
+}
+
+//----- Draw the status line at bottom of the screen -------------
+static void show_status_line(void)
+{
+       int cnt;
+
+       cnt = strlen((char *) status_buffer);
+       place_cursor(rows - 1, 0);      // put cursor on status line
+       if (cnt > 0) {
+               write(1, status_buffer, cnt);
+       }
+       clear_to_eol();
+       place_cursor(crow, ccol);       // put cursor back in correct place
+}
+
+//----- format the status buffer, the bottom line of screen ------
+// print status buffer, with STANDOUT mode
+static void psbs(char *format, ...)
+{
+       va_list args;
+
+       va_start(args, format);
+       strcpy((char *) status_buffer, "\033[7m");      // Terminal standout mode on
+       vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
+                        args);
+       strcat((char *) status_buffer, "\033[0m");      // Terminal standout mode off
+       va_end(args);
+
+       return;
+}
+
+// print status buffer
+static void psb(char *format, ...)
+{
+       va_list args;
+
+       va_start(args, format);
+       vsprintf((char *) status_buffer, format, args);
+       va_end(args);
+       return;
+}
+
+static void ni(Byte * s) // display messages
+{
+       Byte buf[9];
+
+       print_literal(buf, s);
+       psbs("\'%s\' is not implemented", buf);
+}
+
+static void edit_status(void)  // show file status on status line
+{
+       int cur, tot, percent;
+
+       cur = count_lines(text, dot);
+       tot = count_lines(text, end - 1);
+       //    current line         percent
+       //   -------------    ~~ ----------
+       //    total lines            100
+       if (tot > 0) {
+               percent = (100 * cur) / tot;
+       } else {
+               cur = tot = 0;
+               percent = 100;
+       }
+       psb("\"%s\""
+#ifdef BB_FEATURE_VI_READONLY
+               "%s"
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+               "%s line %d of %d --%d%%--  (%dx%d)",
+               (cfn != 0 ? (char *) cfn : "No file"),
+#ifdef BB_FEATURE_VI_READONLY
+               (readonly == TRUE ? " [Read only]" : ""),
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+               (file_modified == TRUE ? " [modified]" : ""),
+               cur, tot, percent, rows, columns);
+}
+
+//----- Force refresh of all Lines -----------------------------
+static void redraw(int full_screen)
+{
+       place_cursor(0, 0);     // put cursor in correct place
+       clear_to_eos();         // tel terminal to erase display
+       screen_erase();         // erase the internal screen buffer
+       refresh(full_screen);   // this will redraw the entire display
+}
+
+//----- 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 old_offset;
+       int li, co, changed;
+       Byte c, buf[MAX_SCR_COLS];
+       Byte *tp, *sp;          // pointer into text[] and screen[]
+
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+       window_size_get(0);
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+       sync_cursor(dot, &crow, &ccol);
+       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++) {
+               // format current text line into buf with "columns" wide
+               for (co = 0; co < columns + offset;) {
+                       c = ' ';        // assume blank
+                       if (li > 0 && co == 0) {
+                               c = '~';        // not first line, assume Tilde
+                       }
+                       // are there chars in text[]
+                       //   and have we gone past the end
+                       if (text < end && tp < end) {
+                               c = *tp++;
+                       }
+                       if (c == '\n')
+                               break;
+                       if (c < ' ' || c > '~') {
+                               if (c == '\t') {
+                                       c = ' ';
+                                       //       co %    8     !=     7
+                                       for (; (co % tabstop) != (tabstop - 1); co++) {
+                                               buf[co] = c;
+                                       }
+                               } else {
+                                       buf[co++] = '^';
+                                       c |= '@';       // make it visible
+                                       c &= 0x7f;      // get rid of hi bit
+                               }
+                       }
+                       // the co++ is done here so that the column will
+                       // not be overwritten when we blank-out the rest of line
+                       buf[co++] = c;
+                       if (tp >= end)
+                               break;
+               }
+               if (co >= columns + offset) {
+                       // skip to the end of the current text[] line
+                       while (tp < end && *tp++ != '\n');
+               }
+               // try to keep the cursor near it's current position
+               // remember how many chars in this row- where the cursor sits
+               // blank out the rest of the buffer
+               while (co < MAX_SCR_COLS - 1) {
+                       buf[co++] = ' ';
+               }
+               buf[co++] = 0;  // NULL terminate the buffer
+
+               // if necessary, update virtual screen[] and terminal from buf[]
+               changed = FALSE;        // assume no change
+               sp = &screen[li * columns];     // start of screen line
+               for (co = 0; co < columns; co++) {
+                       if (sp[co] != buf[co + offset]) {
+                               sp[co] = buf[co + offset];
+                               changed = TRUE; // mark for redraw
+                       }
+               }
+               // if horz offset has changed, force a redraw
+               if (offset != old_offset)
+                       changed = TRUE;
+
+               // write all marked screen lines out to terminal
+               if (changed == TRUE) {
+                       place_cursor(li, 0);    // put cursor in correct place
+                       clear_to_eol(); // Erase to end of line
+                       if (full_screen == FALSE) {
+                               // don't redraw every column on terminal
+                               // look backwards for last non-blank
+                               for (co = columns + offset; co >= 0; co--) {
+                                       // break;
+                                       if (buf[co] != ' ')
+                                               break;
+                               }
+                               co++;
+                       } else {
+                               // redraw every column on terminal
+                               co = columns;
+                       }
+                       // write line out to terminal
+                       write(1, buf + offset, co);
+               }
+       }
+
+       place_cursor(crow, ccol);
+       if (offset != old_offset)
+               old_offset = offset;
+}
index 255bd6b44f7f3220f88c85f575c546bdba1bb33c..c3037973d1598494fced9f36382bddd175d6a275 100644 (file)
 #ifdef BB_UUENCODE
        APPLET(uuencode, uuencode_main, _BB_DIR_USR_BIN)
 #endif
+#ifdef BB_VI
+       APPLET(vi, vi_main, _BB_DIR_BIN)
+#endif
 #ifdef BB_WATCHDOG
        APPLET(watchdog, watchdog_main, _BB_DIR_SBIN)
 #endif
index aa7f0af22792cac176bb11d285ecbaf9d671aae9..abd0156adcbebed472965578104cd9ae88b334c3 100644 (file)
        "$ uudecode busybox busybox > busybox.uu\n" \
        "$\n"
 
+#define vi_trivial_usage \
+       "[OPTION] [FILE]..."
+#define vi_full_usage \
+       "edit FILE.\n\n" \
+       "Options:\n" \
+       "\t-R\tRead-only- do not write to the file." 
+
 #define watchdog_trivial_usage \
        "DEV"
 #define watchdog_full_usage \
diff --git a/usage.h b/usage.h
index aa7f0af22792cac176bb11d285ecbaf9d671aae9..abd0156adcbebed472965578104cd9ae88b334c3 100644 (file)
--- a/usage.h
+++ b/usage.h
        "$ uudecode busybox busybox > busybox.uu\n" \
        "$\n"
 
+#define vi_trivial_usage \
+       "[OPTION] [FILE]..."
+#define vi_full_usage \
+       "edit FILE.\n\n" \
+       "Options:\n" \
+       "\t-R\tRead-only- do not write to the file." 
+
 #define watchdog_trivial_usage \
        "DEV"
 #define watchdog_full_usage \
diff --git a/vi.c b/vi.c
new file mode 100644 (file)
index 0000000..325ab34
--- /dev/null
+++ b/vi.c
@@ -0,0 +1,3683 @@
+/* vi: set sw=8 ts=8: */
+/*
+ * tiny vi.c: A small 'vi' clone
+ * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+char *vi_Version =
+       "$Id: vi.c,v 1.1 2001/04/04 17:31:15 andersen Exp $";
+
+/*
+ * To compile for standalone use:
+ *     gcc -Wall -Os -DSTANDALONE -o vi vi.c
+ *       or
+ *     gcc -Wall -Os -DSTANDALONE -DCRASHME -o vi vi.c         # include testing features
+ *     strip vi
+ */
+
+/*
+ * Things To Do:
+ *     EXINIT
+ *     $HOME/.exrc
+ *     add magic to search     /foo.*bar
+ *     add :help command
+ *     :map macros
+ *     how about mode lines:   vi: set sw=8 ts=8:
+ *     if mark[] values were line numbers rather than pointers
+ *        it would be easier to change the mark when add/delete lines
+ */
+
+//----  Feature --------------  Bytes to immplement
+#ifdef STANDALONE
+#define BB_FEATURE_VI_COLON    // 4288
+#define BB_FEATURE_VI_YANKMARK // 1408
+#define BB_FEATURE_VI_SEARCH   // 1088
+#define BB_FEATURE_VI_USE_SIGNALS      // 1056
+#define BB_FEATURE_VI_DOT_CMD  //  576
+#define BB_FEATURE_VI_READONLY //  128
+#define BB_FEATURE_VI_SETOPTS  //  576
+#define BB_FEATURE_VI_SET      //  224
+#define BB_FEATURE_VI_WIN_RESIZE       //  256  WIN_RESIZE
+// To test editor using CRASHME:
+//    vi -C filename
+// To stop testing, wait until all to text[] is deleted, or
+//    Ctrl-Z and kill -9 %1
+// while in the editor Ctrl-T will toggle the crashme function on and off.
+//#define BB_FEATURE_VI_CRASHME     // randomly pick commands to execute
+#endif                                                 /* STANDALONE */
+
+#ifndef STANDALONE
+#include "busybox.h"
+#endif                                                 /* STANDALONE */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <regex.h>
+#include <ctype.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdarg.h>
+#ifdef __linux__
+#include <stdint.h>            // INT32_MAX
+#else                                                  /* __linux__ */
+#include <values.h>
+extern int strncasecmp(const char *s1, const char *s2, size_t n);
+
+#define INT32_MAX      MAXINT
+#include <libgen.h>            //
+#endif                                                 /* __linux__ */
+
+#ifndef TRUE
+#define TRUE                   ((int)1)
+#define FALSE                  ((int)0)
+#endif                                                 /* TRUE */
+#define MAX_SCR_COLS           300
+
+// Misc. non-Ascii keys that report an escape sequence
+#define VI_K_UP                        128     // cursor key Up
+#define VI_K_DOWN              129     // cursor key Down
+#define VI_K_RIGHT             130     // Cursor Key Right
+#define VI_K_LEFT              131     // cursor key Left
+#define VI_K_HOME              132     // Cursor Key Home
+#define VI_K_END               133     // Cursor Key End
+#define VI_K_INSERT            134     // Cursor Key Insert
+#define VI_K_PAGEUP            135     // Cursor Key Page Up
+#define VI_K_PAGEDOWN          136     // Cursor Key Page Down
+#define VI_K_FUN1              137     // Function Key F1
+#define VI_K_FUN2              138     // Function Key F2
+#define VI_K_FUN3              139     // Function Key F3
+#define VI_K_FUN4              140     // Function Key F4
+#define VI_K_FUN5              141     // Function Key F5
+#define VI_K_FUN6              142     // Function Key F6
+#define VI_K_FUN7              143     // Function Key F7
+#define VI_K_FUN8              144     // Function Key F8
+#define VI_K_FUN9              145     // Function Key F9
+#define VI_K_FUN10             146     // Function Key F10
+#define VI_K_FUN11             147     // Function Key F11
+#define VI_K_FUN12             148     // Function Key F12
+
+static const int YANKONLY = FALSE;
+static const int YANKDEL = TRUE;
+static const int FORWARD = 1;  // code depends on "1"  for array index
+static const int BACK = -1;    // code depends on "-1" for array index
+static const int LIMITED = 0;  // how much of text[] in char_search
+static const int FULL = 1;     // how much of text[] in char_search
+
+static const int S_BEFORE_WS = 1;      // used in skip_thing() for moving "dot"
+static const int S_TO_WS = 2;          // used in skip_thing() for moving "dot"
+static const int S_OVER_WS = 3;                // used in skip_thing() for moving "dot"
+static const int S_END_PUNCT = 4;      // used in skip_thing() for moving "dot"
+static const int S_END_ALNUM = 5;      // used in skip_thing() for moving "dot"
+
+typedef unsigned char Byte;
+
+
+static int editing;            // >0 while we are editing a file
+static int cmd_mode;           // 0=command  1=insert
+static int file_modified;      // buffer contents changed
+static int err_method;         // indicate error with beep or flash
+static int fn_start;           // index of first cmd line file name
+static int save_argc;          // how many file names on cmd line
+static int cmdcnt;             // repetition count
+static fd_set rfds;            // use select() for small sleeps
+static struct timeval tv;      // use select() for small sleeps
+static char erase_char;                // the users erase character
+static int rows, columns;      // the terminal screen is this size
+static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
+static char *SOs, *SOn;
+static Byte *status_buffer;    // mesages to the user
+static Byte last_input_char;   // last char read from user
+static Byte last_forward_char; // last char searched for with 'f'
+static Byte *cfn;              // previous, current, and next file name
+static Byte *text, *end, *textend;     // pointers to the user data in memory
+static Byte *screen;           // pointer to the virtual screen buffer
+static int screensize;         //            and its size
+static Byte *screenbegin;      // index into text[], of top line on the screen
+static Byte *dot;              // where all the action takes place
+static int tabstop;
+static struct termios term_orig, term_vi;      // remember what the cooked mode was
+
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+static jmp_buf restart;                // catch_sig()
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+static struct winsize winsize; // remember the window size
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+#ifdef BB_FEATURE_VI_DOT_CMD
+static int adding2q;           // are we currently adding user input to q
+static Byte *last_modifying_cmd;       // last modifying cmd for "."
+static Byte *ioq, *ioq_start;  // pointer to string for get_one_char to "read"
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+#if    defined(BB_FEATURE_VI_DOT_CMD) || defined(BB_FEATURE_VI_YANKMARK)
+static Byte *modifying_cmds;   // cmds that modify text[]
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD || BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_READONLY
+static int readonly;
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+#ifdef BB_FEATURE_VI_SETOPTS
+static int autoindent;
+static int showmatch;
+static int ignorecase;
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+#ifdef BB_FEATURE_VI_YANKMARK
+static Byte *reg[28];          // named register a-z, "D", and "U" 0-25,26,27
+static int YDreg, Ureg;                // default delete register and orig line for "U"
+static Byte *mark[28];         // user marks points somewhere in text[]-  a-z and previous context ''
+static Byte *context_start, *context_end;
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_SEARCH
+static Byte *last_search_pattern;      // last pattern from a '/' or '?' search
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+
+
+static void edit_file(Byte *); // edit one file
+static void do_cmd(Byte);      // execute a command
+static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
+static Byte *begin_line(Byte *);       // return pointer to cur line B-o-l
+static Byte *end_line(Byte *); // return pointer to cur line E-o-l
+static Byte *dollar_line(Byte *);      // return pointer to just before NL
+static Byte *prev_line(Byte *);        // return pointer to prev line B-o-l
+static Byte *next_line(Byte *);        // return pointer to next line B-o-l
+static Byte *end_screen(void); // get pointer to last char on screen
+static int count_lines(Byte *, Byte *);        // count line from start to stop
+static Byte *find_line(int);   // find begining of line #li
+static Byte *move_to_col(Byte *, int); // move "p" to column l
+static int isblnk(Byte);       // is the char a blank or tab
+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 void dot_delete(void);  // delete the char at 'dot'
+static Byte *bound_dot(Byte *);        // make sure  text[0] <= P < "end"
+static Byte *new_screen(int, int);     // malloc virtual screen memory
+static Byte *new_text(int);    // malloc memory for text[] buffer
+static Byte *char_insert(Byte *, Byte);        // insert the char c at 'p'
+static Byte *stupid_insert(Byte *, Byte);      // stupidly insert the char c at 'p'
+static Byte find_range(Byte **, Byte **, Byte);        // return pointers for an object
+static int st_test(Byte *, int, int, Byte *);  // helper for skip_thing()
+static Byte *skip_thing(Byte *, int, int, int);        // skip some object
+static Byte *find_pair(Byte *, Byte);  // find matching pair ()  []  {}
+static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
+static Byte *text_hole_make(Byte *, int);      // at "p", make a 'size' byte hole
+static Byte *yank_delete(Byte *, Byte *, int, int);    // yank text[] into register then delete
+static void show_help(void);   // display some help info
+static void print_literal(Byte *, Byte *);     // copy s to buf, convert unprintable
+static void rawmode(void);     // set "raw" mode on tty
+static void cookmode(void);    // return to "cooked" mode on tty
+static int mysleep(int);       // sleep for 'h' 1/100 seconds
+static Byte readit(void);      // read (maybe cursor) key from stdin
+static Byte get_one_char(void);        // read 1 char from stdin
+static int file_size(Byte *);  // what is the byte size of "fn"
+static int file_insert(Byte *, Byte *, int);
+static int file_write(Byte *, Byte *, Byte *);
+static void place_cursor(int, int);
+static void screen_erase();
+static void clear_to_eol(void);
+static void clear_to_eos(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 beep(void);                // beep the terminal
+static void indicate_error(char);      // use flash or beep to indicate error
+static void show_status_line(void);    // put a message on the bottom line
+static void psb(char *, ...);  // Print Status Buf
+static void psbs(char *, ...); // Print Status Buf in standout mode
+static void ni(Byte *);                // display messages
+static void edit_status(void); // show file status on status line
+static void redraw(int);       // force a full screen refresh
+static void refresh(int);      // update the terminal from screen[]
+
+#ifdef BB_FEATURE_VI_SEARCH
+static Byte *char_search(Byte *, Byte *, int, int);    // search for pattern starting at p
+static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+#ifdef BB_FEATURE_VI_COLON
+static void Hit_Return(void);
+static Byte *get_address(Byte *, int *);       // get colon addr, if present
+static void colon(Byte *);     // execute the "colon" mode cmds
+#endif                                                 /* BB_FEATURE_VI_COLON */
+#if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
+static Byte *get_input_line(Byte *);   // get input line- use "status line"
+#endif                                                 /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+static void winch_sig(int);    // catch window size changes
+static void suspend_sig(int);  // catch ctrl-Z
+static void alarm_sig(int);    // catch alarm time-outs
+static void catch_sig(int);    // catch ctrl-C
+static void core_sig(int);     // catch a core dump signal
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+#ifdef BB_FEATURE_VI_DOT_CMD
+static void start_new_cmd_q(Byte);     // new queue for command
+static void end_cmd_q();       // stop saving input chars
+#else                                                  /* BB_FEATURE_VI_DOT_CMD */
+#define end_cmd_q()
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+static void window_size_get(int);      // find out what size the window is
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+#ifdef BB_FEATURE_VI_SETOPTS
+static void showmatching(Byte *);      // show the matching pair ()  []  {}
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+#if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
+static Byte *string_insert(Byte *, Byte *);    // insert the string at 'p'
+#endif                                                 /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
+#ifdef BB_FEATURE_VI_YANKMARK
+static Byte *text_yank(Byte *, Byte *, int);   // save copy of "p" into a register
+static Byte what_reg(void);            // what is letter of current YDreg
+static void check_context(Byte);       // remember context for '' command
+static Byte *swap_context(Byte *);     // goto new context for '' command
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_CRASHME
+static void crash_dummy();
+static void crash_test();
+static int crashme = 0;
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+
+
+#ifdef STANDALONE
+int main(int argc, char **argv)
+#else
+extern int vi_main(int argc, char **argv)
+#endif                                 /* STANDALONE */
+{
+       extern char *optarg;
+       char c;
+
+#ifdef BB_FEATURE_VI_YANKMARK
+       int i;
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+
+       SOs = "\033[7m";        // Terminal standout mode on
+       SOn = "\033[0m";        // Terminal standout mode off
+#ifdef BB_FEATURE_VI_CRASHME
+       (void) srand((long) getpid());
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+       status_buffer = (Byte *) malloc(200);   // hold messages to user
+#ifdef BB_FEATURE_VI_READONLY
+       readonly = FALSE;
+       if (strncmp(argv[0], "view", 4) == 0) {
+               readonly = TRUE;
+       }
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+#ifdef BB_FEATURE_VI_SETOPTS
+       autoindent = 1;
+       ignorecase = 1;
+       showmatch = 1;
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+#ifdef BB_FEATURE_VI_YANKMARK
+       for (i = 0; i < 28; i++) {
+               reg[i] = 0;
+       }                                       // init the yank regs
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_DOT_CMD
+       modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~";      // cmds modifying text[]
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+
+       //  1-  process $HOME/.exrc file
+       //  2-  process EXINIT variable from environment
+       //  3-  process command line args
+       while ((c = getopt(argc, argv, "hCR")) != -1) {
+               switch (c) {
+#ifdef BB_FEATURE_VI_CRASHME
+               case 'C':
+                       crashme = 1;
+                       break;
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+#ifdef BB_FEATURE_VI_READONLY
+               case 'R':               // Read-only flag
+                       readonly = TRUE;
+                       break;
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+                       //case 'r':     // recover flag-  ignore- we don't use tmp file
+                       //case 'x':     // encryption flag- ignore
+                       //case 'c':     // execute command first
+                       //case 'h':     // help -- just use default
+               default:
+                       show_help();
+                       break;
+               }
+       }
+
+       // The argv array can be used by the ":next"  and ":rewind" commands
+       // save optind.
+       fn_start = optind;      // remember first file name for :next and :rew
+       save_argc = argc;
+
+       //----- This is the main file handling loop --------------
+       if (optind >= argc) {
+               editing = 1;    // 0= exit,  1= one file,  2= multiple files
+               edit_file(0);
+       } else {
+               for (; optind < argc; optind++) {
+                       editing = 1;    // 0=exit, 1=one file, 2+ =many files
+                       if (cfn != 0)
+                               free(cfn);
+                       cfn = (Byte *) strdup(argv[optind]);
+                       edit_file(cfn);
+               }
+       }
+       //-----------------------------------------------------------
+
+       return (0);
+}
+
+static void edit_file(Byte * fn)
+{
+       char c;
+       int cnt, size;
+
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+       char *msg;
+       int sig;
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+#ifdef BB_FEATURE_VI_YANKMARK
+       static Byte *cur_line;
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+
+       rawmode();
+       rows = 24;
+       columns = 80;
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+       window_size_get(0);
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+       new_screen(rows, columns);      // get memory for virtual screen
+
+       cnt = file_size(fn);    // file size
+       size = 2 * cnt;         // 200% of file size
+       new_text(size);         // get a text[] buffer
+       screenbegin = dot = end = text;
+       if (fn != 0) {
+               file_insert(fn, text, cnt);
+       } else {
+               (void) char_insert(text, '\n'); // start empty buf with dummy line
+       }
+       file_modified = FALSE;
+#ifdef BB_FEATURE_VI_YANKMARK
+       YDreg = 26;                     // default Yank/Delete reg
+       Ureg = 27;                      // hold orig line for "U" cmd
+       for (cnt = 0; cnt < 28; cnt++) {
+               mark[cnt] = 0;
+       }                                       // init the marks
+       mark[26] = mark[27] = text;     // init "previous context"
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+
+       err_method = 1;         // flash
+       last_forward_char = last_input_char = '\0';
+       crow = 0;
+       ccol = 0;
+       edit_status();
+
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+       signal(SIGHUP, catch_sig);
+       signal(SIGINT, catch_sig);
+       signal(SIGALRM, alarm_sig);
+       signal(SIGTERM, catch_sig);
+       signal(SIGQUIT, core_sig);
+       signal(SIGILL, core_sig);
+       signal(SIGTRAP, core_sig);
+       signal(SIGIOT, core_sig);
+       signal(SIGABRT, core_sig);
+       signal(SIGFPE, core_sig);
+       signal(SIGBUS, core_sig);
+       signal(SIGSEGV, core_sig);
+       signal(SIGSYS, core_sig);
+       signal(SIGWINCH, winch_sig);
+       signal(SIGTSTP, suspend_sig);
+       sig = setjmp(restart);
+       if (sig != 0) {
+               msg = "";
+               if (sig == SIGWINCH)
+                       msg = "(window resize)";
+               if (sig == SIGHUP)
+                       msg = "(hangup)";
+               if (sig == SIGINT)
+                       msg = "(interrupt)";
+               if (sig == SIGTERM)
+                       msg = "(terminate)";
+               if (sig == SIGBUS)
+                       msg = "(bus error)";
+               if (sig == SIGSEGV)
+                       msg = "(I tried to touch invalid memory)";
+               if (sig == SIGALRM)
+                       msg = "(alarm)";
+
+               psbs("-- caught signal %d %s--", sig, msg);
+       }
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+
+       editing = 1;
+       cmd_mode = 0;           // 0=command  1=insert  2='R'eplace
+       cmdcnt = 0;
+       tabstop = 8;
+       offset = 0;                     // no horizontal offset
+       c = '\0';
+#ifdef BB_FEATURE_VI_DOT_CMD
+       if (last_modifying_cmd != 0)
+               free(last_modifying_cmd);
+       if (ioq_start != NULL)
+               free(ioq_start);
+       ioq = ioq_start = last_modifying_cmd = 0;
+       adding2q = 0;
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+       redraw(TRUE);
+       show_status_line();
+
+       //------This is the main Vi cmd handling loop -----------------------
+       while (editing > 0) {
+#ifdef BB_FEATURE_VI_CRASHME
+               if (crashme > 0) {
+                       if ((end - text) > 1) {
+                               crash_dummy();  // generate a random command
+                       } else {
+                               crashme = 0;
+                               dot =
+                                       string_insert(text, (Byte *) "\n\n#####  Ran out of text to work on.  #####\n\n");      // insert the string
+                               refresh(FALSE);
+                       }
+               }
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+               last_input_char = c = get_one_char();   // get a cmd from user
+#ifdef BB_FEATURE_VI_YANKMARK
+               // save a copy of the current line- for the 'U" command
+               if (begin_line(dot) != cur_line) {
+                       cur_line = begin_line(dot);
+                       text_yank(begin_line(dot), end_line(dot), Ureg);
+               }
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_DOT_CMD
+               // These are commands that change text[].
+               // Remember the input for the "." command
+               if (!adding2q && ioq_start == 0
+                       && strchr((char *) modifying_cmds, c) != NULL) {
+                       start_new_cmd_q(c);
+               }
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+               do_cmd(c);              // execute the user command
+               //
+               // poll to see if there is input already waiting. if we are
+               // not able to display output fast enough to keep up, skip
+               // the display update until we catch up with input.
+               if (mysleep(0) == 0) {
+                       // no input pending- so update output
+                       refresh(FALSE);
+                       show_status_line();
+               }
+#ifdef BB_FEATURE_VI_CRASHME
+               if (crashme > 0)
+                       crash_test();   // test editor variables
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+       }
+       //-------------------------------------------------------------------
+
+       place_cursor(rows, 0);  // go to bottom of screen
+       clear_to_eol();         // Erase to end of line
+       cookmode();
+}
+
+static Byte readbuffer[BUFSIZ];
+
+#ifdef BB_FEATURE_VI_CRASHME
+static int totalcmds = 0;
+static int Mp = 85;            // Movement command Probability
+static int Np = 90;            // Non-movement command Probability
+static int Dp = 96;            // Delete command Probability
+static int Ip = 97;            // Insert command Probability
+static int Yp = 98;            // Yank command Probability
+static int Pp = 99;            // Put command Probability
+static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
+char chars[20] = "\t012345 abcdABCD-=.$";
+char *words[20] = { "this", "is", "a", "test",
+       "broadcast", "the", "emergency", "of",
+       "system", "quick", "brown", "fox",
+       "jumped", "over", "lazy", "dogs",
+       "back", "January", "Febuary", "March"
+};
+char *lines[20] = {
+       "You should have received a copy of the GNU General Public License\n",
+       "char c, cm, *cmd, *cmd1;\n",
+       "generate a command by percentages\n",
+       "Numbers may be typed as a prefix to some commands.\n",
+       "Quit, discarding changes!\n",
+       "Forced write, if permission originally not valid.\n",
+       "In general, any ex or ed command (such as substitute or delete).\n",
+       "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
+       "Please get w/ me and I will go over it with you.\n",
+       "The following is a list of scheduled, committed changes.\n",
+       "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
+       "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
+       "Any question about transactions please contact Sterling Huxley.\n",
+       "I will try to get back to you by Friday, December 31.\n",
+       "This Change will be implemented on Friday.\n",
+       "Let me know if you have problems accessing this;\n",
+       "Sterling Huxley recently added you to the access list.\n",
+       "Would you like to go to lunch?\n",
+       "The last command will be automatically run.\n",
+       "This is too much english for a computer geek.\n",
+};
+char *multilines[20] = {
+       "You should have received a copy of the GNU General Public License\n",
+       "char c, cm, *cmd, *cmd1;\n",
+       "generate a command by percentages\n",
+       "Numbers may be typed as a prefix to some commands.\n",
+       "Quit, discarding changes!\n",
+       "Forced write, if permission originally not valid.\n",
+       "In general, any ex or ed command (such as substitute or delete).\n",
+       "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
+       "Please get w/ me and I will go over it with you.\n",
+       "The following is a list of scheduled, committed changes.\n",
+       "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
+       "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
+       "Any question about transactions please contact Sterling Huxley.\n",
+       "I will try to get back to you by Friday, December 31.\n",
+       "This Change will be implemented on Friday.\n",
+       "Let me know if you have problems accessing this;\n",
+       "Sterling Huxley recently added you to the access list.\n",
+       "Would you like to go to lunch?\n",
+       "The last command will be automatically run.\n",
+       "This is too much english for a computer geek.\n",
+};
+
+// create a random command to execute
+static void crash_dummy()
+{
+       static int sleeptime;   // how long to pause between commands
+       char c, cm, *cmd, *cmd1;
+       int i, cnt, thing, rbi, startrbi, percent;
+
+       // "dot" movement commands
+       cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
+
+       // is there already a command running?
+       if (strlen((char *) readbuffer) > 0)
+               goto cd1;
+  cd0:
+       startrbi = rbi = 0;
+       sleeptime = 0;          // how long to pause between commands
+       memset(readbuffer, '\0', BUFSIZ - 1);   // clear the read buffer
+       // generate a command by percentages
+       percent = (int) lrand48() % 100;        // get a number from 0-99
+       if (percent < Mp) {     //  Movement commands
+               // available commands
+               cmd = cmd1;
+               M++;
+       } else if (percent < Np) {      //  non-movement commands
+               cmd = "mz<>\'\"";       // available commands
+               N++;
+       } else if (percent < Dp) {      //  Delete commands
+               cmd = "dx";             // available commands
+               D++;
+       } else if (percent < Ip) {      //  Inset commands
+               cmd = "iIaAsrJ";        // available commands
+               I++;
+       } else if (percent < Yp) {      //  Yank commands
+               cmd = "yY";             // available commands
+               Y++;
+       } else if (percent < Pp) {      //  Put commands
+               cmd = "pP";             // available commands
+               P++;
+       } else {
+               // We do not know how to handle this command, try again
+               U++;
+               goto cd0;
+       }
+       // randomly pick one of the available cmds from "cmd[]"
+       i = (int) lrand48() % strlen(cmd);
+       cm = cmd[i];
+       if (strchr(":\024", cm))
+               goto cd0;               // dont allow these commands
+       readbuffer[rbi++] = cm; // put cmd into input buffer
+
+       // now we have the command-
+       // there are 1, 2, and multi char commands
+       // find out which and generate the rest of command as necessary
+       if (strchr("dmryz<>\'\"", cm)) {        // 2-char commands
+               cmd1 = " \n\r0$^-+wWeEbBhjklHL";
+               if (cm == 'm' || cm == '\'' || cm == '\"') {    // pick a reg[]
+                       cmd1 = "abcdefghijklmnopqrstuvwxyz";
+               }
+               thing = (int) lrand48() % strlen(cmd1); // pick a movement command
+               c = cmd1[thing];
+               readbuffer[rbi++] = c;  // add movement to input buffer
+       }
+       if (strchr("iIaAsc", cm)) {     // multi-char commands
+               if (cm == 'c') {
+                       // change some thing
+                       thing = (int) lrand48() % strlen(cmd1); // pick a movement command
+                       c = cmd1[thing];
+                       readbuffer[rbi++] = c;  // add movement to input buffer
+               }
+               thing = (int) lrand48() % 4;    // what thing to insert
+               cnt = (int) lrand48() % 10;     // how many to insert
+               for (i = 0; i < cnt; i++) {
+                       if (thing == 0) {       // insert chars
+                               readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
+                       } else if (thing == 1) {        // insert words
+                               strcat((char *) readbuffer, words[(int) lrand48() % 20]);
+                               strcat((char *) readbuffer, " ");
+                               sleeptime = 0;  // how fast to type
+                       } else if (thing == 2) {        // insert lines
+                               strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
+                               sleeptime = 0;  // how fast to type
+                       } else {        // insert multi-lines
+                               strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
+                               sleeptime = 0;  // how fast to type
+                       }
+               }
+               strcat((char *) readbuffer, "\033");
+       }
+  cd1:
+       totalcmds++;
+       if (sleeptime > 0)
+               (void) mysleep(sleeptime);      // sleep 1/100 sec
+}
+
+// test to see if there are any errors
+static void crash_test()
+{
+       static time_t oldtim;
+       time_t tim;
+       char d[2], buf[100], msg[BUFSIZ];
+
+       msg[0] = '\0';
+       if (end < text) {
+               strcat((char *) msg, "end<text ");
+       }
+       if (end > textend) {
+               strcat((char *) msg, "end>textend ");
+       }
+       if (dot < text) {
+               strcat((char *) msg, "dot<text ");
+       }
+       if (dot > end) {
+               strcat((char *) msg, "dot>end ");
+       }
+       if (screenbegin < text) {
+               strcat((char *) msg, "screenbegin<text ");
+       }
+       if (screenbegin > end - 1) {
+               strcat((char *) msg, "screenbegin>end-1 ");
+       }
+
+       if (strlen(msg) > 0) {
+               alarm(0);
+               sprintf(buf, "\n\n%d: \'%c\' ", totalcmds, last_input_char);
+               write(1, buf, strlen(buf));
+               write(1, msg, strlen(msg));
+               write(1, "\n\n\n", 3);
+               write(1, "\033[7m[Hit return to continue]\033[0m", 32);
+               while (read(0, d, 1) > 0) {
+                       if (d[0] == '\n' || d[0] == '\r')
+                               break;
+               }
+               alarm(3);
+       }
+       tim = (time_t) time((time_t *) 0);
+       if (tim >= (oldtim + 3)) {
+               sprintf((char *) status_buffer,
+                               "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
+                               totalcmds, M, N, I, D, Y, P, U, end - text + 1);
+               oldtim = tim;
+       }
+       return;
+}
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+
+//---------------------------------------------------------------------
+//----- the Ascii Chart -----------------------------------------------
+//
+//  00 nul   01 soh   02 stx   03 etx   04 eot   05 enq   06 ack   07 bel
+//  08 bs    09 ht    0a nl    0b vt    0c np    0d cr    0e so    0f si
+//  10 dle   11 dc1   12 dc2   13 dc3   14 dc4   15 nak   16 syn   17 etb
+//  18 can   19 em    1a sub   1b esc   1c fs    1d gs    1e rs    1f us
+//  20 sp    21 !     22 "     23 #     24 $     25 %     26 &     27 '
+//  28 (     29 )     2a *     2b +     2c ,     2d -     2e .     2f /
+//  30 0     31 1     32 2     33 3     34 4     35 5     36 6     37 7
+//  38 8     39 9     3a :     3b ;     3c <     3d =     3e >     3f ?
+//  40 @     41 A     42 B     43 C     44 D     45 E     46 F     47 G
+//  48 H     49 I     4a J     4b K     4c L     4d M     4e N     4f O
+//  50 P     51 Q     52 R     53 S     54 T     55 U     56 V     57 W
+//  58 X     59 Y     5a Z     5b [     5c \     5d ]     5e ^     5f _
+//  60 `     61 a     62 b     63 c     64 d     65 e     66 f     67 g
+//  68 h     69 i     6a j     6b k     6c l     6d m     6e n     6f o
+//  70 p     71 q     72 r     73 s     74 t     75 u     76 v     77 w
+//  78 x     79 y     7a z     7b {     7c |     7d }     7e ~     7f del
+//---------------------------------------------------------------------
+
+//----- Execute a Vi Command -----------------------------------
+static void do_cmd(Byte c)
+{
+       Byte c1, *p, *q, *msg, buf[9], *save_dot;
+       int cnt, i, j, dir, yf;
+
+       c1 = c;                         // quiet the compiler
+       cnt = yf = dir = 0;     // quiet the compiler
+       p = q = save_dot = msg = buf;   // quiet the compiler
+       memset(buf, '\0', 9);   // clear buf
+       if (cmd_mode == 2) {
+               // we are 'R'eplacing the current *dot with new char
+               if (*dot == '\n') {
+                       // don't Replace past E-o-l
+                       cmd_mode = 1;   // convert to insert
+               } else {
+                       if (1 <= c && c <= 127) {       // only ASCII chars
+                               if (c != 27)
+                                       dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
+                               dot = char_insert(dot, c);      // insert new char
+                       }
+                       goto dc1;
+               }
+       }
+       if (cmd_mode == 1) {
+               // insert the char c at "dot"
+               if (1 <= c && c <= 127) {
+                       dot = char_insert(dot, c);      // only ASCII chars
+               }
+               goto dc1;
+       }
+
+       switch (c) {
+               //case 0x01:        // soh
+               //case 0x09:        // ht
+               //case 0x0b:        // vt
+               //case 0x0e:        // so
+               //case 0x0f:        // si
+               //case 0x10:        // dle
+               //case 0x11:        // dc1
+               //case 0x12:        // dc2
+               //case 0x13:        // dc3
+       case 0x14:                      // dc4  ctrl-T
+#ifdef BB_FEATURE_VI_CRASHME
+               crashme = (crashme == 0) ? 1 : 0;
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+               break;
+               //case 0x16:        // syn
+               //case 0x17:        // etb
+               //case 0x18:        // can
+               //case 0x1c:        // fs
+               //case 0x1d:        // gs
+               //case 0x1e:        // rs
+               //case 0x1f:        // us
+               //case '!':     // !- 
+               //case '#':     // #- 
+               //case '&':     // &- 
+               //case '(':     // (- 
+               //case ')':     // )- 
+               //case '*':     // *- 
+               //case ',':     // ,- 
+               //case '=':     // =- 
+               //case '@':     // @- 
+               //case 'F':     // F- 
+               //case 'G':     // G- 
+               //case 'K':     // K- 
+               //case 'M':     // M- 
+               //case 'Q':     // Q- 
+               //case 'S':     // S- 
+               //case 'T':     // T- 
+               //case 'V':     // V- 
+               //case '[':     // [- 
+               //case '\\':        // \- 
+               //case ']':     // ]- 
+               //case '_':     // _- 
+               //case '`':     // `- 
+               //case 'g':     // g- 
+               //case 'm':     // m- 
+               //case 't':     // t- 
+               //case 'v':     // v- 
+       default:                        // unrecognised command
+               buf[0] = c;
+               buf[1] = '\0';
+               if (c <= ' ') {
+                       buf[0] = '^';
+                       buf[1] = c + '@';
+                       buf[2] = '\0';
+               }
+               ni((Byte *) buf);
+               end_cmd_q();    // stop adding to q
+       case 0x00:                      // nul- ignore
+               break;
+       case 2:                 // ctrl-B  scroll up   full screen
+       case VI_K_PAGEUP:       // Cursor Key Page Up
+               dot_scroll(rows - 2, -1);
+               break;
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+       case 0x03:                      // ctrl-C   interrupt
+               longjmp(restart, 1);
+               break;
+       case 26:                        // ctrl-Z suspend
+               suspend_sig(SIGTSTP);
+               break;
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+       case 4:                 // ctrl-D  scroll down half screen
+               dot_scroll((rows - 2) / 2, 1);
+               break;
+       case 5:                 // ctrl-E  scroll down one line
+               dot_scroll(1, 1);
+               break;
+       case 6:                 // ctrl-F  scroll down full screen
+       case VI_K_PAGEDOWN:     // Cursor Key Page Down
+               dot_scroll(rows - 2, 1);
+               break;
+       case 7:                 // ctrl-G  show current status
+               edit_status();
+               break;
+       case 'h':                       // h- move left
+       case VI_K_LEFT: // cursor key Left
+       case 8:                 // ^h- move left    (This may be ERASE char)
+       case 127:                       // DEL- move left   (This may be ERASE char)
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_left();
+               break;
+       case 10:                        // Newline ^J
+       case 'j':                       // j- goto next line, same col
+       case VI_K_DOWN: // cursor key Down
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_next();             // go to next B-o-l
+               dot = move_to_col(dot, ccol + offset);  // try stay in same col
+               break;
+       case 12:                        // ctrl-L  force redraw whole screen
+               place_cursor(0, 0);     // put cursor in correct place
+               clear_to_eos(); // tel terminal to erase display
+               (void) mysleep(10);
+               screen_erase(); // erase the internal screen buffer
+               refresh(TRUE);  // this will redraw the entire display
+               break;
+       case 13:                        // Carriage Return ^M
+       case '+':                       // +- goto next line
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_next();
+               dot_skip_over_ws();
+               break;
+       case 21:                        // ctrl-U  scroll up   half screen
+               dot_scroll((rows - 2) / 2, -1);
+               break;
+       case 25:                        // ctrl-Y  scroll up one line
+               dot_scroll(1, -1);
+               break;
+       case 0x1b:                      // esc
+               if (cmd_mode == 0)
+                       indicate_error(c);
+               cmd_mode = 0;   // stop insrting
+               end_cmd_q();
+               *status_buffer = '\0';  // clear status buffer
+               break;
+       case ' ':                       // move right
+       case 'l':                       // move right
+       case VI_K_RIGHT:        // Cursor Key Right
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_right();
+               break;
+#ifdef BB_FEATURE_VI_YANKMARK
+       case '"':                       // "- name a register to use for Delete/Yank
+               c1 = get_one_char();
+               c1 = tolower(c1);
+               if (islower(c1)) {
+                       YDreg = c1 - 'a';
+               } else {
+                       indicate_error(c);
+               }
+               break;
+       case '\'':                      // '- goto a specific mark
+               c1 = get_one_char();
+               c1 = tolower(c1);
+               if (islower(c1)) {
+                       c1 = c1 - 'a';
+                       // get the b-o-l
+                       q = mark[(int) c1];
+                       if (text <= q && q < end) {
+                               dot = q;
+                               dot_begin();    // go to B-o-l
+                               dot_skip_over_ws();
+                       }
+               } else if (c1 == '\'') {        // goto previous context
+                       dot = swap_context(dot);        // swap current and previous context
+                       dot_begin();    // go to B-o-l
+                       dot_skip_over_ws();
+               } else {
+                       indicate_error(c);
+               }
+               break;
+       case 'm':                       // m- Mark a line
+               // this is really stupid.  If there are any inserts or deletes
+               // between text[0] and dot then this mark will not point to the
+               // correct location! It could be off by many lines!
+               // Well..., at least its quick and dirty.
+               c1 = get_one_char();
+               c1 = tolower(c1);
+               if (islower(c1)) {
+                       c1 = c1 - 'a';
+                       // remember the line
+                       mark[(int) c1] = dot;
+               } else {
+                       indicate_error(c);
+               }
+               break;
+       case 'P':                       // P- Put register before
+       case 'p':                       // p- put register after
+               p = reg[YDreg];
+               if (p == 0) {
+                       psbs("Nothing in register %c", what_reg());
+                       break;
+               }
+               // are we putting whole lines or strings
+               if (strchr((char *) p, '\n') != NULL) {
+                       if (c == 'P') {
+                               dot_begin();    // putting lines- Put above
+                       }
+                       if (c == 'p') {
+                               // are we putting after very last line?
+                               if (end_line(dot) == (end - 1)) {
+                                       dot = end;      // force dot to end of text[]
+                               } else {
+                                       dot_next();     // next line, then put before
+                               }
+                       }
+               } else {
+                       if (c == 'p')
+                               dot_right();    // move to right, can move to NL
+               }
+               dot = string_insert(dot, p);    // insert the string
+               end_cmd_q();    // stop adding to q
+               break;
+       case 'u':                       // u- 
+       case 'U':                       // U- Undo; replace current line with original version
+               if (reg[Ureg] != 0) {
+                       p = begin_line(dot);
+                       q = end_line(dot);
+                       p = text_hole_delete(p, q);     // delete cur line
+                       p = string_insert(p, reg[Ureg]);        // insert orig line
+                       dot = p;
+                       dot_skip_over_ws();
+               }
+               break;
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+       case '$':                       // $- goto end of line
+       case VI_K_END:          // Cursor Key End
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot = end_line(dot + 1);
+               break;
+       case '%':                       // %- find matching char of pair () [] {}
+               for (q = dot; q < end && *q != '\n'; q++) {
+                       if (strchr("()[]{}", *q) != NULL) {
+                               // we found half of a pair
+                               p = find_pair(q, *q);
+                               if (p == NULL) {
+                                       indicate_error(c);
+                               } else {
+                                       dot = p;
+                               }
+                               break;
+                       }
+               }
+               if (*q == '\n')
+                       indicate_error(c);
+               break;
+       case 'f':                       // f- forward to a user specified char
+               last_forward_char = get_one_char();     // get the search char
+               //
+               // dont seperate these two commands. 'f' depends on ';'
+               //
+               //**** fall thru to ... 'i'
+       case ';':                       // ;- look at rest of line for last forward char
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               q = dot + 1;
+               while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
+                       q++;
+               }
+               if (*q == last_forward_char)
+                       dot = q;
+               break;
+       case '-':                       // -- goto prev line
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_prev();
+               dot_skip_over_ws();
+               break;
+#ifdef BB_FEATURE_VI_DOT_CMD
+       case '.':                       // .- repeat the last modifying command
+               // Stuff the last_modifying_cmd back into stdin
+               // and let it be re-executed.
+               if (last_modifying_cmd != 0) {
+                       ioq = ioq_start = (Byte *) strdup((char *) last_modifying_cmd);
+               }
+               break;
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+#ifdef BB_FEATURE_VI_SEARCH
+       case '?':                       // /- search for a pattern
+       case '/':                       // /- search for a pattern
+               buf[0] = c;
+               buf[1] = '\0';
+               q = get_input_line(buf);        // get input line- use "status line"
+               if (strlen((char *) q) == 1)
+                       goto dc3;       // if no pat re-use old pat
+               if (strlen((char *) q) > 1) {   // new pat- save it and find
+                       // there is a new pat
+                       if (last_search_pattern != 0) {
+                               free(last_search_pattern);
+                       }
+                       last_search_pattern = (Byte *) strdup((char *) q);
+                       goto dc3;       // now find the pattern
+               }
+               // user changed mind and erased the "/"-  do nothing
+               break;
+       case 'N':                       // N- backward search for last pattern
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dir = BACK;             // assume BACKWARD search
+               p = dot - 1;
+               if (last_search_pattern[0] == '?') {
+                       dir = FORWARD;
+                       p = dot + 1;
+               }
+               goto dc4;               // now search for pattern
+               break;
+       case 'n':                       // n- repeat search for last pattern
+               // search rest of text[] starting at next char
+               // if search fails return orignal "p" not the "p+1" address
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+         dc3:
+               if (last_search_pattern == 0) {
+                       msg = (Byte *) "No previous regular expression";
+                       goto dc2;
+               }
+               if (last_search_pattern[0] == '/') {
+                       dir = FORWARD;  // assume FORWARD search
+                       p = dot + 1;
+               }
+               if (last_search_pattern[0] == '?') {
+                       dir = BACK;
+                       p = dot - 1;
+               }
+         dc4:
+               q = char_search(p, last_search_pattern + 1, dir, FULL);
+               if (q != NULL) {
+                       dot = q;        // good search, update "dot"
+                       msg = (Byte *) "";
+                       goto dc2;
+               }
+               // no pattern found between "dot" and "end"- continue at top
+               p = text;
+               if (dir == BACK) {
+                       p = end - 1;
+               }
+               q = char_search(p, last_search_pattern + 1, dir, FULL);
+               if (q != NULL) {        // found something
+                       dot = q;        // found new pattern- goto it
+                       msg = (Byte *) "search hit BOTTOM, continuing at TOP";
+                       if (dir == BACK) {
+                               msg = (Byte *) "search hit TOP, continuing at BOTTOM";
+                       }
+               } else {
+                       msg = (Byte *) "Pattern not found";
+               }
+         dc2:
+               psbs("%s", msg);
+               break;
+       case '{':                       // {- move backward paragraph
+               q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
+               if (q != NULL) {        // found blank line
+                       dot = next_line(q);     // move to next blank line
+               }
+               break;
+       case '}':                       // }- move forward paragraph
+               q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
+               if (q != NULL) {        // found blank line
+                       dot = next_line(q);     // move to next blank line
+               }
+               break;
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+       case '0':                       // 0- goto begining of line
+       case '1':                       // 1- 
+       case '2':                       // 2- 
+       case '3':                       // 3- 
+       case '4':                       // 4- 
+       case '5':                       // 5- 
+       case '6':                       // 6- 
+       case '7':                       // 7- 
+       case '8':                       // 8- 
+       case '9':                       // 9- 
+               if (c == '0' && cmdcnt < 1) {
+                       dot_begin();    // this was a standalone zero
+               } else {
+                       cmdcnt = cmdcnt * 10 + (c - '0');       // this 0 is part of a number
+               }
+               break;
+       case ':':                       // :- the colon mode commands
+#ifdef BB_FEATURE_VI_COLON
+               p = get_input_line((Byte *) ":");       // get input line- use "status line"
+               colon(p);               // execute the command
+#else                                                  /* BB_FEATURE_VI_COLON */
+               *status_buffer = '\0';  // clear the status buffer
+               place_cursor(rows - 1, 0);      // go to Status line, bottom of screen
+               clear_to_eol(); // clear the line
+               write(1, ":", 1);       // write out the : prompt
+               for (cnt = 0; cnt < 8; cnt++) {
+                       c1 = get_one_char();
+                       if (c1 == '\n' || c1 == '\r') {
+                               break;
+                       }
+                       buf[cnt] = c1;
+                       buf[cnt + 1] = '\0';
+                       write(1, buf + cnt, 1); // echo the char
+               }
+               cnt = strlen((char *) buf);
+               if (strncasecmp((char *) buf, "quit", cnt) == 0 ||
+                       strncasecmp((char *) buf, "q!", cnt) == 0) {    // delete lines
+                       if (file_modified == TRUE && buf[1] != '!') {
+                               psbs("No write since last change (:quit! overrides)");
+                       } else {
+                               editing = 0;
+                       }
+               } else if (strncasecmp((char *) buf, "write", cnt) == 0 ||
+                                  strncasecmp((char *) buf, "wq", cnt) == 0) {
+                       cnt = file_write(cfn, text, end - 1);
+                       file_modified = FALSE;
+                       psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
+                       if (buf[1] == 'q') {
+                               editing = 0;
+                       }
+               } else {                // unrecognised cmd
+                       ni((Byte *) buf);
+               }
+#endif                                                 /* BB_FEATURE_VI_COLON */
+               break;
+       case '<':                       // <- Left  shift something
+       case '>':                       // >- Right shift something
+               cnt = count_lines(text, dot);   // remember what line we are on
+               c1 = get_one_char();    // get the type of thing to delete
+               find_range(&p, &q, c1);
+               (void) yank_delete(p, q, 1, YANKONLY);  // save copy before change
+               p = begin_line(p);
+               q = end_line(q);
+               i = count_lines(p, q);  // # of lines we are shifting
+               for ( ; i > 0; i--, p = next_line(p)) {
+                       if (c == '<') {
+                               // shift left- remove tab or 8 spaces
+                               if (*p == '\t') {
+                                       // shrink buffer 1 char
+                                       (void) text_hole_delete(p, p);
+                               } else if (*p == ' ') {
+                                       // we should be calculating columns, not just SPACE
+                                       for (j = 0; *p == ' ' && j < tabstop; j++) {
+                                               (void) text_hole_delete(p, p);
+                                       }
+                               }
+                       } else if (c == '>') {
+                               // shift right -- add tab or 8 spaces
+                               (void) char_insert(p, '\t');
+                       }
+               }
+               dot = find_line(cnt);   // what line were we on
+               dot_skip_over_ws();
+               end_cmd_q();    // stop adding to q
+               break;
+       case 'A':                       // A- append at e-o-l
+               dot_end();              // go to e-o-l
+               //**** fall thru to ... 'a'
+       case 'a':                       // a- append after current char
+               if (*dot != '\n')
+                       dot++;
+               goto dc_i;
+               break;
+       case 'B':                       // B- back a blank-delimited Word
+       case 'E':                       // E- end of a blank-delimited word
+       case 'W':                       // W- forward a blank-delimited word
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dir = FORWARD;
+               if (c == 'B')
+                       dir = BACK;
+               if (c == 'W' || isspace(dot[dir])) {
+                       dot = skip_thing(dot, 1, dir, S_TO_WS);
+                       dot = skip_thing(dot, 2, dir, S_OVER_WS);
+               }
+               if (c != 'W')
+                       dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
+               break;
+       case 'C':                       // C- Change to e-o-l
+       case 'D':                       // D- delete to e-o-l
+               save_dot = dot;
+               dot = dollar_line(dot); // move to before NL
+               // copy text into a register and delete
+               dot = yank_delete(save_dot, dot, 0, YANKDEL);   // delete to e-o-l
+               if (c == 'C')
+                       goto dc_i;      // start inserting
+#ifdef BB_FEATURE_VI_DOT_CMD
+               if (c == 'D')
+                       end_cmd_q();    // stop adding to q
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+               break;
+       case 'H':                       // H- goto top line on screen
+               dot = screenbegin;
+               if (cmdcnt > (rows - 1)) {
+                       cmdcnt = (rows - 1);
+               }
+               if (cmdcnt-- > 1) {
+                       do_cmd('+');
+               }                               // repeat cnt
+               dot_skip_over_ws();
+               break;
+       case 'I':                       // I- insert before first non-blank
+               dot_begin();    // 0
+               dot_skip_over_ws();
+               //**** fall thru to ... 'i'
+       case 'i':                       // i- insert before current char
+       case VI_K_INSERT:       // Cursor Key Insert
+         dc_i:
+               cmd_mode = 1;   // start insrting
+               psb("-- Insert --");
+               break;
+       case 'J':                       // J- join current and next lines together
+               if (cmdcnt-- > 2) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_end();              // move to NL
+               if (dot < end - 1) {    // make sure not last char in text[]
+                       *dot++ = ' ';   // replace NL with space
+                       while (isblnk(*dot)) {  // delete leading WS
+                               dot_delete();
+                       }
+               }
+               end_cmd_q();    // stop adding to q
+               break;
+       case 'L':                       // L- goto bottom line on screen
+               dot = end_screen();
+               if (cmdcnt > (rows - 1)) {
+                       cmdcnt = (rows - 1);
+               }
+               if (cmdcnt-- > 1) {
+                       do_cmd('-');
+               }                               // repeat cnt
+               dot_begin();
+               dot_skip_over_ws();
+               break;
+       case 'O':                       // O- open a empty line above
+               //    0i\n\033-i
+               p = begin_line(dot);
+               if (p[-1] == '\n') {
+                       dot_prev();
+       case 'o':                       // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
+                       dot_end();
+                       dot = char_insert(dot, '\n');
+               } else {
+                       dot_begin();    // 0
+                       dot = char_insert(dot, '\n');   // i\n\033
+                       dot_prev();     // -
+               }
+               goto dc_i;
+               break;
+       case 'R':                       // R- continuous Replace char
+               cmd_mode = 2;
+               psb("-- Replace --");
+               break;
+       case 'X':                       // X- delete char before dot
+       case 'x':                       // x- delete the current char
+       case 's':                       // s- substitute the current char
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dir = 0;
+               if (c == 'X')
+                       dir = -1;
+               if (dot[dir] != '\n') {
+                       if (c == 'X')
+                               dot--;  // delete prev char
+                       dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
+               }
+               if (c == 's')
+                       goto dc_i;      // start insrting
+               end_cmd_q();    // stop adding to q
+               break;
+       case 'Z':                       // Z- if modified, {write}; exit
+               // ZZ means to save file (if necessary), then exit
+               c1 = get_one_char();
+               if (c1 != 'Z') {
+                       indicate_error(c);
+                       break;
+               }
+               if (file_modified == TRUE
+#ifdef BB_FEATURE_VI_READONLY
+                       && readonly == FALSE
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+                       ) {
+                       cnt = file_write(cfn, text, end - 1);
+                       if (cnt == (end - 1 - text + 1)) {
+                               editing = 0;
+                       }
+               } else {
+                       editing = 0;
+               }
+               break;
+       case '^':                       // ^- move to first non-blank on line
+               dot_begin();
+               dot_skip_over_ws();
+               break;
+       case 'b':                       // b- back a word
+       case 'e':                       // e- end of word
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dir = FORWARD;
+               if (c == 'b')
+                       dir = BACK;
+               if ((dot + dir) < text || (dot + dir) > end - 1)
+                       break;
+               dot += dir;
+               if (isspace(*dot)) {
+                       dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
+               }
+               if (isalnum(*dot) || *dot == '_') {
+                       dot = skip_thing(dot, 1, dir, S_END_ALNUM);
+               } else if (ispunct(*dot)) {
+                       dot = skip_thing(dot, 1, dir, S_END_PUNCT);
+               }
+               break;
+       case 'c':                       // c- change something
+       case 'd':                       // d- delete something
+#ifdef BB_FEATURE_VI_YANKMARK
+       case 'y':                       // y- yank   something
+       case 'Y':                       // Y- Yank a line
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+               yf = YANKDEL;   // assume either "c" or "d"
+#ifdef BB_FEATURE_VI_YANKMARK
+               if (c == 'y' || c == 'Y')
+                       yf = YANKONLY;
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+               c1 = 'y';
+               if (c != 'Y')
+                       c1 = get_one_char();    // get the type of thing to delete
+               find_range(&p, &q, c1);
+               if (c1 == 27) { // ESC- user changed mind and wants out
+                       c = c1 = 27;    // Escape- do nothing
+               } else if (strchr("wW", c1)) {
+                       if (c == 'c') {
+                               // don't include trailing WS as part of word
+                               while (isblnk(*q)) {
+                                       if (q <= text || q[-1] == '\n')
+                                               break;
+                                       q--;
+                               }
+                       }
+                       dot = yank_delete(p, q, 0, yf); // delete word
+               } else if (strchr("0bBeE$", c1)) {
+                       // single line copy text into a register and delete
+                       dot = yank_delete(p, q, 0, yf); // delete word
+               } else if (strchr("cdykjHL+-{}\r\n", c1)) {
+                       // multiple line copy text into a register and delete
+                       dot = yank_delete(p, q, 1, yf); // delete lines
+                       if (c == 'd') {
+                               dot_begin();
+                               dot_skip_over_ws();
+                       }
+               } else {
+                       // could not recognize object
+                       c = c1 = 27;    // error-
+                       indicate_error(c);
+               }
+               if (c1 != 27) {
+                       // if CHANGING, not deleting, start inserting after the delete
+                       if (c == 'c') {
+                               strcpy((char *) buf, "Change");
+                               goto dc_i;      // start inserting
+                       }
+                       if (c == 'd') {
+                               strcpy((char *) buf, "Delete");
+                       }
+#ifdef BB_FEATURE_VI_YANKMARK
+                       if (c == 'y' || c == 'Y') {
+                               strcpy((char *) buf, "Yank");
+                       }
+                       p = reg[YDreg];
+                       q = p + strlen((char *) p);
+                       for (cnt = 0; p <= q; p++) {
+                               if (*p == '\n')
+                                       cnt++;
+                       }
+                       psb("%s %d lines (%d chars) using [%c]",
+                               buf, cnt, strlen((char *) reg[YDreg]), what_reg());
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+                       end_cmd_q();    // stop adding to q
+               }
+               break;
+       case 'k':                       // k- goto prev line, same col
+       case VI_K_UP:           // cursor key Up
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               dot_prev();
+               dot = move_to_col(dot, ccol + offset);  // try stay in same col
+               break;
+       case 'r':                       // r- replace the current char with user input
+               c1 = get_one_char();    // get the replacement char
+               if (*dot != '\n') {
+                       *dot = c1;
+                       file_modified = TRUE;   // has the file been modified
+               }
+               end_cmd_q();    // stop adding to q
+               break;
+       case 'w':                       // w- forward a word
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               if (isalnum(*dot) || *dot == '_') {     // we are on ALNUM
+                       dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
+               } else if (ispunct(*dot)) {     // we are on PUNCT
+                       dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
+               }
+               if (dot < end - 1)
+                       dot++;          // move over word
+               if (isspace(*dot)) {
+                       dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
+               }
+               break;
+       case 'z':                       // z-
+               c1 = get_one_char();    // get the replacement char
+               cnt = 0;
+               if (c1 == '.')
+                       cnt = (rows - 2) / 2;   // put dot at center
+               if (c1 == '-')
+                       cnt = rows - 2; // put dot at bottom
+               screenbegin = begin_line(dot);  // start dot at top
+               dot_scroll(cnt, -1);
+               break;
+       case '|':                       // |- move to column "cmdcnt"
+               dot = move_to_col(dot, cmdcnt - 1);     // try to move to column
+               break;
+       case '~':                       // ~- flip the case of letters   a-z -> A-Z
+               if (cmdcnt-- > 1) {
+                       do_cmd(c);
+               }                               // repeat cnt
+               if (islower(*dot)) {
+                       *dot = toupper(*dot);
+                       file_modified = TRUE;   // has the file been modified
+               } else if (isupper(*dot)) {
+                       *dot = tolower(*dot);
+                       file_modified = TRUE;   // has the file been modified
+               }
+               dot_right();
+               end_cmd_q();    // stop adding to q
+               break;
+               //----- The Cursor and Function Keys -----------------------------
+       case VI_K_HOME: // Cursor Key Home
+               dot_begin();
+               break;
+               // The Fn keys could point to do_macro which could translate them
+       case VI_K_FUN1: // Function Key F1
+       case VI_K_FUN2: // Function Key F2
+       case VI_K_FUN3: // Function Key F3
+       case VI_K_FUN4: // Function Key F4
+       case VI_K_FUN5: // Function Key F5
+       case VI_K_FUN6: // Function Key F6
+       case VI_K_FUN7: // Function Key F7
+       case VI_K_FUN8: // Function Key F8
+       case VI_K_FUN9: // Function Key F9
+       case VI_K_FUN10:        // Function Key F10
+       case VI_K_FUN11:        // Function Key F11
+       case VI_K_FUN12:        // Function Key F12
+               break;
+       }
+
+  dc1:
+       // if text[] just became empty, add back an empty line
+       if (end == text) {
+               (void) char_insert(text, '\n'); // start empty buf with dummy line
+               dot = text;
+       }
+       // it is OK for dot to exactly equal to end, otherwise check dot validity
+       if (dot != end) {
+               dot = bound_dot(dot);   // make sure "dot" is valid
+       }
+#ifdef BB_FEATURE_VI_YANKMARK
+       check_context(c);       // update the current context
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+
+       if (!isdigit(c))
+               cmdcnt = 0;             // cmd was not a number, reset cmdcnt
+       cnt = dot - begin_line(dot);
+       // Try to stay off of the Newline
+       if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
+               dot--;
+}
+
+//----- The Colon commands -------------------------------------
+#ifdef BB_FEATURE_VI_COLON
+static Byte *get_address(Byte * p, int *addr)  // get colon addr, if present
+{
+       int st;
+       Byte *q;
+
+#ifdef BB_FEATURE_VI_YANKMARK
+       Byte c;
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_SEARCH
+       Byte *pat, buf[1024];
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+
+       *addr = -1;                     // assume no addr
+       if (*p == '.') {        // the current line
+               p++;
+               q = begin_line(dot);
+               *addr = count_lines(text, q);
+#ifdef BB_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[(int) c];
+                       if (q != NULL) {        // is mark valid
+                               *addr = count_lines(text, q);   // count lines
+                       }
+               }
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_SEARCH
+       } else if (*p == '/') { // a search pattern
+               q = buf;
+               for (p++; *p; p++) {
+                       if (*p == '/')
+                               break;
+                       *q++ = *p;
+                       *q = '\0';
+               }
+               pat = (Byte *) strdup((char *) buf);    // save copy of pattern
+               if (*p == '/')
+                       p++;
+               q = char_search(dot, pat, FORWARD, FULL);
+               if (q != NULL) {
+                       *addr = count_lines(text, q);
+               }
+               free(pat);
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+       } 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((char *) p, "%d%n", addr, &st);
+               p += st;
+       } else {                        // I don't reconise this
+               // unrecognised address- assume -1
+               *addr = -1;
+       }
+       return (p);
+}
+
+static void colon(Byte * buf)
+{
+       Byte c, *orig_buf, *buf1, *q, *r;
+       Byte *fn, cmd[100], args[100];
+       int i, l, li, ch, st, b, e;
+       int useforce, forced;
+
+       // :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"
+       //
+       if (strlen((char *) buf) <= 0)
+               goto vc1;
+       if (*buf == ':')
+               buf++;                  // move past the ':'
+
+       forced = useforce = FALSE;
+       li = st = ch = i = 0;
+       b = e = -1;
+       q = text;                       // assume 1,$ for the range
+       r = end - 1;
+       li = count_lines(text, end - 1);
+       fn = cfn;                       // default to current file
+
+       // look for optional FIRST address(es)  :.  :1  :1,9   :'q,'a
+       while (isblnk(*buf))
+               buf++;
+
+       // get FIRST addr, if present
+       buf = get_address(buf, &b);
+       while (isblnk(*buf))
+               buf++;
+       if (*buf == ',') {
+               buf++;
+               while (isblnk(*buf))
+                       buf++;
+               // look for SECOND address
+               buf = get_address(buf, &e);
+       }
+       while (isblnk(*buf))
+               buf++;
+
+       // remember orig command line
+       orig_buf = buf;
+
+       // get the COMMAND into cmd[]
+       buf1 = cmd;
+       *buf1 = '\0';
+       while (*buf != '\0') {
+               if (isspace(*buf))
+                       break;
+               *buf1++ = *buf++;
+               *buf1 = '\0';
+       }
+       // get any ARGuments
+       while (isblnk(*buf))
+               buf++;
+       strcpy((char *) args, (char *) buf);
+       if (cmd[strlen((char *) cmd) - 1] == '!') {
+               useforce = TRUE;
+               cmd[strlen((char *) cmd) - 1] = '\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((char *) cmd);
+       if (i == 0) {           // :123CR goto line #123
+               if (b >= 0) {
+                       dot = find_line(b);     // what line is #b
+                       dot_skip_over_ws();
+               }
+       } else if (strncmp((char *) cmd, "=", i) == 0) {        // where is the address
+               if (b < 0) {    // no addr given- use defaults
+                       b = e = count_lines(text, dot);
+               }
+               psb("%d", b);
+       } else if (strncasecmp((char *) 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);    // save, then delete lines
+               dot_skip_over_ws();
+       } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
+               // don't exit if the file been modified
+               if (file_modified == TRUE && useforce != TRUE) {
+                       psbs("No write since last change (:edit! overrides)");
+                       goto vc1;
+               }
+               fn = args;
+               if (strlen((char *) fn) <= 0) {
+                       // no file name given, re-edit current file
+                       fn = cfn;
+               }
+               if (cfn != 0)
+                       free(cfn);
+               cfn = (Byte *) strdup((char *) fn);     // make this the current file
+               // delete all the contents of text[]
+               new_text(2 * file_size(fn));
+               screenbegin = dot = end = text;
+               // insert new file
+               if (fn != 0) {
+                       ch = file_insert(fn, text, file_size(fn));
+               }
+               file_modified = FALSE;
+#ifdef BB_FEATURE_VI_YANKMARK
+               if (reg[Ureg] != 0)
+                       free(reg[Ureg]);        //   free orig line reg- for 'U'
+               if (reg[YDreg] != 0)
+                       free(reg[YDreg]);       //   free default yank/delete register
+               for (li = 0; li < 28; li++) {
+                       mark[li] = 0;
+               }                               // init the marks
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+               // how many lines in text[]?
+               li = count_lines(text, end - 1);
+               psb("\"%s\" %dL, %dC", cfn, li, ch);
+       } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
+               if (b != -1 || e != -1) {
+                       ni((Byte *) "No address allowed on this command");
+                       goto vc1;
+               }
+               if (strlen((char *) args) > 0) {
+                       // user wants a new filename
+                       if (cfn != NULL)
+                               free(cfn);
+                       cfn = (Byte *) strdup((char *) args);
+               } else {
+                       // user wants file status info
+                       edit_status();
+               }
+       } else if (strncasecmp((char *) cmd, "features", i) == 0) {     // what features are available
+               // print out values of all features
+               place_cursor(rows - 1, 0);      // go to Status line, bottom of screen
+               clear_to_eol(); // clear the line
+               cookmode();
+               show_help();
+               rawmode();
+               Hit_Return();
+       } else if (strncasecmp((char *) 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);
+               }
+               place_cursor(rows - 1, 0);      // go to Status line, bottom of screen
+               clear_to_eol(); // clear the line
+               write(1, "\r\n", 2);
+               for (; q <= r; q++) {
+                       c = *q;
+                       if (c > '~')
+                               standout_start();
+                       if (c == '\n') {
+                               write(1, "$\r", 2);
+                       } else if (*q < ' ') {
+                               write(1, "^", 1);
+                               c += '@';
+                       }
+                       write(1, &c, 1);
+                       if (c > '~')
+                               standout_end();
+               }
+#ifdef BB_FEATURE_VI_SET
+         vc2:
+#endif                                                 /* BB_FEATURE_VI_SET */
+               Hit_Return();
+       } else if ((strncasecmp((char *) cmd, "quit", i) == 0) ||       // Quit
+                          (strncasecmp((char *) cmd, "next", i) == 0)) {       // edit next file
+               if (useforce == TRUE) {
+                       // force end of argv list
+                       if (*cmd == 'q') {
+                               optind = save_argc;
+                       }
+                       editing = 0;
+                       goto vc1;
+               }
+               // don't exit if the file been modified
+               if (file_modified == TRUE) {
+                       psbs("No write since last change (:%s! overrides)",
+                                (*cmd == 'q' ? "quit" : "next"));
+                       goto vc1;
+               }
+               // are there other file to edit
+               if (*cmd == 'q' && optind < save_argc - 1) {
+                       psbs("%d more file to edit", (save_argc - optind - 1));
+                       goto vc1;
+               }
+               if (*cmd == 'n' && optind >= save_argc - 1) {
+                       psbs("No more files to edit");
+                       goto vc1;
+               }
+               editing = 0;
+       } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
+               fn = args;
+               if (strlen((char *) fn) <= 0) {
+                       psbs("No filename given");
+                       goto vc1;
+               }
+               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);
+               ch = file_insert(fn, q, file_size(fn));
+               if (ch < 0)
+                       goto vc1;       // nothing was inserted
+               // how many lines in text[]?
+               li = count_lines(q, q + ch - 1);
+               psb("\"%s\" %dL, %dC", fn, li, ch);
+               if (ch > 0) {
+                       // if the insert is before "dot" then we need to update
+                       if (q <= dot)
+                               dot += ch;
+                       file_modified = TRUE;
+               }
+       } else if (strncasecmp((char *) cmd, "rewind", i) == 0) {       // rewind cmd line args
+               if (file_modified == TRUE && useforce != TRUE) {
+                       psbs("No write since last change (:rewind! overrides)");
+               } else {
+                       // reset the filenames to edit
+                       optind = fn_start - 1;
+                       editing = 0;
+               }
+#ifdef BB_FEATURE_VI_SET
+       } else if (strncasecmp((char *) cmd, "set", i) == 0) {  // set or clear features
+               i = 0;                  // offset into args
+               if (strlen((char *) args) == 0) {
+                       // print out values of all options
+                       place_cursor(rows - 1, 0);      // go to Status line, bottom of screen
+                       clear_to_eol(); // clear the line
+                       printf("----------------------------------------\r\n");
+#ifdef BB_FEATURE_VI_SETOPTS
+                       if (!autoindent)
+                               printf("no");
+                       printf("autoindent ");
+                       if (!ignorecase)
+                               printf("no");
+                       printf("ignorecase ");
+                       if (!showmatch)
+                               printf("no");
+                       printf("showmatch ");
+                       printf("tabstop=%d ", tabstop);
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+                       printf("\r\n");
+                       goto vc2;
+               }
+               if (strncasecmp((char *) args, "no", 2) == 0)
+                       i = 2;          // ":set noautoindent"
+#ifdef BB_FEATURE_VI_SETOPTS
+               if (strncasecmp((char *) args + i, "autoindent", 10) == 0 ||
+                       strncasecmp((char *) args + i, "ai", 2) == 0) {
+                       autoindent = (i == 2) ? 0 : 1;
+               }
+               if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 ||
+                       strncasecmp((char *) args + i, "ic", 2) == 0) {
+                       ignorecase = (i == 2) ? 0 : 1;
+               }
+               if (strncasecmp((char *) args + i, "showmatch", 9) == 0 ||
+                       strncasecmp((char *) args + i, "sm", 2) == 0) {
+                       showmatch = (i == 2) ? 0 : 1;
+               }
+               if (strncasecmp((char *) args + i, "tabstop", 7) == 0) {
+                       sscanf(strchr((char *) args + i, '='), "=%d", &ch);
+                       if (ch > 0 && ch < columns - 1)
+                               tabstop = ch;
+               }
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+#endif                                                 /* BB_FEATURE_VI_SET */
+#ifdef BB_FEATURE_VI_SEARCH
+       } else if (strncasecmp((char *) cmd, "s", 1) == 0) {    // substitute a pattern with a replacement pattern
+               Byte *ls, *F, *R;
+               int gflag;
+
+               // F points to the "find" pattern
+               // R points to the "replace" pattern
+               // replace the cmd line delimiters "/" with NULLs
+               gflag = 0;              // global replace flag
+               c = orig_buf[1];        // what is the delimiter
+               F = orig_buf + 2;       // start of "find"
+               R = (Byte *) strchr((char *) F, c);     // middle delimiter
+               *R++ = '\0';    // terminate "find"
+               buf1 = (Byte *) strchr((char *) R, c);
+               *buf1++ = '\0'; // terminate "replace"
+               if (*buf1 == 'g') {     // :s/foo/bar/g
+                       buf1++;
+                       gflag++;        // turn on gflag
+               }
+               q = begin_line(q);
+               if (b < 0) {    // maybe :s/foo/bar/
+                       q = begin_line(dot);    // start with cur line
+                       b = count_lines(text, q);       // cur line number
+               }
+               if (e < 0)
+                       e = b;          // maybe :.s/foo/bar/
+               for (i = b; i <= e; i++) {      // so, :20,23 s \0 find \0 replace \0
+                       ls = q;         // orig line start
+                 vc4:
+                       buf1 = char_search(q, F, FORWARD, LIMITED);     // search cur line only for "find"
+                       if (buf1 != NULL) {
+                               // we found the "find" pattern- delete it
+                               (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
+                               // inset the "replace" patern
+                               (void) string_insert(buf1, R);  // insert the string
+                               // check for "global"  :s/foo/bar/g
+                               if (gflag == 1) {
+                                       if ((buf1 + strlen((char *) R)) < end_line(ls)) {
+                                               q = buf1 + strlen((char *) R);
+                                               goto vc4;       // don't let q move past cur line
+                                       }
+                               }
+                       }
+                       q = next_line(ls);
+               }
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+       } else if (strncasecmp((char *) cmd, "version", i) == 0) {      // show software version
+               psb("%s", vi_Version);
+       } else if ((strncasecmp((char *) cmd, "write", i) == 0) ||      // write text to file
+                          (strncasecmp((char *) cmd, "wq", i) == 0)) { // write text to file
+               // is there a file name to write to?
+               if (strlen((char *) args) > 0) {
+                       fn = args;
+               }
+#ifdef BB_FEATURE_VI_READONLY
+               if (readonly == TRUE) {
+                       psbs("\"%s\" File is read only", fn);
+                       goto vc3;
+               }
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+               // how many lines in text[]?
+               li = count_lines(q, r);
+               ch = r - q + 1;
+               if (useforce == TRUE) {
+                       // if "fn" is not write-able, chmod u+w
+                       // sprintf(syscmd, "chmod u+w %s", fn);
+                       // system(syscmd);
+                       forced = TRUE;
+               }
+               l = file_write(fn, q, r);
+               if (useforce == TRUE && forced == TRUE) {
+                       // chmod u-w
+                       // sprintf(syscmd, "chmod u-w %s", fn);
+                       // system(syscmd);
+                       forced = FALSE;
+               }
+               psb("\"%s\" %dL, %dC", fn, li, l);
+               if (q == text && r == end - 1 && l == ch)
+                       file_modified = FALSE;
+               if (cmd[1] == 'q' && l == ch) {
+                       editing = 0;
+               }
+#ifdef BB_FEATURE_VI_READONLY
+         vc3:;
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+#ifdef BB_FEATURE_VI_YANKMARK
+       } else if (strncasecmp((char *) 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);
+               psb("Yank %d lines (%d chars) into [%c]",
+                       li, strlen((char *) reg[YDreg]), what_reg());
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+       } else {
+               // cmd unknown
+               ni((Byte *) cmd);
+       }
+  vc1:
+       dot = bound_dot(dot);   // make sure "dot" is valid
+       return;
+}
+
+static void Hit_Return(void)
+{
+       char c;
+
+       standout_start();       // start reverse video
+       write(1, "[Hit return to continue]", 24);
+       standout_end();         // end reverse video
+       while ((c = get_one_char()) != '\n' && c != '\r')       /*do nothing */
+               ;
+       redraw(TRUE);           // force redraw all
+}
+#endif                                                 /* BB_FEATURE_VI_COLON */
+
+//----- Synchronize the cursor to Dot --------------------------
+static void sync_cursor(Byte * d, int *row, int *col)
+{
+       Byte *beg_cur, *end_cur;        // begin and end of "d" line
+       Byte *beg_scr, *end_scr;        // begin and end of screen
+       Byte *tp;
+       int cnt, ro, co;
+
+       beg_cur = begin_line(d);        // first char of cur line
+       end_cur = end_line(d);  // last char of cur line
+
+       beg_scr = end_scr = screenbegin;        // first char of screen
+       end_scr = end_screen(); // last char of screen
+
+       if (beg_cur < screenbegin) {
+               // "d" is before  top line on screen
+               // how many lines do we have to move
+               cnt = count_lines(beg_cur, screenbegin);
+         sc1:
+               screenbegin = beg_cur;
+               if (cnt > (rows - 1) / 2) {
+                       // we moved too many lines. put "dot" in middle of screen
+                       for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
+                               screenbegin = prev_line(screenbegin);
+                       }
+               }
+       } else if (beg_cur > end_scr) {
+               // "d" is after bottom line on screen
+               // how many lines do we have to move
+               cnt = count_lines(end_scr, beg_cur);
+               if (cnt > (rows - 1) / 2)
+                       goto sc1;       // too many lines
+               for (ro = 0; ro < cnt - 1; ro++) {
+                       // move screen begin the same amount
+                       screenbegin = next_line(screenbegin);
+                       // now, move the end of screen
+                       end_scr = next_line(end_scr);
+                       end_scr = end_line(end_scr);
+               }
+       }
+       // "d" is on screen- find out which row
+       tp = screenbegin;
+       for (ro = 0; ro < rows - 1; ro++) {     // drive "ro" to correct row
+               if (tp == beg_cur)
+                       break;
+               tp = next_line(tp);
+       }
+
+       // find out what col "d" is on
+       co = 0;
+       do {                            // drive "co" to correct column
+               if (*tp == '\n' || *tp == '\0')
+                       break;
+               if (*tp == '\t') {
+                       //         7       - (co %    8  )
+                       co += ((tabstop - 1) - (co % tabstop));
+               } else if (*tp < ' ') {
+                       co++;           // display as ^X, use 2 columns
+               }
+       } while (tp++ < d && ++co);
+
+       // "co" is the column where "dot" is.
+       // The screen has "columns" columns.
+       // The currently displayed columns are  0+offset -- columns+ofset
+       // |-------------------------------------------------------------|
+       //               ^ ^                                ^
+       //        offset | |------- columns ----------------|
+       //
+       // If "co" is already in this range then we do not have to adjust offset
+       //      but, we do have to subtract the "offset" bias from "co".
+       // If "co" is outside this range then we have to change "offset".
+       // If the first char of a line is a tab the cursor will try to stay
+       //  in column 7, but we have to set offset to 0.
+
+       if (co < 0 + offset) {
+               offset = co;
+       }
+       if (co >= columns + offset) {
+               offset = co - columns + 1;
+       }
+       // if the first char of the line is a tab, and "dot" is sitting on it
+       //  force offset to 0.
+       if (d == beg_cur && *d == '\t') {
+               offset = 0;
+       }
+       co -= offset;
+
+       *row = ro;
+       *col = co;
+}
+
+//----- Text Movement Routines ---------------------------------
+static Byte *begin_line(Byte * p) // return pointer to first char cur line
+{
+       while (p > text && p[-1] != '\n')
+               p--;                    // go to cur line B-o-l
+       return (p);
+}
+
+static Byte *end_line(Byte * p) // return pointer to NL of cur line line
+{
+       while (p < end - 1 && *p != '\n')
+               p++;                    // go to cur line E-o-l
+       return (p);
+}
+
+static Byte *dollar_line(Byte * p) // return pointer to just before NL line
+{
+       while (p < end - 1 && *p != '\n')
+               p++;                    // go to cur line E-o-l
+       // Try to stay off of the Newline
+       if (*p == '\n' && (p - begin_line(p)) > 0)
+               p--;
+       return (p);
+}
+
+static Byte *prev_line(Byte * p) // return pointer first char prev line
+{
+       p = begin_line(p);      // goto begining of cur line
+       if (p[-1] == '\n' && p > text)
+               p--;                    // step to prev line
+       p = begin_line(p);      // goto begining of prev line
+       return (p);
+}
+
+static Byte *next_line(Byte * p) // return pointer first char next line
+{
+       p = end_line(p);
+       if (*p == '\n' && p < end - 1)
+               p++;                    // step to next line
+       return (p);
+}
+
+//----- Text Information Routines ------------------------------
+static Byte *end_screen(void)
+{
+       Byte *q;
+       int cnt;
+
+       // find new bottom line
+       q = screenbegin;
+       for (cnt = 0; cnt < rows - 2; cnt++)
+               q = next_line(q);
+       q = end_line(q);
+       return (q);
+}
+
+static int count_lines(Byte * start, Byte * stop) // count line from start to stop
+{
+       Byte *q;
+       int cnt;
+
+       if (stop < start) {     // start and stop are backwards- reverse them
+               q = start;
+               start = stop;
+               stop = q;
+       }
+       cnt = 0;
+       stop = end_line(stop);  // get to end of this line
+       for (q = start; q <= stop && q <= end - 1; q++) {
+               if (*q == '\n')
+                       cnt++;
+       }
+       return (cnt);
+}
+
+static Byte *find_line(int li) // find begining of line #li
+{
+       Byte *q;
+
+       for (q = text; li > 1; li--) {
+               q = next_line(q);
+       }
+       return (q);
+}
+
+//----- Dot Movement Routines ----------------------------------
+static void dot_left(void)
+{
+       if (dot > text && dot[-1] != '\n')
+               dot--;
+}
+
+static void dot_right(void)
+{
+       if (dot < end - 1 && *dot != '\n')
+               dot++;
+}
+
+static void dot_begin(void)
+{
+       dot = begin_line(dot);  // return pointer to first char cur line
+}
+
+static void dot_end(void)
+{
+       dot = end_line(dot);    // return pointer to last char cur line
+}
+
+static Byte *move_to_col(Byte * p, int l)
+{
+       int co;
+
+       p = begin_line(p);
+       co = 0;
+       do {
+               if (*p == '\n' || *p == '\0')
+                       break;
+               if (*p == '\t') {
+                       //         7       - (co %    8  )
+                       co += ((tabstop - 1) - (co % tabstop));
+               } else if (*p < ' ') {
+                       co++;           // display as ^X, use 2 columns
+               }
+       } while (++co <= l && p++ < end);
+       return (p);
+}
+
+static void dot_next(void)
+{
+       dot = next_line(dot);
+}
+
+static void dot_prev(void)
+{
+       dot = prev_line(dot);
+}
+
+static void dot_scroll(int cnt, int dir)
+{
+       Byte *q;
+
+       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);
+               }
+       }
+       // 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();
+}
+
+static void dot_skip_over_ws(void)
+{
+       // skip WS
+       while (isspace(*dot) && *dot != '\n' && dot < end - 1)
+               dot++;
+}
+
+static void dot_delete(void)   // delete the char at 'dot'
+{
+       (void) text_hole_delete(dot, dot);
+}
+
+static Byte *bound_dot(Byte * p) // make sure  text[0] <= P < "end"
+{
+       if (p >= end && end > text) {
+               p = end - 1;
+               indicate_error('1');
+       }
+       if (p < text) {
+               p = text;
+               indicate_error('2');
+       }
+       return (p);
+}
+
+//----- 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 Byte *new_screen(int ro, int co)
+{
+       if (screen != 0)
+               free(screen);
+       screensize = ro * co + 8;
+       screen = (Byte *) malloc(screensize);
+       return (screen);
+}
+
+static Byte *new_text(int size)
+{
+       if (size < 10240)
+               size = 10240;   // have a minimum size for new files
+       if (text != 0) {
+               //text -= 4;
+               free(text);
+       }
+       text = (Byte *) malloc(size + 8);
+       memset(text, '\0', size);       // clear new text[]
+       //text += 4;            // leave some room for "oops"
+       textend = text + size - 1;
+       //textend -= 4;         // leave some root for "oops"
+       return (text);
+}
+
+#ifdef BB_FEATURE_VI_SEARCH
+static int mycmp(Byte * s1, Byte * s2, int len)
+{
+       int i;
+
+       i = strncmp((char *) s1, (char *) s2, len);
+#ifdef BB_FEATURE_VI_SETOPTS
+       if (ignorecase) {
+               i = strncasecmp((char *) s1, (char *) s2, len);
+       }
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+       return (i);
+}
+
+static Byte *char_search(Byte * p, Byte * pat, int dir, int range)     // search for pattern starting at p
+{
+#ifndef REGEX_SEARCH
+       Byte *start, *stop;
+       int len;
+
+       len = strlen((char *) pat);
+       if (dir == 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 if (dir == 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);
+#else                                                  /*REGEX_SEARCH */
+       char *q;
+       struct re_pattern_buffer preg;
+       int i;
+       int size, range;
+
+       re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
+       preg.translate = 0;
+       preg.fastmap = 0;
+       preg.buffer = 0;
+       preg.allocated = 0;
+
+       // assume a LIMITED forward search
+       q = next_line(p);
+       q = end_line(q);
+       q = end - 1;
+       if (dir == BACK) {
+               q = prev_line(p);
+               q = text;
+       }
+       // count the number of chars to search over, forward or backward
+       size = q - p;
+       if (size < 0)
+               size = p - q;
+       // RANGE could be negative if we are searching backwards
+       range = q - p;
+
+       q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
+       if (q != 0) {
+               // The pattern was not compiled
+               psbs("bad search pattern: \"%s\": %s", pat, q);
+               i = 0;                  // return p if pattern not compiled
+               goto cs1;
+       }
+
+       q = p;
+       if (range < 0) {
+               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, 0, range, 0);
+       if (i == -1) {
+               p = 0;
+               i = 0;                  // return NULL if pattern not found
+       }
+  cs1:
+       if (dir == FORWARD) {
+               p = p + i;
+       } else {
+               p = p - i;
+       }
+       return (p);
+#endif                                                 /*REGEX_SEARCH */
+}
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+
+static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
+{
+       if (c == 22) {          // Is this an ctrl-V?
+               p = stupid_insert(p, '^');      // use ^ to indicate literal next
+               p--;                    // backup onto ^
+               refresh(FALSE); // show the ^
+               c = get_one_char();
+               *p = c;
+               p++;
+               file_modified = TRUE;   // has the file been modified
+       } else if (c == 27) {   // Is this an ESC?
+               cmd_mode = 0;
+               cmdcnt = 0;
+               end_cmd_q();    // stop adding to q
+               *status_buffer = '\0';  // clear the status buffer
+               if (p[-1] != '\n') {
+                       p--;
+               }
+       } else if (c == erase_char) {   // Is this a BS
+               //     123456789
+               if (p[-1] != '\n') {
+                       p--;
+                       p = text_hole_delete(p, p);     // shrink buffer 1 char
+#ifdef BB_FEATURE_VI_DOT_CMD
+                       // also rmove char from last_modifying_cmd
+                       if (strlen((char *) last_modifying_cmd) > 0) {
+                               Byte *q;
+
+                               q = last_modifying_cmd;
+                               q[strlen((char *) q) - 1] = '\0';       // erase BS
+                               q[strlen((char *) q) - 1] = '\0';       // erase prev char
+                       }
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+               }
+       } else {
+               // insert a char into text[]
+               Byte *sp;               // "save p"
+
+               if (c == 13)
+                       c = '\n';       // translate \r to \n
+               sp = p;                 // remember addr of insert
+               p = stupid_insert(p, c);        // insert the char
+#ifdef BB_FEATURE_VI_SETOPTS
+               if (showmatch && strchr(")]}", *sp) != NULL) {
+                       showmatching(sp);
+               }
+               if (autoindent && c == '\n') {  // auto indent the new line
+                       Byte *q;
+
+                       q = prev_line(p);       // use prev line as templet
+                       for (; isblnk(*q); q++) {
+                               p = stupid_insert(p, *q);       // insert the char
+                       }
+               }
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+       }
+       return (p);
+}
+
+static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
+{
+       p = text_hole_make(p, 1);
+       if (p != 0) {
+               *p = c;
+               file_modified = TRUE;   // has the file been modified
+               p++;
+       }
+       return (p);
+}
+
+static Byte find_range(Byte ** start, Byte ** stop, Byte c)
+{
+       Byte *save_dot, *p, *q;
+       int cnt;
+
+       save_dot = dot;
+       p = q = dot;
+
+       if (strchr("cdy><", c)) {
+               p = q = begin_line(p);
+               for (cnt = 1; cnt < cmdcnt; cnt++) {
+                       q = next_line(q);
+               }
+               q = end_line(q);
+       } else if (strchr("$0bBeE", c)) {
+               do_cmd(c);              // execute movement cmd
+               q = dot;
+       } else if (strchr("wW", c)) {
+               do_cmd(c);              // execute movement cmd
+               if (dot > text)
+                       dot--;          // move back off of next word
+               if (dot > text && *dot == '\n')
+                       dot--;          // stay off NL
+               q = dot;
+       } else if (strchr("H-k{", c)) {
+               q = end_line(dot);      // find NL
+               do_cmd(c);              // execute movement cmd
+               dot_begin();
+               p = dot;
+       } else if (strchr("L+j}\r\n", c)) {
+               p = begin_line(dot);
+               do_cmd(c);              // execute movement cmd
+               dot_end();              // find NL
+               q = dot;
+       } else {
+               c = 27;                 // error- return an ESC char
+               //break;
+       }
+       *start = p;
+       *stop = q;
+       if (q < p) {
+               *start = q;
+               *stop = p;
+       }
+       dot = save_dot;
+       return (c);
+}
+
+static int st_test(Byte * p, int type, int dir, Byte * tested)
+{
+       Byte c, c0, ci;
+       int test, inc;
+
+       inc = dir;
+       c = c0 = p[0];
+       ci = p[inc];
+       test = 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 == '_');
+       }
+       *tested = c;
+       return (test);
+}
+
+static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
+{
+       Byte c;
+
+       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);
+}
+
+// find matching char of pair  ()  []  {}
+static Byte *find_pair(Byte * p, Byte c)
+{
+       Byte match, *q;
+       int dir, level;
+
+       match = ')';
+       level = 1;
+       dir = 1;                        // assume forward
+       switch (c) {
+       case '(':
+               match = ')';
+               break;
+       case '[':
+               match = ']';
+               break;
+       case '{':
+               match = '}';
+               break;
+       case ')':
+               match = '(';
+               dir = -1;
+               break;
+       case ']':
+               match = '[';
+               dir = -1;
+               break;
+       case '}':
+               match = '{';
+               dir = -1;
+               break;
+       }
+       for (q = p + dir; text <= q && q < end; q += dir) {
+               // look for match, count levels of pairs  (( ))
+               if (*q == c)
+                       level++;        // increase pair levels
+               if (*q == match)
+                       level--;        // reduce pair level
+               if (level == 0)
+                       break;          // found matching pair
+       }
+       if (level != 0)
+               q = NULL;               // indicate no match
+       return (q);
+}
+
+#ifdef BB_FEATURE_VI_SETOPTS
+// show the matching char of a pair,  ()  []  {}
+static void showmatching(Byte * p)
+{
+       Byte *q, *save_dot;
+
+       // we found half of a pair
+       q = find_pair(p, *p);   // get loc of matching char
+       if (q == NULL) {
+               indicate_error('3');    // 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
+               (void) mysleep(40);     // give user some time
+               dot = save_dot; // go back to old loc
+               refresh(FALSE);
+       }
+}
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+
+//  open a hole in text[]
+static Byte *text_hole_make(Byte * p, int size)        // at "p", make a 'size' byte hole
+{
+       Byte *src, *dest;
+       int cnt;
+
+       if (size <= 0)
+               goto thm0;
+       src = p;
+       dest = p + size;
+       cnt = end - src;        // the rest of buffer
+       if (memmove(dest, src, cnt) != dest) {
+               psbs("can't create room for new characters");
+       }
+       memset(p, ' ', size);   // clear new hole
+       end = end + size;       // adjust the new END
+       file_modified = TRUE;   // has the file been modified
+  thm0:
+       return (p);
+}
+
+//  close a hole in text[]
+static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
+{
+       Byte *src, *dest;
+       int cnt, hole_size;
+
+       // 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 (src < text || src > end)
+               goto thd0;
+       if (dest < text || dest >= end)
+               goto thd0;
+       if (src >= end)
+               goto thd_atend; // just delete the end of the buffer
+       if (memmove(dest, src, cnt) != dest) {
+               psbs("can't delete the character");
+       }
+  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
+       file_modified = TRUE;   // has the file been modified
+  thd0:
+       return (dest);
+}
+
+// copy text into register, then delete text.
+// if dist <= 0, do not include, or go past, a NewLine
+//
+static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
+{
+       Byte *p;
+
+       // make sure start <= stop
+       if (start > stop) {
+               // they are backwards, reverse them
+               p = start;
+               start = stop;
+               stop = p;
+       }
+       if (dist <= 0) {
+               // we can not cross NL boundaries
+               p = start;
+               if (*p == '\n')
+                       return (p);
+               // dont go past a NewLine
+               for (; p + 1 <= stop; p++) {
+                       if (p[1] == '\n') {
+                               stop = p;       // "stop" just before NewLine
+                               break;
+                       }
+               }
+       }
+       p = start;
+#ifdef BB_FEATURE_VI_YANKMARK
+       text_yank(start, stop, YDreg);
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+       if (yf == YANKDEL) {
+               p = text_hole_delete(start, stop);
+       }                                       // delete lines
+       return (p);
+}
+
+static void show_help(void)
+{
+       printf("These features are available:\n");
+#ifdef BB_FEATURE_VI_SEARCH
+       printf("\tPattern searches with / and ?\n");
+#endif                                                 /* BB_FEATURE_VI_SEARCH */
+#ifdef BB_FEATURE_VI_DOT_CMD
+       printf("\tLast command repeat with \'.\'\n");
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+#ifdef BB_FEATURE_VI_YANKMARK
+       printf("\tLine marking with  'x\n");
+       printf("\tNamed buffers with  \"x\n");
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+#ifdef BB_FEATURE_VI_READONLY
+       printf("\tReadonly with -R command line arg\n");
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+#ifdef BB_FEATURE_VI_SET
+       printf("\tSome colon mode commands with \':\'\n");
+#endif                                                 /* BB_FEATURE_VI_SET */
+#ifdef BB_FEATURE_VI_SETOPTS
+       printf("\tSettable options with \":set\"\n");
+#endif                                                 /* BB_FEATURE_VI_SETOPTS */
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+       printf("\tSignal catching- ^C\n");
+       printf("\tJob suspend and resume with ^Z\n");
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+       printf("\tAdapt to window re-sizes\n");
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+}
+
+static void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
+{
+       Byte c, b[2];
+
+       b[1] = '\0';
+       strcpy((char *) buf, "");       // init buf
+       if (strlen((char *) s) <= 0)
+               s = (Byte *) "(NULL)";
+       for (; *s > '\0'; s++) {
+               c = *s;
+               if (*s > '~') {
+                       strcat((char *) buf, SOs);
+                       c = *s - 128;
+               }
+               if (*s < ' ') {
+                       strcat((char *) buf, "^");
+                       c += '@';
+               }
+               b[0] = c;
+               strcat((char *) buf, (char *) b);
+               if (*s > '~')
+                       strcat((char *) buf, SOn);
+               if (*s == '\n') {
+                       strcat((char *) buf, "$");
+               }
+       }
+}
+
+#ifdef BB_FEATURE_VI_DOT_CMD
+static void start_new_cmd_q(Byte c)
+{
+       // release old cmd
+       if (last_modifying_cmd != 0)
+               free(last_modifying_cmd);
+       // get buffer for new cmd
+       last_modifying_cmd = (Byte *) malloc(BUFSIZ);
+       memset(last_modifying_cmd, '\0', BUFSIZ);       // clear new cmd queue
+       // if there is a current cmd count put it in the buffer first
+       if (cmdcnt > 0)
+               sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
+       // save char c onto queue
+       last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
+       adding2q = 1;
+       return;
+}
+
+static void end_cmd_q()
+{
+#ifdef BB_FEATURE_VI_YANKMARK
+       YDreg = 26;                     // go back to default Yank/Delete reg
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+       adding2q = 0;
+       return;
+}
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+
+#if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
+static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
+{
+       int cnt, i;
+
+       i = strlen((char *) s);
+       p = text_hole_make(p, i);
+       strncpy((char *) p, (char *) s, i);
+       for (cnt = 0; *s != '\0'; s++) {
+               if (*s == '\n')
+                       cnt++;
+       }
+#ifdef BB_FEATURE_VI_YANKMARK
+       psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+       return (p);
+}
+#endif                                                 /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
+
+#ifdef BB_FEATURE_VI_YANKMARK
+static Byte *text_yank(Byte * p, Byte * q, int dest)   // copy text into a register
+{
+       Byte *t;
+       int cnt;
+
+       if (q < p) {            // they are backwards- reverse them
+               t = q;
+               q = p;
+               p = t;
+       }
+       cnt = q - p + 1;
+       t = reg[dest];
+       if (t != 0) {           // if already a yank register
+               free(t);                //   free it
+       }
+       t = (Byte *) malloc(cnt + 1);   // get a new register
+       memset(t, '\0', cnt + 1);       // clear new text[]
+       strncpy((char *) t, (char *) p, cnt);   // copy text[] into bufer
+       reg[dest] = t;
+       return (p);
+}
+
+static Byte what_reg(void)
+{
+       Byte c;
+       int i;
+
+       i = 0;
+       c = 'D';                        // default to D-reg
+       if (0 <= YDreg && YDreg <= 25)
+               c = 'a' + (Byte) YDreg;
+       if (YDreg == 26)
+               c = 'D';
+       if (YDreg == 27)
+               c = 'U';
+       return (c);
+}
+
+static void check_context(Byte 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((char *) 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;
+               }
+       }
+       return;
+}
+
+static Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
+{
+       Byte *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] = mark[26];
+               mark[26] = tmp;
+               p = mark[26];   // where we are going- previous context
+               context_start = prev_line(prev_line(prev_line(p)));
+               context_end = next_line(next_line(next_line(p)));
+       }
+       return (p);
+}
+#endif                                                 /* BB_FEATURE_VI_YANKMARK */
+
+static int isblnk(Byte c) // is the char a blank or tab
+{
+       return (c == ' ' || c == '\t');
+}
+
+//----- Set terminal attributes --------------------------------
+static void rawmode(void)
+{
+       tcgetattr(0, &term_orig);
+       term_vi = term_orig;
+       term_vi.c_lflag &= (~ICANON & ~ECHO);   // leave ISIG ON- allow intr's
+       term_vi.c_iflag &= (~IXON & ~ICRNL);
+       term_vi.c_cc[VMIN] = 1;
+       term_vi.c_cc[VTIME] = 0;
+       erase_char = term_vi.c_cc[VERASE];
+       tcsetattr(0, TCSANOW, &term_vi);
+}
+
+static void cookmode(void)
+{
+       tcsetattr(0, TCSANOW, &term_orig);
+}
+
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+//----- See what the window size currently is --------------------
+static void window_size_get(int sig)
+{
+       int i;
+
+       i = ioctl(0, TIOCGWINSZ, &winsize);
+       if (i != 0) {
+               // force 24x80
+               winsize.ws_row = 24;
+               winsize.ws_col = 80;
+       }
+       if (winsize.ws_row <= 1) {
+               winsize.ws_row = 24;
+       }
+       if (winsize.ws_col <= 1) {
+               winsize.ws_col = 80;
+       }
+       rows = (int) winsize.ws_row;
+       columns = (int) winsize.ws_col;
+}
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+
+//----- Come here when we get a window resize signal ---------
+#ifdef BB_FEATURE_VI_USE_SIGNALS
+static void winch_sig(int sig)
+{
+       signal(SIGWINCH, winch_sig);
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+       window_size_get(0);
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+       new_screen(rows, columns);      // get memory for virtual screen
+       redraw(TRUE);           // re-draw the screen
+}
+
+//----- Come here when we get a continue signal -------------------
+static void cont_sig(int sig)
+{
+       rawmode();                      // terminal to "raw"
+       *status_buffer = '\0';  // clear the status buffer
+       redraw(TRUE);           // re-draw the screen
+
+       signal(SIGTSTP, suspend_sig);
+       signal(SIGCONT, SIG_DFL);
+       kill(getpid(), SIGCONT);
+}
+
+//----- Come here when we get a Suspend signal -------------------
+static void suspend_sig(int sig)
+{
+       place_cursor(rows, 0);  // go to bottom of screen
+       clear_to_eol();         // Erase to end of line
+       cookmode();                     // terminal to "cooked"
+
+       signal(SIGCONT, cont_sig);
+       signal(SIGTSTP, SIG_DFL);
+       kill(getpid(), SIGTSTP);
+}
+
+//----- Come here when we get a signal        --------------------
+static void catch_sig(int sig)
+{
+       signal(SIGHUP, catch_sig);
+       signal(SIGINT, catch_sig);
+       signal(SIGTERM, catch_sig);
+       longjmp(restart, sig);
+}
+
+static void alarm_sig(int sig)
+{
+       signal(SIGALRM, catch_sig);
+       longjmp(restart, sig);
+}
+
+//----- Come here when we get a core dump signal -----------------
+static void core_sig(int sig)
+{
+       signal(SIGQUIT, core_sig);
+       signal(SIGILL, core_sig);
+       signal(SIGTRAP, core_sig);
+       signal(SIGIOT, core_sig);
+       signal(SIGABRT, core_sig);
+       signal(SIGFPE, core_sig);
+       signal(SIGBUS, core_sig);
+       signal(SIGSEGV, core_sig);
+       signal(SIGSYS, core_sig);
+
+       dot = bound_dot(dot);   // make sure "dot" is valid
+
+       longjmp(restart, sig);
+}
+#endif                                                 /* BB_FEATURE_VI_USE_SIGNALS */
+
+static int mysleep(int hund)   // sleep for 'h' 1/100 seconds
+{
+       // Don't hang- Wait 5/100 seconds-  1 Sec= 1000000
+       FD_ZERO(&rfds);
+       FD_SET(0, &rfds);
+       tv.tv_sec = 0;
+       tv.tv_usec = hund * 10000;
+       select(1, &rfds, NULL, NULL, &tv);
+       return (FD_ISSET(0, &rfds));
+}
+
+//----- IO Routines --------------------------------------------
+static Byte readit(void)       // read (maybe cursor) key from stdin
+{
+       Byte c;
+       int i, bufsiz, cnt, cmdindex;
+       struct esc_cmds {
+               Byte *seq;
+               Byte val;
+       };
+
+       static struct esc_cmds esccmds[] = {
+               {(Byte *) "\eOA", (Byte) VI_K_UP},       // cursor key Up
+               {(Byte *) "\eOB", (Byte) VI_K_DOWN},     // cursor key Down
+               {(Byte *) "\eOC", (Byte) VI_K_RIGHT},    // Cursor Key Right
+               {(Byte *) "\eOD", (Byte) VI_K_LEFT},     // cursor key Left
+               {(Byte *) "\eOH", (Byte) VI_K_HOME},     // Cursor Key Home
+               {(Byte *) "\eOF", (Byte) VI_K_END},      // Cursor Key End
+               {(Byte *) "\e[A", (Byte) VI_K_UP},       // cursor key Up
+               {(Byte *) "\e[B", (Byte) VI_K_DOWN},     // cursor key Down
+               {(Byte *) "\e[C", (Byte) VI_K_RIGHT},    // Cursor Key Right
+               {(Byte *) "\e[D", (Byte) VI_K_LEFT},     // cursor key Left
+               {(Byte *) "\e[H", (Byte) VI_K_HOME},     // Cursor Key Home
+               {(Byte *) "\e[F", (Byte) VI_K_END},      // Cursor Key End
+               {(Byte *) "\e[2~", (Byte) VI_K_INSERT},  // Cursor Key Insert
+               {(Byte *) "\e[5~", (Byte) VI_K_PAGEUP},  // Cursor Key Page Up
+               {(Byte *) "\e[6~", (Byte) VI_K_PAGEDOWN},        // Cursor Key Page Down
+               {(Byte *) "\eOP", (Byte) VI_K_FUN1},     // Function Key F1
+               {(Byte *) "\eOQ", (Byte) VI_K_FUN2},     // Function Key F2
+               {(Byte *) "\eOR", (Byte) VI_K_FUN3},     // Function Key F3
+               {(Byte *) "\eOS", (Byte) VI_K_FUN4},     // Function Key F4
+               {(Byte *) "\e[15~", (Byte) VI_K_FUN5},   // Function Key F5
+               {(Byte *) "\e[17~", (Byte) VI_K_FUN6},   // Function Key F6
+               {(Byte *) "\e[18~", (Byte) VI_K_FUN7},   // Function Key F7
+               {(Byte *) "\e[19~", (Byte) VI_K_FUN8},   // Function Key F8
+               {(Byte *) "\e[20~", (Byte) VI_K_FUN9},   // Function Key F9
+               {(Byte *) "\e[21~", (Byte) VI_K_FUN10},  // Function Key F10
+               {(Byte *) "\e[23~", (Byte) VI_K_FUN11},  // Function Key F11
+               {(Byte *) "\e[24~", (Byte) VI_K_FUN12},  // Function Key F12
+               {(Byte *) "\e[11~", (Byte) VI_K_FUN1},   // Function Key F1
+               {(Byte *) "\e[12~", (Byte) VI_K_FUN2},   // Function Key F2
+               {(Byte *) "\e[13~", (Byte) VI_K_FUN3},   // Function Key F3
+               {(Byte *) "\e[14~", (Byte) VI_K_FUN4},   // Function Key F4
+       };
+
+#define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
+
+       (void) alarm(0);        // turn alarm OFF while we wait for input
+       // get input from User- are there already input chars in Q?
+       bufsiz = strlen((char *) readbuffer);
+       if (bufsiz <= 0) {
+         ri0:
+               // the Q is empty, wait for a typed char
+               bufsiz = read(0, readbuffer, BUFSIZ - 1);
+               if (bufsiz < 0) {
+                       if (errno == EINTR)
+                               goto ri0;       // interrupted sys call
+                       if (errno == EBADF)
+                               editing = 0;
+                       if (errno == EFAULT)
+                               editing = 0;
+                       if (errno == EINVAL)
+                               editing = 0;
+                       if (errno == EIO)
+                               editing = 0;
+                       errno = 0;
+                       bufsiz = 0;
+               }
+               readbuffer[bufsiz] = '\0';
+       }
+       // return char if it is not part of ESC sequence
+       if (readbuffer[0] != 27)
+               goto ri1;
+
+       // This is an ESC char. Is this Esc sequence?
+       // Could be bare Esc key. See if there are any
+       // more chars to read after the ESC. This would
+       // be a Function or Cursor Key sequence.
+       FD_ZERO(&rfds);
+       FD_SET(0, &rfds);
+       tv.tv_sec = 0;
+       tv.tv_usec = 10000;     // Wait 1/100 seconds- 1 Sec=1000000
+
+       // keep reading while there are input chars and room in buffer
+       while (select(1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5)) {
+               // read the rest of the ESC string
+               i = read(0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz);
+               if (i > 0) {
+                       bufsiz += i;
+                       readbuffer[bufsiz] = '\0';      // Terminate the string
+               }
+       }
+       // Maybe cursor or function key?
+       for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++) {
+               cnt = strlen((char *) esccmds[cmdindex].seq);
+               i = strncmp((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt);
+               if (i == 0) {
+                       // is a Cursor key- put derived value back into Q
+                       readbuffer[0] = esccmds[cmdindex].val;
+                       // squeeze out the ESC sequence
+                       for (i = 1; i < cnt; i++) {
+                               memmove(readbuffer + 1, readbuffer + 2, BUFSIZ - 2);
+                               readbuffer[BUFSIZ - 1] = '\0';
+                       }
+                       break;
+               }
+       }
+  ri1:
+       c = readbuffer[0];
+       // remove one char from Q
+       memmove(readbuffer, readbuffer + 1, BUFSIZ - 1);
+       readbuffer[BUFSIZ - 1] = '\0';
+       (void) alarm(3);        // we are done waiting for input, turn alarm ON
+       return (c);
+}
+
+//----- IO Routines --------------------------------------------
+static Byte get_one_char()
+{
+       static Byte c;
+
+#ifdef BB_FEATURE_VI_DOT_CMD
+       // ! adding2q  && ioq == 0  read()
+       // ! adding2q  && ioq != 0  *ioq
+       // adding2q         *last_modifying_cmd= read()
+       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
+                       c = *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 (last_modifying_cmd != 0) {
+                       // add new char to q
+                       last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
+               }
+       }
+#else                                                  /* BB_FEATURE_VI_DOT_CMD */
+       c = readit();           // get the users input
+#endif                                                 /* BB_FEATURE_VI_DOT_CMD */
+       return (c);                     // return the char, where ever it came from
+}
+
+#if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
+static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
+{
+       Byte buf[500];
+       Byte c;
+       int i;
+       static Byte *obufp = NULL;
+
+       strcpy((char *) buf, (char *) prompt);
+       *status_buffer = '\0';  // clear the status buffer
+       place_cursor(rows - 1, 0);      // go to Status line, bottom of screen
+       clear_to_eol();         // clear the line
+       write(1, prompt, strlen((char *) prompt));      // write out the :, /, or ? prompt
+
+       for (i = strlen((char *) buf); i < 500;) {
+               c = get_one_char();     // read user input
+               if (c == '\n' || c == '\r')
+                       break;          // is this end of input
+               if (c == erase_char) {  // user wants to erase prev char
+                       i--;            // backup to prev char
+                       buf[i] = '\0';  // erase the char
+                       buf[i + 1] = '\0';      // null terminate buffer
+                       write(1, "\b \b", 3);     // erase char on screen
+                       if (i <= 0) {   // user backs up before b-o-l, exit
+                               break;
+                       }
+               } else {
+                       buf[i] = c;     // save char in buffer
+                       buf[i + 1] = '\0';      // make sure buffer is null terminated
+                       write(1, buf + i, 1);   // echo the char back to user
+                       i++;
+               }
+       }
+       refresh(FALSE);
+       if (obufp != NULL)
+               free(obufp);
+       obufp = (Byte *) strdup((char *) buf);
+       return (obufp);
+}
+#endif                                                 /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
+
+static int file_size(Byte * fn) // what is the byte size of "fn"
+{
+       struct stat st_buf;
+       int cnt, sr;
+
+       if (fn == 0)
+               return (-1);
+       cnt = -1;
+       sr = stat((char *) fn, &st_buf);        // see if file exists
+       if (sr >= 0) {
+               cnt = (int) st_buf.st_size;
+       }
+       return (cnt);
+}
+
+static int file_insert(Byte * fn, Byte * p, int size)
+{
+       int fd, cnt, sr;
+       struct stat st_buf;
+
+       cnt = -1;
+       if (fn == 0) {
+               psbs("No filename given");
+               goto fi0;
+       }
+       sr = stat((char *) fn, &st_buf);        // see if file exists
+       if (sr < 0) {
+               psbs("\"%s\" %s", fn, "count not stat file");
+               goto fi0;
+       }
+       if (size <= 0 || (int) st_buf.st_size <= 0) {
+               psbs("The file size (%d) is too small", size);
+               if ((int) st_buf.st_size <= 0)
+                       psbs("\"%s\" is empty", fn);
+               goto fi0;
+       }
+       // There is a file with content
+       fd = open((char *) fn, O_RDWR);
+       if (fd < 0) {
+               psbs("\"%s\" %s", fn, "could not open file");
+               goto fi0;
+       }
+       p = text_hole_make(p, size);
+       cnt = read(fd, p, size);
+       close(fd);
+       if (cnt < 0) {
+               cnt = -1;
+               p = text_hole_delete(p, p + size - 1);  // un-do buffer insert
+               psbs("could not read file \"%s\"", fn);
+       } else if (cnt < size) {
+               // There was a partial read, shrink unused space text[]
+               p = text_hole_delete(p + cnt, p + (size - cnt) - 1);    // un-do buffer insert
+               psbs("could not read all of file \"%s\"", fn);
+       }
+       if (cnt >= size)
+               file_modified = TRUE;
+  fi0:
+       return (cnt);
+}
+
+static int file_write(Byte * fn, Byte * first, Byte * last)
+{
+       int fd, cnt, charcnt;
+
+       if (fn == 0) {
+               psbs("No current filename");
+               return (-1);
+       }
+       charcnt = 0;
+       // FIXIT- use the correct umask()
+       fd = open((char *) fn, (O_RDWR | O_CREAT | O_TRUNC), 0664);
+       if (fd < 0)
+               return (-1);
+       cnt = last - first + 1;
+       charcnt = write(fd, first, cnt);
+       if (charcnt == cnt) {
+               // good write
+               //file_modified= FALSE; // the file has not been modified
+       } else {
+               charcnt = 0;
+       }
+       close(fd);
+       return (charcnt);
+}
+
+//----- Terminal Drawing ---------------------------------------
+// The terminal is made up of 'rows' line of 'columns' columns.
+// classicly this would be 24 x 80.
+//  screen coordinates
+//  0,0     ...     0,79
+//  1,0     ...     1,79
+//  .       ...     .
+//  .       ...     .
+//  22,0        ...     22,79
+//  23,0        ...     23,79   status line
+//
+
+//----- Move the cursor to row x col (count from 0, not 1) -------
+static void place_cursor(int row, int col)
+{
+       Byte buf[30];
+       int l;
+
+       if (row < 0)
+               row = 0;
+       if (row >= rows)
+               row = rows - 1;
+       if (col < 0)
+               col = 0;
+       if (col >= columns)
+               col = columns - 1;
+       sprintf((char *) buf, "%c[%d;%dH", 0x1b, row + 1, col + 1);
+       l = strlen((char *) buf);
+       write(1, buf, l);
+}
+
+//----- Erase from cursor to end of line -----------------------
+static void clear_to_eol()
+{
+       write(1, "\033[0K", 4); // Erase from cursor to end of line
+}
+
+//----- Erase from cursor to end of screen -----------------------
+static void clear_to_eos()
+{
+       write(1, "\033[0J", 4); // Erase from cursor to end of screen
+}
+
+//----- Start standout mode ------------------------------------
+static void standout_start() // send "start reverse video" sequence
+{
+       write(1, "\033[7m", 4); // Start reverse video mode
+}
+
+//----- End standout mode --------------------------------------
+static void standout_end() // send "end reverse video" sequence
+{
+       write(1, "\033[0m", 4); // End reverse video mode
+}
+
+//----- Flash the screen  --------------------------------------
+static void flash(int h)
+{
+       standout_start();       // send "start reverse video" sequence
+       redraw(TRUE);
+       (void) mysleep(h);
+       standout_end();         // send "end reverse video" sequence
+       redraw(TRUE);
+}
+
+static void beep()
+{
+       write(1, "\007", 1);    // send out a bell character
+}
+
+static void indicate_error(char c)
+{
+#ifdef BB_FEATURE_VI_CRASHME
+       if (crashme > 0)
+               return;                 // generate a random command
+#endif                                                 /* BB_FEATURE_VI_CRASHME */
+       if (err_method == 0) {
+               beep();
+       } else {
+               flash(10);
+       }
+}
+
+//----- Screen[] Routines --------------------------------------
+//----- Erase the Screen[] memory ------------------------------
+static void screen_erase()
+{
+       int i;
+
+       for (i = 0; i < screensize; i++) {
+               screen[i] = '\0';
+       }
+}
+
+//----- Draw the status line at bottom of the screen -------------
+static void show_status_line(void)
+{
+       int cnt;
+
+       cnt = strlen((char *) status_buffer);
+       place_cursor(rows - 1, 0);      // put cursor on status line
+       if (cnt > 0) {
+               write(1, status_buffer, cnt);
+       }
+       clear_to_eol();
+       place_cursor(crow, ccol);       // put cursor back in correct place
+}
+
+//----- format the status buffer, the bottom line of screen ------
+// print status buffer, with STANDOUT mode
+static void psbs(char *format, ...)
+{
+       va_list args;
+
+       va_start(args, format);
+       strcpy((char *) status_buffer, "\033[7m");      // Terminal standout mode on
+       vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
+                        args);
+       strcat((char *) status_buffer, "\033[0m");      // Terminal standout mode off
+       va_end(args);
+
+       return;
+}
+
+// print status buffer
+static void psb(char *format, ...)
+{
+       va_list args;
+
+       va_start(args, format);
+       vsprintf((char *) status_buffer, format, args);
+       va_end(args);
+       return;
+}
+
+static void ni(Byte * s) // display messages
+{
+       Byte buf[9];
+
+       print_literal(buf, s);
+       psbs("\'%s\' is not implemented", buf);
+}
+
+static void edit_status(void)  // show file status on status line
+{
+       int cur, tot, percent;
+
+       cur = count_lines(text, dot);
+       tot = count_lines(text, end - 1);
+       //    current line         percent
+       //   -------------    ~~ ----------
+       //    total lines            100
+       if (tot > 0) {
+               percent = (100 * cur) / tot;
+       } else {
+               cur = tot = 0;
+               percent = 100;
+       }
+       psb("\"%s\""
+#ifdef BB_FEATURE_VI_READONLY
+               "%s"
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+               "%s line %d of %d --%d%%--  (%dx%d)",
+               (cfn != 0 ? (char *) cfn : "No file"),
+#ifdef BB_FEATURE_VI_READONLY
+               (readonly == TRUE ? " [Read only]" : ""),
+#endif                                                 /* BB_FEATURE_VI_READONLY */
+               (file_modified == TRUE ? " [modified]" : ""),
+               cur, tot, percent, rows, columns);
+}
+
+//----- Force refresh of all Lines -----------------------------
+static void redraw(int full_screen)
+{
+       place_cursor(0, 0);     // put cursor in correct place
+       clear_to_eos();         // tel terminal to erase display
+       screen_erase();         // erase the internal screen buffer
+       refresh(full_screen);   // this will redraw the entire display
+}
+
+//----- 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 old_offset;
+       int li, co, changed;
+       Byte c, buf[MAX_SCR_COLS];
+       Byte *tp, *sp;          // pointer into text[] and screen[]
+
+#ifdef BB_FEATURE_VI_WIN_RESIZE
+       window_size_get(0);
+#endif                                                 /* BB_FEATURE_VI_WIN_RESIZE */
+       sync_cursor(dot, &crow, &ccol);
+       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++) {
+               // format current text line into buf with "columns" wide
+               for (co = 0; co < columns + offset;) {
+                       c = ' ';        // assume blank
+                       if (li > 0 && co == 0) {
+                               c = '~';        // not first line, assume Tilde
+                       }
+                       // are there chars in text[]
+                       //   and have we gone past the end
+                       if (text < end && tp < end) {
+                               c = *tp++;
+                       }
+                       if (c == '\n')
+                               break;
+                       if (c < ' ' || c > '~') {
+                               if (c == '\t') {
+                                       c = ' ';
+                                       //       co %    8     !=     7
+                                       for (; (co % tabstop) != (tabstop - 1); co++) {
+                                               buf[co] = c;
+                                       }
+                               } else {
+                                       buf[co++] = '^';
+                                       c |= '@';       // make it visible
+                                       c &= 0x7f;      // get rid of hi bit
+                               }
+                       }
+                       // the co++ is done here so that the column will
+                       // not be overwritten when we blank-out the rest of line
+                       buf[co++] = c;
+                       if (tp >= end)
+                               break;
+               }
+               if (co >= columns + offset) {
+                       // skip to the end of the current text[] line
+                       while (tp < end && *tp++ != '\n');
+               }
+               // try to keep the cursor near it's current position
+               // remember how many chars in this row- where the cursor sits
+               // blank out the rest of the buffer
+               while (co < MAX_SCR_COLS - 1) {
+                       buf[co++] = ' ';
+               }
+               buf[co++] = 0;  // NULL terminate the buffer
+
+               // if necessary, update virtual screen[] and terminal from buf[]
+               changed = FALSE;        // assume no change
+               sp = &screen[li * columns];     // start of screen line
+               for (co = 0; co < columns; co++) {
+                       if (sp[co] != buf[co + offset]) {
+                               sp[co] = buf[co + offset];
+                               changed = TRUE; // mark for redraw
+                       }
+               }
+               // if horz offset has changed, force a redraw
+               if (offset != old_offset)
+                       changed = TRUE;
+
+               // write all marked screen lines out to terminal
+               if (changed == TRUE) {
+                       place_cursor(li, 0);    // put cursor in correct place
+                       clear_to_eol(); // Erase to end of line
+                       if (full_screen == FALSE) {
+                               // don't redraw every column on terminal
+                               // look backwards for last non-blank
+                               for (co = columns + offset; co >= 0; co--) {
+                                       // break;
+                                       if (buf[co] != ' ')
+                                               break;
+                               }
+                               co++;
+                       } else {
+                               // redraw every column on terminal
+                               co = columns;
+                       }
+                       // write line out to terminal
+                       write(1, buf + offset, co);
+               }
+       }
+
+       place_cursor(crow, ccol);
+       if (offset != old_offset)
+               old_offset = offset;
+}