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