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