source: branches/3.2/mindi-busybox/editors/vi.c @ 3232

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