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"
24 //config: bool "vi (22 kb)"
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 enables you to use yank and put, as well as mark.
66 //config:config FEATURE_VI_SEARCH
67 //config: bool "Enable search and replace cmds"
69 //config: depends on VI
71 //config: Select this if you wish to be able to do search and replace.
73 //config:config FEATURE_VI_REGEX_SEARCH
74 //config: bool "Enable regex in search and replace"
75 //config: default n # Uses GNU regex, which may be unavailable. FIXME
76 //config: depends on FEATURE_VI_SEARCH
78 //config: Use extended regex search.
80 //config:config FEATURE_VI_USE_SIGNALS
81 //config: bool "Catch signals"
83 //config: depends on VI
85 //config: Selecting this option will make vi signal aware. This will support
86 //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
88 //config:config FEATURE_VI_DOT_CMD
89 //config: bool "Remember previous cmd and \".\" cmd"
91 //config: depends on VI
93 //config: Make vi remember the last command and be able to repeat it.
95 //config:config FEATURE_VI_READONLY
96 //config: bool "Enable -R option and \"view\" mode"
98 //config: depends on VI
100 //config: Enable the read-only command line option, which allows the user to
101 //config: open a file in read-only mode.
103 //config:config FEATURE_VI_SETOPTS
104 //config: bool "Enable settable options, ai ic showmatch"
106 //config: depends on VI
108 //config: Enable the editor to set some (ai, ic, showmatch) options.
110 //config:config FEATURE_VI_SET
111 //config: bool "Support :set"
113 //config: depends on VI
115 //config:config FEATURE_VI_WIN_RESIZE
116 //config: bool "Handle window resize"
118 //config: depends on VI
120 //config: Behave nicely with terminals that get resized.
122 //config:config FEATURE_VI_ASK_TERMINAL
123 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
125 //config: depends on VI
127 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
128 //config: this option makes vi perform a last-ditch effort to find it:
129 //config: position cursor to 999,999 and ask terminal to report real
130 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
131 //config: This is not clean but helps a lot on serial lines and such.
133 //config:config FEATURE_VI_UNDO
134 //config: bool "Support undo command \"u\""
136 //config: depends on VI
138 //config: Support the 'u' command to undo insertion, deletion, and replacement
141 //config:config FEATURE_VI_UNDO_QUEUE
142 //config: bool "Enable undo operation queuing"
144 //config: depends on FEATURE_VI_UNDO
146 //config: The vi undo functions can use an intermediate queue to greatly lower
147 //config: malloc() calls and overhead. When the maximum size of this queue is
148 //config: reached, the contents of the queue are committed to the undo stack.
149 //config: This increases the size of the undo code and allows some undo
150 //config: operations (especially un-typing/backspacing) to be far more useful.
152 //config:config FEATURE_VI_UNDO_QUEUE_MAX
153 //config: int "Maximum undo character queue size"
154 //config: default 256
155 //config: range 32 65536
156 //config: depends on FEATURE_VI_UNDO_QUEUE
158 //config: This option sets the number of bytes used at runtime for the queue.
159 //config: Smaller values will create more undo objects and reduce the amount
160 //config: of typed or backspaced characters that are grouped into one undo
161 //config: operation; larger values increase the potential size of each undo
162 //config: and will generally malloc() larger objects and less frequently.
163 //config: Unless you want more (or less) frequent "undo points" while typing,
164 //config: you should probably leave this unchanged.
166 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
168 //kbuild:lib-$(CONFIG_VI) += vi.o
170 //usage:#define vi_trivial_usage
171 //usage: "[OPTIONS] [FILE]..."
172 //usage:#define vi_full_usage "\n\n"
173 //usage: "Edit FILE\n"
174 //usage: IF_FEATURE_VI_COLON(
175 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
177 //usage: IF_FEATURE_VI_READONLY(
178 //usage: "\n -R Read-only"
180 //usage: "\n -H List available features"
183 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
184 #if ENABLE_FEATURE_VI_REGEX_SEARCH
188 /* the CRASHME code is unmaintained, and doesn't currently build */
189 #define ENABLE_FEATURE_VI_CRASHME 0
192 #if ENABLE_LOCALE_SUPPORT
194 #if ENABLE_FEATURE_VI_8BIT
195 //FIXME: this does not work properly for Unicode anyway
196 # define Isprint(c) (isprint)(c)
198 # define Isprint(c) isprint_asciionly(c)
203 /* 0x9b is Meta-ESC */
204 #if ENABLE_FEATURE_VI_8BIT
205 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
207 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
214 MAX_TABSTOP = 32, // sanity limit
215 // User input len. Need not be extra big.
216 // Lines in file being edited *can* be bigger than this.
218 // Sanity limits. We have only one buffer of this size.
219 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
220 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
223 /* VT102 ESC sequences.
224 * See "Xterm Control Sequences"
225 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
227 /* Inverse/Normal text */
228 #define ESC_BOLD_TEXT "\033[7m"
229 #define ESC_NORM_TEXT "\033[0m"
231 #define ESC_BELL "\007"
232 /* Clear-to-end-of-line */
233 #define ESC_CLEAR2EOL "\033[K"
234 /* Clear-to-end-of-screen.
235 * (We use default param here.
236 * Full sequence is "ESC [ <num> J",
237 * <num> is 0/1/2 = "erase below/above/all".)
239 #define ESC_CLEAR2EOS "\033[J"
240 /* Cursor to given coordinate (1,1: top left) */
241 #define ESC_SET_CURSOR_POS "\033[%u;%uH"
243 ///* Cursor up and down */
244 //#define ESC_CURSOR_UP "\033[A"
245 //#define ESC_CURSOR_DOWN "\n"
247 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
248 // cmds modifying text[]
249 // vda: removed "aAiIs" as they switch us into insert mode
250 // and remembering input for replay after them makes no sense
251 static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
257 FORWARD = 1, // code depends on "1" for array index
258 BACK = -1, // code depends on "-1" for array index
259 LIMITED = 0, // how much of text[] in char_search
260 FULL = 1, // how much of text[] in char_search
262 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
263 S_TO_WS = 2, // used in skip_thing() for moving "dot"
264 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
265 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
266 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
270 /* vi.c expects chars to be unsigned. */
271 /* busybox build system provides that, but it's better */
272 /* to audit and fix the source */
275 /* many references - keep near the top of globals */
276 char *text, *end; // pointers to the user data in memory
277 char *dot; // where all the action takes place
278 int text_size; // size of the allocated buffer
282 #define VI_AUTOINDENT 1
283 #define VI_SHOWMATCH 2
284 #define VI_IGNORECASE 4
285 #define VI_ERR_METHOD 8
286 #define autoindent (vi_setops & VI_AUTOINDENT)
287 #define showmatch (vi_setops & VI_SHOWMATCH )
288 #define ignorecase (vi_setops & VI_IGNORECASE)
289 /* indicate error with beep or flash */
290 #define err_method (vi_setops & VI_ERR_METHOD)
292 #if ENABLE_FEATURE_VI_READONLY
293 smallint readonly_mode;
294 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
295 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
296 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
298 #define SET_READONLY_FILE(flags) ((void)0)
299 #define SET_READONLY_MODE(flags) ((void)0)
300 #define UNSET_READONLY_FILE(flags) ((void)0)
303 smallint editing; // >0 while we are editing a file
304 // [code audit says "can be 0, 1 or 2 only"]
305 smallint cmd_mode; // 0=command 1=insert 2=replace
306 int modified_count; // buffer contents changed if !0
307 int last_modified_count; // = -1;
308 int save_argc; // how many file names on cmd line
309 int cmdcnt; // repetition count
310 unsigned rows, columns; // the terminal screen is this size
311 #if ENABLE_FEATURE_VI_ASK_TERMINAL
312 int get_rowcol_error;
314 int crow, ccol; // cursor is on Crow x Ccol
315 int offset; // chars scrolled off the screen to the left
316 int have_status_msg; // is default edit status needed?
317 // [don't make smallint!]
318 int last_status_cksum; // hash of current status line
319 char *current_filename;
320 char *screenbegin; // index into text[], of top line on the screen
321 char *screen; // pointer to the virtual screen buffer
322 int screensize; // and its size
324 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
325 char erase_char; // the users erase character
326 char last_input_char; // last char read from user
328 #if ENABLE_FEATURE_VI_DOT_CMD
329 smallint adding2q; // are we currently adding user input to q
330 int lmc_len; // length of last_modifying_cmd
331 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
333 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
336 #if ENABLE_FEATURE_VI_SEARCH
337 char *last_search_pattern; // last pattern from a '/' or '?' search
341 #if ENABLE_FEATURE_VI_YANKMARK
342 char *edit_file__cur_line;
344 int refresh__old_offset;
345 int format_edit_status__tot;
347 /* a few references only */
348 #if ENABLE_FEATURE_VI_YANKMARK
349 int YDreg, Ureg; // default delete register and orig line for "U"
350 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
351 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
352 char *context_start, *context_end;
354 #if ENABLE_FEATURE_VI_USE_SIGNALS
355 sigjmp_buf restart; // catch_sig()
357 struct termios term_orig; // remember what the cooked mode was
358 #if ENABLE_FEATURE_VI_COLON
359 char *initial_cmds[3]; // currently 2 entries, NULL terminated
361 // Should be just enough to hold a key sequence,
362 // but CRASHME mode uses it as generated command buffer too
363 #if ENABLE_FEATURE_VI_CRASHME
364 char readbuffer[128];
366 char readbuffer[KEYCODE_BUFFER_SIZE];
368 #define STATUS_BUFFER_LEN 200
369 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
370 #if ENABLE_FEATURE_VI_DOT_CMD
371 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
373 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
375 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
376 #if ENABLE_FEATURE_VI_UNDO
377 // undo_push() operations
380 #define UNDO_INS_CHAIN 2
381 #define UNDO_DEL_CHAIN 3
382 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
383 #define UNDO_QUEUED_FLAG 4
384 #define UNDO_INS_QUEUED 4
385 #define UNDO_DEL_QUEUED 5
386 #define UNDO_USE_SPOS 32
387 #define UNDO_EMPTY 64
388 // Pass-through flags for functions that can be undone
391 #define ALLOW_UNDO_CHAIN 2
392 # if ENABLE_FEATURE_VI_UNDO_QUEUE
393 #define ALLOW_UNDO_QUEUED 3
394 char undo_queue_state;
396 char *undo_queue_spos; // Start position of queued operation
397 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
399 // If undo queuing disabled, don't invoke the missing queue logic
400 #define ALLOW_UNDO_QUEUED 1
404 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
405 int start; // Offset where the data should be restored/deleted
406 int length; // total data size
407 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
408 char undo_text[1]; // text that was deleted (if deletion)
410 #endif /* ENABLE_FEATURE_VI_UNDO */
412 #define G (*ptr_to_globals)
413 #define text (G.text )
414 #define text_size (G.text_size )
419 #define vi_setops (G.vi_setops )
420 #define editing (G.editing )
421 #define cmd_mode (G.cmd_mode )
422 #define modified_count (G.modified_count )
423 #define last_modified_count (G.last_modified_count)
424 #define save_argc (G.save_argc )
425 #define cmdcnt (G.cmdcnt )
426 #define rows (G.rows )
427 #define columns (G.columns )
428 #define crow (G.crow )
429 #define ccol (G.ccol )
430 #define offset (G.offset )
431 #define status_buffer (G.status_buffer )
432 #define have_status_msg (G.have_status_msg )
433 #define last_status_cksum (G.last_status_cksum )
434 #define current_filename (G.current_filename )
435 #define screen (G.screen )
436 #define screensize (G.screensize )
437 #define screenbegin (G.screenbegin )
438 #define tabstop (G.tabstop )
439 #define last_forward_char (G.last_forward_char )
440 #define erase_char (G.erase_char )
441 #define last_input_char (G.last_input_char )
442 #if ENABLE_FEATURE_VI_READONLY
443 #define readonly_mode (G.readonly_mode )
445 #define readonly_mode 0
447 #define adding2q (G.adding2q )
448 #define lmc_len (G.lmc_len )
450 #define ioq_start (G.ioq_start )
451 #define my_pid (G.my_pid )
452 #define last_search_pattern (G.last_search_pattern)
454 #define edit_file__cur_line (G.edit_file__cur_line)
455 #define refresh__old_offset (G.refresh__old_offset)
456 #define format_edit_status__tot (G.format_edit_status__tot)
458 #define YDreg (G.YDreg )
459 #define Ureg (G.Ureg )
460 #define mark (G.mark )
461 #define context_start (G.context_start )
462 #define context_end (G.context_end )
463 #define restart (G.restart )
464 #define term_orig (G.term_orig )
465 #define initial_cmds (G.initial_cmds )
466 #define readbuffer (G.readbuffer )
467 #define scr_out_buf (G.scr_out_buf )
468 #define last_modifying_cmd (G.last_modifying_cmd )
469 #define get_input_line__buf (G.get_input_line__buf)
471 #if ENABLE_FEATURE_VI_UNDO
472 #define undo_stack_tail (G.undo_stack_tail )
473 # if ENABLE_FEATURE_VI_UNDO_QUEUE
474 #define undo_queue_state (G.undo_queue_state)
475 #define undo_q (G.undo_q )
476 #define undo_queue (G.undo_queue )
477 #define undo_queue_spos (G.undo_queue_spos )
481 #define INIT_G() do { \
482 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
483 last_modified_count = -1; \
484 /* "" but has space for 2 chars: */ \
485 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
489 static void edit_file(char *); // edit one file
490 static void do_cmd(int); // execute a command
491 static int next_tabstop(int);
492 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
493 static char *begin_line(char *); // return pointer to cur line B-o-l
494 static char *end_line(char *); // return pointer to cur line E-o-l
495 static char *prev_line(char *); // return pointer to prev line B-o-l
496 static char *next_line(char *); // return pointer to next line B-o-l
497 static char *end_screen(void); // get pointer to last char on screen
498 static int count_lines(char *, char *); // count line from start to stop
499 static char *find_line(int); // find beginning of line #li
500 static char *move_to_col(char *, int); // move "p" to column l
501 static void dot_left(void); // move dot left- dont leave line
502 static void dot_right(void); // move dot right- dont leave line
503 static void dot_begin(void); // move dot to B-o-l
504 static void dot_end(void); // move dot to E-o-l
505 static void dot_next(void); // move dot to next line B-o-l
506 static void dot_prev(void); // move dot to prev line B-o-l
507 static void dot_scroll(int, int); // move the screen up or down
508 static void dot_skip_over_ws(void); // move dot pat WS
509 static char *bound_dot(char *); // make sure text[0] <= P < "end"
510 static char *new_screen(int, int); // malloc virtual screen memory
511 #if !ENABLE_FEATURE_VI_UNDO
512 #define char_insert(a,b,c) char_insert(a,b)
514 static char *char_insert(char *, char, int); // insert the char c at 'p'
515 // might reallocate text[]! use p += stupid_insert(p, ...),
516 // and be careful to not use pointers into potentially freed text[]!
517 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
518 static int find_range(char **, char **, char); // return pointers for an object
519 static int st_test(char *, int, int, char *); // helper for skip_thing()
520 static char *skip_thing(char *, int, int, int); // skip some object
521 static char *find_pair(char *, char); // find matching pair () [] {}
522 #if !ENABLE_FEATURE_VI_UNDO
523 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
525 static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
526 // might reallocate text[]! use p += text_hole_make(p, ...),
527 // and be careful to not use pointers into potentially freed text[]!
528 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
529 #if !ENABLE_FEATURE_VI_UNDO
530 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
532 static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
533 static void show_help(void); // display some help info
534 static void rawmode(void); // set "raw" mode on tty
535 static void cookmode(void); // return to "cooked" mode on tty
536 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
537 static int mysleep(int);
538 static int readit(void); // read (maybe cursor) key from stdin
539 static int get_one_char(void); // read 1 char from stdin
540 // file_insert might reallocate text[]!
541 static int file_insert(const char *, char *, int);
542 static int file_write(char *, char *, char *);
543 static void place_cursor(int, int);
544 static void screen_erase(void);
545 static void clear_to_eol(void);
546 static void clear_to_eos(void);
547 static void go_bottom_and_clear_to_eol(void);
548 static void standout_start(void); // send "start reverse video" sequence
549 static void standout_end(void); // send "end reverse video" sequence
550 static void flash(int); // flash the terminal screen
551 static void show_status_line(void); // put a message on the bottom line
552 static void status_line(const char *, ...); // print to status buf
553 static void status_line_bold(const char *, ...);
554 static void status_line_bold_errno(const char *fn);
555 static void not_implemented(const char *); // display "Not implemented" message
556 static int format_edit_status(void); // format file status on status line
557 static void redraw(int); // force a full screen refresh
558 static char* format_line(char* /*, int*/);
559 static void refresh(int); // update the terminal from screen[]
561 static void indicate_error(void); // use flash or beep to indicate error
562 static void Hit_Return(void);
564 #if ENABLE_FEATURE_VI_SEARCH
565 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
567 #if ENABLE_FEATURE_VI_COLON
568 static char *get_one_address(char *, int *); // get colon addr, if present
569 static char *get_address(char *, int *, int *); // get two colon addrs, if present
571 static void colon(char *); // execute the "colon" mode cmds
572 #if ENABLE_FEATURE_VI_USE_SIGNALS
573 static void winch_sig(int); // catch window size changes
574 static void suspend_sig(int); // catch ctrl-Z
575 static void catch_sig(int); // catch ctrl-C and alarm time-outs
577 #if ENABLE_FEATURE_VI_DOT_CMD
578 static void start_new_cmd_q(char); // new queue for command
579 static void end_cmd_q(void); // stop saving input chars
581 #define end_cmd_q() ((void)0)
583 #if ENABLE_FEATURE_VI_SETOPTS
584 static void showmatching(char *); // show the matching pair () [] {}
586 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
587 // might reallocate text[]! use p += string_insert(p, ...),
588 // and be careful to not use pointers into potentially freed text[]!
589 # if !ENABLE_FEATURE_VI_UNDO
590 #define string_insert(a,b,c) string_insert(a,b)
592 static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
594 #if ENABLE_FEATURE_VI_YANKMARK
595 static char *text_yank(char *, char *, int); // save copy of "p" into a register
596 static char what_reg(void); // what is letter of current YDreg
597 static void check_context(char); // remember context for '' command
599 #if ENABLE_FEATURE_VI_UNDO
600 static void flush_undo_data(void);
601 static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
602 static void undo_pop(void); // Undo the last operation
603 # if ENABLE_FEATURE_VI_UNDO_QUEUE
604 static void undo_queue_commit(void); // Flush any queued objects to the undo stack
606 # define undo_queue_commit() ((void)0)
609 #define flush_undo_data() ((void)0)
610 #define undo_queue_commit() ((void)0)
613 #if ENABLE_FEATURE_VI_CRASHME
614 static void crash_dummy();
615 static void crash_test();
616 static int crashme = 0;
619 static void write1(const char *out)
624 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
625 int vi_main(int argc, char **argv)
631 #if ENABLE_FEATURE_VI_UNDO
632 /* undo_stack_tail = NULL; - already is */
633 #if ENABLE_FEATURE_VI_UNDO_QUEUE
634 undo_queue_state = UNDO_EMPTY;
635 /* undo_q = 0; - already is */
639 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
642 #if ENABLE_FEATURE_VI_CRASHME
643 srand((long) my_pid);
645 #ifdef NO_SUCH_APPLET_YET
646 /* If we aren't "vi", we are "view" */
647 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
648 SET_READONLY_MODE(readonly_mode);
652 // autoindent is not default in vim 7.3
653 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
654 // 1- process $HOME/.exrc file (not inplemented yet)
655 // 2- process EXINIT variable from environment
656 // 3- process command line args
657 #if ENABLE_FEATURE_VI_COLON
659 char *p = getenv("EXINIT");
661 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
664 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
666 #if ENABLE_FEATURE_VI_CRASHME
671 #if ENABLE_FEATURE_VI_READONLY
672 case 'R': // Read-only flag
673 SET_READONLY_MODE(readonly_mode);
676 #if ENABLE_FEATURE_VI_COLON
677 case 'c': // cmd line vi command
679 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
691 // The argv array can be used by the ":next" and ":rewind" commands
695 //----- This is the main file handling loop --------------
698 // "Save cursor, use alternate screen buffer, clear screen"
699 write1("\033[?1049h");
701 edit_file(argv[optind]); /* param might be NULL */
702 if (++optind >= argc)
705 // "Use normal screen buffer, restore cursor"
706 write1("\033[?1049l");
707 //-----------------------------------------------------------
712 /* read text from file or create an empty buf */
713 /* will also update current_filename */
714 static int init_text_buffer(char *fn)
718 /* allocate/reallocate text buffer */
721 screenbegin = dot = end = text = xzalloc(text_size);
723 if (fn != current_filename) {
724 free(current_filename);
725 current_filename = xstrdup(fn);
727 rc = file_insert(fn, text, 1);
729 // file doesnt exist. Start empty buf with dummy line
730 char_insert(text, '\n', NO_UNDO);
735 last_modified_count = -1;
736 #if ENABLE_FEATURE_VI_YANKMARK
738 memset(mark, 0, sizeof(mark));
743 #if ENABLE_FEATURE_VI_WIN_RESIZE
744 static int query_screen_dimensions(void)
746 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
747 if (rows > MAX_SCR_ROWS)
749 if (columns > MAX_SCR_COLS)
750 columns = MAX_SCR_COLS;
754 # define query_screen_dimensions() (0)
757 static void edit_file(char *fn)
759 #if ENABLE_FEATURE_VI_YANKMARK
760 #define cur_line edit_file__cur_line
763 #if ENABLE_FEATURE_VI_USE_SIGNALS
767 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
771 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
772 #if ENABLE_FEATURE_VI_ASK_TERMINAL
773 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
775 write1("\033[999;999H" "\033[6n");
777 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
778 if ((int32_t)k == KEYCODE_CURSOR_POS) {
779 uint32_t rc = (k >> 32);
780 columns = (rc & 0x7fff);
781 if (columns > MAX_SCR_COLS)
782 columns = MAX_SCR_COLS;
783 rows = ((rc >> 16) & 0x7fff);
784 if (rows > MAX_SCR_ROWS)
789 new_screen(rows, columns); // get memory for virtual screen
790 init_text_buffer(fn);
792 #if ENABLE_FEATURE_VI_YANKMARK
793 YDreg = 26; // default Yank/Delete reg
794 Ureg = 27; // hold orig line for "U" cmd
795 mark[26] = mark[27] = text; // init "previous context"
798 last_forward_char = last_input_char = '\0';
802 #if ENABLE_FEATURE_VI_USE_SIGNALS
803 signal(SIGINT, catch_sig);
804 signal(SIGWINCH, winch_sig);
805 signal(SIGTSTP, suspend_sig);
806 sig = sigsetjmp(restart, 1);
808 screenbegin = dot = text;
812 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
815 offset = 0; // no horizontal offset
817 #if ENABLE_FEATURE_VI_DOT_CMD
819 ioq = ioq_start = NULL;
824 #if ENABLE_FEATURE_VI_COLON
829 while ((p = initial_cmds[n]) != NULL) {
839 free(initial_cmds[n]);
840 initial_cmds[n] = NULL;
845 redraw(FALSE); // dont force every col re-draw
846 //------This is the main Vi cmd handling loop -----------------------
847 while (editing > 0) {
848 #if ENABLE_FEATURE_VI_CRASHME
850 if ((end - text) > 1) {
851 crash_dummy(); // generate a random command
854 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
860 last_input_char = c = get_one_char(); // get a cmd from user
861 #if ENABLE_FEATURE_VI_YANKMARK
862 // save a copy of the current line- for the 'U" command
863 if (begin_line(dot) != cur_line) {
864 cur_line = begin_line(dot);
865 text_yank(begin_line(dot), end_line(dot), Ureg);
868 #if ENABLE_FEATURE_VI_DOT_CMD
869 // These are commands that change text[].
870 // Remember the input for the "." command
871 if (!adding2q && ioq_start == NULL
872 && cmd_mode == 0 // command mode
873 && c > '\0' // exclude NUL and non-ASCII chars
874 && c < 0x7f // (Unicode and such)
875 && strchr(modifying_cmds, c)
880 do_cmd(c); // execute the user command
882 // poll to see if there is input already waiting. if we are
883 // not able to display output fast enough to keep up, skip
884 // the display update until we catch up with input.
885 if (!readbuffer[0] && mysleep(0) == 0) {
886 // no input pending - so update output
890 #if ENABLE_FEATURE_VI_CRASHME
892 crash_test(); // test editor variables
895 //-------------------------------------------------------------------
897 go_bottom_and_clear_to_eol();
902 //----- The Colon commands -------------------------------------
903 #if ENABLE_FEATURE_VI_COLON
904 static char *get_one_address(char *p, int *addr) // get colon addr, if present
908 IF_FEATURE_VI_YANKMARK(char c;)
909 IF_FEATURE_VI_SEARCH(char *pat;)
911 *addr = -1; // assume no addr
912 if (*p == '.') { // the current line
915 *addr = count_lines(text, q);
917 #if ENABLE_FEATURE_VI_YANKMARK
918 else if (*p == '\'') { // is this a mark addr
922 if (c >= 'a' && c <= 'z') {
925 q = mark[(unsigned char) c];
926 if (q != NULL) { // is mark valid
927 *addr = count_lines(text, q);
932 #if ENABLE_FEATURE_VI_SEARCH
933 else if (*p == '/') { // a search pattern
934 q = strchrnul(++p, '/');
935 pat = xstrndup(p, q - p); // save copy of pattern
939 q = char_search(dot, pat, FORWARD, FULL);
941 *addr = count_lines(text, q);
946 else if (*p == '$') { // the last line in file
948 q = begin_line(end - 1);
949 *addr = count_lines(text, q);
950 } else if (isdigit(*p)) { // specific line number
951 sscanf(p, "%d%n", addr, &st);
954 // unrecognized address - assume -1
960 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
962 //----- get the address' i.e., 1,3 'a,'b -----
963 // get FIRST addr, if present
965 p++; // skip over leading spaces
966 if (*p == '%') { // alias for 1,$
969 *e = count_lines(text, end-1);
972 p = get_one_address(p, b);
975 if (*p == ',') { // is there a address separator
979 // get SECOND addr, if present
980 p = get_one_address(p, e);
984 p++; // skip over trailing spaces
988 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
989 static void setops(const char *args, const char *opname, int flg_no,
990 const char *short_opname, int opt)
992 const char *a = args + flg_no;
993 int l = strlen(opname) - 1; /* opname have + ' ' */
995 // maybe strncmp? we had tons of erroneous strncasecmp's...
996 if (strncasecmp(a, opname, l) == 0
997 || strncasecmp(a, short_opname, 2) == 0
1007 #endif /* FEATURE_VI_COLON */
1009 // buf must be no longer than MAX_INPUT_LEN!
1010 static void colon(char *buf)
1012 #if !ENABLE_FEATURE_VI_COLON
1013 /* Simple ":cmd" handler with minimal set of commands */
1022 if (strncmp(p, "quit", cnt) == 0
1023 || strncmp(p, "q!", cnt) == 0
1025 if (modified_count && p[1] != '!') {
1026 status_line_bold("No write since last change (:%s! overrides)", p);
1032 if (strncmp(p, "write", cnt) == 0
1033 || strncmp(p, "wq", cnt) == 0
1034 || strncmp(p, "wn", cnt) == 0
1035 || (p[0] == 'x' && !p[1])
1037 if (modified_count != 0 || p[0] != 'x') {
1038 cnt = file_write(current_filename, text, end - 1);
1042 status_line_bold("Write error: %s", strerror(errno));
1045 last_modified_count = -1;
1046 status_line("'%s' %dL, %dC",
1048 count_lines(text, end - 1), cnt
1051 || p[1] == 'q' || p[1] == 'n'
1052 || p[1] == 'Q' || p[1] == 'N'
1059 if (strncmp(p, "file", cnt) == 0) {
1060 last_status_cksum = 0; // force status update
1063 if (sscanf(p, "%d", &cnt) > 0) {
1064 dot = find_line(cnt);
1071 char c, *orig_buf, *buf1, *q, *r;
1072 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
1076 // :3154 // if (-e line 3154) goto it else stay put
1077 // :4,33w! foo // write a portion of buffer to file "foo"
1078 // :w // write all of buffer to current file
1080 // :q! // quit- dont care about modified file
1081 // :'a,'z!sort -u // filter block through sort
1082 // :'f // goto mark "f"
1083 // :'fl // list literal the mark "f" line
1084 // :.r bar // read file "bar" into buffer before dot
1085 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1086 // :/xyz/ // goto the "xyz" line
1087 // :s/find/replace/ // substitute pattern "find" with "replace"
1088 // :!<cmd> // run <cmd> then return
1094 buf++; // move past the ':'
1098 q = text; // assume 1,$ for the range
1100 li = count_lines(text, end - 1);
1101 fn = current_filename;
1103 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1104 buf = get_address(buf, &b, &e);
1106 // remember orig command line
1109 // get the COMMAND into cmd[]
1111 while (*buf != '\0') {
1117 // get any ARGuments
1118 while (isblank(*buf))
1122 buf1 = last_char_is(cmd, '!');
1125 *buf1 = '\0'; // get rid of !
1128 // if there is only one addr, then the addr
1129 // is the line number of the single line the
1130 // user wants. So, reset the end
1131 // pointer to point at end of the "b" line
1132 q = find_line(b); // what line is #b
1137 // we were given two addrs. change the
1138 // end pointer to the addr given by user.
1139 r = find_line(e); // what line is #e
1143 // ------------ now look for the command ------------
1145 if (i == 0) { // :123CR goto line #123
1147 dot = find_line(b); // what line is #b
1151 #if ENABLE_FEATURE_ALLOW_EXEC
1152 else if (cmd[0] == '!') { // run a cmd
1154 // :!ls run the <cmd>
1155 go_bottom_and_clear_to_eol();
1157 retcode = system(orig_buf + 1); // run the cmd
1159 printf("\nshell returned %i\n\n", retcode);
1161 Hit_Return(); // let user see results
1164 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1165 if (b < 0) { // no addr given- use defaults
1166 b = e = count_lines(text, dot);
1168 status_line("%d", b);
1169 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1170 if (b < 0) { // no addr given- use defaults
1171 q = begin_line(dot); // assume .,. for the range
1174 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
1176 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1179 // don't edit, if the current file has been modified
1180 if (modified_count && !useforce) {
1181 status_line_bold("No write since last change (:%s! overrides)", cmd);
1185 // the user supplied a file name
1187 } else if (current_filename && current_filename[0]) {
1188 // no user supplied name- use the current filename
1189 // fn = current_filename; was set by default
1191 // no user file name, no current name- punt
1192 status_line_bold("No current filename");
1196 size = init_text_buffer(fn);
1198 #if ENABLE_FEATURE_VI_YANKMARK
1199 if (Ureg >= 0 && Ureg < 28) {
1200 free(reg[Ureg]); // free orig line reg- for 'U'
1203 if (YDreg >= 0 && YDreg < 28) {
1204 free(reg[YDreg]); // free default yank/delete register
1208 // how many lines in text[]?
1209 li = count_lines(text, end - 1);
1210 status_line("'%s'%s"
1211 IF_FEATURE_VI_READONLY("%s")
1214 (size < 0 ? " [New file]" : ""),
1215 IF_FEATURE_VI_READONLY(
1216 ((readonly_mode) ? " [Readonly]" : ""),
1218 li, (int)(end - text)
1220 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1221 if (b != -1 || e != -1) {
1222 status_line_bold("No address allowed on this command");
1226 // user wants a new filename
1227 free(current_filename);
1228 current_filename = xstrdup(args);
1230 // user wants file status info
1231 last_status_cksum = 0; // force status update
1233 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1234 // print out values of all features
1235 go_bottom_and_clear_to_eol();
1240 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1241 if (b < 0) { // no addr given- use defaults
1242 q = begin_line(dot); // assume .,. for the range
1245 go_bottom_and_clear_to_eol();
1247 for (; q <= r; q++) {
1251 c_is_no_print = (c & 0x80) && !Isprint(c);
1252 if (c_is_no_print) {
1258 } else if (c < ' ' || c == 127) {
1270 } else if (strncmp(cmd, "quit", i) == 0 // quit
1271 || strncmp(cmd, "next", i) == 0 // edit next file
1272 || strncmp(cmd, "prev", i) == 0 // edit previous file
1277 // force end of argv list
1283 // don't exit if the file been modified
1284 if (modified_count) {
1285 status_line_bold("No write since last change (:%s! overrides)", cmd);
1288 // are there other file to edit
1289 n = save_argc - optind - 1;
1290 if (*cmd == 'q' && n > 0) {
1291 status_line_bold("%d more file(s) to edit", n);
1294 if (*cmd == 'n' && n <= 0) {
1295 status_line_bold("No more files to edit");
1299 // are there previous files to edit
1301 status_line_bold("No previous files to edit");
1307 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1312 status_line_bold("No filename given");
1315 if (b < 0) { // no addr given- use defaults
1316 q = begin_line(dot); // assume "dot"
1318 // read after current line- unless user said ":0r foo"
1321 // read after last line
1325 { // dance around potentially-reallocated text[]
1326 uintptr_t ofs = q - text;
1327 size = file_insert(fn, q, 0);
1331 goto ret; // nothing was inserted
1332 // how many lines in text[]?
1333 li = count_lines(q, q + size - 1);
1335 IF_FEATURE_VI_READONLY("%s")
1338 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1342 // if the insert is before "dot" then we need to update
1346 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1347 if (modified_count && !useforce) {
1348 status_line_bold("No write since last change (:%s! overrides)", cmd);
1350 // reset the filenames to edit
1351 optind = -1; /* start from 0th file */
1354 #if ENABLE_FEATURE_VI_SET
1355 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1356 #if ENABLE_FEATURE_VI_SETOPTS
1359 i = 0; // offset into args
1360 // only blank is regarded as args delimiter. What about tab '\t'?
1361 if (!args[0] || strcasecmp(args, "all") == 0) {
1362 // print out values of all options
1363 #if ENABLE_FEATURE_VI_SETOPTS
1370 autoindent ? "" : "no",
1371 err_method ? "" : "no",
1372 ignorecase ? "" : "no",
1373 showmatch ? "" : "no",
1379 #if ENABLE_FEATURE_VI_SETOPTS
1382 if (strncmp(argp, "no", 2) == 0)
1383 i = 2; // ":set noautoindent"
1384 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1385 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1386 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1387 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1388 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1390 sscanf(argp + i+8, "%u", &t);
1391 if (t > 0 && t <= MAX_TABSTOP)
1394 argp = skip_non_whitespace(argp);
1395 argp = skip_whitespace(argp);
1397 #endif /* FEATURE_VI_SETOPTS */
1398 #endif /* FEATURE_VI_SET */
1399 #if ENABLE_FEATURE_VI_SEARCH
1400 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1401 char *F, *R, *flags;
1402 size_t len_F, len_R;
1403 int gflag; // global replace flag
1404 #if ENABLE_FEATURE_VI_UNDO
1405 int dont_chain_first_item = ALLOW_UNDO;
1408 // F points to the "find" pattern
1409 // R points to the "replace" pattern
1410 // replace the cmd line delimiters "/" with NULs
1411 c = orig_buf[1]; // what is the delimiter
1412 F = orig_buf + 2; // start of "find"
1413 R = strchr(F, c); // middle delimiter
1417 *R++ = '\0'; // terminate "find"
1418 flags = strchr(R, c);
1422 *flags++ = '\0'; // terminate "replace"
1426 if (b < 0) { // maybe :s/foo/bar/
1427 q = begin_line(dot); // start with cur line
1428 b = count_lines(text, q); // cur line number
1431 e = b; // maybe :.s/foo/bar/
1433 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1434 char *ls = q; // orig line start
1437 found = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1440 // we found the "find" pattern - delete it
1441 // For undo support, the first item should not be chained
1442 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
1443 #if ENABLE_FEATURE_VI_UNDO
1444 dont_chain_first_item = ALLOW_UNDO_CHAIN;
1446 // insert the "replace" patern
1447 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
1450 /*q += bias; - recalculated anyway */
1451 // check for "global" :s/foo/bar/g
1453 if ((found + len_R) < end_line(ls)) {
1455 goto vc4; // don't let q move past cur line
1461 #endif /* FEATURE_VI_SEARCH */
1462 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1463 status_line(BB_VER);
1464 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1465 || strncmp(cmd, "wq", i) == 0
1466 || strncmp(cmd, "wn", i) == 0
1467 || (cmd[0] == 'x' && !cmd[1])
1470 //int forced = FALSE;
1472 // is there a file name to write to?
1476 #if ENABLE_FEATURE_VI_READONLY
1477 if (readonly_mode && !useforce) {
1478 status_line_bold("'%s' is read only", fn);
1483 // if "fn" is not write-able, chmod u+w
1484 // sprintf(syscmd, "chmod u+w %s", fn);
1488 if (modified_count != 0 || cmd[0] != 'x') {
1490 l = file_write(fn, q, r);
1495 //if (useforce && forced) {
1497 // sprintf(syscmd, "chmod u-w %s", fn);
1503 status_line_bold_errno(fn);
1505 // how many lines written
1506 li = count_lines(q, q + l - 1);
1507 status_line("'%s' %dL, %dC", fn, li, l);
1509 if (q == text && q + l == end) {
1511 last_modified_count = -1;
1514 || cmd[1] == 'q' || cmd[1] == 'n'
1515 || cmd[1] == 'Q' || cmd[1] == 'N'
1521 #if ENABLE_FEATURE_VI_YANKMARK
1522 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1523 if (b < 0) { // no addr given- use defaults
1524 q = begin_line(dot); // assume .,. for the range
1527 text_yank(q, r, YDreg);
1528 li = count_lines(q, r);
1529 status_line("Yank %d lines (%d chars) into [%c]",
1530 li, strlen(reg[YDreg]), what_reg());
1534 not_implemented(cmd);
1537 dot = bound_dot(dot); // make sure "dot" is valid
1539 #if ENABLE_FEATURE_VI_SEARCH
1541 status_line(":s expression missing delimiters");
1543 #endif /* FEATURE_VI_COLON */
1546 static void Hit_Return(void)
1551 write1("[Hit return to continue]");
1553 while ((c = get_one_char()) != '\n' && c != '\r')
1555 redraw(TRUE); // force redraw all
1558 static int next_tabstop(int col)
1560 return col + ((tabstop - 1) - (col % tabstop));
1563 //----- Synchronize the cursor to Dot --------------------------
1564 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1566 char *beg_cur; // begin and end of "d" line
1570 beg_cur = begin_line(d); // first char of cur line
1572 if (beg_cur < screenbegin) {
1573 // "d" is before top line on screen
1574 // how many lines do we have to move
1575 cnt = count_lines(beg_cur, screenbegin);
1577 screenbegin = beg_cur;
1578 if (cnt > (rows - 1) / 2) {
1579 // we moved too many lines. put "dot" in middle of screen
1580 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1581 screenbegin = prev_line(screenbegin);
1585 char *end_scr; // begin and end of screen
1586 end_scr = end_screen(); // last char of screen
1587 if (beg_cur > end_scr) {
1588 // "d" is after bottom line on screen
1589 // how many lines do we have to move
1590 cnt = count_lines(end_scr, beg_cur);
1591 if (cnt > (rows - 1) / 2)
1592 goto sc1; // too many lines
1593 for (ro = 0; ro < cnt - 1; ro++) {
1594 // move screen begin the same amount
1595 screenbegin = next_line(screenbegin);
1596 // now, move the end of screen
1597 end_scr = next_line(end_scr);
1598 end_scr = end_line(end_scr);
1602 // "d" is on screen- find out which row
1604 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1610 // find out what col "d" is on
1612 while (tp < d) { // drive "co" to correct column
1613 if (*tp == '\n') //vda || *tp == '\0')
1616 // handle tabs like real vi
1617 if (d == tp && cmd_mode) {
1620 co = next_tabstop(co);
1621 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1622 co++; // display as ^X, use 2 columns
1628 // "co" is the column where "dot" is.
1629 // The screen has "columns" columns.
1630 // The currently displayed columns are 0+offset -- columns+ofset
1631 // |-------------------------------------------------------------|
1633 // offset | |------- columns ----------------|
1635 // If "co" is already in this range then we do not have to adjust offset
1636 // but, we do have to subtract the "offset" bias from "co".
1637 // If "co" is outside this range then we have to change "offset".
1638 // If the first char of a line is a tab the cursor will try to stay
1639 // in column 7, but we have to set offset to 0.
1641 if (co < 0 + offset) {
1644 if (co >= columns + offset) {
1645 offset = co - columns + 1;
1647 // if the first char of the line is a tab, and "dot" is sitting on it
1648 // force offset to 0.
1649 if (d == beg_cur && *d == '\t') {
1658 //----- Text Movement Routines ---------------------------------
1659 static char *begin_line(char *p) // return pointer to first char cur line
1662 p = memrchr(text, '\n', p - text);
1670 static char *end_line(char *p) // return pointer to NL of cur line
1673 p = memchr(p, '\n', end - p - 1);
1680 static char *dollar_line(char *p) // return pointer to just before NL line
1683 // Try to stay off of the Newline
1684 if (*p == '\n' && (p - begin_line(p)) > 0)
1689 static char *prev_line(char *p) // return pointer first char prev line
1691 p = begin_line(p); // goto beginning of cur line
1692 if (p > text && p[-1] == '\n')
1693 p--; // step to prev line
1694 p = begin_line(p); // goto beginning of prev line
1698 static char *next_line(char *p) // return pointer first char next line
1701 if (p < end - 1 && *p == '\n')
1702 p++; // step to next line
1706 //----- Text Information Routines ------------------------------
1707 static char *end_screen(void)
1712 // find new bottom line
1714 for (cnt = 0; cnt < rows - 2; cnt++)
1720 // count line from start to stop
1721 static int count_lines(char *start, char *stop)
1726 if (stop < start) { // start and stop are backwards- reverse them
1732 stop = end_line(stop);
1733 while (start <= stop && start <= end - 1) {
1734 start = end_line(start);
1742 static char *find_line(int li) // find beginning of line #li
1746 for (q = text; li > 1; li--) {
1752 //----- Dot Movement Routines ----------------------------------
1753 static void dot_left(void)
1755 undo_queue_commit();
1756 if (dot > text && dot[-1] != '\n')
1760 static void dot_right(void)
1762 undo_queue_commit();
1763 if (dot < end - 1 && *dot != '\n')
1767 static void dot_begin(void)
1769 undo_queue_commit();
1770 dot = begin_line(dot); // return pointer to first char cur line
1773 static void dot_end(void)
1775 undo_queue_commit();
1776 dot = end_line(dot); // return pointer to last char cur line
1779 static char *move_to_col(char *p, int l)
1785 while (co < l && p < end) {
1786 if (*p == '\n') //vda || *p == '\0')
1789 co = next_tabstop(co);
1790 } else if (*p < ' ' || *p == 127) {
1791 co++; // display as ^X, use 2 columns
1799 static void dot_next(void)
1801 undo_queue_commit();
1802 dot = next_line(dot);
1805 static void dot_prev(void)
1807 undo_queue_commit();
1808 dot = prev_line(dot);
1811 static void dot_scroll(int cnt, int dir)
1815 undo_queue_commit();
1816 for (; cnt > 0; cnt--) {
1819 // ctrl-Y scroll up one line
1820 screenbegin = prev_line(screenbegin);
1823 // ctrl-E scroll down one line
1824 screenbegin = next_line(screenbegin);
1827 // make sure "dot" stays on the screen so we dont scroll off
1828 if (dot < screenbegin)
1830 q = end_screen(); // find new bottom line
1832 dot = begin_line(q); // is dot is below bottom line?
1836 static void dot_skip_over_ws(void)
1839 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1843 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1845 if (p >= end && end > text) {
1856 //----- Helper Utility Routines --------------------------------
1858 //----------------------------------------------------------------
1859 //----- Char Routines --------------------------------------------
1860 /* Chars that are part of a word-
1861 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1862 * Chars that are Not part of a word (stoppers)
1863 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1864 * Chars that are WhiteSpace
1865 * TAB NEWLINE VT FF RETURN SPACE
1866 * DO NOT COUNT NEWLINE AS WHITESPACE
1869 static char *new_screen(int ro, int co)
1874 screensize = ro * co + 8;
1875 screen = xmalloc(screensize);
1876 // initialize the new screen. assume this will be a empty file.
1878 // non-existent text[] lines start with a tilde (~).
1879 for (li = 1; li < ro - 1; li++) {
1880 screen[(li * co) + 0] = '~';
1885 #if ENABLE_FEATURE_VI_SEARCH
1887 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1889 // search for pattern starting at p
1890 static char *char_search(char *p, const char *pat, int dir, int range)
1892 struct re_pattern_buffer preg;
1898 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1900 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1902 memset(&preg, 0, sizeof(preg));
1903 err = re_compile_pattern(pat, strlen(pat), &preg);
1905 status_line_bold("bad search pattern '%s': %s", pat, err);
1909 // assume a LIMITED forward search
1913 // RANGE could be negative if we are searching backwards
1923 // search for the compiled pattern, preg, in p[]
1924 // range < 0: search backward
1925 // range > 0: search forward
1927 // re_search() < 0: not found or error
1928 // re_search() >= 0: index of found pattern
1929 // struct pattern char int int int struct reg
1930 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1931 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1944 # if ENABLE_FEATURE_VI_SETOPTS
1945 static int mycmp(const char *s1, const char *s2, int len)
1948 return strncasecmp(s1, s2, len);
1950 return strncmp(s1, s2, len);
1953 # define mycmp strncmp
1956 static char *char_search(char *p, const char *pat, int dir, int range)
1962 if (dir == FORWARD) {
1963 stop = end - 1; // assume range is p..end-1
1964 if (range == LIMITED)
1965 stop = next_line(p); // range is to next line
1966 for (start = p; start < stop; start++) {
1967 if (mycmp(start, pat, len) == 0) {
1971 } else if (dir == BACK) {
1972 stop = text; // assume range is text..p
1973 if (range == LIMITED)
1974 stop = prev_line(p); // range is to prev line
1975 for (start = p - len; start >= stop; start--) {
1976 if (mycmp(start, pat, len) == 0) {
1981 // pattern not found
1987 #endif /* FEATURE_VI_SEARCH */
1989 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1991 if (c == 22) { // Is this an ctrl-V?
1992 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1993 refresh(FALSE); // show the ^
1996 #if ENABLE_FEATURE_VI_UNDO
1999 undo_push(p, 1, UNDO_INS);
2001 case ALLOW_UNDO_CHAIN:
2002 undo_push(p, 1, UNDO_INS_CHAIN);
2004 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2005 case ALLOW_UNDO_QUEUED:
2006 undo_push(p, 1, UNDO_INS_QUEUED);
2012 #endif /* ENABLE_FEATURE_VI_UNDO */
2014 } else if (c == 27) { // Is this an ESC?
2016 undo_queue_commit();
2018 end_cmd_q(); // stop adding to q
2019 last_status_cksum = 0; // force status update
2020 if ((p[-1] != '\n') && (dot > text)) {
2023 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
2026 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2029 // insert a char into text[]
2031 c = '\n'; // translate \r to \n
2032 #if ENABLE_FEATURE_VI_UNDO
2033 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2035 undo_queue_commit();
2039 undo_push(p, 1, UNDO_INS);
2041 case ALLOW_UNDO_CHAIN:
2042 undo_push(p, 1, UNDO_INS_CHAIN);
2044 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2045 case ALLOW_UNDO_QUEUED:
2046 undo_push(p, 1, UNDO_INS_QUEUED);
2052 #endif /* ENABLE_FEATURE_VI_UNDO */
2053 p += 1 + stupid_insert(p, c); // insert the char
2054 #if ENABLE_FEATURE_VI_SETOPTS
2055 if (showmatch && strchr(")]}", c) != NULL) {
2056 showmatching(p - 1);
2058 if (autoindent && c == '\n') { // auto indent the new line
2061 q = prev_line(p); // use prev line as template
2062 len = strspn(q, " \t"); // space or tab
2065 bias = text_hole_make(p, len);
2068 #if ENABLE_FEATURE_VI_UNDO
2069 undo_push(p, len, UNDO_INS);
2080 // might reallocate text[]! use p += stupid_insert(p, ...),
2081 // and be careful to not use pointers into potentially freed text[]!
2082 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2085 bias = text_hole_make(p, 1);
2091 static int find_range(char **start, char **stop, char c)
2093 char *save_dot, *p, *q, *t;
2094 int cnt, multiline = 0;
2099 if (strchr("cdy><", c)) {
2100 // these cmds operate on whole lines
2101 p = q = begin_line(p);
2102 for (cnt = 1; cnt < cmdcnt; cnt++) {
2106 } else if (strchr("^%$0bBeEfth\b\177", c)) {
2107 // These cmds operate on char positions
2108 do_cmd(c); // execute movement cmd
2110 } else if (strchr("wW", c)) {
2111 do_cmd(c); // execute movement cmd
2112 // if we are at the next word's first char
2113 // step back one char
2114 // but check the possibilities when it is true
2115 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
2116 || (ispunct(dot[-1]) && !ispunct(dot[0]))
2117 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
2118 dot--; // move back off of next word
2119 if (dot > text && *dot == '\n')
2120 dot--; // stay off NL
2122 } else if (strchr("H-k{", c)) {
2123 // these operate on multi-lines backwards
2124 q = end_line(dot); // find NL
2125 do_cmd(c); // execute movement cmd
2128 } else if (strchr("L+j}\r\n", c)) {
2129 // these operate on multi-lines forwards
2130 p = begin_line(dot);
2131 do_cmd(c); // execute movement cmd
2132 dot_end(); // find NL
2135 // nothing -- this causes any other values of c to
2136 // represent the one-character range under the
2137 // cursor. this is correct for ' ' and 'l', but
2138 // perhaps no others.
2147 // backward char movements don't include start position
2148 if (q > p && strchr("^0bBh\b\177", c)) q--;
2151 for (t = p; t <= q; t++) {
2164 static int st_test(char *p, int type, int dir, char *tested)
2174 if (type == S_BEFORE_WS) {
2176 test = (!isspace(c) || c == '\n');
2178 if (type == S_TO_WS) {
2180 test = (!isspace(c) || c == '\n');
2182 if (type == S_OVER_WS) {
2186 if (type == S_END_PUNCT) {
2190 if (type == S_END_ALNUM) {
2192 test = (isalnum(c) || c == '_');
2198 static char *skip_thing(char *p, int linecnt, int dir, int type)
2202 while (st_test(p, type, dir, &c)) {
2203 // make sure we limit search to correct number of lines
2204 if (c == '\n' && --linecnt < 1)
2206 if (dir >= 0 && p >= end - 1)
2208 if (dir < 0 && p <= text)
2210 p += dir; // move to next char
2215 // find matching char of pair () [] {}
2216 // will crash if c is not one of these
2217 static char *find_pair(char *p, const char c)
2219 const char *braces = "()[]{}";
2223 dir = strchr(braces, c) - braces;
2225 match = braces[dir];
2226 dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
2228 // look for match, count levels of pairs (( ))
2232 if (p < text || p >= end)
2235 level++; // increase pair levels
2237 level--; // reduce pair level
2239 return p; // found matching pair
2244 #if ENABLE_FEATURE_VI_SETOPTS
2245 // show the matching char of a pair, () [] {}
2246 static void showmatching(char *p)
2250 // we found half of a pair
2251 q = find_pair(p, *p); // get loc of matching char
2253 indicate_error(); // no matching char
2255 // "q" now points to matching pair
2256 save_dot = dot; // remember where we are
2257 dot = q; // go to new loc
2258 refresh(FALSE); // let the user see it
2259 mysleep(40); // give user some time
2260 dot = save_dot; // go back to old loc
2264 #endif /* FEATURE_VI_SETOPTS */
2266 #if ENABLE_FEATURE_VI_UNDO
2267 static void flush_undo_data(void)
2269 struct undo_object *undo_entry;
2271 while (undo_stack_tail) {
2272 undo_entry = undo_stack_tail;
2273 undo_stack_tail = undo_entry->prev;
2278 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2279 static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack
2281 struct undo_object *undo_entry;
2284 // UNDO_INS: insertion, undo will remove from buffer
2285 // UNDO_DEL: deleted text, undo will restore to buffer
2286 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2287 // The CHAIN operations are for handling multiple operations that the user
2288 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2289 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2290 // for the INS/DEL operation. The raw values should be equal to the values of
2291 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2293 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2294 // This undo queuing functionality groups multiple character typing or backspaces
2295 // into a single large undo object. This greatly reduces calls to malloc() for
2296 // single-character operations while typing and has the side benefit of letting
2297 // an undo operation remove chunks of text rather than a single character.
2299 case UNDO_EMPTY: // Just in case this ever happens...
2301 case UNDO_DEL_QUEUED:
2303 return; // Only queue single characters
2304 switch (undo_queue_state) {
2306 undo_queue_state = UNDO_DEL;
2308 undo_queue_spos = src;
2310 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2311 // If queue is full, dump it into an object
2312 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2313 undo_queue_commit();
2316 // Switch from storing inserted text to deleted text
2317 undo_queue_commit();
2318 undo_push(src, length, UNDO_DEL_QUEUED);
2322 case UNDO_INS_QUEUED:
2325 switch (undo_queue_state) {
2327 undo_queue_state = UNDO_INS;
2328 undo_queue_spos = src;
2330 undo_q++; // Don't need to save any data for insertions
2331 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2332 undo_queue_commit();
2335 // Switch from storing deleted text to inserted text
2336 undo_queue_commit();
2337 undo_push(src, length, UNDO_INS_QUEUED);
2343 // If undo queuing is disabled, ignore the queuing flag entirely
2344 u_type = u_type & ~UNDO_QUEUED_FLAG;
2347 // Allocate a new undo object
2348 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2349 // For UNDO_DEL objects, save deleted text
2350 if ((src + length) == end)
2352 // If this deletion empties text[], strip the newline. When the buffer becomes
2353 // zero-length, a newline is added back, which requires this to compensate.
2354 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
2355 memcpy(undo_entry->undo_text, src, length);
2357 undo_entry = xzalloc(sizeof(*undo_entry));
2359 undo_entry->length = length;
2360 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2361 if ((u_type & UNDO_USE_SPOS) != 0) {
2362 undo_entry->start = undo_queue_spos - text; // use start position from queue
2364 undo_entry->start = src - text; // use offset from start of text buffer
2366 u_type = (u_type & ~UNDO_USE_SPOS);
2368 undo_entry->start = src - text;
2370 undo_entry->u_type = u_type;
2372 // Push it on undo stack
2373 undo_entry->prev = undo_stack_tail;
2374 undo_stack_tail = undo_entry;
2378 static void undo_pop(void) // Undo the last operation
2381 char *u_start, *u_end;
2382 struct undo_object *undo_entry;
2384 // Commit pending undo queue before popping (should be unnecessary)
2385 undo_queue_commit();
2387 undo_entry = undo_stack_tail;
2388 // Check for an empty undo stack
2390 status_line("Already at oldest change");
2394 switch (undo_entry->u_type) {
2396 case UNDO_DEL_CHAIN:
2397 // make hole and put in text that was deleted; deallocate text
2398 u_start = text + undo_entry->start;
2399 text_hole_make(u_start, undo_entry->length);
2400 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2401 status_line("Undo [%d] %s %d chars at position %d",
2402 modified_count, "restored",
2403 undo_entry->length, undo_entry->start
2407 case UNDO_INS_CHAIN:
2408 // delete what was inserted
2409 u_start = undo_entry->start + text;
2410 u_end = u_start - 1 + undo_entry->length;
2411 text_hole_delete(u_start, u_end, NO_UNDO);
2412 status_line("Undo [%d] %s %d chars at position %d",
2413 modified_count, "deleted",
2414 undo_entry->length, undo_entry->start
2419 switch (undo_entry->u_type) {
2420 // If this is the end of a chain, lower modification count and refresh display
2423 dot = (text + undo_entry->start);
2426 case UNDO_DEL_CHAIN:
2427 case UNDO_INS_CHAIN:
2431 // Deallocate the undo object we just processed
2432 undo_stack_tail = undo_entry->prev;
2435 // For chained operations, continue popping all the way down the chain.
2437 undo_pop(); // Follow the undo chain if one exists
2441 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2442 static void undo_queue_commit(void) // Flush any queued objects to the undo stack
2444 // Pushes the queue object onto the undo stack
2446 // Deleted character undo events grow from the end
2447 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2449 (undo_queue_state | UNDO_USE_SPOS)
2451 undo_queue_state = UNDO_EMPTY;
2457 #endif /* ENABLE_FEATURE_VI_UNDO */
2459 // open a hole in text[]
2460 // might reallocate text[]! use p += text_hole_make(p, ...),
2461 // and be careful to not use pointers into potentially freed text[]!
2462 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2468 end += size; // adjust the new END
2469 if (end >= (text + text_size)) {
2471 text_size += end - (text + text_size) + 10240;
2472 new_text = xrealloc(text, text_size);
2473 bias = (new_text - text);
2474 screenbegin += bias;
2478 #if ENABLE_FEATURE_VI_YANKMARK
2481 for (i = 0; i < ARRAY_SIZE(mark); i++)
2488 memmove(p + size, p, end - size - p);
2489 memset(p, ' ', size); // clear new hole
2493 // close a hole in text[]
2494 // "undo" value indicates if this operation should be undo-able
2495 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2500 // move forwards, from beginning
2504 if (q < p) { // they are backward- swap them
2508 hole_size = q - p + 1;
2510 #if ENABLE_FEATURE_VI_UNDO
2515 undo_push(p, hole_size, UNDO_DEL);
2517 case ALLOW_UNDO_CHAIN:
2518 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2520 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2521 case ALLOW_UNDO_QUEUED:
2522 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2528 if (src < text || src > end)
2530 if (dest < text || dest >= end)
2534 goto thd_atend; // just delete the end of the buffer
2535 memmove(dest, src, cnt);
2537 end = end - hole_size; // adjust the new END
2539 dest = end - 1; // make sure dest in below end-1
2541 dest = end = text; // keep pointers valid
2546 // copy text into register, then delete text.
2547 // if dist <= 0, do not include, or go past, a NewLine
2549 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2553 // make sure start <= stop
2555 // they are backwards, reverse them
2561 // we cannot cross NL boundaries
2565 // dont go past a NewLine
2566 for (; p + 1 <= stop; p++) {
2568 stop = p; // "stop" just before NewLine
2574 #if ENABLE_FEATURE_VI_YANKMARK
2575 text_yank(start, stop, YDreg);
2577 if (yf == YANKDEL) {
2578 p = text_hole_delete(start, stop, undo);
2583 static void show_help(void)
2585 puts("These features are available:"
2586 #if ENABLE_FEATURE_VI_SEARCH
2587 "\n\tPattern searches with / and ?"
2589 #if ENABLE_FEATURE_VI_DOT_CMD
2590 "\n\tLast command repeat with ."
2592 #if ENABLE_FEATURE_VI_YANKMARK
2593 "\n\tLine marking with 'x"
2594 "\n\tNamed buffers with \"x"
2596 #if ENABLE_FEATURE_VI_READONLY
2597 //not implemented: "\n\tReadonly if vi is called as \"view\""
2598 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2600 #if ENABLE_FEATURE_VI_SET
2601 "\n\tSome colon mode commands with :"
2603 #if ENABLE_FEATURE_VI_SETOPTS
2604 "\n\tSettable options with \":set\""
2606 #if ENABLE_FEATURE_VI_USE_SIGNALS
2607 "\n\tSignal catching- ^C"
2608 "\n\tJob suspend and resume with ^Z"
2610 #if ENABLE_FEATURE_VI_WIN_RESIZE
2611 "\n\tAdapt to window re-sizes"
2616 #if ENABLE_FEATURE_VI_DOT_CMD
2617 static void start_new_cmd_q(char c)
2619 // get buffer for new cmd
2620 // if there is a current cmd count put it in the buffer first
2622 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2623 } else { // just save char c onto queue
2624 last_modifying_cmd[0] = c;
2630 static void end_cmd_q(void)
2632 #if ENABLE_FEATURE_VI_YANKMARK
2633 YDreg = 26; // go back to default Yank/Delete reg
2637 #endif /* FEATURE_VI_DOT_CMD */
2639 #if ENABLE_FEATURE_VI_YANKMARK \
2640 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2641 || ENABLE_FEATURE_VI_CRASHME
2642 // might reallocate text[]! use p += string_insert(p, ...),
2643 // and be careful to not use pointers into potentially freed text[]!
2644 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2650 #if ENABLE_FEATURE_VI_UNDO
2653 undo_push(p, i, UNDO_INS);
2655 case ALLOW_UNDO_CHAIN:
2656 undo_push(p, i, UNDO_INS_CHAIN);
2660 bias = text_hole_make(p, i);
2663 #if ENABLE_FEATURE_VI_YANKMARK
2666 for (cnt = 0; *s != '\0'; s++) {
2670 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2677 #if ENABLE_FEATURE_VI_YANKMARK
2678 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2681 if (cnt < 0) { // they are backwards- reverse them
2685 free(reg[dest]); // if already a yank register, free it
2686 reg[dest] = xstrndup(p, cnt + 1);
2690 static char what_reg(void)
2694 c = 'D'; // default to D-reg
2695 if (0 <= YDreg && YDreg <= 25)
2696 c = 'a' + (char) YDreg;
2704 static void check_context(char cmd)
2706 // A context is defined to be "modifying text"
2707 // Any modifying command establishes a new context.
2709 if (dot < context_start || dot > context_end) {
2710 if (strchr(modifying_cmds, cmd) != NULL) {
2711 // we are trying to modify text[]- make this the current context
2712 mark[27] = mark[26]; // move cur to prev
2713 mark[26] = dot; // move local to cur
2714 context_start = prev_line(prev_line(dot));
2715 context_end = next_line(next_line(dot));
2716 //loiter= start_loiter= now;
2721 static char *swap_context(char *p) // goto new context for '' command make this the current context
2725 // the current context is in mark[26]
2726 // the previous context is in mark[27]
2727 // only swap context if other context is valid
2728 if (text <= mark[27] && mark[27] <= end - 1) {
2732 context_start = prev_line(prev_line(prev_line(p)));
2733 context_end = next_line(next_line(next_line(p)));
2737 #endif /* FEATURE_VI_YANKMARK */
2739 //----- Set terminal attributes --------------------------------
2740 static void rawmode(void)
2742 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
2743 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
2744 erase_char = term_orig.c_cc[VERASE];
2747 static void cookmode(void)
2750 tcsetattr_stdin_TCSANOW(&term_orig);
2753 #if ENABLE_FEATURE_VI_USE_SIGNALS
2754 //----- Come here when we get a window resize signal ---------
2755 static void winch_sig(int sig UNUSED_PARAM)
2757 int save_errno = errno;
2758 // FIXME: do it in main loop!!!
2759 signal(SIGWINCH, winch_sig);
2760 query_screen_dimensions();
2761 new_screen(rows, columns); // get memory for virtual screen
2762 redraw(TRUE); // re-draw the screen
2766 //----- Come here when we get a continue signal -------------------
2767 static void cont_sig(int sig UNUSED_PARAM)
2769 int save_errno = errno;
2770 rawmode(); // terminal to "raw"
2771 last_status_cksum = 0; // force status update
2772 redraw(TRUE); // re-draw the screen
2774 signal(SIGTSTP, suspend_sig);
2775 signal(SIGCONT, SIG_DFL);
2776 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2780 //----- Come here when we get a Suspend signal -------------------
2781 static void suspend_sig(int sig UNUSED_PARAM)
2783 int save_errno = errno;
2784 go_bottom_and_clear_to_eol();
2785 cookmode(); // terminal to "cooked"
2787 signal(SIGCONT, cont_sig);
2788 signal(SIGTSTP, SIG_DFL);
2789 kill(my_pid, SIGTSTP);
2793 //----- Come here when we get a signal ---------------------------
2794 static void catch_sig(int sig)
2796 signal(SIGINT, catch_sig);
2797 siglongjmp(restart, sig);
2799 #endif /* FEATURE_VI_USE_SIGNALS */
2801 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2803 struct pollfd pfd[1];
2808 pfd[0].fd = STDIN_FILENO;
2809 pfd[0].events = POLLIN;
2810 return safe_poll(pfd, 1, hund*10) > 0;
2813 //----- IO Routines --------------------------------------------
2814 static int readit(void) // read (maybe cursor) key from stdin
2820 // Wait for input. TIMEOUT = -1 makes read_key wait even
2821 // on nonblocking stdin.
2822 // Note: read_key sets errno to 0 on success.
2824 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
2825 if (c == -1) { // EOF/error
2826 if (errno == EAGAIN) // paranoia
2828 go_bottom_and_clear_to_eol();
2829 cookmode(); // terminal to "cooked"
2830 bb_error_msg_and_die("can't read user input");
2835 //----- IO Routines --------------------------------------------
2836 static int get_one_char(void)
2840 #if ENABLE_FEATURE_VI_DOT_CMD
2842 // we are not adding to the q.
2843 // but, we may be reading from a q
2845 // there is no current q, read from STDIN
2846 c = readit(); // get the users input
2848 // there is a queue to get chars from first
2849 // careful with correct sign expansion!
2850 c = (unsigned char)*ioq++;
2852 // the end of the q, read from STDIN
2854 ioq_start = ioq = 0;
2855 c = readit(); // get the users input
2859 // adding STDIN chars to q
2860 c = readit(); // get the users input
2861 if (lmc_len >= MAX_INPUT_LEN - 1) {
2862 status_line_bold("last_modifying_cmd overrun");
2864 // add new char to q
2865 last_modifying_cmd[lmc_len++] = c;
2869 c = readit(); // get the users input
2870 #endif /* FEATURE_VI_DOT_CMD */
2874 // Get input line (uses "status line" area)
2875 static char *get_input_line(const char *prompt)
2877 // char [MAX_INPUT_LEN]
2878 #define buf get_input_line__buf
2883 strcpy(buf, prompt);
2884 last_status_cksum = 0; // force status update
2885 go_bottom_and_clear_to_eol();
2886 write1(prompt); // write out the :, /, or ? prompt
2889 while (i < MAX_INPUT_LEN) {
2891 if (c == '\n' || c == '\r' || c == 27)
2892 break; // this is end of input
2893 if (c == erase_char || c == 8 || c == 127) {
2894 // user wants to erase prev char
2896 write1("\b \b"); // erase char on screen
2897 if (i <= 0) // user backs up before b-o-l, exit
2899 } else if (c > 0 && c < 256) { // exclude Unicode
2900 // (TODO: need to handle Unicode)
2911 // might reallocate text[]!
2912 static int file_insert(const char *fn, char *p, int initial)
2916 struct stat statbuf;
2923 fd = open(fn, O_RDONLY);
2926 status_line_bold_errno(fn);
2931 if (fstat(fd, &statbuf) < 0) {
2932 status_line_bold_errno(fn);
2935 if (!S_ISREG(statbuf.st_mode)) {
2936 status_line_bold("'%s' is not a regular file", fn);
2939 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2940 p += text_hole_make(p, size);
2941 cnt = full_read(fd, p, size);
2943 status_line_bold_errno(fn);
2944 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2945 } else if (cnt < size) {
2946 // There was a partial read, shrink unused space
2947 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2948 status_line_bold("can't read '%s'", fn);
2953 #if ENABLE_FEATURE_VI_READONLY
2955 && ((access(fn, W_OK) < 0) ||
2956 /* root will always have access()
2957 * so we check fileperms too */
2958 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2961 SET_READONLY_FILE(readonly_mode);
2967 static int file_write(char *fn, char *first, char *last)
2969 int fd, cnt, charcnt;
2972 status_line_bold("No current filename");
2975 /* By popular request we do not open file with O_TRUNC,
2976 * but instead ftruncate() it _after_ successful write.
2977 * Might reduce amount of data lost on power fail etc.
2979 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2982 cnt = last - first + 1;
2983 charcnt = full_write(fd, first, cnt);
2984 ftruncate(fd, charcnt);
2985 if (charcnt == cnt) {
2987 //modified_count = FALSE;
2995 //----- Terminal Drawing ---------------------------------------
2996 // The terminal is made up of 'rows' line of 'columns' columns.
2997 // classically this would be 24 x 80.
2998 // screen coordinates
3004 // 23,0 ... 23,79 <- status line
3006 //----- Move the cursor to row x col (count from 0, not 1) -------
3007 static void place_cursor(int row, int col)
3009 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
3011 if (row < 0) row = 0;
3012 if (row >= rows) row = rows - 1;
3013 if (col < 0) col = 0;
3014 if (col >= columns) col = columns - 1;
3016 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
3020 //----- Erase from cursor to end of line -----------------------
3021 static void clear_to_eol(void)
3023 write1(ESC_CLEAR2EOL);
3026 static void go_bottom_and_clear_to_eol(void)
3028 place_cursor(rows - 1, 0);
3032 //----- Erase from cursor to end of screen -----------------------
3033 static void clear_to_eos(void)
3035 write1(ESC_CLEAR2EOS);
3038 //----- Start standout mode ------------------------------------
3039 static void standout_start(void)
3041 write1(ESC_BOLD_TEXT);
3044 //----- End standout mode --------------------------------------
3045 static void standout_end(void)
3047 write1(ESC_NORM_TEXT);
3050 //----- Flash the screen --------------------------------------
3051 static void flash(int h)
3060 static void indicate_error(void)
3062 #if ENABLE_FEATURE_VI_CRASHME
3064 return; // generate a random command
3073 //----- Screen[] Routines --------------------------------------
3074 //----- Erase the Screen[] memory ------------------------------
3075 static void screen_erase(void)
3077 memset(screen, ' ', screensize); // clear new screen
3080 static int bufsum(char *buf, int count)
3083 char *e = buf + count;
3086 sum += (unsigned char) *buf++;
3090 //----- Draw the status line at bottom of the screen -------------
3091 static void show_status_line(void)
3093 int cnt = 0, cksum = 0;
3095 // either we already have an error or status message, or we
3097 if (!have_status_msg) {
3098 cnt = format_edit_status();
3099 cksum = bufsum(status_buffer, cnt);
3101 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3102 last_status_cksum = cksum; // remember if we have seen this line
3103 go_bottom_and_clear_to_eol();
3104 write1(status_buffer);
3105 if (have_status_msg) {
3106 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3108 have_status_msg = 0;
3111 have_status_msg = 0;
3113 place_cursor(crow, ccol); // put cursor back in correct place
3118 //----- format the status buffer, the bottom line of screen ------
3119 // format status buffer, with STANDOUT mode
3120 static void status_line_bold(const char *format, ...)
3124 va_start(args, format);
3125 strcpy(status_buffer, ESC_BOLD_TEXT);
3126 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3127 strcat(status_buffer, ESC_NORM_TEXT);
3130 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3133 static void status_line_bold_errno(const char *fn)
3135 status_line_bold("'%s' %s", fn, strerror(errno));
3138 // format status buffer
3139 static void status_line(const char *format, ...)
3143 va_start(args, format);
3144 vsprintf(status_buffer, format, args);
3147 have_status_msg = 1;
3150 // copy s to buf, convert unprintable
3151 static void print_literal(char *buf, const char *s)
3165 c_is_no_print = (c & 0x80) && !Isprint(c);
3166 if (c_is_no_print) {
3167 strcpy(d, ESC_NORM_TEXT);
3168 d += sizeof(ESC_NORM_TEXT)-1;
3171 if (c < ' ' || c == 0x7f) {
3173 c |= '@'; /* 0x40 */
3179 if (c_is_no_print) {
3180 strcpy(d, ESC_BOLD_TEXT);
3181 d += sizeof(ESC_BOLD_TEXT)-1;
3187 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3192 static void not_implemented(const char *s)
3194 char buf[MAX_INPUT_LEN];
3196 print_literal(buf, s);
3197 status_line_bold("\'%s\' is not implemented", buf);
3200 // show file status on status line
3201 static int format_edit_status(void)
3203 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3205 #define tot format_edit_status__tot
3207 int cur, percent, ret, trunc_at;
3209 // modified_count is now a counter rather than a flag. this
3210 // helps reduce the amount of line counting we need to do.
3211 // (this will cause a mis-reporting of modified status
3212 // once every MAXINT editing operations.)
3214 // it would be nice to do a similar optimization here -- if
3215 // we haven't done a motion that could have changed which line
3216 // we're on, then we shouldn't have to do this count_lines()
3217 cur = count_lines(text, dot);
3219 // count_lines() is expensive.
3220 // Call it only if something was changed since last time
3222 if (modified_count != last_modified_count) {
3223 tot = cur + count_lines(dot, end - 1) - 1;
3224 last_modified_count = modified_count;
3227 // current line percent
3228 // ------------- ~~ ----------
3231 percent = (100 * cur) / tot;
3237 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3238 columns : STATUS_BUFFER_LEN-1;
3240 ret = snprintf(status_buffer, trunc_at+1,
3241 #if ENABLE_FEATURE_VI_READONLY
3242 "%c %s%s%s %d/%d %d%%",
3244 "%c %s%s %d/%d %d%%",
3246 cmd_mode_indicator[cmd_mode & 3],
3247 (current_filename != NULL ? current_filename : "No file"),
3248 #if ENABLE_FEATURE_VI_READONLY
3249 (readonly_mode ? " [Readonly]" : ""),
3251 (modified_count ? " [Modified]" : ""),
3254 if (ret >= 0 && ret < trunc_at)
3255 return ret; /* it all fit */
3257 return trunc_at; /* had to truncate */
3261 //----- Force refresh of all Lines -----------------------------
3262 static void redraw(int full_screen)
3266 screen_erase(); // erase the internal screen buffer
3267 last_status_cksum = 0; // force status update
3268 refresh(full_screen); // this will redraw the entire display
3272 //----- Format a text[] line into a buffer ---------------------
3273 static char* format_line(char *src /*, int li*/)
3278 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3280 c = '~'; // char in col 0 in non-existent lines is '~'
3282 while (co < columns + tabstop) {
3283 // have we gone past the end?
3288 if ((c & 0x80) && !Isprint(c)) {
3291 if (c < ' ' || c == 0x7f) {
3295 while ((co % tabstop) != (tabstop - 1)) {
3303 c += '@'; // Ctrl-X -> 'X'
3308 // discard scrolled-off-to-the-left portion,
3309 // in tabstop-sized pieces
3310 if (ofs >= tabstop && co >= tabstop) {
3311 memmove(dest, dest + tabstop, co);
3318 // check "short line, gigantic offset" case
3321 // discard last scrolled off part
3324 // fill the rest with spaces
3326 memset(&dest[co], ' ', columns - co);
3330 //----- Refresh the changed screen lines -----------------------
3331 // Copy the source line from text[] into the buffer and note
3332 // if the current screenline is different from the new buffer.
3333 // If they differ then that line needs redrawing on the terminal.
3335 static void refresh(int full_screen)
3337 #define old_offset refresh__old_offset
3340 char *tp, *sp; // pointer into text[] and screen[]
3342 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3343 unsigned c = columns, r = rows;
3344 query_screen_dimensions();
3345 full_screen |= (c - columns) | (r - rows);
3347 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3348 tp = screenbegin; // index into text[] of top line
3350 // compare text[] to screen[] and mark screen[] lines that need updating
3351 for (li = 0; li < rows - 1; li++) {
3352 int cs, ce; // column start & end
3354 // format current text line
3355 out_buf = format_line(tp /*, li*/);
3357 // skip to the end of the current text[] line
3359 char *t = memchr(tp, '\n', end - tp);
3360 if (!t) t = end - 1;
3364 // see if there are any changes between virtual screen and out_buf
3365 changed = FALSE; // assume no change
3368 sp = &screen[li * columns]; // start of screen line
3370 // force re-draw of every single column from 0 - columns-1
3373 // compare newly formatted buffer with virtual screen
3374 // look forward for first difference between buf and screen
3375 for (; cs <= ce; cs++) {
3376 if (out_buf[cs] != sp[cs]) {
3377 changed = TRUE; // mark for redraw
3382 // look backward for last difference between out_buf and screen
3383 for (; ce >= cs; ce--) {
3384 if (out_buf[ce] != sp[ce]) {
3385 changed = TRUE; // mark for redraw
3389 // now, cs is index of first diff, and ce is index of last diff
3391 // if horz offset has changed, force a redraw
3392 if (offset != old_offset) {
3397 // make a sanity check of columns indexes
3399 if (ce > columns - 1) ce = columns - 1;
3400 if (cs > ce) { cs = 0; ce = columns - 1; }
3401 // is there a change between virtual screen and out_buf
3403 // copy changed part of buffer to virtual screen
3404 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3405 place_cursor(li, cs);
3406 // write line out to terminal
3407 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3411 place_cursor(crow, ccol);
3413 old_offset = offset;
3417 //---------------------------------------------------------------------
3418 //----- the Ascii Chart -----------------------------------------------
3420 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3421 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3422 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3423 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3424 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3425 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3426 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3427 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3428 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3429 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3430 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3431 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3432 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3433 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3434 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3435 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3436 //---------------------------------------------------------------------
3438 //----- Execute a Vi Command -----------------------------------
3439 static void do_cmd(int c)
3441 char *p, *q, *save_dot;
3447 // c1 = c; // quiet the compiler
3448 // cnt = yf = 0; // quiet the compiler
3449 // p = q = save_dot = buf; // quiet the compiler
3450 memset(buf, '\0', sizeof(buf));
3454 /* if this is a cursor key, skip these checks */
3462 case KEYCODE_PAGEUP:
3463 case KEYCODE_PAGEDOWN:
3464 case KEYCODE_DELETE:
3468 if (cmd_mode == 2) {
3469 // flip-flop Insert/Replace mode
3470 if (c == KEYCODE_INSERT)
3472 // we are 'R'eplacing the current *dot with new char
3474 // don't Replace past E-o-l
3475 cmd_mode = 1; // convert to insert
3476 undo_queue_commit();
3478 if (1 <= c || Isprint(c)) {
3480 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3481 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3486 if (cmd_mode == 1) {
3487 // hitting "Insert" twice means "R" replace mode
3488 if (c == KEYCODE_INSERT) goto dc5;
3489 // insert the char c at "dot"
3490 if (1 <= c || Isprint(c)) {
3491 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3506 #if ENABLE_FEATURE_VI_CRASHME
3507 case 0x14: // dc4 ctrl-T
3508 crashme = (crashme == 0) ? 1 : 0;
3538 default: // unrecognized command
3541 not_implemented(buf);
3542 end_cmd_q(); // stop adding to q
3543 case 0x00: // nul- ignore
3545 case 2: // ctrl-B scroll up full screen
3546 case KEYCODE_PAGEUP: // Cursor Key Page Up
3547 dot_scroll(rows - 2, -1);
3549 case 4: // ctrl-D scroll down half screen
3550 dot_scroll((rows - 2) / 2, 1);
3552 case 5: // ctrl-E scroll down one line
3555 case 6: // ctrl-F scroll down full screen
3556 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3557 dot_scroll(rows - 2, 1);
3559 case 7: // ctrl-G show current status
3560 last_status_cksum = 0; // force status update
3562 case 'h': // h- move left
3563 case KEYCODE_LEFT: // cursor key Left
3564 case 8: // ctrl-H- move left (This may be ERASE char)
3565 case 0x7f: // DEL- move left (This may be ERASE char)
3568 } while (--cmdcnt > 0);
3570 case 10: // Newline ^J
3571 case 'j': // j- goto next line, same col
3572 case KEYCODE_DOWN: // cursor key Down
3574 dot_next(); // go to next B-o-l
3575 // try stay in same col
3576 dot = move_to_col(dot, ccol + offset);
3577 } while (--cmdcnt > 0);
3579 case 12: // ctrl-L force redraw whole screen
3580 case 18: // ctrl-R force redraw
3583 //mysleep(10); // why???
3584 screen_erase(); // erase the internal screen buffer
3585 last_status_cksum = 0; // force status update
3586 refresh(TRUE); // this will redraw the entire display
3588 case 13: // Carriage Return ^M
3589 case '+': // +- goto next line
3593 } while (--cmdcnt > 0);
3595 case 21: // ctrl-U scroll up half screen
3596 dot_scroll((rows - 2) / 2, -1);
3598 case 25: // ctrl-Y scroll up one line
3604 cmd_mode = 0; // stop insrting
3605 undo_queue_commit();
3607 last_status_cksum = 0; // force status update
3609 case ' ': // move right
3610 case 'l': // move right
3611 case KEYCODE_RIGHT: // Cursor Key Right
3614 } while (--cmdcnt > 0);
3616 #if ENABLE_FEATURE_VI_YANKMARK
3617 case '"': // "- name a register to use for Delete/Yank
3618 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3619 if ((unsigned)c1 <= 25) { // a-z?
3625 case '\'': // '- goto a specific mark
3626 c1 = (get_one_char() | 0x20);
3627 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3631 if (text <= q && q < end) {
3633 dot_begin(); // go to B-o-l
3636 } else if (c1 == '\'') { // goto previous context
3637 dot = swap_context(dot); // swap current and previous context
3638 dot_begin(); // go to B-o-l
3644 case 'm': // m- Mark a line
3645 // this is really stupid. If there are any inserts or deletes
3646 // between text[0] and dot then this mark will not point to the
3647 // correct location! It could be off by many lines!
3648 // Well..., at least its quick and dirty.
3649 c1 = (get_one_char() | 0x20) - 'a';
3650 if ((unsigned)c1 <= 25) { // a-z?
3651 // remember the line
3657 case 'P': // P- Put register before
3658 case 'p': // p- put register after
3661 status_line_bold("Nothing in register %c", what_reg());
3664 // are we putting whole lines or strings
3665 if (strchr(p, '\n') != NULL) {
3667 dot_begin(); // putting lines- Put above
3670 // are we putting after very last line?
3671 if (end_line(dot) == (end - 1)) {
3672 dot = end; // force dot to end of text[]
3674 dot_next(); // next line, then put before
3679 dot_right(); // move to right, can move to NL
3681 string_insert(dot, p, ALLOW_UNDO); // insert the string
3682 end_cmd_q(); // stop adding to q
3684 case 'U': // U- Undo; replace current line with original version
3685 if (reg[Ureg] != NULL) {
3686 p = begin_line(dot);
3688 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3689 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3694 #endif /* FEATURE_VI_YANKMARK */
3695 #if ENABLE_FEATURE_VI_UNDO
3696 case 'u': // u- undo last operation
3700 case '$': // $- goto end of line
3701 case KEYCODE_END: // Cursor Key End
3703 dot = end_line(dot);
3709 case '%': // %- find matching char of pair () [] {}
3710 for (q = dot; q < end && *q != '\n'; q++) {
3711 if (strchr("()[]{}", *q) != NULL) {
3712 // we found half of a pair
3713 p = find_pair(q, *q);
3725 case 'f': // f- forward to a user specified char
3726 last_forward_char = get_one_char(); // get the search char
3728 // dont separate these two commands. 'f' depends on ';'
3730 //**** fall through to ... ';'
3731 case ';': // ;- look at rest of line for last forward char
3733 if (last_forward_char == 0)
3736 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3739 if (*q == last_forward_char)
3741 } while (--cmdcnt > 0);
3743 case ',': // repeat latest 'f' in opposite direction
3744 if (last_forward_char == 0)
3748 while (q >= text && *q != '\n' && *q != last_forward_char) {
3751 if (q >= text && *q == last_forward_char)
3753 } while (--cmdcnt > 0);
3756 case '-': // -- goto prev line
3760 } while (--cmdcnt > 0);
3762 #if ENABLE_FEATURE_VI_DOT_CMD
3763 case '.': // .- repeat the last modifying command
3764 // Stuff the last_modifying_cmd back into stdin
3765 // and let it be re-executed.
3767 last_modifying_cmd[lmc_len] = 0;
3768 ioq = ioq_start = xstrdup(last_modifying_cmd);
3772 #if ENABLE_FEATURE_VI_SEARCH
3773 case '?': // /- search for a pattern
3774 case '/': // /- search for a pattern
3777 q = get_input_line(buf); // get input line- use "status line"
3778 if (q[0] && !q[1]) {
3779 if (last_search_pattern[0])
3780 last_search_pattern[0] = c;
3781 goto dc3; // if no pat re-use old pat
3783 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3784 // there is a new pat
3785 free(last_search_pattern);
3786 last_search_pattern = xstrdup(q);
3787 goto dc3; // now find the pattern
3789 // user changed mind and erased the "/"- do nothing
3791 case 'N': // N- backward search for last pattern
3792 dir = BACK; // assume BACKWARD search
3794 if (last_search_pattern[0] == '?') {
3798 goto dc4; // now search for pattern
3800 case 'n': // n- repeat search for last pattern
3801 // search rest of text[] starting at next char
3802 // if search fails return orignal "p" not the "p+1" address
3806 dir = FORWARD; // assume FORWARD search
3808 if (last_search_pattern[0] == '?') {
3813 q = char_search(p, last_search_pattern + 1, dir, FULL);
3815 dot = q; // good search, update "dot"
3819 // no pattern found between "dot" and "end"- continue at top
3824 q = char_search(p, last_search_pattern + 1, dir, FULL);
3825 if (q != NULL) { // found something
3826 dot = q; // found new pattern- goto it
3827 msg = "search hit BOTTOM, continuing at TOP";
3829 msg = "search hit TOP, continuing at BOTTOM";
3832 msg = "Pattern not found";
3836 status_line_bold("%s", msg);
3837 } while (--cmdcnt > 0);
3839 case '{': // {- move backward paragraph
3840 q = char_search(dot, "\n\n", BACK, FULL);
3841 if (q != NULL) { // found blank line
3842 dot = next_line(q); // move to next blank line
3845 case '}': // }- move forward paragraph
3846 q = char_search(dot, "\n\n", FORWARD, FULL);
3847 if (q != NULL) { // found blank line
3848 dot = next_line(q); // move to next blank line
3851 #endif /* FEATURE_VI_SEARCH */
3852 case '0': // 0- goto beginning of line
3862 if (c == '0' && cmdcnt < 1) {
3863 dot_begin(); // this was a standalone zero
3865 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3868 case ':': // :- the colon mode commands
3869 p = get_input_line(":"); // get input line- use "status line"
3870 colon(p); // execute the command
3872 case '<': // <- Left shift something
3873 case '>': // >- Right shift something
3874 cnt = count_lines(text, dot); // remember what line we are on
3875 c1 = get_one_char(); // get the type of thing to delete
3876 find_range(&p, &q, c1);
3877 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3880 i = count_lines(p, q); // # of lines we are shifting
3881 for ( ; i > 0; i--, p = next_line(p)) {
3883 // shift left- remove tab or 8 spaces
3885 // shrink buffer 1 char
3886 text_hole_delete(p, p, NO_UNDO);
3887 } else if (*p == ' ') {
3888 // we should be calculating columns, not just SPACE
3889 for (j = 0; *p == ' ' && j < tabstop; j++) {
3890 text_hole_delete(p, p, NO_UNDO);
3893 } else if (c == '>') {
3894 // shift right -- add tab or 8 spaces
3895 char_insert(p, '\t', ALLOW_UNDO);
3898 dot = find_line(cnt); // what line were we on
3900 end_cmd_q(); // stop adding to q
3902 case 'A': // A- append at e-o-l
3903 dot_end(); // go to e-o-l
3904 //**** fall through to ... 'a'
3905 case 'a': // a- append after current char
3910 case 'B': // B- back a blank-delimited Word
3911 case 'E': // E- end of a blank-delimited word
3912 case 'W': // W- forward a blank-delimited word
3917 if (c == 'W' || isspace(dot[dir])) {
3918 dot = skip_thing(dot, 1, dir, S_TO_WS);
3919 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3922 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3923 } while (--cmdcnt > 0);
3925 case 'C': // C- Change to e-o-l
3926 case 'D': // D- delete to e-o-l
3928 dot = dollar_line(dot); // move to before NL
3929 // copy text into a register and delete
3930 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3932 goto dc_i; // start inserting
3933 #if ENABLE_FEATURE_VI_DOT_CMD
3935 end_cmd_q(); // stop adding to q
3938 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3939 c1 = get_one_char();
3942 // c1 < 0 if the key was special. Try "g<up-arrow>"
3943 // TODO: if Unicode?
3944 buf[1] = (c1 >= 0 ? c1 : '*');
3946 not_implemented(buf);
3952 case 'G': // G- goto to a line number (default= E-O-F)
3953 dot = end - 1; // assume E-O-F
3955 dot = find_line(cmdcnt); // what line is #cmdcnt
3959 case 'H': // H- goto top line on screen
3961 if (cmdcnt > (rows - 1)) {
3962 cmdcnt = (rows - 1);
3969 case 'I': // I- insert before first non-blank
3972 //**** fall through to ... 'i'
3973 case 'i': // i- insert before current char
3974 case KEYCODE_INSERT: // Cursor Key Insert
3976 cmd_mode = 1; // start inserting
3977 undo_queue_commit(); // commit queue when cmd_mode changes
3979 case 'J': // J- join current and next lines together
3981 dot_end(); // move to NL
3982 if (dot < end - 1) { // make sure not last char in text[]
3983 #if ENABLE_FEATURE_VI_UNDO
3984 undo_push(dot, 1, UNDO_DEL);
3985 *dot++ = ' '; // replace NL with space
3986 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3991 while (isblank(*dot)) { // delete leading WS
3992 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3995 } while (--cmdcnt > 0);
3996 end_cmd_q(); // stop adding to q
3998 case 'L': // L- goto bottom line on screen
4000 if (cmdcnt > (rows - 1)) {
4001 cmdcnt = (rows - 1);
4009 case 'M': // M- goto middle line on screen
4011 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
4012 dot = next_line(dot);
4014 case 'O': // O- open a empty line above
4016 p = begin_line(dot);
4017 if (p[-1] == '\n') {
4019 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
4021 dot = char_insert(dot, '\n', ALLOW_UNDO);
4024 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
4029 case 'R': // R- continuous Replace char
4032 undo_queue_commit();
4034 case KEYCODE_DELETE:
4036 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
4038 case 'X': // X- delete char before dot
4039 case 'x': // x- delete the current char
4040 case 's': // s- substitute the current char
4045 if (dot[dir] != '\n') {
4047 dot--; // delete prev char
4048 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
4050 } while (--cmdcnt > 0);
4051 end_cmd_q(); // stop adding to q
4053 goto dc_i; // start inserting
4055 case 'Z': // Z- if modified, {write}; exit
4056 // ZZ means to save file (if necessary), then exit
4057 c1 = get_one_char();
4062 if (modified_count) {
4063 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
4064 status_line_bold("'%s' is read only", current_filename);
4067 cnt = file_write(current_filename, text, end - 1);
4070 status_line_bold("Write error: %s", strerror(errno));
4071 } else if (cnt == (end - 1 - text + 1)) {
4078 case '^': // ^- move to first non-blank on line
4082 case 'b': // b- back a word
4083 case 'e': // e- end of word
4088 if ((dot + dir) < text || (dot + dir) > end - 1)
4091 if (isspace(*dot)) {
4092 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4094 if (isalnum(*dot) || *dot == '_') {
4095 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4096 } else if (ispunct(*dot)) {
4097 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4099 } while (--cmdcnt > 0);
4101 case 'c': // c- change something
4102 case 'd': // d- delete something
4103 #if ENABLE_FEATURE_VI_YANKMARK
4104 case 'y': // y- yank something
4105 case 'Y': // Y- Yank a line
4108 int yf, ml, whole = 0;
4109 yf = YANKDEL; // assume either "c" or "d"
4110 #if ENABLE_FEATURE_VI_YANKMARK
4111 if (c == 'y' || c == 'Y')
4116 c1 = get_one_char(); // get the type of thing to delete
4117 // determine range, and whether it spans lines
4118 ml = find_range(&p, &q, c1);
4120 if (c1 == 27) { // ESC- user changed mind and wants out
4121 c = c1 = 27; // Escape- do nothing
4122 } else if (strchr("wW", c1)) {
4124 // don't include trailing WS as part of word
4125 while (isblank(*q)) {
4126 if (q <= text || q[-1] == '\n')
4131 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4132 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
4133 // partial line copy text into a register and delete
4134 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4135 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
4136 // whole line copy text into a register and delete
4137 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
4140 // could not recognize object
4141 c = c1 = 27; // error-
4147 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4148 // on the last line of file don't move to prev line
4149 if (whole && dot != (end-1)) {
4152 } else if (c == 'd') {
4158 // if CHANGING, not deleting, start inserting after the delete
4160 strcpy(buf, "Change");
4161 goto dc_i; // start inserting
4164 strcpy(buf, "Delete");
4166 #if ENABLE_FEATURE_VI_YANKMARK
4167 if (c == 'y' || c == 'Y') {
4168 strcpy(buf, "Yank");
4172 for (cnt = 0; p <= q; p++) {
4176 status_line("%s %d lines (%d chars) using [%c]",
4177 buf, cnt, strlen(reg[YDreg]), what_reg());
4179 end_cmd_q(); // stop adding to q
4183 case 'k': // k- goto prev line, same col
4184 case KEYCODE_UP: // cursor key Up
4187 dot = move_to_col(dot, ccol + offset); // try stay in same col
4188 } while (--cmdcnt > 0);
4190 case 'r': // r- replace the current char with user input
4191 c1 = get_one_char(); // get the replacement char
4193 #if ENABLE_FEATURE_VI_UNDO
4194 undo_push(dot, 1, UNDO_DEL);
4196 undo_push(dot, 1, UNDO_INS_CHAIN);
4202 end_cmd_q(); // stop adding to q
4204 case 't': // t- move to char prior to next x
4205 last_forward_char = get_one_char();
4207 if (*dot == last_forward_char)
4209 last_forward_char = 0;
4211 case 'w': // w- forward a word
4213 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4214 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4215 } else if (ispunct(*dot)) { // we are on PUNCT
4216 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4219 dot++; // move over word
4220 if (isspace(*dot)) {
4221 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4223 } while (--cmdcnt > 0);
4226 c1 = get_one_char(); // get the replacement char
4229 cnt = (rows - 2) / 2; // put dot at center
4231 cnt = rows - 2; // put dot at bottom
4232 screenbegin = begin_line(dot); // start dot at top
4233 dot_scroll(cnt, -1);
4235 case '|': // |- move to column "cmdcnt"
4236 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4238 case '~': // ~- flip the case of letters a-z -> A-Z
4240 #if ENABLE_FEATURE_VI_UNDO
4241 if (islower(*dot)) {
4242 undo_push(dot, 1, UNDO_DEL);
4243 *dot = toupper(*dot);
4244 undo_push(dot, 1, UNDO_INS_CHAIN);
4245 } else if (isupper(*dot)) {
4246 undo_push(dot, 1, UNDO_DEL);
4247 *dot = tolower(*dot);
4248 undo_push(dot, 1, UNDO_INS_CHAIN);
4251 if (islower(*dot)) {
4252 *dot = toupper(*dot);
4254 } else if (isupper(*dot)) {
4255 *dot = tolower(*dot);
4260 } while (--cmdcnt > 0);
4261 end_cmd_q(); // stop adding to q
4263 //----- The Cursor and Function Keys -----------------------------
4264 case KEYCODE_HOME: // Cursor Key Home
4267 // The Fn keys could point to do_macro which could translate them
4269 case KEYCODE_FUN1: // Function Key F1
4270 case KEYCODE_FUN2: // Function Key F2
4271 case KEYCODE_FUN3: // Function Key F3
4272 case KEYCODE_FUN4: // Function Key F4
4273 case KEYCODE_FUN5: // Function Key F5
4274 case KEYCODE_FUN6: // Function Key F6
4275 case KEYCODE_FUN7: // Function Key F7
4276 case KEYCODE_FUN8: // Function Key F8
4277 case KEYCODE_FUN9: // Function Key F9
4278 case KEYCODE_FUN10: // Function Key F10
4279 case KEYCODE_FUN11: // Function Key F11
4280 case KEYCODE_FUN12: // Function Key F12
4286 // if text[] just became empty, add back an empty line
4288 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4291 // it is OK for dot to exactly equal to end, otherwise check dot validity
4293 dot = bound_dot(dot); // make sure "dot" is valid
4295 #if ENABLE_FEATURE_VI_YANKMARK
4296 check_context(c); // update the current context
4300 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4301 cnt = dot - begin_line(dot);
4302 // Try to stay off of the Newline
4303 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4307 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4308 #if ENABLE_FEATURE_VI_CRASHME
4309 static int totalcmds = 0;
4310 static int Mp = 85; // Movement command Probability
4311 static int Np = 90; // Non-movement command Probability
4312 static int Dp = 96; // Delete command Probability
4313 static int Ip = 97; // Insert command Probability
4314 static int Yp = 98; // Yank command Probability
4315 static int Pp = 99; // Put command Probability
4316 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4317 static const char chars[20] = "\t012345 abcdABCD-=.$";
4318 static const char *const words[20] = {
4319 "this", "is", "a", "test",
4320 "broadcast", "the", "emergency", "of",
4321 "system", "quick", "brown", "fox",
4322 "jumped", "over", "lazy", "dogs",
4323 "back", "January", "Febuary", "March"
4325 static const char *const lines[20] = {
4326 "You should have received a copy of the GNU General Public License\n",
4327 "char c, cm, *cmd, *cmd1;\n",
4328 "generate a command by percentages\n",
4329 "Numbers may be typed as a prefix to some commands.\n",
4330 "Quit, discarding changes!\n",
4331 "Forced write, if permission originally not valid.\n",
4332 "In general, any ex or ed command (such as substitute or delete).\n",
4333 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4334 "Please get w/ me and I will go over it with you.\n",
4335 "The following is a list of scheduled, committed changes.\n",
4336 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4337 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4338 "Any question about transactions please contact Sterling Huxley.\n",
4339 "I will try to get back to you by Friday, December 31.\n",
4340 "This Change will be implemented on Friday.\n",
4341 "Let me know if you have problems accessing this;\n",
4342 "Sterling Huxley recently added you to the access list.\n",
4343 "Would you like to go to lunch?\n",
4344 "The last command will be automatically run.\n",
4345 "This is too much english for a computer geek.\n",
4347 static char *multilines[20] = {
4348 "You should have received a copy of the GNU General Public License\n",
4349 "char c, cm, *cmd, *cmd1;\n",
4350 "generate a command by percentages\n",
4351 "Numbers may be typed as a prefix to some commands.\n",
4352 "Quit, discarding changes!\n",
4353 "Forced write, if permission originally not valid.\n",
4354 "In general, any ex or ed command (such as substitute or delete).\n",
4355 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4356 "Please get w/ me and I will go over it with you.\n",
4357 "The following is a list of scheduled, committed changes.\n",
4358 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4359 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4360 "Any question about transactions please contact Sterling Huxley.\n",
4361 "I will try to get back to you by Friday, December 31.\n",
4362 "This Change will be implemented on Friday.\n",
4363 "Let me know if you have problems accessing this;\n",
4364 "Sterling Huxley recently added you to the access list.\n",
4365 "Would you like to go to lunch?\n",
4366 "The last command will be automatically run.\n",
4367 "This is too much english for a computer geek.\n",
4370 // create a random command to execute
4371 static void crash_dummy()
4373 static int sleeptime; // how long to pause between commands
4374 char c, cm, *cmd, *cmd1;
4375 int i, cnt, thing, rbi, startrbi, percent;
4377 // "dot" movement commands
4378 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4380 // is there already a command running?
4381 if (readbuffer[0] > 0)
4384 readbuffer[0] = 'X';
4386 sleeptime = 0; // how long to pause between commands
4387 memset(readbuffer, '\0', sizeof(readbuffer));
4388 // generate a command by percentages
4389 percent = (int) lrand48() % 100; // get a number from 0-99
4390 if (percent < Mp) { // Movement commands
4391 // available commands
4394 } else if (percent < Np) { // non-movement commands
4395 cmd = "mz<>\'\""; // available commands
4397 } else if (percent < Dp) { // Delete commands
4398 cmd = "dx"; // available commands
4400 } else if (percent < Ip) { // Inset commands
4401 cmd = "iIaAsrJ"; // available commands
4403 } else if (percent < Yp) { // Yank commands
4404 cmd = "yY"; // available commands
4406 } else if (percent < Pp) { // Put commands
4407 cmd = "pP"; // available commands
4410 // We do not know how to handle this command, try again
4414 // randomly pick one of the available cmds from "cmd[]"
4415 i = (int) lrand48() % strlen(cmd);
4417 if (strchr(":\024", cm))
4418 goto cd0; // dont allow colon or ctrl-T commands
4419 readbuffer[rbi++] = cm; // put cmd into input buffer
4421 // now we have the command-
4422 // there are 1, 2, and multi char commands
4423 // find out which and generate the rest of command as necessary
4424 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4425 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4426 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4427 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4429 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4431 readbuffer[rbi++] = c; // add movement to input buffer
4433 if (strchr("iIaAsc", cm)) { // multi-char commands
4435 // change some thing
4436 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4438 readbuffer[rbi++] = c; // add movement to input buffer
4440 thing = (int) lrand48() % 4; // what thing to insert
4441 cnt = (int) lrand48() % 10; // how many to insert
4442 for (i = 0; i < cnt; i++) {
4443 if (thing == 0) { // insert chars
4444 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4445 } else if (thing == 1) { // insert words
4446 strcat(readbuffer, words[(int) lrand48() % 20]);
4447 strcat(readbuffer, " ");
4448 sleeptime = 0; // how fast to type
4449 } else if (thing == 2) { // insert lines
4450 strcat(readbuffer, lines[(int) lrand48() % 20]);
4451 sleeptime = 0; // how fast to type
4452 } else { // insert multi-lines
4453 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4454 sleeptime = 0; // how fast to type
4457 strcat(readbuffer, "\033");
4459 readbuffer[0] = strlen(readbuffer + 1);
4463 mysleep(sleeptime); // sleep 1/100 sec
4466 // test to see if there are any errors
4467 static void crash_test()
4469 static time_t oldtim;
4476 strcat(msg, "end<text ");
4478 if (end > textend) {
4479 strcat(msg, "end>textend ");
4482 strcat(msg, "dot<text ");
4485 strcat(msg, "dot>end ");
4487 if (screenbegin < text) {
4488 strcat(msg, "screenbegin<text ");
4490 if (screenbegin > end - 1) {
4491 strcat(msg, "screenbegin>end-1 ");
4495 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4496 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4498 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4499 if (d[0] == '\n' || d[0] == '\r')
4504 if (tim >= (oldtim + 3)) {
4505 sprintf(status_buffer,
4506 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4507 totalcmds, M, N, I, D, Y, P, U, end - text + 1);