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