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