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