acbede359e23762e2be9bf66dc7d068ab39b5d67
[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 -
2248                 // see "n = 0" below).
2249                 n = safe_read(0, readbuffer, 3);
2250                 if (n <= 0) {
2251  error:
2252                         go_bottom_and_clear_to_eol();
2253                         cookmode(); // terminal to "cooked"
2254                         bb_error_msg_and_die("can't read user input");
2255                 }
2256         }
2257
2258         // Grab character to return from buffer
2259         c = readbuffer[0];
2260         // Returning NUL from this routine would be bad.
2261         if (c == '\0')
2262                 c = ' ';
2263         n--;
2264         if (n) memmove(readbuffer, readbuffer + 1, n);
2265
2266         // If it's an escape sequence, loop through known matches.
2267         if (c == 27) {
2268                 const struct esc_cmds *eindex;
2269                 struct pollfd pfd;
2270
2271                 pfd.fd = STDIN_FILENO;
2272                 pfd.events = POLLIN;
2273                 for (eindex = esccmds; eindex < esccmds + ARRAY_SIZE(esccmds); eindex++) {
2274                         // n - position in sequence we did not read yet
2275                         int i = 0; // position in sequence to compare
2276
2277                         // Loop through chars in this sequence
2278                         for (;;) {
2279                                 // So far escape sequence matches up to [i-1]
2280                                 if (n <= i) {
2281                                         // Need more chars, read another one if it wouldn't block.
2282                                         // (Note that escape sequences come in as a unit,
2283                                         // so if we would block it's not really an escape sequence.)
2284
2285                                         // Timeout is needed to reconnect escape sequences
2286                                         // split up by transmission over a serial console.
2287                                         // Even though inter-char delay on 1200 baud is <10ms,
2288                                         // process scheduling can enlarge it arbitrarily,
2289                                         // on both send and receive sides.
2290                                         // Erring on the safe side - 5 timer ticks on 100 HZ.
2291                                         if (safe_poll(&pfd, 1, 50)) {
2292                                                 if (safe_read(0, readbuffer + n, 1) <= 0)
2293                                                         goto error;
2294                                                 n++;
2295                                         } else {
2296                                                 // No more data!
2297                                                 // Array is sorted from shortest to longest,
2298                                                 // we can't match anything later in array,
2299                                                 // break out of both loops.
2300                                                 goto loop_out;
2301                                         }
2302                                 }
2303                                 if (readbuffer[i] != eindex->seq[i])
2304                                         break; // try next seq
2305                                 i++;
2306                                 if (i == 4 || !eindex->seq[i]) { // entire seq matched
2307                                         c = eindex->val;
2308                                         n = 0;
2309                                         // n -= i; memmove(...);
2310                                         // would be more correct,
2311                                         // but we never read ahead that much,
2312                                         // and n == i here.
2313                                         goto loop_out;
2314                                 }
2315                         }
2316                 }
2317                 // We did not find matching sequence, it was a bare ESC.
2318                 // We possibly read and stored more input in readbuffer by now.
2319         }
2320  loop_out:
2321
2322         chars_to_parse = n;
2323         return c;
2324 }
2325
2326 //----- IO Routines --------------------------------------------
2327 static char get_one_char(void)
2328 {
2329         char c;
2330
2331 #if ENABLE_FEATURE_VI_DOT_CMD
2332         if (!adding2q) {
2333                 // we are not adding to the q.
2334                 // but, we may be reading from a q
2335                 if (ioq == 0) {
2336                         // there is no current q, read from STDIN
2337                         c = readit();   // get the users input
2338                 } else {
2339                         // there is a queue to get chars from first
2340                         c = *ioq++;
2341                         if (c == '\0') {
2342                                 // the end of the q, read from STDIN
2343                                 free(ioq_start);
2344                                 ioq_start = ioq = 0;
2345                                 c = readit();   // get the users input
2346                         }
2347                 }
2348         } else {
2349                 // adding STDIN chars to q
2350                 c = readit();   // get the users input
2351                 if (lmc_len >= MAX_INPUT_LEN - 1) {
2352                         status_line_bold("last_modifying_cmd overrun");
2353                 } else {
2354                         // add new char to q
2355                         last_modifying_cmd[lmc_len++] = c;
2356                 }
2357         }
2358 #else
2359         c = readit();           // get the users input
2360 #endif /* FEATURE_VI_DOT_CMD */
2361         return c;
2362 }
2363
2364 // Get input line (uses "status line" area)
2365 static char *get_input_line(const char *prompt)
2366 {
2367         // char [MAX_INPUT_LEN]
2368 #define buf get_input_line__buf
2369
2370         char c;
2371         int i;
2372
2373         strcpy(buf, prompt);
2374         last_status_cksum = 0;  // force status update
2375         go_bottom_and_clear_to_eol();
2376         write1(prompt);      // write out the :, /, or ? prompt
2377
2378         i = strlen(buf);
2379         while (i < MAX_INPUT_LEN) {
2380                 c = get_one_char();
2381                 if (c == '\n' || c == '\r' || c == 27)
2382                         break;          // this is end of input
2383                 if (c == erase_char || c == 8 || c == 127) {
2384                         // user wants to erase prev char
2385                         buf[--i] = '\0';
2386                         write1("\b \b"); // erase char on screen
2387                         if (i <= 0) // user backs up before b-o-l, exit
2388                                 break;
2389                 } else {
2390                         buf[i] = c;
2391                         buf[++i] = '\0';
2392                         bb_putchar(c);
2393                 }
2394         }
2395         refresh(FALSE);
2396         return buf;
2397 #undef buf
2398 }
2399
2400 static int file_size(const char *fn) // what is the byte size of "fn"
2401 {
2402         struct stat st_buf;
2403         int cnt;
2404
2405         cnt = -1;
2406         if (fn && fn[0] && stat(fn, &st_buf) == 0)      // see if file exists
2407                 cnt = (int) st_buf.st_size;
2408         return cnt;
2409 }
2410
2411 static int file_insert(const char *fn, char *p
2412                 USE_FEATURE_VI_READONLY(, int update_ro_status))
2413 {
2414         int cnt = -1;
2415         int fd, size;
2416         struct stat statbuf;
2417
2418         /* Validate file */
2419         if (stat(fn, &statbuf) < 0) {
2420                 status_line_bold("\"%s\" %s", fn, strerror(errno));
2421                 goto fi0;
2422         }
2423         if (!S_ISREG(statbuf.st_mode)) {
2424                 // This is not a regular file
2425                 status_line_bold("\"%s\" Not a regular file", fn);
2426                 goto fi0;
2427         }
2428         if (p < text || p > end) {
2429                 status_line_bold("Trying to insert file outside of memory");
2430                 goto fi0;
2431         }
2432
2433         // read file to buffer
2434         fd = open(fn, O_RDONLY);
2435         if (fd < 0) {
2436                 status_line_bold("\"%s\" %s", fn, strerror(errno));
2437                 goto fi0;
2438         }
2439         size = statbuf.st_size;
2440         p = text_hole_make(p, size);
2441         cnt = safe_read(fd, p, size);
2442         if (cnt < 0) {
2443                 status_line_bold("\"%s\" %s", fn, strerror(errno));
2444                 p = text_hole_delete(p, p + size - 1);  // un-do buffer insert
2445         } else if (cnt < size) {
2446                 // There was a partial read, shrink unused space text[]
2447                 p = text_hole_delete(p + cnt, p + (size - cnt) - 1);    // un-do buffer insert
2448                 status_line_bold("cannot read all of file \"%s\"", fn);
2449         }
2450         if (cnt >= size)
2451                 file_modified++;
2452         close(fd);
2453  fi0:
2454 #if ENABLE_FEATURE_VI_READONLY
2455         if (update_ro_status
2456          && ((access(fn, W_OK) < 0) ||
2457                 /* root will always have access()
2458                  * so we check fileperms too */
2459                 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2460             )
2461         ) {
2462                 SET_READONLY_FILE(readonly_mode);
2463         }
2464 #endif
2465         return cnt;
2466 }
2467
2468 static int file_write(char *fn, char *first, char *last)
2469 {
2470         int fd, cnt, charcnt;
2471
2472         if (fn == 0) {
2473                 status_line_bold("No current filename");
2474                 return -2;
2475         }
2476         charcnt = 0;
2477         /* By popular request we do not open file with O_TRUNC,
2478          * but instead ftruncate() it _after_ successful write.
2479          * Might reduce amount of data lost on power fail etc.
2480          */
2481         fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2482         if (fd < 0)
2483                 return -1;
2484         cnt = last - first + 1;
2485         charcnt = full_write(fd, first, cnt);
2486         ftruncate(fd, charcnt);
2487         if (charcnt == cnt) {
2488                 // good write
2489                 //file_modified = FALSE;
2490         } else {
2491                 charcnt = 0;
2492         }
2493         close(fd);
2494         return charcnt;
2495 }
2496
2497 //----- Terminal Drawing ---------------------------------------
2498 // The terminal is made up of 'rows' line of 'columns' columns.
2499 // classically this would be 24 x 80.
2500 //  screen coordinates
2501 //  0,0     ...     0,79
2502 //  1,0     ...     1,79
2503 //  .       ...     .
2504 //  .       ...     .
2505 //  22,0    ...     22,79
2506 //  23,0    ...     23,79   <- status line
2507
2508 //----- Move the cursor to row x col (count from 0, not 1) -------
2509 static void place_cursor(int row, int col, int optimize)
2510 {
2511         char cm1[sizeof(CMrc) + sizeof(int)*3 * 2];
2512 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2513         enum {
2514                 SZ_UP = sizeof(CMup),
2515                 SZ_DN = sizeof(CMdown),
2516                 SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN,
2517         };
2518         char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size
2519 #endif
2520         char *cm;
2521
2522         if (row < 0) row = 0;
2523         if (row >= rows) row = rows - 1;
2524         if (col < 0) col = 0;
2525         if (col >= columns) col = columns - 1;
2526
2527         //----- 1.  Try the standard terminal ESC sequence
2528         sprintf(cm1, CMrc, row + 1, col + 1);
2529         cm = cm1;
2530
2531 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2532         if (optimize && col < 16) {
2533                 char *screenp;
2534                 int Rrow = last_row;
2535                 int diff = Rrow - row;
2536
2537                 if (diff < -5 || diff > 5)
2538                         goto skip;
2539
2540                 //----- find the minimum # of chars to move cursor -------------
2541                 //----- 2.  Try moving with discreet chars (Newline, [back]space, ...)
2542                 cm2[0] = '\0';
2543
2544                 // move to the correct row
2545                 while (row < Rrow) {
2546                         // the cursor has to move up
2547                         strcat(cm2, CMup);
2548                         Rrow--;
2549                 }
2550                 while (row > Rrow) {
2551                         // the cursor has to move down
2552                         strcat(cm2, CMdown);
2553                         Rrow++;
2554                 }
2555
2556                 // now move to the correct column
2557                 strcat(cm2, "\r");                      // start at col 0
2558                 // just send out orignal source char to get to correct place
2559                 screenp = &screen[row * columns];       // start of screen line
2560                 strncat(cm2, screenp, col);
2561
2562                 // pick the shortest cursor motion to send out
2563                 if (strlen(cm2) < strlen(cm)) {
2564                         cm = cm2;
2565                 }
2566  skip: ;
2567         }
2568         last_row = row;
2569 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2570         write1(cm);
2571 }
2572
2573 //----- Erase from cursor to end of line -----------------------
2574 static void clear_to_eol(void)
2575 {
2576         write1(Ceol);   // Erase from cursor to end of line
2577 }
2578
2579 static void go_bottom_and_clear_to_eol(void)
2580 {
2581         place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2582         clear_to_eol(); // erase to end of line
2583 }
2584
2585 //----- Erase from cursor to end of screen -----------------------
2586 static void clear_to_eos(void)
2587 {
2588         write1(Ceos);   // Erase from cursor to end of screen
2589 }
2590
2591 //----- Start standout mode ------------------------------------
2592 static void standout_start(void) // send "start reverse video" sequence
2593 {
2594         write1(SOs);     // Start reverse video mode
2595 }
2596
2597 //----- End standout mode --------------------------------------
2598 static void standout_end(void) // send "end reverse video" sequence
2599 {
2600         write1(SOn);     // End reverse video mode
2601 }
2602
2603 //----- Flash the screen  --------------------------------------
2604 static void flash(int h)
2605 {
2606         standout_start();       // send "start reverse video" sequence
2607         redraw(TRUE);
2608         mysleep(h);
2609         standout_end();         // send "end reverse video" sequence
2610         redraw(TRUE);
2611 }
2612
2613 static void Indicate_Error(void)
2614 {
2615 #if ENABLE_FEATURE_VI_CRASHME
2616         if (crashme > 0)
2617                 return;                 // generate a random command
2618 #endif
2619         if (!err_method) {
2620                 write1(bell);   // send out a bell character
2621         } else {
2622                 flash(10);
2623         }
2624 }
2625
2626 //----- Screen[] Routines --------------------------------------
2627 //----- Erase the Screen[] memory ------------------------------
2628 static void screen_erase(void)
2629 {
2630         memset(screen, ' ', screensize);        // clear new screen
2631 }
2632
2633 static int bufsum(char *buf, int count)
2634 {
2635         int sum = 0;
2636         char *e = buf + count;
2637
2638         while (buf < e)
2639                 sum += (unsigned char) *buf++;
2640         return sum;
2641 }
2642
2643 //----- Draw the status line at bottom of the screen -------------
2644 static void show_status_line(void)
2645 {
2646         int cnt = 0, cksum = 0;
2647
2648         // either we already have an error or status message, or we
2649         // create one.
2650         if (!have_status_msg) {
2651                 cnt = format_edit_status();
2652                 cksum = bufsum(status_buffer, cnt);
2653         }
2654         if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2655                 last_status_cksum = cksum;              // remember if we have seen this line
2656                 go_bottom_and_clear_to_eol();
2657                 write1(status_buffer);
2658                 if (have_status_msg) {
2659                         if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2660                                         (columns - 1) ) {
2661                                 have_status_msg = 0;
2662                                 Hit_Return();
2663                         }
2664                         have_status_msg = 0;
2665                 }
2666                 place_cursor(crow, ccol, FALSE);        // put cursor back in correct place
2667         }
2668         fflush(stdout);
2669 }
2670
2671 //----- format the status buffer, the bottom line of screen ------
2672 // format status buffer, with STANDOUT mode
2673 static void status_line_bold(const char *format, ...)
2674 {
2675         va_list args;
2676
2677         va_start(args, format);
2678         strcpy(status_buffer, SOs);     // Terminal standout mode on
2679         vsprintf(status_buffer + sizeof(SOs)-1, format, args);
2680         strcat(status_buffer, SOn);     // Terminal standout mode off
2681         va_end(args);
2682
2683         have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2684 }
2685
2686 // format status buffer
2687 static void status_line(const char *format, ...)
2688 {
2689         va_list args;
2690
2691         va_start(args, format);
2692         vsprintf(status_buffer, format, args);
2693         va_end(args);
2694
2695         have_status_msg = 1;
2696 }
2697
2698 // copy s to buf, convert unprintable
2699 static void print_literal(char *buf, const char *s)
2700 {
2701         unsigned char c;
2702         char b[2];
2703
2704         b[1] = '\0';
2705         buf[0] = '\0';
2706         if (!s[0])
2707                 s = "(NULL)";
2708         for (; *s; s++) {
2709                 int c_is_no_print;
2710
2711                 c = *s;
2712                 c_is_no_print = (c & 0x80) && !Isprint(c);
2713                 if (c_is_no_print) {
2714                         strcat(buf, SOn);
2715                         c = '.';
2716                 }
2717                 if (c < ' ' || c == 127) {
2718                         strcat(buf, "^");
2719                         if (c == 127)
2720                                 c = '?';
2721                         else
2722                                 c += '@';
2723                 }
2724                 b[0] = c;
2725                 strcat(buf, b);
2726                 if (c_is_no_print)
2727                         strcat(buf, SOs);
2728                 if (*s == '\n')
2729                         strcat(buf, "$");
2730                 if (strlen(buf) > MAX_INPUT_LEN - 10) // paranoia
2731                         break;
2732         }
2733 }
2734
2735 static void not_implemented(const char *s)
2736 {
2737         char buf[MAX_INPUT_LEN];
2738
2739         print_literal(buf, s);
2740         status_line_bold("\'%s\' is not implemented", buf);
2741 }
2742
2743 // show file status on status line
2744 static int format_edit_status(void)
2745 {
2746         static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2747
2748 #define tot format_edit_status__tot
2749
2750         int cur, percent, ret, trunc_at;
2751
2752         // file_modified is now a counter rather than a flag.  this
2753         // helps reduce the amount of line counting we need to do.
2754         // (this will cause a mis-reporting of modified status
2755         // once every MAXINT editing operations.)
2756
2757         // it would be nice to do a similar optimization here -- if
2758         // we haven't done a motion that could have changed which line
2759         // we're on, then we shouldn't have to do this count_lines()
2760         cur = count_lines(text, dot);
2761
2762         // reduce counting -- the total lines can't have
2763         // changed if we haven't done any edits.
2764         if (file_modified != last_file_modified) {
2765                 tot = cur + count_lines(dot, end - 1) - 1;
2766                 last_file_modified = file_modified;
2767         }
2768
2769         //    current line         percent
2770         //   -------------    ~~ ----------
2771         //    total lines            100
2772         if (tot > 0) {
2773                 percent = (100 * cur) / tot;
2774         } else {
2775                 cur = tot = 0;
2776                 percent = 100;
2777         }
2778
2779         trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2780                 columns : STATUS_BUFFER_LEN-1;
2781
2782         ret = snprintf(status_buffer, trunc_at+1,
2783 #if ENABLE_FEATURE_VI_READONLY
2784                 "%c %s%s%s %d/%d %d%%",
2785 #else
2786                 "%c %s%s %d/%d %d%%",
2787 #endif
2788                 cmd_mode_indicator[cmd_mode & 3],
2789                 (current_filename != NULL ? current_filename : "No file"),
2790 #if ENABLE_FEATURE_VI_READONLY
2791                 (readonly_mode ? " [Readonly]" : ""),
2792 #endif
2793                 (file_modified ? " [Modified]" : ""),
2794                 cur, tot, percent);
2795
2796         if (ret >= 0 && ret < trunc_at)
2797                 return ret;  /* it all fit */
2798
2799         return trunc_at;  /* had to truncate */
2800 #undef tot
2801 }
2802
2803 //----- Force refresh of all Lines -----------------------------
2804 static void redraw(int full_screen)
2805 {
2806         place_cursor(0, 0, FALSE);      // put cursor in correct place
2807         clear_to_eos();         // tell terminal to erase display
2808         screen_erase();         // erase the internal screen buffer
2809         last_status_cksum = 0;  // force status update
2810         refresh(full_screen);   // this will redraw the entire display
2811         show_status_line();
2812 }
2813
2814 //----- Format a text[] line into a buffer ---------------------
2815 static char* format_line(char *src /*, int li*/)
2816 {
2817         unsigned char c;
2818         int co;
2819         int ofs = offset;
2820         char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2821
2822         c = '~'; // char in col 0 in non-existent lines is '~'
2823         co = 0;
2824         while (co < columns + tabstop) {
2825                 // have we gone past the end?
2826                 if (src < end) {
2827                         c = *src++;
2828                         if (c == '\n')
2829                                 break;
2830                         if ((c & 0x80) && !Isprint(c)) {
2831                                 c = '.';
2832                         }
2833                         if (c < ' ' || c == 0x7f) {
2834                                 if (c == '\t') {
2835                                         c = ' ';
2836                                         //      co %    8     !=     7
2837                                         while ((co % tabstop) != (tabstop - 1)) {
2838                                                 dest[co++] = c;
2839                                         }
2840                                 } else {
2841                                         dest[co++] = '^';
2842                                         if (c == 0x7f)
2843                                                 c = '?';
2844                                         else
2845                                                 c += '@'; // Ctrl-X -> 'X'
2846                                 }
2847                         }
2848                 }
2849                 dest[co++] = c;
2850                 // discard scrolled-off-to-the-left portion,
2851                 // in tabstop-sized pieces
2852                 if (ofs >= tabstop && co >= tabstop) {
2853                         memmove(dest, dest + tabstop, co);
2854                         co -= tabstop;
2855                         ofs -= tabstop;
2856                 }
2857                 if (src >= end)
2858                         break;
2859         }
2860         // check "short line, gigantic offset" case
2861         if (co < ofs)
2862                 ofs = co;
2863         // discard last scrolled off part
2864         co -= ofs;
2865         dest += ofs;
2866         // fill the rest with spaces
2867         if (co < columns)
2868                 memset(&dest[co], ' ', columns - co);
2869         return dest;
2870 }
2871
2872 //----- Refresh the changed screen lines -----------------------
2873 // Copy the source line from text[] into the buffer and note
2874 // if the current screenline is different from the new buffer.
2875 // If they differ then that line needs redrawing on the terminal.
2876 //
2877 static void refresh(int full_screen)
2878 {
2879 #define old_offset refresh__old_offset
2880
2881         int li, changed;
2882         char *tp, *sp;          // pointer into text[] and screen[]
2883
2884         if (ENABLE_FEATURE_VI_WIN_RESIZE) {
2885                 unsigned c = columns, r = rows;
2886                 get_terminal_width_height(0, &columns, &rows);
2887                 if (rows > MAX_SCR_ROWS) rows = MAX_SCR_ROWS;
2888                 if (columns > MAX_SCR_COLS) columns = MAX_SCR_COLS;
2889                 full_screen |= (c - columns) | (r - rows);
2890         }
2891         sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2892         tp = screenbegin;       // index into text[] of top line
2893
2894         // compare text[] to screen[] and mark screen[] lines that need updating
2895         for (li = 0; li < rows - 1; li++) {
2896                 int cs, ce;                             // column start & end
2897                 char *out_buf;
2898                 // format current text line
2899                 out_buf = format_line(tp /*, li*/);
2900
2901                 // skip to the end of the current text[] line
2902                 if (tp < end) {
2903                         char *t = memchr(tp, '\n', end - tp);
2904                         if (!t) t = end - 1;
2905                         tp = t + 1;
2906                 }
2907
2908                 // see if there are any changes between vitual screen and out_buf
2909                 changed = FALSE;        // assume no change
2910                 cs = 0;
2911                 ce = columns - 1;
2912                 sp = &screen[li * columns];     // start of screen line
2913                 if (full_screen) {
2914                         // force re-draw of every single column from 0 - columns-1
2915                         goto re0;
2916                 }
2917                 // compare newly formatted buffer with virtual screen
2918                 // look forward for first difference between buf and screen
2919                 for (; cs <= ce; cs++) {
2920                         if (out_buf[cs] != sp[cs]) {
2921                                 changed = TRUE; // mark for redraw
2922                                 break;
2923                         }
2924                 }
2925
2926                 // look backward for last difference between out_buf and screen
2927                 for (; ce >= cs; ce--) {
2928                         if (out_buf[ce] != sp[ce]) {
2929                                 changed = TRUE; // mark for redraw
2930                                 break;
2931                         }
2932                 }
2933                 // now, cs is index of first diff, and ce is index of last diff
2934
2935                 // if horz offset has changed, force a redraw
2936                 if (offset != old_offset) {
2937  re0:
2938                         changed = TRUE;
2939                 }
2940
2941                 // make a sanity check of columns indexes
2942                 if (cs < 0) cs = 0;
2943                 if (ce > columns - 1) ce = columns - 1;
2944                 if (cs > ce) { cs = 0; ce = columns - 1; }
2945                 // is there a change between vitual screen and out_buf
2946                 if (changed) {
2947                         // copy changed part of buffer to virtual screen
2948                         memcpy(sp+cs, out_buf+cs, ce-cs+1);
2949
2950                         // move cursor to column of first change
2951                         //if (offset != old_offset) {
2952                         //      // place_cursor is still too stupid
2953                         //      // to handle offsets correctly
2954                         //      place_cursor(li, cs, FALSE);
2955                         //} else {
2956                                 place_cursor(li, cs, TRUE);
2957                         //}
2958
2959                         // write line out to terminal
2960                         fwrite(&sp[cs], ce - cs + 1, 1, stdout);
2961                 }
2962         }
2963
2964         place_cursor(crow, ccol, TRUE);
2965
2966         old_offset = offset;
2967 #undef old_offset
2968 }
2969
2970 //---------------------------------------------------------------------
2971 //----- the Ascii Chart -----------------------------------------------
2972 //
2973 //  00 nul   01 soh   02 stx   03 etx   04 eot   05 enq   06 ack   07 bel
2974 //  08 bs    09 ht    0a nl    0b vt    0c np    0d cr    0e so    0f si
2975 //  10 dle   11 dc1   12 dc2   13 dc3   14 dc4   15 nak   16 syn   17 etb
2976 //  18 can   19 em    1a sub   1b esc   1c fs    1d gs    1e rs    1f us
2977 //  20 sp    21 !     22 "     23 #     24 $     25 %     26 &     27 '
2978 //  28 (     29 )     2a *     2b +     2c ,     2d -     2e .     2f /
2979 //  30 0     31 1     32 2     33 3     34 4     35 5     36 6     37 7
2980 //  38 8     39 9     3a :     3b ;     3c <     3d =     3e >     3f ?
2981 //  40 @     41 A     42 B     43 C     44 D     45 E     46 F     47 G
2982 //  48 H     49 I     4a J     4b K     4c L     4d M     4e N     4f O
2983 //  50 P     51 Q     52 R     53 S     54 T     55 U     56 V     57 W
2984 //  58 X     59 Y     5a Z     5b [     5c \     5d ]     5e ^     5f _
2985 //  60 `     61 a     62 b     63 c     64 d     65 e     66 f     67 g
2986 //  68 h     69 i     6a j     6b k     6c l     6d m     6e n     6f o
2987 //  70 p     71 q     72 r     73 s     74 t     75 u     76 v     77 w
2988 //  78 x     79 y     7a z     7b {     7c |     7d }     7e ~     7f del
2989 //---------------------------------------------------------------------
2990
2991 //----- Execute a Vi Command -----------------------------------
2992 static void do_cmd(char c)
2993 {
2994         const char *msg = msg; // for compiler
2995         char c1, *p, *q, *save_dot;
2996         char buf[12];
2997         int dir = dir; // for compiler
2998         int cnt, i, j;
2999
3000 //      c1 = c; // quiet the compiler
3001 //      cnt = yf = 0; // quiet the compiler
3002 //      msg = p = q = save_dot = buf; // quiet the compiler
3003         memset(buf, '\0', 12);
3004
3005         show_status_line();
3006
3007         /* if this is a cursor key, skip these checks */
3008         switch (c) {
3009                 case VI_K_UP:
3010                 case VI_K_DOWN:
3011                 case VI_K_LEFT:
3012                 case VI_K_RIGHT:
3013                 case VI_K_HOME:
3014                 case VI_K_END:
3015                 case VI_K_PAGEUP:
3016                 case VI_K_PAGEDOWN:
3017                 case VI_K_DELETE:
3018                         goto key_cmd_mode;
3019         }
3020
3021         if (cmd_mode == 2) {
3022                 //  flip-flop Insert/Replace mode
3023                 if (c == VI_K_INSERT)
3024                         goto dc_i;
3025                 // we are 'R'eplacing the current *dot with new char
3026                 if (*dot == '\n') {
3027                         // don't Replace past E-o-l
3028                         cmd_mode = 1;   // convert to insert
3029                 } else {
3030                         if (1 <= c || Isprint(c)) {
3031                                 if (c != 27)
3032                                         dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
3033                                 dot = char_insert(dot, c);      // insert new char
3034                         }
3035                         goto dc1;
3036                 }
3037         }
3038         if (cmd_mode == 1) {
3039                 //  hitting "Insert" twice means "R" replace mode
3040                 if (c == VI_K_INSERT) goto dc5;
3041                 // insert the char c at "dot"
3042                 if (1 <= c || Isprint(c)) {
3043                         dot = char_insert(dot, c);
3044                 }
3045                 goto dc1;
3046         }
3047
3048  key_cmd_mode:
3049         switch (c) {
3050                 //case 0x01:    // soh
3051                 //case 0x09:    // ht
3052                 //case 0x0b:    // vt
3053                 //case 0x0e:    // so
3054                 //case 0x0f:    // si
3055                 //case 0x10:    // dle
3056                 //case 0x11:    // dc1
3057                 //case 0x13:    // dc3
3058 #if ENABLE_FEATURE_VI_CRASHME
3059         case 0x14:                      // dc4  ctrl-T
3060                 crashme = (crashme == 0) ? 1 : 0;
3061                 break;
3062 #endif
3063                 //case 0x16:    // syn
3064                 //case 0x17:    // etb
3065                 //case 0x18:    // can
3066                 //case 0x1c:    // fs
3067                 //case 0x1d:    // gs
3068                 //case 0x1e:    // rs
3069                 //case 0x1f:    // us
3070                 //case '!':     // !-
3071                 //case '#':     // #-
3072                 //case '&':     // &-
3073                 //case '(':     // (-
3074                 //case ')':     // )-
3075                 //case '*':     // *-
3076                 //case '=':     // =-
3077                 //case '@':     // @-
3078                 //case 'F':     // F-
3079                 //case 'K':     // K-
3080                 //case 'Q':     // Q-
3081                 //case 'S':     // S-
3082                 //case 'T':     // T-
3083                 //case 'V':     // V-
3084                 //case '[':     // [-
3085                 //case '\\':    // \-
3086                 //case ']':     // ]-
3087                 //case '_':     // _-
3088                 //case '`':     // `-
3089                 //case 'u':     // u- FIXME- there is no undo
3090                 //case 'v':     // v-
3091         default:                        // unrecognised command
3092                 buf[0] = c;
3093                 buf[1] = '\0';
3094                 if (c < ' ') {
3095                         buf[0] = '^';
3096                         buf[1] = c + '@';
3097                         buf[2] = '\0';
3098                 }
3099                 not_implemented(buf);
3100                 end_cmd_q();    // stop adding to q
3101         case 0x00:                      // nul- ignore
3102                 break;
3103         case 2:                 // ctrl-B  scroll up   full screen
3104         case VI_K_PAGEUP:       // Cursor Key Page Up
3105                 dot_scroll(rows - 2, -1);
3106                 break;
3107         case 4:                 // ctrl-D  scroll down half screen
3108                 dot_scroll((rows - 2) / 2, 1);
3109                 break;
3110         case 5:                 // ctrl-E  scroll down one line
3111                 dot_scroll(1, 1);
3112                 break;
3113         case 6:                 // ctrl-F  scroll down full screen
3114         case VI_K_PAGEDOWN:     // Cursor Key Page Down
3115                 dot_scroll(rows - 2, 1);
3116                 break;
3117         case 7:                 // ctrl-G  show current status
3118                 last_status_cksum = 0;  // force status update
3119                 break;
3120         case 'h':                       // h- move left
3121         case VI_K_LEFT: // cursor key Left
3122         case 8:         // ctrl-H- move left    (This may be ERASE char)
3123         case 0x7f:      // DEL- move left   (This may be ERASE char)
3124                 if (cmdcnt-- > 1) {
3125                         do_cmd(c);
3126                 }                               // repeat cnt
3127                 dot_left();
3128                 break;
3129         case 10:                        // Newline ^J
3130         case 'j':                       // j- goto next line, same col
3131         case VI_K_DOWN: // cursor key Down
3132                 if (cmdcnt-- > 1) {
3133                         do_cmd(c);
3134                 }                               // repeat cnt
3135                 dot_next();             // go to next B-o-l
3136                 dot = move_to_col(dot, ccol + offset);  // try stay in same col
3137                 break;
3138         case 12:                        // ctrl-L  force redraw whole screen
3139         case 18:                        // ctrl-R  force redraw
3140                 place_cursor(0, 0, FALSE);      // put cursor in correct place
3141                 clear_to_eos(); // tel terminal to erase display
3142                 mysleep(10);
3143                 screen_erase(); // erase the internal screen buffer
3144                 last_status_cksum = 0;  // force status update
3145                 refresh(TRUE);  // this will redraw the entire display
3146                 break;
3147         case 13:                        // Carriage Return ^M
3148         case '+':                       // +- goto next line
3149                 if (cmdcnt-- > 1) {
3150                         do_cmd(c);
3151                 }                               // repeat cnt
3152                 dot_next();
3153                 dot_skip_over_ws();
3154                 break;
3155         case 21:                        // ctrl-U  scroll up   half screen
3156                 dot_scroll((rows - 2) / 2, -1);
3157                 break;
3158         case 25:                        // ctrl-Y  scroll up one line
3159                 dot_scroll(1, -1);
3160                 break;
3161         case 27:                        // esc
3162                 if (cmd_mode == 0)
3163                         indicate_error(c);
3164                 cmd_mode = 0;   // stop insrting
3165                 end_cmd_q();
3166                 last_status_cksum = 0;  // force status update
3167                 break;
3168         case ' ':                       // move right
3169         case 'l':                       // move right
3170         case VI_K_RIGHT:        // Cursor Key Right
3171                 if (cmdcnt-- > 1) {
3172                         do_cmd(c);
3173                 }                               // repeat cnt
3174                 dot_right();
3175                 break;
3176 #if ENABLE_FEATURE_VI_YANKMARK
3177         case '"':                       // "- name a register to use for Delete/Yank
3178                 c1 = get_one_char();
3179                 c1 = tolower(c1);
3180                 if (islower(c1)) {
3181                         YDreg = c1 - 'a';
3182                 } else {
3183                         indicate_error(c);
3184                 }
3185                 break;
3186         case '\'':                      // '- goto a specific mark
3187                 c1 = get_one_char();
3188                 c1 = tolower(c1);
3189                 if (islower(c1)) {
3190                         c1 = c1 - 'a';
3191                         // get the b-o-l
3192                         q = mark[(unsigned char) c1];
3193                         if (text <= q && q < end) {
3194                                 dot = q;
3195                                 dot_begin();    // go to B-o-l
3196                                 dot_skip_over_ws();
3197                         }
3198                 } else if (c1 == '\'') {        // goto previous context
3199                         dot = swap_context(dot);        // swap current and previous context
3200                         dot_begin();    // go to B-o-l
3201                         dot_skip_over_ws();
3202                 } else {
3203                         indicate_error(c);
3204                 }
3205                 break;
3206         case 'm':                       // m- Mark a line
3207                 // this is really stupid.  If there are any inserts or deletes
3208                 // between text[0] and dot then this mark will not point to the
3209                 // correct location! It could be off by many lines!
3210                 // Well..., at least its quick and dirty.
3211                 c1 = get_one_char();
3212                 c1 = tolower(c1);
3213                 if (islower(c1)) {
3214                         c1 = c1 - 'a';
3215                         // remember the line
3216                         mark[(int) c1] = dot;
3217                 } else {
3218                         indicate_error(c);
3219                 }
3220                 break;
3221         case 'P':                       // P- Put register before
3222         case 'p':                       // p- put register after
3223                 p = reg[YDreg];
3224                 if (p == 0) {
3225                         status_line_bold("Nothing in register %c", what_reg());
3226                         break;
3227                 }
3228                 // are we putting whole lines or strings
3229                 if (strchr(p, '\n') != NULL) {
3230                         if (c == 'P') {
3231                                 dot_begin();    // putting lines- Put above
3232                         }
3233                         if (c == 'p') {
3234                                 // are we putting after very last line?
3235                                 if (end_line(dot) == (end - 1)) {
3236                                         dot = end;      // force dot to end of text[]
3237                                 } else {
3238                                         dot_next();     // next line, then put before
3239                                 }
3240                         }
3241                 } else {
3242                         if (c == 'p')
3243                                 dot_right();    // move to right, can move to NL
3244                 }
3245                 dot = string_insert(dot, p);    // insert the string
3246                 end_cmd_q();    // stop adding to q
3247                 break;
3248         case 'U':                       // U- Undo; replace current line with original version
3249                 if (reg[Ureg] != 0) {
3250                         p = begin_line(dot);
3251                         q = end_line(dot);
3252                         p = text_hole_delete(p, q);     // delete cur line
3253                         p = string_insert(p, reg[Ureg]);        // insert orig line
3254                         dot = p;
3255                         dot_skip_over_ws();
3256                 }
3257                 break;
3258 #endif /* FEATURE_VI_YANKMARK */
3259         case '$':                       // $- goto end of line
3260         case VI_K_END:          // Cursor Key End
3261                 if (cmdcnt-- > 1) {
3262                         do_cmd(c);
3263                 }                               // repeat cnt
3264                 dot = end_line(dot);
3265                 break;
3266         case '%':                       // %- find matching char of pair () [] {}
3267                 for (q = dot; q < end && *q != '\n'; q++) {
3268                         if (strchr("()[]{}", *q) != NULL) {
3269                                 // we found half of a pair
3270                                 p = find_pair(q, *q);
3271                                 if (p == NULL) {
3272                                         indicate_error(c);
3273                                 } else {
3274                                         dot = p;
3275                                 }
3276                                 break;
3277                         }
3278                 }
3279                 if (*q == '\n')
3280                         indicate_error(c);
3281                 break;
3282         case 'f':                       // f- forward to a user specified char
3283                 last_forward_char = get_one_char();     // get the search char
3284                 //
3285                 // dont separate these two commands. 'f' depends on ';'
3286                 //
3287                 //**** fall through to ... ';'
3288         case ';':                       // ;- look at rest of line for last forward char
3289                 if (cmdcnt-- > 1) {
3290                         do_cmd(';');
3291                 }                               // repeat cnt
3292                 if (last_forward_char == 0)
3293                         break;
3294                 q = dot + 1;
3295                 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3296                         q++;
3297                 }
3298                 if (*q == last_forward_char)
3299                         dot = q;
3300                 break;
3301         case ',':           // repeat latest 'f' in opposite direction
3302                 if (cmdcnt-- > 1) {
3303                         do_cmd(',');
3304                 }                               // repeat cnt
3305                 if (last_forward_char == 0)
3306                         break;
3307                 q = dot - 1;
3308                 while (q >= text && *q != '\n' && *q != last_forward_char) {
3309                         q--;
3310                 }
3311                 if (q >= text && *q == last_forward_char)
3312                         dot = q;
3313                 break;
3314
3315         case '-':                       // -- goto prev line
3316                 if (cmdcnt-- > 1) {
3317                         do_cmd(c);
3318                 }                               // repeat cnt
3319                 dot_prev();
3320                 dot_skip_over_ws();
3321                 break;
3322 #if ENABLE_FEATURE_VI_DOT_CMD
3323         case '.':                       // .- repeat the last modifying command
3324                 // Stuff the last_modifying_cmd back into stdin
3325                 // and let it be re-executed.
3326                 if (lmc_len > 0) {
3327                         last_modifying_cmd[lmc_len] = 0;
3328                         ioq = ioq_start = xstrdup(last_modifying_cmd);
3329                 }
3330                 break;
3331 #endif
3332 #if ENABLE_FEATURE_VI_SEARCH
3333         case '?':                       // /- search for a pattern
3334         case '/':                       // /- search for a pattern
3335                 buf[0] = c;
3336                 buf[1] = '\0';
3337                 q = get_input_line(buf);        // get input line- use "status line"
3338                 if (q[0] && !q[1]) {
3339                         if (last_search_pattern[0])
3340                             last_search_pattern[0] = c;
3341                         goto dc3; // if no pat re-use old pat
3342                 }
3343                 if (q[0]) {       // strlen(q) > 1: new pat- save it and find
3344                         // there is a new pat
3345                         free(last_search_pattern);
3346                         last_search_pattern = xstrdup(q);
3347                         goto dc3;       // now find the pattern
3348                 }
3349                 // user changed mind and erased the "/"-  do nothing
3350                 break;
3351         case 'N':                       // N- backward search for last pattern
3352                 if (cmdcnt-- > 1) {
3353                         do_cmd(c);
3354                 }                               // repeat cnt
3355                 dir = BACK;             // assume BACKWARD search
3356                 p = dot - 1;
3357                 if (last_search_pattern[0] == '?') {
3358                         dir = FORWARD;
3359                         p = dot + 1;
3360                 }
3361                 goto dc4;               // now search for pattern
3362                 break;
3363         case 'n':                       // n- repeat search for last pattern
3364                 // search rest of text[] starting at next char
3365                 // if search fails return orignal "p" not the "p+1" address
3366                 if (cmdcnt-- > 1) {
3367                         do_cmd(c);
3368                 }                               // repeat cnt
3369  dc3:
3370                 if (last_search_pattern == 0) {
3371                         msg = "No previous regular expression";
3372                         goto dc2;
3373                 }
3374                 if (last_search_pattern[0] == '/') {
3375                         dir = FORWARD;  // assume FORWARD search
3376                         p = dot + 1;
3377                 }
3378                 if (last_search_pattern[0] == '?') {
3379                         dir = BACK;
3380                         p = dot - 1;
3381                 }
3382  dc4:
3383                 q = char_search(p, last_search_pattern + 1, dir, FULL);
3384                 if (q != NULL) {
3385                         dot = q;        // good search, update "dot"
3386                         msg = "";
3387                         goto dc2;
3388                 }
3389                 // no pattern found between "dot" and "end"- continue at top
3390                 p = text;
3391                 if (dir == BACK) {
3392                         p = end - 1;
3393                 }
3394                 q = char_search(p, last_search_pattern + 1, dir, FULL);
3395                 if (q != NULL) {        // found something
3396                         dot = q;        // found new pattern- goto it
3397                         msg = "search hit BOTTOM, continuing at TOP";
3398                         if (dir == BACK) {
3399                                 msg = "search hit TOP, continuing at BOTTOM";
3400                         }
3401                 } else {
3402                         msg = "Pattern not found";
3403                 }
3404  dc2:
3405                 if (*msg)
3406                         status_line_bold("%s", msg);
3407                 break;
3408         case '{':                       // {- move backward paragraph
3409                 q = char_search(dot, "\n\n", BACK, FULL);
3410                 if (q != NULL) {        // found blank line
3411                         dot = next_line(q);     // move to next blank line
3412                 }
3413                 break;
3414         case '}':                       // }- move forward paragraph
3415                 q = char_search(dot, "\n\n", FORWARD, FULL);
3416                 if (q != NULL) {        // found blank line
3417                         dot = next_line(q);     // move to next blank line
3418                 }
3419                 break;
3420 #endif /* FEATURE_VI_SEARCH */
3421         case '0':                       // 0- goto begining of line
3422         case '1':                       // 1-
3423         case '2':                       // 2-
3424         case '3':                       // 3-
3425         case '4':                       // 4-
3426         case '5':                       // 5-
3427         case '6':                       // 6-
3428         case '7':                       // 7-
3429         case '8':                       // 8-
3430         case '9':                       // 9-
3431                 if (c == '0' && cmdcnt < 1) {
3432                         dot_begin();    // this was a standalone zero
3433                 } else {
3434                         cmdcnt = cmdcnt * 10 + (c - '0');       // this 0 is part of a number
3435                 }
3436                 break;
3437         case ':':                       // :- the colon mode commands
3438                 p = get_input_line(":");        // get input line- use "status line"
3439 #if ENABLE_FEATURE_VI_COLON
3440                 colon(p);               // execute the command
3441 #else
3442                 if (*p == ':')
3443                         p++;                            // move past the ':'
3444                 cnt = strlen(p);
3445                 if (cnt <= 0)
3446                         break;
3447                 if (strncasecmp(p, "quit", cnt) == 0
3448                  || strncasecmp(p, "q!", cnt) == 0   // delete lines
3449                 ) {
3450                         if (file_modified && p[1] != '!') {
3451                                 status_line_bold("No write since last change (:quit! overrides)");
3452                         } else {
3453                                 editing = 0;
3454                         }
3455                 } else if (strncasecmp(p, "write", cnt) == 0
3456                         || strncasecmp(p, "wq", cnt) == 0
3457                         || strncasecmp(p, "wn", cnt) == 0
3458                         || strncasecmp(p, "x", cnt) == 0
3459                 ) {
3460                         cnt = file_write(current_filename, text, end - 1);
3461                         if (cnt < 0) {
3462                                 if (cnt == -1)
3463                                         status_line_bold("Write error: %s", strerror(errno));
3464                         } else {
3465                                 file_modified = 0;
3466                                 last_file_modified = -1;
3467                                 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3468                                 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3469                                  || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3470                                 ) {
3471                                         editing = 0;
3472                                 }
3473                         }
3474                 } else if (strncasecmp(p, "file", cnt) == 0) {
3475                         last_status_cksum = 0;  // force status update
3476                 } else if (sscanf(p, "%d", &j) > 0) {
3477                         dot = find_line(j);             // go to line # j
3478                         dot_skip_over_ws();
3479                 } else {                // unrecognised cmd
3480                         not_implemented(p);
3481                 }
3482 #endif /* !FEATURE_VI_COLON */
3483                 break;
3484         case '<':                       // <- Left  shift something
3485         case '>':                       // >- Right shift something
3486                 cnt = count_lines(text, dot);   // remember what line we are on
3487                 c1 = get_one_char();    // get the type of thing to delete
3488                 find_range(&p, &q, c1);
3489                 yank_delete(p, q, 1, YANKONLY); // save copy before change
3490                 p = begin_line(p);
3491                 q = end_line(q);
3492                 i = count_lines(p, q);  // # of lines we are shifting
3493                 for ( ; i > 0; i--, p = next_line(p)) {
3494                         if (c == '<') {
3495                                 // shift left- remove tab or 8 spaces
3496                                 if (*p == '\t') {
3497                                         // shrink buffer 1 char
3498                                         text_hole_delete(p, p);
3499                                 } else if (*p == ' ') {
3500                                         // we should be calculating columns, not just SPACE
3501                                         for (j = 0; *p == ' ' && j < tabstop; j++) {
3502                                                 text_hole_delete(p, p);
3503                                         }
3504                                 }
3505                         } else if (c == '>') {
3506                                 // shift right -- add tab or 8 spaces
3507                                 char_insert(p, '\t');
3508                         }
3509                 }
3510                 dot = find_line(cnt);   // what line were we on
3511                 dot_skip_over_ws();
3512                 end_cmd_q();    // stop adding to q
3513                 break;
3514         case 'A':                       // A- append at e-o-l
3515                 dot_end();              // go to e-o-l
3516                 //**** fall through to ... 'a'
3517         case 'a':                       // a- append after current char
3518                 if (*dot != '\n')
3519                         dot++;
3520                 goto dc_i;
3521                 break;
3522         case 'B':                       // B- back a blank-delimited Word
3523         case 'E':                       // E- end of a blank-delimited word
3524         case 'W':                       // W- forward a blank-delimited word
3525                 if (cmdcnt-- > 1) {
3526                         do_cmd(c);
3527                 }                               // repeat cnt
3528                 dir = FORWARD;
3529                 if (c == 'B')
3530                         dir = BACK;
3531                 if (c == 'W' || isspace(dot[dir])) {
3532                         dot = skip_thing(dot, 1, dir, S_TO_WS);
3533                         dot = skip_thing(dot, 2, dir, S_OVER_WS);
3534                 }
3535                 if (c != 'W')
3536                         dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3537                 break;
3538         case 'C':                       // C- Change to e-o-l
3539         case 'D':                       // D- delete to e-o-l
3540                 save_dot = dot;
3541                 dot = dollar_line(dot); // move to before NL
3542                 // copy text into a register and delete
3543                 dot = yank_delete(save_dot, dot, 0, YANKDEL);   // delete to e-o-l
3544                 if (c == 'C')
3545                         goto dc_i;      // start inserting
3546 #if ENABLE_FEATURE_VI_DOT_CMD
3547                 if (c == 'D')
3548                         end_cmd_q();    // stop adding to q
3549 #endif
3550                 break;
3551         case 'g':                       // 'gg' goto a line number (from vim)
3552                                         // (default to first line in file)
3553                 c1 = get_one_char();
3554                 if (c1 != 'g') {
3555                         buf[0] = 'g';
3556                         buf[1] = c1;
3557                         buf[2] = '\0';
3558                         not_implemented(buf);
3559                         break;
3560                 }
3561                 if (cmdcnt == 0)
3562                         cmdcnt = 1;
3563                 /* fall through */
3564         case 'G':               // G- goto to a line number (default= E-O-F)
3565                 dot = end - 1;                          // assume E-O-F
3566                 if (cmdcnt > 0) {
3567                         dot = find_line(cmdcnt);        // what line is #cmdcnt
3568                 }
3569                 dot_skip_over_ws();
3570                 break;
3571         case 'H':                       // H- goto top line on screen
3572                 dot = screenbegin;
3573                 if (cmdcnt > (rows - 1)) {
3574                         cmdcnt = (rows - 1);
3575                 }
3576                 if (cmdcnt-- > 1) {
3577                         do_cmd('+');
3578                 }                               // repeat cnt
3579                 dot_skip_over_ws();
3580                 break;
3581         case 'I':                       // I- insert before first non-blank
3582                 dot_begin();    // 0
3583                 dot_skip_over_ws();
3584                 //**** fall through to ... 'i'
3585         case 'i':                       // i- insert before current char
3586         case VI_K_INSERT:       // Cursor Key Insert
3587  dc_i:
3588                 cmd_mode = 1;   // start insrting
3589                 break;
3590         case 'J':                       // J- join current and next lines together
3591                 if (cmdcnt-- > 2) {
3592                         do_cmd(c);
3593                 }                               // repeat cnt
3594                 dot_end();              // move to NL
3595                 if (dot < end - 1) {    // make sure not last char in text[]
3596                         *dot++ = ' ';   // replace NL with space
3597                         file_modified++;
3598                         while (isblank(*dot)) { // delete leading WS
3599                                 dot_delete();
3600                         }
3601                 }
3602                 end_cmd_q();    // stop adding to q
3603                 break;
3604         case 'L':                       // L- goto bottom line on screen
3605                 dot = end_screen();
3606                 if (cmdcnt > (rows - 1)) {
3607                         cmdcnt = (rows - 1);
3608                 }
3609                 if (cmdcnt-- > 1) {
3610                         do_cmd('-');
3611                 }                               // repeat cnt
3612                 dot_begin();
3613                 dot_skip_over_ws();
3614                 break;
3615         case 'M':                       // M- goto middle line on screen
3616                 dot = screenbegin;
3617                 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3618                         dot = next_line(dot);
3619                 break;
3620         case 'O':                       // O- open a empty line above
3621                 //    0i\n ESC -i
3622                 p = begin_line(dot);
3623                 if (p[-1] == '\n') {
3624                         dot_prev();
3625         case 'o':                       // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3626                         dot_end();
3627                         dot = char_insert(dot, '\n');
3628                 } else {
3629                         dot_begin();    // 0
3630                         dot = char_insert(dot, '\n');   // i\n ESC
3631                         dot_prev();     // -
3632                 }
3633                 goto dc_i;
3634                 break;
3635         case 'R':                       // R- continuous Replace char
3636  dc5:
3637                 cmd_mode = 2;
3638                 break;
3639         case VI_K_DELETE:
3640                 c = 'x';
3641                 // fall through
3642         case 'X':                       // X- delete char before dot
3643         case 'x':                       // x- delete the current char
3644         case 's':                       // s- substitute the current char
3645                 if (cmdcnt-- > 1) {
3646                         do_cmd(c);
3647                 }                               // repeat cnt
3648                 dir = 0;
3649                 if (c == 'X')
3650                         dir = -1;
3651                 if (dot[dir] != '\n') {
3652                         if (c == 'X')
3653                                 dot--;  // delete prev char
3654                         dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
3655                 }
3656                 if (c == 's')
3657                         goto dc_i;      // start insrting
3658                 end_cmd_q();    // stop adding to q
3659                 break;
3660         case 'Z':                       // Z- if modified, {write}; exit
3661                 // ZZ means to save file (if necessary), then exit
3662                 c1 = get_one_char();
3663                 if (c1 != 'Z') {
3664                         indicate_error(c);
3665                         break;
3666                 }
3667                 if (file_modified) {
3668                         if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3669                                 status_line_bold("\"%s\" File is read only", current_filename);
3670                                 break;
3671                         }
3672                         cnt = file_write(current_filename, text, end - 1);
3673                         if (cnt < 0) {
3674                                 if (cnt == -1)
3675                                         status_line_bold("Write error: %s", strerror(errno));
3676                         } else if (cnt == (end - 1 - text + 1)) {
3677                                 editing = 0;
3678                         }
3679                 } else {
3680                         editing = 0;
3681                 }
3682                 break;
3683         case '^':                       // ^- move to first non-blank on line
3684                 dot_begin();
3685                 dot_skip_over_ws();
3686                 break;
3687         case 'b':                       // b- back a word
3688         case 'e':                       // e- end of word
3689                 if (cmdcnt-- > 1) {
3690                         do_cmd(c);
3691                 }                               // repeat cnt
3692                 dir = FORWARD;
3693                 if (c == 'b')
3694                         dir = BACK;
3695                 if ((dot + dir) < text || (dot + dir) > end - 1)
3696                         break;
3697                 dot += dir;
3698                 if (isspace(*dot)) {
3699                         dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3700                 }
3701                 if (isalnum(*dot) || *dot == '_') {
3702                         dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3703                 } else if (ispunct(*dot)) {
3704                         dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3705                 }
3706                 break;
3707         case 'c':                       // c- change something
3708         case 'd':                       // d- delete something
3709 #if ENABLE_FEATURE_VI_YANKMARK
3710         case 'y':                       // y- yank   something
3711         case 'Y':                       // Y- Yank a line
3712 #endif
3713         {
3714                 int yf, ml, whole = 0;
3715                 yf = YANKDEL;   // assume either "c" or "d"
3716 #if ENABLE_FEATURE_VI_YANKMARK
3717                 if (c == 'y' || c == 'Y')
3718                         yf = YANKONLY;
3719 #endif
3720                 c1 = 'y';
3721                 if (c != 'Y')
3722                         c1 = get_one_char();    // get the type of thing to delete
3723                 // determine range, and whether it spans lines
3724                 ml = find_range(&p, &q, c1);
3725                 if (c1 == 27) { // ESC- user changed mind and wants out
3726                         c = c1 = 27;    // Escape- do nothing
3727                 } else if (strchr("wW", c1)) {
3728                         if (c == 'c') {
3729                                 // don't include trailing WS as part of word
3730                                 while (isblank(*q)) {
3731                                         if (q <= text || q[-1] == '\n')
3732                                                 break;
3733                                         q--;
3734                                 }
3735                         }
3736                         dot = yank_delete(p, q, ml, yf);        // delete word
3737                 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3738                         // partial line copy text into a register and delete
3739                         dot = yank_delete(p, q, ml, yf);        // delete word
3740                 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3741                         // whole line copy text into a register and delete
3742                         dot = yank_delete(p, q, ml, yf);        // delete lines
3743                         whole = 1;
3744                 } else {
3745                         // could not recognize object
3746                         c = c1 = 27;    // error-
3747                         ml = 0;
3748                         indicate_error(c);
3749                 }
3750                 if (ml && whole) {
3751                         if (c == 'c') {
3752                                 dot = char_insert(dot, '\n');
3753                                 // on the last line of file don't move to prev line
3754                                 if (whole && dot != (end-1)) {
3755                                         dot_prev();
3756                                 }
3757                         } else if (c == 'd') {
3758                                 dot_begin();
3759                                 dot_skip_over_ws();
3760                         }
3761                 }
3762                 if (c1 != 27) {
3763                         // if CHANGING, not deleting, start inserting after the delete
3764                         if (c == 'c') {
3765                                 strcpy(buf, "Change");
3766                                 goto dc_i;      // start inserting
3767                         }
3768                         if (c == 'd') {
3769                                 strcpy(buf, "Delete");
3770                         }
3771 #if ENABLE_FEATURE_VI_YANKMARK
3772                         if (c == 'y' || c == 'Y') {
3773                                 strcpy(buf, "Yank");
3774                         }
3775                         p = reg[YDreg];
3776                         q = p + strlen(p);
3777                         for (cnt = 0; p <= q; p++) {
3778                                 if (*p == '\n')
3779                                         cnt++;
3780                         }
3781                         status_line("%s %d lines (%d chars) using [%c]",
3782                                 buf, cnt, strlen(reg[YDreg]), what_reg());
3783 #endif
3784                         end_cmd_q();    // stop adding to q
3785                 }
3786                 break;
3787         }
3788         case 'k':                       // k- goto prev line, same col
3789         case VI_K_UP:           // cursor key Up
3790                 if (cmdcnt-- > 1) {
3791                         do_cmd(c);
3792                 }                               // repeat cnt
3793                 dot_prev();
3794                 dot = move_to_col(dot, ccol + offset);  // try stay in same col
3795                 break;
3796         case 'r':                       // r- replace the current char with user input
3797                 c1 = get_one_char();    // get the replacement char
3798                 if (*dot != '\n') {
3799                         *dot = c1;
3800                         file_modified++;
3801                 }
3802                 end_cmd_q();    // stop adding to q
3803                 break;
3804         case 't':                       // t- move to char prior to next x
3805                 last_forward_char = get_one_char();
3806                 do_cmd(';');
3807                 if (*dot == last_forward_char)
3808                         dot_left();
3809                 last_forward_char= 0;
3810                 break;
3811         case 'w':                       // w- forward a word
3812                 if (cmdcnt-- > 1) {
3813                         do_cmd(c);
3814                 }                               // repeat cnt
3815                 if (isalnum(*dot) || *dot == '_') {     // we are on ALNUM
3816                         dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3817                 } else if (ispunct(*dot)) {     // we are on PUNCT
3818                         dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3819                 }
3820                 if (dot < end - 1)
3821                         dot++;          // move over word
3822                 if (isspace(*dot)) {
3823                         dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3824                 }
3825                 break;
3826         case 'z':                       // z-
3827                 c1 = get_one_char();    // get the replacement char
3828                 cnt = 0;
3829                 if (c1 == '.')
3830                         cnt = (rows - 2) / 2;   // put dot at center
3831                 if (c1 == '-')
3832                         cnt = rows - 2; // put dot at bottom
3833                 screenbegin = begin_line(dot);  // start dot at top
3834                 dot_scroll(cnt, -1);
3835                 break;
3836         case '|':                       // |- move to column "cmdcnt"
3837                 dot = move_to_col(dot, cmdcnt - 1);     // try to move to column
3838                 break;
3839         case '~':                       // ~- flip the case of letters   a-z -> A-Z
3840                 if (cmdcnt-- > 1) {
3841                         do_cmd(c);
3842                 }                               // repeat cnt
3843                 if (islower(*dot)) {
3844                         *dot = toupper(*dot);
3845                         file_modified++;
3846                 } else if (isupper(*dot)) {
3847                         *dot = tolower(*dot);
3848                         file_modified++;
3849                 }
3850                 dot_right();
3851                 end_cmd_q();    // stop adding to q
3852                 break;
3853                 //----- The Cursor and Function Keys -----------------------------
3854         case VI_K_HOME: // Cursor Key Home
3855                 dot_begin();
3856                 break;
3857                 // The Fn keys could point to do_macro which could translate them
3858         case VI_K_FUN1: // Function Key F1
3859         case VI_K_FUN2: // Function Key F2
3860         case VI_K_FUN3: // Function Key F3
3861         case VI_K_FUN4: // Function Key F4
3862         case VI_K_FUN5: // Function Key F5
3863         case VI_K_FUN6: // Function Key F6
3864         case VI_K_FUN7: // Function Key F7
3865         case VI_K_FUN8: // Function Key F8
3866         case VI_K_FUN9: // Function Key F9
3867         case VI_K_FUN10:        // Function Key F10
3868         case VI_K_FUN11:        // Function Key F11
3869         case VI_K_FUN12:        // Function Key F12
3870                 break;
3871         }
3872
3873  dc1:
3874         // if text[] just became empty, add back an empty line
3875         if (end == text) {
3876                 char_insert(text, '\n');        // start empty buf with dummy line
3877                 dot = text;
3878         }
3879         // it is OK for dot to exactly equal to end, otherwise check dot validity
3880         if (dot != end) {
3881                 dot = bound_dot(dot);   // make sure "dot" is valid
3882         }
3883 #if ENABLE_FEATURE_VI_YANKMARK
3884         check_context(c);       // update the current context
3885 #endif
3886
3887         if (!isdigit(c))
3888                 cmdcnt = 0;             // cmd was not a number, reset cmdcnt
3889         cnt = dot - begin_line(dot);
3890         // Try to stay off of the Newline
3891         if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3892                 dot--;
3893 }
3894
3895 /* NB!  the CRASHME code is unmaintained, and doesn't currently build */
3896 #if ENABLE_FEATURE_VI_CRASHME
3897 static int totalcmds = 0;
3898 static int Mp = 85;             // Movement command Probability
3899 static int Np = 90;             // Non-movement command Probability
3900 static int Dp = 96;             // Delete command Probability
3901 static int Ip = 97;             // Insert command Probability
3902 static int Yp = 98;             // Yank command Probability
3903 static int Pp = 99;             // Put command Probability
3904 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3905 static const char chars[20] = "\t012345 abcdABCD-=.$";
3906 static const char *const words[20] = {
3907         "this", "is", "a", "test",
3908         "broadcast", "the", "emergency", "of",
3909         "system", "quick", "brown", "fox",
3910         "jumped", "over", "lazy", "dogs",
3911         "back", "January", "Febuary", "March"
3912 };
3913 static const char *const lines[20] = {
3914         "You should have received a copy of the GNU General Public License\n",
3915         "char c, cm, *cmd, *cmd1;\n",
3916         "generate a command by percentages\n",
3917         "Numbers may be typed as a prefix to some commands.\n",
3918         "Quit, discarding changes!\n",
3919         "Forced write, if permission originally not valid.\n",
3920         "In general, any ex or ed command (such as substitute or delete).\n",
3921         "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3922         "Please get w/ me and I will go over it with you.\n",
3923         "The following is a list of scheduled, committed changes.\n",
3924         "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3925         "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3926         "Any question about transactions please contact Sterling Huxley.\n",
3927         "I will try to get back to you by Friday, December 31.\n",
3928         "This Change will be implemented on Friday.\n",
3929         "Let me know if you have problems accessing this;\n",
3930         "Sterling Huxley recently added you to the access list.\n",
3931         "Would you like to go to lunch?\n",
3932         "The last command will be automatically run.\n",
3933         "This is too much english for a computer geek.\n",
3934 };
3935 static char *multilines[20] = {
3936         "You should have received a copy of the GNU General Public License\n",
3937         "char c, cm, *cmd, *cmd1;\n",
3938         "generate a command by percentages\n",
3939         "Numbers may be typed as a prefix to some commands.\n",
3940         "Quit, discarding changes!\n",
3941         "Forced write, if permission originally not valid.\n",
3942         "In general, any ex or ed command (such as substitute or delete).\n",
3943         "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3944         "Please get w/ me and I will go over it with you.\n",
3945         "The following is a list of scheduled, committed changes.\n",
3946         "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3947         "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3948         "Any question about transactions please contact Sterling Huxley.\n",
3949         "I will try to get back to you by Friday, December 31.\n",
3950         "This Change will be implemented on Friday.\n",
3951         "Let me know if you have problems accessing this;\n",
3952         "Sterling Huxley recently added you to the access list.\n",
3953         "Would you like to go to lunch?\n",
3954         "The last command will be automatically run.\n",
3955         "This is too much english for a computer geek.\n",
3956 };
3957
3958 // create a random command to execute
3959 static void crash_dummy()
3960 {
3961         static int sleeptime;   // how long to pause between commands
3962         char c, cm, *cmd, *cmd1;
3963         int i, cnt, thing, rbi, startrbi, percent;
3964
3965         // "dot" movement commands
3966         cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3967
3968         // is there already a command running?
3969         if (chars_to_parse > 0)
3970                 goto cd1;
3971  cd0:
3972         startrbi = rbi = 0;
3973         sleeptime = 0;          // how long to pause between commands
3974         memset(readbuffer, '\0', sizeof(readbuffer));
3975         // generate a command by percentages
3976         percent = (int) lrand48() % 100;        // get a number from 0-99
3977         if (percent < Mp) {     //  Movement commands
3978                 // available commands
3979                 cmd = cmd1;
3980                 M++;
3981         } else if (percent < Np) {      //  non-movement commands
3982                 cmd = "mz<>\'\"";       // available commands
3983                 N++;
3984         } else if (percent < Dp) {      //  Delete commands
3985                 cmd = "dx";             // available commands
3986                 D++;
3987         } else if (percent < Ip) {      //  Inset commands
3988                 cmd = "iIaAsrJ";        // available commands
3989                 I++;
3990         } else if (percent < Yp) {      //  Yank commands
3991                 cmd = "yY";             // available commands
3992                 Y++;
3993         } else if (percent < Pp) {      //  Put commands
3994                 cmd = "pP";             // available commands
3995                 P++;
3996         } else {
3997                 // We do not know how to handle this command, try again
3998                 U++;
3999                 goto cd0;
4000         }
4001         // randomly pick one of the available cmds from "cmd[]"
4002         i = (int) lrand48() % strlen(cmd);
4003         cm = cmd[i];
4004         if (strchr(":\024", cm))
4005                 goto cd0;               // dont allow colon or ctrl-T commands
4006         readbuffer[rbi++] = cm; // put cmd into input buffer
4007
4008         // now we have the command-
4009         // there are 1, 2, and multi char commands
4010         // find out which and generate the rest of command as necessary
4011         if (strchr("dmryz<>\'\"", cm)) {        // 2-char commands
4012                 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4013                 if (cm == 'm' || cm == '\'' || cm == '\"') {    // pick a reg[]
4014                         cmd1 = "abcdefghijklmnopqrstuvwxyz";
4015                 }
4016                 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4017                 c = cmd1[thing];
4018                 readbuffer[rbi++] = c;  // add movement to input buffer
4019         }
4020         if (strchr("iIaAsc", cm)) {     // multi-char commands
4021                 if (cm == 'c') {
4022                         // change some thing
4023                         thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4024                         c = cmd1[thing];
4025                         readbuffer[rbi++] = c;  // add movement to input buffer
4026                 }
4027                 thing = (int) lrand48() % 4;    // what thing to insert
4028                 cnt = (int) lrand48() % 10;     // how many to insert
4029                 for (i = 0; i < cnt; i++) {
4030                         if (thing == 0) {       // insert chars
4031                                 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4032                         } else if (thing == 1) {        // insert words
4033                                 strcat(readbuffer, words[(int) lrand48() % 20]);
4034                                 strcat(readbuffer, " ");
4035                                 sleeptime = 0;  // how fast to type
4036                         } else if (thing == 2) {        // insert lines
4037                                 strcat(readbuffer, lines[(int) lrand48() % 20]);
4038                                 sleeptime = 0;  // how fast to type
4039                         } else {        // insert multi-lines
4040                                 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4041                                 sleeptime = 0;  // how fast to type
4042                         }
4043                 }
4044                 strcat(readbuffer, "\033");
4045         }
4046         chars_to_parse = strlen(readbuffer);
4047  cd1:
4048         totalcmds++;
4049         if (sleeptime > 0)
4050                 mysleep(sleeptime);      // sleep 1/100 sec
4051 }
4052
4053 // test to see if there are any errors
4054 static void crash_test()
4055 {
4056         static time_t oldtim;
4057
4058         time_t tim;
4059         char d[2], msg[80];
4060
4061         msg[0] = '\0';
4062         if (end < text) {
4063                 strcat(msg, "end<text ");
4064         }
4065         if (end > textend) {
4066                 strcat(msg, "end>textend ");
4067         }
4068         if (dot < text) {
4069                 strcat(msg, "dot<text ");
4070         }
4071         if (dot > end) {
4072                 strcat(msg, "dot>end ");
4073         }
4074         if (screenbegin < text) {
4075                 strcat(msg, "screenbegin<text ");
4076         }
4077         if (screenbegin > end - 1) {
4078                 strcat(msg, "screenbegin>end-1 ");
4079         }
4080
4081         if (msg[0]) {
4082                 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4083                         totalcmds, last_input_char, msg, SOs, SOn);
4084                 fflush(stdout);
4085                 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4086                         if (d[0] == '\n' || d[0] == '\r')
4087                                 break;
4088                 }
4089         }
4090         tim = time(NULL);
4091         if (tim >= (oldtim + 3)) {
4092                 sprintf(status_buffer,
4093                                 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4094                                 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4095                 oldtim = tim;
4096         }
4097 }
4098 #endif