1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
16 * if mark[] values were line numbers rather than pointers
17 * it would be easier to change the mark when add/delete lines
18 * More intelligence in refresh()
19 * ":r !cmd" and "!cmd" to filter text through an external command
20 * An "ex" line oriented mode- maybe using "cmdedit"
27 //config: 'vi' is a text editor. More specifically, it is the One True
28 //config: text editor <grin>. It does, however, have a rather steep
29 //config: learning curve. If you are not already comfortable with 'vi'
30 //config: you may wish to use something else.
32 //config:config FEATURE_VI_MAX_LEN
33 //config: int "Maximum screen width"
34 //config: range 256 16384
35 //config: default 4096
36 //config: depends on VI
38 //config: Contrary to what you may think, this is not eating much.
39 //config: Make it smaller than 4k only if you are very limited on memory.
41 //config:config FEATURE_VI_8BIT
42 //config: bool "Allow to display 8-bit chars (otherwise shows dots)"
44 //config: depends on VI
46 //config: If your terminal can display characters with high bit set,
47 //config: you may want to enable this. Note: vi is not Unicode-capable.
48 //config: If your terminal combines several 8-bit bytes into one character
49 //config: (as in Unicode mode), this will not work properly.
51 //config:config FEATURE_VI_COLON
52 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
54 //config: depends on VI
56 //config: Enable a limited set of colon commands. This does not
57 //config: provide an "ex" mode.
59 //config:config FEATURE_VI_YANKMARK
60 //config: bool "Enable yank/put commands and mark cmds"
62 //config: depends on VI
64 //config: This will enable you to use yank and put, as well as mark.
66 //config:config FEATURE_VI_SEARCH
67 //config: bool "Enable search and replace cmds"
69 //config: depends on VI
71 //config: Select this if you wish to be able to do search and replace.
73 //config:config FEATURE_VI_REGEX_SEARCH
74 //config: bool "Enable regex in search and replace"
75 //config: default n # Uses GNU regex, which may be unavailable. FIXME
76 //config: depends on FEATURE_VI_SEARCH
78 //config: Use extended regex search.
80 //config:config FEATURE_VI_USE_SIGNALS
81 //config: bool "Catch signals"
83 //config: depends on VI
85 //config: Selecting this option will make vi signal aware. This will support
86 //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
88 //config:config FEATURE_VI_DOT_CMD
89 //config: bool "Remember previous cmd and \".\" cmd"
91 //config: depends on VI
93 //config: Make vi remember the last command and be able to repeat it.
95 //config:config FEATURE_VI_READONLY
96 //config: bool "Enable -R option and \"view\" mode"
98 //config: depends on VI
100 //config: Enable the read-only command line option, which allows the user to
101 //config: open a file in read-only mode.
103 //config:config FEATURE_VI_SETOPTS
104 //config: bool "Enable settable options, ai ic showmatch"
106 //config: depends on VI
108 //config: Enable the editor to set some (ai, ic, showmatch) options.
110 //config:config FEATURE_VI_SET
111 //config: bool "Support :set"
113 //config: depends on VI
115 //config:config FEATURE_VI_WIN_RESIZE
116 //config: bool "Handle window resize"
118 //config: depends on VI
120 //config: Behave nicely with terminals that get resized.
122 //config:config FEATURE_VI_ASK_TERMINAL
123 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
125 //config: depends on VI
127 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
128 //config: this option makes vi perform a last-ditch effort to find it:
129 //config: position cursor to 999,999 and ask terminal to report real
130 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
131 //config: This is not clean but helps a lot on serial lines and such.
133 //config:config FEATURE_VI_UNDO
134 //config: bool "Support undo command \"u\""
136 //config: depends on VI
138 //config: Support the 'u' command to undo insertion, deletion, and replacement
141 //config:config FEATURE_VI_UNDO_QUEUE
142 //config: bool "Enable undo operation queuing"
144 //config: depends on FEATURE_VI_UNDO
146 //config: The vi undo functions can use an intermediate queue to greatly lower
147 //config: malloc() calls and overhead. When the maximum size of this queue is
148 //config: reached, the contents of the queue are committed to the undo stack.
149 //config: This increases the size of the undo code and allows some undo
150 //config: operations (especially un-typing/backspacing) to be far more useful.
152 //config:config FEATURE_VI_UNDO_QUEUE_MAX
153 //config: int "Maximum undo character queue size"
154 //config: default 256
155 //config: range 32 65536
156 //config: depends on FEATURE_VI_UNDO_QUEUE
158 //config: This option sets the number of bytes used at runtime for the queue.
159 //config: Smaller values will create more undo objects and reduce the amount
160 //config: of typed or backspaced characters that are grouped into one undo
161 //config: operation; larger values increase the potential size of each undo
162 //config: and will generally malloc() larger objects and less frequently.
163 //config: Unless you want more (or less) frequent "undo points" while typing,
164 //config: you should probably leave this unchanged.
166 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
168 //kbuild:lib-$(CONFIG_VI) += vi.o
170 //usage:#define vi_trivial_usage
171 //usage: "[OPTIONS] [FILE]..."
172 //usage:#define vi_full_usage "\n\n"
173 //usage: "Edit FILE\n"
174 //usage: IF_FEATURE_VI_COLON(
175 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
177 //usage: IF_FEATURE_VI_READONLY(
178 //usage: "\n -R Read-only"
180 //usage: "\n -H List available features"
183 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
184 #if ENABLE_FEATURE_VI_REGEX_SEARCH
188 /* the CRASHME code is unmaintained, and doesn't currently build */
189 #define ENABLE_FEATURE_VI_CRASHME 0
192 #if ENABLE_LOCALE_SUPPORT
194 #if ENABLE_FEATURE_VI_8BIT
195 //FIXME: this does not work properly for Unicode anyway
196 # define Isprint(c) (isprint)(c)
198 # define Isprint(c) isprint_asciionly(c)
203 /* 0x9b is Meta-ESC */
204 #if ENABLE_FEATURE_VI_8BIT
205 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
207 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
214 MAX_TABSTOP = 32, // sanity limit
215 // User input len. Need not be extra big.
216 // Lines in file being edited *can* be bigger than this.
218 // Sanity limits. We have only one buffer of this size.
219 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
220 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
223 /* VT102 ESC sequences.
224 * See "Xterm Control Sequences"
225 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
227 /* Inverse/Normal text */
228 #define ESC_BOLD_TEXT "\033[7m"
229 #define ESC_NORM_TEXT "\033[0m"
231 #define ESC_BELL "\007"
232 /* Clear-to-end-of-line */
233 #define ESC_CLEAR2EOL "\033[K"
234 /* Clear-to-end-of-screen.
235 * (We use default param here.
236 * Full sequence is "ESC [ <num> J",
237 * <num> is 0/1/2 = "erase below/above/all".)
239 #define ESC_CLEAR2EOS "\033[J"
240 /* Cursor to given coordinate (1,1: top left) */
241 #define ESC_SET_CURSOR_POS "\033[%u;%uH"
243 ///* Cursor up and down */
244 //#define ESC_CURSOR_UP "\033[A"
245 //#define ESC_CURSOR_DOWN "\n"
247 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
248 // cmds modifying text[]
249 // vda: removed "aAiIs" as they switch us into insert mode
250 // and remembering input for replay after them makes no sense
251 static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
257 FORWARD = 1, // code depends on "1" for array index
258 BACK = -1, // code depends on "-1" for array index
259 LIMITED = 0, // how much of text[] in char_search
260 FULL = 1, // how much of text[] in char_search
262 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
263 S_TO_WS = 2, // used in skip_thing() for moving "dot"
264 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
265 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
266 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
270 /* vi.c expects chars to be unsigned. */
271 /* busybox build system provides that, but it's better */
272 /* to audit and fix the source */
275 /* many references - keep near the top of globals */
276 char *text, *end; // pointers to the user data in memory
277 char *dot; // where all the action takes place
278 int text_size; // size of the allocated buffer
282 #define VI_AUTOINDENT 1
283 #define VI_SHOWMATCH 2
284 #define VI_IGNORECASE 4
285 #define VI_ERR_METHOD 8
286 #define autoindent (vi_setops & VI_AUTOINDENT)
287 #define showmatch (vi_setops & VI_SHOWMATCH )
288 #define ignorecase (vi_setops & VI_IGNORECASE)
289 /* indicate error with beep or flash */
290 #define err_method (vi_setops & VI_ERR_METHOD)
292 #if ENABLE_FEATURE_VI_READONLY
293 smallint readonly_mode;
294 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
295 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
296 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
298 #define SET_READONLY_FILE(flags) ((void)0)
299 #define SET_READONLY_MODE(flags) ((void)0)
300 #define UNSET_READONLY_FILE(flags) ((void)0)
303 smallint editing; // >0 while we are editing a file
304 // [code audit says "can be 0, 1 or 2 only"]
305 smallint cmd_mode; // 0=command 1=insert 2=replace
306 int modified_count; // buffer contents changed if !0
307 int last_modified_count; // = -1;
308 int save_argc; // how many file names on cmd line
309 int cmdcnt; // repetition count
310 unsigned rows, columns; // the terminal screen is this size
311 #if ENABLE_FEATURE_VI_ASK_TERMINAL
312 int get_rowcol_error;
314 int crow, ccol; // cursor is on Crow x Ccol
315 int offset; // chars scrolled off the screen to the left
316 int have_status_msg; // is default edit status needed?
317 // [don't make smallint!]
318 int last_status_cksum; // hash of current status line
319 char *current_filename;
320 char *screenbegin; // index into text[], of top line on the screen
321 char *screen; // pointer to the virtual screen buffer
322 int screensize; // and its size
324 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
325 char erase_char; // the users erase character
326 char last_input_char; // last char read from user
328 #if ENABLE_FEATURE_VI_DOT_CMD
329 smallint adding2q; // are we currently adding user input to q
330 int lmc_len; // length of last_modifying_cmd
331 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
333 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
336 #if ENABLE_FEATURE_VI_SEARCH
337 char *last_search_pattern; // last pattern from a '/' or '?' search
341 #if ENABLE_FEATURE_VI_YANKMARK
342 char *edit_file__cur_line;
344 int refresh__old_offset;
345 int format_edit_status__tot;
347 /* a few references only */
348 #if ENABLE_FEATURE_VI_YANKMARK
349 int YDreg, Ureg; // default delete register and orig line for "U"
350 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
351 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
352 char *context_start, *context_end;
354 #if ENABLE_FEATURE_VI_USE_SIGNALS
355 sigjmp_buf restart; // catch_sig()
357 struct termios term_orig, term_vi; // remember what the cooked mode was
358 #if ENABLE_FEATURE_VI_COLON
359 char *initial_cmds[3]; // currently 2 entries, NULL terminated
361 // Should be just enough to hold a key sequence,
362 // but CRASHME mode uses it as generated command buffer too
363 #if ENABLE_FEATURE_VI_CRASHME
364 char readbuffer[128];
366 char readbuffer[KEYCODE_BUFFER_SIZE];
368 #define STATUS_BUFFER_LEN 200
369 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
370 #if ENABLE_FEATURE_VI_DOT_CMD
371 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
373 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
375 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
376 #if ENABLE_FEATURE_VI_UNDO
377 // undo_push() operations
380 #define UNDO_INS_CHAIN 2
381 #define UNDO_DEL_CHAIN 3
382 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
383 #define UNDO_QUEUED_FLAG 4
384 #define UNDO_INS_QUEUED 4
385 #define UNDO_DEL_QUEUED 5
386 #define UNDO_USE_SPOS 32
387 #define UNDO_EMPTY 64
388 // Pass-through flags for functions that can be undone
391 #define ALLOW_UNDO_CHAIN 2
392 # if ENABLE_FEATURE_VI_UNDO_QUEUE
393 #define ALLOW_UNDO_QUEUED 3
394 char undo_queue_state;
396 char *undo_queue_spos; // Start position of queued operation
397 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
399 // If undo queuing disabled, don't invoke the missing queue logic
400 #define ALLOW_UNDO_QUEUED 1
404 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
405 int start; // Offset where the data should be restored/deleted
406 int length; // total data size
407 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
408 char undo_text[1]; // text that was deleted (if deletion)
410 #endif /* ENABLE_FEATURE_VI_UNDO */
412 #define G (*ptr_to_globals)
413 #define text (G.text )
414 #define text_size (G.text_size )
419 #define vi_setops (G.vi_setops )
420 #define editing (G.editing )
421 #define cmd_mode (G.cmd_mode )
422 #define modified_count (G.modified_count )
423 #define last_modified_count (G.last_modified_count)
424 #define save_argc (G.save_argc )
425 #define cmdcnt (G.cmdcnt )
426 #define rows (G.rows )
427 #define columns (G.columns )
428 #define crow (G.crow )
429 #define ccol (G.ccol )
430 #define offset (G.offset )
431 #define status_buffer (G.status_buffer )
432 #define have_status_msg (G.have_status_msg )
433 #define last_status_cksum (G.last_status_cksum )
434 #define current_filename (G.current_filename )
435 #define screen (G.screen )
436 #define screensize (G.screensize )
437 #define screenbegin (G.screenbegin )
438 #define tabstop (G.tabstop )
439 #define last_forward_char (G.last_forward_char )
440 #define erase_char (G.erase_char )
441 #define last_input_char (G.last_input_char )
442 #if ENABLE_FEATURE_VI_READONLY
443 #define readonly_mode (G.readonly_mode )
445 #define readonly_mode 0
447 #define adding2q (G.adding2q )
448 #define lmc_len (G.lmc_len )
450 #define ioq_start (G.ioq_start )
451 #define my_pid (G.my_pid )
452 #define last_search_pattern (G.last_search_pattern)
454 #define edit_file__cur_line (G.edit_file__cur_line)
455 #define refresh__old_offset (G.refresh__old_offset)
456 #define format_edit_status__tot (G.format_edit_status__tot)
458 #define YDreg (G.YDreg )
459 #define Ureg (G.Ureg )
460 #define mark (G.mark )
461 #define context_start (G.context_start )
462 #define context_end (G.context_end )
463 #define restart (G.restart )
464 #define term_orig (G.term_orig )
465 #define term_vi (G.term_vi )
466 #define initial_cmds (G.initial_cmds )
467 #define readbuffer (G.readbuffer )
468 #define scr_out_buf (G.scr_out_buf )
469 #define last_modifying_cmd (G.last_modifying_cmd )
470 #define get_input_line__buf (G.get_input_line__buf)
472 #if ENABLE_FEATURE_VI_UNDO
473 #define undo_stack_tail (G.undo_stack_tail )
474 # if ENABLE_FEATURE_VI_UNDO_QUEUE
475 #define undo_queue_state (G.undo_queue_state)
476 #define undo_q (G.undo_q )
477 #define undo_queue (G.undo_queue )
478 #define undo_queue_spos (G.undo_queue_spos )
482 #define INIT_G() do { \
483 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
484 last_modified_count = -1; \
485 /* "" but has space for 2 chars: */ \
486 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
490 static void edit_file(char *); // edit one file
491 static void do_cmd(int); // execute a command
492 static int next_tabstop(int);
493 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
494 static char *begin_line(char *); // return pointer to cur line B-o-l
495 static char *end_line(char *); // return pointer to cur line E-o-l
496 static char *prev_line(char *); // return pointer to prev line B-o-l
497 static char *next_line(char *); // return pointer to next line B-o-l
498 static char *end_screen(void); // get pointer to last char on screen
499 static int count_lines(char *, char *); // count line from start to stop
500 static char *find_line(int); // find beginning of line #li
501 static char *move_to_col(char *, int); // move "p" to column l
502 static void dot_left(void); // move dot left- dont leave line
503 static void dot_right(void); // move dot right- dont leave line
504 static void dot_begin(void); // move dot to B-o-l
505 static void dot_end(void); // move dot to E-o-l
506 static void dot_next(void); // move dot to next line B-o-l
507 static void dot_prev(void); // move dot to prev line B-o-l
508 static void dot_scroll(int, int); // move the screen up or down
509 static void dot_skip_over_ws(void); // move dot pat WS
510 static char *bound_dot(char *); // make sure text[0] <= P < "end"
511 static char *new_screen(int, int); // malloc virtual screen memory
512 #if !ENABLE_FEATURE_VI_UNDO
513 #define char_insert(a,b,c) char_insert(a,b)
515 static char *char_insert(char *, char, int); // insert the char c at 'p'
516 // might reallocate text[]! use p += stupid_insert(p, ...),
517 // and be careful to not use pointers into potentially freed text[]!
518 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
519 static int find_range(char **, char **, char); // return pointers for an object
520 static int st_test(char *, int, int, char *); // helper for skip_thing()
521 static char *skip_thing(char *, int, int, int); // skip some object
522 static char *find_pair(char *, char); // find matching pair () [] {}
523 #if !ENABLE_FEATURE_VI_UNDO
524 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
526 static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
527 // might reallocate text[]! use p += text_hole_make(p, ...),
528 // and be careful to not use pointers into potentially freed text[]!
529 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
530 #if !ENABLE_FEATURE_VI_UNDO
531 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
533 static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
534 static void show_help(void); // display some help info
535 static void rawmode(void); // set "raw" mode on tty
536 static void cookmode(void); // return to "cooked" mode on tty
537 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
538 static int mysleep(int);
539 static int readit(void); // read (maybe cursor) key from stdin
540 static int get_one_char(void); // read 1 char from stdin
541 // file_insert might reallocate text[]!
542 static int file_insert(const char *, char *, int);
543 static int file_write(char *, char *, char *);
544 static void place_cursor(int, int);
545 static void screen_erase(void);
546 static void clear_to_eol(void);
547 static void clear_to_eos(void);
548 static void go_bottom_and_clear_to_eol(void);
549 static void standout_start(void); // send "start reverse video" sequence
550 static void standout_end(void); // send "end reverse video" sequence
551 static void flash(int); // flash the terminal screen
552 static void show_status_line(void); // put a message on the bottom line
553 static void status_line(const char *, ...); // print to status buf
554 static void status_line_bold(const char *, ...);
555 static void status_line_bold_errno(const char *fn);
556 static void not_implemented(const char *); // display "Not implemented" message
557 static int format_edit_status(void); // format file status on status line
558 static void redraw(int); // force a full screen refresh
559 static char* format_line(char* /*, int*/);
560 static void refresh(int); // update the terminal from screen[]
562 static void indicate_error(void); // use flash or beep to indicate error
563 static void Hit_Return(void);
565 #if ENABLE_FEATURE_VI_SEARCH
566 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
568 #if ENABLE_FEATURE_VI_COLON
569 static char *get_one_address(char *, int *); // get colon addr, if present
570 static char *get_address(char *, int *, int *); // get two colon addrs, if present
572 static void colon(char *); // execute the "colon" mode cmds
573 #if ENABLE_FEATURE_VI_USE_SIGNALS
574 static void winch_sig(int); // catch window size changes
575 static void suspend_sig(int); // catch ctrl-Z
576 static void catch_sig(int); // catch ctrl-C and alarm time-outs
578 #if ENABLE_FEATURE_VI_DOT_CMD
579 static void start_new_cmd_q(char); // new queue for command
580 static void end_cmd_q(void); // stop saving input chars
582 #define end_cmd_q() ((void)0)
584 #if ENABLE_FEATURE_VI_SETOPTS
585 static void showmatching(char *); // show the matching pair () [] {}
587 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
588 // might reallocate text[]! use p += string_insert(p, ...),
589 // and be careful to not use pointers into potentially freed text[]!
590 # if !ENABLE_FEATURE_VI_UNDO
591 #define string_insert(a,b,c) string_insert(a,b)
593 static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
595 #if ENABLE_FEATURE_VI_YANKMARK
596 static char *text_yank(char *, char *, int); // save copy of "p" into a register
597 static char what_reg(void); // what is letter of current YDreg
598 static void check_context(char); // remember context for '' command
600 #if ENABLE_FEATURE_VI_UNDO
601 static void flush_undo_data(void);
602 static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
603 static void undo_pop(void); // Undo the last operation
604 # if ENABLE_FEATURE_VI_UNDO_QUEUE
605 static void undo_queue_commit(void); // Flush any queued objects to the undo stack
607 # define undo_queue_commit() ((void)0)
610 #define flush_undo_data() ((void)0)
611 #define undo_queue_commit() ((void)0)
614 #if ENABLE_FEATURE_VI_CRASHME
615 static void crash_dummy();
616 static void crash_test();
617 static int crashme = 0;
620 static void write1(const char *out)
625 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
626 int vi_main(int argc, char **argv)
632 #if ENABLE_FEATURE_VI_UNDO
633 /* undo_stack_tail = NULL; - already is */
634 #if ENABLE_FEATURE_VI_UNDO_QUEUE
635 undo_queue_state = UNDO_EMPTY;
636 /* undo_q = 0; - already is */
640 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
643 #if ENABLE_FEATURE_VI_CRASHME
644 srand((long) my_pid);
646 #ifdef NO_SUCH_APPLET_YET
647 /* If we aren't "vi", we are "view" */
648 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
649 SET_READONLY_MODE(readonly_mode);
653 // autoindent is not default in vim 7.3
654 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
655 // 1- process $HOME/.exrc file (not inplemented yet)
656 // 2- process EXINIT variable from environment
657 // 3- process command line args
658 #if ENABLE_FEATURE_VI_COLON
660 char *p = getenv("EXINIT");
662 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
665 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
667 #if ENABLE_FEATURE_VI_CRASHME
672 #if ENABLE_FEATURE_VI_READONLY
673 case 'R': // Read-only flag
674 SET_READONLY_MODE(readonly_mode);
677 #if ENABLE_FEATURE_VI_COLON
678 case 'c': // cmd line vi command
680 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
692 // The argv array can be used by the ":next" and ":rewind" commands
696 //----- This is the main file handling loop --------------
699 // "Save cursor, use alternate screen buffer, clear screen"
700 write1("\033[?1049h");
702 edit_file(argv[optind]); /* param might be NULL */
703 if (++optind >= argc)
706 // "Use normal screen buffer, restore cursor"
707 write1("\033[?1049l");
708 //-----------------------------------------------------------
713 /* read text from file or create an empty buf */
714 /* will also update current_filename */
715 static int init_text_buffer(char *fn)
721 last_modified_count = -1;
722 #if ENABLE_FEATURE_VI_YANKMARK
724 memset(mark, 0, sizeof(mark));
727 /* allocate/reallocate text buffer */
730 screenbegin = dot = end = text = xzalloc(text_size);
732 if (fn != current_filename) {
733 free(current_filename);
734 current_filename = xstrdup(fn);
736 rc = file_insert(fn, text, 1);
738 // file doesnt exist. Start empty buf with dummy line
739 char_insert(text, '\n', NO_UNDO);
744 #if ENABLE_FEATURE_VI_WIN_RESIZE
745 static int query_screen_dimensions(void)
747 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
748 if (rows > MAX_SCR_ROWS)
750 if (columns > MAX_SCR_COLS)
751 columns = MAX_SCR_COLS;
755 # define query_screen_dimensions() (0)
758 static void edit_file(char *fn)
760 #if ENABLE_FEATURE_VI_YANKMARK
761 #define cur_line edit_file__cur_line
764 #if ENABLE_FEATURE_VI_USE_SIGNALS
768 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
772 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
773 #if ENABLE_FEATURE_VI_ASK_TERMINAL
774 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
776 write1("\033[999;999H" "\033[6n");
778 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
779 if ((int32_t)k == KEYCODE_CURSOR_POS) {
780 uint32_t rc = (k >> 32);
781 columns = (rc & 0x7fff);
782 if (columns > MAX_SCR_COLS)
783 columns = MAX_SCR_COLS;
784 rows = ((rc >> 16) & 0x7fff);
785 if (rows > MAX_SCR_ROWS)
790 new_screen(rows, columns); // get memory for virtual screen
791 init_text_buffer(fn);
793 #if ENABLE_FEATURE_VI_YANKMARK
794 YDreg = 26; // default Yank/Delete reg
795 Ureg = 27; // hold orig line for "U" cmd
796 mark[26] = mark[27] = text; // init "previous context"
799 last_forward_char = last_input_char = '\0';
803 #if ENABLE_FEATURE_VI_USE_SIGNALS
804 signal(SIGINT, catch_sig);
805 signal(SIGWINCH, winch_sig);
806 signal(SIGTSTP, suspend_sig);
807 sig = sigsetjmp(restart, 1);
809 screenbegin = dot = text;
813 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
816 offset = 0; // no horizontal offset
818 #if ENABLE_FEATURE_VI_DOT_CMD
820 ioq = ioq_start = NULL;
825 #if ENABLE_FEATURE_VI_COLON
830 while ((p = initial_cmds[n]) != NULL) {
840 free(initial_cmds[n]);
841 initial_cmds[n] = NULL;
846 redraw(FALSE); // dont force every col re-draw
847 //------This is the main Vi cmd handling loop -----------------------
848 while (editing > 0) {
849 #if ENABLE_FEATURE_VI_CRASHME
851 if ((end - text) > 1) {
852 crash_dummy(); // generate a random command
855 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
861 last_input_char = c = get_one_char(); // get a cmd from user
862 #if ENABLE_FEATURE_VI_YANKMARK
863 // save a copy of the current line- for the 'U" command
864 if (begin_line(dot) != cur_line) {
865 cur_line = begin_line(dot);
866 text_yank(begin_line(dot), end_line(dot), Ureg);
869 #if ENABLE_FEATURE_VI_DOT_CMD
870 // These are commands that change text[].
871 // Remember the input for the "." command
872 if (!adding2q && ioq_start == NULL
873 && cmd_mode == 0 // command mode
874 && c > '\0' // exclude NUL and non-ASCII chars
875 && c < 0x7f // (Unicode and such)
876 && strchr(modifying_cmds, c)
881 do_cmd(c); // execute the user command
883 // poll to see if there is input already waiting. if we are
884 // not able to display output fast enough to keep up, skip
885 // the display update until we catch up with input.
886 if (!readbuffer[0] && mysleep(0) == 0) {
887 // no input pending - so update output
891 #if ENABLE_FEATURE_VI_CRASHME
893 crash_test(); // test editor variables
896 //-------------------------------------------------------------------
898 go_bottom_and_clear_to_eol();
903 //----- The Colon commands -------------------------------------
904 #if ENABLE_FEATURE_VI_COLON
905 static char *get_one_address(char *p, int *addr) // get colon addr, if present
909 IF_FEATURE_VI_YANKMARK(char c;)
910 IF_FEATURE_VI_SEARCH(char *pat;)
912 *addr = -1; // assume no addr
913 if (*p == '.') { // the current line
916 *addr = count_lines(text, q);
918 #if ENABLE_FEATURE_VI_YANKMARK
919 else if (*p == '\'') { // is this a mark addr
923 if (c >= 'a' && c <= 'z') {
926 q = mark[(unsigned char) c];
927 if (q != NULL) { // is mark valid
928 *addr = count_lines(text, q);
933 #if ENABLE_FEATURE_VI_SEARCH
934 else if (*p == '/') { // a search pattern
935 q = strchrnul(++p, '/');
936 pat = xstrndup(p, q - p); // save copy of pattern
940 q = char_search(dot, pat, FORWARD, FULL);
942 *addr = count_lines(text, q);
947 else if (*p == '$') { // the last line in file
949 q = begin_line(end - 1);
950 *addr = count_lines(text, q);
951 } else if (isdigit(*p)) { // specific line number
952 sscanf(p, "%d%n", addr, &st);
955 // unrecognized address - assume -1
961 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
963 //----- get the address' i.e., 1,3 'a,'b -----
964 // get FIRST addr, if present
966 p++; // skip over leading spaces
967 if (*p == '%') { // alias for 1,$
970 *e = count_lines(text, end-1);
973 p = get_one_address(p, b);
976 if (*p == ',') { // is there a address separator
980 // get SECOND addr, if present
981 p = get_one_address(p, e);
985 p++; // skip over trailing spaces
989 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
990 static void setops(const char *args, const char *opname, int flg_no,
991 const char *short_opname, int opt)
993 const char *a = args + flg_no;
994 int l = strlen(opname) - 1; /* opname have + ' ' */
996 // maybe strncmp? we had tons of erroneous strncasecmp's...
997 if (strncasecmp(a, opname, l) == 0
998 || strncasecmp(a, short_opname, 2) == 0
1008 #endif /* FEATURE_VI_COLON */
1010 // buf must be no longer than MAX_INPUT_LEN!
1011 static void colon(char *buf)
1013 #if !ENABLE_FEATURE_VI_COLON
1014 /* Simple ":cmd" handler with minimal set of commands */
1023 if (strncmp(p, "quit", cnt) == 0
1024 || strncmp(p, "q!", cnt) == 0
1026 if (modified_count && p[1] != '!') {
1027 status_line_bold("No write since last change (:%s! overrides)", p);
1033 if (strncmp(p, "write", cnt) == 0
1034 || strncmp(p, "wq", cnt) == 0
1035 || strncmp(p, "wn", cnt) == 0
1036 || (p[0] == 'x' && !p[1])
1038 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
1049 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
1050 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
1057 if (strncmp(p, "file", cnt) == 0) {
1058 last_status_cksum = 0; // force status update
1061 if (sscanf(p, "%d", &cnt) > 0) {
1062 dot = find_line(cnt);
1069 char c, *orig_buf, *buf1, *q, *r;
1070 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
1074 // :3154 // if (-e line 3154) goto it else stay put
1075 // :4,33w! foo // write a portion of buffer to file "foo"
1076 // :w // write all of buffer to current file
1078 // :q! // quit- dont care about modified file
1079 // :'a,'z!sort -u // filter block through sort
1080 // :'f // goto mark "f"
1081 // :'fl // list literal the mark "f" line
1082 // :.r bar // read file "bar" into buffer before dot
1083 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1084 // :/xyz/ // goto the "xyz" line
1085 // :s/find/replace/ // substitute pattern "find" with "replace"
1086 // :!<cmd> // run <cmd> then return
1092 buf++; // move past the ':'
1096 q = text; // assume 1,$ for the range
1098 li = count_lines(text, end - 1);
1099 fn = current_filename;
1101 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1102 buf = get_address(buf, &b, &e);
1104 // remember orig command line
1107 // get the COMMAND into cmd[]
1109 while (*buf != '\0') {
1115 // get any ARGuments
1116 while (isblank(*buf))
1120 buf1 = last_char_is(cmd, '!');
1123 *buf1 = '\0'; // get rid of !
1126 // if there is only one addr, then the addr
1127 // is the line number of the single line the
1128 // user wants. So, reset the end
1129 // pointer to point at end of the "b" line
1130 q = find_line(b); // what line is #b
1135 // we were given two addrs. change the
1136 // end pointer to the addr given by user.
1137 r = find_line(e); // what line is #e
1141 // ------------ now look for the command ------------
1143 if (i == 0) { // :123CR goto line #123
1145 dot = find_line(b); // what line is #b
1149 #if ENABLE_FEATURE_ALLOW_EXEC
1150 else if (cmd[0] == '!') { // run a cmd
1152 // :!ls run the <cmd>
1153 go_bottom_and_clear_to_eol();
1155 retcode = system(orig_buf + 1); // run the cmd
1157 printf("\nshell returned %i\n\n", retcode);
1159 Hit_Return(); // let user see results
1162 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1163 if (b < 0) { // no addr given- use defaults
1164 b = e = count_lines(text, dot);
1166 status_line("%d", b);
1167 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1168 if (b < 0) { // no addr given- use defaults
1169 q = begin_line(dot); // assume .,. for the range
1172 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
1174 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1177 // don't edit, if the current file has been modified
1178 if (modified_count && !useforce) {
1179 status_line_bold("No write since last change (:%s! overrides)", cmd);
1183 // the user supplied a file name
1185 } else if (current_filename && current_filename[0]) {
1186 // no user supplied name- use the current filename
1187 // fn = current_filename; was set by default
1189 // no user file name, no current name- punt
1190 status_line_bold("No current filename");
1194 size = init_text_buffer(fn);
1196 #if ENABLE_FEATURE_VI_YANKMARK
1197 if (Ureg >= 0 && Ureg < 28) {
1198 free(reg[Ureg]); // free orig line reg- for 'U'
1201 if (YDreg >= 0 && YDreg < 28) {
1202 free(reg[YDreg]); // free default yank/delete register
1206 // how many lines in text[]?
1207 li = count_lines(text, end - 1);
1208 status_line("'%s'%s"
1209 IF_FEATURE_VI_READONLY("%s")
1212 (size < 0 ? " [New file]" : ""),
1213 IF_FEATURE_VI_READONLY(
1214 ((readonly_mode) ? " [Readonly]" : ""),
1216 li, (int)(end - text)
1218 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1219 if (b != -1 || e != -1) {
1220 status_line_bold("No address allowed on this command");
1224 // user wants a new filename
1225 free(current_filename);
1226 current_filename = xstrdup(args);
1228 // user wants file status info
1229 last_status_cksum = 0; // force status update
1231 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1232 // print out values of all features
1233 go_bottom_and_clear_to_eol();
1238 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1239 if (b < 0) { // no addr given- use defaults
1240 q = begin_line(dot); // assume .,. for the range
1243 go_bottom_and_clear_to_eol();
1245 for (; q <= r; q++) {
1249 c_is_no_print = (c & 0x80) && !Isprint(c);
1250 if (c_is_no_print) {
1256 } else if (c < ' ' || c == 127) {
1268 } else if (strncmp(cmd, "quit", i) == 0 // quit
1269 || strncmp(cmd, "next", i) == 0 // edit next file
1270 || strncmp(cmd, "prev", i) == 0 // edit previous file
1275 // force end of argv list
1281 // don't exit if the file been modified
1282 if (modified_count) {
1283 status_line_bold("No write since last change (:%s! overrides)", cmd);
1286 // are there other file to edit
1287 n = save_argc - optind - 1;
1288 if (*cmd == 'q' && n > 0) {
1289 status_line_bold("%d more file(s) to edit", n);
1292 if (*cmd == 'n' && n <= 0) {
1293 status_line_bold("No more files to edit");
1297 // are there previous files to edit
1299 status_line_bold("No previous files to edit");
1305 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1310 status_line_bold("No filename given");
1313 if (b < 0) { // no addr given- use defaults
1314 q = begin_line(dot); // assume "dot"
1316 // read after current line- unless user said ":0r foo"
1319 // read after last line
1323 { // dance around potentially-reallocated text[]
1324 uintptr_t ofs = q - text;
1325 size = file_insert(fn, q, 0);
1329 goto ret; // nothing was inserted
1330 // how many lines in text[]?
1331 li = count_lines(q, q + size - 1);
1333 IF_FEATURE_VI_READONLY("%s")
1336 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1340 // if the insert is before "dot" then we need to update
1344 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1345 if (modified_count && !useforce) {
1346 status_line_bold("No write since last change (:%s! overrides)", cmd);
1348 // reset the filenames to edit
1349 optind = -1; /* start from 0th file */
1352 #if ENABLE_FEATURE_VI_SET
1353 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1354 #if ENABLE_FEATURE_VI_SETOPTS
1357 i = 0; // offset into args
1358 // only blank is regarded as args delimiter. What about tab '\t'?
1359 if (!args[0] || strcasecmp(args, "all") == 0) {
1360 // print out values of all options
1361 #if ENABLE_FEATURE_VI_SETOPTS
1368 autoindent ? "" : "no",
1369 err_method ? "" : "no",
1370 ignorecase ? "" : "no",
1371 showmatch ? "" : "no",
1377 #if ENABLE_FEATURE_VI_SETOPTS
1380 if (strncmp(argp, "no", 2) == 0)
1381 i = 2; // ":set noautoindent"
1382 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1383 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1384 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1385 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1386 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1388 sscanf(argp + i+8, "%u", &t);
1389 if (t > 0 && t <= MAX_TABSTOP)
1392 argp = skip_non_whitespace(argp);
1393 argp = skip_whitespace(argp);
1395 #endif /* FEATURE_VI_SETOPTS */
1396 #endif /* FEATURE_VI_SET */
1397 #if ENABLE_FEATURE_VI_SEARCH
1398 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1399 char *F, *R, *flags;
1400 size_t len_F, len_R;
1401 int gflag; // global replace flag
1402 #if ENABLE_FEATURE_VI_UNDO
1403 int dont_chain_first_item = ALLOW_UNDO;
1406 // F points to the "find" pattern
1407 // R points to the "replace" pattern
1408 // replace the cmd line delimiters "/" with NULs
1409 c = orig_buf[1]; // what is the delimiter
1410 F = orig_buf + 2; // start of "find"
1411 R = strchr(F, c); // middle delimiter
1415 *R++ = '\0'; // terminate "find"
1416 flags = strchr(R, c);
1420 *flags++ = '\0'; // terminate "replace"
1424 if (b < 0) { // maybe :s/foo/bar/
1425 q = begin_line(dot); // start with cur line
1426 b = count_lines(text, q); // cur line number
1429 e = b; // maybe :.s/foo/bar/
1431 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1432 char *ls = q; // orig line start
1435 found = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1438 // we found the "find" pattern - delete it
1439 // For undo support, the first item should not be chained
1440 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
1441 #if ENABLE_FEATURE_VI_UNDO
1442 dont_chain_first_item = ALLOW_UNDO_CHAIN;
1444 // insert the "replace" patern
1445 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
1448 /*q += bias; - recalculated anyway */
1449 // check for "global" :s/foo/bar/g
1451 if ((found + len_R) < end_line(ls)) {
1453 goto vc4; // don't let q move past cur line
1459 #endif /* FEATURE_VI_SEARCH */
1460 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1461 status_line(BB_VER " " BB_BT);
1462 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1463 || strncmp(cmd, "wq", i) == 0
1464 || strncmp(cmd, "wn", i) == 0
1465 || (cmd[0] == 'x' && !cmd[1])
1468 //int forced = FALSE;
1470 // is there a file name to write to?
1474 #if ENABLE_FEATURE_VI_READONLY
1475 if (readonly_mode && !useforce) {
1476 status_line_bold("'%s' is read only", fn);
1480 // how many lines in text[]?
1481 li = count_lines(q, r);
1484 // if "fn" is not write-able, chmod u+w
1485 // sprintf(syscmd, "chmod u+w %s", fn);
1489 l = file_write(fn, q, r);
1490 //if (useforce && forced) {
1492 // sprintf(syscmd, "chmod u-w %s", fn);
1498 status_line_bold_errno(fn);
1500 status_line("'%s' %dL, %dC", fn, li, l);
1501 if (q == text && r == end - 1 && l == size) {
1503 last_modified_count = -1;
1505 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
1506 || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
1513 #if ENABLE_FEATURE_VI_YANKMARK
1514 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1515 if (b < 0) { // no addr given- use defaults
1516 q = begin_line(dot); // assume .,. for the range
1519 text_yank(q, r, YDreg);
1520 li = count_lines(q, r);
1521 status_line("Yank %d lines (%d chars) into [%c]",
1522 li, strlen(reg[YDreg]), what_reg());
1526 not_implemented(cmd);
1529 dot = bound_dot(dot); // make sure "dot" is valid
1531 #if ENABLE_FEATURE_VI_SEARCH
1533 status_line(":s expression missing delimiters");
1535 #endif /* FEATURE_VI_COLON */
1538 static void Hit_Return(void)
1543 write1("[Hit return to continue]");
1545 while ((c = get_one_char()) != '\n' && c != '\r')
1547 redraw(TRUE); // force redraw all
1550 static int next_tabstop(int col)
1552 return col + ((tabstop - 1) - (col % tabstop));
1555 //----- Synchronize the cursor to Dot --------------------------
1556 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1558 char *beg_cur; // begin and end of "d" line
1562 beg_cur = begin_line(d); // first char of cur line
1564 if (beg_cur < screenbegin) {
1565 // "d" is before top line on screen
1566 // how many lines do we have to move
1567 cnt = count_lines(beg_cur, screenbegin);
1569 screenbegin = beg_cur;
1570 if (cnt > (rows - 1) / 2) {
1571 // we moved too many lines. put "dot" in middle of screen
1572 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1573 screenbegin = prev_line(screenbegin);
1577 char *end_scr; // begin and end of screen
1578 end_scr = end_screen(); // last char of screen
1579 if (beg_cur > end_scr) {
1580 // "d" is after bottom line on screen
1581 // how many lines do we have to move
1582 cnt = count_lines(end_scr, beg_cur);
1583 if (cnt > (rows - 1) / 2)
1584 goto sc1; // too many lines
1585 for (ro = 0; ro < cnt - 1; ro++) {
1586 // move screen begin the same amount
1587 screenbegin = next_line(screenbegin);
1588 // now, move the end of screen
1589 end_scr = next_line(end_scr);
1590 end_scr = end_line(end_scr);
1594 // "d" is on screen- find out which row
1596 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1602 // find out what col "d" is on
1604 while (tp < d) { // drive "co" to correct column
1605 if (*tp == '\n') //vda || *tp == '\0')
1608 // handle tabs like real vi
1609 if (d == tp && cmd_mode) {
1612 co = next_tabstop(co);
1613 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1614 co++; // display as ^X, use 2 columns
1620 // "co" is the column where "dot" is.
1621 // The screen has "columns" columns.
1622 // The currently displayed columns are 0+offset -- columns+ofset
1623 // |-------------------------------------------------------------|
1625 // offset | |------- columns ----------------|
1627 // If "co" is already in this range then we do not have to adjust offset
1628 // but, we do have to subtract the "offset" bias from "co".
1629 // If "co" is outside this range then we have to change "offset".
1630 // If the first char of a line is a tab the cursor will try to stay
1631 // in column 7, but we have to set offset to 0.
1633 if (co < 0 + offset) {
1636 if (co >= columns + offset) {
1637 offset = co - columns + 1;
1639 // if the first char of the line is a tab, and "dot" is sitting on it
1640 // force offset to 0.
1641 if (d == beg_cur && *d == '\t') {
1650 //----- Text Movement Routines ---------------------------------
1651 static char *begin_line(char *p) // return pointer to first char cur line
1654 p = memrchr(text, '\n', p - text);
1662 static char *end_line(char *p) // return pointer to NL of cur line
1665 p = memchr(p, '\n', end - p - 1);
1672 static char *dollar_line(char *p) // return pointer to just before NL line
1675 // Try to stay off of the Newline
1676 if (*p == '\n' && (p - begin_line(p)) > 0)
1681 static char *prev_line(char *p) // return pointer first char prev line
1683 p = begin_line(p); // goto beginning of cur line
1684 if (p > text && p[-1] == '\n')
1685 p--; // step to prev line
1686 p = begin_line(p); // goto beginning of prev line
1690 static char *next_line(char *p) // return pointer first char next line
1693 if (p < end - 1 && *p == '\n')
1694 p++; // step to next line
1698 //----- Text Information Routines ------------------------------
1699 static char *end_screen(void)
1704 // find new bottom line
1706 for (cnt = 0; cnt < rows - 2; cnt++)
1712 // count line from start to stop
1713 static int count_lines(char *start, char *stop)
1718 if (stop < start) { // start and stop are backwards- reverse them
1724 stop = end_line(stop);
1725 while (start <= stop && start <= end - 1) {
1726 start = end_line(start);
1734 static char *find_line(int li) // find beginning of line #li
1738 for (q = text; li > 1; li--) {
1744 //----- Dot Movement Routines ----------------------------------
1745 static void dot_left(void)
1747 undo_queue_commit();
1748 if (dot > text && dot[-1] != '\n')
1752 static void dot_right(void)
1754 undo_queue_commit();
1755 if (dot < end - 1 && *dot != '\n')
1759 static void dot_begin(void)
1761 undo_queue_commit();
1762 dot = begin_line(dot); // return pointer to first char cur line
1765 static void dot_end(void)
1767 undo_queue_commit();
1768 dot = end_line(dot); // return pointer to last char cur line
1771 static char *move_to_col(char *p, int l)
1777 while (co < l && p < end) {
1778 if (*p == '\n') //vda || *p == '\0')
1781 co = next_tabstop(co);
1782 } else if (*p < ' ' || *p == 127) {
1783 co++; // display as ^X, use 2 columns
1791 static void dot_next(void)
1793 undo_queue_commit();
1794 dot = next_line(dot);
1797 static void dot_prev(void)
1799 undo_queue_commit();
1800 dot = prev_line(dot);
1803 static void dot_scroll(int cnt, int dir)
1807 undo_queue_commit();
1808 for (; cnt > 0; cnt--) {
1811 // ctrl-Y scroll up one line
1812 screenbegin = prev_line(screenbegin);
1815 // ctrl-E scroll down one line
1816 screenbegin = next_line(screenbegin);
1819 // make sure "dot" stays on the screen so we dont scroll off
1820 if (dot < screenbegin)
1822 q = end_screen(); // find new bottom line
1824 dot = begin_line(q); // is dot is below bottom line?
1828 static void dot_skip_over_ws(void)
1831 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1835 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1837 if (p >= end && end > text) {
1848 //----- Helper Utility Routines --------------------------------
1850 //----------------------------------------------------------------
1851 //----- Char Routines --------------------------------------------
1852 /* Chars that are part of a word-
1853 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1854 * Chars that are Not part of a word (stoppers)
1855 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1856 * Chars that are WhiteSpace
1857 * TAB NEWLINE VT FF RETURN SPACE
1858 * DO NOT COUNT NEWLINE AS WHITESPACE
1861 static char *new_screen(int ro, int co)
1866 screensize = ro * co + 8;
1867 screen = xmalloc(screensize);
1868 // initialize the new screen. assume this will be a empty file.
1870 // non-existent text[] lines start with a tilde (~).
1871 for (li = 1; li < ro - 1; li++) {
1872 screen[(li * co) + 0] = '~';
1877 #if ENABLE_FEATURE_VI_SEARCH
1879 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1881 // search for pattern starting at p
1882 static char *char_search(char *p, const char *pat, int dir, int range)
1884 struct re_pattern_buffer preg;
1890 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1892 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1894 memset(&preg, 0, sizeof(preg));
1895 err = re_compile_pattern(pat, strlen(pat), &preg);
1897 status_line_bold("bad search pattern '%s': %s", pat, err);
1901 // assume a LIMITED forward search
1905 // RANGE could be negative if we are searching backwards
1915 // search for the compiled pattern, preg, in p[]
1916 // range < 0: search backward
1917 // range > 0: search forward
1919 // re_search() < 0: not found or error
1920 // re_search() >= 0: index of found pattern
1921 // struct pattern char int int int struct reg
1922 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1923 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1936 # if ENABLE_FEATURE_VI_SETOPTS
1937 static int mycmp(const char *s1, const char *s2, int len)
1940 return strncasecmp(s1, s2, len);
1942 return strncmp(s1, s2, len);
1945 # define mycmp strncmp
1948 static char *char_search(char *p, const char *pat, int dir, int range)
1954 if (dir == FORWARD) {
1955 stop = end - 1; // assume range is p..end-1
1956 if (range == LIMITED)
1957 stop = next_line(p); // range is to next line
1958 for (start = p; start < stop; start++) {
1959 if (mycmp(start, pat, len) == 0) {
1963 } else if (dir == BACK) {
1964 stop = text; // assume range is text..p
1965 if (range == LIMITED)
1966 stop = prev_line(p); // range is to prev line
1967 for (start = p - len; start >= stop; start--) {
1968 if (mycmp(start, pat, len) == 0) {
1973 // pattern not found
1979 #endif /* FEATURE_VI_SEARCH */
1981 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1983 if (c == 22) { // Is this an ctrl-V?
1984 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1985 refresh(FALSE); // show the ^
1988 #if ENABLE_FEATURE_VI_UNDO
1991 undo_push(p, 1, UNDO_INS);
1993 case ALLOW_UNDO_CHAIN:
1994 undo_push(p, 1, UNDO_INS_CHAIN);
1996 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1997 case ALLOW_UNDO_QUEUED:
1998 undo_push(p, 1, UNDO_INS_QUEUED);
2004 #endif /* ENABLE_FEATURE_VI_UNDO */
2006 } else if (c == 27) { // Is this an ESC?
2008 undo_queue_commit();
2010 end_cmd_q(); // stop adding to q
2011 last_status_cksum = 0; // force status update
2012 if ((p[-1] != '\n') && (dot > text)) {
2015 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
2018 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2021 // insert a char into text[]
2023 c = '\n'; // translate \r to \n
2024 #if ENABLE_FEATURE_VI_UNDO
2025 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2027 undo_queue_commit();
2031 undo_push(p, 1, UNDO_INS);
2033 case ALLOW_UNDO_CHAIN:
2034 undo_push(p, 1, UNDO_INS_CHAIN);
2036 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2037 case ALLOW_UNDO_QUEUED:
2038 undo_push(p, 1, UNDO_INS_QUEUED);
2044 #endif /* ENABLE_FEATURE_VI_UNDO */
2045 p += 1 + stupid_insert(p, c); // insert the char
2046 #if ENABLE_FEATURE_VI_SETOPTS
2047 if (showmatch && strchr(")]}", c) != NULL) {
2048 showmatching(p - 1);
2050 if (autoindent && c == '\n') { // auto indent the new line
2053 q = prev_line(p); // use prev line as template
2054 len = strspn(q, " \t"); // space or tab
2057 bias = text_hole_make(p, len);
2060 #if ENABLE_FEATURE_VI_UNDO
2061 undo_push(p, len, UNDO_INS);
2072 // might reallocate text[]! use p += stupid_insert(p, ...),
2073 // and be careful to not use pointers into potentially freed text[]!
2074 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2077 bias = text_hole_make(p, 1);
2083 static int find_range(char **start, char **stop, char c)
2085 char *save_dot, *p, *q, *t;
2086 int cnt, multiline = 0;
2091 if (strchr("cdy><", c)) {
2092 // these cmds operate on whole lines
2093 p = q = begin_line(p);
2094 for (cnt = 1; cnt < cmdcnt; cnt++) {
2098 } else if (strchr("^%$0bBeEfth\b\177", c)) {
2099 // These cmds operate on char positions
2100 do_cmd(c); // execute movement cmd
2102 } else if (strchr("wW", c)) {
2103 do_cmd(c); // execute movement cmd
2104 // if we are at the next word's first char
2105 // step back one char
2106 // but check the possibilities when it is true
2107 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
2108 || (ispunct(dot[-1]) && !ispunct(dot[0]))
2109 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
2110 dot--; // move back off of next word
2111 if (dot > text && *dot == '\n')
2112 dot--; // stay off NL
2114 } else if (strchr("H-k{", c)) {
2115 // these operate on multi-lines backwards
2116 q = end_line(dot); // find NL
2117 do_cmd(c); // execute movement cmd
2120 } else if (strchr("L+j}\r\n", c)) {
2121 // these operate on multi-lines forwards
2122 p = begin_line(dot);
2123 do_cmd(c); // execute movement cmd
2124 dot_end(); // find NL
2127 // nothing -- this causes any other values of c to
2128 // represent the one-character range under the
2129 // cursor. this is correct for ' ' and 'l', but
2130 // perhaps no others.
2139 // backward char movements don't include start position
2140 if (q > p && strchr("^0bBh\b\177", c)) q--;
2143 for (t = p; t <= q; t++) {
2156 static int st_test(char *p, int type, int dir, char *tested)
2166 if (type == S_BEFORE_WS) {
2168 test = (!isspace(c) || c == '\n');
2170 if (type == S_TO_WS) {
2172 test = (!isspace(c) || c == '\n');
2174 if (type == S_OVER_WS) {
2178 if (type == S_END_PUNCT) {
2182 if (type == S_END_ALNUM) {
2184 test = (isalnum(c) || c == '_');
2190 static char *skip_thing(char *p, int linecnt, int dir, int type)
2194 while (st_test(p, type, dir, &c)) {
2195 // make sure we limit search to correct number of lines
2196 if (c == '\n' && --linecnt < 1)
2198 if (dir >= 0 && p >= end - 1)
2200 if (dir < 0 && p <= text)
2202 p += dir; // move to next char
2207 // find matching char of pair () [] {}
2208 // will crash if c is not one of these
2209 static char *find_pair(char *p, const char c)
2211 const char *braces = "()[]{}";
2215 dir = strchr(braces, c) - braces;
2217 match = braces[dir];
2218 dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
2220 // look for match, count levels of pairs (( ))
2224 if (p < text || p >= end)
2227 level++; // increase pair levels
2229 level--; // reduce pair level
2231 return p; // found matching pair
2236 #if ENABLE_FEATURE_VI_SETOPTS
2237 // show the matching char of a pair, () [] {}
2238 static void showmatching(char *p)
2242 // we found half of a pair
2243 q = find_pair(p, *p); // get loc of matching char
2245 indicate_error(); // no matching char
2247 // "q" now points to matching pair
2248 save_dot = dot; // remember where we are
2249 dot = q; // go to new loc
2250 refresh(FALSE); // let the user see it
2251 mysleep(40); // give user some time
2252 dot = save_dot; // go back to old loc
2256 #endif /* FEATURE_VI_SETOPTS */
2258 #if ENABLE_FEATURE_VI_UNDO
2259 static void flush_undo_data(void)
2261 struct undo_object *undo_entry;
2263 while (undo_stack_tail) {
2264 undo_entry = undo_stack_tail;
2265 undo_stack_tail = undo_entry->prev;
2270 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2271 static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack
2273 struct undo_object *undo_entry;
2276 // UNDO_INS: insertion, undo will remove from buffer
2277 // UNDO_DEL: deleted text, undo will restore to buffer
2278 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2279 // The CHAIN operations are for handling multiple operations that the user
2280 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2281 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2282 // for the INS/DEL operation. The raw values should be equal to the values of
2283 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2285 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2286 // This undo queuing functionality groups multiple character typing or backspaces
2287 // into a single large undo object. This greatly reduces calls to malloc() for
2288 // single-character operations while typing and has the side benefit of letting
2289 // an undo operation remove chunks of text rather than a single character.
2291 case UNDO_EMPTY: // Just in case this ever happens...
2293 case UNDO_DEL_QUEUED:
2295 return; // Only queue single characters
2296 switch (undo_queue_state) {
2298 undo_queue_state = UNDO_DEL;
2300 undo_queue_spos = src;
2302 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2303 // If queue is full, dump it into an object
2304 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2305 undo_queue_commit();
2308 // Switch from storing inserted text to deleted text
2309 undo_queue_commit();
2310 undo_push(src, length, UNDO_DEL_QUEUED);
2314 case UNDO_INS_QUEUED:
2317 switch (undo_queue_state) {
2319 undo_queue_state = UNDO_INS;
2320 undo_queue_spos = src;
2322 undo_q++; // Don't need to save any data for insertions
2323 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2324 undo_queue_commit();
2327 // Switch from storing deleted text to inserted text
2328 undo_queue_commit();
2329 undo_push(src, length, UNDO_INS_QUEUED);
2335 // If undo queuing is disabled, ignore the queuing flag entirely
2336 u_type = u_type & ~UNDO_QUEUED_FLAG;
2339 // Allocate a new undo object
2340 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2341 // For UNDO_DEL objects, save deleted text
2342 if ((src + length) == end)
2344 // If this deletion empties text[], strip the newline. When the buffer becomes
2345 // zero-length, a newline is added back, which requires this to compensate.
2346 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
2347 memcpy(undo_entry->undo_text, src, length);
2349 undo_entry = xzalloc(sizeof(*undo_entry));
2351 undo_entry->length = length;
2352 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2353 if ((u_type & UNDO_USE_SPOS) != 0) {
2354 undo_entry->start = undo_queue_spos - text; // use start position from queue
2356 undo_entry->start = src - text; // use offset from start of text buffer
2358 u_type = (u_type & ~UNDO_USE_SPOS);
2360 undo_entry->start = src - text;
2362 undo_entry->u_type = u_type;
2364 // Push it on undo stack
2365 undo_entry->prev = undo_stack_tail;
2366 undo_stack_tail = undo_entry;
2370 static void undo_pop(void) // Undo the last operation
2373 char *u_start, *u_end;
2374 struct undo_object *undo_entry;
2376 // Commit pending undo queue before popping (should be unnecessary)
2377 undo_queue_commit();
2379 undo_entry = undo_stack_tail;
2380 // Check for an empty undo stack
2382 status_line("Already at oldest change");
2386 switch (undo_entry->u_type) {
2388 case UNDO_DEL_CHAIN:
2389 // make hole and put in text that was deleted; deallocate text
2390 u_start = text + undo_entry->start;
2391 text_hole_make(u_start, undo_entry->length);
2392 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2393 status_line("Undo [%d] %s %d chars at position %d",
2394 modified_count, "restored",
2395 undo_entry->length, undo_entry->start
2399 case UNDO_INS_CHAIN:
2400 // delete what was inserted
2401 u_start = undo_entry->start + text;
2402 u_end = u_start - 1 + undo_entry->length;
2403 text_hole_delete(u_start, u_end, NO_UNDO);
2404 status_line("Undo [%d] %s %d chars at position %d",
2405 modified_count, "deleted",
2406 undo_entry->length, undo_entry->start
2411 switch (undo_entry->u_type) {
2412 // If this is the end of a chain, lower modification count and refresh display
2415 dot = (text + undo_entry->start);
2418 case UNDO_DEL_CHAIN:
2419 case UNDO_INS_CHAIN:
2423 // Deallocate the undo object we just processed
2424 undo_stack_tail = undo_entry->prev;
2427 // For chained operations, continue popping all the way down the chain.
2429 undo_pop(); // Follow the undo chain if one exists
2433 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2434 static void undo_queue_commit(void) // Flush any queued objects to the undo stack
2436 // Pushes the queue object onto the undo stack
2438 // Deleted character undo events grow from the end
2439 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2441 (undo_queue_state | UNDO_USE_SPOS)
2443 undo_queue_state = UNDO_EMPTY;
2449 #endif /* ENABLE_FEATURE_VI_UNDO */
2451 // open a hole in text[]
2452 // might reallocate text[]! use p += text_hole_make(p, ...),
2453 // and be careful to not use pointers into potentially freed text[]!
2454 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2460 end += size; // adjust the new END
2461 if (end >= (text + text_size)) {
2463 text_size += end - (text + text_size) + 10240;
2464 new_text = xrealloc(text, text_size);
2465 bias = (new_text - text);
2466 screenbegin += bias;
2470 #if ENABLE_FEATURE_VI_YANKMARK
2473 for (i = 0; i < ARRAY_SIZE(mark); i++)
2480 memmove(p + size, p, end - size - p);
2481 memset(p, ' ', size); // clear new hole
2485 // close a hole in text[]
2486 // "undo" value indicates if this operation should be undo-able
2487 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2492 // move forwards, from beginning
2496 if (q < p) { // they are backward- swap them
2500 hole_size = q - p + 1;
2502 #if ENABLE_FEATURE_VI_UNDO
2507 undo_push(p, hole_size, UNDO_DEL);
2509 case ALLOW_UNDO_CHAIN:
2510 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2512 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2513 case ALLOW_UNDO_QUEUED:
2514 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2520 if (src < text || src > end)
2522 if (dest < text || dest >= end)
2526 goto thd_atend; // just delete the end of the buffer
2527 memmove(dest, src, cnt);
2529 end = end - hole_size; // adjust the new END
2531 dest = end - 1; // make sure dest in below end-1
2533 dest = end = text; // keep pointers valid
2538 // copy text into register, then delete text.
2539 // if dist <= 0, do not include, or go past, a NewLine
2541 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2545 // make sure start <= stop
2547 // they are backwards, reverse them
2553 // we cannot cross NL boundaries
2557 // dont go past a NewLine
2558 for (; p + 1 <= stop; p++) {
2560 stop = p; // "stop" just before NewLine
2566 #if ENABLE_FEATURE_VI_YANKMARK
2567 text_yank(start, stop, YDreg);
2569 if (yf == YANKDEL) {
2570 p = text_hole_delete(start, stop, undo);
2575 static void show_help(void)
2577 puts("These features are available:"
2578 #if ENABLE_FEATURE_VI_SEARCH
2579 "\n\tPattern searches with / and ?"
2581 #if ENABLE_FEATURE_VI_DOT_CMD
2582 "\n\tLast command repeat with ."
2584 #if ENABLE_FEATURE_VI_YANKMARK
2585 "\n\tLine marking with 'x"
2586 "\n\tNamed buffers with \"x"
2588 #if ENABLE_FEATURE_VI_READONLY
2589 //not implemented: "\n\tReadonly if vi is called as \"view\""
2590 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2592 #if ENABLE_FEATURE_VI_SET
2593 "\n\tSome colon mode commands with :"
2595 #if ENABLE_FEATURE_VI_SETOPTS
2596 "\n\tSettable options with \":set\""
2598 #if ENABLE_FEATURE_VI_USE_SIGNALS
2599 "\n\tSignal catching- ^C"
2600 "\n\tJob suspend and resume with ^Z"
2602 #if ENABLE_FEATURE_VI_WIN_RESIZE
2603 "\n\tAdapt to window re-sizes"
2608 #if ENABLE_FEATURE_VI_DOT_CMD
2609 static void start_new_cmd_q(char c)
2611 // get buffer for new cmd
2612 // if there is a current cmd count put it in the buffer first
2614 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2615 } else { // just save char c onto queue
2616 last_modifying_cmd[0] = c;
2622 static void end_cmd_q(void)
2624 #if ENABLE_FEATURE_VI_YANKMARK
2625 YDreg = 26; // go back to default Yank/Delete reg
2629 #endif /* FEATURE_VI_DOT_CMD */
2631 #if ENABLE_FEATURE_VI_YANKMARK \
2632 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2633 || ENABLE_FEATURE_VI_CRASHME
2634 // might reallocate text[]! use p += string_insert(p, ...),
2635 // and be careful to not use pointers into potentially freed text[]!
2636 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2642 #if ENABLE_FEATURE_VI_UNDO
2645 undo_push(p, i, UNDO_INS);
2647 case ALLOW_UNDO_CHAIN:
2648 undo_push(p, i, UNDO_INS_CHAIN);
2652 bias = text_hole_make(p, i);
2655 #if ENABLE_FEATURE_VI_YANKMARK
2658 for (cnt = 0; *s != '\0'; s++) {
2662 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2669 #if ENABLE_FEATURE_VI_YANKMARK
2670 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2673 if (cnt < 0) { // they are backwards- reverse them
2677 free(reg[dest]); // if already a yank register, free it
2678 reg[dest] = xstrndup(p, cnt + 1);
2682 static char what_reg(void)
2686 c = 'D'; // default to D-reg
2687 if (0 <= YDreg && YDreg <= 25)
2688 c = 'a' + (char) YDreg;
2696 static void check_context(char cmd)
2698 // A context is defined to be "modifying text"
2699 // Any modifying command establishes a new context.
2701 if (dot < context_start || dot > context_end) {
2702 if (strchr(modifying_cmds, cmd) != NULL) {
2703 // we are trying to modify text[]- make this the current context
2704 mark[27] = mark[26]; // move cur to prev
2705 mark[26] = dot; // move local to cur
2706 context_start = prev_line(prev_line(dot));
2707 context_end = next_line(next_line(dot));
2708 //loiter= start_loiter= now;
2713 static char *swap_context(char *p) // goto new context for '' command make this the current context
2717 // the current context is in mark[26]
2718 // the previous context is in mark[27]
2719 // only swap context if other context is valid
2720 if (text <= mark[27] && mark[27] <= end - 1) {
2724 context_start = prev_line(prev_line(prev_line(p)));
2725 context_end = next_line(next_line(next_line(p)));
2729 #endif /* FEATURE_VI_YANKMARK */
2731 //----- Set terminal attributes --------------------------------
2732 static void rawmode(void)
2734 tcgetattr(0, &term_orig);
2735 term_vi = term_orig;
2736 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG on - allow intr's
2737 term_vi.c_iflag &= (~IXON & ~ICRNL);
2738 term_vi.c_oflag &= (~ONLCR);
2739 term_vi.c_cc[VMIN] = 1;
2740 term_vi.c_cc[VTIME] = 0;
2741 erase_char = term_vi.c_cc[VERASE];
2742 tcsetattr_stdin_TCSANOW(&term_vi);
2745 static void cookmode(void)
2748 tcsetattr_stdin_TCSANOW(&term_orig);
2751 #if ENABLE_FEATURE_VI_USE_SIGNALS
2752 //----- Come here when we get a window resize signal ---------
2753 static void winch_sig(int sig UNUSED_PARAM)
2755 int save_errno = errno;
2756 // FIXME: do it in main loop!!!
2757 signal(SIGWINCH, winch_sig);
2758 query_screen_dimensions();
2759 new_screen(rows, columns); // get memory for virtual screen
2760 redraw(TRUE); // re-draw the screen
2764 //----- Come here when we get a continue signal -------------------
2765 static void cont_sig(int sig UNUSED_PARAM)
2767 int save_errno = errno;
2768 rawmode(); // terminal to "raw"
2769 last_status_cksum = 0; // force status update
2770 redraw(TRUE); // re-draw the screen
2772 signal(SIGTSTP, suspend_sig);
2773 signal(SIGCONT, SIG_DFL);
2774 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2778 //----- Come here when we get a Suspend signal -------------------
2779 static void suspend_sig(int sig UNUSED_PARAM)
2781 int save_errno = errno;
2782 go_bottom_and_clear_to_eol();
2783 cookmode(); // terminal to "cooked"
2785 signal(SIGCONT, cont_sig);
2786 signal(SIGTSTP, SIG_DFL);
2787 kill(my_pid, SIGTSTP);
2791 //----- Come here when we get a signal ---------------------------
2792 static void catch_sig(int sig)
2794 signal(SIGINT, catch_sig);
2795 siglongjmp(restart, sig);
2797 #endif /* FEATURE_VI_USE_SIGNALS */
2799 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2801 struct pollfd pfd[1];
2806 pfd[0].fd = STDIN_FILENO;
2807 pfd[0].events = POLLIN;
2808 return safe_poll(pfd, 1, hund*10) > 0;
2811 //----- IO Routines --------------------------------------------
2812 static int readit(void) // read (maybe cursor) key from stdin
2817 c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2818 if (c == -1) { // EOF/error
2819 go_bottom_and_clear_to_eol();
2820 cookmode(); // terminal to "cooked"
2821 bb_error_msg_and_die("can't read user input");
2826 //----- IO Routines --------------------------------------------
2827 static int get_one_char(void)
2831 #if ENABLE_FEATURE_VI_DOT_CMD
2833 // we are not adding to the q.
2834 // but, we may be reading from a q
2836 // there is no current q, read from STDIN
2837 c = readit(); // get the users input
2839 // there is a queue to get chars from first
2840 // careful with correct sign expansion!
2841 c = (unsigned char)*ioq++;
2843 // the end of the q, read from STDIN
2845 ioq_start = ioq = 0;
2846 c = readit(); // get the users input
2850 // adding STDIN chars to q
2851 c = readit(); // get the users input
2852 if (lmc_len >= MAX_INPUT_LEN - 1) {
2853 status_line_bold("last_modifying_cmd overrun");
2855 // add new char to q
2856 last_modifying_cmd[lmc_len++] = c;
2860 c = readit(); // get the users input
2861 #endif /* FEATURE_VI_DOT_CMD */
2865 // Get input line (uses "status line" area)
2866 static char *get_input_line(const char *prompt)
2868 // char [MAX_INPUT_LEN]
2869 #define buf get_input_line__buf
2874 strcpy(buf, prompt);
2875 last_status_cksum = 0; // force status update
2876 go_bottom_and_clear_to_eol();
2877 write1(prompt); // write out the :, /, or ? prompt
2880 while (i < MAX_INPUT_LEN) {
2882 if (c == '\n' || c == '\r' || c == 27)
2883 break; // this is end of input
2884 if (c == erase_char || c == 8 || c == 127) {
2885 // user wants to erase prev char
2887 write1("\b \b"); // erase char on screen
2888 if (i <= 0) // user backs up before b-o-l, exit
2890 } else if (c > 0 && c < 256) { // exclude Unicode
2891 // (TODO: need to handle Unicode)
2902 // might reallocate text[]!
2903 static int file_insert(const char *fn, char *p, int initial)
2907 struct stat statbuf;
2914 fd = open(fn, O_RDONLY);
2917 status_line_bold_errno(fn);
2922 if (fstat(fd, &statbuf) < 0) {
2923 status_line_bold_errno(fn);
2926 if (!S_ISREG(statbuf.st_mode)) {
2927 status_line_bold("'%s' is not a regular file", fn);
2930 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2931 p += text_hole_make(p, size);
2932 cnt = full_read(fd, p, size);
2934 status_line_bold_errno(fn);
2935 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2936 } else if (cnt < size) {
2937 // There was a partial read, shrink unused space
2938 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2939 status_line_bold("can't read '%s'", fn);
2944 #if ENABLE_FEATURE_VI_READONLY
2946 && ((access(fn, W_OK) < 0) ||
2947 /* root will always have access()
2948 * so we check fileperms too */
2949 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2952 SET_READONLY_FILE(readonly_mode);
2958 static int file_write(char *fn, char *first, char *last)
2960 int fd, cnt, charcnt;
2963 status_line_bold("No current filename");
2966 /* By popular request we do not open file with O_TRUNC,
2967 * but instead ftruncate() it _after_ successful write.
2968 * Might reduce amount of data lost on power fail etc.
2970 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2973 cnt = last - first + 1;
2974 charcnt = full_write(fd, first, cnt);
2975 ftruncate(fd, charcnt);
2976 if (charcnt == cnt) {
2978 //modified_count = FALSE;
2986 //----- Terminal Drawing ---------------------------------------
2987 // The terminal is made up of 'rows' line of 'columns' columns.
2988 // classically this would be 24 x 80.
2989 // screen coordinates
2995 // 23,0 ... 23,79 <- status line
2997 //----- Move the cursor to row x col (count from 0, not 1) -------
2998 static void place_cursor(int row, int col)
3000 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
3002 if (row < 0) row = 0;
3003 if (row >= rows) row = rows - 1;
3004 if (col < 0) col = 0;
3005 if (col >= columns) col = columns - 1;
3007 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
3011 //----- Erase from cursor to end of line -----------------------
3012 static void clear_to_eol(void)
3014 write1(ESC_CLEAR2EOL);
3017 static void go_bottom_and_clear_to_eol(void)
3019 place_cursor(rows - 1, 0);
3023 //----- Erase from cursor to end of screen -----------------------
3024 static void clear_to_eos(void)
3026 write1(ESC_CLEAR2EOS);
3029 //----- Start standout mode ------------------------------------
3030 static void standout_start(void)
3032 write1(ESC_BOLD_TEXT);
3035 //----- End standout mode --------------------------------------
3036 static void standout_end(void)
3038 write1(ESC_NORM_TEXT);
3041 //----- Flash the screen --------------------------------------
3042 static void flash(int h)
3051 static void indicate_error(void)
3053 #if ENABLE_FEATURE_VI_CRASHME
3055 return; // generate a random command
3064 //----- Screen[] Routines --------------------------------------
3065 //----- Erase the Screen[] memory ------------------------------
3066 static void screen_erase(void)
3068 memset(screen, ' ', screensize); // clear new screen
3071 static int bufsum(char *buf, int count)
3074 char *e = buf + count;
3077 sum += (unsigned char) *buf++;
3081 //----- Draw the status line at bottom of the screen -------------
3082 static void show_status_line(void)
3084 int cnt = 0, cksum = 0;
3086 // either we already have an error or status message, or we
3088 if (!have_status_msg) {
3089 cnt = format_edit_status();
3090 cksum = bufsum(status_buffer, cnt);
3092 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3093 last_status_cksum = cksum; // remember if we have seen this line
3094 go_bottom_and_clear_to_eol();
3095 write1(status_buffer);
3096 if (have_status_msg) {
3097 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3099 have_status_msg = 0;
3102 have_status_msg = 0;
3104 place_cursor(crow, ccol); // put cursor back in correct place
3109 //----- format the status buffer, the bottom line of screen ------
3110 // format status buffer, with STANDOUT mode
3111 static void status_line_bold(const char *format, ...)
3115 va_start(args, format);
3116 strcpy(status_buffer, ESC_BOLD_TEXT);
3117 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3118 strcat(status_buffer, ESC_NORM_TEXT);
3121 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3124 static void status_line_bold_errno(const char *fn)
3126 status_line_bold("'%s' %s", fn, strerror(errno));
3129 // format status buffer
3130 static void status_line(const char *format, ...)
3134 va_start(args, format);
3135 vsprintf(status_buffer, format, args);
3138 have_status_msg = 1;
3141 // copy s to buf, convert unprintable
3142 static void print_literal(char *buf, const char *s)
3156 c_is_no_print = (c & 0x80) && !Isprint(c);
3157 if (c_is_no_print) {
3158 strcpy(d, ESC_NORM_TEXT);
3159 d += sizeof(ESC_NORM_TEXT)-1;
3162 if (c < ' ' || c == 0x7f) {
3164 c |= '@'; /* 0x40 */
3170 if (c_is_no_print) {
3171 strcpy(d, ESC_BOLD_TEXT);
3172 d += sizeof(ESC_BOLD_TEXT)-1;
3178 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3183 static void not_implemented(const char *s)
3185 char buf[MAX_INPUT_LEN];
3187 print_literal(buf, s);
3188 status_line_bold("\'%s\' is not implemented", buf);
3191 // show file status on status line
3192 static int format_edit_status(void)
3194 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3196 #define tot format_edit_status__tot
3198 int cur, percent, ret, trunc_at;
3200 // modified_count is now a counter rather than a flag. this
3201 // helps reduce the amount of line counting we need to do.
3202 // (this will cause a mis-reporting of modified status
3203 // once every MAXINT editing operations.)
3205 // it would be nice to do a similar optimization here -- if
3206 // we haven't done a motion that could have changed which line
3207 // we're on, then we shouldn't have to do this count_lines()
3208 cur = count_lines(text, dot);
3210 // count_lines() is expensive.
3211 // Call it only if something was changed since last time
3213 if (modified_count != last_modified_count) {
3214 tot = cur + count_lines(dot, end - 1) - 1;
3215 last_modified_count = modified_count;
3218 // current line percent
3219 // ------------- ~~ ----------
3222 percent = (100 * cur) / tot;
3228 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3229 columns : STATUS_BUFFER_LEN-1;
3231 ret = snprintf(status_buffer, trunc_at+1,
3232 #if ENABLE_FEATURE_VI_READONLY
3233 "%c %s%s%s %d/%d %d%%",
3235 "%c %s%s %d/%d %d%%",
3237 cmd_mode_indicator[cmd_mode & 3],
3238 (current_filename != NULL ? current_filename : "No file"),
3239 #if ENABLE_FEATURE_VI_READONLY
3240 (readonly_mode ? " [Readonly]" : ""),
3242 (modified_count ? " [Modified]" : ""),
3245 if (ret >= 0 && ret < trunc_at)
3246 return ret; /* it all fit */
3248 return trunc_at; /* had to truncate */
3252 //----- Force refresh of all Lines -----------------------------
3253 static void redraw(int full_screen)
3257 screen_erase(); // erase the internal screen buffer
3258 last_status_cksum = 0; // force status update
3259 refresh(full_screen); // this will redraw the entire display
3263 //----- Format a text[] line into a buffer ---------------------
3264 static char* format_line(char *src /*, int li*/)
3269 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3271 c = '~'; // char in col 0 in non-existent lines is '~'
3273 while (co < columns + tabstop) {
3274 // have we gone past the end?
3279 if ((c & 0x80) && !Isprint(c)) {
3282 if (c < ' ' || c == 0x7f) {
3286 while ((co % tabstop) != (tabstop - 1)) {
3294 c += '@'; // Ctrl-X -> 'X'
3299 // discard scrolled-off-to-the-left portion,
3300 // in tabstop-sized pieces
3301 if (ofs >= tabstop && co >= tabstop) {
3302 memmove(dest, dest + tabstop, co);
3309 // check "short line, gigantic offset" case
3312 // discard last scrolled off part
3315 // fill the rest with spaces
3317 memset(&dest[co], ' ', columns - co);
3321 //----- Refresh the changed screen lines -----------------------
3322 // Copy the source line from text[] into the buffer and note
3323 // if the current screenline is different from the new buffer.
3324 // If they differ then that line needs redrawing on the terminal.
3326 static void refresh(int full_screen)
3328 #define old_offset refresh__old_offset
3331 char *tp, *sp; // pointer into text[] and screen[]
3333 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3334 unsigned c = columns, r = rows;
3335 query_screen_dimensions();
3336 full_screen |= (c - columns) | (r - rows);
3338 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3339 tp = screenbegin; // index into text[] of top line
3341 // compare text[] to screen[] and mark screen[] lines that need updating
3342 for (li = 0; li < rows - 1; li++) {
3343 int cs, ce; // column start & end
3345 // format current text line
3346 out_buf = format_line(tp /*, li*/);
3348 // skip to the end of the current text[] line
3350 char *t = memchr(tp, '\n', end - tp);
3351 if (!t) t = end - 1;
3355 // see if there are any changes between virtual screen and out_buf
3356 changed = FALSE; // assume no change
3359 sp = &screen[li * columns]; // start of screen line
3361 // force re-draw of every single column from 0 - columns-1
3364 // compare newly formatted buffer with virtual screen
3365 // look forward for first difference between buf and screen
3366 for (; cs <= ce; cs++) {
3367 if (out_buf[cs] != sp[cs]) {
3368 changed = TRUE; // mark for redraw
3373 // look backward for last difference between out_buf and screen
3374 for (; ce >= cs; ce--) {
3375 if (out_buf[ce] != sp[ce]) {
3376 changed = TRUE; // mark for redraw
3380 // now, cs is index of first diff, and ce is index of last diff
3382 // if horz offset has changed, force a redraw
3383 if (offset != old_offset) {
3388 // make a sanity check of columns indexes
3390 if (ce > columns - 1) ce = columns - 1;
3391 if (cs > ce) { cs = 0; ce = columns - 1; }
3392 // is there a change between virtual screen and out_buf
3394 // copy changed part of buffer to virtual screen
3395 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3396 place_cursor(li, cs);
3397 // write line out to terminal
3398 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3402 place_cursor(crow, ccol);
3404 old_offset = offset;
3408 //---------------------------------------------------------------------
3409 //----- the Ascii Chart -----------------------------------------------
3411 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3412 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3413 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3414 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3415 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3416 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3417 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3418 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3419 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3420 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3421 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3422 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3423 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3424 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3425 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3426 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3427 //---------------------------------------------------------------------
3429 //----- Execute a Vi Command -----------------------------------
3430 static void do_cmd(int c)
3432 char *p, *q, *save_dot;
3438 // c1 = c; // quiet the compiler
3439 // cnt = yf = 0; // quiet the compiler
3440 // p = q = save_dot = buf; // quiet the compiler
3441 memset(buf, '\0', sizeof(buf));
3445 /* if this is a cursor key, skip these checks */
3453 case KEYCODE_PAGEUP:
3454 case KEYCODE_PAGEDOWN:
3455 case KEYCODE_DELETE:
3459 if (cmd_mode == 2) {
3460 // flip-flop Insert/Replace mode
3461 if (c == KEYCODE_INSERT)
3463 // we are 'R'eplacing the current *dot with new char
3465 // don't Replace past E-o-l
3466 cmd_mode = 1; // convert to insert
3467 undo_queue_commit();
3469 if (1 <= c || Isprint(c)) {
3471 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3472 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3477 if (cmd_mode == 1) {
3478 // hitting "Insert" twice means "R" replace mode
3479 if (c == KEYCODE_INSERT) goto dc5;
3480 // insert the char c at "dot"
3481 if (1 <= c || Isprint(c)) {
3482 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3497 #if ENABLE_FEATURE_VI_CRASHME
3498 case 0x14: // dc4 ctrl-T
3499 crashme = (crashme == 0) ? 1 : 0;
3529 default: // unrecognized command
3532 not_implemented(buf);
3533 end_cmd_q(); // stop adding to q
3534 case 0x00: // nul- ignore
3536 case 2: // ctrl-B scroll up full screen
3537 case KEYCODE_PAGEUP: // Cursor Key Page Up
3538 dot_scroll(rows - 2, -1);
3540 case 4: // ctrl-D scroll down half screen
3541 dot_scroll((rows - 2) / 2, 1);
3543 case 5: // ctrl-E scroll down one line
3546 case 6: // ctrl-F scroll down full screen
3547 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3548 dot_scroll(rows - 2, 1);
3550 case 7: // ctrl-G show current status
3551 last_status_cksum = 0; // force status update
3553 case 'h': // h- move left
3554 case KEYCODE_LEFT: // cursor key Left
3555 case 8: // ctrl-H- move left (This may be ERASE char)
3556 case 0x7f: // DEL- move left (This may be ERASE char)
3559 } while (--cmdcnt > 0);
3561 case 10: // Newline ^J
3562 case 'j': // j- goto next line, same col
3563 case KEYCODE_DOWN: // cursor key Down
3565 dot_next(); // go to next B-o-l
3566 // try stay in same col
3567 dot = move_to_col(dot, ccol + offset);
3568 } while (--cmdcnt > 0);
3570 case 12: // ctrl-L force redraw whole screen
3571 case 18: // ctrl-R force redraw
3574 //mysleep(10); // why???
3575 screen_erase(); // erase the internal screen buffer
3576 last_status_cksum = 0; // force status update
3577 refresh(TRUE); // this will redraw the entire display
3579 case 13: // Carriage Return ^M
3580 case '+': // +- goto next line
3584 } while (--cmdcnt > 0);
3586 case 21: // ctrl-U scroll up half screen
3587 dot_scroll((rows - 2) / 2, -1);
3589 case 25: // ctrl-Y scroll up one line
3595 cmd_mode = 0; // stop insrting
3596 undo_queue_commit();
3598 last_status_cksum = 0; // force status update
3600 case ' ': // move right
3601 case 'l': // move right
3602 case KEYCODE_RIGHT: // Cursor Key Right
3605 } while (--cmdcnt > 0);
3607 #if ENABLE_FEATURE_VI_YANKMARK
3608 case '"': // "- name a register to use for Delete/Yank
3609 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3610 if ((unsigned)c1 <= 25) { // a-z?
3616 case '\'': // '- goto a specific mark
3617 c1 = (get_one_char() | 0x20);
3618 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3622 if (text <= q && q < end) {
3624 dot_begin(); // go to B-o-l
3627 } else if (c1 == '\'') { // goto previous context
3628 dot = swap_context(dot); // swap current and previous context
3629 dot_begin(); // go to B-o-l
3635 case 'm': // m- Mark a line
3636 // this is really stupid. If there are any inserts or deletes
3637 // between text[0] and dot then this mark will not point to the
3638 // correct location! It could be off by many lines!
3639 // Well..., at least its quick and dirty.
3640 c1 = (get_one_char() | 0x20) - 'a';
3641 if ((unsigned)c1 <= 25) { // a-z?
3642 // remember the line
3648 case 'P': // P- Put register before
3649 case 'p': // p- put register after
3652 status_line_bold("Nothing in register %c", what_reg());
3655 // are we putting whole lines or strings
3656 if (strchr(p, '\n') != NULL) {
3658 dot_begin(); // putting lines- Put above
3661 // are we putting after very last line?
3662 if (end_line(dot) == (end - 1)) {
3663 dot = end; // force dot to end of text[]
3665 dot_next(); // next line, then put before
3670 dot_right(); // move to right, can move to NL
3672 string_insert(dot, p, ALLOW_UNDO); // insert the string
3673 end_cmd_q(); // stop adding to q
3675 case 'U': // U- Undo; replace current line with original version
3676 if (reg[Ureg] != NULL) {
3677 p = begin_line(dot);
3679 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3680 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3685 #endif /* FEATURE_VI_YANKMARK */
3686 #if ENABLE_FEATURE_VI_UNDO
3687 case 'u': // u- undo last operation
3691 case '$': // $- goto end of line
3692 case KEYCODE_END: // Cursor Key End
3694 dot = end_line(dot);
3700 case '%': // %- find matching char of pair () [] {}
3701 for (q = dot; q < end && *q != '\n'; q++) {
3702 if (strchr("()[]{}", *q) != NULL) {
3703 // we found half of a pair
3704 p = find_pair(q, *q);
3716 case 'f': // f- forward to a user specified char
3717 last_forward_char = get_one_char(); // get the search char
3719 // dont separate these two commands. 'f' depends on ';'
3721 //**** fall through to ... ';'
3722 case ';': // ;- look at rest of line for last forward char
3724 if (last_forward_char == 0)
3727 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3730 if (*q == last_forward_char)
3732 } while (--cmdcnt > 0);
3734 case ',': // repeat latest 'f' in opposite direction
3735 if (last_forward_char == 0)
3739 while (q >= text && *q != '\n' && *q != last_forward_char) {
3742 if (q >= text && *q == last_forward_char)
3744 } while (--cmdcnt > 0);
3747 case '-': // -- goto prev line
3751 } while (--cmdcnt > 0);
3753 #if ENABLE_FEATURE_VI_DOT_CMD
3754 case '.': // .- repeat the last modifying command
3755 // Stuff the last_modifying_cmd back into stdin
3756 // and let it be re-executed.
3758 last_modifying_cmd[lmc_len] = 0;
3759 ioq = ioq_start = xstrdup(last_modifying_cmd);
3763 #if ENABLE_FEATURE_VI_SEARCH
3764 case '?': // /- search for a pattern
3765 case '/': // /- search for a pattern
3768 q = get_input_line(buf); // get input line- use "status line"
3769 if (q[0] && !q[1]) {
3770 if (last_search_pattern[0])
3771 last_search_pattern[0] = c;
3772 goto dc3; // if no pat re-use old pat
3774 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3775 // there is a new pat
3776 free(last_search_pattern);
3777 last_search_pattern = xstrdup(q);
3778 goto dc3; // now find the pattern
3780 // user changed mind and erased the "/"- do nothing
3782 case 'N': // N- backward search for last pattern
3783 dir = BACK; // assume BACKWARD search
3785 if (last_search_pattern[0] == '?') {
3789 goto dc4; // now search for pattern
3791 case 'n': // n- repeat search for last pattern
3792 // search rest of text[] starting at next char
3793 // if search fails return orignal "p" not the "p+1" address
3797 dir = FORWARD; // assume FORWARD search
3799 if (last_search_pattern[0] == '?') {
3804 q = char_search(p, last_search_pattern + 1, dir, FULL);
3806 dot = q; // good search, update "dot"
3810 // no pattern found between "dot" and "end"- continue at top
3815 q = char_search(p, last_search_pattern + 1, dir, FULL);
3816 if (q != NULL) { // found something
3817 dot = q; // found new pattern- goto it
3818 msg = "search hit BOTTOM, continuing at TOP";
3820 msg = "search hit TOP, continuing at BOTTOM";
3823 msg = "Pattern not found";
3827 status_line_bold("%s", msg);
3828 } while (--cmdcnt > 0);
3830 case '{': // {- move backward paragraph
3831 q = char_search(dot, "\n\n", BACK, FULL);
3832 if (q != NULL) { // found blank line
3833 dot = next_line(q); // move to next blank line
3836 case '}': // }- move forward paragraph
3837 q = char_search(dot, "\n\n", FORWARD, FULL);
3838 if (q != NULL) { // found blank line
3839 dot = next_line(q); // move to next blank line
3842 #endif /* FEATURE_VI_SEARCH */
3843 case '0': // 0- goto beginning of line
3853 if (c == '0' && cmdcnt < 1) {
3854 dot_begin(); // this was a standalone zero
3856 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3859 case ':': // :- the colon mode commands
3860 p = get_input_line(":"); // get input line- use "status line"
3861 colon(p); // execute the command
3863 case '<': // <- Left shift something
3864 case '>': // >- Right shift something
3865 cnt = count_lines(text, dot); // remember what line we are on
3866 c1 = get_one_char(); // get the type of thing to delete
3867 find_range(&p, &q, c1);
3868 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3871 i = count_lines(p, q); // # of lines we are shifting
3872 for ( ; i > 0; i--, p = next_line(p)) {
3874 // shift left- remove tab or 8 spaces
3876 // shrink buffer 1 char
3877 text_hole_delete(p, p, NO_UNDO);
3878 } else if (*p == ' ') {
3879 // we should be calculating columns, not just SPACE
3880 for (j = 0; *p == ' ' && j < tabstop; j++) {
3881 text_hole_delete(p, p, NO_UNDO);
3884 } else if (c == '>') {
3885 // shift right -- add tab or 8 spaces
3886 char_insert(p, '\t', ALLOW_UNDO);
3889 dot = find_line(cnt); // what line were we on
3891 end_cmd_q(); // stop adding to q
3893 case 'A': // A- append at e-o-l
3894 dot_end(); // go to e-o-l
3895 //**** fall through to ... 'a'
3896 case 'a': // a- append after current char
3901 case 'B': // B- back a blank-delimited Word
3902 case 'E': // E- end of a blank-delimited word
3903 case 'W': // W- forward a blank-delimited word
3908 if (c == 'W' || isspace(dot[dir])) {
3909 dot = skip_thing(dot, 1, dir, S_TO_WS);
3910 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3913 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3914 } while (--cmdcnt > 0);
3916 case 'C': // C- Change to e-o-l
3917 case 'D': // D- delete to e-o-l
3919 dot = dollar_line(dot); // move to before NL
3920 // copy text into a register and delete
3921 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3923 goto dc_i; // start inserting
3924 #if ENABLE_FEATURE_VI_DOT_CMD
3926 end_cmd_q(); // stop adding to q
3929 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3930 c1 = get_one_char();
3933 // c1 < 0 if the key was special. Try "g<up-arrow>"
3934 // TODO: if Unicode?
3935 buf[1] = (c1 >= 0 ? c1 : '*');
3937 not_implemented(buf);
3943 case 'G': // G- goto to a line number (default= E-O-F)
3944 dot = end - 1; // assume E-O-F
3946 dot = find_line(cmdcnt); // what line is #cmdcnt
3950 case 'H': // H- goto top line on screen
3952 if (cmdcnt > (rows - 1)) {
3953 cmdcnt = (rows - 1);
3960 case 'I': // I- insert before first non-blank
3963 //**** fall through to ... 'i'
3964 case 'i': // i- insert before current char
3965 case KEYCODE_INSERT: // Cursor Key Insert
3967 cmd_mode = 1; // start inserting
3968 undo_queue_commit(); // commit queue when cmd_mode changes
3970 case 'J': // J- join current and next lines together
3972 dot_end(); // move to NL
3973 if (dot < end - 1) { // make sure not last char in text[]
3974 #if ENABLE_FEATURE_VI_UNDO
3975 undo_push(dot, 1, UNDO_DEL);
3976 *dot++ = ' '; // replace NL with space
3977 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3982 while (isblank(*dot)) { // delete leading WS
3983 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3986 } while (--cmdcnt > 0);
3987 end_cmd_q(); // stop adding to q
3989 case 'L': // L- goto bottom line on screen
3991 if (cmdcnt > (rows - 1)) {
3992 cmdcnt = (rows - 1);
4000 case 'M': // M- goto middle line on screen
4002 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
4003 dot = next_line(dot);
4005 case 'O': // O- open a empty line above
4007 p = begin_line(dot);
4008 if (p[-1] == '\n') {
4010 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
4012 dot = char_insert(dot, '\n', ALLOW_UNDO);
4015 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
4020 case 'R': // R- continuous Replace char
4023 undo_queue_commit();
4025 case KEYCODE_DELETE:
4027 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
4029 case 'X': // X- delete char before dot
4030 case 'x': // x- delete the current char
4031 case 's': // s- substitute the current char
4036 if (dot[dir] != '\n') {
4038 dot--; // delete prev char
4039 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
4041 } while (--cmdcnt > 0);
4042 end_cmd_q(); // stop adding to q
4044 goto dc_i; // start inserting
4046 case 'Z': // Z- if modified, {write}; exit
4047 // ZZ means to save file (if necessary), then exit
4048 c1 = get_one_char();
4053 if (modified_count) {
4054 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
4055 status_line_bold("'%s' is read only", current_filename);
4058 cnt = file_write(current_filename, text, end - 1);
4061 status_line_bold("Write error: %s", strerror(errno));
4062 } else if (cnt == (end - 1 - text + 1)) {
4069 case '^': // ^- move to first non-blank on line
4073 case 'b': // b- back a word
4074 case 'e': // e- end of word
4079 if ((dot + dir) < text || (dot + dir) > end - 1)
4082 if (isspace(*dot)) {
4083 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4085 if (isalnum(*dot) || *dot == '_') {
4086 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4087 } else if (ispunct(*dot)) {
4088 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4090 } while (--cmdcnt > 0);
4092 case 'c': // c- change something
4093 case 'd': // d- delete something
4094 #if ENABLE_FEATURE_VI_YANKMARK
4095 case 'y': // y- yank something
4096 case 'Y': // Y- Yank a line
4099 int yf, ml, whole = 0;
4100 yf = YANKDEL; // assume either "c" or "d"
4101 #if ENABLE_FEATURE_VI_YANKMARK
4102 if (c == 'y' || c == 'Y')
4107 c1 = get_one_char(); // get the type of thing to delete
4108 // determine range, and whether it spans lines
4109 ml = find_range(&p, &q, c1);
4111 if (c1 == 27) { // ESC- user changed mind and wants out
4112 c = c1 = 27; // Escape- do nothing
4113 } else if (strchr("wW", c1)) {
4115 // don't include trailing WS as part of word
4116 while (isblank(*q)) {
4117 if (q <= text || q[-1] == '\n')
4122 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4123 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
4124 // partial line copy text into a register and delete
4125 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4126 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
4127 // whole line copy text into a register and delete
4128 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
4131 // could not recognize object
4132 c = c1 = 27; // error-
4138 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4139 // on the last line of file don't move to prev line
4140 if (whole && dot != (end-1)) {
4143 } else if (c == 'd') {
4149 // if CHANGING, not deleting, start inserting after the delete
4151 strcpy(buf, "Change");
4152 goto dc_i; // start inserting
4155 strcpy(buf, "Delete");
4157 #if ENABLE_FEATURE_VI_YANKMARK
4158 if (c == 'y' || c == 'Y') {
4159 strcpy(buf, "Yank");
4163 for (cnt = 0; p <= q; p++) {
4167 status_line("%s %d lines (%d chars) using [%c]",
4168 buf, cnt, strlen(reg[YDreg]), what_reg());
4170 end_cmd_q(); // stop adding to q
4174 case 'k': // k- goto prev line, same col
4175 case KEYCODE_UP: // cursor key Up
4178 dot = move_to_col(dot, ccol + offset); // try stay in same col
4179 } while (--cmdcnt > 0);
4181 case 'r': // r- replace the current char with user input
4182 c1 = get_one_char(); // get the replacement char
4184 #if ENABLE_FEATURE_VI_UNDO
4185 undo_push(dot, 1, UNDO_DEL);
4187 undo_push(dot, 1, UNDO_INS_CHAIN);
4193 end_cmd_q(); // stop adding to q
4195 case 't': // t- move to char prior to next x
4196 last_forward_char = get_one_char();
4198 if (*dot == last_forward_char)
4200 last_forward_char = 0;
4202 case 'w': // w- forward a word
4204 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4205 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4206 } else if (ispunct(*dot)) { // we are on PUNCT
4207 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4210 dot++; // move over word
4211 if (isspace(*dot)) {
4212 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4214 } while (--cmdcnt > 0);
4217 c1 = get_one_char(); // get the replacement char
4220 cnt = (rows - 2) / 2; // put dot at center
4222 cnt = rows - 2; // put dot at bottom
4223 screenbegin = begin_line(dot); // start dot at top
4224 dot_scroll(cnt, -1);
4226 case '|': // |- move to column "cmdcnt"
4227 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4229 case '~': // ~- flip the case of letters a-z -> A-Z
4231 #if ENABLE_FEATURE_VI_UNDO
4232 if (islower(*dot)) {
4233 undo_push(dot, 1, UNDO_DEL);
4234 *dot = toupper(*dot);
4235 undo_push(dot, 1, UNDO_INS_CHAIN);
4236 } else if (isupper(*dot)) {
4237 undo_push(dot, 1, UNDO_DEL);
4238 *dot = tolower(*dot);
4239 undo_push(dot, 1, UNDO_INS_CHAIN);
4242 if (islower(*dot)) {
4243 *dot = toupper(*dot);
4245 } else if (isupper(*dot)) {
4246 *dot = tolower(*dot);
4251 } while (--cmdcnt > 0);
4252 end_cmd_q(); // stop adding to q
4254 //----- The Cursor and Function Keys -----------------------------
4255 case KEYCODE_HOME: // Cursor Key Home
4258 // The Fn keys could point to do_macro which could translate them
4260 case KEYCODE_FUN1: // Function Key F1
4261 case KEYCODE_FUN2: // Function Key F2
4262 case KEYCODE_FUN3: // Function Key F3
4263 case KEYCODE_FUN4: // Function Key F4
4264 case KEYCODE_FUN5: // Function Key F5
4265 case KEYCODE_FUN6: // Function Key F6
4266 case KEYCODE_FUN7: // Function Key F7
4267 case KEYCODE_FUN8: // Function Key F8
4268 case KEYCODE_FUN9: // Function Key F9
4269 case KEYCODE_FUN10: // Function Key F10
4270 case KEYCODE_FUN11: // Function Key F11
4271 case KEYCODE_FUN12: // Function Key F12
4277 // if text[] just became empty, add back an empty line
4279 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4282 // it is OK for dot to exactly equal to end, otherwise check dot validity
4284 dot = bound_dot(dot); // make sure "dot" is valid
4286 #if ENABLE_FEATURE_VI_YANKMARK
4287 check_context(c); // update the current context
4291 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4292 cnt = dot - begin_line(dot);
4293 // Try to stay off of the Newline
4294 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4298 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4299 #if ENABLE_FEATURE_VI_CRASHME
4300 static int totalcmds = 0;
4301 static int Mp = 85; // Movement command Probability
4302 static int Np = 90; // Non-movement command Probability
4303 static int Dp = 96; // Delete command Probability
4304 static int Ip = 97; // Insert command Probability
4305 static int Yp = 98; // Yank command Probability
4306 static int Pp = 99; // Put command Probability
4307 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4308 static const char chars[20] = "\t012345 abcdABCD-=.$";
4309 static const char *const words[20] = {
4310 "this", "is", "a", "test",
4311 "broadcast", "the", "emergency", "of",
4312 "system", "quick", "brown", "fox",
4313 "jumped", "over", "lazy", "dogs",
4314 "back", "January", "Febuary", "March"
4316 static const char *const lines[20] = {
4317 "You should have received a copy of the GNU General Public License\n",
4318 "char c, cm, *cmd, *cmd1;\n",
4319 "generate a command by percentages\n",
4320 "Numbers may be typed as a prefix to some commands.\n",
4321 "Quit, discarding changes!\n",
4322 "Forced write, if permission originally not valid.\n",
4323 "In general, any ex or ed command (such as substitute or delete).\n",
4324 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4325 "Please get w/ me and I will go over it with you.\n",
4326 "The following is a list of scheduled, committed changes.\n",
4327 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4328 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4329 "Any question about transactions please contact Sterling Huxley.\n",
4330 "I will try to get back to you by Friday, December 31.\n",
4331 "This Change will be implemented on Friday.\n",
4332 "Let me know if you have problems accessing this;\n",
4333 "Sterling Huxley recently added you to the access list.\n",
4334 "Would you like to go to lunch?\n",
4335 "The last command will be automatically run.\n",
4336 "This is too much english for a computer geek.\n",
4338 static char *multilines[20] = {
4339 "You should have received a copy of the GNU General Public License\n",
4340 "char c, cm, *cmd, *cmd1;\n",
4341 "generate a command by percentages\n",
4342 "Numbers may be typed as a prefix to some commands.\n",
4343 "Quit, discarding changes!\n",
4344 "Forced write, if permission originally not valid.\n",
4345 "In general, any ex or ed command (such as substitute or delete).\n",
4346 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4347 "Please get w/ me and I will go over it with you.\n",
4348 "The following is a list of scheduled, committed changes.\n",
4349 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4350 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4351 "Any question about transactions please contact Sterling Huxley.\n",
4352 "I will try to get back to you by Friday, December 31.\n",
4353 "This Change will be implemented on Friday.\n",
4354 "Let me know if you have problems accessing this;\n",
4355 "Sterling Huxley recently added you to the access list.\n",
4356 "Would you like to go to lunch?\n",
4357 "The last command will be automatically run.\n",
4358 "This is too much english for a computer geek.\n",
4361 // create a random command to execute
4362 static void crash_dummy()
4364 static int sleeptime; // how long to pause between commands
4365 char c, cm, *cmd, *cmd1;
4366 int i, cnt, thing, rbi, startrbi, percent;
4368 // "dot" movement commands
4369 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4371 // is there already a command running?
4372 if (readbuffer[0] > 0)
4375 readbuffer[0] = 'X';
4377 sleeptime = 0; // how long to pause between commands
4378 memset(readbuffer, '\0', sizeof(readbuffer));
4379 // generate a command by percentages
4380 percent = (int) lrand48() % 100; // get a number from 0-99
4381 if (percent < Mp) { // Movement commands
4382 // available commands
4385 } else if (percent < Np) { // non-movement commands
4386 cmd = "mz<>\'\""; // available commands
4388 } else if (percent < Dp) { // Delete commands
4389 cmd = "dx"; // available commands
4391 } else if (percent < Ip) { // Inset commands
4392 cmd = "iIaAsrJ"; // available commands
4394 } else if (percent < Yp) { // Yank commands
4395 cmd = "yY"; // available commands
4397 } else if (percent < Pp) { // Put commands
4398 cmd = "pP"; // available commands
4401 // We do not know how to handle this command, try again
4405 // randomly pick one of the available cmds from "cmd[]"
4406 i = (int) lrand48() % strlen(cmd);
4408 if (strchr(":\024", cm))
4409 goto cd0; // dont allow colon or ctrl-T commands
4410 readbuffer[rbi++] = cm; // put cmd into input buffer
4412 // now we have the command-
4413 // there are 1, 2, and multi char commands
4414 // find out which and generate the rest of command as necessary
4415 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4416 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4417 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4418 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4420 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4422 readbuffer[rbi++] = c; // add movement to input buffer
4424 if (strchr("iIaAsc", cm)) { // multi-char commands
4426 // change some thing
4427 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4429 readbuffer[rbi++] = c; // add movement to input buffer
4431 thing = (int) lrand48() % 4; // what thing to insert
4432 cnt = (int) lrand48() % 10; // how many to insert
4433 for (i = 0; i < cnt; i++) {
4434 if (thing == 0) { // insert chars
4435 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4436 } else if (thing == 1) { // insert words
4437 strcat(readbuffer, words[(int) lrand48() % 20]);
4438 strcat(readbuffer, " ");
4439 sleeptime = 0; // how fast to type
4440 } else if (thing == 2) { // insert lines
4441 strcat(readbuffer, lines[(int) lrand48() % 20]);
4442 sleeptime = 0; // how fast to type
4443 } else { // insert multi-lines
4444 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4445 sleeptime = 0; // how fast to type
4448 strcat(readbuffer, "\033");
4450 readbuffer[0] = strlen(readbuffer + 1);
4454 mysleep(sleeptime); // sleep 1/100 sec
4457 // test to see if there are any errors
4458 static void crash_test()
4460 static time_t oldtim;
4467 strcat(msg, "end<text ");
4469 if (end > textend) {
4470 strcat(msg, "end>textend ");
4473 strcat(msg, "dot<text ");
4476 strcat(msg, "dot>end ");
4478 if (screenbegin < text) {
4479 strcat(msg, "screenbegin<text ");
4481 if (screenbegin > end - 1) {
4482 strcat(msg, "screenbegin>end-1 ");
4486 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4487 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4489 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4490 if (d[0] == '\n' || d[0] == '\r')
4495 if (tim >= (oldtim + 3)) {
4496 sprintf(status_buffer,
4497 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4498 totalcmds, M, N, I, D, Y, P, U, end - text + 1);