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