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