1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
11 * $HOME/.exrc and ./.exrc
12 * add magic to search /foo.*bar
15 * if mark[] values were line numbers rather than pointers
16 * it would be easier to change the mark when add/delete lines
17 * More intelligence in refresh()
18 * ":r !cmd" and "!cmd" to filter text through an external command
19 * An "ex" line oriented mode- maybe using "cmdedit"
22 //config: bool "vi (22 kb)"
25 //config: 'vi' is a text editor. More specifically, it is the One True
26 //config: text editor <grin>. It does, however, have a rather steep
27 //config: learning curve. If you are not already comfortable with 'vi'
28 //config: you may wish to use something else.
30 //config:config FEATURE_VI_MAX_LEN
31 //config: int "Maximum screen width"
32 //config: range 256 16384
33 //config: default 4096
34 //config: depends on VI
36 //config: Contrary to what you may think, this is not eating much.
37 //config: Make it smaller than 4k only if you are very limited on memory.
39 //config:config FEATURE_VI_8BIT
40 //config: bool "Allow to display 8-bit chars (otherwise shows dots)"
42 //config: depends on VI
44 //config: If your terminal can display characters with high bit set,
45 //config: you may want to enable this. Note: vi is not Unicode-capable.
46 //config: If your terminal combines several 8-bit bytes into one character
47 //config: (as in Unicode mode), this will not work properly.
49 //config:config FEATURE_VI_COLON
50 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
52 //config: depends on VI
54 //config: Enable a limited set of colon commands. This does not
55 //config: provide an "ex" mode.
57 //config:config FEATURE_VI_YANKMARK
58 //config: bool "Enable yank/put commands and mark cmds"
60 //config: depends on VI
62 //config: This enables you to use yank and put, as well as mark.
64 //config:config FEATURE_VI_SEARCH
65 //config: bool "Enable search and replace cmds"
67 //config: depends on VI
69 //config: Select this if you wish to be able to do search and replace.
71 //config:config FEATURE_VI_REGEX_SEARCH
72 //config: bool "Enable regex in search and replace"
73 //config: default n # Uses GNU regex, which may be unavailable. FIXME
74 //config: depends on FEATURE_VI_SEARCH
76 //config: Use extended regex search.
78 //config:config FEATURE_VI_USE_SIGNALS
79 //config: bool "Catch signals"
81 //config: depends on VI
83 //config: Selecting this option will make vi signal aware. This will support
84 //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
86 //config:config FEATURE_VI_DOT_CMD
87 //config: bool "Remember previous cmd and \".\" cmd"
89 //config: depends on VI
91 //config: Make vi remember the last command and be able to repeat it.
93 //config:config FEATURE_VI_READONLY
94 //config: bool "Enable -R option and \"view\" mode"
96 //config: depends on VI
98 //config: Enable the read-only command line option, which allows the user to
99 //config: open a file in read-only mode.
101 //config:config FEATURE_VI_SETOPTS
102 //config: bool "Enable settable options, ai ic showmatch"
104 //config: depends on VI
106 //config: Enable the editor to set some (ai, ic, showmatch) options.
108 //config:config FEATURE_VI_SET
109 //config: bool "Support :set"
111 //config: depends on VI
113 //config:config FEATURE_VI_WIN_RESIZE
114 //config: bool "Handle window resize"
116 //config: depends on VI
118 //config: Behave nicely with terminals that get resized.
120 //config:config FEATURE_VI_ASK_TERMINAL
121 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
123 //config: depends on VI
125 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
126 //config: this option makes vi perform a last-ditch effort to find it:
127 //config: position cursor to 999,999 and ask terminal to report real
128 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
129 //config: This is not clean but helps a lot on serial lines and such.
131 //config:config FEATURE_VI_UNDO
132 //config: bool "Support undo command \"u\""
134 //config: depends on VI
136 //config: Support the 'u' command to undo insertion, deletion, and replacement
139 //config:config FEATURE_VI_UNDO_QUEUE
140 //config: bool "Enable undo operation queuing"
142 //config: depends on FEATURE_VI_UNDO
144 //config: The vi undo functions can use an intermediate queue to greatly lower
145 //config: malloc() calls and overhead. When the maximum size of this queue is
146 //config: reached, the contents of the queue are committed to the undo stack.
147 //config: This increases the size of the undo code and allows some undo
148 //config: operations (especially un-typing/backspacing) to be far more useful.
150 //config:config FEATURE_VI_UNDO_QUEUE_MAX
151 //config: int "Maximum undo character queue size"
152 //config: default 256
153 //config: range 32 65536
154 //config: depends on FEATURE_VI_UNDO_QUEUE
156 //config: This option sets the number of bytes used at runtime for the queue.
157 //config: Smaller values will create more undo objects and reduce the amount
158 //config: of typed or backspaced characters that are grouped into one undo
159 //config: operation; larger values increase the potential size of each undo
160 //config: and will generally malloc() larger objects and less frequently.
161 //config: Unless you want more (or less) frequent "undo points" while typing,
162 //config: you should probably leave this unchanged.
164 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
166 //kbuild:lib-$(CONFIG_VI) += vi.o
168 //usage:#define vi_trivial_usage
169 //usage: "[OPTIONS] [FILE]..."
170 //usage:#define vi_full_usage "\n\n"
171 //usage: "Edit FILE\n"
172 //usage: IF_FEATURE_VI_COLON(
173 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
175 //usage: IF_FEATURE_VI_READONLY(
176 //usage: "\n -R Read-only"
178 //usage: "\n -H List available features"
181 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
182 #if ENABLE_FEATURE_VI_REGEX_SEARCH
186 /* the CRASHME code is unmaintained, and doesn't currently build */
187 #define ENABLE_FEATURE_VI_CRASHME 0
190 #if ENABLE_LOCALE_SUPPORT
192 #if ENABLE_FEATURE_VI_8BIT
193 //FIXME: this does not work properly for Unicode anyway
194 # define Isprint(c) (isprint)(c)
196 # define Isprint(c) isprint_asciionly(c)
201 /* 0x9b is Meta-ESC */
202 #if ENABLE_FEATURE_VI_8BIT
203 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
205 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
212 MAX_TABSTOP = 32, // sanity limit
213 // User input len. Need not be extra big.
214 // Lines in file being edited *can* be bigger than this.
216 // Sanity limits. We have only one buffer of this size.
217 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
218 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
221 /* VT102 ESC sequences.
222 * See "Xterm Control Sequences"
223 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
226 /* Inverse/Normal text */
227 #define ESC_BOLD_TEXT ESC"[7m"
228 #define ESC_NORM_TEXT ESC"[m"
230 #define ESC_BELL "\007"
231 /* Clear-to-end-of-line */
232 #define ESC_CLEAR2EOL ESC"[K"
233 /* Clear-to-end-of-screen.
234 * (We use default param here.
235 * Full sequence is "ESC [ <num> J",
236 * <num> is 0/1/2 = "erase below/above/all".)
238 #define ESC_CLEAR2EOS ESC"[J"
239 /* Cursor to given coordinate (1,1: top left) */
240 #define ESC_SET_CURSOR_POS ESC"[%u;%uH"
242 ///* Cursor up and down */
243 //#define ESC_CURSOR_UP ESC"[A"
244 //#define ESC_CURSOR_DOWN "\n"
246 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
247 // cmds modifying text[]
248 // vda: removed "aAiIs" as they switch us into insert mode
249 // and remembering input for replay after them makes no sense
250 static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
256 FORWARD = 1, // code depends on "1" for array index
257 BACK = -1, // code depends on "-1" for array index
258 LIMITED = 0, // how much of text[] in char_search
259 FULL = 1, // how much of text[] in char_search
261 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
262 S_TO_WS = 2, // used in skip_thing() for moving "dot"
263 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
264 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
265 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
269 /* vi.c expects chars to be unsigned. */
270 /* busybox build system provides that, but it's better */
271 /* to audit and fix the source */
274 /* many references - keep near the top of globals */
275 char *text, *end; // pointers to the user data in memory
276 char *dot; // where all the action takes place
277 int text_size; // size of the allocated buffer
281 #define VI_AUTOINDENT 1
282 #define VI_SHOWMATCH 2
283 #define VI_IGNORECASE 4
284 #define VI_ERR_METHOD 8
285 #define autoindent (vi_setops & VI_AUTOINDENT)
286 #define showmatch (vi_setops & VI_SHOWMATCH )
287 #define ignorecase (vi_setops & VI_IGNORECASE)
288 /* indicate error with beep or flash */
289 #define err_method (vi_setops & VI_ERR_METHOD)
291 #if ENABLE_FEATURE_VI_READONLY
292 smallint readonly_mode;
293 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
294 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
295 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
297 #define SET_READONLY_FILE(flags) ((void)0)
298 #define SET_READONLY_MODE(flags) ((void)0)
299 #define UNSET_READONLY_FILE(flags) ((void)0)
302 smallint editing; // >0 while we are editing a file
303 // [code audit says "can be 0, 1 or 2 only"]
304 smallint cmd_mode; // 0=command 1=insert 2=replace
305 int modified_count; // buffer contents changed if !0
306 int last_modified_count; // = -1;
307 int save_argc; // how many file names on cmd line
308 int cmdcnt; // repetition count
309 unsigned rows, columns; // the terminal screen is this size
310 #if ENABLE_FEATURE_VI_ASK_TERMINAL
311 int get_rowcol_error;
313 int crow, ccol; // cursor is on Crow x Ccol
314 int offset; // chars scrolled off the screen to the left
315 int have_status_msg; // is default edit status needed?
316 // [don't make smallint!]
317 int last_status_cksum; // hash of current status line
318 char *current_filename;
319 char *screenbegin; // index into text[], of top line on the screen
320 char *screen; // pointer to the virtual screen buffer
321 int screensize; // and its size
323 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
324 char erase_char; // the users erase character
325 char last_input_char; // last char read from user
327 #if ENABLE_FEATURE_VI_DOT_CMD
328 smallint adding2q; // are we currently adding user input to q
329 int lmc_len; // length of last_modifying_cmd
330 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
332 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
335 #if ENABLE_FEATURE_VI_SEARCH
336 char *last_search_pattern; // last pattern from a '/' or '?' search
340 #if ENABLE_FEATURE_VI_YANKMARK
341 char *edit_file__cur_line;
343 int refresh__old_offset;
344 int format_edit_status__tot;
346 /* a few references only */
347 #if ENABLE_FEATURE_VI_YANKMARK
348 int YDreg, Ureg; // default delete register and orig line for "U"
349 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
350 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
351 char *context_start, *context_end;
353 #if ENABLE_FEATURE_VI_USE_SIGNALS
354 sigjmp_buf restart; // catch_sig()
356 struct termios term_orig; // remember what the cooked mode was
357 #if ENABLE_FEATURE_VI_COLON
358 char *initial_cmds[3]; // currently 2 entries, NULL terminated
360 // Should be just enough to hold a key sequence,
361 // but CRASHME mode uses it as generated command buffer too
362 #if ENABLE_FEATURE_VI_CRASHME
363 char readbuffer[128];
365 char readbuffer[KEYCODE_BUFFER_SIZE];
367 #define STATUS_BUFFER_LEN 200
368 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
369 #if ENABLE_FEATURE_VI_DOT_CMD
370 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
372 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
374 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
375 #if ENABLE_FEATURE_VI_UNDO
376 // undo_push() operations
379 #define UNDO_INS_CHAIN 2
380 #define UNDO_DEL_CHAIN 3
381 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
382 #define UNDO_QUEUED_FLAG 4
383 #define UNDO_INS_QUEUED 4
384 #define UNDO_DEL_QUEUED 5
385 #define UNDO_USE_SPOS 32
386 #define UNDO_EMPTY 64
387 // Pass-through flags for functions that can be undone
390 #define ALLOW_UNDO_CHAIN 2
391 # if ENABLE_FEATURE_VI_UNDO_QUEUE
392 #define ALLOW_UNDO_QUEUED 3
393 char undo_queue_state;
395 char *undo_queue_spos; // Start position of queued operation
396 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
398 // If undo queuing disabled, don't invoke the missing queue logic
399 #define ALLOW_UNDO_QUEUED 1
403 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
404 int start; // Offset where the data should be restored/deleted
405 int length; // total data size
406 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
407 char undo_text[1]; // text that was deleted (if deletion)
409 #endif /* ENABLE_FEATURE_VI_UNDO */
411 #define G (*ptr_to_globals)
412 #define text (G.text )
413 #define text_size (G.text_size )
418 #define vi_setops (G.vi_setops )
419 #define editing (G.editing )
420 #define cmd_mode (G.cmd_mode )
421 #define modified_count (G.modified_count )
422 #define last_modified_count (G.last_modified_count)
423 #define save_argc (G.save_argc )
424 #define cmdcnt (G.cmdcnt )
425 #define rows (G.rows )
426 #define columns (G.columns )
427 #define crow (G.crow )
428 #define ccol (G.ccol )
429 #define offset (G.offset )
430 #define status_buffer (G.status_buffer )
431 #define have_status_msg (G.have_status_msg )
432 #define last_status_cksum (G.last_status_cksum )
433 #define current_filename (G.current_filename )
434 #define screen (G.screen )
435 #define screensize (G.screensize )
436 #define screenbegin (G.screenbegin )
437 #define tabstop (G.tabstop )
438 #define last_forward_char (G.last_forward_char )
439 #define erase_char (G.erase_char )
440 #define last_input_char (G.last_input_char )
441 #if ENABLE_FEATURE_VI_READONLY
442 #define readonly_mode (G.readonly_mode )
444 #define readonly_mode 0
446 #define adding2q (G.adding2q )
447 #define lmc_len (G.lmc_len )
449 #define ioq_start (G.ioq_start )
450 #define my_pid (G.my_pid )
451 #define last_search_pattern (G.last_search_pattern)
453 #define edit_file__cur_line (G.edit_file__cur_line)
454 #define refresh__old_offset (G.refresh__old_offset)
455 #define format_edit_status__tot (G.format_edit_status__tot)
457 #define YDreg (G.YDreg )
458 #define Ureg (G.Ureg )
459 #define mark (G.mark )
460 #define context_start (G.context_start )
461 #define context_end (G.context_end )
462 #define restart (G.restart )
463 #define term_orig (G.term_orig )
464 #define initial_cmds (G.initial_cmds )
465 #define readbuffer (G.readbuffer )
466 #define scr_out_buf (G.scr_out_buf )
467 #define last_modifying_cmd (G.last_modifying_cmd )
468 #define get_input_line__buf (G.get_input_line__buf)
470 #if ENABLE_FEATURE_VI_UNDO
471 #define undo_stack_tail (G.undo_stack_tail )
472 # if ENABLE_FEATURE_VI_UNDO_QUEUE
473 #define undo_queue_state (G.undo_queue_state)
474 #define undo_q (G.undo_q )
475 #define undo_queue (G.undo_queue )
476 #define undo_queue_spos (G.undo_queue_spos )
480 #define INIT_G() do { \
481 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
482 last_modified_count = -1; \
483 /* "" but has space for 2 chars: */ \
484 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
488 static void edit_file(char *); // edit one file
489 static void do_cmd(int); // execute a command
490 static int next_tabstop(int);
491 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
492 static char *begin_line(char *); // return pointer to cur line B-o-l
493 static char *end_line(char *); // return pointer to cur line E-o-l
494 static char *prev_line(char *); // return pointer to prev line B-o-l
495 static char *next_line(char *); // return pointer to next line B-o-l
496 static char *end_screen(void); // get pointer to last char on screen
497 static int count_lines(char *, char *); // count line from start to stop
498 static char *find_line(int); // find beginning of line #li
499 static char *move_to_col(char *, int); // move "p" to column l
500 static void dot_left(void); // move dot left- dont leave line
501 static void dot_right(void); // move dot right- dont leave line
502 static void dot_begin(void); // move dot to B-o-l
503 static void dot_end(void); // move dot to E-o-l
504 static void dot_next(void); // move dot to next line B-o-l
505 static void dot_prev(void); // move dot to prev line B-o-l
506 static void dot_scroll(int, int); // move the screen up or down
507 static void dot_skip_over_ws(void); // move dot pat WS
508 static char *bound_dot(char *); // make sure text[0] <= P < "end"
509 static char *new_screen(int, int); // malloc virtual screen memory
510 #if !ENABLE_FEATURE_VI_UNDO
511 #define char_insert(a,b,c) char_insert(a,b)
513 static char *char_insert(char *, char, int); // insert the char c at 'p'
514 // might reallocate text[]! use p += stupid_insert(p, ...),
515 // and be careful to not use pointers into potentially freed text[]!
516 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
517 static int find_range(char **, char **, char); // return pointers for an object
518 static int st_test(char *, int, int, char *); // helper for skip_thing()
519 static char *skip_thing(char *, int, int, int); // skip some object
520 static char *find_pair(char *, char); // find matching pair () [] {}
521 #if !ENABLE_FEATURE_VI_UNDO
522 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
524 static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
525 // might reallocate text[]! use p += text_hole_make(p, ...),
526 // and be careful to not use pointers into potentially freed text[]!
527 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
528 #if !ENABLE_FEATURE_VI_UNDO
529 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
531 static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
532 static void show_help(void); // display some help info
533 static void rawmode(void); // set "raw" mode on tty
534 static void cookmode(void); // return to "cooked" mode on tty
535 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
536 static int mysleep(int);
537 static int readit(void); // read (maybe cursor) key from stdin
538 static int get_one_char(void); // read 1 char from stdin
539 // file_insert might reallocate text[]!
540 static int file_insert(const char *, char *, int);
541 static int file_write(char *, char *, char *);
542 static void place_cursor(int, int);
543 static void screen_erase(void);
544 static void clear_to_eol(void);
545 static void clear_to_eos(void);
546 static void go_bottom_and_clear_to_eol(void);
547 static void standout_start(void); // send "start reverse video" sequence
548 static void standout_end(void); // send "end reverse video" sequence
549 static void flash(int); // flash the terminal screen
550 static void show_status_line(void); // put a message on the bottom line
551 static void status_line(const char *, ...); // print to status buf
552 static void status_line_bold(const char *, ...);
553 static void status_line_bold_errno(const char *fn);
554 static void not_implemented(const char *); // display "Not implemented" message
555 static int format_edit_status(void); // format file status on status line
556 static void redraw(int); // force a full screen refresh
557 static char* format_line(char* /*, int*/);
558 static void refresh(int); // update the terminal from screen[]
560 static void indicate_error(void); // use flash or beep to indicate error
561 static void Hit_Return(void);
563 #if ENABLE_FEATURE_VI_SEARCH
564 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
566 #if ENABLE_FEATURE_VI_COLON
567 static char *get_one_address(char *, int *); // get colon addr, if present
568 static char *get_address(char *, int *, int *); // get two colon addrs, if present
570 static void colon(char *); // execute the "colon" mode cmds
571 #if ENABLE_FEATURE_VI_USE_SIGNALS
572 static void winch_sig(int); // catch window size changes
573 static void suspend_sig(int); // catch ctrl-Z
574 static void catch_sig(int); // catch ctrl-C and alarm time-outs
576 #if ENABLE_FEATURE_VI_DOT_CMD
577 static void start_new_cmd_q(char); // new queue for command
578 static void end_cmd_q(void); // stop saving input chars
580 #define end_cmd_q() ((void)0)
582 #if ENABLE_FEATURE_VI_SETOPTS
583 static void showmatching(char *); // show the matching pair () [] {}
585 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
586 // might reallocate text[]! use p += string_insert(p, ...),
587 // and be careful to not use pointers into potentially freed text[]!
588 # if !ENABLE_FEATURE_VI_UNDO
589 #define string_insert(a,b,c) string_insert(a,b)
591 static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
593 #if ENABLE_FEATURE_VI_YANKMARK
594 static char *text_yank(char *, char *, int); // save copy of "p" into a register
595 static char what_reg(void); // what is letter of current YDreg
596 static void check_context(char); // remember context for '' command
598 #if ENABLE_FEATURE_VI_UNDO
599 static void flush_undo_data(void);
600 static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
601 static void undo_pop(void); // Undo the last operation
602 # if ENABLE_FEATURE_VI_UNDO_QUEUE
603 static void undo_queue_commit(void); // Flush any queued objects to the undo stack
605 # define undo_queue_commit() ((void)0)
608 #define flush_undo_data() ((void)0)
609 #define undo_queue_commit() ((void)0)
612 #if ENABLE_FEATURE_VI_CRASHME
613 static void crash_dummy();
614 static void crash_test();
615 static int crashme = 0;
618 static void write1(const char *out)
623 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
624 int vi_main(int argc, char **argv)
630 #if ENABLE_FEATURE_VI_UNDO
631 /* undo_stack_tail = NULL; - already is */
632 #if ENABLE_FEATURE_VI_UNDO_QUEUE
633 undo_queue_state = UNDO_EMPTY;
634 /* undo_q = 0; - already is */
638 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
641 #if ENABLE_FEATURE_VI_CRASHME
642 srand((long) my_pid);
644 #ifdef NO_SUCH_APPLET_YET
645 /* If we aren't "vi", we are "view" */
646 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
647 SET_READONLY_MODE(readonly_mode);
651 // autoindent is not default in vim 7.3
652 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
653 // 1- process $HOME/.exrc file (not inplemented yet)
654 // 2- process EXINIT variable from environment
655 // 3- process command line args
656 #if ENABLE_FEATURE_VI_COLON
658 char *p = getenv("EXINIT");
660 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
663 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
665 #if ENABLE_FEATURE_VI_CRASHME
670 #if ENABLE_FEATURE_VI_READONLY
671 case 'R': // Read-only flag
672 SET_READONLY_MODE(readonly_mode);
675 #if ENABLE_FEATURE_VI_COLON
676 case 'c': // cmd line vi command
678 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
690 // The argv array can be used by the ":next" and ":rewind" commands
694 //----- This is the main file handling loop --------------
697 // "Save cursor, use alternate screen buffer, clear screen"
698 write1(ESC"[?1049h");
700 edit_file(argv[optind]); /* param might be NULL */
701 if (++optind >= argc)
704 // "Use normal screen buffer, restore cursor"
705 write1(ESC"[?1049l");
706 //-----------------------------------------------------------
711 /* read text from file or create an empty buf */
712 /* will also update current_filename */
713 static int init_text_buffer(char *fn)
717 /* allocate/reallocate text buffer */
720 screenbegin = dot = end = text = xzalloc(text_size);
722 if (fn != current_filename) {
723 free(current_filename);
724 current_filename = xstrdup(fn);
726 rc = file_insert(fn, text, 1);
728 // file doesnt exist. Start empty buf with dummy line
729 char_insert(text, '\n', NO_UNDO);
734 last_modified_count = -1;
735 #if ENABLE_FEATURE_VI_YANKMARK
737 memset(mark, 0, sizeof(mark));
742 #if ENABLE_FEATURE_VI_WIN_RESIZE
743 static int query_screen_dimensions(void)
745 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
746 if (rows > MAX_SCR_ROWS)
748 if (columns > MAX_SCR_COLS)
749 columns = MAX_SCR_COLS;
753 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, 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, 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, int range)
1900 struct re_pattern_buffer preg;
1906 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1908 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1910 memset(&preg, 0, sizeof(preg));
1911 err = re_compile_pattern(pat, strlen(pat), &preg);
1913 status_line_bold("bad search pattern '%s': %s", pat, err);
1917 // assume a LIMITED forward search
1921 // RANGE could be negative if we are searching backwards
1931 // search for the compiled pattern, preg, in p[]
1932 // range < 0: search backward
1933 // range > 0: search forward
1935 // re_search() < 0: not found or error
1936 // re_search() >= 0: index of found pattern
1937 // struct pattern char int int int struct reg
1938 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1939 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1952 # if ENABLE_FEATURE_VI_SETOPTS
1953 static int mycmp(const char *s1, const char *s2, int len)
1956 return strncasecmp(s1, s2, len);
1958 return strncmp(s1, s2, len);
1961 # define mycmp strncmp
1964 static char *char_search(char *p, const char *pat, int dir, int range)
1970 if (dir == FORWARD) {
1971 stop = end - 1; // assume range is p..end-1
1972 if (range == LIMITED)
1973 stop = next_line(p); // range is to next line
1974 for (start = p; start < stop; start++) {
1975 if (mycmp(start, pat, len) == 0) {
1979 } else if (dir == BACK) {
1980 stop = text; // assume range is text..p
1981 if (range == LIMITED)
1982 stop = prev_line(p); // range is to prev line
1983 for (start = p - len; start >= stop; start--) {
1984 if (mycmp(start, pat, len) == 0) {
1989 // pattern not found
1995 #endif /* FEATURE_VI_SEARCH */
1997 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1999 if (c == 22) { // Is this an ctrl-V?
2000 p += stupid_insert(p, '^'); // use ^ to indicate literal next
2001 refresh(FALSE); // show the ^
2004 #if ENABLE_FEATURE_VI_UNDO
2007 undo_push(p, 1, UNDO_INS);
2009 case ALLOW_UNDO_CHAIN:
2010 undo_push(p, 1, UNDO_INS_CHAIN);
2012 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2013 case ALLOW_UNDO_QUEUED:
2014 undo_push(p, 1, UNDO_INS_QUEUED);
2020 #endif /* ENABLE_FEATURE_VI_UNDO */
2022 } else if (c == 27) { // Is this an ESC?
2024 undo_queue_commit();
2026 end_cmd_q(); // stop adding to q
2027 last_status_cksum = 0; // force status update
2028 if ((p[-1] != '\n') && (dot > text)) {
2031 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
2034 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2037 // insert a char into text[]
2039 c = '\n'; // translate \r to \n
2040 #if ENABLE_FEATURE_VI_UNDO
2041 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2043 undo_queue_commit();
2047 undo_push(p, 1, UNDO_INS);
2049 case ALLOW_UNDO_CHAIN:
2050 undo_push(p, 1, UNDO_INS_CHAIN);
2052 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2053 case ALLOW_UNDO_QUEUED:
2054 undo_push(p, 1, UNDO_INS_QUEUED);
2060 #endif /* ENABLE_FEATURE_VI_UNDO */
2061 p += 1 + stupid_insert(p, c); // insert the char
2062 #if ENABLE_FEATURE_VI_SETOPTS
2063 if (showmatch && strchr(")]}", c) != NULL) {
2064 showmatching(p - 1);
2066 if (autoindent && c == '\n') { // auto indent the new line
2069 q = prev_line(p); // use prev line as template
2070 len = strspn(q, " \t"); // space or tab
2073 bias = text_hole_make(p, len);
2076 #if ENABLE_FEATURE_VI_UNDO
2077 undo_push(p, len, UNDO_INS);
2088 // might reallocate text[]! use p += stupid_insert(p, ...),
2089 // and be careful to not use pointers into potentially freed text[]!
2090 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2093 bias = text_hole_make(p, 1);
2099 static int find_range(char **start, char **stop, char c)
2101 char *save_dot, *p, *q, *t;
2102 int cnt, multiline = 0;
2107 if (strchr("cdy><", c)) {
2108 // these cmds operate on whole lines
2109 p = q = begin_line(p);
2110 for (cnt = 1; cnt < cmdcnt; cnt++) {
2114 } else if (strchr("^%$0bBeEfth\b\177", c)) {
2115 // These cmds operate on char positions
2116 do_cmd(c); // execute movement cmd
2118 } else if (strchr("wW", c)) {
2119 do_cmd(c); // execute movement cmd
2120 // if we are at the next word's first char
2121 // step back one char
2122 // but check the possibilities when it is true
2123 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
2124 || (ispunct(dot[-1]) && !ispunct(dot[0]))
2125 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
2126 dot--; // move back off of next word
2127 if (dot > text && *dot == '\n')
2128 dot--; // stay off NL
2130 } else if (strchr("H-k{", c)) {
2131 // these operate on multi-lines backwards
2132 q = end_line(dot); // find NL
2133 do_cmd(c); // execute movement cmd
2136 } else if (strchr("L+j}\r\n", c)) {
2137 // these operate on multi-lines forwards
2138 p = begin_line(dot);
2139 do_cmd(c); // execute movement cmd
2140 dot_end(); // find NL
2143 // nothing -- this causes any other values of c to
2144 // represent the one-character range under the
2145 // cursor. this is correct for ' ' and 'l', but
2146 // perhaps no others.
2155 // backward char movements don't include start position
2156 if (q > p && strchr("^0bBh\b\177", c)) q--;
2159 for (t = p; t <= q; t++) {
2172 static int st_test(char *p, int type, int dir, char *tested)
2182 if (type == S_BEFORE_WS) {
2184 test = (!isspace(c) || c == '\n');
2186 if (type == S_TO_WS) {
2188 test = (!isspace(c) || c == '\n');
2190 if (type == S_OVER_WS) {
2194 if (type == S_END_PUNCT) {
2198 if (type == S_END_ALNUM) {
2200 test = (isalnum(c) || c == '_');
2206 static char *skip_thing(char *p, int linecnt, int dir, int type)
2210 while (st_test(p, type, dir, &c)) {
2211 // make sure we limit search to correct number of lines
2212 if (c == '\n' && --linecnt < 1)
2214 if (dir >= 0 && p >= end - 1)
2216 if (dir < 0 && p <= text)
2218 p += dir; // move to next char
2223 // find matching char of pair () [] {}
2224 // will crash if c is not one of these
2225 static char *find_pair(char *p, const char c)
2227 const char *braces = "()[]{}";
2231 dir = strchr(braces, c) - braces;
2233 match = braces[dir];
2234 dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
2236 // look for match, count levels of pairs (( ))
2240 if (p < text || p >= end)
2243 level++; // increase pair levels
2245 level--; // reduce pair level
2247 return p; // found matching pair
2252 #if ENABLE_FEATURE_VI_SETOPTS
2253 // show the matching char of a pair, () [] {}
2254 static void showmatching(char *p)
2258 // we found half of a pair
2259 q = find_pair(p, *p); // get loc of matching char
2261 indicate_error(); // no matching char
2263 // "q" now points to matching pair
2264 save_dot = dot; // remember where we are
2265 dot = q; // go to new loc
2266 refresh(FALSE); // let the user see it
2267 mysleep(40); // give user some time
2268 dot = save_dot; // go back to old loc
2272 #endif /* FEATURE_VI_SETOPTS */
2274 #if ENABLE_FEATURE_VI_UNDO
2275 static void flush_undo_data(void)
2277 struct undo_object *undo_entry;
2279 while (undo_stack_tail) {
2280 undo_entry = undo_stack_tail;
2281 undo_stack_tail = undo_entry->prev;
2286 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2287 static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack
2289 struct undo_object *undo_entry;
2292 // UNDO_INS: insertion, undo will remove from buffer
2293 // UNDO_DEL: deleted text, undo will restore to buffer
2294 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2295 // The CHAIN operations are for handling multiple operations that the user
2296 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2297 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2298 // for the INS/DEL operation. The raw values should be equal to the values of
2299 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2301 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2302 // This undo queuing functionality groups multiple character typing or backspaces
2303 // into a single large undo object. This greatly reduces calls to malloc() for
2304 // single-character operations while typing and has the side benefit of letting
2305 // an undo operation remove chunks of text rather than a single character.
2307 case UNDO_EMPTY: // Just in case this ever happens...
2309 case UNDO_DEL_QUEUED:
2311 return; // Only queue single characters
2312 switch (undo_queue_state) {
2314 undo_queue_state = UNDO_DEL;
2316 undo_queue_spos = src;
2318 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2319 // If queue is full, dump it into an object
2320 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2321 undo_queue_commit();
2324 // Switch from storing inserted text to deleted text
2325 undo_queue_commit();
2326 undo_push(src, length, UNDO_DEL_QUEUED);
2330 case UNDO_INS_QUEUED:
2333 switch (undo_queue_state) {
2335 undo_queue_state = UNDO_INS;
2336 undo_queue_spos = src;
2338 undo_q++; // Don't need to save any data for insertions
2339 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2340 undo_queue_commit();
2343 // Switch from storing deleted text to inserted text
2344 undo_queue_commit();
2345 undo_push(src, length, UNDO_INS_QUEUED);
2351 // If undo queuing is disabled, ignore the queuing flag entirely
2352 u_type = u_type & ~UNDO_QUEUED_FLAG;
2355 // Allocate a new undo object
2356 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2357 // For UNDO_DEL objects, save deleted text
2358 if ((src + length) == end)
2360 // If this deletion empties text[], strip the newline. When the buffer becomes
2361 // zero-length, a newline is added back, which requires this to compensate.
2362 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
2363 memcpy(undo_entry->undo_text, src, length);
2365 undo_entry = xzalloc(sizeof(*undo_entry));
2367 undo_entry->length = length;
2368 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2369 if ((u_type & UNDO_USE_SPOS) != 0) {
2370 undo_entry->start = undo_queue_spos - text; // use start position from queue
2372 undo_entry->start = src - text; // use offset from start of text buffer
2374 u_type = (u_type & ~UNDO_USE_SPOS);
2376 undo_entry->start = src - text;
2378 undo_entry->u_type = u_type;
2380 // Push it on undo stack
2381 undo_entry->prev = undo_stack_tail;
2382 undo_stack_tail = undo_entry;
2386 static void undo_pop(void) // Undo the last operation
2389 char *u_start, *u_end;
2390 struct undo_object *undo_entry;
2392 // Commit pending undo queue before popping (should be unnecessary)
2393 undo_queue_commit();
2395 undo_entry = undo_stack_tail;
2396 // Check for an empty undo stack
2398 status_line("Already at oldest change");
2402 switch (undo_entry->u_type) {
2404 case UNDO_DEL_CHAIN:
2405 // make hole and put in text that was deleted; deallocate text
2406 u_start = text + undo_entry->start;
2407 text_hole_make(u_start, undo_entry->length);
2408 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2409 status_line("Undo [%d] %s %d chars at position %d",
2410 modified_count, "restored",
2411 undo_entry->length, undo_entry->start
2415 case UNDO_INS_CHAIN:
2416 // delete what was inserted
2417 u_start = undo_entry->start + text;
2418 u_end = u_start - 1 + undo_entry->length;
2419 text_hole_delete(u_start, u_end, NO_UNDO);
2420 status_line("Undo [%d] %s %d chars at position %d",
2421 modified_count, "deleted",
2422 undo_entry->length, undo_entry->start
2427 switch (undo_entry->u_type) {
2428 // If this is the end of a chain, lower modification count and refresh display
2431 dot = (text + undo_entry->start);
2434 case UNDO_DEL_CHAIN:
2435 case UNDO_INS_CHAIN:
2439 // Deallocate the undo object we just processed
2440 undo_stack_tail = undo_entry->prev;
2443 // For chained operations, continue popping all the way down the chain.
2445 undo_pop(); // Follow the undo chain if one exists
2449 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2450 static void undo_queue_commit(void) // Flush any queued objects to the undo stack
2452 // Pushes the queue object onto the undo stack
2454 // Deleted character undo events grow from the end
2455 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2457 (undo_queue_state | UNDO_USE_SPOS)
2459 undo_queue_state = UNDO_EMPTY;
2465 #endif /* ENABLE_FEATURE_VI_UNDO */
2467 // open a hole in text[]
2468 // might reallocate text[]! use p += text_hole_make(p, ...),
2469 // and be careful to not use pointers into potentially freed text[]!
2470 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2476 end += size; // adjust the new END
2477 if (end >= (text + text_size)) {
2479 text_size += end - (text + text_size) + 10240;
2480 new_text = xrealloc(text, text_size);
2481 bias = (new_text - text);
2482 screenbegin += bias;
2486 #if ENABLE_FEATURE_VI_YANKMARK
2489 for (i = 0; i < ARRAY_SIZE(mark); i++)
2496 memmove(p + size, p, end - size - p);
2497 memset(p, ' ', size); // clear new hole
2501 // close a hole in text[]
2502 // "undo" value indicates if this operation should be undo-able
2503 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2508 // move forwards, from beginning
2512 if (q < p) { // they are backward- swap them
2516 hole_size = q - p + 1;
2518 #if ENABLE_FEATURE_VI_UNDO
2523 undo_push(p, hole_size, UNDO_DEL);
2525 case ALLOW_UNDO_CHAIN:
2526 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2528 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2529 case ALLOW_UNDO_QUEUED:
2530 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2536 if (src < text || src > end)
2538 if (dest < text || dest >= end)
2542 goto thd_atend; // just delete the end of the buffer
2543 memmove(dest, src, cnt);
2545 end = end - hole_size; // adjust the new END
2547 dest = end - 1; // make sure dest in below end-1
2549 dest = end = text; // keep pointers valid
2554 // copy text into register, then delete text.
2555 // if dist <= 0, do not include, or go past, a NewLine
2557 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2561 // make sure start <= stop
2563 // they are backwards, reverse them
2569 // we cannot cross NL boundaries
2573 // dont go past a NewLine
2574 for (; p + 1 <= stop; p++) {
2576 stop = p; // "stop" just before NewLine
2582 #if ENABLE_FEATURE_VI_YANKMARK
2583 text_yank(start, stop, YDreg);
2585 if (yf == YANKDEL) {
2586 p = text_hole_delete(start, stop, undo);
2591 static void show_help(void)
2593 puts("These features are available:"
2594 #if ENABLE_FEATURE_VI_SEARCH
2595 "\n\tPattern searches with / and ?"
2597 #if ENABLE_FEATURE_VI_DOT_CMD
2598 "\n\tLast command repeat with ."
2600 #if ENABLE_FEATURE_VI_YANKMARK
2601 "\n\tLine marking with 'x"
2602 "\n\tNamed buffers with \"x"
2604 #if ENABLE_FEATURE_VI_READONLY
2605 //not implemented: "\n\tReadonly if vi is called as \"view\""
2606 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2608 #if ENABLE_FEATURE_VI_SET
2609 "\n\tSome colon mode commands with :"
2611 #if ENABLE_FEATURE_VI_SETOPTS
2612 "\n\tSettable options with \":set\""
2614 #if ENABLE_FEATURE_VI_USE_SIGNALS
2615 "\n\tSignal catching- ^C"
2616 "\n\tJob suspend and resume with ^Z"
2618 #if ENABLE_FEATURE_VI_WIN_RESIZE
2619 "\n\tAdapt to window re-sizes"
2624 #if ENABLE_FEATURE_VI_DOT_CMD
2625 static void start_new_cmd_q(char c)
2627 // get buffer for new cmd
2628 // if there is a current cmd count put it in the buffer first
2630 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2631 } else { // just save char c onto queue
2632 last_modifying_cmd[0] = c;
2638 static void end_cmd_q(void)
2640 #if ENABLE_FEATURE_VI_YANKMARK
2641 YDreg = 26; // go back to default Yank/Delete reg
2645 #endif /* FEATURE_VI_DOT_CMD */
2647 #if ENABLE_FEATURE_VI_YANKMARK \
2648 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2649 || ENABLE_FEATURE_VI_CRASHME
2650 // might reallocate text[]! use p += string_insert(p, ...),
2651 // and be careful to not use pointers into potentially freed text[]!
2652 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2658 #if ENABLE_FEATURE_VI_UNDO
2661 undo_push(p, i, UNDO_INS);
2663 case ALLOW_UNDO_CHAIN:
2664 undo_push(p, i, UNDO_INS_CHAIN);
2668 bias = text_hole_make(p, i);
2671 #if ENABLE_FEATURE_VI_YANKMARK
2674 for (cnt = 0; *s != '\0'; s++) {
2678 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2685 #if ENABLE_FEATURE_VI_YANKMARK
2686 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2689 if (cnt < 0) { // they are backwards- reverse them
2693 free(reg[dest]); // if already a yank register, free it
2694 reg[dest] = xstrndup(p, cnt + 1);
2698 static char what_reg(void)
2702 c = 'D'; // default to D-reg
2703 if (0 <= YDreg && YDreg <= 25)
2704 c = 'a' + (char) YDreg;
2712 static void check_context(char cmd)
2714 // A context is defined to be "modifying text"
2715 // Any modifying command establishes a new context.
2717 if (dot < context_start || dot > context_end) {
2718 if (strchr(modifying_cmds, cmd) != NULL) {
2719 // we are trying to modify text[]- make this the current context
2720 mark[27] = mark[26]; // move cur to prev
2721 mark[26] = dot; // move local to cur
2722 context_start = prev_line(prev_line(dot));
2723 context_end = next_line(next_line(dot));
2724 //loiter= start_loiter= now;
2729 static char *swap_context(char *p) // goto new context for '' command make this the current context
2733 // the current context is in mark[26]
2734 // the previous context is in mark[27]
2735 // only swap context if other context is valid
2736 if (text <= mark[27] && mark[27] <= end - 1) {
2740 context_start = prev_line(prev_line(prev_line(p)));
2741 context_end = next_line(next_line(next_line(p)));
2745 #endif /* FEATURE_VI_YANKMARK */
2747 //----- Set terminal attributes --------------------------------
2748 static void rawmode(void)
2750 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
2751 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
2752 erase_char = term_orig.c_cc[VERASE];
2755 static void cookmode(void)
2758 tcsetattr_stdin_TCSANOW(&term_orig);
2761 #if ENABLE_FEATURE_VI_USE_SIGNALS
2762 //----- Come here when we get a window resize signal ---------
2763 static void winch_sig(int sig UNUSED_PARAM)
2765 int save_errno = errno;
2766 // FIXME: do it in main loop!!!
2767 signal(SIGWINCH, winch_sig);
2768 query_screen_dimensions();
2769 new_screen(rows, columns); // get memory for virtual screen
2770 redraw(TRUE); // re-draw the screen
2774 //----- Come here when we get a continue signal -------------------
2775 static void cont_sig(int sig UNUSED_PARAM)
2777 int save_errno = errno;
2778 rawmode(); // terminal to "raw"
2779 last_status_cksum = 0; // force status update
2780 redraw(TRUE); // re-draw the screen
2782 signal(SIGTSTP, suspend_sig);
2783 signal(SIGCONT, SIG_DFL);
2784 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2788 //----- Come here when we get a Suspend signal -------------------
2789 static void suspend_sig(int sig UNUSED_PARAM)
2791 int save_errno = errno;
2792 go_bottom_and_clear_to_eol();
2793 cookmode(); // terminal to "cooked"
2795 signal(SIGCONT, cont_sig);
2796 signal(SIGTSTP, SIG_DFL);
2797 kill(my_pid, SIGTSTP);
2801 //----- Come here when we get a signal ---------------------------
2802 static void catch_sig(int sig)
2804 signal(SIGINT, catch_sig);
2805 siglongjmp(restart, sig);
2807 #endif /* FEATURE_VI_USE_SIGNALS */
2809 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2811 struct pollfd pfd[1];
2816 pfd[0].fd = STDIN_FILENO;
2817 pfd[0].events = POLLIN;
2818 return safe_poll(pfd, 1, hund*10) > 0;
2821 //----- IO Routines --------------------------------------------
2822 static int readit(void) // read (maybe cursor) key from stdin
2828 // Wait for input. TIMEOUT = -1 makes read_key wait even
2829 // on nonblocking stdin.
2830 // Note: read_key sets errno to 0 on success.
2832 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
2833 if (c == -1) { // EOF/error
2834 if (errno == EAGAIN) // paranoia
2836 go_bottom_and_clear_to_eol();
2837 cookmode(); // terminal to "cooked"
2838 bb_error_msg_and_die("can't read user input");
2843 //----- IO Routines --------------------------------------------
2844 static int get_one_char(void)
2848 #if ENABLE_FEATURE_VI_DOT_CMD
2850 // we are not adding to the q.
2851 // but, we may be reading from a q
2853 // there is no current q, read from STDIN
2854 c = readit(); // get the users input
2856 // there is a queue to get chars from first
2857 // careful with correct sign expansion!
2858 c = (unsigned char)*ioq++;
2860 // the end of the q, read from STDIN
2862 ioq_start = ioq = 0;
2863 c = readit(); // get the users input
2867 // adding STDIN chars to q
2868 c = readit(); // get the users input
2869 if (lmc_len >= MAX_INPUT_LEN - 1) {
2870 status_line_bold("last_modifying_cmd overrun");
2872 // add new char to q
2873 last_modifying_cmd[lmc_len++] = c;
2877 c = readit(); // get the users input
2878 #endif /* FEATURE_VI_DOT_CMD */
2882 // Get input line (uses "status line" area)
2883 static char *get_input_line(const char *prompt)
2885 // char [MAX_INPUT_LEN]
2886 #define buf get_input_line__buf
2891 strcpy(buf, prompt);
2892 last_status_cksum = 0; // force status update
2893 go_bottom_and_clear_to_eol();
2894 write1(prompt); // write out the :, /, or ? prompt
2897 while (i < MAX_INPUT_LEN) {
2899 if (c == '\n' || c == '\r' || c == 27)
2900 break; // this is end of input
2901 if (c == erase_char || c == 8 || c == 127) {
2902 // user wants to erase prev char
2904 write1("\b \b"); // erase char on screen
2905 if (i <= 0) // user backs up before b-o-l, exit
2907 } else if (c > 0 && c < 256) { // exclude Unicode
2908 // (TODO: need to handle Unicode)
2919 // might reallocate text[]!
2920 static int file_insert(const char *fn, char *p, int initial)
2924 struct stat statbuf;
2931 fd = open(fn, O_RDONLY);
2934 status_line_bold_errno(fn);
2939 if (fstat(fd, &statbuf) < 0) {
2940 status_line_bold_errno(fn);
2943 if (!S_ISREG(statbuf.st_mode)) {
2944 status_line_bold("'%s' is not a regular file", fn);
2947 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2948 p += text_hole_make(p, size);
2949 cnt = full_read(fd, p, size);
2951 status_line_bold_errno(fn);
2952 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2953 } else if (cnt < size) {
2954 // There was a partial read, shrink unused space
2955 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2956 status_line_bold("can't read '%s'", fn);
2961 #if ENABLE_FEATURE_VI_READONLY
2963 && ((access(fn, W_OK) < 0) ||
2964 /* root will always have access()
2965 * so we check fileperms too */
2966 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2969 SET_READONLY_FILE(readonly_mode);
2975 static int file_write(char *fn, char *first, char *last)
2977 int fd, cnt, charcnt;
2980 status_line_bold("No current filename");
2983 /* By popular request we do not open file with O_TRUNC,
2984 * but instead ftruncate() it _after_ successful write.
2985 * Might reduce amount of data lost on power fail etc.
2987 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2990 cnt = last - first + 1;
2991 charcnt = full_write(fd, first, cnt);
2992 ftruncate(fd, charcnt);
2993 if (charcnt == cnt) {
2995 //modified_count = FALSE;
3003 //----- Terminal Drawing ---------------------------------------
3004 // The terminal is made up of 'rows' line of 'columns' columns.
3005 // classically this would be 24 x 80.
3006 // screen coordinates
3012 // 23,0 ... 23,79 <- status line
3014 //----- Move the cursor to row x col (count from 0, not 1) -------
3015 static void place_cursor(int row, int col)
3017 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
3019 if (row < 0) row = 0;
3020 if (row >= rows) row = rows - 1;
3021 if (col < 0) col = 0;
3022 if (col >= columns) col = columns - 1;
3024 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
3028 //----- Erase from cursor to end of line -----------------------
3029 static void clear_to_eol(void)
3031 write1(ESC_CLEAR2EOL);
3034 static void go_bottom_and_clear_to_eol(void)
3036 place_cursor(rows - 1, 0);
3040 //----- Erase from cursor to end of screen -----------------------
3041 static void clear_to_eos(void)
3043 write1(ESC_CLEAR2EOS);
3046 //----- Start standout mode ------------------------------------
3047 static void standout_start(void)
3049 write1(ESC_BOLD_TEXT);
3052 //----- End standout mode --------------------------------------
3053 static void standout_end(void)
3055 write1(ESC_NORM_TEXT);
3058 //----- Flash the screen --------------------------------------
3059 static void flash(int h)
3068 static void indicate_error(void)
3070 #if ENABLE_FEATURE_VI_CRASHME
3072 return; // generate a random command
3081 //----- Screen[] Routines --------------------------------------
3082 //----- Erase the Screen[] memory ------------------------------
3083 static void screen_erase(void)
3085 memset(screen, ' ', screensize); // clear new screen
3088 static int bufsum(char *buf, int count)
3091 char *e = buf + count;
3094 sum += (unsigned char) *buf++;
3098 //----- Draw the status line at bottom of the screen -------------
3099 static void show_status_line(void)
3101 int cnt = 0, cksum = 0;
3103 // either we already have an error or status message, or we
3105 if (!have_status_msg) {
3106 cnt = format_edit_status();
3107 cksum = bufsum(status_buffer, cnt);
3109 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3110 last_status_cksum = cksum; // remember if we have seen this line
3111 go_bottom_and_clear_to_eol();
3112 write1(status_buffer);
3113 if (have_status_msg) {
3114 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3116 have_status_msg = 0;
3119 have_status_msg = 0;
3121 place_cursor(crow, ccol); // put cursor back in correct place
3126 //----- format the status buffer, the bottom line of screen ------
3127 // format status buffer, with STANDOUT mode
3128 static void status_line_bold(const char *format, ...)
3132 va_start(args, format);
3133 strcpy(status_buffer, ESC_BOLD_TEXT);
3134 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3135 strcat(status_buffer, ESC_NORM_TEXT);
3138 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3141 static void status_line_bold_errno(const char *fn)
3143 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
3146 // format status buffer
3147 static void status_line(const char *format, ...)
3151 va_start(args, format);
3152 vsprintf(status_buffer, format, args);
3155 have_status_msg = 1;
3158 // copy s to buf, convert unprintable
3159 static void print_literal(char *buf, const char *s)
3173 c_is_no_print = (c & 0x80) && !Isprint(c);
3174 if (c_is_no_print) {
3175 strcpy(d, ESC_NORM_TEXT);
3176 d += sizeof(ESC_NORM_TEXT)-1;
3179 if (c < ' ' || c == 0x7f) {
3181 c |= '@'; /* 0x40 */
3187 if (c_is_no_print) {
3188 strcpy(d, ESC_BOLD_TEXT);
3189 d += sizeof(ESC_BOLD_TEXT)-1;
3195 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3200 static void not_implemented(const char *s)
3202 char buf[MAX_INPUT_LEN];
3204 print_literal(buf, s);
3205 status_line_bold("\'%s\' is not implemented", buf);
3208 // show file status on status line
3209 static int format_edit_status(void)
3211 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3213 #define tot format_edit_status__tot
3215 int cur, percent, ret, trunc_at;
3217 // modified_count is now a counter rather than a flag. this
3218 // helps reduce the amount of line counting we need to do.
3219 // (this will cause a mis-reporting of modified status
3220 // once every MAXINT editing operations.)
3222 // it would be nice to do a similar optimization here -- if
3223 // we haven't done a motion that could have changed which line
3224 // we're on, then we shouldn't have to do this count_lines()
3225 cur = count_lines(text, dot);
3227 // count_lines() is expensive.
3228 // Call it only if something was changed since last time
3230 if (modified_count != last_modified_count) {
3231 tot = cur + count_lines(dot, end - 1) - 1;
3232 last_modified_count = modified_count;
3235 // current line percent
3236 // ------------- ~~ ----------
3239 percent = (100 * cur) / tot;
3245 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3246 columns : STATUS_BUFFER_LEN-1;
3248 ret = snprintf(status_buffer, trunc_at+1,
3249 #if ENABLE_FEATURE_VI_READONLY
3250 "%c %s%s%s %d/%d %d%%",
3252 "%c %s%s %d/%d %d%%",
3254 cmd_mode_indicator[cmd_mode & 3],
3255 (current_filename != NULL ? current_filename : "No file"),
3256 #if ENABLE_FEATURE_VI_READONLY
3257 (readonly_mode ? " [Readonly]" : ""),
3259 (modified_count ? " [Modified]" : ""),
3262 if (ret >= 0 && ret < trunc_at)
3263 return ret; /* it all fit */
3265 return trunc_at; /* had to truncate */
3269 //----- Force refresh of all Lines -----------------------------
3270 static void redraw(int full_screen)
3274 screen_erase(); // erase the internal screen buffer
3275 last_status_cksum = 0; // force status update
3276 refresh(full_screen); // this will redraw the entire display
3280 //----- Format a text[] line into a buffer ---------------------
3281 static char* format_line(char *src /*, int li*/)
3286 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3288 c = '~'; // char in col 0 in non-existent lines is '~'
3290 while (co < columns + tabstop) {
3291 // have we gone past the end?
3296 if ((c & 0x80) && !Isprint(c)) {
3299 if (c < ' ' || c == 0x7f) {
3303 while ((co % tabstop) != (tabstop - 1)) {
3311 c += '@'; // Ctrl-X -> 'X'
3316 // discard scrolled-off-to-the-left portion,
3317 // in tabstop-sized pieces
3318 if (ofs >= tabstop && co >= tabstop) {
3319 memmove(dest, dest + tabstop, co);
3326 // check "short line, gigantic offset" case
3329 // discard last scrolled off part
3332 // fill the rest with spaces
3334 memset(&dest[co], ' ', columns - co);
3338 //----- Refresh the changed screen lines -----------------------
3339 // Copy the source line from text[] into the buffer and note
3340 // if the current screenline is different from the new buffer.
3341 // If they differ then that line needs redrawing on the terminal.
3343 static void refresh(int full_screen)
3345 #define old_offset refresh__old_offset
3348 char *tp, *sp; // pointer into text[] and screen[]
3350 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3351 unsigned c = columns, r = rows;
3352 query_screen_dimensions();
3353 full_screen |= (c - columns) | (r - rows);
3355 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3356 tp = screenbegin; // index into text[] of top line
3358 // compare text[] to screen[] and mark screen[] lines that need updating
3359 for (li = 0; li < rows - 1; li++) {
3360 int cs, ce; // column start & end
3362 // format current text line
3363 out_buf = format_line(tp /*, li*/);
3365 // skip to the end of the current text[] line
3367 char *t = memchr(tp, '\n', end - tp);
3368 if (!t) t = end - 1;
3372 // see if there are any changes between virtual screen and out_buf
3373 changed = FALSE; // assume no change
3376 sp = &screen[li * columns]; // start of screen line
3378 // force re-draw of every single column from 0 - columns-1
3381 // compare newly formatted buffer with virtual screen
3382 // look forward for first difference between buf and screen
3383 for (; cs <= ce; cs++) {
3384 if (out_buf[cs] != sp[cs]) {
3385 changed = TRUE; // mark for redraw
3390 // look backward for last difference between out_buf and screen
3391 for (; ce >= cs; ce--) {
3392 if (out_buf[ce] != sp[ce]) {
3393 changed = TRUE; // mark for redraw
3397 // now, cs is index of first diff, and ce is index of last diff
3399 // if horz offset has changed, force a redraw
3400 if (offset != old_offset) {
3405 // make a sanity check of columns indexes
3407 if (ce > columns - 1) ce = columns - 1;
3408 if (cs > ce) { cs = 0; ce = columns - 1; }
3409 // is there a change between virtual screen and out_buf
3411 // copy changed part of buffer to virtual screen
3412 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3413 place_cursor(li, cs);
3414 // write line out to terminal
3415 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3419 place_cursor(crow, ccol);
3421 old_offset = offset;
3425 //---------------------------------------------------------------------
3426 //----- the Ascii Chart -----------------------------------------------
3428 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3429 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3430 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3431 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3432 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3433 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3434 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3435 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3436 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3437 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3438 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3439 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3440 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3441 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3442 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3443 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3444 //---------------------------------------------------------------------
3446 //----- Execute a Vi Command -----------------------------------
3447 static void do_cmd(int c)
3449 char *p, *q, *save_dot;
3455 // c1 = c; // quiet the compiler
3456 // cnt = yf = 0; // quiet the compiler
3457 // p = q = save_dot = buf; // quiet the compiler
3458 memset(buf, '\0', sizeof(buf));
3462 /* if this is a cursor key, skip these checks */
3470 case KEYCODE_PAGEUP:
3471 case KEYCODE_PAGEDOWN:
3472 case KEYCODE_DELETE:
3476 if (cmd_mode == 2) {
3477 // flip-flop Insert/Replace mode
3478 if (c == KEYCODE_INSERT)
3480 // we are 'R'eplacing the current *dot with new char
3482 // don't Replace past E-o-l
3483 cmd_mode = 1; // convert to insert
3484 undo_queue_commit();
3486 if (1 <= c || Isprint(c)) {
3488 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3489 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3494 if (cmd_mode == 1) {
3495 // hitting "Insert" twice means "R" replace mode
3496 if (c == KEYCODE_INSERT) goto dc5;
3497 // insert the char c at "dot"
3498 if (1 <= c || Isprint(c)) {
3499 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3514 #if ENABLE_FEATURE_VI_CRASHME
3515 case 0x14: // dc4 ctrl-T
3516 crashme = (crashme == 0) ? 1 : 0;
3546 default: // unrecognized command
3549 not_implemented(buf);
3550 end_cmd_q(); // stop adding to q
3551 case 0x00: // nul- ignore
3553 case 2: // ctrl-B scroll up full screen
3554 case KEYCODE_PAGEUP: // Cursor Key Page Up
3555 dot_scroll(rows - 2, -1);
3557 case 4: // ctrl-D scroll down half screen
3558 dot_scroll((rows - 2) / 2, 1);
3560 case 5: // ctrl-E scroll down one line
3563 case 6: // ctrl-F scroll down full screen
3564 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3565 dot_scroll(rows - 2, 1);
3567 case 7: // ctrl-G show current status
3568 last_status_cksum = 0; // force status update
3570 case 'h': // h- move left
3571 case KEYCODE_LEFT: // cursor key Left
3572 case 8: // ctrl-H- move left (This may be ERASE char)
3573 case 0x7f: // DEL- move left (This may be ERASE char)
3576 } while (--cmdcnt > 0);
3578 case 10: // Newline ^J
3579 case 'j': // j- goto next line, same col
3580 case KEYCODE_DOWN: // cursor key Down
3582 dot_next(); // go to next B-o-l
3583 // try stay in same col
3584 dot = move_to_col(dot, ccol + offset);
3585 } while (--cmdcnt > 0);
3587 case 12: // ctrl-L force redraw whole screen
3588 case 18: // ctrl-R force redraw
3591 //mysleep(10); // why???
3592 screen_erase(); // erase the internal screen buffer
3593 last_status_cksum = 0; // force status update
3594 refresh(TRUE); // this will redraw the entire display
3596 case 13: // Carriage Return ^M
3597 case '+': // +- goto next line
3601 } while (--cmdcnt > 0);
3603 case 21: // ctrl-U scroll up half screen
3604 dot_scroll((rows - 2) / 2, -1);
3606 case 25: // ctrl-Y scroll up one line
3612 cmd_mode = 0; // stop insrting
3613 undo_queue_commit();
3615 last_status_cksum = 0; // force status update
3617 case ' ': // move right
3618 case 'l': // move right
3619 case KEYCODE_RIGHT: // Cursor Key Right
3622 } while (--cmdcnt > 0);
3624 #if ENABLE_FEATURE_VI_YANKMARK
3625 case '"': // "- name a register to use for Delete/Yank
3626 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3627 if ((unsigned)c1 <= 25) { // a-z?
3633 case '\'': // '- goto a specific mark
3634 c1 = (get_one_char() | 0x20);
3635 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3639 if (text <= q && q < end) {
3641 dot_begin(); // go to B-o-l
3644 } else if (c1 == '\'') { // goto previous context
3645 dot = swap_context(dot); // swap current and previous context
3646 dot_begin(); // go to B-o-l
3652 case 'm': // m- Mark a line
3653 // this is really stupid. If there are any inserts or deletes
3654 // between text[0] and dot then this mark will not point to the
3655 // correct location! It could be off by many lines!
3656 // Well..., at least its quick and dirty.
3657 c1 = (get_one_char() | 0x20) - 'a';
3658 if ((unsigned)c1 <= 25) { // a-z?
3659 // remember the line
3665 case 'P': // P- Put register before
3666 case 'p': // p- put register after
3669 status_line_bold("Nothing in register %c", what_reg());
3672 // are we putting whole lines or strings
3673 if (strchr(p, '\n') != NULL) {
3675 dot_begin(); // putting lines- Put above
3678 // are we putting after very last line?
3679 if (end_line(dot) == (end - 1)) {
3680 dot = end; // force dot to end of text[]
3682 dot_next(); // next line, then put before
3687 dot_right(); // move to right, can move to NL
3689 string_insert(dot, p, ALLOW_UNDO); // insert the string
3690 end_cmd_q(); // stop adding to q
3692 case 'U': // U- Undo; replace current line with original version
3693 if (reg[Ureg] != NULL) {
3694 p = begin_line(dot);
3696 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3697 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3702 #endif /* FEATURE_VI_YANKMARK */
3703 #if ENABLE_FEATURE_VI_UNDO
3704 case 'u': // u- undo last operation
3708 case '$': // $- goto end of line
3709 case KEYCODE_END: // Cursor Key End
3711 dot = end_line(dot);
3717 case '%': // %- find matching char of pair () [] {}
3718 for (q = dot; q < end && *q != '\n'; q++) {
3719 if (strchr("()[]{}", *q) != NULL) {
3720 // we found half of a pair
3721 p = find_pair(q, *q);
3733 case 'f': // f- forward to a user specified char
3734 last_forward_char = get_one_char(); // get the search char
3736 // dont separate these two commands. 'f' depends on ';'
3738 //**** fall through to ... ';'
3739 case ';': // ;- look at rest of line for last forward char
3741 if (last_forward_char == 0)
3744 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3747 if (*q == last_forward_char)
3749 } while (--cmdcnt > 0);
3751 case ',': // repeat latest 'f' in opposite direction
3752 if (last_forward_char == 0)
3756 while (q >= text && *q != '\n' && *q != last_forward_char) {
3759 if (q >= text && *q == last_forward_char)
3761 } while (--cmdcnt > 0);
3764 case '-': // -- goto prev line
3768 } while (--cmdcnt > 0);
3770 #if ENABLE_FEATURE_VI_DOT_CMD
3771 case '.': // .- repeat the last modifying command
3772 // Stuff the last_modifying_cmd back into stdin
3773 // and let it be re-executed.
3775 last_modifying_cmd[lmc_len] = 0;
3776 ioq = ioq_start = xstrdup(last_modifying_cmd);
3780 #if ENABLE_FEATURE_VI_SEARCH
3781 case '?': // /- search for a pattern
3782 case '/': // /- search for a pattern
3785 q = get_input_line(buf); // get input line- use "status line"
3786 if (q[0] && !q[1]) {
3787 if (last_search_pattern[0])
3788 last_search_pattern[0] = c;
3789 goto dc3; // if no pat re-use old pat
3791 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3792 // there is a new pat
3793 free(last_search_pattern);
3794 last_search_pattern = xstrdup(q);
3795 goto dc3; // now find the pattern
3797 // user changed mind and erased the "/"- do nothing
3799 case 'N': // N- backward search for last pattern
3800 dir = BACK; // assume BACKWARD search
3802 if (last_search_pattern[0] == '?') {
3806 goto dc4; // now search for pattern
3808 case 'n': // n- repeat search for last pattern
3809 // search rest of text[] starting at next char
3810 // if search fails return orignal "p" not the "p+1" address
3814 dir = FORWARD; // assume FORWARD search
3816 if (last_search_pattern[0] == '?') {
3821 q = char_search(p, last_search_pattern + 1, dir, FULL);
3823 dot = q; // good search, update "dot"
3827 // no pattern found between "dot" and "end"- continue at top
3832 q = char_search(p, last_search_pattern + 1, dir, FULL);
3833 if (q != NULL) { // found something
3834 dot = q; // found new pattern- goto it
3835 msg = "search hit BOTTOM, continuing at TOP";
3837 msg = "search hit TOP, continuing at BOTTOM";
3840 msg = "Pattern not found";
3844 status_line_bold("%s", msg);
3845 } while (--cmdcnt > 0);
3847 case '{': // {- move backward paragraph
3848 q = char_search(dot, "\n\n", BACK, FULL);
3849 if (q != NULL) { // found blank line
3850 dot = next_line(q); // move to next blank line
3853 case '}': // }- move forward paragraph
3854 q = char_search(dot, "\n\n", FORWARD, FULL);
3855 if (q != NULL) { // found blank line
3856 dot = next_line(q); // move to next blank line
3859 #endif /* FEATURE_VI_SEARCH */
3860 case '0': // 0- goto beginning of line
3870 if (c == '0' && cmdcnt < 1) {
3871 dot_begin(); // this was a standalone zero
3873 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3876 case ':': // :- the colon mode commands
3877 p = get_input_line(":"); // get input line- use "status line"
3878 colon(p); // execute the command
3880 case '<': // <- Left shift something
3881 case '>': // >- Right shift something
3882 cnt = count_lines(text, dot); // remember what line we are on
3883 c1 = get_one_char(); // get the type of thing to delete
3884 find_range(&p, &q, c1);
3885 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3888 i = count_lines(p, q); // # of lines we are shifting
3889 for ( ; i > 0; i--, p = next_line(p)) {
3891 // shift left- remove tab or 8 spaces
3893 // shrink buffer 1 char
3894 text_hole_delete(p, p, NO_UNDO);
3895 } else if (*p == ' ') {
3896 // we should be calculating columns, not just SPACE
3897 for (j = 0; *p == ' ' && j < tabstop; j++) {
3898 text_hole_delete(p, p, NO_UNDO);
3901 } else if (c == '>') {
3902 // shift right -- add tab or 8 spaces
3903 char_insert(p, '\t', ALLOW_UNDO);
3906 dot = find_line(cnt); // what line were we on
3908 end_cmd_q(); // stop adding to q
3910 case 'A': // A- append at e-o-l
3911 dot_end(); // go to e-o-l
3912 //**** fall through to ... 'a'
3913 case 'a': // a- append after current char
3918 case 'B': // B- back a blank-delimited Word
3919 case 'E': // E- end of a blank-delimited word
3920 case 'W': // W- forward a blank-delimited word
3925 if (c == 'W' || isspace(dot[dir])) {
3926 dot = skip_thing(dot, 1, dir, S_TO_WS);
3927 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3930 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3931 } while (--cmdcnt > 0);
3933 case 'C': // C- Change to e-o-l
3934 case 'D': // D- delete to e-o-l
3936 dot = dollar_line(dot); // move to before NL
3937 // copy text into a register and delete
3938 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3940 goto dc_i; // start inserting
3941 #if ENABLE_FEATURE_VI_DOT_CMD
3943 end_cmd_q(); // stop adding to q
3946 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3947 c1 = get_one_char();
3950 // c1 < 0 if the key was special. Try "g<up-arrow>"
3951 // TODO: if Unicode?
3952 buf[1] = (c1 >= 0 ? c1 : '*');
3954 not_implemented(buf);
3960 case 'G': // G- goto to a line number (default= E-O-F)
3961 dot = end - 1; // assume E-O-F
3963 dot = find_line(cmdcnt); // what line is #cmdcnt
3967 case 'H': // H- goto top line on screen
3969 if (cmdcnt > (rows - 1)) {
3970 cmdcnt = (rows - 1);
3977 case 'I': // I- insert before first non-blank
3980 //**** fall through to ... 'i'
3981 case 'i': // i- insert before current char
3982 case KEYCODE_INSERT: // Cursor Key Insert
3984 cmd_mode = 1; // start inserting
3985 undo_queue_commit(); // commit queue when cmd_mode changes
3987 case 'J': // J- join current and next lines together
3989 dot_end(); // move to NL
3990 if (dot < end - 1) { // make sure not last char in text[]
3991 #if ENABLE_FEATURE_VI_UNDO
3992 undo_push(dot, 1, UNDO_DEL);
3993 *dot++ = ' '; // replace NL with space
3994 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3999 while (isblank(*dot)) { // delete leading WS
4000 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
4003 } while (--cmdcnt > 0);
4004 end_cmd_q(); // stop adding to q
4006 case 'L': // L- goto bottom line on screen
4008 if (cmdcnt > (rows - 1)) {
4009 cmdcnt = (rows - 1);
4017 case 'M': // M- goto middle line on screen
4019 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
4020 dot = next_line(dot);
4022 case 'O': // O- open a empty line above
4024 p = begin_line(dot);
4025 if (p[-1] == '\n') {
4027 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
4029 dot = char_insert(dot, '\n', ALLOW_UNDO);
4032 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
4037 case 'R': // R- continuous Replace char
4040 undo_queue_commit();
4042 case KEYCODE_DELETE:
4044 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
4046 case 'X': // X- delete char before dot
4047 case 'x': // x- delete the current char
4048 case 's': // s- substitute the current char
4053 if (dot[dir] != '\n') {
4055 dot--; // delete prev char
4056 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
4058 } while (--cmdcnt > 0);
4059 end_cmd_q(); // stop adding to q
4061 goto dc_i; // start inserting
4063 case 'Z': // Z- if modified, {write}; exit
4064 // ZZ means to save file (if necessary), then exit
4065 c1 = get_one_char();
4070 if (modified_count) {
4071 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
4072 status_line_bold("'%s' is read only", current_filename);
4075 cnt = file_write(current_filename, text, end - 1);
4078 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
4079 } else if (cnt == (end - 1 - text + 1)) {
4086 case '^': // ^- move to first non-blank on line
4090 case 'b': // b- back a word
4091 case 'e': // e- end of word
4096 if ((dot + dir) < text || (dot + dir) > end - 1)
4099 if (isspace(*dot)) {
4100 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4102 if (isalnum(*dot) || *dot == '_') {
4103 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4104 } else if (ispunct(*dot)) {
4105 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4107 } while (--cmdcnt > 0);
4109 case 'c': // c- change something
4110 case 'd': // d- delete something
4111 #if ENABLE_FEATURE_VI_YANKMARK
4112 case 'y': // y- yank something
4113 case 'Y': // Y- Yank a line
4116 int yf, ml, whole = 0;
4117 yf = YANKDEL; // assume either "c" or "d"
4118 #if ENABLE_FEATURE_VI_YANKMARK
4119 if (c == 'y' || c == 'Y')
4124 c1 = get_one_char(); // get the type of thing to delete
4125 // determine range, and whether it spans lines
4126 ml = find_range(&p, &q, c1);
4128 if (c1 == 27) { // ESC- user changed mind and wants out
4129 c = c1 = 27; // Escape- do nothing
4130 } else if (strchr("wW", c1)) {
4132 // don't include trailing WS as part of word
4133 while (isblank(*q)) {
4134 if (q <= text || q[-1] == '\n')
4139 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4140 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
4141 // partial line copy text into a register and delete
4142 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4143 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
4144 // whole line copy text into a register and delete
4145 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
4148 // could not recognize object
4149 c = c1 = 27; // error-
4155 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4156 // on the last line of file don't move to prev line
4157 if (whole && dot != (end-1)) {
4160 } else if (c == 'd') {
4166 // if CHANGING, not deleting, start inserting after the delete
4168 strcpy(buf, "Change");
4169 goto dc_i; // start inserting
4172 strcpy(buf, "Delete");
4174 #if ENABLE_FEATURE_VI_YANKMARK
4175 if (c == 'y' || c == 'Y') {
4176 strcpy(buf, "Yank");
4180 for (cnt = 0; p <= q; p++) {
4184 status_line("%s %d lines (%d chars) using [%c]",
4185 buf, cnt, strlen(reg[YDreg]), what_reg());
4187 end_cmd_q(); // stop adding to q
4191 case 'k': // k- goto prev line, same col
4192 case KEYCODE_UP: // cursor key Up
4195 dot = move_to_col(dot, ccol + offset); // try stay in same col
4196 } while (--cmdcnt > 0);
4198 case 'r': // r- replace the current char with user input
4199 c1 = get_one_char(); // get the replacement char
4201 #if ENABLE_FEATURE_VI_UNDO
4202 undo_push(dot, 1, UNDO_DEL);
4204 undo_push(dot, 1, UNDO_INS_CHAIN);
4210 end_cmd_q(); // stop adding to q
4212 case 't': // t- move to char prior to next x
4213 last_forward_char = get_one_char();
4215 if (*dot == last_forward_char)
4217 last_forward_char = 0;
4219 case 'w': // w- forward a word
4221 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4222 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4223 } else if (ispunct(*dot)) { // we are on PUNCT
4224 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4227 dot++; // move over word
4228 if (isspace(*dot)) {
4229 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4231 } while (--cmdcnt > 0);
4234 c1 = get_one_char(); // get the replacement char
4237 cnt = (rows - 2) / 2; // put dot at center
4239 cnt = rows - 2; // put dot at bottom
4240 screenbegin = begin_line(dot); // start dot at top
4241 dot_scroll(cnt, -1);
4243 case '|': // |- move to column "cmdcnt"
4244 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4246 case '~': // ~- flip the case of letters a-z -> A-Z
4248 #if ENABLE_FEATURE_VI_UNDO
4249 if (islower(*dot)) {
4250 undo_push(dot, 1, UNDO_DEL);
4251 *dot = toupper(*dot);
4252 undo_push(dot, 1, UNDO_INS_CHAIN);
4253 } else if (isupper(*dot)) {
4254 undo_push(dot, 1, UNDO_DEL);
4255 *dot = tolower(*dot);
4256 undo_push(dot, 1, UNDO_INS_CHAIN);
4259 if (islower(*dot)) {
4260 *dot = toupper(*dot);
4262 } else if (isupper(*dot)) {
4263 *dot = tolower(*dot);
4268 } while (--cmdcnt > 0);
4269 end_cmd_q(); // stop adding to q
4271 //----- The Cursor and Function Keys -----------------------------
4272 case KEYCODE_HOME: // Cursor Key Home
4275 // The Fn keys could point to do_macro which could translate them
4277 case KEYCODE_FUN1: // Function Key F1
4278 case KEYCODE_FUN2: // Function Key F2
4279 case KEYCODE_FUN3: // Function Key F3
4280 case KEYCODE_FUN4: // Function Key F4
4281 case KEYCODE_FUN5: // Function Key F5
4282 case KEYCODE_FUN6: // Function Key F6
4283 case KEYCODE_FUN7: // Function Key F7
4284 case KEYCODE_FUN8: // Function Key F8
4285 case KEYCODE_FUN9: // Function Key F9
4286 case KEYCODE_FUN10: // Function Key F10
4287 case KEYCODE_FUN11: // Function Key F11
4288 case KEYCODE_FUN12: // Function Key F12
4294 // if text[] just became empty, add back an empty line
4296 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4299 // it is OK for dot to exactly equal to end, otherwise check dot validity
4301 dot = bound_dot(dot); // make sure "dot" is valid
4303 #if ENABLE_FEATURE_VI_YANKMARK
4304 check_context(c); // update the current context
4308 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4309 cnt = dot - begin_line(dot);
4310 // Try to stay off of the Newline
4311 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4315 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4316 #if ENABLE_FEATURE_VI_CRASHME
4317 static int totalcmds = 0;
4318 static int Mp = 85; // Movement command Probability
4319 static int Np = 90; // Non-movement command Probability
4320 static int Dp = 96; // Delete command Probability
4321 static int Ip = 97; // Insert command Probability
4322 static int Yp = 98; // Yank command Probability
4323 static int Pp = 99; // Put command Probability
4324 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4325 static const char chars[20] = "\t012345 abcdABCD-=.$";
4326 static const char *const words[20] = {
4327 "this", "is", "a", "test",
4328 "broadcast", "the", "emergency", "of",
4329 "system", "quick", "brown", "fox",
4330 "jumped", "over", "lazy", "dogs",
4331 "back", "January", "Febuary", "March"
4333 static const char *const lines[20] = {
4334 "You should have received a copy of the GNU General Public License\n",
4335 "char c, cm, *cmd, *cmd1;\n",
4336 "generate a command by percentages\n",
4337 "Numbers may be typed as a prefix to some commands.\n",
4338 "Quit, discarding changes!\n",
4339 "Forced write, if permission originally not valid.\n",
4340 "In general, any ex or ed command (such as substitute or delete).\n",
4341 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4342 "Please get w/ me and I will go over it with you.\n",
4343 "The following is a list of scheduled, committed changes.\n",
4344 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4345 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4346 "Any question about transactions please contact Sterling Huxley.\n",
4347 "I will try to get back to you by Friday, December 31.\n",
4348 "This Change will be implemented on Friday.\n",
4349 "Let me know if you have problems accessing this;\n",
4350 "Sterling Huxley recently added you to the access list.\n",
4351 "Would you like to go to lunch?\n",
4352 "The last command will be automatically run.\n",
4353 "This is too much english for a computer geek.\n",
4355 static char *multilines[20] = {
4356 "You should have received a copy of the GNU General Public License\n",
4357 "char c, cm, *cmd, *cmd1;\n",
4358 "generate a command by percentages\n",
4359 "Numbers may be typed as a prefix to some commands.\n",
4360 "Quit, discarding changes!\n",
4361 "Forced write, if permission originally not valid.\n",
4362 "In general, any ex or ed command (such as substitute or delete).\n",
4363 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4364 "Please get w/ me and I will go over it with you.\n",
4365 "The following is a list of scheduled, committed changes.\n",
4366 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4367 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4368 "Any question about transactions please contact Sterling Huxley.\n",
4369 "I will try to get back to you by Friday, December 31.\n",
4370 "This Change will be implemented on Friday.\n",
4371 "Let me know if you have problems accessing this;\n",
4372 "Sterling Huxley recently added you to the access list.\n",
4373 "Would you like to go to lunch?\n",
4374 "The last command will be automatically run.\n",
4375 "This is too much english for a computer geek.\n",
4378 // create a random command to execute
4379 static void crash_dummy()
4381 static int sleeptime; // how long to pause between commands
4382 char c, cm, *cmd, *cmd1;
4383 int i, cnt, thing, rbi, startrbi, percent;
4385 // "dot" movement commands
4386 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4388 // is there already a command running?
4389 if (readbuffer[0] > 0)
4392 readbuffer[0] = 'X';
4394 sleeptime = 0; // how long to pause between commands
4395 memset(readbuffer, '\0', sizeof(readbuffer));
4396 // generate a command by percentages
4397 percent = (int) lrand48() % 100; // get a number from 0-99
4398 if (percent < Mp) { // Movement commands
4399 // available commands
4402 } else if (percent < Np) { // non-movement commands
4403 cmd = "mz<>\'\""; // available commands
4405 } else if (percent < Dp) { // Delete commands
4406 cmd = "dx"; // available commands
4408 } else if (percent < Ip) { // Inset commands
4409 cmd = "iIaAsrJ"; // available commands
4411 } else if (percent < Yp) { // Yank commands
4412 cmd = "yY"; // available commands
4414 } else if (percent < Pp) { // Put commands
4415 cmd = "pP"; // available commands
4418 // We do not know how to handle this command, try again
4422 // randomly pick one of the available cmds from "cmd[]"
4423 i = (int) lrand48() % strlen(cmd);
4425 if (strchr(":\024", cm))
4426 goto cd0; // dont allow colon or ctrl-T commands
4427 readbuffer[rbi++] = cm; // put cmd into input buffer
4429 // now we have the command-
4430 // there are 1, 2, and multi char commands
4431 // find out which and generate the rest of command as necessary
4432 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4433 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4434 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4435 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4437 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4439 readbuffer[rbi++] = c; // add movement to input buffer
4441 if (strchr("iIaAsc", cm)) { // multi-char commands
4443 // change some thing
4444 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4446 readbuffer[rbi++] = c; // add movement to input buffer
4448 thing = (int) lrand48() % 4; // what thing to insert
4449 cnt = (int) lrand48() % 10; // how many to insert
4450 for (i = 0; i < cnt; i++) {
4451 if (thing == 0) { // insert chars
4452 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4453 } else if (thing == 1) { // insert words
4454 strcat(readbuffer, words[(int) lrand48() % 20]);
4455 strcat(readbuffer, " ");
4456 sleeptime = 0; // how fast to type
4457 } else if (thing == 2) { // insert lines
4458 strcat(readbuffer, lines[(int) lrand48() % 20]);
4459 sleeptime = 0; // how fast to type
4460 } else { // insert multi-lines
4461 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4462 sleeptime = 0; // how fast to type
4465 strcat(readbuffer, ESC);
4467 readbuffer[0] = strlen(readbuffer + 1);
4471 mysleep(sleeptime); // sleep 1/100 sec
4474 // test to see if there are any errors
4475 static void crash_test()
4477 static time_t oldtim;
4484 strcat(msg, "end<text ");
4486 if (end > textend) {
4487 strcat(msg, "end>textend ");
4490 strcat(msg, "dot<text ");
4493 strcat(msg, "dot>end ");
4495 if (screenbegin < text) {
4496 strcat(msg, "screenbegin<text ");
4498 if (screenbegin > end - 1) {
4499 strcat(msg, "screenbegin>end-1 ");
4503 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4504 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4506 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4507 if (d[0] == '\n' || d[0] == '\r')
4512 if (tim >= (oldtim + 3)) {
4513 sprintf(status_buffer,
4514 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4515 totalcmds, M, N, I, D, Y, P, U, end - text + 1);