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