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