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