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[] = "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 */
416 #define G (*ptr_to_globals)
417 #define text (G.text )
418 #define text_size (G.text_size )
423 #define vi_setops (G.vi_setops )
424 #define editing (G.editing )
425 #define cmd_mode (G.cmd_mode )
426 #define modified_count (G.modified_count )
427 #define last_modified_count (G.last_modified_count)
428 #define save_argc (G.save_argc )
429 #define cmdcnt (G.cmdcnt )
430 #define rows (G.rows )
431 #define columns (G.columns )
432 #define crow (G.crow )
433 #define ccol (G.ccol )
434 #define offset (G.offset )
435 #define status_buffer (G.status_buffer )
436 #define have_status_msg (G.have_status_msg )
437 #define last_status_cksum (G.last_status_cksum )
438 #define current_filename (G.current_filename )
439 #define screen (G.screen )
440 #define screensize (G.screensize )
441 #define screenbegin (G.screenbegin )
442 #define tabstop (G.tabstop )
443 #define last_forward_char (G.last_forward_char )
444 #define erase_char (G.erase_char )
445 #define last_input_char (G.last_input_char )
446 #if ENABLE_FEATURE_VI_READONLY
447 #define readonly_mode (G.readonly_mode )
449 #define readonly_mode 0
451 #define adding2q (G.adding2q )
452 #define lmc_len (G.lmc_len )
454 #define ioq_start (G.ioq_start )
455 #define my_pid (G.my_pid )
456 #define last_search_pattern (G.last_search_pattern)
458 #define edit_file__cur_line (G.edit_file__cur_line)
459 #define refresh__old_offset (G.refresh__old_offset)
460 #define format_edit_status__tot (G.format_edit_status__tot)
462 #define YDreg (G.YDreg )
463 #define Ureg (G.Ureg )
464 #define mark (G.mark )
465 #define context_start (G.context_start )
466 #define context_end (G.context_end )
467 #define restart (G.restart )
468 #define term_orig (G.term_orig )
469 #define term_vi (G.term_vi )
470 #define initial_cmds (G.initial_cmds )
471 #define readbuffer (G.readbuffer )
472 #define scr_out_buf (G.scr_out_buf )
473 #define last_modifying_cmd (G.last_modifying_cmd )
474 #define get_input_line__buf (G.get_input_line__buf)
476 #if ENABLE_FEATURE_VI_UNDO
477 #define undo_stack_tail (G.undo_stack_tail )
478 # if ENABLE_FEATURE_VI_UNDO_QUEUE
479 #define undo_queue_state (G.undo_queue_state)
480 #define undo_q (G.undo_q )
481 #define undo_queue (G.undo_queue )
482 #define undo_queue_spos (G.undo_queue_spos )
486 #define INIT_G() do { \
487 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
488 last_modified_count = -1; \
489 /* "" but has space for 2 chars: */ \
490 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
494 static void edit_file(char *); // edit one file
495 static void do_cmd(int); // execute a command
496 static int next_tabstop(int);
497 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
498 static char *begin_line(char *); // return pointer to cur line B-o-l
499 static char *end_line(char *); // return pointer to cur line E-o-l
500 static char *prev_line(char *); // return pointer to prev line B-o-l
501 static char *next_line(char *); // return pointer to next line B-o-l
502 static char *end_screen(void); // get pointer to last char on screen
503 static int count_lines(char *, char *); // count line from start to stop
504 static char *find_line(int); // find begining of line #li
505 static char *move_to_col(char *, int); // move "p" to column l
506 static void dot_left(void); // move dot left- dont leave line
507 static void dot_right(void); // move dot right- dont leave line
508 static void dot_begin(void); // move dot to B-o-l
509 static void dot_end(void); // move dot to E-o-l
510 static void dot_next(void); // move dot to next line B-o-l
511 static void dot_prev(void); // move dot to prev line B-o-l
512 static void dot_scroll(int, int); // move the screen up or down
513 static void dot_skip_over_ws(void); // move dot pat WS
514 static char *bound_dot(char *); // make sure text[0] <= P < "end"
515 static char *new_screen(int, int); // malloc virtual screen memory
516 #if !ENABLE_FEATURE_VI_UNDO
517 #define char_insert(a,b,c) char_insert(a,b)
519 static char *char_insert(char *, char, int); // insert the char c at 'p'
520 // might reallocate text[]! use p += stupid_insert(p, ...),
521 // and be careful to not use pointers into potentially freed text[]!
522 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
523 static int find_range(char **, char **, char); // return pointers for an object
524 static int st_test(char *, int, int, char *); // helper for skip_thing()
525 static char *skip_thing(char *, int, int, int); // skip some object
526 static char *find_pair(char *, char); // find matching pair () [] {}
527 #if !ENABLE_FEATURE_VI_UNDO
528 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
530 static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
531 // might reallocate text[]! use p += text_hole_make(p, ...),
532 // and be careful to not use pointers into potentially freed text[]!
533 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
534 #if !ENABLE_FEATURE_VI_UNDO
535 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
537 static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
538 static void show_help(void); // display some help info
539 static void rawmode(void); // set "raw" mode on tty
540 static void cookmode(void); // return to "cooked" mode on tty
541 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
542 static int mysleep(int);
543 static int readit(void); // read (maybe cursor) key from stdin
544 static int get_one_char(void); // read 1 char from stdin
545 // file_insert might reallocate text[]!
546 static int file_insert(const char *, char *, int);
547 static int file_write(char *, char *, char *);
548 static void place_cursor(int, int);
549 static void screen_erase(void);
550 static void clear_to_eol(void);
551 static void clear_to_eos(void);
552 static void go_bottom_and_clear_to_eol(void);
553 static void standout_start(void); // send "start reverse video" sequence
554 static void standout_end(void); // send "end reverse video" sequence
555 static void flash(int); // flash the terminal screen
556 static void show_status_line(void); // put a message on the bottom line
557 static void status_line(const char *, ...); // print to status buf
558 static void status_line_bold(const char *, ...);
559 static void status_line_bold_errno(const char *fn);
560 static void not_implemented(const char *); // display "Not implemented" message
561 static int format_edit_status(void); // format file status on status line
562 static void redraw(int); // force a full screen refresh
563 static char* format_line(char* /*, int*/);
564 static void refresh(int); // update the terminal from screen[]
566 static void indicate_error(void); // use flash or beep to indicate error
567 static void Hit_Return(void);
569 #if ENABLE_FEATURE_VI_SEARCH
570 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
572 #if ENABLE_FEATURE_VI_COLON
573 static char *get_one_address(char *, int *); // get colon addr, if present
574 static char *get_address(char *, int *, int *); // get two colon addrs, if present
576 static void colon(char *); // execute the "colon" mode cmds
577 #if ENABLE_FEATURE_VI_USE_SIGNALS
578 static void winch_sig(int); // catch window size changes
579 static void suspend_sig(int); // catch ctrl-Z
580 static void catch_sig(int); // catch ctrl-C and alarm time-outs
582 #if ENABLE_FEATURE_VI_DOT_CMD
583 static void start_new_cmd_q(char); // new queue for command
584 static void end_cmd_q(void); // stop saving input chars
586 #define end_cmd_q() ((void)0)
588 #if ENABLE_FEATURE_VI_SETOPTS
589 static void showmatching(char *); // show the matching pair () [] {}
591 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
592 // might reallocate text[]! use p += string_insert(p, ...),
593 // and be careful to not use pointers into potentially freed text[]!
594 # if !ENABLE_FEATURE_VI_UNDO
595 #define string_insert(a,b,c) string_insert(a,b)
597 static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
599 #if ENABLE_FEATURE_VI_YANKMARK
600 static char *text_yank(char *, char *, int); // save copy of "p" into a register
601 static char what_reg(void); // what is letter of current YDreg
602 static void check_context(char); // remember context for '' command
604 #if ENABLE_FEATURE_VI_UNDO
605 static void flush_undo_data(void);
606 static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
607 static void undo_pop(void); // Undo the last operation
608 # if ENABLE_FEATURE_VI_UNDO_QUEUE
609 static void undo_queue_commit(void); // Flush any queued objects to the undo stack
611 # define undo_queue_commit() ((void)0)
614 #define flush_undo_data() ((void)0)
615 #define undo_queue_commit() ((void)0)
618 #if ENABLE_FEATURE_VI_CRASHME
619 static void crash_dummy();
620 static void crash_test();
621 static int crashme = 0;
624 static void write1(const char *out)
629 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
630 int vi_main(int argc, char **argv)
636 #if ENABLE_FEATURE_VI_UNDO
637 /* undo_stack_tail = NULL; - already is */
638 #if ENABLE_FEATURE_VI_UNDO_QUEUE
639 undo_queue_state = UNDO_EMPTY;
640 /* undo_q = 0; - already is */
644 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
647 #if ENABLE_FEATURE_VI_CRASHME
648 srand((long) my_pid);
650 #ifdef NO_SUCH_APPLET_YET
651 /* If we aren't "vi", we are "view" */
652 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
653 SET_READONLY_MODE(readonly_mode);
657 // autoindent is not default in vim 7.3
658 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
659 // 1- process $HOME/.exrc file (not inplemented yet)
660 // 2- process EXINIT variable from environment
661 // 3- process command line args
662 #if ENABLE_FEATURE_VI_COLON
664 char *p = getenv("EXINIT");
666 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
669 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
671 #if ENABLE_FEATURE_VI_CRASHME
676 #if ENABLE_FEATURE_VI_READONLY
677 case 'R': // Read-only flag
678 SET_READONLY_MODE(readonly_mode);
681 #if ENABLE_FEATURE_VI_COLON
682 case 'c': // cmd line vi command
684 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
696 // The argv array can be used by the ":next" and ":rewind" commands
700 //----- This is the main file handling loop --------------
703 // "Save cursor, use alternate screen buffer, clear screen"
704 write1("\033[?1049h");
706 edit_file(argv[optind]); /* param might be NULL */
707 if (++optind >= argc)
710 // "Use normal screen buffer, restore cursor"
711 write1("\033[?1049l");
712 //-----------------------------------------------------------
717 /* read text from file or create an empty buf */
718 /* will also update current_filename */
719 static int init_text_buffer(char *fn)
725 last_modified_count = -1;
726 #if ENABLE_FEATURE_VI_YANKMARK
728 memset(mark, 0, sizeof(mark));
731 /* allocate/reallocate text buffer */
734 screenbegin = dot = end = text = xzalloc(text_size);
736 if (fn != current_filename) {
737 free(current_filename);
738 current_filename = xstrdup(fn);
740 rc = file_insert(fn, text, 1);
742 // file doesnt exist. Start empty buf with dummy line
743 char_insert(text, '\n', NO_UNDO);
748 #if ENABLE_FEATURE_VI_WIN_RESIZE
749 static int query_screen_dimensions(void)
751 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
752 if (rows > MAX_SCR_ROWS)
754 if (columns > MAX_SCR_COLS)
755 columns = MAX_SCR_COLS;
759 # define query_screen_dimensions() (0)
762 static void edit_file(char *fn)
764 #if ENABLE_FEATURE_VI_YANKMARK
765 #define cur_line edit_file__cur_line
768 #if ENABLE_FEATURE_VI_USE_SIGNALS
772 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
776 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
777 #if ENABLE_FEATURE_VI_ASK_TERMINAL
778 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
780 write1("\033[999;999H" "\033[6n");
782 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
783 if ((int32_t)k == KEYCODE_CURSOR_POS) {
784 uint32_t rc = (k >> 32);
785 columns = (rc & 0x7fff);
786 if (columns > MAX_SCR_COLS)
787 columns = MAX_SCR_COLS;
788 rows = ((rc >> 16) & 0x7fff);
789 if (rows > MAX_SCR_ROWS)
794 new_screen(rows, columns); // get memory for virtual screen
795 init_text_buffer(fn);
797 #if ENABLE_FEATURE_VI_YANKMARK
798 YDreg = 26; // default Yank/Delete reg
799 Ureg = 27; // hold orig line for "U" cmd
800 mark[26] = mark[27] = text; // init "previous context"
803 last_forward_char = last_input_char = '\0';
807 #if ENABLE_FEATURE_VI_USE_SIGNALS
808 signal(SIGINT, catch_sig);
809 signal(SIGWINCH, winch_sig);
810 signal(SIGTSTP, suspend_sig);
811 sig = sigsetjmp(restart, 1);
813 screenbegin = dot = text;
817 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
820 offset = 0; // no horizontal offset
822 #if ENABLE_FEATURE_VI_DOT_CMD
824 ioq = ioq_start = NULL;
829 #if ENABLE_FEATURE_VI_COLON
834 while ((p = initial_cmds[n]) != NULL) {
844 free(initial_cmds[n]);
845 initial_cmds[n] = NULL;
850 redraw(FALSE); // dont force every col re-draw
851 //------This is the main Vi cmd handling loop -----------------------
852 while (editing > 0) {
853 #if ENABLE_FEATURE_VI_CRASHME
855 if ((end - text) > 1) {
856 crash_dummy(); // generate a random command
859 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
865 last_input_char = c = get_one_char(); // get a cmd from user
866 #if ENABLE_FEATURE_VI_YANKMARK
867 // save a copy of the current line- for the 'U" command
868 if (begin_line(dot) != cur_line) {
869 cur_line = begin_line(dot);
870 text_yank(begin_line(dot), end_line(dot), Ureg);
873 #if ENABLE_FEATURE_VI_DOT_CMD
874 // These are commands that change text[].
875 // Remember the input for the "." command
876 if (!adding2q && ioq_start == NULL
877 && cmd_mode == 0 // command mode
878 && c > '\0' // exclude NUL and non-ASCII chars
879 && c < 0x7f // (Unicode and such)
880 && strchr(modifying_cmds, c)
885 do_cmd(c); // execute the user command
887 // poll to see if there is input already waiting. if we are
888 // not able to display output fast enough to keep up, skip
889 // the display update until we catch up with input.
890 if (!readbuffer[0] && mysleep(0) == 0) {
891 // no input pending - so update output
895 #if ENABLE_FEATURE_VI_CRASHME
897 crash_test(); // test editor variables
900 //-------------------------------------------------------------------
902 go_bottom_and_clear_to_eol();
907 //----- The Colon commands -------------------------------------
908 #if ENABLE_FEATURE_VI_COLON
909 static char *get_one_address(char *p, int *addr) // get colon addr, if present
913 IF_FEATURE_VI_YANKMARK(char c;)
914 IF_FEATURE_VI_SEARCH(char *pat;)
916 *addr = -1; // assume no addr
917 if (*p == '.') { // the current line
920 *addr = count_lines(text, q);
922 #if ENABLE_FEATURE_VI_YANKMARK
923 else if (*p == '\'') { // is this a mark addr
927 if (c >= 'a' && c <= 'z') {
930 q = mark[(unsigned char) c];
931 if (q != NULL) { // is mark valid
932 *addr = count_lines(text, q);
937 #if ENABLE_FEATURE_VI_SEARCH
938 else if (*p == '/') { // a search pattern
939 q = strchrnul(++p, '/');
940 pat = xstrndup(p, q - p); // save copy of pattern
944 q = char_search(dot, pat, FORWARD, FULL);
946 *addr = count_lines(text, q);
951 else if (*p == '$') { // the last line in file
953 q = begin_line(end - 1);
954 *addr = count_lines(text, q);
955 } else if (isdigit(*p)) { // specific line number
956 sscanf(p, "%d%n", addr, &st);
959 // unrecognized address - assume -1
965 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
967 //----- get the address' i.e., 1,3 'a,'b -----
968 // get FIRST addr, if present
970 p++; // skip over leading spaces
971 if (*p == '%') { // alias for 1,$
974 *e = count_lines(text, end-1);
977 p = get_one_address(p, b);
980 if (*p == ',') { // is there a address separator
984 // get SECOND addr, if present
985 p = get_one_address(p, e);
989 p++; // skip over trailing spaces
993 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
994 static void setops(const char *args, const char *opname, int flg_no,
995 const char *short_opname, int opt)
997 const char *a = args + flg_no;
998 int l = strlen(opname) - 1; /* opname have + ' ' */
1000 // maybe strncmp? we had tons of erroneous strncasecmp's...
1001 if (strncasecmp(a, opname, l) == 0
1002 || strncasecmp(a, short_opname, 2) == 0
1012 #endif /* FEATURE_VI_COLON */
1014 // buf must be no longer than MAX_INPUT_LEN!
1015 static void colon(char *buf)
1017 #if !ENABLE_FEATURE_VI_COLON
1018 /* Simple ":cmd" handler with minimal set of commands */
1027 if (strncmp(p, "quit", cnt) == 0
1028 || strncmp(p, "q!", cnt) == 0
1030 if (modified_count && p[1] != '!') {
1031 status_line_bold("No write since last change (:%s! overrides)", p);
1037 if (strncmp(p, "write", cnt) == 0
1038 || strncmp(p, "wq", cnt) == 0
1039 || strncmp(p, "wn", cnt) == 0
1040 || (p[0] == 'x' && !p[1])
1042 cnt = file_write(current_filename, text, end - 1);
1045 status_line_bold("Write error: %s", strerror(errno));
1048 last_modified_count = -1;
1049 status_line("'%s' %dL, %dC",
1051 count_lines(text, end - 1), cnt
1053 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
1054 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
1061 if (strncmp(p, "file", cnt) == 0) {
1062 last_status_cksum = 0; // force status update
1065 if (sscanf(p, "%d", &cnt) > 0) {
1066 dot = find_line(cnt);
1073 char c, *orig_buf, *buf1, *q, *r;
1074 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
1078 // :3154 // if (-e line 3154) goto it else stay put
1079 // :4,33w! foo // write a portion of buffer to file "foo"
1080 // :w // write all of buffer to current file
1082 // :q! // quit- dont care about modified file
1083 // :'a,'z!sort -u // filter block through sort
1084 // :'f // goto mark "f"
1085 // :'fl // list literal the mark "f" line
1086 // :.r bar // read file "bar" into buffer before dot
1087 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1088 // :/xyz/ // goto the "xyz" line
1089 // :s/find/replace/ // substitute pattern "find" with "replace"
1090 // :!<cmd> // run <cmd> then return
1096 buf++; // move past the ':'
1100 q = text; // assume 1,$ for the range
1102 li = count_lines(text, end - 1);
1103 fn = current_filename;
1105 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1106 buf = get_address(buf, &b, &e);
1108 // remember orig command line
1111 // get the COMMAND into cmd[]
1113 while (*buf != '\0') {
1119 // get any ARGuments
1120 while (isblank(*buf))
1124 buf1 = last_char_is(cmd, '!');
1127 *buf1 = '\0'; // get rid of !
1130 // if there is only one addr, then the addr
1131 // is the line number of the single line the
1132 // user wants. So, reset the end
1133 // pointer to point at end of the "b" line
1134 q = find_line(b); // what line is #b
1139 // we were given two addrs. change the
1140 // end pointer to the addr given by user.
1141 r = find_line(e); // what line is #e
1145 // ------------ now look for the command ------------
1147 if (i == 0) { // :123CR goto line #123
1149 dot = find_line(b); // what line is #b
1153 #if ENABLE_FEATURE_ALLOW_EXEC
1154 else if (cmd[0] == '!') { // run a cmd
1156 // :!ls run the <cmd>
1157 go_bottom_and_clear_to_eol();
1159 retcode = system(orig_buf + 1); // run the cmd
1161 printf("\nshell returned %i\n\n", retcode);
1163 Hit_Return(); // let user see results
1166 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1167 if (b < 0) { // no addr given- use defaults
1168 b = e = count_lines(text, dot);
1170 status_line("%d", b);
1171 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1172 if (b < 0) { // no addr given- use defaults
1173 q = begin_line(dot); // assume .,. for the range
1176 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
1178 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1181 // don't edit, if the current file has been modified
1182 if (modified_count && !useforce) {
1183 status_line_bold("No write since last change (:%s! overrides)", cmd);
1187 // the user supplied a file name
1189 } else if (current_filename && current_filename[0]) {
1190 // no user supplied name- use the current filename
1191 // fn = current_filename; was set by default
1193 // no user file name, no current name- punt
1194 status_line_bold("No current filename");
1198 size = init_text_buffer(fn);
1200 #if ENABLE_FEATURE_VI_YANKMARK
1201 if (Ureg >= 0 && Ureg < 28) {
1202 free(reg[Ureg]); // free orig line reg- for 'U'
1205 if (YDreg >= 0 && YDreg < 28) {
1206 free(reg[YDreg]); // free default yank/delete register
1210 // how many lines in text[]?
1211 li = count_lines(text, end - 1);
1212 status_line("'%s'%s"
1213 IF_FEATURE_VI_READONLY("%s")
1216 (size < 0 ? " [New file]" : ""),
1217 IF_FEATURE_VI_READONLY(
1218 ((readonly_mode) ? " [Readonly]" : ""),
1220 li, (int)(end - text)
1222 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1223 if (b != -1 || e != -1) {
1224 status_line_bold("No address allowed on this command");
1228 // user wants a new filename
1229 free(current_filename);
1230 current_filename = xstrdup(args);
1232 // user wants file status info
1233 last_status_cksum = 0; // force status update
1235 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1236 // print out values of all features
1237 go_bottom_and_clear_to_eol();
1242 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1243 if (b < 0) { // no addr given- use defaults
1244 q = begin_line(dot); // assume .,. for the range
1247 go_bottom_and_clear_to_eol();
1249 for (; q <= r; q++) {
1253 c_is_no_print = (c & 0x80) && !Isprint(c);
1254 if (c_is_no_print) {
1260 } else if (c < ' ' || c == 127) {
1272 } else if (strncmp(cmd, "quit", i) == 0 // quit
1273 || strncmp(cmd, "next", i) == 0 // edit next file
1274 || strncmp(cmd, "prev", i) == 0 // edit previous file
1279 // force end of argv list
1285 // don't exit if the file been modified
1286 if (modified_count) {
1287 status_line_bold("No write since last change (:%s! overrides)", cmd);
1290 // are there other file to edit
1291 n = save_argc - optind - 1;
1292 if (*cmd == 'q' && n > 0) {
1293 status_line_bold("%d more file(s) to edit", n);
1296 if (*cmd == 'n' && n <= 0) {
1297 status_line_bold("No more files to edit");
1301 // are there previous files to edit
1303 status_line_bold("No previous files to edit");
1309 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1314 status_line_bold("No filename given");
1317 if (b < 0) { // no addr given- use defaults
1318 q = begin_line(dot); // assume "dot"
1320 // read after current line- unless user said ":0r foo"
1323 // read after last line
1327 { // dance around potentially-reallocated text[]
1328 uintptr_t ofs = q - text;
1329 size = file_insert(fn, q, 0);
1333 goto ret; // nothing was inserted
1334 // how many lines in text[]?
1335 li = count_lines(q, q + size - 1);
1337 IF_FEATURE_VI_READONLY("%s")
1340 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1344 // if the insert is before "dot" then we need to update
1348 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1349 if (modified_count && !useforce) {
1350 status_line_bold("No write since last change (:%s! overrides)", cmd);
1352 // reset the filenames to edit
1353 optind = -1; /* start from 0th file */
1356 #if ENABLE_FEATURE_VI_SET
1357 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1358 #if ENABLE_FEATURE_VI_SETOPTS
1361 i = 0; // offset into args
1362 // only blank is regarded as args delimiter. What about tab '\t'?
1363 if (!args[0] || strcasecmp(args, "all") == 0) {
1364 // print out values of all options
1365 #if ENABLE_FEATURE_VI_SETOPTS
1372 autoindent ? "" : "no",
1373 err_method ? "" : "no",
1374 ignorecase ? "" : "no",
1375 showmatch ? "" : "no",
1381 #if ENABLE_FEATURE_VI_SETOPTS
1384 if (strncmp(argp, "no", 2) == 0)
1385 i = 2; // ":set noautoindent"
1386 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1387 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1388 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1389 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1390 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1392 sscanf(argp + i+8, "%u", &t);
1393 if (t > 0 && t <= MAX_TABSTOP)
1396 argp = skip_non_whitespace(argp);
1397 argp = skip_whitespace(argp);
1399 #endif /* FEATURE_VI_SETOPTS */
1400 #endif /* FEATURE_VI_SET */
1401 #if ENABLE_FEATURE_VI_SEARCH
1402 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1403 char *F, *R, *flags;
1404 size_t len_F, len_R;
1405 int gflag; // global replace flag
1406 #if ENABLE_FEATURE_VI_UNDO
1407 int dont_chain_first_item = ALLOW_UNDO;
1410 // F points to the "find" pattern
1411 // R points to the "replace" pattern
1412 // replace the cmd line delimiters "/" with NULs
1413 c = orig_buf[1]; // what is the delimiter
1414 F = orig_buf + 2; // start of "find"
1415 R = strchr(F, c); // middle delimiter
1419 *R++ = '\0'; // terminate "find"
1420 flags = strchr(R, c);
1424 *flags++ = '\0'; // terminate "replace"
1428 if (b < 0) { // maybe :s/foo/bar/
1429 q = begin_line(dot); // start with cur line
1430 b = count_lines(text, q); // cur line number
1433 e = b; // maybe :.s/foo/bar/
1435 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1436 char *ls = q; // orig line start
1439 found = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1442 // we found the "find" pattern - delete it
1443 // For undo support, the first item should not be chained
1444 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
1445 #if ENABLE_FEATURE_VI_UNDO
1446 dont_chain_first_item = ALLOW_UNDO_CHAIN;
1448 // insert the "replace" patern
1449 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
1452 /*q += bias; - recalculated anyway */
1453 // check for "global" :s/foo/bar/g
1455 if ((found + len_R) < end_line(ls)) {
1457 goto vc4; // don't let q move past cur line
1463 #endif /* FEATURE_VI_SEARCH */
1464 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1465 status_line(BB_VER " " BB_BT);
1466 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1467 || strncmp(cmd, "wq", i) == 0
1468 || strncmp(cmd, "wn", i) == 0
1469 || (cmd[0] == 'x' && !cmd[1])
1472 //int forced = FALSE;
1474 // is there a file name to write to?
1478 #if ENABLE_FEATURE_VI_READONLY
1479 if (readonly_mode && !useforce) {
1480 status_line_bold("'%s' is read only", fn);
1484 // how many lines in text[]?
1485 li = count_lines(q, r);
1488 // if "fn" is not write-able, chmod u+w
1489 // sprintf(syscmd, "chmod u+w %s", fn);
1493 l = file_write(fn, q, r);
1494 //if (useforce && forced) {
1496 // sprintf(syscmd, "chmod u-w %s", fn);
1502 status_line_bold_errno(fn);
1504 status_line("'%s' %dL, %dC", fn, li, l);
1505 if (q == text && r == end - 1 && l == size) {
1507 last_modified_count = -1;
1509 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
1510 || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
1517 #if ENABLE_FEATURE_VI_YANKMARK
1518 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1519 if (b < 0) { // no addr given- use defaults
1520 q = begin_line(dot); // assume .,. for the range
1523 text_yank(q, r, YDreg);
1524 li = count_lines(q, r);
1525 status_line("Yank %d lines (%d chars) into [%c]",
1526 li, strlen(reg[YDreg]), what_reg());
1530 not_implemented(cmd);
1533 dot = bound_dot(dot); // make sure "dot" is valid
1535 #if ENABLE_FEATURE_VI_SEARCH
1537 status_line(":s expression missing delimiters");
1539 #endif /* FEATURE_VI_COLON */
1542 static void Hit_Return(void)
1547 write1("[Hit return to continue]");
1549 while ((c = get_one_char()) != '\n' && c != '\r')
1551 redraw(TRUE); // force redraw all
1554 static int next_tabstop(int col)
1556 return col + ((tabstop - 1) - (col % tabstop));
1559 //----- Synchronize the cursor to Dot --------------------------
1560 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1562 char *beg_cur; // begin and end of "d" line
1566 beg_cur = begin_line(d); // first char of cur line
1568 if (beg_cur < screenbegin) {
1569 // "d" is before top line on screen
1570 // how many lines do we have to move
1571 cnt = count_lines(beg_cur, screenbegin);
1573 screenbegin = beg_cur;
1574 if (cnt > (rows - 1) / 2) {
1575 // we moved too many lines. put "dot" in middle of screen
1576 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1577 screenbegin = prev_line(screenbegin);
1581 char *end_scr; // begin and end of screen
1582 end_scr = end_screen(); // last char of screen
1583 if (beg_cur > end_scr) {
1584 // "d" is after bottom line on screen
1585 // how many lines do we have to move
1586 cnt = count_lines(end_scr, beg_cur);
1587 if (cnt > (rows - 1) / 2)
1588 goto sc1; // too many lines
1589 for (ro = 0; ro < cnt - 1; ro++) {
1590 // move screen begin the same amount
1591 screenbegin = next_line(screenbegin);
1592 // now, move the end of screen
1593 end_scr = next_line(end_scr);
1594 end_scr = end_line(end_scr);
1598 // "d" is on screen- find out which row
1600 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1606 // find out what col "d" is on
1608 while (tp < d) { // drive "co" to correct column
1609 if (*tp == '\n') //vda || *tp == '\0')
1612 // handle tabs like real vi
1613 if (d == tp && cmd_mode) {
1616 co = next_tabstop(co);
1617 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1618 co++; // display as ^X, use 2 columns
1624 // "co" is the column where "dot" is.
1625 // The screen has "columns" columns.
1626 // The currently displayed columns are 0+offset -- columns+ofset
1627 // |-------------------------------------------------------------|
1629 // offset | |------- columns ----------------|
1631 // If "co" is already in this range then we do not have to adjust offset
1632 // but, we do have to subtract the "offset" bias from "co".
1633 // If "co" is outside this range then we have to change "offset".
1634 // If the first char of a line is a tab the cursor will try to stay
1635 // in column 7, but we have to set offset to 0.
1637 if (co < 0 + offset) {
1640 if (co >= columns + offset) {
1641 offset = co - columns + 1;
1643 // if the first char of the line is a tab, and "dot" is sitting on it
1644 // force offset to 0.
1645 if (d == beg_cur && *d == '\t') {
1654 //----- Text Movement Routines ---------------------------------
1655 static char *begin_line(char *p) // return pointer to first char cur line
1658 p = memrchr(text, '\n', p - text);
1666 static char *end_line(char *p) // return pointer to NL of cur line
1669 p = memchr(p, '\n', end - p - 1);
1676 static char *dollar_line(char *p) // return pointer to just before NL line
1679 // Try to stay off of the Newline
1680 if (*p == '\n' && (p - begin_line(p)) > 0)
1685 static char *prev_line(char *p) // return pointer first char prev line
1687 p = begin_line(p); // goto begining of cur line
1688 if (p > text && p[-1] == '\n')
1689 p--; // step to prev line
1690 p = begin_line(p); // goto begining of prev line
1694 static char *next_line(char *p) // return pointer first char next line
1697 if (p < end - 1 && *p == '\n')
1698 p++; // step to next line
1702 //----- Text Information Routines ------------------------------
1703 static char *end_screen(void)
1708 // find new bottom line
1710 for (cnt = 0; cnt < rows - 2; cnt++)
1716 // count line from start to stop
1717 static int count_lines(char *start, char *stop)
1722 if (stop < start) { // start and stop are backwards- reverse them
1728 stop = end_line(stop);
1729 while (start <= stop && start <= end - 1) {
1730 start = end_line(start);
1738 static char *find_line(int li) // find begining of line #li
1742 for (q = text; li > 1; li--) {
1748 //----- Dot Movement Routines ----------------------------------
1749 static void dot_left(void)
1751 undo_queue_commit();
1752 if (dot > text && dot[-1] != '\n')
1756 static void dot_right(void)
1758 undo_queue_commit();
1759 if (dot < end - 1 && *dot != '\n')
1763 static void dot_begin(void)
1765 undo_queue_commit();
1766 dot = begin_line(dot); // return pointer to first char cur line
1769 static void dot_end(void)
1771 undo_queue_commit();
1772 dot = end_line(dot); // return pointer to last char cur line
1775 static char *move_to_col(char *p, int l)
1781 while (co < l && p < end) {
1782 if (*p == '\n') //vda || *p == '\0')
1785 co = next_tabstop(co);
1786 } else if (*p < ' ' || *p == 127) {
1787 co++; // display as ^X, use 2 columns
1795 static void dot_next(void)
1797 undo_queue_commit();
1798 dot = next_line(dot);
1801 static void dot_prev(void)
1803 undo_queue_commit();
1804 dot = prev_line(dot);
1807 static void dot_scroll(int cnt, int dir)
1811 undo_queue_commit();
1812 for (; cnt > 0; cnt--) {
1815 // ctrl-Y scroll up one line
1816 screenbegin = prev_line(screenbegin);
1819 // ctrl-E scroll down one line
1820 screenbegin = next_line(screenbegin);
1823 // make sure "dot" stays on the screen so we dont scroll off
1824 if (dot < screenbegin)
1826 q = end_screen(); // find new bottom line
1828 dot = begin_line(q); // is dot is below bottom line?
1832 static void dot_skip_over_ws(void)
1835 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1839 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1841 if (p >= end && end > text) {
1852 //----- Helper Utility Routines --------------------------------
1854 //----------------------------------------------------------------
1855 //----- Char Routines --------------------------------------------
1856 /* Chars that are part of a word-
1857 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1858 * Chars that are Not part of a word (stoppers)
1859 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1860 * Chars that are WhiteSpace
1861 * TAB NEWLINE VT FF RETURN SPACE
1862 * DO NOT COUNT NEWLINE AS WHITESPACE
1865 static char *new_screen(int ro, int co)
1870 screensize = ro * co + 8;
1871 screen = xmalloc(screensize);
1872 // initialize the new screen. assume this will be a empty file.
1874 // non-existent text[] lines start with a tilde (~).
1875 for (li = 1; li < ro - 1; li++) {
1876 screen[(li * co) + 0] = '~';
1881 #if ENABLE_FEATURE_VI_SEARCH
1883 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1885 // search for pattern starting at p
1886 static char *char_search(char *p, const char *pat, int dir, int range)
1888 struct re_pattern_buffer preg;
1894 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1896 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1898 memset(&preg, 0, sizeof(preg));
1899 err = re_compile_pattern(pat, strlen(pat), &preg);
1901 status_line_bold("bad search pattern '%s': %s", pat, err);
1905 // assume a LIMITED forward search
1909 // RANGE could be negative if we are searching backwards
1919 // search for the compiled pattern, preg, in p[]
1920 // range < 0: search backward
1921 // range > 0: search forward
1923 // re_search() < 0: not found or error
1924 // re_search() >= 0: index of found pattern
1925 // struct pattern char int int int struct reg
1926 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1927 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1940 # if ENABLE_FEATURE_VI_SETOPTS
1941 static int mycmp(const char *s1, const char *s2, int len)
1944 return strncasecmp(s1, s2, len);
1946 return strncmp(s1, s2, len);
1949 # define mycmp strncmp
1952 static char *char_search(char *p, const char *pat, int dir, int range)
1958 if (dir == FORWARD) {
1959 stop = end - 1; // assume range is p..end-1
1960 if (range == LIMITED)
1961 stop = next_line(p); // range is to next line
1962 for (start = p; start < stop; start++) {
1963 if (mycmp(start, pat, len) == 0) {
1967 } else if (dir == BACK) {
1968 stop = text; // assume range is text..p
1969 if (range == LIMITED)
1970 stop = prev_line(p); // range is to prev line
1971 for (start = p - len; start >= stop; start--) {
1972 if (mycmp(start, pat, len) == 0) {
1977 // pattern not found
1983 #endif /* FEATURE_VI_SEARCH */
1985 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1987 if (c == 22) { // Is this an ctrl-V?
1988 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1989 refresh(FALSE); // show the ^
1992 #if ENABLE_FEATURE_VI_UNDO
1995 undo_push(p, 1, UNDO_INS);
1997 case ALLOW_UNDO_CHAIN:
1998 undo_push(p, 1, UNDO_INS_CHAIN);
2000 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2001 case ALLOW_UNDO_QUEUED:
2002 undo_push(p, 1, UNDO_INS_QUEUED);
2008 #endif /* ENABLE_FEATURE_VI_UNDO */
2010 } else if (c == 27) { // Is this an ESC?
2012 undo_queue_commit();
2014 end_cmd_q(); // stop adding to q
2015 last_status_cksum = 0; // force status update
2016 if ((p[-1] != '\n') && (dot > text)) {
2019 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
2022 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2025 // insert a char into text[]
2027 c = '\n'; // translate \r to \n
2028 #if ENABLE_FEATURE_VI_UNDO
2029 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2031 undo_queue_commit();
2035 undo_push(p, 1, UNDO_INS);
2037 case ALLOW_UNDO_CHAIN:
2038 undo_push(p, 1, UNDO_INS_CHAIN);
2040 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2041 case ALLOW_UNDO_QUEUED:
2042 undo_push(p, 1, UNDO_INS_QUEUED);
2048 #endif /* ENABLE_FEATURE_VI_UNDO */
2049 p += 1 + stupid_insert(p, c); // insert the char
2050 #if ENABLE_FEATURE_VI_SETOPTS
2051 if (showmatch && strchr(")]}", c) != NULL) {
2052 showmatching(p - 1);
2054 if (autoindent && c == '\n') { // auto indent the new line
2057 q = prev_line(p); // use prev line as template
2058 len = strspn(q, " \t"); // space or tab
2061 bias = text_hole_make(p, len);
2064 #if ENABLE_FEATURE_VI_UNDO
2065 undo_push(p, len, UNDO_INS);
2076 // might reallocate text[]! use p += stupid_insert(p, ...),
2077 // and be careful to not use pointers into potentially freed text[]!
2078 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2081 bias = text_hole_make(p, 1);
2087 static int find_range(char **start, char **stop, char c)
2089 char *save_dot, *p, *q, *t;
2090 int cnt, multiline = 0;
2095 if (strchr("cdy><", c)) {
2096 // these cmds operate on whole lines
2097 p = q = begin_line(p);
2098 for (cnt = 1; cnt < cmdcnt; cnt++) {
2102 } else if (strchr("^%$0bBeEfth\b\177", c)) {
2103 // These cmds operate on char positions
2104 do_cmd(c); // execute movement cmd
2106 } else if (strchr("wW", c)) {
2107 do_cmd(c); // execute movement cmd
2108 // if we are at the next word's first char
2109 // step back one char
2110 // but check the possibilities when it is true
2111 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
2112 || (ispunct(dot[-1]) && !ispunct(dot[0]))
2113 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
2114 dot--; // move back off of next word
2115 if (dot > text && *dot == '\n')
2116 dot--; // stay off NL
2118 } else if (strchr("H-k{", c)) {
2119 // these operate on multi-lines backwards
2120 q = end_line(dot); // find NL
2121 do_cmd(c); // execute movement cmd
2124 } else if (strchr("L+j}\r\n", c)) {
2125 // these operate on multi-lines forwards
2126 p = begin_line(dot);
2127 do_cmd(c); // execute movement cmd
2128 dot_end(); // find NL
2131 // nothing -- this causes any other values of c to
2132 // represent the one-character range under the
2133 // cursor. this is correct for ' ' and 'l', but
2134 // perhaps no others.
2143 // backward char movements don't include start position
2144 if (q > p && strchr("^0bBh\b\177", c)) q--;
2147 for (t = p; t <= q; t++) {
2160 static int st_test(char *p, int type, int dir, char *tested)
2170 if (type == S_BEFORE_WS) {
2172 test = (!isspace(c) || c == '\n');
2174 if (type == S_TO_WS) {
2176 test = (!isspace(c) || c == '\n');
2178 if (type == S_OVER_WS) {
2182 if (type == S_END_PUNCT) {
2186 if (type == S_END_ALNUM) {
2188 test = (isalnum(c) || c == '_');
2194 static char *skip_thing(char *p, int linecnt, int dir, int type)
2198 while (st_test(p, type, dir, &c)) {
2199 // make sure we limit search to correct number of lines
2200 if (c == '\n' && --linecnt < 1)
2202 if (dir >= 0 && p >= end - 1)
2204 if (dir < 0 && p <= text)
2206 p += dir; // move to next char
2211 // find matching char of pair () [] {}
2212 // will crash if c is not one of these
2213 static char *find_pair(char *p, const char c)
2215 const char *braces = "()[]{}";
2219 dir = strchr(braces, c) - braces;
2221 match = braces[dir];
2222 dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
2224 // look for match, count levels of pairs (( ))
2228 if (p < text || p >= end)
2231 level++; // increase pair levels
2233 level--; // reduce pair level
2235 return p; // found matching pair
2240 #if ENABLE_FEATURE_VI_SETOPTS
2241 // show the matching char of a pair, () [] {}
2242 static void showmatching(char *p)
2246 // we found half of a pair
2247 q = find_pair(p, *p); // get loc of matching char
2249 indicate_error(); // no matching char
2251 // "q" now points to matching pair
2252 save_dot = dot; // remember where we are
2253 dot = q; // go to new loc
2254 refresh(FALSE); // let the user see it
2255 mysleep(40); // give user some time
2256 dot = save_dot; // go back to old loc
2260 #endif /* FEATURE_VI_SETOPTS */
2262 #if ENABLE_FEATURE_VI_UNDO
2263 static void flush_undo_data(void)
2265 struct undo_object *undo_entry;
2267 while (undo_stack_tail) {
2268 undo_entry = undo_stack_tail;
2269 undo_stack_tail = undo_entry->prev;
2274 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2275 static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack
2277 struct undo_object *undo_entry;
2280 // UNDO_INS: insertion, undo will remove from buffer
2281 // UNDO_DEL: deleted text, undo will restore to buffer
2282 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2283 // The CHAIN operations are for handling multiple operations that the user
2284 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2285 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2286 // for the INS/DEL operation. The raw values should be equal to the values of
2287 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2289 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2290 // This undo queuing functionality groups multiple character typing or backspaces
2291 // into a single large undo object. This greatly reduces calls to malloc() for
2292 // single-character operations while typing and has the side benefit of letting
2293 // an undo operation remove chunks of text rather than a single character.
2295 case UNDO_EMPTY: // Just in case this ever happens...
2297 case UNDO_DEL_QUEUED:
2299 return; // Only queue single characters
2300 switch (undo_queue_state) {
2302 undo_queue_state = UNDO_DEL;
2304 undo_queue_spos = src;
2306 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2307 // If queue is full, dump it into an object
2308 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2309 undo_queue_commit();
2312 // Switch from storing inserted text to deleted text
2313 undo_queue_commit();
2314 undo_push(src, length, UNDO_DEL_QUEUED);
2318 case UNDO_INS_QUEUED:
2321 switch (undo_queue_state) {
2323 undo_queue_state = UNDO_INS;
2324 undo_queue_spos = src;
2326 undo_q++; // Don't need to save any data for insertions
2327 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2328 undo_queue_commit();
2331 // Switch from storing deleted text to inserted text
2332 undo_queue_commit();
2333 undo_push(src, length, UNDO_INS_QUEUED);
2339 // If undo queuing is disabled, ignore the queuing flag entirely
2340 u_type = u_type & ~UNDO_QUEUED_FLAG;
2343 // Allocate a new undo object
2344 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2345 // For UNDO_DEL objects, save deleted text
2346 if ((src + length) == end)
2348 // If this deletion empties text[], strip the newline. When the buffer becomes
2349 // zero-length, a newline is added back, which requires this to compensate.
2350 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
2351 memcpy(undo_entry->undo_text, src, length);
2353 undo_entry = xzalloc(sizeof(*undo_entry));
2355 undo_entry->length = length;
2356 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2357 if ((u_type & UNDO_USE_SPOS) != 0) {
2358 undo_entry->start = undo_queue_spos - text; // use start position from queue
2360 undo_entry->start = src - text; // use offset from start of text buffer
2362 u_type = (u_type & ~UNDO_USE_SPOS);
2364 undo_entry->start = src - text;
2366 undo_entry->u_type = u_type;
2368 // Push it on undo stack
2369 undo_entry->prev = undo_stack_tail;
2370 undo_stack_tail = undo_entry;
2374 static void undo_pop(void) // Undo the last operation
2377 char *u_start, *u_end;
2378 struct undo_object *undo_entry;
2380 // Commit pending undo queue before popping (should be unnecessary)
2381 undo_queue_commit();
2383 undo_entry = undo_stack_tail;
2384 // Check for an empty undo stack
2386 status_line("Already at oldest change");
2390 switch (undo_entry->u_type) {
2392 case UNDO_DEL_CHAIN:
2393 // make hole and put in text that was deleted; deallocate text
2394 u_start = text + undo_entry->start;
2395 text_hole_make(u_start, undo_entry->length);
2396 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2397 status_line("Undo [%d] %s %d chars at position %d",
2398 modified_count, "restored",
2399 undo_entry->length, undo_entry->start
2403 case UNDO_INS_CHAIN:
2404 // delete what was inserted
2405 u_start = undo_entry->start + text;
2406 u_end = u_start - 1 + undo_entry->length;
2407 text_hole_delete(u_start, u_end, NO_UNDO);
2408 status_line("Undo [%d] %s %d chars at position %d",
2409 modified_count, "deleted",
2410 undo_entry->length, undo_entry->start
2415 switch (undo_entry->u_type) {
2416 // If this is the end of a chain, lower modification count and refresh display
2419 dot = (text + undo_entry->start);
2422 case UNDO_DEL_CHAIN:
2423 case UNDO_INS_CHAIN:
2427 // Deallocate the undo object we just processed
2428 undo_stack_tail = undo_entry->prev;
2431 // For chained operations, continue popping all the way down the chain.
2433 undo_pop(); // Follow the undo chain if one exists
2437 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2438 static void undo_queue_commit(void) // Flush any queued objects to the undo stack
2440 // Pushes the queue object onto the undo stack
2442 // Deleted character undo events grow from the end
2443 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2445 (undo_queue_state | UNDO_USE_SPOS)
2447 undo_queue_state = UNDO_EMPTY;
2453 #endif /* ENABLE_FEATURE_VI_UNDO */
2455 // open a hole in text[]
2456 // might reallocate text[]! use p += text_hole_make(p, ...),
2457 // and be careful to not use pointers into potentially freed text[]!
2458 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2464 end += size; // adjust the new END
2465 if (end >= (text + text_size)) {
2467 text_size += end - (text + text_size) + 10240;
2468 new_text = xrealloc(text, text_size);
2469 bias = (new_text - text);
2470 screenbegin += bias;
2474 #if ENABLE_FEATURE_VI_YANKMARK
2477 for (i = 0; i < ARRAY_SIZE(mark); i++)
2484 memmove(p + size, p, end - size - p);
2485 memset(p, ' ', size); // clear new hole
2489 // close a hole in text[]
2490 // "undo" value indicates if this operation should be undo-able
2491 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2496 // move forwards, from beginning
2500 if (q < p) { // they are backward- swap them
2504 hole_size = q - p + 1;
2506 #if ENABLE_FEATURE_VI_UNDO
2511 undo_push(p, hole_size, UNDO_DEL);
2513 case ALLOW_UNDO_CHAIN:
2514 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2516 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2517 case ALLOW_UNDO_QUEUED:
2518 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2524 if (src < text || src > end)
2526 if (dest < text || dest >= end)
2530 goto thd_atend; // just delete the end of the buffer
2531 memmove(dest, src, cnt);
2533 end = end - hole_size; // adjust the new END
2535 dest = end - 1; // make sure dest in below end-1
2537 dest = end = text; // keep pointers valid
2542 // copy text into register, then delete text.
2543 // if dist <= 0, do not include, or go past, a NewLine
2545 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2549 // make sure start <= stop
2551 // they are backwards, reverse them
2557 // we cannot cross NL boundaries
2561 // dont go past a NewLine
2562 for (; p + 1 <= stop; p++) {
2564 stop = p; // "stop" just before NewLine
2570 #if ENABLE_FEATURE_VI_YANKMARK
2571 text_yank(start, stop, YDreg);
2573 if (yf == YANKDEL) {
2574 p = text_hole_delete(start, stop, undo);
2579 static void show_help(void)
2581 puts("These features are available:"
2582 #if ENABLE_FEATURE_VI_SEARCH
2583 "\n\tPattern searches with / and ?"
2585 #if ENABLE_FEATURE_VI_DOT_CMD
2586 "\n\tLast command repeat with ."
2588 #if ENABLE_FEATURE_VI_YANKMARK
2589 "\n\tLine marking with 'x"
2590 "\n\tNamed buffers with \"x"
2592 #if ENABLE_FEATURE_VI_READONLY
2593 //not implemented: "\n\tReadonly if vi is called as \"view\""
2594 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2596 #if ENABLE_FEATURE_VI_SET
2597 "\n\tSome colon mode commands with :"
2599 #if ENABLE_FEATURE_VI_SETOPTS
2600 "\n\tSettable options with \":set\""
2602 #if ENABLE_FEATURE_VI_USE_SIGNALS
2603 "\n\tSignal catching- ^C"
2604 "\n\tJob suspend and resume with ^Z"
2606 #if ENABLE_FEATURE_VI_WIN_RESIZE
2607 "\n\tAdapt to window re-sizes"
2612 #if ENABLE_FEATURE_VI_DOT_CMD
2613 static void start_new_cmd_q(char c)
2615 // get buffer for new cmd
2616 // if there is a current cmd count put it in the buffer first
2618 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2619 } else { // just save char c onto queue
2620 last_modifying_cmd[0] = c;
2626 static void end_cmd_q(void)
2628 #if ENABLE_FEATURE_VI_YANKMARK
2629 YDreg = 26; // go back to default Yank/Delete reg
2633 #endif /* FEATURE_VI_DOT_CMD */
2635 #if ENABLE_FEATURE_VI_YANKMARK \
2636 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2637 || ENABLE_FEATURE_VI_CRASHME
2638 // might reallocate text[]! use p += string_insert(p, ...),
2639 // and be careful to not use pointers into potentially freed text[]!
2640 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2646 #if ENABLE_FEATURE_VI_UNDO
2649 undo_push(p, i, UNDO_INS);
2651 case ALLOW_UNDO_CHAIN:
2652 undo_push(p, i, UNDO_INS_CHAIN);
2656 bias = text_hole_make(p, i);
2659 #if ENABLE_FEATURE_VI_YANKMARK
2662 for (cnt = 0; *s != '\0'; s++) {
2666 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2673 #if ENABLE_FEATURE_VI_YANKMARK
2674 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2677 if (cnt < 0) { // they are backwards- reverse them
2681 free(reg[dest]); // if already a yank register, free it
2682 reg[dest] = xstrndup(p, cnt + 1);
2686 static char what_reg(void)
2690 c = 'D'; // default to D-reg
2691 if (0 <= YDreg && YDreg <= 25)
2692 c = 'a' + (char) YDreg;
2700 static void check_context(char cmd)
2702 // A context is defined to be "modifying text"
2703 // Any modifying command establishes a new context.
2705 if (dot < context_start || dot > context_end) {
2706 if (strchr(modifying_cmds, cmd) != NULL) {
2707 // we are trying to modify text[]- make this the current context
2708 mark[27] = mark[26]; // move cur to prev
2709 mark[26] = dot; // move local to cur
2710 context_start = prev_line(prev_line(dot));
2711 context_end = next_line(next_line(dot));
2712 //loiter= start_loiter= now;
2717 static char *swap_context(char *p) // goto new context for '' command make this the current context
2721 // the current context is in mark[26]
2722 // the previous context is in mark[27]
2723 // only swap context if other context is valid
2724 if (text <= mark[27] && mark[27] <= end - 1) {
2726 mark[27] = mark[26];
2728 p = mark[26]; // where we are going- previous context
2729 context_start = prev_line(prev_line(prev_line(p)));
2730 context_end = next_line(next_line(next_line(p)));
2734 #endif /* FEATURE_VI_YANKMARK */
2736 //----- Set terminal attributes --------------------------------
2737 static void rawmode(void)
2739 tcgetattr(0, &term_orig);
2740 term_vi = term_orig;
2741 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG on - allow intr's
2742 term_vi.c_iflag &= (~IXON & ~ICRNL);
2743 term_vi.c_oflag &= (~ONLCR);
2744 term_vi.c_cc[VMIN] = 1;
2745 term_vi.c_cc[VTIME] = 0;
2746 erase_char = term_vi.c_cc[VERASE];
2747 tcsetattr_stdin_TCSANOW(&term_vi);
2750 static void cookmode(void)
2753 tcsetattr_stdin_TCSANOW(&term_orig);
2756 #if ENABLE_FEATURE_VI_USE_SIGNALS
2757 //----- Come here when we get a window resize signal ---------
2758 static void winch_sig(int sig UNUSED_PARAM)
2760 int save_errno = errno;
2761 // FIXME: do it in main loop!!!
2762 signal(SIGWINCH, winch_sig);
2763 query_screen_dimensions();
2764 new_screen(rows, columns); // get memory for virtual screen
2765 redraw(TRUE); // re-draw the screen
2769 //----- Come here when we get a continue signal -------------------
2770 static void cont_sig(int sig UNUSED_PARAM)
2772 int save_errno = errno;
2773 rawmode(); // terminal to "raw"
2774 last_status_cksum = 0; // force status update
2775 redraw(TRUE); // re-draw the screen
2777 signal(SIGTSTP, suspend_sig);
2778 signal(SIGCONT, SIG_DFL);
2779 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2783 //----- Come here when we get a Suspend signal -------------------
2784 static void suspend_sig(int sig UNUSED_PARAM)
2786 int save_errno = errno;
2787 go_bottom_and_clear_to_eol();
2788 cookmode(); // terminal to "cooked"
2790 signal(SIGCONT, cont_sig);
2791 signal(SIGTSTP, SIG_DFL);
2792 kill(my_pid, SIGTSTP);
2796 //----- Come here when we get a signal ---------------------------
2797 static void catch_sig(int sig)
2799 signal(SIGINT, catch_sig);
2800 siglongjmp(restart, sig);
2802 #endif /* FEATURE_VI_USE_SIGNALS */
2804 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2806 struct pollfd pfd[1];
2811 pfd[0].fd = STDIN_FILENO;
2812 pfd[0].events = POLLIN;
2813 return safe_poll(pfd, 1, hund*10) > 0;
2816 //----- IO Routines --------------------------------------------
2817 static int readit(void) // read (maybe cursor) key from stdin
2822 c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2823 if (c == -1) { // EOF/error
2824 go_bottom_and_clear_to_eol();
2825 cookmode(); // terminal to "cooked"
2826 bb_error_msg_and_die("can't read user input");
2831 //----- IO Routines --------------------------------------------
2832 static int get_one_char(void)
2836 #if ENABLE_FEATURE_VI_DOT_CMD
2838 // we are not adding to the q.
2839 // but, we may be reading from a q
2841 // there is no current q, read from STDIN
2842 c = readit(); // get the users input
2844 // there is a queue to get chars from first
2845 // careful with correct sign expansion!
2846 c = (unsigned char)*ioq++;
2848 // the end of the q, read from STDIN
2850 ioq_start = ioq = 0;
2851 c = readit(); // get the users input
2855 // adding STDIN chars to q
2856 c = readit(); // get the users input
2857 if (lmc_len >= MAX_INPUT_LEN - 1) {
2858 status_line_bold("last_modifying_cmd overrun");
2860 // add new char to q
2861 last_modifying_cmd[lmc_len++] = c;
2865 c = readit(); // get the users input
2866 #endif /* FEATURE_VI_DOT_CMD */
2870 // Get input line (uses "status line" area)
2871 static char *get_input_line(const char *prompt)
2873 // char [MAX_INPUT_LEN]
2874 #define buf get_input_line__buf
2879 strcpy(buf, prompt);
2880 last_status_cksum = 0; // force status update
2881 go_bottom_and_clear_to_eol();
2882 write1(prompt); // write out the :, /, or ? prompt
2885 while (i < MAX_INPUT_LEN) {
2887 if (c == '\n' || c == '\r' || c == 27)
2888 break; // this is end of input
2889 if (c == erase_char || c == 8 || c == 127) {
2890 // user wants to erase prev char
2892 write1("\b \b"); // erase char on screen
2893 if (i <= 0) // user backs up before b-o-l, exit
2895 } else if (c > 0 && c < 256) { // exclude Unicode
2896 // (TODO: need to handle Unicode)
2907 // might reallocate text[]!
2908 static int file_insert(const char *fn, char *p, int initial)
2912 struct stat statbuf;
2914 if (p < text || p > end) {
2915 status_line_bold("Trying to insert file outside of memory");
2919 fd = open(fn, O_RDONLY);
2922 status_line_bold_errno(fn);
2927 if (fstat(fd, &statbuf) < 0) {
2928 status_line_bold_errno(fn);
2931 if (!S_ISREG(statbuf.st_mode)) {
2932 status_line_bold("'%s' is not a regular file", fn);
2935 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2936 p += text_hole_make(p, size);
2937 cnt = full_read(fd, p, size);
2939 status_line_bold_errno(fn);
2940 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2941 } else if (cnt < size) {
2942 // There was a partial read, shrink unused space
2943 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2944 status_line_bold("can't read '%s'", fn);
2949 #if ENABLE_FEATURE_VI_READONLY
2951 && ((access(fn, W_OK) < 0) ||
2952 /* root will always have access()
2953 * so we check fileperms too */
2954 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2957 SET_READONLY_FILE(readonly_mode);
2963 static int file_write(char *fn, char *first, char *last)
2965 int fd, cnt, charcnt;
2968 status_line_bold("No current filename");
2971 /* By popular request we do not open file with O_TRUNC,
2972 * but instead ftruncate() it _after_ successful write.
2973 * Might reduce amount of data lost on power fail etc.
2975 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2978 cnt = last - first + 1;
2979 charcnt = full_write(fd, first, cnt);
2980 ftruncate(fd, charcnt);
2981 if (charcnt == cnt) {
2983 //modified_count = FALSE;
2991 //----- Terminal Drawing ---------------------------------------
2992 // The terminal is made up of 'rows' line of 'columns' columns.
2993 // classically this would be 24 x 80.
2994 // screen coordinates
3000 // 23,0 ... 23,79 <- status line
3002 //----- Move the cursor to row x col (count from 0, not 1) -------
3003 static void place_cursor(int row, int col)
3005 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
3007 if (row < 0) row = 0;
3008 if (row >= rows) row = rows - 1;
3009 if (col < 0) col = 0;
3010 if (col >= columns) col = columns - 1;
3012 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
3016 //----- Erase from cursor to end of line -----------------------
3017 static void clear_to_eol(void)
3019 write1(ESC_CLEAR2EOL);
3022 static void go_bottom_and_clear_to_eol(void)
3024 place_cursor(rows - 1, 0);
3028 //----- Erase from cursor to end of screen -----------------------
3029 static void clear_to_eos(void)
3031 write1(ESC_CLEAR2EOS);
3034 //----- Start standout mode ------------------------------------
3035 static void standout_start(void)
3037 write1(ESC_BOLD_TEXT);
3040 //----- End standout mode --------------------------------------
3041 static void standout_end(void)
3043 write1(ESC_NORM_TEXT);
3046 //----- Flash the screen --------------------------------------
3047 static void flash(int h)
3056 static void indicate_error(void)
3058 #if ENABLE_FEATURE_VI_CRASHME
3060 return; // generate a random command
3069 //----- Screen[] Routines --------------------------------------
3070 //----- Erase the Screen[] memory ------------------------------
3071 static void screen_erase(void)
3073 memset(screen, ' ', screensize); // clear new screen
3076 static int bufsum(char *buf, int count)
3079 char *e = buf + count;
3082 sum += (unsigned char) *buf++;
3086 //----- Draw the status line at bottom of the screen -------------
3087 static void show_status_line(void)
3089 int cnt = 0, cksum = 0;
3091 // either we already have an error or status message, or we
3093 if (!have_status_msg) {
3094 cnt = format_edit_status();
3095 cksum = bufsum(status_buffer, cnt);
3097 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3098 last_status_cksum = cksum; // remember if we have seen this line
3099 go_bottom_and_clear_to_eol();
3100 write1(status_buffer);
3101 if (have_status_msg) {
3102 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3104 have_status_msg = 0;
3107 have_status_msg = 0;
3109 place_cursor(crow, ccol); // put cursor back in correct place
3114 //----- format the status buffer, the bottom line of screen ------
3115 // format status buffer, with STANDOUT mode
3116 static void status_line_bold(const char *format, ...)
3120 va_start(args, format);
3121 strcpy(status_buffer, ESC_BOLD_TEXT);
3122 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3123 strcat(status_buffer, ESC_NORM_TEXT);
3126 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3129 static void status_line_bold_errno(const char *fn)
3131 status_line_bold("'%s' %s", fn, strerror(errno));
3134 // format status buffer
3135 static void status_line(const char *format, ...)
3139 va_start(args, format);
3140 vsprintf(status_buffer, format, args);
3143 have_status_msg = 1;
3146 // copy s to buf, convert unprintable
3147 static void print_literal(char *buf, const char *s)
3161 c_is_no_print = (c & 0x80) && !Isprint(c);
3162 if (c_is_no_print) {
3163 strcpy(d, ESC_NORM_TEXT);
3164 d += sizeof(ESC_NORM_TEXT)-1;
3167 if (c < ' ' || c == 0x7f) {
3169 c |= '@'; /* 0x40 */
3175 if (c_is_no_print) {
3176 strcpy(d, ESC_BOLD_TEXT);
3177 d += sizeof(ESC_BOLD_TEXT)-1;
3183 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3188 static void not_implemented(const char *s)
3190 char buf[MAX_INPUT_LEN];
3192 print_literal(buf, s);
3193 status_line_bold("\'%s\' is not implemented", buf);
3196 // show file status on status line
3197 static int format_edit_status(void)
3199 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3201 #define tot format_edit_status__tot
3203 int cur, percent, ret, trunc_at;
3205 // modified_count is now a counter rather than a flag. this
3206 // helps reduce the amount of line counting we need to do.
3207 // (this will cause a mis-reporting of modified status
3208 // once every MAXINT editing operations.)
3210 // it would be nice to do a similar optimization here -- if
3211 // we haven't done a motion that could have changed which line
3212 // we're on, then we shouldn't have to do this count_lines()
3213 cur = count_lines(text, dot);
3215 // count_lines() is expensive.
3216 // Call it only if something was changed since last time
3218 if (modified_count != last_modified_count) {
3219 tot = cur + count_lines(dot, end - 1) - 1;
3220 last_modified_count = modified_count;
3223 // current line percent
3224 // ------------- ~~ ----------
3227 percent = (100 * cur) / tot;
3233 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3234 columns : STATUS_BUFFER_LEN-1;
3236 ret = snprintf(status_buffer, trunc_at+1,
3237 #if ENABLE_FEATURE_VI_READONLY
3238 "%c %s%s%s %d/%d %d%%",
3240 "%c %s%s %d/%d %d%%",
3242 cmd_mode_indicator[cmd_mode & 3],
3243 (current_filename != NULL ? current_filename : "No file"),
3244 #if ENABLE_FEATURE_VI_READONLY
3245 (readonly_mode ? " [Readonly]" : ""),
3247 (modified_count ? " [Modified]" : ""),
3250 if (ret >= 0 && ret < trunc_at)
3251 return ret; /* it all fit */
3253 return trunc_at; /* had to truncate */
3257 //----- Force refresh of all Lines -----------------------------
3258 static void redraw(int full_screen)
3262 screen_erase(); // erase the internal screen buffer
3263 last_status_cksum = 0; // force status update
3264 refresh(full_screen); // this will redraw the entire display
3268 //----- Format a text[] line into a buffer ---------------------
3269 static char* format_line(char *src /*, int li*/)
3274 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3276 c = '~'; // char in col 0 in non-existent lines is '~'
3278 while (co < columns + tabstop) {
3279 // have we gone past the end?
3284 if ((c & 0x80) && !Isprint(c)) {
3287 if (c < ' ' || c == 0x7f) {
3291 while ((co % tabstop) != (tabstop - 1)) {
3299 c += '@'; // Ctrl-X -> 'X'
3304 // discard scrolled-off-to-the-left portion,
3305 // in tabstop-sized pieces
3306 if (ofs >= tabstop && co >= tabstop) {
3307 memmove(dest, dest + tabstop, co);
3314 // check "short line, gigantic offset" case
3317 // discard last scrolled off part
3320 // fill the rest with spaces
3322 memset(&dest[co], ' ', columns - co);
3326 //----- Refresh the changed screen lines -----------------------
3327 // Copy the source line from text[] into the buffer and note
3328 // if the current screenline is different from the new buffer.
3329 // If they differ then that line needs redrawing on the terminal.
3331 static void refresh(int full_screen)
3333 #define old_offset refresh__old_offset
3336 char *tp, *sp; // pointer into text[] and screen[]
3338 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3339 unsigned c = columns, r = rows;
3340 query_screen_dimensions();
3341 full_screen |= (c - columns) | (r - rows);
3343 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3344 tp = screenbegin; // index into text[] of top line
3346 // compare text[] to screen[] and mark screen[] lines that need updating
3347 for (li = 0; li < rows - 1; li++) {
3348 int cs, ce; // column start & end
3350 // format current text line
3351 out_buf = format_line(tp /*, li*/);
3353 // skip to the end of the current text[] line
3355 char *t = memchr(tp, '\n', end - tp);
3356 if (!t) t = end - 1;
3360 // see if there are any changes between vitual screen and out_buf
3361 changed = FALSE; // assume no change
3364 sp = &screen[li * columns]; // start of screen line
3366 // force re-draw of every single column from 0 - columns-1
3369 // compare newly formatted buffer with virtual screen
3370 // look forward for first difference between buf and screen
3371 for (; cs <= ce; cs++) {
3372 if (out_buf[cs] != sp[cs]) {
3373 changed = TRUE; // mark for redraw
3378 // look backward for last difference between out_buf and screen
3379 for (; ce >= cs; ce--) {
3380 if (out_buf[ce] != sp[ce]) {
3381 changed = TRUE; // mark for redraw
3385 // now, cs is index of first diff, and ce is index of last diff
3387 // if horz offset has changed, force a redraw
3388 if (offset != old_offset) {
3393 // make a sanity check of columns indexes
3395 if (ce > columns - 1) ce = columns - 1;
3396 if (cs > ce) { cs = 0; ce = columns - 1; }
3397 // is there a change between vitual screen and out_buf
3399 // copy changed part of buffer to virtual screen
3400 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3401 place_cursor(li, cs);
3402 // write line out to terminal
3403 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3407 place_cursor(crow, ccol);
3409 old_offset = offset;
3413 //---------------------------------------------------------------------
3414 //----- the Ascii Chart -----------------------------------------------
3416 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3417 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3418 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3419 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3420 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3421 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3422 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3423 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3424 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3425 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3426 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3427 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3428 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3429 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3430 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3431 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3432 //---------------------------------------------------------------------
3434 //----- Execute a Vi Command -----------------------------------
3435 static void do_cmd(int c)
3437 char *p, *q, *save_dot;
3443 // c1 = c; // quiet the compiler
3444 // cnt = yf = 0; // quiet the compiler
3445 // p = q = save_dot = buf; // quiet the compiler
3446 memset(buf, '\0', sizeof(buf));
3450 /* if this is a cursor key, skip these checks */
3458 case KEYCODE_PAGEUP:
3459 case KEYCODE_PAGEDOWN:
3460 case KEYCODE_DELETE:
3464 if (cmd_mode == 2) {
3465 // flip-flop Insert/Replace mode
3466 if (c == KEYCODE_INSERT)
3468 // we are 'R'eplacing the current *dot with new char
3470 // don't Replace past E-o-l
3471 cmd_mode = 1; // convert to insert
3472 undo_queue_commit();
3474 if (1 <= c || Isprint(c)) {
3476 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3477 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3482 if (cmd_mode == 1) {
3483 // hitting "Insert" twice means "R" replace mode
3484 if (c == KEYCODE_INSERT) goto dc5;
3485 // insert the char c at "dot"
3486 if (1 <= c || Isprint(c)) {
3487 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3502 #if ENABLE_FEATURE_VI_CRASHME
3503 case 0x14: // dc4 ctrl-T
3504 crashme = (crashme == 0) ? 1 : 0;
3534 default: // unrecognized command
3537 not_implemented(buf);
3538 end_cmd_q(); // stop adding to q
3539 case 0x00: // nul- ignore
3541 case 2: // ctrl-B scroll up full screen
3542 case KEYCODE_PAGEUP: // Cursor Key Page Up
3543 dot_scroll(rows - 2, -1);
3545 case 4: // ctrl-D scroll down half screen
3546 dot_scroll((rows - 2) / 2, 1);
3548 case 5: // ctrl-E scroll down one line
3551 case 6: // ctrl-F scroll down full screen
3552 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3553 dot_scroll(rows - 2, 1);
3555 case 7: // ctrl-G show current status
3556 last_status_cksum = 0; // force status update
3558 case 'h': // h- move left
3559 case KEYCODE_LEFT: // cursor key Left
3560 case 8: // ctrl-H- move left (This may be ERASE char)
3561 case 0x7f: // DEL- move left (This may be ERASE char)
3564 } while (--cmdcnt > 0);
3566 case 10: // Newline ^J
3567 case 'j': // j- goto next line, same col
3568 case KEYCODE_DOWN: // cursor key Down
3570 dot_next(); // go to next B-o-l
3571 // try stay in same col
3572 dot = move_to_col(dot, ccol + offset);
3573 } while (--cmdcnt > 0);
3575 case 12: // ctrl-L force redraw whole screen
3576 case 18: // ctrl-R force redraw
3579 //mysleep(10); // why???
3580 screen_erase(); // erase the internal screen buffer
3581 last_status_cksum = 0; // force status update
3582 refresh(TRUE); // this will redraw the entire display
3584 case 13: // Carriage Return ^M
3585 case '+': // +- goto next line
3589 } while (--cmdcnt > 0);
3591 case 21: // ctrl-U scroll up half screen
3592 dot_scroll((rows - 2) / 2, -1);
3594 case 25: // ctrl-Y scroll up one line
3600 cmd_mode = 0; // stop insrting
3601 undo_queue_commit();
3603 last_status_cksum = 0; // force status update
3605 case ' ': // move right
3606 case 'l': // move right
3607 case KEYCODE_RIGHT: // Cursor Key Right
3610 } while (--cmdcnt > 0);
3612 #if ENABLE_FEATURE_VI_YANKMARK
3613 case '"': // "- name a register to use for Delete/Yank
3614 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3615 if ((unsigned)c1 <= 25) { // a-z?
3621 case '\'': // '- goto a specific mark
3622 c1 = (get_one_char() | 0x20) - 'a';
3623 if ((unsigned)c1 <= 25) { // a-z?
3626 if (text <= q && q < end) {
3628 dot_begin(); // go to B-o-l
3631 } else if (c1 == '\'') { // goto previous context
3632 dot = swap_context(dot); // swap current and previous context
3633 dot_begin(); // go to B-o-l
3639 case 'm': // m- Mark a line
3640 // this is really stupid. If there are any inserts or deletes
3641 // between text[0] and dot then this mark will not point to the
3642 // correct location! It could be off by many lines!
3643 // Well..., at least its quick and dirty.
3644 c1 = (get_one_char() | 0x20) - 'a';
3645 if ((unsigned)c1 <= 25) { // a-z?
3646 // remember the line
3652 case 'P': // P- Put register before
3653 case 'p': // p- put register after
3656 status_line_bold("Nothing in register %c", what_reg());
3659 // are we putting whole lines or strings
3660 if (strchr(p, '\n') != NULL) {
3662 dot_begin(); // putting lines- Put above
3665 // are we putting after very last line?
3666 if (end_line(dot) == (end - 1)) {
3667 dot = end; // force dot to end of text[]
3669 dot_next(); // next line, then put before
3674 dot_right(); // move to right, can move to NL
3676 string_insert(dot, p, ALLOW_UNDO); // insert the string
3677 end_cmd_q(); // stop adding to q
3679 case 'U': // U- Undo; replace current line with original version
3680 if (reg[Ureg] != NULL) {
3681 p = begin_line(dot);
3683 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3684 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3689 #endif /* FEATURE_VI_YANKMARK */
3690 #if ENABLE_FEATURE_VI_UNDO
3691 case 'u': // u- undo last operation
3695 case '$': // $- goto end of line
3696 case KEYCODE_END: // Cursor Key End
3698 dot = end_line(dot);
3704 case '%': // %- find matching char of pair () [] {}
3705 for (q = dot; q < end && *q != '\n'; q++) {
3706 if (strchr("()[]{}", *q) != NULL) {
3707 // we found half of a pair
3708 p = find_pair(q, *q);
3720 case 'f': // f- forward to a user specified char
3721 last_forward_char = get_one_char(); // get the search char
3723 // dont separate these two commands. 'f' depends on ';'
3725 //**** fall through to ... ';'
3726 case ';': // ;- look at rest of line for last forward char
3728 if (last_forward_char == 0)
3731 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3734 if (*q == last_forward_char)
3736 } while (--cmdcnt > 0);
3738 case ',': // repeat latest 'f' in opposite direction
3739 if (last_forward_char == 0)
3743 while (q >= text && *q != '\n' && *q != last_forward_char) {
3746 if (q >= text && *q == last_forward_char)
3748 } while (--cmdcnt > 0);
3751 case '-': // -- goto prev line
3755 } while (--cmdcnt > 0);
3757 #if ENABLE_FEATURE_VI_DOT_CMD
3758 case '.': // .- repeat the last modifying command
3759 // Stuff the last_modifying_cmd back into stdin
3760 // and let it be re-executed.
3762 last_modifying_cmd[lmc_len] = 0;
3763 ioq = ioq_start = xstrdup(last_modifying_cmd);
3767 #if ENABLE_FEATURE_VI_SEARCH
3768 case '?': // /- search for a pattern
3769 case '/': // /- search for a pattern
3772 q = get_input_line(buf); // get input line- use "status line"
3773 if (q[0] && !q[1]) {
3774 if (last_search_pattern[0])
3775 last_search_pattern[0] = c;
3776 goto dc3; // if no pat re-use old pat
3778 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3779 // there is a new pat
3780 free(last_search_pattern);
3781 last_search_pattern = xstrdup(q);
3782 goto dc3; // now find the pattern
3784 // user changed mind and erased the "/"- do nothing
3786 case 'N': // N- backward search for last pattern
3787 dir = BACK; // assume BACKWARD search
3789 if (last_search_pattern[0] == '?') {
3793 goto dc4; // now search for pattern
3795 case 'n': // n- repeat search for last pattern
3796 // search rest of text[] starting at next char
3797 // if search fails return orignal "p" not the "p+1" address
3801 dir = FORWARD; // assume FORWARD search
3803 if (last_search_pattern[0] == '?') {
3808 q = char_search(p, last_search_pattern + 1, dir, FULL);
3810 dot = q; // good search, update "dot"
3814 // no pattern found between "dot" and "end"- continue at top
3819 q = char_search(p, last_search_pattern + 1, dir, FULL);
3820 if (q != NULL) { // found something
3821 dot = q; // found new pattern- goto it
3822 msg = "search hit BOTTOM, continuing at TOP";
3824 msg = "search hit TOP, continuing at BOTTOM";
3827 msg = "Pattern not found";
3831 status_line_bold("%s", msg);
3832 } while (--cmdcnt > 0);
3834 case '{': // {- move backward paragraph
3835 q = char_search(dot, "\n\n", BACK, FULL);
3836 if (q != NULL) { // found blank line
3837 dot = next_line(q); // move to next blank line
3840 case '}': // }- move forward paragraph
3841 q = char_search(dot, "\n\n", FORWARD, FULL);
3842 if (q != NULL) { // found blank line
3843 dot = next_line(q); // move to next blank line
3846 #endif /* FEATURE_VI_SEARCH */
3847 case '0': // 0- goto begining of line
3857 if (c == '0' && cmdcnt < 1) {
3858 dot_begin(); // this was a standalone zero
3860 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3863 case ':': // :- the colon mode commands
3864 p = get_input_line(":"); // get input line- use "status line"
3865 colon(p); // execute the command
3867 case '<': // <- Left shift something
3868 case '>': // >- Right shift something
3869 cnt = count_lines(text, dot); // remember what line we are on
3870 c1 = get_one_char(); // get the type of thing to delete
3871 find_range(&p, &q, c1);
3872 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3875 i = count_lines(p, q); // # of lines we are shifting
3876 for ( ; i > 0; i--, p = next_line(p)) {
3878 // shift left- remove tab or 8 spaces
3880 // shrink buffer 1 char
3881 text_hole_delete(p, p, NO_UNDO);
3882 } else if (*p == ' ') {
3883 // we should be calculating columns, not just SPACE
3884 for (j = 0; *p == ' ' && j < tabstop; j++) {
3885 text_hole_delete(p, p, NO_UNDO);
3888 } else if (c == '>') {
3889 // shift right -- add tab or 8 spaces
3890 char_insert(p, '\t', ALLOW_UNDO);
3893 dot = find_line(cnt); // what line were we on
3895 end_cmd_q(); // stop adding to q
3897 case 'A': // A- append at e-o-l
3898 dot_end(); // go to e-o-l
3899 //**** fall through to ... 'a'
3900 case 'a': // a- append after current char
3905 case 'B': // B- back a blank-delimited Word
3906 case 'E': // E- end of a blank-delimited word
3907 case 'W': // W- forward a blank-delimited word
3912 if (c == 'W' || isspace(dot[dir])) {
3913 dot = skip_thing(dot, 1, dir, S_TO_WS);
3914 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3917 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3918 } while (--cmdcnt > 0);
3920 case 'C': // C- Change to e-o-l
3921 case 'D': // D- delete to e-o-l
3923 dot = dollar_line(dot); // move to before NL
3924 // copy text into a register and delete
3925 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3927 goto dc_i; // start inserting
3928 #if ENABLE_FEATURE_VI_DOT_CMD
3930 end_cmd_q(); // stop adding to q
3933 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3934 c1 = get_one_char();
3937 buf[1] = c1; // TODO: if Unicode?
3939 not_implemented(buf);
3945 case 'G': // G- goto to a line number (default= E-O-F)
3946 dot = end - 1; // assume E-O-F
3948 dot = find_line(cmdcnt); // what line is #cmdcnt
3952 case 'H': // H- goto top line on screen
3954 if (cmdcnt > (rows - 1)) {
3955 cmdcnt = (rows - 1);
3962 case 'I': // I- insert before first non-blank
3965 //**** fall through to ... 'i'
3966 case 'i': // i- insert before current char
3967 case KEYCODE_INSERT: // Cursor Key Insert
3969 cmd_mode = 1; // start inserting
3970 undo_queue_commit(); // commit queue when cmd_mode changes
3972 case 'J': // J- join current and next lines together
3974 dot_end(); // move to NL
3975 if (dot < end - 1) { // make sure not last char in text[]
3976 #if ENABLE_FEATURE_VI_UNDO
3977 undo_push(dot, 1, UNDO_DEL);
3978 *dot++ = ' '; // replace NL with space
3979 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3984 while (isblank(*dot)) { // delete leading WS
3985 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3988 } while (--cmdcnt > 0);
3989 end_cmd_q(); // stop adding to q
3991 case 'L': // L- goto bottom line on screen
3993 if (cmdcnt > (rows - 1)) {
3994 cmdcnt = (rows - 1);
4002 case 'M': // M- goto middle line on screen
4004 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
4005 dot = next_line(dot);
4007 case 'O': // O- open a empty line above
4009 p = begin_line(dot);
4010 if (p[-1] == '\n') {
4012 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
4014 dot = char_insert(dot, '\n', ALLOW_UNDO);
4017 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
4022 case 'R': // R- continuous Replace char
4025 undo_queue_commit();
4027 case KEYCODE_DELETE:
4029 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
4031 case 'X': // X- delete char before dot
4032 case 'x': // x- delete the current char
4033 case 's': // s- substitute the current char
4038 if (dot[dir] != '\n') {
4040 dot--; // delete prev char
4041 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
4043 } while (--cmdcnt > 0);
4044 end_cmd_q(); // stop adding to q
4046 goto dc_i; // start inserting
4048 case 'Z': // Z- if modified, {write}; exit
4049 // ZZ means to save file (if necessary), then exit
4050 c1 = get_one_char();
4055 if (modified_count) {
4056 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
4057 status_line_bold("'%s' is read only", current_filename);
4060 cnt = file_write(current_filename, text, end - 1);
4063 status_line_bold("Write error: %s", strerror(errno));
4064 } else if (cnt == (end - 1 - text + 1)) {
4071 case '^': // ^- move to first non-blank on line
4075 case 'b': // b- back a word
4076 case 'e': // e- end of word
4081 if ((dot + dir) < text || (dot + dir) > end - 1)
4084 if (isspace(*dot)) {
4085 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4087 if (isalnum(*dot) || *dot == '_') {
4088 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4089 } else if (ispunct(*dot)) {
4090 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4092 } while (--cmdcnt > 0);
4094 case 'c': // c- change something
4095 case 'd': // d- delete something
4096 #if ENABLE_FEATURE_VI_YANKMARK
4097 case 'y': // y- yank something
4098 case 'Y': // Y- Yank a line
4101 int yf, ml, whole = 0;
4102 yf = YANKDEL; // assume either "c" or "d"
4103 #if ENABLE_FEATURE_VI_YANKMARK
4104 if (c == 'y' || c == 'Y')
4109 c1 = get_one_char(); // get the type of thing to delete
4110 // determine range, and whether it spans lines
4111 ml = find_range(&p, &q, c1);
4113 if (c1 == 27) { // ESC- user changed mind and wants out
4114 c = c1 = 27; // Escape- do nothing
4115 } else if (strchr("wW", c1)) {
4117 // don't include trailing WS as part of word
4118 while (isblank(*q)) {
4119 if (q <= text || q[-1] == '\n')
4124 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4125 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
4126 // partial line copy text into a register and delete
4127 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4128 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
4129 // whole line copy text into a register and delete
4130 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
4133 // could not recognize object
4134 c = c1 = 27; // error-
4140 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4141 // on the last line of file don't move to prev line
4142 if (whole && dot != (end-1)) {
4145 } else if (c == 'd') {
4151 // if CHANGING, not deleting, start inserting after the delete
4153 strcpy(buf, "Change");
4154 goto dc_i; // start inserting
4157 strcpy(buf, "Delete");
4159 #if ENABLE_FEATURE_VI_YANKMARK
4160 if (c == 'y' || c == 'Y') {
4161 strcpy(buf, "Yank");
4165 for (cnt = 0; p <= q; p++) {
4169 status_line("%s %d lines (%d chars) using [%c]",
4170 buf, cnt, strlen(reg[YDreg]), what_reg());
4172 end_cmd_q(); // stop adding to q
4176 case 'k': // k- goto prev line, same col
4177 case KEYCODE_UP: // cursor key Up
4180 dot = move_to_col(dot, ccol + offset); // try stay in same col
4181 } while (--cmdcnt > 0);
4183 case 'r': // r- replace the current char with user input
4184 c1 = get_one_char(); // get the replacement char
4186 #if ENABLE_FEATURE_VI_UNDO
4187 undo_push(dot, 1, UNDO_DEL);
4189 undo_push(dot, 1, UNDO_INS_CHAIN);
4195 end_cmd_q(); // stop adding to q
4197 case 't': // t- move to char prior to next x
4198 last_forward_char = get_one_char();
4200 if (*dot == last_forward_char)
4202 last_forward_char = 0;
4204 case 'w': // w- forward a word
4206 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4207 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4208 } else if (ispunct(*dot)) { // we are on PUNCT
4209 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4212 dot++; // move over word
4213 if (isspace(*dot)) {
4214 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4216 } while (--cmdcnt > 0);
4219 c1 = get_one_char(); // get the replacement char
4222 cnt = (rows - 2) / 2; // put dot at center
4224 cnt = rows - 2; // put dot at bottom
4225 screenbegin = begin_line(dot); // start dot at top
4226 dot_scroll(cnt, -1);
4228 case '|': // |- move to column "cmdcnt"
4229 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4231 case '~': // ~- flip the case of letters a-z -> A-Z
4233 #if ENABLE_FEATURE_VI_UNDO
4234 if (islower(*dot)) {
4235 undo_push(dot, 1, UNDO_DEL);
4236 *dot = toupper(*dot);
4237 undo_push(dot, 1, UNDO_INS_CHAIN);
4238 } else if (isupper(*dot)) {
4239 undo_push(dot, 1, UNDO_DEL);
4240 *dot = tolower(*dot);
4241 undo_push(dot, 1, UNDO_INS_CHAIN);
4244 if (islower(*dot)) {
4245 *dot = toupper(*dot);
4247 } else if (isupper(*dot)) {
4248 *dot = tolower(*dot);
4253 } while (--cmdcnt > 0);
4254 end_cmd_q(); // stop adding to q
4256 //----- The Cursor and Function Keys -----------------------------
4257 case KEYCODE_HOME: // Cursor Key Home
4260 // The Fn keys could point to do_macro which could translate them
4262 case KEYCODE_FUN1: // Function Key F1
4263 case KEYCODE_FUN2: // Function Key F2
4264 case KEYCODE_FUN3: // Function Key F3
4265 case KEYCODE_FUN4: // Function Key F4
4266 case KEYCODE_FUN5: // Function Key F5
4267 case KEYCODE_FUN6: // Function Key F6
4268 case KEYCODE_FUN7: // Function Key F7
4269 case KEYCODE_FUN8: // Function Key F8
4270 case KEYCODE_FUN9: // Function Key F9
4271 case KEYCODE_FUN10: // Function Key F10
4272 case KEYCODE_FUN11: // Function Key F11
4273 case KEYCODE_FUN12: // Function Key F12
4279 // if text[] just became empty, add back an empty line
4281 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4284 // it is OK for dot to exactly equal to end, otherwise check dot validity
4286 dot = bound_dot(dot); // make sure "dot" is valid
4288 #if ENABLE_FEATURE_VI_YANKMARK
4289 check_context(c); // update the current context
4293 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4294 cnt = dot - begin_line(dot);
4295 // Try to stay off of the Newline
4296 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4300 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4301 #if ENABLE_FEATURE_VI_CRASHME
4302 static int totalcmds = 0;
4303 static int Mp = 85; // Movement command Probability
4304 static int Np = 90; // Non-movement command Probability
4305 static int Dp = 96; // Delete command Probability
4306 static int Ip = 97; // Insert command Probability
4307 static int Yp = 98; // Yank command Probability
4308 static int Pp = 99; // Put command Probability
4309 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4310 static const char chars[20] = "\t012345 abcdABCD-=.$";
4311 static const char *const words[20] = {
4312 "this", "is", "a", "test",
4313 "broadcast", "the", "emergency", "of",
4314 "system", "quick", "brown", "fox",
4315 "jumped", "over", "lazy", "dogs",
4316 "back", "January", "Febuary", "March"
4318 static const char *const lines[20] = {
4319 "You should have received a copy of the GNU General Public License\n",
4320 "char c, cm, *cmd, *cmd1;\n",
4321 "generate a command by percentages\n",
4322 "Numbers may be typed as a prefix to some commands.\n",
4323 "Quit, discarding changes!\n",
4324 "Forced write, if permission originally not valid.\n",
4325 "In general, any ex or ed command (such as substitute or delete).\n",
4326 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4327 "Please get w/ me and I will go over it with you.\n",
4328 "The following is a list of scheduled, committed changes.\n",
4329 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4330 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4331 "Any question about transactions please contact Sterling Huxley.\n",
4332 "I will try to get back to you by Friday, December 31.\n",
4333 "This Change will be implemented on Friday.\n",
4334 "Let me know if you have problems accessing this;\n",
4335 "Sterling Huxley recently added you to the access list.\n",
4336 "Would you like to go to lunch?\n",
4337 "The last command will be automatically run.\n",
4338 "This is too much english for a computer geek.\n",
4340 static char *multilines[20] = {
4341 "You should have received a copy of the GNU General Public License\n",
4342 "char c, cm, *cmd, *cmd1;\n",
4343 "generate a command by percentages\n",
4344 "Numbers may be typed as a prefix to some commands.\n",
4345 "Quit, discarding changes!\n",
4346 "Forced write, if permission originally not valid.\n",
4347 "In general, any ex or ed command (such as substitute or delete).\n",
4348 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4349 "Please get w/ me and I will go over it with you.\n",
4350 "The following is a list of scheduled, committed changes.\n",
4351 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4352 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4353 "Any question about transactions please contact Sterling Huxley.\n",
4354 "I will try to get back to you by Friday, December 31.\n",
4355 "This Change will be implemented on Friday.\n",
4356 "Let me know if you have problems accessing this;\n",
4357 "Sterling Huxley recently added you to the access list.\n",
4358 "Would you like to go to lunch?\n",
4359 "The last command will be automatically run.\n",
4360 "This is too much english for a computer geek.\n",
4363 // create a random command to execute
4364 static void crash_dummy()
4366 static int sleeptime; // how long to pause between commands
4367 char c, cm, *cmd, *cmd1;
4368 int i, cnt, thing, rbi, startrbi, percent;
4370 // "dot" movement commands
4371 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4373 // is there already a command running?
4374 if (readbuffer[0] > 0)
4377 readbuffer[0] = 'X';
4379 sleeptime = 0; // how long to pause between commands
4380 memset(readbuffer, '\0', sizeof(readbuffer));
4381 // generate a command by percentages
4382 percent = (int) lrand48() % 100; // get a number from 0-99
4383 if (percent < Mp) { // Movement commands
4384 // available commands
4387 } else if (percent < Np) { // non-movement commands
4388 cmd = "mz<>\'\""; // available commands
4390 } else if (percent < Dp) { // Delete commands
4391 cmd = "dx"; // available commands
4393 } else if (percent < Ip) { // Inset commands
4394 cmd = "iIaAsrJ"; // available commands
4396 } else if (percent < Yp) { // Yank commands
4397 cmd = "yY"; // available commands
4399 } else if (percent < Pp) { // Put commands
4400 cmd = "pP"; // available commands
4403 // We do not know how to handle this command, try again
4407 // randomly pick one of the available cmds from "cmd[]"
4408 i = (int) lrand48() % strlen(cmd);
4410 if (strchr(":\024", cm))
4411 goto cd0; // dont allow colon or ctrl-T commands
4412 readbuffer[rbi++] = cm; // put cmd into input buffer
4414 // now we have the command-
4415 // there are 1, 2, and multi char commands
4416 // find out which and generate the rest of command as necessary
4417 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4418 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4419 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4420 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4422 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4424 readbuffer[rbi++] = c; // add movement to input buffer
4426 if (strchr("iIaAsc", cm)) { // multi-char commands
4428 // change some thing
4429 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4431 readbuffer[rbi++] = c; // add movement to input buffer
4433 thing = (int) lrand48() % 4; // what thing to insert
4434 cnt = (int) lrand48() % 10; // how many to insert
4435 for (i = 0; i < cnt; i++) {
4436 if (thing == 0) { // insert chars
4437 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4438 } else if (thing == 1) { // insert words
4439 strcat(readbuffer, words[(int) lrand48() % 20]);
4440 strcat(readbuffer, " ");
4441 sleeptime = 0; // how fast to type
4442 } else if (thing == 2) { // insert lines
4443 strcat(readbuffer, lines[(int) lrand48() % 20]);
4444 sleeptime = 0; // how fast to type
4445 } else { // insert multi-lines
4446 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4447 sleeptime = 0; // how fast to type
4450 strcat(readbuffer, "\033");
4452 readbuffer[0] = strlen(readbuffer + 1);
4456 mysleep(sleeptime); // sleep 1/100 sec
4459 // test to see if there are any errors
4460 static void crash_test()
4462 static time_t oldtim;
4469 strcat(msg, "end<text ");
4471 if (end > textend) {
4472 strcat(msg, "end>textend ");
4475 strcat(msg, "dot<text ");
4478 strcat(msg, "dot>end ");
4480 if (screenbegin < text) {
4481 strcat(msg, "screenbegin<text ");
4483 if (screenbegin > end - 1) {
4484 strcat(msg, "screenbegin>end-1 ");
4488 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4489 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4491 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4492 if (d[0] == '\n' || d[0] == '\r')
4497 if (tim >= (oldtim + 3)) {
4498 sprintf(status_buffer,
4499 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4500 totalcmds, M, N, I, D, Y, P, U, end - text + 1);