busybox/editors/vi.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * tiny vi.c: A small 'vi' clone
   4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
   5 *
   6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
   7 */
   8
   9/*
  10 * Things To Do:
  11 *      EXINIT
  12 *      $HOME/.exrc  and  ./.exrc
  13 *      add magic to search     /foo.*bar
  14 *      add :help command
  15 *      :map macros
  16 *      if mark[] values were line numbers rather than pointers
  17 *         it would be easier to change the mark when add/delete lines
  18 *      More intelligence in refresh()
  19 *      ":r !cmd"  and  "!cmd"  to filter text through an external command
  20 *      A true "undo" facility
  21 *      An "ex" line oriented mode- maybe using "cmdedit"
  22 */
  23
  24//config:config VI
  25//config:       bool "vi"
  26//config:       default y
  27//config:       help
  28//config:         'vi' is a text editor. More specifically, it is the One True
  29//config:         text editor <grin>. It does, however, have a rather steep
  30//config:         learning curve. If you are not already comfortable with 'vi'
  31//config:         you may wish to use something else.
  32//config:
  33//config:config FEATURE_VI_MAX_LEN
  34//config:       int "Maximum screen width in vi"
  35//config:       range 256 16384
  36//config:       default 4096
  37//config:       depends on VI
  38//config:       help
  39//config:         Contrary to what you may think, this is not eating much.
  40//config:         Make it smaller than 4k only if you are very limited on memory.
  41//config:
  42//config:config FEATURE_VI_8BIT
  43//config:       bool "Allow vi to display 8-bit chars (otherwise shows dots)"
  44//config:       default n
  45//config:       depends on VI
  46//config:       help
  47//config:         If your terminal can display characters with high bit set,
  48//config:         you may want to enable this. Note: vi is not Unicode-capable.
  49//config:         If your terminal combines several 8-bit bytes into one character
  50//config:         (as in Unicode mode), this will not work properly.
  51//config:
  52//config:config FEATURE_VI_COLON
  53//config:       bool "Enable \":\" colon commands (no \"ex\" mode)"
  54//config:       default y
  55//config:       depends on VI
  56//config:       help
  57//config:         Enable a limited set of colon commands for vi. This does not
  58//config:         provide an "ex" mode.
  59//config:
  60//config:config FEATURE_VI_YANKMARK
  61//config:       bool "Enable yank/put commands and mark cmds"
  62//config:       default y
  63//config:       depends on VI
  64//config:       help
  65//config:         This will enable you to use yank and put, as well as mark in
  66//config:         busybox vi.
  67//config:
  68//config:config FEATURE_VI_SEARCH
  69//config:       bool "Enable search and replace cmds"
  70//config:       default y
  71//config:       depends on VI
  72//config:       help
  73//config:         Select this if you wish to be able to do search and replace in
  74//config:         busybox vi.
  75//config:
  76//config:config FEATURE_VI_REGEX_SEARCH
  77//config:       bool "Enable regex in search and replace"
  78//config:       default n   # Uses GNU regex, which may be unavailable. FIXME
  79//config:       depends on FEATURE_VI_SEARCH
  80//config:       help
  81//config:         Use extended regex search.
  82//config:
  83//config:config FEATURE_VI_USE_SIGNALS
  84//config:       bool "Catch signals"
  85//config:       default y
  86//config:       depends on VI
  87//config:       help
  88//config:         Selecting this option will make busybox vi signal aware. This will
  89//config:         make busybox vi support SIGWINCH to deal with Window Changes, catch
  90//config:         Ctrl-Z and Ctrl-C and alarms.
  91//config:
  92//config:config FEATURE_VI_DOT_CMD
  93//config:       bool "Remember previous cmd and \".\" cmd"
  94//config:       default y
  95//config:       depends on VI
  96//config:       help
  97//config:         Make busybox vi remember the last command and be able to repeat it.
  98//config:
  99//config:config FEATURE_VI_READONLY
 100//config:       bool "Enable -R option and \"view\" mode"
 101//config:       default y
 102//config:       depends on VI
 103//config:       help
 104//config:         Enable the read-only command line option, which allows the user to
 105//config:         open a file in read-only mode.
 106//config:
 107//config:config FEATURE_VI_SETOPTS
 108//config:       bool "Enable set-able options, ai ic showmatch"
 109//config:       default y
 110//config:       depends on VI
 111//config:       help
 112//config:         Enable the editor to set some (ai, ic, showmatch) options.
 113//config:
 114//config:config FEATURE_VI_SET
 115//config:       bool "Support for :set"
 116//config:       default y
 117//config:       depends on VI
 118//config:       help
 119//config:         Support for ":set".
 120//config:
 121//config:config FEATURE_VI_WIN_RESIZE
 122//config:       bool "Handle window resize"
 123//config:       default y
 124//config:       depends on VI
 125//config:       help
 126//config:         Make busybox vi behave nicely with terminals that get resized.
 127//config:
 128//config:config FEATURE_VI_ASK_TERMINAL
 129//config:       bool "Use 'tell me cursor position' ESC sequence to measure window"
 130//config:       default y
 131//config:       depends on VI
 132//config:       help
 133//config:         If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
 134//config:         this option makes vi perform a last-ditch effort to find it:
 135//config:         position cursor to 999,999 and ask terminal to report real
 136//config:         cursor position using "ESC [ 6 n" escape sequence, then read stdin.
 137//config:
 138//config:         This is not clean but helps a lot on serial lines and such.
 139//config:
 140//config:config FEATURE_VI_OPTIMIZE_CURSOR
 141//config:       bool "Optimize cursor movement"
 142//config:       default y
 143//config:       depends on VI
 144//config:       help
 145//config:         This will make the cursor movement faster, but requires more memory
 146//config:         and it makes the applet a tiny bit larger.
 147
 148//applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
 149
 150//kbuild:lib-$(CONFIG_VI) += vi.o
 151
 152//usage:#define vi_trivial_usage
 153//usage:       "[OPTIONS] [FILE]..."
 154//usage:#define vi_full_usage "\n\n"
 155//usage:       "Edit FILE\n"
 156//usage:        IF_FEATURE_VI_COLON(
 157//usage:     "\n        -c      Initial command to run ($EXINIT also available)"
 158//usage:        )
 159//usage:        IF_FEATURE_VI_READONLY(
 160//usage:     "\n        -R      Read-only"
 161//usage:        )
 162//usage:     "\n        -H      Short help regarding available features"
 163
 164#include "libbb.h"
 165/* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
 166#if ENABLE_FEATURE_VI_REGEX_SEARCH
 167# include <regex.h>
 168#endif
 169
 170/* the CRASHME code is unmaintained, and doesn't currently build */
 171#define ENABLE_FEATURE_VI_CRASHME 0
 172
 173
 174#if ENABLE_LOCALE_SUPPORT
 175
 176#if ENABLE_FEATURE_VI_8BIT
 177//FIXME: this does not work properly for Unicode anyway
 178# define Isprint(c) (isprint)(c)
 179#else
 180# define Isprint(c) isprint_asciionly(c)
 181#endif
 182
 183#else
 184
 185/* 0x9b is Meta-ESC */
 186#if ENABLE_FEATURE_VI_8BIT
 187# define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
 188#else
 189# define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
 190#endif
 191
 192#endif
 193
 194
 195enum {
 196        MAX_TABSTOP = 32, // sanity limit
 197        // User input len. Need not be extra big.
 198        // Lines in file being edited *can* be bigger than this.
 199        MAX_INPUT_LEN = 128,
 200        // Sanity limits. We have only one buffer of this size.
 201        MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
 202        MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
 203};
 204
 205/* vt102 typical ESC sequence */
 206/* terminal standout start/normal ESC sequence */
 207#define SOs "\033[7m"
 208#define SOn "\033[0m"
 209/* terminal bell sequence */
 210#define bell "\007"
 211/* Clear-end-of-line and Clear-end-of-screen ESC sequence */
 212#define Ceol "\033[K"
 213#define Ceos "\033[J"
 214/* Cursor motion arbitrary destination ESC sequence */
 215#define CMrc "\033[%u;%uH"
 216/* Cursor motion up and down ESC sequence */
 217#define CMup "\033[A"
 218#define CMdown "\n"
 219
 220#if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
 221// cmds modifying text[]
 222// vda: removed "aAiIs" as they switch us into insert mode
 223// and remembering input for replay after them makes no sense
 224static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
 225#endif
 226
 227enum {
 228        YANKONLY = FALSE,
 229        YANKDEL = TRUE,
 230        FORWARD = 1,    // code depends on "1"  for array index
 231        BACK = -1,      // code depends on "-1" for array index
 232        LIMITED = 0,    // how much of text[] in char_search
 233        FULL = 1,       // how much of text[] in char_search
 234
 235        S_BEFORE_WS = 1,        // used in skip_thing() for moving "dot"
 236        S_TO_WS = 2,            // used in skip_thing() for moving "dot"
 237        S_OVER_WS = 3,          // used in skip_thing() for moving "dot"
 238        S_END_PUNCT = 4,        // used in skip_thing() for moving "dot"
 239        S_END_ALNUM = 5,        // used in skip_thing() for moving "dot"
 240};
 241
 242
 243/* vi.c expects chars to be unsigned. */
 244/* busybox build system provides that, but it's better */
 245/* to audit and fix the source */
 246
 247struct globals {
 248        /* many references - keep near the top of globals */
 249        char *text, *end;       // pointers to the user data in memory
 250        char *dot;              // where all the action takes place
 251        int text_size;          // size of the allocated buffer
 252
 253        /* the rest */
 254        smallint vi_setops;
 255#define VI_AUTOINDENT 1
 256#define VI_SHOWMATCH  2
 257#define VI_IGNORECASE 4
 258#define VI_ERR_METHOD 8
 259#define autoindent (vi_setops & VI_AUTOINDENT)
 260#define showmatch  (vi_setops & VI_SHOWMATCH )
 261#define ignorecase (vi_setops & VI_IGNORECASE)
 262/* indicate error with beep or flash */
 263#define err_method (vi_setops & VI_ERR_METHOD)
 264
 265#if ENABLE_FEATURE_VI_READONLY
 266        smallint readonly_mode;
 267#define SET_READONLY_FILE(flags)        ((flags) |= 0x01)
 268#define SET_READONLY_MODE(flags)        ((flags) |= 0x02)
 269#define UNSET_READONLY_FILE(flags)      ((flags) &= 0xfe)
 270#else
 271#define SET_READONLY_FILE(flags)        ((void)0)
 272#define SET_READONLY_MODE(flags)        ((void)0)
 273#define UNSET_READONLY_FILE(flags)      ((void)0)
 274#endif
 275
 276        smallint editing;        // >0 while we are editing a file
 277                                 // [code audit says "can be 0, 1 or 2 only"]
 278        smallint cmd_mode;       // 0=command  1=insert 2=replace
 279        int file_modified;       // buffer contents changed (counter, not flag!)
 280        int last_file_modified;  // = -1;
 281        int save_argc;           // how many file names on cmd line
 282        int cmdcnt;              // repetition count
 283        unsigned rows, columns;  // the terminal screen is this size
 284#if ENABLE_FEATURE_VI_ASK_TERMINAL
 285        int get_rowcol_error;
 286#endif
 287        int crow, ccol;          // cursor is on Crow x Ccol
 288        int offset;              // chars scrolled off the screen to the left
 289        int have_status_msg;     // is default edit status needed?
 290                                 // [don't make smallint!]
 291        int last_status_cksum;   // hash of current status line
 292        char *current_filename;
 293        char *screenbegin;       // index into text[], of top line on the screen
 294        char *screen;            // pointer to the virtual screen buffer
 295        int screensize;          //            and its size
 296        int tabstop;
 297        int last_forward_char;   // last char searched for with 'f' (int because of Unicode)
 298        char erase_char;         // the users erase character
 299        char last_input_char;    // last char read from user
 300
 301#if ENABLE_FEATURE_VI_DOT_CMD
 302        smallint adding2q;       // are we currently adding user input to q
 303        int lmc_len;             // length of last_modifying_cmd
 304        char *ioq, *ioq_start;   // pointer to string for get_one_char to "read"
 305#endif
 306#if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
 307        int last_row;            // where the cursor was last moved to
 308#endif
 309#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
 310        int my_pid;
 311#endif
 312#if ENABLE_FEATURE_VI_SEARCH
 313        char *last_search_pattern; // last pattern from a '/' or '?' search
 314#endif
 315
 316        /* former statics */
 317#if ENABLE_FEATURE_VI_YANKMARK
 318        char *edit_file__cur_line;
 319#endif
 320        int refresh__old_offset;
 321        int format_edit_status__tot;
 322
 323        /* a few references only */
 324#if ENABLE_FEATURE_VI_YANKMARK
 325        int YDreg, Ureg;        // default delete register and orig line for "U"
 326        char *reg[28];          // named register a-z, "D", and "U" 0-25,26,27
 327        char *mark[28];         // user marks points somewhere in text[]-  a-z and previous context ''
 328        char *context_start, *context_end;
 329#endif
 330#if ENABLE_FEATURE_VI_USE_SIGNALS
 331        sigjmp_buf restart;     // catch_sig()
 332#endif
 333        struct termios term_orig, term_vi; // remember what the cooked mode was
 334#if ENABLE_FEATURE_VI_COLON
 335        char *initial_cmds[3];  // currently 2 entries, NULL terminated
 336#endif
 337        // Should be just enough to hold a key sequence,
 338        // but CRASHME mode uses it as generated command buffer too
 339#if ENABLE_FEATURE_VI_CRASHME
 340        char readbuffer[128];
 341#else
 342        char readbuffer[KEYCODE_BUFFER_SIZE];
 343#endif
 344#define STATUS_BUFFER_LEN  200
 345        char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
 346#if ENABLE_FEATURE_VI_DOT_CMD
 347        char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
 348#endif
 349        char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
 350
 351        char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
 352};
 353#define G (*ptr_to_globals)
 354#define text           (G.text          )
 355#define text_size      (G.text_size     )
 356#define end            (G.end           )
 357#define dot            (G.dot           )
 358#define reg            (G.reg           )
 359
 360#define vi_setops               (G.vi_setops          )
 361#define editing                 (G.editing            )
 362#define cmd_mode                (G.cmd_mode           )
 363#define file_modified           (G.file_modified      )
 364#define last_file_modified      (G.last_file_modified )
 365#define save_argc               (G.save_argc          )
 366#define cmdcnt                  (G.cmdcnt             )
 367#define rows                    (G.rows               )
 368#define columns                 (G.columns            )
 369#define crow                    (G.crow               )
 370#define ccol                    (G.ccol               )
 371#define offset                  (G.offset             )
 372#define status_buffer           (G.status_buffer      )
 373#define have_status_msg         (G.have_status_msg    )
 374#define last_status_cksum       (G.last_status_cksum  )
 375#define current_filename        (G.current_filename   )
 376#define screen                  (G.screen             )
 377#define screensize              (G.screensize         )
 378#define screenbegin             (G.screenbegin        )
 379#define tabstop                 (G.tabstop            )
 380#define last_forward_char       (G.last_forward_char  )
 381#define erase_char              (G.erase_char         )
 382#define last_input_char         (G.last_input_char    )
 383#if ENABLE_FEATURE_VI_READONLY
 384#define readonly_mode           (G.readonly_mode      )
 385#else
 386#define readonly_mode           0
 387#endif
 388#define adding2q                (G.adding2q           )
 389#define lmc_len                 (G.lmc_len            )
 390#define ioq                     (G.ioq                )
 391#define ioq_start               (G.ioq_start          )
 392#define last_row                (G.last_row           )
 393#define my_pid                  (G.my_pid             )
 394#define last_search_pattern     (G.last_search_pattern)
 395
 396#define edit_file__cur_line     (G.edit_file__cur_line)
 397#define refresh__old_offset     (G.refresh__old_offset)
 398#define format_edit_status__tot (G.format_edit_status__tot)
 399
 400#define YDreg          (G.YDreg         )
 401#define Ureg           (G.Ureg          )
 402#define mark           (G.mark          )
 403#define context_start  (G.context_start )
 404#define context_end    (G.context_end   )
 405#define restart        (G.restart       )
 406#define term_orig      (G.term_orig     )
 407#define term_vi        (G.term_vi       )
 408#define initial_cmds   (G.initial_cmds  )
 409#define readbuffer     (G.readbuffer    )
 410#define scr_out_buf    (G.scr_out_buf   )
 411#define last_modifying_cmd  (G.last_modifying_cmd )
 412#define get_input_line__buf (G.get_input_line__buf)
 413
 414#define INIT_G() do { \
 415        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
 416        last_file_modified = -1; \
 417        /* "" but has space for 2 chars: */ \
 418        IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
 419} while (0)
 420
 421
 422static int init_text_buffer(char *); // init from file or create new
 423static void edit_file(char *);  // edit one file
 424static void do_cmd(int);        // execute a command
 425static int next_tabstop(int);
 426static void sync_cursor(char *, int *, int *);  // synchronize the screen cursor to dot
 427static char *begin_line(char *);        // return pointer to cur line B-o-l
 428static char *end_line(char *);  // return pointer to cur line E-o-l
 429static char *prev_line(char *); // return pointer to prev line B-o-l
 430static char *next_line(char *); // return pointer to next line B-o-l
 431static char *end_screen(void);  // get pointer to last char on screen
 432static int count_lines(char *, char *); // count line from start to stop
 433static char *find_line(int);    // find begining of line #li
 434static char *move_to_col(char *, int);  // move "p" to column l
 435static void dot_left(void);     // move dot left- dont leave line
 436static void dot_right(void);    // move dot right- dont leave line
 437static void dot_begin(void);    // move dot to B-o-l
 438static void dot_end(void);      // move dot to E-o-l
 439static void dot_next(void);     // move dot to next line B-o-l
 440static void dot_prev(void);     // move dot to prev line B-o-l
 441static void dot_scroll(int, int);       // move the screen up or down
 442static void dot_skip_over_ws(void);     // move dot pat WS
 443static void dot_delete(void);   // delete the char at 'dot'
 444static char *bound_dot(char *); // make sure  text[0] <= P < "end"
 445static char *new_screen(int, int);      // malloc virtual screen memory
 446static char *char_insert(char *, char); // insert the char c at 'p'
 447// might reallocate text[]! use p += stupid_insert(p, ...),
 448// and be careful to not use pointers into potentially freed text[]!
 449static uintptr_t stupid_insert(char *, char);   // stupidly insert the char c at 'p'
 450static int find_range(char **, char **, char);  // return pointers for an object
 451static int st_test(char *, int, int, char *);   // helper for skip_thing()
 452static char *skip_thing(char *, int, int, int); // skip some object
 453static char *find_pair(char *, char);   // find matching pair ()  []  {}
 454static char *text_hole_delete(char *, char *);  // at "p", delete a 'size' byte hole
 455// might reallocate text[]! use p += text_hole_make(p, ...),
 456// and be careful to not use pointers into potentially freed text[]!
 457static uintptr_t text_hole_make(char *, int);   // at "p", make a 'size' byte hole
 458static char *yank_delete(char *, char *, int, int);     // yank text[] into register then delete
 459static void show_help(void);    // display some help info
 460static void rawmode(void);      // set "raw" mode on tty
 461static void cookmode(void);     // return to "cooked" mode on tty
 462// sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
 463static int mysleep(int);
 464static int readit(void);        // read (maybe cursor) key from stdin
 465static int get_one_char(void);  // read 1 char from stdin
 466static int file_size(const char *);   // what is the byte size of "fn"
 467#if !ENABLE_FEATURE_VI_READONLY
 468#define file_insert(fn, p, update_ro_status) file_insert(fn, p)
 469#endif
 470// file_insert might reallocate text[]!
 471static int file_insert(const char *, char *, int);
 472static int file_write(char *, char *, char *);
 473#if !ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
 474#define place_cursor(a, b, optimize) place_cursor(a, b)
 475#endif
 476static void place_cursor(int, int, int);
 477static void screen_erase(void);
 478static void clear_to_eol(void);
 479static void clear_to_eos(void);
 480static void go_bottom_and_clear_to_eol(void);
 481static void standout_start(void);       // send "start reverse video" sequence
 482static void standout_end(void); // send "end reverse video" sequence
 483static void flash(int);         // flash the terminal screen
 484static void show_status_line(void);     // put a message on the bottom line
 485static void status_line(const char *, ...);     // print to status buf
 486static void status_line_bold(const char *, ...);
 487static void not_implemented(const char *); // display "Not implemented" message
 488static int format_edit_status(void);    // format file status on status line
 489static void redraw(int);        // force a full screen refresh
 490static char* format_line(char* /*, int*/);
 491static void refresh(int);       // update the terminal from screen[]
 492
 493static void Indicate_Error(void);       // use flash or beep to indicate error
 494#define indicate_error(c) Indicate_Error()
 495static void Hit_Return(void);
 496
 497#if ENABLE_FEATURE_VI_SEARCH
 498static char *char_search(char *, const char *, int, int);       // search for pattern starting at p
 499#endif
 500#if ENABLE_FEATURE_VI_COLON
 501static char *get_one_address(char *, int *);    // get colon addr, if present
 502static char *get_address(char *, int *, int *); // get two colon addrs, if present
 503static void colon(char *);      // execute the "colon" mode cmds
 504#endif
 505#if ENABLE_FEATURE_VI_USE_SIGNALS
 506static void winch_sig(int);     // catch window size changes
 507static void suspend_sig(int);   // catch ctrl-Z
 508static void catch_sig(int);     // catch ctrl-C and alarm time-outs
 509#endif
 510#if ENABLE_FEATURE_VI_DOT_CMD
 511static void start_new_cmd_q(char);      // new queue for command
 512static void end_cmd_q(void);    // stop saving input chars
 513#else
 514#define end_cmd_q() ((void)0)
 515#endif
 516#if ENABLE_FEATURE_VI_SETOPTS
 517static void showmatching(char *);       // show the matching pair ()  []  {}
 518#endif
 519#if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
 520// might reallocate text[]! use p += string_insert(p, ...),
 521// and be careful to not use pointers into potentially freed text[]!
 522static uintptr_t string_insert(char *, const char *);   // insert the string at 'p'
 523#endif
 524#if ENABLE_FEATURE_VI_YANKMARK
 525static char *text_yank(char *, char *, int);    // save copy of "p" into a register
 526static char what_reg(void);             // what is letter of current YDreg
 527static void check_context(char);        // remember context for '' command
 528#endif
 529#if ENABLE_FEATURE_VI_CRASHME
 530static void crash_dummy();
 531static void crash_test();
 532static int crashme = 0;
 533#endif
 534
 535
 536static void write1(const char *out)
 537{
 538        fputs(out, stdout);
 539}
 540
 541int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 542int vi_main(int argc, char **argv)
 543{
 544        int c;
 545
 546        INIT_G();
 547
 548#if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
 549        my_pid = getpid();
 550#endif
 551#if ENABLE_FEATURE_VI_CRASHME
 552        srand((long) my_pid);
 553#endif
 554#ifdef NO_SUCH_APPLET_YET
 555        /* If we aren't "vi", we are "view" */
 556        if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
 557                SET_READONLY_MODE(readonly_mode);
 558        }
 559#endif
 560
 561        vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE;
 562        //  1-  process $HOME/.exrc file (not inplemented yet)
 563        //  2-  process EXINIT variable from environment
 564        //  3-  process command line args
 565#if ENABLE_FEATURE_VI_COLON
 566        {
 567                char *p = getenv("EXINIT");
 568                if (p && *p)
 569                        initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
 570        }
 571#endif
 572        while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
 573                switch (c) {
 574#if ENABLE_FEATURE_VI_CRASHME
 575                case 'C':
 576                        crashme = 1;
 577                        break;
 578#endif
 579#if ENABLE_FEATURE_VI_READONLY
 580                case 'R':               // Read-only flag
 581                        SET_READONLY_MODE(readonly_mode);
 582                        break;
 583#endif
 584#if ENABLE_FEATURE_VI_COLON
 585                case 'c':               // cmd line vi command
 586                        if (*optarg)
 587                                initial_cmds[initial_cmds[0] != 0] = xstrndup(optarg, MAX_INPUT_LEN);
 588                        break;
 589#endif
 590                case 'H':
 591                        show_help();
 592                        /* fall through */
 593                default:
 594                        bb_show_usage();
 595                        return 1;
 596                }
 597        }
 598
 599        // The argv array can be used by the ":next"  and ":rewind" commands
 600        argv += optind;
 601        argc -= optind;
 602        save_argc = argc;
 603        optind = 0;
 604
 605        //----- This is the main file handling loop --------------
 606        while (1) {
 607                edit_file(argv[optind]); /* param might be NULL */
 608                if (++optind >= argc)
 609                        break;
 610        }
 611        //-----------------------------------------------------------
 612
 613        return 0;
 614}
 615
 616/* read text from file or create an empty buf */
 617/* will also update current_filename */
 618static int init_text_buffer(char *fn)
 619{
 620        int rc;
 621        int size = file_size(fn);       // file size. -1 means does not exist.
 622
 623        /* allocate/reallocate text buffer */
 624        free(text);
 625        text_size = size + 10240;
 626        screenbegin = dot = end = text = xzalloc(text_size);
 627
 628        if (fn != current_filename) {
 629                free(current_filename);
 630                current_filename = xstrdup(fn);
 631        }
 632        if (size < 0) {
 633                // file dont exist. Start empty buf with dummy line
 634                char_insert(text, '\n');
 635                rc = 0;
 636        } else {
 637                rc = file_insert(fn, text, 1);
 638        }
 639        file_modified = 0;
 640        last_file_modified = -1;
 641#if ENABLE_FEATURE_VI_YANKMARK
 642        /* init the marks. */
 643        memset(mark, 0, sizeof(mark));
 644#endif
 645        return rc;
 646}
 647
 648#if ENABLE_FEATURE_VI_WIN_RESIZE
 649static int query_screen_dimensions(void)
 650{
 651        int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
 652        if (rows > MAX_SCR_ROWS)
 653                rows = MAX_SCR_ROWS;
 654        if (columns > MAX_SCR_COLS)
 655                columns = MAX_SCR_COLS;
 656        return err;
 657}
 658#else
 659# define query_screen_dimensions() (0)
 660#endif
 661
 662static void edit_file(char *fn)
 663{
 664#if ENABLE_FEATURE_VI_YANKMARK
 665#define cur_line edit_file__cur_line
 666#endif
 667        int c;
 668#if ENABLE_FEATURE_VI_USE_SIGNALS
 669        int sig;
 670#endif
 671
 672        editing = 1;    // 0 = exit, 1 = one file, 2 = multiple files
 673        rawmode();
 674        rows = 24;
 675        columns = 80;
 676        IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
 677#if ENABLE_FEATURE_VI_ASK_TERMINAL
 678        if (G.get_rowcol_error /* TODO? && no input on stdin */) {
 679                uint64_t k;
 680                write1("\033[999;999H" "\033[6n");
 681                fflush_all();
 682                k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
 683                if ((int32_t)k == KEYCODE_CURSOR_POS) {
 684                        uint32_t rc = (k >> 32);
 685                        columns = (rc & 0x7fff);
 686                        if (columns > MAX_SCR_COLS)
 687                                columns = MAX_SCR_COLS;
 688                        rows = ((rc >> 16) & 0x7fff);
 689                        if (rows > MAX_SCR_ROWS)
 690                                rows = MAX_SCR_ROWS;
 691                }
 692        }
 693#endif
 694        new_screen(rows, columns);      // get memory for virtual screen
 695        init_text_buffer(fn);
 696
 697#if ENABLE_FEATURE_VI_YANKMARK
 698        YDreg = 26;                     // default Yank/Delete reg
 699        Ureg = 27;                      // hold orig line for "U" cmd
 700        mark[26] = mark[27] = text;     // init "previous context"
 701#endif
 702
 703        last_forward_char = last_input_char = '\0';
 704        crow = 0;
 705        ccol = 0;
 706
 707#if ENABLE_FEATURE_VI_USE_SIGNALS
 708        signal(SIGINT, catch_sig);
 709        signal(SIGWINCH, winch_sig);
 710        signal(SIGTSTP, suspend_sig);
 711        sig = sigsetjmp(restart, 1);
 712        if (sig != 0) {
 713                screenbegin = dot = text;
 714        }
 715#endif
 716
 717        cmd_mode = 0;           // 0=command  1=insert  2='R'eplace
 718        cmdcnt = 0;
 719        tabstop = 8;
 720        offset = 0;                     // no horizontal offset
 721        c = '\0';
 722#if ENABLE_FEATURE_VI_DOT_CMD
 723        free(ioq_start);
 724        ioq = ioq_start = NULL;
 725        lmc_len = 0;
 726        adding2q = 0;
 727#endif
 728
 729#if ENABLE_FEATURE_VI_COLON
 730        {
 731                char *p, *q;
 732                int n = 0;
 733
 734                while ((p = initial_cmds[n]) != NULL) {
 735                        do {
 736                                q = p;
 737                                p = strchr(q, '\n');
 738                                if (p)
 739                                        while (*p == '\n')
 740                                                *p++ = '\0';
 741                                if (*q)
 742                                        colon(q);
 743                        } while (p);
 744                        free(initial_cmds[n]);
 745                        initial_cmds[n] = NULL;
 746                        n++;
 747                }
 748        }
 749#endif
 750        redraw(FALSE);                  // dont force every col re-draw
 751        //------This is the main Vi cmd handling loop -----------------------
 752        while (editing > 0) {
 753#if ENABLE_FEATURE_VI_CRASHME
 754                if (crashme > 0) {
 755                        if ((end - text) > 1) {
 756                                crash_dummy();  // generate a random command
 757                        } else {
 758                                crashme = 0;
 759                                string_insert(text, "\n\n#####  Ran out of text to work on.  #####\n\n"); // insert the string
 760                                dot = text;
 761                                refresh(FALSE);
 762                        }
 763                }
 764#endif
 765                last_input_char = c = get_one_char();   // get a cmd from user
 766#if ENABLE_FEATURE_VI_YANKMARK
 767                // save a copy of the current line- for the 'U" command
 768                if (begin_line(dot) != cur_line) {
 769                        cur_line = begin_line(dot);
 770                        text_yank(begin_line(dot), end_line(dot), Ureg);
 771                }
 772#endif
 773#if ENABLE_FEATURE_VI_DOT_CMD
 774                // These are commands that change text[].
 775                // Remember the input for the "." command
 776                if (!adding2q && ioq_start == NULL
 777                 && cmd_mode == 0 // command mode
 778                 && c > '\0' // exclude NUL and non-ASCII chars
 779                 && c < 0x7f // (Unicode and such)
 780                 && strchr(modifying_cmds, c)
 781                ) {
 782                        start_new_cmd_q(c);
 783                }
 784#endif
 785                do_cmd(c);              // execute the user command
 786
 787                // poll to see if there is input already waiting. if we are
 788                // not able to display output fast enough to keep up, skip
 789                // the display update until we catch up with input.
 790                if (!readbuffer[0] && mysleep(0) == 0) {
 791                        // no input pending - so update output
 792                        refresh(FALSE);
 793                        show_status_line();
 794                }
 795#if ENABLE_FEATURE_VI_CRASHME
 796                if (crashme > 0)
 797                        crash_test();   // test editor variables
 798#endif
 799        }
 800        //-------------------------------------------------------------------
 801
 802        go_bottom_and_clear_to_eol();
 803        cookmode();
 804#undef cur_line
 805}
 806
 807//----- The Colon commands -------------------------------------
 808#if ENABLE_FEATURE_VI_COLON
 809static char *get_one_address(char *p, int *addr)        // get colon addr, if present
 810{
 811        int st;
 812        char *q;
 813        IF_FEATURE_VI_YANKMARK(char c;)
 814        IF_FEATURE_VI_SEARCH(char *pat;)
 815
 816        *addr = -1;                     // assume no addr
 817        if (*p == '.') {        // the current line
 818                p++;
 819                q = begin_line(dot);
 820                *addr = count_lines(text, q);
 821        }
 822#if ENABLE_FEATURE_VI_YANKMARK
 823        else if (*p == '\'') {  // is this a mark addr
 824                p++;
 825                c = tolower(*p);
 826                p++;
 827                if (c >= 'a' && c <= 'z') {
 828                        // we have a mark
 829                        c = c - 'a';
 830                        q = mark[(unsigned char) c];
 831                        if (q != NULL) {        // is mark valid
 832                                *addr = count_lines(text, q);
 833                        }
 834                }
 835        }
 836#endif
 837#if ENABLE_FEATURE_VI_SEARCH
 838        else if (*p == '/') {   // a search pattern
 839                q = strchrnul(++p, '/');
 840                pat = xstrndup(p, q - p); // save copy of pattern
 841                p = q;
 842                if (*p == '/')
 843                        p++;
 844                q = char_search(dot, pat, FORWARD, FULL);
 845                if (q != NULL) {
 846                        *addr = count_lines(text, q);
 847                }
 848                free(pat);
 849        }
 850#endif
 851        else if (*p == '$') {   // the last line in file
 852                p++;
 853                q = begin_line(end - 1);
 854                *addr = count_lines(text, q);
 855        } else if (isdigit(*p)) {       // specific line number
 856                sscanf(p, "%d%n", addr, &st);
 857                p += st;
 858        } else {
 859                // unrecognized address - assume -1
 860                *addr = -1;
 861        }
 862        return p;
 863}
 864
 865static char *get_address(char *p, int *b, int *e)       // get two colon addrs, if present
 866{
 867        //----- get the address' i.e., 1,3   'a,'b  -----
 868        // get FIRST addr, if present
 869        while (isblank(*p))
 870                p++;                            // skip over leading spaces
 871        if (*p == '%') {                        // alias for 1,$
 872                p++;
 873                *b = 1;
 874                *e = count_lines(text, end-1);
 875                goto ga0;
 876        }
 877        p = get_one_address(p, b);
 878        while (isblank(*p))
 879                p++;
 880        if (*p == ',') {                        // is there a address separator
 881                p++;
 882                while (isblank(*p))
 883                        p++;
 884                // get SECOND addr, if present
 885                p = get_one_address(p, e);
 886        }
 887 ga0:
 888        while (isblank(*p))
 889                p++;                            // skip over trailing spaces
 890        return p;
 891}
 892
 893#if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
 894static void setops(const char *args, const char *opname, int flg_no,
 895                        const char *short_opname, int opt)
 896{
 897        const char *a = args + flg_no;
 898        int l = strlen(opname) - 1; /* opname have + ' ' */
 899
 900        // maybe strncmp? we had tons of erroneous strncasecmp's...
 901        if (strncasecmp(a, opname, l) == 0
 902         || strncasecmp(a, short_opname, 2) == 0
 903        ) {
 904                if (flg_no)
 905                        vi_setops &= ~opt;
 906                else
 907                        vi_setops |= opt;
 908        }
 909}
 910#endif
 911
 912// buf must be no longer than MAX_INPUT_LEN!
 913static void colon(char *buf)
 914{
 915        char c, *orig_buf, *buf1, *q, *r;
 916        char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
 917        int i, l, li, ch, b, e;
 918        int useforce, forced = FALSE;
 919
 920        // :3154        // if (-e line 3154) goto it  else stay put
 921        // :4,33w! foo  // write a portion of buffer to file "foo"
 922        // :w           // write all of buffer to current file
 923        // :q           // quit
 924        // :q!          // quit- dont care about modified file
 925        // :'a,'z!sort -u   // filter block through sort
 926        // :'f          // goto mark "f"
 927        // :'fl         // list literal the mark "f" line
 928        // :.r bar      // read file "bar" into buffer before dot
 929        // :/123/,/abc/d    // delete lines from "123" line to "abc" line
 930        // :/xyz/       // goto the "xyz" line
 931        // :s/find/replace/ // substitute pattern "find" with "replace"
 932        // :!<cmd>      // run <cmd> then return
 933        //
 934
 935        if (!buf[0])
 936                goto ret;
 937        if (*buf == ':')
 938                buf++;                  // move past the ':'
 939
 940        li = ch = i = 0;
 941        b = e = -1;
 942        q = text;                       // assume 1,$ for the range
 943        r = end - 1;
 944        li = count_lines(text, end - 1);
 945        fn = current_filename;
 946
 947        // look for optional address(es)  :.  :1  :1,9   :'q,'a   :%
 948        buf = get_address(buf, &b, &e);
 949
 950        // remember orig command line
 951        orig_buf = buf;
 952
 953        // get the COMMAND into cmd[]
 954        buf1 = cmd;
 955        while (*buf != '\0') {
 956                if (isspace(*buf))
 957                        break;
 958                *buf1++ = *buf++;
 959        }
 960        *buf1 = '\0';
 961        // get any ARGuments
 962        while (isblank(*buf))
 963                buf++;
 964        strcpy(args, buf);
 965        useforce = FALSE;
 966        buf1 = last_char_is(cmd, '!');
 967        if (buf1) {
 968                useforce = TRUE;
 969                *buf1 = '\0';   // get rid of !
 970        }
 971        if (b >= 0) {
 972                // if there is only one addr, then the addr
 973                // is the line number of the single line the
 974                // user wants. So, reset the end
 975                // pointer to point at end of the "b" line
 976                q = find_line(b);       // what line is #b
 977                r = end_line(q);
 978                li = 1;
 979        }
 980        if (e >= 0) {
 981                // we were given two addrs.  change the
 982                // end pointer to the addr given by user.
 983                r = find_line(e);       // what line is #e
 984                r = end_line(r);
 985                li = e - b + 1;
 986        }
 987        // ------------ now look for the command ------------
 988        i = strlen(cmd);
 989        if (i == 0) {           // :123CR goto line #123
 990                if (b >= 0) {
 991                        dot = find_line(b);     // what line is #b
 992                        dot_skip_over_ws();
 993                }
 994        }
 995#if ENABLE_FEATURE_ALLOW_EXEC
 996        else if (cmd[0] == '!') {       // run a cmd
 997                int retcode;
 998                // :!ls   run the <cmd>
 999                go_bottom_and_clear_to_eol();
1000                cookmode();
1001                retcode = system(orig_buf + 1); // run the cmd
1002                if (retcode)
1003                        printf("\nshell returned %i\n\n", retcode);
1004                rawmode();
1005                Hit_Return();                   // let user see results
1006        }
1007#endif
1008        else if (cmd[0] == '=' && !cmd[1]) {    // where is the address
1009                if (b < 0) {    // no addr given- use defaults
1010                        b = e = count_lines(text, dot);
1011                }
1012                status_line("%d", b);
1013        } else if (strncmp(cmd, "delete", i) == 0) {    // delete lines
1014                if (b < 0) {    // no addr given- use defaults
1015                        q = begin_line(dot);    // assume .,. for the range
1016                        r = end_line(dot);
1017                }
1018                dot = yank_delete(q, r, 1, YANKDEL);    // save, then delete lines
1019                dot_skip_over_ws();
1020        } else if (strncmp(cmd, "edit", i) == 0) {      // Edit a file
1021                // don't edit, if the current file has been modified
1022                if (file_modified && !useforce) {
1023                        status_line_bold("No write since last change (:%s! overrides)", cmd);
1024                        goto ret;
1025                }
1026                if (args[0]) {
1027                        // the user supplied a file name
1028                        fn = args;
1029                } else if (current_filename && current_filename[0]) {
1030                        // no user supplied name- use the current filename
1031                        // fn = current_filename;  was set by default
1032                } else {
1033                        // no user file name, no current name- punt
1034                        status_line_bold("No current filename");
1035                        goto ret;
1036                }
1037
1038                if (init_text_buffer(fn) < 0)
1039                        goto ret;
1040
1041#if ENABLE_FEATURE_VI_YANKMARK
1042                if (Ureg >= 0 && Ureg < 28) {
1043                        free(reg[Ureg]);        //   free orig line reg- for 'U'
1044                        reg[Ureg] = NULL;
1045                }
1046                if (YDreg >= 0 && YDreg < 28) {
1047                        free(reg[YDreg]);       //   free default yank/delete register
1048                        reg[YDreg] = NULL;
1049                }
1050#endif
1051                // how many lines in text[]?
1052                li = count_lines(text, end - 1);
1053                status_line("\"%s\"%s"
1054                        IF_FEATURE_VI_READONLY("%s")
1055                        " %dL, %dC", current_filename,
1056                        (file_size(fn) < 0 ? " [New file]" : ""),
1057                        IF_FEATURE_VI_READONLY(
1058                                ((readonly_mode) ? " [Readonly]" : ""),
1059                        )
1060                        li, ch);
1061        } else if (strncmp(cmd, "file", i) == 0) {      // what File is this
1062                if (b != -1 || e != -1) {
1063                        status_line_bold("No address allowed on this command");
1064                        goto ret;
1065                }
1066                if (args[0]) {
1067                        // user wants a new filename
1068                        free(current_filename);
1069                        current_filename = xstrdup(args);
1070                } else {
1071                        // user wants file status info
1072                        last_status_cksum = 0;  // force status update
1073                }
1074        } else if (strncmp(cmd, "features", i) == 0) {  // what features are available
1075                // print out values of all features
1076                go_bottom_and_clear_to_eol();
1077                cookmode();
1078                show_help();
1079                rawmode();
1080                Hit_Return();
1081        } else if (strncmp(cmd, "list", i) == 0) {      // literal print line
1082                if (b < 0) {    // no addr given- use defaults
1083                        q = begin_line(dot);    // assume .,. for the range
1084                        r = end_line(dot);
1085                }
1086                go_bottom_and_clear_to_eol();
1087                puts("\r");
1088                for (; q <= r; q++) {
1089                        int c_is_no_print;
1090
1091                        c = *q;
1092                        c_is_no_print = (c & 0x80) && !Isprint(c);
1093                        if (c_is_no_print) {
1094                                c = '.';
1095                                standout_start();
1096                        }
1097                        if (c == '\n') {
1098                                write1("$\r");
1099                        } else if (c < ' ' || c == 127) {
1100                                bb_putchar('^');
1101                                if (c == 127)
1102                                        c = '?';
1103                                else
1104                                        c += '@';
1105                        }
1106                        bb_putchar(c);
1107                        if (c_is_no_print)
1108                                standout_end();
1109                }
1110                Hit_Return();
1111        } else if (strncmp(cmd, "quit", i) == 0 // quit
1112                || strncmp(cmd, "next", i) == 0 // edit next file
1113                || strncmp(cmd, "prev", i) == 0 // edit previous file
1114        ) {
1115                int n;
1116                if (useforce) {
1117                        if (*cmd == 'q') {
1118                                // force end of argv list
1119                                optind = save_argc;
1120                        }
1121                        editing = 0;
1122                        goto ret;
1123                }
1124                // don't exit if the file been modified
1125                if (file_modified) {
1126                        status_line_bold("No write since last change (:%s! overrides)", cmd);
1127                        goto ret;
1128                }
1129                // are there other file to edit
1130                n = save_argc - optind - 1;
1131                if (*cmd == 'q' && n > 0) {
1132                        status_line_bold("%d more file(s) to edit", n);
1133                        goto ret;
1134                }
1135                if (*cmd == 'n' && n <= 0) {
1136                        status_line_bold("No more files to edit");
1137                        goto ret;
1138                }
1139                if (*cmd == 'p') {
1140                        // are there previous files to edit
1141                        if (optind < 1) {
1142                                status_line_bold("No previous files to edit");
1143                                goto ret;
1144                        }
1145                        optind -= 2;
1146                }
1147                editing = 0;
1148        } else if (strncmp(cmd, "read", i) == 0) {      // read file into text[]
1149                fn = args;
1150                if (!fn[0]) {
1151                        status_line_bold("No filename given");
1152                        goto ret;
1153                }
1154                if (b < 0) {    // no addr given- use defaults
1155                        q = begin_line(dot);    // assume "dot"
1156                }
1157                // read after current line- unless user said ":0r foo"
1158                if (b != 0)
1159                        q = next_line(q);
1160                { // dance around potentially-reallocated text[]
1161                        uintptr_t ofs = q - text;
1162                        ch = file_insert(fn, q, 0);
1163                        q = text + ofs;
1164                }
1165                if (ch < 0)
1166                        goto ret;       // nothing was inserted
1167                // how many lines in text[]?
1168                li = count_lines(q, q + ch - 1);
1169                status_line("\"%s\""
1170                        IF_FEATURE_VI_READONLY("%s")
1171                        " %dL, %dC", fn,
1172                        IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1173                        li, ch);
1174                if (ch > 0) {
1175                        // if the insert is before "dot" then we need to update
1176                        if (q <= dot)
1177                                dot += ch;
1178                        /*file_modified++; - done by file_insert */
1179                }
1180        } else if (strncmp(cmd, "rewind", i) == 0) {    // rewind cmd line args
1181                if (file_modified && !useforce) {
1182                        status_line_bold("No write since last change (:%s! overrides)", cmd);
1183                } else {
1184                        // reset the filenames to edit
1185                        optind = -1; /* start from 0th file */
1186                        editing = 0;
1187                }
1188#if ENABLE_FEATURE_VI_SET
1189        } else if (strncmp(cmd, "set", i) == 0) {       // set or clear features
1190#if ENABLE_FEATURE_VI_SETOPTS
1191                char *argp;
1192#endif
1193                i = 0;                  // offset into args
1194                // only blank is regarded as args delmiter. What about tab '\t' ?
1195                if (!args[0] || strcasecmp(args, "all") == 0) {
1196                        // print out values of all options
1197#if ENABLE_FEATURE_VI_SETOPTS
1198                        status_line_bold(
1199                                "%sautoindent "
1200                                "%sflash "
1201                                "%signorecase "
1202                                "%sshowmatch "
1203                                "tabstop=%u",
1204                                autoindent ? "" : "no",
1205                                err_method ? "" : "no",
1206                                ignorecase ? "" : "no",
1207                                showmatch ? "" : "no",
1208                                tabstop
1209                        );
1210#endif
1211                        goto ret;
1212                }
1213#if ENABLE_FEATURE_VI_SETOPTS
1214                argp = args;
1215                while (*argp) {
1216                        if (strncmp(argp, "no", 2) == 0)
1217                                i = 2;          // ":set noautoindent"
1218                        setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1219                        setops(argp, "flash "     , i, "fl", VI_ERR_METHOD);
1220                        setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1221                        setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1222                        if (strncmp(argp + i, "tabstop=", 8) == 0) {
1223                                int t = 0;
1224                                sscanf(argp + i+8, "%u", &t);
1225                                if (t > 0 && t <= MAX_TABSTOP)
1226                                        tabstop = t;
1227                        }
1228                        argp = skip_non_whitespace(argp);
1229                        argp = skip_whitespace(argp);
1230                }
1231#endif /* FEATURE_VI_SETOPTS */
1232#endif /* FEATURE_VI_SET */
1233#if ENABLE_FEATURE_VI_SEARCH
1234        } else if (cmd[0] == 's') {     // substitute a pattern with a replacement pattern
1235                char *F, *R, *flags;
1236                size_t len_F, len_R;
1237                int gflag;              // global replace flag
1238
1239                // F points to the "find" pattern
1240                // R points to the "replace" pattern
1241                // replace the cmd line delimiters "/" with NULs
1242                c = orig_buf[1];        // what is the delimiter
1243                F = orig_buf + 2;       // start of "find"
1244                R = strchr(F, c);       // middle delimiter
1245                if (!R)
1246                        goto colon_s_fail;
1247                len_F = R - F;
1248                *R++ = '\0';    // terminate "find"
1249                flags = strchr(R, c);
1250                if (!flags)
1251                        goto colon_s_fail;
1252                len_R = flags - R;
1253                *flags++ = '\0';        // terminate "replace"
1254                gflag = *flags;
1255
1256                q = begin_line(q);
1257                if (b < 0) {    // maybe :s/foo/bar/
1258                        q = begin_line(dot);      // start with cur line
1259                        b = count_lines(text, q); // cur line number
1260                }
1261                if (e < 0)
1262                        e = b;          // maybe :.s/foo/bar/
1263
1264                for (i = b; i <= e; i++) {      // so, :20,23 s \0 find \0 replace \0
1265                        char *ls = q;           // orig line start
1266                        char *found;
1267 vc4:
1268                        found = char_search(q, F, FORWARD, LIMITED);    // search cur line only for "find"
1269                        if (found) {
1270                                uintptr_t bias;
1271                                // we found the "find" pattern - delete it
1272                                text_hole_delete(found, found + len_F - 1);
1273                                // inset the "replace" patern
1274                                bias = string_insert(found, R); // insert the string
1275                                found += bias;
1276                                ls += bias;
1277                                /*q += bias; - recalculated anyway */
1278                                // check for "global"  :s/foo/bar/g
1279                                if (gflag == 'g') {
1280                                        if ((found + len_R) < end_line(ls)) {
1281                                                q = found + len_R;
1282                                                goto vc4;       // don't let q move past cur line
1283                                        }
1284                                }
1285                        }
1286                        q = next_line(ls);
1287                }
1288#endif /* FEATURE_VI_SEARCH */
1289        } else if (strncmp(cmd, "version", i) == 0) {  // show software version
1290                status_line(BB_VER " " BB_BT);
1291        } else if (strncmp(cmd, "write", i) == 0  // write text to file
1292                || strncmp(cmd, "wq", i) == 0
1293                || strncmp(cmd, "wn", i) == 0
1294                || (cmd[0] == 'x' && !cmd[1])
1295        ) {
1296                // is there a file name to write to?
1297                if (args[0]) {
1298                        fn = args;
1299                }
1300#if ENABLE_FEATURE_VI_READONLY
1301                if (readonly_mode && !useforce) {
1302                        status_line_bold("\"%s\" File is read only", fn);
1303                        goto ret;
1304                }
1305#endif
1306                // how many lines in text[]?
1307                li = count_lines(q, r);
1308                ch = r - q + 1;
1309                // see if file exists- if not, its just a new file request
1310                if (useforce) {
1311                        // if "fn" is not write-able, chmod u+w
1312                        // sprintf(syscmd, "chmod u+w %s", fn);
1313                        // system(syscmd);
1314                        forced = TRUE;
1315                }
1316                l = file_write(fn, q, r);
1317                if (useforce && forced) {
1318                        // chmod u-w
1319                        // sprintf(syscmd, "chmod u-w %s", fn);
1320                        // system(syscmd);
1321                        forced = FALSE;
1322                }
1323                if (l < 0) {
1324                        if (l == -1)
1325                                status_line_bold("\"%s\" %s", fn, strerror(errno));
1326                } else {
1327                        status_line("\"%s\" %dL, %dC", fn, li, l);
1328                        if (q == text && r == end - 1 && l == ch) {
1329                                file_modified = 0;
1330                                last_file_modified = -1;
1331                        }
1332                        if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
1333                            || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
1334                            )
1335                         && l == ch
1336                        ) {
1337                                editing = 0;
1338                        }
1339                }
1340#if ENABLE_FEATURE_VI_YANKMARK
1341        } else if (strncmp(cmd, "yank", i) == 0) {      // yank lines
1342                if (b < 0) {    // no addr given- use defaults
1343                        q = begin_line(dot);    // assume .,. for the range
1344                        r = end_line(dot);
1345                }
1346                text_yank(q, r, YDreg);
1347                li = count_lines(q, r);
1348                status_line("Yank %d lines (%d chars) into [%c]",
1349                                li, strlen(reg[YDreg]), what_reg());
1350#endif
1351        } else {
1352                // cmd unknown
1353                not_implemented(cmd);
1354        }
1355 ret:
1356        dot = bound_dot(dot);   // make sure "dot" is valid
1357        return;
1358#if ENABLE_FEATURE_VI_SEARCH
1359 colon_s_fail:
1360        status_line(":s expression missing delimiters");
1361#endif
1362}
1363
1364#endif /* FEATURE_VI_COLON */
1365
1366static void Hit_Return(void)
1367{
1368        int c;
1369
1370        standout_start();
1371        write1("[Hit return to continue]");
1372        standout_end();
1373        while ((c = get_one_char()) != '\n' && c != '\r')
1374                continue;
1375        redraw(TRUE);           // force redraw all
1376}
1377
1378static int next_tabstop(int col)
1379{
1380        return col + ((tabstop - 1) - (col % tabstop));
1381}
1382
1383//----- Synchronize the cursor to Dot --------------------------
1384static NOINLINE void sync_cursor(char *d, int *row, int *col)
1385{
1386        char *beg_cur;  // begin and end of "d" line
1387        char *tp;
1388        int cnt, ro, co;
1389
1390        beg_cur = begin_line(d);        // first char of cur line
1391
1392        if (beg_cur < screenbegin) {
1393                // "d" is before top line on screen
1394                // how many lines do we have to move
1395                cnt = count_lines(beg_cur, screenbegin);
1396 sc1:
1397                screenbegin = beg_cur;
1398                if (cnt > (rows - 1) / 2) {
1399                        // we moved too many lines. put "dot" in middle of screen
1400                        for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1401                                screenbegin = prev_line(screenbegin);
1402                        }
1403                }
1404        } else {
1405                char *end_scr;  // begin and end of screen
1406                end_scr = end_screen(); // last char of screen
1407                if (beg_cur > end_scr) {
1408                        // "d" is after bottom line on screen
1409                        // how many lines do we have to move
1410                        cnt = count_lines(end_scr, beg_cur);
1411                        if (cnt > (rows - 1) / 2)
1412                                goto sc1;       // too many lines
1413                        for (ro = 0; ro < cnt - 1; ro++) {
1414                                // move screen begin the same amount
1415                                screenbegin = next_line(screenbegin);
1416                                // now, move the end of screen
1417                                end_scr = next_line(end_scr);
1418                                end_scr = end_line(end_scr);
1419                        }
1420                }
1421        }
1422        // "d" is on screen- find out which row
1423        tp = screenbegin;
1424        for (ro = 0; ro < rows - 1; ro++) {     // drive "ro" to correct row
1425                if (tp == beg_cur)
1426                        break;
1427                tp = next_line(tp);
1428        }
1429
1430        // find out what col "d" is on
1431        co = 0;
1432        while (tp < d) { // drive "co" to correct column
1433                if (*tp == '\n') //vda || *tp == '\0')
1434                        break;
1435                if (*tp == '\t') {
1436                        // handle tabs like real vi
1437                        if (d == tp && cmd_mode) {
1438                                break;
1439                        }
1440                        co = next_tabstop(co);
1441                } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1442                        co++; // display as ^X, use 2 columns
1443                }
1444                co++;
1445                tp++;
1446        }
1447
1448        // "co" is the column where "dot" is.
1449        // The screen has "columns" columns.
1450        // The currently displayed columns are  0+offset -- columns+ofset
1451        // |-------------------------------------------------------------|
1452        //               ^ ^                                ^
1453        //        offset | |------- columns ----------------|
1454        //
1455        // If "co" is already in this range then we do not have to adjust offset
1456        //      but, we do have to subtract the "offset" bias from "co".
1457        // If "co" is outside this range then we have to change "offset".
1458        // If the first char of a line is a tab the cursor will try to stay
1459        //  in column 7, but we have to set offset to 0.
1460
1461        if (co < 0 + offset) {
1462                offset = co;
1463        }
1464        if (co >= columns + offset) {
1465                offset = co - columns + 1;
1466        }
1467        // if the first char of the line is a tab, and "dot" is sitting on it
1468        //  force offset to 0.
1469        if (d == beg_cur && *d == '\t') {
1470                offset = 0;
1471        }
1472        co -= offset;
1473
1474        *row = ro;
1475        *col = co;
1476}
1477
1478//----- Text Movement Routines ---------------------------------
1479static char *begin_line(char *p) // return pointer to first char cur line
1480{
1481        if (p > text) {
1482                p = memrchr(text, '\n', p - text);
1483                if (!p)
1484                        return text;
1485                return p + 1;
1486        }
1487        return p;
1488}
1489
1490static char *end_line(char *p) // return pointer to NL of cur line
1491{
1492        if (p < end - 1) {
1493                p = memchr(p, '\n', end - p - 1);
1494                if (!p)
1495                        return end - 1;
1496        }
1497        return p;
1498}
1499
1500static char *dollar_line(char *p) // return pointer to just before NL line
1501{
1502        p = end_line(p);
1503        // Try to stay off of the Newline
1504        if (*p == '\n' && (p - begin_line(p)) > 0)
1505                p--;
1506        return p;
1507}
1508
1509static char *prev_line(char *p) // return pointer first char prev line
1510{
1511        p = begin_line(p);      // goto begining of cur line
1512        if (p > text && p[-1] == '\n')
1513                p--;                    // step to prev line
1514        p = begin_line(p);      // goto begining of prev line
1515        return p;
1516}
1517
1518static char *next_line(char *p) // return pointer first char next line
1519{
1520        p = end_line(p);
1521        if (p < end - 1 && *p == '\n')
1522                p++;                    // step to next line
1523        return p;
1524}
1525
1526//----- Text Information Routines ------------------------------
1527static char *end_screen(void)
1528{
1529        char *q;
1530        int cnt;
1531
1532        // find new bottom line
1533        q = screenbegin;
1534        for (cnt = 0; cnt < rows - 2; cnt++)
1535                q = next_line(q);
1536        q = end_line(q);
1537        return q;
1538}
1539
1540// count line from start to stop
1541static int count_lines(char *start, char *stop)
1542{
1543        char *q;
1544        int cnt;
1545
1546        if (stop < start) { // start and stop are backwards- reverse them
1547                q = start;
1548                start = stop;
1549                stop = q;
1550        }
1551        cnt = 0;
1552        stop = end_line(stop);
1553        while (start <= stop && start <= end - 1) {
1554                start = end_line(start);
1555                if (*start == '\n')
1556                        cnt++;
1557                start++;
1558        }
1559        return cnt;
1560}
1561
1562static char *find_line(int li)  // find begining of line #li
1563{
1564        char *q;
1565
1566        for (q = text; li > 1; li--) {
1567                q = next_line(q);
1568        }
1569        return q;
1570}
1571
1572//----- Dot Movement Routines ----------------------------------
1573static void dot_left(void)
1574{
1575        if (dot > text && dot[-1] != '\n')
1576                dot--;
1577}
1578
1579static void dot_right(void)
1580{
1581        if (dot < end - 1 && *dot != '\n')
1582                dot++;
1583}
1584
1585static void dot_begin(void)
1586{
1587        dot = begin_line(dot);  // return pointer to first char cur line
1588}
1589
1590static void dot_end(void)
1591{
1592        dot = end_line(dot);    // return pointer to last char cur line
1593}
1594
1595static char *move_to_col(char *p, int l)
1596{
1597        int co;
1598
1599        p = begin_line(p);
1600        co = 0;
1601        while (co < l && p < end) {
1602                if (*p == '\n') //vda || *p == '\0')
1603                        break;
1604                if (*p == '\t') {
1605                        co = next_tabstop(co);
1606                } else if (*p < ' ' || *p == 127) {
1607                        co++; // display as ^X, use 2 columns
1608                }
1609                co++;
1610                p++;
1611        }
1612        return p;
1613}
1614
1615static void dot_next(void)
1616{
1617        dot = next_line(dot);
1618}
1619
1620static void dot_prev(void)
1621{
1622        dot = prev_line(dot);
1623}
1624
1625static void dot_scroll(int cnt, int dir)
1626{
1627        char *q;
1628
1629        for (; cnt > 0; cnt--) {
1630                if (dir < 0) {
1631                        // scroll Backwards
1632                        // ctrl-Y scroll up one line
1633                        screenbegin = prev_line(screenbegin);
1634                } else {
1635                        // scroll Forwards
1636                        // ctrl-E scroll down one line
1637                        screenbegin = next_line(screenbegin);
1638                }
1639        }
1640        // make sure "dot" stays on the screen so we dont scroll off
1641        if (dot < screenbegin)
1642                dot = screenbegin;
1643        q = end_screen();       // find new bottom line
1644        if (dot > q)
1645                dot = begin_line(q);    // is dot is below bottom line?
1646        dot_skip_over_ws();
1647}
1648
1649static void dot_skip_over_ws(void)
1650{
1651        // skip WS
1652        while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1653                dot++;
1654}
1655
1656static void dot_delete(void)    // delete the char at 'dot'
1657{
1658        text_hole_delete(dot, dot);
1659}
1660
1661static char *bound_dot(char *p) // make sure  text[0] <= P < "end"
1662{
1663        if (p >= end && end > text) {
1664                p = end - 1;
1665                indicate_error('1');
1666        }
1667        if (p < text) {
1668                p = text;
1669                indicate_error('2');
1670        }
1671        return p;
1672}
1673
1674//----- Helper Utility Routines --------------------------------
1675
1676//----------------------------------------------------------------
1677//----- Char Routines --------------------------------------------
1678/* Chars that are part of a word-
1679 *    0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1680 * Chars that are Not part of a word (stoppers)
1681 *    !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1682 * Chars that are WhiteSpace
1683 *    TAB NEWLINE VT FF RETURN SPACE
1684 * DO NOT COUNT NEWLINE AS WHITESPACE
1685 */
1686
1687static char *new_screen(int ro, int co)
1688{
1689        int li;
1690
1691        free(screen);
1692        screensize = ro * co + 8;
1693        screen = xmalloc(screensize);
1694        // initialize the new screen. assume this will be a empty file.
1695        screen_erase();
1696        //   non-existent text[] lines start with a tilde (~).
1697        for (li = 1; li < ro - 1; li++) {
1698                screen[(li * co) + 0] = '~';
1699        }
1700        return screen;
1701}
1702
1703#if ENABLE_FEATURE_VI_SEARCH
1704
1705# if ENABLE_FEATURE_VI_REGEX_SEARCH
1706
1707// search for pattern starting at p
1708static char *char_search(char *p, const char *pat, int dir, int range)
1709{
1710        char *q;
1711        struct re_pattern_buffer preg;
1712        int i;
1713        int size;
1714
1715        re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1716        preg.translate = 0;
1717        preg.fastmap = 0;
1718        preg.buffer = 0;
1719        preg.allocated = 0;
1720
1721        // assume a LIMITED forward search
1722        q = next_line(p);
1723        q = end_line(q);
1724        q = end - 1;
1725        if (dir == BACK) {
1726                q = prev_line(p);
1727                q = text;
1728        }
1729        // count the number of chars to search over, forward or backward
1730        size = q - p;
1731        if (size < 0)
1732                size = p - q;
1733        // RANGE could be negative if we are searching backwards
1734        range = q - p;
1735
1736        q = (char *)re_compile_pattern(pat, strlen(pat), (struct re_pattern_buffer *)&preg);
1737        if (q != 0) {
1738                // The pattern was not compiled
1739                status_line_bold("bad search pattern: \"%s\": %s", pat, q);
1740                i = 0;                  // return p if pattern not compiled
1741                goto cs1;
1742        }
1743
1744        q = p;
1745        if (range < 0) {
1746                q = p - size;
1747                if (q < text)
1748                        q = text;
1749        }
1750        // search for the compiled pattern, preg, in p[]
1751        // range < 0-  search backward
1752        // range > 0-  search forward
1753        // 0 < start < size
1754        // re_search() < 0  not found or error
1755        // re_search() > 0  index of found pattern
1756        //            struct pattern    char     int    int    int     struct reg
1757        // re_search (*pattern_buffer,  *string, size,  start, range,  *regs)
1758        i = re_search(&preg, q, size, 0, range, 0);
1759        if (i == -1) {
1760                p = 0;
1761                i = 0;                  // return NULL if pattern not found
1762        }
1763 cs1:
1764        if (dir == FORWARD) {
1765                p = p + i;
1766        } else {
1767                p = p - i;
1768        }
1769        return p;
1770}
1771
1772# else
1773
1774#  if ENABLE_FEATURE_VI_SETOPTS
1775static int mycmp(const char *s1, const char *s2, int len)
1776{
1777        if (ignorecase) {
1778                return strncasecmp(s1, s2, len);
1779        }
1780        return strncmp(s1, s2, len);
1781}
1782#  else
1783#   define mycmp strncmp
1784#  endif
1785
1786static char *char_search(char *p, const char *pat, int dir, int range)
1787{
1788        char *start, *stop;
1789        int len;
1790
1791        len = strlen(pat);
1792        if (dir == FORWARD) {
1793                stop = end - 1; // assume range is p - end-1
1794                if (range == LIMITED)
1795                        stop = next_line(p);    // range is to next line
1796                for (start = p; start < stop; start++) {
1797                        if (mycmp(start, pat, len) == 0) {
1798                                return start;
1799                        }
1800                }
1801        } else if (dir == BACK) {
1802                stop = text;    // assume range is text - p
1803                if (range == LIMITED)
1804                        stop = prev_line(p);    // range is to prev line
1805                for (start = p - len; start >= stop; start--) {
1806                        if (mycmp(start, pat, len) == 0) {
1807                                return start;
1808                        }
1809                }
1810        }
1811        // pattern not found
1812        return NULL;
1813}
1814
1815# endif
1816
1817#endif /* FEATURE_VI_SEARCH */
1818
1819static char *char_insert(char *p, char c) // insert the char c at 'p'
1820{
1821        if (c == 22) {          // Is this an ctrl-V?
1822                p += stupid_insert(p, '^');     // use ^ to indicate literal next
1823                refresh(FALSE); // show the ^
1824                c = get_one_char();
1825                *p = c;
1826                p++;
1827                file_modified++;
1828        } else if (c == 27) {   // Is this an ESC?
1829                cmd_mode = 0;
1830                cmdcnt = 0;
1831                end_cmd_q();    // stop adding to q
1832                last_status_cksum = 0;  // force status update
1833                if ((p[-1] != '\n') && (dot > text)) {
1834                        p--;
1835                }
1836        } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1837                //     123456789
1838                if ((p[-1] != '\n') && (dot>text)) {
1839                        p--;
1840                        p = text_hole_delete(p, p);     // shrink buffer 1 char
1841                }
1842        } else {
1843#if ENABLE_FEATURE_VI_SETOPTS
1844                // insert a char into text[]
1845                char *sp;               // "save p"
1846#endif
1847
1848                if (c == 13)
1849                        c = '\n';       // translate \r to \n
1850#if ENABLE_FEATURE_VI_SETOPTS
1851                sp = p;                 // remember addr of insert
1852#endif
1853                p += 1 + stupid_insert(p, c);   // insert the char
1854#if ENABLE_FEATURE_VI_SETOPTS
1855                if (showmatch && strchr(")]}", *sp) != NULL) {
1856                        showmatching(sp);
1857                }
1858                if (autoindent && c == '\n') {  // auto indent the new line
1859                        char *q;
1860                        size_t len;
1861                        q = prev_line(p);       // use prev line as template
1862                        len = strspn(q, " \t"); // space or tab
1863                        if (len) {
1864                                uintptr_t bias;
1865                                bias = text_hole_make(p, len);
1866                                p += bias;
1867                                q += bias;
1868                                memcpy(p, q, len);
1869                                p += len;
1870                        }
1871                }
1872#endif
1873        }
1874        return p;
1875}
1876
1877// might reallocate text[]! use p += stupid_insert(p, ...),
1878// and be careful to not use pointers into potentially freed text[]!
1879static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1880{
1881        uintptr_t bias;
1882        bias = text_hole_make(p, 1);
1883        p += bias;
1884        *p = c;
1885        //file_modified++; - done by text_hole_make()
1886        return bias;
1887}
1888
1889static int find_range(char **start, char **stop, char c)
1890{
1891        char *save_dot, *p, *q, *t;
1892        int cnt, multiline = 0;
1893
1894        save_dot = dot;
1895        p = q = dot;
1896
1897        if (strchr("cdy><", c)) {
1898                // these cmds operate on whole lines
1899                p = q = begin_line(p);
1900                for (cnt = 1; cnt < cmdcnt; cnt++) {
1901                        q = next_line(q);
1902                }
1903                q = end_line(q);
1904        } else if (strchr("^%$0bBeEfth\b\177", c)) {
1905                // These cmds operate on char positions
1906                do_cmd(c);              // execute movement cmd
1907                q = dot;
1908        } else if (strchr("wW", c)) {
1909                do_cmd(c);              // execute movement cmd
1910                // if we are at the next word's first char
1911                // step back one char
1912                // but check the possibilities when it is true
1913                if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1914                                || (ispunct(dot[-1]) && !ispunct(dot[0]))
1915                                || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1916                        dot--;          // move back off of next word
1917                if (dot > text && *dot == '\n')
1918                        dot--;          // stay off NL
1919                q = dot;
1920        } else if (strchr("H-k{", c)) {
1921                // these operate on multi-lines backwards
1922                q = end_line(dot);      // find NL
1923                do_cmd(c);              // execute movement cmd
1924                dot_begin();
1925                p = dot;
1926        } else if (strchr("L+j}\r\n", c)) {
1927                // these operate on multi-lines forwards
1928                p = begin_line(dot);
1929                do_cmd(c);              // execute movement cmd
1930                dot_end();              // find NL
1931                q = dot;
1932        } else {
1933            // nothing -- this causes any other values of c to
1934            // represent the one-character range under the
1935            // cursor.  this is correct for ' ' and 'l', but
1936            // perhaps no others.
1937            //
1938        }
1939        if (q < p) {
1940                t = q;
1941                q = p;
1942                p = t;
1943        }
1944
1945        // backward char movements don't include start position
1946        if (q > p && strchr("^0bBh\b\177", c)) q--;
1947
1948        multiline = 0;
1949        for (t = p; t <= q; t++) {
1950                if (*t == '\n') {
1951                        multiline = 1;
1952                        break;
1953                }
1954        }
1955
1956        *start = p;
1957        *stop = q;
1958        dot = save_dot;
1959        return multiline;
1960}
1961
1962static int st_test(char *p, int type, int dir, char *tested)
1963{
1964        char c, c0, ci;
1965        int test, inc;
1966
1967        inc = dir;
1968        c = c0 = p[0];
1969        ci = p[inc];
1970        test = 0;
1971
1972        if (type == S_BEFORE_WS) {
1973                c = ci;
1974                test = (!isspace(c) || c == '\n');
1975        }
1976        if (type == S_TO_WS) {
1977                c = c0;
1978                test = (!isspace(c) || c == '\n');
1979        }
1980        if (type == S_OVER_WS) {
1981                c = c0;
1982                test = isspace(c);
1983        }
1984        if (type == S_END_PUNCT) {
1985                c = ci;
1986                test = ispunct(c);
1987        }
1988        if (type == S_END_ALNUM) {
1989                c = ci;
1990                test = (isalnum(c) || c == '_');
1991        }
1992        *tested = c;
1993        return test;
1994}
1995
1996static char *skip_thing(char *p, int linecnt, int dir, int type)
1997{
1998        char c;
1999
2000        while (st_test(p, type, dir, &c)) {
2001                // make sure we limit search to correct number of lines
2002                if (c == '\n' && --linecnt < 1)
2003                        break;
2004                if (dir >= 0 && p >= end - 1)
2005                        break;
2006                if (dir < 0 && p <= text)
2007                        break;
2008                p += dir;               // move to next char
2009        }
2010        return p;
2011}
2012
2013// find matching char of pair  ()  []  {}
2014static char *find_pair(char *p, const char c)
2015{
2016        char match, *q;
2017        int dir, level;
2018
2019        match = ')';
2020        level = 1;
2021        dir = 1;                        // assume forward
2022        switch (c) {
2023        case '(': match = ')'; break;
2024        case '[': match = ']'; break;
2025        case '{': match = '}'; break;
2026        case ')': match = '('; dir = -1; break;
2027        case ']': match = '['; dir = -1; break;
2028        case '}': match = '{'; dir = -1; break;
2029        }
2030        for (q = p + dir; text <= q && q < end; q += dir) {
2031                // look for match, count levels of pairs  (( ))
2032                if (*q == c)
2033                        level++;        // increase pair levels
2034                if (*q == match)
2035                        level--;        // reduce pair level
2036                if (level == 0)
2037                        break;          // found matching pair
2038        }
2039        if (level != 0)
2040                q = NULL;               // indicate no match
2041        return q;
2042}
2043
2044#if ENABLE_FEATURE_VI_SETOPTS
2045// show the matching char of a pair,  ()  []  {}
2046static void showmatching(char *p)
2047{
2048        char *q, *save_dot;
2049
2050        // we found half of a pair
2051        q = find_pair(p, *p);   // get loc of matching char
2052        if (q == NULL) {
2053                indicate_error('3');    // no matching char
2054        } else {
2055                // "q" now points to matching pair
2056                save_dot = dot; // remember where we are
2057                dot = q;                // go to new loc
2058                refresh(FALSE); // let the user see it
2059                mysleep(40);    // give user some time
2060                dot = save_dot; // go back to old loc
2061                refresh(FALSE);
2062        }
2063}
2064#endif /* FEATURE_VI_SETOPTS */
2065
2066// open a hole in text[]
2067// might reallocate text[]! use p += text_hole_make(p, ...),
2068// and be careful to not use pointers into potentially freed text[]!
2069static uintptr_t text_hole_make(char *p, int size)      // at "p", make a 'size' byte hole
2070{
2071        uintptr_t bias = 0;
2072
2073        if (size <= 0)
2074                return bias;
2075        end += size;            // adjust the new END
2076        if (end >= (text + text_size)) {
2077                char *new_text;
2078                text_size += end - (text + text_size) + 10240;
2079                new_text = xrealloc(text, text_size);
2080                bias = (new_text - text);
2081                screenbegin += bias;
2082                dot         += bias;
2083                end         += bias;
2084                p           += bias;
2085#if ENABLE_FEATURE_VI_YANKMARK
2086                {
2087                        int i;
2088                        for (i = 0; i < ARRAY_SIZE(mark); i++)
2089                                if (mark[i])
2090                                        mark[i] += bias;
2091                }
2092#endif
2093                text = new_text;
2094        }
2095        memmove(p + size, p, end - size - p);
2096        memset(p, ' ', size);   // clear new hole
2097        file_modified++;
2098        return bias;
2099}
2100
2101//  close a hole in text[]
2102static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive
2103{
2104        char *src, *dest;
2105        int cnt, hole_size;
2106
2107        // move forwards, from beginning
2108        // assume p <= q
2109        src = q + 1;
2110        dest = p;
2111        if (q < p) {            // they are backward- swap them
2112                src = p + 1;
2113                dest = q;
2114        }
2115        hole_size = q - p + 1;
2116        cnt = end - src;
2117        if (src < text || src > end)
2118                goto thd0;
2119        if (dest < text || dest >= end)
2120                goto thd0;
2121        if (src >= end)
2122                goto thd_atend; // just delete the end of the buffer
2123        memmove(dest, src, cnt);
2124 thd_atend:
2125        end = end - hole_size;  // adjust the new END
2126        if (dest >= end)
2127                dest = end - 1; // make sure dest in below end-1
2128        if (end <= text)
2129                dest = end = text;      // keep pointers valid
2130        file_modified++;
2131 thd0:
2132        return dest;
2133}
2134
2135// copy text into register, then delete text.
2136// if dist <= 0, do not include, or go past, a NewLine
2137//
2138static char *yank_delete(char *start, char *stop, int dist, int yf)
2139{
2140        char *p;
2141
2142        // make sure start <= stop
2143        if (start > stop) {
2144                // they are backwards, reverse them
2145                p = start;
2146                start = stop;
2147                stop = p;
2148        }
2149        if (dist <= 0) {
2150                // we cannot cross NL boundaries
2151                p = start;
2152                if (*p == '\n')
2153                        return p;
2154                // dont go past a NewLine
2155                for (; p + 1 <= stop; p++) {
2156                        if (p[1] == '\n') {
2157                                stop = p;       // "stop" just before NewLine
2158                                break;
2159                        }
2160                }
2161        }
2162        p = start;
2163#if ENABLE_FEATURE_VI_YANKMARK
2164        text_yank(start, stop, YDreg);
2165#endif
2166        if (yf == YANKDEL) {
2167                p = text_hole_delete(start, stop);
2168        }                                       // delete lines
2169        return p;
2170}
2171
2172static void show_help(void)
2173{
2174        puts("These features are available:"
2175#if ENABLE_FEATURE_VI_SEARCH
2176        "\n\tPattern searches with / and ?"
2177#endif
2178#if ENABLE_FEATURE_VI_DOT_CMD
2179        "\n\tLast command repeat with \'.\'"
2180#endif
2181#if ENABLE_FEATURE_VI_YANKMARK
2182        "\n\tLine marking with 'x"
2183        "\n\tNamed buffers with \"x"
2184#endif
2185#if ENABLE_FEATURE_VI_READONLY
2186        //not implemented: "\n\tReadonly if vi is called as \"view\""
2187        //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2188#endif
2189#if ENABLE_FEATURE_VI_SET
2190        "\n\tSome colon mode commands with \':\'"
2191#endif
2192#if ENABLE_FEATURE_VI_SETOPTS
2193        "\n\tSettable options with \":set\""
2194#endif
2195#if ENABLE_FEATURE_VI_USE_SIGNALS
2196        "\n\tSignal catching- ^C"
2197        "\n\tJob suspend and resume with ^Z"
2198#endif
2199#if ENABLE_FEATURE_VI_WIN_RESIZE
2200        "\n\tAdapt to window re-sizes"
2201#endif
2202        );
2203}
2204
2205#if ENABLE_FEATURE_VI_DOT_CMD
2206static void start_new_cmd_q(char c)
2207{
2208        // get buffer for new cmd
2209        // if there is a current cmd count put it in the buffer first
2210        if (cmdcnt > 0) {
2211                lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2212        } else { // just save char c onto queue
2213                last_modifying_cmd[0] = c;
2214                lmc_len = 1;
2215        }
2216        adding2q = 1;
2217}
2218
2219static void end_cmd_q(void)
2220{
2221#if ENABLE_FEATURE_VI_YANKMARK
2222        YDreg = 26;                     // go back to default Yank/Delete reg
2223#endif
2224        adding2q = 0;
2225}
2226#endif /* FEATURE_VI_DOT_CMD */
2227
2228#if ENABLE_FEATURE_VI_YANKMARK \
2229 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2230 || ENABLE_FEATURE_VI_CRASHME
2231// might reallocate text[]! use p += string_insert(p, ...),
2232// and be careful to not use pointers into potentially freed text[]!
2233static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p'
2234{
2235        uintptr_t bias;
2236        int i;
2237
2238        i = strlen(s);
2239        bias = text_hole_make(p, i);
2240        p += bias;
2241        memcpy(p, s, i);
2242#if ENABLE_FEATURE_VI_YANKMARK
2243        {
2244                int cnt;
2245                for (cnt = 0; *s != '\0'; s++) {
2246                        if (*s == '\n')
2247                                cnt++;
2248                }
2249                status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2250        }
2251#endif
2252        return bias;
2253}
2254#endif
2255
2256#if ENABLE_FEATURE_VI_YANKMARK
2257static char *text_yank(char *p, char *q, int dest)      // copy text into a register
2258{
2259        int cnt = q - p;
2260        if (cnt < 0) {          // they are backwards- reverse them
2261                p = q;
2262                cnt = -cnt;
2263        }
2264        free(reg[dest]);        //  if already a yank register, free it
2265        reg[dest] = xstrndup(p, cnt + 1);
2266        return p;
2267}
2268
2269static char what_reg(void)
2270{
2271        char c;
2272
2273        c = 'D';                        // default to D-reg
2274        if (0 <= YDreg && YDreg <= 25)
2275                c = 'a' + (char) YDreg;
2276        if (YDreg == 26)
2277                c = 'D';
2278        if (YDreg == 27)
2279                c = 'U';
2280        return c;
2281}
2282
2283static void check_context(char cmd)
2284{
2285        // A context is defined to be "modifying text"
2286        // Any modifying command establishes a new context.
2287
2288        if (dot < context_start || dot > context_end) {
2289                if (strchr(modifying_cmds, cmd) != NULL) {
2290                        // we are trying to modify text[]- make this the current context
2291                        mark[27] = mark[26];    // move cur to prev
2292                        mark[26] = dot; // move local to cur
2293                        context_start = prev_line(prev_line(dot));
2294                        context_end = next_line(next_line(dot));
2295                        //loiter= start_loiter= now;
2296                }
2297        }
2298}
2299
2300static char *swap_context(char *p) // goto new context for '' command make this the current context
2301{
2302        char *tmp;
2303
2304        // the current context is in mark[26]
2305        // the previous context is in mark[27]
2306        // only swap context if other context is valid
2307        if (text <= mark[27] && mark[27] <= end - 1) {
2308                tmp = mark[27];
2309                mark[27] = mark[26];
2310                mark[26] = tmp;
2311                p = mark[26];   // where we are going- previous context
2312                context_start = prev_line(prev_line(prev_line(p)));
2313                context_end = next_line(next_line(next_line(p)));
2314        }
2315        return p;
2316}
2317#endif /* FEATURE_VI_YANKMARK */
2318
2319//----- Set terminal attributes --------------------------------
2320static void rawmode(void)
2321{
2322        tcgetattr(0, &term_orig);
2323        term_vi = term_orig;
2324        term_vi.c_lflag &= (~ICANON & ~ECHO);   // leave ISIG on - allow intr's
2325        term_vi.c_iflag &= (~IXON & ~ICRNL);
2326        term_vi.c_oflag &= (~ONLCR);
2327        term_vi.c_cc[VMIN] = 1;
2328        term_vi.c_cc[VTIME] = 0;
2329        erase_char = term_vi.c_cc[VERASE];
2330        tcsetattr_stdin_TCSANOW(&term_vi);
2331}
2332
2333static void cookmode(void)
2334{
2335        fflush_all();
2336        tcsetattr_stdin_TCSANOW(&term_orig);
2337}
2338
2339#if ENABLE_FEATURE_VI_USE_SIGNALS
2340//----- Come here when we get a window resize signal ---------
2341static void winch_sig(int sig UNUSED_PARAM)
2342{
2343        int save_errno = errno;
2344        // FIXME: do it in main loop!!!
2345        signal(SIGWINCH, winch_sig);
2346        query_screen_dimensions();
2347        new_screen(rows, columns);      // get memory for virtual screen
2348        redraw(TRUE);           // re-draw the screen
2349        errno = save_errno;
2350}
2351
2352//----- Come here when we get a continue signal -------------------
2353static void cont_sig(int sig UNUSED_PARAM)
2354{
2355        int save_errno = errno;
2356        rawmode(); // terminal to "raw"
2357        last_status_cksum = 0; // force status update
2358        redraw(TRUE); // re-draw the screen
2359
2360        signal(SIGTSTP, suspend_sig);
2361        signal(SIGCONT, SIG_DFL);
2362        //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2363        errno = save_errno;
2364}
2365
2366//----- Come here when we get a Suspend signal -------------------
2367static void suspend_sig(int sig UNUSED_PARAM)
2368{
2369        int save_errno = errno;
2370        go_bottom_and_clear_to_eol();
2371        cookmode(); // terminal to "cooked"
2372
2373        signal(SIGCONT, cont_sig);
2374        signal(SIGTSTP, SIG_DFL);
2375        kill(my_pid, SIGTSTP);
2376        errno = save_errno;
2377}
2378
2379//----- Come here when we get a signal ---------------------------
2380static void catch_sig(int sig)
2381{
2382        signal(SIGINT, catch_sig);
2383        siglongjmp(restart, sig);
2384}
2385#endif /* FEATURE_VI_USE_SIGNALS */
2386
2387static int mysleep(int hund)    // sleep for 'hund' 1/100 seconds or stdin ready
2388{
2389        struct pollfd pfd[1];
2390
2391        pfd[0].fd = STDIN_FILENO;
2392        pfd[0].events = POLLIN;
2393        return safe_poll(pfd, 1, hund*10) > 0;
2394}
2395
2396//----- IO Routines --------------------------------------------
2397static int readit(void) // read (maybe cursor) key from stdin
2398{
2399        int c;
2400
2401        fflush_all();
2402        c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2403        if (c == -1) { // EOF/error
2404                go_bottom_and_clear_to_eol();
2405                cookmode(); // terminal to "cooked"
2406                bb_error_msg_and_die("can't read user input");
2407        }
2408        return c;
2409}
2410
2411//----- IO Routines --------------------------------------------
2412static int get_one_char(void)
2413{
2414        int c;
2415
2416#if ENABLE_FEATURE_VI_DOT_CMD
2417        if (!adding2q) {
2418                // we are not adding to the q.
2419                // but, we may be reading from a q
2420                if (ioq == 0) {
2421                        // there is no current q, read from STDIN
2422                        c = readit();   // get the users input
2423                } else {
2424                        // there is a queue to get chars from first
2425                        // careful with correct sign expansion!
2426                        c = (unsigned char)*ioq++;
2427                        if (c == '\0') {
2428                                // the end of the q, read from STDIN
2429                                free(ioq_start);
2430                                ioq_start = ioq = 0;
2431                                c = readit();   // get the users input
2432                        }
2433                }
2434        } else {
2435                // adding STDIN chars to q
2436                c = readit();   // get the users input
2437                if (lmc_len >= MAX_INPUT_LEN - 1) {
2438                        status_line_bold("last_modifying_cmd overrun");
2439                } else {
2440                        // add new char to q
2441                        last_modifying_cmd[lmc_len++] = c;
2442                }
2443        }
2444#else
2445        c = readit();           // get the users input
2446#endif /* FEATURE_VI_DOT_CMD */
2447        return c;
2448}
2449
2450// Get input line (uses "status line" area)
2451static char *get_input_line(const char *prompt)
2452{
2453        // char [MAX_INPUT_LEN]
2454#define buf get_input_line__buf
2455
2456        int c;
2457        int i;
2458
2459        strcpy(buf, prompt);
2460        last_status_cksum = 0;  // force status update
2461        go_bottom_and_clear_to_eol();
2462        write1(prompt);      // write out the :, /, or ? prompt
2463
2464        i = strlen(buf);
2465        while (i < MAX_INPUT_LEN) {
2466                c = get_one_char();
2467                if (c == '\n' || c == '\r' || c == 27)
2468                        break;          // this is end of input
2469                if (c == erase_char || c == 8 || c == 127) {
2470                        // user wants to erase prev char
2471                        buf[--i] = '\0';
2472                        write1("\b \b"); // erase char on screen
2473                        if (i <= 0) // user backs up before b-o-l, exit
2474                                break;
2475                } else if (c > 0 && c < 256) { // exclude Unicode
2476                        // (TODO: need to handle Unicode)
2477                        buf[i] = c;
2478                        buf[++i] = '\0';
2479                        bb_putchar(c);
2480                }
2481        }
2482        refresh(FALSE);
2483        return buf;
2484#undef buf
2485}
2486
2487static int file_size(const char *fn) // what is the byte size of "fn"
2488{
2489        struct stat st_buf;
2490        int cnt;
2491
2492        cnt = -1;
2493        if (fn && stat(fn, &st_buf) == 0)       // see if file exists
2494                cnt = (int) st_buf.st_size;
2495        return cnt;
2496}
2497
2498// might reallocate text[]!
2499static int file_insert(const char *fn, char *p, int update_ro_status)
2500{
2501        int cnt = -1;
2502        int fd, size;
2503        struct stat statbuf;
2504
2505        /* Validate file */
2506        if (stat(fn, &statbuf) < 0) {
2507                status_line_bold("\"%s\" %s", fn, strerror(errno));
2508                goto fi0;
2509        }
2510        if (!S_ISREG(statbuf.st_mode)) {
2511                // This is not a regular file
2512                status_line_bold("\"%s\" Not a regular file", fn);
2513                goto fi0;
2514        }
2515        if (p < text || p > end) {
2516                status_line_bold("Trying to insert file outside of memory");
2517                goto fi0;
2518        }
2519
2520        // read file to buffer
2521        fd = open(fn, O_RDONLY);
2522        if (fd < 0) {
2523                status_line_bold("\"%s\" %s", fn, strerror(errno));
2524                goto fi0;
2525        }
2526        size = statbuf.st_size;
2527        p += text_hole_make(p, size);
2528        cnt = safe_read(fd, p, size);
2529        if (cnt < 0) {
2530                status_line_bold("\"%s\" %s", fn, strerror(errno));
2531                p = text_hole_delete(p, p + size - 1);  // un-do buffer insert
2532        } else if (cnt < size) {
2533                // There was a partial read, shrink unused space text[]
2534                p = text_hole_delete(p + cnt, p + (size - cnt) - 1);    // un-do buffer insert
2535                status_line_bold("can't read all of file \"%s\"", fn);
2536        }
2537        if (cnt >= size)
2538                file_modified++;
2539        close(fd);
2540 fi0:
2541#if ENABLE_FEATURE_VI_READONLY
2542        if (update_ro_status
2543         && ((access(fn, W_OK) < 0) ||
2544                /* root will always have access()
2545                 * so we check fileperms too */
2546                !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2547            )
2548        ) {
2549                SET_READONLY_FILE(readonly_mode);
2550        }
2551#endif
2552        return cnt;
2553}
2554
2555static int file_write(char *fn, char *first, char *last)
2556{
2557        int fd, cnt, charcnt;
2558
2559        if (fn == 0) {
2560                status_line_bold("No current filename");
2561                return -2;
2562        }
2563        /* By popular request we do not open file with O_TRUNC,
2564         * but instead ftruncate() it _after_ successful write.
2565         * Might reduce amount of data lost on power fail etc.
2566         */
2567        fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2568        if (fd < 0)
2569                return -1;
2570        cnt = last - first + 1;
2571        charcnt = full_write(fd, first, cnt);
2572        ftruncate(fd, charcnt);
2573        if (charcnt == cnt) {
2574                // good write
2575                //file_modified = FALSE;
2576        } else {
2577                charcnt = 0;
2578        }
2579        close(fd);
2580        return charcnt;
2581}
2582
2583//----- Terminal Drawing ---------------------------------------
2584// The terminal is made up of 'rows' line of 'columns' columns.
2585// classically this would be 24 x 80.
2586//  screen coordinates
2587//  0,0     ...     0,79
2588//  1,0     ...     1,79
2589//  .       ...     .
2590//  .       ...     .
2591//  22,0    ...     22,79
2592//  23,0    ...     23,79   <- status line
2593
2594//----- Move the cursor to row x col (count from 0, not 1) -------
2595static void place_cursor(int row, int col, int optimize)
2596{
2597        char cm1[sizeof(CMrc) + sizeof(int)*3 * 2];
2598#if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2599        enum {
2600                SZ_UP = sizeof(CMup),
2601                SZ_DN = sizeof(CMdown),
2602                SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN,
2603        };
2604        char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size
2605#endif
2606        char *cm;
2607
2608        if (row < 0) row = 0;
2609        if (row >= rows) row = rows - 1;
2610        if (col < 0) col = 0;
2611        if (col >= columns) col = columns - 1;
2612
2613        //----- 1.  Try the standard terminal ESC sequence
2614        sprintf(cm1, CMrc, row + 1, col + 1);
2615        cm = cm1;
2616
2617#if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2618        if (optimize && col < 16) {
2619                char *screenp;
2620                int Rrow = last_row;
2621                int diff = Rrow - row;
2622
2623                if (diff < -5 || diff > 5)
2624                        goto skip;
2625
2626                //----- find the minimum # of chars to move cursor -------------
2627                //----- 2.  Try moving with discreet chars (Newline, [back]space, ...)
2628                cm2[0] = '\0';
2629
2630                // move to the correct row
2631                while (row < Rrow) {
2632                        // the cursor has to move up
2633                        strcat(cm2, CMup);
2634                        Rrow--;
2635                }
2636                while (row > Rrow) {
2637                        // the cursor has to move down
2638                        strcat(cm2, CMdown);
2639                        Rrow++;
2640                }
2641
2642                // now move to the correct column
2643                strcat(cm2, "\r");                      // start at col 0
2644                // just send out orignal source char to get to correct place
2645                screenp = &screen[row * columns];       // start of screen line
2646                strncat(cm2, screenp, col);
2647
2648                // pick the shortest cursor motion to send out
2649                if (strlen(cm2) < strlen(cm)) {
2650                        cm = cm2;
2651                }
2652 skip: ;
2653        }
2654        last_row = row;
2655#endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2656        write1(cm);
2657}
2658
2659//----- Erase from cursor to end of line -----------------------
2660static void clear_to_eol(void)
2661{
2662        write1(Ceol);   // Erase from cursor to end of line
2663}
2664
2665static void go_bottom_and_clear_to_eol(void)
2666{
2667        place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2668        clear_to_eol(); // erase to end of line
2669}
2670
2671//----- Erase from cursor to end of screen -----------------------
2672static void clear_to_eos(void)
2673{
2674        write1(Ceos);   // Erase from cursor to end of screen
2675}
2676
2677//----- Start standout mode ------------------------------------
2678static void standout_start(void) // send "start reverse video" sequence
2679{
2680        write1(SOs);     // Start reverse video mode
2681}
2682
2683//----- End standout mode --------------------------------------
2684static void standout_end(void) // send "end reverse video" sequence
2685{
2686        write1(SOn);     // End reverse video mode
2687}
2688
2689//----- Flash the screen  --------------------------------------
2690static void flash(int h)
2691{
2692        standout_start();       // send "start reverse video" sequence
2693        redraw(TRUE);
2694        mysleep(h);
2695        standout_end();         // send "end reverse video" sequence
2696        redraw(TRUE);
2697}
2698
2699static void Indicate_Error(void)
2700{
2701#if ENABLE_FEATURE_VI_CRASHME
2702        if (crashme > 0)
2703                return;                 // generate a random command
2704#endif
2705        if (!err_method) {
2706                write1(bell);   // send out a bell character
2707        } else {
2708                flash(10);
2709        }
2710}
2711
2712//----- Screen[] Routines --------------------------------------
2713//----- Erase the Screen[] memory ------------------------------
2714static void screen_erase(void)
2715{
2716        memset(screen, ' ', screensize);        // clear new screen
2717}
2718
2719static int bufsum(char *buf, int count)
2720{
2721        int sum = 0;
2722        char *e = buf + count;
2723
2724        while (buf < e)
2725                sum += (unsigned char) *buf++;
2726        return sum;
2727}
2728
2729//----- Draw the status line at bottom of the screen -------------
2730static void show_status_line(void)
2731{
2732        int cnt = 0, cksum = 0;
2733
2734        // either we already have an error or status message, or we
2735        // create one.
2736        if (!have_status_msg) {
2737                cnt = format_edit_status();
2738                cksum = bufsum(status_buffer, cnt);
2739        }
2740        if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2741                last_status_cksum = cksum;              // remember if we have seen this line
2742                go_bottom_and_clear_to_eol();
2743                write1(status_buffer);
2744                if (have_status_msg) {
2745                        if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2746                                        (columns - 1) ) {
2747                                have_status_msg = 0;
2748                                Hit_Return();
2749                        }
2750                        have_status_msg = 0;
2751                }
2752                place_cursor(crow, ccol, FALSE);        // put cursor back in correct place
2753        }
2754        fflush_all();
2755}
2756
2757//----- format the status buffer, the bottom line of screen ------
2758// format status buffer, with STANDOUT mode
2759static void status_line_bold(const char *format, ...)
2760{
2761        va_list args;
2762
2763        va_start(args, format);
2764        strcpy(status_buffer, SOs);     // Terminal standout mode on
2765        vsprintf(status_buffer + sizeof(SOs)-1, format, args);
2766        strcat(status_buffer, SOn);     // Terminal standout mode off
2767        va_end(args);
2768
2769        have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2770}
2771
2772// format status buffer
2773static void status_line(const char *format, ...)
2774{
2775        va_list args;
2776
2777        va_start(args, format);
2778        vsprintf(status_buffer, format, args);
2779        va_end(args);
2780
2781        have_status_msg = 1;
2782}
2783
2784// copy s to buf, convert unprintable
2785static void print_literal(char *buf, const char *s)
2786{
2787        char *d;
2788        unsigned char c;
2789
2790        buf[0] = '\0';
2791        if (!s[0])
2792                s = "(NULL)";
2793
2794        d = buf;
2795        for (; *s; s++) {
2796                int c_is_no_print;
2797
2798                c = *s;
2799                c_is_no_print = (c & 0x80) && !Isprint(c);
2800                if (c_is_no_print) {
2801                        strcpy(d, SOn);
2802                        d += sizeof(SOn)-1;
2803                        c = '.';
2804                }
2805                if (c < ' ' || c == 0x7f) {
2806                        *d++ = '^';
2807                        c |= '@'; /* 0x40 */
2808                        if (c == 0x7f)
2809                                c = '?';
2810                }
2811                *d++ = c;
2812                *d = '\0';
2813                if (c_is_no_print) {
2814                        strcpy(d, SOs);
2815                        d += sizeof(SOs)-1;
2816                }
2817                if (*s == '\n') {
2818                        *d++ = '$';
2819                        *d = '\0';
2820                }
2821                if (d - buf > MAX_INPUT_LEN - 10) // paranoia
2822                        break;
2823        }
2824}
2825
2826static void not_implemented(const char *s)
2827{
2828        char buf[MAX_INPUT_LEN];
2829
2830        print_literal(buf, s);
2831        status_line_bold("\'%s\' is not implemented", buf);
2832}
2833
2834// show file status on status line
2835static int format_edit_status(void)
2836{
2837        static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2838
2839#define tot format_edit_status__tot
2840
2841        int cur, percent, ret, trunc_at;
2842
2843        // file_modified is now a counter rather than a flag.  this
2844        // helps reduce the amount of line counting we need to do.
2845        // (this will cause a mis-reporting of modified status
2846        // once every MAXINT editing operations.)
2847
2848        // it would be nice to do a similar optimization here -- if
2849        // we haven't done a motion that could have changed which line
2850        // we're on, then we shouldn't have to do this count_lines()
2851        cur = count_lines(text, dot);
2852
2853        // reduce counting -- the total lines can't have
2854        // changed if we haven't done any edits.
2855        if (file_modified != last_file_modified) {
2856                tot = cur + count_lines(dot, end - 1) - 1;
2857                last_file_modified = file_modified;
2858        }
2859
2860        //    current line         percent
2861        //   -------------    ~~ ----------
2862        //    total lines            100
2863        if (tot > 0) {
2864                percent = (100 * cur) / tot;
2865        } else {
2866                cur = tot = 0;
2867                percent = 100;
2868        }
2869
2870        trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2871                columns : STATUS_BUFFER_LEN-1;
2872
2873        ret = snprintf(status_buffer, trunc_at+1,
2874#if ENABLE_FEATURE_VI_READONLY
2875                "%c %s%s%s %d/%d %d%%",
2876#else
2877                "%c %s%s %d/%d %d%%",
2878#endif
2879                cmd_mode_indicator[cmd_mode & 3],
2880                (current_filename != NULL ? current_filename : "No file"),
2881#if ENABLE_FEATURE_VI_READONLY
2882                (readonly_mode ? " [Readonly]" : ""),
2883#endif
2884                (file_modified ? " [Modified]" : ""),
2885                cur, tot, percent);
2886
2887        if (ret >= 0 && ret < trunc_at)
2888                return ret;  /* it all fit */
2889
2890        return trunc_at;  /* had to truncate */
2891#undef tot
2892}
2893
2894//----- Force refresh of all Lines -----------------------------
2895static void redraw(int full_screen)
2896{
2897        place_cursor(0, 0, FALSE);      // put cursor in correct place
2898        clear_to_eos();         // tell terminal to erase display
2899        screen_erase();         // erase the internal screen buffer
2900        last_status_cksum = 0;  // force status update
2901        refresh(full_screen);   // this will redraw the entire display
2902        show_status_line();
2903}
2904
2905//----- Format a text[] line into a buffer ---------------------
2906static char* format_line(char *src /*, int li*/)
2907{
2908        unsigned char c;
2909        int co;
2910        int ofs = offset;
2911        char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2912
2913        c = '~'; // char in col 0 in non-existent lines is '~'
2914        co = 0;
2915        while (co < columns + tabstop) {
2916                // have we gone past the end?
2917                if (src < end) {
2918                        c = *src++;
2919                        if (c == '\n')
2920                                break;
2921                        if ((c & 0x80) && !Isprint(c)) {
2922                                c = '.';
2923                        }
2924                        if (c < ' ' || c == 0x7f) {
2925                                if (c == '\t') {
2926                                        c = ' ';
2927                                        //      co %    8     !=     7
2928                                        while ((co % tabstop) != (tabstop - 1)) {
2929                                                dest[co++] = c;
2930                                        }
2931                                } else {
2932                                        dest[co++] = '^';
2933                                        if (c == 0x7f)
2934                                                c = '?';
2935                                        else
2936                                                c += '@'; // Ctrl-X -> 'X'
2937                                }
2938                        }
2939                }
2940                dest[co++] = c;
2941                // discard scrolled-off-to-the-left portion,
2942                // in tabstop-sized pieces
2943                if (ofs >= tabstop && co >= tabstop) {
2944                        memmove(dest, dest + tabstop, co);
2945                        co -= tabstop;
2946                        ofs -= tabstop;
2947                }
2948                if (src >= end)
2949                        break;
2950        }
2951        // check "short line, gigantic offset" case
2952        if (co < ofs)
2953                ofs = co;
2954        // discard last scrolled off part
2955        co -= ofs;
2956        dest += ofs;
2957        // fill the rest with spaces
2958        if (co < columns)
2959                memset(&dest[co], ' ', columns - co);
2960        return dest;
2961}
2962
2963//----- Refresh the changed screen lines -----------------------
2964// Copy the source line from text[] into the buffer and note
2965// if the current screenline is different from the new buffer.
2966// If they differ then that line needs redrawing on the terminal.
2967//
2968static void refresh(int full_screen)
2969{
2970#define old_offset refresh__old_offset
2971
2972        int li, changed;
2973        char *tp, *sp;          // pointer into text[] and screen[]
2974
2975        if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
2976                unsigned c = columns, r = rows;
2977                query_screen_dimensions();
2978                full_screen |= (c - columns) | (r - rows);
2979        }
2980        sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2981        tp = screenbegin;       // index into text[] of top line
2982
2983        // compare text[] to screen[] and mark screen[] lines that need updating
2984        for (li = 0; li < rows - 1; li++) {
2985                int cs, ce;                             // column start & end
2986                char *out_buf;
2987                // format current text line
2988                out_buf = format_line(tp /*, li*/);
2989
2990                // skip to the end of the current text[] line
2991                if (tp < end) {
2992                        char *t = memchr(tp, '\n', end - tp);
2993                        if (!t) t = end - 1;
2994                        tp = t + 1;
2995                }
2996
2997                // see if there are any changes between vitual screen and out_buf
2998                changed = FALSE;        // assume no change
2999                cs = 0;
3000                ce = columns - 1;
3001                sp = &screen[li * columns];     // start of screen line
3002                if (full_screen) {
3003                        // force re-draw of every single column from 0 - columns-1
3004                        goto re0;
3005                }
3006                // compare newly formatted buffer with virtual screen
3007                // look forward for first difference between buf and screen
3008                for (; cs <= ce; cs++) {
3009                        if (out_buf[cs] != sp[cs]) {
3010                                changed = TRUE; // mark for redraw
3011                                break;
3012                        }
3013                }
3014
3015                // look backward for last difference between out_buf and screen
3016                for (; ce >= cs; ce--) {
3017                        if (out_buf[ce] != sp[ce]) {
3018                                changed = TRUE; // mark for redraw
3019                                break;
3020                        }
3021                }
3022                // now, cs is index of first diff, and ce is index of last diff
3023
3024                // if horz offset has changed, force a redraw
3025                if (offset != old_offset) {
3026 re0:
3027                        changed = TRUE;
3028                }
3029
3030                // make a sanity check of columns indexes
3031                if (cs < 0) cs = 0;
3032                if (ce > columns - 1) ce = columns - 1;
3033                if (cs > ce) { cs = 0; ce = columns - 1; }
3034                // is there a change between vitual screen and out_buf
3035                if (changed) {
3036                        // copy changed part of buffer to virtual screen
3037                        memcpy(sp+cs, out_buf+cs, ce-cs+1);
3038
3039                        // move cursor to column of first change
3040                        //if (offset != old_offset) {
3041                        //      // place_cursor is still too stupid
3042                        //      // to handle offsets correctly
3043                        //      place_cursor(li, cs, FALSE);
3044                        //} else {
3045                                place_cursor(li, cs, TRUE);
3046                        //}
3047
3048                        // write line out to terminal
3049                        fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3050                }
3051        }
3052
3053        place_cursor(crow, ccol, TRUE);
3054
3055        old_offset = offset;
3056#undef old_offset
3057}
3058
3059//---------------------------------------------------------------------
3060//----- the Ascii Chart -----------------------------------------------
3061//
3062//  00 nul   01 soh   02 stx   03 etx   04 eot   05 enq   06 ack   07 bel
3063//  08 bs    09 ht    0a nl    0b vt    0c np    0d cr    0e so    0f si
3064//  10 dle   11 dc1   12 dc2   13 dc3   14 dc4   15 nak   16 syn   17 etb
3065//  18 can   19 em    1a sub   1b esc   1c fs    1d gs    1e rs    1f us
3066//  20 sp    21 !     22 "     23 #     24 $     25 %     26 &     27 '
3067//  28 (     29 )     2a *     2b +     2c ,     2d -     2e .     2f /
3068//  30 0     31 1     32 2     33 3     34 4     35 5     36 6     37 7
3069//  38 8     39 9     3a :     3b ;     3c <     3d =     3e >     3f ?
3070//  40 @     41 A     42 B     43 C     44 D     45 E     46 F     47 G
3071//  48 H     49 I     4a J     4b K     4c L     4d M     4e N     4f O
3072//  50 P     51 Q     52 R     53 S     54 T     55 U     56 V     57 W
3073//  58 X     59 Y     5a Z     5b [     5c \     5d ]     5e ^     5f _
3074//  60 `     61 a     62 b     63 c     64 d     65 e     66 f     67 g
3075//  68 h     69 i     6a j     6b k     6c l     6d m     6e n     6f o
3076//  70 p     71 q     72 r     73 s     74 t     75 u     76 v     77 w
3077//  78 x     79 y     7a z     7b {     7c |     7d }     7e ~     7f del
3078//---------------------------------------------------------------------
3079
3080//----- Execute a Vi Command -----------------------------------
3081static void do_cmd(int c)
3082{
3083        char *p, *q, *save_dot;
3084        char buf[12];
3085        int dir;
3086        int cnt, i, j;
3087        int c1;
3088
3089//      c1 = c; // quiet the compiler
3090//      cnt = yf = 0; // quiet the compiler
3091//      p = q = save_dot = buf; // quiet the compiler
3092        memset(buf, '\0', sizeof(buf));
3093
3094        show_status_line();
3095
3096        /* if this is a cursor key, skip these checks */
3097        switch (c) {
3098                case KEYCODE_UP:
3099                case KEYCODE_DOWN:
3100                case KEYCODE_LEFT:
3101                case KEYCODE_RIGHT:
3102                case KEYCODE_HOME:
3103                case KEYCODE_END:
3104                case KEYCODE_PAGEUP:
3105                case KEYCODE_PAGEDOWN:
3106                case KEYCODE_DELETE:
3107                        goto key_cmd_mode;
3108        }
3109
3110        if (cmd_mode == 2) {
3111                //  flip-flop Insert/Replace mode
3112                if (c == KEYCODE_INSERT)
3113                        goto dc_i;
3114                // we are 'R'eplacing the current *dot with new char
3115                if (*dot == '\n') {
3116                        // don't Replace past E-o-l
3117                        cmd_mode = 1;   // convert to insert
3118                } else {
3119                        if (1 <= c || Isprint(c)) {
3120                                if (c != 27)
3121                                        dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
3122                                dot = char_insert(dot, c);      // insert new char
3123                        }
3124                        goto dc1;
3125                }
3126        }
3127        if (cmd_mode == 1) {
3128                //  hitting "Insert" twice means "R" replace mode
3129                if (c == KEYCODE_INSERT) goto dc5;
3130                // insert the char c at "dot"
3131                if (1 <= c || Isprint(c)) {
3132                        dot = char_insert(dot, c);
3133                }
3134                goto dc1;
3135        }
3136
3137 key_cmd_mode:
3138        switch (c) {
3139                //case 0x01:    // soh
3140                //case 0x09:    // ht
3141                //case 0x0b:    // vt
3142                //case 0x0e:    // so
3143                //case 0x0f:    // si
3144                //case 0x10:    // dle
3145                //case 0x11:    // dc1
3146                //case 0x13:    // dc3
3147#if ENABLE_FEATURE_VI_CRASHME
3148        case 0x14:                      // dc4  ctrl-T
3149                crashme = (crashme == 0) ? 1 : 0;
3150                break;
3151#endif
3152                //case 0x16:    // syn
3153                //case 0x17:    // etb
3154                //case 0x18:    // can
3155                //case 0x1c:    // fs
3156                //case 0x1d:    // gs
3157                //case 0x1e:    // rs
3158                //case 0x1f:    // us
3159                //case '!':     // !-
3160                //case '#':     // #-
3161                //case '&':     // &-
3162                //case '(':     // (-
3163                //case ')':     // )-
3164                //case '*':     // *-
3165                //case '=':     // =-
3166                //case '@':     // @-
3167                //case 'F':     // F-
3168                //case 'K':     // K-
3169                //case 'Q':     // Q-
3170                //case 'S':     // S-
3171                //case 'T':     // T-
3172                //case 'V':     // V-
3173                //case '[':     // [-
3174                //case '\\':    // \-
3175                //case ']':     // ]-
3176                //case '_':     // _-
3177                //case '`':     // `-
3178                //case 'u':     // u- FIXME- there is no undo
3179                //case 'v':     // v-
3180        default:                        // unrecognized command
3181                buf[0] = c;
3182                buf[1] = '\0';
3183                not_implemented(buf);
3184                end_cmd_q();    // stop adding to q
3185        case 0x00:                      // nul- ignore
3186                break;
3187        case 2:                 // ctrl-B  scroll up   full screen
3188        case KEYCODE_PAGEUP:    // Cursor Key Page Up
3189                dot_scroll(rows - 2, -1);
3190                break;
3191        case 4:                 // ctrl-D  scroll down half screen
3192                dot_scroll((rows - 2) / 2, 1);
3193                break;
3194        case 5:                 // ctrl-E  scroll down one line
3195                dot_scroll(1, 1);
3196                break;
3197        case 6:                 // ctrl-F  scroll down full screen
3198        case KEYCODE_PAGEDOWN:  // Cursor Key Page Down
3199                dot_scroll(rows - 2, 1);
3200                break;
3201        case 7:                 // ctrl-G  show current status
3202                last_status_cksum = 0;  // force status update
3203                break;
3204        case 'h':                       // h- move left
3205        case KEYCODE_LEFT:      // cursor key Left
3206        case 8:         // ctrl-H- move left    (This may be ERASE char)
3207        case 0x7f:      // DEL- move left   (This may be ERASE char)
3208                do {
3209                        dot_left();
3210                } while (--cmdcnt > 0);
3211                break;
3212        case 10:                        // Newline ^J
3213        case 'j':                       // j- goto next line, same col
3214        case KEYCODE_DOWN:      // cursor key Down
3215                do {
3216                        dot_next();             // go to next B-o-l
3217                        // try stay in same col
3218                        dot = move_to_col(dot, ccol + offset);
3219                } while (--cmdcnt > 0);
3220                break;
3221        case 12:                        // ctrl-L  force redraw whole screen
3222        case 18:                        // ctrl-R  force redraw
3223                place_cursor(0, 0, FALSE);      // put cursor in correct place
3224                clear_to_eos(); // tel terminal to erase display
3225                mysleep(10);
3226                screen_erase(); // erase the internal screen buffer
3227                last_status_cksum = 0;  // force status update
3228                refresh(TRUE);  // this will redraw the entire display
3229                break;
3230        case 13:                        // Carriage Return ^M
3231        case '+':                       // +- goto next line
3232                do {
3233                        dot_next();
3234                        dot_skip_over_ws();
3235                } while (--cmdcnt > 0);
3236                break;
3237        case 21:                        // ctrl-U  scroll up   half screen
3238                dot_scroll((rows - 2) / 2, -1);
3239                break;
3240        case 25:                        // ctrl-Y  scroll up one line
3241                dot_scroll(1, -1);
3242                break;
3243        case 27:                        // esc
3244                if (cmd_mode == 0)
3245                        indicate_error(c);
3246                cmd_mode = 0;   // stop insrting
3247                end_cmd_q();
3248                last_status_cksum = 0;  // force status update
3249                break;
3250        case ' ':                       // move right
3251        case 'l':                       // move right
3252        case KEYCODE_RIGHT:     // Cursor Key Right
3253                do {
3254                        dot_right();
3255                } while (--cmdcnt > 0);
3256                break;
3257#if ENABLE_FEATURE_VI_YANKMARK
3258        case '"':                       // "- name a register to use for Delete/Yank
3259                c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3260                if ((unsigned)c1 <= 25) { // a-z?
3261                        YDreg = c1;
3262                } else {
3263                        indicate_error(c);
3264                }
3265                break;
3266        case '\'':                      // '- goto a specific mark
3267                c1 = (get_one_char() | 0x20) - 'a';
3268                if ((unsigned)c1 <= 25) { // a-z?
3269                        // get the b-o-l
3270                        q = mark[c1];
3271                        if (text <= q && q < end) {
3272                                dot = q;
3273                                dot_begin();    // go to B-o-l
3274                                dot_skip_over_ws();
3275                        }
3276                } else if (c1 == '\'') {        // goto previous context
3277                        dot = swap_context(dot);        // swap current and previous context
3278                        dot_begin();    // go to B-o-l
3279                        dot_skip_over_ws();
3280                } else {
3281                        indicate_error(c);
3282                }
3283                break;
3284        case 'm':                       // m- Mark a line
3285                // this is really stupid.  If there are any inserts or deletes
3286                // between text[0] and dot then this mark will not point to the
3287                // correct location! It could be off by many lines!
3288                // Well..., at least its quick and dirty.
3289                c1 = (get_one_char() | 0x20) - 'a';
3290                if ((unsigned)c1 <= 25) { // a-z?
3291                        // remember the line
3292                        mark[c1] = dot;
3293                } else {
3294                        indicate_error(c);
3295                }
3296                break;
3297        case 'P':                       // P- Put register before
3298        case 'p':                       // p- put register after
3299                p = reg[YDreg];
3300                if (p == NULL) {
3301                        status_line_bold("Nothing in register %c", what_reg());
3302                        break;
3303                }
3304                // are we putting whole lines or strings
3305                if (strchr(p, '\n') != NULL) {
3306                        if (c == 'P') {
3307                                dot_begin();    // putting lines- Put above
3308                        }
3309                        if (c == 'p') {
3310                                // are we putting after very last line?
3311                                if (end_line(dot) == (end - 1)) {
3312                                        dot = end;      // force dot to end of text[]
3313                                } else {
3314                                        dot_next();     // next line, then put before
3315                                }
3316                        }
3317                } else {
3318                        if (c == 'p')
3319                                dot_right();    // move to right, can move to NL
3320                }
3321                string_insert(dot, p);  // insert the string
3322                end_cmd_q();    // stop adding to q
3323                break;
3324        case 'U':                       // U- Undo; replace current line with original version
3325                if (reg[Ureg] != NULL) {
3326                        p = begin_line(dot);
3327                        q = end_line(dot);
3328                        p = text_hole_delete(p, q);     // delete cur line
3329                        p += string_insert(p, reg[Ureg]);       // insert orig line
3330                        dot = p;
3331                        dot_skip_over_ws();
3332                }
3333                break;
3334#endif /* FEATURE_VI_YANKMARK */
3335        case '$':                       // $- goto end of line
3336        case KEYCODE_END:               // Cursor Key End
3337                for (;;) {
3338                        dot = end_line(dot);
3339                        if (--cmdcnt <= 0)
3340                                break;
3341                        dot_next();
3342                }
3343                break;
3344        case '%':                       // %- find matching char of pair () [] {}
3345                for (q = dot; q < end && *q != '\n'; q++) {
3346                        if (strchr("()[]{}", *q) != NULL) {
3347                                // we found half of a pair
3348                                p = find_pair(q, *q);
3349                                if (p == NULL) {
3350                                        indicate_error(c);
3351                                } else {
3352                                        dot = p;
3353                                }
3354                                break;
3355                        }
3356                }
3357                if (*q == '\n')
3358                        indicate_error(c);
3359                break;
3360        case 'f':                       // f- forward to a user specified char
3361                last_forward_char = get_one_char();     // get the search char
3362                //
3363                // dont separate these two commands. 'f' depends on ';'
3364                //
3365                //**** fall through to ... ';'
3366        case ';':                       // ;- look at rest of line for last forward char
3367                do {
3368                        if (last_forward_char == 0)
3369                                break;
3370                        q = dot + 1;
3371                        while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3372                                q++;
3373                        }
3374                        if (*q == last_forward_char)
3375                                dot = q;
3376                } while (--cmdcnt > 0);
3377                break;
3378        case ',':           // repeat latest 'f' in opposite direction
3379                if (last_forward_char == 0)
3380                        break;
3381                do {
3382                        q = dot - 1;
3383                        while (q >= text && *q != '\n' && *q != last_forward_char) {
3384                                q--;
3385                        }
3386                        if (q >= text && *q == last_forward_char)
3387                                dot = q;
3388                } while (--cmdcnt > 0);
3389                break;
3390
3391        case '-':                       // -- goto prev line
3392                do {
3393                        dot_prev();
3394                        dot_skip_over_ws();
3395                } while (--cmdcnt > 0);
3396                break;
3397#if ENABLE_FEATURE_VI_DOT_CMD
3398        case '.':                       // .- repeat the last modifying command
3399                // Stuff the last_modifying_cmd back into stdin
3400                // and let it be re-executed.
3401                if (lmc_len > 0) {
3402                        last_modifying_cmd[lmc_len] = 0;
3403                        ioq = ioq_start = xstrdup(last_modifying_cmd);
3404                }
3405                break;
3406#endif
3407#if ENABLE_FEATURE_VI_SEARCH
3408        case '?':                       // /- search for a pattern
3409        case '/':                       // /- search for a pattern
3410                buf[0] = c;
3411                buf[1] = '\0';
3412                q = get_input_line(buf);        // get input line- use "status line"
3413                if (q[0] && !q[1]) {
3414                        if (last_search_pattern[0])
3415                                last_search_pattern[0] = c;
3416                        goto dc3; // if no pat re-use old pat
3417                }
3418                if (q[0]) {       // strlen(q) > 1: new pat- save it and find
3419                        // there is a new pat
3420                        free(last_search_pattern);
3421                        last_search_pattern = xstrdup(q);
3422                        goto dc3;       // now find the pattern
3423                }
3424                // user changed mind and erased the "/"-  do nothing
3425                break;
3426        case 'N':                       // N- backward search for last pattern
3427                dir = BACK;             // assume BACKWARD search
3428                p = dot - 1;
3429                if (last_search_pattern[0] == '?') {
3430                        dir = FORWARD;
3431                        p = dot + 1;
3432                }
3433                goto dc4;               // now search for pattern
3434                break;
3435        case 'n':                       // n- repeat search for last pattern
3436                // search rest of text[] starting at next char
3437                // if search fails return orignal "p" not the "p+1" address
3438                do {
3439                        const char *msg;
3440 dc3:
3441                        dir = FORWARD;  // assume FORWARD search
3442                        p = dot + 1;
3443                        if (last_search_pattern[0] == '?') {
3444                                dir = BACK;
3445                                p = dot - 1;
3446                        }
3447 dc4:
3448                        q = char_search(p, last_search_pattern + 1, dir, FULL);
3449                        if (q != NULL) {
3450                                dot = q;        // good search, update "dot"
3451                                msg = NULL;
3452                                goto dc2;
3453                        }
3454                        // no pattern found between "dot" and "end"- continue at top
3455                        p = text;
3456                        if (dir == BACK) {
3457                                p = end - 1;
3458                        }
3459                        q = char_search(p, last_search_pattern + 1, dir, FULL);
3460                        if (q != NULL) {        // found something
3461                                dot = q;        // found new pattern- goto it
3462                                msg = "search hit BOTTOM, continuing at TOP";
3463                                if (dir == BACK) {
3464                                        msg = "search hit TOP, continuing at BOTTOM";
3465                                }
3466                        } else {
3467                                msg = "Pattern not found";
3468                        }
3469 dc2:
3470                        if (msg)
3471                                status_line_bold("%s", msg);
3472                } while (--cmdcnt > 0);
3473                break;
3474        case '{':                       // {- move backward paragraph
3475                q = char_search(dot, "\n\n", BACK, FULL);
3476                if (q != NULL) {        // found blank line
3477                        dot = next_line(q);     // move to next blank line
3478                }
3479                break;
3480        case '}':                       // }- move forward paragraph
3481                q = char_search(dot, "\n\n", FORWARD, FULL);
3482                if (q != NULL) {        // found blank line
3483                        dot = next_line(q);     // move to next blank line
3484                }
3485                break;
3486#endif /* FEATURE_VI_SEARCH */
3487        case '0':                       // 0- goto begining of line
3488        case '1':                       // 1-
3489        case '2':                       // 2-
3490        case '3':                       // 3-
3491        case '4':                       // 4-
3492        case '5':                       // 5-
3493        case '6':                       // 6-
3494        case '7':                       // 7-
3495        case '8':                       // 8-
3496        case '9':                       // 9-
3497                if (c == '0' && cmdcnt < 1) {
3498                        dot_begin();    // this was a standalone zero
3499                } else {
3500                        cmdcnt = cmdcnt * 10 + (c - '0');       // this 0 is part of a number
3501                }
3502                break;
3503        case ':':                       // :- the colon mode commands
3504                p = get_input_line(":");        // get input line- use "status line"
3505#if ENABLE_FEATURE_VI_COLON
3506                colon(p);               // execute the command
3507#else
3508                if (*p == ':')
3509                        p++;                            // move past the ':'
3510                cnt = strlen(p);
3511                if (cnt <= 0)
3512                        break;
3513                if (strncmp(p, "quit", cnt) == 0
3514                 || strncmp(p, "q!", cnt) == 0   // delete lines
3515                ) {
3516                        if (file_modified && p[1] != '!') {
3517                                status_line_bold("No write since last change (:%s! overrides)", p);
3518                        } else {
3519                                editing = 0;
3520                        }
3521                } else if (strncmp(p, "write", cnt) == 0
3522                        || strncmp(p, "wq", cnt) == 0
3523                        || strncmp(p, "wn", cnt) == 0
3524                        || (p[0] == 'x' && !p[1])
3525                ) {
3526                        cnt = file_write(current_filename, text, end - 1);
3527                        if (cnt < 0) {
3528                                if (cnt == -1)
3529                                        status_line_bold("Write error: %s", strerror(errno));
3530                        } else {
3531                                file_modified = 0;
3532                                last_file_modified = -1;
3533                                status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3534                                if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3535                                 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3536                                ) {
3537                                        editing = 0;
3538                                }
3539                        }
3540                } else if (strncmp(p, "file", cnt) == 0) {
3541                        last_status_cksum = 0;  // force status update
3542                } else if (sscanf(p, "%d", &j) > 0) {
3543                        dot = find_line(j);             // go to line # j
3544                        dot_skip_over_ws();
3545                } else {                // unrecognized cmd
3546                        not_implemented(p);
3547                }
3548#endif /* !FEATURE_VI_COLON */
3549                break;
3550        case '<':                       // <- Left  shift something
3551        case '>':                       // >- Right shift something
3552                cnt = count_lines(text, dot);   // remember what line we are on
3553                c1 = get_one_char();    // get the type of thing to delete
3554                find_range(&p, &q, c1);
3555                yank_delete(p, q, 1, YANKONLY); // save copy before change
3556                p = begin_line(p);
3557                q = end_line(q);
3558                i = count_lines(p, q);  // # of lines we are shifting
3559                for ( ; i > 0; i--, p = next_line(p)) {
3560                        if (c == '<') {
3561                                // shift left- remove tab or 8 spaces
3562                                if (*p == '\t') {
3563                                        // shrink buffer 1 char
3564                                        text_hole_delete(p, p);
3565                                } else if (*p == ' ') {
3566                                        // we should be calculating columns, not just SPACE
3567                                        for (j = 0; *p == ' ' && j < tabstop; j++) {
3568                                                text_hole_delete(p, p);
3569                                        }
3570                                }
3571                        } else if (c == '>') {
3572                                // shift right -- add tab or 8 spaces
3573                                char_insert(p, '\t');
3574                        }
3575                }
3576                dot = find_line(cnt);   // what line were we on
3577                dot_skip_over_ws();
3578                end_cmd_q();    // stop adding to q
3579                break;
3580        case 'A':                       // A- append at e-o-l
3581                dot_end();              // go to e-o-l
3582                //**** fall through to ... 'a'
3583        case 'a':                       // a- append after current char
3584                if (*dot != '\n')
3585                        dot++;
3586                goto dc_i;
3587                break;
3588        case 'B':                       // B- back a blank-delimited Word
3589        case 'E':                       // E- end of a blank-delimited word
3590        case 'W':                       // W- forward a blank-delimited word
3591                dir = FORWARD;
3592                if (c == 'B')
3593                        dir = BACK;
3594                do {
3595                        if (c == 'W' || isspace(dot[dir])) {
3596                                dot = skip_thing(dot, 1, dir, S_TO_WS);
3597                                dot = skip_thing(dot, 2, dir, S_OVER_WS);
3598                        }
3599                        if (c != 'W')
3600                                dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3601                } while (--cmdcnt > 0);
3602                break;
3603        case 'C':                       // C- Change to e-o-l
3604        case 'D':                       // D- delete to e-o-l
3605                save_dot = dot;
3606                dot = dollar_line(dot); // move to before NL
3607                // copy text into a register and delete
3608                dot = yank_delete(save_dot, dot, 0, YANKDEL);   // delete to e-o-l
3609                if (c == 'C')
3610                        goto dc_i;      // start inserting
3611#if ENABLE_FEATURE_VI_DOT_CMD
3612                if (c == 'D')
3613                        end_cmd_q();    // stop adding to q
3614#endif
3615                break;
3616        case 'g': // 'gg' goto a line number (vim) (default: very first line)
3617                c1 = get_one_char();
3618                if (c1 != 'g') {
3619                        buf[0] = 'g';
3620                        buf[1] = c1; // TODO: if Unicode?
3621                        buf[2] = '\0';
3622                        not_implemented(buf);
3623                        break;
3624                }
3625                if (cmdcnt == 0)
3626                        cmdcnt = 1;
3627                /* fall through */
3628        case 'G':               // G- goto to a line number (default= E-O-F)
3629                dot = end - 1;                          // assume E-O-F
3630                if (cmdcnt > 0) {
3631                        dot = find_line(cmdcnt);        // what line is #cmdcnt
3632                }
3633                dot_skip_over_ws();
3634                break;
3635        case 'H':                       // H- goto top line on screen
3636                dot = screenbegin;
3637                if (cmdcnt > (rows - 1)) {
3638                        cmdcnt = (rows - 1);
3639                }
3640                if (--cmdcnt > 0) {
3641                        do_cmd('+');
3642                }
3643                dot_skip_over_ws();
3644                break;
3645        case 'I':                       // I- insert before first non-blank
3646                dot_begin();    // 0
3647                dot_skip_over_ws();
3648                //**** fall through to ... 'i'
3649        case 'i':                       // i- insert before current char
3650        case KEYCODE_INSERT:    // Cursor Key Insert
3651 dc_i:
3652                cmd_mode = 1;   // start inserting
3653                break;
3654        case 'J':                       // J- join current and next lines together
3655                do {
3656                        dot_end();              // move to NL
3657                        if (dot < end - 1) {    // make sure not last char in text[]
3658                                *dot++ = ' ';   // replace NL with space
3659                                file_modified++;
3660                                while (isblank(*dot)) { // delete leading WS
3661                                        dot_delete();
3662                                }
3663                        }
3664                } while (--cmdcnt > 0);
3665                end_cmd_q();    // stop adding to q
3666                break;
3667        case 'L':                       // L- goto bottom line on screen
3668                dot = end_screen();
3669                if (cmdcnt > (rows - 1)) {
3670                        cmdcnt = (rows - 1);
3671                }
3672                if (--cmdcnt > 0) {
3673                        do_cmd('-');
3674                }
3675                dot_begin();
3676                dot_skip_over_ws();
3677                break;
3678        case 'M':                       // M- goto middle line on screen
3679                dot = screenbegin;
3680                for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3681                        dot = next_line(dot);
3682                break;
3683        case 'O':                       // O- open a empty line above
3684                //    0i\n ESC -i
3685                p = begin_line(dot);
3686                if (p[-1] == '\n') {
3687                        dot_prev();
3688        case 'o':                       // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3689                        dot_end();
3690                        dot = char_insert(dot, '\n');
3691                } else {
3692                        dot_begin();    // 0
3693                        dot = char_insert(dot, '\n');   // i\n ESC
3694                        dot_prev();     // -
3695                }
3696                goto dc_i;
3697                break;
3698        case 'R':                       // R- continuous Replace char
3699 dc5:
3700                cmd_mode = 2;
3701                break;
3702        case KEYCODE_DELETE:
3703                c = 'x';
3704                // fall through
3705        case 'X':                       // X- delete char before dot
3706        case 'x':                       // x- delete the current char
3707        case 's':                       // s- substitute the current char
3708                dir = 0;
3709                if (c == 'X')
3710                        dir = -1;
3711                do {
3712                        if (dot[dir] != '\n') {
3713                                if (c == 'X')
3714                                        dot--;  // delete prev char
3715                                dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
3716                        }
3717                } while (--cmdcnt > 0);
3718                end_cmd_q();    // stop adding to q
3719                if (c == 's')
3720                        goto dc_i;      // start inserting
3721                break;
3722        case 'Z':                       // Z- if modified, {write}; exit
3723                // ZZ means to save file (if necessary), then exit
3724                c1 = get_one_char();
3725                if (c1 != 'Z') {
3726                        indicate_error(c);
3727                        break;
3728                }
3729                if (file_modified) {
3730                        if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3731                                status_line_bold("\"%s\" File is read only", current_filename);
3732                                break;
3733                        }
3734                        cnt = file_write(current_filename, text, end - 1);
3735                        if (cnt < 0) {
3736                                if (cnt == -1)
3737                                        status_line_bold("Write error: %s", strerror(errno));
3738                        } else if (cnt == (end - 1 - text + 1)) {
3739                                editing = 0;
3740                        }
3741                } else {
3742                        editing = 0;
3743                }
3744                break;
3745        case '^':                       // ^- move to first non-blank on line
3746                dot_begin();
3747                dot_skip_over_ws();
3748                break;
3749        case 'b':                       // b- back a word
3750        case 'e':                       // e- end of word
3751                dir = FORWARD;
3752                if (c == 'b')
3753                        dir = BACK;
3754                do {
3755                        if ((dot + dir) < text || (dot + dir) > end - 1)
3756                                break;
3757                        dot += dir;
3758                        if (isspace(*dot)) {
3759                                dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3760                        }
3761                        if (isalnum(*dot) || *dot == '_') {
3762                                dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3763                        } else if (ispunct(*dot)) {
3764                                dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3765                        }
3766                } while (--cmdcnt > 0);
3767                break;
3768        case 'c':                       // c- change something
3769        case 'd':                       // d- delete something
3770#if ENABLE_FEATURE_VI_YANKMARK
3771        case 'y':                       // y- yank   something
3772        case 'Y':                       // Y- Yank a line
3773#endif
3774        {
3775                int yf, ml, whole = 0;
3776                yf = YANKDEL;   // assume either "c" or "d"
3777#if ENABLE_FEATURE_VI_YANKMARK
3778                if (c == 'y' || c == 'Y')
3779                        yf = YANKONLY;
3780#endif
3781                c1 = 'y';
3782                if (c != 'Y')
3783                        c1 = get_one_char();    // get the type of thing to delete
3784                // determine range, and whether it spans lines
3785                ml = find_range(&p, &q, c1);
3786                if (c1 == 27) { // ESC- user changed mind and wants out
3787                        c = c1 = 27;    // Escape- do nothing
3788                } else if (strchr("wW", c1)) {
3789                        if (c == 'c') {
3790                                // don't include trailing WS as part of word
3791                                while (isblank(*q)) {
3792                                        if (q <= text || q[-1] == '\n')
3793                                                break;
3794                                        q--;
3795                                }
3796                        }
3797                        dot = yank_delete(p, q, ml, yf);        // delete word
3798                } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3799                        // partial line copy text into a register and delete
3800                        dot = yank_delete(p, q, ml, yf);        // delete word
3801                } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3802                        // whole line copy text into a register and delete
3803                        dot = yank_delete(p, q, ml, yf);        // delete lines
3804                        whole = 1;
3805                } else {
3806                        // could not recognize object
3807                        c = c1 = 27;    // error-
3808                        ml = 0;
3809                        indicate_error(c);
3810                }
3811                if (ml && whole) {
3812                        if (c == 'c') {
3813                                dot = char_insert(dot, '\n');
3814                                // on the last line of file don't move to prev line
3815                                if (whole && dot != (end-1)) {
3816                                        dot_prev();
3817                                }
3818                        } else if (c == 'd') {
3819                                dot_begin();
3820                                dot_skip_over_ws();
3821                        }
3822                }
3823                if (c1 != 27) {
3824                        // if CHANGING, not deleting, start inserting after the delete
3825                        if (c == 'c') {
3826                                strcpy(buf, "Change");
3827                                goto dc_i;      // start inserting
3828                        }
3829                        if (c == 'd') {
3830                                strcpy(buf, "Delete");
3831                        }
3832#if ENABLE_FEATURE_VI_YANKMARK
3833                        if (c == 'y' || c == 'Y') {
3834                                strcpy(buf, "Yank");
3835                        }
3836                        p = reg[YDreg];
3837                        q = p + strlen(p);
3838                        for (cnt = 0; p <= q; p++) {
3839                                if (*p == '\n')
3840                                        cnt++;
3841                        }
3842                        status_line("%s %d lines (%d chars) using [%c]",
3843                                buf, cnt, strlen(reg[YDreg]), what_reg());
3844#endif
3845                        end_cmd_q();    // stop adding to q
3846                }
3847                break;
3848        }
3849        case 'k':                       // k- goto prev line, same col
3850        case KEYCODE_UP:                // cursor key Up
3851                do {
3852                        dot_prev();
3853                        dot = move_to_col(dot, ccol + offset);  // try stay in same col
3854                } while (--cmdcnt > 0);
3855                break;
3856        case 'r':                       // r- replace the current char with user input
3857                c1 = get_one_char();    // get the replacement char
3858                if (*dot != '\n') {
3859                        *dot = c1;
3860                        file_modified++;
3861                }
3862                end_cmd_q();    // stop adding to q
3863                break;
3864        case 't':                       // t- move to char prior to next x
3865                last_forward_char = get_one_char();
3866                do_cmd(';');
3867                if (*dot == last_forward_char)
3868                        dot_left();
3869                last_forward_char = 0;
3870                break;
3871        case 'w':                       // w- forward a word
3872                do {
3873                        if (isalnum(*dot) || *dot == '_') {     // we are on ALNUM
3874                                dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3875                        } else if (ispunct(*dot)) {     // we are on PUNCT
3876                                dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3877                        }
3878                        if (dot < end - 1)
3879                                dot++;          // move over word
3880                        if (isspace(*dot)) {
3881                                dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3882                        }
3883                } while (--cmdcnt > 0);
3884                break;
3885        case 'z':                       // z-
3886                c1 = get_one_char();    // get the replacement char
3887                cnt = 0;
3888                if (c1 == '.')
3889                        cnt = (rows - 2) / 2;   // put dot at center
3890                if (c1 == '-')
3891                        cnt = rows - 2; // put dot at bottom
3892                screenbegin = begin_line(dot);  // start dot at top
3893                dot_scroll(cnt, -1);
3894                break;
3895        case '|':                       // |- move to column "cmdcnt"
3896                dot = move_to_col(dot, cmdcnt - 1);     // try to move to column
3897                break;
3898        case '~':                       // ~- flip the case of letters   a-z -> A-Z
3899                do {
3900                        if (islower(*dot)) {
3901                                *dot = toupper(*dot);
3902                                file_modified++;
3903                        } else if (isupper(*dot)) {
3904                                *dot = tolower(*dot);
3905                                file_modified++;
3906                        }
3907                        dot_right();
3908                } while (--cmdcnt > 0);
3909                end_cmd_q();    // stop adding to q
3910                break;
3911                //----- The Cursor and Function Keys -----------------------------
3912        case KEYCODE_HOME:      // Cursor Key Home
3913                dot_begin();
3914                break;
3915                // The Fn keys could point to do_macro which could translate them
3916#if 0
3917        case KEYCODE_FUN1:      // Function Key F1
3918        case KEYCODE_FUN2:      // Function Key F2
3919        case KEYCODE_FUN3:      // Function Key F3
3920        case KEYCODE_FUN4:      // Function Key F4
3921        case KEYCODE_FUN5:      // Function Key F5
3922        case KEYCODE_FUN6:      // Function Key F6
3923        case KEYCODE_FUN7:      // Function Key F7
3924        case KEYCODE_FUN8:      // Function Key F8
3925        case KEYCODE_FUN9:      // Function Key F9
3926        case KEYCODE_FUN10:     // Function Key F10
3927        case KEYCODE_FUN11:     // Function Key F11
3928        case KEYCODE_FUN12:     // Function Key F12
3929                break;
3930#endif
3931        }
3932
3933 dc1:
3934        // if text[] just became empty, add back an empty line
3935        if (end == text) {
3936                char_insert(text, '\n');        // start empty buf with dummy line
3937                dot = text;
3938        }
3939        // it is OK for dot to exactly equal to end, otherwise check dot validity
3940        if (dot != end) {
3941                dot = bound_dot(dot);   // make sure "dot" is valid
3942        }
3943#if ENABLE_FEATURE_VI_YANKMARK
3944        check_context(c);       // update the current context
3945#endif
3946
3947        if (!isdigit(c))
3948                cmdcnt = 0;             // cmd was not a number, reset cmdcnt
3949        cnt = dot - begin_line(dot);
3950        // Try to stay off of the Newline
3951        if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3952                dot--;
3953}
3954
3955/* NB!  the CRASHME code is unmaintained, and doesn't currently build */
3956#if ENABLE_FEATURE_VI_CRASHME
3957static int totalcmds = 0;
3958static int Mp = 85;             // Movement command Probability
3959static int Np = 90;             // Non-movement command Probability
3960static int Dp = 96;             // Delete command Probability
3961static int Ip = 97;             // Insert command Probability
3962static int Yp = 98;             // Yank command Probability
3963static int Pp = 99;             // Put command Probability
3964static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3965static const char chars[20] = "\t012345 abcdABCD-=.$";
3966static const char *const words[20] = {
3967        "this", "is", "a", "test",
3968        "broadcast", "the", "emergency", "of",
3969        "system", "quick", "brown", "fox",
3970        "jumped", "over", "lazy", "dogs",
3971        "back", "January", "Febuary", "March"
3972};
3973static const char *const lines[20] = {
3974        "You should have received a copy of the GNU General Public License\n",
3975        "char c, cm, *cmd, *cmd1;\n",
3976        "generate a command by percentages\n",
3977        "Numbers may be typed as a prefix to some commands.\n",
3978        "Quit, discarding changes!\n",
3979        "Forced write, if permission originally not valid.\n",
3980        "In general, any ex or ed command (such as substitute or delete).\n",
3981        "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3982        "Please get w/ me and I will go over it with you.\n",
3983        "The following is a list of scheduled, committed changes.\n",
3984        "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3985        "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3986        "Any question about transactions please contact Sterling Huxley.\n",
3987        "I will try to get back to you by Friday, December 31.\n",
3988        "This Change will be implemented on Friday.\n",
3989        "Let me know if you have problems accessing this;\n",
3990        "Sterling Huxley recently added you to the access list.\n",
3991        "Would you like to go to lunch?\n",
3992        "The last command will be automatically run.\n",
3993        "This is too much english for a computer geek.\n",
3994};
3995static char *multilines[20] = {
3996        "You should have received a copy of the GNU General Public License\n",
3997        "char c, cm, *cmd, *cmd1;\n",
3998        "generate a command by percentages\n",
3999        "Numbers may be typed as a prefix to some commands.\n",
4000        "Quit, discarding changes!\n",
4001        "Forced write, if permission originally not valid.\n",
4002        "In general, any ex or ed command (such as substitute or delete).\n",
4003        "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4004        "Please get w/ me and I will go over it with you.\n",
4005        "The following is a list of scheduled, committed changes.\n",
4006        "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4007        "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4008        "Any question about transactions please contact Sterling Huxley.\n",
4009        "I will try to get back to you by Friday, December 31.\n",
4010        "This Change will be implemented on Friday.\n",
4011        "Let me know if you have problems accessing this;\n",
4012        "Sterling Huxley recently added you to the access list.\n",
4013        "Would you like to go to lunch?\n",
4014        "The last command will be automatically run.\n",
4015        "This is too much english for a computer geek.\n",
4016};
4017
4018// create a random command to execute
4019static void crash_dummy()
4020{
4021        static int sleeptime;   // how long to pause between commands
4022        char c, cm, *cmd, *cmd1;
4023        int i, cnt, thing, rbi, startrbi, percent;
4024
4025        // "dot" movement commands
4026        cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4027
4028        // is there already a command running?
4029        if (readbuffer[0] > 0)
4030                goto cd1;
4031 cd0:
4032        readbuffer[0] = 'X';
4033        startrbi = rbi = 1;
4034        sleeptime = 0;          // how long to pause between commands
4035        memset(readbuffer, '\0', sizeof(readbuffer));
4036        // generate a command by percentages
4037        percent = (int) lrand48() % 100;        // get a number from 0-99
4038        if (percent < Mp) {     //  Movement commands
4039                // available commands
4040                cmd = cmd1;
4041                M++;
4042        } else if (percent < Np) {      //  non-movement commands
4043                cmd = "mz<>\'\"";       // available commands
4044                N++;
4045        } else if (percent < Dp) {      //  Delete commands
4046                cmd = "dx";             // available commands
4047                D++;
4048        } else if (percent < Ip) {      //  Inset commands
4049                cmd = "iIaAsrJ";        // available commands
4050                I++;
4051        } else if (percent < Yp) {      //  Yank commands
4052                cmd = "yY";             // available commands
4053                Y++;
4054        } else if (percent < Pp) {      //  Put commands
4055                cmd = "pP";             // available commands
4056                P++;
4057        } else {
4058                // We do not know how to handle this command, try again
4059                U++;
4060                goto cd0;
4061        }
4062        // randomly pick one of the available cmds from "cmd[]"
4063        i = (int) lrand48() % strlen(cmd);
4064        cm = cmd[i];
4065        if (strchr(":\024", cm))
4066                goto cd0;               // dont allow colon or ctrl-T commands
4067        readbuffer[rbi++] = cm; // put cmd into input buffer
4068
4069        // now we have the command-
4070        // there are 1, 2, and multi char commands
4071        // find out which and generate the rest of command as necessary
4072        if (strchr("dmryz<>\'\"", cm)) {        // 2-char commands
4073                cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4074                if (cm == 'm' || cm == '\'' || cm == '\"') {    // pick a reg[]
4075                        cmd1 = "abcdefghijklmnopqrstuvwxyz";
4076                }
4077                thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4078                c = cmd1[thing];
4079                readbuffer[rbi++] = c;  // add movement to input buffer
4080        }
4081        if (strchr("iIaAsc", cm)) {     // multi-char commands
4082                if (cm == 'c') {
4083                        // change some thing
4084                        thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4085                        c = cmd1[thing];
4086                        readbuffer[rbi++] = c;  // add movement to input buffer
4087                }
4088                thing = (int) lrand48() % 4;    // what thing to insert
4089                cnt = (int) lrand48() % 10;     // how many to insert
4090                for (i = 0; i < cnt; i++) {
4091                        if (thing == 0) {       // insert chars
4092                                readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4093                        } else if (thing == 1) {        // insert words
4094                                strcat(readbuffer, words[(int) lrand48() % 20]);
4095                                strcat(readbuffer, " ");
4096                                sleeptime = 0;  // how fast to type
4097                        } else if (thing == 2) {        // insert lines
4098                                strcat(readbuffer, lines[(int) lrand48() % 20]);
4099                                sleeptime = 0;  // how fast to type
4100                        } else {        // insert multi-lines
4101                                strcat(readbuffer, multilines[(int) lrand48() % 20]);
4102                                sleeptime = 0;  // how fast to type
4103                        }
4104                }
4105                strcat(readbuffer, "\033");
4106        }
4107        readbuffer[0] = strlen(readbuffer + 1);
4108 cd1:
4109        totalcmds++;
4110        if (sleeptime > 0)
4111                mysleep(sleeptime);      // sleep 1/100 sec
4112}
4113
4114// test to see if there are any errors
4115static void crash_test()
4116{
4117        static time_t oldtim;
4118
4119        time_t tim;
4120        char d[2], msg[80];
4121
4122        msg[0] = '\0';
4123        if (end < text) {
4124                strcat(msg, "end<text ");
4125        }
4126        if (end > textend) {
4127                strcat(msg, "end>textend ");
4128        }
4129        if (dot < text) {
4130                strcat(msg, "dot<text ");
4131        }
4132        if (dot > end) {
4133                strcat(msg, "dot>end ");
4134        }
4135        if (screenbegin < text) {
4136                strcat(msg, "screenbegin<text ");
4137        }
4138        if (screenbegin > end - 1) {
4139                strcat(msg, "screenbegin>end-1 ");
4140        }
4141
4142        if (msg[0]) {
4143                printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4144                        totalcmds, last_input_char, msg, SOs, SOn);
4145                fflush_all();
4146                while (safe_read(STDIN_FILENO, d, 1) > 0) {
4147                        if (d[0] == '\n' || d[0] == '\r')
4148                                break;
4149                }
4150        }
4151        tim = time(NULL);
4152        if (tim >= (oldtim + 3)) {
4153                sprintf(status_buffer,
4154                                "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4155                                totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4156                oldtim = tim;
4157        }
4158}
4159#endif
4160