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