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