1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
11 * $HOME/.exrc and ./.exrc
12 * add magic to search /foo.*bar
15 * if mark[] values were line numbers rather than pointers
16 * it would be easier to change the mark when add/delete lines
17 * More intelligence in refresh()
18 * ":r !cmd" and "!cmd" to filter text through an external command
19 * An "ex" line oriented mode- maybe using "cmdedit"
22 //config: bool "vi (23 kb)"
25 //config: 'vi' is a text editor. More specifically, it is the One True
26 //config: text editor <grin>. It does, however, have a rather steep
27 //config: learning curve. If you are not already comfortable with 'vi'
28 //config: you may wish to use something else.
30 //config:config FEATURE_VI_MAX_LEN
31 //config: int "Maximum screen width"
32 //config: range 256 16384
33 //config: default 4096
34 //config: depends on VI
36 //config: Contrary to what you may think, this is not eating much.
37 //config: Make it smaller than 4k only if you are very limited on memory.
39 //config:config FEATURE_VI_8BIT
40 //config: bool "Allow to display 8-bit chars (otherwise shows dots)"
42 //config: depends on VI
44 //config: If your terminal can display characters with high bit set,
45 //config: you may want to enable this. Note: vi is not Unicode-capable.
46 //config: If your terminal combines several 8-bit bytes into one character
47 //config: (as in Unicode mode), this will not work properly.
49 //config:config FEATURE_VI_COLON
50 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
52 //config: depends on VI
54 //config: Enable a limited set of colon commands. This does not
55 //config: provide an "ex" mode.
57 //config:config FEATURE_VI_YANKMARK
58 //config: bool "Enable yank/put commands and mark cmds"
60 //config: depends on VI
62 //config: This enables you to use yank and put, as well as mark.
64 //config:config FEATURE_VI_SEARCH
65 //config: bool "Enable search and replace cmds"
67 //config: depends on VI
69 //config: Select this if you wish to be able to do search and replace.
71 //config:config FEATURE_VI_REGEX_SEARCH
72 //config: bool "Enable regex in search and replace"
73 //config: default n # Uses GNU regex, which may be unavailable. FIXME
74 //config: depends on FEATURE_VI_SEARCH
76 //config: Use extended regex search.
78 //config:config FEATURE_VI_USE_SIGNALS
79 //config: bool "Catch signals"
81 //config: depends on VI
83 //config: Selecting this option will make vi signal aware. This will support
84 //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
86 //config:config FEATURE_VI_DOT_CMD
87 //config: bool "Remember previous cmd and \".\" cmd"
89 //config: depends on VI
91 //config: Make vi remember the last command and be able to repeat it.
93 //config:config FEATURE_VI_READONLY
94 //config: bool "Enable -R option and \"view\" mode"
96 //config: depends on VI
98 //config: Enable the read-only command line option, which allows the user to
99 //config: open a file in read-only mode.
101 //config:config FEATURE_VI_SETOPTS
102 //config: bool "Enable settable options, ai ic showmatch"
104 //config: depends on VI
106 //config: Enable the editor to set some (ai, ic, showmatch) options.
108 //config:config FEATURE_VI_SET
109 //config: bool "Support :set"
111 //config: depends on VI
113 //config:config FEATURE_VI_WIN_RESIZE
114 //config: bool "Handle window resize"
116 //config: depends on VI
118 //config: Behave nicely with terminals that get resized.
120 //config:config FEATURE_VI_ASK_TERMINAL
121 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
123 //config: depends on VI
125 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
126 //config: this option makes vi perform a last-ditch effort to find it:
127 //config: position cursor to 999,999 and ask terminal to report real
128 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
129 //config: This is not clean but helps a lot on serial lines and such.
131 //config:config FEATURE_VI_UNDO
132 //config: bool "Support undo command \"u\""
134 //config: depends on VI
136 //config: Support the 'u' command to undo insertion, deletion, and replacement
139 //config:config FEATURE_VI_UNDO_QUEUE
140 //config: bool "Enable undo operation queuing"
142 //config: depends on FEATURE_VI_UNDO
144 //config: The vi undo functions can use an intermediate queue to greatly lower
145 //config: malloc() calls and overhead. When the maximum size of this queue is
146 //config: reached, the contents of the queue are committed to the undo stack.
147 //config: This increases the size of the undo code and allows some undo
148 //config: operations (especially un-typing/backspacing) to be far more useful.
150 //config:config FEATURE_VI_UNDO_QUEUE_MAX
151 //config: int "Maximum undo character queue size"
152 //config: default 256
153 //config: range 32 65536
154 //config: depends on FEATURE_VI_UNDO_QUEUE
156 //config: This option sets the number of bytes used at runtime for the queue.
157 //config: Smaller values will create more undo objects and reduce the amount
158 //config: of typed or backspaced characters that are grouped into one undo
159 //config: operation; larger values increase the potential size of each undo
160 //config: and will generally malloc() larger objects and less frequently.
161 //config: Unless you want more (or less) frequent "undo points" while typing,
162 //config: you should probably leave this unchanged.
164 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
166 //kbuild:lib-$(CONFIG_VI) += vi.o
168 //usage:#define vi_trivial_usage
169 //usage: "[OPTIONS] [FILE]..."
170 //usage:#define vi_full_usage "\n\n"
171 //usage: "Edit FILE\n"
172 //usage: IF_FEATURE_VI_COLON(
173 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
175 //usage: IF_FEATURE_VI_READONLY(
176 //usage: "\n -R Read-only"
178 //usage: "\n -H List available features"
181 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
182 #if ENABLE_FEATURE_VI_REGEX_SEARCH
186 /* the CRASHME code is unmaintained, and doesn't currently build */
187 #define ENABLE_FEATURE_VI_CRASHME 0
190 #if ENABLE_LOCALE_SUPPORT
192 #if ENABLE_FEATURE_VI_8BIT
193 //FIXME: this does not work properly for Unicode anyway
194 # define Isprint(c) (isprint)(c)
196 # define Isprint(c) isprint_asciionly(c)
201 /* 0x9b is Meta-ESC */
202 #if ENABLE_FEATURE_VI_8BIT
203 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
205 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
212 MAX_TABSTOP = 32, // sanity limit
213 // User input len. Need not be extra big.
214 // Lines in file being edited *can* be bigger than this.
216 // Sanity limits. We have only one buffer of this size.
217 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
218 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
221 /* VT102 ESC sequences.
222 * See "Xterm Control Sequences"
223 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
226 /* Inverse/Normal text */
227 #define ESC_BOLD_TEXT ESC"[7m"
228 #define ESC_NORM_TEXT ESC"[m"
230 #define ESC_BELL "\007"
231 /* Clear-to-end-of-line */
232 #define ESC_CLEAR2EOL ESC"[K"
233 /* Clear-to-end-of-screen.
234 * (We use default param here.
235 * Full sequence is "ESC [ <num> J",
236 * <num> is 0/1/2 = "erase below/above/all".)
238 #define ESC_CLEAR2EOS ESC"[J"
239 /* Cursor to given coordinate (1,1: top left) */
240 #define ESC_SET_CURSOR_POS ESC"[%u;%uH"
242 ///* Cursor up and down */
243 //#define ESC_CURSOR_UP ESC"[A"
244 //#define ESC_CURSOR_DOWN "\n"
246 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
247 // cmds modifying text[]
248 // vda: removed "aAiIs" as they switch us into insert mode
249 // and remembering input for replay after them makes no sense
250 static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
256 FORWARD = 1, // code depends on "1" for array index
257 BACK = -1, // code depends on "-1" for array index
258 LIMITED = 0, // char_search() only current line
259 FULL = 1, // char_search() to the end/beginning of entire text
261 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
262 S_TO_WS = 2, // used in skip_thing() for moving "dot"
263 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
264 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
265 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
269 /* vi.c expects chars to be unsigned. */
270 /* busybox build system provides that, but it's better */
271 /* to audit and fix the source */
274 /* many references - keep near the top of globals */
275 char *text, *end; // pointers to the user data in memory
276 char *dot; // where all the action takes place
277 int text_size; // size of the allocated buffer
281 #define VI_AUTOINDENT 1
282 #define VI_SHOWMATCH 2
283 #define VI_IGNORECASE 4
284 #define VI_ERR_METHOD 8
285 #define autoindent (vi_setops & VI_AUTOINDENT)
286 #define showmatch (vi_setops & VI_SHOWMATCH )
287 #define ignorecase (vi_setops & VI_IGNORECASE)
288 /* indicate error with beep or flash */
289 #define err_method (vi_setops & VI_ERR_METHOD)
291 #if ENABLE_FEATURE_VI_READONLY
292 smallint readonly_mode;
293 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
294 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
295 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
297 #define SET_READONLY_FILE(flags) ((void)0)
298 #define SET_READONLY_MODE(flags) ((void)0)
299 #define UNSET_READONLY_FILE(flags) ((void)0)
302 smallint editing; // >0 while we are editing a file
303 // [code audit says "can be 0, 1 or 2 only"]
304 smallint cmd_mode; // 0=command 1=insert 2=replace
305 int modified_count; // buffer contents changed if !0
306 int last_modified_count; // = -1;
307 int save_argc; // how many file names on cmd line
308 int cmdcnt; // repetition count
309 unsigned rows, columns; // the terminal screen is this size
310 #if ENABLE_FEATURE_VI_ASK_TERMINAL
311 int get_rowcol_error;
313 int crow, ccol; // cursor is on Crow x Ccol
314 int offset; // chars scrolled off the screen to the left
315 int have_status_msg; // is default edit status needed?
316 // [don't make smallint!]
317 int last_status_cksum; // hash of current status line
318 char *current_filename;
319 char *screenbegin; // index into text[], of top line on the screen
320 char *screen; // pointer to the virtual screen buffer
321 int screensize; // and its size
323 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
324 char erase_char; // the users erase character
325 char last_input_char; // last char read from user
327 #if ENABLE_FEATURE_VI_DOT_CMD
328 smallint adding2q; // are we currently adding user input to q
329 int lmc_len; // length of last_modifying_cmd
330 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
332 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
335 #if ENABLE_FEATURE_VI_SEARCH
336 char *last_search_pattern; // last pattern from a '/' or '?' search
340 #if ENABLE_FEATURE_VI_YANKMARK
341 char *edit_file__cur_line;
343 int refresh__old_offset;
344 int format_edit_status__tot;
346 /* a few references only */
347 #if ENABLE_FEATURE_VI_YANKMARK
348 int YDreg, Ureg; // default delete register and orig line for "U"
349 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
350 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
351 char *context_start, *context_end;
353 #if ENABLE_FEATURE_VI_USE_SIGNALS
354 sigjmp_buf restart; // catch_sig()
356 struct termios term_orig; // remember what the cooked mode was
357 #if ENABLE_FEATURE_VI_COLON
358 char *initial_cmds[3]; // currently 2 entries, NULL terminated
360 // Should be just enough to hold a key sequence,
361 // but CRASHME mode uses it as generated command buffer too
362 #if ENABLE_FEATURE_VI_CRASHME
363 char readbuffer[128];
365 char readbuffer[KEYCODE_BUFFER_SIZE];
367 #define STATUS_BUFFER_LEN 200
368 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
369 #if ENABLE_FEATURE_VI_DOT_CMD
370 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
372 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
374 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
375 #if ENABLE_FEATURE_VI_UNDO
376 // undo_push() operations
379 #define UNDO_INS_CHAIN 2
380 #define UNDO_DEL_CHAIN 3
381 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
382 #define UNDO_QUEUED_FLAG 4
383 #define UNDO_INS_QUEUED 4
384 #define UNDO_DEL_QUEUED 5
385 #define UNDO_USE_SPOS 32
386 #define UNDO_EMPTY 64
387 // Pass-through flags for functions that can be undone
390 #define ALLOW_UNDO_CHAIN 2
391 # if ENABLE_FEATURE_VI_UNDO_QUEUE
392 #define ALLOW_UNDO_QUEUED 3
393 char undo_queue_state;
395 char *undo_queue_spos; // Start position of queued operation
396 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
398 // If undo queuing disabled, don't invoke the missing queue logic
399 #define ALLOW_UNDO_QUEUED 1
403 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
404 int start; // Offset where the data should be restored/deleted
405 int length; // total data size
406 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
407 char undo_text[1]; // text that was deleted (if deletion)
409 #endif /* ENABLE_FEATURE_VI_UNDO */
411 #define G (*ptr_to_globals)
412 #define text (G.text )
413 #define text_size (G.text_size )
418 #define vi_setops (G.vi_setops )
419 #define editing (G.editing )
420 #define cmd_mode (G.cmd_mode )
421 #define modified_count (G.modified_count )
422 #define last_modified_count (G.last_modified_count)
423 #define save_argc (G.save_argc )
424 #define cmdcnt (G.cmdcnt )
425 #define rows (G.rows )
426 #define columns (G.columns )
427 #define crow (G.crow )
428 #define ccol (G.ccol )
429 #define offset (G.offset )
430 #define status_buffer (G.status_buffer )
431 #define have_status_msg (G.have_status_msg )
432 #define last_status_cksum (G.last_status_cksum )
433 #define current_filename (G.current_filename )
434 #define screen (G.screen )
435 #define screensize (G.screensize )
436 #define screenbegin (G.screenbegin )
437 #define tabstop (G.tabstop )
438 #define last_forward_char (G.last_forward_char )
439 #define erase_char (G.erase_char )
440 #define last_input_char (G.last_input_char )
441 #if ENABLE_FEATURE_VI_READONLY
442 #define readonly_mode (G.readonly_mode )
444 #define readonly_mode 0
446 #define adding2q (G.adding2q )
447 #define lmc_len (G.lmc_len )
449 #define ioq_start (G.ioq_start )
450 #define my_pid (G.my_pid )
451 #define last_search_pattern (G.last_search_pattern)
453 #define edit_file__cur_line (G.edit_file__cur_line)
454 #define refresh__old_offset (G.refresh__old_offset)
455 #define format_edit_status__tot (G.format_edit_status__tot)
457 #define YDreg (G.YDreg )
458 #define Ureg (G.Ureg )
459 #define mark (G.mark )
460 #define context_start (G.context_start )
461 #define context_end (G.context_end )
462 #define restart (G.restart )
463 #define term_orig (G.term_orig )
464 #define initial_cmds (G.initial_cmds )
465 #define readbuffer (G.readbuffer )
466 #define scr_out_buf (G.scr_out_buf )
467 #define last_modifying_cmd (G.last_modifying_cmd )
468 #define get_input_line__buf (G.get_input_line__buf)
470 #if ENABLE_FEATURE_VI_UNDO
471 #define undo_stack_tail (G.undo_stack_tail )
472 # if ENABLE_FEATURE_VI_UNDO_QUEUE
473 #define undo_queue_state (G.undo_queue_state)
474 #define undo_q (G.undo_q )
475 #define undo_queue (G.undo_queue )
476 #define undo_queue_spos (G.undo_queue_spos )
480 #define INIT_G() do { \
481 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
482 last_modified_count = -1; \
483 /* "" but has space for 2 chars: */ \
484 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
488 static void edit_file(char *); // edit one file
489 static void do_cmd(int); // execute a command
490 static int next_tabstop(int);
491 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
492 static char *begin_line(char *); // return pointer to cur line B-o-l
493 static char *end_line(char *); // return pointer to cur line E-o-l
494 static char *prev_line(char *); // return pointer to prev line B-o-l
495 static char *next_line(char *); // return pointer to next line B-o-l
496 static char *end_screen(void); // get pointer to last char on screen
497 static int count_lines(char *, char *); // count line from start to stop
498 static char *find_line(int); // find beginning of line #li
499 static char *move_to_col(char *, int); // move "p" to column l
500 static void dot_left(void); // move dot left- dont leave line
501 static void dot_right(void); // move dot right- dont leave line
502 static void dot_begin(void); // move dot to B-o-l
503 static void dot_end(void); // move dot to E-o-l
504 static void dot_next(void); // move dot to next line B-o-l
505 static void dot_prev(void); // move dot to prev line B-o-l
506 static void dot_scroll(int, int); // move the screen up or down
507 static void dot_skip_over_ws(void); // move dot pat WS
508 static char *bound_dot(char *); // make sure text[0] <= P < "end"
509 static char *new_screen(int, int); // malloc virtual screen memory
510 #if !ENABLE_FEATURE_VI_UNDO
511 #define char_insert(a,b,c) char_insert(a,b)
513 static char *char_insert(char *, char, int); // insert the char c at 'p'
514 // might reallocate text[]! use p += stupid_insert(p, ...),
515 // and be careful to not use pointers into potentially freed text[]!
516 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
517 static int find_range(char **, char **, char); // return pointers for an object
518 static int st_test(char *, int, int, char *); // helper for skip_thing()
519 static char *skip_thing(char *, int, int, int); // skip some object
520 static char *find_pair(char *, char); // find matching pair () [] {}
521 #if !ENABLE_FEATURE_VI_UNDO
522 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
524 static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
525 // might reallocate text[]! use p += text_hole_make(p, ...),
526 // and be careful to not use pointers into potentially freed text[]!
527 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
528 #if !ENABLE_FEATURE_VI_UNDO
529 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
531 static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
532 static void show_help(void); // display some help info
533 static void rawmode(void); // set "raw" mode on tty
534 static void cookmode(void); // return to "cooked" mode on tty
535 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
536 static int mysleep(int);
537 static int readit(void); // read (maybe cursor) key from stdin
538 static int get_one_char(void); // read 1 char from stdin
539 // file_insert might reallocate text[]!
540 static int file_insert(const char *, char *, int);
541 static int file_write(char *, char *, char *);
542 static void place_cursor(int, int);
543 static void screen_erase(void);
544 static void clear_to_eol(void);
545 static void clear_to_eos(void);
546 static void go_bottom_and_clear_to_eol(void);
547 static void standout_start(void); // send "start reverse video" sequence
548 static void standout_end(void); // send "end reverse video" sequence
549 static void flash(int); // flash the terminal screen
550 static void show_status_line(void); // put a message on the bottom line
551 static void status_line(const char *, ...); // print to status buf
552 static void status_line_bold(const char *, ...);
553 static void status_line_bold_errno(const char *fn);
554 static void not_implemented(const char *); // display "Not implemented" message
555 static int format_edit_status(void); // format file status on status line
556 static void redraw(int); // force a full screen refresh
557 static char* format_line(char* /*, int*/);
558 static void refresh(int); // update the terminal from screen[]
560 static void indicate_error(void); // use flash or beep to indicate error
561 static void Hit_Return(void);
563 #if ENABLE_FEATURE_VI_SEARCH
564 static char *char_search(char *, const char *, int); // search for pattern starting at p
566 #if ENABLE_FEATURE_VI_COLON
567 static char *get_one_address(char *, int *); // get colon addr, if present
568 static char *get_address(char *, int *, int *); // get two colon addrs, if present
570 static void colon(char *); // execute the "colon" mode cmds
571 #if ENABLE_FEATURE_VI_USE_SIGNALS
572 static void winch_sig(int); // catch window size changes
573 static void suspend_sig(int); // catch ctrl-Z
574 static void catch_sig(int); // catch ctrl-C and alarm time-outs
576 #if ENABLE_FEATURE_VI_DOT_CMD
577 static void start_new_cmd_q(char); // new queue for command
578 static void end_cmd_q(void); // stop saving input chars
580 #define end_cmd_q() ((void)0)
582 #if ENABLE_FEATURE_VI_SETOPTS
583 static void showmatching(char *); // show the matching pair () [] {}
585 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
586 // might reallocate text[]! use p += string_insert(p, ...),
587 // and be careful to not use pointers into potentially freed text[]!
588 # if !ENABLE_FEATURE_VI_UNDO
589 #define string_insert(a,b,c) string_insert(a,b)
591 static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
593 #if ENABLE_FEATURE_VI_YANKMARK
594 static char *text_yank(char *, char *, int); // save copy of "p" into a register
595 static char what_reg(void); // what is letter of current YDreg
596 static void check_context(char); // remember context for '' command
598 #if ENABLE_FEATURE_VI_UNDO
599 static void flush_undo_data(void);
600 static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
601 static void undo_push_insert(char *, int, int); // convenience function
602 static void undo_pop(void); // Undo the last operation
603 # if ENABLE_FEATURE_VI_UNDO_QUEUE
604 static void undo_queue_commit(void); // Flush any queued objects to the undo stack
606 # define undo_queue_commit() ((void)0)
609 #define flush_undo_data() ((void)0)
610 #define undo_queue_commit() ((void)0)
613 #if ENABLE_FEATURE_VI_CRASHME
614 static void crash_dummy();
615 static void crash_test();
616 static int crashme = 0;
619 static void write1(const char *out)
624 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
625 int vi_main(int argc, char **argv)
631 #if ENABLE_FEATURE_VI_UNDO
632 /* undo_stack_tail = NULL; - already is */
633 #if ENABLE_FEATURE_VI_UNDO_QUEUE
634 undo_queue_state = UNDO_EMPTY;
635 /* undo_q = 0; - already is */
639 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
642 #if ENABLE_FEATURE_VI_CRASHME
643 srand((long) my_pid);
645 #ifdef NO_SUCH_APPLET_YET
646 /* If we aren't "vi", we are "view" */
647 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
648 SET_READONLY_MODE(readonly_mode);
652 // autoindent is not default in vim 7.3
653 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
654 // 1- process $HOME/.exrc file (not inplemented yet)
655 // 2- process EXINIT variable from environment
656 // 3- process command line args
657 #if ENABLE_FEATURE_VI_COLON
659 char *p = getenv("EXINIT");
661 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
664 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
666 #if ENABLE_FEATURE_VI_CRASHME
671 #if ENABLE_FEATURE_VI_READONLY
672 case 'R': // Read-only flag
673 SET_READONLY_MODE(readonly_mode);
676 #if ENABLE_FEATURE_VI_COLON
677 case 'c': // cmd line vi command
679 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
691 // The argv array can be used by the ":next" and ":rewind" commands
695 //----- This is the main file handling loop --------------
698 // "Save cursor, use alternate screen buffer, clear screen"
699 write1(ESC"[?1049h");
701 edit_file(argv[optind]); /* param might be NULL */
702 if (++optind >= argc)
705 // "Use normal screen buffer, restore cursor"
706 write1(ESC"[?1049l");
707 //-----------------------------------------------------------
712 /* read text from file or create an empty buf */
713 /* will also update current_filename */
714 static int init_text_buffer(char *fn)
718 /* allocate/reallocate text buffer */
721 screenbegin = dot = end = text = xzalloc(text_size);
723 if (fn != current_filename) {
724 free(current_filename);
725 current_filename = xstrdup(fn);
727 rc = file_insert(fn, text, 1);
729 // file doesnt exist. Start empty buf with dummy line
730 char_insert(text, '\n', NO_UNDO);
735 last_modified_count = -1;
736 #if ENABLE_FEATURE_VI_YANKMARK
738 memset(mark, 0, sizeof(mark));
743 #if ENABLE_FEATURE_VI_WIN_RESIZE
744 static int query_screen_dimensions(void)
746 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
747 if (rows > MAX_SCR_ROWS)
749 if (columns > MAX_SCR_COLS)
750 columns = MAX_SCR_COLS;
754 static ALWAYS_INLINE int query_screen_dimensions(void)
760 static void edit_file(char *fn)
762 #if ENABLE_FEATURE_VI_YANKMARK
763 #define cur_line edit_file__cur_line
766 #if ENABLE_FEATURE_VI_USE_SIGNALS
770 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
774 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
775 #if ENABLE_FEATURE_VI_ASK_TERMINAL
776 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
778 write1(ESC"[999;999H" ESC"[6n");
780 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
781 if ((int32_t)k == KEYCODE_CURSOR_POS) {
782 uint32_t rc = (k >> 32);
783 columns = (rc & 0x7fff);
784 if (columns > MAX_SCR_COLS)
785 columns = MAX_SCR_COLS;
786 rows = ((rc >> 16) & 0x7fff);
787 if (rows > MAX_SCR_ROWS)
792 new_screen(rows, columns); // get memory for virtual screen
793 init_text_buffer(fn);
795 #if ENABLE_FEATURE_VI_YANKMARK
796 YDreg = 26; // default Yank/Delete reg
797 Ureg = 27; // hold orig line for "U" cmd
798 mark[26] = mark[27] = text; // init "previous context"
801 last_forward_char = last_input_char = '\0';
805 #if ENABLE_FEATURE_VI_USE_SIGNALS
806 signal(SIGINT, catch_sig);
807 signal(SIGWINCH, winch_sig);
808 signal(SIGTSTP, suspend_sig);
809 sig = sigsetjmp(restart, 1);
811 screenbegin = dot = text;
815 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
818 offset = 0; // no horizontal offset
820 #if ENABLE_FEATURE_VI_DOT_CMD
822 ioq = ioq_start = NULL;
827 #if ENABLE_FEATURE_VI_COLON
832 while ((p = initial_cmds[n]) != NULL) {
842 free(initial_cmds[n]);
843 initial_cmds[n] = NULL;
848 redraw(FALSE); // dont force every col re-draw
849 //------This is the main Vi cmd handling loop -----------------------
850 while (editing > 0) {
851 #if ENABLE_FEATURE_VI_CRASHME
853 if ((end - text) > 1) {
854 crash_dummy(); // generate a random command
857 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
863 last_input_char = c = get_one_char(); // get a cmd from user
864 #if ENABLE_FEATURE_VI_YANKMARK
865 // save a copy of the current line- for the 'U" command
866 if (begin_line(dot) != cur_line) {
867 cur_line = begin_line(dot);
868 text_yank(begin_line(dot), end_line(dot), Ureg);
871 #if ENABLE_FEATURE_VI_DOT_CMD
872 // These are commands that change text[].
873 // Remember the input for the "." command
874 if (!adding2q && ioq_start == NULL
875 && cmd_mode == 0 // command mode
876 && c > '\0' // exclude NUL and non-ASCII chars
877 && c < 0x7f // (Unicode and such)
878 && strchr(modifying_cmds, c)
883 do_cmd(c); // execute the user command
885 // poll to see if there is input already waiting. if we are
886 // not able to display output fast enough to keep up, skip
887 // the display update until we catch up with input.
888 if (!readbuffer[0] && mysleep(0) == 0) {
889 // no input pending - so update output
893 #if ENABLE_FEATURE_VI_CRASHME
895 crash_test(); // test editor variables
898 //-------------------------------------------------------------------
900 go_bottom_and_clear_to_eol();
905 //----- The Colon commands -------------------------------------
906 #if ENABLE_FEATURE_VI_COLON
907 static char *get_one_address(char *p, int *addr) // get colon addr, if present
911 IF_FEATURE_VI_YANKMARK(char c;)
912 IF_FEATURE_VI_SEARCH(char *pat;)
914 *addr = -1; // assume no addr
915 if (*p == '.') { // the current line
918 *addr = count_lines(text, q);
920 #if ENABLE_FEATURE_VI_YANKMARK
921 else if (*p == '\'') { // is this a mark addr
925 if (c >= 'a' && c <= 'z') {
928 q = mark[(unsigned char) c];
929 if (q != NULL) { // is mark valid
930 *addr = count_lines(text, q);
935 #if ENABLE_FEATURE_VI_SEARCH
936 else if (*p == '/') { // a search pattern
937 q = strchrnul(++p, '/');
938 pat = xstrndup(p, q - p); // save copy of pattern
942 q = char_search(dot, pat, (FORWARD << 1) | FULL);
944 *addr = count_lines(text, q);
949 else if (*p == '$') { // the last line in file
951 q = begin_line(end - 1);
952 *addr = count_lines(text, q);
953 } else if (isdigit(*p)) { // specific line number
954 sscanf(p, "%d%n", addr, &st);
957 // unrecognized address - assume -1
963 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
965 //----- get the address' i.e., 1,3 'a,'b -----
966 // get FIRST addr, if present
968 p++; // skip over leading spaces
969 if (*p == '%') { // alias for 1,$
972 *e = count_lines(text, end-1);
975 p = get_one_address(p, b);
978 if (*p == ',') { // is there a address separator
982 // get SECOND addr, if present
983 p = get_one_address(p, e);
987 p++; // skip over trailing spaces
991 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
992 static void setops(const char *args, const char *opname, int flg_no,
993 const char *short_opname, int opt)
995 const char *a = args + flg_no;
996 int l = strlen(opname) - 1; /* opname have + ' ' */
998 // maybe strncmp? we had tons of erroneous strncasecmp's...
999 if (strncasecmp(a, opname, l) == 0
1000 || strncasecmp(a, short_opname, 2) == 0
1010 #endif /* FEATURE_VI_COLON */
1012 // buf must be no longer than MAX_INPUT_LEN!
1013 static void colon(char *buf)
1015 #if !ENABLE_FEATURE_VI_COLON
1016 /* Simple ":cmd" handler with minimal set of commands */
1025 if (strncmp(p, "quit", cnt) == 0
1026 || strncmp(p, "q!", cnt) == 0
1028 if (modified_count && p[1] != '!') {
1029 status_line_bold("No write since last change (:%s! overrides)", p);
1035 if (strncmp(p, "write", cnt) == 0
1036 || strncmp(p, "wq", cnt) == 0
1037 || strncmp(p, "wn", cnt) == 0
1038 || (p[0] == 'x' && !p[1])
1040 if (modified_count != 0 || p[0] != 'x') {
1041 cnt = file_write(current_filename, text, end - 1);
1045 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
1048 last_modified_count = -1;
1049 status_line("'%s' %dL, %dC",
1051 count_lines(text, end - 1), cnt
1054 || p[1] == 'q' || p[1] == 'n'
1055 || p[1] == 'Q' || p[1] == 'N'
1062 if (strncmp(p, "file", cnt) == 0) {
1063 last_status_cksum = 0; // force status update
1066 if (sscanf(p, "%d", &cnt) > 0) {
1067 dot = find_line(cnt);
1074 char c, *buf1, *q, *r;
1075 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
1078 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
1082 // :3154 // if (-e line 3154) goto it else stay put
1083 // :4,33w! foo // write a portion of buffer to file "foo"
1084 // :w // write all of buffer to current file
1086 // :q! // quit- dont care about modified file
1087 // :'a,'z!sort -u // filter block through sort
1088 // :'f // goto mark "f"
1089 // :'fl // list literal the mark "f" line
1090 // :.r bar // read file "bar" into buffer before dot
1091 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1092 // :/xyz/ // goto the "xyz" line
1093 // :s/find/replace/ // substitute pattern "find" with "replace"
1094 // :!<cmd> // run <cmd> then return
1100 buf++; // move past the ':'
1104 q = text; // assume 1,$ for the range
1106 li = count_lines(text, end - 1);
1107 fn = current_filename;
1109 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1110 buf = get_address(buf, &b, &e);
1112 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
1113 // remember orig command line
1117 // get the COMMAND into cmd[]
1119 while (*buf != '\0') {
1125 // get any ARGuments
1126 while (isblank(*buf))
1130 buf1 = last_char_is(cmd, '!');
1133 *buf1 = '\0'; // get rid of !
1136 // if there is only one addr, then the addr
1137 // is the line number of the single line the
1138 // user wants. So, reset the end
1139 // pointer to point at end of the "b" line
1140 q = find_line(b); // what line is #b
1145 // we were given two addrs. change the
1146 // end pointer to the addr given by user.
1147 r = find_line(e); // what line is #e
1151 // ------------ now look for the command ------------
1153 if (i == 0) { // :123CR goto line #123
1155 dot = find_line(b); // what line is #b
1159 # if ENABLE_FEATURE_ALLOW_EXEC
1160 else if (cmd[0] == '!') { // run a cmd
1162 // :!ls run the <cmd>
1163 go_bottom_and_clear_to_eol();
1165 retcode = system(orig_buf + 1); // run the cmd
1167 printf("\nshell returned %i\n\n", retcode);
1169 Hit_Return(); // let user see results
1172 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1173 if (b < 0) { // no addr given- use defaults
1174 b = e = count_lines(text, dot);
1176 status_line("%d", b);
1177 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1178 if (b < 0) { // no addr given- use defaults
1179 q = begin_line(dot); // assume .,. for the range
1182 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
1184 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1187 // don't edit, if the current file has been modified
1188 if (modified_count && !useforce) {
1189 status_line_bold("No write since last change (:%s! overrides)", cmd);
1193 // the user supplied a file name
1195 } else if (current_filename && current_filename[0]) {
1196 // no user supplied name- use the current filename
1197 // fn = current_filename; was set by default
1199 // no user file name, no current name- punt
1200 status_line_bold("No current filename");
1204 size = init_text_buffer(fn);
1206 # if ENABLE_FEATURE_VI_YANKMARK
1207 if (Ureg >= 0 && Ureg < 28) {
1208 free(reg[Ureg]); // free orig line reg- for 'U'
1211 if (YDreg >= 0 && YDreg < 28) {
1212 free(reg[YDreg]); // free default yank/delete register
1216 // how many lines in text[]?
1217 li = count_lines(text, end - 1);
1218 status_line("'%s'%s"
1219 IF_FEATURE_VI_READONLY("%s")
1222 (size < 0 ? " [New file]" : ""),
1223 IF_FEATURE_VI_READONLY(
1224 ((readonly_mode) ? " [Readonly]" : ""),
1226 li, (int)(end - text)
1228 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1229 if (b != -1 || e != -1) {
1230 status_line_bold("No address allowed on this command");
1234 // user wants a new filename
1235 free(current_filename);
1236 current_filename = xstrdup(args);
1238 // user wants file status info
1239 last_status_cksum = 0; // force status update
1241 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1242 // print out values of all features
1243 go_bottom_and_clear_to_eol();
1248 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1249 if (b < 0) { // no addr given- use defaults
1250 q = begin_line(dot); // assume .,. for the range
1253 go_bottom_and_clear_to_eol();
1255 for (; q <= r; q++) {
1259 c_is_no_print = (c & 0x80) && !Isprint(c);
1260 if (c_is_no_print) {
1266 } else if (c < ' ' || c == 127) {
1278 } else if (strncmp(cmd, "quit", i) == 0 // quit
1279 || strncmp(cmd, "next", i) == 0 // edit next file
1280 || strncmp(cmd, "prev", i) == 0 // edit previous file
1285 // force end of argv list
1291 // don't exit if the file been modified
1292 if (modified_count) {
1293 status_line_bold("No write since last change (:%s! overrides)", cmd);
1296 // are there other file to edit
1297 n = save_argc - optind - 1;
1298 if (*cmd == 'q' && n > 0) {
1299 status_line_bold("%d more file(s) to edit", n);
1302 if (*cmd == 'n' && n <= 0) {
1303 status_line_bold("No more files to edit");
1307 // are there previous files to edit
1309 status_line_bold("No previous files to edit");
1315 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1320 status_line_bold("No filename given");
1323 if (b < 0) { // no addr given- use defaults
1324 q = begin_line(dot); // assume "dot"
1326 // read after current line- unless user said ":0r foo"
1329 // read after last line
1333 { // dance around potentially-reallocated text[]
1334 uintptr_t ofs = q - text;
1335 size = file_insert(fn, q, 0);
1339 goto ret; // nothing was inserted
1340 // how many lines in text[]?
1341 li = count_lines(q, q + size - 1);
1343 IF_FEATURE_VI_READONLY("%s")
1346 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1350 // if the insert is before "dot" then we need to update
1354 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1355 if (modified_count && !useforce) {
1356 status_line_bold("No write since last change (:%s! overrides)", cmd);
1358 // reset the filenames to edit
1359 optind = -1; /* start from 0th file */
1362 # if ENABLE_FEATURE_VI_SET
1363 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1364 # if ENABLE_FEATURE_VI_SETOPTS
1367 i = 0; // offset into args
1368 // only blank is regarded as args delimiter. What about tab '\t'?
1369 if (!args[0] || strcasecmp(args, "all") == 0) {
1370 // print out values of all options
1371 # if ENABLE_FEATURE_VI_SETOPTS
1378 autoindent ? "" : "no",
1379 err_method ? "" : "no",
1380 ignorecase ? "" : "no",
1381 showmatch ? "" : "no",
1387 # if ENABLE_FEATURE_VI_SETOPTS
1390 if (strncmp(argp, "no", 2) == 0)
1391 i = 2; // ":set noautoindent"
1392 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1393 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1394 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1395 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1396 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1398 sscanf(argp + i+8, "%u", &t);
1399 if (t > 0 && t <= MAX_TABSTOP)
1402 argp = skip_non_whitespace(argp);
1403 argp = skip_whitespace(argp);
1405 # endif /* FEATURE_VI_SETOPTS */
1406 # endif /* FEATURE_VI_SET */
1408 # if ENABLE_FEATURE_VI_SEARCH
1409 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1410 char *F, *R, *flags;
1411 size_t len_F, len_R;
1412 int gflag; // global replace flag
1413 # if ENABLE_FEATURE_VI_UNDO
1414 int dont_chain_first_item = ALLOW_UNDO;
1417 // F points to the "find" pattern
1418 // R points to the "replace" pattern
1419 // replace the cmd line delimiters "/" with NULs
1420 c = orig_buf[1]; // what is the delimiter
1421 F = orig_buf + 2; // start of "find"
1422 R = strchr(F, c); // middle delimiter
1426 *R++ = '\0'; // terminate "find"
1427 flags = strchr(R, c);
1431 *flags++ = '\0'; // terminate "replace"
1435 if (b < 0) { // maybe :s/foo/bar/
1436 q = begin_line(dot); // start with cur line
1437 b = count_lines(text, q); // cur line number
1440 e = b; // maybe :.s/foo/bar/
1442 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1443 char *ls = q; // orig line start
1446 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
1449 // we found the "find" pattern - delete it
1450 // For undo support, the first item should not be chained
1451 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
1452 # if ENABLE_FEATURE_VI_UNDO
1453 dont_chain_first_item = ALLOW_UNDO_CHAIN;
1455 // insert the "replace" patern
1456 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
1459 /*q += bias; - recalculated anyway */
1460 // check for "global" :s/foo/bar/g
1462 if ((found + len_R) < end_line(ls)) {
1464 goto vc4; // don't let q move past cur line
1470 # endif /* FEATURE_VI_SEARCH */
1471 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1472 status_line(BB_VER);
1473 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1474 || strncmp(cmd, "wq", i) == 0
1475 || strncmp(cmd, "wn", i) == 0
1476 || (cmd[0] == 'x' && !cmd[1])
1479 //int forced = FALSE;
1481 // is there a file name to write to?
1485 # if ENABLE_FEATURE_VI_READONLY
1486 if (readonly_mode && !useforce) {
1487 status_line_bold("'%s' is read only", fn);
1492 // if "fn" is not write-able, chmod u+w
1493 // sprintf(syscmd, "chmod u+w %s", fn);
1497 if (modified_count != 0 || cmd[0] != 'x') {
1499 l = file_write(fn, q, r);
1504 //if (useforce && forced) {
1506 // sprintf(syscmd, "chmod u-w %s", fn);
1512 status_line_bold_errno(fn);
1514 // how many lines written
1515 li = count_lines(q, q + l - 1);
1516 status_line("'%s' %dL, %dC", fn, li, l);
1518 if (q == text && q + l == end) {
1520 last_modified_count = -1;
1523 || cmd[1] == 'q' || cmd[1] == 'n'
1524 || cmd[1] == 'Q' || cmd[1] == 'N'
1530 # if ENABLE_FEATURE_VI_YANKMARK
1531 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1532 if (b < 0) { // no addr given- use defaults
1533 q = begin_line(dot); // assume .,. for the range
1536 text_yank(q, r, YDreg);
1537 li = count_lines(q, r);
1538 status_line("Yank %d lines (%d chars) into [%c]",
1539 li, strlen(reg[YDreg]), what_reg());
1543 not_implemented(cmd);
1546 dot = bound_dot(dot); // make sure "dot" is valid
1548 # if ENABLE_FEATURE_VI_SEARCH
1550 status_line(":s expression missing delimiters");
1552 #endif /* FEATURE_VI_COLON */
1555 static void Hit_Return(void)
1560 write1("[Hit return to continue]");
1562 while ((c = get_one_char()) != '\n' && c != '\r')
1564 redraw(TRUE); // force redraw all
1567 static int next_tabstop(int col)
1569 return col + ((tabstop - 1) - (col % tabstop));
1572 //----- Synchronize the cursor to Dot --------------------------
1573 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1575 char *beg_cur; // begin and end of "d" line
1579 beg_cur = begin_line(d); // first char of cur line
1581 if (beg_cur < screenbegin) {
1582 // "d" is before top line on screen
1583 // how many lines do we have to move
1584 cnt = count_lines(beg_cur, screenbegin);
1586 screenbegin = beg_cur;
1587 if (cnt > (rows - 1) / 2) {
1588 // we moved too many lines. put "dot" in middle of screen
1589 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1590 screenbegin = prev_line(screenbegin);
1594 char *end_scr; // begin and end of screen
1595 end_scr = end_screen(); // last char of screen
1596 if (beg_cur > end_scr) {
1597 // "d" is after bottom line on screen
1598 // how many lines do we have to move
1599 cnt = count_lines(end_scr, beg_cur);
1600 if (cnt > (rows - 1) / 2)
1601 goto sc1; // too many lines
1602 for (ro = 0; ro < cnt - 1; ro++) {
1603 // move screen begin the same amount
1604 screenbegin = next_line(screenbegin);
1605 // now, move the end of screen
1606 end_scr = next_line(end_scr);
1607 end_scr = end_line(end_scr);
1611 // "d" is on screen- find out which row
1613 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1619 // find out what col "d" is on
1621 while (tp < d) { // drive "co" to correct column
1622 if (*tp == '\n') //vda || *tp == '\0')
1625 // handle tabs like real vi
1626 if (d == tp && cmd_mode) {
1629 co = next_tabstop(co);
1630 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1631 co++; // display as ^X, use 2 columns
1637 // "co" is the column where "dot" is.
1638 // The screen has "columns" columns.
1639 // The currently displayed columns are 0+offset -- columns+ofset
1640 // |-------------------------------------------------------------|
1642 // offset | |------- columns ----------------|
1644 // If "co" is already in this range then we do not have to adjust offset
1645 // but, we do have to subtract the "offset" bias from "co".
1646 // If "co" is outside this range then we have to change "offset".
1647 // If the first char of a line is a tab the cursor will try to stay
1648 // in column 7, but we have to set offset to 0.
1650 if (co < 0 + offset) {
1653 if (co >= columns + offset) {
1654 offset = co - columns + 1;
1656 // if the first char of the line is a tab, and "dot" is sitting on it
1657 // force offset to 0.
1658 if (d == beg_cur && *d == '\t') {
1667 //----- Text Movement Routines ---------------------------------
1668 static char *begin_line(char *p) // return pointer to first char cur line
1671 p = memrchr(text, '\n', p - text);
1679 static char *end_line(char *p) // return pointer to NL of cur line
1682 p = memchr(p, '\n', end - p - 1);
1689 static char *dollar_line(char *p) // return pointer to just before NL line
1692 // Try to stay off of the Newline
1693 if (*p == '\n' && (p - begin_line(p)) > 0)
1698 static char *prev_line(char *p) // return pointer first char prev line
1700 p = begin_line(p); // goto beginning of cur line
1701 if (p > text && p[-1] == '\n')
1702 p--; // step to prev line
1703 p = begin_line(p); // goto beginning of prev line
1707 static char *next_line(char *p) // return pointer first char next line
1710 if (p < end - 1 && *p == '\n')
1711 p++; // step to next line
1715 //----- Text Information Routines ------------------------------
1716 static char *end_screen(void)
1721 // find new bottom line
1723 for (cnt = 0; cnt < rows - 2; cnt++)
1729 // count line from start to stop
1730 static int count_lines(char *start, char *stop)
1735 if (stop < start) { // start and stop are backwards- reverse them
1741 stop = end_line(stop);
1742 while (start <= stop && start <= end - 1) {
1743 start = end_line(start);
1751 static char *find_line(int li) // find beginning of line #li
1755 for (q = text; li > 1; li--) {
1761 //----- Dot Movement Routines ----------------------------------
1762 static void dot_left(void)
1764 undo_queue_commit();
1765 if (dot > text && dot[-1] != '\n')
1769 static void dot_right(void)
1771 undo_queue_commit();
1772 if (dot < end - 1 && *dot != '\n')
1776 static void dot_begin(void)
1778 undo_queue_commit();
1779 dot = begin_line(dot); // return pointer to first char cur line
1782 static void dot_end(void)
1784 undo_queue_commit();
1785 dot = end_line(dot); // return pointer to last char cur line
1788 static char *move_to_col(char *p, int l)
1794 while (co < l && p < end) {
1795 if (*p == '\n') //vda || *p == '\0')
1798 co = next_tabstop(co);
1799 } else if (*p < ' ' || *p == 127) {
1800 co++; // display as ^X, use 2 columns
1808 static void dot_next(void)
1810 undo_queue_commit();
1811 dot = next_line(dot);
1814 static void dot_prev(void)
1816 undo_queue_commit();
1817 dot = prev_line(dot);
1820 static void dot_scroll(int cnt, int dir)
1824 undo_queue_commit();
1825 for (; cnt > 0; cnt--) {
1828 // ctrl-Y scroll up one line
1829 screenbegin = prev_line(screenbegin);
1832 // ctrl-E scroll down one line
1833 screenbegin = next_line(screenbegin);
1836 // make sure "dot" stays on the screen so we dont scroll off
1837 if (dot < screenbegin)
1839 q = end_screen(); // find new bottom line
1841 dot = begin_line(q); // is dot is below bottom line?
1845 static void dot_skip_over_ws(void)
1848 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1852 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1854 if (p >= end && end > text) {
1865 //----- Helper Utility Routines --------------------------------
1867 //----------------------------------------------------------------
1868 //----- Char Routines --------------------------------------------
1869 /* Chars that are part of a word-
1870 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1871 * Chars that are Not part of a word (stoppers)
1872 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1873 * Chars that are WhiteSpace
1874 * TAB NEWLINE VT FF RETURN SPACE
1875 * DO NOT COUNT NEWLINE AS WHITESPACE
1878 static char *new_screen(int ro, int co)
1883 screensize = ro * co + 8;
1884 screen = xmalloc(screensize);
1885 // initialize the new screen. assume this will be a empty file.
1887 // non-existent text[] lines start with a tilde (~).
1888 for (li = 1; li < ro - 1; li++) {
1889 screen[(li * co) + 0] = '~';
1894 #if ENABLE_FEATURE_VI_SEARCH
1896 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1898 // search for pattern starting at p
1899 static char *char_search(char *p, const char *pat, int dir_and_range)
1901 struct re_pattern_buffer preg;
1908 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1910 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1912 memset(&preg, 0, sizeof(preg));
1913 err = re_compile_pattern(pat, strlen(pat), &preg);
1915 status_line_bold("bad search pattern '%s': %s", pat, err);
1919 range = (dir_and_range & 1);
1920 q = end - 1; // if FULL
1921 if (range == LIMITED)
1923 if (dir_and_range < 0) { // BACK?
1925 if (range == LIMITED)
1929 // RANGE could be negative if we are searching backwards
1939 // search for the compiled pattern, preg, in p[]
1940 // range < 0: search backward
1941 // range > 0: search forward
1943 // re_search() < 0: not found or error
1944 // re_search() >= 0: index of found pattern
1945 // struct pattern char int int int struct reg
1946 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1947 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1951 if (dir_and_range > 0) // FORWARD?
1960 # if ENABLE_FEATURE_VI_SETOPTS
1961 static int mycmp(const char *s1, const char *s2, int len)
1964 return strncasecmp(s1, s2, len);
1966 return strncmp(s1, s2, len);
1969 # define mycmp strncmp
1972 static char *char_search(char *p, const char *pat, int dir_and_range)
1979 range = (dir_and_range & 1);
1980 if (dir_and_range > 0) { //FORWARD?
1981 stop = end - 1; // assume range is p..end-1
1982 if (range == LIMITED)
1983 stop = next_line(p); // range is to next line
1984 for (start = p; start < stop; start++) {
1985 if (mycmp(start, pat, len) == 0) {
1990 stop = text; // assume range is text..p
1991 if (range == LIMITED)
1992 stop = prev_line(p); // range is to prev line
1993 for (start = p - len; start >= stop; start--) {
1994 if (mycmp(start, pat, len) == 0) {
1999 // pattern not found
2005 #endif /* FEATURE_VI_SEARCH */
2007 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
2009 if (c == 22) { // Is this an ctrl-V?
2010 p += stupid_insert(p, '^'); // use ^ to indicate literal next
2011 refresh(FALSE); // show the ^
2014 #if ENABLE_FEATURE_VI_UNDO
2015 undo_push_insert(p, 1, undo);
2018 #endif /* ENABLE_FEATURE_VI_UNDO */
2020 } else if (c == 27) { // Is this an ESC?
2022 undo_queue_commit();
2024 end_cmd_q(); // stop adding to q
2025 last_status_cksum = 0; // force status update
2026 if ((p[-1] != '\n') && (dot > text)) {
2029 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
2032 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2035 // insert a char into text[]
2037 c = '\n'; // translate \r to \n
2038 #if ENABLE_FEATURE_VI_UNDO
2039 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2041 undo_queue_commit();
2043 undo_push_insert(p, 1, undo);
2046 #endif /* ENABLE_FEATURE_VI_UNDO */
2047 p += 1 + stupid_insert(p, c); // insert the char
2048 #if ENABLE_FEATURE_VI_SETOPTS
2049 if (showmatch && strchr(")]}", c) != NULL) {
2050 showmatching(p - 1);
2052 if (autoindent && c == '\n') { // auto indent the new line
2055 q = prev_line(p); // use prev line as template
2056 len = strspn(q, " \t"); // space or tab
2059 bias = text_hole_make(p, len);
2062 #if ENABLE_FEATURE_VI_UNDO
2063 undo_push_insert(p, len, undo);
2074 // might reallocate text[]! use p += stupid_insert(p, ...),
2075 // and be careful to not use pointers into potentially freed text[]!
2076 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2079 bias = text_hole_make(p, 1);
2085 static int find_range(char **start, char **stop, char c)
2087 char *save_dot, *p, *q, *t;
2088 int cnt, multiline = 0;
2093 if (strchr("cdy><", c)) {
2094 // these cmds operate on whole lines
2095 p = q = begin_line(p);
2096 for (cnt = 1; cnt < cmdcnt; cnt++) {
2100 } else if (strchr("^%$0bBeEfth\b\177", c)) {
2101 // These cmds operate on char positions
2102 do_cmd(c); // execute movement cmd
2104 } else if (strchr("wW", c)) {
2105 do_cmd(c); // execute movement cmd
2106 // if we are at the next word's first char
2107 // step back one char
2108 // but check the possibilities when it is true
2109 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
2110 || (ispunct(dot[-1]) && !ispunct(dot[0]))
2111 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
2112 dot--; // move back off of next word
2113 if (dot > text && *dot == '\n')
2114 dot--; // stay off NL
2116 } else if (strchr("H-k{", c)) {
2117 // these operate on multi-lines backwards
2118 q = end_line(dot); // find NL
2119 do_cmd(c); // execute movement cmd
2122 } else if (strchr("L+j}\r\n", c)) {
2123 // these operate on multi-lines forwards
2124 p = begin_line(dot);
2125 do_cmd(c); // execute movement cmd
2126 dot_end(); // find NL
2129 // nothing -- this causes any other values of c to
2130 // represent the one-character range under the
2131 // cursor. this is correct for ' ' and 'l', but
2132 // perhaps no others.
2141 // backward char movements don't include start position
2142 if (q > p && strchr("^0bBh\b\177", c)) q--;
2145 for (t = p; t <= q; t++) {
2158 static int st_test(char *p, int type, int dir, char *tested)
2168 if (type == S_BEFORE_WS) {
2170 test = (!isspace(c) || c == '\n');
2172 if (type == S_TO_WS) {
2174 test = (!isspace(c) || c == '\n');
2176 if (type == S_OVER_WS) {
2180 if (type == S_END_PUNCT) {
2184 if (type == S_END_ALNUM) {
2186 test = (isalnum(c) || c == '_');
2192 static char *skip_thing(char *p, int linecnt, int dir, int type)
2196 while (st_test(p, type, dir, &c)) {
2197 // make sure we limit search to correct number of lines
2198 if (c == '\n' && --linecnt < 1)
2200 if (dir >= 0 && p >= end - 1)
2202 if (dir < 0 && p <= text)
2204 p += dir; // move to next char
2209 // find matching char of pair () [] {}
2210 // will crash if c is not one of these
2211 static char *find_pair(char *p, const char c)
2213 const char *braces = "()[]{}";
2217 dir = strchr(braces, c) - braces;
2219 match = braces[dir];
2220 dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
2222 // look for match, count levels of pairs (( ))
2226 if (p < text || p >= end)
2229 level++; // increase pair levels
2231 level--; // reduce pair level
2233 return p; // found matching pair
2238 #if ENABLE_FEATURE_VI_SETOPTS
2239 // show the matching char of a pair, () [] {}
2240 static void showmatching(char *p)
2244 // we found half of a pair
2245 q = find_pair(p, *p); // get loc of matching char
2247 indicate_error(); // no matching char
2249 // "q" now points to matching pair
2250 save_dot = dot; // remember where we are
2251 dot = q; // go to new loc
2252 refresh(FALSE); // let the user see it
2253 mysleep(40); // give user some time
2254 dot = save_dot; // go back to old loc
2258 #endif /* FEATURE_VI_SETOPTS */
2260 #if ENABLE_FEATURE_VI_UNDO
2261 static void flush_undo_data(void)
2263 struct undo_object *undo_entry;
2265 while (undo_stack_tail) {
2266 undo_entry = undo_stack_tail;
2267 undo_stack_tail = undo_entry->prev;
2272 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2273 static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack
2275 struct undo_object *undo_entry;
2278 // UNDO_INS: insertion, undo will remove from buffer
2279 // UNDO_DEL: deleted text, undo will restore to buffer
2280 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2281 // The CHAIN operations are for handling multiple operations that the user
2282 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2283 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2284 // for the INS/DEL operation. The raw values should be equal to the values of
2285 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2287 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2288 // This undo queuing functionality groups multiple character typing or backspaces
2289 // into a single large undo object. This greatly reduces calls to malloc() for
2290 // single-character operations while typing and has the side benefit of letting
2291 // an undo operation remove chunks of text rather than a single character.
2293 case UNDO_EMPTY: // Just in case this ever happens...
2295 case UNDO_DEL_QUEUED:
2297 return; // Only queue single characters
2298 switch (undo_queue_state) {
2300 undo_queue_state = UNDO_DEL;
2302 undo_queue_spos = src;
2304 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2305 // If queue is full, dump it into an object
2306 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2307 undo_queue_commit();
2310 // Switch from storing inserted text to deleted text
2311 undo_queue_commit();
2312 undo_push(src, length, UNDO_DEL_QUEUED);
2316 case UNDO_INS_QUEUED:
2319 switch (undo_queue_state) {
2321 undo_queue_state = UNDO_INS;
2322 undo_queue_spos = src;
2325 undo_q++; // Don't need to save any data for insertions
2326 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2327 undo_queue_commit();
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 ((text + 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_push_insert(char *p, int len, int undo)
2378 undo_push(p, len, UNDO_INS);
2380 case ALLOW_UNDO_CHAIN:
2381 undo_push(p, len, UNDO_INS_CHAIN);
2383 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2384 case ALLOW_UNDO_QUEUED:
2385 undo_push(p, len, UNDO_INS_QUEUED);
2391 static void undo_pop(void) // Undo the last operation
2394 char *u_start, *u_end;
2395 struct undo_object *undo_entry;
2397 // Commit pending undo queue before popping (should be unnecessary)
2398 undo_queue_commit();
2400 undo_entry = undo_stack_tail;
2401 // Check for an empty undo stack
2403 status_line("Already at oldest change");
2407 switch (undo_entry->u_type) {
2409 case UNDO_DEL_CHAIN:
2410 // make hole and put in text that was deleted; deallocate text
2411 u_start = text + undo_entry->start;
2412 text_hole_make(u_start, undo_entry->length);
2413 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2414 status_line("Undo [%d] %s %d chars at position %d",
2415 modified_count, "restored",
2416 undo_entry->length, undo_entry->start
2420 case UNDO_INS_CHAIN:
2421 // delete what was inserted
2422 u_start = undo_entry->start + text;
2423 u_end = u_start - 1 + undo_entry->length;
2424 text_hole_delete(u_start, u_end, NO_UNDO);
2425 status_line("Undo [%d] %s %d chars at position %d",
2426 modified_count, "deleted",
2427 undo_entry->length, undo_entry->start
2432 switch (undo_entry->u_type) {
2433 // If this is the end of a chain, lower modification count and refresh display
2436 dot = (text + undo_entry->start);
2439 case UNDO_DEL_CHAIN:
2440 case UNDO_INS_CHAIN:
2444 // Deallocate the undo object we just processed
2445 undo_stack_tail = undo_entry->prev;
2448 // For chained operations, continue popping all the way down the chain.
2450 undo_pop(); // Follow the undo chain if one exists
2454 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2455 static void undo_queue_commit(void) // Flush any queued objects to the undo stack
2457 // Pushes the queue object onto the undo stack
2459 // Deleted character undo events grow from the end
2460 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2462 (undo_queue_state | UNDO_USE_SPOS)
2464 undo_queue_state = UNDO_EMPTY;
2470 #endif /* ENABLE_FEATURE_VI_UNDO */
2472 // open a hole in text[]
2473 // might reallocate text[]! use p += text_hole_make(p, ...),
2474 // and be careful to not use pointers into potentially freed text[]!
2475 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2481 end += size; // adjust the new END
2482 if (end >= (text + text_size)) {
2484 text_size += end - (text + text_size) + 10240;
2485 new_text = xrealloc(text, text_size);
2486 bias = (new_text - text);
2487 screenbegin += bias;
2491 #if ENABLE_FEATURE_VI_YANKMARK
2494 for (i = 0; i < ARRAY_SIZE(mark); i++)
2501 memmove(p + size, p, end - size - p);
2502 memset(p, ' ', size); // clear new hole
2506 // close a hole in text[]
2507 // "undo" value indicates if this operation should be undo-able
2508 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2513 // move forwards, from beginning
2517 if (q < p) { // they are backward- swap them
2521 hole_size = q - p + 1;
2523 #if ENABLE_FEATURE_VI_UNDO
2528 undo_push(p, hole_size, UNDO_DEL);
2530 case ALLOW_UNDO_CHAIN:
2531 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2533 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2534 case ALLOW_UNDO_QUEUED:
2535 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2541 if (src < text || src > end)
2543 if (dest < text || dest >= end)
2547 goto thd_atend; // just delete the end of the buffer
2548 memmove(dest, src, cnt);
2550 end = end - hole_size; // adjust the new END
2552 dest = end - 1; // make sure dest in below end-1
2554 dest = end = text; // keep pointers valid
2559 // copy text into register, then delete text.
2560 // if dist <= 0, do not include, or go past, a NewLine
2562 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2566 // make sure start <= stop
2568 // they are backwards, reverse them
2574 // we cannot cross NL boundaries
2578 // dont go past a NewLine
2579 for (; p + 1 <= stop; p++) {
2581 stop = p; // "stop" just before NewLine
2587 #if ENABLE_FEATURE_VI_YANKMARK
2588 text_yank(start, stop, YDreg);
2590 if (yf == YANKDEL) {
2591 p = text_hole_delete(start, stop, undo);
2596 static void show_help(void)
2598 puts("These features are available:"
2599 #if ENABLE_FEATURE_VI_SEARCH
2600 "\n\tPattern searches with / and ?"
2602 #if ENABLE_FEATURE_VI_DOT_CMD
2603 "\n\tLast command repeat with ."
2605 #if ENABLE_FEATURE_VI_YANKMARK
2606 "\n\tLine marking with 'x"
2607 "\n\tNamed buffers with \"x"
2609 #if ENABLE_FEATURE_VI_READONLY
2610 //not implemented: "\n\tReadonly if vi is called as \"view\""
2611 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2613 #if ENABLE_FEATURE_VI_SET
2614 "\n\tSome colon mode commands with :"
2616 #if ENABLE_FEATURE_VI_SETOPTS
2617 "\n\tSettable options with \":set\""
2619 #if ENABLE_FEATURE_VI_USE_SIGNALS
2620 "\n\tSignal catching- ^C"
2621 "\n\tJob suspend and resume with ^Z"
2623 #if ENABLE_FEATURE_VI_WIN_RESIZE
2624 "\n\tAdapt to window re-sizes"
2629 #if ENABLE_FEATURE_VI_DOT_CMD
2630 static void start_new_cmd_q(char c)
2632 // get buffer for new cmd
2633 // if there is a current cmd count put it in the buffer first
2635 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2636 } else { // just save char c onto queue
2637 last_modifying_cmd[0] = c;
2643 static void end_cmd_q(void)
2645 #if ENABLE_FEATURE_VI_YANKMARK
2646 YDreg = 26; // go back to default Yank/Delete reg
2650 #endif /* FEATURE_VI_DOT_CMD */
2652 #if ENABLE_FEATURE_VI_YANKMARK \
2653 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2654 || ENABLE_FEATURE_VI_CRASHME
2655 // might reallocate text[]! use p += string_insert(p, ...),
2656 // and be careful to not use pointers into potentially freed text[]!
2657 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2663 #if ENABLE_FEATURE_VI_UNDO
2664 undo_push_insert(p, i, undo);
2666 bias = text_hole_make(p, i);
2669 #if ENABLE_FEATURE_VI_YANKMARK
2672 for (cnt = 0; *s != '\0'; s++) {
2676 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2683 #if ENABLE_FEATURE_VI_YANKMARK
2684 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2687 if (cnt < 0) { // they are backwards- reverse them
2691 free(reg[dest]); // if already a yank register, free it
2692 reg[dest] = xstrndup(p, cnt + 1);
2696 static char what_reg(void)
2700 c = 'D'; // default to D-reg
2701 if (0 <= YDreg && YDreg <= 25)
2702 c = 'a' + (char) YDreg;
2710 static void check_context(char cmd)
2712 // A context is defined to be "modifying text"
2713 // Any modifying command establishes a new context.
2715 if (dot < context_start || dot > context_end) {
2716 if (strchr(modifying_cmds, cmd) != NULL) {
2717 // we are trying to modify text[]- make this the current context
2718 mark[27] = mark[26]; // move cur to prev
2719 mark[26] = dot; // move local to cur
2720 context_start = prev_line(prev_line(dot));
2721 context_end = next_line(next_line(dot));
2722 //loiter= start_loiter= now;
2727 static char *swap_context(char *p) // goto new context for '' command make this the current context
2731 // the current context is in mark[26]
2732 // the previous context is in mark[27]
2733 // only swap context if other context is valid
2734 if (text <= mark[27] && mark[27] <= end - 1) {
2738 context_start = prev_line(prev_line(prev_line(p)));
2739 context_end = next_line(next_line(next_line(p)));
2743 #endif /* FEATURE_VI_YANKMARK */
2745 //----- Set terminal attributes --------------------------------
2746 static void rawmode(void)
2748 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
2749 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
2750 erase_char = term_orig.c_cc[VERASE];
2753 static void cookmode(void)
2756 tcsetattr_stdin_TCSANOW(&term_orig);
2759 #if ENABLE_FEATURE_VI_USE_SIGNALS
2760 //----- Come here when we get a window resize signal ---------
2761 static void winch_sig(int sig UNUSED_PARAM)
2763 int save_errno = errno;
2764 // FIXME: do it in main loop!!!
2765 signal(SIGWINCH, winch_sig);
2766 query_screen_dimensions();
2767 new_screen(rows, columns); // get memory for virtual screen
2768 redraw(TRUE); // re-draw the screen
2772 //----- Come here when we get a continue signal -------------------
2773 static void cont_sig(int sig UNUSED_PARAM)
2775 int save_errno = errno;
2776 rawmode(); // terminal to "raw"
2777 last_status_cksum = 0; // force status update
2778 redraw(TRUE); // re-draw the screen
2780 signal(SIGTSTP, suspend_sig);
2781 signal(SIGCONT, SIG_DFL);
2782 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2786 //----- Come here when we get a Suspend signal -------------------
2787 static void suspend_sig(int sig UNUSED_PARAM)
2789 int save_errno = errno;
2790 go_bottom_and_clear_to_eol();
2791 cookmode(); // terminal to "cooked"
2793 signal(SIGCONT, cont_sig);
2794 signal(SIGTSTP, SIG_DFL);
2795 kill(my_pid, SIGTSTP);
2799 //----- Come here when we get a signal ---------------------------
2800 static void catch_sig(int sig)
2802 signal(SIGINT, catch_sig);
2803 siglongjmp(restart, sig);
2805 #endif /* FEATURE_VI_USE_SIGNALS */
2807 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2809 struct pollfd pfd[1];
2814 pfd[0].fd = STDIN_FILENO;
2815 pfd[0].events = POLLIN;
2816 return safe_poll(pfd, 1, hund*10) > 0;
2819 //----- IO Routines --------------------------------------------
2820 static int readit(void) // read (maybe cursor) key from stdin
2826 // Wait for input. TIMEOUT = -1 makes read_key wait even
2827 // on nonblocking stdin.
2828 // Note: read_key sets errno to 0 on success.
2830 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
2831 if (c == -1) { // EOF/error
2832 if (errno == EAGAIN) // paranoia
2834 go_bottom_and_clear_to_eol();
2835 cookmode(); // terminal to "cooked"
2836 bb_error_msg_and_die("can't read user input");
2841 //----- IO Routines --------------------------------------------
2842 static int get_one_char(void)
2846 #if ENABLE_FEATURE_VI_DOT_CMD
2848 // we are not adding to the q.
2849 // but, we may be reading from a q
2851 // there is no current q, read from STDIN
2852 c = readit(); // get the users input
2854 // there is a queue to get chars from first
2855 // careful with correct sign expansion!
2856 c = (unsigned char)*ioq++;
2858 // the end of the q, read from STDIN
2860 ioq_start = ioq = 0;
2861 c = readit(); // get the users input
2865 // adding STDIN chars to q
2866 c = readit(); // get the users input
2867 if (lmc_len >= MAX_INPUT_LEN - 1) {
2868 status_line_bold("last_modifying_cmd overrun");
2870 // add new char to q
2871 last_modifying_cmd[lmc_len++] = c;
2875 c = readit(); // get the users input
2876 #endif /* FEATURE_VI_DOT_CMD */
2880 // Get input line (uses "status line" area)
2881 static char *get_input_line(const char *prompt)
2883 // char [MAX_INPUT_LEN]
2884 #define buf get_input_line__buf
2889 strcpy(buf, prompt);
2890 last_status_cksum = 0; // force status update
2891 go_bottom_and_clear_to_eol();
2892 write1(prompt); // write out the :, /, or ? prompt
2895 while (i < MAX_INPUT_LEN) {
2897 if (c == '\n' || c == '\r' || c == 27)
2898 break; // this is end of input
2899 if (c == erase_char || c == 8 || c == 127) {
2900 // user wants to erase prev char
2902 write1("\b \b"); // erase char on screen
2903 if (i <= 0) // user backs up before b-o-l, exit
2905 } else if (c > 0 && c < 256) { // exclude Unicode
2906 // (TODO: need to handle Unicode)
2917 // might reallocate text[]!
2918 static int file_insert(const char *fn, char *p, int initial)
2922 struct stat statbuf;
2929 fd = open(fn, O_RDONLY);
2932 status_line_bold_errno(fn);
2937 if (fstat(fd, &statbuf) < 0) {
2938 status_line_bold_errno(fn);
2941 if (!S_ISREG(statbuf.st_mode)) {
2942 status_line_bold("'%s' is not a regular file", fn);
2945 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2946 p += text_hole_make(p, size);
2947 cnt = full_read(fd, p, size);
2949 status_line_bold_errno(fn);
2950 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2951 } else if (cnt < size) {
2952 // There was a partial read, shrink unused space
2953 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2954 status_line_bold("can't read '%s'", fn);
2959 #if ENABLE_FEATURE_VI_READONLY
2961 && ((access(fn, W_OK) < 0) ||
2962 /* root will always have access()
2963 * so we check fileperms too */
2964 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2967 SET_READONLY_FILE(readonly_mode);
2973 static int file_write(char *fn, char *first, char *last)
2975 int fd, cnt, charcnt;
2978 status_line_bold("No current filename");
2981 /* By popular request we do not open file with O_TRUNC,
2982 * but instead ftruncate() it _after_ successful write.
2983 * Might reduce amount of data lost on power fail etc.
2985 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2988 cnt = last - first + 1;
2989 charcnt = full_write(fd, first, cnt);
2990 ftruncate(fd, charcnt);
2991 if (charcnt == cnt) {
2993 //modified_count = FALSE;
3001 //----- Terminal Drawing ---------------------------------------
3002 // The terminal is made up of 'rows' line of 'columns' columns.
3003 // classically this would be 24 x 80.
3004 // screen coordinates
3010 // 23,0 ... 23,79 <- status line
3012 //----- Move the cursor to row x col (count from 0, not 1) -------
3013 static void place_cursor(int row, int col)
3015 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
3017 if (row < 0) row = 0;
3018 if (row >= rows) row = rows - 1;
3019 if (col < 0) col = 0;
3020 if (col >= columns) col = columns - 1;
3022 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
3026 //----- Erase from cursor to end of line -----------------------
3027 static void clear_to_eol(void)
3029 write1(ESC_CLEAR2EOL);
3032 static void go_bottom_and_clear_to_eol(void)
3034 place_cursor(rows - 1, 0);
3038 //----- Erase from cursor to end of screen -----------------------
3039 static void clear_to_eos(void)
3041 write1(ESC_CLEAR2EOS);
3044 //----- Start standout mode ------------------------------------
3045 static void standout_start(void)
3047 write1(ESC_BOLD_TEXT);
3050 //----- End standout mode --------------------------------------
3051 static void standout_end(void)
3053 write1(ESC_NORM_TEXT);
3056 //----- Flash the screen --------------------------------------
3057 static void flash(int h)
3066 static void indicate_error(void)
3068 #if ENABLE_FEATURE_VI_CRASHME
3070 return; // generate a random command
3079 //----- Screen[] Routines --------------------------------------
3080 //----- Erase the Screen[] memory ------------------------------
3081 static void screen_erase(void)
3083 memset(screen, ' ', screensize); // clear new screen
3086 static int bufsum(char *buf, int count)
3089 char *e = buf + count;
3092 sum += (unsigned char) *buf++;
3096 //----- Draw the status line at bottom of the screen -------------
3097 static void show_status_line(void)
3099 int cnt = 0, cksum = 0;
3101 // either we already have an error or status message, or we
3103 if (!have_status_msg) {
3104 cnt = format_edit_status();
3105 cksum = bufsum(status_buffer, cnt);
3107 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3108 last_status_cksum = cksum; // remember if we have seen this line
3109 go_bottom_and_clear_to_eol();
3110 write1(status_buffer);
3111 if (have_status_msg) {
3112 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3114 have_status_msg = 0;
3117 have_status_msg = 0;
3119 place_cursor(crow, ccol); // put cursor back in correct place
3124 //----- format the status buffer, the bottom line of screen ------
3125 // format status buffer, with STANDOUT mode
3126 static void status_line_bold(const char *format, ...)
3130 va_start(args, format);
3131 strcpy(status_buffer, ESC_BOLD_TEXT);
3132 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3133 strcat(status_buffer, ESC_NORM_TEXT);
3136 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3139 static void status_line_bold_errno(const char *fn)
3141 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
3144 // format status buffer
3145 static void status_line(const char *format, ...)
3149 va_start(args, format);
3150 vsprintf(status_buffer, format, args);
3153 have_status_msg = 1;
3156 // copy s to buf, convert unprintable
3157 static void print_literal(char *buf, const char *s)
3171 c_is_no_print = (c & 0x80) && !Isprint(c);
3172 if (c_is_no_print) {
3173 strcpy(d, ESC_NORM_TEXT);
3174 d += sizeof(ESC_NORM_TEXT)-1;
3177 if (c < ' ' || c == 0x7f) {
3179 c |= '@'; /* 0x40 */
3185 if (c_is_no_print) {
3186 strcpy(d, ESC_BOLD_TEXT);
3187 d += sizeof(ESC_BOLD_TEXT)-1;
3193 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3198 static void not_implemented(const char *s)
3200 char buf[MAX_INPUT_LEN];
3202 print_literal(buf, s);
3203 status_line_bold("\'%s\' is not implemented", buf);
3206 // show file status on status line
3207 static int format_edit_status(void)
3209 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3211 #define tot format_edit_status__tot
3213 int cur, percent, ret, trunc_at;
3215 // modified_count is now a counter rather than a flag. this
3216 // helps reduce the amount of line counting we need to do.
3217 // (this will cause a mis-reporting of modified status
3218 // once every MAXINT editing operations.)
3220 // it would be nice to do a similar optimization here -- if
3221 // we haven't done a motion that could have changed which line
3222 // we're on, then we shouldn't have to do this count_lines()
3223 cur = count_lines(text, dot);
3225 // count_lines() is expensive.
3226 // Call it only if something was changed since last time
3228 if (modified_count != last_modified_count) {
3229 tot = cur + count_lines(dot, end - 1) - 1;
3230 last_modified_count = modified_count;
3233 // current line percent
3234 // ------------- ~~ ----------
3237 percent = (100 * cur) / tot;
3243 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3244 columns : STATUS_BUFFER_LEN-1;
3246 ret = snprintf(status_buffer, trunc_at+1,
3247 #if ENABLE_FEATURE_VI_READONLY
3248 "%c %s%s%s %d/%d %d%%",
3250 "%c %s%s %d/%d %d%%",
3252 cmd_mode_indicator[cmd_mode & 3],
3253 (current_filename != NULL ? current_filename : "No file"),
3254 #if ENABLE_FEATURE_VI_READONLY
3255 (readonly_mode ? " [Readonly]" : ""),
3257 (modified_count ? " [Modified]" : ""),
3260 if (ret >= 0 && ret < trunc_at)
3261 return ret; /* it all fit */
3263 return trunc_at; /* had to truncate */
3267 //----- Force refresh of all Lines -----------------------------
3268 static void redraw(int full_screen)
3272 screen_erase(); // erase the internal screen buffer
3273 last_status_cksum = 0; // force status update
3274 refresh(full_screen); // this will redraw the entire display
3278 //----- Format a text[] line into a buffer ---------------------
3279 static char* format_line(char *src /*, int li*/)
3284 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3286 c = '~'; // char in col 0 in non-existent lines is '~'
3288 while (co < columns + tabstop) {
3289 // have we gone past the end?
3294 if ((c & 0x80) && !Isprint(c)) {
3297 if (c < ' ' || c == 0x7f) {
3301 while ((co % tabstop) != (tabstop - 1)) {
3309 c += '@'; // Ctrl-X -> 'X'
3314 // discard scrolled-off-to-the-left portion,
3315 // in tabstop-sized pieces
3316 if (ofs >= tabstop && co >= tabstop) {
3317 memmove(dest, dest + tabstop, co);
3324 // check "short line, gigantic offset" case
3327 // discard last scrolled off part
3330 // fill the rest with spaces
3332 memset(&dest[co], ' ', columns - co);
3336 //----- Refresh the changed screen lines -----------------------
3337 // Copy the source line from text[] into the buffer and note
3338 // if the current screenline is different from the new buffer.
3339 // If they differ then that line needs redrawing on the terminal.
3341 static void refresh(int full_screen)
3343 #define old_offset refresh__old_offset
3346 char *tp, *sp; // pointer into text[] and screen[]
3348 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3349 unsigned c = columns, r = rows;
3350 query_screen_dimensions();
3351 full_screen |= (c - columns) | (r - rows);
3353 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3354 tp = screenbegin; // index into text[] of top line
3356 // compare text[] to screen[] and mark screen[] lines that need updating
3357 for (li = 0; li < rows - 1; li++) {
3358 int cs, ce; // column start & end
3360 // format current text line
3361 out_buf = format_line(tp /*, li*/);
3363 // skip to the end of the current text[] line
3365 char *t = memchr(tp, '\n', end - tp);
3366 if (!t) t = end - 1;
3370 // see if there are any changes between virtual screen and out_buf
3371 changed = FALSE; // assume no change
3374 sp = &screen[li * columns]; // start of screen line
3376 // force re-draw of every single column from 0 - columns-1
3379 // compare newly formatted buffer with virtual screen
3380 // look forward for first difference between buf and screen
3381 for (; cs <= ce; cs++) {
3382 if (out_buf[cs] != sp[cs]) {
3383 changed = TRUE; // mark for redraw
3388 // look backward for last difference between out_buf and screen
3389 for (; ce >= cs; ce--) {
3390 if (out_buf[ce] != sp[ce]) {
3391 changed = TRUE; // mark for redraw
3395 // now, cs is index of first diff, and ce is index of last diff
3397 // if horz offset has changed, force a redraw
3398 if (offset != old_offset) {
3403 // make a sanity check of columns indexes
3405 if (ce > columns - 1) ce = columns - 1;
3406 if (cs > ce) { cs = 0; ce = columns - 1; }
3407 // is there a change between virtual screen and out_buf
3409 // copy changed part of buffer to virtual screen
3410 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3411 place_cursor(li, cs);
3412 // write line out to terminal
3413 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3417 place_cursor(crow, ccol);
3419 old_offset = offset;
3423 //---------------------------------------------------------------------
3424 //----- the Ascii Chart -----------------------------------------------
3426 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3427 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3428 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3429 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3430 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3431 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3432 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3433 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3434 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3435 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3436 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3437 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3438 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3439 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3440 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3441 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3442 //---------------------------------------------------------------------
3444 //----- Execute a Vi Command -----------------------------------
3445 static void do_cmd(int c)
3447 char *p, *q, *save_dot;
3453 // c1 = c; // quiet the compiler
3454 // cnt = yf = 0; // quiet the compiler
3455 // p = q = save_dot = buf; // quiet the compiler
3456 memset(buf, '\0', sizeof(buf));
3460 /* if this is a cursor key, skip these checks */
3468 case KEYCODE_PAGEUP:
3469 case KEYCODE_PAGEDOWN:
3470 case KEYCODE_DELETE:
3474 if (cmd_mode == 2) {
3475 // flip-flop Insert/Replace mode
3476 if (c == KEYCODE_INSERT)
3478 // we are 'R'eplacing the current *dot with new char
3480 // don't Replace past E-o-l
3481 cmd_mode = 1; // convert to insert
3482 undo_queue_commit();
3484 if (1 <= c || Isprint(c)) {
3486 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3487 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3492 if (cmd_mode == 1) {
3493 // hitting "Insert" twice means "R" replace mode
3494 if (c == KEYCODE_INSERT) goto dc5;
3495 // insert the char c at "dot"
3496 if (1 <= c || Isprint(c)) {
3497 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3512 #if ENABLE_FEATURE_VI_CRASHME
3513 case 0x14: // dc4 ctrl-T
3514 crashme = (crashme == 0) ? 1 : 0;
3544 default: // unrecognized command
3547 not_implemented(buf);
3548 end_cmd_q(); // stop adding to q
3549 case 0x00: // nul- ignore
3551 case 2: // ctrl-B scroll up full screen
3552 case KEYCODE_PAGEUP: // Cursor Key Page Up
3553 dot_scroll(rows - 2, -1);
3555 case 4: // ctrl-D scroll down half screen
3556 dot_scroll((rows - 2) / 2, 1);
3558 case 5: // ctrl-E scroll down one line
3561 case 6: // ctrl-F scroll down full screen
3562 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3563 dot_scroll(rows - 2, 1);
3565 case 7: // ctrl-G show current status
3566 last_status_cksum = 0; // force status update
3568 case 'h': // h- move left
3569 case KEYCODE_LEFT: // cursor key Left
3570 case 8: // ctrl-H- move left (This may be ERASE char)
3571 case 0x7f: // DEL- move left (This may be ERASE char)
3574 } while (--cmdcnt > 0);
3576 case 10: // Newline ^J
3577 case 'j': // j- goto next line, same col
3578 case KEYCODE_DOWN: // cursor key Down
3580 dot_next(); // go to next B-o-l
3581 // try stay in same col
3582 dot = move_to_col(dot, ccol + offset);
3583 } while (--cmdcnt > 0);
3585 case 12: // ctrl-L force redraw whole screen
3586 case 18: // ctrl-R force redraw
3587 redraw(TRUE); // this will redraw the entire display
3589 case 13: // Carriage Return ^M
3590 case '+': // +- goto next line
3594 } while (--cmdcnt > 0);
3596 case 21: // ctrl-U scroll up half screen
3597 dot_scroll((rows - 2) / 2, -1);
3599 case 25: // ctrl-Y scroll up one line
3605 cmd_mode = 0; // stop insrting
3606 undo_queue_commit();
3608 last_status_cksum = 0; // force status update
3610 case ' ': // move right
3611 case 'l': // move right
3612 case KEYCODE_RIGHT: // Cursor Key Right
3615 } while (--cmdcnt > 0);
3617 #if ENABLE_FEATURE_VI_YANKMARK
3618 case '"': // "- name a register to use for Delete/Yank
3619 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3620 if ((unsigned)c1 <= 25) { // a-z?
3626 case '\'': // '- goto a specific mark
3627 c1 = (get_one_char() | 0x20);
3628 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3632 if (text <= q && q < end) {
3634 dot_begin(); // go to B-o-l
3637 } else if (c1 == '\'') { // goto previous context
3638 dot = swap_context(dot); // swap current and previous context
3639 dot_begin(); // go to B-o-l
3645 case 'm': // m- Mark a line
3646 // this is really stupid. If there are any inserts or deletes
3647 // between text[0] and dot then this mark will not point to the
3648 // correct location! It could be off by many lines!
3649 // Well..., at least its quick and dirty.
3650 c1 = (get_one_char() | 0x20) - 'a';
3651 if ((unsigned)c1 <= 25) { // a-z?
3652 // remember the line
3658 case 'P': // P- Put register before
3659 case 'p': // p- put register after
3662 status_line_bold("Nothing in register %c", what_reg());
3665 // are we putting whole lines or strings
3666 if (strchr(p, '\n') != NULL) {
3668 dot_begin(); // putting lines- Put above
3671 // are we putting after very last line?
3672 if (end_line(dot) == (end - 1)) {
3673 dot = end; // force dot to end of text[]
3675 dot_next(); // next line, then put before
3680 dot_right(); // move to right, can move to NL
3682 string_insert(dot, p, ALLOW_UNDO); // insert the string
3683 end_cmd_q(); // stop adding to q
3685 case 'U': // U- Undo; replace current line with original version
3686 if (reg[Ureg] != NULL) {
3687 p = begin_line(dot);
3689 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3690 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3695 #endif /* FEATURE_VI_YANKMARK */
3696 #if ENABLE_FEATURE_VI_UNDO
3697 case 'u': // u- undo last operation
3701 case '$': // $- goto end of line
3702 case KEYCODE_END: // Cursor Key End
3704 dot = end_line(dot);
3710 case '%': // %- find matching char of pair () [] {}
3711 for (q = dot; q < end && *q != '\n'; q++) {
3712 if (strchr("()[]{}", *q) != NULL) {
3713 // we found half of a pair
3714 p = find_pair(q, *q);
3726 case 'f': // f- forward to a user specified char
3727 last_forward_char = get_one_char(); // get the search char
3729 // dont separate these two commands. 'f' depends on ';'
3731 //**** fall through to ... ';'
3732 case ';': // ;- look at rest of line for last forward char
3734 if (last_forward_char == 0)
3737 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3740 if (*q == last_forward_char)
3742 } while (--cmdcnt > 0);
3744 case ',': // repeat latest 'f' in opposite direction
3745 if (last_forward_char == 0)
3749 while (q >= text && *q != '\n' && *q != last_forward_char) {
3752 if (q >= text && *q == last_forward_char)
3754 } while (--cmdcnt > 0);
3757 case '-': // -- goto prev line
3761 } while (--cmdcnt > 0);
3763 #if ENABLE_FEATURE_VI_DOT_CMD
3764 case '.': // .- repeat the last modifying command
3765 // Stuff the last_modifying_cmd back into stdin
3766 // and let it be re-executed.
3768 last_modifying_cmd[lmc_len] = 0;
3769 ioq = ioq_start = xstrdup(last_modifying_cmd);
3773 #if ENABLE_FEATURE_VI_SEARCH
3774 case '?': // /- search for a pattern
3775 case '/': // /- search for a pattern
3778 q = get_input_line(buf); // get input line- use "status line"
3779 if (q[0] && !q[1]) {
3780 if (last_search_pattern[0])
3781 last_search_pattern[0] = c;
3782 goto dc3; // if no pat re-use old pat
3784 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3785 // there is a new pat
3786 free(last_search_pattern);
3787 last_search_pattern = xstrdup(q);
3788 goto dc3; // now find the pattern
3790 // user changed mind and erased the "/"- do nothing
3792 case 'N': // N- backward search for last pattern
3793 dir = BACK; // assume BACKWARD search
3795 if (last_search_pattern[0] == '?') {
3799 goto dc4; // now search for pattern
3801 case 'n': // n- repeat search for last pattern
3802 // search rest of text[] starting at next char
3803 // if search fails return orignal "p" not the "p+1" address
3807 dir = FORWARD; // assume FORWARD search
3809 if (last_search_pattern[0] == '?') {
3814 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3816 dot = q; // good search, update "dot"
3820 // no pattern found between "dot" and "end"- continue at top
3825 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3826 if (q != NULL) { // found something
3827 dot = q; // found new pattern- goto it
3828 msg = "search hit BOTTOM, continuing at TOP";
3830 msg = "search hit TOP, continuing at BOTTOM";
3833 msg = "Pattern not found";
3837 status_line_bold("%s", msg);
3838 } while (--cmdcnt > 0);
3840 case '{': // {- move backward paragraph
3841 q = char_search(dot, "\n\n", (BACK << 1) | FULL);
3842 if (q != NULL) { // found blank line
3843 dot = next_line(q); // move to next blank line
3846 case '}': // }- move forward paragraph
3847 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
3848 if (q != NULL) { // found blank line
3849 dot = next_line(q); // move to next blank line
3852 #endif /* FEATURE_VI_SEARCH */
3853 case '0': // 0- goto beginning of line
3863 if (c == '0' && cmdcnt < 1) {
3864 dot_begin(); // this was a standalone zero
3866 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3869 case ':': // :- the colon mode commands
3870 p = get_input_line(":"); // get input line- use "status line"
3871 colon(p); // execute the command
3873 case '<': // <- Left shift something
3874 case '>': // >- Right shift something
3875 cnt = count_lines(text, dot); // remember what line we are on
3876 c1 = get_one_char(); // get the type of thing to delete
3877 find_range(&p, &q, c1);
3878 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3881 i = count_lines(p, q); // # of lines we are shifting
3882 for ( ; i > 0; i--, p = next_line(p)) {
3884 // shift left- remove tab or 8 spaces
3886 // shrink buffer 1 char
3887 text_hole_delete(p, p, NO_UNDO);
3888 } else if (*p == ' ') {
3889 // we should be calculating columns, not just SPACE
3890 for (j = 0; *p == ' ' && j < tabstop; j++) {
3891 text_hole_delete(p, p, NO_UNDO);
3894 } else if (c == '>') {
3895 // shift right -- add tab or 8 spaces
3896 char_insert(p, '\t', ALLOW_UNDO);
3899 dot = find_line(cnt); // what line were we on
3901 end_cmd_q(); // stop adding to q
3903 case 'A': // A- append at e-o-l
3904 dot_end(); // go to e-o-l
3905 //**** fall through to ... 'a'
3906 case 'a': // a- append after current char
3911 case 'B': // B- back a blank-delimited Word
3912 case 'E': // E- end of a blank-delimited word
3913 case 'W': // W- forward a blank-delimited word
3918 if (c == 'W' || isspace(dot[dir])) {
3919 dot = skip_thing(dot, 1, dir, S_TO_WS);
3920 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3923 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3924 } while (--cmdcnt > 0);
3926 case 'C': // C- Change to e-o-l
3927 case 'D': // D- delete to e-o-l
3929 dot = dollar_line(dot); // move to before NL
3930 // copy text into a register and delete
3931 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3933 goto dc_i; // start inserting
3934 #if ENABLE_FEATURE_VI_DOT_CMD
3936 end_cmd_q(); // stop adding to q
3939 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3940 c1 = get_one_char();
3943 // c1 < 0 if the key was special. Try "g<up-arrow>"
3944 // TODO: if Unicode?
3945 buf[1] = (c1 >= 0 ? c1 : '*');
3947 not_implemented(buf);
3953 case 'G': // G- goto to a line number (default= E-O-F)
3954 dot = end - 1; // assume E-O-F
3956 dot = find_line(cmdcnt); // what line is #cmdcnt
3960 case 'H': // H- goto top line on screen
3962 if (cmdcnt > (rows - 1)) {
3963 cmdcnt = (rows - 1);
3970 case 'I': // I- insert before first non-blank
3973 //**** fall through to ... 'i'
3974 case 'i': // i- insert before current char
3975 case KEYCODE_INSERT: // Cursor Key Insert
3977 cmd_mode = 1; // start inserting
3978 undo_queue_commit(); // commit queue when cmd_mode changes
3980 case 'J': // J- join current and next lines together
3982 dot_end(); // move to NL
3983 if (dot < end - 1) { // make sure not last char in text[]
3984 #if ENABLE_FEATURE_VI_UNDO
3985 undo_push(dot, 1, UNDO_DEL);
3986 *dot++ = ' '; // replace NL with space
3987 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3992 while (isblank(*dot)) { // delete leading WS
3993 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3996 } while (--cmdcnt > 0);
3997 end_cmd_q(); // stop adding to q
3999 case 'L': // L- goto bottom line on screen
4001 if (cmdcnt > (rows - 1)) {
4002 cmdcnt = (rows - 1);
4010 case 'M': // M- goto middle line on screen
4012 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
4013 dot = next_line(dot);
4015 case 'O': // O- open a empty line above
4017 p = begin_line(dot);
4018 if (p[-1] == '\n') {
4020 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
4022 dot = char_insert(dot, '\n', ALLOW_UNDO);
4025 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
4030 case 'R': // R- continuous Replace char
4033 undo_queue_commit();
4035 case KEYCODE_DELETE:
4037 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
4039 case 'X': // X- delete char before dot
4040 case 'x': // x- delete the current char
4041 case 's': // s- substitute the current char
4046 if (dot[dir] != '\n') {
4048 dot--; // delete prev char
4049 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
4051 } while (--cmdcnt > 0);
4052 end_cmd_q(); // stop adding to q
4054 goto dc_i; // start inserting
4056 case 'Z': // Z- if modified, {write}; exit
4057 // ZZ means to save file (if necessary), then exit
4058 c1 = get_one_char();
4063 if (modified_count) {
4064 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
4065 status_line_bold("'%s' is read only", current_filename);
4068 cnt = file_write(current_filename, text, end - 1);
4071 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
4072 } else if (cnt == (end - 1 - text + 1)) {
4079 case '^': // ^- move to first non-blank on line
4083 case 'b': // b- back a word
4084 case 'e': // e- end of word
4089 if ((dot + dir) < text || (dot + dir) > end - 1)
4092 if (isspace(*dot)) {
4093 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4095 if (isalnum(*dot) || *dot == '_') {
4096 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4097 } else if (ispunct(*dot)) {
4098 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4100 } while (--cmdcnt > 0);
4102 case 'c': // c- change something
4103 case 'd': // d- delete something
4104 #if ENABLE_FEATURE_VI_YANKMARK
4105 case 'y': // y- yank something
4106 case 'Y': // Y- Yank a line
4109 int yf, ml, whole = 0;
4110 yf = YANKDEL; // assume either "c" or "d"
4111 #if ENABLE_FEATURE_VI_YANKMARK
4112 if (c == 'y' || c == 'Y')
4117 c1 = get_one_char(); // get the type of thing to delete
4118 // determine range, and whether it spans lines
4119 ml = find_range(&p, &q, c1);
4121 if (c1 == 27) { // ESC- user changed mind and wants out
4122 c = c1 = 27; // Escape- do nothing
4123 } else if (strchr("wW", c1)) {
4125 // don't include trailing WS as part of word
4126 while (isblank(*q)) {
4127 if (q <= text || q[-1] == '\n')
4132 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4133 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
4134 // partial line copy text into a register and delete
4135 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4136 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
4137 // whole line copy text into a register and delete
4138 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
4141 // could not recognize object
4142 c = c1 = 27; // error-
4148 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4149 // on the last line of file don't move to prev line
4150 if (whole && dot != (end-1)) {
4153 } else if (c == 'd') {
4159 // if CHANGING, not deleting, start inserting after the delete
4161 strcpy(buf, "Change");
4162 goto dc_i; // start inserting
4165 strcpy(buf, "Delete");
4167 #if ENABLE_FEATURE_VI_YANKMARK
4168 if (c == 'y' || c == 'Y') {
4169 strcpy(buf, "Yank");
4173 for (cnt = 0; p <= q; p++) {
4177 status_line("%s %d lines (%d chars) using [%c]",
4178 buf, cnt, strlen(reg[YDreg]), what_reg());
4180 end_cmd_q(); // stop adding to q
4184 case 'k': // k- goto prev line, same col
4185 case KEYCODE_UP: // cursor key Up
4188 dot = move_to_col(dot, ccol + offset); // try stay in same col
4189 } while (--cmdcnt > 0);
4191 case 'r': // r- replace the current char with user input
4192 c1 = get_one_char(); // get the replacement char
4194 dot = text_hole_delete(dot, dot, ALLOW_UNDO);
4195 dot = char_insert(dot, c1, ALLOW_UNDO_CHAIN);
4198 end_cmd_q(); // stop adding to q
4200 case 't': // t- move to char prior to next x
4201 last_forward_char = get_one_char();
4203 if (*dot == last_forward_char)
4205 last_forward_char = 0;
4207 case 'w': // w- forward a word
4209 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4210 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4211 } else if (ispunct(*dot)) { // we are on PUNCT
4212 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4215 dot++; // move over word
4216 if (isspace(*dot)) {
4217 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4219 } while (--cmdcnt > 0);
4222 c1 = get_one_char(); // get the replacement char
4225 cnt = (rows - 2) / 2; // put dot at center
4227 cnt = rows - 2; // put dot at bottom
4228 screenbegin = begin_line(dot); // start dot at top
4229 dot_scroll(cnt, -1);
4231 case '|': // |- move to column "cmdcnt"
4232 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4234 case '~': // ~- flip the case of letters a-z -> A-Z
4236 #if ENABLE_FEATURE_VI_UNDO
4237 if (islower(*dot)) {
4238 undo_push(dot, 1, UNDO_DEL);
4239 *dot = toupper(*dot);
4240 undo_push(dot, 1, UNDO_INS_CHAIN);
4241 } else if (isupper(*dot)) {
4242 undo_push(dot, 1, UNDO_DEL);
4243 *dot = tolower(*dot);
4244 undo_push(dot, 1, UNDO_INS_CHAIN);
4247 if (islower(*dot)) {
4248 *dot = toupper(*dot);
4250 } else if (isupper(*dot)) {
4251 *dot = tolower(*dot);
4256 } while (--cmdcnt > 0);
4257 end_cmd_q(); // stop adding to q
4259 //----- The Cursor and Function Keys -----------------------------
4260 case KEYCODE_HOME: // Cursor Key Home
4263 // The Fn keys could point to do_macro which could translate them
4265 case KEYCODE_FUN1: // Function Key F1
4266 case KEYCODE_FUN2: // Function Key F2
4267 case KEYCODE_FUN3: // Function Key F3
4268 case KEYCODE_FUN4: // Function Key F4
4269 case KEYCODE_FUN5: // Function Key F5
4270 case KEYCODE_FUN6: // Function Key F6
4271 case KEYCODE_FUN7: // Function Key F7
4272 case KEYCODE_FUN8: // Function Key F8
4273 case KEYCODE_FUN9: // Function Key F9
4274 case KEYCODE_FUN10: // Function Key F10
4275 case KEYCODE_FUN11: // Function Key F11
4276 case KEYCODE_FUN12: // Function Key F12
4282 // if text[] just became empty, add back an empty line
4284 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4287 // it is OK for dot to exactly equal to end, otherwise check dot validity
4289 dot = bound_dot(dot); // make sure "dot" is valid
4291 #if ENABLE_FEATURE_VI_YANKMARK
4292 check_context(c); // update the current context
4296 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4297 cnt = dot - begin_line(dot);
4298 // Try to stay off of the Newline
4299 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4303 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4304 #if ENABLE_FEATURE_VI_CRASHME
4305 static int totalcmds = 0;
4306 static int Mp = 85; // Movement command Probability
4307 static int Np = 90; // Non-movement command Probability
4308 static int Dp = 96; // Delete command Probability
4309 static int Ip = 97; // Insert command Probability
4310 static int Yp = 98; // Yank command Probability
4311 static int Pp = 99; // Put command Probability
4312 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4313 static const char chars[20] = "\t012345 abcdABCD-=.$";
4314 static const char *const words[20] = {
4315 "this", "is", "a", "test",
4316 "broadcast", "the", "emergency", "of",
4317 "system", "quick", "brown", "fox",
4318 "jumped", "over", "lazy", "dogs",
4319 "back", "January", "Febuary", "March"
4321 static const char *const lines[20] = {
4322 "You should have received a copy of the GNU General Public License\n",
4323 "char c, cm, *cmd, *cmd1;\n",
4324 "generate a command by percentages\n",
4325 "Numbers may be typed as a prefix to some commands.\n",
4326 "Quit, discarding changes!\n",
4327 "Forced write, if permission originally not valid.\n",
4328 "In general, any ex or ed command (such as substitute or delete).\n",
4329 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4330 "Please get w/ me and I will go over it with you.\n",
4331 "The following is a list of scheduled, committed changes.\n",
4332 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4333 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4334 "Any question about transactions please contact Sterling Huxley.\n",
4335 "I will try to get back to you by Friday, December 31.\n",
4336 "This Change will be implemented on Friday.\n",
4337 "Let me know if you have problems accessing this;\n",
4338 "Sterling Huxley recently added you to the access list.\n",
4339 "Would you like to go to lunch?\n",
4340 "The last command will be automatically run.\n",
4341 "This is too much english for a computer geek.\n",
4343 static char *multilines[20] = {
4344 "You should have received a copy of the GNU General Public License\n",
4345 "char c, cm, *cmd, *cmd1;\n",
4346 "generate a command by percentages\n",
4347 "Numbers may be typed as a prefix to some commands.\n",
4348 "Quit, discarding changes!\n",
4349 "Forced write, if permission originally not valid.\n",
4350 "In general, any ex or ed command (such as substitute or delete).\n",
4351 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4352 "Please get w/ me and I will go over it with you.\n",
4353 "The following is a list of scheduled, committed changes.\n",
4354 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4355 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4356 "Any question about transactions please contact Sterling Huxley.\n",
4357 "I will try to get back to you by Friday, December 31.\n",
4358 "This Change will be implemented on Friday.\n",
4359 "Let me know if you have problems accessing this;\n",
4360 "Sterling Huxley recently added you to the access list.\n",
4361 "Would you like to go to lunch?\n",
4362 "The last command will be automatically run.\n",
4363 "This is too much english for a computer geek.\n",
4366 // create a random command to execute
4367 static void crash_dummy()
4369 static int sleeptime; // how long to pause between commands
4370 char c, cm, *cmd, *cmd1;
4371 int i, cnt, thing, rbi, startrbi, percent;
4373 // "dot" movement commands
4374 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4376 // is there already a command running?
4377 if (readbuffer[0] > 0)
4380 readbuffer[0] = 'X';
4382 sleeptime = 0; // how long to pause between commands
4383 memset(readbuffer, '\0', sizeof(readbuffer));
4384 // generate a command by percentages
4385 percent = (int) lrand48() % 100; // get a number from 0-99
4386 if (percent < Mp) { // Movement commands
4387 // available commands
4390 } else if (percent < Np) { // non-movement commands
4391 cmd = "mz<>\'\""; // available commands
4393 } else if (percent < Dp) { // Delete commands
4394 cmd = "dx"; // available commands
4396 } else if (percent < Ip) { // Inset commands
4397 cmd = "iIaAsrJ"; // available commands
4399 } else if (percent < Yp) { // Yank commands
4400 cmd = "yY"; // available commands
4402 } else if (percent < Pp) { // Put commands
4403 cmd = "pP"; // available commands
4406 // We do not know how to handle this command, try again
4410 // randomly pick one of the available cmds from "cmd[]"
4411 i = (int) lrand48() % strlen(cmd);
4413 if (strchr(":\024", cm))
4414 goto cd0; // dont allow colon or ctrl-T commands
4415 readbuffer[rbi++] = cm; // put cmd into input buffer
4417 // now we have the command-
4418 // there are 1, 2, and multi char commands
4419 // find out which and generate the rest of command as necessary
4420 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4421 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4422 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4423 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4425 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4427 readbuffer[rbi++] = c; // add movement to input buffer
4429 if (strchr("iIaAsc", cm)) { // multi-char commands
4431 // change some thing
4432 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4434 readbuffer[rbi++] = c; // add movement to input buffer
4436 thing = (int) lrand48() % 4; // what thing to insert
4437 cnt = (int) lrand48() % 10; // how many to insert
4438 for (i = 0; i < cnt; i++) {
4439 if (thing == 0) { // insert chars
4440 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4441 } else if (thing == 1) { // insert words
4442 strcat(readbuffer, words[(int) lrand48() % 20]);
4443 strcat(readbuffer, " ");
4444 sleeptime = 0; // how fast to type
4445 } else if (thing == 2) { // insert lines
4446 strcat(readbuffer, lines[(int) lrand48() % 20]);
4447 sleeptime = 0; // how fast to type
4448 } else { // insert multi-lines
4449 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4450 sleeptime = 0; // how fast to type
4453 strcat(readbuffer, ESC);
4455 readbuffer[0] = strlen(readbuffer + 1);
4459 mysleep(sleeptime); // sleep 1/100 sec
4462 // test to see if there are any errors
4463 static void crash_test()
4465 static time_t oldtim;
4472 strcat(msg, "end<text ");
4474 if (end > textend) {
4475 strcat(msg, "end>textend ");
4478 strcat(msg, "dot<text ");
4481 strcat(msg, "dot>end ");
4483 if (screenbegin < text) {
4484 strcat(msg, "screenbegin<text ");
4486 if (screenbegin > end - 1) {
4487 strcat(msg, "screenbegin>end-1 ");
4491 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4492 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4494 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4495 if (d[0] == '\n' || d[0] == '\r')
4500 if (tim >= (oldtim + 3)) {
4501 sprintf(status_buffer,
4502 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4503 totalcmds, M, N, I, D, Y, P, U, end - text + 1);