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