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