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