ash: s/int/smallint/. -60 bytes.
[oweals/busybox.git] / editors / vi.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * tiny vi.c: A small 'vi' clone
4  * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
5  *
6  * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
7  */
8
9 /*
10  * Things To Do:
11  *      EXINIT
12  *      $HOME/.exrc  and  ./.exrc
13  *      add magic to search     /foo.*bar
14  *      add :help command
15  *      :map macros
16  *      if mark[] values were line numbers rather than pointers
17  *         it would be easier to change the mark when add/delete lines
18  *      More intelligence in refresh()
19  *      ":r !cmd"  and  "!cmd"  to filter text through an external command
20  *      A true "undo" facility
21  *      An "ex" line oriented mode- maybe using "cmdedit"
22  */
23
24 #include "libbb.h"
25
26 #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);
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 | VI_ERR_METHOD;
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                                 putchar('^');
872                                 if (c == 127)
873                                         c = '?';
874                                 else
875                                         c += '@';
876                         }
877                         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, 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 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;
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                 {"[15~", VI_K_FUN5},   // Function Key F5
2182                 {"[17~", VI_K_FUN6},   // Function Key F6
2183                 {"[18~", VI_K_FUN7},   // Function Key F7
2184                 {"[19~", VI_K_FUN8},   // Function Key F8
2185                 {"[20~", VI_K_FUN9},   // Function Key F9
2186                 {"[21~", VI_K_FUN10},  // Function Key F10
2187                 {"[23~", VI_K_FUN11},  // Function Key F11
2188                 {"[24~", VI_K_FUN12},  // Function Key F12
2189                 {"[11~", VI_K_FUN1},   // Function Key F1
2190                 {"[12~", VI_K_FUN2},   // Function Key F2
2191                 {"[13~", VI_K_FUN3},   // Function Key F3
2192                 {"[14~", VI_K_FUN4},   // Function Key F4
2193         };
2194         enum { ESCCMDS_COUNT = ARRAY_SIZE(esccmds) };
2195
2196         alarm(0);       // turn alarm OFF while we wait for input
2197         fflush(stdout);
2198         n = readed_for_parse;
2199         // get input from User- are there already input chars in Q?
2200         if (n <= 0) {
2201  ri0:
2202                 // the Q is empty, wait for a typed char
2203                 n = read(0, readbuffer, MAX_LINELEN - 1);
2204                 if (n < 0) {
2205                         if (errno == EINTR)
2206                                 goto ri0;       // interrupted sys call
2207                         if (errno == EBADF || errno == EFAULT || errno == EINVAL
2208                                         || errno == EIO)
2209                                 editing = 0;
2210                         errno = 0;
2211                 }
2212                 if (n <= 0)
2213                         return 0;       // error
2214                 if (readbuffer[0] == 27) {
2215                         // This is an ESC char. Is this Esc sequence?
2216                         // Could be bare Esc key. See if there are any
2217                         // more chars to read after the ESC. This would
2218                         // be a Function or Cursor Key sequence.
2219                         struct pollfd pfd[1];
2220                         pfd[0].fd = 0;
2221                         pfd[0].events = POLLIN;
2222                         // Wait 50 ms
2223                         // keep reading while there are input chars and room in buffer
2224                         while (poll(pfd, 1, 50) > 0 && n <= (MAX_LINELEN - 5)) {
2225                                 // read the rest of the ESC string
2226                                 int r = read(0, readbuffer + n, MAX_LINELEN - n);
2227                                 if (r > 0)
2228                                         n += r;
2229                         }
2230                 }
2231                 readed_for_parse = n;
2232         }
2233         c = readbuffer[0];
2234         if (c == 27 && n > 1) {
2235                 // Maybe cursor or function key?
2236                 const struct esc_cmds *eindex;
2237
2238                 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2239                         int cnt = strlen(eindex->seq);
2240
2241                         if (n <= cnt)
2242                                 continue;
2243                         if (strncmp(eindex->seq, readbuffer + 1, cnt))
2244                                 continue;
2245                         // is a Cursor key- put derived value back into Q
2246                         c = eindex->val;
2247                         // for squeeze out the ESC sequence
2248                         n = cnt + 1;
2249                         break;
2250                 }
2251                 if (eindex == &esccmds[ESCCMDS_COUNT]) {
2252                         /* defined ESC sequence not found, set only one ESC */
2253                         n = 1;
2254         }
2255         } else {
2256                 n = 1;
2257         }
2258         // remove key sequence from Q
2259         readed_for_parse -= n;
2260         memmove(readbuffer, readbuffer + n, MAX_LINELEN - n);
2261         alarm(3);       // we are done waiting for input, turn alarm ON
2262         return c;
2263 }
2264
2265 //----- IO Routines --------------------------------------------
2266 static char get_one_char(void)
2267 {
2268         static char c;
2269
2270 #if ENABLE_FEATURE_VI_DOT_CMD
2271         // ! adding2q  && ioq == 0  read()
2272         // ! adding2q  && ioq != 0  *ioq
2273         // adding2q         *last_modifying_cmd= read()
2274         if (!adding2q) {
2275                 // we are not adding to the q.
2276                 // but, we may be reading from a q
2277                 if (ioq == 0) {
2278                         // there is no current q, read from STDIN
2279                         c = readit();   // get the users input
2280                 } else {
2281                         // there is a queue to get chars from first
2282                         c = *ioq++;
2283                         if (c == '\0') {
2284                                 // the end of the q, read from STDIN
2285                                 free(ioq_start);
2286                                 ioq_start = ioq = 0;
2287                                 c = readit();   // get the users input
2288                         }
2289                 }
2290         } else {
2291                 // adding STDIN chars to q
2292                 c = readit();   // get the users input
2293                 if (last_modifying_cmd != 0) {
2294                         int len = strlen(last_modifying_cmd);
2295                         if (len >= MAX_LINELEN - 1) {
2296                                 psbs("last_modifying_cmd overrun");
2297                         } else {
2298                                 // add new char to q
2299                                 last_modifying_cmd[len] = c;
2300                         }
2301                 }
2302         }
2303 #else
2304         c = readit();           // get the users input
2305 #endif /* FEATURE_VI_DOT_CMD */
2306         return c;                       // return the char, where ever it came from
2307 }
2308
2309 static char *get_input_line(const char * prompt) // get input line- use "status line"
2310 {
2311         static char *obufp;
2312
2313         char buf[MAX_LINELEN];
2314         char c;
2315         int i;
2316
2317         strcpy(buf, prompt);
2318         last_status_cksum = 0;  // force status update
2319         place_cursor(rows - 1, 0, FALSE);       // go to Status line, bottom of screen
2320         clear_to_eol();         // clear the line
2321         write1(prompt);      // write out the :, /, or ? prompt
2322
2323         i = strlen(buf);
2324         while (i < MAX_LINELEN) {
2325                 c = get_one_char();     // read user input
2326                 if (c == '\n' || c == '\r' || c == 27)
2327                         break;          // is this end of input
2328                 if (c == erase_char || c == 8 || c == 127) {
2329                         // user wants to erase prev char
2330                         i--;            // backup to prev char
2331                         buf[i] = '\0';  // erase the char
2332                         buf[i + 1] = '\0';      // null terminate buffer
2333                         write1("\b \b");     // erase char on screen
2334                         if (i <= 0) {   // user backs up before b-o-l, exit
2335                                 break;
2336                         }
2337                 } else {
2338                         buf[i] = c;     // save char in buffer
2339                         buf[i + 1] = '\0';      // make sure buffer is null terminated
2340                         putchar(c);   // echo the char back to user
2341                         i++;
2342                 }
2343         }
2344         refresh(FALSE);
2345         free(obufp);
2346         obufp = xstrdup(buf);
2347         return obufp;
2348 }
2349
2350 static int file_size(const char *fn) // what is the byte size of "fn"
2351 {
2352         struct stat st_buf;
2353         int cnt;
2354
2355         cnt = -1;
2356         if (fn && fn[0] && stat(fn, &st_buf) == 0)      // see if file exists
2357                 cnt = (int) st_buf.st_size;
2358         return cnt;
2359 }
2360
2361 static int file_insert(const char * fn, char *p
2362                 USE_FEATURE_VI_READONLY(, int update_ro_status))
2363 {
2364         int cnt = -1;
2365         int fd, size;
2366         struct stat statbuf;
2367
2368         /* Validate file */
2369         if (stat(fn, &statbuf) < 0) {
2370                 psbs("\"%s\" %s", fn, strerror(errno));
2371                 goto fi0;
2372         }
2373         if ((statbuf.st_mode & S_IFREG) == 0) {
2374                 // This is not a regular file
2375                 psbs("\"%s\" Not a regular file", fn);
2376                 goto fi0;
2377         }
2378         /* // this check is done by open()
2379         if ((statbuf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
2380                 // dont have any read permissions
2381                 psbs("\"%s\" Not readable", fn);
2382                 goto fi0;
2383         }
2384         */
2385         if (p < text || p > end) {
2386                 psbs("Trying to insert file outside of memory");
2387                 goto fi0;
2388         }
2389
2390         // read file to buffer
2391         fd = open(fn, O_RDONLY);
2392         if (fd < 0) {
2393                 psbs("\"%s\" %s", fn, strerror(errno));
2394                 goto fi0;
2395         }
2396         size = statbuf.st_size;
2397         p = text_hole_make(p, size);
2398         if (p == NULL)
2399                 goto fi0;
2400         cnt = read(fd, p, size);
2401         if (cnt < 0) {
2402                 psbs("\"%s\" %s", fn, strerror(errno));
2403                 p = text_hole_delete(p, p + size - 1);  // un-do buffer insert
2404         } else if (cnt < size) {
2405                 // There was a partial read, shrink unused space text[]
2406                 p = text_hole_delete(p + cnt, p + (size - cnt) - 1);    // un-do buffer insert
2407                 psbs("cannot read all of file \"%s\"", fn);
2408         }
2409         if (cnt >= size)
2410                 file_modified++;
2411         close(fd);
2412  fi0:
2413 #if ENABLE_FEATURE_VI_READONLY
2414         if (update_ro_status
2415          && ((access(fn, W_OK) < 0) ||
2416                 /* root will always have access()
2417                  * so we check fileperms too */
2418                 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2419             )
2420         ) {
2421                 SET_READONLY_FILE(readonly_mode);
2422         }
2423 #endif
2424         return cnt;
2425 }
2426
2427
2428 static int file_write(char * fn, char * first, char * last)
2429 {
2430         int fd, cnt, charcnt;
2431
2432         if (fn == 0) {
2433                 psbs("No current filename");
2434                 return -2;
2435         }
2436         charcnt = 0;
2437         // FIXIT- use the correct umask()
2438         fd = open(fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2439         if (fd < 0)
2440                 return -1;
2441         cnt = last - first + 1;
2442         charcnt = write(fd, first, cnt);
2443         if (charcnt == cnt) {
2444                 // good write
2445                 //file_modified = FALSE; // the file has not been modified
2446         } else {
2447                 charcnt = 0;
2448         }
2449         close(fd);
2450         return charcnt;
2451 }
2452
2453 //----- Terminal Drawing ---------------------------------------
2454 // The terminal is made up of 'rows' line of 'columns' columns.
2455 // classically this would be 24 x 80.
2456 //  screen coordinates
2457 //  0,0     ...     0,79
2458 //  1,0     ...     1,79
2459 //  .       ...     .
2460 //  .       ...     .
2461 //  22,0    ...     22,79
2462 //  23,0    ...     23,79   status line
2463 //
2464
2465 //----- Move the cursor to row x col (count from 0, not 1) -------
2466 static void place_cursor(int row, int col, int opti)
2467 {
2468         char cm1[MAX_LINELEN];
2469         char *cm;
2470 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2471         char cm2[MAX_LINELEN];
2472         char *screenp;
2473         // char cm3[MAX_LINELEN];
2474         int Rrow = last_row;
2475 #endif
2476
2477         memset(cm1, '\0', MAX_LINELEN);  // clear the buffer
2478
2479         if (row < 0) row = 0;
2480         if (row >= rows) row = rows - 1;
2481         if (col < 0) col = 0;
2482         if (col >= columns) col = columns - 1;
2483
2484         //----- 1.  Try the standard terminal ESC sequence
2485         sprintf(cm1, CMrc, row + 1, col + 1);
2486         cm = cm1;
2487         if (!opti)
2488                 goto pc0;
2489
2490 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2491         //----- find the minimum # of chars to move cursor -------------
2492         //----- 2.  Try moving with discreet chars (Newline, [back]space, ...)
2493         memset(cm2, '\0', MAX_LINELEN);  // clear the buffer
2494
2495         // move to the correct row
2496         while (row < Rrow) {
2497                 // the cursor has to move up
2498                 strcat(cm2, CMup);
2499                 Rrow--;
2500         }
2501         while (row > Rrow) {
2502                 // the cursor has to move down
2503                 strcat(cm2, CMdown);
2504                 Rrow++;
2505         }
2506
2507         // now move to the correct column
2508         strcat(cm2, "\r");                      // start at col 0
2509         // just send out orignal source char to get to correct place
2510         screenp = &screen[row * columns];       // start of screen line
2511         strncat(cm2, screenp, col);
2512
2513         //----- 3.  Try some other way of moving cursor
2514         //---------------------------------------------
2515
2516         // pick the shortest cursor motion to send out
2517         cm = cm1;
2518         if (strlen(cm2) < strlen(cm)) {
2519                 cm = cm2;
2520         }  /* else if (strlen(cm3) < strlen(cm)) {
2521                 cm= cm3;
2522         } */
2523 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2524  pc0:
2525         write1(cm);                 // move the cursor
2526 }
2527
2528 //----- Erase from cursor to end of line -----------------------
2529 static void clear_to_eol(void)
2530 {
2531         write1(Ceol);   // Erase from cursor to end of line
2532 }
2533
2534 //----- Erase from cursor to end of screen -----------------------
2535 static void clear_to_eos(void)
2536 {
2537         write1(Ceos);   // Erase from cursor to end of screen
2538 }
2539
2540 //----- Start standout mode ------------------------------------
2541 static void standout_start(void) // send "start reverse video" sequence
2542 {
2543         write1(SOs);     // Start reverse video mode
2544 }
2545
2546 //----- End standout mode --------------------------------------
2547 static void standout_end(void) // send "end reverse video" sequence
2548 {
2549         write1(SOn);     // End reverse video mode
2550 }
2551
2552 //----- Flash the screen  --------------------------------------
2553 static void flash(int h)
2554 {
2555         standout_start();       // send "start reverse video" sequence
2556         redraw(TRUE);
2557         mysleep(h);
2558         standout_end();         // send "end reverse video" sequence
2559         redraw(TRUE);
2560 }
2561
2562 static void Indicate_Error(void)
2563 {
2564 #if ENABLE_FEATURE_VI_CRASHME
2565         if (crashme > 0)
2566                 return;                 // generate a random command
2567 #endif
2568         if (!err_method) {
2569                 write1(bell);   // send out a bell character
2570         } else {
2571                 flash(10);
2572         }
2573 }
2574
2575 //----- Screen[] Routines --------------------------------------
2576 //----- Erase the Screen[] memory ------------------------------
2577 static void screen_erase(void)
2578 {
2579         memset(screen, ' ', screensize);        // clear new screen
2580 }
2581
2582 static int bufsum(char *buf, int count)
2583 {
2584         int sum = 0;
2585         char *e = buf + count;
2586
2587         while (buf < e)
2588                 sum += (unsigned char) *buf++;
2589         return sum;
2590 }
2591
2592 //----- Draw the status line at bottom of the screen -------------
2593 static void show_status_line(void)
2594 {
2595         int cnt = 0, cksum = 0;
2596
2597         // either we already have an error or status message, or we
2598         // create one.
2599         if (!have_status_msg) {
2600                 cnt = format_edit_status();
2601                 cksum = bufsum(status_buffer, cnt);
2602         }
2603         if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2604                 last_status_cksum= cksum;               // remember if we have seen this line
2605                 place_cursor(rows - 1, 0, FALSE);       // put cursor on status line
2606                 write1(status_buffer);
2607                 clear_to_eol();
2608                 if (have_status_msg) {
2609                         if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2610                                         (columns - 1) ) {
2611                                 have_status_msg = 0;
2612                                 Hit_Return();
2613                         }
2614                         have_status_msg = 0;
2615                 }
2616                 place_cursor(crow, ccol, FALSE);        // put cursor back in correct place
2617         }
2618         fflush(stdout);
2619 }
2620
2621 //----- format the status buffer, the bottom line of screen ------
2622 // format status buffer, with STANDOUT mode
2623 static void psbs(const char *format, ...)
2624 {
2625         va_list args;
2626
2627         va_start(args, format);
2628         strcpy(status_buffer, SOs);     // Terminal standout mode on
2629         vsprintf(status_buffer + strlen(status_buffer), format, args);
2630         strcat(status_buffer, SOn);     // Terminal standout mode off
2631         va_end(args);
2632
2633         have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2634 }
2635
2636 // format status buffer
2637 static void psb(const char *format, ...)
2638 {
2639         va_list args;
2640
2641         va_start(args, format);
2642         vsprintf(status_buffer, format, args);
2643         va_end(args);
2644
2645         have_status_msg = 1;
2646 }
2647
2648 static void ni(const char * s) // display messages
2649 {
2650         char buf[MAX_LINELEN];
2651
2652         print_literal(buf, s);
2653         psbs("\'%s\' is not implemented", buf);
2654 }
2655
2656 static int format_edit_status(void)     // show file status on status line
2657 {
2658         static int tot;
2659         static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2660         int cur, percent, ret, trunc_at;
2661
2662         // file_modified is now a counter rather than a flag.  this
2663         // helps reduce the amount of line counting we need to do.
2664         // (this will cause a mis-reporting of modified status
2665         // once every MAXINT editing operations.)
2666
2667         // it would be nice to do a similar optimization here -- if
2668         // we haven't done a motion that could have changed which line
2669         // we're on, then we shouldn't have to do this count_lines()
2670         cur = count_lines(text, dot);
2671
2672         // reduce counting -- the total lines can't have
2673         // changed if we haven't done any edits.
2674         if (file_modified != last_file_modified) {
2675                 tot = cur + count_lines(dot, end - 1) - 1;
2676                 last_file_modified = file_modified;
2677         }
2678
2679         //    current line         percent
2680         //   -------------    ~~ ----------
2681         //    total lines            100
2682         if (tot > 0) {
2683                 percent = (100 * cur) / tot;
2684         } else {
2685                 cur = tot = 0;
2686                 percent = 100;
2687         }
2688
2689         trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2690                 columns : STATUS_BUFFER_LEN-1;
2691
2692         ret = snprintf(status_buffer, trunc_at+1,
2693 #if ENABLE_FEATURE_VI_READONLY
2694                 "%c %s%s%s %d/%d %d%%",
2695 #else
2696                 "%c %s%s %d/%d %d%%",
2697 #endif
2698                 cmd_mode_indicator[cmd_mode & 3],
2699                 (current_filename != NULL ? current_filename : "No file"),
2700 #if ENABLE_FEATURE_VI_READONLY
2701                 (readonly_mode ? " [Readonly]" : ""),
2702 #endif
2703                 (file_modified ? " [Modified]" : ""),
2704                 cur, tot, percent);
2705
2706         if (ret >= 0 && ret < trunc_at)
2707                 return ret;  /* it all fit */
2708
2709         return trunc_at;  /* had to truncate */
2710 }
2711
2712 //----- Force refresh of all Lines -----------------------------
2713 static void redraw(int full_screen)
2714 {
2715         place_cursor(0, 0, FALSE);      // put cursor in correct place
2716         clear_to_eos();         // tel terminal to erase display
2717         screen_erase();         // erase the internal screen buffer
2718         last_status_cksum = 0;  // force status update
2719         refresh(full_screen);   // this will redraw the entire display
2720         show_status_line();
2721 }
2722
2723 //----- Format a text[] line into a buffer ---------------------
2724 static void format_line(char *dest, char *src, int li)
2725 {
2726         int co;
2727         char c;
2728
2729         for (co = 0; co < MAX_SCR_COLS; co++) {
2730                 c = ' ';                // assume blank
2731                 if (li > 0 && co == 0) {
2732                         c = '~';        // not first line, assume Tilde
2733                 }
2734                 // are there chars in text[] and have we gone past the end
2735                 if (text < end && src < end) {
2736                         c = *src++;
2737                 }
2738                 if (c == '\n')
2739                         break;
2740                 if ((c & 0x80) && !Isprint(c)) {
2741                         c = '.';
2742                 }
2743                 if ((unsigned char)(c) < ' ' || c == 0x7f) {
2744                         if (c == '\t') {
2745                                 c = ' ';
2746                                 //       co %    8     !=     7
2747                                 for (; (co % tabstop) != (tabstop - 1); co++) {
2748                                         dest[co] = c;
2749                                 }
2750                         } else {
2751                                 dest[co++] = '^';
2752                                 if (c == 0x7f)
2753                                         c = '?';
2754                                 else
2755                                         c += '@';       // make it visible
2756                         }
2757                 }
2758                 // the co++ is done here so that the column will
2759                 // not be overwritten when we blank-out the rest of line
2760                 dest[co] = c;
2761                 if (src >= end)
2762                         break;
2763         }
2764 }
2765
2766 //----- Refresh the changed screen lines -----------------------
2767 // Copy the source line from text[] into the buffer and note
2768 // if the current screenline is different from the new buffer.
2769 // If they differ then that line needs redrawing on the terminal.
2770 //
2771 static void refresh(int full_screen)
2772 {
2773         static int old_offset;
2774
2775         int li, changed;
2776         char buf[MAX_SCR_COLS];
2777         char *tp, *sp;          // pointer into text[] and screen[]
2778 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2779         int last_li = -2; // last line that changed- for optimizing cursor movement
2780 #endif
2781
2782         if (ENABLE_FEATURE_VI_WIN_RESIZE)
2783                 get_terminal_width_height(0, &columns, &rows);
2784         sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2785         tp = screenbegin;       // index into text[] of top line
2786
2787         // compare text[] to screen[] and mark screen[] lines that need updating
2788         for (li = 0; li < rows - 1; li++) {
2789                 int cs, ce;                             // column start & end
2790                 memset(buf, ' ', MAX_SCR_COLS);         // blank-out the buffer
2791                 buf[MAX_SCR_COLS-1] = 0;                // NULL terminate the buffer
2792                 // format current text line into buf
2793                 format_line(buf, tp, li);
2794
2795                 // skip to the end of the current text[] line
2796                 while (tp < end && *tp++ != '\n') /*no-op*/;
2797
2798                 // see if there are any changes between vitual screen and buf
2799                 changed = FALSE;        // assume no change
2800                 cs= 0;
2801                 ce= columns-1;
2802                 sp = &screen[li * columns];     // start of screen line
2803                 if (full_screen) {
2804                         // force re-draw of every single column from 0 - columns-1
2805                         goto re0;
2806                 }
2807                 // compare newly formatted buffer with virtual screen
2808                 // look forward for first difference between buf and screen
2809                 for (; cs <= ce; cs++) {
2810                         if (buf[cs + offset] != sp[cs]) {
2811                                 changed = TRUE; // mark for redraw
2812                                 break;
2813                         }
2814                 }
2815
2816                 // look backward for last difference between buf and screen
2817                 for ( ; ce >= cs; ce--) {
2818                         if (buf[ce + offset] != sp[ce]) {
2819                                 changed = TRUE; // mark for redraw
2820                                 break;
2821                         }
2822                 }
2823                 // now, cs is index of first diff, and ce is index of last diff
2824
2825                 // if horz offset has changed, force a redraw
2826                 if (offset != old_offset) {
2827  re0:
2828                         changed = TRUE;
2829                 }
2830
2831                 // make a sanity check of columns indexes
2832                 if (cs < 0) cs= 0;
2833                 if (ce > columns-1) ce= columns-1;
2834                 if (cs > ce) {  cs= 0;  ce= columns-1;  }
2835                 // is there a change between vitual screen and buf
2836                 if (changed) {
2837                         //  copy changed part of buffer to virtual screen
2838                         memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2839
2840                         // move cursor to column of first change
2841                         if (offset != old_offset) {
2842                                 // opti_cur_move is still too stupid
2843                                 // to handle offsets correctly
2844                                 place_cursor(li, cs, FALSE);
2845                         } else {
2846 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2847                                 // if this just the next line
2848                                 //  try to optimize cursor movement
2849                                 //  otherwise, use standard ESC sequence
2850                                 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2851                                 last_li= li;
2852 #else
2853                                 place_cursor(li, cs, FALSE);    // use standard ESC sequence
2854 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2855                         }
2856
2857                         // write line out to terminal
2858                         {
2859                                 int nic = ce - cs + 1;
2860                                 char *out = sp + cs;
2861
2862                                 while (nic-- > 0) {
2863                                         putchar(*out);
2864                                         out++;
2865                                 }
2866                         }
2867 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2868                         last_row = li;
2869 #endif
2870                 }
2871         }
2872
2873 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2874         place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2875         last_row = crow;
2876 #else
2877         place_cursor(crow, ccol, FALSE);
2878 #endif
2879
2880         if (offset != old_offset)
2881                 old_offset = offset;
2882 }
2883
2884 //---------------------------------------------------------------------
2885 //----- the Ascii Chart -----------------------------------------------
2886 //
2887 //  00 nul   01 soh   02 stx   03 etx   04 eot   05 enq   06 ack   07 bel
2888 //  08 bs    09 ht    0a nl    0b vt    0c np    0d cr    0e so    0f si
2889 //  10 dle   11 dc1   12 dc2   13 dc3   14 dc4   15 nak   16 syn   17 etb
2890 //  18 can   19 em    1a sub   1b esc   1c fs    1d gs    1e rs    1f us
2891 //  20 sp    21 !     22 "     23 #     24 $     25 %     26 &     27 '
2892 //  28 (     29 )     2a *     2b +     2c ,     2d -     2e .     2f /
2893 //  30 0     31 1     32 2     33 3     34 4     35 5     36 6     37 7
2894 //  38 8     39 9     3a :     3b ;     3c <     3d =     3e >     3f ?
2895 //  40 @     41 A     42 B     43 C     44 D     45 E     46 F     47 G
2896 //  48 H     49 I     4a J     4b K     4c L     4d M     4e N     4f O
2897 //  50 P     51 Q     52 R     53 S     54 T     55 U     56 V     57 W
2898 //  58 X     59 Y     5a Z     5b [     5c \     5d ]     5e ^     5f _
2899 //  60 `     61 a     62 b     63 c     64 d     65 e     66 f     67 g
2900 //  68 h     69 i     6a j     6b k     6c l     6d m     6e n     6f o
2901 //  70 p     71 q     72 r     73 s     74 t     75 u     76 v     77 w
2902 //  78 x     79 y     7a z     7b {     7c |     7d }     7e ~     7f del
2903 //---------------------------------------------------------------------
2904
2905 //----- Execute a Vi Command -----------------------------------
2906 static void do_cmd(char c)
2907 {
2908         const char *msg;
2909         char c1, *p, *q, buf[9], *save_dot;
2910         int cnt, i, j, dir, yf;
2911
2912         c1 = c;                         // quiet the compiler
2913         cnt = yf = dir = 0;     // quiet the compiler
2914         msg = p = q = save_dot = buf;   // quiet the compiler
2915         memset(buf, '\0', 9);   // clear buf
2916
2917         show_status_line();
2918
2919         /* if this is a cursor key, skip these checks */
2920         switch (c) {
2921                 case VI_K_UP:
2922                 case VI_K_DOWN:
2923                 case VI_K_LEFT:
2924                 case VI_K_RIGHT:
2925                 case VI_K_HOME:
2926                 case VI_K_END:
2927                 case VI_K_PAGEUP:
2928                 case VI_K_PAGEDOWN:
2929                         goto key_cmd_mode;
2930         }
2931
2932         if (cmd_mode == 2) {
2933                 //  flip-flop Insert/Replace mode
2934                 if (c == VI_K_INSERT)
2935                         goto dc_i;
2936                 // we are 'R'eplacing the current *dot with new char
2937                 if (*dot == '\n') {
2938                         // don't Replace past E-o-l
2939                         cmd_mode = 1;   // convert to insert
2940                 } else {
2941                         if (1 <= c || Isprint(c)) {
2942                                 if (c != 27)
2943                                         dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
2944                                 dot = char_insert(dot, c);      // insert new char
2945                         }
2946                         goto dc1;
2947                 }
2948         }
2949         if (cmd_mode == 1) {
2950                 //  hitting "Insert" twice means "R" replace mode
2951                 if (c == VI_K_INSERT) goto dc5;
2952                 // insert the char c at "dot"
2953                 if (1 <= c || Isprint(c)) {
2954                         dot = char_insert(dot, c);
2955                 }
2956                 goto dc1;
2957         }
2958
2959  key_cmd_mode:
2960         switch (c) {
2961                 //case 0x01:    // soh
2962                 //case 0x09:    // ht
2963                 //case 0x0b:    // vt
2964                 //case 0x0e:    // so
2965                 //case 0x0f:    // si
2966                 //case 0x10:    // dle
2967                 //case 0x11:    // dc1
2968                 //case 0x13:    // dc3
2969 #if ENABLE_FEATURE_VI_CRASHME
2970         case 0x14:                      // dc4  ctrl-T
2971                 crashme = (crashme == 0) ? 1 : 0;
2972                 break;
2973 #endif
2974                 //case 0x16:    // syn
2975                 //case 0x17:    // etb
2976                 //case 0x18:    // can
2977                 //case 0x1c:    // fs
2978                 //case 0x1d:    // gs
2979                 //case 0x1e:    // rs
2980                 //case 0x1f:    // us
2981                 //case '!':     // !-
2982                 //case '#':     // #-
2983                 //case '&':     // &-
2984                 //case '(':     // (-
2985                 //case ')':     // )-
2986                 //case '*':     // *-
2987                 //case ',':     // ,-
2988                 //case '=':     // =-
2989                 //case '@':     // @-
2990                 //case 'F':     // F-
2991                 //case 'K':     // K-
2992                 //case 'Q':     // Q-
2993                 //case 'S':     // S-
2994                 //case 'T':     // T-
2995                 //case 'V':     // V-
2996                 //case '[':     // [-
2997                 //case '\\':    // \-
2998                 //case ']':     // ]-
2999                 //case '_':     // _-
3000                 //case '`':     // `-
3001                 //case 'g':     // g-
3002                 //case 'u':     // u- FIXME- there is no undo
3003                 //case 'v':     // v-
3004         default:                        // unrecognised command
3005                 buf[0] = c;
3006                 buf[1] = '\0';
3007                 if (c < ' ') {
3008                         buf[0] = '^';
3009                         buf[1] = c + '@';
3010                         buf[2] = '\0';
3011                 }
3012                 ni(buf);
3013                 end_cmd_q();    // stop adding to q
3014         case 0x00:                      // nul- ignore
3015                 break;
3016         case 2:                 // ctrl-B  scroll up   full screen
3017         case VI_K_PAGEUP:       // Cursor Key Page Up
3018                 dot_scroll(rows - 2, -1);
3019                 break;
3020 #if ENABLE_FEATURE_VI_USE_SIGNALS
3021         case 0x03:                      // ctrl-C   interrupt
3022                 longjmp(restart, 1);
3023                 break;
3024         case 26:                        // ctrl-Z suspend
3025                 suspend_sig(SIGTSTP);
3026                 break;
3027 #endif
3028         case 4:                 // ctrl-D  scroll down half screen
3029                 dot_scroll((rows - 2) / 2, 1);
3030                 break;
3031         case 5:                 // ctrl-E  scroll down one line
3032                 dot_scroll(1, 1);
3033                 break;
3034         case 6:                 // ctrl-F  scroll down full screen
3035         case VI_K_PAGEDOWN:     // Cursor Key Page Down
3036                 dot_scroll(rows - 2, 1);
3037                 break;
3038         case 7:                 // ctrl-G  show current status
3039                 last_status_cksum = 0;  // force status update
3040                 break;
3041         case 'h':                       // h- move left
3042         case VI_K_LEFT: // cursor key Left
3043         case 8:         // ctrl-H- move left    (This may be ERASE char)
3044         case 0x7f:      // DEL- move left   (This may be ERASE char)
3045                 if (cmdcnt-- > 1) {
3046                         do_cmd(c);
3047                 }                               // repeat cnt
3048                 dot_left();
3049                 break;
3050         case 10:                        // Newline ^J
3051         case 'j':                       // j- goto next line, same col
3052         case VI_K_DOWN: // cursor key Down
3053                 if (cmdcnt-- > 1) {
3054                         do_cmd(c);
3055                 }                               // repeat cnt
3056                 dot_next();             // go to next B-o-l
3057                 dot = move_to_col(dot, ccol + offset);  // try stay in same col
3058                 break;
3059         case 12:                        // ctrl-L  force redraw whole screen
3060         case 18:                        // ctrl-R  force redraw
3061                 place_cursor(0, 0, FALSE);      // put cursor in correct place
3062                 clear_to_eos(); // tel terminal to erase display
3063                 mysleep(10);
3064                 screen_erase(); // erase the internal screen buffer
3065                 last_status_cksum = 0;  // force status update
3066                 refresh(TRUE);  // this will redraw the entire display
3067                 break;
3068         case 13:                        // Carriage Return ^M
3069         case '+':                       // +- goto next line
3070                 if (cmdcnt-- > 1) {
3071                         do_cmd(c);
3072                 }                               // repeat cnt
3073                 dot_next();
3074                 dot_skip_over_ws();
3075                 break;
3076         case 21:                        // ctrl-U  scroll up   half screen
3077                 dot_scroll((rows - 2) / 2, -1);
3078                 break;
3079         case 25:                        // ctrl-Y  scroll up one line
3080                 dot_scroll(1, -1);
3081                 break;
3082         case 27:                        // esc
3083                 if (cmd_mode == 0)
3084                         indicate_error(c);
3085                 cmd_mode = 0;   // stop insrting
3086                 end_cmd_q();
3087                 last_status_cksum = 0;  // force status update
3088                 break;
3089         case ' ':                       // move right
3090         case 'l':                       // move right
3091         case VI_K_RIGHT:        // Cursor Key Right
3092                 if (cmdcnt-- > 1) {
3093                         do_cmd(c);
3094                 }                               // repeat cnt
3095                 dot_right();
3096                 break;
3097 #if ENABLE_FEATURE_VI_YANKMARK
3098         case '"':                       // "- name a register to use for Delete/Yank
3099                 c1 = get_one_char();
3100                 c1 = tolower(c1);
3101                 if (islower(c1)) {
3102                         YDreg = c1 - 'a';
3103                 } else {
3104                         indicate_error(c);
3105                 }
3106                 break;
3107         case '\'':                      // '- goto a specific mark
3108                 c1 = get_one_char();
3109                 c1 = tolower(c1);
3110                 if (islower(c1)) {
3111                         c1 = c1 - 'a';
3112                         // get the b-o-l
3113                         q = mark[(unsigned char) c1];
3114                         if (text <= q && q < end) {
3115                                 dot = q;
3116                                 dot_begin();    // go to B-o-l
3117                                 dot_skip_over_ws();
3118                         }
3119                 } else if (c1 == '\'') {        // goto previous context
3120                         dot = swap_context(dot);        // swap current and previous context
3121                         dot_begin();    // go to B-o-l
3122                         dot_skip_over_ws();
3123                 } else {
3124                         indicate_error(c);
3125                 }
3126                 break;
3127         case 'm':                       // m- Mark a line
3128                 // this is really stupid.  If there are any inserts or deletes
3129                 // between text[0] and dot then this mark will not point to the
3130                 // correct location! It could be off by many lines!
3131                 // Well..., at least its quick and dirty.
3132                 c1 = get_one_char();
3133                 c1 = tolower(c1);
3134                 if (islower(c1)) {
3135                         c1 = c1 - 'a';
3136                         // remember the line
3137                         mark[(int) c1] = dot;
3138                 } else {
3139                         indicate_error(c);
3140                 }
3141                 break;
3142         case 'P':                       // P- Put register before
3143         case 'p':                       // p- put register after
3144                 p = reg[YDreg];
3145                 if (p == 0) {
3146                         psbs("Nothing in register %c", what_reg());
3147                         break;
3148                 }
3149                 // are we putting whole lines or strings
3150                 if (strchr(p, '\n') != NULL) {
3151                         if (c == 'P') {
3152                                 dot_begin();    // putting lines- Put above
3153                         }
3154                         if (c == 'p') {
3155                                 // are we putting after very last line?
3156                                 if (end_line(dot) == (end - 1)) {
3157                                         dot = end;      // force dot to end of text[]
3158                                 } else {
3159                                         dot_next();     // next line, then put before
3160                                 }
3161                         }
3162                 } else {
3163                         if (c == 'p')
3164                                 dot_right();    // move to right, can move to NL
3165                 }
3166                 dot = string_insert(dot, p);    // insert the string
3167                 end_cmd_q();    // stop adding to q
3168                 break;
3169         case 'U':                       // U- Undo; replace current line with original version
3170                 if (reg[Ureg] != 0) {
3171                         p = begin_line(dot);
3172                         q = end_line(dot);
3173                         p = text_hole_delete(p, q);     // delete cur line
3174                         p = string_insert(p, reg[Ureg]);        // insert orig line
3175                         dot = p;
3176                         dot_skip_over_ws();
3177                 }
3178                 break;
3179 #endif /* FEATURE_VI_YANKMARK */
3180         case '$':                       // $- goto end of line
3181         case VI_K_END:          // Cursor Key End
3182                 if (cmdcnt-- > 1) {
3183                         do_cmd(c);
3184                 }                               // repeat cnt
3185                 dot = end_line(dot);
3186                 break;
3187         case '%':                       // %- find matching char of pair () [] {}
3188                 for (q = dot; q < end && *q != '\n'; q++) {
3189                         if (strchr("()[]{}", *q) != NULL) {
3190                                 // we found half of a pair
3191                                 p = find_pair(q, *q);
3192                                 if (p == NULL) {
3193                                         indicate_error(c);
3194                                 } else {
3195                                         dot = p;
3196                                 }
3197                                 break;
3198                         }
3199                 }
3200                 if (*q == '\n')
3201                         indicate_error(c);
3202                 break;
3203         case 'f':                       // f- forward to a user specified char
3204                 last_forward_char = get_one_char();     // get the search char
3205                 //
3206                 // dont separate these two commands. 'f' depends on ';'
3207                 //
3208                 //**** fall thru to ... ';'
3209         case ';':                       // ;- look at rest of line for last forward char
3210                 if (cmdcnt-- > 1) {
3211                         do_cmd(';');
3212                 }                               // repeat cnt
3213                 if (last_forward_char == 0)
3214                         break;
3215                 q = dot + 1;
3216                 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3217                         q++;
3218                 }
3219                 if (*q == last_forward_char)
3220                         dot = q;
3221                 break;
3222         case '-':                       // -- goto prev line
3223                 if (cmdcnt-- > 1) {
3224                         do_cmd(c);
3225                 }                               // repeat cnt
3226                 dot_prev();
3227                 dot_skip_over_ws();
3228                 break;
3229 #if ENABLE_FEATURE_VI_DOT_CMD
3230         case '.':                       // .- repeat the last modifying command
3231                 // Stuff the last_modifying_cmd back into stdin
3232                 // and let it be re-executed.
3233                 if (last_modifying_cmd != 0) {
3234                         ioq = ioq_start = xstrdup(last_modifying_cmd);
3235                 }
3236                 break;
3237 #endif
3238 #if ENABLE_FEATURE_VI_SEARCH
3239         case '?':                       // /- search for a pattern
3240         case '/':                       // /- search for a pattern
3241                 buf[0] = c;
3242                 buf[1] = '\0';
3243                 q = get_input_line(buf);        // get input line- use "status line"
3244                 if (q[0] && !q[1])
3245                         goto dc3; // if no pat re-use old pat
3246                 if (q[0]) {       // strlen(q) > 1: new pat- save it and find
3247                         // there is a new pat
3248                         free(last_search_pattern);
3249                         last_search_pattern = xstrdup(q);
3250                         goto dc3;       // now find the pattern
3251                 }
3252                 // user changed mind and erased the "/"-  do nothing
3253                 break;
3254         case 'N':                       // N- backward search for last pattern
3255                 if (cmdcnt-- > 1) {
3256                         do_cmd(c);
3257                 }                               // repeat cnt
3258                 dir = BACK;             // assume BACKWARD search
3259                 p = dot - 1;
3260                 if (last_search_pattern[0] == '?') {
3261                         dir = FORWARD;
3262                         p = dot + 1;
3263                 }
3264                 goto dc4;               // now search for pattern
3265                 break;
3266         case 'n':                       // n- repeat search for last pattern
3267                 // search rest of text[] starting at next char
3268                 // if search fails return orignal "p" not the "p+1" address
3269                 if (cmdcnt-- > 1) {
3270                         do_cmd(c);
3271                 }                               // repeat cnt
3272  dc3:
3273                 if (last_search_pattern == 0) {
3274                         msg = "No previous regular expression";
3275                         goto dc2;
3276                 }
3277                 if (last_search_pattern[0] == '/') {
3278                         dir = FORWARD;  // assume FORWARD search
3279                         p = dot + 1;
3280                 }
3281                 if (last_search_pattern[0] == '?') {
3282                         dir = BACK;
3283                         p = dot - 1;
3284                 }
3285  dc4:
3286                 q = char_search(p, last_search_pattern + 1, dir, FULL);
3287                 if (q != NULL) {
3288                         dot = q;        // good search, update "dot"
3289                         msg = "";
3290                         goto dc2;
3291                 }
3292                 // no pattern found between "dot" and "end"- continue at top
3293                 p = text;
3294                 if (dir == BACK) {
3295                         p = end - 1;
3296                 }
3297                 q = char_search(p, last_search_pattern + 1, dir, FULL);
3298                 if (q != NULL) {        // found something
3299                         dot = q;        // found new pattern- goto it
3300                         msg = "search hit BOTTOM, continuing at TOP";
3301                         if (dir == BACK) {
3302                                 msg = "search hit TOP, continuing at BOTTOM";
3303                         }
3304                 } else {
3305                         msg = "Pattern not found";
3306                 }
3307  dc2:
3308                 if (*msg)
3309                         psbs("%s", msg);
3310                 break;
3311         case '{':                       // {- move backward paragraph
3312                 q = char_search(dot, "\n\n", BACK, FULL);
3313                 if (q != NULL) {        // found blank line
3314                         dot = next_line(q);     // move to next blank line
3315                 }
3316                 break;
3317         case '}':                       // }- move forward paragraph
3318                 q = char_search(dot, "\n\n", FORWARD, FULL);
3319                 if (q != NULL) {        // found blank line
3320                         dot = next_line(q);     // move to next blank line
3321                 }
3322                 break;
3323 #endif /* FEATURE_VI_SEARCH */
3324         case '0':                       // 0- goto begining of line
3325         case '1':                       // 1-
3326         case '2':                       // 2-
3327         case '3':                       // 3-
3328         case '4':                       // 4-
3329         case '5':                       // 5-
3330         case '6':                       // 6-
3331         case '7':                       // 7-
3332         case '8':                       // 8-
3333         case '9':                       // 9-
3334                 if (c == '0' && cmdcnt < 1) {
3335                         dot_begin();    // this was a standalone zero
3336                 } else {
3337                         cmdcnt = cmdcnt * 10 + (c - '0');       // this 0 is part of a number
3338                 }
3339                 break;
3340         case ':':                       // :- the colon mode commands
3341                 p = get_input_line(":");        // get input line- use "status line"
3342 #if ENABLE_FEATURE_VI_COLON
3343                 colon(p);               // execute the command
3344 #else
3345                 if (*p == ':')
3346                         p++;                            // move past the ':'
3347                 cnt = strlen(p);
3348                 if (cnt <= 0)
3349                         break;
3350                 if (strncasecmp(p, "quit", cnt) == 0
3351                  || strncasecmp(p, "q!", cnt) == 0   // delete lines
3352                 ) {
3353                         if (file_modified && p[1] != '!') {
3354                                 psbs("No write since last change (:quit! overrides)");
3355                         } else {
3356                                 editing = 0;
3357                         }
3358                 } else if (strncasecmp(p, "write", cnt) == 0
3359                         || strncasecmp(p, "wq", cnt) == 0
3360                         || strncasecmp(p, "wn", cnt) == 0
3361                         || strncasecmp(p, "x", cnt) == 0
3362                 ) {
3363                         cnt = file_write(current_filename, text, end - 1);
3364                         if (cnt < 0) {
3365                                 if (cnt == -1)
3366                                         psbs("Write error: %s", strerror(errno));
3367                         } else {
3368                                 file_modified = 0;
3369                                 last_file_modified = -1;
3370                                 psb("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3371                                 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3372                                  || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3373                                 ) {
3374                                         editing = 0;
3375                                 }
3376                         }
3377                 } else if (strncasecmp(p, "file", cnt) == 0) {
3378                         last_status_cksum = 0;  // force status update
3379                 } else if (sscanf(p, "%d", &j) > 0) {
3380                         dot = find_line(j);             // go to line # j
3381                         dot_skip_over_ws();
3382                 } else {                // unrecognised cmd
3383                         ni(p);
3384                 }
3385 #endif /* !FEATURE_VI_COLON */
3386                 break;
3387         case '<':                       // <- Left  shift something
3388         case '>':                       // >- Right shift something
3389                 cnt = count_lines(text, dot);   // remember what line we are on
3390                 c1 = get_one_char();    // get the type of thing to delete
3391                 find_range(&p, &q, c1);
3392                 yank_delete(p, q, 1, YANKONLY); // save copy before change
3393                 p = begin_line(p);
3394                 q = end_line(q);
3395                 i = count_lines(p, q);  // # of lines we are shifting
3396                 for ( ; i > 0; i--, p = next_line(p)) {
3397                         if (c == '<') {
3398                                 // shift left- remove tab or 8 spaces
3399                                 if (*p == '\t') {
3400                                         // shrink buffer 1 char
3401                                         text_hole_delete(p, p);
3402                                 } else if (*p == ' ') {
3403                                         // we should be calculating columns, not just SPACE
3404                                         for (j = 0; *p == ' ' && j < tabstop; j++) {
3405                                                 text_hole_delete(p, p);
3406                                         }
3407                                 }
3408                         } else if (c == '>') {
3409                                 // shift right -- add tab or 8 spaces
3410                                 char_insert(p, '\t');
3411                         }
3412                 }
3413                 dot = find_line(cnt);   // what line were we on
3414                 dot_skip_over_ws();
3415                 end_cmd_q();    // stop adding to q
3416                 break;
3417         case 'A':                       // A- append at e-o-l
3418                 dot_end();              // go to e-o-l
3419                 //**** fall thru to ... 'a'
3420         case 'a':                       // a- append after current char
3421                 if (*dot != '\n')
3422                         dot++;
3423                 goto dc_i;
3424                 break;
3425         case 'B':                       // B- back a blank-delimited Word
3426         case 'E':                       // E- end of a blank-delimited word
3427         case 'W':                       // W- forward a blank-delimited word
3428                 if (cmdcnt-- > 1) {
3429                         do_cmd(c);
3430                 }                               // repeat cnt
3431                 dir = FORWARD;
3432                 if (c == 'B')
3433                         dir = BACK;
3434                 if (c == 'W' || isspace(dot[dir])) {
3435                         dot = skip_thing(dot, 1, dir, S_TO_WS);
3436                         dot = skip_thing(dot, 2, dir, S_OVER_WS);
3437                 }
3438                 if (c != 'W')
3439                         dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3440                 break;
3441         case 'C':                       // C- Change to e-o-l
3442         case 'D':                       // D- delete to e-o-l
3443                 save_dot = dot;
3444                 dot = dollar_line(dot); // move to before NL
3445                 // copy text into a register and delete
3446                 dot = yank_delete(save_dot, dot, 0, YANKDEL);   // delete to e-o-l
3447                 if (c == 'C')
3448                         goto dc_i;      // start inserting
3449 #if ENABLE_FEATURE_VI_DOT_CMD
3450                 if (c == 'D')
3451                         end_cmd_q();    // stop adding to q
3452 #endif
3453                 break;
3454         case 'G':               // G- goto to a line number (default= E-O-F)
3455                 dot = end - 1;                          // assume E-O-F
3456                 if (cmdcnt > 0) {
3457                         dot = find_line(cmdcnt);        // what line is #cmdcnt
3458                 }
3459                 dot_skip_over_ws();
3460                 break;
3461         case 'H':                       // H- goto top line on screen
3462                 dot = screenbegin;
3463                 if (cmdcnt > (rows - 1)) {
3464                         cmdcnt = (rows - 1);
3465                 }
3466                 if (cmdcnt-- > 1) {
3467                         do_cmd('+');
3468                 }                               // repeat cnt
3469                 dot_skip_over_ws();
3470                 break;
3471         case 'I':                       // I- insert before first non-blank
3472                 dot_begin();    // 0
3473                 dot_skip_over_ws();
3474                 //**** fall thru to ... 'i'
3475         case 'i':                       // i- insert before current char
3476         case VI_K_INSERT:       // Cursor Key Insert
3477  dc_i:
3478                 cmd_mode = 1;   // start insrting
3479                 break;
3480         case 'J':                       // J- join current and next lines together
3481                 if (cmdcnt-- > 2) {
3482                         do_cmd(c);
3483                 }                               // repeat cnt
3484                 dot_end();              // move to NL
3485                 if (dot < end - 1) {    // make sure not last char in text[]
3486                         *dot++ = ' ';   // replace NL with space
3487                         file_modified++;
3488                         while (isblank(*dot)) { // delete leading WS
3489                                 dot_delete();
3490                         }
3491                 }
3492                 end_cmd_q();    // stop adding to q
3493                 break;
3494         case 'L':                       // L- goto bottom line on screen
3495                 dot = end_screen();
3496                 if (cmdcnt > (rows - 1)) {
3497                         cmdcnt = (rows - 1);
3498                 }
3499                 if (cmdcnt-- > 1) {
3500                         do_cmd('-');
3501                 }                               // repeat cnt
3502                 dot_begin();
3503                 dot_skip_over_ws();
3504                 break;
3505         case 'M':                       // M- goto middle line on screen
3506                 dot = screenbegin;
3507                 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3508                         dot = next_line(dot);
3509                 break;
3510         case 'O':                       // O- open a empty line above
3511                 //    0i\n ESC -i
3512                 p = begin_line(dot);
3513                 if (p[-1] == '\n') {
3514                         dot_prev();
3515         case 'o':                       // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3516                         dot_end();
3517                         dot = char_insert(dot, '\n');
3518                 } else {
3519                         dot_begin();    // 0
3520                         dot = char_insert(dot, '\n');   // i\n ESC
3521                         dot_prev();     // -
3522                 }
3523                 goto dc_i;
3524                 break;
3525         case 'R':                       // R- continuous Replace char
3526  dc5:
3527                 cmd_mode = 2;
3528                 break;
3529         case 'X':                       // X- delete char before dot
3530         case 'x':                       // x- delete the current char
3531         case 's':                       // s- substitute the current char
3532                 if (cmdcnt-- > 1) {
3533                         do_cmd(c);
3534                 }                               // repeat cnt
3535                 dir = 0;
3536                 if (c == 'X')
3537                         dir = -1;
3538                 if (dot[dir] != '\n') {
3539                         if (c == 'X')
3540                                 dot--;  // delete prev char
3541                         dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
3542                 }
3543                 if (c == 's')
3544                         goto dc_i;      // start insrting
3545                 end_cmd_q();    // stop adding to q
3546                 break;
3547         case 'Z':                       // Z- if modified, {write}; exit
3548                 // ZZ means to save file (if necessary), then exit
3549                 c1 = get_one_char();
3550                 if (c1 != 'Z') {
3551                         indicate_error(c);
3552                         break;
3553                 }
3554                 if (file_modified) {
3555                         if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3556                                 psbs("\"%s\" File is read only", current_filename);
3557                                 break;
3558                         }
3559                         cnt = file_write(current_filename, text, end - 1);
3560                         if (cnt < 0) {
3561                                 if (cnt == -1)
3562                                         psbs("Write error: %s", strerror(errno));
3563                         } else if (cnt == (end - 1 - text + 1)) {
3564                                 editing = 0;
3565                         }
3566                 } else {
3567                         editing = 0;
3568                 }
3569                 break;
3570         case '^':                       // ^- move to first non-blank on line
3571                 dot_begin();
3572                 dot_skip_over_ws();
3573                 break;
3574         case 'b':                       // b- back a word
3575         case 'e':                       // e- end of word
3576                 if (cmdcnt-- > 1) {
3577                         do_cmd(c);
3578                 }                               // repeat cnt
3579                 dir = FORWARD;
3580                 if (c == 'b')
3581                         dir = BACK;
3582                 if ((dot + dir) < text || (dot + dir) > end - 1)
3583                         break;
3584                 dot += dir;
3585                 if (isspace(*dot)) {
3586                         dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3587                 }
3588                 if (isalnum(*dot) || *dot == '_') {
3589                         dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3590                 } else if (ispunct(*dot)) {
3591                         dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3592                 }
3593                 break;
3594         case 'c':                       // c- change something
3595         case 'd':                       // d- delete something
3596 #if ENABLE_FEATURE_VI_YANKMARK
3597         case 'y':                       // y- yank   something
3598         case 'Y':                       // Y- Yank a line
3599 #endif
3600                 yf = YANKDEL;   // assume either "c" or "d"
3601 #if ENABLE_FEATURE_VI_YANKMARK
3602                 if (c == 'y' || c == 'Y')
3603                         yf = YANKONLY;
3604 #endif
3605                 c1 = 'y';
3606                 if (c != 'Y')
3607                         c1 = get_one_char();    // get the type of thing to delete
3608                 find_range(&p, &q, c1);
3609                 if (c1 == 27) { // ESC- user changed mind and wants out
3610                         c = c1 = 27;    // Escape- do nothing
3611                 } else if (strchr("wW", c1)) {
3612                         if (c == 'c') {
3613                                 // don't include trailing WS as part of word
3614                                 while (isblank(*q)) {
3615                                         if (q <= text || q[-1] == '\n')
3616                                                 break;
3617                                         q--;
3618                                 }
3619                         }
3620                         dot = yank_delete(p, q, 0, yf); // delete word
3621                 } else if (strchr("^0bBeEft$", c1)) {
3622                         // single line copy text into a register and delete
3623                         dot = yank_delete(p, q, 0, yf); // delete word
3624                 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3625                         // multiple line copy text into a register and delete
3626                         dot = yank_delete(p, q, 1, yf); // delete lines
3627                         if (c == 'c') {
3628                                 dot = char_insert(dot, '\n');
3629                                 // on the last line of file don't move to prev line
3630                                 if (dot != (end-1)) {
3631                                         dot_prev();
3632                                 }
3633                         } else if (c == 'd') {
3634                                 dot_begin();
3635                                 dot_skip_over_ws();
3636                         }
3637                 } else {
3638                         // could not recognize object
3639                         c = c1 = 27;    // error-
3640                         indicate_error(c);
3641                 }
3642                 if (c1 != 27) {
3643                         // if CHANGING, not deleting, start inserting after the delete
3644                         if (c == 'c') {
3645                                 strcpy(buf, "Change");
3646                                 goto dc_i;      // start inserting
3647                         }
3648                         if (c == 'd') {
3649                                 strcpy(buf, "Delete");
3650                         }
3651 #if ENABLE_FEATURE_VI_YANKMARK
3652                         if (c == 'y' || c == 'Y') {
3653                                 strcpy(buf, "Yank");
3654                         }
3655                         p = reg[YDreg];
3656                         q = p + strlen(p);
3657                         for (cnt = 0; p <= q; p++) {
3658                                 if (*p == '\n')
3659                                         cnt++;
3660                         }
3661                         psb("%s %d lines (%d chars) using [%c]",
3662                                 buf, cnt, strlen(reg[YDreg]), what_reg());
3663 #endif
3664                         end_cmd_q();    // stop adding to q
3665                 }
3666                 break;
3667         case 'k':                       // k- goto prev line, same col
3668         case VI_K_UP:           // cursor key Up
3669                 if (cmdcnt-- > 1) {
3670                         do_cmd(c);
3671                 }                               // repeat cnt
3672                 dot_prev();
3673                 dot = move_to_col(dot, ccol + offset);  // try stay in same col
3674                 break;
3675         case 'r':                       // r- replace the current char with user input
3676                 c1 = get_one_char();    // get the replacement char
3677                 if (*dot != '\n') {
3678                         *dot = c1;
3679                         file_modified++;        // has the file been modified
3680                 }
3681                 end_cmd_q();    // stop adding to q
3682                 break;
3683         case 't':                       // t- move to char prior to next x
3684                 last_forward_char = get_one_char();
3685                 do_cmd(';');
3686                 if (*dot == last_forward_char)
3687                         dot_left();
3688                 last_forward_char= 0;
3689                 break;
3690         case 'w':                       // w- forward a word
3691                 if (cmdcnt-- > 1) {
3692                         do_cmd(c);
3693                 }                               // repeat cnt
3694                 if (isalnum(*dot) || *dot == '_') {     // we are on ALNUM
3695                         dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3696                 } else if (ispunct(*dot)) {     // we are on PUNCT
3697                         dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3698                 }
3699                 if (dot < end - 1)
3700                         dot++;          // move over word
3701                 if (isspace(*dot)) {
3702                         dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3703                 }
3704                 break;
3705         case 'z':                       // z-
3706                 c1 = get_one_char();    // get the replacement char
3707                 cnt = 0;
3708                 if (c1 == '.')
3709                         cnt = (rows - 2) / 2;   // put dot at center
3710                 if (c1 == '-')
3711                         cnt = rows - 2; // put dot at bottom
3712                 screenbegin = begin_line(dot);  // start dot at top
3713                 dot_scroll(cnt, -1);
3714                 break;
3715         case '|':                       // |- move to column "cmdcnt"
3716                 dot = move_to_col(dot, cmdcnt - 1);     // try to move to column
3717                 break;
3718         case '~':                       // ~- flip the case of letters   a-z -> A-Z
3719                 if (cmdcnt-- > 1) {
3720                         do_cmd(c);
3721                 }                               // repeat cnt
3722                 if (islower(*dot)) {
3723                         *dot = toupper(*dot);
3724                         file_modified++;        // has the file been modified
3725                 } else if (isupper(*dot)) {
3726                         *dot = tolower(*dot);
3727                         file_modified++;        // has the file been modified
3728                 }
3729                 dot_right();
3730                 end_cmd_q();    // stop adding to q
3731                 break;
3732                 //----- The Cursor and Function Keys -----------------------------
3733         case VI_K_HOME: // Cursor Key Home
3734                 dot_begin();
3735                 break;
3736                 // The Fn keys could point to do_macro which could translate them
3737         case VI_K_FUN1: // Function Key F1
3738         case VI_K_FUN2: // Function Key F2
3739         case VI_K_FUN3: // Function Key F3
3740         case VI_K_FUN4: // Function Key F4
3741         case VI_K_FUN5: // Function Key F5
3742         case VI_K_FUN6: // Function Key F6
3743         case VI_K_FUN7: // Function Key F7
3744         case VI_K_FUN8: // Function Key F8
3745         case VI_K_FUN9: // Function Key F9
3746         case VI_K_FUN10:        // Function Key F10
3747         case VI_K_FUN11:        // Function Key F11
3748         case VI_K_FUN12:        // Function Key F12
3749                 break;
3750         }
3751
3752  dc1:
3753         // if text[] just became empty, add back an empty line
3754         if (end == text) {
3755                 char_insert(text, '\n');        // start empty buf with dummy line
3756                 dot = text;
3757         }
3758         // it is OK for dot to exactly equal to end, otherwise check dot validity
3759         if (dot != end) {
3760                 dot = bound_dot(dot);   // make sure "dot" is valid
3761         }
3762 #if ENABLE_FEATURE_VI_YANKMARK
3763         check_context(c);       // update the current context
3764 #endif
3765
3766         if (!isdigit(c))
3767                 cmdcnt = 0;             // cmd was not a number, reset cmdcnt
3768         cnt = dot - begin_line(dot);
3769         // Try to stay off of the Newline
3770         if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3771                 dot--;
3772 }
3773
3774 #if ENABLE_FEATURE_VI_CRASHME
3775 static int totalcmds = 0;
3776 static int Mp = 85;             // Movement command Probability
3777 static int Np = 90;             // Non-movement command Probability
3778 static int Dp = 96;             // Delete command Probability
3779 static int Ip = 97;             // Insert command Probability
3780 static int Yp = 98;             // Yank command Probability
3781 static int Pp = 99;             // Put command Probability
3782 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3783 const char chars[20] = "\t012345 abcdABCD-=.$";
3784 const char *const words[20] = {
3785         "this", "is", "a", "test",
3786         "broadcast", "the", "emergency", "of",
3787         "system", "quick", "brown", "fox",
3788         "jumped", "over", "lazy", "dogs",
3789         "back", "January", "Febuary", "March"
3790 };
3791 const char *const lines[20] = {
3792         "You should have received a copy of the GNU General Public License\n",
3793         "char c, cm, *cmd, *cmd1;\n",
3794         "generate a command by percentages\n",
3795         "Numbers may be typed as a prefix to some commands.\n",
3796         "Quit, discarding changes!\n",
3797         "Forced write, if permission originally not valid.\n",
3798         "In general, any ex or ed command (such as substitute or delete).\n",
3799         "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3800         "Please get w/ me and I will go over it with you.\n",
3801         "The following is a list of scheduled, committed changes.\n",
3802         "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3803         "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3804         "Any question about transactions please contact Sterling Huxley.\n",
3805         "I will try to get back to you by Friday, December 31.\n",
3806         "This Change will be implemented on Friday.\n",
3807         "Let me know if you have problems accessing this;\n",
3808         "Sterling Huxley recently added you to the access list.\n",
3809         "Would you like to go to lunch?\n",
3810         "The last command will be automatically run.\n",
3811         "This is too much english for a computer geek.\n",
3812 };
3813 char *multilines[20] = {
3814         "You should have received a copy of the GNU General Public License\n",
3815         "char c, cm, *cmd, *cmd1;\n",
3816         "generate a command by percentages\n",
3817         "Numbers may be typed as a prefix to some commands.\n",
3818         "Quit, discarding changes!\n",
3819         "Forced write, if permission originally not valid.\n",
3820         "In general, any ex or ed command (such as substitute or delete).\n",
3821         "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3822         "Please get w/ me and I will go over it with you.\n",
3823         "The following is a list of scheduled, committed changes.\n",
3824         "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3825         "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3826         "Any question about transactions please contact Sterling Huxley.\n",
3827         "I will try to get back to you by Friday, December 31.\n",
3828         "This Change will be implemented on Friday.\n",
3829         "Let me know if you have problems accessing this;\n",
3830         "Sterling Huxley recently added you to the access list.\n",
3831         "Would you like to go to lunch?\n",
3832         "The last command will be automatically run.\n",
3833         "This is too much english for a computer geek.\n",
3834 };
3835
3836 // create a random command to execute
3837 static void crash_dummy()
3838 {
3839         static int sleeptime;   // how long to pause between commands
3840         char c, cm, *cmd, *cmd1;
3841         int i, cnt, thing, rbi, startrbi, percent;
3842
3843         // "dot" movement commands
3844         cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3845
3846         // is there already a command running?
3847         if (readed_for_parse > 0)
3848                 goto cd1;
3849  cd0:
3850         startrbi = rbi = 0;
3851         sleeptime = 0;          // how long to pause between commands
3852         memset(readbuffer, '\0', MAX_LINELEN);   // clear the read buffer
3853         // generate a command by percentages
3854         percent = (int) lrand48() % 100;        // get a number from 0-99
3855         if (percent < Mp) {     //  Movement commands
3856                 // available commands
3857                 cmd = cmd1;
3858                 M++;
3859         } else if (percent < Np) {      //  non-movement commands
3860                 cmd = "mz<>\'\"";       // available commands
3861                 N++;
3862         } else if (percent < Dp) {      //  Delete commands
3863                 cmd = "dx";             // available commands
3864                 D++;
3865         } else if (percent < Ip) {      //  Inset commands
3866                 cmd = "iIaAsrJ";        // available commands
3867                 I++;
3868         } else if (percent < Yp) {      //  Yank commands
3869                 cmd = "yY";             // available commands
3870                 Y++;
3871         } else if (percent < Pp) {      //  Put commands
3872                 cmd = "pP";             // available commands
3873                 P++;
3874         } else {
3875                 // We do not know how to handle this command, try again
3876                 U++;
3877                 goto cd0;
3878         }
3879         // randomly pick one of the available cmds from "cmd[]"
3880         i = (int) lrand48() % strlen(cmd);
3881         cm = cmd[i];
3882         if (strchr(":\024", cm))
3883                 goto cd0;               // dont allow colon or ctrl-T commands
3884         readbuffer[rbi++] = cm; // put cmd into input buffer
3885
3886         // now we have the command-
3887         // there are 1, 2, and multi char commands
3888         // find out which and generate the rest of command as necessary
3889         if (strchr("dmryz<>\'\"", cm)) {        // 2-char commands
3890                 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3891                 if (cm == 'm' || cm == '\'' || cm == '\"') {    // pick a reg[]
3892                         cmd1 = "abcdefghijklmnopqrstuvwxyz";
3893                 }
3894                 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3895                 c = cmd1[thing];
3896                 readbuffer[rbi++] = c;  // add movement to input buffer
3897         }
3898         if (strchr("iIaAsc", cm)) {     // multi-char commands
3899                 if (cm == 'c') {
3900                         // change some thing
3901                         thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3902                         c = cmd1[thing];
3903                         readbuffer[rbi++] = c;  // add movement to input buffer
3904                 }
3905                 thing = (int) lrand48() % 4;    // what thing to insert
3906                 cnt = (int) lrand48() % 10;     // how many to insert
3907                 for (i = 0; i < cnt; i++) {
3908                         if (thing == 0) {       // insert chars
3909                                 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3910                         } else if (thing == 1) {        // insert words
3911                                 strcat(readbuffer, words[(int) lrand48() % 20]);
3912                                 strcat(readbuffer, " ");
3913                                 sleeptime = 0;  // how fast to type
3914                         } else if (thing == 2) {        // insert lines
3915                                 strcat(readbuffer, lines[(int) lrand48() % 20]);
3916                                 sleeptime = 0;  // how fast to type
3917                         } else {        // insert multi-lines
3918                                 strcat(readbuffer, multilines[(int) lrand48() % 20]);
3919                                 sleeptime = 0;  // how fast to type
3920                         }
3921                 }
3922                 strcat(readbuffer, "\033");
3923         }
3924         readed_for_parse = strlen(readbuffer);
3925  cd1:
3926         totalcmds++;
3927         if (sleeptime > 0)
3928                 mysleep(sleeptime);      // sleep 1/100 sec
3929 }
3930
3931 // test to see if there are any errors
3932 static void crash_test()
3933 {
3934         static time_t oldtim;
3935
3936         time_t tim;
3937         char d[2], msg[MAX_LINELEN];
3938
3939         msg[0] = '\0';
3940         if (end < text) {
3941                 strcat(msg, "end<text ");
3942         }
3943         if (end > textend) {
3944                 strcat(msg, "end>textend ");
3945         }
3946         if (dot < text) {
3947                 strcat(msg, "dot<text ");
3948         }
3949         if (dot > end) {
3950                 strcat(msg, "dot>end ");
3951         }
3952         if (screenbegin < text) {
3953                 strcat(msg, "screenbegin<text ");
3954         }
3955         if (screenbegin > end - 1) {
3956                 strcat(msg, "screenbegin>end-1 ");
3957         }
3958
3959         if (msg[0]) {
3960                 alarm(0);
3961                 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3962                         totalcmds, last_input_char, msg, SOs, SOn);
3963                 fflush(stdout);
3964                 while (read(0, d, 1) > 0) {
3965                         if (d[0] == '\n' || d[0] == '\r')
3966                                 break;
3967                 }
3968                 alarm(3);
3969         }
3970         tim = (time_t) time((time_t *) 0);
3971         if (tim >= (oldtim + 3)) {
3972                 sprintf(status_buffer,
3973                                 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3974                                 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3975                 oldtim = tim;
3976         }
3977 }
3978 #endif