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