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