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