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