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