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