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