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