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