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