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