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