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