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