660c01c3ce5cf98e8538e209ca1d641b3b9d8635
[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, *end_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         end_cur = end_line(d);  // last char of cur line
1098
1099         beg_scr = end_scr = screenbegin;        // first char of screen
1100         end_scr = end_screen(); // last char of screen
1101
1102         if (beg_cur < screenbegin) {
1103                 // "d" is before  top line on screen
1104                 // how many lines do we have to move
1105                 cnt = count_lines(beg_cur, screenbegin);
1106           sc1:
1107                 screenbegin = beg_cur;
1108                 if (cnt > (rows - 1) / 2) {
1109                         // we moved too many lines. put "dot" in middle of screen
1110                         for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1111                                 screenbegin = prev_line(screenbegin);
1112                         }
1113                 }
1114         } else if (beg_cur > end_scr) {
1115                 // "d" is after bottom line on screen
1116                 // how many lines do we have to move
1117                 cnt = count_lines(end_scr, beg_cur);
1118                 if (cnt > (rows - 1) / 2)
1119                         goto sc1;       // too many lines
1120                 for (ro = 0; ro < cnt - 1; ro++) {
1121                         // move screen begin the same amount
1122                         screenbegin = next_line(screenbegin);
1123                         // now, move the end of screen
1124                         end_scr = next_line(end_scr);
1125                         end_scr = end_line(end_scr);
1126                 }
1127         }
1128         // "d" is on screen- find out which row
1129         tp = screenbegin;
1130         for (ro = 0; ro < rows - 1; ro++) {     // drive "ro" to correct row
1131                 if (tp == beg_cur)
1132                         break;
1133                 tp = next_line(tp);
1134         }
1135
1136         // find out what col "d" is on
1137         co = 0;
1138         do {                            // drive "co" to correct column
1139                 if (*tp == '\n' || *tp == '\0')
1140                         break;
1141                 if (*tp == '\t') {
1142                         //         7       - (co %    8  )
1143                         co += ((tabstop - 1) - (co % tabstop));
1144                 } else if (*tp < ' ' || *tp == 127) {
1145                         co++;           // display as ^X, use 2 columns
1146                 }
1147         } while (tp++ < d && ++co);
1148
1149         // "co" is the column where "dot" is.
1150         // The screen has "columns" columns.
1151         // The currently displayed columns are  0+offset -- columns+ofset
1152         // |-------------------------------------------------------------|
1153         //               ^ ^                                ^
1154         //        offset | |------- columns ----------------|
1155         //
1156         // If "co" is already in this range then we do not have to adjust offset
1157         //      but, we do have to subtract the "offset" bias from "co".
1158         // If "co" is outside this range then we have to change "offset".
1159         // If the first char of a line is a tab the cursor will try to stay
1160         //  in column 7, but we have to set offset to 0.
1161
1162         if (co < 0 + offset) {
1163                 offset = co;
1164         }
1165         if (co >= columns + offset) {
1166                 offset = co - columns + 1;
1167         }
1168         // if the first char of the line is a tab, and "dot" is sitting on it
1169         //  force offset to 0.
1170         if (d == beg_cur && *d == '\t') {
1171                 offset = 0;
1172         }
1173         co -= offset;
1174
1175         *row = ro;
1176         *col = co;
1177 }
1178
1179 //----- Text Movement Routines ---------------------------------
1180 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1181 {
1182         while (p > text && p[-1] != '\n')
1183                 p--;                    // go to cur line B-o-l
1184         return (p);
1185 }
1186
1187 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1188 {
1189         while (p < end - 1 && *p != '\n')
1190                 p++;                    // go to cur line E-o-l
1191         return (p);
1192 }
1193
1194 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1195 {
1196         while (p < end - 1 && *p != '\n')
1197                 p++;                    // go to cur line E-o-l
1198         // Try to stay off of the Newline
1199         if (*p == '\n' && (p - begin_line(p)) > 0)
1200                 p--;
1201         return (p);
1202 }
1203
1204 static Byte *prev_line(Byte * p) // return pointer first char prev line
1205 {
1206         p = begin_line(p);      // goto begining of cur line
1207         if (p[-1] == '\n' && p > text)
1208                 p--;                    // step to prev line
1209         p = begin_line(p);      // goto begining of prev line
1210         return (p);
1211 }
1212
1213 static Byte *next_line(Byte * p) // return pointer first char next line
1214 {
1215         p = end_line(p);
1216         if (*p == '\n' && p < end - 1)
1217                 p++;                    // step to next line
1218         return (p);
1219 }
1220
1221 //----- Text Information Routines ------------------------------
1222 static Byte *end_screen(void)
1223 {
1224         Byte *q;
1225         int cnt;
1226
1227         // find new bottom line
1228         q = screenbegin;
1229         for (cnt = 0; cnt < rows - 2; cnt++)
1230                 q = next_line(q);
1231         q = end_line(q);
1232         return (q);
1233 }
1234
1235 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1236 {
1237         Byte *q;
1238         int cnt;
1239
1240         if (stop < start) {     // start and stop are backwards- reverse them
1241                 q = start;
1242                 start = stop;
1243                 stop = q;
1244         }
1245         cnt = 0;
1246         stop = end_line(stop);  // get to end of this line
1247         for (q = start; q <= stop && q <= end - 1; q++) {
1248                 if (*q == '\n')
1249                         cnt++;
1250         }
1251         return (cnt);
1252 }
1253
1254 static Byte *find_line(int li)  // find begining of line #li
1255 {
1256         Byte *q;
1257
1258         for (q = text; li > 1; li--) {
1259                 q = next_line(q);
1260         }
1261         return (q);
1262 }
1263
1264 //----- Dot Movement Routines ----------------------------------
1265 static void dot_left(void)
1266 {
1267         if (dot > text && dot[-1] != '\n')
1268                 dot--;
1269 }
1270
1271 static void dot_right(void)
1272 {
1273         if (dot < end - 1 && *dot != '\n')
1274                 dot++;
1275 }
1276
1277 static void dot_begin(void)
1278 {
1279         dot = begin_line(dot);  // return pointer to first char cur line
1280 }
1281
1282 static void dot_end(void)
1283 {
1284         dot = end_line(dot);    // return pointer to last char cur line
1285 }
1286
1287 static Byte *move_to_col(Byte * p, int l)
1288 {
1289         int co;
1290
1291         p = begin_line(p);
1292         co = 0;
1293         do {
1294                 if (*p == '\n' || *p == '\0')
1295                         break;
1296                 if (*p == '\t') {
1297                         //         7       - (co %    8  )
1298                         co += ((tabstop - 1) - (co % tabstop));
1299                 } else if (*p < ' ' || *p == 127) {
1300                         co++;           // display as ^X, use 2 columns
1301                 }
1302         } while (++co <= l && p++ < end);
1303         return (p);
1304 }
1305
1306 static void dot_next(void)
1307 {
1308         dot = next_line(dot);
1309 }
1310
1311 static void dot_prev(void)
1312 {
1313         dot = prev_line(dot);
1314 }
1315
1316 static void dot_scroll(int cnt, int dir)
1317 {
1318         Byte *q;
1319
1320         for (; cnt > 0; cnt--) {
1321                 if (dir < 0) {
1322                         // scroll Backwards
1323                         // ctrl-Y  scroll up one line
1324                         screenbegin = prev_line(screenbegin);
1325                 } else {
1326                         // scroll Forwards
1327                         // ctrl-E  scroll down one line
1328                         screenbegin = next_line(screenbegin);
1329                 }
1330         }
1331         // make sure "dot" stays on the screen so we dont scroll off
1332         if (dot < screenbegin)
1333                 dot = screenbegin;
1334         q = end_screen();       // find new bottom line
1335         if (dot > q)
1336                 dot = begin_line(q);    // is dot is below bottom line?
1337         dot_skip_over_ws();
1338 }
1339
1340 static void dot_skip_over_ws(void)
1341 {
1342         // skip WS
1343         while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1344                 dot++;
1345 }
1346
1347 static void dot_delete(void)    // delete the char at 'dot'
1348 {
1349         (void) text_hole_delete(dot, dot);
1350 }
1351
1352 static Byte *bound_dot(Byte * p) // make sure  text[0] <= P < "end"
1353 {
1354         if (p >= end && end > text) {
1355                 p = end - 1;
1356                 indicate_error('1');
1357         }
1358         if (p < text) {
1359                 p = text;
1360                 indicate_error('2');
1361         }
1362         return (p);
1363 }
1364
1365 //----- Helper Utility Routines --------------------------------
1366
1367 //----------------------------------------------------------------
1368 //----- Char Routines --------------------------------------------
1369 /* Chars that are part of a word-
1370  *    0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1371  * Chars that are Not part of a word (stoppers)
1372  *    !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1373  * Chars that are WhiteSpace
1374  *    TAB NEWLINE VT FF RETURN SPACE
1375  * DO NOT COUNT NEWLINE AS WHITESPACE
1376  */
1377
1378 static Byte *new_screen(int ro, int co)
1379 {
1380         int li;
1381
1382         free(screen);
1383         screensize = ro * co + 8;
1384         screen = (Byte *) xmalloc(screensize);
1385         // initialize the new screen. assume this will be a empty file.
1386         screen_erase();
1387         //   non-existent text[] lines start with a tilde (~).
1388         for (li = 1; li < ro - 1; li++) {
1389                 screen[(li * co) + 0] = '~';
1390         }
1391         return (screen);
1392 }
1393
1394 static Byte *new_text(int size)
1395 {
1396         if (size < 10240)
1397                 size = 10240;   // have a minimum size for new files
1398         free(text);
1399         text = (Byte *) xmalloc(size + 8);
1400         memset(text, '\0', size);       // clear new text[]
1401         //text += 4;            // leave some room for "oops"
1402         textend = text + size - 1;
1403         //textend -= 4;         // leave some root for "oops"
1404         return (text);
1405 }
1406
1407 #ifdef CONFIG_FEATURE_VI_SEARCH
1408 static int mycmp(Byte * s1, Byte * s2, int len)
1409 {
1410         int i;
1411
1412         i = strncmp((char *) s1, (char *) s2, len);
1413 #ifdef CONFIG_FEATURE_VI_SETOPTS
1414         if (ignorecase) {
1415                 i = strncasecmp((char *) s1, (char *) s2, len);
1416         }
1417 #endif                                                  /* CONFIG_FEATURE_VI_SETOPTS */
1418         return (i);
1419 }
1420
1421 static Byte *char_search(Byte * p, Byte * pat, int dir, int range)      // search for pattern starting at p
1422 {
1423 #ifndef REGEX_SEARCH
1424         Byte *start, *stop;
1425         int len;
1426
1427         len = strlen((char *) pat);
1428         if (dir == FORWARD) {
1429                 stop = end - 1; // assume range is p - end-1
1430                 if (range == LIMITED)
1431                         stop = next_line(p);    // range is to next line
1432                 for (start = p; start < stop; start++) {
1433                         if (mycmp(start, pat, len) == 0) {
1434                                 return (start);
1435                         }
1436                 }
1437         } else if (dir == BACK) {
1438                 stop = text;    // assume range is text - p
1439                 if (range == LIMITED)
1440                         stop = prev_line(p);    // range is to prev line
1441                 for (start = p - len; start >= stop; start--) {
1442                         if (mycmp(start, pat, len) == 0) {
1443                                 return (start);
1444                         }
1445                 }
1446         }
1447         // pattern not found
1448         return (NULL);
1449 #else                                                   /*REGEX_SEARCH */
1450         char *q;
1451         struct re_pattern_buffer preg;
1452         int i;
1453         int size, range;
1454
1455         re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1456         preg.translate = 0;
1457         preg.fastmap = 0;
1458         preg.buffer = 0;
1459         preg.allocated = 0;
1460
1461         // assume a LIMITED forward search
1462         q = next_line(p);
1463         q = end_line(q);
1464         q = end - 1;
1465         if (dir == BACK) {
1466                 q = prev_line(p);
1467                 q = text;
1468         }
1469         // count the number of chars to search over, forward or backward
1470         size = q - p;
1471         if (size < 0)
1472                 size = p - q;
1473         // RANGE could be negative if we are searching backwards
1474         range = q - p;
1475
1476         q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1477         if (q != 0) {
1478                 // The pattern was not compiled
1479                 psbs("bad search pattern: \"%s\": %s", pat, q);
1480                 i = 0;                  // return p if pattern not compiled
1481                 goto cs1;
1482         }
1483
1484         q = p;
1485         if (range < 0) {
1486                 q = p - size;
1487                 if (q < text)
1488                         q = text;
1489         }
1490         // search for the compiled pattern, preg, in p[]
1491         // range < 0-  search backward
1492         // range > 0-  search forward
1493         // 0 < start < size
1494         // re_search() < 0  not found or error
1495         // re_search() > 0  index of found pattern
1496         //            struct pattern    char     int    int    int     struct reg
1497         // re_search (*pattern_buffer,  *string, size,  start, range,  *regs)
1498         i = re_search(&preg, q, size, 0, range, 0);
1499         if (i == -1) {
1500                 p = 0;
1501                 i = 0;                  // return NULL if pattern not found
1502         }
1503   cs1:
1504         if (dir == FORWARD) {
1505                 p = p + i;
1506         } else {
1507                 p = p - i;
1508         }
1509         return (p);
1510 #endif                                                  /*REGEX_SEARCH */
1511 }
1512 #endif                                                  /* CONFIG_FEATURE_VI_SEARCH */
1513
1514 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1515 {
1516         if (c == 22) {          // Is this an ctrl-V?
1517                 p = stupid_insert(p, '^');      // use ^ to indicate literal next
1518                 p--;                    // backup onto ^
1519                 refresh(FALSE); // show the ^
1520                 c = get_one_char();
1521                 *p = c;
1522                 p++;
1523                 file_modified++;        // has the file been modified
1524         } else if (c == 27) {   // Is this an ESC?
1525                 cmd_mode = 0;
1526                 cmdcnt = 0;
1527                 end_cmd_q();    // stop adding to q
1528                 last_status_cksum = 0;  // force status update
1529                 if ((p[-1] != '\n') && (dot>text)) {
1530                         p--;
1531                 }
1532         } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1533                 //     123456789
1534                 if ((p[-1] != '\n') && (dot>text)) {
1535                         p--;
1536                         p = text_hole_delete(p, p);     // shrink buffer 1 char
1537                 }
1538         } else {
1539                 // insert a char into text[]
1540                 Byte *sp;               // "save p"
1541
1542                 if (c == 13)
1543                         c = '\n';       // translate \r to \n
1544                 sp = p;                 // remember addr of insert
1545                 p = stupid_insert(p, c);        // insert the char
1546 #ifdef CONFIG_FEATURE_VI_SETOPTS
1547                 if (showmatch && strchr(")]}", *sp) != NULL) {
1548                         showmatching(sp);
1549                 }
1550                 if (autoindent && c == '\n') {  // auto indent the new line
1551                         Byte *q;
1552
1553                         q = prev_line(p);       // use prev line as templet
1554                         for (; isblnk(*q); q++) {
1555                                 p = stupid_insert(p, *q);       // insert the char
1556                         }
1557                 }
1558 #endif                                                  /* CONFIG_FEATURE_VI_SETOPTS */
1559         }
1560         return (p);
1561 }
1562
1563 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1564 {
1565         p = text_hole_make(p, 1);
1566         if (p != 0) {
1567                 *p = c;
1568                 file_modified++;        // has the file been modified
1569                 p++;
1570         }
1571         return (p);
1572 }
1573
1574 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1575 {
1576         Byte *save_dot, *p, *q;
1577         int cnt;
1578
1579         save_dot = dot;
1580         p = q = dot;
1581
1582         if (strchr("cdy><", c)) {
1583                 // these cmds operate on whole lines
1584                 p = q = begin_line(p);
1585                 for (cnt = 1; cnt < cmdcnt; cnt++) {
1586                         q = next_line(q);
1587                 }
1588                 q = end_line(q);
1589         } else if (strchr("^%$0bBeEft", c)) {
1590                 // These cmds operate on char positions
1591                 do_cmd(c);              // execute movement cmd
1592                 q = dot;
1593         } else if (strchr("wW", c)) {
1594                 do_cmd(c);              // execute movement cmd
1595                 // if we are at the next word's first char
1596                 // step back one char
1597                 // but check the possibilities when it is true
1598                 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1599                                 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1600                                 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1601                         dot--;          // move back off of next word
1602                 if (dot > text && *dot == '\n')
1603                         dot--;          // stay off NL
1604                 q = dot;
1605         } else if (strchr("H-k{", c)) {
1606                 // these operate on multi-lines backwards
1607                 q = end_line(dot);      // find NL
1608                 do_cmd(c);              // execute movement cmd
1609                 dot_begin();
1610                 p = dot;
1611         } else if (strchr("L+j}\r\n", c)) {
1612                 // these operate on multi-lines forwards
1613                 p = begin_line(dot);
1614                 do_cmd(c);              // execute movement cmd
1615                 dot_end();              // find NL
1616                 q = dot;
1617         } else {
1618                 c = 27;                 // error- return an ESC char
1619                 //break;
1620         }
1621         *start = p;
1622         *stop = q;
1623         if (q < p) {
1624                 *start = q;
1625                 *stop = p;
1626         }
1627         dot = save_dot;
1628         return (c);
1629 }
1630
1631 static int st_test(Byte * p, int type, int dir, Byte * tested)
1632 {
1633         Byte c, c0, ci;
1634         int test, inc;
1635
1636         inc = dir;
1637         c = c0 = p[0];
1638         ci = p[inc];
1639         test = 0;
1640
1641         if (type == S_BEFORE_WS) {
1642                 c = ci;
1643                 test = ((!isspace(c)) || c == '\n');
1644         }
1645         if (type == S_TO_WS) {
1646                 c = c0;
1647                 test = ((!isspace(c)) || c == '\n');
1648         }
1649         if (type == S_OVER_WS) {
1650                 c = c0;
1651                 test = ((isspace(c)));
1652         }
1653         if (type == S_END_PUNCT) {
1654                 c = ci;
1655                 test = ((ispunct(c)));
1656         }
1657         if (type == S_END_ALNUM) {
1658                 c = ci;
1659                 test = ((isalnum(c)) || c == '_');
1660         }
1661         *tested = c;
1662         return (test);
1663 }
1664
1665 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1666 {
1667         Byte c;
1668
1669         while (st_test(p, type, dir, &c)) {
1670                 // make sure we limit search to correct number of lines
1671                 if (c == '\n' && --linecnt < 1)
1672                         break;
1673                 if (dir >= 0 && p >= end - 1)
1674                         break;
1675                 if (dir < 0 && p <= text)
1676                         break;
1677                 p += dir;               // move to next char
1678         }
1679         return (p);
1680 }
1681
1682 // find matching char of pair  ()  []  {}
1683 static Byte *find_pair(Byte * p, Byte c)
1684 {
1685         Byte match, *q;
1686         int dir, level;
1687
1688         match = ')';
1689         level = 1;
1690         dir = 1;                        // assume forward
1691         switch (c) {
1692         case '(':
1693                 match = ')';
1694                 break;
1695         case '[':
1696                 match = ']';
1697                 break;
1698         case '{':
1699                 match = '}';
1700                 break;
1701         case ')':
1702                 match = '(';
1703                 dir = -1;
1704                 break;
1705         case ']':
1706                 match = '[';
1707                 dir = -1;
1708                 break;
1709         case '}':
1710                 match = '{';
1711                 dir = -1;
1712                 break;
1713         }
1714         for (q = p + dir; text <= q && q < end; q += dir) {
1715                 // look for match, count levels of pairs  (( ))
1716                 if (*q == c)
1717                         level++;        // increase pair levels
1718                 if (*q == match)
1719                         level--;        // reduce pair level
1720                 if (level == 0)
1721                         break;          // found matching pair
1722         }
1723         if (level != 0)
1724                 q = NULL;               // indicate no match
1725         return (q);
1726 }
1727
1728 #ifdef CONFIG_FEATURE_VI_SETOPTS
1729 // show the matching char of a pair,  ()  []  {}
1730 static void showmatching(Byte * p)
1731 {
1732         Byte *q, *save_dot;
1733
1734         // we found half of a pair
1735         q = find_pair(p, *p);   // get loc of matching char
1736         if (q == NULL) {
1737                 indicate_error('3');    // no matching char
1738         } else {
1739                 // "q" now points to matching pair
1740                 save_dot = dot; // remember where we are
1741                 dot = q;                // go to new loc
1742                 refresh(FALSE); // let the user see it
1743                 (void) mysleep(40);     // give user some time
1744                 dot = save_dot; // go back to old loc
1745                 refresh(FALSE);
1746         }
1747 }
1748 #endif                                                  /* CONFIG_FEATURE_VI_SETOPTS */
1749
1750 //  open a hole in text[]
1751 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1752 {
1753         Byte *src, *dest;
1754         int cnt;
1755
1756         if (size <= 0)
1757                 goto thm0;
1758         src = p;
1759         dest = p + size;
1760         cnt = end - src;        // the rest of buffer
1761         if (memmove(dest, src, cnt) != dest) {
1762                 psbs("can't create room for new characters");
1763         }
1764         memset(p, ' ', size);   // clear new hole
1765         end = end + size;       // adjust the new END
1766         file_modified++;        // has the file been modified
1767   thm0:
1768         return (p);
1769 }
1770
1771 //  close a hole in text[]
1772 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1773 {
1774         Byte *src, *dest;
1775         int cnt, hole_size;
1776
1777         // move forwards, from beginning
1778         // assume p <= q
1779         src = q + 1;
1780         dest = p;
1781         if (q < p) {            // they are backward- swap them
1782                 src = p + 1;
1783                 dest = q;
1784         }
1785         hole_size = q - p + 1;
1786         cnt = end - src;
1787         if (src < text || src > end)
1788                 goto thd0;
1789         if (dest < text || dest >= end)
1790                 goto thd0;
1791         if (src >= end)
1792                 goto thd_atend; // just delete the end of the buffer
1793         if (memmove(dest, src, cnt) != dest) {
1794                 psbs("can't delete the character");
1795         }
1796   thd_atend:
1797         end = end - hole_size;  // adjust the new END
1798         if (dest >= end)
1799                 dest = end - 1; // make sure dest in below end-1
1800         if (end <= text)
1801                 dest = end = text;      // keep pointers valid
1802         file_modified++;        // has the file been modified
1803   thd0:
1804         return (dest);
1805 }
1806
1807 // copy text into register, then delete text.
1808 // if dist <= 0, do not include, or go past, a NewLine
1809 //
1810 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1811 {
1812         Byte *p;
1813
1814         // make sure start <= stop
1815         if (start > stop) {
1816                 // they are backwards, reverse them
1817                 p = start;
1818                 start = stop;
1819                 stop = p;
1820         }
1821         if (dist <= 0) {
1822                 // we can not cross NL boundaries
1823                 p = start;
1824                 if (*p == '\n')
1825                         return (p);
1826                 // dont go past a NewLine
1827                 for (; p + 1 <= stop; p++) {
1828                         if (p[1] == '\n') {
1829                                 stop = p;       // "stop" just before NewLine
1830                                 break;
1831                         }
1832                 }
1833         }
1834         p = start;
1835 #ifdef CONFIG_FEATURE_VI_YANKMARK
1836         text_yank(start, stop, YDreg);
1837 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
1838         if (yf == YANKDEL) {
1839                 p = text_hole_delete(start, stop);
1840         }                                       // delete lines
1841         return (p);
1842 }
1843
1844 static void show_help(void)
1845 {
1846         puts("These features are available:"
1847 #ifdef CONFIG_FEATURE_VI_SEARCH
1848         "\n\tPattern searches with / and ?"
1849 #endif                                                  /* CONFIG_FEATURE_VI_SEARCH */
1850 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1851         "\n\tLast command repeat with \'.\'"
1852 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD */
1853 #ifdef CONFIG_FEATURE_VI_YANKMARK
1854         "\n\tLine marking with  'x"
1855         "\n\tNamed buffers with  \"x"
1856 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
1857 #ifdef CONFIG_FEATURE_VI_READONLY
1858         "\n\tReadonly if vi is called as \"view\""
1859         "\n\tReadonly with -R command line arg"
1860 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
1861 #ifdef CONFIG_FEATURE_VI_SET
1862         "\n\tSome colon mode commands with \':\'"
1863 #endif                                                  /* CONFIG_FEATURE_VI_SET */
1864 #ifdef CONFIG_FEATURE_VI_SETOPTS
1865         "\n\tSettable options with \":set\""
1866 #endif                                                  /* CONFIG_FEATURE_VI_SETOPTS */
1867 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1868         "\n\tSignal catching- ^C"
1869         "\n\tJob suspend and resume with ^Z"
1870 #endif                                                  /* CONFIG_FEATURE_VI_USE_SIGNALS */
1871 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1872         "\n\tAdapt to window re-sizes"
1873 #endif                                                  /* CONFIG_FEATURE_VI_WIN_RESIZE */
1874         );
1875 }
1876
1877 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1878 {
1879         Byte c, b[2];
1880
1881         b[1] = '\0';
1882         strcpy((char *) buf, "");       // init buf
1883         if (strlen((char *) s) <= 0)
1884                 s = (Byte *) "(NULL)";
1885         for (; *s > '\0'; s++) {
1886                 int c_is_no_print;
1887
1888                 c = *s;
1889                 c_is_no_print = c > 127 && !Isprint(c);
1890                 if (c_is_no_print) {
1891                         strcat((char *) buf, SOn);
1892                         c = '.';
1893                 }
1894                 if (c < ' ' || c == 127) {
1895                         strcat((char *) buf, "^");
1896                         if(c == 127)
1897                                 c = '?';
1898                          else
1899                         c += '@';
1900                 }
1901                 b[0] = c;
1902                 strcat((char *) buf, (char *) b);
1903                 if (c_is_no_print)
1904                         strcat((char *) buf, SOs);
1905                 if (*s == '\n') {
1906                         strcat((char *) buf, "$");
1907                 }
1908         }
1909 }
1910
1911 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1912 static void start_new_cmd_q(Byte c)
1913 {
1914         // release old cmd
1915         free(last_modifying_cmd);
1916         // get buffer for new cmd
1917         last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1918         memset(last_modifying_cmd, '\0', BUFSIZ);       // clear new cmd queue
1919         // if there is a current cmd count put it in the buffer first
1920         if (cmdcnt > 0)
1921                 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1922         else // just save char c onto queue
1923                 last_modifying_cmd[0] = c;
1924         adding2q = 1;
1925 }
1926
1927 static void end_cmd_q(void)
1928 {
1929 #ifdef CONFIG_FEATURE_VI_YANKMARK
1930         YDreg = 26;                     // go back to default Yank/Delete reg
1931 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
1932         adding2q = 0;
1933         return;
1934 }
1935 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD */
1936
1937 #if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME)
1938 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
1939 {
1940         int cnt, i;
1941
1942         i = strlen((char *) s);
1943         p = text_hole_make(p, i);
1944         strncpy((char *) p, (char *) s, i);
1945         for (cnt = 0; *s != '\0'; s++) {
1946                 if (*s == '\n')
1947                         cnt++;
1948         }
1949 #ifdef CONFIG_FEATURE_VI_YANKMARK
1950         psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
1951 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
1952         return (p);
1953 }
1954 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
1955
1956 #ifdef CONFIG_FEATURE_VI_YANKMARK
1957 static Byte *text_yank(Byte * p, Byte * q, int dest)    // copy text into a register
1958 {
1959         Byte *t;
1960         int cnt;
1961
1962         if (q < p) {            // they are backwards- reverse them
1963                 t = q;
1964                 q = p;
1965                 p = t;
1966         }
1967         cnt = q - p + 1;
1968         t = reg[dest];
1969         free(t);                //  if already a yank register, free it
1970         t = (Byte *) xmalloc(cnt + 1);  // get a new register
1971         memset(t, '\0', cnt + 1);       // clear new text[]
1972         strncpy((char *) t, (char *) p, cnt);   // copy text[] into bufer
1973         reg[dest] = t;
1974         return (p);
1975 }
1976
1977 static Byte what_reg(void)
1978 {
1979         Byte c;
1980         int i;
1981
1982         i = 0;
1983         c = 'D';                        // default to D-reg
1984         if (0 <= YDreg && YDreg <= 25)
1985                 c = 'a' + (Byte) YDreg;
1986         if (YDreg == 26)
1987                 c = 'D';
1988         if (YDreg == 27)
1989                 c = 'U';
1990         return (c);
1991 }
1992
1993 static void check_context(Byte cmd)
1994 {
1995         // A context is defined to be "modifying text"
1996         // Any modifying command establishes a new context.
1997
1998         if (dot < context_start || dot > context_end) {
1999                 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2000                         // we are trying to modify text[]- make this the current context
2001                         mark[27] = mark[26];    // move cur to prev
2002                         mark[26] = dot; // move local to cur
2003                         context_start = prev_line(prev_line(dot));
2004                         context_end = next_line(next_line(dot));
2005                         //loiter= start_loiter= now;
2006                 }
2007         }
2008         return;
2009 }
2010
2011 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2012 {
2013         Byte *tmp;
2014
2015         // the current context is in mark[26]
2016         // the previous context is in mark[27]
2017         // only swap context if other context is valid
2018         if (text <= mark[27] && mark[27] <= end - 1) {
2019                 tmp = mark[27];
2020                 mark[27] = mark[26];
2021                 mark[26] = tmp;
2022                 p = mark[26];   // where we are going- previous context
2023                 context_start = prev_line(prev_line(prev_line(p)));
2024                 context_end = next_line(next_line(next_line(p)));
2025         }
2026         return (p);
2027 }
2028 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
2029
2030 static int isblnk(Byte c) // is the char a blank or tab
2031 {
2032         return (c == ' ' || c == '\t');
2033 }
2034
2035 //----- Set terminal attributes --------------------------------
2036 static void rawmode(void)
2037 {
2038         tcgetattr(0, &term_orig);
2039         term_vi = term_orig;
2040         term_vi.c_lflag &= (~ICANON & ~ECHO);   // leave ISIG ON- allow intr's
2041         term_vi.c_iflag &= (~IXON & ~ICRNL);
2042         term_vi.c_oflag &= (~ONLCR);
2043         term_vi.c_cc[VMIN] = 1;
2044         term_vi.c_cc[VTIME] = 0;
2045         erase_char = term_vi.c_cc[VERASE];
2046         tcsetattr(0, TCSANOW, &term_vi);
2047 }
2048
2049 static void cookmode(void)
2050 {
2051         fflush(stdout);
2052         tcsetattr(0, TCSANOW, &term_orig);
2053 }
2054
2055 //----- Come here when we get a window resize signal ---------
2056 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2057 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2058 {
2059         signal(SIGWINCH, winch_sig);
2060         if (ENABLE_FEATURE_VI_WIN_RESIZE)
2061            get_terminal_width_height(0, &columns, &rows);
2062         new_screen(rows, columns);      // get memory for virtual screen
2063         redraw(TRUE);           // re-draw the screen
2064 }
2065
2066 //----- Come here when we get a continue signal -------------------
2067 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2068 {
2069         rawmode();                      // terminal to "raw"
2070         last_status_cksum = 0;  // force status update
2071         redraw(TRUE);           // re-draw the screen
2072
2073         signal(SIGTSTP, suspend_sig);
2074         signal(SIGCONT, SIG_DFL);
2075         kill(my_pid, SIGCONT);
2076 }
2077
2078 //----- Come here when we get a Suspend signal -------------------
2079 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2080 {
2081         place_cursor(rows - 1, 0, FALSE);       // go to bottom of screen
2082         clear_to_eol();         // Erase to end of line
2083         cookmode();                     // terminal to "cooked"
2084
2085         signal(SIGCONT, cont_sig);
2086         signal(SIGTSTP, SIG_DFL);
2087         kill(my_pid, SIGTSTP);
2088 }
2089
2090 //----- Come here when we get a signal ---------------------------
2091 static void catch_sig(int sig)
2092 {
2093         signal(SIGINT, catch_sig);
2094         if(sig)
2095                 longjmp(restart, sig);
2096 }
2097 #endif                                                  /* CONFIG_FEATURE_VI_USE_SIGNALS */
2098
2099 static int mysleep(int hund)    // sleep for 'h' 1/100 seconds
2100 {
2101         // Don't hang- Wait 5/100 seconds-  1 Sec= 1000000
2102         fflush(stdout);
2103         FD_ZERO(&rfds);
2104         FD_SET(0, &rfds);
2105         tv.tv_sec = 0;
2106         tv.tv_usec = hund * 10000;
2107         select(1, &rfds, NULL, NULL, &tv);
2108         return (FD_ISSET(0, &rfds));
2109 }
2110
2111 #define readbuffer bb_common_bufsiz1
2112
2113 static int readed_for_parse;
2114
2115 //----- IO Routines --------------------------------------------
2116 static Byte readit(void)        // read (maybe cursor) key from stdin
2117 {
2118         Byte c;
2119         int n;
2120         struct esc_cmds {
2121                 const char *seq;
2122                 Byte val;
2123         };
2124
2125         static const struct esc_cmds esccmds[] = {
2126                 {"OA", (Byte) VI_K_UP},       // cursor key Up
2127                 {"OB", (Byte) VI_K_DOWN},     // cursor key Down
2128                 {"OC", (Byte) VI_K_RIGHT},    // Cursor Key Right
2129                 {"OD", (Byte) VI_K_LEFT},     // cursor key Left
2130                 {"OH", (Byte) VI_K_HOME},     // Cursor Key Home
2131                 {"OF", (Byte) VI_K_END},      // Cursor Key End
2132                 {"[A", (Byte) VI_K_UP},       // cursor key Up
2133                 {"[B", (Byte) VI_K_DOWN},     // cursor key Down
2134                 {"[C", (Byte) VI_K_RIGHT},    // Cursor Key Right
2135                 {"[D", (Byte) VI_K_LEFT},     // cursor key Left
2136                 {"[H", (Byte) VI_K_HOME},     // Cursor Key Home
2137                 {"[F", (Byte) VI_K_END},      // Cursor Key End
2138                 {"[1~", (Byte) VI_K_HOME},     // Cursor Key Home
2139                 {"[2~", (Byte) VI_K_INSERT},  // Cursor Key Insert
2140                 {"[4~", (Byte) VI_K_END},      // Cursor Key End
2141                 {"[5~", (Byte) VI_K_PAGEUP},  // Cursor Key Page Up
2142                 {"[6~", (Byte) VI_K_PAGEDOWN},        // Cursor Key Page Down
2143                 {"OP", (Byte) VI_K_FUN1},     // Function Key F1
2144                 {"OQ", (Byte) VI_K_FUN2},     // Function Key F2
2145                 {"OR", (Byte) VI_K_FUN3},     // Function Key F3
2146                 {"OS", (Byte) VI_K_FUN4},     // Function Key F4
2147                 {"[15~", (Byte) VI_K_FUN5},   // Function Key F5
2148                 {"[17~", (Byte) VI_K_FUN6},   // Function Key F6
2149                 {"[18~", (Byte) VI_K_FUN7},   // Function Key F7
2150                 {"[19~", (Byte) VI_K_FUN8},   // Function Key F8
2151                 {"[20~", (Byte) VI_K_FUN9},   // Function Key F9
2152                 {"[21~", (Byte) VI_K_FUN10},  // Function Key F10
2153                 {"[23~", (Byte) VI_K_FUN11},  // Function Key F11
2154                 {"[24~", (Byte) VI_K_FUN12},  // Function Key F12
2155                 {"[11~", (Byte) VI_K_FUN1},   // Function Key F1
2156                 {"[12~", (Byte) VI_K_FUN2},   // Function Key F2
2157                 {"[13~", (Byte) VI_K_FUN3},   // Function Key F3
2158                 {"[14~", (Byte) VI_K_FUN4},   // Function Key F4
2159         };
2160
2161 #define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
2162
2163         (void) alarm(0);        // turn alarm OFF while we wait for input
2164         fflush(stdout);
2165         n = readed_for_parse;
2166         // get input from User- are there already input chars in Q?
2167         if (n <= 0) {
2168           ri0:
2169                 // the Q is empty, wait for a typed char
2170                 n = read(0, readbuffer, BUFSIZ - 1);
2171                 if (n < 0) {
2172                         if (errno == EINTR)
2173                                 goto ri0;       // interrupted sys call
2174                         if (errno == EBADF)
2175                                 editing = 0;
2176                         if (errno == EFAULT)
2177                                 editing = 0;
2178                         if (errno == EINVAL)
2179                                 editing = 0;
2180                         if (errno == EIO)
2181                                 editing = 0;
2182                         errno = 0;
2183                 }
2184                 if(n <= 0)
2185                         return 0;       // error
2186                 if (readbuffer[0] == 27) {
2187         // This is an ESC char. Is this Esc sequence?
2188         // Could be bare Esc key. See if there are any
2189         // more chars to read after the ESC. This would
2190         // be a Function or Cursor Key sequence.
2191         FD_ZERO(&rfds);
2192         FD_SET(0, &rfds);
2193         tv.tv_sec = 0;
2194         tv.tv_usec = 50000;     // Wait 5/100 seconds- 1 Sec=1000000
2195
2196         // keep reading while there are input chars and room in buffer
2197                         while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) {
2198                 // read the rest of the ESC string
2199                                 int r = read(0, (void *) (readbuffer + n), BUFSIZ - n);
2200                                 if (r > 0) {
2201                                         n += r;
2202                                 }
2203                         }
2204                 }
2205                 readed_for_parse = n;
2206         }
2207         c = readbuffer[0];
2208         if(c == 27 && n > 1) {
2209         // Maybe cursor or function key?
2210                 const struct esc_cmds *eindex;
2211
2212                 for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
2213                         int cnt = strlen(eindex->seq);
2214
2215                         if(n <= cnt)
2216                                 continue;
2217                         if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt))
2218                                 continue;
2219                         // is a Cursor key- put derived value back into Q
2220                         c = eindex->val;
2221                         // for squeeze out the ESC sequence
2222                         n = cnt + 1;
2223                         break;
2224                 }
2225                 if(eindex == &esccmds[ESCCMDS_COUNT]) {
2226                         /* defined ESC sequence not found, set only one ESC */
2227                         n = 1;
2228         }
2229         } else {
2230                 n = 1;
2231         }
2232         // remove key sequence from Q
2233         readed_for_parse -= n;
2234         memmove(readbuffer, readbuffer + n, BUFSIZ - n);
2235         (void) alarm(3);        // we are done waiting for input, turn alarm ON
2236         return (c);
2237 }
2238
2239 //----- IO Routines --------------------------------------------
2240 static Byte get_one_char(void)
2241 {
2242         static Byte c;
2243
2244 #ifdef CONFIG_FEATURE_VI_DOT_CMD
2245         // ! adding2q  && ioq == 0  read()
2246         // ! adding2q  && ioq != 0  *ioq
2247         // adding2q         *last_modifying_cmd= read()
2248         if (!adding2q) {
2249                 // we are not adding to the q.
2250                 // but, we may be reading from a q
2251                 if (ioq == 0) {
2252                         // there is no current q, read from STDIN
2253                         c = readit();   // get the users input
2254                 } else {
2255                         // there is a queue to get chars from first
2256                         c = *ioq++;
2257                         if (c == '\0') {
2258                                 // the end of the q, read from STDIN
2259                                 free(ioq_start);
2260                                 ioq_start = ioq = 0;
2261                                 c = readit();   // get the users input
2262                         }
2263                 }
2264         } else {
2265                 // adding STDIN chars to q
2266                 c = readit();   // get the users input
2267                 if (last_modifying_cmd != 0) {
2268                         int len = strlen((char *) last_modifying_cmd);
2269                         if (len + 1 >= BUFSIZ) {
2270                                 psbs("last_modifying_cmd overrun");
2271                         } else {
2272                                 // add new char to q
2273                                 last_modifying_cmd[len] = c;
2274                         }
2275                 }
2276         }
2277 #else                                                   /* CONFIG_FEATURE_VI_DOT_CMD */
2278         c = readit();           // get the users input
2279 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD */
2280         return (c);                     // return the char, where ever it came from
2281 }
2282
2283 static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
2284 {
2285         Byte buf[BUFSIZ];
2286         Byte c;
2287         int i;
2288         static Byte *obufp = NULL;
2289
2290         strcpy((char *) buf, (char *) prompt);
2291         last_status_cksum = 0;  // force status update
2292         place_cursor(rows - 1, 0, FALSE);       // go to Status line, bottom of screen
2293         clear_to_eol();         // clear the line
2294         write1((char *) prompt);      // write out the :, /, or ? prompt
2295
2296         for (i = strlen((char *) buf); i < BUFSIZ;) {
2297                 c = get_one_char();     // read user input
2298                 if (c == '\n' || c == '\r' || c == 27)
2299                         break;          // is this end of input
2300                 if (c == erase_char || c == 8 || c == 127) {
2301                         // user wants to erase prev char
2302                         i--;            // backup to prev char
2303                         buf[i] = '\0';  // erase the char
2304                         buf[i + 1] = '\0';      // null terminate buffer
2305                         write1("\b \b");     // erase char on screen
2306                         if (i <= 0) {   // user backs up before b-o-l, exit
2307                                 break;
2308                         }
2309                 } else {
2310                         buf[i] = c;     // save char in buffer
2311                         buf[i + 1] = '\0';      // make sure buffer is null terminated
2312                         putchar(c);   // echo the char back to user
2313                         i++;
2314                 }
2315         }
2316         refresh(FALSE);
2317         free(obufp);
2318         obufp = (Byte *) bb_xstrdup((char *) buf);
2319         return (obufp);
2320 }
2321
2322 static int file_size(const Byte * fn) // what is the byte size of "fn"
2323 {
2324         struct stat st_buf;
2325         int cnt, sr;
2326
2327         if (fn == 0 || strlen((char *)fn) <= 0)
2328                 return (-1);
2329         cnt = -1;
2330         sr = stat((char *) fn, &st_buf);        // see if file exists
2331         if (sr >= 0) {
2332                 cnt = (int) st_buf.st_size;
2333         }
2334         return (cnt);
2335 }
2336
2337 static int file_insert(Byte * fn, Byte * p, int size)
2338 {
2339         int fd, cnt;
2340
2341         cnt = -1;
2342 #ifdef CONFIG_FEATURE_VI_READONLY
2343         readonly = FALSE;
2344 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
2345         if (fn == 0 || strlen((char*) fn) <= 0) {
2346                 psbs("No filename given");
2347                 goto fi0;
2348         }
2349         if (size == 0) {
2350                 // OK- this is just a no-op
2351                 cnt = 0;
2352                 goto fi0;
2353         }
2354         if (size < 0) {
2355                 psbs("Trying to insert a negative number (%d) of characters", size);
2356                 goto fi0;
2357         }
2358         if (p < text || p > end) {
2359                 psbs("Trying to insert file outside of memory");
2360                 goto fi0;
2361         }
2362
2363         // see if we can open the file
2364 #ifdef CONFIG_FEATURE_VI_READONLY
2365         if (vi_readonly) goto fi1;              // do not try write-mode
2366 #endif
2367         fd = open((char *) fn, O_RDWR);                 // assume read & write
2368         if (fd < 0) {
2369                 // could not open for writing- maybe file is read only
2370 #ifdef CONFIG_FEATURE_VI_READONLY
2371   fi1:
2372 #endif
2373                 fd = open((char *) fn, O_RDONLY);       // try read-only
2374                 if (fd < 0) {
2375                         psbs("\"%s\" %s", fn, "could not open file");
2376                         goto fi0;
2377                 }
2378 #ifdef CONFIG_FEATURE_VI_READONLY
2379                 // got the file- read-only
2380                 readonly = TRUE;
2381 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
2382         }
2383         p = text_hole_make(p, size);
2384         cnt = read(fd, p, size);
2385         close(fd);
2386         if (cnt < 0) {
2387                 cnt = -1;
2388                 p = text_hole_delete(p, p + size - 1);  // un-do buffer insert
2389                 psbs("could not read file \"%s\"", fn);
2390         } else if (cnt < size) {
2391                 // There was a partial read, shrink unused space text[]
2392                 p = text_hole_delete(p + cnt, p + (size - cnt) - 1);    // un-do buffer insert
2393                 psbs("could not read all of file \"%s\"", fn);
2394         }
2395         if (cnt >= size)
2396                 file_modified++;
2397   fi0:
2398         return (cnt);
2399 }
2400
2401 static int file_write(Byte * fn, Byte * first, Byte * last)
2402 {
2403         int fd, cnt, charcnt;
2404
2405         if (fn == 0) {
2406                 psbs("No current filename");
2407                 return (-2);
2408         }
2409         charcnt = 0;
2410         // FIXIT- use the correct umask()
2411         fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664);
2412         if (fd < 0)
2413                 return (-1);
2414         cnt = last - first + 1;
2415         charcnt = write(fd, first, cnt);
2416         if (charcnt == cnt) {
2417                 // good write
2418                 //file_modified= FALSE; // the file has not been modified
2419         } else {
2420                 charcnt = 0;
2421         }
2422         close(fd);
2423         return (charcnt);
2424 }
2425
2426 //----- Terminal Drawing ---------------------------------------
2427 // The terminal is made up of 'rows' line of 'columns' columns.
2428 // classically this would be 24 x 80.
2429 //  screen coordinates
2430 //  0,0     ...     0,79
2431 //  1,0     ...     1,79
2432 //  .       ...     .
2433 //  .       ...     .
2434 //  22,0    ...     22,79
2435 //  23,0    ...     23,79   status line
2436 //
2437
2438 //----- Move the cursor to row x col (count from 0, not 1) -------
2439 static void place_cursor(int row, int col, int opti)
2440 {
2441         char cm1[BUFSIZ];
2442         char *cm;
2443 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2444         char cm2[BUFSIZ];
2445         Byte *screenp;
2446         // char cm3[BUFSIZ];
2447         int Rrow= last_row;
2448 #endif                                                  /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2449
2450         memset(cm1, '\0', BUFSIZ - 1);  // clear the buffer
2451
2452         if (row < 0) row = 0;
2453         if (row >= rows) row = rows - 1;
2454         if (col < 0) col = 0;
2455         if (col >= columns) col = columns - 1;
2456
2457         //----- 1.  Try the standard terminal ESC sequence
2458         sprintf((char *) cm1, CMrc, row + 1, col + 1);
2459         cm= cm1;
2460         if (! opti) goto pc0;
2461
2462 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2463         //----- find the minimum # of chars to move cursor -------------
2464         //----- 2.  Try moving with discreet chars (Newline, [back]space, ...)
2465         memset(cm2, '\0', BUFSIZ - 1);  // clear the buffer
2466
2467         // move to the correct row
2468         while (row < Rrow) {
2469                 // the cursor has to move up
2470                 strcat(cm2, CMup);
2471                 Rrow--;
2472         }
2473         while (row > Rrow) {
2474                 // the cursor has to move down
2475                 strcat(cm2, CMdown);
2476                 Rrow++;
2477         }
2478
2479         // now move to the correct column
2480         strcat(cm2, "\r");                      // start at col 0
2481         // just send out orignal source char to get to correct place
2482         screenp = &screen[row * columns];       // start of screen line
2483         strncat(cm2, (char* )screenp, col);
2484
2485         //----- 3.  Try some other way of moving cursor
2486         //---------------------------------------------
2487
2488         // pick the shortest cursor motion to send out
2489         cm= cm1;
2490         if (strlen(cm2) < strlen(cm)) {
2491                 cm= cm2;
2492         }  /* else if (strlen(cm3) < strlen(cm)) {
2493                 cm= cm3;
2494         } */
2495 #endif                                                  /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2496   pc0:
2497         write1(cm);                 // move the cursor
2498 }
2499
2500 //----- Erase from cursor to end of line -----------------------
2501 static void clear_to_eol(void)
2502 {
2503         write1(Ceol);   // Erase from cursor to end of line
2504 }
2505
2506 //----- Erase from cursor to end of screen -----------------------
2507 static void clear_to_eos(void)
2508 {
2509         write1(Ceos);   // Erase from cursor to end of screen
2510 }
2511
2512 //----- Start standout mode ------------------------------------
2513 static void standout_start(void) // send "start reverse video" sequence
2514 {
2515         write1(SOs);     // Start reverse video mode
2516 }
2517
2518 //----- End standout mode --------------------------------------
2519 static void standout_end(void) // send "end reverse video" sequence
2520 {
2521         write1(SOn);     // End reverse video mode
2522 }
2523
2524 //----- Flash the screen  --------------------------------------
2525 static void flash(int h)
2526 {
2527         standout_start();       // send "start reverse video" sequence
2528         redraw(TRUE);
2529         (void) mysleep(h);
2530         standout_end();         // send "end reverse video" sequence
2531         redraw(TRUE);
2532 }
2533
2534 static void Indicate_Error(void)
2535 {
2536 #ifdef CONFIG_FEATURE_VI_CRASHME
2537         if (crashme > 0)
2538                 return;                 // generate a random command
2539 #endif                                                  /* CONFIG_FEATURE_VI_CRASHME */
2540         if (!err_method) {
2541                 write1(bell);   // send out a bell character
2542         } else {
2543                 flash(10);
2544         }
2545 }
2546
2547 //----- Screen[] Routines --------------------------------------
2548 //----- Erase the Screen[] memory ------------------------------
2549 static void screen_erase(void)
2550 {
2551         memset(screen, ' ', screensize);        // clear new screen
2552 }
2553
2554 static int bufsum(unsigned char *buf, int count)
2555 {
2556         int sum = 0;
2557         unsigned char *e = buf + count;
2558         while (buf < e)
2559                 sum += *buf++;
2560         return sum;
2561 }
2562
2563 //----- Draw the status line at bottom of the screen -------------
2564 static void show_status_line(void)
2565 {
2566         int cnt = 0, cksum = 0;
2567
2568         // either we already have an error or status message, or we
2569         // create one.
2570         if (!have_status_msg) {
2571                 cnt = format_edit_status();
2572                 cksum = bufsum(status_buffer, cnt);
2573         }
2574         if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2575                 last_status_cksum= cksum;               // remember if we have seen this line
2576                 place_cursor(rows - 1, 0, FALSE);       // put cursor on status line
2577                 write1((char*)status_buffer);
2578                 clear_to_eol();
2579                 if (have_status_msg) {
2580                         if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) >
2581                                         (columns - 1) ) {
2582                                 have_status_msg = 0;
2583                                 Hit_Return();
2584                         }
2585                         have_status_msg = 0;
2586                 }
2587                 place_cursor(crow, ccol, FALSE);        // put cursor back in correct place
2588         }
2589         fflush(stdout);
2590 }
2591
2592 //----- format the status buffer, the bottom line of screen ------
2593 // format status buffer, with STANDOUT mode
2594 static void psbs(const char *format, ...)
2595 {
2596         va_list args;
2597
2598         va_start(args, format);
2599         strcpy((char *) status_buffer, SOs);    // Terminal standout mode on
2600         vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args);
2601         strcat((char *) status_buffer, SOn);    // Terminal standout mode off
2602         va_end(args);
2603
2604         have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2605
2606         return;
2607 }
2608
2609 // format status buffer
2610 static void psb(const char *format, ...)
2611 {
2612         va_list args;
2613
2614         va_start(args, format);
2615         vsprintf((char *) status_buffer, format, args);
2616         va_end(args);
2617
2618         have_status_msg = 1;
2619
2620         return;
2621 }
2622
2623 static void ni(Byte * s) // display messages
2624 {
2625         Byte buf[BUFSIZ];
2626
2627         print_literal(buf, s);
2628         psbs("\'%s\' is not implemented", buf);
2629 }
2630
2631 static int format_edit_status(void)     // show file status on status line
2632 {
2633         int cur, percent, ret, trunc_at;
2634         static int tot;
2635
2636         // file_modified is now a counter rather than a flag.  this
2637         // helps reduce the amount of line counting we need to do.
2638         // (this will cause a mis-reporting of modified status
2639         // once every MAXINT editing operations.)
2640
2641         // it would be nice to do a similar optimization here -- if
2642         // we haven't done a motion that could have changed which line
2643         // we're on, then we shouldn't have to do this count_lines()
2644         cur = count_lines(text, dot);
2645
2646         // reduce counting -- the total lines can't have
2647         // changed if we haven't done any edits.
2648         if (file_modified != last_file_modified) {
2649                 tot = cur + count_lines(dot, end - 1) - 1;
2650                 last_file_modified = file_modified;
2651         }
2652
2653         //    current line         percent
2654         //   -------------    ~~ ----------
2655         //    total lines            100
2656         if (tot > 0) {
2657                 percent = (100 * cur) / tot;
2658         } else {
2659                 cur = tot = 0;
2660                 percent = 100;
2661         }
2662
2663         trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2664                 columns : STATUS_BUFFER_LEN-1;
2665
2666         ret = snprintf((char *) status_buffer, trunc_at+1,
2667 #ifdef CONFIG_FEATURE_VI_READONLY
2668                 "%c %s%s%s %d/%d %d%%",
2669 #else
2670                 "%c %s%s %d/%d %d%%",
2671 #endif
2672                 (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'),
2673                 (cfn != 0 ? (char *) cfn : "No file"),
2674 #ifdef CONFIG_FEATURE_VI_READONLY
2675                 ((vi_readonly || readonly) ? " [Read-only]" : ""),
2676 #endif
2677                 (file_modified ? " [modified]" : ""),
2678                 cur, tot, percent);
2679
2680         if (ret >= 0 && ret < trunc_at)
2681                 return ret;  /* it all fit */
2682
2683         return trunc_at;  /* had to truncate */
2684 }
2685
2686 //----- Force refresh of all Lines -----------------------------
2687 static void redraw(int full_screen)
2688 {
2689         place_cursor(0, 0, FALSE);      // put cursor in correct place
2690         clear_to_eos();         // tel terminal to erase display
2691         screen_erase();         // erase the internal screen buffer
2692         last_status_cksum = 0;  // force status update
2693         refresh(full_screen);   // this will redraw the entire display
2694         show_status_line();
2695 }
2696
2697 //----- Format a text[] line into a buffer ---------------------
2698 static void format_line(Byte *dest, Byte *src, int li)
2699 {
2700         int co;
2701         Byte c;
2702
2703         for (co= 0; co < MAX_SCR_COLS; co++) {
2704                 c= ' ';         // assume blank
2705                 if (li > 0 && co == 0) {
2706                         c = '~';        // not first line, assume Tilde
2707                 }
2708                 // are there chars in text[] and have we gone past the end
2709                 if (text < end && src < end) {
2710                         c = *src++;
2711                 }
2712                 if (c == '\n')
2713                         break;
2714                 if (c > 127 && !Isprint(c)) {
2715                         c = '.';
2716                 }
2717                 if (c < ' ' || c == 127) {
2718                         if (c == '\t') {
2719                                 c = ' ';
2720                                 //       co %    8     !=     7
2721                                 for (; (co % tabstop) != (tabstop - 1); co++) {
2722                                         dest[co] = c;
2723                                 }
2724                         } else {
2725                                 dest[co++] = '^';
2726                                 if(c == 127)
2727                                         c = '?';
2728                                  else
2729                                         c += '@';       // make it visible
2730                         }
2731                 }
2732                 // the co++ is done here so that the column will
2733                 // not be overwritten when we blank-out the rest of line
2734                 dest[co] = c;
2735                 if (src >= end)
2736                         break;
2737         }
2738 }
2739
2740 //----- Refresh the changed screen lines -----------------------
2741 // Copy the source line from text[] into the buffer and note
2742 // if the current screenline is different from the new buffer.
2743 // If they differ then that line needs redrawing on the terminal.
2744 //
2745 static void refresh(int full_screen)
2746 {
2747         static int old_offset;
2748         int li, changed;
2749         Byte buf[MAX_SCR_COLS];
2750         Byte *tp, *sp;          // pointer into text[] and screen[]
2751 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2752         int last_li= -2;                                // last line that changed- for optimizing cursor movement
2753 #endif                                                  /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2754
2755         if (ENABLE_FEATURE_VI_WIN_RESIZE)
2756                 get_terminal_width_height(0, &columns, &rows);
2757         sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2758         tp = screenbegin;       // index into text[] of top line
2759
2760         // compare text[] to screen[] and mark screen[] lines that need updating
2761         for (li = 0; li < rows - 1; li++) {
2762                 int cs, ce;                             // column start & end
2763                 memset(buf, ' ', MAX_SCR_COLS);         // blank-out the buffer
2764                 buf[MAX_SCR_COLS-1] = 0;                // NULL terminate the buffer
2765                 // format current text line into buf
2766                 format_line(buf, tp, li);
2767
2768                 // skip to the end of the current text[] line
2769                 while (tp < end && *tp++ != '\n') /*no-op*/ ;
2770
2771                 // see if there are any changes between vitual screen and buf
2772                 changed = FALSE;        // assume no change
2773                 cs= 0;
2774                 ce= columns-1;
2775                 sp = &screen[li * columns];     // start of screen line
2776                 if (full_screen) {
2777                         // force re-draw of every single column from 0 - columns-1
2778                         goto re0;
2779                 }
2780                 // compare newly formatted buffer with virtual screen
2781                 // look forward for first difference between buf and screen
2782                 for ( ; cs <= ce; cs++) {
2783                         if (buf[cs + offset] != sp[cs]) {
2784                                 changed = TRUE; // mark for redraw
2785                                 break;
2786                         }
2787                 }
2788
2789                 // look backward for last difference between buf and screen
2790                 for ( ; ce >= cs; ce--) {
2791                         if (buf[ce + offset] != sp[ce]) {
2792                                 changed = TRUE; // mark for redraw
2793                                 break;
2794                         }
2795                 }
2796                 // now, cs is index of first diff, and ce is index of last diff
2797
2798                 // if horz offset has changed, force a redraw
2799                 if (offset != old_offset) {
2800   re0:
2801                         changed = TRUE;
2802                 }
2803
2804                 // make a sanity check of columns indexes
2805                 if (cs < 0) cs= 0;
2806                 if (ce > columns-1) ce= columns-1;
2807                 if (cs > ce) {  cs= 0;  ce= columns-1;  }
2808                 // is there a change between vitual screen and buf
2809                 if (changed) {
2810                         //  copy changed part of buffer to virtual screen
2811                         memmove(sp+cs, buf+(cs+offset), ce-cs+1);
2812
2813                         // move cursor to column of first change
2814                         if (offset != old_offset) {
2815                                 // opti_cur_move is still too stupid
2816                                 // to handle offsets correctly
2817                                 place_cursor(li, cs, FALSE);
2818                         } else {
2819 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2820                                 // if this just the next line
2821                                 //  try to optimize cursor movement
2822                                 //  otherwise, use standard ESC sequence
2823                                 place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE);
2824                                 last_li= li;
2825 #else                                                   /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2826                                 place_cursor(li, cs, FALSE);    // use standard ESC sequence
2827 #endif                                                  /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2828                         }
2829
2830                         // write line out to terminal
2831                         {
2832                                 int nic = ce-cs+1;
2833                                 char *out = (char*)sp+cs;
2834
2835                                 while(nic-- > 0) {
2836                                         putchar(*out);
2837                                         out++;
2838                                 }
2839                         }
2840 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2841                         last_row = li;
2842 #endif                                                  /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2843                 }
2844         }
2845
2846 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
2847         place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE);
2848         last_row = crow;
2849 #else
2850         place_cursor(crow, ccol, FALSE);
2851 #endif                                                  /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
2852
2853         if (offset != old_offset)
2854                 old_offset = offset;
2855 }
2856
2857 //---------------------------------------------------------------------
2858 //----- the Ascii Chart -----------------------------------------------
2859 //
2860 //  00 nul   01 soh   02 stx   03 etx   04 eot   05 enq   06 ack   07 bel
2861 //  08 bs    09 ht    0a nl    0b vt    0c np    0d cr    0e so    0f si
2862 //  10 dle   11 dc1   12 dc2   13 dc3   14 dc4   15 nak   16 syn   17 etb
2863 //  18 can   19 em    1a sub   1b esc   1c fs    1d gs    1e rs    1f us
2864 //  20 sp    21 !     22 "     23 #     24 $     25 %     26 &     27 '
2865 //  28 (     29 )     2a *     2b +     2c ,     2d -     2e .     2f /
2866 //  30 0     31 1     32 2     33 3     34 4     35 5     36 6     37 7
2867 //  38 8     39 9     3a :     3b ;     3c <     3d =     3e >     3f ?
2868 //  40 @     41 A     42 B     43 C     44 D     45 E     46 F     47 G
2869 //  48 H     49 I     4a J     4b K     4c L     4d M     4e N     4f O
2870 //  50 P     51 Q     52 R     53 S     54 T     55 U     56 V     57 W
2871 //  58 X     59 Y     5a Z     5b [     5c \     5d ]     5e ^     5f _
2872 //  60 `     61 a     62 b     63 c     64 d     65 e     66 f     67 g
2873 //  68 h     69 i     6a j     6b k     6c l     6d m     6e n     6f o
2874 //  70 p     71 q     72 r     73 s     74 t     75 u     76 v     77 w
2875 //  78 x     79 y     7a z     7b {     7c |     7d }     7e ~     7f del
2876 //---------------------------------------------------------------------
2877
2878 //----- Execute a Vi Command -----------------------------------
2879 static void do_cmd(Byte c)
2880 {
2881         Byte c1, *p, *q, *msg, buf[9], *save_dot;
2882         int cnt, i, j, dir, yf;
2883
2884         c1 = c;                         // quiet the compiler
2885         cnt = yf = dir = 0;     // quiet the compiler
2886         p = q = save_dot = msg = buf;   // quiet the compiler
2887         memset(buf, '\0', 9);   // clear buf
2888
2889         show_status_line();
2890
2891         /* if this is a cursor key, skip these checks */
2892         switch (c) {
2893                 case VI_K_UP:
2894                 case VI_K_DOWN:
2895                 case VI_K_LEFT:
2896                 case VI_K_RIGHT:
2897                 case VI_K_HOME:
2898                 case VI_K_END:
2899                 case VI_K_PAGEUP:
2900                 case VI_K_PAGEDOWN:
2901                         goto key_cmd_mode;
2902         }
2903
2904         if (cmd_mode == 2) {
2905                 //  flip-flop Insert/Replace mode
2906                 if (c == VI_K_INSERT) goto dc_i;
2907                 // we are 'R'eplacing the current *dot with new char
2908                 if (*dot == '\n') {
2909                         // don't Replace past E-o-l
2910                         cmd_mode = 1;   // convert to insert
2911                 } else {
2912                         if (1 <= c || Isprint(c)) {
2913                                 if (c != 27)
2914                                         dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
2915                                 dot = char_insert(dot, c);      // insert new char
2916                         }
2917                         goto dc1;
2918                 }
2919         }
2920         if (cmd_mode == 1) {
2921                 //  hitting "Insert" twice means "R" replace mode
2922                 if (c == VI_K_INSERT) goto dc5;
2923                 // insert the char c at "dot"
2924                 if (1 <= c || Isprint(c)) {
2925                         dot = char_insert(dot, c);
2926                 }
2927                 goto dc1;
2928         }
2929
2930 key_cmd_mode:
2931         switch (c) {
2932                 //case 0x01:    // soh
2933                 //case 0x09:    // ht
2934                 //case 0x0b:    // vt
2935                 //case 0x0e:    // so
2936                 //case 0x0f:    // si
2937                 //case 0x10:    // dle
2938                 //case 0x11:    // dc1
2939                 //case 0x13:    // dc3
2940 #ifdef CONFIG_FEATURE_VI_CRASHME
2941         case 0x14:                      // dc4  ctrl-T
2942                 crashme = (crashme == 0) ? 1 : 0;
2943                 break;
2944 #endif                                                  /* CONFIG_FEATURE_VI_CRASHME */
2945                 //case 0x16:    // syn
2946                 //case 0x17:    // etb
2947                 //case 0x18:    // can
2948                 //case 0x1c:    // fs
2949                 //case 0x1d:    // gs
2950                 //case 0x1e:    // rs
2951                 //case 0x1f:    // us
2952                 //case '!':     // !-
2953                 //case '#':     // #-
2954                 //case '&':     // &-
2955                 //case '(':     // (-
2956                 //case ')':     // )-
2957                 //case '*':     // *-
2958                 //case ',':     // ,-
2959                 //case '=':     // =-
2960                 //case '@':     // @-
2961                 //case 'F':     // F-
2962                 //case 'K':     // K-
2963                 //case 'Q':     // Q-
2964                 //case 'S':     // S-
2965                 //case 'T':     // T-
2966                 //case 'V':     // V-
2967                 //case '[':     // [-
2968                 //case '\\':    // \-
2969                 //case ']':     // ]-
2970                 //case '_':     // _-
2971                 //case '`':     // `-
2972                 //case 'g':     // g-
2973                 //case 'u':     // u- FIXME- there is no undo
2974                 //case 'v':     // v-
2975         default:                        // unrecognised command
2976                 buf[0] = c;
2977                 buf[1] = '\0';
2978                 if (c < ' ') {
2979                         buf[0] = '^';
2980                         buf[1] = c + '@';
2981                         buf[2] = '\0';
2982                 }
2983                 ni((Byte *) buf);
2984                 end_cmd_q();    // stop adding to q
2985         case 0x00:                      // nul- ignore
2986                 break;
2987         case 2:                 // ctrl-B  scroll up   full screen
2988         case VI_K_PAGEUP:       // Cursor Key Page Up
2989                 dot_scroll(rows - 2, -1);
2990                 break;
2991 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2992         case 0x03:                      // ctrl-C   interrupt
2993                 longjmp(restart, 1);
2994                 break;
2995         case 26:                        // ctrl-Z suspend
2996                 suspend_sig(SIGTSTP);
2997                 break;
2998 #endif                                                  /* CONFIG_FEATURE_VI_USE_SIGNALS */
2999         case 4:                 // ctrl-D  scroll down half screen
3000                 dot_scroll((rows - 2) / 2, 1);
3001                 break;
3002         case 5:                 // ctrl-E  scroll down one line
3003                 dot_scroll(1, 1);
3004                 break;
3005         case 6:                 // ctrl-F  scroll down full screen
3006         case VI_K_PAGEDOWN:     // Cursor Key Page Down
3007                 dot_scroll(rows - 2, 1);
3008                 break;
3009         case 7:                 // ctrl-G  show current status
3010                 last_status_cksum = 0;  // force status update
3011                 break;
3012         case 'h':                       // h- move left
3013         case VI_K_LEFT: // cursor key Left
3014         case 8:         // ctrl-H- move left    (This may be ERASE char)
3015         case 127:       // DEL- move left   (This may be ERASE char)
3016                 if (cmdcnt-- > 1) {
3017                         do_cmd(c);
3018                 }                               // repeat cnt
3019                 dot_left();
3020                 break;
3021         case 10:                        // Newline ^J
3022         case 'j':                       // j- goto next line, same col
3023         case VI_K_DOWN: // cursor key Down
3024                 if (cmdcnt-- > 1) {
3025                         do_cmd(c);
3026                 }                               // repeat cnt
3027                 dot_next();             // go to next B-o-l
3028                 dot = move_to_col(dot, ccol + offset);  // try stay in same col
3029                 break;
3030         case 12:                        // ctrl-L  force redraw whole screen
3031         case 18:                        // ctrl-R  force redraw
3032                 place_cursor(0, 0, FALSE);      // put cursor in correct place
3033                 clear_to_eos(); // tel terminal to erase display
3034                 (void) mysleep(10);
3035                 screen_erase(); // erase the internal screen buffer
3036                 last_status_cksum = 0;  // force status update
3037                 refresh(TRUE);  // this will redraw the entire display
3038                 break;
3039         case 13:                        // Carriage Return ^M
3040         case '+':                       // +- goto next line
3041                 if (cmdcnt-- > 1) {
3042                         do_cmd(c);
3043                 }                               // repeat cnt
3044                 dot_next();
3045                 dot_skip_over_ws();
3046                 break;
3047         case 21:                        // ctrl-U  scroll up   half screen
3048                 dot_scroll((rows - 2) / 2, -1);
3049                 break;
3050         case 25:                        // ctrl-Y  scroll up one line
3051                 dot_scroll(1, -1);
3052                 break;
3053         case 27:                        // esc
3054                 if (cmd_mode == 0)
3055                         indicate_error(c);
3056                 cmd_mode = 0;   // stop insrting
3057                 end_cmd_q();
3058                 last_status_cksum = 0;  // force status update
3059                 break;
3060         case ' ':                       // move right
3061         case 'l':                       // move right
3062         case VI_K_RIGHT:        // Cursor Key Right
3063                 if (cmdcnt-- > 1) {
3064                         do_cmd(c);
3065                 }                               // repeat cnt
3066                 dot_right();
3067                 break;
3068 #ifdef CONFIG_FEATURE_VI_YANKMARK
3069         case '"':                       // "- name a register to use for Delete/Yank
3070                 c1 = get_one_char();
3071                 c1 = tolower(c1);
3072                 if (islower(c1)) {
3073                         YDreg = c1 - 'a';
3074                 } else {
3075                         indicate_error(c);
3076                 }
3077                 break;
3078         case '\'':                      // '- goto a specific mark
3079                 c1 = get_one_char();
3080                 c1 = tolower(c1);
3081                 if (islower(c1)) {
3082                         c1 = c1 - 'a';
3083                         // get the b-o-l
3084                         q = mark[(int) c1];
3085                         if (text <= q && q < end) {
3086                                 dot = q;
3087                                 dot_begin();    // go to B-o-l
3088                                 dot_skip_over_ws();
3089                         }
3090                 } else if (c1 == '\'') {        // goto previous context
3091                         dot = swap_context(dot);        // swap current and previous context
3092                         dot_begin();    // go to B-o-l
3093                         dot_skip_over_ws();
3094                 } else {
3095                         indicate_error(c);
3096                 }
3097                 break;
3098         case 'm':                       // m- Mark a line
3099                 // this is really stupid.  If there are any inserts or deletes
3100                 // between text[0] and dot then this mark will not point to the
3101                 // correct location! It could be off by many lines!
3102                 // Well..., at least its quick and dirty.
3103                 c1 = get_one_char();
3104                 c1 = tolower(c1);
3105                 if (islower(c1)) {
3106                         c1 = c1 - 'a';
3107                         // remember the line
3108                         mark[(int) c1] = dot;
3109                 } else {
3110                         indicate_error(c);
3111                 }
3112                 break;
3113         case 'P':                       // P- Put register before
3114         case 'p':                       // p- put register after
3115                 p = reg[YDreg];
3116                 if (p == 0) {
3117                         psbs("Nothing in register %c", what_reg());
3118                         break;
3119                 }
3120                 // are we putting whole lines or strings
3121                 if (strchr((char *) p, '\n') != NULL) {
3122                         if (c == 'P') {
3123                                 dot_begin();    // putting lines- Put above
3124                         }
3125                         if (c == 'p') {
3126                                 // are we putting after very last line?
3127                                 if (end_line(dot) == (end - 1)) {
3128                                         dot = end;      // force dot to end of text[]
3129                                 } else {
3130                                         dot_next();     // next line, then put before
3131                                 }
3132                         }
3133                 } else {
3134                         if (c == 'p')
3135                                 dot_right();    // move to right, can move to NL
3136                 }
3137                 dot = string_insert(dot, p);    // insert the string
3138                 end_cmd_q();    // stop adding to q
3139                 break;
3140         case 'U':                       // U- Undo; replace current line with original version
3141                 if (reg[Ureg] != 0) {
3142                         p = begin_line(dot);
3143                         q = end_line(dot);
3144                         p = text_hole_delete(p, q);     // delete cur line
3145                         p = string_insert(p, reg[Ureg]);        // insert orig line
3146                         dot = p;
3147                         dot_skip_over_ws();
3148                 }
3149                 break;
3150 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
3151         case '$':                       // $- goto end of line
3152         case VI_K_END:          // Cursor Key End
3153                 if (cmdcnt-- > 1) {
3154                         do_cmd(c);
3155                 }                               // repeat cnt
3156                 dot = end_line(dot);
3157                 break;
3158         case '%':                       // %- find matching char of pair () [] {}
3159                 for (q = dot; q < end && *q != '\n'; q++) {
3160                         if (strchr("()[]{}", *q) != NULL) {
3161                                 // we found half of a pair
3162                                 p = find_pair(q, *q);
3163                                 if (p == NULL) {
3164                                         indicate_error(c);
3165                                 } else {
3166                                         dot = p;
3167                                 }
3168                                 break;
3169                         }
3170                 }
3171                 if (*q == '\n')
3172                         indicate_error(c);
3173                 break;
3174         case 'f':                       // f- forward to a user specified char
3175                 last_forward_char = get_one_char();     // get the search char
3176                 //
3177                 // dont separate these two commands. 'f' depends on ';'
3178                 //
3179                 //**** fall thru to ... ';'
3180         case ';':                       // ;- look at rest of line for last forward char
3181                 if (cmdcnt-- > 1) {
3182                         do_cmd(';');
3183                 }                               // repeat cnt
3184                 if (last_forward_char == 0) break;
3185                 q = dot + 1;
3186                 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3187                         q++;
3188                 }
3189                 if (*q == last_forward_char)
3190                         dot = q;
3191                 break;
3192         case '-':                       // -- goto prev line
3193                 if (cmdcnt-- > 1) {
3194                         do_cmd(c);
3195                 }                               // repeat cnt
3196                 dot_prev();
3197                 dot_skip_over_ws();
3198                 break;
3199 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3200         case '.':                       // .- repeat the last modifying command
3201                 // Stuff the last_modifying_cmd back into stdin
3202                 // and let it be re-executed.
3203                 if (last_modifying_cmd != 0) {
3204                         ioq = ioq_start = (Byte *) bb_xstrdup((char *) last_modifying_cmd);
3205                 }
3206                 break;
3207 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD */
3208 #ifdef CONFIG_FEATURE_VI_SEARCH
3209         case '?':                       // /- search for a pattern
3210         case '/':                       // /- search for a pattern
3211                 buf[0] = c;
3212                 buf[1] = '\0';
3213                 q = get_input_line(buf);        // get input line- use "status line"
3214                 if (strlen((char *) q) == 1)
3215                         goto dc3;       // if no pat re-use old pat
3216                 if (strlen((char *) q) > 1) {   // new pat- save it and find
3217                         // there is a new pat
3218                         free(last_search_pattern);
3219                         last_search_pattern = (Byte *) bb_xstrdup((char *) q);
3220                         goto dc3;       // now find the pattern
3221                 }
3222                 // user changed mind and erased the "/"-  do nothing
3223                 break;
3224         case 'N':                       // N- backward search for last pattern
3225                 if (cmdcnt-- > 1) {
3226                         do_cmd(c);
3227                 }                               // repeat cnt
3228                 dir = BACK;             // assume BACKWARD search
3229                 p = dot - 1;
3230                 if (last_search_pattern[0] == '?') {
3231                         dir = FORWARD;
3232                         p = dot + 1;
3233                 }
3234                 goto dc4;               // now search for pattern
3235                 break;
3236         case 'n':                       // n- repeat search for last pattern
3237                 // search rest of text[] starting at next char
3238                 // if search fails return orignal "p" not the "p+1" address
3239                 if (cmdcnt-- > 1) {
3240                         do_cmd(c);
3241                 }                               // repeat cnt
3242           dc3:
3243                 if (last_search_pattern == 0) {
3244                         msg = (Byte *) "No previous regular expression";
3245                         goto dc2;
3246                 }
3247                 if (last_search_pattern[0] == '/') {
3248                         dir = FORWARD;  // assume FORWARD search
3249                         p = dot + 1;
3250                 }
3251                 if (last_search_pattern[0] == '?') {
3252                         dir = BACK;
3253                         p = dot - 1;
3254                 }
3255           dc4:
3256                 q = char_search(p, last_search_pattern + 1, dir, FULL);
3257                 if (q != NULL) {
3258                         dot = q;        // good search, update "dot"
3259                         msg = (Byte *) "";
3260                         goto dc2;
3261                 }
3262                 // no pattern found between "dot" and "end"- continue at top
3263                 p = text;
3264                 if (dir == BACK) {
3265                         p = end - 1;
3266                 }
3267                 q = char_search(p, last_search_pattern + 1, dir, FULL);
3268                 if (q != NULL) {        // found something
3269                         dot = q;        // found new pattern- goto it
3270                         msg = (Byte *) "search hit BOTTOM, continuing at TOP";
3271                         if (dir == BACK) {
3272                                 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
3273                         }
3274                 } else {
3275                         msg = (Byte *) "Pattern not found";
3276                 }
3277           dc2:
3278                 if (*msg) psbs("%s", msg);
3279                 break;
3280         case '{':                       // {- move backward paragraph
3281                 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
3282                 if (q != NULL) {        // found blank line
3283                         dot = next_line(q);     // move to next blank line
3284                 }
3285                 break;
3286         case '}':                       // }- move forward paragraph
3287                 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
3288                 if (q != NULL) {        // found blank line
3289                         dot = next_line(q);     // move to next blank line
3290                 }
3291                 break;
3292 #endif                                                  /* CONFIG_FEATURE_VI_SEARCH */
3293         case '0':                       // 0- goto begining of line
3294         case '1':                       // 1-
3295         case '2':                       // 2-
3296         case '3':                       // 3-
3297         case '4':                       // 4-
3298         case '5':                       // 5-
3299         case '6':                       // 6-
3300         case '7':                       // 7-
3301         case '8':                       // 8-
3302         case '9':                       // 9-
3303                 if (c == '0' && cmdcnt < 1) {
3304                         dot_begin();    // this was a standalone zero
3305                 } else {
3306                         cmdcnt = cmdcnt * 10 + (c - '0');       // this 0 is part of a number
3307                 }
3308                 break;
3309         case ':':                       // :- the colon mode commands
3310                 p = get_input_line((Byte *) ":");       // get input line- use "status line"
3311 #ifdef CONFIG_FEATURE_VI_COLON
3312                 colon(p);               // execute the command
3313 #else                                                   /* CONFIG_FEATURE_VI_COLON */
3314                 if (*p == ':')
3315                         p++;                            // move past the ':'
3316                 cnt = strlen((char *) p);
3317                 if (cnt <= 0)
3318                         break;
3319                 if (strncasecmp((char *) p, "quit", cnt) == 0 ||
3320                         strncasecmp((char *) p, "q!", cnt) == 0) {      // delete lines
3321                         if (file_modified && p[1] != '!') {
3322                                 psbs("No write since last change (:quit! overrides)");
3323                         } else {
3324                                 editing = 0;
3325                         }
3326                 } else if (strncasecmp((char *) p, "write", cnt) == 0
3327                                 || strncasecmp((char *) p, "wq", cnt) == 0
3328                                 || strncasecmp((char *) p, "wn", cnt) == 0
3329                                 || strncasecmp((char *) p, "x", cnt) == 0) {
3330                         cnt = file_write(cfn, text, end - 1);
3331                         if (cnt < 0) {
3332                                 if (cnt == -1)
3333                                         psbs("Write error: %s", strerror(errno));
3334                         } else {
3335                                 file_modified = 0;
3336                                 last_file_modified = -1;
3337                                 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
3338                                 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' ||
3339                                     p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') {
3340                                         editing = 0;
3341                                 }
3342                         }
3343                 } else if (strncasecmp((char *) p, "file", cnt) == 0 ) {
3344                         last_status_cksum = 0;  // force status update
3345                 } else if (sscanf((char *) p, "%d", &j) > 0) {
3346                         dot = find_line(j);             // go to line # j
3347                         dot_skip_over_ws();
3348                 } else {                // unrecognised cmd
3349                         ni((Byte *) p);
3350                 }
3351 #endif                                                  /* CONFIG_FEATURE_VI_COLON */
3352                 break;
3353         case '<':                       // <- Left  shift something
3354         case '>':                       // >- Right shift something
3355                 cnt = count_lines(text, dot);   // remember what line we are on
3356                 c1 = get_one_char();    // get the type of thing to delete
3357                 find_range(&p, &q, c1);
3358                 (void) yank_delete(p, q, 1, YANKONLY);  // save copy before change
3359                 p = begin_line(p);
3360                 q = end_line(q);
3361                 i = count_lines(p, q);  // # of lines we are shifting
3362                 for ( ; i > 0; i--, p = next_line(p)) {
3363                         if (c == '<') {
3364                                 // shift left- remove tab or 8 spaces
3365                                 if (*p == '\t') {
3366                                         // shrink buffer 1 char
3367                                         (void) text_hole_delete(p, p);
3368                                 } else if (*p == ' ') {
3369                                         // we should be calculating columns, not just SPACE
3370                                         for (j = 0; *p == ' ' && j < tabstop; j++) {
3371                                                 (void) text_hole_delete(p, p);
3372                                         }
3373                                 }
3374                         } else if (c == '>') {
3375                                 // shift right -- add tab or 8 spaces
3376                                 (void) char_insert(p, '\t');
3377                         }
3378                 }
3379                 dot = find_line(cnt);   // what line were we on
3380                 dot_skip_over_ws();
3381                 end_cmd_q();    // stop adding to q
3382                 break;
3383         case 'A':                       // A- append at e-o-l
3384                 dot_end();              // go to e-o-l
3385                 //**** fall thru to ... 'a'
3386         case 'a':                       // a- append after current char
3387                 if (*dot != '\n')
3388                         dot++;
3389                 goto dc_i;
3390                 break;
3391         case 'B':                       // B- back a blank-delimited Word
3392         case 'E':                       // E- end of a blank-delimited word
3393         case 'W':                       // W- forward a blank-delimited word
3394                 if (cmdcnt-- > 1) {
3395                         do_cmd(c);
3396                 }                               // repeat cnt
3397                 dir = FORWARD;
3398                 if (c == 'B')
3399                         dir = BACK;
3400                 if (c == 'W' || isspace(dot[dir])) {
3401                         dot = skip_thing(dot, 1, dir, S_TO_WS);
3402                         dot = skip_thing(dot, 2, dir, S_OVER_WS);
3403                 }
3404                 if (c != 'W')
3405                         dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3406                 break;
3407         case 'C':                       // C- Change to e-o-l
3408         case 'D':                       // D- delete to e-o-l
3409                 save_dot = dot;
3410                 dot = dollar_line(dot); // move to before NL
3411                 // copy text into a register and delete
3412                 dot = yank_delete(save_dot, dot, 0, YANKDEL);   // delete to e-o-l
3413                 if (c == 'C')
3414                         goto dc_i;      // start inserting
3415 #ifdef CONFIG_FEATURE_VI_DOT_CMD
3416                 if (c == 'D')
3417                         end_cmd_q();    // stop adding to q
3418 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD */
3419                 break;
3420         case 'G':               // G- goto to a line number (default= E-O-F)
3421                 dot = end - 1;                          // assume E-O-F
3422                 if (cmdcnt > 0) {
3423                         dot = find_line(cmdcnt);        // what line is #cmdcnt
3424                 }
3425                 dot_skip_over_ws();
3426                 break;
3427         case 'H':                       // H- goto top line on screen
3428                 dot = screenbegin;
3429                 if (cmdcnt > (rows - 1)) {
3430                         cmdcnt = (rows - 1);
3431                 }
3432                 if (cmdcnt-- > 1) {
3433                         do_cmd('+');
3434                 }                               // repeat cnt
3435                 dot_skip_over_ws();
3436                 break;
3437         case 'I':                       // I- insert before first non-blank
3438                 dot_begin();    // 0
3439                 dot_skip_over_ws();
3440                 //**** fall thru to ... 'i'
3441         case 'i':                       // i- insert before current char
3442         case VI_K_INSERT:       // Cursor Key Insert
3443           dc_i:
3444                 cmd_mode = 1;   // start insrting
3445                 break;
3446         case 'J':                       // J- join current and next lines together
3447                 if (cmdcnt-- > 2) {
3448                         do_cmd(c);
3449                 }                               // repeat cnt
3450                 dot_end();              // move to NL
3451                 if (dot < end - 1) {    // make sure not last char in text[]
3452                         *dot++ = ' ';   // replace NL with space
3453                         file_modified++;
3454                         while (isblnk(*dot)) {  // delete leading WS
3455                                 dot_delete();
3456                         }
3457                 }
3458                 end_cmd_q();    // stop adding to q
3459                 break;
3460         case 'L':                       // L- goto bottom line on screen
3461                 dot = end_screen();
3462                 if (cmdcnt > (rows - 1)) {
3463                         cmdcnt = (rows - 1);
3464                 }
3465                 if (cmdcnt-- > 1) {
3466                         do_cmd('-');
3467                 }                               // repeat cnt
3468                 dot_begin();
3469                 dot_skip_over_ws();
3470                 break;
3471         case 'M':                       // M- goto middle line on screen
3472                 dot = screenbegin;
3473                 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3474                         dot = next_line(dot);
3475                 break;
3476         case 'O':                       // O- open a empty line above
3477                 //    0i\n ESC -i
3478                 p = begin_line(dot);
3479                 if (p[-1] == '\n') {
3480                         dot_prev();
3481         case 'o':                       // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3482                         dot_end();
3483                         dot = char_insert(dot, '\n');
3484                 } else {
3485                         dot_begin();    // 0
3486                         dot = char_insert(dot, '\n');   // i\n ESC
3487                         dot_prev();     // -
3488                 }
3489                 goto dc_i;
3490                 break;
3491         case 'R':                       // R- continuous Replace char
3492           dc5:
3493                 cmd_mode = 2;
3494                 break;
3495         case 'X':                       // X- delete char before dot
3496         case 'x':                       // x- delete the current char
3497         case 's':                       // s- substitute the current char
3498                 if (cmdcnt-- > 1) {
3499                         do_cmd(c);
3500                 }                               // repeat cnt
3501                 dir = 0;
3502                 if (c == 'X')
3503                         dir = -1;
3504                 if (dot[dir] != '\n') {
3505                         if (c == 'X')
3506                                 dot--;  // delete prev char
3507                         dot = yank_delete(dot, dot, 0, YANKDEL);        // delete char
3508                 }
3509                 if (c == 's')
3510                         goto dc_i;      // start insrting
3511                 end_cmd_q();    // stop adding to q
3512                 break;
3513         case 'Z':                       // Z- if modified, {write}; exit
3514                 // ZZ means to save file (if necessary), then exit
3515                 c1 = get_one_char();
3516                 if (c1 != 'Z') {
3517                         indicate_error(c);
3518                         break;
3519                 }
3520                 if (file_modified) {
3521 #ifdef CONFIG_FEATURE_VI_READONLY
3522                         if (vi_readonly || readonly) {
3523                             psbs("\"%s\" File is read only", cfn);
3524                             break;
3525                         }
3526 #endif          /* CONFIG_FEATURE_VI_READONLY */
3527                         cnt = file_write(cfn, text, end - 1);
3528                         if (cnt < 0) {
3529                                 if (cnt == -1)
3530                                         psbs("Write error: %s", strerror(errno));
3531                         } else if (cnt == (end - 1 - text + 1)) {
3532                                 editing = 0;
3533                         }
3534                 } else {
3535                         editing = 0;
3536                 }
3537                 break;
3538         case '^':                       // ^- move to first non-blank on line
3539                 dot_begin();
3540                 dot_skip_over_ws();
3541                 break;
3542         case 'b':                       // b- back a word
3543         case 'e':                       // e- end of word
3544                 if (cmdcnt-- > 1) {
3545                         do_cmd(c);
3546                 }                               // repeat cnt
3547                 dir = FORWARD;
3548                 if (c == 'b')
3549                         dir = BACK;
3550                 if ((dot + dir) < text || (dot + dir) > end - 1)
3551                         break;
3552                 dot += dir;
3553                 if (isspace(*dot)) {
3554                         dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3555                 }
3556                 if (isalnum(*dot) || *dot == '_') {
3557                         dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3558                 } else if (ispunct(*dot)) {
3559                         dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3560                 }
3561                 break;
3562         case 'c':                       // c- change something
3563         case 'd':                       // d- delete something
3564 #ifdef CONFIG_FEATURE_VI_YANKMARK
3565         case 'y':                       // y- yank   something
3566         case 'Y':                       // Y- Yank a line
3567 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
3568                 yf = YANKDEL;   // assume either "c" or "d"
3569 #ifdef CONFIG_FEATURE_VI_YANKMARK
3570                 if (c == 'y' || c == 'Y')
3571                         yf = YANKONLY;
3572 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
3573                 c1 = 'y';
3574                 if (c != 'Y')
3575                         c1 = get_one_char();    // get the type of thing to delete
3576                 find_range(&p, &q, c1);
3577                 if (c1 == 27) { // ESC- user changed mind and wants out
3578                         c = c1 = 27;    // Escape- do nothing
3579                 } else if (strchr("wW", c1)) {
3580                         if (c == 'c') {
3581                                 // don't include trailing WS as part of word
3582                                 while (isblnk(*q)) {
3583                                         if (q <= text || q[-1] == '\n')
3584                                                 break;
3585                                         q--;
3586                                 }
3587                         }
3588                         dot = yank_delete(p, q, 0, yf); // delete word
3589                 } else if (strchr("^0bBeEft$", c1)) {
3590                         // single line copy text into a register and delete
3591                         dot = yank_delete(p, q, 0, yf); // delete word
3592                 } else if (strchr("cdykjHL%+-{}\r\n", c1)) {
3593                         // multiple line copy text into a register and delete
3594                         dot = yank_delete(p, q, 1, yf); // delete lines
3595                         if (c == 'c') {
3596                                 dot = char_insert(dot, '\n');
3597                                 // on the last line of file don't move to prev line
3598                                 if (dot != (end-1)) {
3599                                         dot_prev();
3600                                 }
3601                         } else if (c == 'd') {
3602                                 dot_begin();
3603                                 dot_skip_over_ws();
3604                         }
3605                 } else {
3606                         // could not recognize object
3607                         c = c1 = 27;    // error-
3608                         indicate_error(c);
3609                 }
3610                 if (c1 != 27) {
3611                         // if CHANGING, not deleting, start inserting after the delete
3612                         if (c == 'c') {
3613                                 strcpy((char *) buf, "Change");
3614                                 goto dc_i;      // start inserting
3615                         }
3616                         if (c == 'd') {
3617                                 strcpy((char *) buf, "Delete");
3618                         }
3619 #ifdef CONFIG_FEATURE_VI_YANKMARK
3620                         if (c == 'y' || c == 'Y') {
3621                                 strcpy((char *) buf, "Yank");
3622                         }
3623                         p = reg[YDreg];
3624                         q = p + strlen((char *) p);
3625                         for (cnt = 0; p <= q; p++) {
3626                                 if (*p == '\n')
3627                                         cnt++;
3628                         }
3629                         psb("%s %d lines (%d chars) using [%c]",
3630                                 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
3631 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
3632                         end_cmd_q();    // stop adding to q
3633                 }
3634                 break;
3635         case 'k':                       // k- goto prev line, same col
3636         case VI_K_UP:           // cursor key Up
3637                 if (cmdcnt-- > 1) {
3638                         do_cmd(c);
3639                 }                               // repeat cnt
3640                 dot_prev();
3641                 dot = move_to_col(dot, ccol + offset);  // try stay in same col
3642                 break;
3643         case 'r':                       // r- replace the current char with user input
3644                 c1 = get_one_char();    // get the replacement char
3645                 if (*dot != '\n') {
3646                         *dot = c1;
3647                         file_modified++;        // has the file been modified
3648                 }
3649                 end_cmd_q();    // stop adding to q
3650                 break;
3651         case 't':                       // t- move to char prior to next x
3652                 last_forward_char = get_one_char();
3653                 do_cmd(';');
3654                 if (*dot == last_forward_char)
3655                         dot_left();
3656                 last_forward_char= 0;
3657                 break;
3658         case 'w':                       // w- forward a word
3659                 if (cmdcnt-- > 1) {
3660                         do_cmd(c);
3661                 }                               // repeat cnt
3662                 if (isalnum(*dot) || *dot == '_') {     // we are on ALNUM
3663                         dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3664                 } else if (ispunct(*dot)) {     // we are on PUNCT
3665                         dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3666                 }
3667                 if (dot < end - 1)
3668                         dot++;          // move over word
3669                 if (isspace(*dot)) {
3670                         dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3671                 }
3672                 break;
3673         case 'z':                       // z-
3674                 c1 = get_one_char();    // get the replacement char
3675                 cnt = 0;
3676                 if (c1 == '.')
3677                         cnt = (rows - 2) / 2;   // put dot at center
3678                 if (c1 == '-')
3679                         cnt = rows - 2; // put dot at bottom
3680                 screenbegin = begin_line(dot);  // start dot at top
3681                 dot_scroll(cnt, -1);
3682                 break;
3683         case '|':                       // |- move to column "cmdcnt"
3684                 dot = move_to_col(dot, cmdcnt - 1);     // try to move to column
3685                 break;
3686         case '~':                       // ~- flip the case of letters   a-z -> A-Z
3687                 if (cmdcnt-- > 1) {
3688                         do_cmd(c);
3689                 }                               // repeat cnt
3690                 if (islower(*dot)) {
3691                         *dot = toupper(*dot);
3692                         file_modified++;        // has the file been modified
3693                 } else if (isupper(*dot)) {
3694                         *dot = tolower(*dot);
3695                         file_modified++;        // has the file been modified
3696                 }
3697                 dot_right();
3698                 end_cmd_q();    // stop adding to q
3699                 break;
3700                 //----- The Cursor and Function Keys -----------------------------
3701         case VI_K_HOME: // Cursor Key Home
3702                 dot_begin();
3703                 break;
3704                 // The Fn keys could point to do_macro which could translate them
3705         case VI_K_FUN1: // Function Key F1
3706         case VI_K_FUN2: // Function Key F2
3707         case VI_K_FUN3: // Function Key F3
3708         case VI_K_FUN4: // Function Key F4
3709         case VI_K_FUN5: // Function Key F5
3710         case VI_K_FUN6: // Function Key F6
3711         case VI_K_FUN7: // Function Key F7
3712         case VI_K_FUN8: // Function Key F8
3713         case VI_K_FUN9: // Function Key F9
3714         case VI_K_FUN10:        // Function Key F10
3715         case VI_K_FUN11:        // Function Key F11
3716         case VI_K_FUN12:        // Function Key F12
3717                 break;
3718         }
3719
3720   dc1:
3721         // if text[] just became empty, add back an empty line
3722         if (end == text) {
3723                 (void) char_insert(text, '\n'); // start empty buf with dummy line
3724                 dot = text;
3725         }
3726         // it is OK for dot to exactly equal to end, otherwise check dot validity
3727         if (dot != end) {
3728                 dot = bound_dot(dot);   // make sure "dot" is valid
3729         }
3730 #ifdef CONFIG_FEATURE_VI_YANKMARK
3731         check_context(c);       // update the current context
3732 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
3733
3734         if (!isdigit(c))
3735                 cmdcnt = 0;             // cmd was not a number, reset cmdcnt
3736         cnt = dot - begin_line(dot);
3737         // Try to stay off of the Newline
3738         if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3739                 dot--;
3740 }
3741
3742 #ifdef CONFIG_FEATURE_VI_CRASHME
3743 static int totalcmds = 0;
3744 static int Mp = 85;             // Movement command Probability
3745 static int Np = 90;             // Non-movement command Probability
3746 static int Dp = 96;             // Delete command Probability
3747 static int Ip = 97;             // Insert command Probability
3748 static int Yp = 98;             // Yank command Probability
3749 static int Pp = 99;             // Put command Probability
3750 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3751 char chars[20] = "\t012345 abcdABCD-=.$";
3752 char *words[20] = { "this", "is", "a", "test",
3753         "broadcast", "the", "emergency", "of",
3754         "system", "quick", "brown", "fox",
3755         "jumped", "over", "lazy", "dogs",
3756         "back", "January", "Febuary", "March"
3757 };
3758 char *lines[20] = {
3759         "You should have received a copy of the GNU General Public License\n",
3760         "char c, cm, *cmd, *cmd1;\n",
3761         "generate a command by percentages\n",
3762         "Numbers may be typed as a prefix to some commands.\n",
3763         "Quit, discarding changes!\n",
3764         "Forced write, if permission originally not valid.\n",
3765         "In general, any ex or ed command (such as substitute or delete).\n",
3766         "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3767         "Please get w/ me and I will go over it with you.\n",
3768         "The following is a list of scheduled, committed changes.\n",
3769         "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3770         "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3771         "Any question about transactions please contact Sterling Huxley.\n",
3772         "I will try to get back to you by Friday, December 31.\n",
3773         "This Change will be implemented on Friday.\n",
3774         "Let me know if you have problems accessing this;\n",
3775         "Sterling Huxley recently added you to the access list.\n",
3776         "Would you like to go to lunch?\n",
3777         "The last command will be automatically run.\n",
3778         "This is too much english for a computer geek.\n",
3779 };
3780 char *multilines[20] = {
3781         "You should have received a copy of the GNU General Public License\n",
3782         "char c, cm, *cmd, *cmd1;\n",
3783         "generate a command by percentages\n",
3784         "Numbers may be typed as a prefix to some commands.\n",
3785         "Quit, discarding changes!\n",
3786         "Forced write, if permission originally not valid.\n",
3787         "In general, any ex or ed command (such as substitute or delete).\n",
3788         "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3789         "Please get w/ me and I will go over it with you.\n",
3790         "The following is a list of scheduled, committed changes.\n",
3791         "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3792         "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3793         "Any question about transactions please contact Sterling Huxley.\n",
3794         "I will try to get back to you by Friday, December 31.\n",
3795         "This Change will be implemented on Friday.\n",
3796         "Let me know if you have problems accessing this;\n",
3797         "Sterling Huxley recently added you to the access list.\n",
3798         "Would you like to go to lunch?\n",
3799         "The last command will be automatically run.\n",
3800         "This is too much english for a computer geek.\n",
3801 };
3802
3803 // create a random command to execute
3804 static void crash_dummy()
3805 {
3806         static int sleeptime;   // how long to pause between commands
3807         char c, cm, *cmd, *cmd1;
3808         int i, cnt, thing, rbi, startrbi, percent;
3809
3810         // "dot" movement commands
3811         cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3812
3813         // is there already a command running?
3814         if (readed_for_parse > 0)
3815                 goto cd1;
3816   cd0:
3817         startrbi = rbi = 0;
3818         sleeptime = 0;          // how long to pause between commands
3819         memset(readbuffer, '\0', BUFSIZ);   // clear the read buffer
3820         // generate a command by percentages
3821         percent = (int) lrand48() % 100;        // get a number from 0-99
3822         if (percent < Mp) {     //  Movement commands
3823                 // available commands
3824                 cmd = cmd1;
3825                 M++;
3826         } else if (percent < Np) {      //  non-movement commands
3827                 cmd = "mz<>\'\"";       // available commands
3828                 N++;
3829         } else if (percent < Dp) {      //  Delete commands
3830                 cmd = "dx";             // available commands
3831                 D++;
3832         } else if (percent < Ip) {      //  Inset commands
3833                 cmd = "iIaAsrJ";        // available commands
3834                 I++;
3835         } else if (percent < Yp) {      //  Yank commands
3836                 cmd = "yY";             // available commands
3837                 Y++;
3838         } else if (percent < Pp) {      //  Put commands
3839                 cmd = "pP";             // available commands
3840                 P++;
3841         } else {
3842                 // We do not know how to handle this command, try again
3843                 U++;
3844                 goto cd0;
3845         }
3846         // randomly pick one of the available cmds from "cmd[]"
3847         i = (int) lrand48() % strlen(cmd);
3848         cm = cmd[i];
3849         if (strchr(":\024", cm))
3850                 goto cd0;               // dont allow colon or ctrl-T commands
3851         readbuffer[rbi++] = cm; // put cmd into input buffer
3852
3853         // now we have the command-
3854         // there are 1, 2, and multi char commands
3855         // find out which and generate the rest of command as necessary
3856         if (strchr("dmryz<>\'\"", cm)) {        // 2-char commands
3857                 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
3858                 if (cm == 'm' || cm == '\'' || cm == '\"') {    // pick a reg[]
3859                         cmd1 = "abcdefghijklmnopqrstuvwxyz";
3860                 }
3861                 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3862                 c = cmd1[thing];
3863                 readbuffer[rbi++] = c;  // add movement to input buffer
3864         }
3865         if (strchr("iIaAsc", cm)) {     // multi-char commands
3866                 if (cm == 'c') {
3867                         // change some thing
3868                         thing = (int) lrand48() % strlen(cmd1); // pick a movement command
3869                         c = cmd1[thing];
3870                         readbuffer[rbi++] = c;  // add movement to input buffer
3871                 }
3872                 thing = (int) lrand48() % 4;    // what thing to insert
3873                 cnt = (int) lrand48() % 10;     // how many to insert
3874                 for (i = 0; i < cnt; i++) {
3875                         if (thing == 0) {       // insert chars
3876                                 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
3877                         } else if (thing == 1) {        // insert words
3878                                 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
3879                                 strcat((char *) readbuffer, " ");
3880                                 sleeptime = 0;  // how fast to type
3881                         } else if (thing == 2) {        // insert lines
3882                                 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
3883                                 sleeptime = 0;  // how fast to type
3884                         } else {        // insert multi-lines
3885                                 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
3886                                 sleeptime = 0;  // how fast to type
3887                         }
3888                 }
3889                 strcat((char *) readbuffer, "\033");
3890         }
3891         readed_for_parse = strlen(readbuffer);
3892   cd1:
3893         totalcmds++;
3894         if (sleeptime > 0)
3895                 (void) mysleep(sleeptime);      // sleep 1/100 sec
3896 }
3897
3898 // test to see if there are any errors
3899 static void crash_test()
3900 {
3901         static time_t oldtim;
3902         time_t tim;
3903         char d[2], msg[BUFSIZ];
3904
3905         msg[0] = '\0';
3906         if (end < text) {
3907                 strcat((char *) msg, "end<text ");
3908         }
3909         if (end > textend) {
3910                 strcat((char *) msg, "end>textend ");
3911         }
3912         if (dot < text) {
3913                 strcat((char *) msg, "dot<text ");
3914         }
3915         if (dot > end) {
3916                 strcat((char *) msg, "dot>end ");
3917         }
3918         if (screenbegin < text) {
3919                 strcat((char *) msg, "screenbegin<text ");
3920         }
3921         if (screenbegin > end - 1) {
3922                 strcat((char *) msg, "screenbegin>end-1 ");
3923         }
3924
3925         if (strlen(msg) > 0) {
3926                 alarm(0);
3927                 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
3928                         totalcmds, last_input_char, msg, SOs, SOn);
3929                 fflush(stdout);
3930                 while (read(0, d, 1) > 0) {
3931                         if (d[0] == '\n' || d[0] == '\r')
3932                                 break;
3933                 }
3934                 alarm(3);
3935         }
3936         tim = (time_t) time((time_t *) 0);
3937         if (tim >= (oldtim + 3)) {
3938                 sprintf((char *) status_buffer,
3939                                 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
3940                                 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
3941                 oldtim = tim;
3942         }
3943         return;
3944 }
3945 #endif                                            /* CONFIG_FEATURE_VI_CRASHME */