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