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