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