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