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

Last change on this file since 1770 was 1770, checked in by Bruno Cornec, 12 years ago
  • Better output for mindi-busybox revision
  • Remove dummy file created on NFS - report from Arnaud Tiger <arnaud.tiger_at_hp.com>
  • strace useful for debug
  • fix new versions for pb (2.0.0 for mindi and 1.7.2 for mindi-busybox)
  • fix build process for mindi-busybox + options used in that version (dd for label-partitions-as-necessary)
  • fix typo in label-partitions-as-necessary which doesn't seem to work
  • Update to busybox 1.7.2
  • perl is now required at restore time to support uuid swap partitions (and will be used for many other thigs

in the future for sure)

  • next mindi version will be 2.0.0 due to all the changes made in it (udev may break working distros)
  • small optimization in mindi on keyboard handling (one single find instead of multiple)
  • better interaction for USB device when launching mindi manually
  • attempt to automatically guess block disk size for ramdisk
  • fix typos in bkphw
  • Fix the remaining problem with UUID support for swap partitions
  • Updates mondoarchive man page for USB support
  • Adds preliminary Hardware support to mindi (Proliant SSSTK)
  • Tries to add udev support also for rhel4
  • Fix UUID support which was still broken.
  • Be conservative in test for the start-nfs script
  • Update config file for mindi-busybox for 1.7.2 migration
  • Try to run around a busybox bug (1.2.2 pb on inexistant links)
  • Add build content for mindi-busybox in pb
  • Remove distributions content for mindi-busybox
  • Fix a warning on inexistant raidtab
  • Solve problem on tmpfs in restore init (Problem of inexistant symlink and busybox)
  • Create MONDO_CACHE and use it everywhere + creation at start
  • Really never try to eject a USB device
  • Fix a issue with &> usage (replaced with 1> and 2>)
  • Adds magic file to depllist in order to have file working + ldd which helps for debugging issues
  • tty modes correct to avoid sh error messages
  • Use ext3 normally and not ext2 instead
  • USB device should be corrected after reading (take 1st part)
  • Adds a mount_USB_here function derived from mount_CDROM_here
  • usb detection place before /dev detection in device name at restore time
  • Fix when restoring from USB: media is asked in interactive mode
  • Adds USB support for mondorestore
  • mount_cdrom => mount_media
  • elilo.efi is now searched throughout /boot/efi and not in a fixed place as there is no standard
  • untar-and-softlink => untar (+ interface change)
  • suppress useless softlinks creation/removal in boot process
  • avoids udevd messages on groups
  • Increase # of disks to 99 as in mindi at restore time (should be a conf file parameter)
  • skip existing big file creation
  • seems to work correctly for USB mindi boot
  • Adds group and tty link to udev conf
  • Always load usb-torage (even 2.6) to initiate USB bus discovery
  • Better printing of messages
  • Attempt to fix a bug in supporting OpenSusE 10.3 kernel for initramfs (mindi may now use multiple regex for kernel initrd detection)
  • Links were not correctly done as non relative for modules in mindi
  • exclusion of modules denied now works
  • Also create modules in their ordinary place, so that classical modprobe works + copy modules.dep
  • Fix bugs for DENY_MODS handling
  • Add device /dev/console for udev
  • ide-generic should now really be excluded
  • Fix a bug in major number for tty
  • If udev then adds modprobe/insmod to rootfs
  • tty0 is also cretaed with udev
  • ide-generic put rather in DENY_MODS
  • udevd remove from deplist s handled in mindi directly
  • better default for mindi when using --usb
  • Handles dynamically linked busybox (in case we want to use it soon ;-)
  • Adds fixed devices to create for udev
  • ide-generic should not be part of the initrd when using libata v2
  • support a dynamically linked udev (case on Ubuntu 7.10 and Mandriva 2008.0 so should be quite generic) This will give incitation to move to dyn. linked binaries in the initrd which will help for other tasks (ia6 4)
  • Improvement in udev support (do not use cl options not available in busybox)
  • Udev in mindi
    • auto creation of the right links at boot time with udev-links.conf(from Mandriva 2008.0)
    • rework startup of udev as current makes kernel crash (from Mandriva 2008.0)
    • add support for 64 bits udev
  • Try to render MyInsmod? silent at boot time
  • Adds udev support (mandatory for newest distributions to avoid remapping of devices in a different way as on the original system)
  • We also need vaft format support for USB boot
  • Adds libusual support (Ubuntu 7.10 needs it for USB)
  • Improve Ubuntu/Debian? keyboard detection and support
  • pbinit adapted to new pb (0.8.10). Filtering of docs done in it
  • Suppress some mondo warnings and errors on USB again
  • Tries to fix lack of files in deb mindi package
  • Verify should now work for USB devices
  • More log/mesages improvement for USB support
  • - Supress g_erase_tmpdir_and_scratchdir
  • Improve some log messages for USB support
  • Try to improve install in mindi to avoid issues with isolinux.cfg not installed vene if in the pkg :-(
  • Improve mindi-busybox build
  • In conformity with pb 0.8.9
  • Add support for Ubuntu 7.10 in build process
  • Add USB Key button to Menu UI (CD streamer removed)
  • Attempt to fix error messages on tmp/scratch files at the end by removing those dir at the latest possible.
  • Fix a bug linked to the size of the -E param which could be used (Arnaud Tiger/René? Ribaud).
  • Integrate ~/.pbrc content into mondorescue.pb (required project-builder >= 0.8.7)
  • Put mondorescue in conformity with new pb filtering rules
  • Add USB support at restore time (no test done yet). New start-usb script PB varibale added where useful
  • Unmounting USB device before removal of temporary scratchdir
  • Stil refining USB copy back to mondo (one command was not executed)
  • No need to have the image subdor in the csratchdir when USB.
  • umount the USB partition before attempting to use it
  • Remove useless copy from mindi to mondo at end of USB handling

(risky merge, we are raising the limits of 2 diverging branches. The status of stable is not completely sure as such. Will need lots of tests, but it's not yet done :-()
(merge -r1692:1769 $SVN_M/branches/2.2.5)

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