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