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