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