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