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