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