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