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