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