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