51dfc120924b8f4826d3b340fad5b06e9b11d289
[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         IF_FEATURE_VI_SEARCH(char *pat;)
2255
2256         *addr = -1;                     // assume no addr
2257         if (*p == '.') {        // the current line
2258                 p++;
2259                 q = begin_line(dot);
2260                 *addr = count_lines(text, q);
2261         }
2262 #if ENABLE_FEATURE_VI_YANKMARK
2263         else if (*p == '\'') {  // is this a mark addr
2264                 p++;
2265                 c = tolower(*p);
2266                 p++;
2267                 if (c >= 'a' && c <= 'z') {
2268                         // we have a mark
2269                         c = c - 'a';
2270                         q = mark[(unsigned char) c];
2271                         if (q != NULL) {        // is mark valid
2272                                 *addr = count_lines(text, q);
2273                         }
2274                 }
2275         }
2276 #endif
2277 #if ENABLE_FEATURE_VI_SEARCH
2278         else if (*p == '/') {   // a search pattern
2279                 q = strchrnul(++p, '/');
2280                 pat = xstrndup(p, q - p); // save copy of pattern
2281                 p = q;
2282                 if (*p == '/')
2283                         p++;
2284                 q = char_search(dot, pat, (FORWARD << 1) | FULL);
2285                 if (q != NULL) {
2286                         *addr = count_lines(text, q);
2287                 }
2288                 free(pat);
2289         }
2290 #endif
2291         else if (*p == '$') {   // the last line in file
2292                 p++;
2293                 q = begin_line(end - 1);
2294                 *addr = count_lines(text, q);
2295         } else if (isdigit(*p)) {       // specific line number
2296                 sscanf(p, "%d%n", addr, &st);
2297                 p += st;
2298         } else {
2299                 // unrecognized address - assume -1
2300                 *addr = -1;
2301         }
2302         return p;
2303 }
2304
2305 static char *get_address(char *p, int *b, int *e)       // get two colon addrs, if present
2306 {
2307         //----- get the address' i.e., 1,3   'a,'b  -----
2308         // get FIRST addr, if present
2309         while (isblank(*p))
2310                 p++;                            // skip over leading spaces
2311         if (*p == '%') {                        // alias for 1,$
2312                 p++;
2313                 *b = 1;
2314                 *e = count_lines(text, end-1);
2315                 goto ga0;
2316         }
2317         p = get_one_address(p, b);
2318         while (isblank(*p))
2319                 p++;
2320         if (*p == ',') {                        // is there a address separator
2321                 p++;
2322                 while (isblank(*p))
2323                         p++;
2324                 // get SECOND addr, if present
2325                 p = get_one_address(p, e);
2326         }
2327  ga0:
2328         while (isblank(*p))
2329                 p++;                            // skip over trailing spaces
2330         return p;
2331 }
2332
2333 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
2334 static void setops(const char *args, const char *opname, int flg_no,
2335                         const char *short_opname, int opt)
2336 {
2337         const char *a = args + flg_no;
2338         int l = strlen(opname) - 1; // opname have + ' '
2339
2340         // maybe strncmp? we had tons of erroneous strncasecmp's...
2341         if (strncasecmp(a, opname, l) == 0
2342          || strncasecmp(a, short_opname, 2) == 0
2343         ) {
2344                 if (flg_no)
2345                         vi_setops &= ~opt;
2346                 else
2347                         vi_setops |= opt;
2348         }
2349 }
2350 #endif
2351
2352 #endif /* FEATURE_VI_COLON */
2353
2354 // buf must be no longer than MAX_INPUT_LEN!
2355 static void colon(char *buf)
2356 {
2357 #if !ENABLE_FEATURE_VI_COLON
2358         // Simple ":cmd" handler with minimal set of commands
2359         char *p = buf;
2360         int cnt;
2361
2362         if (*p == ':')
2363                 p++;
2364         cnt = strlen(p);
2365         if (cnt == 0)
2366                 return;
2367         if (strncmp(p, "quit", cnt) == 0
2368          || strncmp(p, "q!", cnt) == 0
2369         ) {
2370                 if (modified_count && p[1] != '!') {
2371                         status_line_bold("No write since last change (:%s! overrides)", p);
2372                 } else {
2373                         editing = 0;
2374                 }
2375                 return;
2376         }
2377         if (strncmp(p, "write", cnt) == 0
2378          || strncmp(p, "wq", cnt) == 0
2379          || strncmp(p, "wn", cnt) == 0
2380          || (p[0] == 'x' && !p[1])
2381         ) {
2382                 if (modified_count != 0 || p[0] != 'x') {
2383                         cnt = file_write(current_filename, text, end - 1);
2384                 }
2385                 if (cnt < 0) {
2386                         if (cnt == -1)
2387                                 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
2388                 } else {
2389                         modified_count = 0;
2390                         last_modified_count = -1;
2391                         status_line("'%s' %uL, %uC",
2392                                 current_filename,
2393                                 count_lines(text, end - 1), cnt
2394                         );
2395                         if (p[0] == 'x'
2396                          || p[1] == 'q' || p[1] == 'n'
2397                          || p[1] == 'Q' || p[1] == 'N'
2398                         ) {
2399                                 editing = 0;
2400                         }
2401                 }
2402                 return;
2403         }
2404         if (strncmp(p, "file", cnt) == 0) {
2405                 last_status_cksum = 0;  // force status update
2406                 return;
2407         }
2408         if (sscanf(p, "%d", &cnt) > 0) {
2409                 dot = find_line(cnt);
2410                 dot_skip_over_ws();
2411                 return;
2412         }
2413         not_implemented(p);
2414 #else
2415
2416         char c, *buf1, *q, *r;
2417         char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
2418         int i, l, li, b, e;
2419         int useforce;
2420 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2421         char *orig_buf;
2422 # endif
2423
2424         // :3154        // if (-e line 3154) goto it  else stay put
2425         // :4,33w! foo  // write a portion of buffer to file "foo"
2426         // :w           // write all of buffer to current file
2427         // :q           // quit
2428         // :q!          // quit- dont care about modified file
2429         // :'a,'z!sort -u   // filter block through sort
2430         // :'f          // goto mark "f"
2431         // :'fl         // list literal the mark "f" line
2432         // :.r bar      // read file "bar" into buffer before dot
2433         // :/123/,/abc/d    // delete lines from "123" line to "abc" line
2434         // :/xyz/       // goto the "xyz" line
2435         // :s/find/replace/ // substitute pattern "find" with "replace"
2436         // :!<cmd>      // run <cmd> then return
2437         //
2438
2439         if (!buf[0])
2440                 goto ret;
2441         if (*buf == ':')
2442                 buf++;                  // move past the ':'
2443
2444         li = i = 0;
2445         b = e = -1;
2446         q = text;                       // assume 1,$ for the range
2447         r = end - 1;
2448         li = count_lines(text, end - 1);
2449         fn = current_filename;
2450
2451         // look for optional address(es)  :.  :1  :1,9   :'q,'a   :%
2452         buf = get_address(buf, &b, &e);
2453
2454 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
2455         // remember orig command line
2456         orig_buf = buf;
2457 # endif
2458
2459         // get the COMMAND into cmd[]
2460         buf1 = cmd;
2461         while (*buf != '\0') {
2462                 if (isspace(*buf))
2463                         break;
2464                 *buf1++ = *buf++;
2465         }
2466         *buf1 = '\0';
2467         // get any ARGuments
2468         while (isblank(*buf))
2469                 buf++;
2470         strcpy(args, buf);
2471         useforce = FALSE;
2472         buf1 = last_char_is(cmd, '!');
2473         if (buf1) {
2474                 useforce = TRUE;
2475                 *buf1 = '\0';   // get rid of !
2476         }
2477         if (b >= 0) {
2478                 // if there is only one addr, then the addr
2479                 // is the line number of the single line the
2480                 // user wants. So, reset the end
2481                 // pointer to point at end of the "b" line
2482                 q = find_line(b);       // what line is #b
2483                 r = end_line(q);
2484                 li = 1;
2485         }
2486         if (e >= 0) {
2487                 // we were given two addrs.  change the
2488                 // end pointer to the addr given by user.
2489                 r = find_line(e);       // what line is #e
2490                 r = end_line(r);
2491                 li = e - b + 1;
2492         }
2493         // ------------ now look for the command ------------
2494         i = strlen(cmd);
2495         if (i == 0) {           // :123CR goto line #123
2496                 if (b >= 0) {
2497                         dot = find_line(b);     // what line is #b
2498                         dot_skip_over_ws();
2499                 }
2500         }
2501 # if ENABLE_FEATURE_ALLOW_EXEC
2502         else if (cmd[0] == '!') {       // run a cmd
2503                 int retcode;
2504                 // :!ls   run the <cmd>
2505                 go_bottom_and_clear_to_eol();
2506                 cookmode();
2507                 retcode = system(orig_buf + 1); // run the cmd
2508                 if (retcode)
2509                         printf("\nshell returned %i\n\n", retcode);
2510                 rawmode();
2511                 Hit_Return();                   // let user see results
2512         }
2513 # endif
2514         else if (cmd[0] == '=' && !cmd[1]) {    // where is the address
2515                 if (b < 0) {    // no addr given- use defaults
2516                         b = e = count_lines(text, dot);
2517                 }
2518                 status_line("%d", b);
2519         } else if (strncmp(cmd, "delete", i) == 0) {    // delete lines
2520                 if (b < 0) {    // no addr given- use defaults
2521                         q = begin_line(dot);    // assume .,. for the range
2522                         r = end_line(dot);
2523                 }
2524                 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO);        // save, then delete lines
2525                 dot_skip_over_ws();
2526         } else if (strncmp(cmd, "edit", i) == 0) {      // Edit a file
2527                 int size;
2528
2529                 // don't edit, if the current file has been modified
2530                 if (modified_count && !useforce) {
2531                         status_line_bold("No write since last change (:%s! overrides)", cmd);
2532                         goto ret;
2533                 }
2534                 if (args[0]) {
2535                         // the user supplied a file name
2536                         fn = args;
2537                 } else if (current_filename && current_filename[0]) {
2538                         // no user supplied name- use the current filename
2539                         // fn = current_filename;  was set by default
2540                 } else {
2541                         // no user file name, no current name- punt
2542                         status_line_bold("No current filename");
2543                         goto ret;
2544                 }
2545
2546                 size = init_text_buffer(fn);
2547
2548 # if ENABLE_FEATURE_VI_YANKMARK
2549                 if (Ureg >= 0 && Ureg < 28) {
2550                         free(reg[Ureg]);        //   free orig line reg- for 'U'
2551                         reg[Ureg] = NULL;
2552                 }
2553                 /*if (YDreg < 28) - always true*/ {
2554                         free(reg[YDreg]);       //   free default yank/delete register
2555                         reg[YDreg] = NULL;
2556                 }
2557 # endif
2558                 // how many lines in text[]?
2559                 li = count_lines(text, end - 1);
2560                 status_line("'%s'%s"
2561                         IF_FEATURE_VI_READONLY("%s")
2562                         " %uL, %uC",
2563                         current_filename,
2564                         (size < 0 ? " [New file]" : ""),
2565                         IF_FEATURE_VI_READONLY(
2566                                 ((readonly_mode) ? " [Readonly]" : ""),
2567                         )
2568                         li, (int)(end - text)
2569                 );
2570         } else if (strncmp(cmd, "file", i) == 0) {      // what File is this
2571                 if (b != -1 || e != -1) {
2572                         status_line_bold("No address allowed on this command");
2573                         goto ret;
2574                 }
2575                 if (args[0]) {
2576                         // user wants a new filename
2577                         free(current_filename);
2578                         current_filename = xstrdup(args);
2579                 } else {
2580                         // user wants file status info
2581                         last_status_cksum = 0;  // force status update
2582                 }
2583         } else if (strncmp(cmd, "features", i) == 0) {  // what features are available
2584                 // print out values of all features
2585                 go_bottom_and_clear_to_eol();
2586                 cookmode();
2587                 show_help();
2588                 rawmode();
2589                 Hit_Return();
2590         } else if (strncmp(cmd, "list", i) == 0) {      // literal print line
2591                 if (b < 0) {    // no addr given- use defaults
2592                         q = begin_line(dot);    // assume .,. for the range
2593                         r = end_line(dot);
2594                 }
2595                 go_bottom_and_clear_to_eol();
2596                 puts("\r");
2597                 for (; q <= r; q++) {
2598                         int c_is_no_print;
2599
2600                         c = *q;
2601                         c_is_no_print = (c & 0x80) && !Isprint(c);
2602                         if (c_is_no_print) {
2603                                 c = '.';
2604                                 standout_start();
2605                         }
2606                         if (c == '\n') {
2607                                 write1("$\r");
2608                         } else if (c < ' ' || c == 127) {
2609                                 bb_putchar('^');
2610                                 if (c == 127)
2611                                         c = '?';
2612                                 else
2613                                         c += '@';
2614                         }
2615                         bb_putchar(c);
2616                         if (c_is_no_print)
2617                                 standout_end();
2618                 }
2619                 Hit_Return();
2620         } else if (strncmp(cmd, "quit", i) == 0 // quit
2621                 || strncmp(cmd, "next", i) == 0 // edit next file
2622                 || strncmp(cmd, "prev", i) == 0 // edit previous file
2623         ) {
2624                 int n;
2625                 if (useforce) {
2626                         if (*cmd == 'q') {
2627                                 // force end of argv list
2628                                 optind = cmdline_filecnt;
2629                         }
2630                         editing = 0;
2631                         goto ret;
2632                 }
2633                 // don't exit if the file been modified
2634                 if (modified_count) {
2635                         status_line_bold("No write since last change (:%s! overrides)", cmd);
2636                         goto ret;
2637                 }
2638                 // are there other file to edit
2639                 n = cmdline_filecnt - optind - 1;
2640                 if (*cmd == 'q' && n > 0) {
2641                         status_line_bold("%u more file(s) to edit", n);
2642                         goto ret;
2643                 }
2644                 if (*cmd == 'n' && n <= 0) {
2645                         status_line_bold("No more files to edit");
2646                         goto ret;
2647                 }
2648                 if (*cmd == 'p') {
2649                         // are there previous files to edit
2650                         if (optind < 1) {
2651                                 status_line_bold("No previous files to edit");
2652                                 goto ret;
2653                         }
2654                         optind -= 2;
2655                 }
2656                 editing = 0;
2657         } else if (strncmp(cmd, "read", i) == 0) {      // read file into text[]
2658                 int size;
2659
2660                 fn = args;
2661                 if (!fn[0]) {
2662                         status_line_bold("No filename given");
2663                         goto ret;
2664                 }
2665                 if (b < 0) {    // no addr given- use defaults
2666                         q = begin_line(dot);    // assume "dot"
2667                 }
2668                 // read after current line- unless user said ":0r foo"
2669                 if (b != 0) {
2670                         q = next_line(q);
2671                         // read after last line
2672                         if (q == end-1)
2673                                 ++q;
2674                 }
2675                 { // dance around potentially-reallocated text[]
2676                         uintptr_t ofs = q - text;
2677                         size = file_insert(fn, q, 0);
2678                         q = text + ofs;
2679                 }
2680                 if (size < 0)
2681                         goto ret;       // nothing was inserted
2682                 // how many lines in text[]?
2683                 li = count_lines(q, q + size - 1);
2684                 status_line("'%s'"
2685                         IF_FEATURE_VI_READONLY("%s")
2686                         " %uL, %uC",
2687                         fn,
2688                         IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
2689                         li, size
2690                 );
2691                 if (size > 0) {
2692                         // if the insert is before "dot" then we need to update
2693                         if (q <= dot)
2694                                 dot += size;
2695                 }
2696         } else if (strncmp(cmd, "rewind", i) == 0) {    // rewind cmd line args
2697                 if (modified_count && !useforce) {
2698                         status_line_bold("No write since last change (:%s! overrides)", cmd);
2699                 } else {
2700                         // reset the filenames to edit
2701                         optind = -1; // start from 0th file
2702                         editing = 0;
2703                 }
2704 # if ENABLE_FEATURE_VI_SET
2705         } else if (strncmp(cmd, "set", i) == 0) {       // set or clear features
2706 #  if ENABLE_FEATURE_VI_SETOPTS
2707                 char *argp;
2708 #  endif
2709                 i = 0;                  // offset into args
2710                 // only blank is regarded as args delimiter. What about tab '\t'?
2711                 if (!args[0] || strcasecmp(args, "all") == 0) {
2712                         // print out values of all options
2713 #  if ENABLE_FEATURE_VI_SETOPTS
2714                         status_line_bold(
2715                                 "%sautoindent "
2716                                 "%sflash "
2717                                 "%signorecase "
2718                                 "%sshowmatch "
2719                                 "tabstop=%u",
2720                                 autoindent ? "" : "no",
2721                                 err_method ? "" : "no",
2722                                 ignorecase ? "" : "no",
2723                                 showmatch ? "" : "no",
2724                                 tabstop
2725                         );
2726 #  endif
2727                         goto ret;
2728                 }
2729 #  if ENABLE_FEATURE_VI_SETOPTS
2730                 argp = args;
2731                 while (*argp) {
2732                         if (strncmp(argp, "no", 2) == 0)
2733                                 i = 2;          // ":set noautoindent"
2734                         setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
2735                         setops(argp, "flash "     , i, "fl", VI_ERR_METHOD);
2736                         setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
2737                         setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
2738                         if (strncmp(argp + i, "tabstop=", 8) == 0) {
2739                                 int t = 0;
2740                                 sscanf(argp + i+8, "%u", &t);
2741                                 if (t > 0 && t <= MAX_TABSTOP)
2742                                         tabstop = t;
2743                         }
2744                         argp = skip_non_whitespace(argp);
2745                         argp = skip_whitespace(argp);
2746                 }
2747 #  endif /* FEATURE_VI_SETOPTS */
2748 # endif /* FEATURE_VI_SET */
2749
2750 # if ENABLE_FEATURE_VI_SEARCH
2751         } else if (cmd[0] == 's') {     // substitute a pattern with a replacement pattern
2752                 char *F, *R, *flags;
2753                 size_t len_F, len_R;
2754                 int gflag;              // global replace flag
2755 #  if ENABLE_FEATURE_VI_UNDO
2756                 int dont_chain_first_item = ALLOW_UNDO;
2757 #  endif
2758
2759                 // F points to the "find" pattern
2760                 // R points to the "replace" pattern
2761                 // replace the cmd line delimiters "/" with NULs
2762                 c = orig_buf[1];        // what is the delimiter
2763                 F = orig_buf + 2;       // start of "find"
2764                 R = strchr(F, c);       // middle delimiter
2765                 if (!R)
2766                         goto colon_s_fail;
2767                 len_F = R - F;
2768                 *R++ = '\0';    // terminate "find"
2769                 flags = strchr(R, c);
2770                 if (!flags)
2771                         goto colon_s_fail;
2772                 len_R = flags - R;
2773                 *flags++ = '\0';        // terminate "replace"
2774                 gflag = *flags;
2775
2776                 q = begin_line(q);
2777                 if (b < 0) {    // maybe :s/foo/bar/
2778                         q = begin_line(dot);      // start with cur line
2779                         b = count_lines(text, q); // cur line number
2780                 }
2781                 if (e < 0)
2782                         e = b;          // maybe :.s/foo/bar/
2783
2784                 for (i = b; i <= e; i++) {      // so, :20,23 s \0 find \0 replace \0
2785                         char *ls = q;           // orig line start
2786                         char *found;
2787  vc4:
2788                         found = char_search(q, F, (FORWARD << 1) | LIMITED);    // search cur line only for "find"
2789                         if (found) {
2790                                 uintptr_t bias;
2791                                 // we found the "find" pattern - delete it
2792                                 // For undo support, the first item should not be chained
2793                                 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
2794 #  if ENABLE_FEATURE_VI_UNDO
2795                                 dont_chain_first_item = ALLOW_UNDO_CHAIN;
2796 #  endif
2797                                 // insert the "replace" patern
2798                                 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
2799                                 found += bias;
2800                                 ls += bias;
2801                                 //q += bias; - recalculated anyway
2802                                 // check for "global"  :s/foo/bar/g
2803                                 if (gflag == 'g') {
2804                                         if ((found + len_R) < end_line(ls)) {
2805                                                 q = found + len_R;
2806                                                 goto vc4;       // don't let q move past cur line
2807                                         }
2808                                 }
2809                         }
2810                         q = next_line(ls);
2811                 }
2812 # endif /* FEATURE_VI_SEARCH */
2813         } else if (strncmp(cmd, "version", i) == 0) {  // show software version
2814                 status_line(BB_VER);
2815         } else if (strncmp(cmd, "write", i) == 0  // write text to file
2816                 || strncmp(cmd, "wq", i) == 0
2817                 || strncmp(cmd, "wn", i) == 0
2818                 || (cmd[0] == 'x' && !cmd[1])
2819         ) {
2820                 int size;
2821                 //int forced = FALSE;
2822
2823                 // is there a file name to write to?
2824                 if (args[0]) {
2825                         fn = args;
2826                 }
2827 # if ENABLE_FEATURE_VI_READONLY
2828                 if (readonly_mode && !useforce) {
2829                         status_line_bold("'%s' is read only", fn);
2830                         goto ret;
2831                 }
2832 # endif
2833                 //if (useforce) {
2834                         // if "fn" is not write-able, chmod u+w
2835                         // sprintf(syscmd, "chmod u+w %s", fn);
2836                         // system(syscmd);
2837                         // forced = TRUE;
2838                 //}
2839                 if (modified_count != 0 || cmd[0] != 'x') {
2840                         size = r - q + 1;
2841                         l = file_write(fn, q, r);
2842                 } else {
2843                         size = 0;
2844                         l = 0;
2845                 }
2846                 //if (useforce && forced) {
2847                         // chmod u-w
2848                         // sprintf(syscmd, "chmod u-w %s", fn);
2849                         // system(syscmd);
2850                         // forced = FALSE;
2851                 //}
2852                 if (l < 0) {
2853                         if (l == -1)
2854                                 status_line_bold_errno(fn);
2855                 } else {
2856                         // how many lines written
2857                         li = count_lines(q, q + l - 1);
2858                         status_line("'%s' %uL, %uC", fn, li, l);
2859                         if (l == size) {
2860                                 if (q == text && q + l == end) {
2861                                         modified_count = 0;
2862                                         last_modified_count = -1;
2863                                 }
2864                                 if (cmd[0] == 'x'
2865                                  || cmd[1] == 'q' || cmd[1] == 'n'
2866                                  || cmd[1] == 'Q' || cmd[1] == 'N'
2867                                 ) {
2868                                         editing = 0;
2869                                 }
2870                         }
2871                 }
2872 # if ENABLE_FEATURE_VI_YANKMARK
2873         } else if (strncmp(cmd, "yank", i) == 0) {      // yank lines
2874                 if (b < 0) {    // no addr given- use defaults
2875                         q = begin_line(dot);    // assume .,. for the range
2876                         r = end_line(dot);
2877                 }
2878                 text_yank(q, r, YDreg);
2879                 li = count_lines(q, r);
2880                 status_line("Yank %d lines (%d chars) into [%c]",
2881                                 li, strlen(reg[YDreg]), what_reg());
2882 # endif
2883         } else {
2884                 // cmd unknown
2885                 not_implemented(cmd);
2886         }
2887  ret:
2888         dot = bound_dot(dot);   // make sure "dot" is valid
2889         return;
2890 # if ENABLE_FEATURE_VI_SEARCH
2891  colon_s_fail:
2892         status_line(":s expression missing delimiters");
2893 # endif
2894 #endif /* FEATURE_VI_COLON */
2895 }
2896
2897 //----- Char Routines --------------------------------------------
2898 // Chars that are part of a word-
2899 //    0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2900 // Chars that are Not part of a word (stoppers)
2901 //    !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2902 // Chars that are WhiteSpace
2903 //    TAB NEWLINE VT FF RETURN SPACE
2904 // DO NOT COUNT NEWLINE AS WHITESPACE
2905
2906 static int st_test(char *p, int type, int dir, char *tested)
2907 {
2908         char c, c0, ci;
2909         int test, inc;
2910
2911         inc = dir;
2912         c = c0 = p[0];
2913         ci = p[inc];
2914         test = 0;
2915
2916         if (type == S_BEFORE_WS) {
2917                 c = ci;
2918                 test = (!isspace(c) || c == '\n');
2919         }
2920         if (type == S_TO_WS) {
2921                 c = c0;
2922                 test = (!isspace(c) || c == '\n');
2923         }
2924         if (type == S_OVER_WS) {
2925                 c = c0;
2926                 test = isspace(c);
2927         }
2928         if (type == S_END_PUNCT) {
2929                 c = ci;
2930                 test = ispunct(c);
2931         }
2932         if (type == S_END_ALNUM) {
2933                 c = ci;
2934                 test = (isalnum(c) || c == '_');
2935         }
2936         *tested = c;
2937         return test;
2938 }
2939
2940 static char *skip_thing(char *p, int linecnt, int dir, int type)
2941 {
2942         char c;
2943
2944         while (st_test(p, type, dir, &c)) {
2945                 // make sure we limit search to correct number of lines
2946                 if (c == '\n' && --linecnt < 1)
2947                         break;
2948                 if (dir >= 0 && p >= end - 1)
2949                         break;
2950                 if (dir < 0 && p <= text)
2951                         break;
2952                 p += dir;               // move to next char
2953         }
2954         return p;
2955 }
2956
2957 #if ENABLE_FEATURE_VI_USE_SIGNALS
2958 static void winch_handler(int sig UNUSED_PARAM)
2959 {
2960         int save_errno = errno;
2961         // FIXME: do it in main loop!!!
2962         signal(SIGWINCH, winch_handler);
2963         query_screen_dimensions();
2964         new_screen(rows, columns);      // get memory for virtual screen
2965         redraw(TRUE);           // re-draw the screen
2966         errno = save_errno;
2967 }
2968 static void tstp_handler(int sig UNUSED_PARAM)
2969 {
2970         int save_errno = errno;
2971
2972         // ioctl inside cookmode() was seen to generate SIGTTOU,
2973         // stopping us too early. Prevent that:
2974         signal(SIGTTOU, SIG_IGN);
2975
2976         go_bottom_and_clear_to_eol();
2977         cookmode(); // terminal to "cooked"
2978
2979         // stop now
2980         //signal(SIGTSTP, SIG_DFL);
2981         //raise(SIGTSTP);
2982         raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
2983         //signal(SIGTSTP, tstp_handler);
2984
2985         // we have been "continued" with SIGCONT, restore screen and termios
2986         rawmode(); // terminal to "raw"
2987         last_status_cksum = 0; // force status update
2988         redraw(TRUE); // re-draw the screen
2989
2990         errno = save_errno;
2991 }
2992 static void int_handler(int sig)
2993 {
2994         signal(SIGINT, int_handler);
2995         siglongjmp(restart, sig);
2996 }
2997 #endif /* FEATURE_VI_USE_SIGNALS */
2998
2999 static void do_cmd(int c);
3000
3001 static int find_range(char **start, char **stop, char c)
3002 {
3003         char *save_dot, *p, *q, *t;
3004         int cnt, multiline = 0, forward;
3005
3006         save_dot = dot;
3007         p = q = dot;
3008
3009         // will a 'G' command move forwards or backwards?
3010         forward = cmdcnt == 0 || cmdcnt > count_lines(text, dot);
3011
3012         if (strchr("cdy><", c)) {
3013                 // these cmds operate on whole lines
3014                 p = q = begin_line(p);
3015                 for (cnt = 1; cnt < cmdcnt; cnt++) {
3016                         q = next_line(q);
3017                 }
3018                 q = end_line(q);
3019         } else if (strchr("^%$0bBeEfth\b\177", c)) {
3020                 // These cmds operate on char positions
3021                 do_cmd(c);              // execute movement cmd
3022                 q = dot;
3023         } else if (strchr("wW", c)) {
3024                 do_cmd(c);              // execute movement cmd
3025                 // if we are at the next word's first char
3026                 // step back one char
3027                 // but check the possibilities when it is true
3028                 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
3029                                 || (ispunct(dot[-1]) && !ispunct(dot[0]))
3030                                 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
3031                         dot--;          // move back off of next word
3032                 if (dot > text && *dot == '\n')
3033                         dot--;          // stay off NL
3034                 q = dot;
3035         } else if (strchr("H-k{", c) || (c == 'G' && !forward)) {
3036                 // these operate on multi-lines backwards
3037                 q = end_line(dot);      // find NL
3038                 do_cmd(c);              // execute movement cmd
3039                 dot_begin();
3040                 p = dot;
3041         } else if (strchr("L+j}\r\n", c) || (c == 'G' && forward)) {
3042                 // these operate on multi-lines forwards
3043                 p = begin_line(dot);
3044                 do_cmd(c);              // execute movement cmd
3045                 dot_end();              // find NL
3046                 q = dot;
3047         } else {
3048                 // nothing -- this causes any other values of c to
3049                 // represent the one-character range under the
3050                 // cursor.  this is correct for ' ' and 'l', but
3051                 // perhaps no others.
3052                 //
3053         }
3054         if (q < p) {
3055                 t = q;
3056                 q = p;
3057                 p = t;
3058         }
3059
3060         // backward char movements don't include start position
3061         if (q > p && strchr("^0bBh\b\177", c)) q--;
3062
3063         multiline = 0;
3064         for (t = p; t <= q; t++) {
3065                 if (*t == '\n') {
3066                         multiline = 1;
3067                         break;
3068                 }
3069         }
3070
3071         *start = p;
3072         *stop = q;
3073         dot = save_dot;
3074         return multiline;
3075 }
3076
3077 //---------------------------------------------------------------------
3078 //----- the Ascii Chart -----------------------------------------------
3079 //  00 nul   01 soh   02 stx   03 etx   04 eot   05 enq   06 ack   07 bel
3080 //  08 bs    09 ht    0a nl    0b vt    0c np    0d cr    0e so    0f si
3081 //  10 dle   11 dc1   12 dc2   13 dc3   14 dc4   15 nak   16 syn   17 etb
3082 //  18 can   19 em    1a sub   1b esc   1c fs    1d gs    1e rs    1f us
3083 //  20 sp    21 !     22 "     23 #     24 $     25 %     26 &     27 '
3084 //  28 (     29 )     2a *     2b +     2c ,     2d -     2e .     2f /
3085 //  30 0     31 1     32 2     33 3     34 4     35 5     36 6     37 7
3086 //  38 8     39 9     3a :     3b ;     3c <     3d =     3e >     3f ?
3087 //  40 @     41 A     42 B     43 C     44 D     45 E     46 F     47 G
3088 //  48 H     49 I     4a J     4b K     4c L     4d M     4e N     4f O
3089 //  50 P     51 Q     52 R     53 S     54 T     55 U     56 V     57 W
3090 //  58 X     59 Y     5a Z     5b [     5c \     5d ]     5e ^     5f _
3091 //  60 `     61 a     62 b     63 c     64 d     65 e     66 f     67 g
3092 //  68 h     69 i     6a j     6b k     6c l     6d m     6e n     6f o
3093 //  70 p     71 q     72 r     73 s     74 t     75 u     76 v     77 w
3094 //  78 x     79 y     7a z     7b {     7c |     7d }     7e ~     7f del
3095 //---------------------------------------------------------------------
3096
3097 //----- Execute a Vi Command -----------------------------------
3098 static void do_cmd(int c)
3099 {
3100         char *p, *q, *save_dot;
3101         char buf[12];
3102         int dir;
3103         int cnt, i, j;
3104         int c1;
3105
3106 //      c1 = c; // quiet the compiler
3107 //      cnt = yf = 0; // quiet the compiler
3108 //      p = q = save_dot = buf; // quiet the compiler
3109         memset(buf, '\0', sizeof(buf));
3110
3111         show_status_line();
3112
3113         // if this is a cursor key, skip these checks
3114         switch (c) {
3115                 case KEYCODE_UP:
3116                 case KEYCODE_DOWN:
3117                 case KEYCODE_LEFT:
3118                 case KEYCODE_RIGHT:
3119                 case KEYCODE_HOME:
3120                 case KEYCODE_END:
3121                 case KEYCODE_PAGEUP:
3122                 case KEYCODE_PAGEDOWN:
3123                 case KEYCODE_DELETE:
3124                         goto key_cmd_mode;
3125         }
3126
3127         if (cmd_mode == 2) {
3128                 //  flip-flop Insert/Replace mode
3129                 if (c == KEYCODE_INSERT)
3130                         goto dc_i;
3131                 // we are 'R'eplacing the current *dot with new char
3132                 if (*dot == '\n') {
3133                         // don't Replace past E-o-l
3134                         cmd_mode = 1;   // convert to insert
3135                         undo_queue_commit();
3136                 } else {
3137                         if (1 <= c || Isprint(c)) {
3138                                 if (c != 27)
3139                                         dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO);    // delete char
3140                                 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN);    // insert new char
3141                         }
3142                         goto dc1;
3143                 }
3144         }
3145         if (cmd_mode == 1) {
3146                 // hitting "Insert" twice means "R" replace mode
3147                 if (c == KEYCODE_INSERT) goto dc5;
3148                 // insert the char c at "dot"
3149                 if (1 <= c || Isprint(c)) {
3150                         dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3151                 }
3152                 goto dc1;
3153         }
3154
3155  key_cmd_mode:
3156         switch (c) {
3157                 //case 0x01:    // soh
3158                 //case 0x09:    // ht
3159                 //case 0x0b:    // vt
3160                 //case 0x0e:    // so
3161                 //case 0x0f:    // si
3162                 //case 0x10:    // dle
3163                 //case 0x11:    // dc1
3164                 //case 0x13:    // dc3
3165 #if ENABLE_FEATURE_VI_CRASHME
3166         case 0x14:                      // dc4  ctrl-T
3167                 crashme = (crashme == 0) ? 1 : 0;
3168                 break;
3169 #endif
3170                 //case 0x16:    // syn
3171                 //case 0x17:    // etb
3172                 //case 0x18:    // can
3173                 //case 0x1c:    // fs
3174                 //case 0x1d:    // gs
3175                 //case 0x1e:    // rs
3176                 //case 0x1f:    // us
3177                 //case '!':     // !-
3178                 //case '#':     // #-
3179                 //case '&':     // &-
3180                 //case '(':     // (-
3181                 //case ')':     // )-
3182                 //case '*':     // *-
3183                 //case '=':     // =-
3184                 //case '@':     // @-
3185                 //case 'F':     // F-
3186                 //case 'K':     // K-
3187                 //case 'Q':     // Q-
3188                 //case 'S':     // S-
3189                 //case 'T':     // T-
3190                 //case 'V':     // V-
3191                 //case '[':     // [-
3192                 //case '\\':    // \-
3193                 //case ']':     // ]-
3194                 //case '_':     // _-
3195                 //case '`':     // `-
3196                 //case 'v':     // v-
3197         default:                        // unrecognized command
3198                 buf[0] = c;
3199                 buf[1] = '\0';
3200                 not_implemented(buf);
3201                 end_cmd_q();    // stop adding to q
3202         case 0x00:                      // nul- ignore
3203                 break;
3204         case 2:                 // ctrl-B  scroll up   full screen
3205         case KEYCODE_PAGEUP:    // Cursor Key Page Up
3206                 dot_scroll(rows - 2, -1);
3207                 break;
3208         case 4:                 // ctrl-D  scroll down half screen
3209                 dot_scroll((rows - 2) / 2, 1);
3210                 break;
3211         case 5:                 // ctrl-E  scroll down one line
3212                 dot_scroll(1, 1);
3213                 break;
3214         case 6:                 // ctrl-F  scroll down full screen
3215         case KEYCODE_PAGEDOWN:  // Cursor Key Page Down
3216                 dot_scroll(rows - 2, 1);
3217                 break;
3218         case 7:                 // ctrl-G  show current status
3219                 last_status_cksum = 0;  // force status update
3220                 break;
3221         case 'h':                       // h- move left
3222         case KEYCODE_LEFT:      // cursor key Left
3223         case 8:         // ctrl-H- move left    (This may be ERASE char)
3224         case 0x7f:      // DEL- move left   (This may be ERASE char)
3225                 do {
3226                         dot_left();
3227                 } while (--cmdcnt > 0);
3228                 break;
3229         case 10:                        // Newline ^J
3230         case 'j':                       // j- goto next line, same col
3231         case KEYCODE_DOWN:      // cursor key Down
3232                 do {
3233                         dot_next();             // go to next B-o-l
3234                         // try stay in same col
3235                         dot = move_to_col(dot, ccol + offset);
3236                 } while (--cmdcnt > 0);
3237                 break;
3238         case 12:                        // ctrl-L  force redraw whole screen
3239         case 18:                        // ctrl-R  force redraw
3240                 redraw(TRUE);   // this will redraw the entire display
3241                 break;
3242         case 13:                        // Carriage Return ^M
3243         case '+':                       // +- goto next line
3244                 do {
3245                         dot_next();
3246                         dot_skip_over_ws();
3247                 } while (--cmdcnt > 0);
3248                 break;
3249         case 21:                        // ctrl-U  scroll up half screen
3250                 dot_scroll((rows - 2) / 2, -1);
3251                 break;
3252         case 25:                        // ctrl-Y  scroll up one line
3253                 dot_scroll(1, -1);
3254                 break;
3255         case 27:                        // esc
3256                 if (cmd_mode == 0)
3257                         indicate_error();
3258                 cmd_mode = 0;   // stop inserting
3259                 undo_queue_commit();
3260                 end_cmd_q();
3261                 last_status_cksum = 0;  // force status update
3262                 break;
3263         case ' ':                       // move right
3264         case 'l':                       // move right
3265         case KEYCODE_RIGHT:     // Cursor Key Right
3266                 do {
3267                         dot_right();
3268                 } while (--cmdcnt > 0);
3269                 break;
3270 #if ENABLE_FEATURE_VI_YANKMARK
3271         case '"':                       // "- name a register to use for Delete/Yank
3272                 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3273                 if ((unsigned)c1 <= 25) { // a-z?
3274                         YDreg = c1;
3275                 } else {
3276                         indicate_error();
3277                 }
3278                 break;
3279         case '\'':                      // '- goto a specific mark
3280                 c1 = (get_one_char() | 0x20);
3281                 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3282                         c1 = (c1 - 'a');
3283                         // get the b-o-l
3284                         q = mark[c1];
3285                         if (text <= q && q < end) {
3286                                 dot = q;
3287                                 dot_begin();    // go to B-o-l
3288                                 dot_skip_over_ws();
3289                         }
3290                 } else if (c1 == '\'') {        // goto previous context
3291                         dot = swap_context(dot);        // swap current and previous context
3292                         dot_begin();    // go to B-o-l
3293                         dot_skip_over_ws();
3294                 } else {
3295                         indicate_error();
3296                 }
3297                 break;
3298         case 'm':                       // m- Mark a line
3299                 // this is really stupid.  If there are any inserts or deletes
3300                 // between text[0] and dot then this mark will not point to the
3301                 // correct location! It could be off by many lines!
3302                 // Well..., at least its quick and dirty.
3303                 c1 = (get_one_char() | 0x20) - 'a';
3304                 if ((unsigned)c1 <= 25) { // a-z?
3305                         // remember the line
3306                         mark[c1] = dot;
3307                 } else {
3308                         indicate_error();
3309                 }
3310                 break;
3311         case 'P':                       // P- Put register before
3312         case 'p':                       // p- put register after
3313                 p = reg[YDreg];
3314                 if (p == NULL) {
3315                         status_line_bold("Nothing in register %c", what_reg());
3316                         break;
3317                 }
3318                 // are we putting whole lines or strings
3319                 if (strchr(p, '\n') != NULL) {
3320                         if (c == 'P') {
3321                                 dot_begin();    // putting lines- Put above
3322                         }
3323                         if (c == 'p') {
3324                                 // are we putting after very last line?
3325                                 if (end_line(dot) == (end - 1)) {
3326                                         dot = end;      // force dot to end of text[]
3327                                 } else {
3328                                         dot_next();     // next line, then put before
3329                                 }
3330                         }
3331                 } else {
3332                         if (c == 'p')
3333                                 dot_right();    // move to right, can move to NL
3334                 }
3335                 string_insert(dot, p, ALLOW_UNDO);      // insert the string
3336                 end_cmd_q();    // stop adding to q
3337                 break;
3338         case 'U':                       // U- Undo; replace current line with original version
3339                 if (reg[Ureg] != NULL) {
3340                         p = begin_line(dot);
3341                         q = end_line(dot);
3342                         p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3343                         p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN);     // insert orig line
3344                         dot = p;
3345                         dot_skip_over_ws();
3346                 }
3347                 break;
3348 #endif /* FEATURE_VI_YANKMARK */
3349 #if ENABLE_FEATURE_VI_UNDO
3350         case 'u':       // u- undo last operation
3351                 undo_pop();
3352                 break;
3353 #endif
3354         case '$':                       // $- goto end of line
3355         case KEYCODE_END:               // Cursor Key End
3356                 for (;;) {
3357                         dot = end_line(dot);
3358                         if (--cmdcnt <= 0)
3359                                 break;
3360                         dot_next();
3361                 }
3362                 break;
3363         case '%':                       // %- find matching char of pair () [] {}
3364                 for (q = dot; q < end && *q != '\n'; q++) {
3365                         if (strchr("()[]{}", *q) != NULL) {
3366                                 // we found half of a pair
3367                                 p = find_pair(q, *q);
3368                                 if (p == NULL) {
3369                                         indicate_error();
3370                                 } else {
3371                                         dot = p;
3372                                 }
3373                                 break;
3374                         }
3375                 }
3376                 if (*q == '\n')
3377                         indicate_error();
3378                 break;
3379         case 'f':                       // f- forward to a user specified char
3380                 last_forward_char = get_one_char();     // get the search char
3381                 //
3382                 // dont separate these two commands. 'f' depends on ';'
3383                 //
3384                 //**** fall through to ... ';'
3385         case ';':                       // ;- look at rest of line for last forward char
3386                 do {
3387                         if (last_forward_char == 0)
3388                                 break;
3389                         q = dot + 1;
3390                         while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3391                                 q++;
3392                         }
3393                         if (*q == last_forward_char)
3394                                 dot = q;
3395                 } while (--cmdcnt > 0);
3396                 break;
3397         case ',':           // repeat latest 'f' in opposite direction
3398                 if (last_forward_char == 0)
3399                         break;
3400                 do {
3401                         q = dot - 1;
3402                         while (q >= text && *q != '\n' && *q != last_forward_char) {
3403                                 q--;
3404                         }
3405                         if (q >= text && *q == last_forward_char)
3406                                 dot = q;
3407                 } while (--cmdcnt > 0);
3408                 break;
3409
3410         case '-':                       // -- goto prev line
3411                 do {
3412                         dot_prev();
3413                         dot_skip_over_ws();
3414                 } while (--cmdcnt > 0);
3415                 break;
3416 #if ENABLE_FEATURE_VI_DOT_CMD
3417         case '.':                       // .- repeat the last modifying command
3418                 // Stuff the last_modifying_cmd back into stdin
3419                 // and let it be re-executed.
3420                 if (lmc_len != 0) {
3421                         ioq = ioq_start = xstrndup(last_modifying_cmd, lmc_len);
3422                 }
3423                 break;
3424 #endif
3425 #if ENABLE_FEATURE_VI_SEARCH
3426         case '?':                       // /- search for a pattern
3427         case '/':                       // /- search for a pattern
3428                 buf[0] = c;
3429                 buf[1] = '\0';
3430                 q = get_input_line(buf);        // get input line- use "status line"
3431                 if (q[0] && !q[1]) {
3432                         if (last_search_pattern[0])
3433                                 last_search_pattern[0] = c;
3434                         goto dc3; // if no pat re-use old pat
3435                 }
3436                 if (q[0]) {       // strlen(q) > 1: new pat- save it and find
3437                         // there is a new pat
3438                         free(last_search_pattern);
3439                         last_search_pattern = xstrdup(q);
3440                         goto dc3;       // now find the pattern
3441                 }
3442                 // user changed mind and erased the "/"-  do nothing
3443                 break;
3444         case 'N':                       // N- backward search for last pattern
3445                 dir = BACK;             // assume BACKWARD search
3446                 p = dot - 1;
3447                 if (last_search_pattern[0] == '?') {
3448                         dir = FORWARD;
3449                         p = dot + 1;
3450                 }
3451                 goto dc4;               // now search for pattern
3452                 break;
3453         case 'n':                       // n- repeat search for last pattern
3454                 // search rest of text[] starting at next char
3455                 // if search fails return orignal "p" not the "p+1" address
3456                 do {
3457                         const char *msg;
3458  dc3:
3459                         dir = FORWARD;  // assume FORWARD search
3460                         p = dot + 1;
3461                         if (last_search_pattern[0] == '?') {
3462                                 dir = BACK;
3463                                 p = dot - 1;
3464                         }
3465  dc4:
3466                         q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3467                         if (q != NULL) {
3468                                 dot = q;        // good search, update "dot"
3469                                 msg = NULL;
3470                                 goto dc2;
3471                         }
3472                         // no pattern found between "dot" and "end"- continue at top
3473                         p = text;
3474                         if (dir == BACK) {
3475                                 p = end - 1;
3476                         }
3477                         q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3478                         if (q != NULL) {        // found something
3479                                 dot = q;        // found new pattern- goto it
3480                                 msg = "search hit BOTTOM, continuing at TOP";
3481                                 if (dir == BACK) {
3482                                         msg = "search hit TOP, continuing at BOTTOM";
3483                                 }
3484                         } else {
3485                                 msg = "Pattern not found";
3486                         }
3487  dc2:
3488                         if (msg)
3489                                 status_line_bold("%s", msg);
3490                 } while (--cmdcnt > 0);
3491                 break;
3492         case '{':                       // {- move backward paragraph
3493                 q = char_search(dot, "\n\n", ((unsigned)BACK << 1) | FULL);
3494                 if (q != NULL) {        // found blank line
3495                         dot = next_line(q);     // move to next blank line
3496                 }
3497                 break;
3498         case '}':                       // }- move forward paragraph
3499                 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
3500                 if (q != NULL) {        // found blank line
3501                         dot = next_line(q);     // move to next blank line
3502                 }
3503                 break;
3504 #endif /* FEATURE_VI_SEARCH */
3505         case '0':                       // 0- goto beginning of line
3506         case '1':                       // 1-
3507         case '2':                       // 2-
3508         case '3':                       // 3-
3509         case '4':                       // 4-
3510         case '5':                       // 5-
3511         case '6':                       // 6-
3512         case '7':                       // 7-
3513         case '8':                       // 8-
3514         case '9':                       // 9-
3515                 if (c == '0' && cmdcnt < 1) {
3516                         dot_begin();    // this was a standalone zero
3517                 } else {
3518                         cmdcnt = cmdcnt * 10 + (c - '0');       // this 0 is part of a number
3519                 }
3520                 break;
3521         case ':':                       // :- the colon mode commands
3522                 p = get_input_line(":");        // get input line- use "status line"
3523                 colon(p);               // execute the command
3524                 break;
3525         case '<':                       // <- Left  shift something
3526         case '>':                       // >- Right shift something
3527                 cnt = count_lines(text, dot);   // remember what line we are on
3528                 c1 = get_one_char();    // get the type of thing to delete
3529                 find_range(&p, &q, c1);
3530                 yank_delete(p, q, 1, YANKONLY, NO_UNDO);        // save copy before change
3531                 p = begin_line(p);
3532                 q = end_line(q);
3533                 i = count_lines(p, q);  // # of lines we are shifting
3534                 for ( ; i > 0; i--, p = next_line(p)) {
3535                         if (c == '<') {
3536                                 // shift left- remove tab or 8 spaces
3537                                 if (*p == '\t') {
3538                                         // shrink buffer 1 char
3539                                         text_hole_delete(p, p, NO_UNDO);
3540                                 } else if (*p == ' ') {
3541                                         // we should be calculating columns, not just SPACE
3542                                         for (j = 0; *p == ' ' && j < tabstop; j++) {
3543                                                 text_hole_delete(p, p, NO_UNDO);
3544                                         }
3545                                 }
3546                         } else if (c == '>') {
3547                                 // shift right -- add tab or 8 spaces
3548                                 char_insert(p, '\t', ALLOW_UNDO);
3549                         }
3550                 }
3551                 dot = find_line(cnt);   // what line were we on
3552                 dot_skip_over_ws();
3553                 end_cmd_q();    // stop adding to q
3554                 break;
3555         case 'A':                       // A- append at e-o-l
3556                 dot_end();              // go to e-o-l
3557                 //**** fall through to ... 'a'
3558         case 'a':                       // a- append after current char
3559                 if (*dot != '\n')
3560                         dot++;
3561                 goto dc_i;
3562                 break;
3563         case 'B':                       // B- back a blank-delimited Word
3564         case 'E':                       // E- end of a blank-delimited word
3565         case 'W':                       // W- forward a blank-delimited word
3566                 dir = FORWARD;
3567                 if (c == 'B')
3568                         dir = BACK;
3569                 do {
3570                         if (c == 'W' || isspace(dot[dir])) {
3571                                 dot = skip_thing(dot, 1, dir, S_TO_WS);
3572                                 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3573                         }
3574                         if (c != 'W')
3575                                 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3576                 } while (--cmdcnt > 0);
3577                 break;
3578         case 'C':                       // C- Change to e-o-l
3579         case 'D':                       // D- delete to e-o-l
3580                 save_dot = dot;
3581                 dot = dollar_line(dot); // move to before NL
3582                 // copy text into a register and delete
3583                 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO);       // delete to e-o-l
3584                 if (c == 'C')
3585                         goto dc_i;      // start inserting
3586 #if ENABLE_FEATURE_VI_DOT_CMD
3587                 if (c == 'D')
3588                         end_cmd_q();    // stop adding to q
3589 #endif
3590                 break;
3591         case 'g': // 'gg' goto a line number (vim) (default: very first line)
3592                 c1 = get_one_char();
3593                 if (c1 != 'g') {
3594                         buf[0] = 'g';
3595                         // c1 < 0 if the key was special. Try "g<up-arrow>"
3596                         // TODO: if Unicode?
3597                         buf[1] = (c1 >= 0 ? c1 : '*');
3598                         buf[2] = '\0';
3599                         not_implemented(buf);
3600                         break;
3601                 }
3602                 if (cmdcnt == 0)
3603                         cmdcnt = 1;
3604                 // fall through
3605         case 'G':               // G- goto to a line number (default= E-O-F)
3606                 dot = end - 1;                          // assume E-O-F
3607                 if (cmdcnt > 0) {
3608                         dot = find_line(cmdcnt);        // what line is #cmdcnt
3609                 }
3610                 dot_skip_over_ws();
3611                 break;
3612         case 'H':                       // H- goto top line on screen
3613                 dot = screenbegin;
3614                 if (cmdcnt > (rows - 1)) {
3615                         cmdcnt = (rows - 1);
3616                 }
3617                 if (--cmdcnt > 0) {
3618                         do_cmd('+');
3619                 }
3620                 dot_skip_over_ws();
3621                 break;
3622         case 'I':                       // I- insert before first non-blank
3623                 dot_begin();    // 0
3624                 dot_skip_over_ws();
3625                 //**** fall through to ... 'i'
3626         case 'i':                       // i- insert before current char
3627         case KEYCODE_INSERT:    // Cursor Key Insert
3628  dc_i:
3629                 cmd_mode = 1;   // start inserting
3630                 undo_queue_commit();    // commit queue when cmd_mode changes
3631                 break;
3632         case 'J':                       // J- join current and next lines together
3633                 do {
3634                         dot_end();              // move to NL
3635                         if (dot < end - 1) {    // make sure not last char in text[]
3636 #if ENABLE_FEATURE_VI_UNDO
3637                                 undo_push(dot, 1, UNDO_DEL);
3638                                 *dot++ = ' ';   // replace NL with space
3639                                 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3640 #else
3641                                 *dot++ = ' ';
3642                                 modified_count++;
3643 #endif
3644                                 while (isblank(*dot)) { // delete leading WS
3645                                         text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3646                                 }
3647                         }
3648                 } while (--cmdcnt > 0);
3649                 end_cmd_q();    // stop adding to q
3650                 break;
3651         case 'L':                       // L- goto bottom line on screen
3652                 dot = end_screen();
3653                 if (cmdcnt > (rows - 1)) {
3654                         cmdcnt = (rows - 1);
3655                 }
3656                 if (--cmdcnt > 0) {
3657                         do_cmd('-');
3658                 }
3659                 dot_begin();
3660                 dot_skip_over_ws();
3661                 break;
3662         case 'M':                       // M- goto middle line on screen
3663                 dot = screenbegin;
3664                 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3665                         dot = next_line(dot);
3666                 break;
3667         case 'O':                       // O- open a empty line above
3668                 //    0i\n ESC -i
3669                 p = begin_line(dot);
3670                 if (p[-1] == '\n') {
3671                         dot_prev();
3672         case 'o':                       // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3673                         dot_end();
3674                         dot = char_insert(dot, '\n', ALLOW_UNDO);
3675                 } else {
3676                         dot_begin();    // 0
3677                         dot = char_insert(dot, '\n', ALLOW_UNDO);       // i\n ESC
3678                         dot_prev();     // -
3679                 }
3680                 goto dc_i;
3681                 break;
3682         case 'R':                       // R- continuous Replace char
3683  dc5:
3684                 cmd_mode = 2;
3685                 undo_queue_commit();
3686                 break;
3687         case KEYCODE_DELETE:
3688                 if (dot < end - 1)
3689                         dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
3690                 break;
3691         case 'X':                       // X- delete char before dot
3692         case 'x':                       // x- delete the current char
3693         case 's':                       // s- substitute the current char
3694                 dir = 0;
3695                 if (c == 'X')
3696                         dir = -1;
3697                 do {
3698                         if (dot[dir] != '\n') {
3699                                 if (c == 'X')
3700                                         dot--;  // delete prev char
3701                                 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO);    // delete char
3702                         }
3703                 } while (--cmdcnt > 0);
3704                 end_cmd_q();    // stop adding to q
3705                 if (c == 's')
3706                         goto dc_i;      // start inserting
3707                 break;
3708         case 'Z':                       // Z- if modified, {write}; exit
3709                 // ZZ means to save file (if necessary), then exit
3710                 c1 = get_one_char();
3711                 if (c1 != 'Z') {
3712                         indicate_error();
3713                         break;
3714                 }
3715                 if (modified_count) {
3716                         if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3717                                 status_line_bold("'%s' is read only", current_filename);
3718                                 break;
3719                         }
3720                         cnt = file_write(current_filename, text, end - 1);
3721                         if (cnt < 0) {
3722                                 if (cnt == -1)
3723                                         status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
3724                         } else if (cnt == (end - 1 - text + 1)) {
3725                                 editing = 0;
3726                         }
3727                 } else {
3728                         editing = 0;
3729                 }
3730                 break;
3731         case '^':                       // ^- move to first non-blank on line
3732                 dot_begin();
3733                 dot_skip_over_ws();
3734                 break;
3735         case 'b':                       // b- back a word
3736         case 'e':                       // e- end of word
3737                 dir = FORWARD;
3738                 if (c == 'b')
3739                         dir = BACK;
3740                 do {
3741                         if ((dot + dir) < text || (dot + dir) > end - 1)
3742                                 break;
3743                         dot += dir;
3744                         if (isspace(*dot)) {
3745                                 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3746                         }
3747                         if (isalnum(*dot) || *dot == '_') {
3748                                 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3749                         } else if (ispunct(*dot)) {
3750                                 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3751                         }
3752                 } while (--cmdcnt > 0);
3753                 break;
3754         case 'c':                       // c- change something
3755         case 'd':                       // d- delete something
3756 #if ENABLE_FEATURE_VI_YANKMARK
3757         case 'y':                       // y- yank   something
3758         case 'Y':                       // Y- Yank a line
3759 #endif
3760         {
3761                 int yf, ml, whole = 0;
3762                 yf = YANKDEL;   // assume either "c" or "d"
3763 #if ENABLE_FEATURE_VI_YANKMARK
3764                 if (c == 'y' || c == 'Y')
3765                         yf = YANKONLY;
3766 #endif
3767                 c1 = 'y';
3768                 if (c != 'Y')
3769                         c1 = get_one_char();    // get the type of thing to delete
3770                 // determine range, and whether it spans lines
3771                 ml = find_range(&p, &q, c1);
3772                 place_cursor(0, 0);
3773                 if (c1 == 27) { // ESC- user changed mind and wants out
3774                         c = c1 = 27;    // Escape- do nothing
3775                 } else if (strchr("wW", c1)) {
3776                         ml = 0; // multi-line ranges aren't allowed for words
3777                         if (c == 'c') {
3778                                 // don't include trailing WS as part of word
3779                                 while (isspace(*q) && q > p) {
3780                                         q--;
3781                                 }
3782                         }
3783                         dot = yank_delete(p, q, ml, yf, ALLOW_UNDO);    // delete word
3784                 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3785                         // partial line copy text into a register and delete
3786                         dot = yank_delete(p, q, ml, yf, ALLOW_UNDO);    // delete word
3787                 } else if (strchr("cdykjGHL+-{}\r\n", c1)) {
3788                         // whole line copy text into a register and delete
3789                         dot = yank_delete(p, q, ml, yf, ALLOW_UNDO);    // delete lines
3790                         whole = 1;
3791                 } else {
3792                         // could not recognize object
3793                         c = c1 = 27;    // error-
3794                         ml = 0;
3795                         indicate_error();
3796                 }
3797                 if (ml && whole) {
3798                         if (c == 'c') {
3799                                 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
3800                                 // on the last line of file don't move to prev line
3801                                 if (whole && dot != (end-1)) {
3802                                         dot_prev();
3803                                 }
3804                         } else if (c == 'd') {
3805                                 dot_begin();
3806                                 dot_skip_over_ws();
3807                         }
3808                 }
3809                 if (c1 != 27) {
3810                         // if CHANGING, not deleting, start inserting after the delete
3811                         if (c == 'c') {
3812                                 strcpy(buf, "Change");
3813                                 goto dc_i;      // start inserting
3814                         }
3815                         if (c == 'd') {
3816                                 strcpy(buf, "Delete");
3817                         }
3818 #if ENABLE_FEATURE_VI_YANKMARK
3819                         if (c == 'y' || c == 'Y') {
3820                                 strcpy(buf, "Yank");
3821                         }
3822                         p = reg[YDreg];
3823                         q = p + strlen(p);
3824                         for (cnt = 0; p <= q; p++) {
3825                                 if (*p == '\n')
3826                                         cnt++;
3827                         }
3828                         status_line("%s %u lines (%u chars) using [%c]",
3829                                 buf, cnt, (unsigned)strlen(reg[YDreg]), what_reg());
3830 #endif
3831                         end_cmd_q();    // stop adding to q
3832                 }
3833                 break;
3834         }
3835         case 'k':                       // k- goto prev line, same col
3836         case KEYCODE_UP:                // cursor key Up
3837                 do {
3838                         dot_prev();
3839                         dot = move_to_col(dot, ccol + offset);  // try stay in same col
3840                 } while (--cmdcnt > 0);
3841                 break;
3842         case 'r':                       // r- replace the current char with user input
3843                 c1 = get_one_char();    // get the replacement char
3844                 if (*dot != '\n') {
3845                         dot = text_hole_delete(dot, dot, ALLOW_UNDO);
3846                         dot = char_insert(dot, c1, ALLOW_UNDO_CHAIN);
3847                         dot_left();
3848                 }
3849                 end_cmd_q();    // stop adding to q
3850                 break;
3851         case 't':                       // t- move to char prior to next x
3852                 last_forward_char = get_one_char();
3853                 do_cmd(';');
3854                 if (*dot == last_forward_char)
3855                         dot_left();
3856                 last_forward_char = 0;
3857                 break;
3858         case 'w':                       // w- forward a word
3859                 do {
3860                         if (isalnum(*dot) || *dot == '_') {     // we are on ALNUM
3861                                 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3862                         } else if (ispunct(*dot)) {     // we are on PUNCT
3863                                 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3864                         }
3865                         if (dot < end - 1)
3866                                 dot++;          // move over word
3867                         if (isspace(*dot)) {
3868                                 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3869                         }
3870                 } while (--cmdcnt > 0);
3871                 break;
3872         case 'z':                       // z-
3873                 c1 = get_one_char();    // get the replacement char
3874                 cnt = 0;
3875                 if (c1 == '.')
3876                         cnt = (rows - 2) / 2;   // put dot at center
3877                 if (c1 == '-')
3878                         cnt = rows - 2; // put dot at bottom
3879                 screenbegin = begin_line(dot);  // start dot at top
3880                 dot_scroll(cnt, -1);
3881                 break;
3882         case '|':                       // |- move to column "cmdcnt"
3883                 dot = move_to_col(dot, cmdcnt - 1);     // try to move to column
3884                 break;
3885         case '~':                       // ~- flip the case of letters   a-z -> A-Z
3886                 do {
3887 #if ENABLE_FEATURE_VI_UNDO
3888                         if (islower(*dot)) {
3889                                 undo_push(dot, 1, UNDO_DEL);
3890                                 *dot = toupper(*dot);
3891                                 undo_push(dot, 1, UNDO_INS_CHAIN);
3892                         } else if (isupper(*dot)) {
3893                                 undo_push(dot, 1, UNDO_DEL);
3894                                 *dot = tolower(*dot);
3895                                 undo_push(dot, 1, UNDO_INS_CHAIN);
3896                         }
3897 #else
3898                         if (islower(*dot)) {
3899                                 *dot = toupper(*dot);
3900                                 modified_count++;
3901                         } else if (isupper(*dot)) {
3902                                 *dot = tolower(*dot);
3903                                 modified_count++;
3904                         }
3905 #endif
3906                         dot_right();
3907                 } while (--cmdcnt > 0);
3908                 end_cmd_q();    // stop adding to q
3909                 break;
3910                 //----- The Cursor and Function Keys -----------------------------
3911         case KEYCODE_HOME:      // Cursor Key Home
3912                 dot_begin();
3913                 break;
3914                 // The Fn keys could point to do_macro which could translate them
3915 #if 0
3916         case KEYCODE_FUN1:      // Function Key F1
3917         case KEYCODE_FUN2:      // Function Key F2
3918         case KEYCODE_FUN3:      // Function Key F3
3919         case KEYCODE_FUN4:      // Function Key F4
3920         case KEYCODE_FUN5:      // Function Key F5
3921         case KEYCODE_FUN6:      // Function Key F6
3922         case KEYCODE_FUN7:      // Function Key F7
3923         case KEYCODE_FUN8:      // Function Key F8
3924         case KEYCODE_FUN9:      // Function Key F9
3925         case KEYCODE_FUN10:     // Function Key F10
3926         case KEYCODE_FUN11:     // Function Key F11
3927         case KEYCODE_FUN12:     // Function Key F12
3928                 break;
3929 #endif
3930         }
3931
3932  dc1:
3933         // if text[] just became empty, add back an empty line
3934         if (end == text) {
3935                 char_insert(text, '\n', NO_UNDO);       // start empty buf with dummy line
3936                 dot = text;
3937         }
3938         // it is OK for dot to exactly equal to end, otherwise check dot validity
3939         if (dot != end) {
3940                 dot = bound_dot(dot);   // make sure "dot" is valid
3941         }
3942 #if ENABLE_FEATURE_VI_YANKMARK
3943         check_context(c);       // update the current context
3944 #endif
3945
3946         if (!isdigit(c))
3947                 cmdcnt = 0;             // cmd was not a number, reset cmdcnt
3948         cnt = dot - begin_line(dot);
3949         // Try to stay off of the Newline
3950         if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3951                 dot--;
3952 }
3953
3954 // NB!  the CRASHME code is unmaintained, and doesn't currently build
3955 #if ENABLE_FEATURE_VI_CRASHME
3956 static int totalcmds = 0;
3957 static int Mp = 85;             // Movement command Probability
3958 static int Np = 90;             // Non-movement command Probability
3959 static int Dp = 96;             // Delete command Probability
3960 static int Ip = 97;             // Insert command Probability
3961 static int Yp = 98;             // Yank command Probability
3962 static int Pp = 99;             // Put command Probability
3963 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3964 static const char chars[20] = "\t012345 abcdABCD-=.$";
3965 static const char *const words[20] = {
3966         "this", "is", "a", "test",
3967         "broadcast", "the", "emergency", "of",
3968         "system", "quick", "brown", "fox",
3969         "jumped", "over", "lazy", "dogs",
3970         "back", "January", "Febuary", "March"
3971 };
3972 static const char *const lines[20] = {
3973         "You should have received a copy of the GNU General Public License\n",
3974         "char c, cm, *cmd, *cmd1;\n",
3975         "generate a command by percentages\n",
3976         "Numbers may be typed as a prefix to some commands.\n",
3977         "Quit, discarding changes!\n",
3978         "Forced write, if permission originally not valid.\n",
3979         "In general, any ex or ed command (such as substitute or delete).\n",
3980         "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3981         "Please get w/ me and I will go over it with you.\n",
3982         "The following is a list of scheduled, committed changes.\n",
3983         "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3984         "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3985         "Any question about transactions please contact Sterling Huxley.\n",
3986         "I will try to get back to you by Friday, December 31.\n",
3987         "This Change will be implemented on Friday.\n",
3988         "Let me know if you have problems accessing this;\n",
3989         "Sterling Huxley recently added you to the access list.\n",
3990         "Would you like to go to lunch?\n",
3991         "The last command will be automatically run.\n",
3992         "This is too much english for a computer geek.\n",
3993 };
3994 static char *multilines[20] = {
3995         "You should have received a copy of the GNU General Public License\n",
3996         "char c, cm, *cmd, *cmd1;\n",
3997         "generate a command by percentages\n",
3998         "Numbers may be typed as a prefix to some commands.\n",
3999         "Quit, discarding changes!\n",
4000         "Forced write, if permission originally not valid.\n",
4001         "In general, any ex or ed command (such as substitute or delete).\n",
4002         "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4003         "Please get w/ me and I will go over it with you.\n",
4004         "The following is a list of scheduled, committed changes.\n",
4005         "1.   Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4006         "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4007         "Any question about transactions please contact Sterling Huxley.\n",
4008         "I will try to get back to you by Friday, December 31.\n",
4009         "This Change will be implemented on Friday.\n",
4010         "Let me know if you have problems accessing this;\n",
4011         "Sterling Huxley recently added you to the access list.\n",
4012         "Would you like to go to lunch?\n",
4013         "The last command will be automatically run.\n",
4014         "This is too much english for a computer geek.\n",
4015 };
4016
4017 // create a random command to execute
4018 static void crash_dummy()
4019 {
4020         static int sleeptime;   // how long to pause between commands
4021         char c, cm, *cmd, *cmd1;
4022         int i, cnt, thing, rbi, startrbi, percent;
4023
4024         // "dot" movement commands
4025         cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4026
4027         // is there already a command running?
4028         if (readbuffer[0] > 0)
4029                 goto cd1;
4030  cd0:
4031         readbuffer[0] = 'X';
4032         startrbi = rbi = 1;
4033         sleeptime = 0;          // how long to pause between commands
4034         memset(readbuffer, '\0', sizeof(readbuffer));
4035         // generate a command by percentages
4036         percent = (int) lrand48() % 100;        // get a number from 0-99
4037         if (percent < Mp) {     //  Movement commands
4038                 // available commands
4039                 cmd = cmd1;
4040                 M++;
4041         } else if (percent < Np) {      //  non-movement commands
4042                 cmd = "mz<>\'\"";       // available commands
4043                 N++;
4044         } else if (percent < Dp) {      //  Delete commands
4045                 cmd = "dx";             // available commands
4046                 D++;
4047         } else if (percent < Ip) {      //  Inset commands
4048                 cmd = "iIaAsrJ";        // available commands
4049                 I++;
4050         } else if (percent < Yp) {      //  Yank commands
4051                 cmd = "yY";             // available commands
4052                 Y++;
4053         } else if (percent < Pp) {      //  Put commands
4054                 cmd = "pP";             // available commands
4055                 P++;
4056         } else {
4057                 // We do not know how to handle this command, try again
4058                 U++;
4059                 goto cd0;
4060         }
4061         // randomly pick one of the available cmds from "cmd[]"
4062         i = (int) lrand48() % strlen(cmd);
4063         cm = cmd[i];
4064         if (strchr(":\024", cm))
4065                 goto cd0;               // dont allow colon or ctrl-T commands
4066         readbuffer[rbi++] = cm; // put cmd into input buffer
4067
4068         // now we have the command-
4069         // there are 1, 2, and multi char commands
4070         // find out which and generate the rest of command as necessary
4071         if (strchr("dmryz<>\'\"", cm)) {        // 2-char commands
4072                 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4073                 if (cm == 'm' || cm == '\'' || cm == '\"') {    // pick a reg[]
4074                         cmd1 = "abcdefghijklmnopqrstuvwxyz";
4075                 }
4076                 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4077                 c = cmd1[thing];
4078                 readbuffer[rbi++] = c;  // add movement to input buffer
4079         }
4080         if (strchr("iIaAsc", cm)) {     // multi-char commands
4081                 if (cm == 'c') {
4082                         // change some thing
4083                         thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4084                         c = cmd1[thing];
4085                         readbuffer[rbi++] = c;  // add movement to input buffer
4086                 }
4087                 thing = (int) lrand48() % 4;    // what thing to insert
4088                 cnt = (int) lrand48() % 10;     // how many to insert
4089                 for (i = 0; i < cnt; i++) {
4090                         if (thing == 0) {       // insert chars
4091                                 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4092                         } else if (thing == 1) {        // insert words
4093                                 strcat(readbuffer, words[(int) lrand48() % 20]);
4094                                 strcat(readbuffer, " ");
4095                                 sleeptime = 0;  // how fast to type
4096                         } else if (thing == 2) {        // insert lines
4097                                 strcat(readbuffer, lines[(int) lrand48() % 20]);
4098                                 sleeptime = 0;  // how fast to type
4099                         } else {        // insert multi-lines
4100                                 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4101                                 sleeptime = 0;  // how fast to type
4102                         }
4103                 }
4104                 strcat(readbuffer, ESC);
4105         }
4106         readbuffer[0] = strlen(readbuffer + 1);
4107  cd1:
4108         totalcmds++;
4109         if (sleeptime > 0)
4110                 mysleep(sleeptime);      // sleep 1/100 sec
4111 }
4112
4113 // test to see if there are any errors
4114 static void crash_test()
4115 {
4116         static time_t oldtim;
4117
4118         time_t tim;
4119         char d[2], msg[80];
4120
4121         msg[0] = '\0';
4122         if (end < text) {
4123                 strcat(msg, "end<text ");
4124         }
4125         if (end > textend) {
4126                 strcat(msg, "end>textend ");
4127         }
4128         if (dot < text) {
4129                 strcat(msg, "dot<text ");
4130         }
4131         if (dot > end) {
4132                 strcat(msg, "dot>end ");
4133         }
4134         if (screenbegin < text) {
4135                 strcat(msg, "screenbegin<text ");
4136         }
4137         if (screenbegin > end - 1) {
4138                 strcat(msg, "screenbegin>end-1 ");
4139         }
4140
4141         if (msg[0]) {
4142                 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4143                         totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4144                 fflush_all();
4145                 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4146                         if (d[0] == '\n' || d[0] == '\r')
4147                                 break;
4148                 }
4149         }
4150         tim = time(NULL);
4151         if (tim >= (oldtim + 3)) {
4152                 sprintf(status_buffer,
4153                                 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4154                                 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
4155                 oldtim = tim;
4156         }
4157 }
4158 #endif
4159
4160 static void edit_file(char *fn)
4161 {
4162 #if ENABLE_FEATURE_VI_YANKMARK
4163 #define cur_line edit_file__cur_line
4164 #endif
4165         int c;
4166 #if ENABLE_FEATURE_VI_USE_SIGNALS
4167         int sig;
4168 #endif
4169
4170         editing = 1;    // 0 = exit, 1 = one file, 2 = multiple files
4171         rawmode();
4172         rows = 24;
4173         columns = 80;
4174         IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
4175 #if ENABLE_FEATURE_VI_ASK_TERMINAL
4176         if (G.get_rowcol_error /* TODO? && no input on stdin */) {
4177                 uint64_t k;
4178                 write1(ESC"[999;999H" ESC"[6n");
4179                 fflush_all();
4180                 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4181                 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4182                         uint32_t rc = (k >> 32);
4183                         columns = (rc & 0x7fff);
4184                         if (columns > MAX_SCR_COLS)
4185                                 columns = MAX_SCR_COLS;
4186                         rows = ((rc >> 16) & 0x7fff);
4187                         if (rows > MAX_SCR_ROWS)
4188                                 rows = MAX_SCR_ROWS;
4189                 }
4190         }
4191 #endif
4192         new_screen(rows, columns);      // get memory for virtual screen
4193         init_text_buffer(fn);
4194
4195 #if ENABLE_FEATURE_VI_YANKMARK
4196         YDreg = 26;                     // default Yank/Delete reg
4197 //      Ureg = 27; - const              // hold orig line for "U" cmd
4198         mark[26] = mark[27] = text;     // init "previous context"
4199 #endif
4200
4201         last_forward_char = '\0';
4202 #if ENABLE_FEATURE_VI_CRASHME
4203         last_input_char = '\0';
4204 #endif
4205         crow = 0;
4206         ccol = 0;
4207
4208 #if ENABLE_FEATURE_VI_USE_SIGNALS
4209         signal(SIGWINCH, winch_handler);
4210         signal(SIGTSTP, tstp_handler);
4211         sig = sigsetjmp(restart, 1);
4212         if (sig != 0) {
4213                 screenbegin = dot = text;
4214         }
4215         // int_handler() can jump to "restart",
4216         // must install handler *after* initializing "restart"
4217         signal(SIGINT, int_handler);
4218 #endif
4219
4220         cmd_mode = 0;           // 0=command  1=insert  2='R'eplace
4221         cmdcnt = 0;
4222         tabstop = 8;
4223         offset = 0;                     // no horizontal offset
4224         c = '\0';
4225 #if ENABLE_FEATURE_VI_DOT_CMD
4226         free(ioq_start);
4227         ioq_start = NULL;
4228         lmc_len = 0;
4229         adding2q = 0;
4230 #endif
4231
4232 #if ENABLE_FEATURE_VI_COLON
4233         {
4234                 char *p, *q;
4235                 int n = 0;
4236
4237                 while ((p = initial_cmds[n]) != NULL) {
4238                         do {
4239                                 q = p;
4240                                 p = strchr(q, '\n');
4241                                 if (p)
4242                                         while (*p == '\n')
4243                                                 *p++ = '\0';
4244                                 if (*q)
4245                                         colon(q);
4246                         } while (p);
4247                         free(initial_cmds[n]);
4248                         initial_cmds[n] = NULL;
4249                         n++;
4250                 }
4251         }
4252 #endif
4253         redraw(FALSE);                  // dont force every col re-draw
4254         //------This is the main Vi cmd handling loop -----------------------
4255         while (editing > 0) {
4256 #if ENABLE_FEATURE_VI_CRASHME
4257                 if (crashme > 0) {
4258                         if ((end - text) > 1) {
4259                                 crash_dummy();  // generate a random command
4260                         } else {
4261                                 crashme = 0;
4262                                 string_insert(text, "\n\n#####  Ran out of text to work on.  #####\n\n", NO_UNDO);
4263                                 dot = text;
4264                                 refresh(FALSE);
4265                         }
4266                 }
4267 #endif
4268                 c = get_one_char();     // get a cmd from user
4269 #if ENABLE_FEATURE_VI_CRASHME
4270                 last_input_char = c;
4271 #endif
4272 #if ENABLE_FEATURE_VI_YANKMARK
4273                 // save a copy of the current line- for the 'U" command
4274                 if (begin_line(dot) != cur_line) {
4275                         cur_line = begin_line(dot);
4276                         text_yank(begin_line(dot), end_line(dot), Ureg);
4277                 }
4278 #endif
4279 #if ENABLE_FEATURE_VI_DOT_CMD
4280                 // If c is a command that changes text[],
4281                 // (re)start remembering the input for the "." command.
4282                 if (!adding2q
4283                  && ioq_start == NULL
4284                  && cmd_mode == 0 // command mode
4285                  && c > '\0' // exclude NUL and non-ASCII chars
4286                  && c < 0x7f // (Unicode and such)
4287                  && strchr(modifying_cmds, c)
4288                 ) {
4289                         start_new_cmd_q(c);
4290                 }
4291 #endif
4292                 do_cmd(c);              // execute the user command
4293
4294                 // poll to see if there is input already waiting. if we are
4295                 // not able to display output fast enough to keep up, skip
4296                 // the display update until we catch up with input.
4297                 if (!readbuffer[0] && mysleep(0) == 0) {
4298                         // no input pending - so update output
4299                         refresh(FALSE);
4300                         show_status_line();
4301                 }
4302 #if ENABLE_FEATURE_VI_CRASHME
4303                 if (crashme > 0)
4304                         crash_test();   // test editor variables
4305 #endif
4306         }
4307         //-------------------------------------------------------------------
4308
4309         go_bottom_and_clear_to_eol();
4310         cookmode();
4311 #undef cur_line
4312 }
4313
4314 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
4315 int vi_main(int argc, char **argv)
4316 {
4317         int c;
4318
4319         INIT_G();
4320
4321 #if ENABLE_FEATURE_VI_UNDO
4322         //undo_stack_tail = NULL; - already is
4323 # if ENABLE_FEATURE_VI_UNDO_QUEUE
4324         undo_queue_state = UNDO_EMPTY;
4325         //undo_q = 0; - already is
4326 # endif
4327 #endif
4328
4329 #if ENABLE_FEATURE_VI_CRASHME
4330         srand((long) getpid());
4331 #endif
4332 #ifdef NO_SUCH_APPLET_YET
4333         // if we aren't "vi", we are "view"
4334         if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
4335                 SET_READONLY_MODE(readonly_mode);
4336         }
4337 #endif
4338
4339         // autoindent is not default in vim 7.3
4340         vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
4341         //  1-  process $HOME/.exrc file (not inplemented yet)
4342         //  2-  process EXINIT variable from environment
4343         //  3-  process command line args
4344 #if ENABLE_FEATURE_VI_COLON
4345         {
4346                 char *p = getenv("EXINIT");
4347                 if (p && *p)
4348                         initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
4349         }
4350 #endif
4351         while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
4352                 switch (c) {
4353 #if ENABLE_FEATURE_VI_CRASHME
4354                 case 'C':
4355                         crashme = 1;
4356                         break;
4357 #endif
4358 #if ENABLE_FEATURE_VI_READONLY
4359                 case 'R':               // Read-only flag
4360                         SET_READONLY_MODE(readonly_mode);
4361                         break;
4362 #endif
4363 #if ENABLE_FEATURE_VI_COLON
4364                 case 'c':               // cmd line vi command
4365                         if (*optarg)
4366                                 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
4367                         break;
4368 #endif
4369                 case 'H':
4370                         show_help();
4371                         // fall through
4372                 default:
4373                         bb_show_usage();
4374                         return 1;
4375                 }
4376         }
4377
4378         argv += optind;
4379         cmdline_filecnt = argc - optind;
4380
4381         // "Save cursor, use alternate screen buffer, clear screen"
4382         write1(ESC"[?1049h");
4383         // This is the main file handling loop
4384         optind = 0;
4385         while (1) {
4386                 edit_file(argv[optind]); // might be NULL on 1st iteration
4387                 // NB: optind can be changed by ":next" and ":rewind" commands
4388                 optind++;
4389                 if (optind >= cmdline_filecnt)
4390                         break;
4391         }
4392         // "Use normal screen buffer, restore cursor"
4393         write1(ESC"[?1049l");
4394
4395         return 0;
4396 }