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