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