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