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