fix bug #474:
[people/mcb30/busybox.git] / editors / vi.c
1 /* vi: set sw=8 ts=8: */
2 /*
3  * tiny vi.c: A small 'vi' clone
4  * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20
21 static const char vi_Version[] =
22         "$Id: vi.c,v 1.38 2004/08/19 19:15:06 andersen Exp $";
23
24 /*
25  * To compile for standalone use:
26  *      gcc -Wall -Os -s -DSTANDALONE -o vi vi.c
27  *        or
28  *      gcc -Wall -Os -s -DSTANDALONE -DCONFIG_FEATURE_VI_CRASHME -o vi vi.c            # include testing features
29  *      strip vi
30  */
31
32 /*
33  * Things To Do:
34  *      EXINIT
35  *      $HOME/.exrc  and  ./.exrc
36  *      add magic to search     /foo.*bar
37  *      add :help command
38  *      :map macros
39  *      how about mode lines:   vi: set sw=8 ts=8:
40  *      if mark[] values were line numbers rather than pointers
41  *         it would be easier to change the mark when add/delete lines
42  *      More intelligence in refresh()
43  *      ":r !cmd"  and  "!cmd"  to filter text through an external command
44  *      A true "undo" facility
45  *      An "ex" line oriented mode- maybe using "cmdedit"
46  */
47
48 //----  Feature --------------  Bytes to implement
49 #ifdef STANDALONE
50 #define vi_main                 main
51 #define CONFIG_FEATURE_VI_COLON // 4288
52 #define CONFIG_FEATURE_VI_YANKMARK      // 1408
53 #define CONFIG_FEATURE_VI_SEARCH        // 1088
54 #define CONFIG_FEATURE_VI_USE_SIGNALS   // 1056
55 #define CONFIG_FEATURE_VI_DOT_CMD       //  576
56 #define CONFIG_FEATURE_VI_READONLY      //  128
57 #define CONFIG_FEATURE_VI_SETOPTS       //  576
58 #define CONFIG_FEATURE_VI_SET   //  224
59 #define CONFIG_FEATURE_VI_WIN_RESIZE    //  256  WIN_RESIZE
60 // To test editor using CRASHME:
61 //    vi -C filename
62 // To stop testing, wait until all to text[] is deleted, or
63 //    Ctrl-Z and kill -9 %1
64 // while in the editor Ctrl-T will toggle the crashme function on and off.
65 //#define CONFIG_FEATURE_VI_CRASHME             // randomly pick commands to execute
66 #endif                                                  /* STANDALONE */
67
68 #include <stdio.h>
69 #include <stdlib.h>
70 #include <string.h>
71 #include <termios.h>
72 #include <unistd.h>
73 #include <sys/ioctl.h>
74 #include <sys/time.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <time.h>
78 #include <fcntl.h>
79 #include <signal.h>
80 #include <setjmp.h>
81 #include <regex.h>
82 #include <ctype.h>
83 #include <assert.h>
84 #include <errno.h>
85 #include <stdarg.h>
86 #ifndef STANDALONE
87 #include "busybox.h"
88 #endif                                                  /* STANDALONE */
89
90 #ifdef CONFIG_LOCALE_SUPPORT
91 #define Isprint(c) isprint((c))
92 #else
93 #define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') )
94 #endif
95
96 #ifndef TRUE
97 #define TRUE                    ((int)1)
98 #define FALSE                   ((int)0)
99 #endif                                                  /* TRUE */
100 #define MAX_SCR_COLS            BUFSIZ
101
102 // Misc. non-Ascii keys that report an escape sequence
103 #define VI_K_UP                 128     // cursor key Up
104 #define VI_K_DOWN               129     // cursor key Down
105 #define VI_K_RIGHT              130     // Cursor Key Right
106 #define VI_K_LEFT               131     // cursor key Left
107 #define VI_K_HOME               132     // Cursor Key Home
108 #define VI_K_END                133     // Cursor Key End
109 #define VI_K_INSERT             134     // Cursor Key Insert
110 #define VI_K_PAGEUP             135     // Cursor Key Page Up
111 #define VI_K_PAGEDOWN           136     // Cursor Key Page Down
112 #define VI_K_FUN1               137     // Function Key F1
113 #define VI_K_FUN2               138     // Function Key F2
114 #define VI_K_FUN3               139     // Function Key F3
115 #define VI_K_FUN4               140     // Function Key F4
116 #define VI_K_FUN5               141     // Function Key F5
117 #define VI_K_FUN6               142     // Function Key F6
118 #define VI_K_FUN7               143     // Function Key F7
119 #define VI_K_FUN8               144     // Function Key F8
120 #define VI_K_FUN9               145     // Function Key F9
121 #define VI_K_FUN10              146     // Function Key F10
122 #define VI_K_FUN11              147     // Function Key F11
123 #define VI_K_FUN12              148     // Function Key F12
124
125 /* vt102 typical ESC sequence */
126 /* terminal standout start/normal ESC sequence */
127 static const char SOs[] = "\033[7m";
128 static const char SOn[] = "\033[0m";
129 /* terminal bell sequence */
130 static const char bell[] = "\007";
131 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
132 static const char Ceol[] = "\033[0K";
133 static const char Ceos [] = "\033[0J";
134 /* Cursor motion arbitrary destination ESC sequence */
135 static const char CMrc[] = "\033[%d;%dH";
136 /* Cursor motion up and down ESC sequence */
137 static const char CMup[] = "\033[A";
138 static const char CMdown[] = "\n";
139
140
141 static const int YANKONLY = FALSE;
142 static const int YANKDEL = TRUE;
143 static const int FORWARD = 1;   // code depends on "1"  for array index
144 static const int BACK = -1;     // code depends on "-1" for array index
145 static const int LIMITED = 0;   // how much of text[] in char_search
146 static const int FULL = 1;      // how much of text[] in char_search
147
148 static const int S_BEFORE_WS = 1;       // used in skip_thing() for moving "dot"
149 static const int S_TO_WS = 2;           // used in skip_thing() for moving "dot"
150 static const int S_OVER_WS = 3;         // used in skip_thing() for moving "dot"
151 static const int S_END_PUNCT = 4;       // used in skip_thing() for moving "dot"
152 static const int S_END_ALNUM = 5;       // used in skip_thing() for moving "dot"
153
154 typedef unsigned char Byte;
155
156 static int vi_setops;
157 #define VI_AUTOINDENT 1
158 #define VI_SHOWMATCH  2
159 #define VI_IGNORECASE 4
160 #define VI_ERR_METHOD 8
161 #define autoindent (vi_setops & VI_AUTOINDENT)
162 #define showmatch  (vi_setops & VI_SHOWMATCH )
163 #define ignorecase (vi_setops & VI_IGNORECASE)
164 /* indicate error with beep or flash */
165 #define err_method (vi_setops & VI_ERR_METHOD)
166
167
168 static int editing;             // >0 while we are editing a file
169 static int cmd_mode;            // 0=command  1=insert 2=replace
170 static int file_modified;       // buffer contents changed
171 static int last_file_modified = -1;
172 static int fn_start;            // index of first cmd line file name
173 static int save_argc;           // how many file names on cmd line
174 static int cmdcnt;              // repetition count
175 static fd_set rfds;             // use select() for small sleeps
176 static struct timeval tv;       // use select() for small sleeps
177 static int rows, columns;       // the terminal screen is this size
178 static int crow, ccol, offset;  // cursor is on Crow x Ccol with Horz Ofset
179 static Byte *status_buffer;     // mesages to the user
180 #define STATUS_BUFFER_LEN  200
181 static int have_status_msg;     // is default edit status needed?
182 static int last_status_cksum;   // hash of current status line
183 static Byte *cfn;               // previous, current, and next file name
184 static Byte *text, *end, *textend;      // pointers to the user data in memory
185 static Byte *screen;            // pointer to the virtual screen buffer
186 static int screensize;          //            and its size
187 static Byte *screenbegin;       // index into text[], of top line on the screen
188 static Byte *dot;               // where all the action takes place
189 static int tabstop;
190 static struct termios term_orig, term_vi;       // remember what the cooked mode was
191 static Byte erase_char;         // the users erase character
192 static Byte last_input_char;    // last char read from user
193 static Byte last_forward_char;  // last char searched for with 'f'
194
195 #ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR
196 static int last_row;            // where the cursor was last moved to
197 #endif                                                  /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */
198 #ifdef CONFIG_FEATURE_VI_USE_SIGNALS
199 static jmp_buf restart;         // catch_sig()
200 #endif                                                  /* CONFIG_FEATURE_VI_USE_SIGNALS */
201 #if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME)
202 static int my_pid;
203 #endif
204 #ifdef CONFIG_FEATURE_VI_DOT_CMD
205 static int adding2q;            // are we currently adding user input to q
206 static Byte *last_modifying_cmd;        // last modifying cmd for "."
207 static Byte *ioq, *ioq_start;   // pointer to string for get_one_char to "read"
208 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD */
209 #if     defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK)
210 static Byte *modifying_cmds;    // cmds that modify text[]
211 #endif                                                  /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */
212 #ifdef CONFIG_FEATURE_VI_READONLY
213 static int vi_readonly, readonly;
214 #endif                                                  /* CONFIG_FEATURE_VI_READONLY */
215 #ifdef CONFIG_FEATURE_VI_YANKMARK
216 static Byte *reg[28];           // named register a-z, "D", and "U" 0-25,26,27
217 static int YDreg, Ureg;         // default delete register and orig line for "U"
218 static Byte *mark[28];          // user marks points somewhere in text[]-  a-z and previous context ''
219 static Byte *context_start, *context_end;
220 #endif                                                  /* CONFIG_FEATURE_VI_YANKMARK */
221 #ifdef CONFIG_FEATURE_VI_SEARCH
222 static Byte *last_search_pattern;       // last pattern from a '/' or '?' search
223 #endif                                                  /* CONFIG_FEATURE_VI_SEARCH */
224
225
226 static void edit_file(Byte *);  // edit one file
227 static void do_cmd(Byte);       // execute a command
228 static void sync_cursor(Byte *, int *, int *);  // synchronize the screen cursor to dot
229 static Byte *begin_line(Byte *);        // return pointer to cur line B-o-l
230 static Byte *end_line(Byte *);  // return pointer to cur line E-o-l
231 static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
232 static Byte *next_line(Byte *); // return pointer to next line B-o-l
233 static Byte *end_screen(void);  // get pointer to last char on screen
234 static int count_lines(Byte *, Byte *); // count line from start to stop
235 static Byte *find_line(int);    // find begining of line #li
236 static Byte *move_to_col(Byte *, int);  // move "p" to column l
237 static int isblnk(Byte);        // is the char a blank or tab
238 static void dot_left(void);     // move dot left- dont leave line
239 static void dot_right(void);    // move dot right- dont leave line
240 static void dot_begin(void);    // move dot to B-o-l
241 static void dot_end(void);      // move dot to E-o-l
242 static void dot_next(void);     // move dot to next line B-o-l
243 static void dot_prev(void);     // move dot to prev line B-o-l
244 static void dot_scroll(int, int);       // move the screen up or down
245 static void dot_skip_over_ws(void);     // move dot pat WS
246 static void dot_delete(void);   // delete the char at 'dot'
247 static Byte *bound_dot(Byte *); // make sure  text[0] <= P < "end"
248 static Byte *new_screen(int, int);      // malloc virtual screen memory
249 static Byte *new_text(int);     // malloc memory for text[] buffer
250 static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
251 static Byte *stupid_insert(Byte *, Byte);       // stupidly insert the char c at 'p'
252 static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
253 static int st_test(Byte *, int, int, Byte *);   // helper for skip_thing()
254 static Byte *skip_thing(Byte *, int, int, int); // skip some object
255 static Byte *find_pair(Byte *, Byte);   // find matching pair ()  []  {}
256 static Byte *text_hole_delete(Byte *, Byte *);  // at "p", delete a 'size' byte hole
257 static Byte *text_hole_make(Byte *, int);       // at "p", make a 'size' byte hole
258 static Byte *yank_delete(Byte *, Byte *, int, int);     // yank text[] into register then delete
259 static void show_help(void);    // display some help info
260 static void rawmode(void);      // set "raw" mode on tty
261 static void cookmode(void);     // return to "cooked" mode on tty
262 static int mysleep(int);        // sleep for 'h' 1/100 seconds
263 static Byte readit(void);       // read (maybe cursor) key from stdin
264 static Byte get_one_char(void); // read 1 char from stdin
265 static int file_size(const Byte *);   // what is the byte size of "fn"
266 static int file_insert(Byte *, Byte *, int);
267 static int file_write(Byte *, Byte *, Byte *);
268 static void place_cursor(int, int, int);
269 static void screen_erase(void);
270 static void clear_to_eol(void);
271 static void clear_to_eos(void);
272 static void standout_start(void);       // send "start reverse video" sequence
273 static void standout_end(void); // send "end reverse video" sequence
274 static void flash(int);         // flash the terminal screen
275 static void show_status_line(void);     // put a message on the bottom line
276 static void psb(const char *, ...);     // Print Status Buf
277 static void psbs(const char *, ...);    // Print Status Buf in standout mode
278 static void ni(Byte *);         // display messages
279 static int format_edit_status(void);    // format file status on status line
280 static void redraw(int);        // force a full screen refresh
281 static void format_line(Byte*, Byte*, int);
282 static void refresh(int);       // update the terminal from screen[]
283
284 static void Indicate_Error(void);       // use flash or beep to indicate error
285 #define indicate_error(c) Indicate_Error()
286 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