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