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.
11 * $HOME/.exrc and ./.exrc
12 * add magic to search /foo.*bar
15 * if mark[] values were line numbers rather than pointers
16 * it would be easier to change the mark when add/delete lines
17 * More intelligence in refresh()
18 * ":r !cmd" and "!cmd" to filter text through an external command
19 * An "ex" line oriented mode- maybe using "cmdedit"
22 //config: bool "vi (22 kb)"
25 //config: 'vi' is a text editor. More specifically, it is the One True
26 //config: text editor <grin>. It does, however, have a rather steep
27 //config: learning curve. If you are not already comfortable with 'vi'
28 //config: you may wish to use something else.
30 //config:config FEATURE_VI_MAX_LEN
31 //config: int "Maximum screen width"
32 //config: range 256 16384
33 //config: default 4096
34 //config: depends on VI
36 //config: Contrary to what you may think, this is not eating much.
37 //config: Make it smaller than 4k only if you are very limited on memory.
39 //config:config FEATURE_VI_8BIT
40 //config: bool "Allow to display 8-bit chars (otherwise shows dots)"
42 //config: depends on VI
44 //config: If your terminal can display characters with high bit set,
45 //config: you may want to enable this. Note: vi is not Unicode-capable.
46 //config: If your terminal combines several 8-bit bytes into one character
47 //config: (as in Unicode mode), this will not work properly.
49 //config:config FEATURE_VI_COLON
50 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
52 //config: depends on VI
54 //config: Enable a limited set of colon commands. This does not
55 //config: provide an "ex" mode.
57 //config:config FEATURE_VI_YANKMARK
58 //config: bool "Enable yank/put commands and mark cmds"
60 //config: depends on VI
62 //config: This enables you to use yank and put, as well as mark.
64 //config:config FEATURE_VI_SEARCH
65 //config: bool "Enable search and replace cmds"
67 //config: depends on VI
69 //config: Select this if you wish to be able to do search and replace.
71 //config:config FEATURE_VI_REGEX_SEARCH
72 //config: bool "Enable regex in search and replace"
73 //config: default n # Uses GNU regex, which may be unavailable. FIXME
74 //config: depends on FEATURE_VI_SEARCH
76 //config: Use extended regex search.
78 //config:config FEATURE_VI_USE_SIGNALS
79 //config: bool "Catch signals"
81 //config: depends on VI
83 //config: Selecting this option will make vi signal aware. This will support
84 //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
86 //config:config FEATURE_VI_DOT_CMD
87 //config: bool "Remember previous cmd and \".\" cmd"
89 //config: depends on VI
91 //config: Make vi remember the last command and be able to repeat it.
93 //config:config FEATURE_VI_READONLY
94 //config: bool "Enable -R option and \"view\" mode"
96 //config: depends on VI
98 //config: Enable the read-only command line option, which allows the user to
99 //config: open a file in read-only mode.
101 //config:config FEATURE_VI_SETOPTS
102 //config: bool "Enable settable options, ai ic showmatch"
104 //config: depends on VI
106 //config: Enable the editor to set some (ai, ic, showmatch) options.
108 //config:config FEATURE_VI_SET
109 //config: bool "Support :set"
111 //config: depends on VI
113 //config:config FEATURE_VI_WIN_RESIZE
114 //config: bool "Handle window resize"
116 //config: depends on VI
118 //config: Behave nicely with terminals that get resized.
120 //config:config FEATURE_VI_ASK_TERMINAL
121 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
123 //config: depends on VI
125 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
126 //config: this option makes vi perform a last-ditch effort to find it:
127 //config: position cursor to 999,999 and ask terminal to report real
128 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
129 //config: This is not clean but helps a lot on serial lines and such.
131 //config:config FEATURE_VI_UNDO
132 //config: bool "Support undo command \"u\""
134 //config: depends on VI
136 //config: Support the 'u' command to undo insertion, deletion, and replacement
139 //config:config FEATURE_VI_UNDO_QUEUE
140 //config: bool "Enable undo operation queuing"
142 //config: depends on FEATURE_VI_UNDO
144 //config: The vi undo functions can use an intermediate queue to greatly lower
145 //config: malloc() calls and overhead. When the maximum size of this queue is
146 //config: reached, the contents of the queue are committed to the undo stack.
147 //config: This increases the size of the undo code and allows some undo
148 //config: operations (especially un-typing/backspacing) to be far more useful.
150 //config:config FEATURE_VI_UNDO_QUEUE_MAX
151 //config: int "Maximum undo character queue size"
152 //config: default 256
153 //config: range 32 65536
154 //config: depends on FEATURE_VI_UNDO_QUEUE
156 //config: This option sets the number of bytes used at runtime for the queue.
157 //config: Smaller values will create more undo objects and reduce the amount
158 //config: of typed or backspaced characters that are grouped into one undo
159 //config: operation; larger values increase the potential size of each undo
160 //config: and will generally malloc() larger objects and less frequently.
161 //config: Unless you want more (or less) frequent "undo points" while typing,
162 //config: you should probably leave this unchanged.
164 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
166 //kbuild:lib-$(CONFIG_VI) += vi.o
168 //usage:#define vi_trivial_usage
169 //usage: "[OPTIONS] [FILE]..."
170 //usage:#define vi_full_usage "\n\n"
171 //usage: "Edit FILE\n"
172 //usage: IF_FEATURE_VI_COLON(
173 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
175 //usage: IF_FEATURE_VI_READONLY(
176 //usage: "\n -R Read-only"
178 //usage: "\n -H List available features"
181 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
182 #if ENABLE_FEATURE_VI_REGEX_SEARCH
186 /* the CRASHME code is unmaintained, and doesn't currently build */
187 #define ENABLE_FEATURE_VI_CRASHME 0
190 #if ENABLE_LOCALE_SUPPORT
192 #if ENABLE_FEATURE_VI_8BIT
193 //FIXME: this does not work properly for Unicode anyway
194 # define Isprint(c) (isprint)(c)
196 # define Isprint(c) isprint_asciionly(c)
201 /* 0x9b is Meta-ESC */
202 #if ENABLE_FEATURE_VI_8BIT
203 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
205 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
212 MAX_TABSTOP = 32, // sanity limit
213 // User input len. Need not be extra big.
214 // Lines in file being edited *can* be bigger than this.
216 // Sanity limits. We have only one buffer of this size.
217 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
218 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
221 /* VT102 ESC sequences.
222 * See "Xterm Control Sequences"
223 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
226 /* Inverse/Normal text */
227 #define ESC_BOLD_TEXT ESC"[7m"
228 #define ESC_NORM_TEXT ESC"[m"
230 #define ESC_BELL "\007"
231 /* Clear-to-end-of-line */
232 #define ESC_CLEAR2EOL ESC"[K"
233 /* Clear-to-end-of-screen.
234 * (We use default param here.
235 * Full sequence is "ESC [ <num> J",
236 * <num> is 0/1/2 = "erase below/above/all".)
238 #define ESC_CLEAR2EOS ESC"[J"
239 /* Cursor to given coordinate (1,1: top left) */
240 #define ESC_SET_CURSOR_POS ESC"[%u;%uH"
242 ///* Cursor up and down */
243 //#define ESC_CURSOR_UP ESC"[A"
244 //#define ESC_CURSOR_DOWN "\n"
246 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
247 // cmds modifying text[]
248 // vda: removed "aAiIs" as they switch us into insert mode
249 // and remembering input for replay after them makes no sense
250 static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
256 FORWARD = 1, // code depends on "1" for array index
257 BACK = -1, // code depends on "-1" for array index
258 LIMITED = 0, // how much of text[] in char_search
259 FULL = 1, // how much of text[] in char_search
261 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
262 S_TO_WS = 2, // used in skip_thing() for moving "dot"
263 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
264 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
265 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
269 /* vi.c expects chars to be unsigned. */
270 /* busybox build system provides that, but it's better */
271 /* to audit and fix the source */
274 /* many references - keep near the top of globals */
275 char *text, *end; // pointers to the user data in memory
276 char *dot; // where all the action takes place
277 int text_size; // size of the allocated buffer
281 #define VI_AUTOINDENT 1
282 #define VI_SHOWMATCH 2
283 #define VI_IGNORECASE 4
284 #define VI_ERR_METHOD 8
285 #define autoindent (vi_setops & VI_AUTOINDENT)
286 #define showmatch (vi_setops & VI_SHOWMATCH )
287 #define ignorecase (vi_setops & VI_IGNORECASE)
288 /* indicate error with beep or flash */
289 #define err_method (vi_setops & VI_ERR_METHOD)
291 #if ENABLE_FEATURE_VI_READONLY
292 smallint readonly_mode;
293 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
294 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
295 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
297 #define SET_READONLY_FILE(flags) ((void)0)
298 #define SET_READONLY_MODE(flags) ((void)0)
299 #define UNSET_READONLY_FILE(flags) ((void)0)
302 smallint editing; // >0 while we are editing a file
303 // [code audit says "can be 0, 1 or 2 only"]
304 smallint cmd_mode; // 0=command 1=insert 2=replace
305 int modified_count; // buffer contents changed if !0
306 int last_modified_count; // = -1;
307 int save_argc; // how many file names on cmd line
308 int cmdcnt; // repetition count
309 unsigned rows, columns; // the terminal screen is this size
310 #if ENABLE_FEATURE_VI_ASK_TERMINAL
311 int get_rowcol_error;
313 int crow, ccol; // cursor is on Crow x Ccol
314 int offset; // chars scrolled off the screen to the left
315 int have_status_msg; // is default edit status needed?
316 // [don't make smallint!]
317 int last_status_cksum; // hash of current status line
318 char *current_filename;
319 char *screenbegin; // index into text[], of top line on the screen
320 char *screen; // pointer to the virtual screen buffer
321 int screensize; // and its size
323 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
324 char erase_char; // the users erase character
325 char last_input_char; // last char read from user
327 #if ENABLE_FEATURE_VI_DOT_CMD
328 smallint adding2q; // are we currently adding user input to q
329 int lmc_len; // length of last_modifying_cmd
330 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
332 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
335 #if ENABLE_FEATURE_VI_SEARCH
336 char *last_search_pattern; // last pattern from a '/' or '?' search
340 #if ENABLE_FEATURE_VI_YANKMARK
341 char *edit_file__cur_line;
343 int refresh__old_offset;
344 int format_edit_status__tot;
346 /* a few references only */
347 #if ENABLE_FEATURE_VI_YANKMARK
348 int YDreg, Ureg; // default delete register and orig line for "U"
349 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
350 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
351 char *context_start, *context_end;
353 #if ENABLE_FEATURE_VI_USE_SIGNALS
354 sigjmp_buf restart; // catch_sig()
356 struct termios term_orig; // remember what the cooked mode was
357 #if ENABLE_FEATURE_VI_COLON
358 char *initial_cmds[3]; // currently 2 entries, NULL terminated
360 // Should be just enough to hold a key sequence,
361 // but CRASHME mode uses it as generated command buffer too
362 #if ENABLE_FEATURE_VI_CRASHME
363 char readbuffer[128];
365 char readbuffer[KEYCODE_BUFFER_SIZE];
367 #define STATUS_BUFFER_LEN 200
368 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
369 #if ENABLE_FEATURE_VI_DOT_CMD
370 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
372 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
374 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
375 #if ENABLE_FEATURE_VI_UNDO
376 // undo_push() operations
379 #define UNDO_INS_CHAIN 2
380 #define UNDO_DEL_CHAIN 3
381 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
382 #define UNDO_QUEUED_FLAG 4
383 #define UNDO_INS_QUEUED 4
384 #define UNDO_DEL_QUEUED 5
385 #define UNDO_USE_SPOS 32
386 #define UNDO_EMPTY 64
387 // Pass-through flags for functions that can be undone
390 #define ALLOW_UNDO_CHAIN 2
391 # if ENABLE_FEATURE_VI_UNDO_QUEUE
392 #define ALLOW_UNDO_QUEUED 3
393 char undo_queue_state;
395 char *undo_queue_spos; // Start position of queued operation
396 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
398 // If undo queuing disabled, don't invoke the missing queue logic
399 #define ALLOW_UNDO_QUEUED 1
403 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
404 int start; // Offset where the data should be restored/deleted
405 int length; // total data size
406 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
407 char undo_text[1]; // text that was deleted (if deletion)
409 #endif /* ENABLE_FEATURE_VI_UNDO */
411 #define G (*ptr_to_globals)
412 #define text (G.text )
413 #define text_size (G.text_size )
418 #define vi_setops (G.vi_setops )
419 #define editing (G.editing )
420 #define cmd_mode (G.cmd_mode )
421 #define modified_count (G.modified_count )
422 #define last_modified_count (G.last_modified_count)
423 #define save_argc (G.save_argc )
424 #define cmdcnt (G.cmdcnt )
425 #define rows (G.rows )
426 #define columns (G.columns )
427 #define crow (G.crow )
428 #define ccol (G.ccol )
429 #define offset (G.offset )
430 #define status_buffer (G.status_buffer )
431 #define have_status_msg (G.have_status_msg )
432 #define last_status_cksum (G.last_status_cksum )
433 #define current_filename (G.current_filename )
434 #define screen (G.screen )
435 #define screensize (G.screensize )
436 #define screenbegin (G.screenbegin )
437 #define tabstop (G.tabstop )
438 #define last_forward_char (G.last_forward_char )
439 #define erase_char (G.erase_char )
440 #define last_input_char (G.last_input_char )
441 #if ENABLE_FEATURE_VI_READONLY
442 #define readonly_mode (G.readonly_mode )
444 #define readonly_mode 0
446 #define adding2q (G.adding2q )
447 #define lmc_len (G.lmc_len )
449 #define ioq_start (G.ioq_start )
450 #define my_pid (G.my_pid )
451 #define last_search_pattern (G.last_search_pattern)
453 #define edit_file__cur_line (G.edit_file__cur_line)
454 #define refresh__old_offset (G.refresh__old_offset)
455 #define format_edit_status__tot (G.format_edit_status__tot)
457 #define YDreg (G.YDreg )
458 #define Ureg (G.Ureg )
459 #define mark (G.mark )
460 #define context_start (G.context_start )
461 #define context_end (G.context_end )
462 #define restart (G.restart )
463 #define term_orig (G.term_orig )
464 #define initial_cmds (G.initial_cmds )
465 #define readbuffer (G.readbuffer )
466 #define scr_out_buf (G.scr_out_buf )
467 #define last_modifying_cmd (G.last_modifying_cmd )
468 #define get_input_line__buf (G.get_input_line__buf)
470 #if ENABLE_FEATURE_VI_UNDO
471 #define undo_stack_tail (G.undo_stack_tail )
472 # if ENABLE_FEATURE_VI_UNDO_QUEUE
473 #define undo_queue_state (G.undo_queue_state)
474 #define undo_q (G.undo_q )
475 #define undo_queue (G.undo_queue )
476 #define undo_queue_spos (G.undo_queue_spos )
480 #define INIT_G() do { \
481 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
482 last_modified_count = -1; \
483 /* "" but has space for 2 chars: */ \
484 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
488 static void edit_file(char *); // edit one file
489 static void do_cmd(int); // execute a command
490 static int next_tabstop(int);
491 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
492 static char *begin_line(char *); // return pointer to cur line B-o-l
493 static char *end_line(char *); // return pointer to cur line E-o-l
494 static char *prev_line(char *); // return pointer to prev line B-o-l
495 static char *next_line(char *); // return pointer to next line B-o-l
496 static char *end_screen(void); // get pointer to last char on screen
497 static int count_lines(char *, char *); // count line from start to stop
498 static char *find_line(int); // find beginning of line #li
499 static char *move_to_col(char *, int); // move "p" to column l
500 static void dot_left(void); // move dot left- dont leave line
501 static void dot_right(void); // move dot right- dont leave line
502 static void dot_begin(void); // move dot to B-o-l
503 static void dot_end(void); // move dot to E-o-l
504 static void dot_next(void); // move dot to next line B-o-l
505 static void dot_prev(void); // move dot to prev line B-o-l
506 static void dot_scroll(int, int); // move the screen up or down
507 static void dot_skip_over_ws(void); // move dot pat WS
508 static char *bound_dot(char *); // make sure text[0] <= P < "end"
509 static char *new_screen(int, int); // malloc virtual screen memory
510 #if !ENABLE_FEATURE_VI_UNDO
511 #define char_insert(a,b,c) char_insert(a,b)
513 static char *char_insert(char *, char, int); // insert the char c at 'p'
514 // might reallocate text[]! use p += stupid_insert(p, ...),
515 // and be careful to not use pointers into potentially freed text[]!
516 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
517 static int find_range(char **, char **, char); // return pointers for an object
518 static int st_test(char *, int, int, char *); // helper for skip_thing()
519 static char *skip_thing(char *, int, int, int); // skip some object
520 static char *find_pair(char *, char); // find matching pair () [] {}
521 #if !ENABLE_FEATURE_VI_UNDO
522 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
524 static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
525 // might reallocate text[]! use p += text_hole_make(p, ...),
526 // and be careful to not use pointers into potentially freed text[]!
527 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
528 #if !ENABLE_FEATURE_VI_UNDO
529 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
531 static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
532 static void show_help(void); // display some help info
533 static void rawmode(void); // set "raw" mode on tty
534 static void cookmode(void); // return to "cooked" mode on tty
535 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
536 static int mysleep(int);
537 static int readit(void); // read (maybe cursor) key from stdin
538 static int get_one_char(void); // read 1 char from stdin
539 // file_insert might reallocate text[]!
540 static int file_insert(const char *, char *, int);
541 static int file_write(char *, char *, char *);
542 static void place_cursor(int, int);
543 static void screen_erase(void);
544 static void clear_to_eol(void);
545 static void clear_to_eos(void);
546 static void go_bottom_and_clear_to_eol(void);
547 static void standout_start(void); // send "start reverse video" sequence
548 static void standout_end(void); // send "end reverse video" sequence
549 static void flash(int); // flash the terminal screen
550 static void show_status_line(void); // put a message on the bottom line
551 static void status_line(const char *, ...); // print to status buf
552 static void status_line_bold(const char *, ...);
553 static void status_line_bold_errno(const char *fn);
554 static void not_implemented(const char *); // display "Not implemented" message
555 static int format_edit_status(void); // format file status on status line
556 static void redraw(int); // force a full screen refresh
557 static char* format_line(char* /*, int*/);
558 static void refresh(int); // update the terminal from screen[]
560 static void indicate_error(void); // use flash or beep to indicate error
561 static void Hit_Return(void);
563 #if ENABLE_FEATURE_VI_SEARCH
564 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
566 #if ENABLE_FEATURE_VI_COLON
567 static char *get_one_address(char *, int *); // get colon addr, if present
568 static char *get_address(char *, int *, int *); // get two colon addrs, if present
570 static void colon(char *); // execute the "colon" mode cmds
571 #if ENABLE_FEATURE_VI_USE_SIGNALS
572 static void winch_sig(int); // catch window size changes
573 static void suspend_sig(int); // catch ctrl-Z
574 static void catch_sig(int); // catch ctrl-C and alarm time-outs
576 #if ENABLE_FEATURE_VI_DOT_CMD
577 static void start_new_cmd_q(char); // new queue for command
578 static void end_cmd_q(void); // stop saving input chars
580 #define end_cmd_q() ((void)0)
582 #if ENABLE_FEATURE_VI_SETOPTS
583 static void showmatching(char *); // show the matching pair () [] {}
585 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
586 // might reallocate text[]! use p += string_insert(p, ...),
587 // and be careful to not use pointers into potentially freed text[]!
588 # if !ENABLE_FEATURE_VI_UNDO
589 #define string_insert(a,b,c) string_insert(a,b)
591 static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
593 #if ENABLE_FEATURE_VI_YANKMARK
594 static char *text_yank(char *, char *, int); // save copy of "p" into a register
595 static char what_reg(void); // what is letter of current YDreg
596 static void check_context(char); // remember context for '' command
598 #if ENABLE_FEATURE_VI_UNDO
599 static void flush_undo_data(void);
600 static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
601 static void undo_pop(void); // Undo the last operation
602 # if ENABLE_FEATURE_VI_UNDO_QUEUE
603 static void undo_queue_commit(void); // Flush any queued objects to the undo stack
605 # define undo_queue_commit() ((void)0)
608 #define flush_undo_data() ((void)0)
609 #define undo_queue_commit() ((void)0)
612 #if ENABLE_FEATURE_VI_CRASHME
613 static void crash_dummy();
614 static void crash_test();
615 static int crashme = 0;
618 static void write1(const char *out)
623 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
624 int vi_main(int argc, char **argv)
630 #if ENABLE_FEATURE_VI_UNDO
631 /* undo_stack_tail = NULL; - already is */
632 #if ENABLE_FEATURE_VI_UNDO_QUEUE
633 undo_queue_state = UNDO_EMPTY;
634 /* undo_q = 0; - already is */
638 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
641 #if ENABLE_FEATURE_VI_CRASHME
642 srand((long) my_pid);
644 #ifdef NO_SUCH_APPLET_YET
645 /* If we aren't "vi", we are "view" */
646 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
647 SET_READONLY_MODE(readonly_mode);
651 // autoindent is not default in vim 7.3
652 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
653 // 1- process $HOME/.exrc file (not inplemented yet)
654 // 2- process EXINIT variable from environment
655 // 3- process command line args
656 #if ENABLE_FEATURE_VI_COLON
658 char *p = getenv("EXINIT");
660 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
663 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
665 #if ENABLE_FEATURE_VI_CRASHME
670 #if ENABLE_FEATURE_VI_READONLY
671 case 'R': // Read-only flag
672 SET_READONLY_MODE(readonly_mode);
675 #if ENABLE_FEATURE_VI_COLON
676 case 'c': // cmd line vi command
678 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
690 // The argv array can be used by the ":next" and ":rewind" commands
694 //----- This is the main file handling loop --------------
697 // "Save cursor, use alternate screen buffer, clear screen"
698 write1(ESC"[?1049h");
700 edit_file(argv[optind]); /* param might be NULL */
701 if (++optind >= argc)
704 // "Use normal screen buffer, restore cursor"
705 write1(ESC"[?1049l");
706 //-----------------------------------------------------------
711 /* read text from file or create an empty buf */
712 /* will also update current_filename */
713 static int init_text_buffer(char *fn)
717 /* allocate/reallocate text buffer */
720 screenbegin = dot = end = text = xzalloc(text_size);
722 if (fn != current_filename) {
723 free(current_filename);
724 current_filename = xstrdup(fn);
726 rc = file_insert(fn, text, 1);
728 // file doesnt exist. Start empty buf with dummy line
729 char_insert(text, '\n', NO_UNDO);
734 last_modified_count = -1;
735 #if ENABLE_FEATURE_VI_YANKMARK
737 memset(mark, 0, sizeof(mark));
742 #if ENABLE_FEATURE_VI_WIN_RESIZE
743 static int query_screen_dimensions(void)
745 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
746 if (rows > MAX_SCR_ROWS)
748 if (columns > MAX_SCR_COLS)
749 columns = MAX_SCR_COLS;
753 # define query_screen_dimensions() (0)
756 static void edit_file(char *fn)
758 #if ENABLE_FEATURE_VI_YANKMARK
759 #define cur_line edit_file__cur_line
762 #if ENABLE_FEATURE_VI_USE_SIGNALS
766 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
770 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
771 #if ENABLE_FEATURE_VI_ASK_TERMINAL
772 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
774 write1(ESC"[999;999H" ESC"[6n");
776 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
777 if ((int32_t)k == KEYCODE_CURSOR_POS) {
778 uint32_t rc = (k >> 32);
779 columns = (rc & 0x7fff);
780 if (columns > MAX_SCR_COLS)
781 columns = MAX_SCR_COLS;
782 rows = ((rc >> 16) & 0x7fff);
783 if (rows > MAX_SCR_ROWS)
788 new_screen(rows, columns); // get memory for virtual screen
789 init_text_buffer(fn);
791 #if ENABLE_FEATURE_VI_YANKMARK
792 YDreg = 26; // default Yank/Delete reg
793 Ureg = 27; // hold orig line for "U" cmd
794 mark[26] = mark[27] = text; // init "previous context"
797 last_forward_char = last_input_char = '\0';
801 #if ENABLE_FEATURE_VI_USE_SIGNALS
802 signal(SIGINT, catch_sig);
803 signal(SIGWINCH, winch_sig);
804 signal(SIGTSTP, suspend_sig);
805 sig = sigsetjmp(restart, 1);
807 screenbegin = dot = text;
811 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
814 offset = 0; // no horizontal offset
816 #if ENABLE_FEATURE_VI_DOT_CMD
818 ioq = ioq_start = NULL;
823 #if ENABLE_FEATURE_VI_COLON
828 while ((p = initial_cmds[n]) != NULL) {
838 free(initial_cmds[n]);
839 initial_cmds[n] = NULL;
844 redraw(FALSE); // dont force every col re-draw
845 //------This is the main Vi cmd handling loop -----------------------
846 while (editing > 0) {
847 #if ENABLE_FEATURE_VI_CRASHME
849 if ((end - text) > 1) {
850 crash_dummy(); // generate a random command
853 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
859 last_input_char = c = get_one_char(); // get a cmd from user
860 #if ENABLE_FEATURE_VI_YANKMARK
861 // save a copy of the current line- for the 'U" command
862 if (begin_line(dot) != cur_line) {
863 cur_line = begin_line(dot);
864 text_yank(begin_line(dot), end_line(dot), Ureg);
867 #if ENABLE_FEATURE_VI_DOT_CMD
868 // These are commands that change text[].
869 // Remember the input for the "." command
870 if (!adding2q && ioq_start == NULL
871 && cmd_mode == 0 // command mode
872 && c > '\0' // exclude NUL and non-ASCII chars
873 && c < 0x7f // (Unicode and such)
874 && strchr(modifying_cmds, c)
879 do_cmd(c); // execute the user command
881 // poll to see if there is input already waiting. if we are
882 // not able to display output fast enough to keep up, skip
883 // the display update until we catch up with input.
884 if (!readbuffer[0] && mysleep(0) == 0) {
885 // no input pending - so update output
889 #if ENABLE_FEATURE_VI_CRASHME
891 crash_test(); // test editor variables
894 //-------------------------------------------------------------------
896 go_bottom_and_clear_to_eol();
901 //----- The Colon commands -------------------------------------
902 #if ENABLE_FEATURE_VI_COLON
903 static char *get_one_address(char *p, int *addr) // get colon addr, if present
907 IF_FEATURE_VI_YANKMARK(char c;)
908 IF_FEATURE_VI_SEARCH(char *pat;)
910 *addr = -1; // assume no addr
911 if (*p == '.') { // the current line
914 *addr = count_lines(text, q);
916 #if ENABLE_FEATURE_VI_YANKMARK
917 else if (*p == '\'') { // is this a mark addr
921 if (c >= 'a' && c <= 'z') {
924 q = mark[(unsigned char) c];
925 if (q != NULL) { // is mark valid
926 *addr = count_lines(text, q);
931 #if ENABLE_FEATURE_VI_SEARCH
932 else if (*p == '/') { // a search pattern
933 q = strchrnul(++p, '/');
934 pat = xstrndup(p, q - p); // save copy of pattern
938 q = char_search(dot, pat, FORWARD, FULL);
940 *addr = count_lines(text, q);
945 else if (*p == '$') { // the last line in file
947 q = begin_line(end - 1);
948 *addr = count_lines(text, q);
949 } else if (isdigit(*p)) { // specific line number
950 sscanf(p, "%d%n", addr, &st);
953 // unrecognized address - assume -1
959 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
961 //----- get the address' i.e., 1,3 'a,'b -----
962 // get FIRST addr, if present
964 p++; // skip over leading spaces
965 if (*p == '%') { // alias for 1,$
968 *e = count_lines(text, end-1);
971 p = get_one_address(p, b);
974 if (*p == ',') { // is there a address separator
978 // get SECOND addr, if present
979 p = get_one_address(p, e);
983 p++; // skip over trailing spaces
987 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
988 static void setops(const char *args, const char *opname, int flg_no,
989 const char *short_opname, int opt)
991 const char *a = args + flg_no;
992 int l = strlen(opname) - 1; /* opname have + ' ' */
994 // maybe strncmp? we had tons of erroneous strncasecmp's...
995 if (strncasecmp(a, opname, l) == 0
996 || strncasecmp(a, short_opname, 2) == 0
1006 #endif /* FEATURE_VI_COLON */
1008 // buf must be no longer than MAX_INPUT_LEN!
1009 static void colon(char *buf)
1011 #if !ENABLE_FEATURE_VI_COLON
1012 /* Simple ":cmd" handler with minimal set of commands */
1021 if (strncmp(p, "quit", cnt) == 0
1022 || strncmp(p, "q!", cnt) == 0
1024 if (modified_count && p[1] != '!') {
1025 status_line_bold("No write since last change (:%s! overrides)", p);
1031 if (strncmp(p, "write", cnt) == 0
1032 || strncmp(p, "wq", cnt) == 0
1033 || strncmp(p, "wn", cnt) == 0
1034 || (p[0] == 'x' && !p[1])
1036 if (modified_count != 0 || p[0] != 'x') {
1037 cnt = file_write(current_filename, text, end - 1);
1041 status_line_bold("Write error: %s", strerror(errno));
1044 last_modified_count = -1;
1045 status_line("'%s' %dL, %dC",
1047 count_lines(text, end - 1), cnt
1050 || p[1] == 'q' || p[1] == 'n'
1051 || p[1] == 'Q' || p[1] == 'N'
1058 if (strncmp(p, "file", cnt) == 0) {
1059 last_status_cksum = 0; // force status update
1062 if (sscanf(p, "%d", &cnt) > 0) {
1063 dot = find_line(cnt);
1070 char c, *orig_buf, *buf1, *q, *r;
1071 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
1075 // :3154 // if (-e line 3154) goto it else stay put
1076 // :4,33w! foo // write a portion of buffer to file "foo"
1077 // :w // write all of buffer to current file
1079 // :q! // quit- dont care about modified file
1080 // :'a,'z!sort -u // filter block through sort
1081 // :'f // goto mark "f"
1082 // :'fl // list literal the mark "f" line
1083 // :.r bar // read file "bar" into buffer before dot
1084 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1085 // :/xyz/ // goto the "xyz" line
1086 // :s/find/replace/ // substitute pattern "find" with "replace"
1087 // :!<cmd> // run <cmd> then return
1093 buf++; // move past the ':'
1097 q = text; // assume 1,$ for the range
1099 li = count_lines(text, end - 1);
1100 fn = current_filename;
1102 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1103 buf = get_address(buf, &b, &e);
1105 // remember orig command line
1108 // get the COMMAND into cmd[]
1110 while (*buf != '\0') {
1116 // get any ARGuments
1117 while (isblank(*buf))
1121 buf1 = last_char_is(cmd, '!');
1124 *buf1 = '\0'; // get rid of !
1127 // if there is only one addr, then the addr
1128 // is the line number of the single line the
1129 // user wants. So, reset the end
1130 // pointer to point at end of the "b" line
1131 q = find_line(b); // what line is #b
1136 // we were given two addrs. change the
1137 // end pointer to the addr given by user.
1138 r = find_line(e); // what line is #e
1142 // ------------ now look for the command ------------
1144 if (i == 0) { // :123CR goto line #123
1146 dot = find_line(b); // what line is #b
1150 #if ENABLE_FEATURE_ALLOW_EXEC
1151 else if (cmd[0] == '!') { // run a cmd
1153 // :!ls run the <cmd>
1154 go_bottom_and_clear_to_eol();
1156 retcode = system(orig_buf + 1); // run the cmd
1158 printf("\nshell returned %i\n\n", retcode);
1160 Hit_Return(); // let user see results
1163 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1164 if (b < 0) { // no addr given- use defaults
1165 b = e = count_lines(text, dot);
1167 status_line("%d", b);
1168 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1169 if (b < 0) { // no addr given- use defaults
1170 q = begin_line(dot); // assume .,. for the range
1173 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
1175 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1178 // don't edit, if the current file has been modified
1179 if (modified_count && !useforce) {
1180 status_line_bold("No write since last change (:%s! overrides)", cmd);
1184 // the user supplied a file name
1186 } else if (current_filename && current_filename[0]) {
1187 // no user supplied name- use the current filename
1188 // fn = current_filename; was set by default
1190 // no user file name, no current name- punt
1191 status_line_bold("No current filename");
1195 size = init_text_buffer(fn);
1197 #if ENABLE_FEATURE_VI_YANKMARK
1198 if (Ureg >= 0 && Ureg < 28) {
1199 free(reg[Ureg]); // free orig line reg- for 'U'
1202 if (YDreg >= 0 && YDreg < 28) {
1203 free(reg[YDreg]); // free default yank/delete register
1207 // how many lines in text[]?
1208 li = count_lines(text, end - 1);
1209 status_line("'%s'%s"
1210 IF_FEATURE_VI_READONLY("%s")
1213 (size < 0 ? " [New file]" : ""),
1214 IF_FEATURE_VI_READONLY(
1215 ((readonly_mode) ? " [Readonly]" : ""),
1217 li, (int)(end - text)
1219 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1220 if (b != -1 || e != -1) {
1221 status_line_bold("No address allowed on this command");
1225 // user wants a new filename
1226 free(current_filename);
1227 current_filename = xstrdup(args);
1229 // user wants file status info
1230 last_status_cksum = 0; // force status update
1232 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1233 // print out values of all features
1234 go_bottom_and_clear_to_eol();
1239 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1240 if (b < 0) { // no addr given- use defaults
1241 q = begin_line(dot); // assume .,. for the range
1244 go_bottom_and_clear_to_eol();
1246 for (; q <= r; q++) {
1250 c_is_no_print = (c & 0x80) && !Isprint(c);
1251 if (c_is_no_print) {
1257 } else if (c < ' ' || c == 127) {
1269 } else if (strncmp(cmd, "quit", i) == 0 // quit
1270 || strncmp(cmd, "next", i) == 0 // edit next file
1271 || strncmp(cmd, "prev", i) == 0 // edit previous file
1276 // force end of argv list
1282 // don't exit if the file been modified
1283 if (modified_count) {
1284 status_line_bold("No write since last change (:%s! overrides)", cmd);
1287 // are there other file to edit
1288 n = save_argc - optind - 1;
1289 if (*cmd == 'q' && n > 0) {
1290 status_line_bold("%d more file(s) to edit", n);
1293 if (*cmd == 'n' && n <= 0) {
1294 status_line_bold("No more files to edit");
1298 // are there previous files to edit
1300 status_line_bold("No previous files to edit");
1306 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1311 status_line_bold("No filename given");
1314 if (b < 0) { // no addr given- use defaults
1315 q = begin_line(dot); // assume "dot"
1317 // read after current line- unless user said ":0r foo"
1320 // read after last line
1324 { // dance around potentially-reallocated text[]
1325 uintptr_t ofs = q - text;
1326 size = file_insert(fn, q, 0);
1330 goto ret; // nothing was inserted
1331 // how many lines in text[]?
1332 li = count_lines(q, q + size - 1);
1334 IF_FEATURE_VI_READONLY("%s")
1337 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1341 // if the insert is before "dot" then we need to update
1345 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1346 if (modified_count && !useforce) {
1347 status_line_bold("No write since last change (:%s! overrides)", cmd);
1349 // reset the filenames to edit
1350 optind = -1; /* start from 0th file */
1353 #if ENABLE_FEATURE_VI_SET
1354 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1355 #if ENABLE_FEATURE_VI_SETOPTS
1358 i = 0; // offset into args
1359 // only blank is regarded as args delimiter. What about tab '\t'?
1360 if (!args[0] || strcasecmp(args, "all") == 0) {
1361 // print out values of all options
1362 #if ENABLE_FEATURE_VI_SETOPTS
1369 autoindent ? "" : "no",
1370 err_method ? "" : "no",
1371 ignorecase ? "" : "no",
1372 showmatch ? "" : "no",
1378 #if ENABLE_FEATURE_VI_SETOPTS
1381 if (strncmp(argp, "no", 2) == 0)
1382 i = 2; // ":set noautoindent"
1383 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1384 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1385 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1386 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1387 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1389 sscanf(argp + i+8, "%u", &t);
1390 if (t > 0 && t <= MAX_TABSTOP)
1393 argp = skip_non_whitespace(argp);
1394 argp = skip_whitespace(argp);
1396 #endif /* FEATURE_VI_SETOPTS */
1397 #endif /* FEATURE_VI_SET */
1398 #if ENABLE_FEATURE_VI_SEARCH
1399 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1400 char *F, *R, *flags;
1401 size_t len_F, len_R;
1402 int gflag; // global replace flag
1403 #if ENABLE_FEATURE_VI_UNDO
1404 int dont_chain_first_item = ALLOW_UNDO;
1407 // F points to the "find" pattern
1408 // R points to the "replace" pattern
1409 // replace the cmd line delimiters "/" with NULs
1410 c = orig_buf[1]; // what is the delimiter
1411 F = orig_buf + 2; // start of "find"
1412 R = strchr(F, c); // middle delimiter
1416 *R++ = '\0'; // terminate "find"
1417 flags = strchr(R, c);
1421 *flags++ = '\0'; // terminate "replace"
1425 if (b < 0) { // maybe :s/foo/bar/
1426 q = begin_line(dot); // start with cur line
1427 b = count_lines(text, q); // cur line number
1430 e = b; // maybe :.s/foo/bar/
1432 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1433 char *ls = q; // orig line start
1436 found = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1439 // we found the "find" pattern - delete it
1440 // For undo support, the first item should not be chained
1441 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
1442 #if ENABLE_FEATURE_VI_UNDO
1443 dont_chain_first_item = ALLOW_UNDO_CHAIN;
1445 // insert the "replace" patern
1446 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
1449 /*q += bias; - recalculated anyway */
1450 // check for "global" :s/foo/bar/g
1452 if ((found + len_R) < end_line(ls)) {
1454 goto vc4; // don't let q move past cur line
1460 #endif /* FEATURE_VI_SEARCH */
1461 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1462 status_line(BB_VER);
1463 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1464 || strncmp(cmd, "wq", i) == 0
1465 || strncmp(cmd, "wn", i) == 0
1466 || (cmd[0] == 'x' && !cmd[1])
1469 //int forced = FALSE;
1471 // is there a file name to write to?
1475 #if ENABLE_FEATURE_VI_READONLY
1476 if (readonly_mode && !useforce) {
1477 status_line_bold("'%s' is read only", fn);
1482 // if "fn" is not write-able, chmod u+w
1483 // sprintf(syscmd, "chmod u+w %s", fn);
1487 if (modified_count != 0 || cmd[0] != 'x') {
1489 l = file_write(fn, q, r);
1494 //if (useforce && forced) {
1496 // sprintf(syscmd, "chmod u-w %s", fn);
1502 status_line_bold_errno(fn);
1504 // how many lines written
1505 li = count_lines(q, q + l - 1);
1506 status_line("'%s' %dL, %dC", fn, li, l);
1508 if (q == text && q + l == end) {
1510 last_modified_count = -1;
1513 || cmd[1] == 'q' || cmd[1] == 'n'
1514 || cmd[1] == 'Q' || cmd[1] == 'N'
1520 #if ENABLE_FEATURE_VI_YANKMARK
1521 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1522 if (b < 0) { // no addr given- use defaults
1523 q = begin_line(dot); // assume .,. for the range
1526 text_yank(q, r, YDreg);
1527 li = count_lines(q, r);
1528 status_line("Yank %d lines (%d chars) into [%c]",
1529 li, strlen(reg[YDreg]), what_reg());
1533 not_implemented(cmd);
1536 dot = bound_dot(dot); // make sure "dot" is valid
1538 #if ENABLE_FEATURE_VI_SEARCH
1540 status_line(":s expression missing delimiters");
1542 #endif /* FEATURE_VI_COLON */
1545 static void Hit_Return(void)
1550 write1("[Hit return to continue]");
1552 while ((c = get_one_char()) != '\n' && c != '\r')
1554 redraw(TRUE); // force redraw all
1557 static int next_tabstop(int col)
1559 return col + ((tabstop - 1) - (col % tabstop));
1562 //----- Synchronize the cursor to Dot --------------------------
1563 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1565 char *beg_cur; // begin and end of "d" line
1569 beg_cur = begin_line(d); // first char of cur line
1571 if (beg_cur < screenbegin) {
1572 // "d" is before top line on screen
1573 // how many lines do we have to move
1574 cnt = count_lines(beg_cur, screenbegin);
1576 screenbegin = beg_cur;
1577 if (cnt > (rows - 1) / 2) {
1578 // we moved too many lines. put "dot" in middle of screen
1579 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1580 screenbegin = prev_line(screenbegin);
1584 char *end_scr; // begin and end of screen
1585 end_scr = end_screen(); // last char of screen
1586 if (beg_cur > end_scr) {
1587 // "d" is after bottom line on screen
1588 // how many lines do we have to move
1589 cnt = count_lines(end_scr, beg_cur);
1590 if (cnt > (rows - 1) / 2)
1591 goto sc1; // too many lines
1592 for (ro = 0; ro < cnt - 1; ro++) {
1593 // move screen begin the same amount
1594 screenbegin = next_line(screenbegin);
1595 // now, move the end of screen
1596 end_scr = next_line(end_scr);
1597 end_scr = end_line(end_scr);
1601 // "d" is on screen- find out which row
1603 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1609 // find out what col "d" is on
1611 while (tp < d) { // drive "co" to correct column
1612 if (*tp == '\n') //vda || *tp == '\0')
1615 // handle tabs like real vi
1616 if (d == tp && cmd_mode) {
1619 co = next_tabstop(co);
1620 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1621 co++; // display as ^X, use 2 columns
1627 // "co" is the column where "dot" is.
1628 // The screen has "columns" columns.
1629 // The currently displayed columns are 0+offset -- columns+ofset
1630 // |-------------------------------------------------------------|
1632 // offset | |------- columns ----------------|
1634 // If "co" is already in this range then we do not have to adjust offset
1635 // but, we do have to subtract the "offset" bias from "co".
1636 // If "co" is outside this range then we have to change "offset".
1637 // If the first char of a line is a tab the cursor will try to stay
1638 // in column 7, but we have to set offset to 0.
1640 if (co < 0 + offset) {
1643 if (co >= columns + offset) {
1644 offset = co - columns + 1;
1646 // if the first char of the line is a tab, and "dot" is sitting on it
1647 // force offset to 0.
1648 if (d == beg_cur && *d == '\t') {
1657 //----- Text Movement Routines ---------------------------------
1658 static char *begin_line(char *p) // return pointer to first char cur line
1661 p = memrchr(text, '\n', p - text);
1669 static char *end_line(char *p) // return pointer to NL of cur line
1672 p = memchr(p, '\n', end - p - 1);
1679 static char *dollar_line(char *p) // return pointer to just before NL line
1682 // Try to stay off of the Newline
1683 if (*p == '\n' && (p - begin_line(p)) > 0)
1688 static char *prev_line(char *p) // return pointer first char prev line
1690 p = begin_line(p); // goto beginning of cur line
1691 if (p > text && p[-1] == '\n')
1692 p--; // step to prev line
1693 p = begin_line(p); // goto beginning of prev line
1697 static char *next_line(char *p) // return pointer first char next line
1700 if (p < end - 1 && *p == '\n')
1701 p++; // step to next line
1705 //----- Text Information Routines ------------------------------
1706 static char *end_screen(void)
1711 // find new bottom line
1713 for (cnt = 0; cnt < rows - 2; cnt++)
1719 // count line from start to stop
1720 static int count_lines(char *start, char *stop)
1725 if (stop < start) { // start and stop are backwards- reverse them
1731 stop = end_line(stop);
1732 while (start <= stop && start <= end - 1) {
1733 start = end_line(start);
1741 static char *find_line(int li) // find beginning of line #li
1745 for (q = text; li > 1; li--) {
1751 //----- Dot Movement Routines ----------------------------------
1752 static void dot_left(void)
1754 undo_queue_commit();
1755 if (dot > text && dot[-1] != '\n')
1759 static void dot_right(void)
1761 undo_queue_commit();
1762 if (dot < end - 1 && *dot != '\n')
1766 static void dot_begin(void)
1768 undo_queue_commit();
1769 dot = begin_line(dot); // return pointer to first char cur line
1772 static void dot_end(void)
1774 undo_queue_commit();
1775 dot = end_line(dot); // return pointer to last char cur line
1778 static char *move_to_col(char *p, int l)
1784 while (co < l && p < end) {
1785 if (*p == '\n') //vda || *p == '\0')
1788 co = next_tabstop(co);
1789 } else if (*p < ' ' || *p == 127) {
1790 co++; // display as ^X, use 2 columns
1798 static void dot_next(void)
1800 undo_queue_commit();
1801 dot = next_line(dot);
1804 static void dot_prev(void)
1806 undo_queue_commit();
1807 dot = prev_line(dot);
1810 static void dot_scroll(int cnt, int dir)
1814 undo_queue_commit();
1815 for (; cnt > 0; cnt--) {
1818 // ctrl-Y scroll up one line
1819 screenbegin = prev_line(screenbegin);
1822 // ctrl-E scroll down one line
1823 screenbegin = next_line(screenbegin);
1826 // make sure "dot" stays on the screen so we dont scroll off
1827 if (dot < screenbegin)
1829 q = end_screen(); // find new bottom line
1831 dot = begin_line(q); // is dot is below bottom line?
1835 static void dot_skip_over_ws(void)
1838 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1842 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1844 if (p >= end && end > text) {
1855 //----- Helper Utility Routines --------------------------------
1857 //----------------------------------------------------------------
1858 //----- Char Routines --------------------------------------------
1859 /* Chars that are part of a word-
1860 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1861 * Chars that are Not part of a word (stoppers)
1862 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1863 * Chars that are WhiteSpace
1864 * TAB NEWLINE VT FF RETURN SPACE
1865 * DO NOT COUNT NEWLINE AS WHITESPACE
1868 static char *new_screen(int ro, int co)
1873 screensize = ro * co + 8;
1874 screen = xmalloc(screensize);
1875 // initialize the new screen. assume this will be a empty file.
1877 // non-existent text[] lines start with a tilde (~).
1878 for (li = 1; li < ro - 1; li++) {
1879 screen[(li * co) + 0] = '~';
1884 #if ENABLE_FEATURE_VI_SEARCH
1886 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1888 // search for pattern starting at p
1889 static char *char_search(char *p, const char *pat, int dir, int range)
1891 struct re_pattern_buffer preg;
1897 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1899 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1901 memset(&preg, 0, sizeof(preg));
1902 err = re_compile_pattern(pat, strlen(pat), &preg);
1904 status_line_bold("bad search pattern '%s': %s", pat, err);
1908 // assume a LIMITED forward search
1912 // RANGE could be negative if we are searching backwards
1922 // search for the compiled pattern, preg, in p[]
1923 // range < 0: search backward
1924 // range > 0: search forward
1926 // re_search() < 0: not found or error
1927 // re_search() >= 0: index of found pattern
1928 // struct pattern char int int int struct reg
1929 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1930 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1943 # if ENABLE_FEATURE_VI_SETOPTS
1944 static int mycmp(const char *s1, const char *s2, int len)
1947 return strncasecmp(s1, s2, len);
1949 return strncmp(s1, s2, len);
1952 # define mycmp strncmp
1955 static char *char_search(char *p, const char *pat, int dir, int range)
1961 if (dir == FORWARD) {
1962 stop = end - 1; // assume range is p..end-1
1963 if (range == LIMITED)
1964 stop = next_line(p); // range is to next line
1965 for (start = p; start < stop; start++) {
1966 if (mycmp(start, pat, len) == 0) {
1970 } else if (dir == BACK) {
1971 stop = text; // assume range is text..p
1972 if (range == LIMITED)
1973 stop = prev_line(p); // range is to prev line
1974 for (start = p - len; start >= stop; start--) {
1975 if (mycmp(start, pat, len) == 0) {
1980 // pattern not found
1986 #endif /* FEATURE_VI_SEARCH */
1988 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1990 if (c == 22) { // Is this an ctrl-V?
1991 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1992 refresh(FALSE); // show the ^
1995 #if ENABLE_FEATURE_VI_UNDO
1998 undo_push(p, 1, UNDO_INS);
2000 case ALLOW_UNDO_CHAIN:
2001 undo_push(p, 1, UNDO_INS_CHAIN);
2003 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2004 case ALLOW_UNDO_QUEUED:
2005 undo_push(p, 1, UNDO_INS_QUEUED);
2011 #endif /* ENABLE_FEATURE_VI_UNDO */
2013 } else if (c == 27) { // Is this an ESC?
2015 undo_queue_commit();
2017 end_cmd_q(); // stop adding to q
2018 last_status_cksum = 0; // force status update
2019 if ((p[-1] != '\n') && (dot > text)) {
2022 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
2025 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2028 // insert a char into text[]
2030 c = '\n'; // translate \r to \n
2031 #if ENABLE_FEATURE_VI_UNDO
2032 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2034 undo_queue_commit();
2038 undo_push(p, 1, UNDO_INS);
2040 case ALLOW_UNDO_CHAIN:
2041 undo_push(p, 1, UNDO_INS_CHAIN);
2043 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2044 case ALLOW_UNDO_QUEUED:
2045 undo_push(p, 1, UNDO_INS_QUEUED);
2051 #endif /* ENABLE_FEATURE_VI_UNDO */
2052 p += 1 + stupid_insert(p, c); // insert the char
2053 #if ENABLE_FEATURE_VI_SETOPTS
2054 if (showmatch && strchr(")]}", c) != NULL) {
2055 showmatching(p - 1);
2057 if (autoindent && c == '\n') { // auto indent the new line
2060 q = prev_line(p); // use prev line as template
2061 len = strspn(q, " \t"); // space or tab
2064 bias = text_hole_make(p, len);
2067 #if ENABLE_FEATURE_VI_UNDO
2068 undo_push(p, len, UNDO_INS);
2079 // might reallocate text[]! use p += stupid_insert(p, ...),
2080 // and be careful to not use pointers into potentially freed text[]!
2081 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2084 bias = text_hole_make(p, 1);
2090 static int find_range(char **start, char **stop, char c)
2092 char *save_dot, *p, *q, *t;
2093 int cnt, multiline = 0;
2098 if (strchr("cdy><", c)) {
2099 // these cmds operate on whole lines
2100 p = q = begin_line(p);
2101 for (cnt = 1; cnt < cmdcnt; cnt++) {
2105 } else if (strchr("^%$0bBeEfth\b\177", c)) {
2106 // These cmds operate on char positions
2107 do_cmd(c); // execute movement cmd
2109 } else if (strchr("wW", c)) {
2110 do_cmd(c); // execute movement cmd
2111 // if we are at the next word's first char
2112 // step back one char
2113 // but check the possibilities when it is true
2114 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
2115 || (ispunct(dot[-1]) && !ispunct(dot[0]))
2116 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
2117 dot--; // move back off of next word
2118 if (dot > text && *dot == '\n')
2119 dot--; // stay off NL
2121 } else if (strchr("H-k{", c)) {
2122 // these operate on multi-lines backwards
2123 q = end_line(dot); // find NL
2124 do_cmd(c); // execute movement cmd
2127 } else if (strchr("L+j}\r\n", c)) {
2128 // these operate on multi-lines forwards
2129 p = begin_line(dot);
2130 do_cmd(c); // execute movement cmd
2131 dot_end(); // find NL
2134 // nothing -- this causes any other values of c to
2135 // represent the one-character range under the
2136 // cursor. this is correct for ' ' and 'l', but
2137 // perhaps no others.
2146 // backward char movements don't include start position
2147 if (q > p && strchr("^0bBh\b\177", c)) q--;
2150 for (t = p; t <= q; t++) {
2163 static int st_test(char *p, int type, int dir, char *tested)
2173 if (type == S_BEFORE_WS) {
2175 test = (!isspace(c) || c == '\n');
2177 if (type == S_TO_WS) {
2179 test = (!isspace(c) || c == '\n');
2181 if (type == S_OVER_WS) {
2185 if (type == S_END_PUNCT) {
2189 if (type == S_END_ALNUM) {
2191 test = (isalnum(c) || c == '_');
2197 static char *skip_thing(char *p, int linecnt, int dir, int type)
2201 while (st_test(p, type, dir, &c)) {
2202 // make sure we limit search to correct number of lines
2203 if (c == '\n' && --linecnt < 1)
2205 if (dir >= 0 && p >= end - 1)
2207 if (dir < 0 && p <= text)
2209 p += dir; // move to next char
2214 // find matching char of pair () [] {}
2215 // will crash if c is not one of these
2216 static char *find_pair(char *p, const char c)
2218 const char *braces = "()[]{}";
2222 dir = strchr(braces, c) - braces;
2224 match = braces[dir];
2225 dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
2227 // look for match, count levels of pairs (( ))
2231 if (p < text || p >= end)
2234 level++; // increase pair levels
2236 level--; // reduce pair level
2238 return p; // found matching pair
2243 #if ENABLE_FEATURE_VI_SETOPTS
2244 // show the matching char of a pair, () [] {}
2245 static void showmatching(char *p)
2249 // we found half of a pair
2250 q = find_pair(p, *p); // get loc of matching char
2252 indicate_error(); // no matching char
2254 // "q" now points to matching pair
2255 save_dot = dot; // remember where we are
2256 dot = q; // go to new loc
2257 refresh(FALSE); // let the user see it
2258 mysleep(40); // give user some time
2259 dot = save_dot; // go back to old loc
2263 #endif /* FEATURE_VI_SETOPTS */
2265 #if ENABLE_FEATURE_VI_UNDO
2266 static void flush_undo_data(void)
2268 struct undo_object *undo_entry;
2270 while (undo_stack_tail) {
2271 undo_entry = undo_stack_tail;
2272 undo_stack_tail = undo_entry->prev;
2277 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2278 static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack
2280 struct undo_object *undo_entry;
2283 // UNDO_INS: insertion, undo will remove from buffer
2284 // UNDO_DEL: deleted text, undo will restore to buffer
2285 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2286 // The CHAIN operations are for handling multiple operations that the user
2287 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2288 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2289 // for the INS/DEL operation. The raw values should be equal to the values of
2290 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2292 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2293 // This undo queuing functionality groups multiple character typing or backspaces
2294 // into a single large undo object. This greatly reduces calls to malloc() for
2295 // single-character operations while typing and has the side benefit of letting
2296 // an undo operation remove chunks of text rather than a single character.
2298 case UNDO_EMPTY: // Just in case this ever happens...
2300 case UNDO_DEL_QUEUED:
2302 return; // Only queue single characters
2303 switch (undo_queue_state) {
2305 undo_queue_state = UNDO_DEL;
2307 undo_queue_spos = src;
2309 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2310 // If queue is full, dump it into an object
2311 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2312 undo_queue_commit();
2315 // Switch from storing inserted text to deleted text
2316 undo_queue_commit();
2317 undo_push(src, length, UNDO_DEL_QUEUED);
2321 case UNDO_INS_QUEUED:
2324 switch (undo_queue_state) {
2326 undo_queue_state = UNDO_INS;
2327 undo_queue_spos = src;
2329 undo_q++; // Don't need to save any data for insertions
2330 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2331 undo_queue_commit();
2334 // Switch from storing deleted text to inserted text
2335 undo_queue_commit();
2336 undo_push(src, length, UNDO_INS_QUEUED);
2342 // If undo queuing is disabled, ignore the queuing flag entirely
2343 u_type = u_type & ~UNDO_QUEUED_FLAG;
2346 // Allocate a new undo object
2347 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2348 // For UNDO_DEL objects, save deleted text
2349 if ((src + length) == end)
2351 // If this deletion empties text[], strip the newline. When the buffer becomes
2352 // zero-length, a newline is added back, which requires this to compensate.
2353 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
2354 memcpy(undo_entry->undo_text, src, length);
2356 undo_entry = xzalloc(sizeof(*undo_entry));
2358 undo_entry->length = length;
2359 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2360 if ((u_type & UNDO_USE_SPOS) != 0) {
2361 undo_entry->start = undo_queue_spos - text; // use start position from queue
2363 undo_entry->start = src - text; // use offset from start of text buffer
2365 u_type = (u_type & ~UNDO_USE_SPOS);
2367 undo_entry->start = src - text;
2369 undo_entry->u_type = u_type;
2371 // Push it on undo stack
2372 undo_entry->prev = undo_stack_tail;
2373 undo_stack_tail = undo_entry;
2377 static void undo_pop(void) // Undo the last operation
2380 char *u_start, *u_end;
2381 struct undo_object *undo_entry;
2383 // Commit pending undo queue before popping (should be unnecessary)
2384 undo_queue_commit();
2386 undo_entry = undo_stack_tail;
2387 // Check for an empty undo stack
2389 status_line("Already at oldest change");
2393 switch (undo_entry->u_type) {
2395 case UNDO_DEL_CHAIN:
2396 // make hole and put in text that was deleted; deallocate text
2397 u_start = text + undo_entry->start;
2398 text_hole_make(u_start, undo_entry->length);
2399 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2400 status_line("Undo [%d] %s %d chars at position %d",
2401 modified_count, "restored",
2402 undo_entry->length, undo_entry->start
2406 case UNDO_INS_CHAIN:
2407 // delete what was inserted
2408 u_start = undo_entry->start + text;
2409 u_end = u_start - 1 + undo_entry->length;
2410 text_hole_delete(u_start, u_end, NO_UNDO);
2411 status_line("Undo [%d] %s %d chars at position %d",
2412 modified_count, "deleted",
2413 undo_entry->length, undo_entry->start
2418 switch (undo_entry->u_type) {
2419 // If this is the end of a chain, lower modification count and refresh display
2422 dot = (text + undo_entry->start);
2425 case UNDO_DEL_CHAIN:
2426 case UNDO_INS_CHAIN:
2430 // Deallocate the undo object we just processed
2431 undo_stack_tail = undo_entry->prev;
2434 // For chained operations, continue popping all the way down the chain.
2436 undo_pop(); // Follow the undo chain if one exists
2440 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2441 static void undo_queue_commit(void) // Flush any queued objects to the undo stack
2443 // Pushes the queue object onto the undo stack
2445 // Deleted character undo events grow from the end
2446 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2448 (undo_queue_state | UNDO_USE_SPOS)
2450 undo_queue_state = UNDO_EMPTY;
2456 #endif /* ENABLE_FEATURE_VI_UNDO */
2458 // open a hole in text[]
2459 // might reallocate text[]! use p += text_hole_make(p, ...),
2460 // and be careful to not use pointers into potentially freed text[]!
2461 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2467 end += size; // adjust the new END
2468 if (end >= (text + text_size)) {
2470 text_size += end - (text + text_size) + 10240;
2471 new_text = xrealloc(text, text_size);
2472 bias = (new_text - text);
2473 screenbegin += bias;
2477 #if ENABLE_FEATURE_VI_YANKMARK
2480 for (i = 0; i < ARRAY_SIZE(mark); i++)
2487 memmove(p + size, p, end - size - p);
2488 memset(p, ' ', size); // clear new hole
2492 // close a hole in text[]
2493 // "undo" value indicates if this operation should be undo-able
2494 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2499 // move forwards, from beginning
2503 if (q < p) { // they are backward- swap them
2507 hole_size = q - p + 1;
2509 #if ENABLE_FEATURE_VI_UNDO
2514 undo_push(p, hole_size, UNDO_DEL);
2516 case ALLOW_UNDO_CHAIN:
2517 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2519 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2520 case ALLOW_UNDO_QUEUED:
2521 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2527 if (src < text || src > end)
2529 if (dest < text || dest >= end)
2533 goto thd_atend; // just delete the end of the buffer
2534 memmove(dest, src, cnt);
2536 end = end - hole_size; // adjust the new END
2538 dest = end - 1; // make sure dest in below end-1
2540 dest = end = text; // keep pointers valid
2545 // copy text into register, then delete text.
2546 // if dist <= 0, do not include, or go past, a NewLine
2548 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2552 // make sure start <= stop
2554 // they are backwards, reverse them
2560 // we cannot cross NL boundaries
2564 // dont go past a NewLine
2565 for (; p + 1 <= stop; p++) {
2567 stop = p; // "stop" just before NewLine
2573 #if ENABLE_FEATURE_VI_YANKMARK
2574 text_yank(start, stop, YDreg);
2576 if (yf == YANKDEL) {
2577 p = text_hole_delete(start, stop, undo);
2582 static void show_help(void)
2584 puts("These features are available:"
2585 #if ENABLE_FEATURE_VI_SEARCH
2586 "\n\tPattern searches with / and ?"
2588 #if ENABLE_FEATURE_VI_DOT_CMD
2589 "\n\tLast command repeat with ."
2591 #if ENABLE_FEATURE_VI_YANKMARK
2592 "\n\tLine marking with 'x"
2593 "\n\tNamed buffers with \"x"
2595 #if ENABLE_FEATURE_VI_READONLY
2596 //not implemented: "\n\tReadonly if vi is called as \"view\""
2597 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2599 #if ENABLE_FEATURE_VI_SET
2600 "\n\tSome colon mode commands with :"
2602 #if ENABLE_FEATURE_VI_SETOPTS
2603 "\n\tSettable options with \":set\""
2605 #if ENABLE_FEATURE_VI_USE_SIGNALS
2606 "\n\tSignal catching- ^C"
2607 "\n\tJob suspend and resume with ^Z"
2609 #if ENABLE_FEATURE_VI_WIN_RESIZE
2610 "\n\tAdapt to window re-sizes"
2615 #if ENABLE_FEATURE_VI_DOT_CMD
2616 static void start_new_cmd_q(char c)
2618 // get buffer for new cmd
2619 // if there is a current cmd count put it in the buffer first
2621 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2622 } else { // just save char c onto queue
2623 last_modifying_cmd[0] = c;
2629 static void end_cmd_q(void)
2631 #if ENABLE_FEATURE_VI_YANKMARK
2632 YDreg = 26; // go back to default Yank/Delete reg
2636 #endif /* FEATURE_VI_DOT_CMD */
2638 #if ENABLE_FEATURE_VI_YANKMARK \
2639 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2640 || ENABLE_FEATURE_VI_CRASHME
2641 // might reallocate text[]! use p += string_insert(p, ...),
2642 // and be careful to not use pointers into potentially freed text[]!
2643 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2649 #if ENABLE_FEATURE_VI_UNDO
2652 undo_push(p, i, UNDO_INS);
2654 case ALLOW_UNDO_CHAIN:
2655 undo_push(p, i, UNDO_INS_CHAIN);
2659 bias = text_hole_make(p, i);
2662 #if ENABLE_FEATURE_VI_YANKMARK
2665 for (cnt = 0; *s != '\0'; s++) {
2669 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2676 #if ENABLE_FEATURE_VI_YANKMARK
2677 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2680 if (cnt < 0) { // they are backwards- reverse them
2684 free(reg[dest]); // if already a yank register, free it
2685 reg[dest] = xstrndup(p, cnt + 1);
2689 static char what_reg(void)
2693 c = 'D'; // default to D-reg
2694 if (0 <= YDreg && YDreg <= 25)
2695 c = 'a' + (char) YDreg;
2703 static void check_context(char cmd)
2705 // A context is defined to be "modifying text"
2706 // Any modifying command establishes a new context.
2708 if (dot < context_start || dot > context_end) {
2709 if (strchr(modifying_cmds, cmd) != NULL) {
2710 // we are trying to modify text[]- make this the current context
2711 mark[27] = mark[26]; // move cur to prev
2712 mark[26] = dot; // move local to cur
2713 context_start = prev_line(prev_line(dot));
2714 context_end = next_line(next_line(dot));
2715 //loiter= start_loiter= now;
2720 static char *swap_context(char *p) // goto new context for '' command make this the current context
2724 // the current context is in mark[26]
2725 // the previous context is in mark[27]
2726 // only swap context if other context is valid
2727 if (text <= mark[27] && mark[27] <= end - 1) {
2731 context_start = prev_line(prev_line(prev_line(p)));
2732 context_end = next_line(next_line(next_line(p)));
2736 #endif /* FEATURE_VI_YANKMARK */
2738 //----- Set terminal attributes --------------------------------
2739 static void rawmode(void)
2741 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
2742 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
2743 erase_char = term_orig.c_cc[VERASE];
2746 static void cookmode(void)
2749 tcsetattr_stdin_TCSANOW(&term_orig);
2752 #if ENABLE_FEATURE_VI_USE_SIGNALS
2753 //----- Come here when we get a window resize signal ---------
2754 static void winch_sig(int sig UNUSED_PARAM)
2756 int save_errno = errno;
2757 // FIXME: do it in main loop!!!
2758 signal(SIGWINCH, winch_sig);
2759 query_screen_dimensions();
2760 new_screen(rows, columns); // get memory for virtual screen
2761 redraw(TRUE); // re-draw the screen
2765 //----- Come here when we get a continue signal -------------------
2766 static void cont_sig(int sig UNUSED_PARAM)
2768 int save_errno = errno;
2769 rawmode(); // terminal to "raw"
2770 last_status_cksum = 0; // force status update
2771 redraw(TRUE); // re-draw the screen
2773 signal(SIGTSTP, suspend_sig);
2774 signal(SIGCONT, SIG_DFL);
2775 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2779 //----- Come here when we get a Suspend signal -------------------
2780 static void suspend_sig(int sig UNUSED_PARAM)
2782 int save_errno = errno;
2783 go_bottom_and_clear_to_eol();
2784 cookmode(); // terminal to "cooked"
2786 signal(SIGCONT, cont_sig);
2787 signal(SIGTSTP, SIG_DFL);
2788 kill(my_pid, SIGTSTP);
2792 //----- Come here when we get a signal ---------------------------
2793 static void catch_sig(int sig)
2795 signal(SIGINT, catch_sig);
2796 siglongjmp(restart, sig);
2798 #endif /* FEATURE_VI_USE_SIGNALS */
2800 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2802 struct pollfd pfd[1];
2807 pfd[0].fd = STDIN_FILENO;
2808 pfd[0].events = POLLIN;
2809 return safe_poll(pfd, 1, hund*10) > 0;
2812 //----- IO Routines --------------------------------------------
2813 static int readit(void) // read (maybe cursor) key from stdin
2819 // Wait for input. TIMEOUT = -1 makes read_key wait even
2820 // on nonblocking stdin.
2821 // Note: read_key sets errno to 0 on success.
2823 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
2824 if (c == -1) { // EOF/error
2825 if (errno == EAGAIN) // paranoia
2827 go_bottom_and_clear_to_eol();
2828 cookmode(); // terminal to "cooked"
2829 bb_error_msg_and_die("can't read user input");
2834 //----- IO Routines --------------------------------------------
2835 static int get_one_char(void)
2839 #if ENABLE_FEATURE_VI_DOT_CMD
2841 // we are not adding to the q.
2842 // but, we may be reading from a q
2844 // there is no current q, read from STDIN
2845 c = readit(); // get the users input
2847 // there is a queue to get chars from first
2848 // careful with correct sign expansion!
2849 c = (unsigned char)*ioq++;
2851 // the end of the q, read from STDIN
2853 ioq_start = ioq = 0;
2854 c = readit(); // get the users input
2858 // adding STDIN chars to q
2859 c = readit(); // get the users input
2860 if (lmc_len >= MAX_INPUT_LEN - 1) {
2861 status_line_bold("last_modifying_cmd overrun");
2863 // add new char to q
2864 last_modifying_cmd[lmc_len++] = c;
2868 c = readit(); // get the users input
2869 #endif /* FEATURE_VI_DOT_CMD */
2873 // Get input line (uses "status line" area)
2874 static char *get_input_line(const char *prompt)
2876 // char [MAX_INPUT_LEN]
2877 #define buf get_input_line__buf
2882 strcpy(buf, prompt);
2883 last_status_cksum = 0; // force status update
2884 go_bottom_and_clear_to_eol();
2885 write1(prompt); // write out the :, /, or ? prompt
2888 while (i < MAX_INPUT_LEN) {
2890 if (c == '\n' || c == '\r' || c == 27)
2891 break; // this is end of input
2892 if (c == erase_char || c == 8 || c == 127) {
2893 // user wants to erase prev char
2895 write1("\b \b"); // erase char on screen
2896 if (i <= 0) // user backs up before b-o-l, exit
2898 } else if (c > 0 && c < 256) { // exclude Unicode
2899 // (TODO: need to handle Unicode)
2910 // might reallocate text[]!
2911 static int file_insert(const char *fn, char *p, int initial)
2915 struct stat statbuf;
2922 fd = open(fn, O_RDONLY);
2925 status_line_bold_errno(fn);
2930 if (fstat(fd, &statbuf) < 0) {
2931 status_line_bold_errno(fn);
2934 if (!S_ISREG(statbuf.st_mode)) {
2935 status_line_bold("'%s' is not a regular file", fn);
2938 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2939 p += text_hole_make(p, size);
2940 cnt = full_read(fd, p, size);
2942 status_line_bold_errno(fn);
2943 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2944 } else if (cnt < size) {
2945 // There was a partial read, shrink unused space
2946 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2947 status_line_bold("can't read '%s'", fn);
2952 #if ENABLE_FEATURE_VI_READONLY
2954 && ((access(fn, W_OK) < 0) ||
2955 /* root will always have access()
2956 * so we check fileperms too */
2957 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2960 SET_READONLY_FILE(readonly_mode);
2966 static int file_write(char *fn, char *first, char *last)
2968 int fd, cnt, charcnt;
2971 status_line_bold("No current filename");
2974 /* By popular request we do not open file with O_TRUNC,
2975 * but instead ftruncate() it _after_ successful write.
2976 * Might reduce amount of data lost on power fail etc.
2978 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2981 cnt = last - first + 1;
2982 charcnt = full_write(fd, first, cnt);
2983 ftruncate(fd, charcnt);
2984 if (charcnt == cnt) {
2986 //modified_count = FALSE;
2994 //----- Terminal Drawing ---------------------------------------
2995 // The terminal is made up of 'rows' line of 'columns' columns.
2996 // classically this would be 24 x 80.
2997 // screen coordinates
3003 // 23,0 ... 23,79 <- status line
3005 //----- Move the cursor to row x col (count from 0, not 1) -------
3006 static void place_cursor(int row, int col)
3008 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
3010 if (row < 0) row = 0;
3011 if (row >= rows) row = rows - 1;
3012 if (col < 0) col = 0;
3013 if (col >= columns) col = columns - 1;
3015 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
3019 //----- Erase from cursor to end of line -----------------------
3020 static void clear_to_eol(void)
3022 write1(ESC_CLEAR2EOL);
3025 static void go_bottom_and_clear_to_eol(void)
3027 place_cursor(rows - 1, 0);
3031 //----- Erase from cursor to end of screen -----------------------
3032 static void clear_to_eos(void)
3034 write1(ESC_CLEAR2EOS);
3037 //----- Start standout mode ------------------------------------
3038 static void standout_start(void)
3040 write1(ESC_BOLD_TEXT);
3043 //----- End standout mode --------------------------------------
3044 static void standout_end(void)
3046 write1(ESC_NORM_TEXT);
3049 //----- Flash the screen --------------------------------------
3050 static void flash(int h)
3059 static void indicate_error(void)
3061 #if ENABLE_FEATURE_VI_CRASHME
3063 return; // generate a random command
3072 //----- Screen[] Routines --------------------------------------
3073 //----- Erase the Screen[] memory ------------------------------
3074 static void screen_erase(void)
3076 memset(screen, ' ', screensize); // clear new screen
3079 static int bufsum(char *buf, int count)
3082 char *e = buf + count;
3085 sum += (unsigned char) *buf++;
3089 //----- Draw the status line at bottom of the screen -------------
3090 static void show_status_line(void)
3092 int cnt = 0, cksum = 0;
3094 // either we already have an error or status message, or we
3096 if (!have_status_msg) {
3097 cnt = format_edit_status();
3098 cksum = bufsum(status_buffer, cnt);
3100 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3101 last_status_cksum = cksum; // remember if we have seen this line
3102 go_bottom_and_clear_to_eol();
3103 write1(status_buffer);
3104 if (have_status_msg) {
3105 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3107 have_status_msg = 0;
3110 have_status_msg = 0;
3112 place_cursor(crow, ccol); // put cursor back in correct place
3117 //----- format the status buffer, the bottom line of screen ------
3118 // format status buffer, with STANDOUT mode
3119 static void status_line_bold(const char *format, ...)
3123 va_start(args, format);
3124 strcpy(status_buffer, ESC_BOLD_TEXT);
3125 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3126 strcat(status_buffer, ESC_NORM_TEXT);
3129 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3132 static void status_line_bold_errno(const char *fn)
3134 status_line_bold("'%s' %s", fn, strerror(errno));
3137 // format status buffer
3138 static void status_line(const char *format, ...)
3142 va_start(args, format);
3143 vsprintf(status_buffer, format, args);
3146 have_status_msg = 1;
3149 // copy s to buf, convert unprintable
3150 static void print_literal(char *buf, const char *s)
3164 c_is_no_print = (c & 0x80) && !Isprint(c);
3165 if (c_is_no_print) {
3166 strcpy(d, ESC_NORM_TEXT);
3167 d += sizeof(ESC_NORM_TEXT)-1;
3170 if (c < ' ' || c == 0x7f) {
3172 c |= '@'; /* 0x40 */
3178 if (c_is_no_print) {
3179 strcpy(d, ESC_BOLD_TEXT);
3180 d += sizeof(ESC_BOLD_TEXT)-1;
3186 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3191 static void not_implemented(const char *s)
3193 char buf[MAX_INPUT_LEN];
3195 print_literal(buf, s);
3196 status_line_bold("\'%s\' is not implemented", buf);
3199 // show file status on status line
3200 static int format_edit_status(void)
3202 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3204 #define tot format_edit_status__tot
3206 int cur, percent, ret, trunc_at;
3208 // modified_count is now a counter rather than a flag. this
3209 // helps reduce the amount of line counting we need to do.
3210 // (this will cause a mis-reporting of modified status
3211 // once every MAXINT editing operations.)
3213 // it would be nice to do a similar optimization here -- if
3214 // we haven't done a motion that could have changed which line
3215 // we're on, then we shouldn't have to do this count_lines()
3216 cur = count_lines(text, dot);
3218 // count_lines() is expensive.
3219 // Call it only if something was changed since last time
3221 if (modified_count != last_modified_count) {
3222 tot = cur + count_lines(dot, end - 1) - 1;
3223 last_modified_count = modified_count;
3226 // current line percent
3227 // ------------- ~~ ----------
3230 percent = (100 * cur) / tot;
3236 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3237 columns : STATUS_BUFFER_LEN-1;
3239 ret = snprintf(status_buffer, trunc_at+1,
3240 #if ENABLE_FEATURE_VI_READONLY
3241 "%c %s%s%s %d/%d %d%%",
3243 "%c %s%s %d/%d %d%%",
3245 cmd_mode_indicator[cmd_mode & 3],
3246 (current_filename != NULL ? current_filename : "No file"),
3247 #if ENABLE_FEATURE_VI_READONLY
3248 (readonly_mode ? " [Readonly]" : ""),
3250 (modified_count ? " [Modified]" : ""),
3253 if (ret >= 0 && ret < trunc_at)
3254 return ret; /* it all fit */
3256 return trunc_at; /* had to truncate */
3260 //----- Force refresh of all Lines -----------------------------
3261 static void redraw(int full_screen)
3265 screen_erase(); // erase the internal screen buffer
3266 last_status_cksum = 0; // force status update
3267 refresh(full_screen); // this will redraw the entire display
3271 //----- Format a text[] line into a buffer ---------------------
3272 static char* format_line(char *src /*, int li*/)
3277 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3279 c = '~'; // char in col 0 in non-existent lines is '~'
3281 while (co < columns + tabstop) {
3282 // have we gone past the end?
3287 if ((c & 0x80) && !Isprint(c)) {
3290 if (c < ' ' || c == 0x7f) {
3294 while ((co % tabstop) != (tabstop - 1)) {
3302 c += '@'; // Ctrl-X -> 'X'
3307 // discard scrolled-off-to-the-left portion,
3308 // in tabstop-sized pieces
3309 if (ofs >= tabstop && co >= tabstop) {
3310 memmove(dest, dest + tabstop, co);
3317 // check "short line, gigantic offset" case
3320 // discard last scrolled off part
3323 // fill the rest with spaces
3325 memset(&dest[co], ' ', columns - co);
3329 //----- Refresh the changed screen lines -----------------------
3330 // Copy the source line from text[] into the buffer and note
3331 // if the current screenline is different from the new buffer.
3332 // If they differ then that line needs redrawing on the terminal.
3334 static void refresh(int full_screen)
3336 #define old_offset refresh__old_offset
3339 char *tp, *sp; // pointer into text[] and screen[]
3341 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3342 unsigned c = columns, r = rows;
3343 query_screen_dimensions();
3344 full_screen |= (c - columns) | (r - rows);
3346 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3347 tp = screenbegin; // index into text[] of top line
3349 // compare text[] to screen[] and mark screen[] lines that need updating
3350 for (li = 0; li < rows - 1; li++) {
3351 int cs, ce; // column start & end
3353 // format current text line
3354 out_buf = format_line(tp /*, li*/);
3356 // skip to the end of the current text[] line
3358 char *t = memchr(tp, '\n', end - tp);
3359 if (!t) t = end - 1;
3363 // see if there are any changes between virtual screen and out_buf
3364 changed = FALSE; // assume no change
3367 sp = &screen[li * columns]; // start of screen line
3369 // force re-draw of every single column from 0 - columns-1
3372 // compare newly formatted buffer with virtual screen
3373 // look forward for first difference between buf and screen
3374 for (; cs <= ce; cs++) {
3375 if (out_buf[cs] != sp[cs]) {
3376 changed = TRUE; // mark for redraw
3381 // look backward for last difference between out_buf and screen
3382 for (; ce >= cs; ce--) {
3383 if (out_buf[ce] != sp[ce]) {
3384 changed = TRUE; // mark for redraw
3388 // now, cs is index of first diff, and ce is index of last diff
3390 // if horz offset has changed, force a redraw
3391 if (offset != old_offset) {
3396 // make a sanity check of columns indexes
3398 if (ce > columns - 1) ce = columns - 1;
3399 if (cs > ce) { cs = 0; ce = columns - 1; }
3400 // is there a change between virtual screen and out_buf
3402 // copy changed part of buffer to virtual screen
3403 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3404 place_cursor(li, cs);
3405 // write line out to terminal
3406 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3410 place_cursor(crow, ccol);
3412 old_offset = offset;
3416 //---------------------------------------------------------------------
3417 //----- the Ascii Chart -----------------------------------------------
3419 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3420 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3421 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3422 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3423 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3424 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3425 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3426 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3427 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3428 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3429 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3430 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3431 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3432 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3433 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3434 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3435 //---------------------------------------------------------------------
3437 //----- Execute a Vi Command -----------------------------------
3438 static void do_cmd(int c)
3440 char *p, *q, *save_dot;
3446 // c1 = c; // quiet the compiler
3447 // cnt = yf = 0; // quiet the compiler
3448 // p = q = save_dot = buf; // quiet the compiler
3449 memset(buf, '\0', sizeof(buf));
3453 /* if this is a cursor key, skip these checks */
3461 case KEYCODE_PAGEUP:
3462 case KEYCODE_PAGEDOWN:
3463 case KEYCODE_DELETE:
3467 if (cmd_mode == 2) {
3468 // flip-flop Insert/Replace mode
3469 if (c == KEYCODE_INSERT)
3471 // we are 'R'eplacing the current *dot with new char
3473 // don't Replace past E-o-l
3474 cmd_mode = 1; // convert to insert
3475 undo_queue_commit();
3477 if (1 <= c || Isprint(c)) {
3479 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3480 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3485 if (cmd_mode == 1) {
3486 // hitting "Insert" twice means "R" replace mode
3487 if (c == KEYCODE_INSERT) goto dc5;
3488 // insert the char c at "dot"
3489 if (1 <= c || Isprint(c)) {
3490 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3505 #if ENABLE_FEATURE_VI_CRASHME
3506 case 0x14: // dc4 ctrl-T
3507 crashme = (crashme == 0) ? 1 : 0;
3537 default: // unrecognized command
3540 not_implemented(buf);
3541 end_cmd_q(); // stop adding to q
3542 case 0x00: // nul- ignore
3544 case 2: // ctrl-B scroll up full screen
3545 case KEYCODE_PAGEUP: // Cursor Key Page Up
3546 dot_scroll(rows - 2, -1);
3548 case 4: // ctrl-D scroll down half screen
3549 dot_scroll((rows - 2) / 2, 1);
3551 case 5: // ctrl-E scroll down one line
3554 case 6: // ctrl-F scroll down full screen
3555 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3556 dot_scroll(rows - 2, 1);
3558 case 7: // ctrl-G show current status
3559 last_status_cksum = 0; // force status update
3561 case 'h': // h- move left
3562 case KEYCODE_LEFT: // cursor key Left
3563 case 8: // ctrl-H- move left (This may be ERASE char)
3564 case 0x7f: // DEL- move left (This may be ERASE char)
3567 } while (--cmdcnt > 0);
3569 case 10: // Newline ^J
3570 case 'j': // j- goto next line, same col
3571 case KEYCODE_DOWN: // cursor key Down
3573 dot_next(); // go to next B-o-l
3574 // try stay in same col
3575 dot = move_to_col(dot, ccol + offset);
3576 } while (--cmdcnt > 0);
3578 case 12: // ctrl-L force redraw whole screen
3579 case 18: // ctrl-R force redraw
3582 //mysleep(10); // why???
3583 screen_erase(); // erase the internal screen buffer
3584 last_status_cksum = 0; // force status update
3585 refresh(TRUE); // this will redraw the entire display
3587 case 13: // Carriage Return ^M
3588 case '+': // +- goto next line
3592 } while (--cmdcnt > 0);
3594 case 21: // ctrl-U scroll up half screen
3595 dot_scroll((rows - 2) / 2, -1);
3597 case 25: // ctrl-Y scroll up one line
3603 cmd_mode = 0; // stop insrting
3604 undo_queue_commit();
3606 last_status_cksum = 0; // force status update
3608 case ' ': // move right
3609 case 'l': // move right
3610 case KEYCODE_RIGHT: // Cursor Key Right
3613 } while (--cmdcnt > 0);
3615 #if ENABLE_FEATURE_VI_YANKMARK
3616 case '"': // "- name a register to use for Delete/Yank
3617 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3618 if ((unsigned)c1 <= 25) { // a-z?
3624 case '\'': // '- goto a specific mark
3625 c1 = (get_one_char() | 0x20);
3626 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3630 if (text <= q && q < end) {
3632 dot_begin(); // go to B-o-l
3635 } else if (c1 == '\'') { // goto previous context
3636 dot = swap_context(dot); // swap current and previous context
3637 dot_begin(); // go to B-o-l
3643 case 'm': // m- Mark a line
3644 // this is really stupid. If there are any inserts or deletes
3645 // between text[0] and dot then this mark will not point to the
3646 // correct location! It could be off by many lines!
3647 // Well..., at least its quick and dirty.
3648 c1 = (get_one_char() | 0x20) - 'a';
3649 if ((unsigned)c1 <= 25) { // a-z?
3650 // remember the line
3656 case 'P': // P- Put register before
3657 case 'p': // p- put register after
3660 status_line_bold("Nothing in register %c", what_reg());
3663 // are we putting whole lines or strings
3664 if (strchr(p, '\n') != NULL) {
3666 dot_begin(); // putting lines- Put above
3669 // are we putting after very last line?
3670 if (end_line(dot) == (end - 1)) {
3671 dot = end; // force dot to end of text[]
3673 dot_next(); // next line, then put before
3678 dot_right(); // move to right, can move to NL
3680 string_insert(dot, p, ALLOW_UNDO); // insert the string
3681 end_cmd_q(); // stop adding to q
3683 case 'U': // U- Undo; replace current line with original version
3684 if (reg[Ureg] != NULL) {
3685 p = begin_line(dot);
3687 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3688 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3693 #endif /* FEATURE_VI_YANKMARK */
3694 #if ENABLE_FEATURE_VI_UNDO
3695 case 'u': // u- undo last operation
3699 case '$': // $- goto end of line
3700 case KEYCODE_END: // Cursor Key End
3702 dot = end_line(dot);
3708 case '%': // %- find matching char of pair () [] {}
3709 for (q = dot; q < end && *q != '\n'; q++) {
3710 if (strchr("()[]{}", *q) != NULL) {
3711 // we found half of a pair
3712 p = find_pair(q, *q);
3724 case 'f': // f- forward to a user specified char
3725 last_forward_char = get_one_char(); // get the search char
3727 // dont separate these two commands. 'f' depends on ';'
3729 //**** fall through to ... ';'
3730 case ';': // ;- look at rest of line for last forward char
3732 if (last_forward_char == 0)
3735 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3738 if (*q == last_forward_char)
3740 } while (--cmdcnt > 0);
3742 case ',': // repeat latest 'f' in opposite direction
3743 if (last_forward_char == 0)
3747 while (q >= text && *q != '\n' && *q != last_forward_char) {
3750 if (q >= text && *q == last_forward_char)
3752 } while (--cmdcnt > 0);
3755 case '-': // -- goto prev line
3759 } while (--cmdcnt > 0);
3761 #if ENABLE_FEATURE_VI_DOT_CMD
3762 case '.': // .- repeat the last modifying command
3763 // Stuff the last_modifying_cmd back into stdin
3764 // and let it be re-executed.
3766 last_modifying_cmd[lmc_len] = 0;
3767 ioq = ioq_start = xstrdup(last_modifying_cmd);
3771 #if ENABLE_FEATURE_VI_SEARCH
3772 case '?': // /- search for a pattern
3773 case '/': // /- search for a pattern
3776 q = get_input_line(buf); // get input line- use "status line"
3777 if (q[0] && !q[1]) {
3778 if (last_search_pattern[0])
3779 last_search_pattern[0] = c;
3780 goto dc3; // if no pat re-use old pat
3782 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3783 // there is a new pat
3784 free(last_search_pattern);
3785 last_search_pattern = xstrdup(q);
3786 goto dc3; // now find the pattern
3788 // user changed mind and erased the "/"- do nothing
3790 case 'N': // N- backward search for last pattern
3791 dir = BACK; // assume BACKWARD search
3793 if (last_search_pattern[0] == '?') {
3797 goto dc4; // now search for pattern
3799 case 'n': // n- repeat search for last pattern
3800 // search rest of text[] starting at next char
3801 // if search fails return orignal "p" not the "p+1" address
3805 dir = FORWARD; // assume FORWARD search
3807 if (last_search_pattern[0] == '?') {
3812 q = char_search(p, last_search_pattern + 1, dir, FULL);
3814 dot = q; // good search, update "dot"
3818 // no pattern found between "dot" and "end"- continue at top
3823 q = char_search(p, last_search_pattern + 1, dir, FULL);
3824 if (q != NULL) { // found something
3825 dot = q; // found new pattern- goto it
3826 msg = "search hit BOTTOM, continuing at TOP";
3828 msg = "search hit TOP, continuing at BOTTOM";
3831 msg = "Pattern not found";
3835 status_line_bold("%s", msg);
3836 } while (--cmdcnt > 0);
3838 case '{': // {- move backward paragraph
3839 q = char_search(dot, "\n\n", BACK, FULL);
3840 if (q != NULL) { // found blank line
3841 dot = next_line(q); // move to next blank line
3844 case '}': // }- move forward paragraph
3845 q = char_search(dot, "\n\n", FORWARD, FULL);
3846 if (q != NULL) { // found blank line
3847 dot = next_line(q); // move to next blank line
3850 #endif /* FEATURE_VI_SEARCH */
3851 case '0': // 0- goto beginning of line
3861 if (c == '0' && cmdcnt < 1) {
3862 dot_begin(); // this was a standalone zero
3864 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3867 case ':': // :- the colon mode commands
3868 p = get_input_line(":"); // get input line- use "status line"
3869 colon(p); // execute the command
3871 case '<': // <- Left shift something
3872 case '>': // >- Right shift something
3873 cnt = count_lines(text, dot); // remember what line we are on
3874 c1 = get_one_char(); // get the type of thing to delete
3875 find_range(&p, &q, c1);
3876 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3879 i = count_lines(p, q); // # of lines we are shifting
3880 for ( ; i > 0; i--, p = next_line(p)) {
3882 // shift left- remove tab or 8 spaces
3884 // shrink buffer 1 char
3885 text_hole_delete(p, p, NO_UNDO);
3886 } else if (*p == ' ') {
3887 // we should be calculating columns, not just SPACE
3888 for (j = 0; *p == ' ' && j < tabstop; j++) {
3889 text_hole_delete(p, p, NO_UNDO);
3892 } else if (c == '>') {
3893 // shift right -- add tab or 8 spaces
3894 char_insert(p, '\t', ALLOW_UNDO);
3897 dot = find_line(cnt); // what line were we on
3899 end_cmd_q(); // stop adding to q
3901 case 'A': // A- append at e-o-l
3902 dot_end(); // go to e-o-l
3903 //**** fall through to ... 'a'
3904 case 'a': // a- append after current char
3909 case 'B': // B- back a blank-delimited Word
3910 case 'E': // E- end of a blank-delimited word
3911 case 'W': // W- forward a blank-delimited word
3916 if (c == 'W' || isspace(dot[dir])) {
3917 dot = skip_thing(dot, 1, dir, S_TO_WS);
3918 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3921 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3922 } while (--cmdcnt > 0);
3924 case 'C': // C- Change to e-o-l
3925 case 'D': // D- delete to e-o-l
3927 dot = dollar_line(dot); // move to before NL
3928 // copy text into a register and delete
3929 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3931 goto dc_i; // start inserting
3932 #if ENABLE_FEATURE_VI_DOT_CMD
3934 end_cmd_q(); // stop adding to q
3937 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3938 c1 = get_one_char();
3941 // c1 < 0 if the key was special. Try "g<up-arrow>"
3942 // TODO: if Unicode?
3943 buf[1] = (c1 >= 0 ? c1 : '*');
3945 not_implemented(buf);
3951 case 'G': // G- goto to a line number (default= E-O-F)
3952 dot = end - 1; // assume E-O-F
3954 dot = find_line(cmdcnt); // what line is #cmdcnt
3958 case 'H': // H- goto top line on screen
3960 if (cmdcnt > (rows - 1)) {
3961 cmdcnt = (rows - 1);
3968 case 'I': // I- insert before first non-blank
3971 //**** fall through to ... 'i'
3972 case 'i': // i- insert before current char
3973 case KEYCODE_INSERT: // Cursor Key Insert
3975 cmd_mode = 1; // start inserting
3976 undo_queue_commit(); // commit queue when cmd_mode changes
3978 case 'J': // J- join current and next lines together
3980 dot_end(); // move to NL
3981 if (dot < end - 1) { // make sure not last char in text[]
3982 #if ENABLE_FEATURE_VI_UNDO
3983 undo_push(dot, 1, UNDO_DEL);
3984 *dot++ = ' '; // replace NL with space
3985 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3990 while (isblank(*dot)) { // delete leading WS
3991 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3994 } while (--cmdcnt > 0);
3995 end_cmd_q(); // stop adding to q
3997 case 'L': // L- goto bottom line on screen
3999 if (cmdcnt > (rows - 1)) {
4000 cmdcnt = (rows - 1);
4008 case 'M': // M- goto middle line on screen
4010 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
4011 dot = next_line(dot);
4013 case 'O': // O- open a empty line above
4015 p = begin_line(dot);
4016 if (p[-1] == '\n') {
4018 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
4020 dot = char_insert(dot, '\n', ALLOW_UNDO);
4023 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
4028 case 'R': // R- continuous Replace char
4031 undo_queue_commit();
4033 case KEYCODE_DELETE:
4035 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
4037 case 'X': // X- delete char before dot
4038 case 'x': // x- delete the current char
4039 case 's': // s- substitute the current char
4044 if (dot[dir] != '\n') {
4046 dot--; // delete prev char
4047 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
4049 } while (--cmdcnt > 0);
4050 end_cmd_q(); // stop adding to q
4052 goto dc_i; // start inserting
4054 case 'Z': // Z- if modified, {write}; exit
4055 // ZZ means to save file (if necessary), then exit
4056 c1 = get_one_char();
4061 if (modified_count) {
4062 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
4063 status_line_bold("'%s' is read only", current_filename);
4066 cnt = file_write(current_filename, text, end - 1);
4069 status_line_bold("Write error: %s", strerror(errno));
4070 } else if (cnt == (end - 1 - text + 1)) {
4077 case '^': // ^- move to first non-blank on line
4081 case 'b': // b- back a word
4082 case 'e': // e- end of word
4087 if ((dot + dir) < text || (dot + dir) > end - 1)
4090 if (isspace(*dot)) {
4091 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4093 if (isalnum(*dot) || *dot == '_') {
4094 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4095 } else if (ispunct(*dot)) {
4096 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4098 } while (--cmdcnt > 0);
4100 case 'c': // c- change something
4101 case 'd': // d- delete something
4102 #if ENABLE_FEATURE_VI_YANKMARK
4103 case 'y': // y- yank something
4104 case 'Y': // Y- Yank a line
4107 int yf, ml, whole = 0;
4108 yf = YANKDEL; // assume either "c" or "d"
4109 #if ENABLE_FEATURE_VI_YANKMARK
4110 if (c == 'y' || c == 'Y')
4115 c1 = get_one_char(); // get the type of thing to delete
4116 // determine range, and whether it spans lines
4117 ml = find_range(&p, &q, c1);
4119 if (c1 == 27) { // ESC- user changed mind and wants out
4120 c = c1 = 27; // Escape- do nothing
4121 } else if (strchr("wW", c1)) {
4123 // don't include trailing WS as part of word
4124 while (isblank(*q)) {
4125 if (q <= text || q[-1] == '\n')
4130 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4131 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
4132 // partial line copy text into a register and delete
4133 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4134 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
4135 // whole line copy text into a register and delete
4136 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
4139 // could not recognize object
4140 c = c1 = 27; // error-
4146 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4147 // on the last line of file don't move to prev line
4148 if (whole && dot != (end-1)) {
4151 } else if (c == 'd') {
4157 // if CHANGING, not deleting, start inserting after the delete
4159 strcpy(buf, "Change");
4160 goto dc_i; // start inserting
4163 strcpy(buf, "Delete");
4165 #if ENABLE_FEATURE_VI_YANKMARK
4166 if (c == 'y' || c == 'Y') {
4167 strcpy(buf, "Yank");
4171 for (cnt = 0; p <= q; p++) {
4175 status_line("%s %d lines (%d chars) using [%c]",
4176 buf, cnt, strlen(reg[YDreg]), what_reg());
4178 end_cmd_q(); // stop adding to q
4182 case 'k': // k- goto prev line, same col
4183 case KEYCODE_UP: // cursor key Up
4186 dot = move_to_col(dot, ccol + offset); // try stay in same col
4187 } while (--cmdcnt > 0);
4189 case 'r': // r- replace the current char with user input
4190 c1 = get_one_char(); // get the replacement char
4192 #if ENABLE_FEATURE_VI_UNDO
4193 undo_push(dot, 1, UNDO_DEL);
4195 undo_push(dot, 1, UNDO_INS_CHAIN);
4201 end_cmd_q(); // stop adding to q
4203 case 't': // t- move to char prior to next x
4204 last_forward_char = get_one_char();
4206 if (*dot == last_forward_char)
4208 last_forward_char = 0;
4210 case 'w': // w- forward a word
4212 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4213 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4214 } else if (ispunct(*dot)) { // we are on PUNCT
4215 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4218 dot++; // move over word
4219 if (isspace(*dot)) {
4220 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4222 } while (--cmdcnt > 0);
4225 c1 = get_one_char(); // get the replacement char
4228 cnt = (rows - 2) / 2; // put dot at center
4230 cnt = rows - 2; // put dot at bottom
4231 screenbegin = begin_line(dot); // start dot at top
4232 dot_scroll(cnt, -1);
4234 case '|': // |- move to column "cmdcnt"
4235 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4237 case '~': // ~- flip the case of letters a-z -> A-Z
4239 #if ENABLE_FEATURE_VI_UNDO
4240 if (islower(*dot)) {
4241 undo_push(dot, 1, UNDO_DEL);
4242 *dot = toupper(*dot);
4243 undo_push(dot, 1, UNDO_INS_CHAIN);
4244 } else if (isupper(*dot)) {
4245 undo_push(dot, 1, UNDO_DEL);
4246 *dot = tolower(*dot);
4247 undo_push(dot, 1, UNDO_INS_CHAIN);
4250 if (islower(*dot)) {
4251 *dot = toupper(*dot);
4253 } else if (isupper(*dot)) {
4254 *dot = tolower(*dot);
4259 } while (--cmdcnt > 0);
4260 end_cmd_q(); // stop adding to q
4262 //----- The Cursor and Function Keys -----------------------------
4263 case KEYCODE_HOME: // Cursor Key Home
4266 // The Fn keys could point to do_macro which could translate them
4268 case KEYCODE_FUN1: // Function Key F1
4269 case KEYCODE_FUN2: // Function Key F2
4270 case KEYCODE_FUN3: // Function Key F3
4271 case KEYCODE_FUN4: // Function Key F4
4272 case KEYCODE_FUN5: // Function Key F5
4273 case KEYCODE_FUN6: // Function Key F6
4274 case KEYCODE_FUN7: // Function Key F7
4275 case KEYCODE_FUN8: // Function Key F8
4276 case KEYCODE_FUN9: // Function Key F9
4277 case KEYCODE_FUN10: // Function Key F10
4278 case KEYCODE_FUN11: // Function Key F11
4279 case KEYCODE_FUN12: // Function Key F12
4285 // if text[] just became empty, add back an empty line
4287 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4290 // it is OK for dot to exactly equal to end, otherwise check dot validity
4292 dot = bound_dot(dot); // make sure "dot" is valid
4294 #if ENABLE_FEATURE_VI_YANKMARK
4295 check_context(c); // update the current context
4299 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4300 cnt = dot - begin_line(dot);
4301 // Try to stay off of the Newline
4302 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4306 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4307 #if ENABLE_FEATURE_VI_CRASHME
4308 static int totalcmds = 0;
4309 static int Mp = 85; // Movement command Probability
4310 static int Np = 90; // Non-movement command Probability
4311 static int Dp = 96; // Delete command Probability
4312 static int Ip = 97; // Insert command Probability
4313 static int Yp = 98; // Yank command Probability
4314 static int Pp = 99; // Put command Probability
4315 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4316 static const char chars[20] = "\t012345 abcdABCD-=.$";
4317 static const char *const words[20] = {
4318 "this", "is", "a", "test",
4319 "broadcast", "the", "emergency", "of",
4320 "system", "quick", "brown", "fox",
4321 "jumped", "over", "lazy", "dogs",
4322 "back", "January", "Febuary", "March"
4324 static const char *const lines[20] = {
4325 "You should have received a copy of the GNU General Public License\n",
4326 "char c, cm, *cmd, *cmd1;\n",
4327 "generate a command by percentages\n",
4328 "Numbers may be typed as a prefix to some commands.\n",
4329 "Quit, discarding changes!\n",
4330 "Forced write, if permission originally not valid.\n",
4331 "In general, any ex or ed command (such as substitute or delete).\n",
4332 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4333 "Please get w/ me and I will go over it with you.\n",
4334 "The following is a list of scheduled, committed changes.\n",
4335 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4336 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4337 "Any question about transactions please contact Sterling Huxley.\n",
4338 "I will try to get back to you by Friday, December 31.\n",
4339 "This Change will be implemented on Friday.\n",
4340 "Let me know if you have problems accessing this;\n",
4341 "Sterling Huxley recently added you to the access list.\n",
4342 "Would you like to go to lunch?\n",
4343 "The last command will be automatically run.\n",
4344 "This is too much english for a computer geek.\n",
4346 static char *multilines[20] = {
4347 "You should have received a copy of the GNU General Public License\n",
4348 "char c, cm, *cmd, *cmd1;\n",
4349 "generate a command by percentages\n",
4350 "Numbers may be typed as a prefix to some commands.\n",
4351 "Quit, discarding changes!\n",
4352 "Forced write, if permission originally not valid.\n",
4353 "In general, any ex or ed command (such as substitute or delete).\n",
4354 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4355 "Please get w/ me and I will go over it with you.\n",
4356 "The following is a list of scheduled, committed changes.\n",
4357 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4358 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4359 "Any question about transactions please contact Sterling Huxley.\n",
4360 "I will try to get back to you by Friday, December 31.\n",
4361 "This Change will be implemented on Friday.\n",
4362 "Let me know if you have problems accessing this;\n",
4363 "Sterling Huxley recently added you to the access list.\n",
4364 "Would you like to go to lunch?\n",
4365 "The last command will be automatically run.\n",
4366 "This is too much english for a computer geek.\n",
4369 // create a random command to execute
4370 static void crash_dummy()
4372 static int sleeptime; // how long to pause between commands
4373 char c, cm, *cmd, *cmd1;
4374 int i, cnt, thing, rbi, startrbi, percent;
4376 // "dot" movement commands
4377 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4379 // is there already a command running?
4380 if (readbuffer[0] > 0)
4383 readbuffer[0] = 'X';
4385 sleeptime = 0; // how long to pause between commands
4386 memset(readbuffer, '\0', sizeof(readbuffer));
4387 // generate a command by percentages
4388 percent = (int) lrand48() % 100; // get a number from 0-99
4389 if (percent < Mp) { // Movement commands
4390 // available commands
4393 } else if (percent < Np) { // non-movement commands
4394 cmd = "mz<>\'\""; // available commands
4396 } else if (percent < Dp) { // Delete commands
4397 cmd = "dx"; // available commands
4399 } else if (percent < Ip) { // Inset commands
4400 cmd = "iIaAsrJ"; // available commands
4402 } else if (percent < Yp) { // Yank commands
4403 cmd = "yY"; // available commands
4405 } else if (percent < Pp) { // Put commands
4406 cmd = "pP"; // available commands
4409 // We do not know how to handle this command, try again
4413 // randomly pick one of the available cmds from "cmd[]"
4414 i = (int) lrand48() % strlen(cmd);
4416 if (strchr(":\024", cm))
4417 goto cd0; // dont allow colon or ctrl-T commands
4418 readbuffer[rbi++] = cm; // put cmd into input buffer
4420 // now we have the command-
4421 // there are 1, 2, and multi char commands
4422 // find out which and generate the rest of command as necessary
4423 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4424 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4425 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4426 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4428 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4430 readbuffer[rbi++] = c; // add movement to input buffer
4432 if (strchr("iIaAsc", cm)) { // multi-char commands
4434 // change some thing
4435 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4437 readbuffer[rbi++] = c; // add movement to input buffer
4439 thing = (int) lrand48() % 4; // what thing to insert
4440 cnt = (int) lrand48() % 10; // how many to insert
4441 for (i = 0; i < cnt; i++) {
4442 if (thing == 0) { // insert chars
4443 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4444 } else if (thing == 1) { // insert words
4445 strcat(readbuffer, words[(int) lrand48() % 20]);
4446 strcat(readbuffer, " ");
4447 sleeptime = 0; // how fast to type
4448 } else if (thing == 2) { // insert lines
4449 strcat(readbuffer, lines[(int) lrand48() % 20]);
4450 sleeptime = 0; // how fast to type
4451 } else { // insert multi-lines
4452 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4453 sleeptime = 0; // how fast to type
4456 strcat(readbuffer, ESC);
4458 readbuffer[0] = strlen(readbuffer + 1);
4462 mysleep(sleeptime); // sleep 1/100 sec
4465 // test to see if there are any errors
4466 static void crash_test()
4468 static time_t oldtim;
4475 strcat(msg, "end<text ");
4477 if (end > textend) {
4478 strcat(msg, "end>textend ");
4481 strcat(msg, "dot<text ");
4484 strcat(msg, "dot>end ");
4486 if (screenbegin < text) {
4487 strcat(msg, "screenbegin<text ");
4489 if (screenbegin > end - 1) {
4490 strcat(msg, "screenbegin>end-1 ");
4494 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4495 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4497 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4498 if (d[0] == '\n' || d[0] == '\r')
4503 if (tim >= (oldtim + 3)) {
4504 sprintf(status_buffer,
4505 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4506 totalcmds, M, N, I, D, Y, P, U, end - text + 1);