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