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