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