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