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