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