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