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