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