Patch from Jason Schoon to add optional SIGUSR1 support to dd.
[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 <termios.h>
57 #include <unistd.h>
58 #include <sys/ioctl.h>
59 #include <sys/time.h>
60 #include <sys/types.h>
61 #include <sys/stat.h>
62 #include <time.h>
63 #include <fcntl.h>
64 #include <signal.h>
65 #include <setjmp.h>
66 #include <regex.h>
67 #include <ctype.h>
68 #include <assert.h>
69 #include <errno.h>
70 #include <stdarg.h>
71 #ifndef STANDALONE
72 #include "busybox.h"
73 #define vi_Version BB_VER " " BB_BT
74 #else
75 #define vi_Version "standalone"
76 #endif                                                  /* STANDALONE */
77
78 #ifdef CONFIG_LOCALE_SUPPORT
79 #define Isprint(c) isprint((c))
80 #else
81 #define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
82 #endif
83
84 #ifndef TRUE
85 #define TRUE                    ((int)1)
86 #define FALSE                   ((int)0)
87 #endif                                                  /* TRUE */
88 #define MAX_SCR_COLS            BUFSIZ
89
90 // Misc. non-Ascii keys that report an escape sequence
91 #define VI_K_UP                 128     // cursor key Up
92 #define VI_K_DOWN               129     // cursor key Down
93 #define VI_K_RIGHT              130     // Cursor Key Right
94 #define VI_K_LEFT               131     // cursor key Left
95 #define VI_K_HOME               132     // Cursor Key Home
96 #define VI_K_END                133     // Cursor Key End
97 #define VI_K_INSERT             134     // Cursor Key Insert
98 #define VI_K_PAGEUP             135     // Cursor Key Page Up
99 #define VI_K_PAGEDOWN           136     // Cursor Key Page Down
100 #define VI_K_FUN1               137     // Function Key F1
101 #define VI_K_FUN2               138     // Function Key F2
102 #define VI_K_FUN3               139     // Function Key F3
103 #define VI_K_FUN4               140     // Function Key F4
104 #define VI_K_FUN5               141     // Function Key F5
105 #define VI_K_FUN6               142     // Function Key F6
106 #define VI_K_FUN7               143     // Function Key F7
107 #define VI_K_FUN8               144     // Function Key F8
108 #define VI_K_FUN9               145     // Function Key F9
109 #define VI_K_FUN10              146     // Function Key F10
110 #define VI_K_FUN11              147     // Function Key F11
111 #define VI_K_FUN12              148     // Function Key F12
112
113 /* vt102 typical ESC sequence */
114 /* terminal standout start/normal ESC sequence */
115 static const char SOs[] = "\033[7m";
116 static const char SOn[] = "\033[0m";
117 /* terminal bell sequence */
118 static const char bell[] = "\007";
119 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
120 static const char Ceol[] = "\033[0K";
121 static const char Ceos [] = "\033[0J";
122 /* Cursor motion arbitrary destination ESC sequence */
123 static const char CMrc[] = "\033[%d;%dH";
124 /* Cursor motion up and down ESC sequence */
125 static const char CMup[] = "\033[A";
126 static const char CMdown[] = "\n";
127
128
129 enum {
130         YANKONLY = FALSE,
131         YANKDEL = TRUE,
132         FORWARD = 1,    // code depends on "1"  for array index
133         BACK = -1,      // code depends on "-1" for array index
134         LIMITED = 0,    // how much of text[] in char_search
135         FULL = 1,       // how much of text[] in char_search
136
137         S_BEFORE_WS = 1,        // used in skip_thing() for moving "dot"
138         S_TO_WS = 2,            // used in skip_thing() for moving "dot"
139         S_OVER_WS = 3,          // used in skip_thing() for moving "dot"
140         S_END_PUNCT = 4,        // used in skip_thing() for moving "dot"
141         S_END_ALNUM = 5         // used in skip_thing() for moving "dot"
142 };
143
144 typedef unsigned char Byte;
145
146 static int vi_setops;
147 #define VI_AUTOINDENT 1
148 #define VI_SHOWMATCH  2
149 #define VI_IGNORECASE 4
150 #define VI_ERR_METHOD 8
151 #define autoindent (vi_setops & VI_AUTOINDENT)
152 #define showmatch  (vi_setops & VI_SHOWMATCH )
153 #define ignorecase (vi_setops & VI_IGNORECASE)
154 /* indicate error with beep or flash */
155 #define err_method (vi_setops & VI_ERR_METHOD)
156
157
158 static int editing;             // >0 while we are editing a file
159 static int cmd_mode;            // 0=command  1=insert 2=replace
160 static int file_modified;       // buffer contents changed
161 static int last_file_modified = -1;
162 static int fn_start;            // index of first cmd line file name
163 static int save_argc;           // how many file names on cmd line
164 static int cmdcnt;              // repetition count
165 static fd_set rfds;             // use select() for small sleeps
166 static struct timeval tv;       // use select() for small sleeps
167 static int rows, columns;       // the terminal screen is this size
168 static int crow, ccol, offset;  // cursor is on Crow x Ccol with Horz Ofset
169 static Byte *status_buffer;     // mesages to the user
170 #define STATUS_BUFFER_LEN  200
171 static int have_status_msg;     // is default edit status needed?
172 static int last_status_cksum;   // hash of current status line
173 static Byte *cfn;               // previous, current, and next file name
174 static Byte *text, *end, *textend;      // pointers to the user data in memory
175 static Byte *screen;            // pointer to the virtual screen buffer
176 static int screensize;          //            and its size
177 static Byte *screenbegin;       // index into text[], of top line on the screen
178 static Byte *dot;               // where all the action takes place
179 static int tabstop;
180 static struct termios term_orig, term_vi;       // remember what the cooked mode was
181 static Byte erase_char;         // the users erase character
182 static Byte last_input_char;    // last char read from user
183 static Byte last_forward_char;  // last char searched for with 'f'
184
185 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
186 static int last_row;            // where the cursor was last moved to
187 #endif                                                  /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
188 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
189 static jmp_buf restart;         // catch_sig()
190 #endif                                                  /* CONFIG_FEATURE_VI_USE_SIGNALS */
191 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
192 static int my_pid;
193 #endif
194 #ifdef CONFIG_FEATURE_VI_DOT_CMD
195 static int adding2q;            // are we currently adding user input to q
196 static Byte *last_modifying_cmd;        // last modifying cmd for "."
197 static Byte *ioq, *ioq_start;   // pointer to string for get_one_char to "read"
198 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD */
199 #if     defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
200 static Byte *modifying_cmds;    // cmds that modify text[]
201 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
202 #ifdef CONFIG_FEATURE_VI_READONLY
203 static int vi_readonly, readonly;
204 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
205 #ifdef CONFIG_FEATURE_VI_YANKMARK
206 static Byte *reg[28];           // named register a-z, "D", and "U" 0-25,26,27
207 static int YDreg, Ureg;         // default delete register and orig line for "U"
208 static Byte *mark[28];          // user marks points somewhere in text[]-  a-z and previous context ''
209 static Byte *context_start, *context_end;
210 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
211 #ifdef CONFIG_FEATURE_VI_SEARCH
212 static Byte *last_search_pattern;       // last pattern from a '/' or '?' search
213 #endif                                                  /* CONFIG_FEATURE_VI_SEARCH */
214
215
216 static void edit_file(Byte *);  // edit one file
217 static void do_cmd(Byte);       // execute a command
218 static void sync_cursor(Byte *, int *, int *);  // synchronize the screen cursor to dot
219 static Byte *begin_line(Byte *);        // return pointer to cur line B-o-l
220 static Byte *end_line(Byte *);  // return pointer to cur line E-o-l
221 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
222 static Byte *next_line(Byte *); // return pointer to next line B-o-l
223 static Byte *end_screen(void);  // get pointer to last char on screen
224 static int count_lines(Byte *, Byte *); // count line from start to stop
225 static Byte *find_line(int);    // find begining of line #li
226 static Byte *move_to_col(Byte *, int);  // move "p" to column l
227 static int isblnk(Byte);        // is the char a blank or tab
228 static void dot_left(void);     // move dot left- dont leave line
229 static void dot_right(void);    // move dot right- dont leave line
230 static void dot_begin(void);    // move dot to B-o-l
231 static void dot_end(void);      // move dot to E-o-l
232 static void dot_next(void);     // move dot to next line B-o-l
233 static void dot_prev(void);     // move dot to prev line B-o-l
234 static void dot_scroll(int, int);       // move the screen up or down
235 static void dot_skip_over_ws(void);     // move dot pat WS
236 static void dot_delete(void);   // delete the char at 'dot'
237 static Byte *bound_dot(Byte *); // make sure  text[0] <= P < "end"
238 static Byte *new_screen(int, int);      // malloc virtual screen memory
239 static Byte *new_text(int);     // malloc memory for text[] buffer
240 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
241 static Byte *stupid_insert(Byte *, Byte);       // stupidly insert the char c at 'p'
242 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
243 static int st_test(Byte *, int, int, Byte *);   // helper for skip_thing()
244 static Byte *skip_thing(Byte *, int, int, int); // skip some object
245 static Byte *find_pair(Byte *, Byte);   // find matching pair ()  []  {}
246 static Byte *text_hole_delete(Byte *, Byte *);  // at "p", delete a 'size' byte hole
247 static Byte *text_hole_make(Byte *, int);       // at "p", make a 'size' byte hole
248 static Byte *yank_delete(Byte *, Byte *, int, int);     // yank text[] into register then delete
249 static void show_help(void);    // display some help info
250 static void rawmode(void);      // set "raw" mode on tty
251 static void cookmode(void);     // return to "cooked" mode on tty
252 static int mysleep(int);        // sleep for 'h' 1/100 seconds
253 static Byte readit(void);       // read (maybe cursor) key from stdin
254 static Byte get_one_char(void); // read 1 char from stdin
255 static int file_size(const Byte *);   // what is the byte size of "fn"
256 static int file_insert(Byte *, Byte *, int);
257 static int file_write(Byte *, Byte *, Byte *);
258 static void place_cursor(int, int, int);
259 static void screen_erase(void);
260 static void clear_to_eol(void);
261 static void clear_to_eos(void);
262 static void standout_start(void);       // send "start reverse video" sequence
263 static void standout_end(void); // send "end reverse video" sequence
264 static void flash(int);         // flash the terminal screen
265 static void show_status_line(void);     // put a message on the bottom line
266 static void psb(const char *, ...);     // Print Status Buf
267 static void psbs(const char *, ...);    // Print Status Buf in standout mode
268 static void ni(Byte *);         // display messages
269 static int format_edit_status(void);    // format file status on status line
270 static void redraw(int);        // force a full screen refresh
271 static void format_line(Byte*, Byte*, int);
272 static void refresh(int);       // update the terminal from screen[]
273
274 static void Indicate_Error(void);       // use flash or beep to indicate error
275 #define indicate_error(c) Indicate_Error()
276 static void Hit_Return(void);
277
278 #ifdef CONFIG_FEATURE_VI_SEARCH
279 static Byte *char_search(Byte *, Byte *, int, int);     // search for pattern starting at p
280 static int mycmp(Byte *, Byte *, int);  // string cmp based in "ignorecase"
281 #endif                                                  /* CONFIG_FEATURE_VI_SEARCH */
282 #ifdef CONFIG_FEATURE_VI_COLON
283 static Byte *get_one_address(Byte *, int *);    // get colon addr, if present
284 static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present
285 static void colon(Byte *);      // execute the "colon" mode cmds
286 #endif                                                  /* CONFIG_FEATURE_VI_COLON */
287 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
288 static void winch_sig(int);     // catch window size changes
289 static void suspend_sig(int);   // catch ctrl-Z
290 static void catch_sig(int);     // catch ctrl-C and alarm time-outs
291 static void core_sig(int);      // catch a core dump signal
292 #endif                                                  /* CONFIG_FEATURE_VI_USE_SIGNALS */
293 #ifdef CONFIG_FEATURE_VI_DOT_CMD
294 static void start_new_cmd_q(Byte);      // new queue for command
295 static void end_cmd_q(void);    // stop saving input chars
296 #else                                                   /* CONFIG_FEATURE_VI_DOT_CMD */
297 #define end_cmd_q()
298 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD */
299 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
300 static void window_size_get(int);       // find out what size the window is
301 #endif                                                  /* CONFIG_FEATURE_VI_WIN_RESIZE */
302 #ifdef CONFIG_FEATURE_VI_SETOPTS
303 static void showmatching(Byte *);       // show the matching pair ()  []  {}
304 #endif                                                  /* CONFIG_FEATURE_VI_SETOPTS */
305 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_SEARCH) || defined(CONFIG_FEATURE_VI_CRASHME)
306 static Byte *string_insert(Byte *, Byte *);     // insert the string at 'p'
307 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_SEARCH || CONFIG_FEATURE_VI_CRASHME */
308 #ifdef CONFIG_FEATURE_VI_YANKMARK
309 static Byte *text_yank(Byte *, Byte *, int);    // save copy of "p" into a register
310 static Byte what_reg(void);             // what is letter of current YDreg
311 static void check_context(Byte);        // remember context for '' command
312 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
313 #ifdef CONFIG_FEATURE_VI_CRASHME
314 static void crash_dummy();
315 static void crash_test();
316 static int crashme = 0;
317 #endif                                                  /* CONFIG_FEATURE_VI_CRASHME */
318
319
320 static void write1(const char *out)
321 {
322         fputs(out, stdout);
323 }
324
325 int vi_main(int argc, char **argv)
326 {
327         int c;
328         RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN);
329
330 #ifdef CONFIG_FEATURE_VI_YANKMARK
331         int i;
332 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
333 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
334         my_pid = getpid();
335 #endif
336 #ifdef CONFIG_FEATURE_VI_CRASHME
337         (void) srand((long) my_pid);
338 #endif                                                  /* CONFIG_FEATURE_VI_CRASHME */
339
340         status_buffer = (Byte *)STATUS_BUFFER;
341         last_status_cksum = 0;
342
343 #ifdef CONFIG_FEATURE_VI_READONLY
344         vi_readonly = readonly = FALSE;
345         if (strncmp(argv[0], "view", 4) == 0) {
346                 readonly = TRUE;
347                 vi_readonly = TRUE;
348         }
349 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
350         vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD;
351 #ifdef CONFIG_FEATURE_VI_YANKMARK
352         for (i = 0; i < 28; i++) {
353                 reg[i] = 0;
354         }                                       // init the yank regs
355 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
356 #if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
357         modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~";      // cmds modifying text[]
358 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD */
359
360         //  1-  process $HOME/.exrc file
361         //  2-  process EXINIT variable from environment
362         //  3-  process command line args
363         while ((c = getopt(argc, argv, "hCR")) != -1) {
364                 switch (c) {
365 #ifdef CONFIG_FEATURE_VI_CRASHME
366                 case 'C':
367                         crashme = 1;
368                         break;
369 #endif                                                  /* CONFIG_FEATURE_VI_CRASHME */
370 #ifdef CONFIG_FEATURE_VI_READONLY
371                 case 'R':               // Read-only flag
372                         readonly = TRUE;
373                         vi_readonly = TRUE;
374                         break;
375 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
376                         //case 'r':     // recover flag-  ignore- we don't use tmp file
377                         //case 'x':     // encryption flag- ignore
378                         //case 'c':     // execute command first
379                         //case 'h':     // help -- just use default
380                 default:
381                         show_help();
382                         return 1;
383                 }
384         }
385
386         // The argv array can be used by the ":next"  and ":rewind" commands
387         // save optind.
388         fn_start = optind;      // remember first file name for :next and :rew
389         save_argc = argc;
390
391         //----- This is the main file handling loop --------------
392         if (optind >= argc) {
393                 editing = 1;    // 0= exit,  1= one file,  2= multiple files
394                 edit_file(0);
395         } else {
396                 for (; optind < argc; optind++) {
397                         editing = 1;    // 0=exit, 1=one file, 2+ =many files
398                         free(cfn);
399                         cfn = (Byte *) bb_xstrdup(argv[optind]);
400                         edit_file(cfn);
401                 }
402         }
403         //-----------------------------------------------------------
404
405         return (0);
406 }
407
408 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
409 //----- See what the window size currently is --------------------
410 static inline void window_size_get(int fd)
411 {
412         get_terminal_width_height(fd, &columns, &rows);
413 }
414 #endif                                                  /* CONFIG_FEATURE_VI_WIN_RESIZE */
415
416 static void edit_file(Byte * fn)
417 {
418         Byte c;
419         int cnt, size, ch;
420
421 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
422         int sig;
423 #endif                                                  /* CONFIG_FEATURE_VI_USE_SIGNALS */
424 #ifdef CONFIG_FEATURE_VI_YANKMARK
425         static Byte *cur_line;
426 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
427
428         rawmode();
429         rows = 24;
430         columns = 80;
431         ch= -1;
432 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
433         window_size_get(0);
434 #endif                                                  /* CONFIG_FEATURE_VI_WIN_RESIZE */
435         new_screen(rows, columns);      // get memory for virtual screen
436
437         cnt = file_size(fn);    // file size
438         size = 2 * cnt;         // 200% of file size
439         new_text(size);         // get a text[] buffer
440         screenbegin = dot = end = text;
441         if (fn != 0) {
442                 ch= file_insert(fn, text, cnt);
443         }
444         if (ch < 1) {
445                 (void) char_insert(text, '\n'); // start empty buf with dummy line
446         }
447         file_modified = 0;
448         last_file_modified = -1;
449 #ifdef CONFIG_FEATURE_VI_YANKMARK
450         YDreg = 26;                     // default Yank/Delete reg
451         Ureg = 27;                      // hold orig line for "U" cmd
452         for (cnt = 0; cnt < 28; cnt++) {
453                 mark[cnt] = 0;
454         }                                       // init the marks
455         mark[26] = mark[27] = text;     // init "previous context"
456 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
457
458         last_forward_char = last_input_char = '\0';
459         crow = 0;
460         ccol = 0;
461
462 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
463         catch_sig(0);
464         core_sig(0);
465         signal(SIGWINCH, winch_sig);
466         signal(SIGTSTP, suspend_sig);
467         sig = setjmp(restart);
468         if (sig != 0) {
469                 const char *msg = "";
470
471                 if (sig == SIGWINCH)
472                         msg = "(window resize)";
473                 if (sig == SIGHUP)
474                         msg = "(hangup)";
475                 if (sig == SIGINT)
476                         msg = "(interrupt)";
477                 if (sig == SIGTERM)
478                         msg = "(terminate)";
479                 if (sig == SIGBUS)
480                         msg = "(bus error)";
481                 if (sig == SIGSEGV)
482                         msg = "(I tried to touch invalid memory)";
483                 if (sig == SIGALRM)
484                         msg = "(alarm)";
485
486                 psbs("-- caught signal %d %s--", sig, msg);
487                 screenbegin = dot = text;
488         }
489 #endif                                                  /* CONFIG_FEATURE_VI_USE_SIGNALS */
490
491         editing = 1;
492         cmd_mode = 0;           // 0=command  1=insert  2='R'eplace
493         cmdcnt = 0;
494         tabstop = 8;
495         offset = 0;                     // no horizontal offset
496         c = '\0';
497 #ifdef CONFIG_FEATURE_VI_DOT_CMD
498         free(last_modifying_cmd);
499         free(ioq_start);
500         ioq = ioq_start = last_modifying_cmd = 0;
501         adding2q = 0;
502 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD */
503         redraw(FALSE);                  // dont force every col re-draw
504         show_status_line();
505
506         //------This is the main Vi cmd handling loop -----------------------
507         while (editing > 0) {
508 #ifdef CONFIG_FEATURE_VI_CRASHME
509                 if (crashme > 0) {
510                         if ((end - text) > 1) {
511                                 crash_dummy();  // generate a random command
512                         } else {
513                                 crashme = 0;
514                                 dot =
515                                         string_insert(text, (Byte *) "\n\n#####  Ran out of text to work on.  #####\n\n");      // insert the string
516                                 refresh(FALSE);
517                         }
518                 }
519 #endif                                                  /* CONFIG_FEATURE_VI_CRASHME */
520                 last_input_char = c = get_one_char();   // get a cmd from user
521 #ifdef CONFIG_FEATURE_VI_YANKMARK
522                 // save a copy of the current line- for the 'U" command
523                 if (begin_line(dot) != cur_line) {
524                         cur_line = begin_line(dot);
525                         text_yank(begin_line(dot), end_line(dot), Ureg);
526                 }
527 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
528 #ifdef CONFIG_FEATURE_VI_DOT_CMD
529                 // These are commands that change text[].
530                 // Remember the input for the "." command
531                 if (!adding2q && ioq_start == 0
532                         && strchr((char *) modifying_cmds, c) != NULL) {
533                         start_new_cmd_q(c);
534                 }
535 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD */
536                 do_cmd(c);              // execute the user command
537                 //
538                 // poll to see if there is input already waiting. if we are
539                 // not able to display output fast enough to keep up, skip
540                 // the display update until we catch up with input.
541                 if (mysleep(0) == 0) {
542                         // no input pending- so update output
543                         refresh(FALSE);
544                         show_status_line();
545                 }
546 #ifdef CONFIG_FEATURE_VI_CRASHME
547                 if (crashme > 0)
548                         crash_test();   // test editor variables
549 #endif                                                  /* CONFIG_FEATURE_VI_CRASHME */
550         }
551         //-------------------------------------------------------------------
552
553         place_cursor(rows, 0, FALSE);   // go to bottom of screen
554         clear_to_eol();         // Erase to end of line
555         cookmode();
556 }
557
558 //----- The Colon commands -------------------------------------
559 #ifdef CONFIG_FEATURE_VI_COLON
560 static Byte *get_one_address(Byte * p, int *addr)       // get colon addr, if present
561 {
562         int st;
563         Byte *q;
564
565 #ifdef CONFIG_FEATURE_VI_YANKMARK
566         Byte c;
567 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
568 #ifdef CONFIG_FEATURE_VI_SEARCH
569         Byte *pat, buf[BUFSIZ];
570 #endif                                                  /* CONFIG_FEATURE_VI_SEARCH */
571
572         *addr = -1;                     // assume no addr
573         if (*p == '.') {        // the current line
574                 p++;
575                 q = begin_line(dot);
576                 *addr = count_lines(text, q);
577 #ifdef CONFIG_FEATURE_VI_YANKMARK
578         } else if (*p == '\'') {        // is this a mark addr
579                 p++;
580                 c = tolower(*p);
581                 p++;
582                 if (c >= 'a' && c <= 'z') {
583                         // we have a mark
584                         c = c - 'a';
585                         q = mark[(int) c];
586                         if (q != NULL) {        // is mark valid
587                                 *addr = count_lines(text, q);   // count lines
588                         }
589                 }
590 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
591 #ifdef CONFIG_FEATURE_VI_SEARCH
592         } else if (*p == '/') { // a search pattern
593                 q = buf;
594                 for (p++; *p; p++) {
595                         if (*p == '/')
596                                 break;
597                         *q++ = *p;
598                         *q = '\0';
599                 }
600                 pat = (Byte *) bb_xstrdup((char *) buf);        // save copy of pattern
601                 if (*p == '/')
602                         p++;
603                 q = char_search(dot, pat, FORWARD, FULL);
604                 if (q != NULL) {
605                         *addr = count_lines(text, q);
606                 }
607                 free(pat);
608 #endif                                                  /* CONFIG_FEATURE_VI_SEARCH */
609         } else if (*p == '$') { // the last line in file
610                 p++;
611                 q = begin_line(end - 1);
612                 *addr = count_lines(text, q);
613         } else if (isdigit(*p)) {       // specific line number
614                 sscanf((char *) p, "%d%n", addr, &st);
615                 p += st;
616         } else {                        // I don't reconise this
617                 // unrecognised address- assume -1
618                 *addr = -1;
619         }
620         return (p);
621 }
622
623 static Byte *get_address(Byte *p, int *b, int *e)       // get two colon addrs, if present
624 {
625         //----- get the address' i.e., 1,3   'a,'b  -----
626         // get FIRST addr, if present
627         while (isblnk(*p))
628                 p++;                            // skip over leading spaces
629         if (*p == '%') {                        // alias for 1,$
630                 p++;
631                 *b = 1;
632                 *e = count_lines(text, end-1);
633                 goto ga0;
634         }
635         p = get_one_address(p, b);
636         while (isblnk(*p))
637                 p++;
638         if (*p == ',') {                        // is there a address separator
639                 p++;
640                 while (isblnk(*p))
641                         p++;
642                 // get SECOND addr, if present
643                 p = get_one_address(p, e);
644         }
645 ga0:
646         while (isblnk(*p))
647                 p++;                            // skip over trailing spaces
648         return (p);
649 }
650
651 #ifdef CONFIG_FEATURE_VI_SETOPTS
652 static void setops(const Byte *args, const char *opname, int flg_no,
653                         const char *short_opname, int opt)
654 {
655         const char *a = (char *) args + flg_no;
656         int l = strlen(opname) - 1; /* opname have + ' ' */
657
658         if (strncasecmp(a, opname, l) == 0 ||
659                         strncasecmp(a, short_opname, 2) == 0) {
660                 if(flg_no)
661                         vi_setops &= ~opt;
662                  else
663                         vi_setops |= opt;
664         }
665 }
666 #endif
667
668 static void colon(Byte * buf)
669 {
670         Byte c, *orig_buf, *buf1, *q, *r;
671         Byte *fn, cmd[BUFSIZ], args[BUFSIZ];
672         int i, l, li, ch, st, b, e;
673         int useforce = FALSE, forced = FALSE;
674         struct stat st_buf;
675
676         // :3154        // if (-e line 3154) goto it  else stay put
677         // :4,33w! foo  // write a portion of buffer to file "foo"
678         // :w           // write all of buffer to current file
679         // :q           // quit
680         // :q!          // quit- dont care about modified file
681         // :'a,'z!sort -u   // filter block through sort
682         // :'f          // goto mark "f"
683         // :'fl         // list literal the mark "f" line
684         // :.r bar      // read file "bar" into buffer before dot
685         // :/123/,/abc/d    // delete lines from "123" line to "abc" line
686         // :/xyz/       // goto the "xyz" line
687         // :s/find/replace/ // substitute pattern "find" with "replace"
688         // :!<cmd>      // run <cmd> then return
689         //
690
691         if (strlen((char *) buf) <= 0)
692                 goto vc1;
693         if (*buf == ':')
694                 buf++;                  // move past the ':'
695
696         li = st = ch = i = 0;
697         b = e = -1;
698         q = text;                       // assume 1,$ for the range
699         r = end - 1;
700         li = count_lines(text, end - 1);
701         fn = cfn;                       // default to current file
702         memset(cmd, '\0', BUFSIZ);      // clear cmd[]
703         memset(args, '\0', BUFSIZ);     // clear args[]
704
705         // look for optional address(es)  :.  :1  :1,9   :'q,'a   :%
706         buf = get_address(buf, &b, &e);
707
708         // remember orig command line
709         orig_buf = buf;
710
711         // get the COMMAND into cmd[]
712         buf1 = cmd;
713         while (*buf != '\0') {
714                 if (isspace(*buf))
715                         break;
716                 *buf1++ = *buf++;
717         }
718         // get any ARGuments
719         while (isblnk(*buf))
720                 buf++;
721         strcpy((char *) args, (char *) buf);
722         buf1 = (Byte*)last_char_is((char *)cmd, '!');
723         if (buf1) {
724                 useforce = TRUE;
725                 *buf1 = '\0';   // get rid of !
726         }
727         if (b >= 0) {
728                 // if there is only one addr, then the addr
729                 // is the line number of the single line the
730                 // user wants. So, reset the end
731                 // pointer to point at end of the "b" line
732                 q = find_line(b);       // what line is #b
733                 r = end_line(q);
734                 li = 1;
735         }
736         if (e >= 0) {
737                 // we were given two addrs.  change the
738                 // end pointer to the addr given by user.
739                 r = find_line(e);       // what line is #e
740                 r = end_line(r);
741                 li = e - b + 1;
742         }
743         // ------------ now look for the command ------------
744         i = strlen((char *) cmd);
745         if (i == 0) {           // :123CR goto line #123
746                 if (b >= 0) {
747                         dot = find_line(b);     // what line is #b
748                         dot_skip_over_ws();
749                 }
750         } else if (strncmp((char *) cmd, "!", 1) == 0) {        // run a cmd
751                 // :!ls   run the <cmd>
752                 (void) alarm(0);                // wait for input- no alarms
753                 place_cursor(rows - 1, 0, FALSE);       // go to Status line
754                 clear_to_eol();                 // clear the line
755                 cookmode();
756                 system((char*)(orig_buf+1));            // run the cmd
757                 rawmode();
758                 Hit_Return();                   // let user see results
759                 (void) alarm(3);                // done waiting for input
760         } else if (strncmp((char *) cmd, "=", i) == 0) {        // where is the address
761                 if (b < 0) {    // no addr given- use defaults
762                         b = e = count_lines(text, dot);
763                 }
764                 psb("%d", b);
765         } else if (strncasecmp((char *) cmd, "delete", i) == 0) {       // delete lines
766                 if (b < 0) {    // no addr given- use defaults
767                         q = begin_line(dot);    // assume .,. for the range
768                         r = end_line(dot);
769                 }
770                 dot = yank_delete(q, r, 1, YANKDEL);    // save, then delete lines
771                 dot_skip_over_ws();
772         } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
773                 int sr;
774                 sr= 0;
775                 // don't edit, if the current file has been modified
776                 if (file_modified && ! useforce) {
777                         psbs("No write since last change (:edit! overrides)");
778                         goto vc1;
779                 }
780                 if (strlen((char*)args) > 0) {
781                         // the user supplied a file name
782                         fn= args;
783                 } else if (cfn != 0 && strlen((char*)cfn) > 0) {
784                         // no user supplied name- use the current filename
785                         fn= cfn;
786                         goto vc5;
787                 } else {
788                         // no user file name, no current name- punt
789                         psbs("No current filename");
790                         goto vc1;
791                 }
792
793                 // see if file exists- if not, its just a new file request
794                 if ((sr=stat((char*)fn, &st_buf)) < 0) {
795                         // This is just a request for a new file creation.
796                         // The file_insert below will fail but we get
797                         // an empty buffer with a file name.  Then the "write"
798                         // command can do the create.
799                 } else {
800                         if ((st_buf.st_mode & (S_IFREG)) == 0) {
801                                 // This is not a regular file
802                                 psbs("\"%s\" is not a regular file", fn);
803                                 goto vc1;
804                         }
805                         if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) {
806                                 // dont have any read permissions
807                                 psbs("\"%s\" is not readable", fn);
808                                 goto vc1;
809                         }
810                 }
811
812                 // There is a read-able regular file
813                 // make this the current file
814                 q = (Byte *) bb_xstrdup((char *) fn);   // save the cfn
815                 free(cfn);              // free the old name
816                 cfn = q;                        // remember new cfn
817
818           vc5:
819                 // delete all the contents of text[]
820                 new_text(2 * file_size(fn));
821                 screenbegin = dot = end = text;
822
823                 // insert new file
824                 ch = file_insert(fn, text, file_size(fn));
825
826                 if (ch < 1) {
827                         // start empty buf with dummy line
828                         (void) char_insert(text, '\n');
829                         ch= 1;
830                 }
831                 file_modified = 0;
832                 last_file_modified = -1;
833 #ifdef CONFIG_FEATURE_VI_YANKMARK
834                 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
835                         free(reg[Ureg]);        //   free orig line reg- for 'U'
836                         reg[Ureg]= 0;
837                 }
838                 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
839                         free(reg[YDreg]);       //   free default yank/delete register
840                         reg[YDreg]= 0;
841                 }
842                 for (li = 0; li < 28; li++) {
843                         mark[li] = 0;
844                 }                               // init the marks
845 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
846                 // how many lines in text[]?
847                 li = count_lines(text, end - 1);
848                 psb("\"%s\"%s"
849 #ifdef CONFIG_FEATURE_VI_READONLY
850                         "%s"
851 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
852                         " %dL, %dC", cfn,
853                         (sr < 0 ? " [New file]" : ""),
854 #ifdef CONFIG_FEATURE_VI_READONLY
855                         ((vi_readonly || readonly) ? " [Read only]" : ""),
856 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
857                         li, ch);
858         } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
859                 if (b != -1 || e != -1) {
860                         ni((Byte *) "No address allowed on this command");
861                         goto vc1;
862                 }
863                 if (strlen((char *) args) > 0) {
864                         // user wants a new filename
865                         free(cfn);
866                         cfn = (Byte *) bb_xstrdup((char *) args);
867                 } else {
868                         // user wants file status info
869                         last_status_cksum = 0;  // force status update
870                 }
871         } else if (strncasecmp((char *) cmd, "features", i) == 0) {     // what features are available
872                 // print out values of all features
873                 place_cursor(rows - 1, 0, FALSE);       // go to Status line, bottom of screen
874                 clear_to_eol(); // clear the line
875                 cookmode();
876                 show_help();
877                 rawmode();
878                 Hit_Return();
879         } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
880                 if (b < 0) {    // no addr given- use defaults
881                         q = begin_line(dot);    // assume .,. for the range
882                         r = end_line(dot);
883                 }
884                 place_cursor(rows - 1, 0, FALSE);       // go to Status line, bottom of screen
885                 clear_to_eol(); // clear the line
886                 puts("\r");
887                 for (; q <= r; q++) {
888                         int c_is_no_print;
889
890                         c = *q;
891                         c_is_no_print = c > 127 && !Isprint(c);
892                         if (c_is_no_print) {
893                                 c = '.';
894                                 standout_start();
895                                 }
896                         if (c == '\n') {
897                                 write1("$\r");
898                         } else if (c < ' ' || c == 127) {
899                                 putchar('^');
900                                 if(c == 127)
901                                         c = '?';
902                                  else
903                                 c += '@';
904                         }
905                         putchar(c);
906                         if (c_is_no_print)
907                                 standout_end();
908                 }
909 #ifdef CONFIG_FEATURE_VI_SET
910           vc2:
911 #endif                                                  /* CONFIG_FEATURE_VI_SET */
912                 Hit_Return();
913         } else if ((strncasecmp((char *) cmd, "quit", i) == 0) ||       // Quit
914                            (strncasecmp((char *) cmd, "next", i) == 0)) {       // edit next file
915                 if (useforce) {
916                         // force end of argv list
917                         if (*cmd == 'q') {
918                                 optind = save_argc;
919                         }
920                         editing = 0;
921                         goto vc1;
922                 }
923                 // don't exit if the file been modified
924                 if (file_modified) {
925                         psbs("No write since last change (:%s! overrides)",
926                                  (*cmd == 'q' ? "quit" : "next"));
927                         goto vc1;
928                 }
929                 // are there other file to edit
930                 if (*cmd == 'q' && optind < save_argc - 1) {
931                         psbs("%d more file to edit", (save_argc - optind - 1));
932                         goto vc1;
933                 }
934                 if (*cmd == 'n' && optind >= save_argc - 1) {
935                         psbs("No more files to edit");
936                         goto vc1;
937                 }
938                 editing = 0;
939         } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
940                 fn = args;
941                 if (strlen((char *) fn) <= 0) {
942                         psbs("No filename given");
943                         goto vc1;
944                 }
945                 if (b < 0) {    // no addr given- use defaults
946                         q = begin_line(dot);    // assume "dot"
947                 }
948                 // read after current line- unless user said ":0r foo"
949                 if (b != 0)
950                         q = next_line(q);
951 #ifdef CONFIG_FEATURE_VI_READONLY
952                 l= readonly;                    // remember current files' status
953 #endif
954                 ch = file_insert(fn, q, file_size(fn));
955 #ifdef CONFIG_FEATURE_VI_READONLY
956                 readonly= l;
957 #endif
958                 if (ch < 0)
959                         goto vc1;       // nothing was inserted
960                 // how many lines in text[]?
961                 li = count_lines(q, q + ch - 1);
962                 psb("\"%s\""
963 #ifdef CONFIG_FEATURE_VI_READONLY
964                         "%s"
965 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
966                         " %dL, %dC", fn,
967 #ifdef CONFIG_FEATURE_VI_READONLY
968                         ((vi_readonly || readonly) ? " [Read only]" : ""),
969 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
970                         li, ch);
971                 if (ch > 0) {
972                         // if the insert is before "dot" then we need to update
973                         if (q <= dot)
974                                 dot += ch;
975                         file_modified++;
976                 }
977         } else if (strncasecmp((char *) cmd, "rewind", i) == 0) {       // rewind cmd line args
978                 if (file_modified && ! useforce) {
979                         psbs("No write since last change (:rewind! overrides)");
980                 } else {
981                         // reset the filenames to edit
982                         optind = fn_start - 1;
983                         editing = 0;
984                 }
985 #ifdef CONFIG_FEATURE_VI_SET
986         } else if (strncasecmp((char *) cmd, "set", i) == 0) {  // set or clear features
987                 i = 0;                  // offset into args
988                 if (strlen((char *) args) == 0) {
989                         // print out values of all options
990                         place_cursor(rows - 1, 0, FALSE);       // go to Status line, bottom of screen
991                         clear_to_eol(); // clear the line
992                         printf("----------------------------------------\r\n");
993 #ifdef CONFIG_FEATURE_VI_SETOPTS
994                         if (!autoindent)
995                                 printf("no");
996                         printf("autoindent ");
997                         if (!err_method)
998                                 printf("no");
999                         printf("flash ");
1000                         if (!ignorecase)
1001                                 printf("no");
1002                         printf("ignorecase ");
1003                         if (!showmatch)
1004                                 printf("no");
1005                         printf("showmatch ");
1006                         printf("tabstop=%d ", tabstop);
1007 #endif                                                  /* CONFIG_FEATURE_VI_SETOPTS */
1008                         printf("\r\n");
1009                         goto vc2;
1010                 }
1011                 if (strncasecmp((char *) args, "no", 2) == 0)
1012                         i = 2;          // ":set noautoindent"
1013 #ifdef CONFIG_FEATURE_VI_SETOPTS
1014                 setops(args, "autoindent ", i, "ai", VI_AUTOINDENT);
1015                 setops(args, "flash ", i, "fl", VI_ERR_METHOD);
1016                 setops(args, "ignorecase ", i, "ic", VI_IGNORECASE);
1017                 setops(args, "showmatch ", i, "ic", VI_SHOWMATCH);
1018                 if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) {
1019                         sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1020                         if (ch > 0 && ch < columns - 1)
1021                                 tabstop = ch;
1022                 }
1023 #endif                                                  /* CONFIG_FEATURE_VI_SETOPTS */
1024 #endif                                                  /* CONFIG_FEATURE_VI_SET */
1025 #ifdef CONFIG_FEATURE_VI_SEARCH
1026         } else if (strncasecmp((char *) cmd, "s", 1) == 0) {    // substitute a pattern with a replacement pattern
1027                 Byte *ls, *F, *R;
1028                 int gflag;
1029
1030                 // F points to the "find" pattern
1031                 // R points to the "replace" pattern
1032                 // replace the cmd line delimiters "/" with NULLs
1033                 gflag = 0;              // global replace flag
1034                 c = orig_buf[1];        // what is the delimiter
1035                 F = orig_buf + 2;       // start of "find"
1036                 R = (Byte *) strchr((char *) F, c);     // middle delimiter
1037                 if (!R) goto colon_s_fail;
1038                 *R++ = '\0';    // terminate "find"
1039                 buf1 = (Byte *) strchr((char *) R, c);
1040                 if (!buf1) goto colon_s_fail;
1041                 *buf1++ = '\0'; // terminate "replace"
1042                 if (*buf1 == 'g') {     // :s/foo/bar/g
1043                         buf1++;
1044                         gflag++;        // turn on gflag
1045                 }
1046                 q = begin_line(q);
1047                 if (b < 0) {    // maybe :s/foo/bar/
1048                         q = begin_line(dot);    // start with cur line
1049                         b = count_lines(text, q);       // cur line number
1050                 }
1051                 if (e < 0)
1052                         e = b;          // maybe :.s/foo/bar/
1053                 for (i = b; i <= e; i++) {      // so, :20,23 s \0 find \0 replace \0
1054                         ls = q;         // orig line start
1055                   vc4:
1056                         buf1 = char_search(q, F, FORWARD, LIMITED);     // search cur line only for "find"
1057                         if (buf1 != NULL) {
1058                                 // we found the "find" pattern- delete it
1059                                 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1060                                 // inset the "replace" patern
1061                                 (void) string_insert(buf1, R);  // insert the string
1062                                 // check for "global"  :s/foo/bar/g
1063                                 if (gflag == 1) {
1064                                         if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1065                                                 q = buf1 + strlen((char *) R);
1066                                                 goto vc4;       // don't let q move past cur line
1067                                         }
1068                                 }
1069                         }
1070                         q = next_line(ls);
1071                 }
1072 #endif                                                  /* CONFIG_FEATURE_VI_SEARCH */
1073         } else if (strncasecmp((char *) cmd, "version", i) == 0) {      // show software version
1074                 psb("%s", vi_Version);
1075         } else if (strncasecmp((char *) cmd, "write", i) == 0           // write text to file
1076                         || strncasecmp((char *) cmd, "wq", i) == 0
1077                         || strncasecmp((char *) cmd, "wn", i) == 0
1078                         || strncasecmp((char *) cmd, "x", i) == 0) {
1079                 // is there a file name to write to?
1080                 if (strlen((char *) args) > 0) {
1081                         fn = args;
1082                 }
1083 #ifdef CONFIG_FEATURE_VI_READONLY
1084                 if ((vi_readonly || readonly) && ! useforce) {
1085                         psbs("\"%s\" File is read only", fn);
1086                         goto vc3;
1087                 }
1088 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
1089                 // how many lines in text[]?
1090                 li = count_lines(q, r);
1091                 ch = r - q + 1;
1092                 // see if file exists- if not, its just a new file request
1093                 if (useforce) {
1094                         // if "fn" is not write-able, chmod u+w
1095                         // sprintf(syscmd, "chmod u+w %s", fn);
1096                         // system(syscmd);
1097                         forced = TRUE;
1098                 }
1099                 l = file_write(fn, q, r);
1100                 if (useforce && forced) {
1101                         // chmod u-w
1102                         // sprintf(syscmd, "chmod u-w %s", fn);
1103                         // system(syscmd);
1104                         forced = FALSE;
1105                 }
1106                 if (l < 0) {
1107                         if (l == -1)
1108                                 psbs("Write error: %s", strerror(errno));
1109                 } else {
1110                         psb("\"%s\" %dL, %dC", fn, li, l);
1111                         if (q == text && r == end - 1 && l == ch) {
1112                                 file_modified = 0;
1113                                 last_file_modified = -1;
1114                         }
1115                         if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' ||
1116                              cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N')
1117                              && l == ch) {
1118                                 editing = 0;
1119                         }
1120                 }
1121 #ifdef CONFIG_FEATURE_VI_READONLY
1122           vc3:;
1123 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
1124 #ifdef CONFIG_FEATURE_VI_YANKMARK
1125         } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
1126                 if (b < 0) {    // no addr given- use defaults
1127                         q = begin_line(dot);    // assume .,. for the range
1128                         r = end_line(dot);
1129                 }
1130                 text_yank(q, r, YDreg);
1131                 li = count_lines(q, r);
1132                 psb("Yank %d lines (%d chars) into [%c]",
1133                         li, strlen((char *) reg[YDreg]), what_reg());
1134 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
1135         } else {
1136                 // cmd unknown
1137                 ni((Byte *) cmd);
1138         }
1139   vc1:
1140         dot = bound_dot(dot);   // make sure "dot" is valid
1141         return;
1142 #ifdef CONFIG_FEATURE_VI_SEARCH
1143 colon_s_fail:
1144         psb(":s expression missing delimiters");
1145 #endif
1146 }
1147
1148 #endif                                                  /* CONFIG_FEATURE_VI_COLON */
1149
1150 static void Hit_Return(void)
1151 {
1152         char c;
1153
1154         standout_start();       // start reverse video
1155         write1("[Hit return to continue]");
1156         standout_end();         // end reverse video
1157         while ((c = get_one_char()) != '\n' && c != '\r')       /*do nothing */
1158                 ;
1159         redraw(TRUE);           // force redraw all
1160 }
1161
1162 //----- Synchronize the cursor to Dot --------------------------
1163 static void sync_cursor(Byte * d, int *row, int *col)
1164 {
1165         Byte *beg_cur, *end_cur;        // begin and end of "d" line
1166         Byte *beg_scr, *end_scr;        // begin and end of screen
1167         Byte *tp;
1168         int cnt, ro, co;
1169
1170         beg_cur = begin_line(d);        // first char of cur line
1171         end_cur = end_line(d);  // last char of cur line
1172
1173         beg_scr = end_scr = screenbegin;        // first char of screen
1174         end_scr = end_screen(); // last char of screen
1175
1176         if (beg_cur < screenbegin) {
1177                 // "d" is before  top line on screen
1178                 // how many lines do we have to move
1179                 cnt = count_lines(beg_cur, screenbegin);
1180           sc1:
1181                 screenbegin = beg_cur;
1182                 if (cnt > (rows - 1) / 2) {
1183                         // we moved too many lines. put "dot" in middle of screen
1184                         for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1185                                 screenbegin = prev_line(screenbegin);
1186                         }
1187                 }
1188         } else if (beg_cur > end_scr) {
1189                 // "d" is after bottom line on screen
1190                 // how many lines do we have to move
1191                 cnt = count_lines(end_scr, beg_cur);
1192                 if (cnt > (rows - 1) / 2)
1193                         goto sc1;       // too many lines
1194                 for (ro = 0; ro < cnt - 1; ro++) {
1195                         // move screen begin the same amount
1196                         screenbegin = next_line(screenbegin);
1197                         // now, move the end of screen
1198                         end_scr = next_line(end_scr);
1199                         end_scr = end_line(end_scr);
1200                 }
1201         }
1202         // "d" is on screen- find out which row
1203         tp = screenbegin;
1204         for (ro = 0; ro < rows - 1; ro++) {     // drive "ro" to correct row
1205                 if (tp == beg_cur)
1206                         break;
1207                 tp = next_line(tp);
1208         }
1209
1210         // find out what col "d" is on
1211         co = 0;
1212         do {                            // drive "co" to correct column
1213                 if (*tp == '\n' || *tp == '\0')
1214                         break;
1215                 if (*tp == '\t') {
1216                         //         7       - (co %    8  )
1217                         co += ((tabstop - 1) - (co % tabstop));
1218                 } else if (*tp < ' ' || *tp == 127) {
1219                         co++;           // display as ^X, use 2 columns
1220                 }
1221         } while (tp++ < d && ++co);
1222
1223         // "co" is the column where "dot" is.
1224         // The screen has "columns" columns.
1225         // The currently displayed columns are  0+offset -- columns+ofset
1226         // |-------------------------------------------------------------|
1227         //               ^ ^                                ^
1228         //        offset | |------- columns ----------------|
1229         //
1230         // If "co" is already in this range then we do not have to adjust offset
1231         //      but, we do have to subtract the "offset" bias from "co".
1232         // If "co" is outside this range then we have to change "offset".
1233         // If the first char of a line is a tab the cursor will try to stay
1234         //  in column 7, but we have to set offset to 0.
1235
1236         if (co < 0 + offset) {
1237                 offset = co;
1238         }
1239         if (co >= columns + offset) {
1240                 offset = co - columns + 1;
1241         }
1242         // if the first char of the line is a tab, and "dot" is sitting on it
1243         //  force offset to 0.
1244         if (d == beg_cur && *d == '\t') {
1245                 offset = 0;
1246         }
1247         co -= offset;
1248
1249         *row = ro;
1250         *col = co;
1251 }
1252
1253 //----- Text Movement Routines ---------------------------------
1254 static Byte *begin_line(Byte * p) // return pointer to first char cur line
1255 {
1256         while (p > text && p[-1] != '\n')
1257                 p--;                    // go to cur line B-o-l
1258         return (p);
1259 }
1260
1261 static Byte *end_line(Byte * p) // return pointer to NL of cur line line
1262 {
1263         while (p < end - 1 && *p != '\n')
1264                 p++;                    // go to cur line E-o-l
1265         return (p);
1266 }
1267
1268 static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line
1269 {
1270         while (p < end - 1 && *p != '\n')
1271                 p++;                    // go to cur line E-o-l
1272         // Try to stay off of the Newline
1273         if (*p == '\n' && (p - begin_line(p)) > 0)
1274                 p--;
1275         return (p);
1276 }
1277
1278 static Byte *prev_line(Byte * p) // return pointer first char prev line
1279 {
1280         p = begin_line(p);      // goto begining of cur line
1281         if (p[-1] == '\n' && p > text)
1282                 p--;                    // step to prev line
1283         p = begin_line(p);      // goto begining of prev line
1284         return (p);
1285 }
1286
1287 static Byte *next_line(Byte * p) // return pointer first char next line
1288 {
1289         p = end_line(p);
1290         if (*p == '\n' && p < end - 1)
1291                 p++;                    // step to next line
1292         return (p);
1293 }
1294
1295 //----- Text Information Routines ------------------------------
1296 static Byte *end_screen(void)
1297 {
1298         Byte *q;
1299         int cnt;
1300
1301         // find new bottom line
1302         q = screenbegin;
1303         for (cnt = 0; cnt < rows - 2; cnt++)
1304                 q = next_line(q);
1305         q = end_line(q);
1306         return (q);
1307 }
1308
1309 static int count_lines(Byte * start, Byte * stop) // count line from start to stop
1310 {
1311         Byte *q;
1312         int cnt;
1313
1314         if (stop < start) {     // start and stop are backwards- reverse them
1315                 q = start;
1316                 start = stop;
1317                 stop = q;
1318         }
1319         cnt = 0;
1320         stop = end_line(stop);  // get to end of this line
1321         for (q = start; q <= stop && q <= end - 1; q++) {
1322                 if (*q == '\n')
1323                         cnt++;
1324         }
1325         return (cnt);
1326 }
1327
1328 static Byte *find_line(int li)  // find begining of line #li
1329 {
1330         Byte *q;
1331
1332         for (q = text; li > 1; li--) {
1333                 q = next_line(q);
1334         }
1335         return (q);
1336 }
1337
1338 //----- Dot Movement Routines ----------------------------------
1339 static void dot_left(void)
1340 {
1341         if (dot > text && dot[-1] != '\n')
1342                 dot--;
1343 }
1344
1345 static void dot_right(void)
1346 {
1347         if (dot < end - 1 && *dot != '\n')
1348                 dot++;
1349 }
1350
1351 static void dot_begin(void)
1352 {
1353         dot = begin_line(dot);  // return pointer to first char cur line
1354 }
1355
1356 static void dot_end(void)
1357 {
1358         dot = end_line(dot);    // return pointer to last char cur line
1359 }
1360
1361 static Byte *move_to_col(Byte * p, int l)
1362 {
1363         int co;
1364
1365         p = begin_line(p);
1366         co = 0;
1367         do {
1368                 if (*p == '\n' || *p == '\0')
1369                         break;
1370                 if (*p == '\t') {
1371                         //         7       - (co %    8  )
1372                         co += ((tabstop - 1) - (co % tabstop));
1373                 } else if (*p < ' ' || *p == 127) {
1374                         co++;           // display as ^X, use 2 columns
1375                 }
1376         } while (++co <= l && p++ < end);
1377         return (p);
1378 }
1379
1380 static void dot_next(void)
1381 {
1382         dot = next_line(dot);
1383 }
1384
1385 static void dot_prev(void)
1386 {
1387         dot = prev_line(dot);
1388 }
1389
1390 static void dot_scroll(int cnt, int dir)
1391 {
1392         Byte *q;
1393
1394         for (; cnt > 0; cnt--) {
1395                 if (dir < 0) {
1396                         // scroll Backwards
1397                         // ctrl-Y  scroll up one line
1398                         screenbegin = prev_line(screenbegin);
1399                 } else {
1400                         // scroll Forwards
1401                         // ctrl-E  scroll down one line
1402                         screenbegin = next_line(screenbegin);
1403                 }
1404         }
1405         // make sure "dot" stays on the screen so we dont scroll off
1406         if (dot < screenbegin)
1407                 dot = screenbegin;
1408         q = end_screen();       // find new bottom line
1409         if (dot > q)
1410                 dot = begin_line(q);    // is dot is below bottom line?
1411         dot_skip_over_ws();
1412 }
1413
1414 static void dot_skip_over_ws(void)
1415 {
1416         // skip WS
1417         while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1418                 dot++;
1419 }
1420
1421 static void dot_delete(void)    // delete the char at 'dot'
1422 {
1423         (void) text_hole_delete(dot, dot);
1424 }
1425
1426 static Byte *bound_dot(Byte * p) // make sure  text[0] <= P < "end"
1427 {
1428         if (p >= end && end > text) {
1429                 p = end - 1;
1430                 indicate_error('1');
1431         }
1432         if (p < text) {
1433                 p = text;
1434                 indicate_error('2');
1435         }
1436         return (p);
1437 }
1438
1439 //----- Helper Utility Routines --------------------------------
1440
1441 //----------------------------------------------------------------
1442 //----- Char Routines --------------------------------------------
1443 /* Chars that are part of a word-
1444  *    0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1445  * Chars that are Not part of a word (stoppers)
1446  *    !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1447  * Chars that are WhiteSpace
1448  *    TAB NEWLINE VT FF RETURN SPACE
1449  * DO NOT COUNT NEWLINE AS WHITESPACE
1450  */
1451
1452 static Byte *new_screen(int ro, int co)
1453 {
1454         int li;
1455
1456         free(screen);
1457         screensize = ro * co + 8;
1458         screen = (Byte *) xmalloc(screensize);
1459         // initialize the new screen. assume this will be a empty file.
1460         screen_erase();
1461         //   non-existent text[] lines start with a tilde (~).
1462         for (li = 1; li < ro - 1; li++) {
1463                 screen[(li * co) + 0] = '~';
1464         }
1465         return (screen);
1466 }
1467
1468 static Byte *new_text(int size)
1469 {
1470         if (size < 10240)
1471                 size = 10240;   // have a minimum size for new files
1472         free(text);
1473         text = (Byte *) xmalloc(size + 8);
1474         memset(text, '\0', size);       // clear new text[]
1475         //text += 4;            // leave some room for "oops"
1476         textend = text + size - 1;
1477         //textend -= 4;         // leave some root for "oops"
1478         return (text);
1479 }
1480
1481 #ifdef CONFIG_FEATURE_VI_SEARCH
1482 static int mycmp(Byte * s1, Byte * s2, int len)
1483 {
1484         int i;
1485
1486         i = strncmp((char *) s1, (char *) s2, len);
1487 #ifdef CONFIG_FEATURE_VI_SETOPTS
1488         if (ignorecase) {
1489                 i = strncasecmp((char *) s1, (char *) s2, len);
1490         }
1491 #endif                                                  /* CONFIG_FEATURE_VI_SETOPTS */
1492         return (i);
1493 }
1494
1495 static Byte *char_search(Byte * p, Byte * pat, int dir, int range)      // search for pattern starting at p
1496 {
1497 #ifndef REGEX_SEARCH
1498         Byte *start, *stop;
1499         int len;
1500
1501         len = strlen((char *) pat);
1502         if (dir == FORWARD) {
1503                 stop = end - 1; // assume range is p - end-1
1504                 if (range == LIMITED)
1505                         stop = next_line(p);    // range is to next line
1506                 for (start = p; start < stop; start++) {
1507                         if (mycmp(start, pat, len) == 0) {
1508                                 return (start);
1509                         }
1510                 }
1511         } else if (dir == BACK) {
1512                 stop = text;    // assume range is text - p
1513                 if (range == LIMITED)
1514                         stop = prev_line(p);    // range is to prev line
1515                 for (start = p - len; start >= stop; start--) {
1516                         if (mycmp(start, pat, len) == 0) {
1517                                 return (start);
1518                         }
1519                 }
1520         }
1521         // pattern not found
1522         return (NULL);
1523 #else                                                   /*REGEX_SEARCH */
1524         char *q;
1525         struct re_pattern_buffer preg;
1526         int i;
1527         int size, range;
1528
1529         re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1530         preg.translate = 0;
1531         preg.fastmap = 0;
1532         preg.buffer = 0;
1533         preg.allocated = 0;
1534
1535         // assume a LIMITED forward search
1536         q = next_line(p);
1537         q = end_line(q);
1538         q = end - 1;
1539         if (dir == BACK) {
1540                 q = prev_line(p);
1541                 q = text;
1542         }
1543         // count the number of chars to search over, forward or backward
1544         size = q - p;
1545         if (size < 0)
1546                 size = p - q;
1547         // RANGE could be negative if we are searching backwards
1548         range = q - p;
1549
1550         q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
1551         if (q != 0) {
1552                 // The pattern was not compiled
1553                 psbs("bad search pattern: \"%s\": %s", pat, q);
1554                 i = 0;                  // return p if pattern not compiled
1555                 goto cs1;
1556         }
1557
1558         q = p;
1559         if (range < 0) {
1560                 q = p - size;
1561                 if (q < text)
1562                         q = text;
1563         }
1564         // search for the compiled pattern, preg, in p[]
1565         // range < 0-  search backward
1566         // range > 0-  search forward
1567         // 0 < start < size
1568         // re_search() < 0  not found or error
1569         // re_search() > 0  index of found pattern
1570         //            struct pattern    char     int    int    int     struct reg
1571         // re_search (*pattern_buffer,  *string, size,  start, range,  *regs)
1572         i = re_search(&preg, q, size, 0, range, 0);
1573         if (i == -1) {
1574                 p = 0;
1575                 i = 0;                  // return NULL if pattern not found
1576         }
1577   cs1:
1578         if (dir == FORWARD) {
1579                 p = p + i;
1580         } else {
1581                 p = p - i;
1582         }
1583         return (p);
1584 #endif                                                  /*REGEX_SEARCH */
1585 }
1586 #endif                                                  /* CONFIG_FEATURE_VI_SEARCH */
1587
1588 static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
1589 {
1590         if (c == 22) {          // Is this an ctrl-V?
1591                 p = stupid_insert(p, '^');      // use ^ to indicate literal next
1592                 p--;                    // backup onto ^
1593                 refresh(FALSE); // show the ^
1594                 c = get_one_char();
1595                 *p = c;
1596                 p++;
1597                 file_modified++;        // has the file been modified
1598         } else if (c == 27) {   // Is this an ESC?
1599                 cmd_mode = 0;
1600                 cmdcnt = 0;
1601                 end_cmd_q();    // stop adding to q
1602                 last_status_cksum = 0;  // force status update
1603                 if ((p[-1] != '\n') && (dot>text)) {
1604                         p--;
1605                 }
1606         } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1607                 //     123456789
1608                 if ((p[-1] != '\n') && (dot>text)) {
1609                         p--;
1610                         p = text_hole_delete(p, p);     // shrink buffer 1 char
1611                 }
1612         } else {
1613                 // insert a char into text[]
1614                 Byte *sp;               // "save p"
1615
1616                 if (c == 13)
1617                         c = '\n';       // translate \r to \n
1618                 sp = p;                 // remember addr of insert
1619                 p = stupid_insert(p, c);        // insert the char
1620 #ifdef CONFIG_FEATURE_VI_SETOPTS
1621                 if (showmatch && strchr(")]}", *sp) != NULL) {
1622                         showmatching(sp);
1623                 }
1624                 if (autoindent && c == '\n') {  // auto indent the new line
1625                         Byte *q;
1626
1627                         q = prev_line(p);       // use prev line as templet
1628                         for (; isblnk(*q); q++) {
1629                                 p = stupid_insert(p, *q);       // insert the char
1630                         }
1631                 }
1632 #endif                                                  /* CONFIG_FEATURE_VI_SETOPTS */
1633         }
1634         return (p);
1635 }
1636
1637 static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
1638 {
1639         p = text_hole_make(p, 1);
1640         if (p != 0) {
1641                 *p = c;
1642                 file_modified++;        // has the file been modified
1643                 p++;
1644         }
1645         return (p);
1646 }
1647
1648 static Byte find_range(Byte ** start, Byte ** stop, Byte c)
1649 {
1650         Byte *save_dot, *p, *q;
1651         int cnt;
1652
1653         save_dot = dot;
1654         p = q = dot;
1655
1656         if (strchr("cdy><", c)) {
1657                 // these cmds operate on whole lines
1658                 p = q = begin_line(p);
1659                 for (cnt = 1; cnt < cmdcnt; cnt++) {
1660                         q = next_line(q);
1661                 }
1662                 q = end_line(q);
1663         } else if (strchr("^%$0bBeEft", c)) {
1664                 // These cmds operate on char positions
1665                 do_cmd(c);              // execute movement cmd
1666                 q = dot;
1667         } else if (strchr("wW", c)) {
1668                 do_cmd(c);              // execute movement cmd
1669                 // if we are at the next word's first char
1670                 // step back one char
1671                 // but check the possibilities when it is true
1672                 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1673                                 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1674                                 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1675                         dot--;          // move back off of next word
1676                 if (dot > text && *dot == '\n')
1677                         dot--;          // stay off NL
1678                 q = dot;
1679         } else if (strchr("H-k{", c)) {
1680                 // these operate on multi-lines backwards
1681                 q = end_line(dot);      // find NL
1682                 do_cmd(c);              // execute movement cmd
1683                 dot_begin();
1684                 p = dot;
1685         } else if (strchr("L+j}\r\n", c)) {
1686                 // these operate on multi-lines forwards
1687                 p = begin_line(dot);
1688                 do_cmd(c);              // execute movement cmd
1689                 dot_end();              // find NL
1690                 q = dot;
1691         } else {
1692                 c = 27;                 // error- return an ESC char
1693                 //break;
1694         }
1695         *start = p;
1696         *stop = q;
1697         if (q < p) {
1698                 *start = q;
1699                 *stop = p;
1700         }
1701         dot = save_dot;
1702         return (c);
1703 }
1704
1705 static int st_test(Byte * p, int type, int dir, Byte * tested)
1706 {
1707         Byte c, c0, ci;
1708         int test, inc;
1709
1710         inc = dir;
1711         c = c0 = p[0];
1712         ci = p[inc];
1713         test = 0;
1714
1715         if (type == S_BEFORE_WS) {
1716                 c = ci;
1717                 test = ((!isspace(c)) || c == '\n');
1718         }
1719         if (type == S_TO_WS) {
1720                 c = c0;
1721                 test = ((!isspace(c)) || c == '\n');
1722         }
1723         if (type == S_OVER_WS) {
1724                 c = c0;
1725                 test = ((isspace(c)));
1726         }
1727         if (type == S_END_PUNCT) {
1728                 c = ci;
1729                 test = ((ispunct(c)));
1730         }
1731         if (type == S_END_ALNUM) {
1732                 c = ci;
1733                 test = ((isalnum(c)) || c == '_');
1734         }
1735         *tested = c;
1736         return (test);
1737 }
1738
1739 static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
1740 {
1741         Byte c;
1742
1743         while (st_test(p, type, dir, &c)) {
1744                 // make sure we limit search to correct number of lines
1745                 if (c == '\n' && --linecnt < 1)
1746                         break;
1747                 if (dir >= 0 && p >= end - 1)
1748                         break;
1749                 if (dir < 0 && p <= text)
1750                         break;
1751                 p += dir;               // move to next char
1752         }
1753         return (p);
1754 }
1755
1756 // find matching char of pair  ()  []  {}
1757 static Byte *find_pair(Byte * p, Byte c)
1758 {
1759         Byte match, *q;
1760         int dir, level;
1761
1762         match = ')';
1763         level = 1;
1764         dir = 1;                        // assume forward
1765         switch (c) {
1766         case '(':
1767                 match = ')';
1768                 break;
1769         case '[':
1770                 match = ']';
1771                 break;
1772         case '{':
1773                 match = '}';
1774                 break;
1775         case ')':
1776                 match = '(';
1777                 dir = -1;
1778                 break;
1779         case ']':
1780                 match = '[';
1781                 dir = -1;
1782                 break;
1783         case '}':
1784                 match = '{';
1785                 dir = -1;
1786                 break;
1787         }
1788         for (q = p + dir; text <= q && q < end; q += dir) {
1789                 // look for match, count levels of pairs  (( ))
1790                 if (*q == c)
1791                         level++;        // increase pair levels
1792                 if (*q == match)
1793                         level--;        // reduce pair level
1794                 if (level == 0)
1795                         break;          // found matching pair
1796         }
1797         if (level != 0)
1798                 q = NULL;               // indicate no match
1799         return (q);
1800 }
1801
1802 #ifdef CONFIG_FEATURE_VI_SETOPTS
1803 // show the matching char of a pair,  ()  []  {}
1804 static void showmatching(Byte * p)
1805 {
1806         Byte *q, *save_dot;
1807
1808         // we found half of a pair
1809         q = find_pair(p, *p);   // get loc of matching char
1810         if (q == NULL) {
1811                 indicate_error('3');    // no matching char
1812         } else {
1813                 // "q" now points to matching pair
1814                 save_dot = dot; // remember where we are
1815                 dot = q;                // go to new loc
1816                 refresh(FALSE); // let the user see it
1817                 (void) mysleep(40);     // give user some time
1818                 dot = save_dot; // go back to old loc
1819                 refresh(FALSE);
1820         }
1821 }
1822 #endif                                                  /* CONFIG_FEATURE_VI_SETOPTS */
1823
1824 //  open a hole in text[]
1825 static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
1826 {
1827         Byte *src, *dest;
1828         int cnt;
1829
1830         if (size <= 0)
1831                 goto thm0;
1832         src = p;
1833         dest = p + size;
1834         cnt = end - src;        // the rest of buffer
1835         if (memmove(dest, src, cnt) != dest) {
1836                 psbs("can't create room for new characters");
1837         }
1838         memset(p, ' ', size);   // clear new hole
1839         end = end + size;       // adjust the new END
1840         file_modified++;        // has the file been modified
1841   thm0:
1842         return (p);
1843 }
1844
1845 //  close a hole in text[]
1846 static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
1847 {
1848         Byte *src, *dest;
1849         int cnt, hole_size;
1850
1851         // move forwards, from beginning
1852         // assume p <= q
1853         src = q + 1;
1854         dest = p;
1855         if (q < p) {            // they are backward- swap them
1856                 src = p + 1;
1857                 dest = q;
1858         }
1859         hole_size = q - p + 1;
1860         cnt = end - src;
1861         if (src < text || src > end)
1862                 goto thd0;
1863         if (dest < text || dest >= end)
1864                 goto thd0;
1865         if (src >= end)
1866                 goto thd_atend; // just delete the end of the buffer
1867         if (memmove(dest, src, cnt) != dest) {
1868                 psbs("can't delete the character");
1869         }
1870   thd_atend:
1871         end = end - hole_size;  // adjust the new END
1872         if (dest >= end)
1873                 dest = end - 1; // make sure dest in below end-1
1874         if (end <= text)
1875                 dest = end = text;      // keep pointers valid
1876         file_modified++;        // has the file been modified
1877   thd0:
1878         return (dest);
1879 }
1880
1881 // copy text into register, then delete text.
1882 // if dist <= 0, do not include, or go past, a NewLine
1883 //
1884 static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
1885 {
1886         Byte *p;
1887
1888         // make sure start <= stop
1889         if (start > stop) {
1890                 // they are backwards, reverse them
1891                 p = start;
1892                 start = stop;
1893                 stop = p;
1894         }
1895         if (dist <= 0) {
1896                 // we can not cross NL boundaries
1897                 p = start;
1898                 if (*p == '\n')
1899                         return (p);
1900                 // dont go past a NewLine
1901                 for (; p + 1 <= stop; p++) {
1902                         if (p[1] == '\n') {
1903                                 stop = p;       // "stop" just before NewLine
1904                                 break;
1905                         }
1906                 }
1907         }
1908         p = start;
1909 #ifdef CONFIG_FEATURE_VI_YANKMARK
1910         text_yank(start, stop, YDreg);
1911 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
1912         if (yf == YANKDEL) {
1913                 p = text_hole_delete(start, stop);
1914         }                                       // delete lines
1915         return (p);
1916 }
1917
1918 static void show_help(void)
1919 {
1920         puts("These features are available:"
1921 #ifdef CONFIG_FEATURE_VI_SEARCH
1922         "\n\tPattern searches with / and ?"
1923 #endif                                                  /* CONFIG_FEATURE_VI_SEARCH */
1924 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1925         "\n\tLast command repeat with \'.\'"
1926 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD */
1927 #ifdef CONFIG_FEATURE_VI_YANKMARK
1928         "\n\tLine marking with  'x"
1929         "\n\tNamed buffers with  \"x"
1930 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
1931 #ifdef CONFIG_FEATURE_VI_READONLY
1932         "\n\tReadonly if vi is called as \"view\""
1933         "\n\tReadonly with -R command line arg"
1934 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
1935 #ifdef CONFIG_FEATURE_VI_SET
1936         "\n\tSome colon mode commands with \':\'"
1937 #endif                                                  /* CONFIG_FEATURE_VI_SET */
1938 #ifdef CONFIG_FEATURE_VI_SETOPTS
1939         "\n\tSettable options with \":set\""
1940 #endif                                                  /* CONFIG_FEATURE_VI_SETOPTS */
1941 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
1942         "\n\tSignal catching- ^C"
1943         "\n\tJob suspend and resume with ^Z"
1944 #endif                                                  /* CONFIG_FEATURE_VI_USE_SIGNALS */
1945 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
1946         "\n\tAdapt to window re-sizes"
1947 #endif                                                  /* CONFIG_FEATURE_VI_WIN_RESIZE */
1948         );
1949 }
1950
1951 static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
1952 {
1953         Byte c, b[2];
1954
1955         b[1] = '\0';
1956         strcpy((char *) buf, "");       // init buf
1957         if (strlen((char *) s) <= 0)
1958                 s = (Byte *) "(NULL)";
1959         for (; *s > '\0'; s++) {
1960                 int c_is_no_print;
1961
1962                 c = *s;
1963                 c_is_no_print = c > 127 && !Isprint(c);
1964                 if (c_is_no_print) {
1965                         strcat((char *) buf, SOn);
1966                         c = '.';
1967                 }
1968                 if (c < ' ' || c == 127) {
1969                         strcat((char *) buf, "^");
1970                         if(c == 127)
1971                                 c = '?';
1972                          else
1973                         c += '@';
1974                 }
1975                 b[0] = c;
1976                 strcat((char *) buf, (char *) b);
1977                 if (c_is_no_print)
1978                         strcat((char *) buf, SOs);
1979                 if (*s == '\n') {
1980                         strcat((char *) buf, "$");
1981                 }
1982         }
1983 }
1984
1985 #ifdef CONFIG_FEATURE_VI_DOT_CMD
1986 static void start_new_cmd_q(Byte c)
1987 {
1988         // release old cmd
1989         free(last_modifying_cmd);
1990         // get buffer for new cmd
1991         last_modifying_cmd = (Byte *) xmalloc(BUFSIZ);
1992         memset(last_modifying_cmd, '\0', BUFSIZ);       // clear new cmd queue
1993         // if there is a current cmd count put it in the buffer first
1994         if (cmdcnt > 0)
1995                 sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c);
1996         else // just save char c onto queue
1997                 last_modifying_cmd[0] = c;
1998         adding2q = 1;
1999 }
2000
2001 static void end_cmd_q(void)
2002 {
2003 #ifdef CONFIG_FEATURE_VI_YANKMARK
2004         YDreg = 26;                     // go back to default Yank/Delete reg
2005 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
2006         adding2q = 0;
2007         return;
2008 }
2009 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD */
2010
2011 #if defined(CONFIG_FEATURE_VI_YANKMARK) || defined(CONFIG_FEATURE_VI_SEARCH) || defined(CONFIG_FEATURE_VI_CRASHME)
2012 static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2013 {
2014         int cnt, i;
2015
2016         i = strlen((char *) s);
2017         p = text_hole_make(p, i);
2018         strncpy((char *) p, (char *) s, i);
2019         for (cnt = 0; *s != '\0'; s++) {
2020                 if (*s == '\n')
2021                         cnt++;
2022         }
2023 #ifdef CONFIG_FEATURE_VI_YANKMARK
2024         psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2025 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
2026         return (p);
2027 }
2028 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */
2029
2030 #ifdef CONFIG_FEATURE_VI_YANKMARK
2031 static Byte *text_yank(Byte * p, Byte * q, int dest)    // copy text into a register
2032 {
2033         Byte *t;
2034         int cnt;
2035
2036         if (q < p) {            // they are backwards- reverse them
2037                 t = q;
2038                 q = p;
2039                 p = t;
2040         }
2041         cnt = q - p + 1;
2042         t = reg[dest];
2043         free(t);                //  if already a yank register, free it
2044         t = (Byte *) xmalloc(cnt + 1);  // get a new register
2045         memset(t, '\0', cnt + 1);       // clear new text[]
2046         strncpy((char *) t, (char *) p, cnt);   // copy text[] into bufer
2047         reg[dest] = t;
2048         return (p);
2049 }
2050
2051 static Byte what_reg(void)
2052 {
2053         Byte c;
2054         int i;
2055
2056         i = 0;
2057         c = 'D';                        // default to D-reg
2058         if (0 <= YDreg && YDreg <= 25)
2059                 c = 'a' + (Byte) YDreg;
2060         if (YDreg == 26)
2061                 c = 'D';
2062         if (YDreg == 27)
2063                 c = 'U';
2064         return (c);
2065 }
2066
2067 static void check_context(Byte cmd)
2068 {
2069         // A context is defined to be "modifying text"
2070         // Any modifying command establishes a new context.
2071
2072         if (dot < context_start || dot > context_end) {
2073                 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2074                         // we are trying to modify text[]- make this the current context
2075                         mark[27] = mark[26];    // move cur to prev
2076                         mark[26] = dot; // move local to cur
2077                         context_start = prev_line(prev_line(dot));
2078                         context_end = next_line(next_line(dot));
2079                         //loiter= start_loiter= now;
2080                 }
2081         }
2082         return;
2083 }
2084
2085 static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2086 {
2087         Byte *tmp;
2088
2089         // the current context is in mark[26]
2090         // the previous context is in mark[27]
2091         // only swap context if other context is valid
2092         if (text <= mark[27] && mark[27] <= end - 1) {
2093                 tmp = mark[27];
2094                 mark[27] = mark[26];
2095                 mark[26] = tmp;
2096                 p = mark[26];   // where we are going- previous context
2097                 context_start = prev_line(prev_line(prev_line(p)));
2098                 context_end = next_line(next_line(next_line(p)));
2099         }
2100         return (p);
2101 }
2102 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
2103
2104 static int isblnk(Byte c) // is the char a blank or tab
2105 {
2106         return (c == ' ' || c == '\t');
2107 }
2108
2109 //----- Set terminal attributes --------------------------------
2110 static void rawmode(void)
2111 {
2112         tcgetattr(0, &term_orig);
2113         term_vi = term_orig;
2114         term_vi.c_lflag &= (~ICANON & ~ECHO);   // leave ISIG ON- allow intr's
2115         term_vi.c_iflag &= (~IXON & ~ICRNL);
2116         term_vi.c_oflag &= (~ONLCR);
2117         term_vi.c_cc[VMIN] = 1;
2118         term_vi.c_cc[VTIME] = 0;
2119         erase_char = term_vi.c_cc[VERASE];
2120         tcsetattr(0, TCSANOW, &term_vi);
2121 }
2122
2123 static void cookmode(void)
2124 {
2125         fflush(stdout);
2126         tcsetattr(0, TCSANOW, &term_orig);
2127 }
2128
2129 //----- Come here when we get a window resize signal ---------
2130 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
2131 static void winch_sig(int sig ATTRIBUTE_UNUSED)
2132 {
2133         signal(SIGWINCH, winch_sig);
2134 #ifdef CONFIG_FEATURE_VI_WIN_RESIZE
2135         window_size_get(0);
2136 #endif                                                  /* CONFIG_FEATURE_VI_WIN_RESIZE */
2137         new_screen(rows, columns);      // get memory for virtual screen
2138         redraw(TRUE);           // re-draw the screen
2139 }
2140
2141 //----- Come here when we get a continue signal -------------------
2142 static void cont_sig(int sig ATTRIBUTE_UNUSED)
2143 {
2144         rawmode();                      // terminal to "raw"
2145         last_status_cksum = 0;  // force status update
2146         redraw(TRUE);           // re-draw the screen
2147
2148         signal(SIGTSTP, suspend_sig);
2149         signal(SIGCONT, SIG_DFL);
2150         kill(my_pid, SIGCONT);
2151 }
2152
2153 //----- Come here when we get a Suspend signal -------------------
2154 static void suspend_sig(int sig ATTRIBUTE_UNUSED)
2155 {
2156         place_cursor(rows - 1, 0, FALSE);       // go to bottom of screen
2157         clear_to_eol();         // Erase to end of line
2158         cookmode();                     // terminal to "cooked"
2159
2160         signal(SIGCONT, cont_sig);
2161         signal(SIGTSTP, SIG_DFL);
2162         kill(my_pid, SIGTSTP);
2163 }
2164
2165 //----- Come here when we get a signal ---------------------------
2166 static void catch_sig(int sig)
2167 {
2168         signal(SIGHUP, catch_sig);
2169         signal(SIGINT, catch_sig);
2170         signal(SIGTERM, catch_sig);
2171         signal(SIGALRM, catch_sig);
2172         if(sig)
2173                 longjmp(restart, sig);
2174 }
2175
2176 //----- Come here when we get a core dump signal -----------------
2177 static void core_sig(int sig)
2178 {
2179         signal(SIGQUIT, core_sig);
2180         signal(SIGILL, core_sig);
2181         signal(SIGTRAP, core_sig);
2182         signal(SIGIOT, 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 */