1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
11 * $HOME/.exrc and ./.exrc
12 * add magic to search /foo.*bar
15 * if mark[] values were line numbers rather than pointers
16 * it would be easier to change the mark when add/delete lines
17 * More intelligence in refresh()
18 * ":r !cmd" and "!cmd" to filter text through an external command
19 * An "ex" line oriented mode- maybe using "cmdedit"
22 //config: bool "vi (22 kb)"
25 //config: 'vi' is a text editor. More specifically, it is the One True
26 //config: text editor <grin>. It does, however, have a rather steep
27 //config: learning curve. If you are not already comfortable with 'vi'
28 //config: you may wish to use something else.
30 //config:config FEATURE_VI_MAX_LEN
31 //config: int "Maximum screen width"
32 //config: range 256 16384
33 //config: default 4096
34 //config: depends on VI
36 //config: Contrary to what you may think, this is not eating much.
37 //config: Make it smaller than 4k only if you are very limited on memory.
39 //config:config FEATURE_VI_8BIT
40 //config: bool "Allow to display 8-bit chars (otherwise shows dots)"
42 //config: depends on VI
44 //config: If your terminal can display characters with high bit set,
45 //config: you may want to enable this. Note: vi is not Unicode-capable.
46 //config: If your terminal combines several 8-bit bytes into one character
47 //config: (as in Unicode mode), this will not work properly.
49 //config:config FEATURE_VI_COLON
50 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
52 //config: depends on VI
54 //config: Enable a limited set of colon commands. This does not
55 //config: provide an "ex" mode.
57 //config:config FEATURE_VI_YANKMARK
58 //config: bool "Enable yank/put commands and mark cmds"
60 //config: depends on VI
62 //config: This enables you to use yank and put, as well as mark.
64 //config:config FEATURE_VI_SEARCH
65 //config: bool "Enable search and replace cmds"
67 //config: depends on VI
69 //config: Select this if you wish to be able to do search and replace.
71 //config:config FEATURE_VI_REGEX_SEARCH
72 //config: bool "Enable regex in search and replace"
73 //config: default n # Uses GNU regex, which may be unavailable. FIXME
74 //config: depends on FEATURE_VI_SEARCH
76 //config: Use extended regex search.
78 //config:config FEATURE_VI_USE_SIGNALS
79 //config: bool "Catch signals"
81 //config: depends on VI
83 //config: Selecting this option will make vi signal aware. This will support
84 //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
86 //config:config FEATURE_VI_DOT_CMD
87 //config: bool "Remember previous cmd and \".\" cmd"
89 //config: depends on VI
91 //config: Make vi remember the last command and be able to repeat it.
93 //config:config FEATURE_VI_READONLY
94 //config: bool "Enable -R option and \"view\" mode"
96 //config: depends on VI
98 //config: Enable the read-only command line option, which allows the user to
99 //config: open a file in read-only mode.
101 //config:config FEATURE_VI_SETOPTS
102 //config: bool "Enable settable options, ai ic showmatch"
104 //config: depends on VI
106 //config: Enable the editor to set some (ai, ic, showmatch) options.
108 //config:config FEATURE_VI_SET
109 //config: bool "Support :set"
111 //config: depends on VI
113 //config:config FEATURE_VI_WIN_RESIZE
114 //config: bool "Handle window resize"
116 //config: depends on VI
118 //config: Behave nicely with terminals that get resized.
120 //config:config FEATURE_VI_ASK_TERMINAL
121 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
123 //config: depends on VI
125 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
126 //config: this option makes vi perform a last-ditch effort to find it:
127 //config: position cursor to 999,999 and ask terminal to report real
128 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
129 //config: This is not clean but helps a lot on serial lines and such.
131 //config:config FEATURE_VI_UNDO
132 //config: bool "Support undo command \"u\""
134 //config: depends on VI
136 //config: Support the 'u' command to undo insertion, deletion, and replacement
139 //config:config FEATURE_VI_UNDO_QUEUE
140 //config: bool "Enable undo operation queuing"
142 //config: depends on FEATURE_VI_UNDO
144 //config: The vi undo functions can use an intermediate queue to greatly lower
145 //config: malloc() calls and overhead. When the maximum size of this queue is
146 //config: reached, the contents of the queue are committed to the undo stack.
147 //config: This increases the size of the undo code and allows some undo
148 //config: operations (especially un-typing/backspacing) to be far more useful.
150 //config:config FEATURE_VI_UNDO_QUEUE_MAX
151 //config: int "Maximum undo character queue size"
152 //config: default 256
153 //config: range 32 65536
154 //config: depends on FEATURE_VI_UNDO_QUEUE
156 //config: This option sets the number of bytes used at runtime for the queue.
157 //config: Smaller values will create more undo objects and reduce the amount
158 //config: of typed or backspaced characters that are grouped into one undo
159 //config: operation; larger values increase the potential size of each undo
160 //config: and will generally malloc() larger objects and less frequently.
161 //config: Unless you want more (or less) frequent "undo points" while typing,
162 //config: you should probably leave this unchanged.
164 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
166 //kbuild:lib-$(CONFIG_VI) += vi.o
168 //usage:#define vi_trivial_usage
169 //usage: "[OPTIONS] [FILE]..."
170 //usage:#define vi_full_usage "\n\n"
171 //usage: "Edit FILE\n"
172 //usage: IF_FEATURE_VI_COLON(
173 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
175 //usage: IF_FEATURE_VI_READONLY(
176 //usage: "\n -R Read-only"
178 //usage: "\n -H List available features"
181 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
182 #if ENABLE_FEATURE_VI_REGEX_SEARCH
186 /* the CRASHME code is unmaintained, and doesn't currently build */
187 #define ENABLE_FEATURE_VI_CRASHME 0
190 #if ENABLE_LOCALE_SUPPORT
192 #if ENABLE_FEATURE_VI_8BIT
193 //FIXME: this does not work properly for Unicode anyway
194 # define Isprint(c) (isprint)(c)
196 # define Isprint(c) isprint_asciionly(c)
201 /* 0x9b is Meta-ESC */
202 #if ENABLE_FEATURE_VI_8BIT
203 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
205 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
212 MAX_TABSTOP = 32, // sanity limit
213 // User input len. Need not be extra big.
214 // Lines in file being edited *can* be bigger than this.
216 // Sanity limits. We have only one buffer of this size.
217 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
218 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
221 /* VT102 ESC sequences.
222 * See "Xterm Control Sequences"
223 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
226 /* Inverse/Normal text */
227 #define ESC_BOLD_TEXT ESC"[7m"
228 #define ESC_NORM_TEXT ESC"[m"
230 #define ESC_BELL "\007"
231 /* Clear-to-end-of-line */
232 #define ESC_CLEAR2EOL ESC"[K"
233 /* Clear-to-end-of-screen.
234 * (We use default param here.
235 * Full sequence is "ESC [ <num> J",
236 * <num> is 0/1/2 = "erase below/above/all".)
238 #define ESC_CLEAR2EOS ESC"[J"
239 /* Cursor to given coordinate (1,1: top left) */
240 #define ESC_SET_CURSOR_POS ESC"[%u;%uH"
242 ///* Cursor up and down */
243 //#define ESC_CURSOR_UP ESC"[A"
244 //#define ESC_CURSOR_DOWN "\n"
246 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
247 // cmds modifying text[]
248 // vda: removed "aAiIs" as they switch us into insert mode
249 // and remembering input for replay after them makes no sense
250 static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
256 FORWARD = 1, // code depends on "1" for array index
257 BACK = -1, // code depends on "-1" for array index
258 LIMITED = 0, // char_search() only current line
259 FULL = 1, // char_search() to the end/beginning of entire text
261 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
262 S_TO_WS = 2, // used in skip_thing() for moving "dot"
263 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
264 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
265 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
269 /* vi.c expects chars to be unsigned. */
270 /* busybox build system provides that, but it's better */
271 /* to audit and fix the source */
274 /* many references - keep near the top of globals */
275 char *text, *end; // pointers to the user data in memory
276 char *dot; // where all the action takes place
277 int text_size; // size of the allocated buffer
281 #define VI_AUTOINDENT 1
282 #define VI_SHOWMATCH 2
283 #define VI_IGNORECASE 4
284 #define VI_ERR_METHOD 8
285 #define autoindent (vi_setops & VI_AUTOINDENT)
286 #define showmatch (vi_setops & VI_SHOWMATCH )
287 #define ignorecase (vi_setops & VI_IGNORECASE)
288 /* indicate error with beep or flash */
289 #define err_method (vi_setops & VI_ERR_METHOD)
291 #if ENABLE_FEATURE_VI_READONLY
292 smallint readonly_mode;
293 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
294 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
295 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
297 #define SET_READONLY_FILE(flags) ((void)0)
298 #define SET_READONLY_MODE(flags) ((void)0)
299 #define UNSET_READONLY_FILE(flags) ((void)0)
302 smallint editing; // >0 while we are editing a file
303 // [code audit says "can be 0, 1 or 2 only"]
304 smallint cmd_mode; // 0=command 1=insert 2=replace
305 int modified_count; // buffer contents changed if !0
306 int last_modified_count; // = -1;
307 int save_argc; // how many file names on cmd line
308 int cmdcnt; // repetition count
309 unsigned rows, columns; // the terminal screen is this size
310 #if ENABLE_FEATURE_VI_ASK_TERMINAL
311 int get_rowcol_error;
313 int crow, ccol; // cursor is on Crow x Ccol
314 int offset; // chars scrolled off the screen to the left
315 int have_status_msg; // is default edit status needed?
316 // [don't make smallint!]
317 int last_status_cksum; // hash of current status line
318 char *current_filename;
319 char *screenbegin; // index into text[], of top line on the screen
320 char *screen; // pointer to the virtual screen buffer
321 int screensize; // and its size
323 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
324 char erase_char; // the users erase character
325 char last_input_char; // last char read from user
327 #if ENABLE_FEATURE_VI_DOT_CMD
328 smallint adding2q; // are we currently adding user input to q
329 int lmc_len; // length of last_modifying_cmd
330 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
332 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
335 #if ENABLE_FEATURE_VI_SEARCH
336 char *last_search_pattern; // last pattern from a '/' or '?' search
340 #if ENABLE_FEATURE_VI_YANKMARK
341 char *edit_file__cur_line;
343 int refresh__old_offset;
344 int format_edit_status__tot;
346 /* a few references only */
347 #if ENABLE_FEATURE_VI_YANKMARK
348 int YDreg, Ureg; // default delete register and orig line for "U"
349 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
350 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
351 char *context_start, *context_end;
353 #if ENABLE_FEATURE_VI_USE_SIGNALS
354 sigjmp_buf restart; // catch_sig()
356 struct termios term_orig; // remember what the cooked mode was
357 #if ENABLE_FEATURE_VI_COLON
358 char *initial_cmds[3]; // currently 2 entries, NULL terminated
360 // Should be just enough to hold a key sequence,
361 // but CRASHME mode uses it as generated command buffer too
362 #if ENABLE_FEATURE_VI_CRASHME
363 char readbuffer[128];
365 char readbuffer[KEYCODE_BUFFER_SIZE];
367 #define STATUS_BUFFER_LEN 200
368 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
369 #if ENABLE_FEATURE_VI_DOT_CMD
370 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
372 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
374 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
375 #if ENABLE_FEATURE_VI_UNDO
376 // undo_push() operations
379 #define UNDO_INS_CHAIN 2
380 #define UNDO_DEL_CHAIN 3
381 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
382 #define UNDO_QUEUED_FLAG 4
383 #define UNDO_INS_QUEUED 4
384 #define UNDO_DEL_QUEUED 5
385 #define UNDO_USE_SPOS 32
386 #define UNDO_EMPTY 64
387 // Pass-through flags for functions that can be undone
390 #define ALLOW_UNDO_CHAIN 2
391 # if ENABLE_FEATURE_VI_UNDO_QUEUE
392 #define ALLOW_UNDO_QUEUED 3
393 char undo_queue_state;
395 char *undo_queue_spos; // Start position of queued operation
396 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
398 // If undo queuing disabled, don't invoke the missing queue logic
399 #define ALLOW_UNDO_QUEUED 1
403 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
404 int start; // Offset where the data should be restored/deleted
405 int length; // total data size
406 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
407 char undo_text[1]; // text that was deleted (if deletion)
409 #endif /* ENABLE_FEATURE_VI_UNDO */
411 #define G (*ptr_to_globals)
412 #define text (G.text )
413 #define text_size (G.text_size )
418 #define vi_setops (G.vi_setops )
419 #define editing (G.editing )
420 #define cmd_mode (G.cmd_mode )
421 #define modified_count (G.modified_count )
422 #define last_modified_count (G.last_modified_count)
423 #define save_argc (G.save_argc )
424 #define cmdcnt (G.cmdcnt )
425 #define rows (G.rows )
426 #define columns (G.columns )
427 #define crow (G.crow )
428 #define ccol (G.ccol )
429 #define offset (G.offset )
430 #define status_buffer (G.status_buffer )
431 #define have_status_msg (G.have_status_msg )
432 #define last_status_cksum (G.last_status_cksum )
433 #define current_filename (G.current_filename )
434 #define screen (G.screen )
435 #define screensize (G.screensize )
436 #define screenbegin (G.screenbegin )
437 #define tabstop (G.tabstop )
438 #define last_forward_char (G.last_forward_char )
439 #define erase_char (G.erase_char )
440 #define last_input_char (G.last_input_char )
441 #if ENABLE_FEATURE_VI_READONLY
442 #define readonly_mode (G.readonly_mode )
444 #define readonly_mode 0
446 #define adding2q (G.adding2q )
447 #define lmc_len (G.lmc_len )
449 #define ioq_start (G.ioq_start )
450 #define my_pid (G.my_pid )
451 #define last_search_pattern (G.last_search_pattern)
453 #define edit_file__cur_line (G.edit_file__cur_line)
454 #define refresh__old_offset (G.refresh__old_offset)
455 #define format_edit_status__tot (G.format_edit_status__tot)
457 #define YDreg (G.YDreg )
458 #define Ureg (G.Ureg )
459 #define mark (G.mark )
460 #define context_start (G.context_start )
461 #define context_end (G.context_end )
462 #define restart (G.restart )
463 #define term_orig (G.term_orig )
464 #define initial_cmds (G.initial_cmds )
465 #define readbuffer (G.readbuffer )
466 #define scr_out_buf (G.scr_out_buf )
467 #define last_modifying_cmd (G.last_modifying_cmd )
468 #define get_input_line__buf (G.get_input_line__buf)
470 #if ENABLE_FEATURE_VI_UNDO
471 #define undo_stack_tail (G.undo_stack_tail )
472 # if ENABLE_FEATURE_VI_UNDO_QUEUE
473 #define undo_queue_state (G.undo_queue_state)
474 #define undo_q (G.undo_q )
475 #define undo_queue (G.undo_queue )
476 #define undo_queue_spos (G.undo_queue_spos )
480 #define INIT_G() do { \
481 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
482 last_modified_count = -1; \
483 /* "" but has space for 2 chars: */ \
484 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
488 static void edit_file(char *); // edit one file
489 static void do_cmd(int); // execute a command
490 static int next_tabstop(int);
491 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
492 static char *begin_line(char *); // return pointer to cur line B-o-l
493 static char *end_line(char *); // return pointer to cur line E-o-l
494 static char *prev_line(char *); // return pointer to prev line B-o-l
495 static char *next_line(char *); // return pointer to next line B-o-l
496 static char *end_screen(void); // get pointer to last char on screen
497 static int count_lines(char *, char *); // count line from start to stop
498 static char *find_line(int); // find beginning of line #li
499 static char *move_to_col(char *, int); // move "p" to column l
500 static void dot_left(void); // move dot left- dont leave line
501 static void dot_right(void); // move dot right- dont leave line
502 static void dot_begin(void); // move dot to B-o-l
503 static void dot_end(void); // move dot to E-o-l
504 static void dot_next(void); // move dot to next line B-o-l
505 static void dot_prev(void); // move dot to prev line B-o-l
506 static void dot_scroll(int, int); // move the screen up or down
507 static void dot_skip_over_ws(void); // move dot pat WS
508 static char *bound_dot(char *); // make sure text[0] <= P < "end"
509 static char *new_screen(int, int); // malloc virtual screen memory
510 #if !ENABLE_FEATURE_VI_UNDO
511 #define char_insert(a,b,c) char_insert(a,b)
513 static char *char_insert(char *, char, int); // insert the char c at 'p'
514 // might reallocate text[]! use p += stupid_insert(p, ...),
515 // and be careful to not use pointers into potentially freed text[]!
516 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
517 static int find_range(char **, char **, char); // return pointers for an object
518 static int st_test(char *, int, int, char *); // helper for skip_thing()
519 static char *skip_thing(char *, int, int, int); // skip some object
520 static char *find_pair(char *, char); // find matching pair () [] {}
521 #if !ENABLE_FEATURE_VI_UNDO
522 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
524 static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
525 // might reallocate text[]! use p += text_hole_make(p, ...),
526 // and be careful to not use pointers into potentially freed text[]!
527 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
528 #if !ENABLE_FEATURE_VI_UNDO
529 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
531 static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
532 static void show_help(void); // display some help info
533 static void rawmode(void); // set "raw" mode on tty
534 static void cookmode(void); // return to "cooked" mode on tty
535 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
536 static int mysleep(int);
537 static int readit(void); // read (maybe cursor) key from stdin
538 static int get_one_char(void); // read 1 char from stdin
539 // file_insert might reallocate text[]!
540 static int file_insert(const char *, char *, int);
541 static int file_write(char *, char *, char *);
542 static void place_cursor(int, int);
543 static void screen_erase(void);
544 static void clear_to_eol(void);
545 static void clear_to_eos(void);
546 static void go_bottom_and_clear_to_eol(void);
547 static void standout_start(void); // send "start reverse video" sequence
548 static void standout_end(void); // send "end reverse video" sequence
549 static void flash(int); // flash the terminal screen
550 static void show_status_line(void); // put a message on the bottom line
551 static void status_line(const char *, ...); // print to status buf
552 static void status_line_bold(const char *, ...);
553 static void status_line_bold_errno(const char *fn);
554 static void not_implemented(const char *); // display "Not implemented" message
555 static int format_edit_status(void); // format file status on status line
556 static void redraw(int); // force a full screen refresh
557 static char* format_line(char* /*, int*/);
558 static void refresh(int); // update the terminal from screen[]
560 static void indicate_error(void); // use flash or beep to indicate error
561 static void Hit_Return(void);
563 #if ENABLE_FEATURE_VI_SEARCH
564 static char *char_search(char *, const char *, int); // search for pattern starting at p
566 #if ENABLE_FEATURE_VI_COLON
567 static char *get_one_address(char *, int *); // get colon addr, if present
568 static char *get_address(char *, int *, int *); // get two colon addrs, if present
570 static void colon(char *); // execute the "colon" mode cmds
571 #if ENABLE_FEATURE_VI_USE_SIGNALS
572 static void winch_sig(int); // catch window size changes
573 static void suspend_sig(int); // catch ctrl-Z
574 static void catch_sig(int); // catch ctrl-C and alarm time-outs
576 #if ENABLE_FEATURE_VI_DOT_CMD
577 static void start_new_cmd_q(char); // new queue for command
578 static void end_cmd_q(void); // stop saving input chars
580 #define end_cmd_q() ((void)0)
582 #if ENABLE_FEATURE_VI_SETOPTS
583 static void showmatching(char *); // show the matching pair () [] {}
585 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
586 // might reallocate text[]! use p += string_insert(p, ...),
587 // and be careful to not use pointers into potentially freed text[]!
588 # if !ENABLE_FEATURE_VI_UNDO
589 #define string_insert(a,b,c) string_insert(a,b)
591 static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
593 #if ENABLE_FEATURE_VI_YANKMARK
594 static char *text_yank(char *, char *, int); // save copy of "p" into a register
595 static char what_reg(void); // what is letter of current YDreg
596 static void check_context(char); // remember context for '' command
598 #if ENABLE_FEATURE_VI_UNDO
599 static void flush_undo_data(void);
600 static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
601 static void undo_pop(void); // Undo the last operation
602 # if ENABLE_FEATURE_VI_UNDO_QUEUE
603 static void undo_queue_commit(void); // Flush any queued objects to the undo stack
605 # define undo_queue_commit() ((void)0)
608 #define flush_undo_data() ((void)0)
609 #define undo_queue_commit() ((void)0)
612 #if ENABLE_FEATURE_VI_CRASHME
613 static void crash_dummy();
614 static void crash_test();
615 static int crashme = 0;
618 static void write1(const char *out)
623 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
624 int vi_main(int argc, char **argv)
630 #if ENABLE_FEATURE_VI_UNDO
631 /* undo_stack_tail = NULL; - already is */
632 #if ENABLE_FEATURE_VI_UNDO_QUEUE
633 undo_queue_state = UNDO_EMPTY;
634 /* undo_q = 0; - already is */
638 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
641 #if ENABLE_FEATURE_VI_CRASHME
642 srand((long) my_pid);
644 #ifdef NO_SUCH_APPLET_YET
645 /* If we aren't "vi", we are "view" */
646 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
647 SET_READONLY_MODE(readonly_mode);
651 // autoindent is not default in vim 7.3
652 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
653 // 1- process $HOME/.exrc file (not inplemented yet)
654 // 2- process EXINIT variable from environment
655 // 3- process command line args
656 #if ENABLE_FEATURE_VI_COLON
658 char *p = getenv("EXINIT");
660 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
663 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
665 #if ENABLE_FEATURE_VI_CRASHME
670 #if ENABLE_FEATURE_VI_READONLY
671 case 'R': // Read-only flag
672 SET_READONLY_MODE(readonly_mode);
675 #if ENABLE_FEATURE_VI_COLON
676 case 'c': // cmd line vi command
678 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
690 // The argv array can be used by the ":next" and ":rewind" commands
694 //----- This is the main file handling loop --------------
697 // "Save cursor, use alternate screen buffer, clear screen"
698 write1(ESC"[?1049h");
700 edit_file(argv[optind]); /* param might be NULL */
701 if (++optind >= argc)
704 // "Use normal screen buffer, restore cursor"
705 write1(ESC"[?1049l");
706 //-----------------------------------------------------------
711 /* read text from file or create an empty buf */
712 /* will also update current_filename */
713 static int init_text_buffer(char *fn)
717 /* allocate/reallocate text buffer */
720 screenbegin = dot = end = text = xzalloc(text_size);
722 if (fn != current_filename) {
723 free(current_filename);
724 current_filename = xstrdup(fn);
726 rc = file_insert(fn, text, 1);
728 // file doesnt exist. Start empty buf with dummy line
729 char_insert(text, '\n', NO_UNDO);
734 last_modified_count = -1;
735 #if ENABLE_FEATURE_VI_YANKMARK
737 memset(mark, 0, sizeof(mark));
742 #if ENABLE_FEATURE_VI_WIN_RESIZE
743 static int query_screen_dimensions(void)
745 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
746 if (rows > MAX_SCR_ROWS)
748 if (columns > MAX_SCR_COLS)
749 columns = MAX_SCR_COLS;
753 static ALWAYS_INLINE int query_screen_dimensions(void)
759 static void edit_file(char *fn)
761 #if ENABLE_FEATURE_VI_YANKMARK
762 #define cur_line edit_file__cur_line
765 #if ENABLE_FEATURE_VI_USE_SIGNALS
769 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
773 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
774 #if ENABLE_FEATURE_VI_ASK_TERMINAL
775 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
777 write1(ESC"[999;999H" ESC"[6n");
779 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
780 if ((int32_t)k == KEYCODE_CURSOR_POS) {
781 uint32_t rc = (k >> 32);
782 columns = (rc & 0x7fff);
783 if (columns > MAX_SCR_COLS)
784 columns = MAX_SCR_COLS;
785 rows = ((rc >> 16) & 0x7fff);
786 if (rows > MAX_SCR_ROWS)
791 new_screen(rows, columns); // get memory for virtual screen
792 init_text_buffer(fn);
794 #if ENABLE_FEATURE_VI_YANKMARK
795 YDreg = 26; // default Yank/Delete reg
796 Ureg = 27; // hold orig line for "U" cmd
797 mark[26] = mark[27] = text; // init "previous context"
800 last_forward_char = last_input_char = '\0';
804 #if ENABLE_FEATURE_VI_USE_SIGNALS
805 signal(SIGINT, catch_sig);
806 signal(SIGWINCH, winch_sig);
807 signal(SIGTSTP, suspend_sig);
808 sig = sigsetjmp(restart, 1);
810 screenbegin = dot = text;
814 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
817 offset = 0; // no horizontal offset
819 #if ENABLE_FEATURE_VI_DOT_CMD
821 ioq = ioq_start = NULL;
826 #if ENABLE_FEATURE_VI_COLON
831 while ((p = initial_cmds[n]) != NULL) {
841 free(initial_cmds[n]);
842 initial_cmds[n] = NULL;
847 redraw(FALSE); // dont force every col re-draw
848 //------This is the main Vi cmd handling loop -----------------------
849 while (editing > 0) {
850 #if ENABLE_FEATURE_VI_CRASHME
852 if ((end - text) > 1) {
853 crash_dummy(); // generate a random command
856 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
862 last_input_char = c = get_one_char(); // get a cmd from user
863 #if ENABLE_FEATURE_VI_YANKMARK
864 // save a copy of the current line- for the 'U" command
865 if (begin_line(dot) != cur_line) {
866 cur_line = begin_line(dot);
867 text_yank(begin_line(dot), end_line(dot), Ureg);
870 #if ENABLE_FEATURE_VI_DOT_CMD
871 // These are commands that change text[].
872 // Remember the input for the "." command
873 if (!adding2q && ioq_start == NULL
874 && cmd_mode == 0 // command mode
875 && c > '\0' // exclude NUL and non-ASCII chars
876 && c < 0x7f // (Unicode and such)
877 && strchr(modifying_cmds, c)
882 do_cmd(c); // execute the user command
884 // poll to see if there is input already waiting. if we are
885 // not able to display output fast enough to keep up, skip
886 // the display update until we catch up with input.
887 if (!readbuffer[0] && mysleep(0) == 0) {
888 // no input pending - so update output
892 #if ENABLE_FEATURE_VI_CRASHME
894 crash_test(); // test editor variables
897 //-------------------------------------------------------------------
899 go_bottom_and_clear_to_eol();
904 //----- The Colon commands -------------------------------------
905 #if ENABLE_FEATURE_VI_COLON
906 static char *get_one_address(char *p, int *addr) // get colon addr, if present
910 IF_FEATURE_VI_YANKMARK(char c;)
911 IF_FEATURE_VI_SEARCH(char *pat;)
913 *addr = -1; // assume no addr
914 if (*p == '.') { // the current line
917 *addr = count_lines(text, q);
919 #if ENABLE_FEATURE_VI_YANKMARK
920 else if (*p == '\'') { // is this a mark addr
924 if (c >= 'a' && c <= 'z') {
927 q = mark[(unsigned char) c];
928 if (q != NULL) { // is mark valid
929 *addr = count_lines(text, q);
934 #if ENABLE_FEATURE_VI_SEARCH
935 else if (*p == '/') { // a search pattern
936 q = strchrnul(++p, '/');
937 pat = xstrndup(p, q - p); // save copy of pattern
941 q = char_search(dot, pat, (FORWARD << 1) | FULL);
943 *addr = count_lines(text, q);
948 else if (*p == '$') { // the last line in file
950 q = begin_line(end - 1);
951 *addr = count_lines(text, q);
952 } else if (isdigit(*p)) { // specific line number
953 sscanf(p, "%d%n", addr, &st);
956 // unrecognized address - assume -1
962 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
964 //----- get the address' i.e., 1,3 'a,'b -----
965 // get FIRST addr, if present
967 p++; // skip over leading spaces
968 if (*p == '%') { // alias for 1,$
971 *e = count_lines(text, end-1);
974 p = get_one_address(p, b);
977 if (*p == ',') { // is there a address separator
981 // get SECOND addr, if present
982 p = get_one_address(p, e);
986 p++; // skip over trailing spaces
990 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
991 static void setops(const char *args, const char *opname, int flg_no,
992 const char *short_opname, int opt)
994 const char *a = args + flg_no;
995 int l = strlen(opname) - 1; /* opname have + ' ' */
997 // maybe strncmp? we had tons of erroneous strncasecmp's...
998 if (strncasecmp(a, opname, l) == 0
999 || strncasecmp(a, short_opname, 2) == 0
1009 #endif /* FEATURE_VI_COLON */
1011 // buf must be no longer than MAX_INPUT_LEN!
1012 static void colon(char *buf)
1014 #if !ENABLE_FEATURE_VI_COLON
1015 /* Simple ":cmd" handler with minimal set of commands */
1024 if (strncmp(p, "quit", cnt) == 0
1025 || strncmp(p, "q!", cnt) == 0
1027 if (modified_count && p[1] != '!') {
1028 status_line_bold("No write since last change (:%s! overrides)", p);
1034 if (strncmp(p, "write", cnt) == 0
1035 || strncmp(p, "wq", cnt) == 0
1036 || strncmp(p, "wn", cnt) == 0
1037 || (p[0] == 'x' && !p[1])
1039 if (modified_count != 0 || p[0] != 'x') {
1040 cnt = file_write(current_filename, text, end - 1);
1044 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
1047 last_modified_count = -1;
1048 status_line("'%s' %dL, %dC",
1050 count_lines(text, end - 1), cnt
1053 || p[1] == 'q' || p[1] == 'n'
1054 || p[1] == 'Q' || p[1] == 'N'
1061 if (strncmp(p, "file", cnt) == 0) {
1062 last_status_cksum = 0; // force status update
1065 if (sscanf(p, "%d", &cnt) > 0) {
1066 dot = find_line(cnt);
1073 char c, *buf1, *q, *r;
1074 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
1077 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
1081 // :3154 // if (-e line 3154) goto it else stay put
1082 // :4,33w! foo // write a portion of buffer to file "foo"
1083 // :w // write all of buffer to current file
1085 // :q! // quit- dont care about modified file
1086 // :'a,'z!sort -u // filter block through sort
1087 // :'f // goto mark "f"
1088 // :'fl // list literal the mark "f" line
1089 // :.r bar // read file "bar" into buffer before dot
1090 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1091 // :/xyz/ // goto the "xyz" line
1092 // :s/find/replace/ // substitute pattern "find" with "replace"
1093 // :!<cmd> // run <cmd> then return
1099 buf++; // move past the ':'
1103 q = text; // assume 1,$ for the range
1105 li = count_lines(text, end - 1);
1106 fn = current_filename;
1108 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1109 buf = get_address(buf, &b, &e);
1111 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
1112 // remember orig command line
1116 // get the COMMAND into cmd[]
1118 while (*buf != '\0') {
1124 // get any ARGuments
1125 while (isblank(*buf))
1129 buf1 = last_char_is(cmd, '!');
1132 *buf1 = '\0'; // get rid of !
1135 // if there is only one addr, then the addr
1136 // is the line number of the single line the
1137 // user wants. So, reset the end
1138 // pointer to point at end of the "b" line
1139 q = find_line(b); // what line is #b
1144 // we were given two addrs. change the
1145 // end pointer to the addr given by user.
1146 r = find_line(e); // what line is #e
1150 // ------------ now look for the command ------------
1152 if (i == 0) { // :123CR goto line #123
1154 dot = find_line(b); // what line is #b
1158 # if ENABLE_FEATURE_ALLOW_EXEC
1159 else if (cmd[0] == '!') { // run a cmd
1161 // :!ls run the <cmd>
1162 go_bottom_and_clear_to_eol();
1164 retcode = system(orig_buf + 1); // run the cmd
1166 printf("\nshell returned %i\n\n", retcode);
1168 Hit_Return(); // let user see results
1171 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1172 if (b < 0) { // no addr given- use defaults
1173 b = e = count_lines(text, dot);
1175 status_line("%d", b);
1176 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1177 if (b < 0) { // no addr given- use defaults
1178 q = begin_line(dot); // assume .,. for the range
1181 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
1183 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1186 // don't edit, if the current file has been modified
1187 if (modified_count && !useforce) {
1188 status_line_bold("No write since last change (:%s! overrides)", cmd);
1192 // the user supplied a file name
1194 } else if (current_filename && current_filename[0]) {
1195 // no user supplied name- use the current filename
1196 // fn = current_filename; was set by default
1198 // no user file name, no current name- punt
1199 status_line_bold("No current filename");
1203 size = init_text_buffer(fn);
1205 # if ENABLE_FEATURE_VI_YANKMARK
1206 if (Ureg >= 0 && Ureg < 28) {
1207 free(reg[Ureg]); // free orig line reg- for 'U'
1210 if (YDreg >= 0 && YDreg < 28) {
1211 free(reg[YDreg]); // free default yank/delete register
1215 // how many lines in text[]?
1216 li = count_lines(text, end - 1);
1217 status_line("'%s'%s"
1218 IF_FEATURE_VI_READONLY("%s")
1221 (size < 0 ? " [New file]" : ""),
1222 IF_FEATURE_VI_READONLY(
1223 ((readonly_mode) ? " [Readonly]" : ""),
1225 li, (int)(end - text)
1227 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1228 if (b != -1 || e != -1) {
1229 status_line_bold("No address allowed on this command");
1233 // user wants a new filename
1234 free(current_filename);
1235 current_filename = xstrdup(args);
1237 // user wants file status info
1238 last_status_cksum = 0; // force status update
1240 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1241 // print out values of all features
1242 go_bottom_and_clear_to_eol();
1247 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1248 if (b < 0) { // no addr given- use defaults
1249 q = begin_line(dot); // assume .,. for the range
1252 go_bottom_and_clear_to_eol();
1254 for (; q <= r; q++) {
1258 c_is_no_print = (c & 0x80) && !Isprint(c);
1259 if (c_is_no_print) {
1265 } else if (c < ' ' || c == 127) {
1277 } else if (strncmp(cmd, "quit", i) == 0 // quit
1278 || strncmp(cmd, "next", i) == 0 // edit next file
1279 || strncmp(cmd, "prev", i) == 0 // edit previous file
1284 // force end of argv list
1290 // don't exit if the file been modified
1291 if (modified_count) {
1292 status_line_bold("No write since last change (:%s! overrides)", cmd);
1295 // are there other file to edit
1296 n = save_argc - optind - 1;
1297 if (*cmd == 'q' && n > 0) {
1298 status_line_bold("%d more file(s) to edit", n);
1301 if (*cmd == 'n' && n <= 0) {
1302 status_line_bold("No more files to edit");
1306 // are there previous files to edit
1308 status_line_bold("No previous files to edit");
1314 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1319 status_line_bold("No filename given");
1322 if (b < 0) { // no addr given- use defaults
1323 q = begin_line(dot); // assume "dot"
1325 // read after current line- unless user said ":0r foo"
1328 // read after last line
1332 { // dance around potentially-reallocated text[]
1333 uintptr_t ofs = q - text;
1334 size = file_insert(fn, q, 0);
1338 goto ret; // nothing was inserted
1339 // how many lines in text[]?
1340 li = count_lines(q, q + size - 1);
1342 IF_FEATURE_VI_READONLY("%s")
1345 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1349 // if the insert is before "dot" then we need to update
1353 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1354 if (modified_count && !useforce) {
1355 status_line_bold("No write since last change (:%s! overrides)", cmd);
1357 // reset the filenames to edit
1358 optind = -1; /* start from 0th file */
1361 # if ENABLE_FEATURE_VI_SET
1362 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1363 # if ENABLE_FEATURE_VI_SETOPTS
1366 i = 0; // offset into args
1367 // only blank is regarded as args delimiter. What about tab '\t'?
1368 if (!args[0] || strcasecmp(args, "all") == 0) {
1369 // print out values of all options
1370 # if ENABLE_FEATURE_VI_SETOPTS
1377 autoindent ? "" : "no",
1378 err_method ? "" : "no",
1379 ignorecase ? "" : "no",
1380 showmatch ? "" : "no",
1386 # if ENABLE_FEATURE_VI_SETOPTS
1389 if (strncmp(argp, "no", 2) == 0)
1390 i = 2; // ":set noautoindent"
1391 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1392 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1393 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1394 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1395 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1397 sscanf(argp + i+8, "%u", &t);
1398 if (t > 0 && t <= MAX_TABSTOP)
1401 argp = skip_non_whitespace(argp);
1402 argp = skip_whitespace(argp);
1404 # endif /* FEATURE_VI_SETOPTS */
1405 # endif /* FEATURE_VI_SET */
1407 # if ENABLE_FEATURE_VI_SEARCH
1408 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1409 char *F, *R, *flags;
1410 size_t len_F, len_R;
1411 int gflag; // global replace flag
1412 # if ENABLE_FEATURE_VI_UNDO
1413 int dont_chain_first_item = ALLOW_UNDO;
1416 // F points to the "find" pattern
1417 // R points to the "replace" pattern
1418 // replace the cmd line delimiters "/" with NULs
1419 c = orig_buf[1]; // what is the delimiter
1420 F = orig_buf + 2; // start of "find"
1421 R = strchr(F, c); // middle delimiter
1425 *R++ = '\0'; // terminate "find"
1426 flags = strchr(R, c);
1430 *flags++ = '\0'; // terminate "replace"
1434 if (b < 0) { // maybe :s/foo/bar/
1435 q = begin_line(dot); // start with cur line
1436 b = count_lines(text, q); // cur line number
1439 e = b; // maybe :.s/foo/bar/
1441 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1442 char *ls = q; // orig line start
1445 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
1448 // we found the "find" pattern - delete it
1449 // For undo support, the first item should not be chained
1450 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
1451 # if ENABLE_FEATURE_VI_UNDO
1452 dont_chain_first_item = ALLOW_UNDO_CHAIN;
1454 // insert the "replace" patern
1455 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
1458 /*q += bias; - recalculated anyway */
1459 // check for "global" :s/foo/bar/g
1461 if ((found + len_R) < end_line(ls)) {
1463 goto vc4; // don't let q move past cur line
1469 # endif /* FEATURE_VI_SEARCH */
1470 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1471 status_line(BB_VER);
1472 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1473 || strncmp(cmd, "wq", i) == 0
1474 || strncmp(cmd, "wn", i) == 0
1475 || (cmd[0] == 'x' && !cmd[1])
1478 //int forced = FALSE;
1480 // is there a file name to write to?
1484 # if ENABLE_FEATURE_VI_READONLY
1485 if (readonly_mode && !useforce) {
1486 status_line_bold("'%s' is read only", fn);
1491 // if "fn" is not write-able, chmod u+w
1492 // sprintf(syscmd, "chmod u+w %s", fn);
1496 if (modified_count != 0 || cmd[0] != 'x') {
1498 l = file_write(fn, q, r);
1503 //if (useforce && forced) {
1505 // sprintf(syscmd, "chmod u-w %s", fn);
1511 status_line_bold_errno(fn);
1513 // how many lines written
1514 li = count_lines(q, q + l - 1);
1515 status_line("'%s' %dL, %dC", fn, li, l);
1517 if (q == text && q + l == end) {
1519 last_modified_count = -1;
1522 || cmd[1] == 'q' || cmd[1] == 'n'
1523 || cmd[1] == 'Q' || cmd[1] == 'N'
1529 # if ENABLE_FEATURE_VI_YANKMARK
1530 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1531 if (b < 0) { // no addr given- use defaults
1532 q = begin_line(dot); // assume .,. for the range
1535 text_yank(q, r, YDreg);
1536 li = count_lines(q, r);
1537 status_line("Yank %d lines (%d chars) into [%c]",
1538 li, strlen(reg[YDreg]), what_reg());
1542 not_implemented(cmd);
1545 dot = bound_dot(dot); // make sure "dot" is valid
1547 # if ENABLE_FEATURE_VI_SEARCH
1549 status_line(":s expression missing delimiters");
1551 #endif /* FEATURE_VI_COLON */
1554 static void Hit_Return(void)
1559 write1("[Hit return to continue]");
1561 while ((c = get_one_char()) != '\n' && c != '\r')
1563 redraw(TRUE); // force redraw all
1566 static int next_tabstop(int col)
1568 return col + ((tabstop - 1) - (col % tabstop));
1571 //----- Synchronize the cursor to Dot --------------------------
1572 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1574 char *beg_cur; // begin and end of "d" line
1578 beg_cur = begin_line(d); // first char of cur line
1580 if (beg_cur < screenbegin) {
1581 // "d" is before top line on screen
1582 // how many lines do we have to move
1583 cnt = count_lines(beg_cur, screenbegin);
1585 screenbegin = beg_cur;
1586 if (cnt > (rows - 1) / 2) {
1587 // we moved too many lines. put "dot" in middle of screen
1588 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1589 screenbegin = prev_line(screenbegin);
1593 char *end_scr; // begin and end of screen
1594 end_scr = end_screen(); // last char of screen
1595 if (beg_cur > end_scr) {
1596 // "d" is after bottom line on screen
1597 // how many lines do we have to move
1598 cnt = count_lines(end_scr, beg_cur);
1599 if (cnt > (rows - 1) / 2)
1600 goto sc1; // too many lines
1601 for (ro = 0; ro < cnt - 1; ro++) {
1602 // move screen begin the same amount
1603 screenbegin = next_line(screenbegin);
1604 // now, move the end of screen
1605 end_scr = next_line(end_scr);
1606 end_scr = end_line(end_scr);
1610 // "d" is on screen- find out which row
1612 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1618 // find out what col "d" is on
1620 while (tp < d) { // drive "co" to correct column
1621 if (*tp == '\n') //vda || *tp == '\0')
1624 // handle tabs like real vi
1625 if (d == tp && cmd_mode) {
1628 co = next_tabstop(co);
1629 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1630 co++; // display as ^X, use 2 columns
1636 // "co" is the column where "dot" is.
1637 // The screen has "columns" columns.
1638 // The currently displayed columns are 0+offset -- columns+ofset
1639 // |-------------------------------------------------------------|
1641 // offset | |------- columns ----------------|
1643 // If "co" is already in this range then we do not have to adjust offset
1644 // but, we do have to subtract the "offset" bias from "co".
1645 // If "co" is outside this range then we have to change "offset".
1646 // If the first char of a line is a tab the cursor will try to stay
1647 // in column 7, but we have to set offset to 0.
1649 if (co < 0 + offset) {
1652 if (co >= columns + offset) {
1653 offset = co - columns + 1;
1655 // if the first char of the line is a tab, and "dot" is sitting on it
1656 // force offset to 0.
1657 if (d == beg_cur && *d == '\t') {
1666 //----- Text Movement Routines ---------------------------------
1667 static char *begin_line(char *p) // return pointer to first char cur line
1670 p = memrchr(text, '\n', p - text);
1678 static char *end_line(char *p) // return pointer to NL of cur line
1681 p = memchr(p, '\n', end - p - 1);
1688 static char *dollar_line(char *p) // return pointer to just before NL line
1691 // Try to stay off of the Newline
1692 if (*p == '\n' && (p - begin_line(p)) > 0)
1697 static char *prev_line(char *p) // return pointer first char prev line
1699 p = begin_line(p); // goto beginning of cur line
1700 if (p > text && p[-1] == '\n')
1701 p--; // step to prev line
1702 p = begin_line(p); // goto beginning of prev line
1706 static char *next_line(char *p) // return pointer first char next line
1709 if (p < end - 1 && *p == '\n')
1710 p++; // step to next line
1714 //----- Text Information Routines ------------------------------
1715 static char *end_screen(void)
1720 // find new bottom line
1722 for (cnt = 0; cnt < rows - 2; cnt++)
1728 // count line from start to stop
1729 static int count_lines(char *start, char *stop)
1734 if (stop < start) { // start and stop are backwards- reverse them
1740 stop = end_line(stop);
1741 while (start <= stop && start <= end - 1) {
1742 start = end_line(start);
1750 static char *find_line(int li) // find beginning of line #li
1754 for (q = text; li > 1; li--) {
1760 //----- Dot Movement Routines ----------------------------------
1761 static void dot_left(void)
1763 undo_queue_commit();
1764 if (dot > text && dot[-1] != '\n')
1768 static void dot_right(void)
1770 undo_queue_commit();
1771 if (dot < end - 1 && *dot != '\n')
1775 static void dot_begin(void)
1777 undo_queue_commit();
1778 dot = begin_line(dot); // return pointer to first char cur line
1781 static void dot_end(void)
1783 undo_queue_commit();
1784 dot = end_line(dot); // return pointer to last char cur line
1787 static char *move_to_col(char *p, int l)
1793 while (co < l && p < end) {
1794 if (*p == '\n') //vda || *p == '\0')
1797 co = next_tabstop(co);
1798 } else if (*p < ' ' || *p == 127) {
1799 co++; // display as ^X, use 2 columns
1807 static void dot_next(void)
1809 undo_queue_commit();
1810 dot = next_line(dot);
1813 static void dot_prev(void)
1815 undo_queue_commit();
1816 dot = prev_line(dot);
1819 static void dot_scroll(int cnt, int dir)
1823 undo_queue_commit();
1824 for (; cnt > 0; cnt--) {
1827 // ctrl-Y scroll up one line
1828 screenbegin = prev_line(screenbegin);
1831 // ctrl-E scroll down one line
1832 screenbegin = next_line(screenbegin);
1835 // make sure "dot" stays on the screen so we dont scroll off
1836 if (dot < screenbegin)
1838 q = end_screen(); // find new bottom line
1840 dot = begin_line(q); // is dot is below bottom line?
1844 static void dot_skip_over_ws(void)
1847 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1851 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1853 if (p >= end && end > text) {
1864 //----- Helper Utility Routines --------------------------------
1866 //----------------------------------------------------------------
1867 //----- Char Routines --------------------------------------------
1868 /* Chars that are part of a word-
1869 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1870 * Chars that are Not part of a word (stoppers)
1871 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1872 * Chars that are WhiteSpace
1873 * TAB NEWLINE VT FF RETURN SPACE
1874 * DO NOT COUNT NEWLINE AS WHITESPACE
1877 static char *new_screen(int ro, int co)
1882 screensize = ro * co + 8;
1883 screen = xmalloc(screensize);
1884 // initialize the new screen. assume this will be a empty file.
1886 // non-existent text[] lines start with a tilde (~).
1887 for (li = 1; li < ro - 1; li++) {
1888 screen[(li * co) + 0] = '~';
1893 #if ENABLE_FEATURE_VI_SEARCH
1895 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1897 // search for pattern starting at p
1898 static char *char_search(char *p, const char *pat, int dir_and_range)
1900 struct re_pattern_buffer preg;
1907 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1909 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1911 memset(&preg, 0, sizeof(preg));
1912 err = re_compile_pattern(pat, strlen(pat), &preg);
1914 status_line_bold("bad search pattern '%s': %s", pat, err);
1918 range = (dir_and_range & 1);
1919 q = end - 1; // if FULL
1920 if (range == LIMITED)
1922 if (dir_and_range < 0) { // BACK?
1924 if (range == LIMITED)
1928 // RANGE could be negative if we are searching backwards
1938 // search for the compiled pattern, preg, in p[]
1939 // range < 0: search backward
1940 // range > 0: search forward
1942 // re_search() < 0: not found or error
1943 // re_search() >= 0: index of found pattern
1944 // struct pattern char int int int struct reg
1945 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1946 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1950 if (dir_and_range > 0) // FORWARD?
1959 # if ENABLE_FEATURE_VI_SETOPTS
1960 static int mycmp(const char *s1, const char *s2, int len)
1963 return strncasecmp(s1, s2, len);
1965 return strncmp(s1, s2, len);
1968 # define mycmp strncmp
1971 static char *char_search(char *p, const char *pat, int dir_and_range)
1978 range = (dir_and_range & 1);
1979 if (dir_and_range > 0) { //FORWARD?
1980 stop = end - 1; // assume range is p..end-1
1981 if (range == LIMITED)
1982 stop = next_line(p); // range is to next line
1983 for (start = p; start < stop; start++) {
1984 if (mycmp(start, pat, len) == 0) {
1989 stop = text; // assume range is text..p
1990 if (range == LIMITED)
1991 stop = prev_line(p); // range is to prev line
1992 for (start = p - len; start >= stop; start--) {
1993 if (mycmp(start, pat, len) == 0) {
1998 // pattern not found
2004 #endif /* FEATURE_VI_SEARCH */
2006 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
2008 if (c == 22) { // Is this an ctrl-V?
2009 p += stupid_insert(p, '^'); // use ^ to indicate literal next
2010 refresh(FALSE); // show the ^
2013 #if ENABLE_FEATURE_VI_UNDO
2016 undo_push(p, 1, UNDO_INS);
2018 case ALLOW_UNDO_CHAIN:
2019 undo_push(p, 1, UNDO_INS_CHAIN);
2021 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2022 case ALLOW_UNDO_QUEUED:
2023 undo_push(p, 1, UNDO_INS_QUEUED);
2029 #endif /* ENABLE_FEATURE_VI_UNDO */
2031 } else if (c == 27) { // Is this an ESC?
2033 undo_queue_commit();
2035 end_cmd_q(); // stop adding to q
2036 last_status_cksum = 0; // force status update
2037 if ((p[-1] != '\n') && (dot > text)) {
2040 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
2043 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2046 // insert a char into text[]
2048 c = '\n'; // translate \r to \n
2049 #if ENABLE_FEATURE_VI_UNDO
2050 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2052 undo_queue_commit();
2056 undo_push(p, 1, UNDO_INS);
2058 case ALLOW_UNDO_CHAIN:
2059 undo_push(p, 1, UNDO_INS_CHAIN);
2061 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2062 case ALLOW_UNDO_QUEUED:
2063 undo_push(p, 1, UNDO_INS_QUEUED);
2069 #endif /* ENABLE_FEATURE_VI_UNDO */
2070 p += 1 + stupid_insert(p, c); // insert the char
2071 #if ENABLE_FEATURE_VI_SETOPTS
2072 if (showmatch && strchr(")]}", c) != NULL) {
2073 showmatching(p - 1);
2075 if (autoindent && c == '\n') { // auto indent the new line
2078 q = prev_line(p); // use prev line as template
2079 len = strspn(q, " \t"); // space or tab
2082 bias = text_hole_make(p, len);
2085 #if ENABLE_FEATURE_VI_UNDO
2086 undo_push(p, len, UNDO_INS);
2097 // might reallocate text[]! use p += stupid_insert(p, ...),
2098 // and be careful to not use pointers into potentially freed text[]!
2099 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2102 bias = text_hole_make(p, 1);
2108 static int find_range(char **start, char **stop, char c)
2110 char *save_dot, *p, *q, *t;
2111 int cnt, multiline = 0;
2116 if (strchr("cdy><", c)) {
2117 // these cmds operate on whole lines
2118 p = q = begin_line(p);
2119 for (cnt = 1; cnt < cmdcnt; cnt++) {
2123 } else if (strchr("^%$0bBeEfth\b\177", c)) {
2124 // These cmds operate on char positions
2125 do_cmd(c); // execute movement cmd
2127 } else if (strchr("wW", c)) {
2128 do_cmd(c); // execute movement cmd
2129 // if we are at the next word's first char
2130 // step back one char
2131 // but check the possibilities when it is true
2132 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
2133 || (ispunct(dot[-1]) && !ispunct(dot[0]))
2134 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
2135 dot--; // move back off of next word
2136 if (dot > text && *dot == '\n')
2137 dot--; // stay off NL
2139 } else if (strchr("H-k{", c)) {
2140 // these operate on multi-lines backwards
2141 q = end_line(dot); // find NL
2142 do_cmd(c); // execute movement cmd
2145 } else if (strchr("L+j}\r\n", c)) {
2146 // these operate on multi-lines forwards
2147 p = begin_line(dot);
2148 do_cmd(c); // execute movement cmd
2149 dot_end(); // find NL
2152 // nothing -- this causes any other values of c to
2153 // represent the one-character range under the
2154 // cursor. this is correct for ' ' and 'l', but
2155 // perhaps no others.
2164 // backward char movements don't include start position
2165 if (q > p && strchr("^0bBh\b\177", c)) q--;
2168 for (t = p; t <= q; t++) {
2181 static int st_test(char *p, int type, int dir, char *tested)
2191 if (type == S_BEFORE_WS) {
2193 test = (!isspace(c) || c == '\n');
2195 if (type == S_TO_WS) {
2197 test = (!isspace(c) || c == '\n');
2199 if (type == S_OVER_WS) {
2203 if (type == S_END_PUNCT) {
2207 if (type == S_END_ALNUM) {
2209 test = (isalnum(c) || c == '_');
2215 static char *skip_thing(char *p, int linecnt, int dir, int type)
2219 while (st_test(p, type, dir, &c)) {
2220 // make sure we limit search to correct number of lines
2221 if (c == '\n' && --linecnt < 1)
2223 if (dir >= 0 && p >= end - 1)
2225 if (dir < 0 && p <= text)
2227 p += dir; // move to next char
2232 // find matching char of pair () [] {}
2233 // will crash if c is not one of these
2234 static char *find_pair(char *p, const char c)
2236 const char *braces = "()[]{}";
2240 dir = strchr(braces, c) - braces;
2242 match = braces[dir];
2243 dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
2245 // look for match, count levels of pairs (( ))
2249 if (p < text || p >= end)
2252 level++; // increase pair levels
2254 level--; // reduce pair level
2256 return p; // found matching pair
2261 #if ENABLE_FEATURE_VI_SETOPTS
2262 // show the matching char of a pair, () [] {}
2263 static void showmatching(char *p)
2267 // we found half of a pair
2268 q = find_pair(p, *p); // get loc of matching char
2270 indicate_error(); // no matching char
2272 // "q" now points to matching pair
2273 save_dot = dot; // remember where we are
2274 dot = q; // go to new loc
2275 refresh(FALSE); // let the user see it
2276 mysleep(40); // give user some time
2277 dot = save_dot; // go back to old loc
2281 #endif /* FEATURE_VI_SETOPTS */
2283 #if ENABLE_FEATURE_VI_UNDO
2284 static void flush_undo_data(void)
2286 struct undo_object *undo_entry;
2288 while (undo_stack_tail) {
2289 undo_entry = undo_stack_tail;
2290 undo_stack_tail = undo_entry->prev;
2295 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2296 static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack
2298 struct undo_object *undo_entry;
2301 // UNDO_INS: insertion, undo will remove from buffer
2302 // UNDO_DEL: deleted text, undo will restore to buffer
2303 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2304 // The CHAIN operations are for handling multiple operations that the user
2305 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2306 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2307 // for the INS/DEL operation. The raw values should be equal to the values of
2308 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2310 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2311 // This undo queuing functionality groups multiple character typing or backspaces
2312 // into a single large undo object. This greatly reduces calls to malloc() for
2313 // single-character operations while typing and has the side benefit of letting
2314 // an undo operation remove chunks of text rather than a single character.
2316 case UNDO_EMPTY: // Just in case this ever happens...
2318 case UNDO_DEL_QUEUED:
2320 return; // Only queue single characters
2321 switch (undo_queue_state) {
2323 undo_queue_state = UNDO_DEL;
2325 undo_queue_spos = src;
2327 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2328 // If queue is full, dump it into an object
2329 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2330 undo_queue_commit();
2333 // Switch from storing inserted text to deleted text
2334 undo_queue_commit();
2335 undo_push(src, length, UNDO_DEL_QUEUED);
2339 case UNDO_INS_QUEUED:
2342 switch (undo_queue_state) {
2344 undo_queue_state = UNDO_INS;
2345 undo_queue_spos = src;
2347 undo_q++; // Don't need to save any data for insertions
2348 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2349 undo_queue_commit();
2352 // Switch from storing deleted text to inserted text
2353 undo_queue_commit();
2354 undo_push(src, length, UNDO_INS_QUEUED);
2360 // If undo queuing is disabled, ignore the queuing flag entirely
2361 u_type = u_type & ~UNDO_QUEUED_FLAG;
2364 // Allocate a new undo object
2365 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2366 // For UNDO_DEL objects, save deleted text
2367 if ((text + length) == end)
2369 // If this deletion empties text[], strip the newline. When the buffer becomes
2370 // zero-length, a newline is added back, which requires this to compensate.
2371 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
2372 memcpy(undo_entry->undo_text, src, length);
2374 undo_entry = xzalloc(sizeof(*undo_entry));
2376 undo_entry->length = length;
2377 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2378 if ((u_type & UNDO_USE_SPOS) != 0) {
2379 undo_entry->start = undo_queue_spos - text; // use start position from queue
2381 undo_entry->start = src - text; // use offset from start of text buffer
2383 u_type = (u_type & ~UNDO_USE_SPOS);
2385 undo_entry->start = src - text;
2387 undo_entry->u_type = u_type;
2389 // Push it on undo stack
2390 undo_entry->prev = undo_stack_tail;
2391 undo_stack_tail = undo_entry;
2395 static void undo_pop(void) // Undo the last operation
2398 char *u_start, *u_end;
2399 struct undo_object *undo_entry;
2401 // Commit pending undo queue before popping (should be unnecessary)
2402 undo_queue_commit();
2404 undo_entry = undo_stack_tail;
2405 // Check for an empty undo stack
2407 status_line("Already at oldest change");
2411 switch (undo_entry->u_type) {
2413 case UNDO_DEL_CHAIN:
2414 // make hole and put in text that was deleted; deallocate text
2415 u_start = text + undo_entry->start;
2416 text_hole_make(u_start, undo_entry->length);
2417 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2418 status_line("Undo [%d] %s %d chars at position %d",
2419 modified_count, "restored",
2420 undo_entry->length, undo_entry->start
2424 case UNDO_INS_CHAIN:
2425 // delete what was inserted
2426 u_start = undo_entry->start + text;
2427 u_end = u_start - 1 + undo_entry->length;
2428 text_hole_delete(u_start, u_end, NO_UNDO);
2429 status_line("Undo [%d] %s %d chars at position %d",
2430 modified_count, "deleted",
2431 undo_entry->length, undo_entry->start
2436 switch (undo_entry->u_type) {
2437 // If this is the end of a chain, lower modification count and refresh display
2440 dot = (text + undo_entry->start);
2443 case UNDO_DEL_CHAIN:
2444 case UNDO_INS_CHAIN:
2448 // Deallocate the undo object we just processed
2449 undo_stack_tail = undo_entry->prev;
2452 // For chained operations, continue popping all the way down the chain.
2454 undo_pop(); // Follow the undo chain if one exists
2458 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2459 static void undo_queue_commit(void) // Flush any queued objects to the undo stack
2461 // Pushes the queue object onto the undo stack
2463 // Deleted character undo events grow from the end
2464 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2466 (undo_queue_state | UNDO_USE_SPOS)
2468 undo_queue_state = UNDO_EMPTY;
2474 #endif /* ENABLE_FEATURE_VI_UNDO */
2476 // open a hole in text[]
2477 // might reallocate text[]! use p += text_hole_make(p, ...),
2478 // and be careful to not use pointers into potentially freed text[]!
2479 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2485 end += size; // adjust the new END
2486 if (end >= (text + text_size)) {
2488 text_size += end - (text + text_size) + 10240;
2489 new_text = xrealloc(text, text_size);
2490 bias = (new_text - text);
2491 screenbegin += bias;
2495 #if ENABLE_FEATURE_VI_YANKMARK
2498 for (i = 0; i < ARRAY_SIZE(mark); i++)
2505 memmove(p + size, p, end - size - p);
2506 memset(p, ' ', size); // clear new hole
2510 // close a hole in text[]
2511 // "undo" value indicates if this operation should be undo-able
2512 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2517 // move forwards, from beginning
2521 if (q < p) { // they are backward- swap them
2525 hole_size = q - p + 1;
2527 #if ENABLE_FEATURE_VI_UNDO
2532 undo_push(p, hole_size, UNDO_DEL);
2534 case ALLOW_UNDO_CHAIN:
2535 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2537 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2538 case ALLOW_UNDO_QUEUED:
2539 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2545 if (src < text || src > end)
2547 if (dest < text || dest >= end)
2551 goto thd_atend; // just delete the end of the buffer
2552 memmove(dest, src, cnt);
2554 end = end - hole_size; // adjust the new END
2556 dest = end - 1; // make sure dest in below end-1
2558 dest = end = text; // keep pointers valid
2563 // copy text into register, then delete text.
2564 // if dist <= 0, do not include, or go past, a NewLine
2566 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2570 // make sure start <= stop
2572 // they are backwards, reverse them
2578 // we cannot cross NL boundaries
2582 // dont go past a NewLine
2583 for (; p + 1 <= stop; p++) {
2585 stop = p; // "stop" just before NewLine
2591 #if ENABLE_FEATURE_VI_YANKMARK
2592 text_yank(start, stop, YDreg);
2594 if (yf == YANKDEL) {
2595 p = text_hole_delete(start, stop, undo);
2600 static void show_help(void)
2602 puts("These features are available:"
2603 #if ENABLE_FEATURE_VI_SEARCH
2604 "\n\tPattern searches with / and ?"
2606 #if ENABLE_FEATURE_VI_DOT_CMD
2607 "\n\tLast command repeat with ."
2609 #if ENABLE_FEATURE_VI_YANKMARK
2610 "\n\tLine marking with 'x"
2611 "\n\tNamed buffers with \"x"
2613 #if ENABLE_FEATURE_VI_READONLY
2614 //not implemented: "\n\tReadonly if vi is called as \"view\""
2615 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2617 #if ENABLE_FEATURE_VI_SET
2618 "\n\tSome colon mode commands with :"
2620 #if ENABLE_FEATURE_VI_SETOPTS
2621 "\n\tSettable options with \":set\""
2623 #if ENABLE_FEATURE_VI_USE_SIGNALS
2624 "\n\tSignal catching- ^C"
2625 "\n\tJob suspend and resume with ^Z"
2627 #if ENABLE_FEATURE_VI_WIN_RESIZE
2628 "\n\tAdapt to window re-sizes"
2633 #if ENABLE_FEATURE_VI_DOT_CMD
2634 static void start_new_cmd_q(char c)
2636 // get buffer for new cmd
2637 // if there is a current cmd count put it in the buffer first
2639 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2640 } else { // just save char c onto queue
2641 last_modifying_cmd[0] = c;
2647 static void end_cmd_q(void)
2649 #if ENABLE_FEATURE_VI_YANKMARK
2650 YDreg = 26; // go back to default Yank/Delete reg
2654 #endif /* FEATURE_VI_DOT_CMD */
2656 #if ENABLE_FEATURE_VI_YANKMARK \
2657 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2658 || ENABLE_FEATURE_VI_CRASHME
2659 // might reallocate text[]! use p += string_insert(p, ...),
2660 // and be careful to not use pointers into potentially freed text[]!
2661 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2667 #if ENABLE_FEATURE_VI_UNDO
2670 undo_push(p, i, UNDO_INS);
2672 case ALLOW_UNDO_CHAIN:
2673 undo_push(p, i, UNDO_INS_CHAIN);
2677 bias = text_hole_make(p, i);
2680 #if ENABLE_FEATURE_VI_YANKMARK
2683 for (cnt = 0; *s != '\0'; s++) {
2687 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2694 #if ENABLE_FEATURE_VI_YANKMARK
2695 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2698 if (cnt < 0) { // they are backwards- reverse them
2702 free(reg[dest]); // if already a yank register, free it
2703 reg[dest] = xstrndup(p, cnt + 1);
2707 static char what_reg(void)
2711 c = 'D'; // default to D-reg
2712 if (0 <= YDreg && YDreg <= 25)
2713 c = 'a' + (char) YDreg;
2721 static void check_context(char cmd)
2723 // A context is defined to be "modifying text"
2724 // Any modifying command establishes a new context.
2726 if (dot < context_start || dot > context_end) {
2727 if (strchr(modifying_cmds, cmd) != NULL) {
2728 // we are trying to modify text[]- make this the current context
2729 mark[27] = mark[26]; // move cur to prev
2730 mark[26] = dot; // move local to cur
2731 context_start = prev_line(prev_line(dot));
2732 context_end = next_line(next_line(dot));
2733 //loiter= start_loiter= now;
2738 static char *swap_context(char *p) // goto new context for '' command make this the current context
2742 // the current context is in mark[26]
2743 // the previous context is in mark[27]
2744 // only swap context if other context is valid
2745 if (text <= mark[27] && mark[27] <= end - 1) {
2749 context_start = prev_line(prev_line(prev_line(p)));
2750 context_end = next_line(next_line(next_line(p)));
2754 #endif /* FEATURE_VI_YANKMARK */
2756 //----- Set terminal attributes --------------------------------
2757 static void rawmode(void)
2759 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
2760 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
2761 erase_char = term_orig.c_cc[VERASE];
2764 static void cookmode(void)
2767 tcsetattr_stdin_TCSANOW(&term_orig);
2770 #if ENABLE_FEATURE_VI_USE_SIGNALS
2771 //----- Come here when we get a window resize signal ---------
2772 static void winch_sig(int sig UNUSED_PARAM)
2774 int save_errno = errno;
2775 // FIXME: do it in main loop!!!
2776 signal(SIGWINCH, winch_sig);
2777 query_screen_dimensions();
2778 new_screen(rows, columns); // get memory for virtual screen
2779 redraw(TRUE); // re-draw the screen
2783 //----- Come here when we get a continue signal -------------------
2784 static void cont_sig(int sig UNUSED_PARAM)
2786 int save_errno = errno;
2787 rawmode(); // terminal to "raw"
2788 last_status_cksum = 0; // force status update
2789 redraw(TRUE); // re-draw the screen
2791 signal(SIGTSTP, suspend_sig);
2792 signal(SIGCONT, SIG_DFL);
2793 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2797 //----- Come here when we get a Suspend signal -------------------
2798 static void suspend_sig(int sig UNUSED_PARAM)
2800 int save_errno = errno;
2801 go_bottom_and_clear_to_eol();
2802 cookmode(); // terminal to "cooked"
2804 signal(SIGCONT, cont_sig);
2805 signal(SIGTSTP, SIG_DFL);
2806 kill(my_pid, SIGTSTP);
2810 //----- Come here when we get a signal ---------------------------
2811 static void catch_sig(int sig)
2813 signal(SIGINT, catch_sig);
2814 siglongjmp(restart, sig);
2816 #endif /* FEATURE_VI_USE_SIGNALS */
2818 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2820 struct pollfd pfd[1];
2825 pfd[0].fd = STDIN_FILENO;
2826 pfd[0].events = POLLIN;
2827 return safe_poll(pfd, 1, hund*10) > 0;
2830 //----- IO Routines --------------------------------------------
2831 static int readit(void) // read (maybe cursor) key from stdin
2837 // Wait for input. TIMEOUT = -1 makes read_key wait even
2838 // on nonblocking stdin.
2839 // Note: read_key sets errno to 0 on success.
2841 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
2842 if (c == -1) { // EOF/error
2843 if (errno == EAGAIN) // paranoia
2845 go_bottom_and_clear_to_eol();
2846 cookmode(); // terminal to "cooked"
2847 bb_error_msg_and_die("can't read user input");
2852 //----- IO Routines --------------------------------------------
2853 static int get_one_char(void)
2857 #if ENABLE_FEATURE_VI_DOT_CMD
2859 // we are not adding to the q.
2860 // but, we may be reading from a q
2862 // there is no current q, read from STDIN
2863 c = readit(); // get the users input
2865 // there is a queue to get chars from first
2866 // careful with correct sign expansion!
2867 c = (unsigned char)*ioq++;
2869 // the end of the q, read from STDIN
2871 ioq_start = ioq = 0;
2872 c = readit(); // get the users input
2876 // adding STDIN chars to q
2877 c = readit(); // get the users input
2878 if (lmc_len >= MAX_INPUT_LEN - 1) {
2879 status_line_bold("last_modifying_cmd overrun");
2881 // add new char to q
2882 last_modifying_cmd[lmc_len++] = c;
2886 c = readit(); // get the users input
2887 #endif /* FEATURE_VI_DOT_CMD */
2891 // Get input line (uses "status line" area)
2892 static char *get_input_line(const char *prompt)
2894 // char [MAX_INPUT_LEN]
2895 #define buf get_input_line__buf
2900 strcpy(buf, prompt);
2901 last_status_cksum = 0; // force status update
2902 go_bottom_and_clear_to_eol();
2903 write1(prompt); // write out the :, /, or ? prompt
2906 while (i < MAX_INPUT_LEN) {
2908 if (c == '\n' || c == '\r' || c == 27)
2909 break; // this is end of input
2910 if (c == erase_char || c == 8 || c == 127) {
2911 // user wants to erase prev char
2913 write1("\b \b"); // erase char on screen
2914 if (i <= 0) // user backs up before b-o-l, exit
2916 } else if (c > 0 && c < 256) { // exclude Unicode
2917 // (TODO: need to handle Unicode)
2928 // might reallocate text[]!
2929 static int file_insert(const char *fn, char *p, int initial)
2933 struct stat statbuf;
2940 fd = open(fn, O_RDONLY);
2943 status_line_bold_errno(fn);
2948 if (fstat(fd, &statbuf) < 0) {
2949 status_line_bold_errno(fn);
2952 if (!S_ISREG(statbuf.st_mode)) {
2953 status_line_bold("'%s' is not a regular file", fn);
2956 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2957 p += text_hole_make(p, size);
2958 cnt = full_read(fd, p, size);
2960 status_line_bold_errno(fn);
2961 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2962 } else if (cnt < size) {
2963 // There was a partial read, shrink unused space
2964 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2965 status_line_bold("can't read '%s'", fn);
2970 #if ENABLE_FEATURE_VI_READONLY
2972 && ((access(fn, W_OK) < 0) ||
2973 /* root will always have access()
2974 * so we check fileperms too */
2975 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2978 SET_READONLY_FILE(readonly_mode);
2984 static int file_write(char *fn, char *first, char *last)
2986 int fd, cnt, charcnt;
2989 status_line_bold("No current filename");
2992 /* By popular request we do not open file with O_TRUNC,
2993 * but instead ftruncate() it _after_ successful write.
2994 * Might reduce amount of data lost on power fail etc.
2996 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2999 cnt = last - first + 1;
3000 charcnt = full_write(fd, first, cnt);
3001 ftruncate(fd, charcnt);
3002 if (charcnt == cnt) {
3004 //modified_count = FALSE;
3012 //----- Terminal Drawing ---------------------------------------
3013 // The terminal is made up of 'rows' line of 'columns' columns.
3014 // classically this would be 24 x 80.
3015 // screen coordinates
3021 // 23,0 ... 23,79 <- status line
3023 //----- Move the cursor to row x col (count from 0, not 1) -------
3024 static void place_cursor(int row, int col)
3026 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
3028 if (row < 0) row = 0;
3029 if (row >= rows) row = rows - 1;
3030 if (col < 0) col = 0;
3031 if (col >= columns) col = columns - 1;
3033 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
3037 //----- Erase from cursor to end of line -----------------------
3038 static void clear_to_eol(void)
3040 write1(ESC_CLEAR2EOL);
3043 static void go_bottom_and_clear_to_eol(void)
3045 place_cursor(rows - 1, 0);
3049 //----- Erase from cursor to end of screen -----------------------
3050 static void clear_to_eos(void)
3052 write1(ESC_CLEAR2EOS);
3055 //----- Start standout mode ------------------------------------
3056 static void standout_start(void)
3058 write1(ESC_BOLD_TEXT);
3061 //----- End standout mode --------------------------------------
3062 static void standout_end(void)
3064 write1(ESC_NORM_TEXT);
3067 //----- Flash the screen --------------------------------------
3068 static void flash(int h)
3077 static void indicate_error(void)
3079 #if ENABLE_FEATURE_VI_CRASHME
3081 return; // generate a random command
3090 //----- Screen[] Routines --------------------------------------
3091 //----- Erase the Screen[] memory ------------------------------
3092 static void screen_erase(void)
3094 memset(screen, ' ', screensize); // clear new screen
3097 static int bufsum(char *buf, int count)
3100 char *e = buf + count;
3103 sum += (unsigned char) *buf++;
3107 //----- Draw the status line at bottom of the screen -------------
3108 static void show_status_line(void)
3110 int cnt = 0, cksum = 0;
3112 // either we already have an error or status message, or we
3114 if (!have_status_msg) {
3115 cnt = format_edit_status();
3116 cksum = bufsum(status_buffer, cnt);
3118 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3119 last_status_cksum = cksum; // remember if we have seen this line
3120 go_bottom_and_clear_to_eol();
3121 write1(status_buffer);
3122 if (have_status_msg) {
3123 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3125 have_status_msg = 0;
3128 have_status_msg = 0;
3130 place_cursor(crow, ccol); // put cursor back in correct place
3135 //----- format the status buffer, the bottom line of screen ------
3136 // format status buffer, with STANDOUT mode
3137 static void status_line_bold(const char *format, ...)
3141 va_start(args, format);
3142 strcpy(status_buffer, ESC_BOLD_TEXT);
3143 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3144 strcat(status_buffer, ESC_NORM_TEXT);
3147 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3150 static void status_line_bold_errno(const char *fn)
3152 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
3155 // format status buffer
3156 static void status_line(const char *format, ...)
3160 va_start(args, format);
3161 vsprintf(status_buffer, format, args);
3164 have_status_msg = 1;
3167 // copy s to buf, convert unprintable
3168 static void print_literal(char *buf, const char *s)
3182 c_is_no_print = (c & 0x80) && !Isprint(c);
3183 if (c_is_no_print) {
3184 strcpy(d, ESC_NORM_TEXT);
3185 d += sizeof(ESC_NORM_TEXT)-1;
3188 if (c < ' ' || c == 0x7f) {
3190 c |= '@'; /* 0x40 */
3196 if (c_is_no_print) {
3197 strcpy(d, ESC_BOLD_TEXT);
3198 d += sizeof(ESC_BOLD_TEXT)-1;
3204 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3209 static void not_implemented(const char *s)
3211 char buf[MAX_INPUT_LEN];
3213 print_literal(buf, s);
3214 status_line_bold("\'%s\' is not implemented", buf);
3217 // show file status on status line
3218 static int format_edit_status(void)
3220 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3222 #define tot format_edit_status__tot
3224 int cur, percent, ret, trunc_at;
3226 // modified_count is now a counter rather than a flag. this
3227 // helps reduce the amount of line counting we need to do.
3228 // (this will cause a mis-reporting of modified status
3229 // once every MAXINT editing operations.)
3231 // it would be nice to do a similar optimization here -- if
3232 // we haven't done a motion that could have changed which line
3233 // we're on, then we shouldn't have to do this count_lines()
3234 cur = count_lines(text, dot);
3236 // count_lines() is expensive.
3237 // Call it only if something was changed since last time
3239 if (modified_count != last_modified_count) {
3240 tot = cur + count_lines(dot, end - 1) - 1;
3241 last_modified_count = modified_count;
3244 // current line percent
3245 // ------------- ~~ ----------
3248 percent = (100 * cur) / tot;
3254 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3255 columns : STATUS_BUFFER_LEN-1;
3257 ret = snprintf(status_buffer, trunc_at+1,
3258 #if ENABLE_FEATURE_VI_READONLY
3259 "%c %s%s%s %d/%d %d%%",
3261 "%c %s%s %d/%d %d%%",
3263 cmd_mode_indicator[cmd_mode & 3],
3264 (current_filename != NULL ? current_filename : "No file"),
3265 #if ENABLE_FEATURE_VI_READONLY
3266 (readonly_mode ? " [Readonly]" : ""),
3268 (modified_count ? " [Modified]" : ""),
3271 if (ret >= 0 && ret < trunc_at)
3272 return ret; /* it all fit */
3274 return trunc_at; /* had to truncate */
3278 //----- Force refresh of all Lines -----------------------------
3279 static void redraw(int full_screen)
3283 screen_erase(); // erase the internal screen buffer
3284 last_status_cksum = 0; // force status update
3285 refresh(full_screen); // this will redraw the entire display
3289 //----- Format a text[] line into a buffer ---------------------
3290 static char* format_line(char *src /*, int li*/)
3295 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3297 c = '~'; // char in col 0 in non-existent lines is '~'
3299 while (co < columns + tabstop) {
3300 // have we gone past the end?
3305 if ((c & 0x80) && !Isprint(c)) {
3308 if (c < ' ' || c == 0x7f) {
3312 while ((co % tabstop) != (tabstop - 1)) {
3320 c += '@'; // Ctrl-X -> 'X'
3325 // discard scrolled-off-to-the-left portion,
3326 // in tabstop-sized pieces
3327 if (ofs >= tabstop && co >= tabstop) {
3328 memmove(dest, dest + tabstop, co);
3335 // check "short line, gigantic offset" case
3338 // discard last scrolled off part
3341 // fill the rest with spaces
3343 memset(&dest[co], ' ', columns - co);
3347 //----- Refresh the changed screen lines -----------------------
3348 // Copy the source line from text[] into the buffer and note
3349 // if the current screenline is different from the new buffer.
3350 // If they differ then that line needs redrawing on the terminal.
3352 static void refresh(int full_screen)
3354 #define old_offset refresh__old_offset
3357 char *tp, *sp; // pointer into text[] and screen[]
3359 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3360 unsigned c = columns, r = rows;
3361 query_screen_dimensions();
3362 full_screen |= (c - columns) | (r - rows);
3364 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3365 tp = screenbegin; // index into text[] of top line
3367 // compare text[] to screen[] and mark screen[] lines that need updating
3368 for (li = 0; li < rows - 1; li++) {
3369 int cs, ce; // column start & end
3371 // format current text line
3372 out_buf = format_line(tp /*, li*/);
3374 // skip to the end of the current text[] line
3376 char *t = memchr(tp, '\n', end - tp);
3377 if (!t) t = end - 1;
3381 // see if there are any changes between virtual screen and out_buf
3382 changed = FALSE; // assume no change
3385 sp = &screen[li * columns]; // start of screen line
3387 // force re-draw of every single column from 0 - columns-1
3390 // compare newly formatted buffer with virtual screen
3391 // look forward for first difference between buf and screen
3392 for (; cs <= ce; cs++) {
3393 if (out_buf[cs] != sp[cs]) {
3394 changed = TRUE; // mark for redraw
3399 // look backward for last difference between out_buf and screen
3400 for (; ce >= cs; ce--) {
3401 if (out_buf[ce] != sp[ce]) {
3402 changed = TRUE; // mark for redraw
3406 // now, cs is index of first diff, and ce is index of last diff
3408 // if horz offset has changed, force a redraw
3409 if (offset != old_offset) {
3414 // make a sanity check of columns indexes
3416 if (ce > columns - 1) ce = columns - 1;
3417 if (cs > ce) { cs = 0; ce = columns - 1; }
3418 // is there a change between virtual screen and out_buf
3420 // copy changed part of buffer to virtual screen
3421 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3422 place_cursor(li, cs);
3423 // write line out to terminal
3424 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3428 place_cursor(crow, ccol);
3430 old_offset = offset;
3434 //---------------------------------------------------------------------
3435 //----- the Ascii Chart -----------------------------------------------
3437 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3438 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3439 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3440 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3441 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3442 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3443 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3444 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3445 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3446 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3447 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3448 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3449 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3450 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3451 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3452 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3453 //---------------------------------------------------------------------
3455 //----- Execute a Vi Command -----------------------------------
3456 static void do_cmd(int c)
3458 char *p, *q, *save_dot;
3464 // c1 = c; // quiet the compiler
3465 // cnt = yf = 0; // quiet the compiler
3466 // p = q = save_dot = buf; // quiet the compiler
3467 memset(buf, '\0', sizeof(buf));
3471 /* if this is a cursor key, skip these checks */
3479 case KEYCODE_PAGEUP:
3480 case KEYCODE_PAGEDOWN:
3481 case KEYCODE_DELETE:
3485 if (cmd_mode == 2) {
3486 // flip-flop Insert/Replace mode
3487 if (c == KEYCODE_INSERT)
3489 // we are 'R'eplacing the current *dot with new char
3491 // don't Replace past E-o-l
3492 cmd_mode = 1; // convert to insert
3493 undo_queue_commit();
3495 if (1 <= c || Isprint(c)) {
3497 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3498 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3503 if (cmd_mode == 1) {
3504 // hitting "Insert" twice means "R" replace mode
3505 if (c == KEYCODE_INSERT) goto dc5;
3506 // insert the char c at "dot"
3507 if (1 <= c || Isprint(c)) {
3508 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3523 #if ENABLE_FEATURE_VI_CRASHME
3524 case 0x14: // dc4 ctrl-T
3525 crashme = (crashme == 0) ? 1 : 0;
3555 default: // unrecognized command
3558 not_implemented(buf);
3559 end_cmd_q(); // stop adding to q
3560 case 0x00: // nul- ignore
3562 case 2: // ctrl-B scroll up full screen
3563 case KEYCODE_PAGEUP: // Cursor Key Page Up
3564 dot_scroll(rows - 2, -1);
3566 case 4: // ctrl-D scroll down half screen
3567 dot_scroll((rows - 2) / 2, 1);
3569 case 5: // ctrl-E scroll down one line
3572 case 6: // ctrl-F scroll down full screen
3573 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3574 dot_scroll(rows - 2, 1);
3576 case 7: // ctrl-G show current status
3577 last_status_cksum = 0; // force status update
3579 case 'h': // h- move left
3580 case KEYCODE_LEFT: // cursor key Left
3581 case 8: // ctrl-H- move left (This may be ERASE char)
3582 case 0x7f: // DEL- move left (This may be ERASE char)
3585 } while (--cmdcnt > 0);
3587 case 10: // Newline ^J
3588 case 'j': // j- goto next line, same col
3589 case KEYCODE_DOWN: // cursor key Down
3591 dot_next(); // go to next B-o-l
3592 // try stay in same col
3593 dot = move_to_col(dot, ccol + offset);
3594 } while (--cmdcnt > 0);
3596 case 12: // ctrl-L force redraw whole screen
3597 case 18: // ctrl-R force redraw
3598 redraw(TRUE); // this will redraw the entire display
3600 case 13: // Carriage Return ^M
3601 case '+': // +- goto next line
3605 } while (--cmdcnt > 0);
3607 case 21: // ctrl-U scroll up half screen
3608 dot_scroll((rows - 2) / 2, -1);
3610 case 25: // ctrl-Y scroll up one line
3616 cmd_mode = 0; // stop insrting
3617 undo_queue_commit();
3619 last_status_cksum = 0; // force status update
3621 case ' ': // move right
3622 case 'l': // move right
3623 case KEYCODE_RIGHT: // Cursor Key Right
3626 } while (--cmdcnt > 0);
3628 #if ENABLE_FEATURE_VI_YANKMARK
3629 case '"': // "- name a register to use for Delete/Yank
3630 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3631 if ((unsigned)c1 <= 25) { // a-z?
3637 case '\'': // '- goto a specific mark
3638 c1 = (get_one_char() | 0x20);
3639 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3643 if (text <= q && q < end) {
3645 dot_begin(); // go to B-o-l
3648 } else if (c1 == '\'') { // goto previous context
3649 dot = swap_context(dot); // swap current and previous context
3650 dot_begin(); // go to B-o-l
3656 case 'm': // m- Mark a line
3657 // this is really stupid. If there are any inserts or deletes
3658 // between text[0] and dot then this mark will not point to the
3659 // correct location! It could be off by many lines!
3660 // Well..., at least its quick and dirty.
3661 c1 = (get_one_char() | 0x20) - 'a';
3662 if ((unsigned)c1 <= 25) { // a-z?
3663 // remember the line
3669 case 'P': // P- Put register before
3670 case 'p': // p- put register after
3673 status_line_bold("Nothing in register %c", what_reg());
3676 // are we putting whole lines or strings
3677 if (strchr(p, '\n') != NULL) {
3679 dot_begin(); // putting lines- Put above
3682 // are we putting after very last line?
3683 if (end_line(dot) == (end - 1)) {
3684 dot = end; // force dot to end of text[]
3686 dot_next(); // next line, then put before
3691 dot_right(); // move to right, can move to NL
3693 string_insert(dot, p, ALLOW_UNDO); // insert the string
3694 end_cmd_q(); // stop adding to q
3696 case 'U': // U- Undo; replace current line with original version
3697 if (reg[Ureg] != NULL) {
3698 p = begin_line(dot);
3700 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3701 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3706 #endif /* FEATURE_VI_YANKMARK */
3707 #if ENABLE_FEATURE_VI_UNDO
3708 case 'u': // u- undo last operation
3712 case '$': // $- goto end of line
3713 case KEYCODE_END: // Cursor Key End
3715 dot = end_line(dot);
3721 case '%': // %- find matching char of pair () [] {}
3722 for (q = dot; q < end && *q != '\n'; q++) {
3723 if (strchr("()[]{}", *q) != NULL) {
3724 // we found half of a pair
3725 p = find_pair(q, *q);
3737 case 'f': // f- forward to a user specified char
3738 last_forward_char = get_one_char(); // get the search char
3740 // dont separate these two commands. 'f' depends on ';'
3742 //**** fall through to ... ';'
3743 case ';': // ;- look at rest of line for last forward char
3745 if (last_forward_char == 0)
3748 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3751 if (*q == last_forward_char)
3753 } while (--cmdcnt > 0);
3755 case ',': // repeat latest 'f' in opposite direction
3756 if (last_forward_char == 0)
3760 while (q >= text && *q != '\n' && *q != last_forward_char) {
3763 if (q >= text && *q == last_forward_char)
3765 } while (--cmdcnt > 0);
3768 case '-': // -- goto prev line
3772 } while (--cmdcnt > 0);
3774 #if ENABLE_FEATURE_VI_DOT_CMD
3775 case '.': // .- repeat the last modifying command
3776 // Stuff the last_modifying_cmd back into stdin
3777 // and let it be re-executed.
3779 last_modifying_cmd[lmc_len] = 0;
3780 ioq = ioq_start = xstrdup(last_modifying_cmd);
3784 #if ENABLE_FEATURE_VI_SEARCH
3785 case '?': // /- search for a pattern
3786 case '/': // /- search for a pattern
3789 q = get_input_line(buf); // get input line- use "status line"
3790 if (q[0] && !q[1]) {
3791 if (last_search_pattern[0])
3792 last_search_pattern[0] = c;
3793 goto dc3; // if no pat re-use old pat
3795 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3796 // there is a new pat
3797 free(last_search_pattern);
3798 last_search_pattern = xstrdup(q);
3799 goto dc3; // now find the pattern
3801 // user changed mind and erased the "/"- do nothing
3803 case 'N': // N- backward search for last pattern
3804 dir = BACK; // assume BACKWARD search
3806 if (last_search_pattern[0] == '?') {
3810 goto dc4; // now search for pattern
3812 case 'n': // n- repeat search for last pattern
3813 // search rest of text[] starting at next char
3814 // if search fails return orignal "p" not the "p+1" address
3818 dir = FORWARD; // assume FORWARD search
3820 if (last_search_pattern[0] == '?') {
3825 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3827 dot = q; // good search, update "dot"
3831 // no pattern found between "dot" and "end"- continue at top
3836 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3837 if (q != NULL) { // found something
3838 dot = q; // found new pattern- goto it
3839 msg = "search hit BOTTOM, continuing at TOP";
3841 msg = "search hit TOP, continuing at BOTTOM";
3844 msg = "Pattern not found";
3848 status_line_bold("%s", msg);
3849 } while (--cmdcnt > 0);
3851 case '{': // {- move backward paragraph
3852 q = char_search(dot, "\n\n", (BACK << 1) | FULL);
3853 if (q != NULL) { // found blank line
3854 dot = next_line(q); // move to next blank line
3857 case '}': // }- move forward paragraph
3858 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
3859 if (q != NULL) { // found blank line
3860 dot = next_line(q); // move to next blank line
3863 #endif /* FEATURE_VI_SEARCH */
3864 case '0': // 0- goto beginning of line
3874 if (c == '0' && cmdcnt < 1) {
3875 dot_begin(); // this was a standalone zero
3877 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3880 case ':': // :- the colon mode commands
3881 p = get_input_line(":"); // get input line- use "status line"
3882 colon(p); // execute the command
3884 case '<': // <- Left shift something
3885 case '>': // >- Right shift something
3886 cnt = count_lines(text, dot); // remember what line we are on
3887 c1 = get_one_char(); // get the type of thing to delete
3888 find_range(&p, &q, c1);
3889 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3892 i = count_lines(p, q); // # of lines we are shifting
3893 for ( ; i > 0; i--, p = next_line(p)) {
3895 // shift left- remove tab or 8 spaces
3897 // shrink buffer 1 char
3898 text_hole_delete(p, p, NO_UNDO);
3899 } else if (*p == ' ') {
3900 // we should be calculating columns, not just SPACE
3901 for (j = 0; *p == ' ' && j < tabstop; j++) {
3902 text_hole_delete(p, p, NO_UNDO);
3905 } else if (c == '>') {
3906 // shift right -- add tab or 8 spaces
3907 char_insert(p, '\t', ALLOW_UNDO);
3910 dot = find_line(cnt); // what line were we on
3912 end_cmd_q(); // stop adding to q
3914 case 'A': // A- append at e-o-l
3915 dot_end(); // go to e-o-l
3916 //**** fall through to ... 'a'
3917 case 'a': // a- append after current char
3922 case 'B': // B- back a blank-delimited Word
3923 case 'E': // E- end of a blank-delimited word
3924 case 'W': // W- forward a blank-delimited word
3929 if (c == 'W' || isspace(dot[dir])) {
3930 dot = skip_thing(dot, 1, dir, S_TO_WS);
3931 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3934 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3935 } while (--cmdcnt > 0);
3937 case 'C': // C- Change to e-o-l
3938 case 'D': // D- delete to e-o-l
3940 dot = dollar_line(dot); // move to before NL
3941 // copy text into a register and delete
3942 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3944 goto dc_i; // start inserting
3945 #if ENABLE_FEATURE_VI_DOT_CMD
3947 end_cmd_q(); // stop adding to q
3950 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3951 c1 = get_one_char();
3954 // c1 < 0 if the key was special. Try "g<up-arrow>"
3955 // TODO: if Unicode?
3956 buf[1] = (c1 >= 0 ? c1 : '*');
3958 not_implemented(buf);
3964 case 'G': // G- goto to a line number (default= E-O-F)
3965 dot = end - 1; // assume E-O-F
3967 dot = find_line(cmdcnt); // what line is #cmdcnt
3971 case 'H': // H- goto top line on screen
3973 if (cmdcnt > (rows - 1)) {
3974 cmdcnt = (rows - 1);
3981 case 'I': // I- insert before first non-blank
3984 //**** fall through to ... 'i'
3985 case 'i': // i- insert before current char
3986 case KEYCODE_INSERT: // Cursor Key Insert
3988 cmd_mode = 1; // start inserting
3989 undo_queue_commit(); // commit queue when cmd_mode changes
3991 case 'J': // J- join current and next lines together
3993 dot_end(); // move to NL
3994 if (dot < end - 1) { // make sure not last char in text[]
3995 #if ENABLE_FEATURE_VI_UNDO
3996 undo_push(dot, 1, UNDO_DEL);
3997 *dot++ = ' '; // replace NL with space
3998 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
4003 while (isblank(*dot)) { // delete leading WS
4004 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
4007 } while (--cmdcnt > 0);
4008 end_cmd_q(); // stop adding to q
4010 case 'L': // L- goto bottom line on screen
4012 if (cmdcnt > (rows - 1)) {
4013 cmdcnt = (rows - 1);
4021 case 'M': // M- goto middle line on screen
4023 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
4024 dot = next_line(dot);
4026 case 'O': // O- open a empty line above
4028 p = begin_line(dot);
4029 if (p[-1] == '\n') {
4031 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
4033 dot = char_insert(dot, '\n', ALLOW_UNDO);
4036 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
4041 case 'R': // R- continuous Replace char
4044 undo_queue_commit();
4046 case KEYCODE_DELETE:
4048 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
4050 case 'X': // X- delete char before dot
4051 case 'x': // x- delete the current char
4052 case 's': // s- substitute the current char
4057 if (dot[dir] != '\n') {
4059 dot--; // delete prev char
4060 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
4062 } while (--cmdcnt > 0);
4063 end_cmd_q(); // stop adding to q
4065 goto dc_i; // start inserting
4067 case 'Z': // Z- if modified, {write}; exit
4068 // ZZ means to save file (if necessary), then exit
4069 c1 = get_one_char();
4074 if (modified_count) {
4075 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
4076 status_line_bold("'%s' is read only", current_filename);
4079 cnt = file_write(current_filename, text, end - 1);
4082 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
4083 } else if (cnt == (end - 1 - text + 1)) {
4090 case '^': // ^- move to first non-blank on line
4094 case 'b': // b- back a word
4095 case 'e': // e- end of word
4100 if ((dot + dir) < text || (dot + dir) > end - 1)
4103 if (isspace(*dot)) {
4104 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4106 if (isalnum(*dot) || *dot == '_') {
4107 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4108 } else if (ispunct(*dot)) {
4109 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4111 } while (--cmdcnt > 0);
4113 case 'c': // c- change something
4114 case 'd': // d- delete something
4115 #if ENABLE_FEATURE_VI_YANKMARK
4116 case 'y': // y- yank something
4117 case 'Y': // Y- Yank a line
4120 int yf, ml, whole = 0;
4121 yf = YANKDEL; // assume either "c" or "d"
4122 #if ENABLE_FEATURE_VI_YANKMARK
4123 if (c == 'y' || c == 'Y')
4128 c1 = get_one_char(); // get the type of thing to delete
4129 // determine range, and whether it spans lines
4130 ml = find_range(&p, &q, c1);
4132 if (c1 == 27) { // ESC- user changed mind and wants out
4133 c = c1 = 27; // Escape- do nothing
4134 } else if (strchr("wW", c1)) {
4136 // don't include trailing WS as part of word
4137 while (isblank(*q)) {
4138 if (q <= text || q[-1] == '\n')
4143 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4144 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
4145 // partial line copy text into a register and delete
4146 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4147 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
4148 // whole line copy text into a register and delete
4149 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
4152 // could not recognize object
4153 c = c1 = 27; // error-
4159 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4160 // on the last line of file don't move to prev line
4161 if (whole && dot != (end-1)) {
4164 } else if (c == 'd') {
4170 // if CHANGING, not deleting, start inserting after the delete
4172 strcpy(buf, "Change");
4173 goto dc_i; // start inserting
4176 strcpy(buf, "Delete");
4178 #if ENABLE_FEATURE_VI_YANKMARK
4179 if (c == 'y' || c == 'Y') {
4180 strcpy(buf, "Yank");
4184 for (cnt = 0; p <= q; p++) {
4188 status_line("%s %d lines (%d chars) using [%c]",
4189 buf, cnt, strlen(reg[YDreg]), what_reg());
4191 end_cmd_q(); // stop adding to q
4195 case 'k': // k- goto prev line, same col
4196 case KEYCODE_UP: // cursor key Up
4199 dot = move_to_col(dot, ccol + offset); // try stay in same col
4200 } while (--cmdcnt > 0);
4202 case 'r': // r- replace the current char with user input
4203 c1 = get_one_char(); // get the replacement char
4205 #if ENABLE_FEATURE_VI_UNDO
4206 undo_push(dot, 1, UNDO_DEL);
4208 undo_push(dot, 1, UNDO_INS_CHAIN);
4214 end_cmd_q(); // stop adding to q
4216 case 't': // t- move to char prior to next x
4217 last_forward_char = get_one_char();
4219 if (*dot == last_forward_char)
4221 last_forward_char = 0;
4223 case 'w': // w- forward a word
4225 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4226 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4227 } else if (ispunct(*dot)) { // we are on PUNCT
4228 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4231 dot++; // move over word
4232 if (isspace(*dot)) {
4233 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4235 } while (--cmdcnt > 0);
4238 c1 = get_one_char(); // get the replacement char
4241 cnt = (rows - 2) / 2; // put dot at center
4243 cnt = rows - 2; // put dot at bottom
4244 screenbegin = begin_line(dot); // start dot at top
4245 dot_scroll(cnt, -1);
4247 case '|': // |- move to column "cmdcnt"
4248 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4250 case '~': // ~- flip the case of letters a-z -> A-Z
4252 #if ENABLE_FEATURE_VI_UNDO
4253 if (islower(*dot)) {
4254 undo_push(dot, 1, UNDO_DEL);
4255 *dot = toupper(*dot);
4256 undo_push(dot, 1, UNDO_INS_CHAIN);
4257 } else if (isupper(*dot)) {
4258 undo_push(dot, 1, UNDO_DEL);
4259 *dot = tolower(*dot);
4260 undo_push(dot, 1, UNDO_INS_CHAIN);
4263 if (islower(*dot)) {
4264 *dot = toupper(*dot);
4266 } else if (isupper(*dot)) {
4267 *dot = tolower(*dot);
4272 } while (--cmdcnt > 0);
4273 end_cmd_q(); // stop adding to q
4275 //----- The Cursor and Function Keys -----------------------------
4276 case KEYCODE_HOME: // Cursor Key Home
4279 // The Fn keys could point to do_macro which could translate them
4281 case KEYCODE_FUN1: // Function Key F1
4282 case KEYCODE_FUN2: // Function Key F2
4283 case KEYCODE_FUN3: // Function Key F3
4284 case KEYCODE_FUN4: // Function Key F4
4285 case KEYCODE_FUN5: // Function Key F5
4286 case KEYCODE_FUN6: // Function Key F6
4287 case KEYCODE_FUN7: // Function Key F7
4288 case KEYCODE_FUN8: // Function Key F8
4289 case KEYCODE_FUN9: // Function Key F9
4290 case KEYCODE_FUN10: // Function Key F10
4291 case KEYCODE_FUN11: // Function Key F11
4292 case KEYCODE_FUN12: // Function Key F12
4298 // if text[] just became empty, add back an empty line
4300 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4303 // it is OK for dot to exactly equal to end, otherwise check dot validity
4305 dot = bound_dot(dot); // make sure "dot" is valid
4307 #if ENABLE_FEATURE_VI_YANKMARK
4308 check_context(c); // update the current context
4312 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4313 cnt = dot - begin_line(dot);
4314 // Try to stay off of the Newline
4315 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4319 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4320 #if ENABLE_FEATURE_VI_CRASHME
4321 static int totalcmds = 0;
4322 static int Mp = 85; // Movement command Probability
4323 static int Np = 90; // Non-movement command Probability
4324 static int Dp = 96; // Delete command Probability
4325 static int Ip = 97; // Insert command Probability
4326 static int Yp = 98; // Yank command Probability
4327 static int Pp = 99; // Put command Probability
4328 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4329 static const char chars[20] = "\t012345 abcdABCD-=.$";
4330 static const char *const words[20] = {
4331 "this", "is", "a", "test",
4332 "broadcast", "the", "emergency", "of",
4333 "system", "quick", "brown", "fox",
4334 "jumped", "over", "lazy", "dogs",
4335 "back", "January", "Febuary", "March"
4337 static const char *const lines[20] = {
4338 "You should have received a copy of the GNU General Public License\n",
4339 "char c, cm, *cmd, *cmd1;\n",
4340 "generate a command by percentages\n",
4341 "Numbers may be typed as a prefix to some commands.\n",
4342 "Quit, discarding changes!\n",
4343 "Forced write, if permission originally not valid.\n",
4344 "In general, any ex or ed command (such as substitute or delete).\n",
4345 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4346 "Please get w/ me and I will go over it with you.\n",
4347 "The following is a list of scheduled, committed changes.\n",
4348 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4349 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4350 "Any question about transactions please contact Sterling Huxley.\n",
4351 "I will try to get back to you by Friday, December 31.\n",
4352 "This Change will be implemented on Friday.\n",
4353 "Let me know if you have problems accessing this;\n",
4354 "Sterling Huxley recently added you to the access list.\n",
4355 "Would you like to go to lunch?\n",
4356 "The last command will be automatically run.\n",
4357 "This is too much english for a computer geek.\n",
4359 static char *multilines[20] = {
4360 "You should have received a copy of the GNU General Public License\n",
4361 "char c, cm, *cmd, *cmd1;\n",
4362 "generate a command by percentages\n",
4363 "Numbers may be typed as a prefix to some commands.\n",
4364 "Quit, discarding changes!\n",
4365 "Forced write, if permission originally not valid.\n",
4366 "In general, any ex or ed command (such as substitute or delete).\n",
4367 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4368 "Please get w/ me and I will go over it with you.\n",
4369 "The following is a list of scheduled, committed changes.\n",
4370 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4371 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4372 "Any question about transactions please contact Sterling Huxley.\n",
4373 "I will try to get back to you by Friday, December 31.\n",
4374 "This Change will be implemented on Friday.\n",
4375 "Let me know if you have problems accessing this;\n",
4376 "Sterling Huxley recently added you to the access list.\n",
4377 "Would you like to go to lunch?\n",
4378 "The last command will be automatically run.\n",
4379 "This is too much english for a computer geek.\n",
4382 // create a random command to execute
4383 static void crash_dummy()
4385 static int sleeptime; // how long to pause between commands
4386 char c, cm, *cmd, *cmd1;
4387 int i, cnt, thing, rbi, startrbi, percent;
4389 // "dot" movement commands
4390 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4392 // is there already a command running?
4393 if (readbuffer[0] > 0)
4396 readbuffer[0] = 'X';
4398 sleeptime = 0; // how long to pause between commands
4399 memset(readbuffer, '\0', sizeof(readbuffer));
4400 // generate a command by percentages
4401 percent = (int) lrand48() % 100; // get a number from 0-99
4402 if (percent < Mp) { // Movement commands
4403 // available commands
4406 } else if (percent < Np) { // non-movement commands
4407 cmd = "mz<>\'\""; // available commands
4409 } else if (percent < Dp) { // Delete commands
4410 cmd = "dx"; // available commands
4412 } else if (percent < Ip) { // Inset commands
4413 cmd = "iIaAsrJ"; // available commands
4415 } else if (percent < Yp) { // Yank commands
4416 cmd = "yY"; // available commands
4418 } else if (percent < Pp) { // Put commands
4419 cmd = "pP"; // available commands
4422 // We do not know how to handle this command, try again
4426 // randomly pick one of the available cmds from "cmd[]"
4427 i = (int) lrand48() % strlen(cmd);
4429 if (strchr(":\024", cm))
4430 goto cd0; // dont allow colon or ctrl-T commands
4431 readbuffer[rbi++] = cm; // put cmd into input buffer
4433 // now we have the command-
4434 // there are 1, 2, and multi char commands
4435 // find out which and generate the rest of command as necessary
4436 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4437 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4438 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4439 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4441 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4443 readbuffer[rbi++] = c; // add movement to input buffer
4445 if (strchr("iIaAsc", cm)) { // multi-char commands
4447 // change some thing
4448 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4450 readbuffer[rbi++] = c; // add movement to input buffer
4452 thing = (int) lrand48() % 4; // what thing to insert
4453 cnt = (int) lrand48() % 10; // how many to insert
4454 for (i = 0; i < cnt; i++) {
4455 if (thing == 0) { // insert chars
4456 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4457 } else if (thing == 1) { // insert words
4458 strcat(readbuffer, words[(int) lrand48() % 20]);
4459 strcat(readbuffer, " ");
4460 sleeptime = 0; // how fast to type
4461 } else if (thing == 2) { // insert lines
4462 strcat(readbuffer, lines[(int) lrand48() % 20]);
4463 sleeptime = 0; // how fast to type
4464 } else { // insert multi-lines
4465 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4466 sleeptime = 0; // how fast to type
4469 strcat(readbuffer, ESC);
4471 readbuffer[0] = strlen(readbuffer + 1);
4475 mysleep(sleeptime); // sleep 1/100 sec
4478 // test to see if there are any errors
4479 static void crash_test()
4481 static time_t oldtim;
4488 strcat(msg, "end<text ");
4490 if (end > textend) {
4491 strcat(msg, "end>textend ");
4494 strcat(msg, "dot<text ");
4497 strcat(msg, "dot>end ");
4499 if (screenbegin < text) {
4500 strcat(msg, "screenbegin<text ");
4502 if (screenbegin > end - 1) {
4503 strcat(msg, "screenbegin>end-1 ");
4507 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4508 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4510 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4511 if (d[0] == '\n' || d[0] == '\r')
4516 if (tim >= (oldtim + 3)) {
4517 sprintf(status_buffer,
4518 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4519 totalcmds, M, N, I, D, Y, P, U, end - text + 1);