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