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