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