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