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