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