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