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