Magellan Linux

Annotation of /trunk/mkinitrd-magellan/busybox/editors/vi.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 532 - (hide annotations) (download)
Sat Sep 1 22:45:15 2007 UTC (16 years, 9 months ago) by niro
File MIME type: text/plain
File size: 108803 byte(s)
-import if magellan mkinitrd; it is a fork of redhats mkinitrd-5.0.8 with all magellan patches and features; deprecates magellan-src/mkinitrd

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