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