source: MondoRescue/branches/stable/mindi-busybox/editors/vi.c @ 821

Last change on this file since 821 was 821, checked in by Bruno Cornec, 14 years ago

Addition of busybox 1.2.1 as a mindi-busybox new package
This should avoid delivering binary files in mindi not built there (Fedora and Debian are quite serious about that)

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