vi: move mark[i] pointers if text[] moves after realloc
[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) {
1044                         free(reg[Ureg]);        //   free orig line reg- for 'U'
1045                         reg[Ureg] = NULL;
1046                 }
1047                 if (YDreg >= 0 && YDreg < 28) {
1048                         free(reg[YDreg]);       //   free default yank/delete register
1049                         reg[YDreg] = NULL;
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 *F, *R, *flags;
1229                 size_t len_F, len_R;
1230                 int gflag;              // global replace flag
1231
1232                 // F points to the "find" pattern
1233                 // R points to the "replace" pattern
1234                 // replace the cmd line delimiters "/" with NULs
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                 len_F = R - F;
1241                 *R++ = '\0';    // terminate "find"
1242                 flags = strchr(R, c);
1243                 if (!flags)
1244                         goto colon_s_fail;
1245                 len_R = flags - R;
1246                 *flags++ = '\0';        // terminate "replace"
1247                 gflag = *flags;
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
1257                 for (i = b; i <= e; i++) {      // so, :20,23 s \0 find \0 replace \0
1258                         char *ls = q;           // orig line start
1259                         char *found;
1260  vc4:
1261                         found = char_search(q, F, FORWARD, LIMITED);    // search cur line only for "find"
1262                         if (found) {
1263                                 uintptr_t bias;
1264                                 // we found the "find" pattern - delete it
1265                                 text_hole_delete(found, found + len_F - 1);
1266                                 // inset the "replace" patern
1267                                 bias = string_insert(found, R); // insert the string
1268                                 found += bias;
1269                                 ls += bias;
1270                                 /*q += bias; - recalculated anyway */
1271                                 // check for "global"  :s/foo/bar/g
1272                                 if (gflag == 'g') {
1273                                         if ((found + len_R) < end_line(ls)) {
1274                                                 q = found + len_R;
1275                                                 goto vc4;       // don't let q move past cur line
1276                                         }
1277                                 }
1278                         }
1279                         q = next_line(ls);
1280                 }
1281 #endif /* FEATURE_VI_SEARCH */
1282         } else if (strncmp(cmd, "version", i) == 0) {  // show software version
1283                 status_line(BB_VER " " BB_BT);
1284         } else if (strncmp(cmd, "write", i) == 0  // write text to file
1285                 || strncmp(cmd, "wq", i) == 0
1286                 || strncmp(cmd, "wn", i) == 0
1287                 || (cmd[0] == 'x' && !cmd[1])
1288         ) {
1289                 // is there a file name to write to?
1290                 if (args[0]) {
1291                         fn = args;
1292                 }
1293 #if ENABLE_FEATURE_VI_READONLY
1294                 if (readonly_mode && !useforce) {
1295                         status_line_bold("\"%s\" File is read only", fn);
1296                         goto ret;
1297                 }
1298 #endif
1299                 // how many lines in text[]?
1300                 li = count_lines(q, r);
1301                 ch = r - q + 1;
1302                 // see if file exists- if not, its just a new file request
1303                 if (useforce) {
1304                         // if "fn" is not write-able, chmod u+w
1305                         // sprintf(syscmd, "chmod u+w %s", fn);
1306                         // system(syscmd);
1307                         forced = TRUE;
1308                 }
1309                 l = file_write(fn, q, r);
1310                 if (useforce && forced) {
1311                         // chmod u-w
1312                         // sprintf(syscmd, "chmod u-w %s", fn);
1313                         // system(syscmd);
1314                         forced = FALSE;
1315                 }
1316                 if (l < 0) {
1317                         if (l == -1)
1318                                 status_line_bold("\"%s\" %s", fn, strerror(errno));
1319                 } else {
1320                         status_line("\"%s\" %dL, %dC", fn, li, l);
1321                         if (q == text && r == end - 1 && l == ch) {
1322                                 file_modified = 0;
1323                                 last_file_modified = -1;
1324                         }
1325                         if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
1326                             || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
1327                             )
1328                          && l == ch
1329                         ) {
1330                                 editing = 0;
1331                         }
1332                 }
1333 #if ENABLE_FEATURE_VI_YANKMARK
1334         } else if (strncmp(cmd, "yank", i) == 0) {      // yank lines
1335                 if (b < 0) {    // no addr given- use defaults
1336                         q = begin_line(dot);    // assume .,. for the range
1337                         r = end_line(dot);
1338                 }
1339                 text_yank(q, r, YDreg);
1340                 li = count_lines(q, r);
1341                 status_line("Yank %d lines (%d chars) into [%c]",
1342                                 li, strlen(reg[YDreg]), what_reg());
1343 #endif
1344         } else {
1345                 // cmd unknown
1346                 not_implemented(cmd);
1347         }
1348  ret:
1349         dot = bound_dot(dot);   // make sure "dot" is valid
1350         return;
1351 #if ENABLE_FEATURE_VI_SEARCH
1352  colon_s_fail:
1353         status_line(":s expression missing delimiters");
1354 #endif
1355 }
1356
1357 #endif /* FEATURE_VI_COLON */
1358
1359 static void Hit_Return(void)
1360 {
1361         int c;
1362
1363         standout_start();
1364         write1("[Hit return to continue]");
1365         standout_end();
1366         while ((c = get_one_char()) != '\n' && c != '\r')
1367                 continue;
1368         redraw(TRUE);           // force redraw all
1369 }
1370
1371 static int next_tabstop(int col)
1372 {
1373         return col + ((tabstop - 1) - (col % tabstop));
1374 }
1375
1376 //----- Synchronize the cursor to Dot --------------------------
1377 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1378 {
1379         char *beg_cur;  // begin and end of "d" line
1380         char *tp;
1381         int cnt, ro, co;
1382
1383         beg_cur = begin_line(d);        // first char of cur line
1384
1385         if (beg_cur < screenbegin) {
1386                 // "d" is before top line on screen
1387                 // how many lines do we have to move
1388                 cnt = count_lines(beg_cur, screenbegin);
1389  sc1:
1390                 screenbegin = beg_cur;
1391                 if (cnt > (rows - 1) / 2) {
1392                         // we moved too many lines. put "dot" in middle of screen
1393                         for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1394                                 screenbegin = prev_line(screenbegin);
1395                         }
1396                 }
1397         } else {
1398                 char *end_scr;  // begin and end of screen
1399                 end_scr = end_screen(); // last char of screen
1400                 if (beg_cur > end_scr) {
1401                         // "d" is after bottom line on screen
1402                         // how many lines do we have to move
1403                         cnt = count_lines(end_scr, beg_cur);
1404                         if (cnt > (rows - 1) / 2)
1405                                 goto sc1;       // too many lines
1406                         for (ro = 0; ro < cnt - 1; ro++) {
1407                                 // move screen begin the same amount
1408                                 screenbegin = next_line(screenbegin);
1409                                 // now, move the end of screen
1410                                 end_scr = next_line(end_scr);
1411                                 end_scr = end_line(end_scr);
1412                         }
1413                 }
1414         }
1415         // "d" is on screen- find out which row
1416         tp = screenbegin;
1417         for (ro = 0; ro < rows - 1; ro++) {     // drive "ro" to correct row
1418                 if (tp == beg_cur)
1419                         break;
1420                 tp = next_line(tp);
1421         }
1422
1423         // find out what col "d" is on
1424         co = 0;
1425         while (tp < d) { // drive "co" to correct column
1426                 if (*tp == '\n') //vda || *tp == '\0')
1427                         break;
1428                 if (*tp == '\t') {
1429                         // handle tabs like real vi
1430                         if (d == tp && cmd_mode) {
1431                                 break;
1432                         }
1433                         co = next_tabstop(co);
1434                 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1435                         co++; // display as ^X, use 2 columns
1436                 }
1437                 co++;
1438                 tp++;
1439         }
1440
1441         // "co" is the column where "dot" is.
1442         // The screen has "columns" columns.
1443         // The currently displayed columns are  0+offset -- columns+ofset
1444         // |-------------------------------------------------------------|
1445         //               ^ ^                                ^
1446         //        offset | |------- columns ----------------|
1447         //
1448         // If "co" is already in this range then we do not have to adjust offset
1449         //      but, we do have to subtract the "offset" bias from "co".
1450         // If "co" is outside this range then we have to change "offset".
1451         // If the first char of a line is a tab the cursor will try to stay
1452         //  in column 7, but we have to set offset to 0.
1453
1454         if (co < 0 + offset) {
1455                 offset = co;
1456         }
1457         if (co >= columns + offset) {
1458                 offset = co - columns + 1;
1459         }
1460         // if the first char of the line is a tab, and "dot" is sitting on it
1461         //  force offset to 0.
1462         if (d == beg_cur && *d == '\t') {
1463                 offset = 0;
1464         }
1465         co -= offset;
1466
1467         *row = ro;
1468         *col = co;
1469 }
1470
1471 //----- Text Movement Routines ---------------------------------
1472 static char *begin_line(char *p) // return pointer to first char cur line
1473 {
1474         if (p > text) {
1475                 p = memrchr(text, '\n', p - text);
1476                 if (!p)
1477                         return text;
1478                 return p + 1;
1479         }
1480         return p;
1481 }
1482
1483 static char *end_line(char *p) // return pointer to NL of cur line
1484 {
1485         if (p < end - 1) {
1486                 p = memchr(p, '\n', end - p - 1);
1487                 if (!p)
1488                         return end - 1;
1489         }
1490         return p;
1491 }
1492
1493 static char *dollar_line(char *p) // return pointer to just before NL line
1494 {
1495         p = end_line(p);
1496         // Try to stay off of the Newline
1497         if (*p == '\n' && (p - begin_line(p)) > 0)
1498                 p--;
1499         return p;
1500 }
1501
1502 static char *prev_line(char *p) // return pointer first char prev line
1503 {
1504         p = begin_line(p);      // goto begining of cur line
1505         if (p > text && p[-1] == '\n')
1506                 p--;                    // step to prev line
1507         p = begin_line(p);      // goto begining of prev line
1508         return p;
1509 }
1510
1511 static char *next_line(char *p) // return pointer first char next line
1512 {
1513         p = end_line(p);
1514         if (p < end - 1 && *p == '\n')
1515                 p++;                    // step to next line
1516         return p;
1517 }
1518
1519 //----- Text Information Routines ------------------------------
1520 static char *end_screen(void)
1521 {
1522         char *q;
1523         int cnt;
1524
1525         // find new bottom line
1526         q = screenbegin;
1527         for (cnt = 0; cnt < rows - 2; cnt++)
1528                 q = next_line(q);
1529         q = end_line(q);
1530         return q;
1531 }
1532
1533 // count line from start to stop
1534 static int count_lines(char *start, char *stop)
1535 {
1536         char *q;
1537         int cnt;
1538
1539         if (stop < start) { // start and stop are backwards- reverse them
1540                 q = start;
1541                 start = stop;
1542                 stop = q;
1543         }
1544         cnt = 0;
1545         stop = end_line(stop);
1546         while (start <= stop && start <= end - 1) {
1547                 start = end_line(start);
1548                 if (*start == '\n')
1549                         cnt++;
1550                 start++;
1551         }
1552         return cnt;
1553 }
1554
1555 static char *find_line(int li)  // find begining of line #li
1556 {
1557         char *q;
1558
1559         for (q = text; li > 1; li--) {
1560                 q = next_line(q);
1561         }
1562         return q;
1563 }
1564
1565 //----- Dot Movement Routines ----------------------------------
1566 static void dot_left(void)
1567 {
1568         if (dot > text && dot[-1] != '\n')
1569                 dot--;
1570 }
1571
1572 static void dot_right(void)
1573 {
1574         if (dot < end - 1 && *dot != '\n')
1575                 dot++;
1576 }
1577
1578 static void dot_begin(void)
1579 {
1580         dot = begin_line(dot);  // return pointer to first char cur line
1581 }
1582
1583 static void dot_end(void)
1584 {
1585         dot = end_line(dot);    // return pointer to last char cur line
1586 }
1587
1588 static char *move_to_col(char *p, int l)
1589 {
1590         int co;
1591
1592         p = begin_line(p);
1593         co = 0;
1594         while (co < l && p < end) {
1595                 if (*p == '\n') //vda || *p == '\0')
1596                         break;
1597                 if (*p == '\t') {
1598                         co = next_tabstop(co);
1599                 } else if (*p < ' ' || *p == 127) {
1600                         co++; // display as ^X, use 2 columns
1601                 }
1602                 co++;
1603                 p++;
1604         }
1605         return p;
1606 }
1607
1608 static void dot_next(void)
1609 {
1610         dot = next_line(dot);
1611 }
1612
1613 static void dot_prev(void)
1614 {
1615         dot = prev_line(dot);
1616 }
1617
1618 static void dot_scroll(int cnt, int dir)
1619 {
1620         char *q;
1621
1622         for (; cnt > 0; cnt--) {
1623                 if (dir < 0) {
1624                         // scroll Backwards
1625                         // ctrl-Y scroll up one line
1626                         screenbegin = prev_line(screenbegin);
1627                 } else {
1628                         // scroll Forwards
1629                         // ctrl-E scroll down one line
1630                         screenbegin = next_line(screenbegin);
1631                 }
1632         }
1633         // make sure "dot" stays on the screen so we dont scroll off
1634         if (dot < screenbegin)
1635                 dot = screenbegin;
1636         q = end_screen();       // find new bottom line
1637         if (dot > q)
1638                 dot = begin_line(q);    // is dot is below bottom line?
1639         dot_skip_over_ws();
1640 }
1641
1642 static void dot_skip_over_ws(void)
1643 {
1644         // skip WS
1645         while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1646                 dot++;
1647 }
1648
1649 static void dot_delete(void)    // delete the char at 'dot'
1650 {
1651         text_hole_delete(dot, dot);
1652 }
1653
1654 static char *bound_dot(char *p) // make sure  text[0] <= P < "end"
1655 {
1656         if (p >= end && end > text) {
1657                 p = end - 1;
1658                 indicate_error('1');
1659         }
1660         if (p < text) {
1661                 p = text;
1662                 indicate_error('2');
1663         }
1664         return p;
1665 }
1666
1667 //----- Helper Utility Routines --------------------------------
1668
1669 //----------------------------------------------------------------
1670 //----- Char Routines --------------------------------------------
1671 /* Chars that are part of a word-
1672  *    0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1673  * Chars that are Not part of a word (stoppers)
1674  *    !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1675  * Chars that are WhiteSpace
1676  *    TAB NEWLINE VT FF RETURN SPACE
1677  * DO NOT COUNT NEWLINE AS WHITESPACE
1678  */
1679
1680 static char *new_screen(int ro, int co)
1681 {
1682         int li;
1683
1684         free(screen);
1685         screensize = ro * co + 8;
1686         screen = xmalloc(screensize);
1687         // initialize the new screen. assume this will be a empty file.
1688         screen_erase();
1689         //   non-existent text[] lines start with a tilde (~).
1690         for (li = 1; li < ro - 1; li++) {
1691                 screen[(li * co) + 0] = '~';
1692         }
1693         return screen;
1694 }
1695
1696 #if ENABLE_FEATURE_VI_SEARCH
1697
1698 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1699
1700 // search for pattern starting at p
1701 static char *char_search(char *p, const char *pat, int dir, int range)
1702 {
1703         char *q;
1704         struct re_pattern_buffer preg;
1705         int i;
1706         int size;
1707
1708         re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1709         preg.translate = 0;
1710         preg.fastmap = 0;
1711         preg.buffer = 0;
1712         preg.allocated = 0;
1713
1714         // assume a LIMITED forward search
1715         q = next_line(p);
1716         q = end_line(q);
1717         q = end - 1;
1718         if (dir == BACK) {
1719                 q = prev_line(p);
1720                 q = text;
1721         }
1722         // count the number of chars to search over, forward or backward
1723         size = q - p;
1724         if (size < 0)
1725                 size = p - q;
1726         // RANGE could be negative if we are searching backwards
1727         range = q - p;
1728
1729         q = (char *)re_compile_pattern(pat, strlen(pat), (struct re_pattern_buffer *)&preg);
1730         if (q != 0) {
1731                 // The pattern was not compiled
1732                 status_line_bold("bad search pattern: \"%s\": %s", pat, q);
1733                 i = 0;                  // return p if pattern not compiled
1734                 goto cs1;
1735         }
1736
1737         q = p;
1738         if (range < 0) {
1739                 q = p - size;
1740                 if (q < text)
1741                         q = text;
1742         }
1743         // search for the compiled pattern, preg, in p[]
1744         // range < 0-  search backward
1745         // range > 0-  search forward
1746         // 0 < start < size
1747         // re_search() < 0  not found or error
1748         // re_search() > 0  index of found pattern
1749         //            struct pattern    char     int    int    int     struct reg
1750         // re_search (*pattern_buffer,  *string, size,  start, range,  *regs)
1751         i = re_search(&preg, q, size, 0, range, 0);
1752         if (i == -1) {
1753                 p = 0;
1754                 i = 0;                  // return NULL if pattern not found
1755         }
1756  cs1:
1757         if (dir == FORWARD) {
1758                 p = p + i;
1759         } else {
1760                 p = p - i;
1761         }
1762         return p;
1763 }
1764
1765 # else
1766
1767 #  if ENABLE_FEATURE_VI_SETOPTS
1768 static int mycmp(const char *s1, const char *s2, int len)
1769 {
1770         if (ignorecase) {
1771                 return strncasecmp(s1, s2, len);
1772         }
1773         return strncmp(s1, s2, len);
1774 }
1775 #  else
1776 #   define mycmp strncmp
1777 #  endif
1778
1779 static char *char_search(char *p, const char *pat, int dir, int range)
1780 {
1781         char *start, *stop;
1782         int len;
1783
1784         len = strlen(pat);
1785         if (dir == FORWARD) {
1786                 stop = end - 1; // assume range is p - end-1
1787                 if (range == LIMITED)
1788                         stop = next_line(p);    // range is to next line
1789                 for (start = p; start < stop; start++) {
1790                         if (mycmp(start, pat, len) == 0) {
1791                                 return start;
1792                         }
1793                 }
1794         } else if (dir == BACK) {
1795                 stop = text;    // assume range is text - p
1796                 if (range == LIMITED)
1797                         stop = prev_line(p);    // range is to prev line
1798                 for (start = p - len; start >= stop; start--) {
1799                         if (mycmp(start, pat, len) == 0) {
1800                                 return start;
1801                         }
1802                 }
1803         }
1804         // pattern not found
1805         return NULL;
1806 }
1807
1808 # endif
1809
1810 #endif /* FEATURE_VI_SEARCH */
1811
1812 static char *char_insert(char *p, char c) // insert the char c at 'p'
1813 {
1814         if (c == 22) {          // Is this an ctrl-V?
1815                 p += stupid_insert(p, '^');     // use ^ to indicate literal next
1816                 refresh(FALSE); // show the ^
1817                 c = get_one_char();
1818                 *p = c;
1819                 p++;
1820                 file_modified++;
1821         } else if (c == 27) {   // Is this an ESC?
1822                 cmd_mode = 0;
1823                 cmdcnt = 0;
1824                 end_cmd_q();    // stop adding to q
1825                 last_status_cksum = 0;  // force status update
1826                 if ((p[-1] != '\n') && (dot > text)) {
1827                         p--;
1828                 }
1829         } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1830                 //     123456789
1831                 if ((p[-1] != '\n') && (dot>text)) {
1832                         p--;
1833                         p = text_hole_delete(p, p);     // shrink buffer 1 char
1834                 }
1835         } else {
1836 #if ENABLE_FEATURE_VI_SETOPTS
1837                 // insert a char into text[]
1838                 char *sp;               // "save p"
1839 #endif
1840
1841                 if (c == 13)
1842                         c = '\n';       // translate \r to \n
1843 #if ENABLE_FEATURE_VI_SETOPTS
1844                 sp = p;                 // remember addr of insert
1845 #endif
1846                 p += 1 + stupid_insert(p, c);   // insert the char
1847 #if ENABLE_FEATURE_VI_SETOPTS
1848                 if (showmatch && strchr(")]}", *sp) != NULL) {
1849                         showmatching(sp);
1850                 }
1851                 if (autoindent && c == '\n') {  // auto indent the new line
1852                         char *q;
1853                         size_t len;
1854                         q = prev_line(p);       // use prev line as template
1855                         len = strspn(q, " \t"); // space or tab
1856                         if (len) {
1857                                 uintptr_t bias;
1858                                 bias = text_hole_make(p, len);
1859                                 p += bias;
1860                                 q += bias;
1861                                 memcpy(p, q, len);
1862                                 p += len;
1863                         }
1864                 }
1865 #endif
1866         }
1867         return p;
1868 }
1869
1870 // might reallocate text[]! use p += stupid_insert(p, ...),
1871 // and be careful to not use pointers into potentially freed text[]!
1872 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1873 {
1874         uintptr_t bias;
1875         bias = text_hole_make(p, 1);
1876         p += bias;
1877         *p = c;
1878         //file_modified++; - done by text_hole_make()
1879         return bias;
1880 }
1881
1882 static int find_range(char **start, char **stop, char c)
1883 {
1884         char *save_dot, *p, *q, *t;
1885         int cnt, multiline = 0;
1886
1887         save_dot = dot;
1888         p = q = dot;
1889
1890         if (strchr("cdy><", c)) {
1891                 // these cmds operate on whole lines
1892                 p = q = begin_line(p);
1893                 for (cnt = 1; cnt < cmdcnt; cnt++) {
1894                         q = next_line(q);
1895                 }
1896                 q = end_line(q);
1897         } else if (strchr("^%$0bBeEfth\b\177", c)) {
1898                 // These cmds operate on char positions
1899                 do_cmd(c);              // execute movement cmd
1900                 q = dot;
1901         } else if (strchr("wW", c)) {
1902                 do_cmd(c);              // execute movement cmd
1903                 // if we are at the next word's first char
1904                 // step back one char
1905                 // but check the possibilities when it is true
1906                 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1907                                 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1908                                 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1909                         dot--;          // move back off of next word
1910                 if (dot > text && *dot == '\n')
1911                         dot--;          // stay off NL
1912                 q = dot;
1913         } else if (strchr("H-k{", c)) {
1914                 // these operate on multi-lines backwards
1915                 q = end_line(dot);      // find NL
1916                 do_cmd(c);              // execute movement cmd
1917                 dot_begin();
1918                 p = dot;
1919         } else if (strchr("L+j}\r\n", c)) {
1920                 // these operate on multi-lines forwards
1921                 p = begin_line(dot);
1922                 do_cmd(c);              // execute movement cmd
1923                 dot_end();              // find NL
1924                 q = dot;
1925         } else {
1926             // nothing -- this causes any other values of c to
1927             // represent the one-character range under the
1928             // cursor.  this is correct for ' ' and 'l', but
1929             // perhaps no others.
1930             //
1931         }
1932         if (q < p) {
1933                 t = q;
1934                 q = p;
1935                 p = t;
1936         }
1937
1938         // backward char movements don't include start position
1939         if (q > p && strchr("^0bBh\b\177", c)) q--;
1940
1941         multiline = 0;
1942         for (t = p; t <= q; t++) {
1943                 if (*t == '\n') {
1944                         multiline = 1;
1945                         break;
1946                 }
1947         }
1948
1949         *start = p;
1950         *stop = q;
1951         dot = save_dot;
1952         return multiline;
1953 }
1954
1955 static int st_test(char *p, int type, int dir, char *tested)
1956 {
1957         char c, c0, ci;
1958         int test, inc;
1959
1960         inc = dir;
1961         c = c0 = p[0];
1962         ci = p[inc];
1963         test = 0;
1964
1965         if (type == S_BEFORE_WS) {
1966                 c = ci;
1967                 test = (!isspace(c) || c == '\n');
1968         }
1969         if (type == S_TO_WS) {
1970                 c = c0;
1971                 test = (!isspace(c) || c == '\n');
1972         }
1973         if (type == S_OVER_WS) {
1974                 c = c0;
1975                 test = isspace(c);
1976         }
1977         if (type == S_END_PUNCT) {
1978                 c = ci;
1979                 test = ispunct(c);
1980         }
1981         if (type == S_END_ALNUM) {
1982                 c = ci;
1983                 test = (isalnum(c) || c == '_');
1984         }
1985         *tested = c;
1986         return test;
1987 }
1988
1989 static char *skip_thing(char *p, int linecnt, int dir, int type)
1990 {
1991         char c;
1992
1993         while (st_test(p, type, dir, &c)) {
1994                 // make sure we limit search to correct number of lines
1995                 if (c == '\n' && --linecnt < 1)
1996                         break;
1997                 if (dir >= 0 && p >= end - 1)
1998                         break;
1999                 if (dir < 0 && p <= text)
2000                         break;
2001                 p += dir;               // move to next char
2002         }
2003         return p;
2004 }
2005
2006 // find matching char of pair  ()  []  {}
2007 static char *find_pair(char *p, const char c)
2008 {
2009         char match, *q;
2010         int dir, level;
2011
2012         match = ')';
2013         level = 1;
2014         dir = 1;                        // assume forward
2015         switch (c) {
2016         case '(': match = ')'; break;
2017         case '[': match = ']'; break;
2018         case '{': match = '}'; break;
2019         case ')': match = '('; dir = -1; break;
2020         case ']': match = '['; dir = -1; break;
2021         case '}': match = '{'; dir = -1; break;
2022         }
2023         for (q = p + dir; text <= q && q < end; q += dir) {
2024                 // look for match, count levels of pairs  (( ))
2025                 if (*q == c)
2026                         level++;        // increase pair levels
2027                 if (*q == match)
2028                         level--;        // reduce pair level
2029                 if (level == 0)
2030                         break;          // found matching pair
2031         }
2032         if (level != 0)
2033                 q = NULL;               // indicate no match
2034         return q;
2035 }
2036
2037 #if ENABLE_FEATURE_VI_SETOPTS
2038 // show the matching char of a pair,  ()  []  {}
2039 static void showmatching(char *p)
2040 {
2041         char *q, *save_dot;
2042
2043         // we found half of a pair
2044         q = find_pair(p, *p);   // get loc of matching char
2045         if (q == NULL) {
2046                 indicate_error('3');    // no matching char
2047         } else {
2048                 // "q" now points to matching pair
2049                 save_dot = dot; // remember where we are
2050                 dot = q;                // go to new loc
2051                 refresh(FALSE); // let the user see it
2052                 mysleep(40);    // give user some time
2053                 dot = save_dot; // go back to old loc
2054                 refresh(FALSE);
2055         }
2056 }
2057 #endif /* FEATURE_VI_SETOPTS */
2058
2059 // open a hole in text[]
2060 // might reallocate text[]! use p += text_hole_make(p, ...),
2061 // and be careful to not use pointers into potentially freed text[]!
2062 static uintptr_t text_hole_make(char *p, int size)      // at "p", make a 'size' byte hole
2063 {
2064         uintptr_t bias = 0;
2065
2066         if (size <= 0)
2067                 return bias;
2068         end += size;            // adjust the new END
2069         if (end >= (text + text_size)) {
2070                 char *new_text;
2071                 text_size += end - (text + text_size) + 10240;
2072                 new_text = xrealloc(text, text_size);
2073                 bias = (new_text - text);
2074                 screenbegin += bias;
2075                 dot         += bias;
2076                 end         += bias;
2077                 p           += bias;
2078 #if ENABLE_FEATURE_VI_YANKMARK
2079                 {
2080                         int i;
2081                         for (i = 0; i < ARRAY_SIZE(mark); i++)
2082                                 if (mark[i])
2083                                         mark[i] += bias;
2084                 }
2085 #endif
2086                 text = new_text;
2087         }
2088         memmove(p + size, p, end - size - p);
2089         memset(p, ' ', size);   // clear new hole
2090         file_modified++;
2091         return bias;
2092 }
2093
2094 //  close a hole in text[]
2095 static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive
2096 {
2097         char *src, *dest;
2098         int cnt, hole_size;
2099
2100         // move forwards, from beginning
2101         // assume p <= q
2102         src = q + 1;
2103         dest = p;
2104         if (q < p) {            // they are backward- swap them
2105                 src = p + 1;
2106                 dest = q;
2107         }
2108         hole_size = q - p + 1;
2109         cnt = end - src;
2110         if (src < text || src > end)
2111                 goto thd0;
2112         if (dest < text || dest >= end)
2113                 goto thd0;
2114         if (src >= end)
2115                 goto thd_atend; // just delete the end of the buffer
2116         memmove(dest, src, cnt);
2117  thd_atend:
2118         end = end - hole_size;  // adjust the new END
2119         if (dest >= end)
2120                 dest = end - 1; // make sure dest in below end-1
2121         if (end <= text)
2122                 dest = end = text;      // keep pointers valid
2123         file_modified++;
2124  thd0:
2125         return dest;
2126 }
2127
2128 // copy text into register, then delete text.
2129 // if dist <= 0, do not include, or go past, a NewLine
2130 //
2131 static char *yank_delete(char *start, char *stop, int dist, int yf)
2132 {
2133         char *p;
2134
2135         // make sure start <= stop
2136         if (start > stop) {
2137                 // they are backwards, reverse them
2138                 p = start;
2139                 start = stop;
2140                 stop = p;
2141         }
2142         if (dist <= 0) {
2143                 // we cannot cross NL boundaries
2144                 p = start;
2145                 if (*p == '\n')
2146                         return p;
2147                 // dont go past a NewLine
2148                 for (; p + 1 <= stop; p++) {
2149                         if (p[1] == '\n') {
2150                                 stop = p;       // "stop" just before NewLine
2151                                 break;
2152                         }
2153                 }
2154         }
2155         p = start;
2156 #if ENABLE_FEATURE_VI_YANKMARK
2157         text_yank(start, stop, YDreg);
2158 #endif
2159         if (yf == YANKDEL) {
2160                 p = text_hole_delete(start, stop);
2161         }                                       // delete lines
2162         return p;
2163 }
2164
2165 static void show_help(void)
2166 {
2167         puts("These features are available:"
2168 #if ENABLE_FEATURE_VI_SEARCH
2169         "\n\tPattern searches with / and ?"
2170 #endif
2171 #if ENABLE_FEATURE_VI_DOT_CMD
2172         "\n\tLast command repeat with \'.\'"
2173 #endif
2174 #if ENABLE_FEATURE_VI_YANKMARK
2175         "\n\tLine marking with 'x"
2176         "\n\tNamed buffers with \"x"
2177 #endif
2178 #if ENABLE_FEATURE_VI_READONLY
2179         //not implemented: "\n\tReadonly if vi is called as \"view\""
2180         //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2181 #endif
2182 #if ENABLE_FEATURE_VI_SET
2183         "\n\tSome colon mode commands with \':\'"
2184 #endif
2185 #if ENABLE_FEATURE_VI_SETOPTS
2186         "\n\tSettable options with \":set\""
2187 #endif
2188 #if ENABLE_FEATURE_VI_USE_SIGNALS
2189         "\n\tSignal catching- ^C"
2190         "\n\tJob suspend and resume with ^Z"
2191 #endif
2192 #if ENABLE_FEATURE_VI_WIN_RESIZE
2193         "\n\tAdapt to window re-sizes"
2194 #endif
2195         );
2196 }
2197
2198 #if ENABLE_FEATURE_VI_DOT_CMD
2199 static void start_new_cmd_q(char c)
2200 {
2201         // get buffer for new cmd
2202         // if there is a current cmd count put it in the buffer first
2203         if (cmdcnt > 0) {
2204                 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2205         } else { // just save char c onto queue
2206                 last_modifying_cmd[0] = c;
2207                 lmc_len = 1;
2208         }
2209         adding2q = 1;
2210 }
2211
2212 static void end_cmd_q(void)
2213 {
2214 #if ENABLE_FEATURE_VI_YANKMARK
2215         YDreg = 26;                     // go back to default Yank/Delete reg
2216 #endif
2217         adding2q = 0;
2218 }
2219 #endif /* FEATURE_VI_DOT_CMD */
2220
2221 #if ENABLE_FEATURE_VI_YANKMARK \
2222  || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2223  || ENABLE_FEATURE_VI_CRASHME
2224 // might reallocate text[]! use p += string_insert(p, ...),
2225 // and be careful to not use pointers into potentially freed text[]!
2226 static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p'
2227 {
2228         uintptr_t bias;
2229         int i;
2230
2231         i = strlen(s);
2232         bias = text_hole_make(p, i);
2233         p += bias;
2234         memcpy(p, s, i);
2235 #if ENABLE_FEATURE_VI_YANKMARK
2236         {
2237                 int cnt;
2238                 for (cnt = 0; *s != '\0'; s++) {
2239                         if (*s == '\n')
2240                                 cnt++;
2241                 }
2242                 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2243         }
2244 #endif
2245         return bias;
2246 }
2247 #endif
2248
2249 #if ENABLE_FEATURE_VI_YANKMARK
2250 static char *text_yank(char *p, char *q, int dest)      // copy text into a register
2251 {
2252         int cnt = q - p;
2253         if (cnt < 0) {          // they are backwards- reverse them
2254                 p = q;
2255                 cnt = -cnt;
2256         }
2257         free(reg[dest]);        //  if already a yank register, free it
2258         reg[dest] = xstrndup(p, cnt + 1);
2259         return p;
2260 }
2261
2262 static char what_reg(void)
2263 {
2264         char c;
2265
2266         c = 'D';                        // default to D-reg
2267         if (0 <= YDreg && YDreg <= 25)
2268                 c = 'a' + (char) YDreg;
2269         if (YDreg == 26)
2270                 c = 'D';
2271         if (YDreg == 27)
2272                 c = 'U';
2273         return c;
2274 }
2275
2276 static void check_context(char cmd)
2277 {
2278         // A context is defined to be "modifying text"
2279         // Any modifying command establishes a new context.
2280
2281         if (dot < context_start || dot > context_end) {
2282                 if (strchr(modifying_cmds, cmd) != NULL) {
2283                         // we are trying to modify text[]- make this the current context
2284                         mark[27] = mark[26];    // move cur to prev
2285                         mark[26] = dot; // move local to cur
2286                         context_start = prev_line(prev_line(dot));
2287                         context_end = next_line(next_line(dot));
2288                         //loiter= start_loiter= now;
2289                 }
2290         }
2291 }
2292
2293 static char *swap_context(char *p) // goto new context for '' command make this the current context
2294 {
2295         char *tmp;
2296
2297         // the current context is in mark[26]
2298         // the previous context is in mark[27]
2299         // only swap context if other context is valid
2300         if (text <= mark[27] && mark[27] <= end - 1) {
2301                 tmp = mark[27];
2302                 mark[27] = mark[26];
2303                 mark[26] = tmp;
2304                 p = mark[26];   // where we are going- previous context
2305                 context_start = prev_line(prev_line(prev_line(p)));
2306                 context_end = next_line(next_line(next_line(p)));
2307         }
2308         return p;
2309 }
2310 #endif /* FEATURE_VI_YANKMARK */
2311
2312 //----- Set terminal attributes --------------------------------
2313 static void rawmode(void)
2314 {
2315         tcgetattr(0, &term_orig);
2316         term_vi = term_orig;
2317         term_vi.c_lflag &= (~ICANON & ~ECHO);   // leave ISIG on - allow intr's
2318         term_vi.c_iflag &= (~IXON & ~ICRNL);
2319         term_vi.c_oflag &= (~ONLCR);
2320         term_vi.c_cc[VMIN] = 1;
2321         term_vi.c_cc[VTIME] = 0;
2322         erase_char = term_vi.c_cc[VERASE];
2323         tcsetattr_stdin_TCSANOW(&term_vi);
2324 }
2325
2326 static void cookmode(void)
2327 {
2328         fflush_all();
2329         tcsetattr_stdin_TCSANOW(&term_orig);
2330 }
2331
2332 #if ENABLE_FEATURE_VI_USE_SIGNALS
2333 //----- Come here when we get a window resize signal ---------
2334 static void winch_sig(int sig UNUSED_PARAM)
2335 {
2336         int save_errno = errno;
2337         // FIXME: do it in main loop!!!
2338         signal(SIGWINCH, winch_sig);
2339         query_screen_dimensions();
2340         new_screen(rows, columns);      // get memory for virtual screen
2341         redraw(TRUE);           // re-draw the screen
2342         errno = save_errno;
2343 }
2344
2345 //----- Come here when we get a continue signal -------------------
2346 static void cont_sig(int sig UNUSED_PARAM)
2347 {
2348         int save_errno = errno;
2349         rawmode(); // terminal to "raw"
2350         last_status_cksum = 0; // force status update
2351         redraw(TRUE); // re-draw the screen
2352
2353         signal(SIGTSTP, suspend_sig);
2354         signal(SIGCONT, SIG_DFL);
2355         //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2356         errno = save_errno;
2357 }
2358
2359 //----- Come here when we get a Suspend signal -------------------
2360 static void suspend_sig(int sig UNUSED_PARAM)
2361 {
2362         int save_errno = errno;
2363         go_bottom_and_clear_to_eol();
2364         cookmode(); // terminal to "cooked"
2365
2366         signal(SIGCONT, cont_sig);
2367         signal(SIGTSTP, SIG_DFL);
2368         kill(my_pid, SIGTSTP);
2369         errno = save_errno;
2370 }
2371
2372 //----- Come here when we get a signal ---------------------------
2373 static void catch_sig(int sig)
2374 {
2375         signal(SIGINT, catch_sig);
2376         siglongjmp(restart, sig);
2377 }
2378 #endif /* FEATURE_VI_USE_SIGNALS */
2379
2380 static int mysleep(int hund)    // sleep for 'hund' 1/100 seconds or stdin ready
2381 {
2382         struct pollfd pfd[1];
2383
2384         pfd[0].fd = STDIN_FILENO;
2385         pfd[0].events = POLLIN;
2386         return safe_poll(pfd, 1, hund*10) > 0;
2387 }
2388
2389 //----- IO Routines --------------------------------------------
2390 static int readit(void) // read (maybe cursor) key from stdin
2391 {
2392         int c;
2393
2394         fflush_all();
2395         c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2396         if (c == -1) { // EOF/error
2397                 go_bottom_and_clear_to_eol();
2398                 cookmode(); // terminal to "cooked"
2399                 bb_error_msg_and_die("can't read user input");
2400         }
2401         return c;
2402 }
2403
2404 //----- IO Routines --------------------------------------------
2405 static int get_one_char(void)
2406 {
2407         int c;
2408
2409 #if ENABLE_FEATURE_VI_DOT_CMD
2410         if (!adding2q) {
2411                 // we are not adding to the q.
2412                 // but, we may be reading from a q
2413                 if (ioq == 0) {
2414                         // there is no current q, read from STDIN
2415                         c = readit();   // get the users input
2416                 } else {
2417                         // there is a queue to get chars from first
2418                         // careful with correct sign expansion!
2419                         c = (unsigned char)*ioq++;
2420                         if (c == '\0') {
2421                                 // the end of the q, read from STDIN
2422                                 free(ioq_start);
2423                                 ioq_start = ioq = 0;
2424                                 c = readit();   // get the users input
2425                         }
2426                 }
2427         } else {
2428                 // adding STDIN chars to q
2429                 c = readit();   // get the users input
2430                 if (lmc_len >= MAX_INPUT_LEN - 1) {
2431                         status_line_bold("last_modifying_cmd overrun");
2432                 } else {
2433                         // add new char to q
2434                         last_modifying_cmd[lmc_len++] = c;
2435                 }
2436         }
2437 #else
2438         c = readit();           // get the users input
2439 #endif /* FEATURE_VI_DOT_CMD */
2440         return c;
2441 }
2442
2443 // Get input line (uses "status line" area)
2444 static char *get_input_line(const char *prompt)
2445 {
2446         // char [MAX_INPUT_LEN]
2447 #define buf get_input_line__buf
2448
2449         int c;
2450         int i;
2451
2452         strcpy(buf, prompt);
2453         last_status_cksum = 0;  // force status update
2454         go_bottom_and_clear_to_eol();
2455         write1(prompt);      // write out the :, /, or ? prompt
2456
2457         i = strlen(buf);
2458         while (i < MAX_INPUT_LEN) {
2459                 c = get_one_char();
2460                 if (c == '\n' || c == '\r' || c == 27)
2461                         break;          // this is end of input
2462                 if (c == erase_char || c == 8 || c == 127) {
2463                         // user wants to erase prev char
2464                         buf[--i] = '\0';
2465                         write1("\b \b"); // erase char on screen
2466                         if (i <= 0) // user backs up before b-o-l, exit
2467                                 break;
2468                 } else if (c > 0 && c < 256) { // exclude Unicode
2469                         // (TODO: need to handle Unicode)
2470                         buf[i] = c;
2471                         buf[++i] = '\0';
2472                         bb_putchar(c);
2473                 }
2474         }
2475         refresh(FALSE);
2476         return buf;
2477 #undef buf
2478 }
2479
2480 static int file_size(const char *fn) // what is the byte size of "fn"
2481 {
2482         struct stat st_buf;
2483         int cnt;
2484
2485         cnt = -1;
2486         if (fn && stat(fn, &st_buf) == 0)       // see if file exists
2487                 cnt = (int) st_buf.st_size;
2488         return cnt;
2489 }
2490
2491 // might reallocate text[]!
2492 static int file_insert(const char *fn, char *p, int update_ro_status)
2493 {
2494         int cnt = -1;
2495         int fd, size;
2496         struct stat statbuf;
2497
2498         /* Validate file */
2499         if (stat(fn, &statbuf) < 0) {
2500                 status_line_bold("\"%s\" %s", fn, strerror(errno));
2501                 goto fi0;
2502         }
2503         if (!S_ISREG(statbuf.st_mode)) {
2504                 // This is not a regular file
2505                 status_line_bold("\"%s\" Not a regular file", fn);
2506                 goto fi0;
2507         }
2508         if (p < text || p > end) {
2509                 status_line_bold("Trying to insert file outside of memory");
2510                 goto fi0;
2511         }
2512
2513         // read file to buffer
2514         fd = open(fn, O_RDONLY);
2515         if (fd < 0) {
2516                 status_line_bold("\"%s\" %s", fn, strerror(errno));
2517                 goto fi0;
2518         }
2519         size = statbuf.st_size;
2520         p += text_hole_make(p, size);
2521         cnt = safe_read(fd, p, size);
2522         if (cnt < 0) {
2523                 status_line_bold("\"%s\" %s", fn, strerror(errno));
2524                 p = text_hole_delete(p, p + size - 1);  // un-do buffer insert
2525         } else if (cnt < size) {
2526                 // There was a partial read, shrink unused space text[]
2527                 p = text_hole_delete(p + cnt, p + (size - cnt) - 1);    // un-do buffer insert
2528                 status_line_bold("can't read all of file \"%s\"", fn);
2529         }
2530         if (cnt >= size)
2531                 file_modified++;
2532         close(fd);
2533  fi0:
2534 #if ENABLE_FEATURE_VI_READONLY
2535         if (update_ro_status
2536          && ((access(fn, W_OK) < 0) ||
2537                 /* root will always have access()
2538                  * so we check fileperms too */
2539                 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2540             )
2541         ) {
2542                 SET_READONLY_FILE(readonly_mode);
2543         }
2544 #endif
2545         return cnt;
2546 }
2547
2548 static int file_write(char *fn, char *first, char *last)
2549 {
2550         int fd, cnt, charcnt;
2551
2552         if (fn == 0) {
2553                 status_line_bold("No current filename");
2554                 return -2;
2555         }
2556         /* By popular request we do not open file with O_TRUNC,
2557          * but instead ftruncate() it _after_ successful write.
2558          * Might reduce amount of data lost on power fail etc.
2559          */
2560         fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2561         if (fd < 0)
2562                 return -1;
2563         cnt = last - first + 1;
2564         charcnt = full_write(fd, first, cnt);
2565         ftruncate(fd, charcnt);
2566         if (charcnt == cnt) {
2567                 // good write
2568                 //file_modified = FALSE;
2569         } else {
2570                 charcnt = 0;
2571         }
2572         close(fd);
2573         return charcnt;
2574 }
2575
2576 //----- Terminal Drawing ---------------------------------------
2577 // The terminal is made up of 'rows' line of 'columns' columns.
2578 // classically this would be 24 x 80.
2579 //  screen coordinates
2580 //  0,0     ...     0,79
2581 //  1,0     ...     1,79
2582 //  .       ...     .
2583 //  .       ...     .
2584 //  22,0    ...     22,79
2585 //  23,0    ...     23,79   <- status line
2586
2587 //----- Move the cursor to row x col (count from 0, not 1) -------
2588 static void place_cursor(int row, int col, int optimize)
2589 {
2590         char cm1[sizeof(CMrc) + sizeof(int)*3 * 2];
2591 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2592         enum {
2593                 SZ_UP = sizeof(CMup),
2594                 SZ_DN = sizeof(CMdown),
2595                 SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN,
2596         };
2597         char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size
2598 #endif
2599         char *cm;
2600
2601         if (row < 0) row = 0;
2602         if (row >= rows) row = rows - 1;
2603         if (col < 0) col = 0;
2604         if (col >= columns) col = columns - 1;
2605
2606         //----- 1.  Try the standard terminal ESC sequence
2607         sprintf(cm1, CMrc, row + 1, col + 1);
2608         cm = cm1;
2609
2610 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2611         if (optimize && col < 16) {
2612                 char *screenp;
2613                 int Rrow = last_row;
2614                 int diff = Rrow - row;
2615
2616                 if (diff < -5 || diff > 5)
2617                         goto skip;
2618
2619                 //----- find the minimum # of chars to move cursor -------------
2620                 //----- 2.  Try moving with discreet chars (Newline, [back]space, ...)
2621                 cm2[0] = '\0';
2622
2623                 // move to the correct row
2624                 while (row < Rrow) {
2625                         // the cursor has to move up
2626                         strcat(cm2, CMup);
2627                         Rrow--;
2628                 }
2629                 while (row > Rrow) {
2630                         // the cursor has to move down
2631                         strcat(cm2, CMdown);
2632                         Rrow++;
2633                 }
2634
2635                 // now move to the correct column
2636                 strcat(cm2, "\r");                      // start at col 0
2637                 // just send out orignal source char to get to correct place
2638                 screenp = &screen[row * columns];       // start of screen line
2639                 strncat(cm2, screenp, col);
2640
2641                 // pick the shortest cursor motion to send out
2642                 if (strlen(cm2) < strlen(cm)) {
2643                         cm = cm2;
2644                 }
2645  skip: ;
2646         }
2647         last_row = row;
2648 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2649         write1(cm);
2650 }
2651
2652 //----- Erase from cursor to end of line -----------------------
2653 static void clear_to_eol(void)
2654 {
2655         write1(Ceol);   // Erase from cursor to end of line
2656 }
2657
2658 static void go_bottom_and_clear_to_eol(void)
2659 {
2660         place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2661         clear_to_eol(); // erase to end of line
2662 }
2663
2664 //----- Erase from cursor to end of screen -----------------------
2665 static void clear_to_eos(void)
2666 {
2667         write1(Ceos);   // Erase from cursor to end of screen
2668 }
2669
2670 //----- Start standout mode ------------------------------------
2671 static void standout_start(void) // send "start reverse video" sequence
2672 {
2673         write1(SOs);     // Start reverse video mode
2674 }
2675
2676 //----- End standout mode --------------------------------------
2677 static void standout_end(void) // send "end reverse video" sequence
2678 {
2679         write1(SOn);     // End reverse video mode
2680 }
2681
2682 //----- Flash the screen  --------------------------------------
2683 static void flash(int h)
2684 {
2685         standout_start();       // send "start reverse video" sequence
2686         redraw(TRUE);
2687         mysleep(h);
2688         standout_end();         // send "end reverse video" sequence
2689         redraw(TRUE);
2690 }
2691
2692 static void Indicate_Error(void)
2693 {
2694 #if ENABLE_FEATURE_VI_CRASHME
2695         if (crashme > 0)
2696                 return;                 // generate a random command
2697 #endif
2698         if (!err_method) {
2699                 write1(bell);   // send out a bell character
2700         } else {
2701                 flash(10);
2702         }
2703 }
2704
2705 //----- Screen[] Routines --------------------------------------
2706 //----- Erase the Screen[] memory ------------------------------
2707 static void screen_erase(void)
2708 {
2709         memset(screen, ' ', screensize);        // clear new screen
2710 }
2711
2712 static int bufsum(char *buf, int count)
2713 {
2714         int sum = 0;
2715         char *e = buf + count;
2716
2717         while (buf < e)
2718                 sum += (unsigned char) *buf++;
2719         return sum;
2720 }
2721
2722 //----- Draw the status line at bottom of the screen -------------
2723 static void show_status_line(void)
2724 {
2725         int cnt = 0, cksum = 0;
2726
2727         // either we already have an error or status message, or we
2728         // create one.
2729         if (!have_status_msg) {
2730                 cnt = format_edit_status();
2731                 cksum = bufsum(status_buffer, cnt);
2732         }
2733         if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2734                 last_status_cksum = cksum;              // remember if we have seen this line
2735                 go_bottom_and_clear_to_eol();
2736                 write1(status_buffer);
2737                 if (have_status_msg) {
2738                         if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2739                                         (columns - 1) ) {
2740                                 have_status_msg = 0;
2741                                 Hit_Return();
2742                         }
2743                         have_status_msg = 0;
2744                 }
2745                 place_cursor(crow, ccol, FALSE);        // put cursor back in correct place
2746         }
2747         fflush_all();
2748 }
2749
2750 //----- format the status buffer, the bottom line of screen ------
2751 // format status buffer, with STANDOUT mode
2752 static void status_line_bold(const char *format, ...)
2753 {
2754         va_list args;
2755
2756         va_start(args, format);
2757         strcpy(status_buffer, SOs);     // Terminal standout mode on
2758         vsprintf(status_buffer + sizeof(SOs)-1, format, args);
2759         strcat(status_buffer, SOn);     // Terminal standout mode off
2760         va_end(args);
2761
2762         have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2763 }
2764
2765 // format status buffer
2766 static void status_line(const char *format, ...)
2767 {
2768         va_list args;
2769
2770         va_start(args, format);
2771         vsprintf(status_buffer, format, args);
2772         va_end(args);
2773
2774         have_status_msg = 1;
2775 }
2776
2777 // copy s to buf, convert unprintable
2778 static void print_literal(char *buf, const char *s)
2779 {
2780         char *d;
2781         unsigned char c;
2782
2783         buf[0] = '\0';
2784         if (!s[0])
2785                 s = "(NULL)";
2786
2787         d = buf;
2788         for (; *s; s++) {
2789                 int c_is_no_print;
2790
2791                 c = *s;
2792                 c_is_no_print = (c & 0x80) && !Isprint(c);
2793                 if (c_is_no_print) {
2794                         strcpy(d, SOn);
2795                         d += sizeof(SOn)-1;
2796                         c = '.';
2797                 }
2798                 if (c < ' ' || c == 0x7f) {
2799                         *d++ = '^';
2800                         c |= '@'; /* 0x40 */
2801                         if (c == 0x7f)
2802                                 c = '?';
2803                 }
2804                 *d++ = c;
2805                 *d = '\0';
2806                 if (c_is_no_print) {
2807                         strcpy(d, SOs);
2808                         d += sizeof(SOs)-1;
2809                 }
2810                 if (*s == '\n') {
2811                         *d++ = '$';
2812                         *d = '\0';
2813                 }
2814                 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
2815                         break;
2816         }
2817 }
2818
2819 static void not_implemented(const char *s)
2820 {
2821         char buf[MAX_INPUT_LEN];
2822
2823         print_literal(buf, s);
2824         status_line_bold("\'%s\' is not implemented", buf);
2825 }
2826
2827 // show file status on status line
2828 static int format_edit_status(void)
2829 {
2830         static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2831
2832 #define tot format_edit_status__tot
2833
2834         int cur, percent, ret, trunc_at;
2835
2836         // file_modified is now a counter rather than a flag.  this
2837         // helps reduce the amount of line counting we need to do.
2838         // (this will cause a mis-reporting of modified status
2839         // once every MAXINT editing operations.)
2840
2841         // it would be nice to do a similar optimization here -- if
2842         // we haven't done a motion that could have changed which line
2843         // we're on, then we shouldn't have to do this count_lines()
2844         cur = count_lines(text, dot);
2845
2846         // reduce counting -- the total lines can't have
2847         // changed if we haven't done any edits.
2848         if (file_modified != last_file_modified) {
2849                 tot = cur + count_lines(dot, end - 1) - 1;
2850                 last_file_modified = file_modified;
2851         }
2852
2853         //    current line         percent
2854         //   -------------    ~~ ----------
2855         //    total lines            100
2856         if (tot > 0) {
2857                 percent = (100 * cur) / tot;
2858         } else {
2859                 cur = tot = 0;
2860                 percent = 100;
2861         }
2862
2863         trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2864                 columns : STATUS_BUFFER_LEN-1;
2865
2866         ret = snprintf(status_buffer, trunc_at+1,
2867 #if ENABLE_FEATURE_VI_READONLY
2868                 "%c %s%s%s %d/%d %d%%",
2869 #else
2870                 "%c %s%s %d/%d %d%%",
2871 #endif
2872                 cmd_mode_indicator[cmd_mode & 3],
2873                 (current_filename != NULL ? current_filename : "No file"),
2874 #if ENABLE_FEATURE_VI_READONLY
2875                 (readonly_mode ? " [Readonly]" : ""),
2876 #endif
2877                 (file_modified ? " [Modified]" : ""),
2878                 cur, tot, percent);
2879
2880         if (ret >= 0 && ret < trunc_at)
2881                 return ret;  /* it all fit */
2882
2883         return trunc_at;  /* had to truncate */
2884 #undef tot
2885 }
2886
2887 //----- Force refresh of all Lines -----------------------------
2888 static void redraw(int full_screen)
2889 {
2890         place_cursor(0, 0, FALSE);      // put cursor in correct place
2891         clear_to_eos();         // tell terminal to erase display
2892         screen_erase();         // erase the internal screen buffer
2893         last_status_cksum = 0;  // force status update
2894         refresh(full_screen);   // this will redraw the entire display
2895         show_status_line();
2896 }
2897
2898 //----- Format a text[] line into a buffer ---------------------
2899 static char* format_line(char *src /*, int li*/)
2900 {
2901         unsigned char c;
2902         int co;
2903         int ofs = offset;
2904         char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2905
2906         c = '~'; // char in col 0 in non-existent lines is '~'
2907         co = 0;
2908         while (co < columns + tabstop) {
2909                 // have we gone past the end?
2910                 if (src < end) {
2911                         c = *src++;
2912                         if (c == '\n')
2913                                 break;
2914                         if ((c & 0x80) && !Isprint(c)) {
2915                                 c = '.';
2916                         }
2917                         if (c < ' ' || c == 0x7f) {
2918                                 if (c == '\t') {
2919                                         c = ' ';
2920                                         //      co %    8     !=     7
2921                                         while ((co % tabstop) != (tabstop - 1)) {
2922                                                 dest[co++] = c;
2923                                         }
2924                                 } else {
2925                                         dest[co++] = '^';
2926                                         if (c == 0x7f)
2927                                                 c = '?';
2928                                         else
2929                                                 c += '@'; // Ctrl-X -> 'X'
2930                                 }
2931                         }
2932                 }
2933                 dest[co++] = c;
2934                 // discard scrolled-off-to-the-left portion,
2935                 // in tabstop-sized pieces
2936                 if (ofs >= tabstop && co >= tabstop) {
2937                         memmove(dest, dest + tabstop, co);
2938                         co -= tabstop;
2939                         ofs -= tabstop;
2940                 }
2941                 if (src >= end)
2942                         break;
2943         }
2944         // check "short line, gigantic offset" case
2945         if (co < ofs)
2946                 ofs = co;
2947         // discard last scrolled off part
2948         co -= ofs;
2949         dest += ofs;
2950         // fill the rest with spaces
2951         if (co < columns)
2952                 memset(&dest[co], ' ', columns - co);
2953         return dest;
2954 }
2955
2956 //----- Refresh the changed screen lines -----------------------
2957 // Copy the source line from text[] into the buffer and note
2958 // if the current screenline is different from the new buffer.
2959 // If they differ then that line needs redrawing on the terminal.
2960 //
2961 static void refresh(int full_screen)
2962 {
2963 #define old_offset refresh__old_offset
2964
2965         int li, changed;
2966         char *tp, *sp;          // pointer into text[] and screen[]
2967
2968         if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
2969                 unsigned c = columns, r = rows;
2970                 query_screen_dimensions();
2971                 full_screen |= (c - columns) | (r - rows);
2972         }
2973         sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2974         tp = screenbegin;       // index into text[] of top line
2975
2976         // compare text[] to screen[] and mark screen[] lines that need updating
2977         for (li = 0; li < rows - 1; li++) {
2978                 int cs, ce;                             // column start & end
2979                 char *out_buf;
2980                 // format current text line
2981                 out_buf = format_line(tp /*, li*/);
2982
2983                 // skip to the end of the current text[] line
2984                 if (tp < end) {
2985                         char *t = memchr(tp, '\n', end - tp);
2986                         if (!t) t = end - 1;
2987                         tp = t + 1;
2988                 }
2989
2990                 // see if there are any changes between vitual screen and out_buf
2991                 changed = FALSE;        // assume no change
2992                 cs = 0;
2993                 ce = columns - 1;
2994                 sp = &screen[li * columns];     // start of screen line
2995                 if (full_screen) {
2996                         // force re-draw of every single column from 0 - columns-1
2997                         goto re0;
2998                 }
2999                 // compare newly formatted buffer with virtual screen
3000                 // look forward for first difference between buf and screen
3001                 for (; cs <= ce; cs++) {
3002                         if (out_buf[cs] != sp[cs]) {
3003                                 changed = TRUE; // mark for redraw
3004                                 break;
3005                         }
3006                 }
3007
3008                 // look backward for last difference between out_buf and screen
3009                 for (; ce >= cs; ce--) {
3010                         if (out_buf[ce] != sp[ce]) {
3011                                 changed = TRUE; // mark for redraw
3012                                 break;
3013                         }
3014                 }
3015                 // now, cs is index of first diff, and ce is index of last diff
3016
3017                 // if horz offset has changed, force a redraw
3018                 if (offset != old_offset) {
3019  re0:
3020                         changed = TRUE;
3021                 }
3022
3023                 // make a sanity check of columns indexes
3024                 if (cs < 0) cs = 0;
3025                 if (ce > columns - 1) ce = columns - 1;
3026                 if (cs > ce) { cs = 0; ce = columns - 1; }
3027                 // is there a change between vitual screen and out_buf
3028                 if (changed) {
3029                         // copy changed part of buffer to virtual screen
3030                         memcpy(sp+cs, out_buf+cs, ce-cs+1);
3031
3032                         // move cursor to column of first change
3033                         //if (offset != old_offset) {
3034                         //      // place_cursor is still too stupid
3035                         //      // to handle offsets correctly
3036                         //      place_cursor(li, cs, FALSE);
3037                         //} else {
3038                                 place_cursor(li, cs, TRUE);
3039                         //}
3040
3041                         // write line out to terminal
3042                         fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3043                 }
3044         }
3045
3046         place_cursor(crow, ccol, TRUE);
3047
3048         old_offset = offset;
3049 #undef old_offset
3050 }
3051
3052 //---------------------------------------------------------------------
3053 //----- the Ascii Chart -----------------------------------------------
3054 //
3055 //  00 nul   01 soh   02 stx   03 etx   04 eot   05 enq   06 ack   07 bel
3056 //  08 bs    09 ht    0a nl    0b vt    0c np    0d cr    0e so    0f si
3057 //  10 dle   11 dc1   12 dc2   13 dc3   14 dc4   15 nak   16 syn   17 etb
3058 //  18 can   19 em    1a sub   1b esc   1c fs    1d gs    1e rs    1f us
3059 //  20 sp    21 !     22 "     23 #     24 $     25 %     26 &     27 '
3060 //  28 (     29 )     2a *     2b +     2c ,     2d -     2e .     2f /
3061 //  30 0     31 1     32 2     33 3     34 4     35 5     36 6     37 7
3062 //  38 8     39 9     3a :     3b ;     3c <     3d =     3e >     3f ?
3063 //  40 @     41 A     42 B     43 C     44 D     45 E     46 F     47 G
3064 //  48 H     49 I     4a J     4b K     4c L     4d M     4e N     4f O
3065 //  50 P     51 Q     52 R     53 S     54 T     55 U     56 V     57 W
3066 //  58 X     59 Y     5a Z     5b [     5c \     5d ]     5e ^     5f _
3067 //  60 `     61 a     62 b     63 c     64 d     65 e     66 f     67 g
3068 //  68 h     69 i     6a j     6b k     6c l     6d m     6e n     6f o
3069 //  70 p     71 q     72 r     73 s     74 t     75 u     76 v     77 w
3070 //  78 x     79 y     7a z     7b {     7c |     7d }     7e ~     7f del
3071 //---------------------------------------------------------------------
3072
3073 //----- Execute a Vi Command -----------------------------------
3074 static void do_cmd(int c)
3075 {
3076         char *p, *q, *save_dot;
3077         char buf[12];
3078         int dir;
3079         int cnt, i, j;
3080         int c1;
3081
3082 //      c1 = c; // quiet the compiler
3083 //      cnt = yf = 0; // quiet the compiler
3084 //      p = q = save_dot = buf; // quiet the compiler
3085         memset(buf, '\0', sizeof(buf));
3086
3087         show_status_line();
3088
3089         /* if this is a cursor key, skip these checks */
3090         switch (c) {
3091                 case KEYCODE_UP:
3092                 case KEYCODE_DOWN:
3093                 case KEYCODE_LEFT:
3094                 case KEYCODE_RIGHT:
3095                 case KEYCODE_HOME:
3096                 case KEYCODE_END:
3097                 case KEYCODE_PAGEUP:
3098                 case KEYCODE_PAGEDOWN:
3099                 case KEYCODE_DELETE:
3100                         goto key_cmd_mode;
3101         }
3102
3103         if (cmd_mode == 2) {
3104                 //  flip-flop Insert/Replace mode
3105                 if (c == KEYCODE_INSERT)
3106                         goto dc_i;
3107                 // we are 'R'eplacing the current *dot with new char
3108                 if (*dot == '\n') {
3109                         // don't Replace past E-o-l
3110                         cmd_mode = 1;   // convert to insert
3111                 } else {
3112                         if (1 <= c || Isprint(c)) {
3113                                 if (c != 27)
3114                                         dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
3115                                 dot = char_insert(dot, c);      // insert new char
3116                         }
3117                         goto dc1;
3118                 }
3119         }
3120         if (cmd_mode == 1) {
3121                 //  hitting "Insert" twice means "R" replace mode
3122                 if (c == KEYCODE_INSERT) goto dc5;
3123                 // insert the char c at "dot"
3124                 if (1 <= c || Isprint(c)) {
3125                         dot = char_insert(dot, c);
3126                 }
3127                 goto dc1;
3128         }
3129
3130  key_cmd_mode:
3131         switch (c) {
3132                 //case 0x01:    // soh
3133                 //case 0x09:    // ht
3134                 //case 0x0b:    // vt
3135                 //case 0x0e:    // so
3136                 //case 0x0f:    // si
3137                 //case 0x10:    // dle
3138                 //case 0x11:    // dc1
3139                 //case 0x13:    // dc3
3140 #if ENABLE_FEATURE_VI_CRASHME
3141         case 0x14:                      // dc4  ctrl-T
3142                 crashme = (crashme == 0) ? 1 : 0;
3143                 break;
3144 #endif
3145                 //case 0x16:    // syn
3146                 //case 0x17:    // etb
3147                 //case 0x18:    // can
3148                 //case 0x1c:    // fs
3149                 //case 0x1d:    // gs
3150                 //case 0x1e:    // rs
3151                 //case 0x1f:    // us
3152                 //case '!':     // !-
3153                 //case '#':     // #-
3154                 //case '&':     // &-
3155                 //case '(':     // (-
3156                 //case ')':     // )-
3157                 //case '*':     // *-
3158                 //case '=':     // =-
3159                 //case '@':     // @-
3160                 //case 'F':     // F-
3161                 //case 'K':     // K-
3162                 //case 'Q':     // Q-
3163                 //case 'S':     // S-
3164                 //case 'T':     // T-
3165                 //case 'V':     // V-
3166                 //case '[':     // [-
3167                 //case '\\':    // \-
3168                 //case ']':     // ]-
3169                 //case '_':     // _-
3170                 //case '`':     // `-
3171                 //case 'u':     // u- FIXME- there is no undo
3172                 //case 'v':     // v-
3173         default:                        // unrecognized command
3174                 buf[0] = c;
3175                 buf[1] = '\0';
3176                 not_implemented(buf);
3177                 end_cmd_q();    // stop adding to q
3178         case 0x00:                      // nul- ignore
3179                 break;
3180         case 2:                 // ctrl-B  scroll up   full screen
3181         case KEYCODE_PAGEUP:    // Cursor Key Page Up
3182                 dot_scroll(rows - 2, -1);
3183                 break;
3184         case 4:                 // ctrl-D  scroll down half screen
3185                 dot_scroll((rows - 2) / 2, 1);
3186                 break;
3187         case 5:                 // ctrl-E  scroll down one line
3188                 dot_scroll(1, 1);
3189                 break;
3190         case 6:                 // ctrl-F  scroll down full screen
3191         case KEYCODE_PAGEDOWN:  // Cursor Key Page Down
3192                 dot_scroll(rows - 2, 1);
3193                 break;
3194         case 7:                 // ctrl-G  show current status
3195                 last_status_cksum = 0;  // force status update
3196                 break;
3197         case 'h':                       // h- move left
3198         case KEYCODE_LEFT:      // cursor key Left
3199         case 8:         // ctrl-H- move left    (This may be ERASE char)
3200         case 0x7f:      // DEL- move left   (This may be ERASE char)
3201                 do {
3202                         dot_left();
3203                 } while (--cmdcnt > 0);
3204                 break;
3205         case 10:                        // Newline ^J
3206         case 'j':                       // j- goto next line, same col
3207         case KEYCODE_DOWN:      // cursor key Down
3208                 do {
3209                         dot_next();             // go to next B-o-l
3210                         // try stay in same col
3211                         dot = move_to_col(dot, ccol + offset);
3212                 } while (--cmdcnt > 0);
3213                 break;
3214         case 12:                        // ctrl-L  force redraw whole screen
3215         case 18:                        // ctrl-R  force redraw
3216                 place_cursor(0, 0, FALSE);      // put cursor in correct place
3217                 clear_to_eos(); // tel terminal to erase display
3218                 mysleep(10);
3219                 screen_erase(); // erase the internal screen buffer
3220                 last_status_cksum = 0;  // force status update
3221                 refresh(TRUE);  // this will redraw the entire display
3222                 break;
3223         case 13:                        // Carriage Return ^M
3224         case '+':                       // +- goto next line
3225                 do {
3226                         dot_next();
3227                         dot_skip_over_ws();
3228                 } while (--cmdcnt > 0);
3229                 break;
3230         case 21:                        // ctrl-U  scroll up   half screen
3231                 dot_scroll((rows - 2) / 2, -1);
3232                 break;
3233         case 25:                        // ctrl-Y  scroll up one line
3234                 dot_scroll(1, -1);
3235                 break;
3236         case 27:                        // esc
3237                 if (cmd_mode == 0)
3238                         indicate_error(c);
3239                 cmd_mode = 0;   // stop insrting
3240                 end_cmd_q();
3241                 last_status_cksum = 0;  // force status update
3242                 break;
3243         case ' ':                       // move right
3244         case 'l':                       // move right
3245         case KEYCODE_RIGHT:     // Cursor Key Right
3246                 do {
3247                         dot_right();
3248                 } while (--cmdcnt > 0);
3249                 break;
3250 #if ENABLE_FEATURE_VI_YANKMARK
3251         case '"':                       // "- name a register to use for Delete/Yank
3252                 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3253                 if ((unsigned)c1 <= 25) { // a-z?
3254                         YDreg = c1;
3255                 } else {
3256                         indicate_error(c);
3257                 }
3258                 break;
3259         case '\'':                      // '- goto a specific mark
3260                 c1 = (get_one_char() | 0x20) - 'a';
3261                 if ((unsigned)c1 <= 25) { // a-z?
3262                         // get the b-o-l
3263                         q = mark[c1];
3264                         if (text <= q && q < end) {
3265                                 dot = q;
3266                                 dot_begin();    // go to B-o-l
3267                                 dot_skip_over_ws();
3268                         }
3269                 } else if (c1 == '\'') {        // goto previous context
3270                         dot = swap_context(dot);        // swap current and previous context
3271                         dot_begin();    // go to B-o-l
3272                         dot_skip_over_ws();
3273                 } else {
3274                         indicate_error(c);
3275                 }
3276                 break;
3277         case 'm':                       // m- Mark a line
3278                 // this is really stupid.  If there are any inserts or deletes
3279                 // between text[0] and dot then this mark will not point to the
3280                 // correct location! It could be off by many lines!
3281                 // Well..., at least its quick and dirty.
3282                 c1 = (get_one_char() | 0x20) - 'a';
3283                 if ((unsigned)c1 <= 25) { // a-z?
3284                         // remember the line
3285                         mark[c1] = dot;
3286                 } else {
3287                         indicate_error(c);
3288                 }
3289                 break;
3290         case 'P':                       // P- Put register before
3291         case 'p':                       // p- put register after
3292                 p = reg[YDreg];
3293                 if (p == NULL) {
3294                         status_line_bold("Nothing in register %c", what_reg());
3295                         break;
3296                 }
3297                 // are we putting whole lines or strings
3298                 if (strchr(p, '\n') != NULL) {
3299                         if (c == 'P') {
3300                                 dot_begin();    // putting lines- Put above
3301                         }
3302                         if (c == 'p') {
3303                                 // are we putting after very last line?
3304                                 if (end_line(dot) == (end - 1)) {
3305                                         dot = end;      // force dot to end of text[]
3306                                 } else {
3307                                         dot_next();     // next line, then put before
3308                                 }
3309                         }
3310                 } else {
3311                         if (c == 'p')
3312                                 dot_right();    // move to right, can move to NL
3313                 }
3314                 string_insert(dot, p);  // insert the string
3315                 end_cmd_q();    // stop adding to q
3316                 break;
3317         case 'U':                       // U- Undo; replace current line with original version
3318                 if (reg[Ureg] != NULL) {
3319                         p = begin_line(dot);
3320                         q = end_line(dot);
3321                         p = text_hole_delete(p, q);     // delete cur line
3322                         p += string_insert(p, reg[Ureg]);       // insert orig line
3323                         dot = p;
3324                         dot_skip_over_ws();
3325                 }
3326                 break;
3327 #endif /* FEATURE_VI_YANKMARK */
3328         case '$':                       // $- goto end of line
3329         case KEYCODE_END:               // Cursor Key End
3330                 for (;;) {
3331                         dot = end_line(dot);
3332                         if (--cmdcnt <= 0)
3333                                 break;
3334                         dot_next();
3335                 }
3336                 break;
3337         case '%':                       // %- find matching char of pair () [] {}
3338                 for (q = dot; q < end && *q != '\n'; q++) {
3339                         if (strchr("()[]{}", *q) != NULL) {
3340                                 // we found half of a pair
3341                                 p = find_pair(q, *q);
3342                                 if (p == NULL) {
3343                                         indicate_error(c);
3344                                 } else {
3345                                         dot = p;
3346                                 }
3347                                 break;
3348                         }
3349                 }
3350                 if (*q == '\n')
3351                         indicate_error(c);
3352                 break;
3353         case 'f':                       // f- forward to a user specified char
3354                 last_forward_char = get_one_char();     // get the search char
3355                 //
3356                 // dont separate these two commands. 'f' depends on ';'
3357                 //
3358                 //**** fall through to ... ';'
3359         case ';':                       // ;- look at rest of line for last forward char
3360                 do {
3361                         if (last_forward_char == 0)
3362                                 break;
3363                         q = dot + 1;
3364                         while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3365                                 q++;
3366                         }
3367                         if (*q == last_forward_char)
3368                                 dot = q;
3369                 } while (--cmdcnt > 0);
3370                 break;
3371         case ',':           // repeat latest 'f' in opposite direction
3372                 if (last_forward_char == 0)
3373                         break;
3374                 do {
3375                         q = dot - 1;
3376                         while (q >= text && *q != '\n' && *q != last_forward_char) {
3377                                 q--;
3378                         }
3379                         if (q >= text && *q == last_forward_char)
3380                                 dot = q;
3381                 } while (--cmdcnt > 0);
3382                 break;
3383
3384         case '-':                       // -- goto prev line
3385                 do {
3386                         dot_prev();
3387                         dot_skip_over_ws();
3388                 } while (--cmdcnt > 0);
3389                 break;
3390 #if ENABLE_FEATURE_VI_DOT_CMD
3391         case '.':                       // .- repeat the last modifying command
3392                 // Stuff the last_modifying_cmd back into stdin
3393                 // and let it be re-executed.
3394                 if (lmc_len > 0) {
3395                         last_modifying_cmd[lmc_len] = 0;
3396                         ioq = ioq_start = xstrdup(last_modifying_cmd);
3397                 }
3398                 break;
3399 #endif
3400 #if ENABLE_FEATURE_VI_SEARCH
3401         case '?':                       // /- search for a pattern
3402         case '/':                       // /- search for a pattern
3403                 buf[0] = c;
3404                 buf[1] = '\0';
3405                 q = get_input_line(buf);        // get input line- use "status line"
3406                 if (q[0] && !q[1]) {
3407                         if (last_search_pattern[0])
3408                                 last_search_pattern[0] = c;
3409                         goto dc3; // if no pat re-use old pat
3410                 }
3411                 if (q[0]) {       // strlen(q) > 1: new pat- save it and find
3412                         // there is a new pat
3413                         free(last_search_pattern);
3414                         last_search_pattern = xstrdup(q);
3415                         goto dc3;       // now find the pattern
3416                 }
3417                 // user changed mind and erased the "/"-  do nothing
3418                 break;
3419         case 'N':                       // N- backward search for last pattern
3420                 dir = BACK;             // assume BACKWARD search
3421                 p = dot - 1;
3422                 if (last_search_pattern[0] == '?') {
3423                         dir = FORWARD;
3424                         p = dot + 1;
3425                 }
3426                 goto dc4;               // now search for pattern
3427                 break;
3428         case 'n':                       // n- repeat search for last pattern
3429                 // search rest of text[] starting at next char
3430                 // if search fails return orignal "p" not the "p+1" address
3431                 do {
3432                         const char *msg;
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 = NULL;
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                 } while (--cmdcnt > 0);
3466                 break;
3467         case '{':                       // {- move backward paragraph
3468                 q = char_search(dot, "\n\n", BACK, FULL);
3469                 if (q != NULL) {        // found blank line
3470                         dot = next_line(q);     // move to next blank line
3471                 }
3472                 break;
3473         case '}':                       // }- move forward paragraph
3474                 q = char_search(dot, "\n\n", FORWARD, FULL);
3475                 if (q != NULL) {        // found blank line
3476                         dot = next_line(q);     // move to next blank line
3477                 }
3478                 break;
3479 #endif /* FEATURE_VI_SEARCH */
3480         case '0':                       // 0- goto begining of line
3481         case '1':                       // 1-
3482         case '2':                       // 2-
3483         case '3':                       // 3-
3484         case '4':                       // 4-
3485         case '5':                       // 5-
3486         case '6':                       // 6-
3487         case '7':                       // 7-
3488         case '8':                       // 8-
3489         case '9':                       // 9-
3490                 if (c == '0' && cmdcnt < 1) {
3491                         dot_begin();    // this was a standalone zero
3492                 } else {
3493                         cmdcnt = cmdcnt * 10 + (c - '0');       // this 0 is part of a number
3494                 }
3495                 break;
3496         case ':':                       // :- the colon mode commands
3497                 p = get_input_line(":");        // get input line- use "status line"
3498 #if ENABLE_FEATURE_VI_COLON
3499                 colon(p);               // execute the command
3500 #else
3501                 if (*p == ':')
3502                         p++;                            // move past the ':'
3503                 cnt = strlen(p);
3504                 if (cnt <= 0)
3505                         break;
3506                 if (strncmp(p, "quit", cnt) == 0
3507                  || strncmp(p, "q!", cnt) == 0   // delete lines
3508                 ) {
3509                         if (file_modified && p[1] != '!') {
3510                                 status_line_bold("No write since last change (:quit! overrides)");
3511                         } else {
3512                                 editing = 0;
3513                         }
3514                 } else if (strncmp(p, "write", cnt) == 0
3515                         || strncmp(p, "wq", cnt) == 0
3516                         || strncmp(p, "wn", cnt) == 0
3517                         || (p[0] == 'x' && !p[1])
3518                 ) {
3519                         cnt = file_write(current_filename, text, end - 1);
3520                         if (cnt < 0) {
3521                                 if (cnt == -1)
3522                                         status_line_bold("Write error: %s", strerror(errno));
3523                         } else {
3524                                 file_modified = 0;
3525                                 last_file_modified = -1;
3526                                 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3527                                 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3528                                  || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3529                                 ) {
3530                                         editing = 0;
3531                                 }
3532                         }
3533                 } else if (strncmp(p, "file", cnt) == 0) {
3534                         last_status_cksum = 0;  // force status update
3535                 } else if (sscanf(p, "%d", &j) > 0) {
3536                         dot = find_line(j);             // go to line # j
3537                         dot_skip_over_ws();
3538                 } else {                // unrecognized cmd
3539                         not_implemented(p);
3540                 }
3541 #endif /* !FEATURE_VI_COLON */
3542                 break;
3543         case '<':                       // <- Left  shift something
3544         case '>':                       // >- Right shift something
3545                 cnt = count_lines(text, dot);   // remember what line we are on
3546                 c1 = get_one_char();    // get the type of thing to delete
3547                 find_range(&p, &q, c1);
3548                 yank_delete(p, q, 1, YANKONLY); // save copy before change
3549                 p = begin_line(p);
3550                 q = end_line(q);
3551                 i = count_lines(p, q);  // # of lines we are shifting
3552                 for ( ; i > 0; i--, p = next_line(p)) {
3553                         if (c == '<') {
3554                                 // shift left- remove tab or 8 spaces
3555                                 if (*p == '\t') {
3556                                         // shrink buffer 1 char
3557                                         text_hole_delete(p, p);
3558                                 } else if (*p == ' ') {
3559                                         // we should be calculating columns, not just SPACE
3560                                         for (j = 0; *p == ' ' && j < tabstop; j++) {
3561                                                 text_hole_delete(p, p);
3562                                         }
3563                                 }
3564                         } else if (c == '>') {
3565                                 // shift right -- add tab or 8 spaces
3566                                 char_insert(p, '\t');
3567                         }
3568                 }
3569                 dot = find_line(cnt);   // what line were we on
3570                 dot_skip_over_ws();
3571                 end_cmd_q();    // stop adding to q
3572                 break;
3573         case 'A':                       // A- append at e-o-l
3574                 dot_end();              // go to e-o-l
3575                 //**** fall through to ... 'a'
3576         case 'a':                       // a- append after current char
3577                 if (*dot != '\n')
3578                         dot++;
3579                 goto dc_i;
3580                 break;
3581         case 'B':                       // B- back a blank-delimited Word
3582         case 'E':                       // E- end of a blank-delimited word
3583         case 'W':                       // W- forward a blank-delimited word
3584                 dir = FORWARD;
3585                 if (c == 'B')
3586                         dir = BACK;
3587                 do {
3588                         if (c == 'W' || isspace(dot[dir])) {
3589                                 dot = skip_thing(dot, 1, dir, S_TO_WS);
3590                                 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3591                         }
3592                         if (c != 'W')
3593                                 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3594                 } while (--cmdcnt > 0);
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 inserting
3646                 break;
3647         case 'J':                       // J- join current and next lines together
3648                 do {
3649                         dot_end();              // move to NL
3650                         if (dot < end - 1) {    // make sure not last char in text[]
3651                                 *dot++ = ' ';   // replace NL with space
3652                                 file_modified++;
3653                                 while (isblank(*dot)) { // delete leading WS
3654                                         dot_delete();
3655                                 }
3656                         }
3657                 } while (--cmdcnt > 0);
3658                 end_cmd_q();    // stop adding to q
3659                 break;
3660         case 'L':                       // L- goto bottom line on screen
3661                 dot = end_screen();
3662                 if (cmdcnt > (rows - 1)) {
3663                         cmdcnt = (rows - 1);
3664                 }
3665                 if (--cmdcnt > 0) {
3666                         do_cmd('-');
3667                 }
3668                 dot_begin();
3669                 dot_skip_over_ws();
3670                 break;
3671         case 'M':                       // M- goto middle line on screen
3672                 dot = screenbegin;
3673                 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3674                         dot = next_line(dot);
3675                 break;
3676         case 'O':                       // O- open a empty line above
3677                 //    0i\n ESC -i
3678                 p = begin_line(dot);
3679                 if (p[-1] == '\n') {
3680                         dot_prev();
3681         case 'o':                       // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3682                         dot_end();
3683                         dot = char_insert(dot, '\n');
3684                 } else {
3685                         dot_begin();    // 0
3686                         dot = char_insert(dot, '\n');   // i\n ESC
3687                         dot_prev();     // -
3688                 }
3689                 goto dc_i;
3690                 break;
3691         case 'R':                       // R- continuous Replace char
3692  dc5:
3693                 cmd_mode = 2;
3694                 break;
3695         case KEYCODE_DELETE:
3696                 c = 'x';
3697                 // fall through
3698         case 'X':                       // X- delete char before dot
3699         case 'x':                       // x- delete the current char
3700         case 's':                       // s- substitute the current char
3701                 dir = 0;
3702                 if (c == 'X')
3703                         dir = -1;
3704                 do {
3705                         if (dot[dir] != '\n') {
3706                                 if (c == 'X')
3707                                         dot--;  // delete prev char
3708                                 dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
3709                         }
3710                 } while (--cmdcnt > 0);
3711                 end_cmd_q();    // stop adding to q
3712                 if (c == 's')
3713                         goto dc_i;      // start inserting
3714                 break;
3715         case 'Z':                       // Z- if modified, {write}; exit
3716                 // ZZ means to save file (if necessary), then exit
3717                 c1 = get_one_char();
3718                 if (c1 != 'Z') {
3719                         indicate_error(c);
3720                         break;
3721                 }
3722                 if (file_modified) {
3723                         if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3724                                 status_line_bold("\"%s\" File is read only", current_filename);
3725                                 break;
3726                         }
3727                         cnt = file_write(current_filename, text, end - 1);
3728                         if (cnt < 0) {
3729                                 if (cnt == -1)
3730                                         status_line_bold("Write error: %s", strerror(errno));
3731                         } else if (cnt == (end - 1 - text + 1)) {
3732                                 editing = 0;
3733                         }
3734                 } else {
3735                         editing = 0;
3736                 }
3737                 break;
3738         case '^':                       // ^- move to first non-blank on line
3739                 dot_begin();
3740                 dot_skip_over_ws();
3741                 break;
3742         case 'b':                       // b- back a word
3743         case 'e':                       // e- end of word
3744                 dir = FORWARD;
3745                 if (c == 'b')
3746                         dir = BACK;
3747                 do {
3748                         if ((dot + dir) < text || (dot + dir) > end - 1)
3749                                 break;
3750                         dot += dir;
3751                         if (isspace(*dot)) {
3752                                 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3753                         }
3754                         if (isalnum(*dot) || *dot == '_') {
3755                                 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3756                         } else if (ispunct(*dot)) {
3757                                 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3758                         }
3759                 } while (--cmdcnt > 0);
3760                 break;
3761         case 'c':                       // c- change something
3762         case 'd':                       // d- delete something
3763 #if ENABLE_FEATURE_VI_YANKMARK
3764         case 'y':                       // y- yank   something
3765         case 'Y':                       // Y- Yank a line
3766 #endif
3767         {
3768                 int yf, ml, whole = 0;
3769                 yf = YANKDEL;   // assume either "c" or "d"
3770 #if ENABLE_FEATURE_VI_YANKMARK
3771                 if (c == 'y' || c == 'Y')
3772                         yf = YANKONLY;
3773 #endif
3774                 c1 = 'y';
3775                 if (c != 'Y')
3776                         c1 = get_one_char();    // get the type of thing to delete
3777                 // determine range, and whether it spans lines
3778                 ml = find_range(&p, &q, c1);
3779                 if (c1 == 27) { // ESC- user changed mind and wants out
3780                         c = c1 = 27;    // Escape- do nothing
3781                 } else if (strchr("wW", c1)) {
3782                         if (c == 'c') {
3783                                 // don't include trailing WS as part of word
3784                                 while (isblank(*q)) {
3785                                         if (q <= text || q[-1] == '\n')
3786                                                 break;
3787                                         q--;
3788                                 }
3789                         }
3790                         dot = yank_delete(p, q, ml, yf);        // delete word
3791                 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3792                         // partial line copy text into a register and delete
3793                         dot = yank_delete(p, q, ml, yf);        // delete word
3794                 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3795                         // whole line copy text into a register and delete
3796                         dot = yank_delete(p, q, ml, yf);        // delete lines
3797                         whole = 1;
3798                 } else {
3799                         // could not recognize object
3800                         c = c1 = 27;    // error-
3801                         ml = 0;
3802                         indicate_error(c);
3803                 }
3804                 if (ml && whole) {
3805                         if (c == 'c') {
3806                                 dot = char_insert(dot, '\n');
3807                                 // on the last line of file don't move to prev line
3808                                 if (whole && dot != (end-1)) {
3809                                         dot_prev();
3810                                 }
3811                         } else if (c == 'd') {
3812                                 dot_begin();
3813                                 dot_skip_over_ws();
3814                         }
3815                 }
3816                 if (c1 != 27) {
3817                         // if CHANGING, not deleting, start inserting after the delete
3818                         if (c == 'c') {
3819                                 strcpy(buf, "Change");
3820                                 goto dc_i;      // start inserting
3821                         }
3822                         if (c == 'd') {
3823                                 strcpy(buf, "Delete");
3824                         }
3825 #if ENABLE_FEATURE_VI_YANKMARK
3826                         if (c == 'y' || c == 'Y') {
3827                                 strcpy(buf, "Yank");
3828                         }
3829                         p = reg[YDreg];
3830                         q = p + strlen(p);
3831                         for (cnt = 0; p <= q; p++) {
3832                                 if (*p == '\n')
3833                                         cnt++;
3834                         }
3835                         status_line("%s %d lines (%d chars) using [%c]",
3836                                 buf, cnt, strlen(reg[YDreg]), what_reg());
3837 #endif
3838                         end_cmd_q();    // stop adding to q
3839                 }
3840                 break;
3841         }
3842         case 'k':                       // k- goto prev line, same col
3843         case KEYCODE_UP:                // cursor key Up
3844                 do {
3845                         dot_prev();
3846                         dot = move_to_col(dot, ccol + offset);  // try stay in same col
3847                 } while (--cmdcnt > 0);
3848                 break;
3849         case 'r':                       // r- replace the current char with user input
3850                 c1 = get_one_char();    // get the replacement char
3851                 if (*dot != '\n') {
3852                         *dot = c1;
3853                         file_modified++;
3854                 }
3855                 end_cmd_q();    // stop adding to q
3856                 break;
3857         case 't':                       // t- move to char prior to next x
3858                 last_forward_char = get_one_char();
3859                 do_cmd(';');
3860                 if (*dot == last_forward_char)
3861                         dot_left();
3862                 last_forward_char = 0;
3863                 break;
3864         case 'w':                       // w- forward a word
3865                 do {
3866                         if (isalnum(*dot) || *dot == '_') {     // we are on ALNUM
3867                                 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3868                         } else if (ispunct(*dot)) {     // we are on PUNCT
3869                                 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3870                         }
3871                         if (dot < end - 1)
3872                                 dot++;          // move over word
3873                         if (isspace(*dot)) {
3874                                 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3875                         }
3876                 } while (--cmdcnt > 0);
3877                 break;
3878         case 'z':                       // z-
3879                 c1 = get_one_char();    // get the replacement char
3880                 cnt = 0;
3881                 if (c1 == '.')
3882                         cnt = (rows - 2) / 2;   // put dot at center
3883                 if (c1 == '-')
3884                         cnt = rows - 2; // put dot at bottom
3885                 screenbegin = begin_line(dot);  // start dot at top
3886                 dot_scroll(cnt, -1);
3887                 break;
3888         case '|':                       // |- move to column "cmdcnt"
3889                 dot = move_to_col(dot, cmdcnt - 1);     // try to move to column
3890                 break;
3891         case '~':                       // ~- flip the case of letters   a-z -> A-Z
3892                 do {
3893                         if (islower(*dot)) {
3894                                 *dot = toupper(*dot);
3895                                 file_modified++;
3896                         } else if (isupper(*dot)) {
3897                                 *dot = tolower(*dot);
3898                                 file_modified++;
3899                         }
3900                         dot_right();
3901                 } while (--cmdcnt > 0);
3902                 end_cmd_q();    // stop adding to q
3903                 break;
3904                 //----- The Cursor and Function Keys -----------------------------
3905         case KEYCODE_HOME:      // Cursor Key Home
3906                 dot_begin();
3907                 break;
3908                 // The Fn keys could point to do_macro which could translate them
3909 #if 0
3910         case KEYCODE_FUN1:      // Function Key F1
3911         case KEYCODE_FUN2:      // Function Key F2
3912         case KEYCODE_FUN3:      // Function Key F3
3913         case KEYCODE_FUN4:      // Function Key F4
3914         case KEYCODE_FUN5:      // Function Key F5
3915         case KEYCODE_FUN6:      // Function Key F6
3916         case KEYCODE_FUN7:      // Function Key F7
3917         case KEYCODE_FUN8:      // Function Key F8
3918         case KEYCODE_FUN9:      // Function Key F9
3919         case KEYCODE_FUN10:     // Function Key F10
3920         case KEYCODE_FUN11:     // Function Key F11
3921         case KEYCODE_FUN12:     // Function Key F12
3922                 break;
3923 #endif
3924         }
3925
3926  dc1:
3927         // if text[] just became empty, add back an empty line
3928         if (end == text) {
3929                 char_insert(text, '\n');        // start empty buf with dummy line
3930                 dot = text;
3931         }
3932         // it is OK for dot to exactly equal to end, otherwise check dot validity
3933         if (dot != end) {
3934                 dot = bound_dot(dot);   // make sure "dot" is valid
3935         }
3936 #if ENABLE_FEATURE_VI_YANKMARK
3937         check_context(c);       // update the current context
3938 #endif
3939
3940         if (!isdigit(c))
3941                 cmdcnt = 0;             // cmd was not a number, reset cmdcnt
3942         cnt = dot - begin_line(dot);
3943         // Try to stay off of the Newline
3944         if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3945                 dot--;
3946 }
3947
3948 /* NB!  the CRASHME code is unmaintained, and doesn't currently build */
3949 #if ENABLE_FEATURE_VI_CRASHME
3950 static int totalcmds = 0;
3951 static int Mp = 85;             // Movement command Probability
3952 static int Np = 90;             // Non-movement command Probability
3953 static int Dp = 96;             // Delete command Probability
3954 static int Ip = 97;             // Insert command Probability
3955 static int Yp = 98;             // Yank command Probability
3956 static int Pp = 99;             // Put command Probability
3957 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3958 static const char chars[20] = "\t012345 abcdABCD-=.$";
3959 static const char *const words[20] = {
3960         "this", "is", "a", "test",
3961         "broadcast", "the", "emergency", "of",
3962         "system", "quick", "brown", "fox",
3963         "jumped", "over", "lazy", "dogs",
3964         "back", "January", "Febuary", "March"
3965 };
3966 static const char *const lines[20] = {
3967         "You should have received a copy of the GNU General Public License\n",
3968         "char c, cm, *cmd, *cmd1;\n",
3969         "generate a command by percentages\n",
3970         "Numbers may be typed as a prefix to some commands.\n",
3971         "Quit, discarding changes!\n",
3972         "Forced write, if permission originally not valid.\n",
3973         "In general, any ex or ed command (such as substitute or delete).\n",
3974         "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3975         "Please get w/ me and I will go over it with you.\n",
3976         "The following is a list of scheduled, committed changes.\n",
3977         "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3978         "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3979         "Any question about transactions please contact Sterling Huxley.\n",
3980         "I will try to get back to you by Friday, December 31.\n",
3981         "This Change will be implemented on Friday.\n",
3982         "Let me know if you have problems accessing this;\n",
3983         "Sterling Huxley recently added you to the access list.\n",
3984         "Would you like to go to lunch?\n",
3985         "The last command will be automatically run.\n",
3986         "This is too much english for a computer geek.\n",
3987 };
3988 static char *multilines[20] = {
3989         "You should have received a copy of the GNU General Public License\n",
3990         "char c, cm, *cmd, *cmd1;\n",
3991         "generate a command by percentages\n",
3992         "Numbers may be typed as a prefix to some commands.\n",
3993         "Quit, discarding changes!\n",
3994         "Forced write, if permission originally not valid.\n",
3995         "In general, any ex or ed command (such as substitute or delete).\n",
3996         "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3997         "Please get w/ me and I will go over it with you.\n",
3998         "The following is a list of scheduled, committed changes.\n",
3999         "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4000         "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4001         "Any question about transactions please contact Sterling Huxley.\n",
4002         "I will try to get back to you by Friday, December 31.\n",
4003         "This Change will be implemented on Friday.\n",
4004         "Let me know if you have problems accessing this;\n",
4005         "Sterling Huxley recently added you to the access list.\n",
4006         "Would you like to go to lunch?\n",
4007         "The last command will be automatically run.\n",
4008         "This is too much english for a computer geek.\n",
4009 };
4010
4011 // create a random command to execute
4012 static void crash_dummy()
4013 {
4014         static int sleeptime;   // how long to pause between commands
4015         char c, cm, *cmd, *cmd1;
4016         int i, cnt, thing, rbi, startrbi, percent;
4017
4018         // "dot" movement commands
4019         cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4020
4021         // is there already a command running?
4022         if (readbuffer[0] > 0)
4023                 goto cd1;
4024  cd0:
4025         readbuffer[0] = 'X';
4026         startrbi = rbi = 1;
4027         sleeptime = 0;          // how long to pause between commands
4028         memset(readbuffer, '\0', sizeof(readbuffer));
4029         // generate a command by percentages
4030         percent = (int) lrand48() % 100;        // get a number from 0-99
4031         if (percent < Mp) {     //  Movement commands
4032                 // available commands
4033                 cmd = cmd1;
4034                 M++;
4035         } else if (percent < Np) {      //  non-movement commands
4036                 cmd = "mz<>\'\"";       // available commands
4037                 N++;
4038         } else if (percent < Dp) {      //  Delete commands
4039                 cmd = "dx";             // available commands
4040                 D++;
4041         } else if (percent < Ip) {      //  Inset commands
4042                 cmd = "iIaAsrJ";        // available commands
4043                 I++;
4044         } else if (percent < Yp) {      //  Yank commands
4045                 cmd = "yY";             // available commands
4046                 Y++;
4047         } else if (percent < Pp) {      //  Put commands
4048                 cmd = "pP";             // available commands
4049                 P++;
4050         } else {
4051                 // We do not know how to handle this command, try again
4052                 U++;
4053                 goto cd0;
4054         }
4055         // randomly pick one of the available cmds from "cmd[]"
4056         i = (int) lrand48() % strlen(cmd);
4057         cm = cmd[i];
4058         if (strchr(":\024", cm))
4059                 goto cd0;               // dont allow colon or ctrl-T commands
4060         readbuffer[rbi++] = cm; // put cmd into input buffer
4061
4062         // now we have the command-
4063         // there are 1, 2, and multi char commands
4064         // find out which and generate the rest of command as necessary
4065         if (strchr("dmryz<>\'\"", cm)) {        // 2-char commands
4066                 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4067                 if (cm == 'm' || cm == '\'' || cm == '\"') {    // pick a reg[]
4068                         cmd1 = "abcdefghijklmnopqrstuvwxyz";
4069                 }
4070                 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4071                 c = cmd1[thing];
4072                 readbuffer[rbi++] = c;  // add movement to input buffer
4073         }
4074         if (strchr("iIaAsc", cm)) {     // multi-char commands
4075                 if (cm == 'c') {
4076                         // change some thing
4077                         thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4078                         c = cmd1[thing];
4079                         readbuffer[rbi++] = c;  // add movement to input buffer
4080                 }
4081                 thing = (int) lrand48() % 4;    // what thing to insert
4082                 cnt = (int) lrand48() % 10;     // how many to insert
4083                 for (i = 0; i < cnt; i++) {
4084                         if (thing == 0) {       // insert chars
4085                                 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4086                         } else if (thing == 1) {        // insert words
4087                                 strcat(readbuffer, words[(int) lrand48() % 20]);
4088                                 strcat(readbuffer, " ");
4089                                 sleeptime = 0;  // how fast to type
4090                         } else if (thing == 2) {        // insert lines
4091                                 strcat(readbuffer, lines[(int) lrand48() % 20]);
4092                                 sleeptime = 0;  // how fast to type
4093                         } else {        // insert multi-lines
4094                                 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4095                                 sleeptime = 0;  // how fast to type
4096                         }
4097                 }
4098                 strcat(readbuffer, "\033");
4099         }
4100         readbuffer[0] = strlen(readbuffer + 1);
4101  cd1:
4102         totalcmds++;
4103         if (sleeptime > 0)
4104                 mysleep(sleeptime);      // sleep 1/100 sec
4105 }
4106
4107 // test to see if there are any errors
4108 static void crash_test()
4109 {
4110         static time_t oldtim;
4111
4112         time_t tim;
4113         char d[2], msg[80];
4114
4115         msg[0] = '\0';
4116         if (end < text) {
4117                 strcat(msg, "end<text ");
4118         }
4119         if (end > textend) {
4120                 strcat(msg, "end>textend ");
4121         }
4122         if (dot < text) {
4123                 strcat(msg, "dot<text ");
4124         }
4125         if (dot > end) {
4126                 strcat(msg, "dot>end ");
4127         }
4128         if (screenbegin < text) {
4129                 strcat(msg, "screenbegin<text ");
4130         }
4131         if (screenbegin > end - 1) {
4132                 strcat(msg, "screenbegin>end-1 ");
4133         }
4134
4135         if (msg[0]) {
4136                 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4137                         totalcmds, last_input_char, msg, SOs, SOn);
4138                 fflush_all();
4139                 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4140                         if (d[0] == '\n' || d[0] == '\r')
4141                                 break;
4142                 }
4143         }
4144         tim = time(NULL);
4145         if (tim >= (oldtim + 3)) {
4146                 sprintf(status_buffer,
4147                                 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4148                                 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4149                 oldtim = tim;
4150         }
4151 }
4152 #endif