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