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