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