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