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