1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
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"
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.
32 //config:config FEATURE_VI_MAX_LEN
33 //config: int "Maximum screen width"
34 //config: range 256 16384
35 //config: default 4096
36 //config: depends on VI
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.
41 //config:config FEATURE_VI_8BIT
42 //config: bool "Allow to display 8-bit chars (otherwise shows dots)"
44 //config: depends on VI
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.
51 //config:config FEATURE_VI_COLON
52 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
54 //config: depends on VI
56 //config: Enable a limited set of colon commands. This does not
57 //config: provide an "ex" mode.
59 //config:config FEATURE_VI_YANKMARK
60 //config: bool "Enable yank/put commands and mark cmds"
62 //config: depends on VI
64 //config: This will enable you to use yank and put, as well as mark.
66 //config:config FEATURE_VI_SEARCH
67 //config: bool "Enable search and replace cmds"
69 //config: depends on VI
71 //config: Select this if you wish to be able to do search and replace.
73 //config:config FEATURE_VI_REGEX_SEARCH
74 //config: bool "Enable regex in search and replace"
75 //config: default n # Uses GNU regex, which may be unavailable. FIXME
76 //config: depends on FEATURE_VI_SEARCH
78 //config: Use extended regex search.
80 //config:config FEATURE_VI_USE_SIGNALS
81 //config: bool "Catch signals"
83 //config: depends on VI
85 //config: Selecting this option will make vi signal aware. This will support
86 //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
88 //config:config FEATURE_VI_DOT_CMD
89 //config: bool "Remember previous cmd and \".\" cmd"
91 //config: depends on VI
93 //config: Make vi remember the last command and be able to repeat it.
95 //config:config FEATURE_VI_READONLY
96 //config: bool "Enable -R option and \"view\" mode"
98 //config: depends on VI
100 //config: Enable the read-only command line option, which allows the user to
101 //config: open a file in read-only mode.
103 //config:config FEATURE_VI_SETOPTS
104 //config: bool "Enable settable options, ai ic showmatch"
106 //config: depends on VI
108 //config: Enable the editor to set some (ai, ic, showmatch) options.
110 //config:config FEATURE_VI_SET
111 //config: bool "Support :set"
113 //config: depends on VI
115 //config:config FEATURE_VI_WIN_RESIZE
116 //config: bool "Handle window resize"
118 //config: depends on VI
120 //config: Behave nicely with terminals that get resized.
122 //config:config FEATURE_VI_ASK_TERMINAL
123 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
125 //config: depends on VI
127 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
128 //config: this option makes vi perform a last-ditch effort to find it:
129 //config: position cursor to 999,999 and ask terminal to report real
130 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
131 //config: This is not clean but helps a lot on serial lines and such.
133 //config:config FEATURE_VI_UNDO
134 //config: bool "Support undo command \"u\""
136 //config: depends on VI
138 //config: Support the 'u' command to undo insertion, deletion, and replacement
141 //config:config FEATURE_VI_UNDO_QUEUE
142 //config: bool "Enable undo operation queuing"
144 //config: depends on FEATURE_VI_UNDO
146 //config: The vi undo functions can use an intermediate queue to greatly lower
147 //config: malloc() calls and overhead. When the maximum size of this queue is
148 //config: reached, the contents of the queue are committed to the undo stack.
149 //config: This increases the size of the undo code and allows some undo
150 //config: operations (especially un-typing/backspacing) to be far more useful.
152 //config:config FEATURE_VI_UNDO_QUEUE_MAX
153 //config: int "Maximum undo character queue size"
154 //config: default 256
155 //config: range 32 65536
156 //config: depends on FEATURE_VI_UNDO_QUEUE
158 //config: This option sets the number of bytes used at runtime for the queue.
159 //config: Smaller values will create more undo objects and reduce the amount
160 //config: of typed or backspaced characters that are grouped into one undo
161 //config: operation; larger values increase the potential size of each undo
162 //config: and will generally malloc() larger objects and less frequently.
163 //config: Unless you want more (or less) frequent "undo points" while typing,
164 //config: you should probably leave this unchanged.
166 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
168 //kbuild:lib-$(CONFIG_VI) += vi.o
170 //usage:#define vi_trivial_usage
171 //usage: "[OPTIONS] [FILE]..."
172 //usage:#define vi_full_usage "\n\n"
173 //usage: "Edit FILE\n"
174 //usage: IF_FEATURE_VI_COLON(
175 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
177 //usage: IF_FEATURE_VI_READONLY(
178 //usage: "\n -R Read-only"
180 //usage: "\n -H List available features"
183 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
184 #if ENABLE_FEATURE_VI_REGEX_SEARCH
188 /* the CRASHME code is unmaintained, and doesn't currently build */
189 #define ENABLE_FEATURE_VI_CRASHME 0
192 #if ENABLE_LOCALE_SUPPORT
194 #if ENABLE_FEATURE_VI_8BIT
195 //FIXME: this does not work properly for Unicode anyway
196 # define Isprint(c) (isprint)(c)
198 # define Isprint(c) isprint_asciionly(c)
203 /* 0x9b is Meta-ESC */
204 #if ENABLE_FEATURE_VI_8BIT
205 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
207 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
214 MAX_TABSTOP = 32, // sanity limit
215 // User input len. Need not be extra big.
216 // Lines in file being edited *can* be bigger than this.
218 // Sanity limits. We have only one buffer of this size.
219 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
220 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
223 /* VT102 ESC sequences.
224 * See "Xterm Control Sequences"
225 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
227 /* Inverse/Normal text */
228 #define ESC_BOLD_TEXT "\033[7m"
229 #define ESC_NORM_TEXT "\033[0m"
231 #define ESC_BELL "\007"
232 /* Clear-to-end-of-line */
233 #define ESC_CLEAR2EOL "\033[K"
234 /* Clear-to-end-of-screen.
235 * (We use default param here.
236 * Full sequence is "ESC [ <num> J",
237 * <num> is 0/1/2 = "erase below/above/all".)
239 #define ESC_CLEAR2EOS "\033[J"
240 /* Cursor to given coordinate (1,1: top left) */
241 #define ESC_SET_CURSOR_POS "\033[%u;%uH"
243 ///* Cursor up and down */
244 //#define ESC_CURSOR_UP "\033[A"
245 //#define ESC_CURSOR_DOWN "\n"
247 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
248 // cmds modifying text[]
249 // vda: removed "aAiIs" as they switch us into insert mode
250 // and remembering input for replay after them makes no sense
251 static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
257 FORWARD = 1, // code depends on "1" for array index
258 BACK = -1, // code depends on "-1" for array index
259 LIMITED = 0, // how much of text[] in char_search
260 FULL = 1, // how much of text[] in char_search
262 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
263 S_TO_WS = 2, // used in skip_thing() for moving "dot"
264 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
265 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
266 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
270 /* vi.c expects chars to be unsigned. */
271 /* busybox build system provides that, but it's better */
272 /* to audit and fix the source */
275 /* many references - keep near the top of globals */
276 char *text, *end; // pointers to the user data in memory
277 char *dot; // where all the action takes place
278 int text_size; // size of the allocated buffer
282 #define VI_AUTOINDENT 1
283 #define VI_SHOWMATCH 2
284 #define VI_IGNORECASE 4
285 #define VI_ERR_METHOD 8
286 #define autoindent (vi_setops & VI_AUTOINDENT)
287 #define showmatch (vi_setops & VI_SHOWMATCH )
288 #define ignorecase (vi_setops & VI_IGNORECASE)
289 /* indicate error with beep or flash */
290 #define err_method (vi_setops & VI_ERR_METHOD)
292 #if ENABLE_FEATURE_VI_READONLY
293 smallint readonly_mode;
294 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
295 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
296 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
298 #define SET_READONLY_FILE(flags) ((void)0)
299 #define SET_READONLY_MODE(flags) ((void)0)
300 #define UNSET_READONLY_FILE(flags) ((void)0)
303 smallint editing; // >0 while we are editing a file
304 // [code audit says "can be 0, 1 or 2 only"]
305 smallint cmd_mode; // 0=command 1=insert 2=replace
306 int modified_count; // buffer contents changed if !0
307 int last_modified_count; // = -1;
308 int save_argc; // how many file names on cmd line
309 int cmdcnt; // repetition count
310 unsigned rows, columns; // the terminal screen is this size
311 #if ENABLE_FEATURE_VI_ASK_TERMINAL
312 int get_rowcol_error;
314 int crow, ccol; // cursor is on Crow x Ccol
315 int offset; // chars scrolled off the screen to the left
316 int have_status_msg; // is default edit status needed?
317 // [don't make smallint!]
318 int last_status_cksum; // hash of current status line
319 char *current_filename;
320 char *screenbegin; // index into text[], of top line on the screen
321 char *screen; // pointer to the virtual screen buffer
322 int screensize; // and its size
324 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
325 char erase_char; // the users erase character
326 char last_input_char; // last char read from user
328 #if ENABLE_FEATURE_VI_DOT_CMD
329 smallint adding2q; // are we currently adding user input to q
330 int lmc_len; // length of last_modifying_cmd
331 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
333 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
336 #if ENABLE_FEATURE_VI_SEARCH
337 char *last_search_pattern; // last pattern from a '/' or '?' search
341 #if ENABLE_FEATURE_VI_YANKMARK
342 char *edit_file__cur_line;
344 int refresh__old_offset;
345 int format_edit_status__tot;
347 /* a few references only */
348 #if ENABLE_FEATURE_VI_YANKMARK
349 int YDreg, Ureg; // default delete register and orig line for "U"
350 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
351 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
352 char *context_start, *context_end;
354 #if ENABLE_FEATURE_VI_USE_SIGNALS
355 sigjmp_buf restart; // catch_sig()
357 struct termios term_orig; // remember what the cooked mode was
358 #if ENABLE_FEATURE_VI_COLON
359 char *initial_cmds[3]; // currently 2 entries, NULL terminated
361 // Should be just enough to hold a key sequence,
362 // but CRASHME mode uses it as generated command buffer too
363 #if ENABLE_FEATURE_VI_CRASHME
364 char readbuffer[128];
366 char readbuffer[KEYCODE_BUFFER_SIZE];
368 #define STATUS_BUFFER_LEN 200
369 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
370 #if ENABLE_FEATURE_VI_DOT_CMD
371 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
373 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
375 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
376 #if ENABLE_FEATURE_VI_UNDO
377 // undo_push() operations
380 #define UNDO_INS_CHAIN 2
381 #define UNDO_DEL_CHAIN 3
382 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
383 #define UNDO_QUEUED_FLAG 4
384 #define UNDO_INS_QUEUED 4
385 #define UNDO_DEL_QUEUED 5
386 #define UNDO_USE_SPOS 32
387 #define UNDO_EMPTY 64
388 // Pass-through flags for functions that can be undone
391 #define ALLOW_UNDO_CHAIN 2
392 # if ENABLE_FEATURE_VI_UNDO_QUEUE
393 #define ALLOW_UNDO_QUEUED 3
394 char undo_queue_state;
396 char *undo_queue_spos; // Start position of queued operation
397 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
399 // If undo queuing disabled, don't invoke the missing queue logic
400 #define ALLOW_UNDO_QUEUED 1
404 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
405 int start; // Offset where the data should be restored/deleted
406 int length; // total data size
407 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
408 char undo_text[1]; // text that was deleted (if deletion)
410 #endif /* ENABLE_FEATURE_VI_UNDO */
412 #define G (*ptr_to_globals)
413 #define text (G.text )
414 #define text_size (G.text_size )
419 #define vi_setops (G.vi_setops )
420 #define editing (G.editing )
421 #define cmd_mode (G.cmd_mode )
422 #define modified_count (G.modified_count )
423 #define last_modified_count (G.last_modified_count)
424 #define save_argc (G.save_argc )
425 #define cmdcnt (G.cmdcnt )
426 #define rows (G.rows )
427 #define columns (G.columns )
428 #define crow (G.crow )
429 #define ccol (G.ccol )
430 #define offset (G.offset )
431 #define status_buffer (G.status_buffer )
432 #define have_status_msg (G.have_status_msg )
433 #define last_status_cksum (G.last_status_cksum )
434 #define current_filename (G.current_filename )
435 #define screen (G.screen )
436 #define screensize (G.screensize )
437 #define screenbegin (G.screenbegin )
438 #define tabstop (G.tabstop )
439 #define last_forward_char (G.last_forward_char )
440 #define erase_char (G.erase_char )
441 #define last_input_char (G.last_input_char )
442 #if ENABLE_FEATURE_VI_READONLY
443 #define readonly_mode (G.readonly_mode )
445 #define readonly_mode 0
447 #define adding2q (G.adding2q )
448 #define lmc_len (G.lmc_len )
450 #define ioq_start (G.ioq_start )
451 #define my_pid (G.my_pid )
452 #define last_search_pattern (G.last_search_pattern)
454 #define edit_file__cur_line (G.edit_file__cur_line)
455 #define refresh__old_offset (G.refresh__old_offset)
456 #define format_edit_status__tot (G.format_edit_status__tot)
458 #define YDreg (G.YDreg )
459 #define Ureg (G.Ureg )
460 #define mark (G.mark )
461 #define context_start (G.context_start )
462 #define context_end (G.context_end )
463 #define restart (G.restart )
464 #define term_orig (G.term_orig )
465 #define initial_cmds (G.initial_cmds )
466 #define readbuffer (G.readbuffer )
467 #define scr_out_buf (G.scr_out_buf )
468 #define last_modifying_cmd (G.last_modifying_cmd )
469 #define get_input_line__buf (G.get_input_line__buf)
471 #if ENABLE_FEATURE_VI_UNDO
472 #define undo_stack_tail (G.undo_stack_tail )
473 # if ENABLE_FEATURE_VI_UNDO_QUEUE
474 #define undo_queue_state (G.undo_queue_state)
475 #define undo_q (G.undo_q )
476 #define undo_queue (G.undo_queue )
477 #define undo_queue_spos (G.undo_queue_spos )
481 #define INIT_G() do { \
482 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
483 last_modified_count = -1; \
484 /* "" but has space for 2 chars: */ \
485 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
489 static void edit_file(char *); // edit one file
490 static void do_cmd(int); // execute a command
491 static int next_tabstop(int);
492 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
493 static char *begin_line(char *); // return pointer to cur line B-o-l
494 static char *end_line(char *); // return pointer to cur line E-o-l
495 static char *prev_line(char *); // return pointer to prev line B-o-l
496 static char *next_line(char *); // return pointer to next line B-o-l
497 static char *end_screen(void); // get pointer to last char on screen
498 static int count_lines(char *, char *); // count line from start to stop
499 static char *find_line(int); // find beginning of line #li
500 static char *move_to_col(char *, int); // move "p" to column l
501 static void dot_left(void); // move dot left- dont leave line
502 static void dot_right(void); // move dot right- dont leave line
503 static void dot_begin(void); // move dot to B-o-l
504 static void dot_end(void); // move dot to E-o-l
505 static void dot_next(void); // move dot to next line B-o-l
506 static void dot_prev(void); // move dot to prev line B-o-l
507 static void dot_scroll(int, int); // move the screen up or down
508 static void dot_skip_over_ws(void); // move dot pat WS
509 static char *bound_dot(char *); // make sure text[0] <= P < "end"
510 static char *new_screen(int, int); // malloc virtual screen memory
511 #if !ENABLE_FEATURE_VI_UNDO
512 #define char_insert(a,b,c) char_insert(a,b)
514 static char *char_insert(char *, char, int); // insert the char c at 'p'
515 // might reallocate text[]! use p += stupid_insert(p, ...),
516 // and be careful to not use pointers into potentially freed text[]!
517 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
518 static int find_range(char **, char **, char); // return pointers for an object
519 static int st_test(char *, int, int, char *); // helper for skip_thing()
520 static char *skip_thing(char *, int, int, int); // skip some object
521 static char *find_pair(char *, char); // find matching pair () [] {}
522 #if !ENABLE_FEATURE_VI_UNDO
523 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
525 static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
526 // might reallocate text[]! use p += text_hole_make(p, ...),
527 // and be careful to not use pointers into potentially freed text[]!
528 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
529 #if !ENABLE_FEATURE_VI_UNDO
530 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
532 static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
533 static void show_help(void); // display some help info
534 static void rawmode(void); // set "raw" mode on tty
535 static void cookmode(void); // return to "cooked" mode on tty
536 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
537 static int mysleep(int);
538 static int readit(void); // read (maybe cursor) key from stdin
539 static int get_one_char(void); // read 1 char from stdin
540 // file_insert might reallocate text[]!
541 static int file_insert(const char *, char *, int);
542 static int file_write(char *, char *, char *);
543 static void place_cursor(int, int);
544 static void screen_erase(void);
545 static void clear_to_eol(void);
546 static void clear_to_eos(void);
547 static void go_bottom_and_clear_to_eol(void);
548 static void standout_start(void); // send "start reverse video" sequence
549 static void standout_end(void); // send "end reverse video" sequence
550 static void flash(int); // flash the terminal screen
551 static void show_status_line(void); // put a message on the bottom line
552 static void status_line(const char *, ...); // print to status buf
553 static void status_line_bold(const char *, ...);
554 static void status_line_bold_errno(const char *fn);
555 static void not_implemented(const char *); // display "Not implemented" message
556 static int format_edit_status(void); // format file status on status line
557 static void redraw(int); // force a full screen refresh
558 static char* format_line(char* /*, int*/);
559 static void refresh(int); // update the terminal from screen[]
561 static void indicate_error(void); // use flash or beep to indicate error
562 static void Hit_Return(void);
564 #if ENABLE_FEATURE_VI_SEARCH
565 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
567 #if ENABLE_FEATURE_VI_COLON
568 static char *get_one_address(char *, int *); // get colon addr, if present
569 static char *get_address(char *, int *, int *); // get two colon addrs, if present
571 static void colon(char *); // execute the "colon" mode cmds
572 #if ENABLE_FEATURE_VI_USE_SIGNALS
573 static void winch_sig(int); // catch window size changes
574 static void suspend_sig(int); // catch ctrl-Z
575 static void catch_sig(int); // catch ctrl-C and alarm time-outs
577 #if ENABLE_FEATURE_VI_DOT_CMD
578 static void start_new_cmd_q(char); // new queue for command
579 static void end_cmd_q(void); // stop saving input chars
581 #define end_cmd_q() ((void)0)
583 #if ENABLE_FEATURE_VI_SETOPTS
584 static void showmatching(char *); // show the matching pair () [] {}
586 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
587 // might reallocate text[]! use p += string_insert(p, ...),
588 // and be careful to not use pointers into potentially freed text[]!
589 # if !ENABLE_FEATURE_VI_UNDO
590 #define string_insert(a,b,c) string_insert(a,b)
592 static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
594 #if ENABLE_FEATURE_VI_YANKMARK
595 static char *text_yank(char *, char *, int); // save copy of "p" into a register
596 static char what_reg(void); // what is letter of current YDreg
597 static void check_context(char); // remember context for '' command
599 #if ENABLE_FEATURE_VI_UNDO
600 static void flush_undo_data(void);
601 static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
602 static void undo_pop(void); // Undo the last operation
603 # if ENABLE_FEATURE_VI_UNDO_QUEUE
604 static void undo_queue_commit(void); // Flush any queued objects to the undo stack
606 # define undo_queue_commit() ((void)0)
609 #define flush_undo_data() ((void)0)
610 #define undo_queue_commit() ((void)0)
613 #if ENABLE_FEATURE_VI_CRASHME
614 static void crash_dummy();
615 static void crash_test();
616 static int crashme = 0;
619 static void write1(const char *out)
624 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
625 int vi_main(int argc, char **argv)
631 #if ENABLE_FEATURE_VI_UNDO
632 /* undo_stack_tail = NULL; - already is */
633 #if ENABLE_FEATURE_VI_UNDO_QUEUE
634 undo_queue_state = UNDO_EMPTY;
635 /* undo_q = 0; - already is */
639 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
642 #if ENABLE_FEATURE_VI_CRASHME
643 srand((long) my_pid);
645 #ifdef NO_SUCH_APPLET_YET
646 /* If we aren't "vi", we are "view" */
647 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
648 SET_READONLY_MODE(readonly_mode);
652 // autoindent is not default in vim 7.3
653 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
654 // 1- process $HOME/.exrc file (not inplemented yet)
655 // 2- process EXINIT variable from environment
656 // 3- process command line args
657 #if ENABLE_FEATURE_VI_COLON
659 char *p = getenv("EXINIT");
661 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
664 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
666 #if ENABLE_FEATURE_VI_CRASHME
671 #if ENABLE_FEATURE_VI_READONLY
672 case 'R': // Read-only flag
673 SET_READONLY_MODE(readonly_mode);
676 #if ENABLE_FEATURE_VI_COLON
677 case 'c': // cmd line vi command
679 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
691 // The argv array can be used by the ":next" and ":rewind" commands
695 //----- This is the main file handling loop --------------
698 // "Save cursor, use alternate screen buffer, clear screen"
699 write1("\033[?1049h");
701 edit_file(argv[optind]); /* param might be NULL */
702 if (++optind >= argc)
705 // "Use normal screen buffer, restore cursor"
706 write1("\033[?1049l");
707 //-----------------------------------------------------------
712 /* read text from file or create an empty buf */
713 /* will also update current_filename */
714 static int init_text_buffer(char *fn)
720 last_modified_count = -1;
721 #if ENABLE_FEATURE_VI_YANKMARK
723 memset(mark, 0, sizeof(mark));
726 /* allocate/reallocate text buffer */
729 screenbegin = dot = end = text = xzalloc(text_size);
731 if (fn != current_filename) {
732 free(current_filename);
733 current_filename = xstrdup(fn);
735 rc = file_insert(fn, text, 1);
737 // file doesnt exist. Start empty buf with dummy line
738 char_insert(text, '\n', NO_UNDO);
743 #if ENABLE_FEATURE_VI_WIN_RESIZE
744 static int query_screen_dimensions(void)
746 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
747 if (rows > MAX_SCR_ROWS)
749 if (columns > MAX_SCR_COLS)
750 columns = MAX_SCR_COLS;
754 # define query_screen_dimensions() (0)
757 static void edit_file(char *fn)
759 #if ENABLE_FEATURE_VI_YANKMARK
760 #define cur_line edit_file__cur_line
763 #if ENABLE_FEATURE_VI_USE_SIGNALS
767 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
771 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
772 #if ENABLE_FEATURE_VI_ASK_TERMINAL
773 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
775 write1("\033[999;999H" "\033[6n");
777 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
778 if ((int32_t)k == KEYCODE_CURSOR_POS) {
779 uint32_t rc = (k >> 32);
780 columns = (rc & 0x7fff);
781 if (columns > MAX_SCR_COLS)
782 columns = MAX_SCR_COLS;
783 rows = ((rc >> 16) & 0x7fff);
784 if (rows > MAX_SCR_ROWS)
789 new_screen(rows, columns); // get memory for virtual screen
790 init_text_buffer(fn);
792 #if ENABLE_FEATURE_VI_YANKMARK
793 YDreg = 26; // default Yank/Delete reg
794 Ureg = 27; // hold orig line for "U" cmd
795 mark[26] = mark[27] = text; // init "previous context"
798 last_forward_char = last_input_char = '\0';
802 #if ENABLE_FEATURE_VI_USE_SIGNALS
803 signal(SIGINT, catch_sig);
804 signal(SIGWINCH, winch_sig);
805 signal(SIGTSTP, suspend_sig);
806 sig = sigsetjmp(restart, 1);
808 screenbegin = dot = text;
812 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
815 offset = 0; // no horizontal offset
817 #if ENABLE_FEATURE_VI_DOT_CMD
819 ioq = ioq_start = NULL;
824 #if ENABLE_FEATURE_VI_COLON
829 while ((p = initial_cmds[n]) != NULL) {
839 free(initial_cmds[n]);
840 initial_cmds[n] = NULL;
845 redraw(FALSE); // dont force every col re-draw
846 //------This is the main Vi cmd handling loop -----------------------
847 while (editing > 0) {
848 #if ENABLE_FEATURE_VI_CRASHME
850 if ((end - text) > 1) {
851 crash_dummy(); // generate a random command
854 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
860 last_input_char = c = get_one_char(); // get a cmd from user
861 #if ENABLE_FEATURE_VI_YANKMARK
862 // save a copy of the current line- for the 'U" command
863 if (begin_line(dot) != cur_line) {
864 cur_line = begin_line(dot);
865 text_yank(begin_line(dot), end_line(dot), Ureg);
868 #if ENABLE_FEATURE_VI_DOT_CMD
869 // These are commands that change text[].
870 // Remember the input for the "." command
871 if (!adding2q && ioq_start == NULL
872 && cmd_mode == 0 // command mode
873 && c > '\0' // exclude NUL and non-ASCII chars
874 && c < 0x7f // (Unicode and such)
875 && strchr(modifying_cmds, c)
880 do_cmd(c); // execute the user command
882 // poll to see if there is input already waiting. if we are
883 // not able to display output fast enough to keep up, skip
884 // the display update until we catch up with input.
885 if (!readbuffer[0] && mysleep(0) == 0) {
886 // no input pending - so update output
890 #if ENABLE_FEATURE_VI_CRASHME
892 crash_test(); // test editor variables
895 //-------------------------------------------------------------------
897 go_bottom_and_clear_to_eol();
902 //----- The Colon commands -------------------------------------
903 #if ENABLE_FEATURE_VI_COLON
904 static char *get_one_address(char *p, int *addr) // get colon addr, if present
908 IF_FEATURE_VI_YANKMARK(char c;)
909 IF_FEATURE_VI_SEARCH(char *pat;)
911 *addr = -1; // assume no addr
912 if (*p == '.') { // the current line
915 *addr = count_lines(text, q);
917 #if ENABLE_FEATURE_VI_YANKMARK
918 else if (*p == '\'') { // is this a mark addr
922 if (c >= 'a' && c <= 'z') {
925 q = mark[(unsigned char) c];
926 if (q != NULL) { // is mark valid
927 *addr = count_lines(text, q);
932 #if ENABLE_FEATURE_VI_SEARCH
933 else if (*p == '/') { // a search pattern
934 q = strchrnul(++p, '/');
935 pat = xstrndup(p, q - p); // save copy of pattern
939 q = char_search(dot, pat, FORWARD, FULL);
941 *addr = count_lines(text, q);
946 else if (*p == '$') { // the last line in file
948 q = begin_line(end - 1);
949 *addr = count_lines(text, q);
950 } else if (isdigit(*p)) { // specific line number
951 sscanf(p, "%d%n", addr, &st);
954 // unrecognized address - assume -1
960 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
962 //----- get the address' i.e., 1,3 'a,'b -----
963 // get FIRST addr, if present
965 p++; // skip over leading spaces
966 if (*p == '%') { // alias for 1,$
969 *e = count_lines(text, end-1);
972 p = get_one_address(p, b);
975 if (*p == ',') { // is there a address separator
979 // get SECOND addr, if present
980 p = get_one_address(p, e);
984 p++; // skip over trailing spaces
988 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
989 static void setops(const char *args, const char *opname, int flg_no,
990 const char *short_opname, int opt)
992 const char *a = args + flg_no;
993 int l = strlen(opname) - 1; /* opname have + ' ' */
995 // maybe strncmp? we had tons of erroneous strncasecmp's...
996 if (strncasecmp(a, opname, l) == 0
997 || strncasecmp(a, short_opname, 2) == 0
1007 #endif /* FEATURE_VI_COLON */
1009 // buf must be no longer than MAX_INPUT_LEN!
1010 static void colon(char *buf)
1012 #if !ENABLE_FEATURE_VI_COLON
1013 /* Simple ":cmd" handler with minimal set of commands */
1022 if (strncmp(p, "quit", cnt) == 0
1023 || strncmp(p, "q!", cnt) == 0
1025 if (modified_count && p[1] != '!') {
1026 status_line_bold("No write since last change (:%s! overrides)", p);
1032 if (strncmp(p, "write", cnt) == 0
1033 || strncmp(p, "wq", cnt) == 0
1034 || strncmp(p, "wn", cnt) == 0
1035 || (p[0] == 'x' && !p[1])
1037 cnt = file_write(current_filename, text, end - 1);
1040 status_line_bold("Write error: %s", strerror(errno));
1043 last_modified_count = -1;
1044 status_line("'%s' %dL, %dC",
1046 count_lines(text, end - 1), cnt
1048 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
1049 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
1056 if (strncmp(p, "file", cnt) == 0) {
1057 last_status_cksum = 0; // force status update
1060 if (sscanf(p, "%d", &cnt) > 0) {
1061 dot = find_line(cnt);
1068 char c, *orig_buf, *buf1, *q, *r;
1069 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
1073 // :3154 // if (-e line 3154) goto it else stay put
1074 // :4,33w! foo // write a portion of buffer to file "foo"
1075 // :w // write all of buffer to current file
1077 // :q! // quit- dont care about modified file
1078 // :'a,'z!sort -u // filter block through sort
1079 // :'f // goto mark "f"
1080 // :'fl // list literal the mark "f" line
1081 // :.r bar // read file "bar" into buffer before dot
1082 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1083 // :/xyz/ // goto the "xyz" line
1084 // :s/find/replace/ // substitute pattern "find" with "replace"
1085 // :!<cmd> // run <cmd> then return
1091 buf++; // move past the ':'
1095 q = text; // assume 1,$ for the range
1097 li = count_lines(text, end - 1);
1098 fn = current_filename;
1100 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1101 buf = get_address(buf, &b, &e);
1103 // remember orig command line
1106 // get the COMMAND into cmd[]
1108 while (*buf != '\0') {
1114 // get any ARGuments
1115 while (isblank(*buf))
1119 buf1 = last_char_is(cmd, '!');
1122 *buf1 = '\0'; // get rid of !
1125 // if there is only one addr, then the addr
1126 // is the line number of the single line the
1127 // user wants. So, reset the end
1128 // pointer to point at end of the "b" line
1129 q = find_line(b); // what line is #b
1134 // we were given two addrs. change the
1135 // end pointer to the addr given by user.
1136 r = find_line(e); // what line is #e
1140 // ------------ now look for the command ------------
1142 if (i == 0) { // :123CR goto line #123
1144 dot = find_line(b); // what line is #b
1148 #if ENABLE_FEATURE_ALLOW_EXEC
1149 else if (cmd[0] == '!') { // run a cmd
1151 // :!ls run the <cmd>
1152 go_bottom_and_clear_to_eol();
1154 retcode = system(orig_buf + 1); // run the cmd
1156 printf("\nshell returned %i\n\n", retcode);
1158 Hit_Return(); // let user see results
1161 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1162 if (b < 0) { // no addr given- use defaults
1163 b = e = count_lines(text, dot);
1165 status_line("%d", b);
1166 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1167 if (b < 0) { // no addr given- use defaults
1168 q = begin_line(dot); // assume .,. for the range
1171 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
1173 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1176 // don't edit, if the current file has been modified
1177 if (modified_count && !useforce) {
1178 status_line_bold("No write since last change (:%s! overrides)", cmd);
1182 // the user supplied a file name
1184 } else if (current_filename && current_filename[0]) {
1185 // no user supplied name- use the current filename
1186 // fn = current_filename; was set by default
1188 // no user file name, no current name- punt
1189 status_line_bold("No current filename");
1193 size = init_text_buffer(fn);
1195 #if ENABLE_FEATURE_VI_YANKMARK
1196 if (Ureg >= 0 && Ureg < 28) {
1197 free(reg[Ureg]); // free orig line reg- for 'U'
1200 if (YDreg >= 0 && YDreg < 28) {
1201 free(reg[YDreg]); // free default yank/delete register
1205 // how many lines in text[]?
1206 li = count_lines(text, end - 1);
1207 status_line("'%s'%s"
1208 IF_FEATURE_VI_READONLY("%s")
1211 (size < 0 ? " [New file]" : ""),
1212 IF_FEATURE_VI_READONLY(
1213 ((readonly_mode) ? " [Readonly]" : ""),
1215 li, (int)(end - text)
1217 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1218 if (b != -1 || e != -1) {
1219 status_line_bold("No address allowed on this command");
1223 // user wants a new filename
1224 free(current_filename);
1225 current_filename = xstrdup(args);
1227 // user wants file status info
1228 last_status_cksum = 0; // force status update
1230 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1231 // print out values of all features
1232 go_bottom_and_clear_to_eol();
1237 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1238 if (b < 0) { // no addr given- use defaults
1239 q = begin_line(dot); // assume .,. for the range
1242 go_bottom_and_clear_to_eol();
1244 for (; q <= r; q++) {
1248 c_is_no_print = (c & 0x80) && !Isprint(c);
1249 if (c_is_no_print) {
1255 } else if (c < ' ' || c == 127) {
1267 } else if (strncmp(cmd, "quit", i) == 0 // quit
1268 || strncmp(cmd, "next", i) == 0 // edit next file
1269 || strncmp(cmd, "prev", i) == 0 // edit previous file
1274 // force end of argv list
1280 // don't exit if the file been modified
1281 if (modified_count) {
1282 status_line_bold("No write since last change (:%s! overrides)", cmd);
1285 // are there other file to edit
1286 n = save_argc - optind - 1;
1287 if (*cmd == 'q' && n > 0) {
1288 status_line_bold("%d more file(s) to edit", n);
1291 if (*cmd == 'n' && n <= 0) {
1292 status_line_bold("No more files to edit");
1296 // are there previous files to edit
1298 status_line_bold("No previous files to edit");
1304 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1309 status_line_bold("No filename given");
1312 if (b < 0) { // no addr given- use defaults
1313 q = begin_line(dot); // assume "dot"
1315 // read after current line- unless user said ":0r foo"
1318 // read after last line
1322 { // dance around potentially-reallocated text[]
1323 uintptr_t ofs = q - text;
1324 size = file_insert(fn, q, 0);
1328 goto ret; // nothing was inserted
1329 // how many lines in text[]?
1330 li = count_lines(q, q + size - 1);
1332 IF_FEATURE_VI_READONLY("%s")
1335 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1339 // if the insert is before "dot" then we need to update
1343 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1344 if (modified_count && !useforce) {
1345 status_line_bold("No write since last change (:%s! overrides)", cmd);
1347 // reset the filenames to edit
1348 optind = -1; /* start from 0th file */
1351 #if ENABLE_FEATURE_VI_SET
1352 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1353 #if ENABLE_FEATURE_VI_SETOPTS
1356 i = 0; // offset into args
1357 // only blank is regarded as args delimiter. What about tab '\t'?
1358 if (!args[0] || strcasecmp(args, "all") == 0) {
1359 // print out values of all options
1360 #if ENABLE_FEATURE_VI_SETOPTS
1367 autoindent ? "" : "no",
1368 err_method ? "" : "no",
1369 ignorecase ? "" : "no",
1370 showmatch ? "" : "no",
1376 #if ENABLE_FEATURE_VI_SETOPTS
1379 if (strncmp(argp, "no", 2) == 0)
1380 i = 2; // ":set noautoindent"
1381 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1382 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1383 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1384 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1385 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1387 sscanf(argp + i+8, "%u", &t);
1388 if (t > 0 && t <= MAX_TABSTOP)
1391 argp = skip_non_whitespace(argp);
1392 argp = skip_whitespace(argp);
1394 #endif /* FEATURE_VI_SETOPTS */
1395 #endif /* FEATURE_VI_SET */
1396 #if ENABLE_FEATURE_VI_SEARCH
1397 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1398 char *F, *R, *flags;
1399 size_t len_F, len_R;
1400 int gflag; // global replace flag
1401 #if ENABLE_FEATURE_VI_UNDO
1402 int dont_chain_first_item = ALLOW_UNDO;
1405 // F points to the "find" pattern
1406 // R points to the "replace" pattern
1407 // replace the cmd line delimiters "/" with NULs
1408 c = orig_buf[1]; // what is the delimiter
1409 F = orig_buf + 2; // start of "find"
1410 R = strchr(F, c); // middle delimiter
1414 *R++ = '\0'; // terminate "find"
1415 flags = strchr(R, c);
1419 *flags++ = '\0'; // terminate "replace"
1423 if (b < 0) { // maybe :s/foo/bar/
1424 q = begin_line(dot); // start with cur line
1425 b = count_lines(text, q); // cur line number
1428 e = b; // maybe :.s/foo/bar/
1430 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1431 char *ls = q; // orig line start
1434 found = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1437 // we found the "find" pattern - delete it
1438 // For undo support, the first item should not be chained
1439 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
1440 #if ENABLE_FEATURE_VI_UNDO
1441 dont_chain_first_item = ALLOW_UNDO_CHAIN;
1443 // insert the "replace" patern
1444 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
1447 /*q += bias; - recalculated anyway */
1448 // check for "global" :s/foo/bar/g
1450 if ((found + len_R) < end_line(ls)) {
1452 goto vc4; // don't let q move past cur line
1458 #endif /* FEATURE_VI_SEARCH */
1459 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1460 status_line(BB_VER);
1461 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1462 || strncmp(cmd, "wq", i) == 0
1463 || strncmp(cmd, "wn", i) == 0
1464 || (cmd[0] == 'x' && !cmd[1])
1467 //int forced = FALSE;
1469 // is there a file name to write to?
1473 #if ENABLE_FEATURE_VI_READONLY
1474 if (readonly_mode && !useforce) {
1475 status_line_bold("'%s' is read only", fn);
1479 // how many lines in text[]?
1480 li = count_lines(q, r);
1483 // if "fn" is not write-able, chmod u+w
1484 // sprintf(syscmd, "chmod u+w %s", fn);
1488 l = file_write(fn, q, r);
1489 //if (useforce && forced) {
1491 // sprintf(syscmd, "chmod u-w %s", fn);
1497 status_line_bold_errno(fn);
1499 status_line("'%s' %dL, %dC", fn, li, l);
1500 if (q == text && r == end - 1 && l == size) {
1502 last_modified_count = -1;
1504 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
1505 || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
1512 #if ENABLE_FEATURE_VI_YANKMARK
1513 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1514 if (b < 0) { // no addr given- use defaults
1515 q = begin_line(dot); // assume .,. for the range
1518 text_yank(q, r, YDreg);
1519 li = count_lines(q, r);
1520 status_line("Yank %d lines (%d chars) into [%c]",
1521 li, strlen(reg[YDreg]), what_reg());
1525 not_implemented(cmd);
1528 dot = bound_dot(dot); // make sure "dot" is valid
1530 #if ENABLE_FEATURE_VI_SEARCH
1532 status_line(":s expression missing delimiters");
1534 #endif /* FEATURE_VI_COLON */
1537 static void Hit_Return(void)
1542 write1("[Hit return to continue]");
1544 while ((c = get_one_char()) != '\n' && c != '\r')
1546 redraw(TRUE); // force redraw all
1549 static int next_tabstop(int col)
1551 return col + ((tabstop - 1) - (col % tabstop));
1554 //----- Synchronize the cursor to Dot --------------------------
1555 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1557 char *beg_cur; // begin and end of "d" line
1561 beg_cur = begin_line(d); // first char of cur line
1563 if (beg_cur < screenbegin) {
1564 // "d" is before top line on screen
1565 // how many lines do we have to move
1566 cnt = count_lines(beg_cur, screenbegin);
1568 screenbegin = beg_cur;
1569 if (cnt > (rows - 1) / 2) {
1570 // we moved too many lines. put "dot" in middle of screen
1571 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1572 screenbegin = prev_line(screenbegin);
1576 char *end_scr; // begin and end of screen
1577 end_scr = end_screen(); // last char of screen
1578 if (beg_cur > end_scr) {
1579 // "d" is after bottom line on screen
1580 // how many lines do we have to move
1581 cnt = count_lines(end_scr, beg_cur);
1582 if (cnt > (rows - 1) / 2)
1583 goto sc1; // too many lines
1584 for (ro = 0; ro < cnt - 1; ro++) {
1585 // move screen begin the same amount
1586 screenbegin = next_line(screenbegin);
1587 // now, move the end of screen
1588 end_scr = next_line(end_scr);
1589 end_scr = end_line(end_scr);
1593 // "d" is on screen- find out which row
1595 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1601 // find out what col "d" is on
1603 while (tp < d) { // drive "co" to correct column
1604 if (*tp == '\n') //vda || *tp == '\0')
1607 // handle tabs like real vi
1608 if (d == tp && cmd_mode) {
1611 co = next_tabstop(co);
1612 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1613 co++; // display as ^X, use 2 columns
1619 // "co" is the column where "dot" is.
1620 // The screen has "columns" columns.
1621 // The currently displayed columns are 0+offset -- columns+ofset
1622 // |-------------------------------------------------------------|
1624 // offset | |------- columns ----------------|
1626 // If "co" is already in this range then we do not have to adjust offset
1627 // but, we do have to subtract the "offset" bias from "co".
1628 // If "co" is outside this range then we have to change "offset".
1629 // If the first char of a line is a tab the cursor will try to stay
1630 // in column 7, but we have to set offset to 0.
1632 if (co < 0 + offset) {
1635 if (co >= columns + offset) {
1636 offset = co - columns + 1;
1638 // if the first char of the line is a tab, and "dot" is sitting on it
1639 // force offset to 0.
1640 if (d == beg_cur && *d == '\t') {
1649 //----- Text Movement Routines ---------------------------------
1650 static char *begin_line(char *p) // return pointer to first char cur line
1653 p = memrchr(text, '\n', p - text);
1661 static char *end_line(char *p) // return pointer to NL of cur line
1664 p = memchr(p, '\n', end - p - 1);
1671 static char *dollar_line(char *p) // return pointer to just before NL line
1674 // Try to stay off of the Newline
1675 if (*p == '\n' && (p - begin_line(p)) > 0)
1680 static char *prev_line(char *p) // return pointer first char prev line
1682 p = begin_line(p); // goto beginning of cur line
1683 if (p > text && p[-1] == '\n')
1684 p--; // step to prev line
1685 p = begin_line(p); // goto beginning of prev line
1689 static char *next_line(char *p) // return pointer first char next line
1692 if (p < end - 1 && *p == '\n')
1693 p++; // step to next line
1697 //----- Text Information Routines ------------------------------
1698 static char *end_screen(void)
1703 // find new bottom line
1705 for (cnt = 0; cnt < rows - 2; cnt++)
1711 // count line from start to stop
1712 static int count_lines(char *start, char *stop)
1717 if (stop < start) { // start and stop are backwards- reverse them
1723 stop = end_line(stop);
1724 while (start <= stop && start <= end - 1) {
1725 start = end_line(start);
1733 static char *find_line(int li) // find beginning of line #li
1737 for (q = text; li > 1; li--) {
1743 //----- Dot Movement Routines ----------------------------------
1744 static void dot_left(void)
1746 undo_queue_commit();
1747 if (dot > text && dot[-1] != '\n')
1751 static void dot_right(void)
1753 undo_queue_commit();
1754 if (dot < end - 1 && *dot != '\n')
1758 static void dot_begin(void)
1760 undo_queue_commit();
1761 dot = begin_line(dot); // return pointer to first char cur line
1764 static void dot_end(void)
1766 undo_queue_commit();
1767 dot = end_line(dot); // return pointer to last char cur line
1770 static char *move_to_col(char *p, int l)
1776 while (co < l && p < end) {
1777 if (*p == '\n') //vda || *p == '\0')
1780 co = next_tabstop(co);
1781 } else if (*p < ' ' || *p == 127) {
1782 co++; // display as ^X, use 2 columns
1790 static void dot_next(void)
1792 undo_queue_commit();
1793 dot = next_line(dot);
1796 static void dot_prev(void)
1798 undo_queue_commit();
1799 dot = prev_line(dot);
1802 static void dot_scroll(int cnt, int dir)
1806 undo_queue_commit();
1807 for (; cnt > 0; cnt--) {
1810 // ctrl-Y scroll up one line
1811 screenbegin = prev_line(screenbegin);
1814 // ctrl-E scroll down one line
1815 screenbegin = next_line(screenbegin);
1818 // make sure "dot" stays on the screen so we dont scroll off
1819 if (dot < screenbegin)
1821 q = end_screen(); // find new bottom line
1823 dot = begin_line(q); // is dot is below bottom line?
1827 static void dot_skip_over_ws(void)
1830 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1834 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1836 if (p >= end && end > text) {
1847 //----- Helper Utility Routines --------------------------------
1849 //----------------------------------------------------------------
1850 //----- Char Routines --------------------------------------------
1851 /* Chars that are part of a word-
1852 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1853 * Chars that are Not part of a word (stoppers)
1854 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1855 * Chars that are WhiteSpace
1856 * TAB NEWLINE VT FF RETURN SPACE
1857 * DO NOT COUNT NEWLINE AS WHITESPACE
1860 static char *new_screen(int ro, int co)
1865 screensize = ro * co + 8;
1866 screen = xmalloc(screensize);
1867 // initialize the new screen. assume this will be a empty file.
1869 // non-existent text[] lines start with a tilde (~).
1870 for (li = 1; li < ro - 1; li++) {
1871 screen[(li * co) + 0] = '~';
1876 #if ENABLE_FEATURE_VI_SEARCH
1878 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1880 // search for pattern starting at p
1881 static char *char_search(char *p, const char *pat, int dir, int range)
1883 struct re_pattern_buffer preg;
1889 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1891 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1893 memset(&preg, 0, sizeof(preg));
1894 err = re_compile_pattern(pat, strlen(pat), &preg);
1896 status_line_bold("bad search pattern '%s': %s", pat, err);
1900 // assume a LIMITED forward search
1904 // RANGE could be negative if we are searching backwards
1914 // search for the compiled pattern, preg, in p[]
1915 // range < 0: search backward
1916 // range > 0: search forward
1918 // re_search() < 0: not found or error
1919 // re_search() >= 0: index of found pattern
1920 // struct pattern char int int int struct reg
1921 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1922 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1935 # if ENABLE_FEATURE_VI_SETOPTS
1936 static int mycmp(const char *s1, const char *s2, int len)
1939 return strncasecmp(s1, s2, len);
1941 return strncmp(s1, s2, len);
1944 # define mycmp strncmp
1947 static char *char_search(char *p, const char *pat, int dir, int range)
1953 if (dir == FORWARD) {
1954 stop = end - 1; // assume range is p..end-1
1955 if (range == LIMITED)
1956 stop = next_line(p); // range is to next line
1957 for (start = p; start < stop; start++) {
1958 if (mycmp(start, pat, len) == 0) {
1962 } else if (dir == BACK) {
1963 stop = text; // assume range is text..p
1964 if (range == LIMITED)
1965 stop = prev_line(p); // range is to prev line
1966 for (start = p - len; start >= stop; start--) {
1967 if (mycmp(start, pat, len) == 0) {
1972 // pattern not found
1978 #endif /* FEATURE_VI_SEARCH */
1980 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1982 if (c == 22) { // Is this an ctrl-V?
1983 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1984 refresh(FALSE); // show the ^
1987 #if ENABLE_FEATURE_VI_UNDO
1990 undo_push(p, 1, UNDO_INS);
1992 case ALLOW_UNDO_CHAIN:
1993 undo_push(p, 1, UNDO_INS_CHAIN);
1995 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1996 case ALLOW_UNDO_QUEUED:
1997 undo_push(p, 1, UNDO_INS_QUEUED);
2003 #endif /* ENABLE_FEATURE_VI_UNDO */
2005 } else if (c == 27) { // Is this an ESC?
2007 undo_queue_commit();
2009 end_cmd_q(); // stop adding to q
2010 last_status_cksum = 0; // force status update
2011 if ((p[-1] != '\n') && (dot > text)) {
2014 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
2017 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2020 // insert a char into text[]
2022 c = '\n'; // translate \r to \n
2023 #if ENABLE_FEATURE_VI_UNDO
2024 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2026 undo_queue_commit();
2030 undo_push(p, 1, UNDO_INS);
2032 case ALLOW_UNDO_CHAIN:
2033 undo_push(p, 1, UNDO_INS_CHAIN);
2035 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2036 case ALLOW_UNDO_QUEUED:
2037 undo_push(p, 1, UNDO_INS_QUEUED);
2043 #endif /* ENABLE_FEATURE_VI_UNDO */
2044 p += 1 + stupid_insert(p, c); // insert the char
2045 #if ENABLE_FEATURE_VI_SETOPTS
2046 if (showmatch && strchr(")]}", c) != NULL) {
2047 showmatching(p - 1);
2049 if (autoindent && c == '\n') { // auto indent the new line
2052 q = prev_line(p); // use prev line as template
2053 len = strspn(q, " \t"); // space or tab
2056 bias = text_hole_make(p, len);
2059 #if ENABLE_FEATURE_VI_UNDO
2060 undo_push(p, len, UNDO_INS);
2071 // might reallocate text[]! use p += stupid_insert(p, ...),
2072 // and be careful to not use pointers into potentially freed text[]!
2073 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2076 bias = text_hole_make(p, 1);
2082 static int find_range(char **start, char **stop, char c)
2084 char *save_dot, *p, *q, *t;
2085 int cnt, multiline = 0;
2090 if (strchr("cdy><", c)) {
2091 // these cmds operate on whole lines
2092 p = q = begin_line(p);
2093 for (cnt = 1; cnt < cmdcnt; cnt++) {
2097 } else if (strchr("^%$0bBeEfth\b\177", c)) {
2098 // These cmds operate on char positions
2099 do_cmd(c); // execute movement cmd
2101 } else if (strchr("wW", c)) {
2102 do_cmd(c); // execute movement cmd
2103 // if we are at the next word's first char
2104 // step back one char
2105 // but check the possibilities when it is true
2106 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
2107 || (ispunct(dot[-1]) && !ispunct(dot[0]))
2108 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
2109 dot--; // move back off of next word
2110 if (dot > text && *dot == '\n')
2111 dot--; // stay off NL
2113 } else if (strchr("H-k{", c)) {
2114 // these operate on multi-lines backwards
2115 q = end_line(dot); // find NL
2116 do_cmd(c); // execute movement cmd
2119 } else if (strchr("L+j}\r\n", c)) {
2120 // these operate on multi-lines forwards
2121 p = begin_line(dot);
2122 do_cmd(c); // execute movement cmd
2123 dot_end(); // find NL
2126 // nothing -- this causes any other values of c to
2127 // represent the one-character range under the
2128 // cursor. this is correct for ' ' and 'l', but
2129 // perhaps no others.
2138 // backward char movements don't include start position
2139 if (q > p && strchr("^0bBh\b\177", c)) q--;
2142 for (t = p; t <= q; t++) {
2155 static int st_test(char *p, int type, int dir, char *tested)
2165 if (type == S_BEFORE_WS) {
2167 test = (!isspace(c) || c == '\n');
2169 if (type == S_TO_WS) {
2171 test = (!isspace(c) || c == '\n');
2173 if (type == S_OVER_WS) {
2177 if (type == S_END_PUNCT) {
2181 if (type == S_END_ALNUM) {
2183 test = (isalnum(c) || c == '_');
2189 static char *skip_thing(char *p, int linecnt, int dir, int type)
2193 while (st_test(p, type, dir, &c)) {
2194 // make sure we limit search to correct number of lines
2195 if (c == '\n' && --linecnt < 1)
2197 if (dir >= 0 && p >= end - 1)
2199 if (dir < 0 && p <= text)
2201 p += dir; // move to next char
2206 // find matching char of pair () [] {}
2207 // will crash if c is not one of these
2208 static char *find_pair(char *p, const char c)
2210 const char *braces = "()[]{}";
2214 dir = strchr(braces, c) - braces;
2216 match = braces[dir];
2217 dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
2219 // look for match, count levels of pairs (( ))
2223 if (p < text || p >= end)
2226 level++; // increase pair levels
2228 level--; // reduce pair level
2230 return p; // found matching pair
2235 #if ENABLE_FEATURE_VI_SETOPTS
2236 // show the matching char of a pair, () [] {}
2237 static void showmatching(char *p)
2241 // we found half of a pair
2242 q = find_pair(p, *p); // get loc of matching char
2244 indicate_error(); // no matching char
2246 // "q" now points to matching pair
2247 save_dot = dot; // remember where we are
2248 dot = q; // go to new loc
2249 refresh(FALSE); // let the user see it
2250 mysleep(40); // give user some time
2251 dot = save_dot; // go back to old loc
2255 #endif /* FEATURE_VI_SETOPTS */
2257 #if ENABLE_FEATURE_VI_UNDO
2258 static void flush_undo_data(void)
2260 struct undo_object *undo_entry;
2262 while (undo_stack_tail) {
2263 undo_entry = undo_stack_tail;
2264 undo_stack_tail = undo_entry->prev;
2269 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2270 static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack
2272 struct undo_object *undo_entry;
2275 // UNDO_INS: insertion, undo will remove from buffer
2276 // UNDO_DEL: deleted text, undo will restore to buffer
2277 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2278 // The CHAIN operations are for handling multiple operations that the user
2279 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2280 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2281 // for the INS/DEL operation. The raw values should be equal to the values of
2282 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2284 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2285 // This undo queuing functionality groups multiple character typing or backspaces
2286 // into a single large undo object. This greatly reduces calls to malloc() for
2287 // single-character operations while typing and has the side benefit of letting
2288 // an undo operation remove chunks of text rather than a single character.
2290 case UNDO_EMPTY: // Just in case this ever happens...
2292 case UNDO_DEL_QUEUED:
2294 return; // Only queue single characters
2295 switch (undo_queue_state) {
2297 undo_queue_state = UNDO_DEL;
2299 undo_queue_spos = src;
2301 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2302 // If queue is full, dump it into an object
2303 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2304 undo_queue_commit();
2307 // Switch from storing inserted text to deleted text
2308 undo_queue_commit();
2309 undo_push(src, length, UNDO_DEL_QUEUED);
2313 case UNDO_INS_QUEUED:
2316 switch (undo_queue_state) {
2318 undo_queue_state = UNDO_INS;
2319 undo_queue_spos = src;
2321 undo_q++; // Don't need to save any data for insertions
2322 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2323 undo_queue_commit();
2326 // Switch from storing deleted text to inserted text
2327 undo_queue_commit();
2328 undo_push(src, length, UNDO_INS_QUEUED);
2334 // If undo queuing is disabled, ignore the queuing flag entirely
2335 u_type = u_type & ~UNDO_QUEUED_FLAG;
2338 // Allocate a new undo object
2339 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2340 // For UNDO_DEL objects, save deleted text
2341 if ((src + length) == end)
2343 // If this deletion empties text[], strip the newline. When the buffer becomes
2344 // zero-length, a newline is added back, which requires this to compensate.
2345 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
2346 memcpy(undo_entry->undo_text, src, length);
2348 undo_entry = xzalloc(sizeof(*undo_entry));
2350 undo_entry->length = length;
2351 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2352 if ((u_type & UNDO_USE_SPOS) != 0) {
2353 undo_entry->start = undo_queue_spos - text; // use start position from queue
2355 undo_entry->start = src - text; // use offset from start of text buffer
2357 u_type = (u_type & ~UNDO_USE_SPOS);
2359 undo_entry->start = src - text;
2361 undo_entry->u_type = u_type;
2363 // Push it on undo stack
2364 undo_entry->prev = undo_stack_tail;
2365 undo_stack_tail = undo_entry;
2369 static void undo_pop(void) // Undo the last operation
2372 char *u_start, *u_end;
2373 struct undo_object *undo_entry;
2375 // Commit pending undo queue before popping (should be unnecessary)
2376 undo_queue_commit();
2378 undo_entry = undo_stack_tail;
2379 // Check for an empty undo stack
2381 status_line("Already at oldest change");
2385 switch (undo_entry->u_type) {
2387 case UNDO_DEL_CHAIN:
2388 // make hole and put in text that was deleted; deallocate text
2389 u_start = text + undo_entry->start;
2390 text_hole_make(u_start, undo_entry->length);
2391 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2392 status_line("Undo [%d] %s %d chars at position %d",
2393 modified_count, "restored",
2394 undo_entry->length, undo_entry->start
2398 case UNDO_INS_CHAIN:
2399 // delete what was inserted
2400 u_start = undo_entry->start + text;
2401 u_end = u_start - 1 + undo_entry->length;
2402 text_hole_delete(u_start, u_end, NO_UNDO);
2403 status_line("Undo [%d] %s %d chars at position %d",
2404 modified_count, "deleted",
2405 undo_entry->length, undo_entry->start
2410 switch (undo_entry->u_type) {
2411 // If this is the end of a chain, lower modification count and refresh display
2414 dot = (text + undo_entry->start);
2417 case UNDO_DEL_CHAIN:
2418 case UNDO_INS_CHAIN:
2422 // Deallocate the undo object we just processed
2423 undo_stack_tail = undo_entry->prev;
2426 // For chained operations, continue popping all the way down the chain.
2428 undo_pop(); // Follow the undo chain if one exists
2432 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2433 static void undo_queue_commit(void) // Flush any queued objects to the undo stack
2435 // Pushes the queue object onto the undo stack
2437 // Deleted character undo events grow from the end
2438 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2440 (undo_queue_state | UNDO_USE_SPOS)
2442 undo_queue_state = UNDO_EMPTY;
2448 #endif /* ENABLE_FEATURE_VI_UNDO */
2450 // open a hole in text[]
2451 // might reallocate text[]! use p += text_hole_make(p, ...),
2452 // and be careful to not use pointers into potentially freed text[]!
2453 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2459 end += size; // adjust the new END
2460 if (end >= (text + text_size)) {
2462 text_size += end - (text + text_size) + 10240;
2463 new_text = xrealloc(text, text_size);
2464 bias = (new_text - text);
2465 screenbegin += bias;
2469 #if ENABLE_FEATURE_VI_YANKMARK
2472 for (i = 0; i < ARRAY_SIZE(mark); i++)
2479 memmove(p + size, p, end - size - p);
2480 memset(p, ' ', size); // clear new hole
2484 // close a hole in text[]
2485 // "undo" value indicates if this operation should be undo-able
2486 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2491 // move forwards, from beginning
2495 if (q < p) { // they are backward- swap them
2499 hole_size = q - p + 1;
2501 #if ENABLE_FEATURE_VI_UNDO
2506 undo_push(p, hole_size, UNDO_DEL);
2508 case ALLOW_UNDO_CHAIN:
2509 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2511 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2512 case ALLOW_UNDO_QUEUED:
2513 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2519 if (src < text || src > end)
2521 if (dest < text || dest >= end)
2525 goto thd_atend; // just delete the end of the buffer
2526 memmove(dest, src, cnt);
2528 end = end - hole_size; // adjust the new END
2530 dest = end - 1; // make sure dest in below end-1
2532 dest = end = text; // keep pointers valid
2537 // copy text into register, then delete text.
2538 // if dist <= 0, do not include, or go past, a NewLine
2540 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2544 // make sure start <= stop
2546 // they are backwards, reverse them
2552 // we cannot cross NL boundaries
2556 // dont go past a NewLine
2557 for (; p + 1 <= stop; p++) {
2559 stop = p; // "stop" just before NewLine
2565 #if ENABLE_FEATURE_VI_YANKMARK
2566 text_yank(start, stop, YDreg);
2568 if (yf == YANKDEL) {
2569 p = text_hole_delete(start, stop, undo);
2574 static void show_help(void)
2576 puts("These features are available:"
2577 #if ENABLE_FEATURE_VI_SEARCH
2578 "\n\tPattern searches with / and ?"
2580 #if ENABLE_FEATURE_VI_DOT_CMD
2581 "\n\tLast command repeat with ."
2583 #if ENABLE_FEATURE_VI_YANKMARK
2584 "\n\tLine marking with 'x"
2585 "\n\tNamed buffers with \"x"
2587 #if ENABLE_FEATURE_VI_READONLY
2588 //not implemented: "\n\tReadonly if vi is called as \"view\""
2589 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2591 #if ENABLE_FEATURE_VI_SET
2592 "\n\tSome colon mode commands with :"
2594 #if ENABLE_FEATURE_VI_SETOPTS
2595 "\n\tSettable options with \":set\""
2597 #if ENABLE_FEATURE_VI_USE_SIGNALS
2598 "\n\tSignal catching- ^C"
2599 "\n\tJob suspend and resume with ^Z"
2601 #if ENABLE_FEATURE_VI_WIN_RESIZE
2602 "\n\tAdapt to window re-sizes"
2607 #if ENABLE_FEATURE_VI_DOT_CMD
2608 static void start_new_cmd_q(char c)
2610 // get buffer for new cmd
2611 // if there is a current cmd count put it in the buffer first
2613 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2614 } else { // just save char c onto queue
2615 last_modifying_cmd[0] = c;
2621 static void end_cmd_q(void)
2623 #if ENABLE_FEATURE_VI_YANKMARK
2624 YDreg = 26; // go back to default Yank/Delete reg
2628 #endif /* FEATURE_VI_DOT_CMD */
2630 #if ENABLE_FEATURE_VI_YANKMARK \
2631 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2632 || ENABLE_FEATURE_VI_CRASHME
2633 // might reallocate text[]! use p += string_insert(p, ...),
2634 // and be careful to not use pointers into potentially freed text[]!
2635 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2641 #if ENABLE_FEATURE_VI_UNDO
2644 undo_push(p, i, UNDO_INS);
2646 case ALLOW_UNDO_CHAIN:
2647 undo_push(p, i, UNDO_INS_CHAIN);
2651 bias = text_hole_make(p, i);
2654 #if ENABLE_FEATURE_VI_YANKMARK
2657 for (cnt = 0; *s != '\0'; s++) {
2661 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2668 #if ENABLE_FEATURE_VI_YANKMARK
2669 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2672 if (cnt < 0) { // they are backwards- reverse them
2676 free(reg[dest]); // if already a yank register, free it
2677 reg[dest] = xstrndup(p, cnt + 1);
2681 static char what_reg(void)
2685 c = 'D'; // default to D-reg
2686 if (0 <= YDreg && YDreg <= 25)
2687 c = 'a' + (char) YDreg;
2695 static void check_context(char cmd)
2697 // A context is defined to be "modifying text"
2698 // Any modifying command establishes a new context.
2700 if (dot < context_start || dot > context_end) {
2701 if (strchr(modifying_cmds, cmd) != NULL) {
2702 // we are trying to modify text[]- make this the current context
2703 mark[27] = mark[26]; // move cur to prev
2704 mark[26] = dot; // move local to cur
2705 context_start = prev_line(prev_line(dot));
2706 context_end = next_line(next_line(dot));
2707 //loiter= start_loiter= now;
2712 static char *swap_context(char *p) // goto new context for '' command make this the current context
2716 // the current context is in mark[26]
2717 // the previous context is in mark[27]
2718 // only swap context if other context is valid
2719 if (text <= mark[27] && mark[27] <= end - 1) {
2723 context_start = prev_line(prev_line(prev_line(p)));
2724 context_end = next_line(next_line(next_line(p)));
2728 #endif /* FEATURE_VI_YANKMARK */
2730 //----- Set terminal attributes --------------------------------
2731 static void rawmode(void)
2733 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
2734 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
2735 erase_char = term_orig.c_cc[VERASE];
2738 static void cookmode(void)
2741 tcsetattr_stdin_TCSANOW(&term_orig);
2744 #if ENABLE_FEATURE_VI_USE_SIGNALS
2745 //----- Come here when we get a window resize signal ---------
2746 static void winch_sig(int sig UNUSED_PARAM)
2748 int save_errno = errno;
2749 // FIXME: do it in main loop!!!
2750 signal(SIGWINCH, winch_sig);
2751 query_screen_dimensions();
2752 new_screen(rows, columns); // get memory for virtual screen
2753 redraw(TRUE); // re-draw the screen
2757 //----- Come here when we get a continue signal -------------------
2758 static void cont_sig(int sig UNUSED_PARAM)
2760 int save_errno = errno;
2761 rawmode(); // terminal to "raw"
2762 last_status_cksum = 0; // force status update
2763 redraw(TRUE); // re-draw the screen
2765 signal(SIGTSTP, suspend_sig);
2766 signal(SIGCONT, SIG_DFL);
2767 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2771 //----- Come here when we get a Suspend signal -------------------
2772 static void suspend_sig(int sig UNUSED_PARAM)
2774 int save_errno = errno;
2775 go_bottom_and_clear_to_eol();
2776 cookmode(); // terminal to "cooked"
2778 signal(SIGCONT, cont_sig);
2779 signal(SIGTSTP, SIG_DFL);
2780 kill(my_pid, SIGTSTP);
2784 //----- Come here when we get a signal ---------------------------
2785 static void catch_sig(int sig)
2787 signal(SIGINT, catch_sig);
2788 siglongjmp(restart, sig);
2790 #endif /* FEATURE_VI_USE_SIGNALS */
2792 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2794 struct pollfd pfd[1];
2799 pfd[0].fd = STDIN_FILENO;
2800 pfd[0].events = POLLIN;
2801 return safe_poll(pfd, 1, hund*10) > 0;
2804 //----- IO Routines --------------------------------------------
2805 static int readit(void) // read (maybe cursor) key from stdin
2810 c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2811 if (c == -1) { // EOF/error
2812 go_bottom_and_clear_to_eol();
2813 cookmode(); // terminal to "cooked"
2814 bb_error_msg_and_die("can't read user input");
2819 //----- IO Routines --------------------------------------------
2820 static int get_one_char(void)
2824 #if ENABLE_FEATURE_VI_DOT_CMD
2826 // we are not adding to the q.
2827 // but, we may be reading from a q
2829 // there is no current q, read from STDIN
2830 c = readit(); // get the users input
2832 // there is a queue to get chars from first
2833 // careful with correct sign expansion!
2834 c = (unsigned char)*ioq++;
2836 // the end of the q, read from STDIN
2838 ioq_start = ioq = 0;
2839 c = readit(); // get the users input
2843 // adding STDIN chars to q
2844 c = readit(); // get the users input
2845 if (lmc_len >= MAX_INPUT_LEN - 1) {
2846 status_line_bold("last_modifying_cmd overrun");
2848 // add new char to q
2849 last_modifying_cmd[lmc_len++] = c;
2853 c = readit(); // get the users input
2854 #endif /* FEATURE_VI_DOT_CMD */
2858 // Get input line (uses "status line" area)
2859 static char *get_input_line(const char *prompt)
2861 // char [MAX_INPUT_LEN]
2862 #define buf get_input_line__buf
2867 strcpy(buf, prompt);
2868 last_status_cksum = 0; // force status update
2869 go_bottom_and_clear_to_eol();
2870 write1(prompt); // write out the :, /, or ? prompt
2873 while (i < MAX_INPUT_LEN) {
2875 if (c == '\n' || c == '\r' || c == 27)
2876 break; // this is end of input
2877 if (c == erase_char || c == 8 || c == 127) {
2878 // user wants to erase prev char
2880 write1("\b \b"); // erase char on screen
2881 if (i <= 0) // user backs up before b-o-l, exit
2883 } else if (c > 0 && c < 256) { // exclude Unicode
2884 // (TODO: need to handle Unicode)
2895 // might reallocate text[]!
2896 static int file_insert(const char *fn, char *p, int initial)
2900 struct stat statbuf;
2907 fd = open(fn, O_RDONLY);
2910 status_line_bold_errno(fn);
2915 if (fstat(fd, &statbuf) < 0) {
2916 status_line_bold_errno(fn);
2919 if (!S_ISREG(statbuf.st_mode)) {
2920 status_line_bold("'%s' is not a regular file", fn);
2923 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2924 p += text_hole_make(p, size);
2925 cnt = full_read(fd, p, size);
2927 status_line_bold_errno(fn);
2928 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2929 } else if (cnt < size) {
2930 // There was a partial read, shrink unused space
2931 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2932 status_line_bold("can't read '%s'", fn);
2937 #if ENABLE_FEATURE_VI_READONLY
2939 && ((access(fn, W_OK) < 0) ||
2940 /* root will always have access()
2941 * so we check fileperms too */
2942 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2945 SET_READONLY_FILE(readonly_mode);
2951 static int file_write(char *fn, char *first, char *last)
2953 int fd, cnt, charcnt;
2956 status_line_bold("No current filename");
2959 /* By popular request we do not open file with O_TRUNC,
2960 * but instead ftruncate() it _after_ successful write.
2961 * Might reduce amount of data lost on power fail etc.
2963 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2966 cnt = last - first + 1;
2967 charcnt = full_write(fd, first, cnt);
2968 ftruncate(fd, charcnt);
2969 if (charcnt == cnt) {
2971 //modified_count = FALSE;
2979 //----- Terminal Drawing ---------------------------------------
2980 // The terminal is made up of 'rows' line of 'columns' columns.
2981 // classically this would be 24 x 80.
2982 // screen coordinates
2988 // 23,0 ... 23,79 <- status line
2990 //----- Move the cursor to row x col (count from 0, not 1) -------
2991 static void place_cursor(int row, int col)
2993 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
2995 if (row < 0) row = 0;
2996 if (row >= rows) row = rows - 1;
2997 if (col < 0) col = 0;
2998 if (col >= columns) col = columns - 1;
3000 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
3004 //----- Erase from cursor to end of line -----------------------
3005 static void clear_to_eol(void)
3007 write1(ESC_CLEAR2EOL);
3010 static void go_bottom_and_clear_to_eol(void)
3012 place_cursor(rows - 1, 0);
3016 //----- Erase from cursor to end of screen -----------------------
3017 static void clear_to_eos(void)
3019 write1(ESC_CLEAR2EOS);
3022 //----- Start standout mode ------------------------------------
3023 static void standout_start(void)
3025 write1(ESC_BOLD_TEXT);
3028 //----- End standout mode --------------------------------------
3029 static void standout_end(void)
3031 write1(ESC_NORM_TEXT);
3034 //----- Flash the screen --------------------------------------
3035 static void flash(int h)
3044 static void indicate_error(void)
3046 #if ENABLE_FEATURE_VI_CRASHME
3048 return; // generate a random command
3057 //----- Screen[] Routines --------------------------------------
3058 //----- Erase the Screen[] memory ------------------------------
3059 static void screen_erase(void)
3061 memset(screen, ' ', screensize); // clear new screen
3064 static int bufsum(char *buf, int count)
3067 char *e = buf + count;
3070 sum += (unsigned char) *buf++;
3074 //----- Draw the status line at bottom of the screen -------------
3075 static void show_status_line(void)
3077 int cnt = 0, cksum = 0;
3079 // either we already have an error or status message, or we
3081 if (!have_status_msg) {
3082 cnt = format_edit_status();
3083 cksum = bufsum(status_buffer, cnt);
3085 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3086 last_status_cksum = cksum; // remember if we have seen this line
3087 go_bottom_and_clear_to_eol();
3088 write1(status_buffer);
3089 if (have_status_msg) {
3090 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3092 have_status_msg = 0;
3095 have_status_msg = 0;
3097 place_cursor(crow, ccol); // put cursor back in correct place
3102 //----- format the status buffer, the bottom line of screen ------
3103 // format status buffer, with STANDOUT mode
3104 static void status_line_bold(const char *format, ...)
3108 va_start(args, format);
3109 strcpy(status_buffer, ESC_BOLD_TEXT);
3110 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3111 strcat(status_buffer, ESC_NORM_TEXT);
3114 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3117 static void status_line_bold_errno(const char *fn)
3119 status_line_bold("'%s' %s", fn, strerror(errno));
3122 // format status buffer
3123 static void status_line(const char *format, ...)
3127 va_start(args, format);
3128 vsprintf(status_buffer, format, args);
3131 have_status_msg = 1;
3134 // copy s to buf, convert unprintable
3135 static void print_literal(char *buf, const char *s)
3149 c_is_no_print = (c & 0x80) && !Isprint(c);
3150 if (c_is_no_print) {
3151 strcpy(d, ESC_NORM_TEXT);
3152 d += sizeof(ESC_NORM_TEXT)-1;
3155 if (c < ' ' || c == 0x7f) {
3157 c |= '@'; /* 0x40 */
3163 if (c_is_no_print) {
3164 strcpy(d, ESC_BOLD_TEXT);
3165 d += sizeof(ESC_BOLD_TEXT)-1;
3171 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3176 static void not_implemented(const char *s)
3178 char buf[MAX_INPUT_LEN];
3180 print_literal(buf, s);
3181 status_line_bold("\'%s\' is not implemented", buf);
3184 // show file status on status line
3185 static int format_edit_status(void)
3187 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3189 #define tot format_edit_status__tot
3191 int cur, percent, ret, trunc_at;
3193 // modified_count is now a counter rather than a flag. this
3194 // helps reduce the amount of line counting we need to do.
3195 // (this will cause a mis-reporting of modified status
3196 // once every MAXINT editing operations.)
3198 // it would be nice to do a similar optimization here -- if
3199 // we haven't done a motion that could have changed which line
3200 // we're on, then we shouldn't have to do this count_lines()
3201 cur = count_lines(text, dot);
3203 // count_lines() is expensive.
3204 // Call it only if something was changed since last time
3206 if (modified_count != last_modified_count) {
3207 tot = cur + count_lines(dot, end - 1) - 1;
3208 last_modified_count = modified_count;
3211 // current line percent
3212 // ------------- ~~ ----------
3215 percent = (100 * cur) / tot;
3221 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3222 columns : STATUS_BUFFER_LEN-1;
3224 ret = snprintf(status_buffer, trunc_at+1,
3225 #if ENABLE_FEATURE_VI_READONLY
3226 "%c %s%s%s %d/%d %d%%",
3228 "%c %s%s %d/%d %d%%",
3230 cmd_mode_indicator[cmd_mode & 3],
3231 (current_filename != NULL ? current_filename : "No file"),
3232 #if ENABLE_FEATURE_VI_READONLY
3233 (readonly_mode ? " [Readonly]" : ""),
3235 (modified_count ? " [Modified]" : ""),
3238 if (ret >= 0 && ret < trunc_at)
3239 return ret; /* it all fit */
3241 return trunc_at; /* had to truncate */
3245 //----- Force refresh of all Lines -----------------------------
3246 static void redraw(int full_screen)
3250 screen_erase(); // erase the internal screen buffer
3251 last_status_cksum = 0; // force status update
3252 refresh(full_screen); // this will redraw the entire display
3256 //----- Format a text[] line into a buffer ---------------------
3257 static char* format_line(char *src /*, int li*/)
3262 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3264 c = '~'; // char in col 0 in non-existent lines is '~'
3266 while (co < columns + tabstop) {
3267 // have we gone past the end?
3272 if ((c & 0x80) && !Isprint(c)) {
3275 if (c < ' ' || c == 0x7f) {
3279 while ((co % tabstop) != (tabstop - 1)) {
3287 c += '@'; // Ctrl-X -> 'X'
3292 // discard scrolled-off-to-the-left portion,
3293 // in tabstop-sized pieces
3294 if (ofs >= tabstop && co >= tabstop) {
3295 memmove(dest, dest + tabstop, co);
3302 // check "short line, gigantic offset" case
3305 // discard last scrolled off part
3308 // fill the rest with spaces
3310 memset(&dest[co], ' ', columns - co);
3314 //----- Refresh the changed screen lines -----------------------
3315 // Copy the source line from text[] into the buffer and note
3316 // if the current screenline is different from the new buffer.
3317 // If they differ then that line needs redrawing on the terminal.
3319 static void refresh(int full_screen)
3321 #define old_offset refresh__old_offset
3324 char *tp, *sp; // pointer into text[] and screen[]
3326 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3327 unsigned c = columns, r = rows;
3328 query_screen_dimensions();
3329 full_screen |= (c - columns) | (r - rows);
3331 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3332 tp = screenbegin; // index into text[] of top line
3334 // compare text[] to screen[] and mark screen[] lines that need updating
3335 for (li = 0; li < rows - 1; li++) {
3336 int cs, ce; // column start & end
3338 // format current text line
3339 out_buf = format_line(tp /*, li*/);
3341 // skip to the end of the current text[] line
3343 char *t = memchr(tp, '\n', end - tp);
3344 if (!t) t = end - 1;
3348 // see if there are any changes between virtual screen and out_buf
3349 changed = FALSE; // assume no change
3352 sp = &screen[li * columns]; // start of screen line
3354 // force re-draw of every single column from 0 - columns-1
3357 // compare newly formatted buffer with virtual screen
3358 // look forward for first difference between buf and screen
3359 for (; cs <= ce; cs++) {
3360 if (out_buf[cs] != sp[cs]) {
3361 changed = TRUE; // mark for redraw
3366 // look backward for last difference between out_buf and screen
3367 for (; ce >= cs; ce--) {
3368 if (out_buf[ce] != sp[ce]) {
3369 changed = TRUE; // mark for redraw
3373 // now, cs is index of first diff, and ce is index of last diff
3375 // if horz offset has changed, force a redraw
3376 if (offset != old_offset) {
3381 // make a sanity check of columns indexes
3383 if (ce > columns - 1) ce = columns - 1;
3384 if (cs > ce) { cs = 0; ce = columns - 1; }
3385 // is there a change between virtual screen and out_buf
3387 // copy changed part of buffer to virtual screen
3388 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3389 place_cursor(li, cs);
3390 // write line out to terminal
3391 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3395 place_cursor(crow, ccol);
3397 old_offset = offset;
3401 //---------------------------------------------------------------------
3402 //----- the Ascii Chart -----------------------------------------------
3404 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3405 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3406 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3407 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3408 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3409 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3410 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3411 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3412 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3413 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3414 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3415 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3416 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3417 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3418 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3419 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3420 //---------------------------------------------------------------------
3422 //----- Execute a Vi Command -----------------------------------
3423 static void do_cmd(int c)
3425 char *p, *q, *save_dot;
3431 // c1 = c; // quiet the compiler
3432 // cnt = yf = 0; // quiet the compiler
3433 // p = q = save_dot = buf; // quiet the compiler
3434 memset(buf, '\0', sizeof(buf));
3438 /* if this is a cursor key, skip these checks */
3446 case KEYCODE_PAGEUP:
3447 case KEYCODE_PAGEDOWN:
3448 case KEYCODE_DELETE:
3452 if (cmd_mode == 2) {
3453 // flip-flop Insert/Replace mode
3454 if (c == KEYCODE_INSERT)
3456 // we are 'R'eplacing the current *dot with new char
3458 // don't Replace past E-o-l
3459 cmd_mode = 1; // convert to insert
3460 undo_queue_commit();
3462 if (1 <= c || Isprint(c)) {
3464 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3465 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3470 if (cmd_mode == 1) {
3471 // hitting "Insert" twice means "R" replace mode
3472 if (c == KEYCODE_INSERT) goto dc5;
3473 // insert the char c at "dot"
3474 if (1 <= c || Isprint(c)) {
3475 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3490 #if ENABLE_FEATURE_VI_CRASHME
3491 case 0x14: // dc4 ctrl-T
3492 crashme = (crashme == 0) ? 1 : 0;
3522 default: // unrecognized command
3525 not_implemented(buf);
3526 end_cmd_q(); // stop adding to q
3527 case 0x00: // nul- ignore
3529 case 2: // ctrl-B scroll up full screen
3530 case KEYCODE_PAGEUP: // Cursor Key Page Up
3531 dot_scroll(rows - 2, -1);
3533 case 4: // ctrl-D scroll down half screen
3534 dot_scroll((rows - 2) / 2, 1);
3536 case 5: // ctrl-E scroll down one line
3539 case 6: // ctrl-F scroll down full screen
3540 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3541 dot_scroll(rows - 2, 1);
3543 case 7: // ctrl-G show current status
3544 last_status_cksum = 0; // force status update
3546 case 'h': // h- move left
3547 case KEYCODE_LEFT: // cursor key Left
3548 case 8: // ctrl-H- move left (This may be ERASE char)
3549 case 0x7f: // DEL- move left (This may be ERASE char)
3552 } while (--cmdcnt > 0);
3554 case 10: // Newline ^J
3555 case 'j': // j- goto next line, same col
3556 case KEYCODE_DOWN: // cursor key Down
3558 dot_next(); // go to next B-o-l
3559 // try stay in same col
3560 dot = move_to_col(dot, ccol + offset);
3561 } while (--cmdcnt > 0);
3563 case 12: // ctrl-L force redraw whole screen
3564 case 18: // ctrl-R force redraw
3567 //mysleep(10); // why???
3568 screen_erase(); // erase the internal screen buffer
3569 last_status_cksum = 0; // force status update
3570 refresh(TRUE); // this will redraw the entire display
3572 case 13: // Carriage Return ^M
3573 case '+': // +- goto next line
3577 } while (--cmdcnt > 0);
3579 case 21: // ctrl-U scroll up half screen
3580 dot_scroll((rows - 2) / 2, -1);
3582 case 25: // ctrl-Y scroll up one line
3588 cmd_mode = 0; // stop insrting
3589 undo_queue_commit();
3591 last_status_cksum = 0; // force status update
3593 case ' ': // move right
3594 case 'l': // move right
3595 case KEYCODE_RIGHT: // Cursor Key Right
3598 } while (--cmdcnt > 0);
3600 #if ENABLE_FEATURE_VI_YANKMARK
3601 case '"': // "- name a register to use for Delete/Yank
3602 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3603 if ((unsigned)c1 <= 25) { // a-z?
3609 case '\'': // '- goto a specific mark
3610 c1 = (get_one_char() | 0x20);
3611 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3615 if (text <= q && q < end) {
3617 dot_begin(); // go to B-o-l
3620 } else if (c1 == '\'') { // goto previous context
3621 dot = swap_context(dot); // swap current and previous context
3622 dot_begin(); // go to B-o-l
3628 case 'm': // m- Mark a line
3629 // this is really stupid. If there are any inserts or deletes
3630 // between text[0] and dot then this mark will not point to the
3631 // correct location! It could be off by many lines!
3632 // Well..., at least its quick and dirty.
3633 c1 = (get_one_char() | 0x20) - 'a';
3634 if ((unsigned)c1 <= 25) { // a-z?
3635 // remember the line
3641 case 'P': // P- Put register before
3642 case 'p': // p- put register after
3645 status_line_bold("Nothing in register %c", what_reg());
3648 // are we putting whole lines or strings
3649 if (strchr(p, '\n') != NULL) {
3651 dot_begin(); // putting lines- Put above
3654 // are we putting after very last line?
3655 if (end_line(dot) == (end - 1)) {
3656 dot = end; // force dot to end of text[]
3658 dot_next(); // next line, then put before
3663 dot_right(); // move to right, can move to NL
3665 string_insert(dot, p, ALLOW_UNDO); // insert the string
3666 end_cmd_q(); // stop adding to q
3668 case 'U': // U- Undo; replace current line with original version
3669 if (reg[Ureg] != NULL) {
3670 p = begin_line(dot);
3672 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3673 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3678 #endif /* FEATURE_VI_YANKMARK */
3679 #if ENABLE_FEATURE_VI_UNDO
3680 case 'u': // u- undo last operation
3684 case '$': // $- goto end of line
3685 case KEYCODE_END: // Cursor Key End
3687 dot = end_line(dot);
3693 case '%': // %- find matching char of pair () [] {}
3694 for (q = dot; q < end && *q != '\n'; q++) {
3695 if (strchr("()[]{}", *q) != NULL) {
3696 // we found half of a pair
3697 p = find_pair(q, *q);
3709 case 'f': // f- forward to a user specified char
3710 last_forward_char = get_one_char(); // get the search char
3712 // dont separate these two commands. 'f' depends on ';'
3714 //**** fall through to ... ';'
3715 case ';': // ;- look at rest of line for last forward char
3717 if (last_forward_char == 0)
3720 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3723 if (*q == last_forward_char)
3725 } while (--cmdcnt > 0);
3727 case ',': // repeat latest 'f' in opposite direction
3728 if (last_forward_char == 0)
3732 while (q >= text && *q != '\n' && *q != last_forward_char) {
3735 if (q >= text && *q == last_forward_char)
3737 } while (--cmdcnt > 0);
3740 case '-': // -- goto prev line
3744 } while (--cmdcnt > 0);
3746 #if ENABLE_FEATURE_VI_DOT_CMD
3747 case '.': // .- repeat the last modifying command
3748 // Stuff the last_modifying_cmd back into stdin
3749 // and let it be re-executed.
3751 last_modifying_cmd[lmc_len] = 0;
3752 ioq = ioq_start = xstrdup(last_modifying_cmd);
3756 #if ENABLE_FEATURE_VI_SEARCH
3757 case '?': // /- search for a pattern
3758 case '/': // /- search for a pattern
3761 q = get_input_line(buf); // get input line- use "status line"
3762 if (q[0] && !q[1]) {
3763 if (last_search_pattern[0])
3764 last_search_pattern[0] = c;
3765 goto dc3; // if no pat re-use old pat
3767 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3768 // there is a new pat
3769 free(last_search_pattern);
3770 last_search_pattern = xstrdup(q);
3771 goto dc3; // now find the pattern
3773 // user changed mind and erased the "/"- do nothing
3775 case 'N': // N- backward search for last pattern
3776 dir = BACK; // assume BACKWARD search
3778 if (last_search_pattern[0] == '?') {
3782 goto dc4; // now search for pattern
3784 case 'n': // n- repeat search for last pattern
3785 // search rest of text[] starting at next char
3786 // if search fails return orignal "p" not the "p+1" address
3790 dir = FORWARD; // assume FORWARD search
3792 if (last_search_pattern[0] == '?') {
3797 q = char_search(p, last_search_pattern + 1, dir, FULL);
3799 dot = q; // good search, update "dot"
3803 // no pattern found between "dot" and "end"- continue at top
3808 q = char_search(p, last_search_pattern + 1, dir, FULL);
3809 if (q != NULL) { // found something
3810 dot = q; // found new pattern- goto it
3811 msg = "search hit BOTTOM, continuing at TOP";
3813 msg = "search hit TOP, continuing at BOTTOM";
3816 msg = "Pattern not found";
3820 status_line_bold("%s", msg);
3821 } while (--cmdcnt > 0);
3823 case '{': // {- move backward paragraph
3824 q = char_search(dot, "\n\n", BACK, FULL);
3825 if (q != NULL) { // found blank line
3826 dot = next_line(q); // move to next blank line
3829 case '}': // }- move forward paragraph
3830 q = char_search(dot, "\n\n", FORWARD, FULL);
3831 if (q != NULL) { // found blank line
3832 dot = next_line(q); // move to next blank line
3835 #endif /* FEATURE_VI_SEARCH */
3836 case '0': // 0- goto beginning of line
3846 if (c == '0' && cmdcnt < 1) {
3847 dot_begin(); // this was a standalone zero
3849 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3852 case ':': // :- the colon mode commands
3853 p = get_input_line(":"); // get input line- use "status line"
3854 colon(p); // execute the command
3856 case '<': // <- Left shift something
3857 case '>': // >- Right shift something
3858 cnt = count_lines(text, dot); // remember what line we are on
3859 c1 = get_one_char(); // get the type of thing to delete
3860 find_range(&p, &q, c1);
3861 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3864 i = count_lines(p, q); // # of lines we are shifting
3865 for ( ; i > 0; i--, p = next_line(p)) {
3867 // shift left- remove tab or 8 spaces
3869 // shrink buffer 1 char
3870 text_hole_delete(p, p, NO_UNDO);
3871 } else if (*p == ' ') {
3872 // we should be calculating columns, not just SPACE
3873 for (j = 0; *p == ' ' && j < tabstop; j++) {
3874 text_hole_delete(p, p, NO_UNDO);
3877 } else if (c == '>') {
3878 // shift right -- add tab or 8 spaces
3879 char_insert(p, '\t', ALLOW_UNDO);
3882 dot = find_line(cnt); // what line were we on
3884 end_cmd_q(); // stop adding to q
3886 case 'A': // A- append at e-o-l
3887 dot_end(); // go to e-o-l
3888 //**** fall through to ... 'a'
3889 case 'a': // a- append after current char
3894 case 'B': // B- back a blank-delimited Word
3895 case 'E': // E- end of a blank-delimited word
3896 case 'W': // W- forward a blank-delimited word
3901 if (c == 'W' || isspace(dot[dir])) {
3902 dot = skip_thing(dot, 1, dir, S_TO_WS);
3903 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3906 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3907 } while (--cmdcnt > 0);
3909 case 'C': // C- Change to e-o-l
3910 case 'D': // D- delete to e-o-l
3912 dot = dollar_line(dot); // move to before NL
3913 // copy text into a register and delete
3914 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3916 goto dc_i; // start inserting
3917 #if ENABLE_FEATURE_VI_DOT_CMD
3919 end_cmd_q(); // stop adding to q
3922 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3923 c1 = get_one_char();
3926 // c1 < 0 if the key was special. Try "g<up-arrow>"
3927 // TODO: if Unicode?
3928 buf[1] = (c1 >= 0 ? c1 : '*');
3930 not_implemented(buf);
3936 case 'G': // G- goto to a line number (default= E-O-F)
3937 dot = end - 1; // assume E-O-F
3939 dot = find_line(cmdcnt); // what line is #cmdcnt
3943 case 'H': // H- goto top line on screen
3945 if (cmdcnt > (rows - 1)) {
3946 cmdcnt = (rows - 1);
3953 case 'I': // I- insert before first non-blank
3956 //**** fall through to ... 'i'
3957 case 'i': // i- insert before current char
3958 case KEYCODE_INSERT: // Cursor Key Insert
3960 cmd_mode = 1; // start inserting
3961 undo_queue_commit(); // commit queue when cmd_mode changes
3963 case 'J': // J- join current and next lines together
3965 dot_end(); // move to NL
3966 if (dot < end - 1) { // make sure not last char in text[]
3967 #if ENABLE_FEATURE_VI_UNDO
3968 undo_push(dot, 1, UNDO_DEL);
3969 *dot++ = ' '; // replace NL with space
3970 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3975 while (isblank(*dot)) { // delete leading WS
3976 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3979 } while (--cmdcnt > 0);
3980 end_cmd_q(); // stop adding to q
3982 case 'L': // L- goto bottom line on screen
3984 if (cmdcnt > (rows - 1)) {
3985 cmdcnt = (rows - 1);
3993 case 'M': // M- goto middle line on screen
3995 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3996 dot = next_line(dot);
3998 case 'O': // O- open a empty line above
4000 p = begin_line(dot);
4001 if (p[-1] == '\n') {
4003 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
4005 dot = char_insert(dot, '\n', ALLOW_UNDO);
4008 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
4013 case 'R': // R- continuous Replace char
4016 undo_queue_commit();
4018 case KEYCODE_DELETE:
4020 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
4022 case 'X': // X- delete char before dot
4023 case 'x': // x- delete the current char
4024 case 's': // s- substitute the current char
4029 if (dot[dir] != '\n') {
4031 dot--; // delete prev char
4032 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
4034 } while (--cmdcnt > 0);
4035 end_cmd_q(); // stop adding to q
4037 goto dc_i; // start inserting
4039 case 'Z': // Z- if modified, {write}; exit
4040 // ZZ means to save file (if necessary), then exit
4041 c1 = get_one_char();
4046 if (modified_count) {
4047 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
4048 status_line_bold("'%s' is read only", current_filename);
4051 cnt = file_write(current_filename, text, end - 1);
4054 status_line_bold("Write error: %s", strerror(errno));
4055 } else if (cnt == (end - 1 - text + 1)) {
4062 case '^': // ^- move to first non-blank on line
4066 case 'b': // b- back a word
4067 case 'e': // e- end of word
4072 if ((dot + dir) < text || (dot + dir) > end - 1)
4075 if (isspace(*dot)) {
4076 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4078 if (isalnum(*dot) || *dot == '_') {
4079 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4080 } else if (ispunct(*dot)) {
4081 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4083 } while (--cmdcnt > 0);
4085 case 'c': // c- change something
4086 case 'd': // d- delete something
4087 #if ENABLE_FEATURE_VI_YANKMARK
4088 case 'y': // y- yank something
4089 case 'Y': // Y- Yank a line
4092 int yf, ml, whole = 0;
4093 yf = YANKDEL; // assume either "c" or "d"
4094 #if ENABLE_FEATURE_VI_YANKMARK
4095 if (c == 'y' || c == 'Y')
4100 c1 = get_one_char(); // get the type of thing to delete
4101 // determine range, and whether it spans lines
4102 ml = find_range(&p, &q, c1);
4104 if (c1 == 27) { // ESC- user changed mind and wants out
4105 c = c1 = 27; // Escape- do nothing
4106 } else if (strchr("wW", c1)) {
4108 // don't include trailing WS as part of word
4109 while (isblank(*q)) {
4110 if (q <= text || q[-1] == '\n')
4115 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4116 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
4117 // partial line copy text into a register and delete
4118 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4119 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
4120 // whole line copy text into a register and delete
4121 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
4124 // could not recognize object
4125 c = c1 = 27; // error-
4131 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4132 // on the last line of file don't move to prev line
4133 if (whole && dot != (end-1)) {
4136 } else if (c == 'd') {
4142 // if CHANGING, not deleting, start inserting after the delete
4144 strcpy(buf, "Change");
4145 goto dc_i; // start inserting
4148 strcpy(buf, "Delete");
4150 #if ENABLE_FEATURE_VI_YANKMARK
4151 if (c == 'y' || c == 'Y') {
4152 strcpy(buf, "Yank");
4156 for (cnt = 0; p <= q; p++) {
4160 status_line("%s %d lines (%d chars) using [%c]",
4161 buf, cnt, strlen(reg[YDreg]), what_reg());
4163 end_cmd_q(); // stop adding to q
4167 case 'k': // k- goto prev line, same col
4168 case KEYCODE_UP: // cursor key Up
4171 dot = move_to_col(dot, ccol + offset); // try stay in same col
4172 } while (--cmdcnt > 0);
4174 case 'r': // r- replace the current char with user input
4175 c1 = get_one_char(); // get the replacement char
4177 #if ENABLE_FEATURE_VI_UNDO
4178 undo_push(dot, 1, UNDO_DEL);
4180 undo_push(dot, 1, UNDO_INS_CHAIN);
4186 end_cmd_q(); // stop adding to q
4188 case 't': // t- move to char prior to next x
4189 last_forward_char = get_one_char();
4191 if (*dot == last_forward_char)
4193 last_forward_char = 0;
4195 case 'w': // w- forward a word
4197 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4198 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4199 } else if (ispunct(*dot)) { // we are on PUNCT
4200 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4203 dot++; // move over word
4204 if (isspace(*dot)) {
4205 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4207 } while (--cmdcnt > 0);
4210 c1 = get_one_char(); // get the replacement char
4213 cnt = (rows - 2) / 2; // put dot at center
4215 cnt = rows - 2; // put dot at bottom
4216 screenbegin = begin_line(dot); // start dot at top
4217 dot_scroll(cnt, -1);
4219 case '|': // |- move to column "cmdcnt"
4220 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4222 case '~': // ~- flip the case of letters a-z -> A-Z
4224 #if ENABLE_FEATURE_VI_UNDO
4225 if (islower(*dot)) {
4226 undo_push(dot, 1, UNDO_DEL);
4227 *dot = toupper(*dot);
4228 undo_push(dot, 1, UNDO_INS_CHAIN);
4229 } else if (isupper(*dot)) {
4230 undo_push(dot, 1, UNDO_DEL);
4231 *dot = tolower(*dot);
4232 undo_push(dot, 1, UNDO_INS_CHAIN);
4235 if (islower(*dot)) {
4236 *dot = toupper(*dot);
4238 } else if (isupper(*dot)) {
4239 *dot = tolower(*dot);
4244 } while (--cmdcnt > 0);
4245 end_cmd_q(); // stop adding to q
4247 //----- The Cursor and Function Keys -----------------------------
4248 case KEYCODE_HOME: // Cursor Key Home
4251 // The Fn keys could point to do_macro which could translate them
4253 case KEYCODE_FUN1: // Function Key F1
4254 case KEYCODE_FUN2: // Function Key F2
4255 case KEYCODE_FUN3: // Function Key F3
4256 case KEYCODE_FUN4: // Function Key F4
4257 case KEYCODE_FUN5: // Function Key F5
4258 case KEYCODE_FUN6: // Function Key F6
4259 case KEYCODE_FUN7: // Function Key F7
4260 case KEYCODE_FUN8: // Function Key F8
4261 case KEYCODE_FUN9: // Function Key F9
4262 case KEYCODE_FUN10: // Function Key F10
4263 case KEYCODE_FUN11: // Function Key F11
4264 case KEYCODE_FUN12: // Function Key F12
4270 // if text[] just became empty, add back an empty line
4272 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4275 // it is OK for dot to exactly equal to end, otherwise check dot validity
4277 dot = bound_dot(dot); // make sure "dot" is valid
4279 #if ENABLE_FEATURE_VI_YANKMARK
4280 check_context(c); // update the current context
4284 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4285 cnt = dot - begin_line(dot);
4286 // Try to stay off of the Newline
4287 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4291 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4292 #if ENABLE_FEATURE_VI_CRASHME
4293 static int totalcmds = 0;
4294 static int Mp = 85; // Movement command Probability
4295 static int Np = 90; // Non-movement command Probability
4296 static int Dp = 96; // Delete command Probability
4297 static int Ip = 97; // Insert command Probability
4298 static int Yp = 98; // Yank command Probability
4299 static int Pp = 99; // Put command Probability
4300 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4301 static const char chars[20] = "\t012345 abcdABCD-=.$";
4302 static const char *const words[20] = {
4303 "this", "is", "a", "test",
4304 "broadcast", "the", "emergency", "of",
4305 "system", "quick", "brown", "fox",
4306 "jumped", "over", "lazy", "dogs",
4307 "back", "January", "Febuary", "March"
4309 static const char *const lines[20] = {
4310 "You should have received a copy of the GNU General Public License\n",
4311 "char c, cm, *cmd, *cmd1;\n",
4312 "generate a command by percentages\n",
4313 "Numbers may be typed as a prefix to some commands.\n",
4314 "Quit, discarding changes!\n",
4315 "Forced write, if permission originally not valid.\n",
4316 "In general, any ex or ed command (such as substitute or delete).\n",
4317 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4318 "Please get w/ me and I will go over it with you.\n",
4319 "The following is a list of scheduled, committed changes.\n",
4320 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4321 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4322 "Any question about transactions please contact Sterling Huxley.\n",
4323 "I will try to get back to you by Friday, December 31.\n",
4324 "This Change will be implemented on Friday.\n",
4325 "Let me know if you have problems accessing this;\n",
4326 "Sterling Huxley recently added you to the access list.\n",
4327 "Would you like to go to lunch?\n",
4328 "The last command will be automatically run.\n",
4329 "This is too much english for a computer geek.\n",
4331 static char *multilines[20] = {
4332 "You should have received a copy of the GNU General Public License\n",
4333 "char c, cm, *cmd, *cmd1;\n",
4334 "generate a command by percentages\n",
4335 "Numbers may be typed as a prefix to some commands.\n",
4336 "Quit, discarding changes!\n",
4337 "Forced write, if permission originally not valid.\n",
4338 "In general, any ex or ed command (such as substitute or delete).\n",
4339 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4340 "Please get w/ me and I will go over it with you.\n",
4341 "The following is a list of scheduled, committed changes.\n",
4342 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4343 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4344 "Any question about transactions please contact Sterling Huxley.\n",
4345 "I will try to get back to you by Friday, December 31.\n",
4346 "This Change will be implemented on Friday.\n",
4347 "Let me know if you have problems accessing this;\n",
4348 "Sterling Huxley recently added you to the access list.\n",
4349 "Would you like to go to lunch?\n",
4350 "The last command will be automatically run.\n",
4351 "This is too much english for a computer geek.\n",
4354 // create a random command to execute
4355 static void crash_dummy()
4357 static int sleeptime; // how long to pause between commands
4358 char c, cm, *cmd, *cmd1;
4359 int i, cnt, thing, rbi, startrbi, percent;
4361 // "dot" movement commands
4362 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4364 // is there already a command running?
4365 if (readbuffer[0] > 0)
4368 readbuffer[0] = 'X';
4370 sleeptime = 0; // how long to pause between commands
4371 memset(readbuffer, '\0', sizeof(readbuffer));
4372 // generate a command by percentages
4373 percent = (int) lrand48() % 100; // get a number from 0-99
4374 if (percent < Mp) { // Movement commands
4375 // available commands
4378 } else if (percent < Np) { // non-movement commands
4379 cmd = "mz<>\'\""; // available commands
4381 } else if (percent < Dp) { // Delete commands
4382 cmd = "dx"; // available commands
4384 } else if (percent < Ip) { // Inset commands
4385 cmd = "iIaAsrJ"; // available commands
4387 } else if (percent < Yp) { // Yank commands
4388 cmd = "yY"; // available commands
4390 } else if (percent < Pp) { // Put commands
4391 cmd = "pP"; // available commands
4394 // We do not know how to handle this command, try again
4398 // randomly pick one of the available cmds from "cmd[]"
4399 i = (int) lrand48() % strlen(cmd);
4401 if (strchr(":\024", cm))
4402 goto cd0; // dont allow colon or ctrl-T commands
4403 readbuffer[rbi++] = cm; // put cmd into input buffer
4405 // now we have the command-
4406 // there are 1, 2, and multi char commands
4407 // find out which and generate the rest of command as necessary
4408 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4409 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4410 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4411 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4413 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4415 readbuffer[rbi++] = c; // add movement to input buffer
4417 if (strchr("iIaAsc", cm)) { // multi-char commands
4419 // change some thing
4420 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4422 readbuffer[rbi++] = c; // add movement to input buffer
4424 thing = (int) lrand48() % 4; // what thing to insert
4425 cnt = (int) lrand48() % 10; // how many to insert
4426 for (i = 0; i < cnt; i++) {
4427 if (thing == 0) { // insert chars
4428 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4429 } else if (thing == 1) { // insert words
4430 strcat(readbuffer, words[(int) lrand48() % 20]);
4431 strcat(readbuffer, " ");
4432 sleeptime = 0; // how fast to type
4433 } else if (thing == 2) { // insert lines
4434 strcat(readbuffer, lines[(int) lrand48() % 20]);
4435 sleeptime = 0; // how fast to type
4436 } else { // insert multi-lines
4437 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4438 sleeptime = 0; // how fast to type
4441 strcat(readbuffer, "\033");
4443 readbuffer[0] = strlen(readbuffer + 1);
4447 mysleep(sleeptime); // sleep 1/100 sec
4450 // test to see if there are any errors
4451 static void crash_test()
4453 static time_t oldtim;
4460 strcat(msg, "end<text ");
4462 if (end > textend) {
4463 strcat(msg, "end>textend ");
4466 strcat(msg, "dot<text ");
4469 strcat(msg, "dot>end ");
4471 if (screenbegin < text) {
4472 strcat(msg, "screenbegin<text ");
4474 if (screenbegin > end - 1) {
4475 strcat(msg, "screenbegin>end-1 ");
4479 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4480 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4482 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4483 if (d[0] == '\n' || d[0] == '\r')
4488 if (tim >= (oldtim + 3)) {
4489 sprintf(status_buffer,
4490 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4491 totalcmds, M, N, I, D, Y, P, U, end - text + 1);