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