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