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