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