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 smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
350 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
351 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
352 char *context_start, *context_end;
354 #if ENABLE_FEATURE_VI_USE_SIGNALS
355 sigjmp_buf restart; // catch_sig()
357 struct termios term_orig; // remember what the cooked mode was
358 #if ENABLE_FEATURE_VI_COLON
359 char *initial_cmds[3]; // currently 2 entries, NULL terminated
361 // Should be just enough to hold a key sequence,
362 // but CRASHME mode uses it as generated command buffer too
363 #if ENABLE_FEATURE_VI_CRASHME
364 char readbuffer[128];
366 char readbuffer[KEYCODE_BUFFER_SIZE];
368 #define STATUS_BUFFER_LEN 200
369 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
370 #if ENABLE_FEATURE_VI_DOT_CMD
371 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
373 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
375 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
376 #if ENABLE_FEATURE_VI_UNDO
377 // undo_push() operations
380 #define UNDO_INS_CHAIN 2
381 #define UNDO_DEL_CHAIN 3
382 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
383 #define UNDO_QUEUED_FLAG 4
384 #define UNDO_INS_QUEUED 4
385 #define UNDO_DEL_QUEUED 5
386 #define UNDO_USE_SPOS 32
387 #define UNDO_EMPTY 64
388 // Pass-through flags for functions that can be undone
391 #define ALLOW_UNDO_CHAIN 2
392 # if ENABLE_FEATURE_VI_UNDO_QUEUE
393 #define ALLOW_UNDO_QUEUED 3
394 char undo_queue_state;
396 char *undo_queue_spos; // Start position of queued operation
397 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
399 // If undo queuing disabled, don't invoke the missing queue logic
400 #define ALLOW_UNDO_QUEUED 1
404 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
405 int start; // Offset where the data should be restored/deleted
406 int length; // total data size
407 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
408 char undo_text[1]; // text that was deleted (if deletion)
410 #endif /* ENABLE_FEATURE_VI_UNDO */
412 #define G (*ptr_to_globals)
413 #define text (G.text )
414 #define text_size (G.text_size )
419 #define vi_setops (G.vi_setops )
420 #define editing (G.editing )
421 #define cmd_mode (G.cmd_mode )
422 #define modified_count (G.modified_count )
423 #define last_modified_count (G.last_modified_count)
424 #define save_argc (G.save_argc )
425 #define cmdcnt (G.cmdcnt )
426 #define rows (G.rows )
427 #define columns (G.columns )
428 #define crow (G.crow )
429 #define ccol (G.ccol )
430 #define offset (G.offset )
431 #define status_buffer (G.status_buffer )
432 #define have_status_msg (G.have_status_msg )
433 #define last_status_cksum (G.last_status_cksum )
434 #define current_filename (G.current_filename )
435 #define screen (G.screen )
436 #define screensize (G.screensize )
437 #define screenbegin (G.screenbegin )
438 #define tabstop (G.tabstop )
439 #define last_forward_char (G.last_forward_char )
440 #define erase_char (G.erase_char )
441 #define last_input_char (G.last_input_char )
442 #if ENABLE_FEATURE_VI_READONLY
443 #define readonly_mode (G.readonly_mode )
445 #define readonly_mode 0
447 #define adding2q (G.adding2q )
448 #define lmc_len (G.lmc_len )
450 #define ioq_start (G.ioq_start )
451 #define my_pid (G.my_pid )
452 #define last_search_pattern (G.last_search_pattern)
454 #define edit_file__cur_line (G.edit_file__cur_line)
455 #define refresh__old_offset (G.refresh__old_offset)
456 #define format_edit_status__tot (G.format_edit_status__tot)
458 #define YDreg (G.YDreg )
459 //#define Ureg (G.Ureg )
460 #define mark (G.mark )
461 #define context_start (G.context_start )
462 #define context_end (G.context_end )
463 #define restart (G.restart )
464 #define term_orig (G.term_orig )
465 #define initial_cmds (G.initial_cmds )
466 #define readbuffer (G.readbuffer )
467 #define scr_out_buf (G.scr_out_buf )
468 #define last_modifying_cmd (G.last_modifying_cmd )
469 #define get_input_line__buf (G.get_input_line__buf)
471 #if ENABLE_FEATURE_VI_UNDO
472 #define undo_stack_tail (G.undo_stack_tail )
473 # if ENABLE_FEATURE_VI_UNDO_QUEUE
474 #define undo_queue_state (G.undo_queue_state)
475 #define undo_q (G.undo_q )
476 #define undo_queue (G.undo_queue )
477 #define undo_queue_spos (G.undo_queue_spos )
481 #define INIT_G() do { \
482 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
483 last_modified_count = -1; \
484 /* "" but has space for 2 chars: */ \
485 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
489 static void edit_file(char *); // edit one file
490 static void do_cmd(int); // execute a command
491 static int next_tabstop(int);
492 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
493 static char *begin_line(char *); // return pointer to cur line B-o-l
494 static char *end_line(char *); // return pointer to cur line E-o-l
495 static char *prev_line(char *); // return pointer to prev line B-o-l
496 static char *next_line(char *); // return pointer to next line B-o-l
497 static char *end_screen(void); // get pointer to last char on screen
498 static int count_lines(char *, char *); // count line from start to stop
499 static char *find_line(int); // find beginning of line #li
500 static char *move_to_col(char *, int); // move "p" to column l
501 static void dot_left(void); // move dot left- dont leave line
502 static void dot_right(void); // move dot right- dont leave line
503 static void dot_begin(void); // move dot to B-o-l
504 static void dot_end(void); // move dot to E-o-l
505 static void dot_next(void); // move dot to next line B-o-l
506 static void dot_prev(void); // move dot to prev line B-o-l
507 static void dot_scroll(int, int); // move the screen up or down
508 static void dot_skip_over_ws(void); // move dot pat WS
509 static char *bound_dot(char *); // make sure text[0] <= P < "end"
510 static char *new_screen(int, int); // malloc virtual screen memory
511 #if !ENABLE_FEATURE_VI_UNDO
512 #define char_insert(a,b,c) char_insert(a,b)
514 static char *char_insert(char *, char, int); // insert the char c at 'p'
515 // might reallocate text[]! use p += stupid_insert(p, ...),
516 // and be careful to not use pointers into potentially freed text[]!
517 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
518 static int find_range(char **, char **, char); // return pointers for an object
519 static int st_test(char *, int, int, char *); // helper for skip_thing()
520 static char *skip_thing(char *, int, int, int); // skip some object
521 static char *find_pair(char *, char); // find matching pair () [] {}
522 #if !ENABLE_FEATURE_VI_UNDO
523 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
525 static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
526 // might reallocate text[]! use p += text_hole_make(p, ...),
527 // and be careful to not use pointers into potentially freed text[]!
528 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
529 #if !ENABLE_FEATURE_VI_UNDO
530 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
532 static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
533 static void show_help(void); // display some help info
534 static void rawmode(void); // set "raw" mode on tty
535 static void cookmode(void); // return to "cooked" mode on tty
536 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
537 static int mysleep(int);
538 static int readit(void); // read (maybe cursor) key from stdin
539 static int get_one_char(void); // read 1 char from stdin
540 // file_insert might reallocate text[]!
541 static int file_insert(const char *, char *, int);
542 static int file_write(char *, char *, char *);
543 static void place_cursor(int, int);
544 static void screen_erase(void);
545 static void clear_to_eol(void);
546 static void clear_to_eos(void);
547 static void go_bottom_and_clear_to_eol(void);
548 static void standout_start(void); // send "start reverse video" sequence
549 static void standout_end(void); // send "end reverse video" sequence
550 static void flash(int); // flash the terminal screen
551 static void show_status_line(void); // put a message on the bottom line
552 static void status_line(const char *, ...); // print to status buf
553 static void status_line_bold(const char *, ...);
554 static void status_line_bold_errno(const char *fn);
555 static void not_implemented(const char *); // display "Not implemented" message
556 static int format_edit_status(void); // format file status on status line
557 static void redraw(int); // force a full screen refresh
558 static char* format_line(char* /*, int*/);
559 static void refresh(int); // update the terminal from screen[]
561 static void indicate_error(void); // use flash or beep to indicate error
562 static void Hit_Return(void);
564 #if ENABLE_FEATURE_VI_SEARCH
565 static char *char_search(char *, const char *, int); // search for pattern starting at p
567 #if ENABLE_FEATURE_VI_COLON
568 static char *get_one_address(char *, int *); // get colon addr, if present
569 static char *get_address(char *, int *, int *); // get two colon addrs, if present
571 static void colon(char *); // execute the "colon" mode cmds
572 #if ENABLE_FEATURE_VI_USE_SIGNALS
573 static void winch_sig(int); // catch window size changes
574 static void suspend_sig(int); // catch ctrl-Z
575 static void catch_sig(int); // catch ctrl-C and alarm time-outs
577 #if ENABLE_FEATURE_VI_DOT_CMD
578 static void start_new_cmd_q(char); // new queue for command
579 static void end_cmd_q(void); // stop saving input chars
581 #define end_cmd_q() ((void)0)
583 #if ENABLE_FEATURE_VI_SETOPTS
584 static void showmatching(char *); // show the matching pair () [] {}
586 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
587 // might reallocate text[]! use p += string_insert(p, ...),
588 // and be careful to not use pointers into potentially freed text[]!
589 # if !ENABLE_FEATURE_VI_UNDO
590 #define string_insert(a,b,c) string_insert(a,b)
592 static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
594 #if ENABLE_FEATURE_VI_YANKMARK
595 static char *text_yank(char *, char *, int); // save copy of "p" into a register
596 static char what_reg(void); // what is letter of current YDreg
597 static void check_context(char); // remember context for '' command
599 #if ENABLE_FEATURE_VI_UNDO
600 static void flush_undo_data(void);
601 static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
602 static void undo_push_insert(char *, int, int); // convenience function
603 static void undo_pop(void); // Undo the last operation
604 # if ENABLE_FEATURE_VI_UNDO_QUEUE
605 static void undo_queue_commit(void); // Flush any queued objects to the undo stack
607 # define undo_queue_commit() ((void)0)
610 #define flush_undo_data() ((void)0)
611 #define undo_queue_commit() ((void)0)
614 #if ENABLE_FEATURE_VI_CRASHME
615 static void crash_dummy();
616 static void crash_test();
617 static int crashme = 0;
620 static void write1(const char *out)
625 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
626 int vi_main(int argc, char **argv)
632 #if ENABLE_FEATURE_VI_UNDO
633 /* undo_stack_tail = NULL; - already is */
634 #if ENABLE_FEATURE_VI_UNDO_QUEUE
635 undo_queue_state = UNDO_EMPTY;
636 /* undo_q = 0; - already is */
640 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
643 #if ENABLE_FEATURE_VI_CRASHME
644 srand((long) my_pid);
646 #ifdef NO_SUCH_APPLET_YET
647 /* If we aren't "vi", we are "view" */
648 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
649 SET_READONLY_MODE(readonly_mode);
653 // autoindent is not default in vim 7.3
654 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
655 // 1- process $HOME/.exrc file (not inplemented yet)
656 // 2- process EXINIT variable from environment
657 // 3- process command line args
658 #if ENABLE_FEATURE_VI_COLON
660 char *p = getenv("EXINIT");
662 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
665 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
667 #if ENABLE_FEATURE_VI_CRASHME
672 #if ENABLE_FEATURE_VI_READONLY
673 case 'R': // Read-only flag
674 SET_READONLY_MODE(readonly_mode);
677 #if ENABLE_FEATURE_VI_COLON
678 case 'c': // cmd line vi command
680 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
692 // The argv array can be used by the ":next" and ":rewind" commands
696 //----- This is the main file handling loop --------------
699 // "Save cursor, use alternate screen buffer, clear screen"
700 write1(ESC"[?1049h");
702 edit_file(argv[optind]); /* param might be NULL */
703 if (++optind >= argc)
706 // "Use normal screen buffer, restore cursor"
707 write1(ESC"[?1049l");
708 //-----------------------------------------------------------
713 /* read text from file or create an empty buf */
714 /* will also update current_filename */
715 static int init_text_buffer(char *fn)
719 /* allocate/reallocate text buffer */
722 screenbegin = dot = end = text = xzalloc(text_size);
724 if (fn != current_filename) {
725 free(current_filename);
726 current_filename = xstrdup(fn);
728 rc = file_insert(fn, text, 1);
730 // file doesnt exist. Start empty buf with dummy line
731 char_insert(text, '\n', NO_UNDO);
736 last_modified_count = -1;
737 #if ENABLE_FEATURE_VI_YANKMARK
739 memset(mark, 0, sizeof(mark));
744 #if ENABLE_FEATURE_VI_WIN_RESIZE
745 static int query_screen_dimensions(void)
747 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
748 if (rows > MAX_SCR_ROWS)
750 if (columns > MAX_SCR_COLS)
751 columns = MAX_SCR_COLS;
755 static ALWAYS_INLINE int query_screen_dimensions(void)
761 static void edit_file(char *fn)
763 #if ENABLE_FEATURE_VI_YANKMARK
764 #define cur_line edit_file__cur_line
767 #if ENABLE_FEATURE_VI_USE_SIGNALS
771 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
775 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
776 #if ENABLE_FEATURE_VI_ASK_TERMINAL
777 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
779 write1(ESC"[999;999H" ESC"[6n");
781 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
782 if ((int32_t)k == KEYCODE_CURSOR_POS) {
783 uint32_t rc = (k >> 32);
784 columns = (rc & 0x7fff);
785 if (columns > MAX_SCR_COLS)
786 columns = MAX_SCR_COLS;
787 rows = ((rc >> 16) & 0x7fff);
788 if (rows > MAX_SCR_ROWS)
793 new_screen(rows, columns); // get memory for virtual screen
794 init_text_buffer(fn);
796 #if ENABLE_FEATURE_VI_YANKMARK
797 YDreg = 26; // default Yank/Delete reg
798 // Ureg = 27; - const // hold orig line for "U" cmd
799 mark[26] = mark[27] = text; // init "previous context"
802 last_forward_char = last_input_char = '\0';
806 #if ENABLE_FEATURE_VI_USE_SIGNALS
807 signal(SIGINT, catch_sig);
808 signal(SIGWINCH, winch_sig);
809 signal(SIGTSTP, suspend_sig);
810 sig = sigsetjmp(restart, 1);
812 screenbegin = dot = text;
816 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
819 offset = 0; // no horizontal offset
821 #if ENABLE_FEATURE_VI_DOT_CMD
823 ioq = ioq_start = NULL;
828 #if ENABLE_FEATURE_VI_COLON
833 while ((p = initial_cmds[n]) != NULL) {
843 free(initial_cmds[n]);
844 initial_cmds[n] = NULL;
849 redraw(FALSE); // dont force every col re-draw
850 //------This is the main Vi cmd handling loop -----------------------
851 while (editing > 0) {
852 #if ENABLE_FEATURE_VI_CRASHME
854 if ((end - text) > 1) {
855 crash_dummy(); // generate a random command
858 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
864 last_input_char = c = get_one_char(); // get a cmd from user
865 #if ENABLE_FEATURE_VI_YANKMARK
866 // save a copy of the current line- for the 'U" command
867 if (begin_line(dot) != cur_line) {
868 cur_line = begin_line(dot);
869 text_yank(begin_line(dot), end_line(dot), Ureg);
872 #if ENABLE_FEATURE_VI_DOT_CMD
873 // These are commands that change text[].
874 // Remember the input for the "." command
875 if (!adding2q && ioq_start == NULL
876 && cmd_mode == 0 // command mode
877 && c > '\0' // exclude NUL and non-ASCII chars
878 && c < 0x7f // (Unicode and such)
879 && strchr(modifying_cmds, c)
884 do_cmd(c); // execute the user command
886 // poll to see if there is input already waiting. if we are
887 // not able to display output fast enough to keep up, skip
888 // the display update until we catch up with input.
889 if (!readbuffer[0] && mysleep(0) == 0) {
890 // no input pending - so update output
894 #if ENABLE_FEATURE_VI_CRASHME
896 crash_test(); // test editor variables
899 //-------------------------------------------------------------------
901 go_bottom_and_clear_to_eol();
906 //----- The Colon commands -------------------------------------
907 #if ENABLE_FEATURE_VI_COLON
908 static char *get_one_address(char *p, int *addr) // get colon addr, if present
912 IF_FEATURE_VI_YANKMARK(char c;)
913 IF_FEATURE_VI_SEARCH(char *pat;)
915 *addr = -1; // assume no addr
916 if (*p == '.') { // the current line
919 *addr = count_lines(text, q);
921 #if ENABLE_FEATURE_VI_YANKMARK
922 else if (*p == '\'') { // is this a mark addr
926 if (c >= 'a' && c <= 'z') {
929 q = mark[(unsigned char) c];
930 if (q != NULL) { // is mark valid
931 *addr = count_lines(text, q);
936 #if ENABLE_FEATURE_VI_SEARCH
937 else if (*p == '/') { // a search pattern
938 q = strchrnul(++p, '/');
939 pat = xstrndup(p, q - p); // save copy of pattern
943 q = char_search(dot, pat, (FORWARD << 1) | FULL);
945 *addr = count_lines(text, q);
950 else if (*p == '$') { // the last line in file
952 q = begin_line(end - 1);
953 *addr = count_lines(text, q);
954 } else if (isdigit(*p)) { // specific line number
955 sscanf(p, "%d%n", addr, &st);
958 // unrecognized address - assume -1
964 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
966 //----- get the address' i.e., 1,3 'a,'b -----
967 // get FIRST addr, if present
969 p++; // skip over leading spaces
970 if (*p == '%') { // alias for 1,$
973 *e = count_lines(text, end-1);
976 p = get_one_address(p, b);
979 if (*p == ',') { // is there a address separator
983 // get SECOND addr, if present
984 p = get_one_address(p, e);
988 p++; // skip over trailing spaces
992 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
993 static void setops(const char *args, const char *opname, int flg_no,
994 const char *short_opname, int opt)
996 const char *a = args + flg_no;
997 int l = strlen(opname) - 1; /* opname have + ' ' */
999 // maybe strncmp? we had tons of erroneous strncasecmp's...
1000 if (strncasecmp(a, opname, l) == 0
1001 || strncasecmp(a, short_opname, 2) == 0
1011 #endif /* FEATURE_VI_COLON */
1013 // buf must be no longer than MAX_INPUT_LEN!
1014 static void colon(char *buf)
1016 #if !ENABLE_FEATURE_VI_COLON
1017 /* Simple ":cmd" handler with minimal set of commands */
1026 if (strncmp(p, "quit", cnt) == 0
1027 || strncmp(p, "q!", cnt) == 0
1029 if (modified_count && p[1] != '!') {
1030 status_line_bold("No write since last change (:%s! overrides)", p);
1036 if (strncmp(p, "write", cnt) == 0
1037 || strncmp(p, "wq", cnt) == 0
1038 || strncmp(p, "wn", cnt) == 0
1039 || (p[0] == 'x' && !p[1])
1041 if (modified_count != 0 || p[0] != 'x') {
1042 cnt = file_write(current_filename, text, end - 1);
1046 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
1049 last_modified_count = -1;
1050 status_line("'%s' %dL, %dC",
1052 count_lines(text, end - 1), cnt
1055 || p[1] == 'q' || p[1] == 'n'
1056 || p[1] == 'Q' || p[1] == 'N'
1063 if (strncmp(p, "file", cnt) == 0) {
1064 last_status_cksum = 0; // force status update
1067 if (sscanf(p, "%d", &cnt) > 0) {
1068 dot = find_line(cnt);
1075 char c, *buf1, *q, *r;
1076 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
1079 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
1083 // :3154 // if (-e line 3154) goto it else stay put
1084 // :4,33w! foo // write a portion of buffer to file "foo"
1085 // :w // write all of buffer to current file
1087 // :q! // quit- dont care about modified file
1088 // :'a,'z!sort -u // filter block through sort
1089 // :'f // goto mark "f"
1090 // :'fl // list literal the mark "f" line
1091 // :.r bar // read file "bar" into buffer before dot
1092 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1093 // :/xyz/ // goto the "xyz" line
1094 // :s/find/replace/ // substitute pattern "find" with "replace"
1095 // :!<cmd> // run <cmd> then return
1101 buf++; // move past the ':'
1105 q = text; // assume 1,$ for the range
1107 li = count_lines(text, end - 1);
1108 fn = current_filename;
1110 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1111 buf = get_address(buf, &b, &e);
1113 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
1114 // remember orig command line
1118 // get the COMMAND into cmd[]
1120 while (*buf != '\0') {
1126 // get any ARGuments
1127 while (isblank(*buf))
1131 buf1 = last_char_is(cmd, '!');
1134 *buf1 = '\0'; // get rid of !
1137 // if there is only one addr, then the addr
1138 // is the line number of the single line the
1139 // user wants. So, reset the end
1140 // pointer to point at end of the "b" line
1141 q = find_line(b); // what line is #b
1146 // we were given two addrs. change the
1147 // end pointer to the addr given by user.
1148 r = find_line(e); // what line is #e
1152 // ------------ now look for the command ------------
1154 if (i == 0) { // :123CR goto line #123
1156 dot = find_line(b); // what line is #b
1160 # if ENABLE_FEATURE_ALLOW_EXEC
1161 else if (cmd[0] == '!') { // run a cmd
1163 // :!ls run the <cmd>
1164 go_bottom_and_clear_to_eol();
1166 retcode = system(orig_buf + 1); // run the cmd
1168 printf("\nshell returned %i\n\n", retcode);
1170 Hit_Return(); // let user see results
1173 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1174 if (b < 0) { // no addr given- use defaults
1175 b = e = count_lines(text, dot);
1177 status_line("%d", b);
1178 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1179 if (b < 0) { // no addr given- use defaults
1180 q = begin_line(dot); // assume .,. for the range
1183 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
1185 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1188 // don't edit, if the current file has been modified
1189 if (modified_count && !useforce) {
1190 status_line_bold("No write since last change (:%s! overrides)", cmd);
1194 // the user supplied a file name
1196 } else if (current_filename && current_filename[0]) {
1197 // no user supplied name- use the current filename
1198 // fn = current_filename; was set by default
1200 // no user file name, no current name- punt
1201 status_line_bold("No current filename");
1205 size = init_text_buffer(fn);
1207 # if ENABLE_FEATURE_VI_YANKMARK
1208 if (Ureg >= 0 && Ureg < 28) {
1209 free(reg[Ureg]); // free orig line reg- for 'U'
1212 if (YDreg >= 0 && YDreg < 28) {
1213 free(reg[YDreg]); // free default yank/delete register
1217 // how many lines in text[]?
1218 li = count_lines(text, end - 1);
1219 status_line("'%s'%s"
1220 IF_FEATURE_VI_READONLY("%s")
1223 (size < 0 ? " [New file]" : ""),
1224 IF_FEATURE_VI_READONLY(
1225 ((readonly_mode) ? " [Readonly]" : ""),
1227 li, (int)(end - text)
1229 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1230 if (b != -1 || e != -1) {
1231 status_line_bold("No address allowed on this command");
1235 // user wants a new filename
1236 free(current_filename);
1237 current_filename = xstrdup(args);
1239 // user wants file status info
1240 last_status_cksum = 0; // force status update
1242 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1243 // print out values of all features
1244 go_bottom_and_clear_to_eol();
1249 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1250 if (b < 0) { // no addr given- use defaults
1251 q = begin_line(dot); // assume .,. for the range
1254 go_bottom_and_clear_to_eol();
1256 for (; q <= r; q++) {
1260 c_is_no_print = (c & 0x80) && !Isprint(c);
1261 if (c_is_no_print) {
1267 } else if (c < ' ' || c == 127) {
1279 } else if (strncmp(cmd, "quit", i) == 0 // quit
1280 || strncmp(cmd, "next", i) == 0 // edit next file
1281 || strncmp(cmd, "prev", i) == 0 // edit previous file
1286 // force end of argv list
1292 // don't exit if the file been modified
1293 if (modified_count) {
1294 status_line_bold("No write since last change (:%s! overrides)", cmd);
1297 // are there other file to edit
1298 n = save_argc - optind - 1;
1299 if (*cmd == 'q' && n > 0) {
1300 status_line_bold("%d more file(s) to edit", n);
1303 if (*cmd == 'n' && n <= 0) {
1304 status_line_bold("No more files to edit");
1308 // are there previous files to edit
1310 status_line_bold("No previous files to edit");
1316 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1321 status_line_bold("No filename given");
1324 if (b < 0) { // no addr given- use defaults
1325 q = begin_line(dot); // assume "dot"
1327 // read after current line- unless user said ":0r foo"
1330 // read after last line
1334 { // dance around potentially-reallocated text[]
1335 uintptr_t ofs = q - text;
1336 size = file_insert(fn, q, 0);
1340 goto ret; // nothing was inserted
1341 // how many lines in text[]?
1342 li = count_lines(q, q + size - 1);
1344 IF_FEATURE_VI_READONLY("%s")
1347 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1351 // if the insert is before "dot" then we need to update
1355 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1356 if (modified_count && !useforce) {
1357 status_line_bold("No write since last change (:%s! overrides)", cmd);
1359 // reset the filenames to edit
1360 optind = -1; /* start from 0th file */
1363 # if ENABLE_FEATURE_VI_SET
1364 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1365 # if ENABLE_FEATURE_VI_SETOPTS
1368 i = 0; // offset into args
1369 // only blank is regarded as args delimiter. What about tab '\t'?
1370 if (!args[0] || strcasecmp(args, "all") == 0) {
1371 // print out values of all options
1372 # if ENABLE_FEATURE_VI_SETOPTS
1379 autoindent ? "" : "no",
1380 err_method ? "" : "no",
1381 ignorecase ? "" : "no",
1382 showmatch ? "" : "no",
1388 # if ENABLE_FEATURE_VI_SETOPTS
1391 if (strncmp(argp, "no", 2) == 0)
1392 i = 2; // ":set noautoindent"
1393 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1394 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1395 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1396 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1397 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1399 sscanf(argp + i+8, "%u", &t);
1400 if (t > 0 && t <= MAX_TABSTOP)
1403 argp = skip_non_whitespace(argp);
1404 argp = skip_whitespace(argp);
1406 # endif /* FEATURE_VI_SETOPTS */
1407 # endif /* FEATURE_VI_SET */
1409 # if ENABLE_FEATURE_VI_SEARCH
1410 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1411 char *F, *R, *flags;
1412 size_t len_F, len_R;
1413 int gflag; // global replace flag
1414 # if ENABLE_FEATURE_VI_UNDO
1415 int dont_chain_first_item = ALLOW_UNDO;
1418 // F points to the "find" pattern
1419 // R points to the "replace" pattern
1420 // replace the cmd line delimiters "/" with NULs
1421 c = orig_buf[1]; // what is the delimiter
1422 F = orig_buf + 2; // start of "find"
1423 R = strchr(F, c); // middle delimiter
1427 *R++ = '\0'; // terminate "find"
1428 flags = strchr(R, c);
1432 *flags++ = '\0'; // terminate "replace"
1436 if (b < 0) { // maybe :s/foo/bar/
1437 q = begin_line(dot); // start with cur line
1438 b = count_lines(text, q); // cur line number
1441 e = b; // maybe :.s/foo/bar/
1443 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1444 char *ls = q; // orig line start
1447 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
1450 // we found the "find" pattern - delete it
1451 // For undo support, the first item should not be chained
1452 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
1453 # if ENABLE_FEATURE_VI_UNDO
1454 dont_chain_first_item = ALLOW_UNDO_CHAIN;
1456 // insert the "replace" patern
1457 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
1460 /*q += bias; - recalculated anyway */
1461 // check for "global" :s/foo/bar/g
1463 if ((found + len_R) < end_line(ls)) {
1465 goto vc4; // don't let q move past cur line
1471 # endif /* FEATURE_VI_SEARCH */
1472 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1473 status_line(BB_VER);
1474 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1475 || strncmp(cmd, "wq", i) == 0
1476 || strncmp(cmd, "wn", i) == 0
1477 || (cmd[0] == 'x' && !cmd[1])
1480 //int forced = FALSE;
1482 // is there a file name to write to?
1486 # if ENABLE_FEATURE_VI_READONLY
1487 if (readonly_mode && !useforce) {
1488 status_line_bold("'%s' is read only", fn);
1493 // if "fn" is not write-able, chmod u+w
1494 // sprintf(syscmd, "chmod u+w %s", fn);
1498 if (modified_count != 0 || cmd[0] != 'x') {
1500 l = file_write(fn, q, r);
1505 //if (useforce && forced) {
1507 // sprintf(syscmd, "chmod u-w %s", fn);
1513 status_line_bold_errno(fn);
1515 // how many lines written
1516 li = count_lines(q, q + l - 1);
1517 status_line("'%s' %dL, %dC", fn, li, l);
1519 if (q == text && q + l == end) {
1521 last_modified_count = -1;
1524 || cmd[1] == 'q' || cmd[1] == 'n'
1525 || cmd[1] == 'Q' || cmd[1] == 'N'
1531 # if ENABLE_FEATURE_VI_YANKMARK
1532 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1533 if (b < 0) { // no addr given- use defaults
1534 q = begin_line(dot); // assume .,. for the range
1537 text_yank(q, r, YDreg);
1538 li = count_lines(q, r);
1539 status_line("Yank %d lines (%d chars) into [%c]",
1540 li, strlen(reg[YDreg]), what_reg());
1544 not_implemented(cmd);
1547 dot = bound_dot(dot); // make sure "dot" is valid
1549 # if ENABLE_FEATURE_VI_SEARCH
1551 status_line(":s expression missing delimiters");
1553 #endif /* FEATURE_VI_COLON */
1556 static void Hit_Return(void)
1561 write1("[Hit return to continue]");
1563 while ((c = get_one_char()) != '\n' && c != '\r')
1565 redraw(TRUE); // force redraw all
1568 static int next_tabstop(int col)
1570 return col + ((tabstop - 1) - (col % tabstop));
1573 //----- Synchronize the cursor to Dot --------------------------
1574 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1576 char *beg_cur; // begin and end of "d" line
1580 beg_cur = begin_line(d); // first char of cur line
1582 if (beg_cur < screenbegin) {
1583 // "d" is before top line on screen
1584 // how many lines do we have to move
1585 cnt = count_lines(beg_cur, screenbegin);
1587 screenbegin = beg_cur;
1588 if (cnt > (rows - 1) / 2) {
1589 // we moved too many lines. put "dot" in middle of screen
1590 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1591 screenbegin = prev_line(screenbegin);
1595 char *end_scr; // begin and end of screen
1596 end_scr = end_screen(); // last char of screen
1597 if (beg_cur > end_scr) {
1598 // "d" is after bottom line on screen
1599 // how many lines do we have to move
1600 cnt = count_lines(end_scr, beg_cur);
1601 if (cnt > (rows - 1) / 2)
1602 goto sc1; // too many lines
1603 for (ro = 0; ro < cnt - 1; ro++) {
1604 // move screen begin the same amount
1605 screenbegin = next_line(screenbegin);
1606 // now, move the end of screen
1607 end_scr = next_line(end_scr);
1608 end_scr = end_line(end_scr);
1612 // "d" is on screen- find out which row
1614 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1620 // find out what col "d" is on
1622 while (tp < d) { // drive "co" to correct column
1623 if (*tp == '\n') //vda || *tp == '\0')
1626 // handle tabs like real vi
1627 if (d == tp && cmd_mode) {
1630 co = next_tabstop(co);
1631 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1632 co++; // display as ^X, use 2 columns
1638 // "co" is the column where "dot" is.
1639 // The screen has "columns" columns.
1640 // The currently displayed columns are 0+offset -- columns+ofset
1641 // |-------------------------------------------------------------|
1643 // offset | |------- columns ----------------|
1645 // If "co" is already in this range then we do not have to adjust offset
1646 // but, we do have to subtract the "offset" bias from "co".
1647 // If "co" is outside this range then we have to change "offset".
1648 // If the first char of a line is a tab the cursor will try to stay
1649 // in column 7, but we have to set offset to 0.
1651 if (co < 0 + offset) {
1654 if (co >= columns + offset) {
1655 offset = co - columns + 1;
1657 // if the first char of the line is a tab, and "dot" is sitting on it
1658 // force offset to 0.
1659 if (d == beg_cur && *d == '\t') {
1668 //----- Text Movement Routines ---------------------------------
1669 static char *begin_line(char *p) // return pointer to first char cur line
1672 p = memrchr(text, '\n', p - text);
1680 static char *end_line(char *p) // return pointer to NL of cur line
1683 p = memchr(p, '\n', end - p - 1);
1690 static char *dollar_line(char *p) // return pointer to just before NL line
1693 // Try to stay off of the Newline
1694 if (*p == '\n' && (p - begin_line(p)) > 0)
1699 static char *prev_line(char *p) // return pointer first char prev line
1701 p = begin_line(p); // goto beginning of cur line
1702 if (p > text && p[-1] == '\n')
1703 p--; // step to prev line
1704 p = begin_line(p); // goto beginning of prev line
1708 static char *next_line(char *p) // return pointer first char next line
1711 if (p < end - 1 && *p == '\n')
1712 p++; // step to next line
1716 //----- Text Information Routines ------------------------------
1717 static char *end_screen(void)
1722 // find new bottom line
1724 for (cnt = 0; cnt < rows - 2; cnt++)
1730 // count line from start to stop
1731 static int count_lines(char *start, char *stop)
1736 if (stop < start) { // start and stop are backwards- reverse them
1742 stop = end_line(stop);
1743 while (start <= stop && start <= end - 1) {
1744 start = end_line(start);
1752 static char *find_line(int li) // find beginning of line #li
1756 for (q = text; li > 1; li--) {
1762 //----- Dot Movement Routines ----------------------------------
1763 static void dot_left(void)
1765 undo_queue_commit();
1766 if (dot > text && dot[-1] != '\n')
1770 static void dot_right(void)
1772 undo_queue_commit();
1773 if (dot < end - 1 && *dot != '\n')
1777 static void dot_begin(void)
1779 undo_queue_commit();
1780 dot = begin_line(dot); // return pointer to first char cur line
1783 static void dot_end(void)
1785 undo_queue_commit();
1786 dot = end_line(dot); // return pointer to last char cur line
1789 static char *move_to_col(char *p, int l)
1795 while (co < l && p < end) {
1796 if (*p == '\n') //vda || *p == '\0')
1799 co = next_tabstop(co);
1800 } else if (*p < ' ' || *p == 127) {
1801 co++; // display as ^X, use 2 columns
1809 static void dot_next(void)
1811 undo_queue_commit();
1812 dot = next_line(dot);
1815 static void dot_prev(void)
1817 undo_queue_commit();
1818 dot = prev_line(dot);
1821 static void dot_scroll(int cnt, int dir)
1825 undo_queue_commit();
1826 for (; cnt > 0; cnt--) {
1829 // ctrl-Y scroll up one line
1830 screenbegin = prev_line(screenbegin);
1833 // ctrl-E scroll down one line
1834 screenbegin = next_line(screenbegin);
1837 // make sure "dot" stays on the screen so we dont scroll off
1838 if (dot < screenbegin)
1840 q = end_screen(); // find new bottom line
1842 dot = begin_line(q); // is dot is below bottom line?
1846 static void dot_skip_over_ws(void)
1849 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1853 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1855 if (p >= end && end > text) {
1866 //----- Helper Utility Routines --------------------------------
1868 //----------------------------------------------------------------
1869 //----- Char Routines --------------------------------------------
1870 /* Chars that are part of a word-
1871 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1872 * Chars that are Not part of a word (stoppers)
1873 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1874 * Chars that are WhiteSpace
1875 * TAB NEWLINE VT FF RETURN SPACE
1876 * DO NOT COUNT NEWLINE AS WHITESPACE
1879 static char *new_screen(int ro, int co)
1884 screensize = ro * co + 8;
1885 screen = xmalloc(screensize);
1886 // initialize the new screen. assume this will be a empty file.
1888 // non-existent text[] lines start with a tilde (~).
1889 for (li = 1; li < ro - 1; li++) {
1890 screen[(li * co) + 0] = '~';
1895 #if ENABLE_FEATURE_VI_SEARCH
1897 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1899 // search for pattern starting at p
1900 static char *char_search(char *p, const char *pat, int dir_and_range)
1902 struct re_pattern_buffer preg;
1909 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1911 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1913 memset(&preg, 0, sizeof(preg));
1914 err = re_compile_pattern(pat, strlen(pat), &preg);
1916 status_line_bold("bad search pattern '%s': %s", pat, err);
1920 range = (dir_and_range & 1);
1921 q = end - 1; // if FULL
1922 if (range == LIMITED)
1924 if (dir_and_range < 0) { // BACK?
1926 if (range == LIMITED)
1930 // RANGE could be negative if we are searching backwards
1940 // search for the compiled pattern, preg, in p[]
1941 // range < 0: search backward
1942 // range > 0: search forward
1944 // re_search() < 0: not found or error
1945 // re_search() >= 0: index of found pattern
1946 // struct pattern char int int int struct reg
1947 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1948 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1952 if (dir_and_range > 0) // FORWARD?
1961 # if ENABLE_FEATURE_VI_SETOPTS
1962 static int mycmp(const char *s1, const char *s2, int len)
1965 return strncasecmp(s1, s2, len);
1967 return strncmp(s1, s2, len);
1970 # define mycmp strncmp
1973 static char *char_search(char *p, const char *pat, int dir_and_range)
1980 range = (dir_and_range & 1);
1981 if (dir_and_range > 0) { //FORWARD?
1982 stop = end - 1; // assume range is p..end-1
1983 if (range == LIMITED)
1984 stop = next_line(p); // range is to next line
1985 for (start = p; start < stop; start++) {
1986 if (mycmp(start, pat, len) == 0) {
1991 stop = text; // assume range is text..p
1992 if (range == LIMITED)
1993 stop = prev_line(p); // range is to prev line
1994 for (start = p - len; start >= stop; start--) {
1995 if (mycmp(start, pat, len) == 0) {
2000 // pattern not found
2006 #endif /* FEATURE_VI_SEARCH */
2008 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
2010 if (c == 22) { // Is this an ctrl-V?
2011 p += stupid_insert(p, '^'); // use ^ to indicate literal next
2012 refresh(FALSE); // show the ^
2015 #if ENABLE_FEATURE_VI_UNDO
2016 undo_push_insert(p, 1, undo);
2019 #endif /* ENABLE_FEATURE_VI_UNDO */
2021 } else if (c == 27) { // Is this an ESC?
2023 undo_queue_commit();
2025 end_cmd_q(); // stop adding to q
2026 last_status_cksum = 0; // force status update
2027 if ((p[-1] != '\n') && (dot > text)) {
2030 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
2033 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2036 // insert a char into text[]
2038 c = '\n'; // translate \r to \n
2039 #if ENABLE_FEATURE_VI_UNDO
2040 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2042 undo_queue_commit();
2044 undo_push_insert(p, 1, undo);
2047 #endif /* ENABLE_FEATURE_VI_UNDO */
2048 p += 1 + stupid_insert(p, c); // insert the char
2049 #if ENABLE_FEATURE_VI_SETOPTS
2050 if (showmatch && strchr(")]}", c) != NULL) {
2051 showmatching(p - 1);
2053 if (autoindent && c == '\n') { // auto indent the new line
2056 q = prev_line(p); // use prev line as template
2057 len = strspn(q, " \t"); // space or tab
2060 bias = text_hole_make(p, len);
2063 #if ENABLE_FEATURE_VI_UNDO
2064 undo_push_insert(p, len, undo);
2075 // might reallocate text[]! use p += stupid_insert(p, ...),
2076 // and be careful to not use pointers into potentially freed text[]!
2077 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2080 bias = text_hole_make(p, 1);
2086 static int find_range(char **start, char **stop, char c)
2088 char *save_dot, *p, *q, *t;
2089 int cnt, multiline = 0;
2094 if (strchr("cdy><", c)) {
2095 // these cmds operate on whole lines
2096 p = q = begin_line(p);
2097 for (cnt = 1; cnt < cmdcnt; cnt++) {
2101 } else if (strchr("^%$0bBeEfth\b\177", c)) {
2102 // These cmds operate on char positions
2103 do_cmd(c); // execute movement cmd
2105 } else if (strchr("wW", c)) {
2106 do_cmd(c); // execute movement cmd
2107 // if we are at the next word's first char
2108 // step back one char
2109 // but check the possibilities when it is true
2110 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
2111 || (ispunct(dot[-1]) && !ispunct(dot[0]))
2112 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
2113 dot--; // move back off of next word
2114 if (dot > text && *dot == '\n')
2115 dot--; // stay off NL
2117 } else if (strchr("H-k{", c)) {
2118 // these operate on multi-lines backwards
2119 q = end_line(dot); // find NL
2120 do_cmd(c); // execute movement cmd
2123 } else if (strchr("L+j}\r\n", c)) {
2124 // these operate on multi-lines forwards
2125 p = begin_line(dot);
2126 do_cmd(c); // execute movement cmd
2127 dot_end(); // find NL
2130 // nothing -- this causes any other values of c to
2131 // represent the one-character range under the
2132 // cursor. this is correct for ' ' and 'l', but
2133 // perhaps no others.
2142 // backward char movements don't include start position
2143 if (q > p && strchr("^0bBh\b\177", c)) q--;
2146 for (t = p; t <= q; t++) {
2159 static int st_test(char *p, int type, int dir, char *tested)
2169 if (type == S_BEFORE_WS) {
2171 test = (!isspace(c) || c == '\n');
2173 if (type == S_TO_WS) {
2175 test = (!isspace(c) || c == '\n');
2177 if (type == S_OVER_WS) {
2181 if (type == S_END_PUNCT) {
2185 if (type == S_END_ALNUM) {
2187 test = (isalnum(c) || c == '_');
2193 static char *skip_thing(char *p, int linecnt, int dir, int type)
2197 while (st_test(p, type, dir, &c)) {
2198 // make sure we limit search to correct number of lines
2199 if (c == '\n' && --linecnt < 1)
2201 if (dir >= 0 && p >= end - 1)
2203 if (dir < 0 && p <= text)
2205 p += dir; // move to next char
2210 // find matching char of pair () [] {}
2211 // will crash if c is not one of these
2212 static char *find_pair(char *p, const char c)
2214 const char *braces = "()[]{}";
2218 dir = strchr(braces, c) - braces;
2220 match = braces[dir];
2221 dir = ((dir & 1) << 1) - 1; /* 1 for ([{, -1 for )\} */
2223 // look for match, count levels of pairs (( ))
2227 if (p < text || p >= end)
2230 level++; // increase pair levels
2232 level--; // reduce pair level
2234 return p; // found matching pair
2239 #if ENABLE_FEATURE_VI_SETOPTS
2240 // show the matching char of a pair, () [] {}
2241 static void showmatching(char *p)
2245 // we found half of a pair
2246 q = find_pair(p, *p); // get loc of matching char
2248 indicate_error(); // no matching char
2250 // "q" now points to matching pair
2251 save_dot = dot; // remember where we are
2252 dot = q; // go to new loc
2253 refresh(FALSE); // let the user see it
2254 mysleep(40); // give user some time
2255 dot = save_dot; // go back to old loc
2259 #endif /* FEATURE_VI_SETOPTS */
2261 #if ENABLE_FEATURE_VI_UNDO
2262 static void flush_undo_data(void)
2264 struct undo_object *undo_entry;
2266 while (undo_stack_tail) {
2267 undo_entry = undo_stack_tail;
2268 undo_stack_tail = undo_entry->prev;
2273 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2274 static void undo_push(char *src, unsigned int length, uint8_t u_type) // Add to the undo stack
2276 struct undo_object *undo_entry;
2279 // UNDO_INS: insertion, undo will remove from buffer
2280 // UNDO_DEL: deleted text, undo will restore to buffer
2281 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2282 // The CHAIN operations are for handling multiple operations that the user
2283 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2284 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2285 // for the INS/DEL operation. The raw values should be equal to the values of
2286 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2288 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2289 // This undo queuing functionality groups multiple character typing or backspaces
2290 // into a single large undo object. This greatly reduces calls to malloc() for
2291 // single-character operations while typing and has the side benefit of letting
2292 // an undo operation remove chunks of text rather than a single character.
2294 case UNDO_EMPTY: // Just in case this ever happens...
2296 case UNDO_DEL_QUEUED:
2298 return; // Only queue single characters
2299 switch (undo_queue_state) {
2301 undo_queue_state = UNDO_DEL;
2303 undo_queue_spos = src;
2305 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2306 // If queue is full, dump it into an object
2307 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2308 undo_queue_commit();
2311 // Switch from storing inserted text to deleted text
2312 undo_queue_commit();
2313 undo_push(src, length, UNDO_DEL_QUEUED);
2317 case UNDO_INS_QUEUED:
2320 switch (undo_queue_state) {
2322 undo_queue_state = UNDO_INS;
2323 undo_queue_spos = src;
2326 undo_q++; // Don't need to save any data for insertions
2327 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2328 undo_queue_commit();
2332 // Switch from storing deleted text to inserted text
2333 undo_queue_commit();
2334 undo_push(src, length, UNDO_INS_QUEUED);
2340 // If undo queuing is disabled, ignore the queuing flag entirely
2341 u_type = u_type & ~UNDO_QUEUED_FLAG;
2344 // Allocate a new undo object
2345 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2346 // For UNDO_DEL objects, save deleted text
2347 if ((text + length) == end)
2349 // If this deletion empties text[], strip the newline. When the buffer becomes
2350 // zero-length, a newline is added back, which requires this to compensate.
2351 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
2352 memcpy(undo_entry->undo_text, src, length);
2354 undo_entry = xzalloc(sizeof(*undo_entry));
2356 undo_entry->length = length;
2357 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2358 if ((u_type & UNDO_USE_SPOS) != 0) {
2359 undo_entry->start = undo_queue_spos - text; // use start position from queue
2361 undo_entry->start = src - text; // use offset from start of text buffer
2363 u_type = (u_type & ~UNDO_USE_SPOS);
2365 undo_entry->start = src - text;
2367 undo_entry->u_type = u_type;
2369 // Push it on undo stack
2370 undo_entry->prev = undo_stack_tail;
2371 undo_stack_tail = undo_entry;
2375 static void undo_push_insert(char *p, int len, int undo)
2379 undo_push(p, len, UNDO_INS);
2381 case ALLOW_UNDO_CHAIN:
2382 undo_push(p, len, UNDO_INS_CHAIN);
2384 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2385 case ALLOW_UNDO_QUEUED:
2386 undo_push(p, len, UNDO_INS_QUEUED);
2392 static void undo_pop(void) // Undo the last operation
2395 char *u_start, *u_end;
2396 struct undo_object *undo_entry;
2398 // Commit pending undo queue before popping (should be unnecessary)
2399 undo_queue_commit();
2401 undo_entry = undo_stack_tail;
2402 // Check for an empty undo stack
2404 status_line("Already at oldest change");
2408 switch (undo_entry->u_type) {
2410 case UNDO_DEL_CHAIN:
2411 // make hole and put in text that was deleted; deallocate text
2412 u_start = text + undo_entry->start;
2413 text_hole_make(u_start, undo_entry->length);
2414 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2415 status_line("Undo [%d] %s %d chars at position %d",
2416 modified_count, "restored",
2417 undo_entry->length, undo_entry->start
2421 case UNDO_INS_CHAIN:
2422 // delete what was inserted
2423 u_start = undo_entry->start + text;
2424 u_end = u_start - 1 + undo_entry->length;
2425 text_hole_delete(u_start, u_end, NO_UNDO);
2426 status_line("Undo [%d] %s %d chars at position %d",
2427 modified_count, "deleted",
2428 undo_entry->length, undo_entry->start
2433 switch (undo_entry->u_type) {
2434 // If this is the end of a chain, lower modification count and refresh display
2437 dot = (text + undo_entry->start);
2440 case UNDO_DEL_CHAIN:
2441 case UNDO_INS_CHAIN:
2445 // Deallocate the undo object we just processed
2446 undo_stack_tail = undo_entry->prev;
2449 // For chained operations, continue popping all the way down the chain.
2451 undo_pop(); // Follow the undo chain if one exists
2455 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2456 static void undo_queue_commit(void) // Flush any queued objects to the undo stack
2458 // Pushes the queue object onto the undo stack
2460 // Deleted character undo events grow from the end
2461 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2463 (undo_queue_state | UNDO_USE_SPOS)
2465 undo_queue_state = UNDO_EMPTY;
2471 #endif /* ENABLE_FEATURE_VI_UNDO */
2473 // open a hole in text[]
2474 // might reallocate text[]! use p += text_hole_make(p, ...),
2475 // and be careful to not use pointers into potentially freed text[]!
2476 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2482 end += size; // adjust the new END
2483 if (end >= (text + text_size)) {
2485 text_size += end - (text + text_size) + 10240;
2486 new_text = xrealloc(text, text_size);
2487 bias = (new_text - text);
2488 screenbegin += bias;
2492 #if ENABLE_FEATURE_VI_YANKMARK
2495 for (i = 0; i < ARRAY_SIZE(mark); i++)
2502 memmove(p + size, p, end - size - p);
2503 memset(p, ' ', size); // clear new hole
2507 // close a hole in text[]
2508 // "undo" value indicates if this operation should be undo-able
2509 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2514 // move forwards, from beginning
2518 if (q < p) { // they are backward- swap them
2522 hole_size = q - p + 1;
2524 #if ENABLE_FEATURE_VI_UNDO
2529 undo_push(p, hole_size, UNDO_DEL);
2531 case ALLOW_UNDO_CHAIN:
2532 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2534 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2535 case ALLOW_UNDO_QUEUED:
2536 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2542 if (src < text || src > end)
2544 if (dest < text || dest >= end)
2548 goto thd_atend; // just delete the end of the buffer
2549 memmove(dest, src, cnt);
2551 end = end - hole_size; // adjust the new END
2553 dest = end - 1; // make sure dest in below end-1
2555 dest = end = text; // keep pointers valid
2560 // copy text into register, then delete text.
2561 // if dist <= 0, do not include, or go past, a NewLine
2563 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2567 // make sure start <= stop
2569 // they are backwards, reverse them
2575 // we cannot cross NL boundaries
2579 // dont go past a NewLine
2580 for (; p + 1 <= stop; p++) {
2582 stop = p; // "stop" just before NewLine
2588 #if ENABLE_FEATURE_VI_YANKMARK
2589 text_yank(start, stop, YDreg);
2591 if (yf == YANKDEL) {
2592 p = text_hole_delete(start, stop, undo);
2597 static void show_help(void)
2599 puts("These features are available:"
2600 #if ENABLE_FEATURE_VI_SEARCH
2601 "\n\tPattern searches with / and ?"
2603 #if ENABLE_FEATURE_VI_DOT_CMD
2604 "\n\tLast command repeat with ."
2606 #if ENABLE_FEATURE_VI_YANKMARK
2607 "\n\tLine marking with 'x"
2608 "\n\tNamed buffers with \"x"
2610 #if ENABLE_FEATURE_VI_READONLY
2611 //not implemented: "\n\tReadonly if vi is called as \"view\""
2612 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2614 #if ENABLE_FEATURE_VI_SET
2615 "\n\tSome colon mode commands with :"
2617 #if ENABLE_FEATURE_VI_SETOPTS
2618 "\n\tSettable options with \":set\""
2620 #if ENABLE_FEATURE_VI_USE_SIGNALS
2621 "\n\tSignal catching- ^C"
2622 "\n\tJob suspend and resume with ^Z"
2624 #if ENABLE_FEATURE_VI_WIN_RESIZE
2625 "\n\tAdapt to window re-sizes"
2630 #if ENABLE_FEATURE_VI_DOT_CMD
2631 static void start_new_cmd_q(char c)
2633 // get buffer for new cmd
2634 // if there is a current cmd count put it in the buffer first
2636 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2637 } else { // just save char c onto queue
2638 last_modifying_cmd[0] = c;
2644 static void end_cmd_q(void)
2646 #if ENABLE_FEATURE_VI_YANKMARK
2647 YDreg = 26; // go back to default Yank/Delete reg
2651 #endif /* FEATURE_VI_DOT_CMD */
2653 #if ENABLE_FEATURE_VI_YANKMARK \
2654 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2655 || ENABLE_FEATURE_VI_CRASHME
2656 // might reallocate text[]! use p += string_insert(p, ...),
2657 // and be careful to not use pointers into potentially freed text[]!
2658 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2664 #if ENABLE_FEATURE_VI_UNDO
2665 undo_push_insert(p, i, undo);
2667 bias = text_hole_make(p, i);
2670 #if ENABLE_FEATURE_VI_YANKMARK
2673 for (cnt = 0; *s != '\0'; s++) {
2677 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2684 #if ENABLE_FEATURE_VI_YANKMARK
2685 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2688 if (cnt < 0) { // they are backwards- reverse them
2692 free(reg[dest]); // if already a yank register, free it
2693 reg[dest] = xstrndup(p, cnt + 1);
2697 static char what_reg(void)
2701 c = 'D'; // default to D-reg
2702 if (0 <= YDreg && YDreg <= 25)
2703 c = 'a' + (char) YDreg;
2711 static void check_context(char cmd)
2713 // A context is defined to be "modifying text"
2714 // Any modifying command establishes a new context.
2716 if (dot < context_start || dot > context_end) {
2717 if (strchr(modifying_cmds, cmd) != NULL) {
2718 // we are trying to modify text[]- make this the current context
2719 mark[27] = mark[26]; // move cur to prev
2720 mark[26] = dot; // move local to cur
2721 context_start = prev_line(prev_line(dot));
2722 context_end = next_line(next_line(dot));
2723 //loiter= start_loiter= now;
2728 static char *swap_context(char *p) // goto new context for '' command make this the current context
2732 // the current context is in mark[26]
2733 // the previous context is in mark[27]
2734 // only swap context if other context is valid
2735 if (text <= mark[27] && mark[27] <= end - 1) {
2739 context_start = prev_line(prev_line(prev_line(p)));
2740 context_end = next_line(next_line(next_line(p)));
2744 #endif /* FEATURE_VI_YANKMARK */
2746 //----- Set terminal attributes --------------------------------
2747 static void rawmode(void)
2749 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
2750 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
2751 erase_char = term_orig.c_cc[VERASE];
2754 static void cookmode(void)
2757 tcsetattr_stdin_TCSANOW(&term_orig);
2760 #if ENABLE_FEATURE_VI_USE_SIGNALS
2761 //----- Come here when we get a window resize signal ---------
2762 static void winch_sig(int sig UNUSED_PARAM)
2764 int save_errno = errno;
2765 // FIXME: do it in main loop!!!
2766 signal(SIGWINCH, winch_sig);
2767 query_screen_dimensions();
2768 new_screen(rows, columns); // get memory for virtual screen
2769 redraw(TRUE); // re-draw the screen
2773 //----- Come here when we get a continue signal -------------------
2774 static void cont_sig(int sig UNUSED_PARAM)
2776 int save_errno = errno;
2777 rawmode(); // terminal to "raw"
2778 last_status_cksum = 0; // force status update
2779 redraw(TRUE); // re-draw the screen
2781 signal(SIGTSTP, suspend_sig);
2782 signal(SIGCONT, SIG_DFL);
2783 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2787 //----- Come here when we get a Suspend signal -------------------
2788 static void suspend_sig(int sig UNUSED_PARAM)
2790 int save_errno = errno;
2791 go_bottom_and_clear_to_eol();
2792 cookmode(); // terminal to "cooked"
2794 signal(SIGCONT, cont_sig);
2795 signal(SIGTSTP, SIG_DFL);
2796 kill(my_pid, SIGTSTP);
2800 //----- Come here when we get a signal ---------------------------
2801 static void catch_sig(int sig)
2803 signal(SIGINT, catch_sig);
2804 siglongjmp(restart, sig);
2806 #endif /* FEATURE_VI_USE_SIGNALS */
2808 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2810 struct pollfd pfd[1];
2815 pfd[0].fd = STDIN_FILENO;
2816 pfd[0].events = POLLIN;
2817 return safe_poll(pfd, 1, hund*10) > 0;
2820 //----- IO Routines --------------------------------------------
2821 static int readit(void) // read (maybe cursor) key from stdin
2827 // Wait for input. TIMEOUT = -1 makes read_key wait even
2828 // on nonblocking stdin.
2829 // Note: read_key sets errno to 0 on success.
2831 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
2832 if (c == -1) { // EOF/error
2833 if (errno == EAGAIN) // paranoia
2835 go_bottom_and_clear_to_eol();
2836 cookmode(); // terminal to "cooked"
2837 bb_error_msg_and_die("can't read user input");
2842 //----- IO Routines --------------------------------------------
2843 static int get_one_char(void)
2847 #if ENABLE_FEATURE_VI_DOT_CMD
2849 // we are not adding to the q.
2850 // but, we may be reading from a q
2852 // there is no current q, read from STDIN
2853 c = readit(); // get the users input
2855 // there is a queue to get chars from first
2856 // careful with correct sign expansion!
2857 c = (unsigned char)*ioq++;
2859 // the end of the q, read from STDIN
2861 ioq_start = ioq = 0;
2862 c = readit(); // get the users input
2866 // adding STDIN chars to q
2867 c = readit(); // get the users input
2868 if (lmc_len >= MAX_INPUT_LEN - 1) {
2869 status_line_bold("last_modifying_cmd overrun");
2871 // add new char to q
2872 last_modifying_cmd[lmc_len++] = c;
2876 c = readit(); // get the users input
2877 #endif /* FEATURE_VI_DOT_CMD */
2881 // Get input line (uses "status line" area)
2882 static char *get_input_line(const char *prompt)
2884 // char [MAX_INPUT_LEN]
2885 #define buf get_input_line__buf
2890 strcpy(buf, prompt);
2891 last_status_cksum = 0; // force status update
2892 go_bottom_and_clear_to_eol();
2893 write1(prompt); // write out the :, /, or ? prompt
2896 while (i < MAX_INPUT_LEN) {
2898 if (c == '\n' || c == '\r' || c == 27)
2899 break; // this is end of input
2900 if (c == erase_char || c == 8 || c == 127) {
2901 // user wants to erase prev char
2903 write1("\b \b"); // erase char on screen
2904 if (i <= 0) // user backs up before b-o-l, exit
2906 } else if (c > 0 && c < 256) { // exclude Unicode
2907 // (TODO: need to handle Unicode)
2918 // might reallocate text[]!
2919 static int file_insert(const char *fn, char *p, int initial)
2923 struct stat statbuf;
2930 fd = open(fn, O_RDONLY);
2933 status_line_bold_errno(fn);
2938 if (fstat(fd, &statbuf) < 0) {
2939 status_line_bold_errno(fn);
2942 if (!S_ISREG(statbuf.st_mode)) {
2943 status_line_bold("'%s' is not a regular file", fn);
2946 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2947 p += text_hole_make(p, size);
2948 cnt = full_read(fd, p, size);
2950 status_line_bold_errno(fn);
2951 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2952 } else if (cnt < size) {
2953 // There was a partial read, shrink unused space
2954 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2955 status_line_bold("can't read '%s'", fn);
2960 #if ENABLE_FEATURE_VI_READONLY
2962 && ((access(fn, W_OK) < 0) ||
2963 /* root will always have access()
2964 * so we check fileperms too */
2965 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2968 SET_READONLY_FILE(readonly_mode);
2974 static int file_write(char *fn, char *first, char *last)
2976 int fd, cnt, charcnt;
2979 status_line_bold("No current filename");
2982 /* By popular request we do not open file with O_TRUNC,
2983 * but instead ftruncate() it _after_ successful write.
2984 * Might reduce amount of data lost on power fail etc.
2986 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2989 cnt = last - first + 1;
2990 charcnt = full_write(fd, first, cnt);
2991 ftruncate(fd, charcnt);
2992 if (charcnt == cnt) {
2994 //modified_count = FALSE;
3002 //----- Terminal Drawing ---------------------------------------
3003 // The terminal is made up of 'rows' line of 'columns' columns.
3004 // classically this would be 24 x 80.
3005 // screen coordinates
3011 // 23,0 ... 23,79 <- status line
3013 //----- Move the cursor to row x col (count from 0, not 1) -------
3014 static void place_cursor(int row, int col)
3016 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
3018 if (row < 0) row = 0;
3019 if (row >= rows) row = rows - 1;
3020 if (col < 0) col = 0;
3021 if (col >= columns) col = columns - 1;
3023 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
3027 //----- Erase from cursor to end of line -----------------------
3028 static void clear_to_eol(void)
3030 write1(ESC_CLEAR2EOL);
3033 static void go_bottom_and_clear_to_eol(void)
3035 place_cursor(rows - 1, 0);
3039 //----- Erase from cursor to end of screen -----------------------
3040 static void clear_to_eos(void)
3042 write1(ESC_CLEAR2EOS);
3045 //----- Start standout mode ------------------------------------
3046 static void standout_start(void)
3048 write1(ESC_BOLD_TEXT);
3051 //----- End standout mode --------------------------------------
3052 static void standout_end(void)
3054 write1(ESC_NORM_TEXT);
3057 //----- Flash the screen --------------------------------------
3058 static void flash(int h)
3067 static void indicate_error(void)
3069 #if ENABLE_FEATURE_VI_CRASHME
3071 return; // generate a random command
3080 //----- Screen[] Routines --------------------------------------
3081 //----- Erase the Screen[] memory ------------------------------
3082 static void screen_erase(void)
3084 memset(screen, ' ', screensize); // clear new screen
3087 static int bufsum(char *buf, int count)
3090 char *e = buf + count;
3093 sum += (unsigned char) *buf++;
3097 //----- Draw the status line at bottom of the screen -------------
3098 static void show_status_line(void)
3100 int cnt = 0, cksum = 0;
3102 // either we already have an error or status message, or we
3104 if (!have_status_msg) {
3105 cnt = format_edit_status();
3106 cksum = bufsum(status_buffer, cnt);
3108 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3109 last_status_cksum = cksum; // remember if we have seen this line
3110 go_bottom_and_clear_to_eol();
3111 write1(status_buffer);
3112 if (have_status_msg) {
3113 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3115 have_status_msg = 0;
3118 have_status_msg = 0;
3120 place_cursor(crow, ccol); // put cursor back in correct place
3125 //----- format the status buffer, the bottom line of screen ------
3126 // format status buffer, with STANDOUT mode
3127 static void status_line_bold(const char *format, ...)
3131 va_start(args, format);
3132 strcpy(status_buffer, ESC_BOLD_TEXT);
3133 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3134 strcat(status_buffer, ESC_NORM_TEXT);
3137 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3140 static void status_line_bold_errno(const char *fn)
3142 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
3145 // format status buffer
3146 static void status_line(const char *format, ...)
3150 va_start(args, format);
3151 vsprintf(status_buffer, format, args);
3154 have_status_msg = 1;
3157 // copy s to buf, convert unprintable
3158 static void print_literal(char *buf, const char *s)
3172 c_is_no_print = (c & 0x80) && !Isprint(c);
3173 if (c_is_no_print) {
3174 strcpy(d, ESC_NORM_TEXT);
3175 d += sizeof(ESC_NORM_TEXT)-1;
3178 if (c < ' ' || c == 0x7f) {
3180 c |= '@'; /* 0x40 */
3186 if (c_is_no_print) {
3187 strcpy(d, ESC_BOLD_TEXT);
3188 d += sizeof(ESC_BOLD_TEXT)-1;
3194 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3199 static void not_implemented(const char *s)
3201 char buf[MAX_INPUT_LEN];
3203 print_literal(buf, s);
3204 status_line_bold("\'%s\' is not implemented", buf);
3207 // show file status on status line
3208 static int format_edit_status(void)
3210 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3212 #define tot format_edit_status__tot
3214 int cur, percent, ret, trunc_at;
3216 // modified_count is now a counter rather than a flag. this
3217 // helps reduce the amount of line counting we need to do.
3218 // (this will cause a mis-reporting of modified status
3219 // once every MAXINT editing operations.)
3221 // it would be nice to do a similar optimization here -- if
3222 // we haven't done a motion that could have changed which line
3223 // we're on, then we shouldn't have to do this count_lines()
3224 cur = count_lines(text, dot);
3226 // count_lines() is expensive.
3227 // Call it only if something was changed since last time
3229 if (modified_count != last_modified_count) {
3230 tot = cur + count_lines(dot, end - 1) - 1;
3231 last_modified_count = modified_count;
3234 // current line percent
3235 // ------------- ~~ ----------
3238 percent = (100 * cur) / tot;
3244 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3245 columns : STATUS_BUFFER_LEN-1;
3247 ret = snprintf(status_buffer, trunc_at+1,
3248 #if ENABLE_FEATURE_VI_READONLY
3249 "%c %s%s%s %d/%d %d%%",
3251 "%c %s%s %d/%d %d%%",
3253 cmd_mode_indicator[cmd_mode & 3],
3254 (current_filename != NULL ? current_filename : "No file"),
3255 #if ENABLE_FEATURE_VI_READONLY
3256 (readonly_mode ? " [Readonly]" : ""),
3258 (modified_count ? " [Modified]" : ""),
3261 if (ret >= 0 && ret < trunc_at)
3262 return ret; /* it all fit */
3264 return trunc_at; /* had to truncate */
3268 //----- Force refresh of all Lines -----------------------------
3269 static void redraw(int full_screen)
3273 screen_erase(); // erase the internal screen buffer
3274 last_status_cksum = 0; // force status update
3275 refresh(full_screen); // this will redraw the entire display
3279 //----- Format a text[] line into a buffer ---------------------
3280 static char* format_line(char *src /*, int li*/)
3285 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3287 c = '~'; // char in col 0 in non-existent lines is '~'
3289 while (co < columns + tabstop) {
3290 // have we gone past the end?
3295 if ((c & 0x80) && !Isprint(c)) {
3298 if (c < ' ' || c == 0x7f) {
3302 while ((co % tabstop) != (tabstop - 1)) {
3310 c += '@'; // Ctrl-X -> 'X'
3315 // discard scrolled-off-to-the-left portion,
3316 // in tabstop-sized pieces
3317 if (ofs >= tabstop && co >= tabstop) {
3318 memmove(dest, dest + tabstop, co);
3325 // check "short line, gigantic offset" case
3328 // discard last scrolled off part
3331 // fill the rest with spaces
3333 memset(&dest[co], ' ', columns - co);
3337 //----- Refresh the changed screen lines -----------------------
3338 // Copy the source line from text[] into the buffer and note
3339 // if the current screenline is different from the new buffer.
3340 // If they differ then that line needs redrawing on the terminal.
3342 static void refresh(int full_screen)
3344 #define old_offset refresh__old_offset
3347 char *tp, *sp; // pointer into text[] and screen[]
3349 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3350 unsigned c = columns, r = rows;
3351 query_screen_dimensions();
3352 #if ENABLE_FEATURE_VI_USE_SIGNALS
3353 full_screen |= (c - columns) | (r - rows);
3355 if (c != columns || r != rows) {
3357 /* update screen memory since SIGWINCH won't have done it */
3358 new_screen(rows, columns);
3362 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3363 tp = screenbegin; // index into text[] of top line
3365 // compare text[] to screen[] and mark screen[] lines that need updating
3366 for (li = 0; li < rows - 1; li++) {
3367 int cs, ce; // column start & end
3369 // format current text line
3370 out_buf = format_line(tp /*, li*/);
3372 // skip to the end of the current text[] line
3374 char *t = memchr(tp, '\n', end - tp);
3375 if (!t) t = end - 1;
3379 // see if there are any changes between virtual screen and out_buf
3380 changed = FALSE; // assume no change
3383 sp = &screen[li * columns]; // start of screen line
3385 // force re-draw of every single column from 0 - columns-1
3388 // compare newly formatted buffer with virtual screen
3389 // look forward for first difference between buf and screen
3390 for (; cs <= ce; cs++) {
3391 if (out_buf[cs] != sp[cs]) {
3392 changed = TRUE; // mark for redraw
3397 // look backward for last difference between out_buf and screen
3398 for (; ce >= cs; ce--) {
3399 if (out_buf[ce] != sp[ce]) {
3400 changed = TRUE; // mark for redraw
3404 // now, cs is index of first diff, and ce is index of last diff
3406 // if horz offset has changed, force a redraw
3407 if (offset != old_offset) {
3412 // make a sanity check of columns indexes
3414 if (ce > columns - 1) ce = columns - 1;
3415 if (cs > ce) { cs = 0; ce = columns - 1; }
3416 // is there a change between virtual screen and out_buf
3418 // copy changed part of buffer to virtual screen
3419 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3420 place_cursor(li, cs);
3421 // write line out to terminal
3422 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3426 place_cursor(crow, ccol);
3428 old_offset = offset;
3432 //---------------------------------------------------------------------
3433 //----- the Ascii Chart -----------------------------------------------
3435 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3436 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3437 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3438 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3439 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3440 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3441 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3442 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3443 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3444 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3445 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3446 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3447 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3448 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3449 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3450 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3451 //---------------------------------------------------------------------
3453 //----- Execute a Vi Command -----------------------------------
3454 static void do_cmd(int c)
3456 char *p, *q, *save_dot;
3462 // c1 = c; // quiet the compiler
3463 // cnt = yf = 0; // quiet the compiler
3464 // p = q = save_dot = buf; // quiet the compiler
3465 memset(buf, '\0', sizeof(buf));
3469 /* if this is a cursor key, skip these checks */
3477 case KEYCODE_PAGEUP:
3478 case KEYCODE_PAGEDOWN:
3479 case KEYCODE_DELETE:
3483 if (cmd_mode == 2) {
3484 // flip-flop Insert/Replace mode
3485 if (c == KEYCODE_INSERT)
3487 // we are 'R'eplacing the current *dot with new char
3489 // don't Replace past E-o-l
3490 cmd_mode = 1; // convert to insert
3491 undo_queue_commit();
3493 if (1 <= c || Isprint(c)) {
3495 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3496 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3501 if (cmd_mode == 1) {
3502 // hitting "Insert" twice means "R" replace mode
3503 if (c == KEYCODE_INSERT) goto dc5;
3504 // insert the char c at "dot"
3505 if (1 <= c || Isprint(c)) {
3506 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3521 #if ENABLE_FEATURE_VI_CRASHME
3522 case 0x14: // dc4 ctrl-T
3523 crashme = (crashme == 0) ? 1 : 0;
3553 default: // unrecognized command
3556 not_implemented(buf);
3557 end_cmd_q(); // stop adding to q
3558 case 0x00: // nul- ignore
3560 case 2: // ctrl-B scroll up full screen
3561 case KEYCODE_PAGEUP: // Cursor Key Page Up
3562 dot_scroll(rows - 2, -1);
3564 case 4: // ctrl-D scroll down half screen
3565 dot_scroll((rows - 2) / 2, 1);
3567 case 5: // ctrl-E scroll down one line
3570 case 6: // ctrl-F scroll down full screen
3571 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3572 dot_scroll(rows - 2, 1);
3574 case 7: // ctrl-G show current status
3575 last_status_cksum = 0; // force status update
3577 case 'h': // h- move left
3578 case KEYCODE_LEFT: // cursor key Left
3579 case 8: // ctrl-H- move left (This may be ERASE char)
3580 case 0x7f: // DEL- move left (This may be ERASE char)
3583 } while (--cmdcnt > 0);
3585 case 10: // Newline ^J
3586 case 'j': // j- goto next line, same col
3587 case KEYCODE_DOWN: // cursor key Down
3589 dot_next(); // go to next B-o-l
3590 // try stay in same col
3591 dot = move_to_col(dot, ccol + offset);
3592 } while (--cmdcnt > 0);
3594 case 12: // ctrl-L force redraw whole screen
3595 case 18: // ctrl-R force redraw
3596 redraw(TRUE); // this will redraw the entire display
3598 case 13: // Carriage Return ^M
3599 case '+': // +- goto next line
3603 } while (--cmdcnt > 0);
3605 case 21: // ctrl-U scroll up half screen
3606 dot_scroll((rows - 2) / 2, -1);
3608 case 25: // ctrl-Y scroll up one line
3614 cmd_mode = 0; // stop insrting
3615 undo_queue_commit();
3617 last_status_cksum = 0; // force status update
3619 case ' ': // move right
3620 case 'l': // move right
3621 case KEYCODE_RIGHT: // Cursor Key Right
3624 } while (--cmdcnt > 0);
3626 #if ENABLE_FEATURE_VI_YANKMARK
3627 case '"': // "- name a register to use for Delete/Yank
3628 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3629 if ((unsigned)c1 <= 25) { // a-z?
3635 case '\'': // '- goto a specific mark
3636 c1 = (get_one_char() | 0x20);
3637 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3641 if (text <= q && q < end) {
3643 dot_begin(); // go to B-o-l
3646 } else if (c1 == '\'') { // goto previous context
3647 dot = swap_context(dot); // swap current and previous context
3648 dot_begin(); // go to B-o-l
3654 case 'm': // m- Mark a line
3655 // this is really stupid. If there are any inserts or deletes
3656 // between text[0] and dot then this mark will not point to the
3657 // correct location! It could be off by many lines!
3658 // Well..., at least its quick and dirty.
3659 c1 = (get_one_char() | 0x20) - 'a';
3660 if ((unsigned)c1 <= 25) { // a-z?
3661 // remember the line
3667 case 'P': // P- Put register before
3668 case 'p': // p- put register after
3671 status_line_bold("Nothing in register %c", what_reg());
3674 // are we putting whole lines or strings
3675 if (strchr(p, '\n') != NULL) {
3677 dot_begin(); // putting lines- Put above
3680 // are we putting after very last line?
3681 if (end_line(dot) == (end - 1)) {
3682 dot = end; // force dot to end of text[]
3684 dot_next(); // next line, then put before
3689 dot_right(); // move to right, can move to NL
3691 string_insert(dot, p, ALLOW_UNDO); // insert the string
3692 end_cmd_q(); // stop adding to q
3694 case 'U': // U- Undo; replace current line with original version
3695 if (reg[Ureg] != NULL) {
3696 p = begin_line(dot);
3698 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3699 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3704 #endif /* FEATURE_VI_YANKMARK */
3705 #if ENABLE_FEATURE_VI_UNDO
3706 case 'u': // u- undo last operation
3710 case '$': // $- goto end of line
3711 case KEYCODE_END: // Cursor Key End
3713 dot = end_line(dot);
3719 case '%': // %- find matching char of pair () [] {}
3720 for (q = dot; q < end && *q != '\n'; q++) {
3721 if (strchr("()[]{}", *q) != NULL) {
3722 // we found half of a pair
3723 p = find_pair(q, *q);
3735 case 'f': // f- forward to a user specified char
3736 last_forward_char = get_one_char(); // get the search char
3738 // dont separate these two commands. 'f' depends on ';'
3740 //**** fall through to ... ';'
3741 case ';': // ;- look at rest of line for last forward char
3743 if (last_forward_char == 0)
3746 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3749 if (*q == last_forward_char)
3751 } while (--cmdcnt > 0);
3753 case ',': // repeat latest 'f' in opposite direction
3754 if (last_forward_char == 0)
3758 while (q >= text && *q != '\n' && *q != last_forward_char) {
3761 if (q >= text && *q == last_forward_char)
3763 } while (--cmdcnt > 0);
3766 case '-': // -- goto prev line
3770 } while (--cmdcnt > 0);
3772 #if ENABLE_FEATURE_VI_DOT_CMD
3773 case '.': // .- repeat the last modifying command
3774 // Stuff the last_modifying_cmd back into stdin
3775 // and let it be re-executed.
3777 last_modifying_cmd[lmc_len] = 0;
3778 ioq = ioq_start = xstrdup(last_modifying_cmd);
3782 #if ENABLE_FEATURE_VI_SEARCH
3783 case '?': // /- search for a pattern
3784 case '/': // /- search for a pattern
3787 q = get_input_line(buf); // get input line- use "status line"
3788 if (q[0] && !q[1]) {
3789 if (last_search_pattern[0])
3790 last_search_pattern[0] = c;
3791 goto dc3; // if no pat re-use old pat
3793 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3794 // there is a new pat
3795 free(last_search_pattern);
3796 last_search_pattern = xstrdup(q);
3797 goto dc3; // now find the pattern
3799 // user changed mind and erased the "/"- do nothing
3801 case 'N': // N- backward search for last pattern
3802 dir = BACK; // assume BACKWARD search
3804 if (last_search_pattern[0] == '?') {
3808 goto dc4; // now search for pattern
3810 case 'n': // n- repeat search for last pattern
3811 // search rest of text[] starting at next char
3812 // if search fails return orignal "p" not the "p+1" address
3816 dir = FORWARD; // assume FORWARD search
3818 if (last_search_pattern[0] == '?') {
3823 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3825 dot = q; // good search, update "dot"
3829 // no pattern found between "dot" and "end"- continue at top
3834 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3835 if (q != NULL) { // found something
3836 dot = q; // found new pattern- goto it
3837 msg = "search hit BOTTOM, continuing at TOP";
3839 msg = "search hit TOP, continuing at BOTTOM";
3842 msg = "Pattern not found";
3846 status_line_bold("%s", msg);
3847 } while (--cmdcnt > 0);
3849 case '{': // {- move backward paragraph
3850 q = char_search(dot, "\n\n", (BACK << 1) | FULL);
3851 if (q != NULL) { // found blank line
3852 dot = next_line(q); // move to next blank line
3855 case '}': // }- move forward paragraph
3856 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
3857 if (q != NULL) { // found blank line
3858 dot = next_line(q); // move to next blank line
3861 #endif /* FEATURE_VI_SEARCH */
3862 case '0': // 0- goto beginning of line
3872 if (c == '0' && cmdcnt < 1) {
3873 dot_begin(); // this was a standalone zero
3875 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3878 case ':': // :- the colon mode commands
3879 p = get_input_line(":"); // get input line- use "status line"
3880 colon(p); // execute the command
3882 case '<': // <- Left shift something
3883 case '>': // >- Right shift something
3884 cnt = count_lines(text, dot); // remember what line we are on
3885 c1 = get_one_char(); // get the type of thing to delete
3886 find_range(&p, &q, c1);
3887 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3890 i = count_lines(p, q); // # of lines we are shifting
3891 for ( ; i > 0; i--, p = next_line(p)) {
3893 // shift left- remove tab or 8 spaces
3895 // shrink buffer 1 char
3896 text_hole_delete(p, p, NO_UNDO);
3897 } else if (*p == ' ') {
3898 // we should be calculating columns, not just SPACE
3899 for (j = 0; *p == ' ' && j < tabstop; j++) {
3900 text_hole_delete(p, p, NO_UNDO);
3903 } else if (c == '>') {
3904 // shift right -- add tab or 8 spaces
3905 char_insert(p, '\t', ALLOW_UNDO);
3908 dot = find_line(cnt); // what line were we on
3910 end_cmd_q(); // stop adding to q
3912 case 'A': // A- append at e-o-l
3913 dot_end(); // go to e-o-l
3914 //**** fall through to ... 'a'
3915 case 'a': // a- append after current char
3920 case 'B': // B- back a blank-delimited Word
3921 case 'E': // E- end of a blank-delimited word
3922 case 'W': // W- forward a blank-delimited word
3927 if (c == 'W' || isspace(dot[dir])) {
3928 dot = skip_thing(dot, 1, dir, S_TO_WS);
3929 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3932 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3933 } while (--cmdcnt > 0);
3935 case 'C': // C- Change to e-o-l
3936 case 'D': // D- delete to e-o-l
3938 dot = dollar_line(dot); // move to before NL
3939 // copy text into a register and delete
3940 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3942 goto dc_i; // start inserting
3943 #if ENABLE_FEATURE_VI_DOT_CMD
3945 end_cmd_q(); // stop adding to q
3948 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3949 c1 = get_one_char();
3952 // c1 < 0 if the key was special. Try "g<up-arrow>"
3953 // TODO: if Unicode?
3954 buf[1] = (c1 >= 0 ? c1 : '*');
3956 not_implemented(buf);
3962 case 'G': // G- goto to a line number (default= E-O-F)
3963 dot = end - 1; // assume E-O-F
3965 dot = find_line(cmdcnt); // what line is #cmdcnt
3969 case 'H': // H- goto top line on screen
3971 if (cmdcnt > (rows - 1)) {
3972 cmdcnt = (rows - 1);
3979 case 'I': // I- insert before first non-blank
3982 //**** fall through to ... 'i'
3983 case 'i': // i- insert before current char
3984 case KEYCODE_INSERT: // Cursor Key Insert
3986 cmd_mode = 1; // start inserting
3987 undo_queue_commit(); // commit queue when cmd_mode changes
3989 case 'J': // J- join current and next lines together
3991 dot_end(); // move to NL
3992 if (dot < end - 1) { // make sure not last char in text[]
3993 #if ENABLE_FEATURE_VI_UNDO
3994 undo_push(dot, 1, UNDO_DEL);
3995 *dot++ = ' '; // replace NL with space
3996 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
4001 while (isblank(*dot)) { // delete leading WS
4002 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
4005 } while (--cmdcnt > 0);
4006 end_cmd_q(); // stop adding to q
4008 case 'L': // L- goto bottom line on screen
4010 if (cmdcnt > (rows - 1)) {
4011 cmdcnt = (rows - 1);
4019 case 'M': // M- goto middle line on screen
4021 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
4022 dot = next_line(dot);
4024 case 'O': // O- open a empty line above
4026 p = begin_line(dot);
4027 if (p[-1] == '\n') {
4029 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
4031 dot = char_insert(dot, '\n', ALLOW_UNDO);
4034 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
4039 case 'R': // R- continuous Replace char
4042 undo_queue_commit();
4044 case KEYCODE_DELETE:
4046 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
4048 case 'X': // X- delete char before dot
4049 case 'x': // x- delete the current char
4050 case 's': // s- substitute the current char
4055 if (dot[dir] != '\n') {
4057 dot--; // delete prev char
4058 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
4060 } while (--cmdcnt > 0);
4061 end_cmd_q(); // stop adding to q
4063 goto dc_i; // start inserting
4065 case 'Z': // Z- if modified, {write}; exit
4066 // ZZ means to save file (if necessary), then exit
4067 c1 = get_one_char();
4072 if (modified_count) {
4073 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
4074 status_line_bold("'%s' is read only", current_filename);
4077 cnt = file_write(current_filename, text, end - 1);
4080 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
4081 } else if (cnt == (end - 1 - text + 1)) {
4088 case '^': // ^- move to first non-blank on line
4092 case 'b': // b- back a word
4093 case 'e': // e- end of word
4098 if ((dot + dir) < text || (dot + dir) > end - 1)
4101 if (isspace(*dot)) {
4102 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4104 if (isalnum(*dot) || *dot == '_') {
4105 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4106 } else if (ispunct(*dot)) {
4107 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4109 } while (--cmdcnt > 0);
4111 case 'c': // c- change something
4112 case 'd': // d- delete something
4113 #if ENABLE_FEATURE_VI_YANKMARK
4114 case 'y': // y- yank something
4115 case 'Y': // Y- Yank a line
4118 int yf, ml, whole = 0;
4119 yf = YANKDEL; // assume either "c" or "d"
4120 #if ENABLE_FEATURE_VI_YANKMARK
4121 if (c == 'y' || c == 'Y')
4126 c1 = get_one_char(); // get the type of thing to delete
4127 // determine range, and whether it spans lines
4128 ml = find_range(&p, &q, c1);
4130 if (c1 == 27) { // ESC- user changed mind and wants out
4131 c = c1 = 27; // Escape- do nothing
4132 } else if (strchr("wW", c1)) {
4134 // don't include trailing WS as part of word
4135 while (isblank(*q)) {
4136 if (q <= text || q[-1] == '\n')
4141 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4142 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
4143 // partial line copy text into a register and delete
4144 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4145 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
4146 // whole line copy text into a register and delete
4147 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
4150 // could not recognize object
4151 c = c1 = 27; // error-
4157 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4158 // on the last line of file don't move to prev line
4159 if (whole && dot != (end-1)) {
4162 } else if (c == 'd') {
4168 // if CHANGING, not deleting, start inserting after the delete
4170 strcpy(buf, "Change");
4171 goto dc_i; // start inserting
4174 strcpy(buf, "Delete");
4176 #if ENABLE_FEATURE_VI_YANKMARK
4177 if (c == 'y' || c == 'Y') {
4178 strcpy(buf, "Yank");
4182 for (cnt = 0; p <= q; p++) {
4186 status_line("%s %u lines (%u chars) using [%c]",
4187 buf, cnt, (unsigned)strlen(reg[YDreg]), what_reg());
4189 end_cmd_q(); // stop adding to q
4193 case 'k': // k- goto prev line, same col
4194 case KEYCODE_UP: // cursor key Up
4197 dot = move_to_col(dot, ccol + offset); // try stay in same col
4198 } while (--cmdcnt > 0);
4200 case 'r': // r- replace the current char with user input
4201 c1 = get_one_char(); // get the replacement char
4203 dot = text_hole_delete(dot, dot, ALLOW_UNDO);
4204 dot = char_insert(dot, c1, ALLOW_UNDO_CHAIN);
4207 end_cmd_q(); // stop adding to q
4209 case 't': // t- move to char prior to next x
4210 last_forward_char = get_one_char();
4212 if (*dot == last_forward_char)
4214 last_forward_char = 0;
4216 case 'w': // w- forward a word
4218 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4219 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4220 } else if (ispunct(*dot)) { // we are on PUNCT
4221 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4224 dot++; // move over word
4225 if (isspace(*dot)) {
4226 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4228 } while (--cmdcnt > 0);
4231 c1 = get_one_char(); // get the replacement char
4234 cnt = (rows - 2) / 2; // put dot at center
4236 cnt = rows - 2; // put dot at bottom
4237 screenbegin = begin_line(dot); // start dot at top
4238 dot_scroll(cnt, -1);
4240 case '|': // |- move to column "cmdcnt"
4241 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4243 case '~': // ~- flip the case of letters a-z -> A-Z
4245 #if ENABLE_FEATURE_VI_UNDO
4246 if (islower(*dot)) {
4247 undo_push(dot, 1, UNDO_DEL);
4248 *dot = toupper(*dot);
4249 undo_push(dot, 1, UNDO_INS_CHAIN);
4250 } else if (isupper(*dot)) {
4251 undo_push(dot, 1, UNDO_DEL);
4252 *dot = tolower(*dot);
4253 undo_push(dot, 1, UNDO_INS_CHAIN);
4256 if (islower(*dot)) {
4257 *dot = toupper(*dot);
4259 } else if (isupper(*dot)) {
4260 *dot = tolower(*dot);
4265 } while (--cmdcnt > 0);
4266 end_cmd_q(); // stop adding to q
4268 //----- The Cursor and Function Keys -----------------------------
4269 case KEYCODE_HOME: // Cursor Key Home
4272 // The Fn keys could point to do_macro which could translate them
4274 case KEYCODE_FUN1: // Function Key F1
4275 case KEYCODE_FUN2: // Function Key F2
4276 case KEYCODE_FUN3: // Function Key F3
4277 case KEYCODE_FUN4: // Function Key F4
4278 case KEYCODE_FUN5: // Function Key F5
4279 case KEYCODE_FUN6: // Function Key F6
4280 case KEYCODE_FUN7: // Function Key F7
4281 case KEYCODE_FUN8: // Function Key F8
4282 case KEYCODE_FUN9: // Function Key F9
4283 case KEYCODE_FUN10: // Function Key F10
4284 case KEYCODE_FUN11: // Function Key F11
4285 case KEYCODE_FUN12: // Function Key F12
4291 // if text[] just became empty, add back an empty line
4293 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4296 // it is OK for dot to exactly equal to end, otherwise check dot validity
4298 dot = bound_dot(dot); // make sure "dot" is valid
4300 #if ENABLE_FEATURE_VI_YANKMARK
4301 check_context(c); // update the current context
4305 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4306 cnt = dot - begin_line(dot);
4307 // Try to stay off of the Newline
4308 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4312 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4313 #if ENABLE_FEATURE_VI_CRASHME
4314 static int totalcmds = 0;
4315 static int Mp = 85; // Movement command Probability
4316 static int Np = 90; // Non-movement command Probability
4317 static int Dp = 96; // Delete command Probability
4318 static int Ip = 97; // Insert command Probability
4319 static int Yp = 98; // Yank command Probability
4320 static int Pp = 99; // Put command Probability
4321 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4322 static const char chars[20] = "\t012345 abcdABCD-=.$";
4323 static const char *const words[20] = {
4324 "this", "is", "a", "test",
4325 "broadcast", "the", "emergency", "of",
4326 "system", "quick", "brown", "fox",
4327 "jumped", "over", "lazy", "dogs",
4328 "back", "January", "Febuary", "March"
4330 static const char *const lines[20] = {
4331 "You should have received a copy of the GNU General Public License\n",
4332 "char c, cm, *cmd, *cmd1;\n",
4333 "generate a command by percentages\n",
4334 "Numbers may be typed as a prefix to some commands.\n",
4335 "Quit, discarding changes!\n",
4336 "Forced write, if permission originally not valid.\n",
4337 "In general, any ex or ed command (such as substitute or delete).\n",
4338 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4339 "Please get w/ me and I will go over it with you.\n",
4340 "The following is a list of scheduled, committed changes.\n",
4341 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4342 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4343 "Any question about transactions please contact Sterling Huxley.\n",
4344 "I will try to get back to you by Friday, December 31.\n",
4345 "This Change will be implemented on Friday.\n",
4346 "Let me know if you have problems accessing this;\n",
4347 "Sterling Huxley recently added you to the access list.\n",
4348 "Would you like to go to lunch?\n",
4349 "The last command will be automatically run.\n",
4350 "This is too much english for a computer geek.\n",
4352 static char *multilines[20] = {
4353 "You should have received a copy of the GNU General Public License\n",
4354 "char c, cm, *cmd, *cmd1;\n",
4355 "generate a command by percentages\n",
4356 "Numbers may be typed as a prefix to some commands.\n",
4357 "Quit, discarding changes!\n",
4358 "Forced write, if permission originally not valid.\n",
4359 "In general, any ex or ed command (such as substitute or delete).\n",
4360 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4361 "Please get w/ me and I will go over it with you.\n",
4362 "The following is a list of scheduled, committed changes.\n",
4363 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4364 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4365 "Any question about transactions please contact Sterling Huxley.\n",
4366 "I will try to get back to you by Friday, December 31.\n",
4367 "This Change will be implemented on Friday.\n",
4368 "Let me know if you have problems accessing this;\n",
4369 "Sterling Huxley recently added you to the access list.\n",
4370 "Would you like to go to lunch?\n",
4371 "The last command will be automatically run.\n",
4372 "This is too much english for a computer geek.\n",
4375 // create a random command to execute
4376 static void crash_dummy()
4378 static int sleeptime; // how long to pause between commands
4379 char c, cm, *cmd, *cmd1;
4380 int i, cnt, thing, rbi, startrbi, percent;
4382 // "dot" movement commands
4383 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4385 // is there already a command running?
4386 if (readbuffer[0] > 0)
4389 readbuffer[0] = 'X';
4391 sleeptime = 0; // how long to pause between commands
4392 memset(readbuffer, '\0', sizeof(readbuffer));
4393 // generate a command by percentages
4394 percent = (int) lrand48() % 100; // get a number from 0-99
4395 if (percent < Mp) { // Movement commands
4396 // available commands
4399 } else if (percent < Np) { // non-movement commands
4400 cmd = "mz<>\'\""; // available commands
4402 } else if (percent < Dp) { // Delete commands
4403 cmd = "dx"; // available commands
4405 } else if (percent < Ip) { // Inset commands
4406 cmd = "iIaAsrJ"; // available commands
4408 } else if (percent < Yp) { // Yank commands
4409 cmd = "yY"; // available commands
4411 } else if (percent < Pp) { // Put commands
4412 cmd = "pP"; // available commands
4415 // We do not know how to handle this command, try again
4419 // randomly pick one of the available cmds from "cmd[]"
4420 i = (int) lrand48() % strlen(cmd);
4422 if (strchr(":\024", cm))
4423 goto cd0; // dont allow colon or ctrl-T commands
4424 readbuffer[rbi++] = cm; // put cmd into input buffer
4426 // now we have the command-
4427 // there are 1, 2, and multi char commands
4428 // find out which and generate the rest of command as necessary
4429 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4430 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4431 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4432 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4434 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4436 readbuffer[rbi++] = c; // add movement to input buffer
4438 if (strchr("iIaAsc", cm)) { // multi-char commands
4440 // change some thing
4441 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4443 readbuffer[rbi++] = c; // add movement to input buffer
4445 thing = (int) lrand48() % 4; // what thing to insert
4446 cnt = (int) lrand48() % 10; // how many to insert
4447 for (i = 0; i < cnt; i++) {
4448 if (thing == 0) { // insert chars
4449 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4450 } else if (thing == 1) { // insert words
4451 strcat(readbuffer, words[(int) lrand48() % 20]);
4452 strcat(readbuffer, " ");
4453 sleeptime = 0; // how fast to type
4454 } else if (thing == 2) { // insert lines
4455 strcat(readbuffer, lines[(int) lrand48() % 20]);
4456 sleeptime = 0; // how fast to type
4457 } else { // insert multi-lines
4458 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4459 sleeptime = 0; // how fast to type
4462 strcat(readbuffer, ESC);
4464 readbuffer[0] = strlen(readbuffer + 1);
4468 mysleep(sleeptime); // sleep 1/100 sec
4471 // test to see if there are any errors
4472 static void crash_test()
4474 static time_t oldtim;
4481 strcat(msg, "end<text ");
4483 if (end > textend) {
4484 strcat(msg, "end>textend ");
4487 strcat(msg, "dot<text ");
4490 strcat(msg, "dot>end ");
4492 if (screenbegin < text) {
4493 strcat(msg, "screenbegin<text ");
4495 if (screenbegin > end - 1) {
4496 strcat(msg, "screenbegin>end-1 ");
4500 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4501 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4503 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4504 if (d[0] == '\n' || d[0] == '\r')
4509 if (tim >= (oldtim + 3)) {
4510 sprintf(status_buffer,
4511 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4512 totalcmds, M, N, I, D, Y, P, U, end - text + 1);