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