1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
16 * if mark[] values were line numbers rather than pointers
17 * it would be easier to change the mark when add/delete lines
18 * More intelligence in refresh()
19 * ":r !cmd" and "!cmd" to filter text through an external command
20 * An "ex" line oriented mode- maybe using "cmdedit"
27 //config: 'vi' is a text editor. More specifically, it is the One True
28 //config: text editor <grin>. It does, however, have a rather steep
29 //config: learning curve. If you are not already comfortable with 'vi'
30 //config: you may wish to use something else.
32 //config:config FEATURE_VI_MAX_LEN
33 //config: int "Maximum screen width in vi"
34 //config: range 256 16384
35 //config: default 4096
36 //config: depends on VI
38 //config: Contrary to what you may think, this is not eating much.
39 //config: Make it smaller than 4k only if you are very limited on memory.
41 //config:config FEATURE_VI_8BIT
42 //config: bool "Allow vi to display 8-bit chars (otherwise shows dots)"
44 //config: depends on VI
46 //config: If your terminal can display characters with high bit set,
47 //config: you may want to enable this. Note: vi is not Unicode-capable.
48 //config: If your terminal combines several 8-bit bytes into one character
49 //config: (as in Unicode mode), this will not work properly.
51 //config:config FEATURE_VI_COLON
52 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
54 //config: depends on VI
56 //config: Enable a limited set of colon commands for vi. This does not
57 //config: provide an "ex" mode.
59 //config:config FEATURE_VI_YANKMARK
60 //config: bool "Enable yank/put commands and mark cmds"
62 //config: depends on VI
64 //config: This will enable you to use yank and put, as well as mark in
67 //config:config FEATURE_VI_SEARCH
68 //config: bool "Enable search and replace cmds"
70 //config: depends on VI
72 //config: Select this if you wish to be able to do search and replace in
75 //config:config FEATURE_VI_REGEX_SEARCH
76 //config: bool "Enable regex in search and replace"
77 //config: default n # Uses GNU regex, which may be unavailable. FIXME
78 //config: depends on FEATURE_VI_SEARCH
80 //config: Use extended regex search.
82 //config:config FEATURE_VI_USE_SIGNALS
83 //config: bool "Catch signals"
85 //config: depends on VI
87 //config: Selecting this option will make busybox vi signal aware. This will
88 //config: make busybox vi support SIGWINCH to deal with Window Changes, catch
89 //config: Ctrl-Z and Ctrl-C and alarms.
91 //config:config FEATURE_VI_DOT_CMD
92 //config: bool "Remember previous cmd and \".\" cmd"
94 //config: depends on VI
96 //config: Make busybox vi remember the last command and be able to repeat it.
98 //config:config FEATURE_VI_READONLY
99 //config: bool "Enable -R option and \"view\" mode"
101 //config: depends on VI
103 //config: Enable the read-only command line option, which allows the user to
104 //config: open a file in read-only mode.
106 //config:config FEATURE_VI_SETOPTS
107 //config: bool "Enable set-able options, ai ic showmatch"
109 //config: depends on VI
111 //config: Enable the editor to set some (ai, ic, showmatch) options.
113 //config:config FEATURE_VI_SET
114 //config: bool "Support for :set"
116 //config: depends on VI
118 //config: Support for ":set".
120 //config:config FEATURE_VI_WIN_RESIZE
121 //config: bool "Handle window resize"
123 //config: depends on VI
125 //config: Make busybox vi behave nicely with terminals that get resized.
127 //config:config FEATURE_VI_ASK_TERMINAL
128 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
130 //config: depends on VI
132 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
133 //config: this option makes vi perform a last-ditch effort to find it:
134 //config: position cursor to 999,999 and ask terminal to report real
135 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
137 //config: This is not clean but helps a lot on serial lines and such.
138 //config:config FEATURE_VI_UNDO
139 //config: bool "Support undo command 'u'"
141 //config: depends on VI
143 //config: Support the 'u' command to undo insertion, deletion, and replacement
145 //config:config FEATURE_VI_UNDO_QUEUE
146 //config: bool "Enable undo operation queuing"
148 //config: depends on FEATURE_VI_UNDO
150 //config: The vi undo functions can use an intermediate queue to greatly lower
151 //config: malloc() calls and overhead. When the maximum size of this queue is
152 //config: reached, the contents of the queue are committed to the undo stack.
153 //config: This increases the size of the undo code and allows some undo
154 //config: operations (especially un-typing/backspacing) to be far more useful.
155 //config:config FEATURE_VI_UNDO_QUEUE_MAX
156 //config: int "Maximum undo character queue size"
157 //config: default 256
158 //config: range 32 65536
159 //config: depends on FEATURE_VI_UNDO_QUEUE
161 //config: This option sets the number of bytes used at runtime for the queue.
162 //config: Smaller values will create more undo objects and reduce the amount
163 //config: of typed or backspaced characters that are grouped into one undo
164 //config: operation; larger values increase the potential size of each undo
165 //config: and will generally malloc() larger objects and less frequently.
166 //config: Unless you want more (or less) frequent "undo points" while typing,
167 //config: you should probably leave this unchanged.
169 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
171 //kbuild:lib-$(CONFIG_VI) += vi.o
173 //usage:#define vi_trivial_usage
174 //usage: "[OPTIONS] [FILE]..."
175 //usage:#define vi_full_usage "\n\n"
176 //usage: "Edit FILE\n"
177 //usage: IF_FEATURE_VI_COLON(
178 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
180 //usage: IF_FEATURE_VI_READONLY(
181 //usage: "\n -R Read-only"
183 //usage: "\n -H List available features"
186 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
187 #if ENABLE_FEATURE_VI_REGEX_SEARCH
191 /* the CRASHME code is unmaintained, and doesn't currently build */
192 #define ENABLE_FEATURE_VI_CRASHME 0
195 #if ENABLE_LOCALE_SUPPORT
197 #if ENABLE_FEATURE_VI_8BIT
198 //FIXME: this does not work properly for Unicode anyway
199 # define Isprint(c) (isprint)(c)
201 # define Isprint(c) isprint_asciionly(c)
206 /* 0x9b is Meta-ESC */
207 #if ENABLE_FEATURE_VI_8BIT
208 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
210 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
217 MAX_TABSTOP = 32, // sanity limit
218 // User input len. Need not be extra big.
219 // Lines in file being edited *can* be bigger than this.
221 // Sanity limits. We have only one buffer of this size.
222 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
223 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
226 /* VT102 ESC sequences.
227 * See "Xterm Control Sequences"
228 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
230 /* Inverse/Normal text */
231 #define ESC_BOLD_TEXT "\033[7m"
232 #define ESC_NORM_TEXT "\033[0m"
234 #define ESC_BELL "\007"
235 /* Clear-to-end-of-line */
236 #define ESC_CLEAR2EOL "\033[K"
237 /* Clear-to-end-of-screen.
238 * (We use default param here.
239 * Full sequence is "ESC [ <num> J",
240 * <num> is 0/1/2 = "erase below/above/all".)
242 #define ESC_CLEAR2EOS "\033[J"
243 /* Cursor to given coordinate (1,1: top left) */
244 #define ESC_SET_CURSOR_POS "\033[%u;%uH"
246 ///* Cursor up and down */
247 //#define ESC_CURSOR_UP "\033[A"
248 //#define ESC_CURSOR_DOWN "\n"
250 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
251 // cmds modifying text[]
252 // vda: removed "aAiIs" as they switch us into insert mode
253 // and remembering input for replay after them makes no sense
254 static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
260 FORWARD = 1, // code depends on "1" for array index
261 BACK = -1, // code depends on "-1" for array index
262 LIMITED = 0, // how much of text[] in char_search
263 FULL = 1, // how much of text[] in char_search
265 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
266 S_TO_WS = 2, // used in skip_thing() for moving "dot"
267 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
268 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
269 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
273 /* vi.c expects chars to be unsigned. */
274 /* busybox build system provides that, but it's better */
275 /* to audit and fix the source */
278 /* many references - keep near the top of globals */
279 char *text, *end; // pointers to the user data in memory
280 char *dot; // where all the action takes place
281 int text_size; // size of the allocated buffer
285 #define VI_AUTOINDENT 1
286 #define VI_SHOWMATCH 2
287 #define VI_IGNORECASE 4
288 #define VI_ERR_METHOD 8
289 #define autoindent (vi_setops & VI_AUTOINDENT)
290 #define showmatch (vi_setops & VI_SHOWMATCH )
291 #define ignorecase (vi_setops & VI_IGNORECASE)
292 /* indicate error with beep or flash */
293 #define err_method (vi_setops & VI_ERR_METHOD)
295 #if ENABLE_FEATURE_VI_READONLY
296 smallint readonly_mode;
297 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
298 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
299 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
301 #define SET_READONLY_FILE(flags) ((void)0)
302 #define SET_READONLY_MODE(flags) ((void)0)
303 #define UNSET_READONLY_FILE(flags) ((void)0)
306 smallint editing; // >0 while we are editing a file
307 // [code audit says "can be 0, 1 or 2 only"]
308 smallint cmd_mode; // 0=command 1=insert 2=replace
309 int modified_count; // buffer contents changed if !0
310 int last_modified_count; // = -1;
311 int save_argc; // how many file names on cmd line
312 int cmdcnt; // repetition count
313 unsigned rows, columns; // the terminal screen is this size
314 #if ENABLE_FEATURE_VI_ASK_TERMINAL
315 int get_rowcol_error;
317 int crow, ccol; // cursor is on Crow x Ccol
318 int offset; // chars scrolled off the screen to the left
319 int have_status_msg; // is default edit status needed?
320 // [don't make smallint!]
321 int last_status_cksum; // hash of current status line
322 char *current_filename;
323 char *screenbegin; // index into text[], of top line on the screen
324 char *screen; // pointer to the virtual screen buffer
325 int screensize; // and its size
327 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
328 char erase_char; // the users erase character
329 char last_input_char; // last char read from user
331 #if ENABLE_FEATURE_VI_DOT_CMD
332 smallint adding2q; // are we currently adding user input to q
333 int lmc_len; // length of last_modifying_cmd
334 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
336 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
339 #if ENABLE_FEATURE_VI_SEARCH
340 char *last_search_pattern; // last pattern from a '/' or '?' search
344 #if ENABLE_FEATURE_VI_YANKMARK
345 char *edit_file__cur_line;
347 int refresh__old_offset;
348 int format_edit_status__tot;
350 /* a few references only */
351 #if ENABLE_FEATURE_VI_YANKMARK
352 int YDreg, Ureg; // default delete register and orig line for "U"
353 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
354 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
355 char *context_start, *context_end;
357 #if ENABLE_FEATURE_VI_USE_SIGNALS
358 sigjmp_buf restart; // catch_sig()
360 struct termios term_orig, term_vi; // remember what the cooked mode was
361 #if ENABLE_FEATURE_VI_COLON
362 char *initial_cmds[3]; // currently 2 entries, NULL terminated
364 // Should be just enough to hold a key sequence,
365 // but CRASHME mode uses it as generated command buffer too
366 #if ENABLE_FEATURE_VI_CRASHME
367 char readbuffer[128];
369 char readbuffer[KEYCODE_BUFFER_SIZE];
371 #define STATUS_BUFFER_LEN 200
372 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
373 #if ENABLE_FEATURE_VI_DOT_CMD
374 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
376 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
378 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
379 #if ENABLE_FEATURE_VI_UNDO
380 // undo_push() operations
383 #define UNDO_INS_CHAIN 2
384 #define UNDO_DEL_CHAIN 3
385 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
386 #define UNDO_QUEUED_FLAG 4
387 #define UNDO_INS_QUEUED 4
388 #define UNDO_DEL_QUEUED 5
389 #define UNDO_USE_SPOS 32
390 #define UNDO_EMPTY 64
391 // Pass-through flags for functions that can be undone
394 #define ALLOW_UNDO_CHAIN 2
395 # if ENABLE_FEATURE_VI_UNDO_QUEUE
396 #define ALLOW_UNDO_QUEUED 3
397 char undo_queue_state;
399 char *undo_queue_spos; // Start position of queued operation
400 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
402 // If undo queuing disabled, don't invoke the missing queue logic
403 #define ALLOW_UNDO_QUEUED 1
407 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
408 int start; // Offset where the data should be restored/deleted
409 int length; // total data size
410 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
411 char undo_text[1]; // text that was deleted (if deletion)
413 #endif /* ENABLE_FEATURE_VI_UNDO */
415 #define G (*ptr_to_globals)
416 #define text (G.text )
417 #define text_size (G.text_size )
422 #define vi_setops (G.vi_setops )
423 #define editing (G.editing )
424 #define cmd_mode (G.cmd_mode )
425 #define modified_count (G.modified_count )
426 #define last_modified_count (G.last_modified_count)
427 #define save_argc (G.save_argc )
428 #define cmdcnt (G.cmdcnt )
429 #define rows (G.rows )
430 #define columns (G.columns )
431 #define crow (G.crow )
432 #define ccol (G.ccol )
433 #define offset (G.offset )
434 #define status_buffer (G.status_buffer )
435 #define have_status_msg (G.have_status_msg )
436 #define last_status_cksum (G.last_status_cksum )
437 #define current_filename (G.current_filename )
438 #define screen (G.screen )
439 #define screensize (G.screensize )
440 #define screenbegin (G.screenbegin )
441 #define tabstop (G.tabstop )
442 #define last_forward_char (G.last_forward_char )
443 #define erase_char (G.erase_char )
444 #define last_input_char (G.last_input_char )
445 #if ENABLE_FEATURE_VI_READONLY
446 #define readonly_mode (G.readonly_mode )
448 #define readonly_mode 0
450 #define adding2q (G.adding2q )
451 #define lmc_len (G.lmc_len )
453 #define ioq_start (G.ioq_start )
454 #define my_pid (G.my_pid )
455 #define last_search_pattern (G.last_search_pattern)
457 #define edit_file__cur_line (G.edit_file__cur_line)
458 #define refresh__old_offset (G.refresh__old_offset)
459 #define format_edit_status__tot (G.format_edit_status__tot)
461 #define YDreg (G.YDreg )
462 #define Ureg (G.Ureg )
463 #define mark (G.mark )
464 #define context_start (G.context_start )
465 #define context_end (G.context_end )
466 #define restart (G.restart )
467 #define term_orig (G.term_orig )
468 #define term_vi (G.term_vi )
469 #define initial_cmds (G.initial_cmds )
470 #define readbuffer (G.readbuffer )
471 #define scr_out_buf (G.scr_out_buf )
472 #define last_modifying_cmd (G.last_modifying_cmd )
473 #define get_input_line__buf (G.get_input_line__buf)
475 #if ENABLE_FEATURE_VI_UNDO
476 #define undo_stack_tail (G.undo_stack_tail )
477 # if ENABLE_FEATURE_VI_UNDO_QUEUE
478 #define undo_queue_state (G.undo_queue_state)
479 #define undo_q (G.undo_q )
480 #define undo_queue (G.undo_queue )
481 #define undo_queue_spos (G.undo_queue_spos )
485 #define INIT_G() do { \
486 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
487 last_modified_count = -1; \
488 /* "" but has space for 2 chars: */ \
489 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
493 static void edit_file(char *); // edit one file
494 static void do_cmd(int); // execute a command
495 static int next_tabstop(int);
496 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
497 static char *begin_line(char *); // return pointer to cur line B-o-l
498 static char *end_line(char *); // return pointer to cur line E-o-l
499 static char *prev_line(char *); // return pointer to prev line B-o-l
500 static char *next_line(char *); // return pointer to next line B-o-l
501 static char *end_screen(void); // get pointer to last char on screen
502 static int count_lines(char *, char *); // count line from start to stop
503 static char *find_line(int); // find beginning of line #li
504 static char *move_to_col(char *, int); // move "p" to column l
505 static void dot_left(void); // move dot left- dont leave line
506 static void dot_right(void); // move dot right- dont leave line
507 static void dot_begin(void); // move dot to B-o-l
508 static void dot_end(void); // move dot to E-o-l
509 static void dot_next(void); // move dot to next line B-o-l
510 static void dot_prev(void); // move dot to prev line B-o-l
511 static void dot_scroll(int, int); // move the screen up or down
512 static void dot_skip_over_ws(void); // move dot pat WS
513 static char *bound_dot(char *); // make sure text[0] <= P < "end"
514 static char *new_screen(int, int); // malloc virtual screen memory
515 #if !ENABLE_FEATURE_VI_UNDO
516 #define char_insert(a,b,c) char_insert(a,b)
518 static char *char_insert(char *, char, int); // insert the char c at 'p'
519 // might reallocate text[]! use p += stupid_insert(p, ...),
520 // and be careful to not use pointers into potentially freed text[]!
521 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
522 static int find_range(char **, char **, char); // return pointers for an object
523 static int st_test(char *, int, int, char *); // helper for skip_thing()
524 static char *skip_thing(char *, int, int, int); // skip some object
525 static char *find_pair(char *, char); // find matching pair () [] {}
526 #if !ENABLE_FEATURE_VI_UNDO
527 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
529 static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
530 // might reallocate text[]! use p += text_hole_make(p, ...),
531 // and be careful to not use pointers into potentially freed text[]!
532 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
533 #if !ENABLE_FEATURE_VI_UNDO
534 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
536 static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
537 static void show_help(void); // display some help info
538 static void rawmode(void); // set "raw" mode on tty
539 static void cookmode(void); // return to "cooked" mode on tty
540 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
541 static int mysleep(int);
542 static int readit(void); // read (maybe cursor) key from stdin
543 static int get_one_char(void); // read 1 char from stdin
544 // file_insert might reallocate text[]!
545 static int file_insert(const char *, char *, int);
546 static int file_write(char *, char *, char *);
547 static void place_cursor(int, int);
548 static void screen_erase(void);
549 static void clear_to_eol(void);
550 static void clear_to_eos(void);
551 static void go_bottom_and_clear_to_eol(void);
552 static void standout_start(void); // send "start reverse video" sequence
553 static void standout_end(void); // send "end reverse video" sequence
554 static void flash(int); // flash the terminal screen
555 static void show_status_line(void); // put a message on the bottom line
556 static void status_line(const char *, ...); // print to status buf
557 static void status_line_bold(const char *, ...);
558 static void status_line_bold_errno(const char *fn);
559 static void not_implemented(const char *); // display "Not implemented" message
560 static int format_edit_status(void); // format file status on status line
561 static void redraw(int); // force a full screen refresh
562 static char* format_line(char* /*, int*/);
563 static void refresh(int); // update the terminal from screen[]
565 static void indicate_error(void); // use flash or beep to indicate error
566 static void Hit_Return(void);
568 #if ENABLE_FEATURE_VI_SEARCH
569 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
571 #if ENABLE_FEATURE_VI_COLON
572 static char *get_one_address(char *, int *); // get colon addr, if present
573 static char *get_address(char *, int *, int *); // get two colon addrs, if present
575 static void colon(char *); // execute the "colon" mode cmds
576 #if ENABLE_FEATURE_VI_USE_SIGNALS
577 static void winch_sig(int); // catch window size changes
578 static void suspend_sig(int); // catch ctrl-Z
579 static void catch_sig(int); // catch ctrl-C and alarm time-outs
581 #if ENABLE_FEATURE_VI_DOT_CMD
582 static void start_new_cmd_q(char); // new queue for command
583 static void end_cmd_q(void); // stop saving input chars
585 #define end_cmd_q() ((void)0)
587 #if ENABLE_FEATURE_VI_SETOPTS
588 static void showmatching(char *); // show the matching pair () [] {}
590 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
591 // might reallocate text[]! use p += string_insert(p, ...),
592 // and be careful to not use pointers into potentially freed text[]!
593 # if !ENABLE_FEATURE_VI_UNDO
594 #define string_insert(a,b,c) string_insert(a,b)
596 static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
598 #if ENABLE_FEATURE_VI_YANKMARK
599 static char *text_yank(char *, char *, int); // save copy of "p" into a register
600 static char what_reg(void); // what is letter of current YDreg
601 static void check_context(char); // remember context for '' command
603 #if ENABLE_FEATURE_VI_UNDO
604 static void flush_undo_data(void);
605 static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
606 static void undo_pop(void); // Undo the last operation
607 # if ENABLE_FEATURE_VI_UNDO_QUEUE
608 static void undo_queue_commit(void); // Flush any queued objects to the undo stack
610 # define undo_queue_commit() ((void)0)
613 #define flush_undo_data() ((void)0)
614 #define undo_queue_commit() ((void)0)
617 #if ENABLE_FEATURE_VI_CRASHME
618 static void crash_dummy();
619 static void crash_test();
620 static int crashme = 0;
623 static void write1(const char *out)
628 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
629 int vi_main(int argc, char **argv)
635 #if ENABLE_FEATURE_VI_UNDO
636 /* undo_stack_tail = NULL; - already is */
637 #if ENABLE_FEATURE_VI_UNDO_QUEUE
638 undo_queue_state = UNDO_EMPTY;
639 /* undo_q = 0; - already is */
643 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
646 #if ENABLE_FEATURE_VI_CRASHME
647 srand((long) my_pid);
649 #ifdef NO_SUCH_APPLET_YET
650 /* If we aren't "vi", we are "view" */
651 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
652 SET_READONLY_MODE(readonly_mode);
656 // autoindent is not default in vim 7.3
657 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
658 // 1- process $HOME/.exrc file (not inplemented yet)
659 // 2- process EXINIT variable from environment
660 // 3- process command line args
661 #if ENABLE_FEATURE_VI_COLON
663 char *p = getenv("EXINIT");
665 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
668 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
670 #if ENABLE_FEATURE_VI_CRASHME
675 #if ENABLE_FEATURE_VI_READONLY
676 case 'R': // Read-only flag
677 SET_READONLY_MODE(readonly_mode);
680 #if ENABLE_FEATURE_VI_COLON
681 case 'c': // cmd line vi command
683 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
695 // The argv array can be used by the ":next" and ":rewind" commands
699 //----- This is the main file handling loop --------------
702 // "Save cursor, use alternate screen buffer, clear screen"
703 write1("\033[?1049h");
705 edit_file(argv[optind]); /* param might be NULL */
706 if (++optind >= argc)
709 // "Use normal screen buffer, restore cursor"
710 write1("\033[?1049l");
711 //-----------------------------------------------------------
716 /* read text from file or create an empty buf */
717 /* will also update current_filename */
718 static int init_text_buffer(char *fn)
724 last_modified_count = -1;
725 #if ENABLE_FEATURE_VI_YANKMARK
727 memset(mark, 0, sizeof(mark));
730 /* allocate/reallocate text buffer */
733 screenbegin = dot = end = text = xzalloc(text_size);
735 if (fn != current_filename) {
736 free(current_filename);
737 current_filename = xstrdup(fn);
739 rc = file_insert(fn, text, 1);
741 // file doesnt exist. Start empty buf with dummy line
742 char_insert(text, '\n', NO_UNDO);
747 #if ENABLE_FEATURE_VI_WIN_RESIZE
748 static int query_screen_dimensions(void)
750 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
751 if (rows > MAX_SCR_ROWS)
753 if (columns > MAX_SCR_COLS)
754 columns = MAX_SCR_COLS;
758 # define query_screen_dimensions() (0)
761 static void edit_file(char *fn)
763 #if ENABLE_FEATURE_VI_YANKMARK
764 #define cur_line edit_file__cur_line
767 #if ENABLE_FEATURE_VI_USE_SIGNALS
771 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
775 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
776 #if ENABLE_FEATURE_VI_ASK_TERMINAL
777 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
779 write1("\033[999;999H" "\033[6n");
781 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
782 if ((int32_t)k == KEYCODE_CURSOR_POS) {
783 uint32_t rc = (k >> 32);
784 columns = (rc & 0x7fff);
785 if (columns > MAX_SCR_COLS)
786 columns = MAX_SCR_COLS;
787 rows = ((rc >> 16) & 0x7fff);
788 if (rows > MAX_SCR_ROWS)
793 new_screen(rows, columns); // get memory for virtual screen
794 init_text_buffer(fn);
796 #if ENABLE_FEATURE_VI_YANKMARK
797 YDreg = 26; // default Yank/Delete reg
798 Ureg = 27; // hold orig line for "U" cmd
799 mark[26] = mark[27] = text; // init "previous context"
802 last_forward_char = last_input_char = '\0';
806 #if ENABLE_FEATURE_VI_USE_SIGNALS
807 signal(SIGINT, catch_sig);
808 signal(SIGWINCH, winch_sig);
809 signal(SIGTSTP, suspend_sig);
810 sig = sigsetjmp(restart, 1);
812 screenbegin = dot = text;
816 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
819 offset = 0; // no horizontal offset
821 #if ENABLE_FEATURE_VI_DOT_CMD
823 ioq = ioq_start = NULL;
828 #if ENABLE_FEATURE_VI_COLON
833 while ((p = initial_cmds[n]) != NULL) {
843 free(initial_cmds[n]);
844 initial_cmds[n] = NULL;
849 redraw(FALSE); // dont force every col re-draw
850 //------This is the main Vi cmd handling loop -----------------------
851 while (editing > 0) {
852 #if ENABLE_FEATURE_VI_CRASHME
854 if ((end - text) > 1) {
855 crash_dummy(); // generate a random command
858 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
864 last_input_char = c = get_one_char(); // get a cmd from user
865 #if ENABLE_FEATURE_VI_YANKMARK
866 // save a copy of the current line- for the 'U" command
867 if (begin_line(dot) != cur_line) {
868 cur_line = begin_line(dot);
869 text_yank(begin_line(dot), end_line(dot), Ureg);
872 #if ENABLE_FEATURE_VI_DOT_CMD
873 // These are commands that change text[].
874 // Remember the input for the "." command
875 if (!adding2q && ioq_start == NULL
876 && cmd_mode == 0 // command mode
877 && c > '\0' // exclude NUL and non-ASCII chars
878 && c < 0x7f // (Unicode and such)
879 && strchr(modifying_cmds, c)
884 do_cmd(c); // execute the user command
886 // poll to see if there is input already waiting. if we are
887 // not able to display output fast enough to keep up, skip
888 // the display update until we catch up with input.
889 if (!readbuffer[0] && mysleep(0) == 0) {
890 // no input pending - so update output
894 #if ENABLE_FEATURE_VI_CRASHME
896 crash_test(); // test editor variables
899 //-------------------------------------------------------------------
901 go_bottom_and_clear_to_eol();
906 //----- The Colon commands -------------------------------------
907 #if ENABLE_FEATURE_VI_COLON
908 static char *get_one_address(char *p, int *addr) // get colon addr, if present
912 IF_FEATURE_VI_YANKMARK(char c;)
913 IF_FEATURE_VI_SEARCH(char *pat;)
915 *addr = -1; // assume no addr
916 if (*p == '.') { // the current line
919 *addr = count_lines(text, q);
921 #if ENABLE_FEATURE_VI_YANKMARK
922 else if (*p == '\'') { // is this a mark addr
926 if (c >= 'a' && c <= 'z') {
929 q = mark[(unsigned char) c];
930 if (q != NULL) { // is mark valid
931 *addr = count_lines(text, q);
936 #if ENABLE_FEATURE_VI_SEARCH
937 else if (*p == '/') { // a search pattern
938 q = strchrnul(++p, '/');
939 pat = xstrndup(p, q - p); // save copy of pattern
943 q = char_search(dot, pat, FORWARD, FULL);
945 *addr = count_lines(text, q);
950 else if (*p == '$') { // the last line in file
952 q = begin_line(end - 1);
953 *addr = count_lines(text, q);
954 } else if (isdigit(*p)) { // specific line number
955 sscanf(p, "%d%n", addr, &st);
958 // unrecognized address - assume -1
964 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
966 //----- get the address' i.e., 1,3 'a,'b -----
967 // get FIRST addr, if present
969 p++; // skip over leading spaces
970 if (*p == '%') { // alias for 1,$
973 *e = count_lines(text, end-1);
976 p = get_one_address(p, b);
979 if (*p == ',') { // is there a address separator
983 // get SECOND addr, if present
984 p = get_one_address(p, e);
988 p++; // skip over trailing spaces
992 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
993 static void setops(const char *args, const char *opname, int flg_no,
994 const char *short_opname, int opt)
996 const char *a = args + flg_no;
997 int l = strlen(opname) - 1; /* opname have + ' ' */
999 // maybe strncmp? we had tons of erroneous strncasecmp's...
1000 if (strncasecmp(a, opname, l) == 0
1001 || strncasecmp(a, short_opname, 2) == 0
1011 #endif /* FEATURE_VI_COLON */
1013 // buf must be no longer than MAX_INPUT_LEN!
1014 static void colon(char *buf)
1016 #if !ENABLE_FEATURE_VI_COLON
1017 /* Simple ":cmd" handler with minimal set of commands */
1026 if (strncmp(p, "quit", cnt) == 0
1027 || strncmp(p, "q!", cnt) == 0
1029 if (modified_count && p[1] != '!') {
1030 status_line_bold("No write since last change (:%s! overrides)", p);
1036 if (strncmp(p, "write", cnt) == 0
1037 || strncmp(p, "wq", cnt) == 0
1038 || strncmp(p, "wn", cnt) == 0
1039 || (p[0] == 'x' && !p[1])
1041 cnt = file_write(current_filename, text, end - 1);
1044 status_line_bold("Write error: %s", strerror(errno));
1047 last_modified_count = -1;
1048 status_line("'%s' %dL, %dC",
1050 count_lines(text, end - 1), cnt
1052 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
1053 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
1060 if (strncmp(p, "file", cnt) == 0) {
1061 last_status_cksum = 0; // force status update
1064 if (sscanf(p, "%d", &cnt) > 0) {
1065 dot = find_line(cnt);
1072 char c, *orig_buf, *buf1, *q, *r;
1073 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
1077 // :3154 // if (-e line 3154) goto it else stay put
1078 // :4,33w! foo // write a portion of buffer to file "foo"
1079 // :w // write all of buffer to current file
1081 // :q! // quit- dont care about modified file
1082 // :'a,'z!sort -u // filter block through sort
1083 // :'f // goto mark "f"
1084 // :'fl // list literal the mark "f" line
1085 // :.r bar // read file "bar" into buffer before dot
1086 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1087 // :/xyz/ // goto the "xyz" line
1088 // :s/find/replace/ // substitute pattern "find" with "replace"
1089 // :!<cmd> // run <cmd> then return
1095 buf++; // move past the ':'
1099 q = text; // assume 1,$ for the range
1101 li = count_lines(text, end - 1);
1102 fn = current_filename;
1104 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1105 buf = get_address(buf, &b, &e);
1107 // remember orig command line
1110 // get the COMMAND into cmd[]
1112 while (*buf != '\0') {
1118 // get any ARGuments
1119 while (isblank(*buf))
1123 buf1 = last_char_is(cmd, '!');
1126 *buf1 = '\0'; // get rid of !
1129 // if there is only one addr, then the addr
1130 // is the line number of the single line the
1131 // user wants. So, reset the end
1132 // pointer to point at end of the "b" line
1133 q = find_line(b); // what line is #b
1138 // we were given two addrs. change the
1139 // end pointer to the addr given by user.
1140 r = find_line(e); // what line is #e
1144 // ------------ now look for the command ------------
1146 if (i == 0) { // :123CR goto line #123
1148 dot = find_line(b); // what line is #b
1152 #if ENABLE_FEATURE_ALLOW_EXEC
1153 else if (cmd[0] == '!') { // run a cmd
1155 // :!ls run the <cmd>
1156 go_bottom_and_clear_to_eol();
1158 retcode = system(orig_buf + 1); // run the cmd
1160 printf("\nshell returned %i\n\n", retcode);
1162 Hit_Return(); // let user see results
1165 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1166 if (b < 0) { // no addr given- use defaults
1167 b = e = count_lines(text, dot);
1169 status_line("%d", b);
1170 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1171 if (b < 0) { // no addr given- use defaults
1172 q = begin_line(dot); // assume .,. for the range
1175 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
1177 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1180 // don't edit, if the current file has been modified
1181 if (modified_count && !useforce) {
1182 status_line_bold("No write since last change (:%s! overrides)", cmd);
1186 // the user supplied a file name
1188 } else if (current_filename && current_filename[0]) {
1189 // no user supplied name- use the current filename
1190 // fn = current_filename; was set by default
1192 // no user file name, no current name- punt
1193 status_line_bold("No current filename");
1197 size = init_text_buffer(fn);
1199 #if ENABLE_FEATURE_VI_YANKMARK
1200 if (Ureg >= 0 && Ureg < 28) {
1201 free(reg[Ureg]); // free orig line reg- for 'U'
1204 if (YDreg >= 0 && YDreg < 28) {
1205 free(reg[YDreg]); // free default yank/delete register
1209 // how many lines in text[]?
1210 li = count_lines(text, end - 1);
1211 status_line("'%s'%s"
1212 IF_FEATURE_VI_READONLY("%s")
1215 (size < 0 ? " [New file]" : ""),
1216 IF_FEATURE_VI_READONLY(
1217 ((readonly_mode) ? " [Readonly]" : ""),
1219 li, (int)(end - text)
1221 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1222 if (b != -1 || e != -1) {
1223 status_line_bold("No address allowed on this command");
1227 // user wants a new filename
1228 free(current_filename);
1229 current_filename = xstrdup(args);
1231 // user wants file status info
1232 last_status_cksum = 0; // force status update
1234 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1235 // print out values of all features
1236 go_bottom_and_clear_to_eol();
1241 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1242 if (b < 0) { // no addr given- use defaults
1243 q = begin_line(dot); // assume .,. for the range
1246 go_bottom_and_clear_to_eol();
1248 for (; q <= r; q++) {
1252 c_is_no_print = (c & 0x80) && !Isprint(c);
1253 if (c_is_no_print) {
1259 } else if (c < ' ' || c == 127) {
1271 } else if (strncmp(cmd, "quit", i) == 0 // quit
1272 || strncmp(cmd, "next", i) == 0 // edit next file
1273 || strncmp(cmd, "prev", i) == 0 // edit previous file
1278 // force end of argv list
1284 // don't exit if the file been modified
1285 if (modified_count) {
1286 status_line_bold("No write since last change (:%s! overrides)", cmd);
1289 // are there other file to edit
1290 n = save_argc - optind - 1;
1291 if (*cmd == 'q' && n > 0) {
1292 status_line_bold("%d more file(s) to edit", n);
1295 if (*cmd == 'n' && n <= 0) {
1296 status_line_bold("No more files to edit");
1300 // are there previous files to edit
1302 status_line_bold("No previous files to edit");
1308 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1313 status_line_bold("No filename given");
1316 if (b < 0) { // no addr given- use defaults
1317 q = begin_line(dot); // assume "dot"
1319 // read after current line- unless user said ":0r foo"
1322 // read after last line
1326 { // dance around potentially-reallocated text[]
1327 uintptr_t ofs = q - text;
1328 size = file_insert(fn, q, 0);
1332 goto ret; // nothing was inserted
1333 // how many lines in text[]?
1334 li = count_lines(q, q + size - 1);
1336 IF_FEATURE_VI_READONLY("%s")
1339 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1343 // if the insert is before "dot" then we need to update
1347 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1348 if (modified_count && !useforce) {
1349 status_line_bold("No write since last change (:%s! overrides)", cmd);
1351 // reset the filenames to edit
1352 optind = -1; /* start from 0th file */
1355 #if ENABLE_FEATURE_VI_SET
1356 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1357 #if ENABLE_FEATURE_VI_SETOPTS
1360 i = 0; // offset into args
1361 // only blank is regarded as args delimiter. What about tab '\t'?
1362 if (!args[0] || strcasecmp(args, "all") == 0) {
1363 // print out values of all options
1364 #if ENABLE_FEATURE_VI_SETOPTS
1371 autoindent ? "" : "no",
1372 err_method ? "" : "no",
1373 ignorecase ? "" : "no",
1374 showmatch ? "" : "no",
1380 #if ENABLE_FEATURE_VI_SETOPTS
1383 if (strncmp(argp, "no", 2) == 0)
1384 i = 2; // ":set noautoindent"
1385 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1386 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1387 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1388 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1389 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1391 sscanf(argp + i+8, "%u", &t);
1392 if (t > 0 && t <= MAX_TABSTOP)
1395 argp = skip_non_whitespace(argp);
1396 argp = skip_whitespace(argp);
1398 #endif /* FEATURE_VI_SETOPTS */
1399 #endif /* FEATURE_VI_SET */
1400 #if ENABLE_FEATURE_VI_SEARCH
1401 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1402 char *F, *R, *flags;
1403 size_t len_F, len_R;
1404 int gflag; // global replace flag
1405 #if ENABLE_FEATURE_VI_UNDO
1406 int dont_chain_first_item = ALLOW_UNDO;
1409 // F points to the "find" pattern
1410 // R points to the "replace" pattern
1411 // replace the cmd line delimiters "/" with NULs
1412 c = orig_buf[1]; // what is the delimiter
1413 F = orig_buf + 2; // start of "find"
1414 R = strchr(F, c); // middle delimiter
1418 *R++ = '\0'; // terminate "find"
1419 flags = strchr(R, c);
1423 *flags++ = '\0'; // terminate "replace"
1427 if (b < 0) { // maybe :s/foo/bar/
1428 q = begin_line(dot); // start with cur line
1429 b = count_lines(text, q); // cur line number
1432 e = b; // maybe :.s/foo/bar/
1434 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1435 char *ls = q; // orig line start
1438 found = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1441 // we found the "find" pattern - delete it
1442 // For undo support, the first item should not be chained
1443 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
1444 #if ENABLE_FEATURE_VI_UNDO
1445 dont_chain_first_item = ALLOW_UNDO_CHAIN;
1447 // insert the "replace" patern
1448 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
1451 /*q += bias; - recalculated anyway */
1452 // check for "global" :s/foo/bar/g
1454 if ((found + len_R) < end_line(ls)) {
1456 goto vc4; // don't let q move past cur line
1462 #endif /* FEATURE_VI_SEARCH */
1463 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1464 status_line(BB_VER " " BB_BT);
1465 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1466 || strncmp(cmd, "wq", i) == 0
1467 || strncmp(cmd, "wn", i) == 0
1468 || (cmd[0] == 'x' && !cmd[1])
1471 //int forced = FALSE;
1473 // is there a file name to write to?
1477 #if ENABLE_FEATURE_VI_READONLY
1478 if (readonly_mode && !useforce) {
1479 status_line_bold("'%s' is read only", fn);
1483 // how many lines in text[]?
1484 li = count_lines(q, r);
1487 // if "fn" is not write-able, chmod u+w
1488 // sprintf(syscmd, "chmod u+w %s", fn);
1492 l = file_write(fn, q, r);
1493 //if (useforce && forced) {
1495 // sprintf(syscmd, "chmod u-w %s", fn);
1501 status_line_bold_errno(fn);
1503 status_line("'%s' %dL, %dC", fn, li, l);
1504 if (q == text && r == end - 1 && l == size) {
1506 last_modified_count = -1;
1508 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
1509 || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
1516 #if ENABLE_FEATURE_VI_YANKMARK
1517 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1518 if (b < 0) { // no addr given- use defaults
1519 q = begin_line(dot); // assume .,. for the range
1522 text_yank(q, r, YDreg);
1523 li = count_lines(q, r);
1524 status_line("Yank %d lines (%d chars) into [%c]",
1525 li, strlen(reg[YDreg]), what_reg());
1529 not_implemented(cmd);
1532 dot = bound_dot(dot); // make sure "dot" is valid
1534 #if ENABLE_FEATURE_VI_SEARCH
1536 status_line(":s expression missing delimiters");
1538 #endif /* FEATURE_VI_COLON */
1541 static void Hit_Return(void)
1546 write1("[Hit return to continue]");
1548 while ((c = get_one_char()) != '\n' && c != '\r')
1550 redraw(TRUE); // force redraw all
1553 static int next_tabstop(int col)
1555 return col + ((tabstop - 1) - (col % tabstop));
1558 //----- Synchronize the cursor to Dot --------------------------
1559 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1561 char *beg_cur; // begin and end of "d" line
1565 beg_cur = begin_line(d); // first char of cur line
1567 if (beg_cur < screenbegin) {
1568 // "d" is before top line on screen
1569 // how many lines do we have to move
1570 cnt = count_lines(beg_cur, screenbegin);
1572 screenbegin = beg_cur;
1573 if (cnt > (rows - 1) / 2) {
1574 // we moved too many lines. put "dot" in middle of screen
1575 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1576 screenbegin = prev_line(screenbegin);
1580 char *end_scr; // begin and end of screen
1581 end_scr = end_screen(); // last char of screen
1582 if (beg_cur > end_scr) {
1583 // "d" is after bottom line on screen
1584 // how many lines do we have to move
1585 cnt = count_lines(end_scr, beg_cur);
1586 if (cnt > (rows - 1) / 2)
1587 goto sc1; // too many lines
1588 for (ro = 0; ro < cnt - 1; ro++) {
1589 // move screen begin the same amount
1590 screenbegin = next_line(screenbegin);
1591 // now, move the end of screen
1592 end_scr = next_line(end_scr);
1593 end_scr = end_line(end_scr);
1597 // "d" is on screen- find out which row
1599 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1605 // find out what col "d" is on
1607 while (tp < d) { // drive "co" to correct column
1608 if (*tp == '\n') //vda || *tp == '\0')
1611 // handle tabs like real vi
1612 if (d == tp && cmd_mode) {
1615 co = next_tabstop(co);
1616 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1617 co++; // display as ^X, use 2 columns
1623 // "co" is the column where "dot" is.
1624 // The screen has "columns" columns.
1625 // The currently displayed columns are 0+offset -- columns+ofset
1626 // |-------------------------------------------------------------|
1628 // offset | |------- columns ----------------|
1630 // If "co" is already in this range then we do not have to adjust offset
1631 // but, we do have to subtract the "offset" bias from "co".
1632 // If "co" is outside this range then we have to change "offset".
1633 // If the first char of a line is a tab the cursor will try to stay
1634 // in column 7, but we have to set offset to 0.
1636 if (co < 0 + offset) {
1639 if (co >= columns + offset) {
1640 offset = co - columns + 1;
1642 // if the first char of the line is a tab, and "dot" is sitting on it
1643 // force offset to 0.
1644 if (d == beg_cur && *d == '\t') {
1653 //----- Text Movement Routines ---------------------------------
1654 static char *begin_line(char *p) // return pointer to first char cur line
1657 p = memrchr(text, '\n', p - text);
1665 static char *end_line(char *p) // return pointer to NL of cur line
1668 p = memchr(p, '\n', end - p - 1);
1675 static char *dollar_line(char *p) // return pointer to just before NL line
1678 // Try to stay off of the Newline
1679 if (*p == '\n' && (p - begin_line(p)) > 0)
1684 static char *prev_line(char *p) // return pointer first char prev line
1686 p = begin_line(p); // goto beginning of cur line
1687 if (p > text && p[-1] == '\n')
1688 p--; // step to prev line
1689 p = begin_line(p); // goto beginning of prev line
1693 static char *next_line(char *p) // return pointer first char next line
1696 if (p < end - 1 && *p == '\n')
1697 p++; // step to next line
1701 //----- Text Information Routines ------------------------------
1702 static char *end_screen(void)
1707 // find new bottom line
1709 for (cnt = 0; cnt < rows - 2; cnt++)
1715 // count line from start to stop
1716 static int count_lines(char *start, char *stop)
1721 if (stop < start) { // start and stop are backwards- reverse them
1727 stop = end_line(stop);
1728 while (start <= stop && start <= end - 1) {
1729 start = end_line(start);
1737 static char *find_line(int li) // find beginning of line #li
1741 for (q = text; li > 1; li--) {
1747 //----- Dot Movement Routines ----------------------------------
1748 static void dot_left(void)
1750 undo_queue_commit();
1751 if (dot > text && dot[-1] != '\n')
1755 static void dot_right(void)
1757 undo_queue_commit();
1758 if (dot < end - 1 && *dot != '\n')
1762 static void dot_begin(void)
1764 undo_queue_commit();
1765 dot = begin_line(dot); // return pointer to first char cur line
1768 static void dot_end(void)
1770 undo_queue_commit();
1771 dot = end_line(dot); // return pointer to last char cur line
1774 static char *move_to_col(char *p, int l)
1780 while (co < l && p < end) {
1781 if (*p == '\n') //vda || *p == '\0')
1784 co = next_tabstop(co);
1785 } else if (*p < ' ' || *p == 127) {
1786 co++; // display as ^X, use 2 columns
1794 static void dot_next(void)
1796 undo_queue_commit();
1797 dot = next_line(dot);
1800 static void dot_prev(void)
1802 undo_queue_commit();
1803 dot = prev_line(dot);
1806 static void dot_scroll(int cnt, int dir)
1810 undo_queue_commit();
1811 for (; cnt > 0; cnt--) {
1814 // ctrl-Y scroll up one line
1815 screenbegin = prev_line(screenbegin);
1818 // ctrl-E scroll down one line
1819 screenbegin = next_line(screenbegin);
1822 // make sure "dot" stays on the screen so we dont scroll off
1823 if (dot < screenbegin)
1825 q = end_screen(); // find new bottom line
1827 dot = begin_line(q); // is dot is below bottom line?
1831 static void dot_skip_over_ws(void)
1834 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1838 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1840 if (p >= end && end > text) {
1851 //----- Helper Utility Routines --------------------------------
1853 //----------------------------------------------------------------
1854 //----- Char Routines --------------------------------------------
1855 /* Chars that are part of a word-
1856 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1857 * Chars that are Not part of a word (stoppers)
1858 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1859 * Chars that are WhiteSpace
1860 * TAB NEWLINE VT FF RETURN SPACE
1861 * DO NOT COUNT NEWLINE AS WHITESPACE
1864 static char *new_screen(int ro, int co)
1869 screensize = ro * co + 8;
1870 screen = xmalloc(screensize);
1871 // initialize the new screen. assume this will be a empty file.
1873 // non-existent text[] lines start with a tilde (~).
1874 for (li = 1; li < ro - 1; li++) {
1875 screen[(li * co) + 0] = '~';
1880 #if ENABLE_FEATURE_VI_SEARCH
1882 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1884 // search for pattern starting at p
1885 static char *char_search(char *p, const char *pat, int dir, int range)
1887 struct re_pattern_buffer preg;
1893 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1895 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1897 memset(&preg, 0, sizeof(preg));
1898 err = re_compile_pattern(pat, strlen(pat), &preg);
1900 status_line_bold("bad search pattern '%s': %s", pat, err);
1904 // assume a LIMITED forward search
1908 // RANGE could be negative if we are searching backwards
1918 // search for the compiled pattern, preg, in p[]
1919 // range < 0: search backward
1920 // range > 0: search forward
1922 // re_search() < 0: not found or error
1923 // re_search() >= 0: index of found pattern
1924 // struct pattern char int int int struct reg
1925 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1926 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1939 # if ENABLE_FEATURE_VI_SETOPTS
1940 static int mycmp(const char *s1, const char *s2, int len)
1943 return strncasecmp(s1, s2, len);
1945 return strncmp(s1, s2, len);
1948 # define mycmp strncmp
1951 static char *char_search(char *p, const char *pat, int dir, int range)
1957 if (dir == FORWARD) {
1958 stop = end - 1; // assume range is p..end-1
1959 if (range == LIMITED)
1960 stop = next_line(p); // range is to next line
1961 for (start = p; start < stop; start++) {
1962 if (mycmp(start, pat, len) == 0) {
1966 } else if (dir == BACK) {
1967 stop = text; // assume range is text..p
1968 if (range == LIMITED)
1969 stop = prev_line(p); // range is to prev line
1970 for (start = p - len; start >= stop; start--) {
1971 if (mycmp(start, pat, len) == 0) {
1976 // pattern not found
1982 #endif /* FEATURE_VI_SEARCH */
1984 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1986 if (c == 22) { // Is this an ctrl-V?
1987 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1988 refresh(FALSE); // show the ^
1991 #if ENABLE_FEATURE_VI_UNDO
1994 undo_push(p, 1, UNDO_INS);
1996 case ALLOW_UNDO_CHAIN:
1997 undo_push(p, 1, UNDO_INS_CHAIN);
1999 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2000 case ALLOW_UNDO_QUEUED:
2001 undo_push(p, 1, UNDO_INS_QUEUED);
2007 #endif /* ENABLE_FEATURE_VI_UNDO */
2009 } else if (c == 27) { // Is this an ESC?
2011 undo_queue_commit();
2013 end_cmd_q(); // stop adding to q
2014 last_status_cksum = 0; // force status update
2015 if ((p[-1] != '\n') && (dot > text)) {
2018 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
2021 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2024 // insert a char into text[]
2026 c = '\n'; // translate \r to \n
2027 #if ENABLE_FEATURE_VI_UNDO
2028 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2030 undo_queue_commit();
2034 undo_push(p, 1, UNDO_INS);
2036 case ALLOW_UNDO_CHAIN:
2037 undo_push(p, 1, UNDO_INS_CHAIN);
2039 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2040 case ALLOW_UNDO_QUEUED:
2041 undo_push(p, 1, UNDO_INS_QUEUED);
2047 #endif /* ENABLE_FEATURE_VI_UNDO */
2048 p += 1 + stupid_insert(p, c); // insert the char
2049 #if ENABLE_FEATURE_VI_SETOPTS
2050 if (showmatch && strchr(")]}", c) != NULL) {
2051 showmatching(p - 1);
2053 if (autoindent && c == '\n') { // auto indent the new line
2056 q = prev_line(p); // use prev line as template
2057 len = strspn(q, " \t"); // space or tab
2060 bias = text_hole_make(p, len);
2063 #if ENABLE_FEATURE_VI_UNDO
2064 undo_push(p, len, UNDO_INS);
2075 // might reallocate text[]! use p += stupid_insert(p, ...),
2076 // and be careful to not use pointers into potentially freed text[]!
2077 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2080 bias = text_hole_make(p, 1);
2086 static int find_range(char **start, char **stop, char c)
2088 char *save_dot, *p, *q, *t;
2089 int cnt, multiline = 0;
2094 if (strchr("cdy><", c)) {
2095 // these cmds operate on whole lines
2096 p = q = begin_line(p);
2097 for (cnt = 1; cnt < cmdcnt; cnt++) {
2101 } else if (strchr("^%$0bBeEfth\b\177", c)) {
2102 // These cmds operate on char positions
2103 do_cmd(c); // execute movement cmd
2105 } else if (strchr("wW", c)) {
2106 do_cmd(c); // execute movement cmd
2107 // if we are at the next word's first char
2108 // step back one char
2109 // but check the possibilities when it is true
2110 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
2111 || (ispunct(dot[-1]) && !ispunct(dot[0]))
2112 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
2113 dot--; // move back off of next word
2114 if (dot > text && *dot == '\n')
2115 dot--; // stay off NL
2117 } else if (strchr("H-k{", c)) {
2118 // these operate on multi-lines backwards
2119 q = end_line(dot); // find NL
2120 do_cmd(c); // execute movement cmd
2123 } else if (strchr("L+j}\r\n", c)) {
2124 // these operate on multi-lines forwards
2125 p = begin_line(dot);
2126 do_cmd(c); // execute movement cmd
2127 dot_end(); // find NL
2130 // nothing -- this causes any other values of c to
2131 // represent the one-character range under the
2132 // cursor. this is correct for ' ' and 'l', but
2133 // perhaps no others.
2142 // backward char movements don't include start position
2143 if (q > p && strchr("^0bBh\b\177", c)) q--;
2146 for (t = p; t <= q; t++) {
2159 static int st_test(char *p, int type, int dir, char *tested)
2169 if (type == S_BEFORE_WS) {
2171 test = (!isspace(c) || c == '\n');
2173 if (type == S_TO_WS) {
2175 test = (!isspace(c) || c == '\n');
2177 if (type == S_OVER_WS) {
2181 if (type == S_END_PUNCT) {
2185 if (type == S_END_ALNUM) {
2187 test = (isalnum(c) || c == '_');
2193 static char *skip_thing(char *p, int linecnt, int dir, int type)
2197 while (st_test(p, type, dir, &c)) {
2198 // make sure we limit search to correct number of lines
2199 if (c == '\n' && --linecnt < 1)
2201 if (dir >= 0 && p >= end - 1)
2203 if (dir < 0 && p <= text)
2205 p += dir; // move to next char
2210 // find matching char of pair () [] {}
2211 // will crash if c is not one of these
2212 static char *find_pair(char *p, const char c)
2214 const char *braces = "()[]{}";
2218 dir = strchr(braces, c) - braces;
2220 match = braces[dir];
2221 dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
2223 // look for match, count levels of pairs (( ))
2227 if (p < text || p >= end)
2230 level++; // increase pair levels
2232 level--; // reduce pair level
2234 return p; // found matching pair
2239 #if ENABLE_FEATURE_VI_SETOPTS
2240 // show the matching char of a pair, () [] {}
2241 static void showmatching(char *p)
2245 // we found half of a pair
2246 q = find_pair(p, *p); // get loc of matching char
2248 indicate_error(); // no matching char
2250 // "q" now points to matching pair
2251 save_dot = dot; // remember where we are
2252 dot = q; // go to new loc
2253 refresh(FALSE); // let the user see it
2254 mysleep(40); // give user some time
2255 dot = save_dot; // go back to old loc
2259 #endif /* FEATURE_VI_SETOPTS */
2261 #if ENABLE_FEATURE_VI_UNDO
2262 static void flush_undo_data(void)
2264 struct undo_object *undo_entry;
2266 while (undo_stack_tail) {
2267 undo_entry = undo_stack_tail;
2268 undo_stack_tail = undo_entry->prev;
2273 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2274 static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack
2276 struct undo_object *undo_entry;
2279 // UNDO_INS: insertion, undo will remove from buffer
2280 // UNDO_DEL: deleted text, undo will restore to buffer
2281 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2282 // The CHAIN operations are for handling multiple operations that the user
2283 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2284 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2285 // for the INS/DEL operation. The raw values should be equal to the values of
2286 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2288 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2289 // This undo queuing functionality groups multiple character typing or backspaces
2290 // into a single large undo object. This greatly reduces calls to malloc() for
2291 // single-character operations while typing and has the side benefit of letting
2292 // an undo operation remove chunks of text rather than a single character.
2294 case UNDO_EMPTY: // Just in case this ever happens...
2296 case UNDO_DEL_QUEUED:
2298 return; // Only queue single characters
2299 switch (undo_queue_state) {
2301 undo_queue_state = UNDO_DEL;
2303 undo_queue_spos = src;
2305 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2306 // If queue is full, dump it into an object
2307 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2308 undo_queue_commit();
2311 // Switch from storing inserted text to deleted text
2312 undo_queue_commit();
2313 undo_push(src, length, UNDO_DEL_QUEUED);
2317 case UNDO_INS_QUEUED:
2320 switch (undo_queue_state) {
2322 undo_queue_state = UNDO_INS;
2323 undo_queue_spos = src;
2325 undo_q++; // Don't need to save any data for insertions
2326 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2327 undo_queue_commit();
2330 // Switch from storing deleted text to inserted text
2331 undo_queue_commit();
2332 undo_push(src, length, UNDO_INS_QUEUED);
2338 // If undo queuing is disabled, ignore the queuing flag entirely
2339 u_type = u_type & ~UNDO_QUEUED_FLAG;
2342 // Allocate a new undo object
2343 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2344 // For UNDO_DEL objects, save deleted text
2345 if ((src + length) == end)
2347 // If this deletion empties text[], strip the newline. When the buffer becomes
2348 // zero-length, a newline is added back, which requires this to compensate.
2349 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
2350 memcpy(undo_entry->undo_text, src, length);
2352 undo_entry = xzalloc(sizeof(*undo_entry));
2354 undo_entry->length = length;
2355 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2356 if ((u_type & UNDO_USE_SPOS) != 0) {
2357 undo_entry->start = undo_queue_spos - text; // use start position from queue
2359 undo_entry->start = src - text; // use offset from start of text buffer
2361 u_type = (u_type & ~UNDO_USE_SPOS);
2363 undo_entry->start = src - text;
2365 undo_entry->u_type = u_type;
2367 // Push it on undo stack
2368 undo_entry->prev = undo_stack_tail;
2369 undo_stack_tail = undo_entry;
2373 static void undo_pop(void) // Undo the last operation
2376 char *u_start, *u_end;
2377 struct undo_object *undo_entry;
2379 // Commit pending undo queue before popping (should be unnecessary)
2380 undo_queue_commit();
2382 undo_entry = undo_stack_tail;
2383 // Check for an empty undo stack
2385 status_line("Already at oldest change");
2389 switch (undo_entry->u_type) {
2391 case UNDO_DEL_CHAIN:
2392 // make hole and put in text that was deleted; deallocate text
2393 u_start = text + undo_entry->start;
2394 text_hole_make(u_start, undo_entry->length);
2395 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2396 status_line("Undo [%d] %s %d chars at position %d",
2397 modified_count, "restored",
2398 undo_entry->length, undo_entry->start
2402 case UNDO_INS_CHAIN:
2403 // delete what was inserted
2404 u_start = undo_entry->start + text;
2405 u_end = u_start - 1 + undo_entry->length;
2406 text_hole_delete(u_start, u_end, NO_UNDO);
2407 status_line("Undo [%d] %s %d chars at position %d",
2408 modified_count, "deleted",
2409 undo_entry->length, undo_entry->start
2414 switch (undo_entry->u_type) {
2415 // If this is the end of a chain, lower modification count and refresh display
2418 dot = (text + undo_entry->start);
2421 case UNDO_DEL_CHAIN:
2422 case UNDO_INS_CHAIN:
2426 // Deallocate the undo object we just processed
2427 undo_stack_tail = undo_entry->prev;
2430 // For chained operations, continue popping all the way down the chain.
2432 undo_pop(); // Follow the undo chain if one exists
2436 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2437 static void undo_queue_commit(void) // Flush any queued objects to the undo stack
2439 // Pushes the queue object onto the undo stack
2441 // Deleted character undo events grow from the end
2442 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2444 (undo_queue_state | UNDO_USE_SPOS)
2446 undo_queue_state = UNDO_EMPTY;
2452 #endif /* ENABLE_FEATURE_VI_UNDO */
2454 // open a hole in text[]
2455 // might reallocate text[]! use p += text_hole_make(p, ...),
2456 // and be careful to not use pointers into potentially freed text[]!
2457 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2463 end += size; // adjust the new END
2464 if (end >= (text + text_size)) {
2466 text_size += end - (text + text_size) + 10240;
2467 new_text = xrealloc(text, text_size);
2468 bias = (new_text - text);
2469 screenbegin += bias;
2473 #if ENABLE_FEATURE_VI_YANKMARK
2476 for (i = 0; i < ARRAY_SIZE(mark); i++)
2483 memmove(p + size, p, end - size - p);
2484 memset(p, ' ', size); // clear new hole
2488 // close a hole in text[]
2489 // "undo" value indicates if this operation should be undo-able
2490 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2495 // move forwards, from beginning
2499 if (q < p) { // they are backward- swap them
2503 hole_size = q - p + 1;
2505 #if ENABLE_FEATURE_VI_UNDO
2510 undo_push(p, hole_size, UNDO_DEL);
2512 case ALLOW_UNDO_CHAIN:
2513 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2515 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2516 case ALLOW_UNDO_QUEUED:
2517 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2523 if (src < text || src > end)
2525 if (dest < text || dest >= end)
2529 goto thd_atend; // just delete the end of the buffer
2530 memmove(dest, src, cnt);
2532 end = end - hole_size; // adjust the new END
2534 dest = end - 1; // make sure dest in below end-1
2536 dest = end = text; // keep pointers valid
2541 // copy text into register, then delete text.
2542 // if dist <= 0, do not include, or go past, a NewLine
2544 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2548 // make sure start <= stop
2550 // they are backwards, reverse them
2556 // we cannot cross NL boundaries
2560 // dont go past a NewLine
2561 for (; p + 1 <= stop; p++) {
2563 stop = p; // "stop" just before NewLine
2569 #if ENABLE_FEATURE_VI_YANKMARK
2570 text_yank(start, stop, YDreg);
2572 if (yf == YANKDEL) {
2573 p = text_hole_delete(start, stop, undo);
2578 static void show_help(void)
2580 puts("These features are available:"
2581 #if ENABLE_FEATURE_VI_SEARCH
2582 "\n\tPattern searches with / and ?"
2584 #if ENABLE_FEATURE_VI_DOT_CMD
2585 "\n\tLast command repeat with ."
2587 #if ENABLE_FEATURE_VI_YANKMARK
2588 "\n\tLine marking with 'x"
2589 "\n\tNamed buffers with \"x"
2591 #if ENABLE_FEATURE_VI_READONLY
2592 //not implemented: "\n\tReadonly if vi is called as \"view\""
2593 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2595 #if ENABLE_FEATURE_VI_SET
2596 "\n\tSome colon mode commands with :"
2598 #if ENABLE_FEATURE_VI_SETOPTS
2599 "\n\tSettable options with \":set\""
2601 #if ENABLE_FEATURE_VI_USE_SIGNALS
2602 "\n\tSignal catching- ^C"
2603 "\n\tJob suspend and resume with ^Z"
2605 #if ENABLE_FEATURE_VI_WIN_RESIZE
2606 "\n\tAdapt to window re-sizes"
2611 #if ENABLE_FEATURE_VI_DOT_CMD
2612 static void start_new_cmd_q(char c)
2614 // get buffer for new cmd
2615 // if there is a current cmd count put it in the buffer first
2617 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2618 } else { // just save char c onto queue
2619 last_modifying_cmd[0] = c;
2625 static void end_cmd_q(void)
2627 #if ENABLE_FEATURE_VI_YANKMARK
2628 YDreg = 26; // go back to default Yank/Delete reg
2632 #endif /* FEATURE_VI_DOT_CMD */
2634 #if ENABLE_FEATURE_VI_YANKMARK \
2635 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2636 || ENABLE_FEATURE_VI_CRASHME
2637 // might reallocate text[]! use p += string_insert(p, ...),
2638 // and be careful to not use pointers into potentially freed text[]!
2639 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2645 #if ENABLE_FEATURE_VI_UNDO
2648 undo_push(p, i, UNDO_INS);
2650 case ALLOW_UNDO_CHAIN:
2651 undo_push(p, i, UNDO_INS_CHAIN);
2655 bias = text_hole_make(p, i);
2658 #if ENABLE_FEATURE_VI_YANKMARK
2661 for (cnt = 0; *s != '\0'; s++) {
2665 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2672 #if ENABLE_FEATURE_VI_YANKMARK
2673 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2676 if (cnt < 0) { // they are backwards- reverse them
2680 free(reg[dest]); // if already a yank register, free it
2681 reg[dest] = xstrndup(p, cnt + 1);
2685 static char what_reg(void)
2689 c = 'D'; // default to D-reg
2690 if (0 <= YDreg && YDreg <= 25)
2691 c = 'a' + (char) YDreg;
2699 static void check_context(char cmd)
2701 // A context is defined to be "modifying text"
2702 // Any modifying command establishes a new context.
2704 if (dot < context_start || dot > context_end) {
2705 if (strchr(modifying_cmds, cmd) != NULL) {
2706 // we are trying to modify text[]- make this the current context
2707 mark[27] = mark[26]; // move cur to prev
2708 mark[26] = dot; // move local to cur
2709 context_start = prev_line(prev_line(dot));
2710 context_end = next_line(next_line(dot));
2711 //loiter= start_loiter= now;
2716 static char *swap_context(char *p) // goto new context for '' command make this the current context
2720 // the current context is in mark[26]
2721 // the previous context is in mark[27]
2722 // only swap context if other context is valid
2723 if (text <= mark[27] && mark[27] <= end - 1) {
2727 context_start = prev_line(prev_line(prev_line(p)));
2728 context_end = next_line(next_line(next_line(p)));
2732 #endif /* FEATURE_VI_YANKMARK */
2734 //----- Set terminal attributes --------------------------------
2735 static void rawmode(void)
2737 tcgetattr(0, &term_orig);
2738 term_vi = term_orig;
2739 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG on - allow intr's
2740 term_vi.c_iflag &= (~IXON & ~ICRNL);
2741 term_vi.c_oflag &= (~ONLCR);
2742 term_vi.c_cc[VMIN] = 1;
2743 term_vi.c_cc[VTIME] = 0;
2744 erase_char = term_vi.c_cc[VERASE];
2745 tcsetattr_stdin_TCSANOW(&term_vi);
2748 static void cookmode(void)
2751 tcsetattr_stdin_TCSANOW(&term_orig);
2754 #if ENABLE_FEATURE_VI_USE_SIGNALS
2755 //----- Come here when we get a window resize signal ---------
2756 static void winch_sig(int sig UNUSED_PARAM)
2758 int save_errno = errno;
2759 // FIXME: do it in main loop!!!
2760 signal(SIGWINCH, winch_sig);
2761 query_screen_dimensions();
2762 new_screen(rows, columns); // get memory for virtual screen
2763 redraw(TRUE); // re-draw the screen
2767 //----- Come here when we get a continue signal -------------------
2768 static void cont_sig(int sig UNUSED_PARAM)
2770 int save_errno = errno;
2771 rawmode(); // terminal to "raw"
2772 last_status_cksum = 0; // force status update
2773 redraw(TRUE); // re-draw the screen
2775 signal(SIGTSTP, suspend_sig);
2776 signal(SIGCONT, SIG_DFL);
2777 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2781 //----- Come here when we get a Suspend signal -------------------
2782 static void suspend_sig(int sig UNUSED_PARAM)
2784 int save_errno = errno;
2785 go_bottom_and_clear_to_eol();
2786 cookmode(); // terminal to "cooked"
2788 signal(SIGCONT, cont_sig);
2789 signal(SIGTSTP, SIG_DFL);
2790 kill(my_pid, SIGTSTP);
2794 //----- Come here when we get a signal ---------------------------
2795 static void catch_sig(int sig)
2797 signal(SIGINT, catch_sig);
2798 siglongjmp(restart, sig);
2800 #endif /* FEATURE_VI_USE_SIGNALS */
2802 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2804 struct pollfd pfd[1];
2809 pfd[0].fd = STDIN_FILENO;
2810 pfd[0].events = POLLIN;
2811 return safe_poll(pfd, 1, hund*10) > 0;
2814 //----- IO Routines --------------------------------------------
2815 static int readit(void) // read (maybe cursor) key from stdin
2820 c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2821 if (c == -1) { // EOF/error
2822 go_bottom_and_clear_to_eol();
2823 cookmode(); // terminal to "cooked"
2824 bb_error_msg_and_die("can't read user input");
2829 //----- IO Routines --------------------------------------------
2830 static int get_one_char(void)
2834 #if ENABLE_FEATURE_VI_DOT_CMD
2836 // we are not adding to the q.
2837 // but, we may be reading from a q
2839 // there is no current q, read from STDIN
2840 c = readit(); // get the users input
2842 // there is a queue to get chars from first
2843 // careful with correct sign expansion!
2844 c = (unsigned char)*ioq++;
2846 // the end of the q, read from STDIN
2848 ioq_start = ioq = 0;
2849 c = readit(); // get the users input
2853 // adding STDIN chars to q
2854 c = readit(); // get the users input
2855 if (lmc_len >= MAX_INPUT_LEN - 1) {
2856 status_line_bold("last_modifying_cmd overrun");
2858 // add new char to q
2859 last_modifying_cmd[lmc_len++] = c;
2863 c = readit(); // get the users input
2864 #endif /* FEATURE_VI_DOT_CMD */
2868 // Get input line (uses "status line" area)
2869 static char *get_input_line(const char *prompt)
2871 // char [MAX_INPUT_LEN]
2872 #define buf get_input_line__buf
2877 strcpy(buf, prompt);
2878 last_status_cksum = 0; // force status update
2879 go_bottom_and_clear_to_eol();
2880 write1(prompt); // write out the :, /, or ? prompt
2883 while (i < MAX_INPUT_LEN) {
2885 if (c == '\n' || c == '\r' || c == 27)
2886 break; // this is end of input
2887 if (c == erase_char || c == 8 || c == 127) {
2888 // user wants to erase prev char
2890 write1("\b \b"); // erase char on screen
2891 if (i <= 0) // user backs up before b-o-l, exit
2893 } else if (c > 0 && c < 256) { // exclude Unicode
2894 // (TODO: need to handle Unicode)
2905 // might reallocate text[]!
2906 static int file_insert(const char *fn, char *p, int initial)
2910 struct stat statbuf;
2917 fd = open(fn, O_RDONLY);
2920 status_line_bold_errno(fn);
2925 if (fstat(fd, &statbuf) < 0) {
2926 status_line_bold_errno(fn);
2929 if (!S_ISREG(statbuf.st_mode)) {
2930 status_line_bold("'%s' is not a regular file", fn);
2933 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2934 p += text_hole_make(p, size);
2935 cnt = full_read(fd, p, size);
2937 status_line_bold_errno(fn);
2938 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2939 } else if (cnt < size) {
2940 // There was a partial read, shrink unused space
2941 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2942 status_line_bold("can't read '%s'", fn);
2947 #if ENABLE_FEATURE_VI_READONLY
2949 && ((access(fn, W_OK) < 0) ||
2950 /* root will always have access()
2951 * so we check fileperms too */
2952 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2955 SET_READONLY_FILE(readonly_mode);
2961 static int file_write(char *fn, char *first, char *last)
2963 int fd, cnt, charcnt;
2966 status_line_bold("No current filename");
2969 /* By popular request we do not open file with O_TRUNC,
2970 * but instead ftruncate() it _after_ successful write.
2971 * Might reduce amount of data lost on power fail etc.
2973 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2976 cnt = last - first + 1;
2977 charcnt = full_write(fd, first, cnt);
2978 ftruncate(fd, charcnt);
2979 if (charcnt == cnt) {
2981 //modified_count = FALSE;
2989 //----- Terminal Drawing ---------------------------------------
2990 // The terminal is made up of 'rows' line of 'columns' columns.
2991 // classically this would be 24 x 80.
2992 // screen coordinates
2998 // 23,0 ... 23,79 <- status line
3000 //----- Move the cursor to row x col (count from 0, not 1) -------
3001 static void place_cursor(int row, int col)
3003 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
3005 if (row < 0) row = 0;
3006 if (row >= rows) row = rows - 1;
3007 if (col < 0) col = 0;
3008 if (col >= columns) col = columns - 1;
3010 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
3014 //----- Erase from cursor to end of line -----------------------
3015 static void clear_to_eol(void)
3017 write1(ESC_CLEAR2EOL);
3020 static void go_bottom_and_clear_to_eol(void)
3022 place_cursor(rows - 1, 0);
3026 //----- Erase from cursor to end of screen -----------------------
3027 static void clear_to_eos(void)
3029 write1(ESC_CLEAR2EOS);
3032 //----- Start standout mode ------------------------------------
3033 static void standout_start(void)
3035 write1(ESC_BOLD_TEXT);
3038 //----- End standout mode --------------------------------------
3039 static void standout_end(void)
3041 write1(ESC_NORM_TEXT);
3044 //----- Flash the screen --------------------------------------
3045 static void flash(int h)
3054 static void indicate_error(void)
3056 #if ENABLE_FEATURE_VI_CRASHME
3058 return; // generate a random command
3067 //----- Screen[] Routines --------------------------------------
3068 //----- Erase the Screen[] memory ------------------------------
3069 static void screen_erase(void)
3071 memset(screen, ' ', screensize); // clear new screen
3074 static int bufsum(char *buf, int count)
3077 char *e = buf + count;
3080 sum += (unsigned char) *buf++;
3084 //----- Draw the status line at bottom of the screen -------------
3085 static void show_status_line(void)
3087 int cnt = 0, cksum = 0;
3089 // either we already have an error or status message, or we
3091 if (!have_status_msg) {
3092 cnt = format_edit_status();
3093 cksum = bufsum(status_buffer, cnt);
3095 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3096 last_status_cksum = cksum; // remember if we have seen this line
3097 go_bottom_and_clear_to_eol();
3098 write1(status_buffer);
3099 if (have_status_msg) {
3100 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3102 have_status_msg = 0;
3105 have_status_msg = 0;
3107 place_cursor(crow, ccol); // put cursor back in correct place
3112 //----- format the status buffer, the bottom line of screen ------
3113 // format status buffer, with STANDOUT mode
3114 static void status_line_bold(const char *format, ...)
3118 va_start(args, format);
3119 strcpy(status_buffer, ESC_BOLD_TEXT);
3120 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3121 strcat(status_buffer, ESC_NORM_TEXT);
3124 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3127 static void status_line_bold_errno(const char *fn)
3129 status_line_bold("'%s' %s", fn, strerror(errno));
3132 // format status buffer
3133 static void status_line(const char *format, ...)
3137 va_start(args, format);
3138 vsprintf(status_buffer, format, args);
3141 have_status_msg = 1;
3144 // copy s to buf, convert unprintable
3145 static void print_literal(char *buf, const char *s)
3159 c_is_no_print = (c & 0x80) && !Isprint(c);
3160 if (c_is_no_print) {
3161 strcpy(d, ESC_NORM_TEXT);
3162 d += sizeof(ESC_NORM_TEXT)-1;
3165 if (c < ' ' || c == 0x7f) {
3167 c |= '@'; /* 0x40 */
3173 if (c_is_no_print) {
3174 strcpy(d, ESC_BOLD_TEXT);
3175 d += sizeof(ESC_BOLD_TEXT)-1;
3181 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3186 static void not_implemented(const char *s)
3188 char buf[MAX_INPUT_LEN];
3190 print_literal(buf, s);
3191 status_line_bold("\'%s\' is not implemented", buf);
3194 // show file status on status line
3195 static int format_edit_status(void)
3197 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3199 #define tot format_edit_status__tot
3201 int cur, percent, ret, trunc_at;
3203 // modified_count is now a counter rather than a flag. this
3204 // helps reduce the amount of line counting we need to do.
3205 // (this will cause a mis-reporting of modified status
3206 // once every MAXINT editing operations.)
3208 // it would be nice to do a similar optimization here -- if
3209 // we haven't done a motion that could have changed which line
3210 // we're on, then we shouldn't have to do this count_lines()
3211 cur = count_lines(text, dot);
3213 // count_lines() is expensive.
3214 // Call it only if something was changed since last time
3216 if (modified_count != last_modified_count) {
3217 tot = cur + count_lines(dot, end - 1) - 1;
3218 last_modified_count = modified_count;
3221 // current line percent
3222 // ------------- ~~ ----------
3225 percent = (100 * cur) / tot;
3231 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3232 columns : STATUS_BUFFER_LEN-1;
3234 ret = snprintf(status_buffer, trunc_at+1,
3235 #if ENABLE_FEATURE_VI_READONLY
3236 "%c %s%s%s %d/%d %d%%",
3238 "%c %s%s %d/%d %d%%",
3240 cmd_mode_indicator[cmd_mode & 3],
3241 (current_filename != NULL ? current_filename : "No file"),
3242 #if ENABLE_FEATURE_VI_READONLY
3243 (readonly_mode ? " [Readonly]" : ""),
3245 (modified_count ? " [Modified]" : ""),
3248 if (ret >= 0 && ret < trunc_at)
3249 return ret; /* it all fit */
3251 return trunc_at; /* had to truncate */
3255 //----- Force refresh of all Lines -----------------------------
3256 static void redraw(int full_screen)
3260 screen_erase(); // erase the internal screen buffer
3261 last_status_cksum = 0; // force status update
3262 refresh(full_screen); // this will redraw the entire display
3266 //----- Format a text[] line into a buffer ---------------------
3267 static char* format_line(char *src /*, int li*/)
3272 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3274 c = '~'; // char in col 0 in non-existent lines is '~'
3276 while (co < columns + tabstop) {
3277 // have we gone past the end?
3282 if ((c & 0x80) && !Isprint(c)) {
3285 if (c < ' ' || c == 0x7f) {
3289 while ((co % tabstop) != (tabstop - 1)) {
3297 c += '@'; // Ctrl-X -> 'X'
3302 // discard scrolled-off-to-the-left portion,
3303 // in tabstop-sized pieces
3304 if (ofs >= tabstop && co >= tabstop) {
3305 memmove(dest, dest + tabstop, co);
3312 // check "short line, gigantic offset" case
3315 // discard last scrolled off part
3318 // fill the rest with spaces
3320 memset(&dest[co], ' ', columns - co);
3324 //----- Refresh the changed screen lines -----------------------
3325 // Copy the source line from text[] into the buffer and note
3326 // if the current screenline is different from the new buffer.
3327 // If they differ then that line needs redrawing on the terminal.
3329 static void refresh(int full_screen)
3331 #define old_offset refresh__old_offset
3334 char *tp, *sp; // pointer into text[] and screen[]
3336 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3337 unsigned c = columns, r = rows;
3338 query_screen_dimensions();
3339 full_screen |= (c - columns) | (r - rows);
3341 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3342 tp = screenbegin; // index into text[] of top line
3344 // compare text[] to screen[] and mark screen[] lines that need updating
3345 for (li = 0; li < rows - 1; li++) {
3346 int cs, ce; // column start & end
3348 // format current text line
3349 out_buf = format_line(tp /*, li*/);
3351 // skip to the end of the current text[] line
3353 char *t = memchr(tp, '\n', end - tp);
3354 if (!t) t = end - 1;
3358 // see if there are any changes between virtual screen and out_buf
3359 changed = FALSE; // assume no change
3362 sp = &screen[li * columns]; // start of screen line
3364 // force re-draw of every single column from 0 - columns-1
3367 // compare newly formatted buffer with virtual screen
3368 // look forward for first difference between buf and screen
3369 for (; cs <= ce; cs++) {
3370 if (out_buf[cs] != sp[cs]) {
3371 changed = TRUE; // mark for redraw
3376 // look backward for last difference between out_buf and screen
3377 for (; ce >= cs; ce--) {
3378 if (out_buf[ce] != sp[ce]) {
3379 changed = TRUE; // mark for redraw
3383 // now, cs is index of first diff, and ce is index of last diff
3385 // if horz offset has changed, force a redraw
3386 if (offset != old_offset) {
3391 // make a sanity check of columns indexes
3393 if (ce > columns - 1) ce = columns - 1;
3394 if (cs > ce) { cs = 0; ce = columns - 1; }
3395 // is there a change between virtual screen and out_buf
3397 // copy changed part of buffer to virtual screen
3398 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3399 place_cursor(li, cs);
3400 // write line out to terminal
3401 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3405 place_cursor(crow, ccol);
3407 old_offset = offset;
3411 //---------------------------------------------------------------------
3412 //----- the Ascii Chart -----------------------------------------------
3414 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3415 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3416 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3417 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3418 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3419 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3420 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3421 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3422 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3423 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3424 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3425 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3426 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3427 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3428 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3429 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3430 //---------------------------------------------------------------------
3432 //----- Execute a Vi Command -----------------------------------
3433 static void do_cmd(int c)
3435 char *p, *q, *save_dot;
3441 // c1 = c; // quiet the compiler
3442 // cnt = yf = 0; // quiet the compiler
3443 // p = q = save_dot = buf; // quiet the compiler
3444 memset(buf, '\0', sizeof(buf));
3448 /* if this is a cursor key, skip these checks */
3456 case KEYCODE_PAGEUP:
3457 case KEYCODE_PAGEDOWN:
3458 case KEYCODE_DELETE:
3462 if (cmd_mode == 2) {
3463 // flip-flop Insert/Replace mode
3464 if (c == KEYCODE_INSERT)
3466 // we are 'R'eplacing the current *dot with new char
3468 // don't Replace past E-o-l
3469 cmd_mode = 1; // convert to insert
3470 undo_queue_commit();
3472 if (1 <= c || Isprint(c)) {
3474 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3475 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3480 if (cmd_mode == 1) {
3481 // hitting "Insert" twice means "R" replace mode
3482 if (c == KEYCODE_INSERT) goto dc5;
3483 // insert the char c at "dot"
3484 if (1 <= c || Isprint(c)) {
3485 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3500 #if ENABLE_FEATURE_VI_CRASHME
3501 case 0x14: // dc4 ctrl-T
3502 crashme = (crashme == 0) ? 1 : 0;
3532 default: // unrecognized command
3535 not_implemented(buf);
3536 end_cmd_q(); // stop adding to q
3537 case 0x00: // nul- ignore
3539 case 2: // ctrl-B scroll up full screen
3540 case KEYCODE_PAGEUP: // Cursor Key Page Up
3541 dot_scroll(rows - 2, -1);
3543 case 4: // ctrl-D scroll down half screen
3544 dot_scroll((rows - 2) / 2, 1);
3546 case 5: // ctrl-E scroll down one line
3549 case 6: // ctrl-F scroll down full screen
3550 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3551 dot_scroll(rows - 2, 1);
3553 case 7: // ctrl-G show current status
3554 last_status_cksum = 0; // force status update
3556 case 'h': // h- move left
3557 case KEYCODE_LEFT: // cursor key Left
3558 case 8: // ctrl-H- move left (This may be ERASE char)
3559 case 0x7f: // DEL- move left (This may be ERASE char)
3562 } while (--cmdcnt > 0);
3564 case 10: // Newline ^J
3565 case 'j': // j- goto next line, same col
3566 case KEYCODE_DOWN: // cursor key Down
3568 dot_next(); // go to next B-o-l
3569 // try stay in same col
3570 dot = move_to_col(dot, ccol + offset);
3571 } while (--cmdcnt > 0);
3573 case 12: // ctrl-L force redraw whole screen
3574 case 18: // ctrl-R force redraw
3577 //mysleep(10); // why???
3578 screen_erase(); // erase the internal screen buffer
3579 last_status_cksum = 0; // force status update
3580 refresh(TRUE); // this will redraw the entire display
3582 case 13: // Carriage Return ^M
3583 case '+': // +- goto next line
3587 } while (--cmdcnt > 0);
3589 case 21: // ctrl-U scroll up half screen
3590 dot_scroll((rows - 2) / 2, -1);
3592 case 25: // ctrl-Y scroll up one line
3598 cmd_mode = 0; // stop insrting
3599 undo_queue_commit();
3601 last_status_cksum = 0; // force status update
3603 case ' ': // move right
3604 case 'l': // move right
3605 case KEYCODE_RIGHT: // Cursor Key Right
3608 } while (--cmdcnt > 0);
3610 #if ENABLE_FEATURE_VI_YANKMARK
3611 case '"': // "- name a register to use for Delete/Yank
3612 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3613 if ((unsigned)c1 <= 25) { // a-z?
3619 case '\'': // '- goto a specific mark
3620 c1 = (get_one_char() | 0x20);
3621 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3625 if (text <= q && q < end) {
3627 dot_begin(); // go to B-o-l
3630 } else if (c1 == '\'') { // goto previous context
3631 dot = swap_context(dot); // swap current and previous context
3632 dot_begin(); // go to B-o-l
3638 case 'm': // m- Mark a line
3639 // this is really stupid. If there are any inserts or deletes
3640 // between text[0] and dot then this mark will not point to the
3641 // correct location! It could be off by many lines!
3642 // Well..., at least its quick and dirty.
3643 c1 = (get_one_char() | 0x20) - 'a';
3644 if ((unsigned)c1 <= 25) { // a-z?
3645 // remember the line
3651 case 'P': // P- Put register before
3652 case 'p': // p- put register after
3655 status_line_bold("Nothing in register %c", what_reg());
3658 // are we putting whole lines or strings
3659 if (strchr(p, '\n') != NULL) {
3661 dot_begin(); // putting lines- Put above
3664 // are we putting after very last line?
3665 if (end_line(dot) == (end - 1)) {
3666 dot = end; // force dot to end of text[]
3668 dot_next(); // next line, then put before
3673 dot_right(); // move to right, can move to NL
3675 string_insert(dot, p, ALLOW_UNDO); // insert the string
3676 end_cmd_q(); // stop adding to q
3678 case 'U': // U- Undo; replace current line with original version
3679 if (reg[Ureg] != NULL) {
3680 p = begin_line(dot);
3682 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3683 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3688 #endif /* FEATURE_VI_YANKMARK */
3689 #if ENABLE_FEATURE_VI_UNDO
3690 case 'u': // u- undo last operation
3694 case '$': // $- goto end of line
3695 case KEYCODE_END: // Cursor Key End
3697 dot = end_line(dot);
3703 case '%': // %- find matching char of pair () [] {}
3704 for (q = dot; q < end && *q != '\n'; q++) {
3705 if (strchr("()[]{}", *q) != NULL) {
3706 // we found half of a pair
3707 p = find_pair(q, *q);
3719 case 'f': // f- forward to a user specified char
3720 last_forward_char = get_one_char(); // get the search char
3722 // dont separate these two commands. 'f' depends on ';'
3724 //**** fall through to ... ';'
3725 case ';': // ;- look at rest of line for last forward char
3727 if (last_forward_char == 0)
3730 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3733 if (*q == last_forward_char)
3735 } while (--cmdcnt > 0);
3737 case ',': // repeat latest 'f' in opposite direction
3738 if (last_forward_char == 0)
3742 while (q >= text && *q != '\n' && *q != last_forward_char) {
3745 if (q >= text && *q == last_forward_char)
3747 } while (--cmdcnt > 0);
3750 case '-': // -- goto prev line
3754 } while (--cmdcnt > 0);
3756 #if ENABLE_FEATURE_VI_DOT_CMD
3757 case '.': // .- repeat the last modifying command
3758 // Stuff the last_modifying_cmd back into stdin
3759 // and let it be re-executed.
3761 last_modifying_cmd[lmc_len] = 0;
3762 ioq = ioq_start = xstrdup(last_modifying_cmd);
3766 #if ENABLE_FEATURE_VI_SEARCH
3767 case '?': // /- search for a pattern
3768 case '/': // /- search for a pattern
3771 q = get_input_line(buf); // get input line- use "status line"
3772 if (q[0] && !q[1]) {
3773 if (last_search_pattern[0])
3774 last_search_pattern[0] = c;
3775 goto dc3; // if no pat re-use old pat
3777 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3778 // there is a new pat
3779 free(last_search_pattern);
3780 last_search_pattern = xstrdup(q);
3781 goto dc3; // now find the pattern
3783 // user changed mind and erased the "/"- do nothing
3785 case 'N': // N- backward search for last pattern
3786 dir = BACK; // assume BACKWARD search
3788 if (last_search_pattern[0] == '?') {
3792 goto dc4; // now search for pattern
3794 case 'n': // n- repeat search for last pattern
3795 // search rest of text[] starting at next char
3796 // if search fails return orignal "p" not the "p+1" address
3800 dir = FORWARD; // assume FORWARD search
3802 if (last_search_pattern[0] == '?') {
3807 q = char_search(p, last_search_pattern + 1, dir, FULL);
3809 dot = q; // good search, update "dot"
3813 // no pattern found between "dot" and "end"- continue at top
3818 q = char_search(p, last_search_pattern + 1, dir, FULL);
3819 if (q != NULL) { // found something
3820 dot = q; // found new pattern- goto it
3821 msg = "search hit BOTTOM, continuing at TOP";
3823 msg = "search hit TOP, continuing at BOTTOM";
3826 msg = "Pattern not found";
3830 status_line_bold("%s", msg);
3831 } while (--cmdcnt > 0);
3833 case '{': // {- move backward paragraph
3834 q = char_search(dot, "\n\n", BACK, FULL);
3835 if (q != NULL) { // found blank line
3836 dot = next_line(q); // move to next blank line
3839 case '}': // }- move forward paragraph
3840 q = char_search(dot, "\n\n", FORWARD, FULL);
3841 if (q != NULL) { // found blank line
3842 dot = next_line(q); // move to next blank line
3845 #endif /* FEATURE_VI_SEARCH */
3846 case '0': // 0- goto beginning of line
3856 if (c == '0' && cmdcnt < 1) {
3857 dot_begin(); // this was a standalone zero
3859 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3862 case ':': // :- the colon mode commands
3863 p = get_input_line(":"); // get input line- use "status line"
3864 colon(p); // execute the command
3866 case '<': // <- Left shift something
3867 case '>': // >- Right shift something
3868 cnt = count_lines(text, dot); // remember what line we are on
3869 c1 = get_one_char(); // get the type of thing to delete
3870 find_range(&p, &q, c1);
3871 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3874 i = count_lines(p, q); // # of lines we are shifting
3875 for ( ; i > 0; i--, p = next_line(p)) {
3877 // shift left- remove tab or 8 spaces
3879 // shrink buffer 1 char
3880 text_hole_delete(p, p, NO_UNDO);
3881 } else if (*p == ' ') {
3882 // we should be calculating columns, not just SPACE
3883 for (j = 0; *p == ' ' && j < tabstop; j++) {
3884 text_hole_delete(p, p, NO_UNDO);
3887 } else if (c == '>') {
3888 // shift right -- add tab or 8 spaces
3889 char_insert(p, '\t', ALLOW_UNDO);
3892 dot = find_line(cnt); // what line were we on
3894 end_cmd_q(); // stop adding to q
3896 case 'A': // A- append at e-o-l
3897 dot_end(); // go to e-o-l
3898 //**** fall through to ... 'a'
3899 case 'a': // a- append after current char
3904 case 'B': // B- back a blank-delimited Word
3905 case 'E': // E- end of a blank-delimited word
3906 case 'W': // W- forward a blank-delimited word
3911 if (c == 'W' || isspace(dot[dir])) {
3912 dot = skip_thing(dot, 1, dir, S_TO_WS);
3913 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3916 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3917 } while (--cmdcnt > 0);
3919 case 'C': // C- Change to e-o-l
3920 case 'D': // D- delete to e-o-l
3922 dot = dollar_line(dot); // move to before NL
3923 // copy text into a register and delete
3924 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3926 goto dc_i; // start inserting
3927 #if ENABLE_FEATURE_VI_DOT_CMD
3929 end_cmd_q(); // stop adding to q
3932 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3933 c1 = get_one_char();
3936 // c1 < 0 if the key was special. Try "g<up-arrow>"
3937 // TODO: if Unicode?
3938 buf[1] = (c1 >= 0 ? c1 : '*');
3940 not_implemented(buf);
3946 case 'G': // G- goto to a line number (default= E-O-F)
3947 dot = end - 1; // assume E-O-F
3949 dot = find_line(cmdcnt); // what line is #cmdcnt
3953 case 'H': // H- goto top line on screen
3955 if (cmdcnt > (rows - 1)) {
3956 cmdcnt = (rows - 1);
3963 case 'I': // I- insert before first non-blank
3966 //**** fall through to ... 'i'
3967 case 'i': // i- insert before current char
3968 case KEYCODE_INSERT: // Cursor Key Insert
3970 cmd_mode = 1; // start inserting
3971 undo_queue_commit(); // commit queue when cmd_mode changes
3973 case 'J': // J- join current and next lines together
3975 dot_end(); // move to NL
3976 if (dot < end - 1) { // make sure not last char in text[]
3977 #if ENABLE_FEATURE_VI_UNDO
3978 undo_push(dot, 1, UNDO_DEL);
3979 *dot++ = ' '; // replace NL with space
3980 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3985 while (isblank(*dot)) { // delete leading WS
3986 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3989 } while (--cmdcnt > 0);
3990 end_cmd_q(); // stop adding to q
3992 case 'L': // L- goto bottom line on screen
3994 if (cmdcnt > (rows - 1)) {
3995 cmdcnt = (rows - 1);
4003 case 'M': // M- goto middle line on screen
4005 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
4006 dot = next_line(dot);
4008 case 'O': // O- open a empty line above
4010 p = begin_line(dot);
4011 if (p[-1] == '\n') {
4013 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
4015 dot = char_insert(dot, '\n', ALLOW_UNDO);
4018 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
4023 case 'R': // R- continuous Replace char
4026 undo_queue_commit();
4028 case KEYCODE_DELETE:
4030 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
4032 case 'X': // X- delete char before dot
4033 case 'x': // x- delete the current char
4034 case 's': // s- substitute the current char
4039 if (dot[dir] != '\n') {
4041 dot--; // delete prev char
4042 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
4044 } while (--cmdcnt > 0);
4045 end_cmd_q(); // stop adding to q
4047 goto dc_i; // start inserting
4049 case 'Z': // Z- if modified, {write}; exit
4050 // ZZ means to save file (if necessary), then exit
4051 c1 = get_one_char();
4056 if (modified_count) {
4057 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
4058 status_line_bold("'%s' is read only", current_filename);
4061 cnt = file_write(current_filename, text, end - 1);
4064 status_line_bold("Write error: %s", strerror(errno));
4065 } else if (cnt == (end - 1 - text + 1)) {
4072 case '^': // ^- move to first non-blank on line
4076 case 'b': // b- back a word
4077 case 'e': // e- end of word
4082 if ((dot + dir) < text || (dot + dir) > end - 1)
4085 if (isspace(*dot)) {
4086 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4088 if (isalnum(*dot) || *dot == '_') {
4089 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4090 } else if (ispunct(*dot)) {
4091 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4093 } while (--cmdcnt > 0);
4095 case 'c': // c- change something
4096 case 'd': // d- delete something
4097 #if ENABLE_FEATURE_VI_YANKMARK
4098 case 'y': // y- yank something
4099 case 'Y': // Y- Yank a line
4102 int yf, ml, whole = 0;
4103 yf = YANKDEL; // assume either "c" or "d"
4104 #if ENABLE_FEATURE_VI_YANKMARK
4105 if (c == 'y' || c == 'Y')
4110 c1 = get_one_char(); // get the type of thing to delete
4111 // determine range, and whether it spans lines
4112 ml = find_range(&p, &q, c1);
4114 if (c1 == 27) { // ESC- user changed mind and wants out
4115 c = c1 = 27; // Escape- do nothing
4116 } else if (strchr("wW", c1)) {
4118 // don't include trailing WS as part of word
4119 while (isblank(*q)) {
4120 if (q <= text || q[-1] == '\n')
4125 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4126 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
4127 // partial line copy text into a register and delete
4128 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4129 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
4130 // whole line copy text into a register and delete
4131 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
4134 // could not recognize object
4135 c = c1 = 27; // error-
4141 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4142 // on the last line of file don't move to prev line
4143 if (whole && dot != (end-1)) {
4146 } else if (c == 'd') {
4152 // if CHANGING, not deleting, start inserting after the delete
4154 strcpy(buf, "Change");
4155 goto dc_i; // start inserting
4158 strcpy(buf, "Delete");
4160 #if ENABLE_FEATURE_VI_YANKMARK
4161 if (c == 'y' || c == 'Y') {
4162 strcpy(buf, "Yank");
4166 for (cnt = 0; p <= q; p++) {
4170 status_line("%s %d lines (%d chars) using [%c]",
4171 buf, cnt, strlen(reg[YDreg]), what_reg());
4173 end_cmd_q(); // stop adding to q
4177 case 'k': // k- goto prev line, same col
4178 case KEYCODE_UP: // cursor key Up
4181 dot = move_to_col(dot, ccol + offset); // try stay in same col
4182 } while (--cmdcnt > 0);
4184 case 'r': // r- replace the current char with user input
4185 c1 = get_one_char(); // get the replacement char
4187 #if ENABLE_FEATURE_VI_UNDO
4188 undo_push(dot, 1, UNDO_DEL);
4190 undo_push(dot, 1, UNDO_INS_CHAIN);
4196 end_cmd_q(); // stop adding to q
4198 case 't': // t- move to char prior to next x
4199 last_forward_char = get_one_char();
4201 if (*dot == last_forward_char)
4203 last_forward_char = 0;
4205 case 'w': // w- forward a word
4207 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4208 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4209 } else if (ispunct(*dot)) { // we are on PUNCT
4210 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4213 dot++; // move over word
4214 if (isspace(*dot)) {
4215 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4217 } while (--cmdcnt > 0);
4220 c1 = get_one_char(); // get the replacement char
4223 cnt = (rows - 2) / 2; // put dot at center
4225 cnt = rows - 2; // put dot at bottom
4226 screenbegin = begin_line(dot); // start dot at top
4227 dot_scroll(cnt, -1);
4229 case '|': // |- move to column "cmdcnt"
4230 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4232 case '~': // ~- flip the case of letters a-z -> A-Z
4234 #if ENABLE_FEATURE_VI_UNDO
4235 if (islower(*dot)) {
4236 undo_push(dot, 1, UNDO_DEL);
4237 *dot = toupper(*dot);
4238 undo_push(dot, 1, UNDO_INS_CHAIN);
4239 } else if (isupper(*dot)) {
4240 undo_push(dot, 1, UNDO_DEL);
4241 *dot = tolower(*dot);
4242 undo_push(dot, 1, UNDO_INS_CHAIN);
4245 if (islower(*dot)) {
4246 *dot = toupper(*dot);
4248 } else if (isupper(*dot)) {
4249 *dot = tolower(*dot);
4254 } while (--cmdcnt > 0);
4255 end_cmd_q(); // stop adding to q
4257 //----- The Cursor and Function Keys -----------------------------
4258 case KEYCODE_HOME: // Cursor Key Home
4261 // The Fn keys could point to do_macro which could translate them
4263 case KEYCODE_FUN1: // Function Key F1
4264 case KEYCODE_FUN2: // Function Key F2
4265 case KEYCODE_FUN3: // Function Key F3
4266 case KEYCODE_FUN4: // Function Key F4
4267 case KEYCODE_FUN5: // Function Key F5
4268 case KEYCODE_FUN6: // Function Key F6
4269 case KEYCODE_FUN7: // Function Key F7
4270 case KEYCODE_FUN8: // Function Key F8
4271 case KEYCODE_FUN9: // Function Key F9
4272 case KEYCODE_FUN10: // Function Key F10
4273 case KEYCODE_FUN11: // Function Key F11
4274 case KEYCODE_FUN12: // Function Key F12
4280 // if text[] just became empty, add back an empty line
4282 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4285 // it is OK for dot to exactly equal to end, otherwise check dot validity
4287 dot = bound_dot(dot); // make sure "dot" is valid
4289 #if ENABLE_FEATURE_VI_YANKMARK
4290 check_context(c); // update the current context
4294 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4295 cnt = dot - begin_line(dot);
4296 // Try to stay off of the Newline
4297 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4301 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4302 #if ENABLE_FEATURE_VI_CRASHME
4303 static int totalcmds = 0;
4304 static int Mp = 85; // Movement command Probability
4305 static int Np = 90; // Non-movement command Probability
4306 static int Dp = 96; // Delete command Probability
4307 static int Ip = 97; // Insert command Probability
4308 static int Yp = 98; // Yank command Probability
4309 static int Pp = 99; // Put command Probability
4310 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4311 static const char chars[20] = "\t012345 abcdABCD-=.$";
4312 static const char *const words[20] = {
4313 "this", "is", "a", "test",
4314 "broadcast", "the", "emergency", "of",
4315 "system", "quick", "brown", "fox",
4316 "jumped", "over", "lazy", "dogs",
4317 "back", "January", "Febuary", "March"
4319 static const char *const lines[20] = {
4320 "You should have received a copy of the GNU General Public License\n",
4321 "char c, cm, *cmd, *cmd1;\n",
4322 "generate a command by percentages\n",
4323 "Numbers may be typed as a prefix to some commands.\n",
4324 "Quit, discarding changes!\n",
4325 "Forced write, if permission originally not valid.\n",
4326 "In general, any ex or ed command (such as substitute or delete).\n",
4327 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4328 "Please get w/ me and I will go over it with you.\n",
4329 "The following is a list of scheduled, committed changes.\n",
4330 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4331 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4332 "Any question about transactions please contact Sterling Huxley.\n",
4333 "I will try to get back to you by Friday, December 31.\n",
4334 "This Change will be implemented on Friday.\n",
4335 "Let me know if you have problems accessing this;\n",
4336 "Sterling Huxley recently added you to the access list.\n",
4337 "Would you like to go to lunch?\n",
4338 "The last command will be automatically run.\n",
4339 "This is too much english for a computer geek.\n",
4341 static char *multilines[20] = {
4342 "You should have received a copy of the GNU General Public License\n",
4343 "char c, cm, *cmd, *cmd1;\n",
4344 "generate a command by percentages\n",
4345 "Numbers may be typed as a prefix to some commands.\n",
4346 "Quit, discarding changes!\n",
4347 "Forced write, if permission originally not valid.\n",
4348 "In general, any ex or ed command (such as substitute or delete).\n",
4349 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4350 "Please get w/ me and I will go over it with you.\n",
4351 "The following is a list of scheduled, committed changes.\n",
4352 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4353 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4354 "Any question about transactions please contact Sterling Huxley.\n",
4355 "I will try to get back to you by Friday, December 31.\n",
4356 "This Change will be implemented on Friday.\n",
4357 "Let me know if you have problems accessing this;\n",
4358 "Sterling Huxley recently added you to the access list.\n",
4359 "Would you like to go to lunch?\n",
4360 "The last command will be automatically run.\n",
4361 "This is too much english for a computer geek.\n",
4364 // create a random command to execute
4365 static void crash_dummy()
4367 static int sleeptime; // how long to pause between commands
4368 char c, cm, *cmd, *cmd1;
4369 int i, cnt, thing, rbi, startrbi, percent;
4371 // "dot" movement commands
4372 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4374 // is there already a command running?
4375 if (readbuffer[0] > 0)
4378 readbuffer[0] = 'X';
4380 sleeptime = 0; // how long to pause between commands
4381 memset(readbuffer, '\0', sizeof(readbuffer));
4382 // generate a command by percentages
4383 percent = (int) lrand48() % 100; // get a number from 0-99
4384 if (percent < Mp) { // Movement commands
4385 // available commands
4388 } else if (percent < Np) { // non-movement commands
4389 cmd = "mz<>\'\""; // available commands
4391 } else if (percent < Dp) { // Delete commands
4392 cmd = "dx"; // available commands
4394 } else if (percent < Ip) { // Inset commands
4395 cmd = "iIaAsrJ"; // available commands
4397 } else if (percent < Yp) { // Yank commands
4398 cmd = "yY"; // available commands
4400 } else if (percent < Pp) { // Put commands
4401 cmd = "pP"; // available commands
4404 // We do not know how to handle this command, try again
4408 // randomly pick one of the available cmds from "cmd[]"
4409 i = (int) lrand48() % strlen(cmd);
4411 if (strchr(":\024", cm))
4412 goto cd0; // dont allow colon or ctrl-T commands
4413 readbuffer[rbi++] = cm; // put cmd into input buffer
4415 // now we have the command-
4416 // there are 1, 2, and multi char commands
4417 // find out which and generate the rest of command as necessary
4418 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4419 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4420 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4421 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4423 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4425 readbuffer[rbi++] = c; // add movement to input buffer
4427 if (strchr("iIaAsc", cm)) { // multi-char commands
4429 // change some thing
4430 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4432 readbuffer[rbi++] = c; // add movement to input buffer
4434 thing = (int) lrand48() % 4; // what thing to insert
4435 cnt = (int) lrand48() % 10; // how many to insert
4436 for (i = 0; i < cnt; i++) {
4437 if (thing == 0) { // insert chars
4438 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4439 } else if (thing == 1) { // insert words
4440 strcat(readbuffer, words[(int) lrand48() % 20]);
4441 strcat(readbuffer, " ");
4442 sleeptime = 0; // how fast to type
4443 } else if (thing == 2) { // insert lines
4444 strcat(readbuffer, lines[(int) lrand48() % 20]);
4445 sleeptime = 0; // how fast to type
4446 } else { // insert multi-lines
4447 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4448 sleeptime = 0; // how fast to type
4451 strcat(readbuffer, "\033");
4453 readbuffer[0] = strlen(readbuffer + 1);
4457 mysleep(sleeptime); // sleep 1/100 sec
4460 // test to see if there are any errors
4461 static void crash_test()
4463 static time_t oldtim;
4470 strcat(msg, "end<text ");
4472 if (end > textend) {
4473 strcat(msg, "end>textend ");
4476 strcat(msg, "dot<text ");
4479 strcat(msg, "dot>end ");
4481 if (screenbegin < text) {
4482 strcat(msg, "screenbegin<text ");
4484 if (screenbegin > end - 1) {
4485 strcat(msg, "screenbegin>end-1 ");
4489 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4490 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4492 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4493 if (d[0] == '\n' || d[0] == '\r')
4498 if (tim >= (oldtim + 3)) {
4499 sprintf(status_buffer,
4500 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4501 totalcmds, M, N, I, D, Y, P, U, end - text + 1);