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