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