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