1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
16 * if mark[] values were line numbers rather than pointers
17 * it would be easier to change the mark when add/delete lines
18 * More intelligence in refresh()
19 * ":r !cmd" and "!cmd" to filter text through an external command
20 * An "ex" line oriented mode- maybe using "cmdedit"
27 //config: 'vi' is a text editor. More specifically, it is the One True
28 //config: text editor <grin>. It does, however, have a rather steep
29 //config: learning curve. If you are not already comfortable with 'vi'
30 //config: you may wish to use something else.
32 //config:config FEATURE_VI_MAX_LEN
33 //config: int "Maximum screen width in vi"
34 //config: range 256 16384
35 //config: default 4096
36 //config: depends on VI
38 //config: Contrary to what you may think, this is not eating much.
39 //config: Make it smaller than 4k only if you are very limited on memory.
41 //config:config FEATURE_VI_8BIT
42 //config: bool "Allow vi to display 8-bit chars (otherwise shows dots)"
44 //config: depends on VI
46 //config: If your terminal can display characters with high bit set,
47 //config: you may want to enable this. Note: vi is not Unicode-capable.
48 //config: If your terminal combines several 8-bit bytes into one character
49 //config: (as in Unicode mode), this will not work properly.
51 //config:config FEATURE_VI_COLON
52 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
54 //config: depends on VI
56 //config: Enable a limited set of colon commands for vi. This does not
57 //config: provide an "ex" mode.
59 //config:config FEATURE_VI_YANKMARK
60 //config: bool "Enable yank/put commands and mark cmds"
62 //config: depends on VI
64 //config: This will enable you to use yank and put, as well as mark in
67 //config:config FEATURE_VI_SEARCH
68 //config: bool "Enable search and replace cmds"
70 //config: depends on VI
72 //config: Select this if you wish to be able to do search and replace in
75 //config:config FEATURE_VI_REGEX_SEARCH
76 //config: bool "Enable regex in search and replace"
77 //config: default n # Uses GNU regex, which may be unavailable. FIXME
78 //config: depends on FEATURE_VI_SEARCH
80 //config: Use extended regex search.
82 //config:config FEATURE_VI_USE_SIGNALS
83 //config: bool "Catch signals"
85 //config: depends on VI
87 //config: Selecting this option will make busybox vi signal aware. This will
88 //config: make busybox vi support SIGWINCH to deal with Window Changes, catch
89 //config: Ctrl-Z and Ctrl-C and alarms.
91 //config:config FEATURE_VI_DOT_CMD
92 //config: bool "Remember previous cmd and \".\" cmd"
94 //config: depends on VI
96 //config: Make busybox vi remember the last command and be able to repeat it.
98 //config:config FEATURE_VI_READONLY
99 //config: bool "Enable -R option and \"view\" mode"
101 //config: depends on VI
103 //config: Enable the read-only command line option, which allows the user to
104 //config: open a file in read-only mode.
106 //config:config FEATURE_VI_SETOPTS
107 //config: bool "Enable set-able options, ai ic showmatch"
109 //config: depends on VI
111 //config: Enable the editor to set some (ai, ic, showmatch) options.
113 //config:config FEATURE_VI_SET
114 //config: bool "Support for :set"
116 //config: depends on VI
118 //config: Support for ":set".
120 //config:config FEATURE_VI_WIN_RESIZE
121 //config: bool "Handle window resize"
123 //config: depends on VI
125 //config: Make busybox vi behave nicely with terminals that get resized.
127 //config:config FEATURE_VI_ASK_TERMINAL
128 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
130 //config: depends on VI
132 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
133 //config: this option makes vi perform a last-ditch effort to find it:
134 //config: position cursor to 999,999 and ask terminal to report real
135 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
137 //config: This is not clean but helps a lot on serial lines and such.
138 //config:config FEATURE_VI_UNDO
139 //config: bool "Support undo command 'u'"
141 //config: depends on VI
143 //config: Support the 'u' command to undo insertion, deletion, and replacement
145 //config:config FEATURE_VI_UNDO_QUEUE
146 //config: bool "Enable undo operation queuing"
148 //config: depends on FEATURE_VI_UNDO
150 //config: The vi undo functions can use an intermediate queue to greatly lower
151 //config: malloc() calls and overhead. When the maximum size of this queue is
152 //config: reached, the contents of the queue are committed to the undo stack.
153 //config: This increases the size of the undo code and allows some undo
154 //config: operations (especially un-typing/backspacing) to be far more useful.
155 //config:config FEATURE_VI_UNDO_QUEUE_MAX
156 //config: int "Maximum undo character queue size"
157 //config: default 256
158 //config: range 32 65536
159 //config: depends on FEATURE_VI_UNDO_QUEUE
161 //config: This option sets the number of bytes used at runtime for the queue.
162 //config: Smaller values will create more undo objects and reduce the amount
163 //config: of typed or backspaced characters that are grouped into one undo
164 //config: operation; larger values increase the potential size of each undo
165 //config: and will generally malloc() larger objects and less frequently.
166 //config: Unless you want more (or less) frequent "undo points" while typing,
167 //config: you should probably leave this unchanged.
169 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
171 //kbuild:lib-$(CONFIG_VI) += vi.o
173 //usage:#define vi_trivial_usage
174 //usage: "[OPTIONS] [FILE]..."
175 //usage:#define vi_full_usage "\n\n"
176 //usage: "Edit FILE\n"
177 //usage: IF_FEATURE_VI_COLON(
178 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
180 //usage: IF_FEATURE_VI_READONLY(
181 //usage: "\n -R Read-only"
183 //usage: "\n -H List available features"
186 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
187 #if ENABLE_FEATURE_VI_REGEX_SEARCH
191 /* the CRASHME code is unmaintained, and doesn't currently build */
192 #define ENABLE_FEATURE_VI_CRASHME 0
195 #if ENABLE_LOCALE_SUPPORT
197 #if ENABLE_FEATURE_VI_8BIT
198 //FIXME: this does not work properly for Unicode anyway
199 # define Isprint(c) (isprint)(c)
201 # define Isprint(c) isprint_asciionly(c)
206 /* 0x9b is Meta-ESC */
207 #if ENABLE_FEATURE_VI_8BIT
208 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
210 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
217 MAX_TABSTOP = 32, // sanity limit
218 // User input len. Need not be extra big.
219 // Lines in file being edited *can* be bigger than this.
221 // Sanity limits. We have only one buffer of this size.
222 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
223 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
226 /* VT102 ESC sequences.
227 * See "Xterm Control Sequences"
228 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
230 /* Inverse/Normal text */
231 #define ESC_BOLD_TEXT "\033[7m"
232 #define ESC_NORM_TEXT "\033[0m"
234 #define ESC_BELL "\007"
235 /* Clear-to-end-of-line */
236 #define ESC_CLEAR2EOL "\033[K"
237 /* Clear-to-end-of-screen.
238 * (We use default param here.
239 * Full sequence is "ESC [ <num> J",
240 * <num> is 0/1/2 = "erase below/above/all".)
242 #define ESC_CLEAR2EOS "\033[J"
243 /* Cursor to given coordinate (1,1: top left) */
244 #define ESC_SET_CURSOR_POS "\033[%u;%uH"
246 ///* Cursor up and down */
247 //#define ESC_CURSOR_UP "\033[A"
248 //#define ESC_CURSOR_DOWN "\n"
250 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
251 // cmds modifying text[]
252 // vda: removed "aAiIs" as they switch us into insert mode
253 // and remembering input for replay after them makes no sense
254 static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
260 FORWARD = 1, // code depends on "1" for array index
261 BACK = -1, // code depends on "-1" for array index
262 LIMITED = 0, // how much of text[] in char_search
263 FULL = 1, // how much of text[] in char_search
265 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
266 S_TO_WS = 2, // used in skip_thing() for moving "dot"
267 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
268 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
269 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
273 /* vi.c expects chars to be unsigned. */
274 /* busybox build system provides that, but it's better */
275 /* to audit and fix the source */
278 /* many references - keep near the top of globals */
279 char *text, *end; // pointers to the user data in memory
280 char *dot; // where all the action takes place
281 int text_size; // size of the allocated buffer
285 #define VI_AUTOINDENT 1
286 #define VI_SHOWMATCH 2
287 #define VI_IGNORECASE 4
288 #define VI_ERR_METHOD 8
289 #define autoindent (vi_setops & VI_AUTOINDENT)
290 #define showmatch (vi_setops & VI_SHOWMATCH )
291 #define ignorecase (vi_setops & VI_IGNORECASE)
292 /* indicate error with beep or flash */
293 #define err_method (vi_setops & VI_ERR_METHOD)
295 #if ENABLE_FEATURE_VI_READONLY
296 smallint readonly_mode;
297 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
298 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
299 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
301 #define SET_READONLY_FILE(flags) ((void)0)
302 #define SET_READONLY_MODE(flags) ((void)0)
303 #define UNSET_READONLY_FILE(flags) ((void)0)
306 smallint editing; // >0 while we are editing a file
307 // [code audit says "can be 0, 1 or 2 only"]
308 smallint cmd_mode; // 0=command 1=insert 2=replace
309 int file_modified; // buffer contents changed (counter, not flag!)
310 int last_file_modified; // = -1;
311 int save_argc; // how many file names on cmd line
312 int cmdcnt; // repetition count
313 unsigned rows, columns; // the terminal screen is this size
314 #if ENABLE_FEATURE_VI_ASK_TERMINAL
315 int get_rowcol_error;
317 int crow, ccol; // cursor is on Crow x Ccol
318 int offset; // chars scrolled off the screen to the left
319 int have_status_msg; // is default edit status needed?
320 // [don't make smallint!]
321 int last_status_cksum; // hash of current status line
322 char *current_filename;
323 char *screenbegin; // index into text[], of top line on the screen
324 char *screen; // pointer to the virtual screen buffer
325 int screensize; // and its size
327 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
328 char erase_char; // the users erase character
329 char last_input_char; // last char read from user
331 #if ENABLE_FEATURE_VI_DOT_CMD
332 smallint adding2q; // are we currently adding user input to q
333 int lmc_len; // length of last_modifying_cmd
334 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
336 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
339 #if ENABLE_FEATURE_VI_SEARCH
340 char *last_search_pattern; // last pattern from a '/' or '?' search
344 #if ENABLE_FEATURE_VI_YANKMARK
345 char *edit_file__cur_line;
347 int refresh__old_offset;
348 int format_edit_status__tot;
350 /* a few references only */
351 #if ENABLE_FEATURE_VI_YANKMARK
352 int YDreg, Ureg; // default delete register and orig line for "U"
353 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
354 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
355 char *context_start, *context_end;
357 #if ENABLE_FEATURE_VI_USE_SIGNALS
358 sigjmp_buf restart; // catch_sig()
360 struct termios term_orig, term_vi; // remember what the cooked mode was
361 #if ENABLE_FEATURE_VI_COLON
362 char *initial_cmds[3]; // currently 2 entries, NULL terminated
364 // Should be just enough to hold a key sequence,
365 // but CRASHME mode uses it as generated command buffer too
366 #if ENABLE_FEATURE_VI_CRASHME
367 char readbuffer[128];
369 char readbuffer[KEYCODE_BUFFER_SIZE];
371 #define STATUS_BUFFER_LEN 200
372 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
373 #if ENABLE_FEATURE_VI_DOT_CMD
374 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
376 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
378 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
379 #if ENABLE_FEATURE_VI_UNDO
380 // undo_push() operations
383 #define UNDO_INS_CHAIN 2
384 #define UNDO_DEL_CHAIN 3
385 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
386 #define UNDO_QUEUED_FLAG 4
387 #define UNDO_INS_QUEUED 4
388 #define UNDO_DEL_QUEUED 5
389 #define UNDO_USE_SPOS 32
390 #define UNDO_EMPTY 64
391 // Pass-through flags for functions that can be undone
394 #define ALLOW_UNDO_CHAIN 2
395 #if ENABLE_FEATURE_VI_UNDO_QUEUE
396 #define ALLOW_UNDO_QUEUED 3
397 char undo_queue_state;
399 char *undo_queue_spos; // Start position of queued operation
400 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
402 // If undo queuing disabled, don't invoke the missing queue logic
403 #define ALLOW_UNDO_QUEUED 1
404 #endif /* ENABLE_FEATURE_VI_UNDO_QUEUE */
407 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
408 int u_type; // 0=deleted, 1=inserted, 2=swapped
409 int start; // Offset where the data should be restored/deleted
410 int length; // total data size
411 char *undo_text; // ptr to text that will be inserted
413 #endif /* ENABLE_FEATURE_VI_UNDO */
416 #define G (*ptr_to_globals)
417 #define text (G.text )
418 #define text_size (G.text_size )
423 #define vi_setops (G.vi_setops )
424 #define editing (G.editing )
425 #define cmd_mode (G.cmd_mode )
426 #define file_modified (G.file_modified )
427 #define last_file_modified (G.last_file_modified )
428 #define save_argc (G.save_argc )
429 #define cmdcnt (G.cmdcnt )
430 #define rows (G.rows )
431 #define columns (G.columns )
432 #define crow (G.crow )
433 #define ccol (G.ccol )
434 #define offset (G.offset )
435 #define status_buffer (G.status_buffer )
436 #define have_status_msg (G.have_status_msg )
437 #define last_status_cksum (G.last_status_cksum )
438 #define current_filename (G.current_filename )
439 #define screen (G.screen )
440 #define screensize (G.screensize )
441 #define screenbegin (G.screenbegin )
442 #define tabstop (G.tabstop )
443 #define last_forward_char (G.last_forward_char )
444 #define erase_char (G.erase_char )
445 #define last_input_char (G.last_input_char )
446 #if ENABLE_FEATURE_VI_READONLY
447 #define readonly_mode (G.readonly_mode )
449 #define readonly_mode 0
451 #define adding2q (G.adding2q )
452 #define lmc_len (G.lmc_len )
454 #define ioq_start (G.ioq_start )
455 #define my_pid (G.my_pid )
456 #define last_search_pattern (G.last_search_pattern)
458 #define edit_file__cur_line (G.edit_file__cur_line)
459 #define refresh__old_offset (G.refresh__old_offset)
460 #define format_edit_status__tot (G.format_edit_status__tot)
462 #define YDreg (G.YDreg )
463 #define Ureg (G.Ureg )
464 #define mark (G.mark )
465 #define context_start (G.context_start )
466 #define context_end (G.context_end )
467 #define restart (G.restart )
468 #define term_orig (G.term_orig )
469 #define term_vi (G.term_vi )
470 #define initial_cmds (G.initial_cmds )
471 #define readbuffer (G.readbuffer )
472 #define scr_out_buf (G.scr_out_buf )
473 #define last_modifying_cmd (G.last_modifying_cmd )
474 #define get_input_line__buf (G.get_input_line__buf)
476 #if ENABLE_FEATURE_VI_UNDO
477 #define undo_stack_tail (G.undo_stack_tail )
478 # if ENABLE_FEATURE_VI_UNDO_QUEUE
479 #define undo_queue_state (G.undo_queue_state)
480 #define undo_q (G.undo_q )
481 #define undo_queue (G.undo_queue )
482 #define undo_queue_spos (G.undo_queue_spos )
486 #define INIT_G() do { \
487 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
488 last_file_modified = -1; \
489 /* "" but has space for 2 chars: */ \
490 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
494 static int init_text_buffer(char *); // init from file or create new
495 static void edit_file(char *); // edit one file
496 static void do_cmd(int); // execute a command
497 static int next_tabstop(int);
498 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
499 static char *begin_line(char *); // return pointer to cur line B-o-l
500 static char *end_line(char *); // return pointer to cur line E-o-l
501 static char *prev_line(char *); // return pointer to prev line B-o-l
502 static char *next_line(char *); // return pointer to next line B-o-l
503 static char *end_screen(void); // get pointer to last char on screen
504 static int count_lines(char *, char *); // count line from start to stop
505 static char *find_line(int); // find begining of line #li
506 static char *move_to_col(char *, int); // move "p" to column l
507 static void dot_left(void); // move dot left- dont leave line
508 static void dot_right(void); // move dot right- dont leave line
509 static void dot_begin(void); // move dot to B-o-l
510 static void dot_end(void); // move dot to E-o-l
511 static void dot_next(void); // move dot to next line B-o-l
512 static void dot_prev(void); // move dot to prev line B-o-l
513 static void dot_scroll(int, int); // move the screen up or down
514 static void dot_skip_over_ws(void); // move dot pat WS
515 static char *bound_dot(char *); // make sure text[0] <= P < "end"
516 static char *new_screen(int, int); // malloc virtual screen memory
517 #if !ENABLE_FEATURE_VI_UNDO
518 #define char_insert(a,b,c) char_insert(a,b)
520 static char *char_insert(char *, char, int); // insert the char c at 'p'
521 // might reallocate text[]! use p += stupid_insert(p, ...),
522 // and be careful to not use pointers into potentially freed text[]!
523 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
524 static int find_range(char **, char **, char); // return pointers for an object
525 static int st_test(char *, int, int, char *); // helper for skip_thing()
526 static char *skip_thing(char *, int, int, int); // skip some object
527 static char *find_pair(char *, char); // find matching pair () [] {}
528 #if !ENABLE_FEATURE_VI_UNDO
529 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
531 static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
532 // might reallocate text[]! use p += text_hole_make(p, ...),
533 // and be careful to not use pointers into potentially freed text[]!
534 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
535 #if !ENABLE_FEATURE_VI_UNDO
536 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
538 static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
539 static void show_help(void); // display some help info
540 static void rawmode(void); // set "raw" mode on tty
541 static void cookmode(void); // return to "cooked" mode on tty
542 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
543 static int mysleep(int);
544 static int readit(void); // read (maybe cursor) key from stdin
545 static int get_one_char(void); // read 1 char from stdin
546 static int file_size(const char *); // what is the byte size of "fn"
547 #if !ENABLE_FEATURE_VI_READONLY
548 #define file_insert(fn, p, update_ro_status) file_insert(fn, p)
550 // file_insert might reallocate text[]!
551 static int file_insert(const char *, char *, int);
552 static int file_write(char *, char *, char *);
553 static void place_cursor(int, int);
554 static void screen_erase(void);
555 static void clear_to_eol(void);
556 static void clear_to_eos(void);
557 static void go_bottom_and_clear_to_eol(void);
558 static void standout_start(void); // send "start reverse video" sequence
559 static void standout_end(void); // send "end reverse video" sequence
560 static void flash(int); // flash the terminal screen
561 static void show_status_line(void); // put a message on the bottom line
562 static void status_line(const char *, ...); // print to status buf
563 static void status_line_bold(const char *, ...);
564 static void status_line_bold_errno(const char *fn);
565 static void not_implemented(const char *); // display "Not implemented" message
566 static int format_edit_status(void); // format file status on status line
567 static void redraw(int); // force a full screen refresh
568 static char* format_line(char* /*, int*/);
569 static void refresh(int); // update the terminal from screen[]
571 static void Indicate_Error(void); // use flash or beep to indicate error
572 #define indicate_error(c) Indicate_Error()
573 static void Hit_Return(void);
575 #if ENABLE_FEATURE_VI_SEARCH
576 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
578 #if ENABLE_FEATURE_VI_COLON
579 static char *get_one_address(char *, int *); // get colon addr, if present
580 static char *get_address(char *, int *, int *); // get two colon addrs, if present
581 static void colon(char *); // execute the "colon" mode cmds
583 #if ENABLE_FEATURE_VI_USE_SIGNALS
584 static void winch_sig(int); // catch window size changes
585 static void suspend_sig(int); // catch ctrl-Z
586 static void catch_sig(int); // catch ctrl-C and alarm time-outs
588 #if ENABLE_FEATURE_VI_DOT_CMD
589 static void start_new_cmd_q(char); // new queue for command
590 static void end_cmd_q(void); // stop saving input chars
592 #define end_cmd_q() ((void)0)
594 #if ENABLE_FEATURE_VI_SETOPTS
595 static void showmatching(char *); // show the matching pair () [] {}
597 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
598 // might reallocate text[]! use p += string_insert(p, ...),
599 // and be careful to not use pointers into potentially freed text[]!
600 # if !ENABLE_FEATURE_VI_UNDO
601 #define string_insert(a,b,c) string_insert(a,b)
603 static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
605 #if ENABLE_FEATURE_VI_YANKMARK
606 static char *text_yank(char *, char *, int); // save copy of "p" into a register
607 static char what_reg(void); // what is letter of current YDreg
608 static void check_context(char); // remember context for '' command
610 #if ENABLE_FEATURE_VI_UNDO
611 static void undo_push(char *, unsigned int, unsigned char); // Push an operation on the undo stack
612 static void undo_pop(void); // Undo the last operation
613 # if ENABLE_FEATURE_VI_UNDO_QUEUE
614 static void undo_queue_commit(void); // Flush any queued objects to the undo stack
616 # define undo_queue_commit() ((void)0)
619 #define undo_queue_commit() ((void)0)
622 #if ENABLE_FEATURE_VI_CRASHME
623 static void crash_dummy();
624 static void crash_test();
625 static int crashme = 0;
628 static void write1(const char *out)
633 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
634 int vi_main(int argc, char **argv)
640 #if ENABLE_FEATURE_VI_UNDO
641 /* undo_stack_tail = NULL; - already is */
642 #if ENABLE_FEATURE_VI_UNDO_QUEUE
643 undo_queue_state = UNDO_EMPTY;
644 /* undo_q = 0; - already is */
648 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
651 #if ENABLE_FEATURE_VI_CRASHME
652 srand((long) my_pid);
654 #ifdef NO_SUCH_APPLET_YET
655 /* If we aren't "vi", we are "view" */
656 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
657 SET_READONLY_MODE(readonly_mode);
661 // autoindent is not default in vim 7.3
662 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
663 // 1- process $HOME/.exrc file (not inplemented yet)
664 // 2- process EXINIT variable from environment
665 // 3- process command line args
666 #if ENABLE_FEATURE_VI_COLON
668 char *p = getenv("EXINIT");
670 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
673 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
675 #if ENABLE_FEATURE_VI_CRASHME
680 #if ENABLE_FEATURE_VI_READONLY
681 case 'R': // Read-only flag
682 SET_READONLY_MODE(readonly_mode);
685 #if ENABLE_FEATURE_VI_COLON
686 case 'c': // cmd line vi command
688 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
700 // The argv array can be used by the ":next" and ":rewind" commands
704 //----- This is the main file handling loop --------------
707 // "Save cursor, use alternate screen buffer, clear screen"
708 write1("\033[?1049h");
710 edit_file(argv[optind]); /* param might be NULL */
711 if (++optind >= argc)
714 // "Use normal screen buffer, restore cursor"
715 write1("\033[?1049l");
716 //-----------------------------------------------------------
721 /* read text from file or create an empty buf */
722 /* will also update current_filename */
723 static int init_text_buffer(char *fn)
726 int size = file_size(fn); // file size. -1 means does not exist.
728 /* allocate/reallocate text buffer */
730 text_size = size + 10240;
731 screenbegin = dot = end = text = xzalloc(text_size);
733 if (fn != current_filename) {
734 free(current_filename);
735 current_filename = xstrdup(fn);
738 // file dont exist. Start empty buf with dummy line
739 char_insert(text, '\n', NO_UNDO);
742 rc = file_insert(fn, text, 1);
745 last_file_modified = -1;
746 #if ENABLE_FEATURE_VI_YANKMARK
747 /* init the marks. */
748 memset(mark, 0, sizeof(mark));
753 #if ENABLE_FEATURE_VI_WIN_RESIZE
754 static int query_screen_dimensions(void)
756 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
757 if (rows > MAX_SCR_ROWS)
759 if (columns > MAX_SCR_COLS)
760 columns = MAX_SCR_COLS;
764 # define query_screen_dimensions() (0)
767 static void edit_file(char *fn)
769 #if ENABLE_FEATURE_VI_YANKMARK
770 #define cur_line edit_file__cur_line
773 #if ENABLE_FEATURE_VI_USE_SIGNALS
777 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
781 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
782 #if ENABLE_FEATURE_VI_ASK_TERMINAL
783 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
785 write1("\033[999;999H" "\033[6n");
787 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
788 if ((int32_t)k == KEYCODE_CURSOR_POS) {
789 uint32_t rc = (k >> 32);
790 columns = (rc & 0x7fff);
791 if (columns > MAX_SCR_COLS)
792 columns = MAX_SCR_COLS;
793 rows = ((rc >> 16) & 0x7fff);
794 if (rows > MAX_SCR_ROWS)
799 new_screen(rows, columns); // get memory for virtual screen
800 init_text_buffer(fn);
802 #if ENABLE_FEATURE_VI_YANKMARK
803 YDreg = 26; // default Yank/Delete reg
804 Ureg = 27; // hold orig line for "U" cmd
805 mark[26] = mark[27] = text; // init "previous context"
808 last_forward_char = last_input_char = '\0';
812 #if ENABLE_FEATURE_VI_USE_SIGNALS
813 signal(SIGINT, catch_sig);
814 signal(SIGWINCH, winch_sig);
815 signal(SIGTSTP, suspend_sig);
816 sig = sigsetjmp(restart, 1);
818 screenbegin = dot = text;
822 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
825 offset = 0; // no horizontal offset
827 #if ENABLE_FEATURE_VI_DOT_CMD
829 ioq = ioq_start = NULL;
834 #if ENABLE_FEATURE_VI_COLON
839 while ((p = initial_cmds[n]) != NULL) {
849 free(initial_cmds[n]);
850 initial_cmds[n] = NULL;
855 redraw(FALSE); // dont force every col re-draw
856 //------This is the main Vi cmd handling loop -----------------------
857 while (editing > 0) {
858 #if ENABLE_FEATURE_VI_CRASHME
860 if ((end - text) > 1) {
861 crash_dummy(); // generate a random command
864 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
870 last_input_char = c = get_one_char(); // get a cmd from user
871 #if ENABLE_FEATURE_VI_YANKMARK
872 // save a copy of the current line- for the 'U" command
873 if (begin_line(dot) != cur_line) {
874 cur_line = begin_line(dot);
875 text_yank(begin_line(dot), end_line(dot), Ureg);
878 #if ENABLE_FEATURE_VI_DOT_CMD
879 // These are commands that change text[].
880 // Remember the input for the "." command
881 if (!adding2q && ioq_start == NULL
882 && cmd_mode == 0 // command mode
883 && c > '\0' // exclude NUL and non-ASCII chars
884 && c < 0x7f // (Unicode and such)
885 && strchr(modifying_cmds, c)
890 do_cmd(c); // execute the user command
892 // poll to see if there is input already waiting. if we are
893 // not able to display output fast enough to keep up, skip
894 // the display update until we catch up with input.
895 if (!readbuffer[0] && mysleep(0) == 0) {
896 // no input pending - so update output
900 #if ENABLE_FEATURE_VI_CRASHME
902 crash_test(); // test editor variables
905 //-------------------------------------------------------------------
907 go_bottom_and_clear_to_eol();
912 //----- The Colon commands -------------------------------------
913 #if ENABLE_FEATURE_VI_COLON
914 static char *get_one_address(char *p, int *addr) // get colon addr, if present
918 IF_FEATURE_VI_YANKMARK(char c;)
919 IF_FEATURE_VI_SEARCH(char *pat;)
921 *addr = -1; // assume no addr
922 if (*p == '.') { // the current line
925 *addr = count_lines(text, q);
927 #if ENABLE_FEATURE_VI_YANKMARK
928 else if (*p == '\'') { // is this a mark addr
932 if (c >= 'a' && c <= 'z') {
935 q = mark[(unsigned char) c];
936 if (q != NULL) { // is mark valid
937 *addr = count_lines(text, q);
942 #if ENABLE_FEATURE_VI_SEARCH
943 else if (*p == '/') { // a search pattern
944 q = strchrnul(++p, '/');
945 pat = xstrndup(p, q - p); // save copy of pattern
949 q = char_search(dot, pat, FORWARD, FULL);
951 *addr = count_lines(text, q);
956 else if (*p == '$') { // the last line in file
958 q = begin_line(end - 1);
959 *addr = count_lines(text, q);
960 } else if (isdigit(*p)) { // specific line number
961 sscanf(p, "%d%n", addr, &st);
964 // unrecognized address - assume -1
970 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
972 //----- get the address' i.e., 1,3 'a,'b -----
973 // get FIRST addr, if present
975 p++; // skip over leading spaces
976 if (*p == '%') { // alias for 1,$
979 *e = count_lines(text, end-1);
982 p = get_one_address(p, b);
985 if (*p == ',') { // is there a address separator
989 // get SECOND addr, if present
990 p = get_one_address(p, e);
994 p++; // skip over trailing spaces
998 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
999 static void setops(const char *args, const char *opname, int flg_no,
1000 const char *short_opname, int opt)
1002 const char *a = args + flg_no;
1003 int l = strlen(opname) - 1; /* opname have + ' ' */
1005 // maybe strncmp? we had tons of erroneous strncasecmp's...
1006 if (strncasecmp(a, opname, l) == 0
1007 || strncasecmp(a, short_opname, 2) == 0
1017 // buf must be no longer than MAX_INPUT_LEN!
1018 static void colon(char *buf)
1020 char c, *orig_buf, *buf1, *q, *r;
1021 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
1022 int i, l, li, ch, b, e;
1023 int useforce, forced = FALSE;
1025 // :3154 // if (-e line 3154) goto it else stay put
1026 // :4,33w! foo // write a portion of buffer to file "foo"
1027 // :w // write all of buffer to current file
1029 // :q! // quit- dont care about modified file
1030 // :'a,'z!sort -u // filter block through sort
1031 // :'f // goto mark "f"
1032 // :'fl // list literal the mark "f" line
1033 // :.r bar // read file "bar" into buffer before dot
1034 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1035 // :/xyz/ // goto the "xyz" line
1036 // :s/find/replace/ // substitute pattern "find" with "replace"
1037 // :!<cmd> // run <cmd> then return
1043 buf++; // move past the ':'
1047 q = text; // assume 1,$ for the range
1049 li = count_lines(text, end - 1);
1050 fn = current_filename;
1052 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1053 buf = get_address(buf, &b, &e);
1055 // remember orig command line
1058 // get the COMMAND into cmd[]
1060 while (*buf != '\0') {
1066 // get any ARGuments
1067 while (isblank(*buf))
1071 buf1 = last_char_is(cmd, '!');
1074 *buf1 = '\0'; // get rid of !
1077 // if there is only one addr, then the addr
1078 // is the line number of the single line the
1079 // user wants. So, reset the end
1080 // pointer to point at end of the "b" line
1081 q = find_line(b); // what line is #b
1086 // we were given two addrs. change the
1087 // end pointer to the addr given by user.
1088 r = find_line(e); // what line is #e
1092 // ------------ now look for the command ------------
1094 if (i == 0) { // :123CR goto line #123
1096 dot = find_line(b); // what line is #b
1100 #if ENABLE_FEATURE_ALLOW_EXEC
1101 else if (cmd[0] == '!') { // run a cmd
1103 // :!ls run the <cmd>
1104 go_bottom_and_clear_to_eol();
1106 retcode = system(orig_buf + 1); // run the cmd
1108 printf("\nshell returned %i\n\n", retcode);
1110 Hit_Return(); // let user see results
1113 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1114 if (b < 0) { // no addr given- use defaults
1115 b = e = count_lines(text, dot);
1117 status_line("%d", b);
1118 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1119 if (b < 0) { // no addr given- use defaults
1120 q = begin_line(dot); // assume .,. for the range
1123 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
1125 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1126 // don't edit, if the current file has been modified
1127 if (file_modified && !useforce) {
1128 status_line_bold("No write since last change (:%s! overrides)", cmd);
1132 // the user supplied a file name
1134 } else if (current_filename && current_filename[0]) {
1135 // no user supplied name- use the current filename
1136 // fn = current_filename; was set by default
1138 // no user file name, no current name- punt
1139 status_line_bold("No current filename");
1143 if (init_text_buffer(fn) < 0)
1146 #if ENABLE_FEATURE_VI_YANKMARK
1147 if (Ureg >= 0 && Ureg < 28) {
1148 free(reg[Ureg]); // free orig line reg- for 'U'
1151 if (YDreg >= 0 && YDreg < 28) {
1152 free(reg[YDreg]); // free default yank/delete register
1156 // how many lines in text[]?
1157 li = count_lines(text, end - 1);
1158 status_line("'%s'%s"
1159 IF_FEATURE_VI_READONLY("%s")
1160 " %dL, %dC", current_filename,
1161 (file_size(fn) < 0 ? " [New file]" : ""),
1162 IF_FEATURE_VI_READONLY(
1163 ((readonly_mode) ? " [Readonly]" : ""),
1166 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1167 if (b != -1 || e != -1) {
1168 status_line_bold("No address allowed on this command");
1172 // user wants a new filename
1173 free(current_filename);
1174 current_filename = xstrdup(args);
1176 // user wants file status info
1177 last_status_cksum = 0; // force status update
1179 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1180 // print out values of all features
1181 go_bottom_and_clear_to_eol();
1186 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1187 if (b < 0) { // no addr given- use defaults
1188 q = begin_line(dot); // assume .,. for the range
1191 go_bottom_and_clear_to_eol();
1193 for (; q <= r; q++) {
1197 c_is_no_print = (c & 0x80) && !Isprint(c);
1198 if (c_is_no_print) {
1204 } else if (c < ' ' || c == 127) {
1216 } else if (strncmp(cmd, "quit", i) == 0 // quit
1217 || strncmp(cmd, "next", i) == 0 // edit next file
1218 || strncmp(cmd, "prev", i) == 0 // edit previous file
1223 // force end of argv list
1229 // don't exit if the file been modified
1230 if (file_modified) {
1231 status_line_bold("No write since last change (:%s! overrides)", cmd);
1234 // are there other file to edit
1235 n = save_argc - optind - 1;
1236 if (*cmd == 'q' && n > 0) {
1237 status_line_bold("%d more file(s) to edit", n);
1240 if (*cmd == 'n' && n <= 0) {
1241 status_line_bold("No more files to edit");
1245 // are there previous files to edit
1247 status_line_bold("No previous files to edit");
1253 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1256 status_line_bold("No filename given");
1259 if (b < 0) { // no addr given- use defaults
1260 q = begin_line(dot); // assume "dot"
1262 // read after current line- unless user said ":0r foo"
1265 { // dance around potentially-reallocated text[]
1266 uintptr_t ofs = q - text;
1267 ch = file_insert(fn, q, 0);
1271 goto ret; // nothing was inserted
1272 // how many lines in text[]?
1273 li = count_lines(q, q + ch - 1);
1275 IF_FEATURE_VI_READONLY("%s")
1277 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1280 // if the insert is before "dot" then we need to update
1285 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1286 if (file_modified && !useforce) {
1287 status_line_bold("No write since last change (:%s! overrides)", cmd);
1289 // reset the filenames to edit
1290 optind = -1; /* start from 0th file */
1293 #if ENABLE_FEATURE_VI_SET
1294 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1295 #if ENABLE_FEATURE_VI_SETOPTS
1298 i = 0; // offset into args
1299 // only blank is regarded as args delimiter. What about tab '\t'?
1300 if (!args[0] || strcasecmp(args, "all") == 0) {
1301 // print out values of all options
1302 #if ENABLE_FEATURE_VI_SETOPTS
1309 autoindent ? "" : "no",
1310 err_method ? "" : "no",
1311 ignorecase ? "" : "no",
1312 showmatch ? "" : "no",
1318 #if ENABLE_FEATURE_VI_SETOPTS
1321 if (strncmp(argp, "no", 2) == 0)
1322 i = 2; // ":set noautoindent"
1323 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1324 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1325 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1326 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1327 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1329 sscanf(argp + i+8, "%u", &t);
1330 if (t > 0 && t <= MAX_TABSTOP)
1333 argp = skip_non_whitespace(argp);
1334 argp = skip_whitespace(argp);
1336 #endif /* FEATURE_VI_SETOPTS */
1337 #endif /* FEATURE_VI_SET */
1338 #if ENABLE_FEATURE_VI_SEARCH
1339 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1340 char *F, *R, *flags;
1341 size_t len_F, len_R;
1342 int gflag; // global replace flag
1343 #if ENABLE_FEATURE_VI_UNDO
1344 int dont_chain_first_item = ALLOW_UNDO;
1347 // F points to the "find" pattern
1348 // R points to the "replace" pattern
1349 // replace the cmd line delimiters "/" with NULs
1350 c = orig_buf[1]; // what is the delimiter
1351 F = orig_buf + 2; // start of "find"
1352 R = strchr(F, c); // middle delimiter
1356 *R++ = '\0'; // terminate "find"
1357 flags = strchr(R, c);
1361 *flags++ = '\0'; // terminate "replace"
1365 if (b < 0) { // maybe :s/foo/bar/
1366 q = begin_line(dot); // start with cur line
1367 b = count_lines(text, q); // cur line number
1370 e = b; // maybe :.s/foo/bar/
1372 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1373 char *ls = q; // orig line start
1376 found = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1379 // we found the "find" pattern - delete it
1380 // For undo support, the first item should not be chained
1381 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
1382 #if ENABLE_FEATURE_VI_UNDO
1383 dont_chain_first_item = ALLOW_UNDO_CHAIN;
1385 // insert the "replace" patern
1386 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
1389 /*q += bias; - recalculated anyway */
1390 // check for "global" :s/foo/bar/g
1392 if ((found + len_R) < end_line(ls)) {
1394 goto vc4; // don't let q move past cur line
1400 #endif /* FEATURE_VI_SEARCH */
1401 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1402 status_line(BB_VER " " BB_BT);
1403 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1404 || strncmp(cmd, "wq", i) == 0
1405 || strncmp(cmd, "wn", i) == 0
1406 || (cmd[0] == 'x' && !cmd[1])
1408 // is there a file name to write to?
1412 #if ENABLE_FEATURE_VI_READONLY
1413 if (readonly_mode && !useforce) {
1414 status_line_bold("'%s' is read only", fn);
1418 // how many lines in text[]?
1419 li = count_lines(q, r);
1421 // see if file exists- if not, its just a new file request
1423 // if "fn" is not write-able, chmod u+w
1424 // sprintf(syscmd, "chmod u+w %s", fn);
1428 l = file_write(fn, q, r);
1429 if (useforce && forced) {
1431 // sprintf(syscmd, "chmod u-w %s", fn);
1437 status_line_bold_errno(fn);
1439 status_line("'%s' %dL, %dC", fn, li, l);
1440 if (q == text && r == end - 1 && l == ch) {
1442 last_file_modified = -1;
1444 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
1445 || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
1452 #if ENABLE_FEATURE_VI_YANKMARK
1453 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1454 if (b < 0) { // no addr given- use defaults
1455 q = begin_line(dot); // assume .,. for the range
1458 text_yank(q, r, YDreg);
1459 li = count_lines(q, r);
1460 status_line("Yank %d lines (%d chars) into [%c]",
1461 li, strlen(reg[YDreg]), what_reg());
1465 not_implemented(cmd);
1468 dot = bound_dot(dot); // make sure "dot" is valid
1470 #if ENABLE_FEATURE_VI_SEARCH
1472 status_line(":s expression missing delimiters");
1476 #endif /* FEATURE_VI_COLON */
1478 static void Hit_Return(void)
1483 write1("[Hit return to continue]");
1485 while ((c = get_one_char()) != '\n' && c != '\r')
1487 redraw(TRUE); // force redraw all
1490 static int next_tabstop(int col)
1492 return col + ((tabstop - 1) - (col % tabstop));
1495 //----- Synchronize the cursor to Dot --------------------------
1496 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1498 char *beg_cur; // begin and end of "d" line
1502 beg_cur = begin_line(d); // first char of cur line
1504 if (beg_cur < screenbegin) {
1505 // "d" is before top line on screen
1506 // how many lines do we have to move
1507 cnt = count_lines(beg_cur, screenbegin);
1509 screenbegin = beg_cur;
1510 if (cnt > (rows - 1) / 2) {
1511 // we moved too many lines. put "dot" in middle of screen
1512 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1513 screenbegin = prev_line(screenbegin);
1517 char *end_scr; // begin and end of screen
1518 end_scr = end_screen(); // last char of screen
1519 if (beg_cur > end_scr) {
1520 // "d" is after bottom line on screen
1521 // how many lines do we have to move
1522 cnt = count_lines(end_scr, beg_cur);
1523 if (cnt > (rows - 1) / 2)
1524 goto sc1; // too many lines
1525 for (ro = 0; ro < cnt - 1; ro++) {
1526 // move screen begin the same amount
1527 screenbegin = next_line(screenbegin);
1528 // now, move the end of screen
1529 end_scr = next_line(end_scr);
1530 end_scr = end_line(end_scr);
1534 // "d" is on screen- find out which row
1536 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1542 // find out what col "d" is on
1544 while (tp < d) { // drive "co" to correct column
1545 if (*tp == '\n') //vda || *tp == '\0')
1548 // handle tabs like real vi
1549 if (d == tp && cmd_mode) {
1552 co = next_tabstop(co);
1553 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1554 co++; // display as ^X, use 2 columns
1560 // "co" is the column where "dot" is.
1561 // The screen has "columns" columns.
1562 // The currently displayed columns are 0+offset -- columns+ofset
1563 // |-------------------------------------------------------------|
1565 // offset | |------- columns ----------------|
1567 // If "co" is already in this range then we do not have to adjust offset
1568 // but, we do have to subtract the "offset" bias from "co".
1569 // If "co" is outside this range then we have to change "offset".
1570 // If the first char of a line is a tab the cursor will try to stay
1571 // in column 7, but we have to set offset to 0.
1573 if (co < 0 + offset) {
1576 if (co >= columns + offset) {
1577 offset = co - columns + 1;
1579 // if the first char of the line is a tab, and "dot" is sitting on it
1580 // force offset to 0.
1581 if (d == beg_cur && *d == '\t') {
1590 //----- Text Movement Routines ---------------------------------
1591 static char *begin_line(char *p) // return pointer to first char cur line
1594 p = memrchr(text, '\n', p - text);
1602 static char *end_line(char *p) // return pointer to NL of cur line
1605 p = memchr(p, '\n', end - p - 1);
1612 static char *dollar_line(char *p) // return pointer to just before NL line
1615 // Try to stay off of the Newline
1616 if (*p == '\n' && (p - begin_line(p)) > 0)
1621 static char *prev_line(char *p) // return pointer first char prev line
1623 p = begin_line(p); // goto begining of cur line
1624 if (p > text && p[-1] == '\n')
1625 p--; // step to prev line
1626 p = begin_line(p); // goto begining of prev line
1630 static char *next_line(char *p) // return pointer first char next line
1633 if (p < end - 1 && *p == '\n')
1634 p++; // step to next line
1638 //----- Text Information Routines ------------------------------
1639 static char *end_screen(void)
1644 // find new bottom line
1646 for (cnt = 0; cnt < rows - 2; cnt++)
1652 // count line from start to stop
1653 static int count_lines(char *start, char *stop)
1658 if (stop < start) { // start and stop are backwards- reverse them
1664 stop = end_line(stop);
1665 while (start <= stop && start <= end - 1) {
1666 start = end_line(start);
1674 static char *find_line(int li) // find begining of line #li
1678 for (q = text; li > 1; li--) {
1684 //----- Dot Movement Routines ----------------------------------
1685 static void dot_left(void)
1687 undo_queue_commit();
1688 if (dot > text && dot[-1] != '\n')
1692 static void dot_right(void)
1694 undo_queue_commit();
1695 if (dot < end - 1 && *dot != '\n')
1699 static void dot_begin(void)
1701 undo_queue_commit();
1702 dot = begin_line(dot); // return pointer to first char cur line
1705 static void dot_end(void)
1707 undo_queue_commit();
1708 dot = end_line(dot); // return pointer to last char cur line
1711 static char *move_to_col(char *p, int l)
1717 while (co < l && p < end) {
1718 if (*p == '\n') //vda || *p == '\0')
1721 co = next_tabstop(co);
1722 } else if (*p < ' ' || *p == 127) {
1723 co++; // display as ^X, use 2 columns
1731 static void dot_next(void)
1733 undo_queue_commit();
1734 dot = next_line(dot);
1737 static void dot_prev(void)
1739 undo_queue_commit();
1740 dot = prev_line(dot);
1743 static void dot_scroll(int cnt, int dir)
1747 undo_queue_commit();
1748 for (; cnt > 0; cnt--) {
1751 // ctrl-Y scroll up one line
1752 screenbegin = prev_line(screenbegin);
1755 // ctrl-E scroll down one line
1756 screenbegin = next_line(screenbegin);
1759 // make sure "dot" stays on the screen so we dont scroll off
1760 if (dot < screenbegin)
1762 q = end_screen(); // find new bottom line
1764 dot = begin_line(q); // is dot is below bottom line?
1768 static void dot_skip_over_ws(void)
1771 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1775 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1777 if (p >= end && end > text) {
1779 indicate_error('1');
1783 indicate_error('2');
1788 //----- Helper Utility Routines --------------------------------
1790 //----------------------------------------------------------------
1791 //----- Char Routines --------------------------------------------
1792 /* Chars that are part of a word-
1793 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1794 * Chars that are Not part of a word (stoppers)
1795 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1796 * Chars that are WhiteSpace
1797 * TAB NEWLINE VT FF RETURN SPACE
1798 * DO NOT COUNT NEWLINE AS WHITESPACE
1801 static char *new_screen(int ro, int co)
1806 screensize = ro * co + 8;
1807 screen = xmalloc(screensize);
1808 // initialize the new screen. assume this will be a empty file.
1810 // non-existent text[] lines start with a tilde (~).
1811 for (li = 1; li < ro - 1; li++) {
1812 screen[(li * co) + 0] = '~';
1817 #if ENABLE_FEATURE_VI_SEARCH
1819 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1821 // search for pattern starting at p
1822 static char *char_search(char *p, const char *pat, int dir, int range)
1824 struct re_pattern_buffer preg;
1830 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1832 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1834 memset(&preg, 0, sizeof(preg));
1835 err = re_compile_pattern(pat, strlen(pat), &preg);
1837 status_line_bold("bad search pattern '%s': %s", pat, err);
1841 // assume a LIMITED forward search
1845 // RANGE could be negative if we are searching backwards
1855 // search for the compiled pattern, preg, in p[]
1856 // range < 0: search backward
1857 // range > 0: search forward
1859 // re_search() < 0: not found or error
1860 // re_search() >= 0: index of found pattern
1861 // struct pattern char int int int struct reg
1862 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1863 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1876 # if ENABLE_FEATURE_VI_SETOPTS
1877 static int mycmp(const char *s1, const char *s2, int len)
1880 return strncasecmp(s1, s2, len);
1882 return strncmp(s1, s2, len);
1885 # define mycmp strncmp
1888 static char *char_search(char *p, const char *pat, int dir, int range)
1894 if (dir == FORWARD) {
1895 stop = end - 1; // assume range is p..end-1
1896 if (range == LIMITED)
1897 stop = next_line(p); // range is to next line
1898 for (start = p; start < stop; start++) {
1899 if (mycmp(start, pat, len) == 0) {
1903 } else if (dir == BACK) {
1904 stop = text; // assume range is text..p
1905 if (range == LIMITED)
1906 stop = prev_line(p); // range is to prev line
1907 for (start = p - len; start >= stop; start--) {
1908 if (mycmp(start, pat, len) == 0) {
1913 // pattern not found
1919 #endif /* FEATURE_VI_SEARCH */
1921 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1923 if (c == 22) { // Is this an ctrl-V?
1924 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1925 refresh(FALSE); // show the ^
1928 #if ENABLE_FEATURE_VI_UNDO
1931 undo_push(p, 1, UNDO_INS);
1933 case ALLOW_UNDO_CHAIN:
1934 undo_push(p, 1, UNDO_INS_CHAIN);
1936 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1937 case ALLOW_UNDO_QUEUED:
1938 undo_push(p, 1, UNDO_INS_QUEUED);
1944 #endif /* ENABLE_FEATURE_VI_UNDO */
1946 } else if (c == 27) { // Is this an ESC?
1948 undo_queue_commit();
1950 end_cmd_q(); // stop adding to q
1951 last_status_cksum = 0; // force status update
1952 if ((p[-1] != '\n') && (dot > text)) {
1955 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1957 if ((p[-1] != '\n') && (dot>text)) {
1959 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
1962 #if ENABLE_FEATURE_VI_SETOPTS
1963 // insert a char into text[]
1964 char *sp; // "save p"
1968 c = '\n'; // translate \r to \n
1969 #if ENABLE_FEATURE_VI_SETOPTS
1970 sp = p; // remember addr of insert
1972 #if ENABLE_FEATURE_VI_UNDO
1973 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1975 undo_queue_commit();
1979 undo_push(p, 1, UNDO_INS);
1981 case ALLOW_UNDO_CHAIN:
1982 undo_push(p, 1, UNDO_INS_CHAIN);
1984 # if ENABLE_FEATURE_VI_UNDO_QUEUE
1985 case ALLOW_UNDO_QUEUED:
1986 undo_push(p, 1, UNDO_INS_QUEUED);
1992 #endif /* ENABLE_FEATURE_VI_UNDO */
1993 p += 1 + stupid_insert(p, c); // insert the char
1994 #if ENABLE_FEATURE_VI_SETOPTS
1995 if (showmatch && strchr(")]}", *sp) != NULL) {
1998 if (autoindent && c == '\n') { // auto indent the new line
2001 q = prev_line(p); // use prev line as template
2002 len = strspn(q, " \t"); // space or tab
2005 bias = text_hole_make(p, len);
2008 #if ENABLE_FEATURE_VI_UNDO
2009 undo_push(p, len, UNDO_INS);
2020 // might reallocate text[]! use p += stupid_insert(p, ...),
2021 // and be careful to not use pointers into potentially freed text[]!
2022 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2025 bias = text_hole_make(p, 1);
2031 static int find_range(char **start, char **stop, char c)
2033 char *save_dot, *p, *q, *t;
2034 int cnt, multiline = 0;
2039 if (strchr("cdy><", c)) {
2040 // these cmds operate on whole lines
2041 p = q = begin_line(p);
2042 for (cnt = 1; cnt < cmdcnt; cnt++) {
2046 } else if (strchr("^%$0bBeEfth\b\177", c)) {
2047 // These cmds operate on char positions
2048 do_cmd(c); // execute movement cmd
2050 } else if (strchr("wW", c)) {
2051 do_cmd(c); // execute movement cmd
2052 // if we are at the next word's first char
2053 // step back one char
2054 // but check the possibilities when it is true
2055 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
2056 || (ispunct(dot[-1]) && !ispunct(dot[0]))
2057 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
2058 dot--; // move back off of next word
2059 if (dot > text && *dot == '\n')
2060 dot--; // stay off NL
2062 } else if (strchr("H-k{", c)) {
2063 // these operate on multi-lines backwards
2064 q = end_line(dot); // find NL
2065 do_cmd(c); // execute movement cmd
2068 } else if (strchr("L+j}\r\n", c)) {
2069 // these operate on multi-lines forwards
2070 p = begin_line(dot);
2071 do_cmd(c); // execute movement cmd
2072 dot_end(); // find NL
2075 // nothing -- this causes any other values of c to
2076 // represent the one-character range under the
2077 // cursor. this is correct for ' ' and 'l', but
2078 // perhaps no others.
2087 // backward char movements don't include start position
2088 if (q > p && strchr("^0bBh\b\177", c)) q--;
2091 for (t = p; t <= q; t++) {
2104 static int st_test(char *p, int type, int dir, char *tested)
2114 if (type == S_BEFORE_WS) {
2116 test = (!isspace(c) || c == '\n');
2118 if (type == S_TO_WS) {
2120 test = (!isspace(c) || c == '\n');
2122 if (type == S_OVER_WS) {
2126 if (type == S_END_PUNCT) {
2130 if (type == S_END_ALNUM) {
2132 test = (isalnum(c) || c == '_');
2138 static char *skip_thing(char *p, int linecnt, int dir, int type)
2142 while (st_test(p, type, dir, &c)) {
2143 // make sure we limit search to correct number of lines
2144 if (c == '\n' && --linecnt < 1)
2146 if (dir >= 0 && p >= end - 1)
2148 if (dir < 0 && p <= text)
2150 p += dir; // move to next char
2155 // find matching char of pair () [] {}
2156 static char *find_pair(char *p, const char c)
2163 dir = 1; // assume forward
2165 case '(': match = ')'; break;
2166 case '[': match = ']'; break;
2167 case '{': match = '}'; break;
2168 case ')': match = '('; dir = -1; break;
2169 case ']': match = '['; dir = -1; break;
2170 case '}': match = '{'; dir = -1; break;
2172 for (q = p + dir; text <= q && q < end; q += dir) {
2173 // look for match, count levels of pairs (( ))
2175 level++; // increase pair levels
2177 level--; // reduce pair level
2179 break; // found matching pair
2182 q = NULL; // indicate no match
2186 #if ENABLE_FEATURE_VI_SETOPTS
2187 // show the matching char of a pair, () [] {}
2188 static void showmatching(char *p)
2192 // we found half of a pair
2193 q = find_pair(p, *p); // get loc of matching char
2195 indicate_error('3'); // no matching char
2197 // "q" now points to matching pair
2198 save_dot = dot; // remember where we are
2199 dot = q; // go to new loc
2200 refresh(FALSE); // let the user see it
2201 mysleep(40); // give user some time
2202 dot = save_dot; // go back to old loc
2206 #endif /* FEATURE_VI_SETOPTS */
2208 #if ENABLE_FEATURE_VI_UNDO
2209 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2210 static void undo_push(char *src, unsigned int length, unsigned char u_type) // Add to the undo stack
2212 struct undo_object *undo_temp;
2214 // UNDO_INS: insertion, undo will remove from buffer
2215 // UNDO_DEL: deleted text, undo will restore to buffer
2216 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2217 // The CHAIN operations are for handling multiple operations that the user
2218 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2219 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2220 // for the INS/DEL operation. The raw values should be equal to the values of
2221 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2223 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2224 // This undo queuing functionality groups multiple character typing or backspaces
2225 // into a single large undo object. This greatly reduces calls to malloc() for
2226 // single-character operations while typing and has the side benefit of letting
2227 // an undo operation remove chunks of text rather than a single character.
2229 case UNDO_EMPTY: // Just in case this ever happens...
2231 case UNDO_DEL_QUEUED:
2233 return; // Only queue single characters
2234 switch (undo_queue_state) {
2236 undo_queue_state = UNDO_DEL;
2238 undo_queue_spos = src;
2240 undo_queue[(CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q)] = *src;
2241 // If queue is full, dump it into an object
2242 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2243 undo_queue_commit();
2246 // Switch from storing inserted text to deleted text
2247 undo_queue_commit();
2248 undo_push(src, length, UNDO_DEL_QUEUED);
2252 case UNDO_INS_QUEUED:
2255 switch (undo_queue_state) {
2257 undo_queue_state = UNDO_INS;
2258 undo_queue_spos = src;
2260 undo_q++; // Don't need to save any data for insertions
2261 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2262 undo_queue_commit();
2265 // Switch from storing deleted text to inserted text
2266 undo_queue_commit();
2267 undo_push(src, length, UNDO_INS_QUEUED);
2273 // If undo queuing is disabled, ignore the queuing flag entirely
2274 u_type = u_type & ~UNDO_QUEUED_FLAG;
2277 // Allocate a new undo object and use it as the stack tail
2278 undo_temp = undo_stack_tail;
2279 undo_stack_tail = xmalloc(sizeof(struct undo_object));
2280 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2281 if ((u_type & UNDO_USE_SPOS) != 0) {
2282 undo_stack_tail->start = undo_queue_spos - text; // use start position from queue
2284 undo_stack_tail->start = src - text; // use offset from start of text buffer
2286 u_type = (u_type & ~UNDO_USE_SPOS);
2288 undo_stack_tail->start = src - text;
2289 #endif /* ENABLE_FEATURE_VI_UNDO_QUEUE */
2290 // For UNDO_DEL objects, copy the deleted text somewhere
2293 case UNDO_DEL_CHAIN:
2294 if ((src + length) == end)
2296 // If this deletion empties text[], strip the newline. When the buffer becomes
2297 // zero-length, a newline is added back, which requires this to compensate.
2298 undo_stack_tail->undo_text = xmalloc(length);
2299 memcpy(undo_stack_tail->undo_text, src, length);
2302 undo_stack_tail->prev = undo_temp;
2303 undo_stack_tail->length = length;
2304 undo_stack_tail->u_type = u_type;
2308 static void undo_pop(void) // Undo the last operation
2311 char *u_start, *u_end;
2312 struct undo_object *undo_temp;
2314 // Commit pending undo queue before popping (should be unnecessary)
2315 undo_queue_commit();
2317 // Check for an empty undo stack
2318 if (undo_stack_tail == NULL) {
2319 status_line("Already at oldest change");
2323 switch (undo_stack_tail->u_type) {
2325 case UNDO_DEL_CHAIN:
2326 // make hole and put in text that was deleted; deallocate text
2327 u_start = text + undo_stack_tail->start;
2328 text_hole_make(u_start, undo_stack_tail->length);
2329 memcpy(u_start, undo_stack_tail->undo_text, undo_stack_tail->length);
2330 free(undo_stack_tail->undo_text);
2331 status_line("Undo [%d] %s %d chars at position %d",
2332 file_modified, "restored",
2333 undo_stack_tail->length, undo_stack_tail->start);
2336 case UNDO_INS_CHAIN:
2337 // delete what was inserted
2338 u_start = undo_stack_tail->start + text;
2339 u_end = u_start - 1 + undo_stack_tail->length;
2340 text_hole_delete(u_start, u_end, NO_UNDO);
2341 status_line("Undo [%d] %s %d chars at position %d",
2342 file_modified, "deleted",
2343 undo_stack_tail->length, undo_stack_tail->start);
2346 // For chained operations, continue popping all the way down the chain.
2347 // If this is the end of a chain, lower modification count and refresh display
2348 switch (undo_stack_tail->u_type) {
2351 dot = (text + undo_stack_tail->start);
2354 case UNDO_DEL_CHAIN:
2355 case UNDO_INS_CHAIN:
2359 // Deallocate the undo object we just processed
2360 undo_temp = undo_stack_tail->prev;
2361 free(undo_stack_tail);
2362 undo_stack_tail = undo_temp;
2365 undo_pop(); // Follow the undo chain if one exists
2369 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2370 static void undo_queue_commit(void) // Flush any queued objects to the undo stack
2372 // Pushes the queue object onto the undo stack
2374 // Deleted character undo events grow from the end
2375 undo_push((undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q),
2377 (undo_queue_state | UNDO_USE_SPOS)
2379 undo_queue_state = UNDO_EMPTY;
2385 #endif /* ENABLE_FEATURE_VI_UNDO */
2387 // open a hole in text[]
2388 // might reallocate text[]! use p += text_hole_make(p, ...),
2389 // and be careful to not use pointers into potentially freed text[]!
2390 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2396 end += size; // adjust the new END
2397 if (end >= (text + text_size)) {
2399 text_size += end - (text + text_size) + 10240;
2400 new_text = xrealloc(text, text_size);
2401 bias = (new_text - text);
2402 screenbegin += bias;
2406 #if ENABLE_FEATURE_VI_YANKMARK
2409 for (i = 0; i < ARRAY_SIZE(mark); i++)
2416 memmove(p + size, p, end - size - p);
2417 memset(p, ' ', size); // clear new hole
2421 // close a hole in text[]
2422 // "undo" value indicates if this operation should be undo-able
2423 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2428 // move forwards, from beginning
2432 if (q < p) { // they are backward- swap them
2436 hole_size = q - p + 1;
2438 #if ENABLE_FEATURE_VI_UNDO
2443 undo_push(p, hole_size, UNDO_DEL);
2445 case ALLOW_UNDO_CHAIN:
2446 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2448 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2449 case ALLOW_UNDO_QUEUED:
2450 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2456 if (src < text || src > end)
2458 if (dest < text || dest >= end)
2462 goto thd_atend; // just delete the end of the buffer
2463 memmove(dest, src, cnt);
2465 end = end - hole_size; // adjust the new END
2467 dest = end - 1; // make sure dest in below end-1
2469 dest = end = text; // keep pointers valid
2474 // copy text into register, then delete text.
2475 // if dist <= 0, do not include, or go past, a NewLine
2477 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2481 // make sure start <= stop
2483 // they are backwards, reverse them
2489 // we cannot cross NL boundaries
2493 // dont go past a NewLine
2494 for (; p + 1 <= stop; p++) {
2496 stop = p; // "stop" just before NewLine
2502 #if ENABLE_FEATURE_VI_YANKMARK
2503 text_yank(start, stop, YDreg);
2505 if (yf == YANKDEL) {
2506 p = text_hole_delete(start, stop, undo);
2511 static void show_help(void)
2513 puts("These features are available:"
2514 #if ENABLE_FEATURE_VI_SEARCH
2515 "\n\tPattern searches with / and ?"
2517 #if ENABLE_FEATURE_VI_DOT_CMD
2518 "\n\tLast command repeat with ."
2520 #if ENABLE_FEATURE_VI_YANKMARK
2521 "\n\tLine marking with 'x"
2522 "\n\tNamed buffers with \"x"
2524 #if ENABLE_FEATURE_VI_READONLY
2525 //not implemented: "\n\tReadonly if vi is called as \"view\""
2526 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2528 #if ENABLE_FEATURE_VI_SET
2529 "\n\tSome colon mode commands with :"
2531 #if ENABLE_FEATURE_VI_SETOPTS
2532 "\n\tSettable options with \":set\""
2534 #if ENABLE_FEATURE_VI_USE_SIGNALS
2535 "\n\tSignal catching- ^C"
2536 "\n\tJob suspend and resume with ^Z"
2538 #if ENABLE_FEATURE_VI_WIN_RESIZE
2539 "\n\tAdapt to window re-sizes"
2544 #if ENABLE_FEATURE_VI_DOT_CMD
2545 static void start_new_cmd_q(char c)
2547 // get buffer for new cmd
2548 // if there is a current cmd count put it in the buffer first
2550 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2551 } else { // just save char c onto queue
2552 last_modifying_cmd[0] = c;
2558 static void end_cmd_q(void)
2560 #if ENABLE_FEATURE_VI_YANKMARK
2561 YDreg = 26; // go back to default Yank/Delete reg
2565 #endif /* FEATURE_VI_DOT_CMD */
2567 #if ENABLE_FEATURE_VI_YANKMARK \
2568 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2569 || ENABLE_FEATURE_VI_CRASHME
2570 // might reallocate text[]! use p += string_insert(p, ...),
2571 // and be careful to not use pointers into potentially freed text[]!
2572 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2578 #if ENABLE_FEATURE_VI_UNDO
2581 undo_push(p, i, UNDO_INS);
2583 case ALLOW_UNDO_CHAIN:
2584 undo_push(p, i, UNDO_INS_CHAIN);
2588 bias = text_hole_make(p, i);
2591 #if ENABLE_FEATURE_VI_YANKMARK
2594 for (cnt = 0; *s != '\0'; s++) {
2598 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2605 #if ENABLE_FEATURE_VI_YANKMARK
2606 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2609 if (cnt < 0) { // they are backwards- reverse them
2613 free(reg[dest]); // if already a yank register, free it
2614 reg[dest] = xstrndup(p, cnt + 1);
2618 static char what_reg(void)
2622 c = 'D'; // default to D-reg
2623 if (0 <= YDreg && YDreg <= 25)
2624 c = 'a' + (char) YDreg;
2632 static void check_context(char cmd)
2634 // A context is defined to be "modifying text"
2635 // Any modifying command establishes a new context.
2637 if (dot < context_start || dot > context_end) {
2638 if (strchr(modifying_cmds, cmd) != NULL) {
2639 // we are trying to modify text[]- make this the current context
2640 mark[27] = mark[26]; // move cur to prev
2641 mark[26] = dot; // move local to cur
2642 context_start = prev_line(prev_line(dot));
2643 context_end = next_line(next_line(dot));
2644 //loiter= start_loiter= now;
2649 static char *swap_context(char *p) // goto new context for '' command make this the current context
2653 // the current context is in mark[26]
2654 // the previous context is in mark[27]
2655 // only swap context if other context is valid
2656 if (text <= mark[27] && mark[27] <= end - 1) {
2658 mark[27] = mark[26];
2660 p = mark[26]; // where we are going- previous context
2661 context_start = prev_line(prev_line(prev_line(p)));
2662 context_end = next_line(next_line(next_line(p)));
2666 #endif /* FEATURE_VI_YANKMARK */
2668 //----- Set terminal attributes --------------------------------
2669 static void rawmode(void)
2671 tcgetattr(0, &term_orig);
2672 term_vi = term_orig;
2673 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG on - allow intr's
2674 term_vi.c_iflag &= (~IXON & ~ICRNL);
2675 term_vi.c_oflag &= (~ONLCR);
2676 term_vi.c_cc[VMIN] = 1;
2677 term_vi.c_cc[VTIME] = 0;
2678 erase_char = term_vi.c_cc[VERASE];
2679 tcsetattr_stdin_TCSANOW(&term_vi);
2682 static void cookmode(void)
2685 tcsetattr_stdin_TCSANOW(&term_orig);
2688 #if ENABLE_FEATURE_VI_USE_SIGNALS
2689 //----- Come here when we get a window resize signal ---------
2690 static void winch_sig(int sig UNUSED_PARAM)
2692 int save_errno = errno;
2693 // FIXME: do it in main loop!!!
2694 signal(SIGWINCH, winch_sig);
2695 query_screen_dimensions();
2696 new_screen(rows, columns); // get memory for virtual screen
2697 redraw(TRUE); // re-draw the screen
2701 //----- Come here when we get a continue signal -------------------
2702 static void cont_sig(int sig UNUSED_PARAM)
2704 int save_errno = errno;
2705 rawmode(); // terminal to "raw"
2706 last_status_cksum = 0; // force status update
2707 redraw(TRUE); // re-draw the screen
2709 signal(SIGTSTP, suspend_sig);
2710 signal(SIGCONT, SIG_DFL);
2711 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2715 //----- Come here when we get a Suspend signal -------------------
2716 static void suspend_sig(int sig UNUSED_PARAM)
2718 int save_errno = errno;
2719 go_bottom_and_clear_to_eol();
2720 cookmode(); // terminal to "cooked"
2722 signal(SIGCONT, cont_sig);
2723 signal(SIGTSTP, SIG_DFL);
2724 kill(my_pid, SIGTSTP);
2728 //----- Come here when we get a signal ---------------------------
2729 static void catch_sig(int sig)
2731 signal(SIGINT, catch_sig);
2732 siglongjmp(restart, sig);
2734 #endif /* FEATURE_VI_USE_SIGNALS */
2736 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2738 struct pollfd pfd[1];
2740 pfd[0].fd = STDIN_FILENO;
2741 pfd[0].events = POLLIN;
2742 return safe_poll(pfd, 1, hund*10) > 0;
2745 //----- IO Routines --------------------------------------------
2746 static int readit(void) // read (maybe cursor) key from stdin
2751 c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2752 if (c == -1) { // EOF/error
2753 go_bottom_and_clear_to_eol();
2754 cookmode(); // terminal to "cooked"
2755 bb_error_msg_and_die("can't read user input");
2760 //----- IO Routines --------------------------------------------
2761 static int get_one_char(void)
2765 #if ENABLE_FEATURE_VI_DOT_CMD
2767 // we are not adding to the q.
2768 // but, we may be reading from a q
2770 // there is no current q, read from STDIN
2771 c = readit(); // get the users input
2773 // there is a queue to get chars from first
2774 // careful with correct sign expansion!
2775 c = (unsigned char)*ioq++;
2777 // the end of the q, read from STDIN
2779 ioq_start = ioq = 0;
2780 c = readit(); // get the users input
2784 // adding STDIN chars to q
2785 c = readit(); // get the users input
2786 if (lmc_len >= MAX_INPUT_LEN - 1) {
2787 status_line_bold("last_modifying_cmd overrun");
2789 // add new char to q
2790 last_modifying_cmd[lmc_len++] = c;
2794 c = readit(); // get the users input
2795 #endif /* FEATURE_VI_DOT_CMD */
2799 // Get input line (uses "status line" area)
2800 static char *get_input_line(const char *prompt)
2802 // char [MAX_INPUT_LEN]
2803 #define buf get_input_line__buf
2808 strcpy(buf, prompt);
2809 last_status_cksum = 0; // force status update
2810 go_bottom_and_clear_to_eol();
2811 write1(prompt); // write out the :, /, or ? prompt
2814 while (i < MAX_INPUT_LEN) {
2816 if (c == '\n' || c == '\r' || c == 27)
2817 break; // this is end of input
2818 if (c == erase_char || c == 8 || c == 127) {
2819 // user wants to erase prev char
2821 write1("\b \b"); // erase char on screen
2822 if (i <= 0) // user backs up before b-o-l, exit
2824 } else if (c > 0 && c < 256) { // exclude Unicode
2825 // (TODO: need to handle Unicode)
2836 static int file_size(const char *fn) // what is the byte size of "fn"
2842 if (fn && stat(fn, &st_buf) == 0) // see if file exists
2843 cnt = (int) st_buf.st_size;
2847 // might reallocate text[]!
2848 static int file_insert(const char *fn, char *p, int update_ro_status)
2852 struct stat statbuf;
2855 if (stat(fn, &statbuf) < 0) {
2856 status_line_bold_errno(fn);
2859 if (!S_ISREG(statbuf.st_mode)) {
2860 // This is not a regular file
2861 status_line_bold("'%s' is not a regular file", fn);
2864 if (p < text || p > end) {
2865 status_line_bold("Trying to insert file outside of memory");
2869 // read file to buffer
2870 fd = open(fn, O_RDONLY);
2872 status_line_bold_errno(fn);
2875 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2876 p += text_hole_make(p, size);
2877 cnt = safe_read(fd, p, size);
2879 status_line_bold_errno(fn);
2880 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2881 } else if (cnt < size) {
2882 // There was a partial read, shrink unused space text[]
2883 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO); // un-do buffer insert
2884 status_line_bold("can't read '%s'", fn);
2890 #if ENABLE_FEATURE_VI_READONLY
2891 if (update_ro_status
2892 && ((access(fn, W_OK) < 0) ||
2893 /* root will always have access()
2894 * so we check fileperms too */
2895 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2898 SET_READONLY_FILE(readonly_mode);
2904 static int file_write(char *fn, char *first, char *last)
2906 int fd, cnt, charcnt;
2909 status_line_bold("No current filename");
2912 /* By popular request we do not open file with O_TRUNC,
2913 * but instead ftruncate() it _after_ successful write.
2914 * Might reduce amount of data lost on power fail etc.
2916 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2919 cnt = last - first + 1;
2920 charcnt = full_write(fd, first, cnt);
2921 ftruncate(fd, charcnt);
2922 if (charcnt == cnt) {
2924 //file_modified = FALSE;
2932 //----- Terminal Drawing ---------------------------------------
2933 // The terminal is made up of 'rows' line of 'columns' columns.
2934 // classically this would be 24 x 80.
2935 // screen coordinates
2941 // 23,0 ... 23,79 <- status line
2943 //----- Move the cursor to row x col (count from 0, not 1) -------
2944 static void place_cursor(int row, int col)
2946 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
2948 if (row < 0) row = 0;
2949 if (row >= rows) row = rows - 1;
2950 if (col < 0) col = 0;
2951 if (col >= columns) col = columns - 1;
2953 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
2957 //----- Erase from cursor to end of line -----------------------
2958 static void clear_to_eol(void)
2960 write1(ESC_CLEAR2EOL);
2963 static void go_bottom_and_clear_to_eol(void)
2965 place_cursor(rows - 1, 0);
2969 //----- Erase from cursor to end of screen -----------------------
2970 static void clear_to_eos(void)
2972 write1(ESC_CLEAR2EOS);
2975 //----- Start standout mode ------------------------------------
2976 static void standout_start(void)
2978 write1(ESC_BOLD_TEXT);
2981 //----- End standout mode --------------------------------------
2982 static void standout_end(void)
2984 write1(ESC_NORM_TEXT);
2987 //----- Flash the screen --------------------------------------
2988 static void flash(int h)
2997 static void Indicate_Error(void)
2999 #if ENABLE_FEATURE_VI_CRASHME
3001 return; // generate a random command
3010 //----- Screen[] Routines --------------------------------------
3011 //----- Erase the Screen[] memory ------------------------------
3012 static void screen_erase(void)
3014 memset(screen, ' ', screensize); // clear new screen
3017 static int bufsum(char *buf, int count)
3020 char *e = buf + count;
3023 sum += (unsigned char) *buf++;
3027 //----- Draw the status line at bottom of the screen -------------
3028 static void show_status_line(void)
3030 int cnt = 0, cksum = 0;
3032 // either we already have an error or status message, or we
3034 if (!have_status_msg) {
3035 cnt = format_edit_status();
3036 cksum = bufsum(status_buffer, cnt);
3038 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3039 last_status_cksum = cksum; // remember if we have seen this line
3040 go_bottom_and_clear_to_eol();
3041 write1(status_buffer);
3042 if (have_status_msg) {
3043 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3045 have_status_msg = 0;
3048 have_status_msg = 0;
3050 place_cursor(crow, ccol); // put cursor back in correct place
3055 //----- format the status buffer, the bottom line of screen ------
3056 // format status buffer, with STANDOUT mode
3057 static void status_line_bold(const char *format, ...)
3061 va_start(args, format);
3062 strcpy(status_buffer, ESC_BOLD_TEXT);
3063 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3064 strcat(status_buffer, ESC_NORM_TEXT);
3067 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3070 static void status_line_bold_errno(const char *fn)
3072 status_line_bold("'%s' %s", fn, strerror(errno));
3075 // format status buffer
3076 static void status_line(const char *format, ...)
3080 va_start(args, format);
3081 vsprintf(status_buffer, format, args);
3084 have_status_msg = 1;
3087 // copy s to buf, convert unprintable
3088 static void print_literal(char *buf, const char *s)
3102 c_is_no_print = (c & 0x80) && !Isprint(c);
3103 if (c_is_no_print) {
3104 strcpy(d, ESC_NORM_TEXT);
3105 d += sizeof(ESC_NORM_TEXT)-1;
3108 if (c < ' ' || c == 0x7f) {
3110 c |= '@'; /* 0x40 */
3116 if (c_is_no_print) {
3117 strcpy(d, ESC_BOLD_TEXT);
3118 d += sizeof(ESC_BOLD_TEXT)-1;
3124 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3129 static void not_implemented(const char *s)
3131 char buf[MAX_INPUT_LEN];
3133 print_literal(buf, s);
3134 status_line_bold("\'%s\' is not implemented", buf);
3137 // show file status on status line
3138 static int format_edit_status(void)
3140 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3142 #define tot format_edit_status__tot
3144 int cur, percent, ret, trunc_at;
3146 // file_modified is now a counter rather than a flag. this
3147 // helps reduce the amount of line counting we need to do.
3148 // (this will cause a mis-reporting of modified status
3149 // once every MAXINT editing operations.)
3151 // it would be nice to do a similar optimization here -- if
3152 // we haven't done a motion that could have changed which line
3153 // we're on, then we shouldn't have to do this count_lines()
3154 cur = count_lines(text, dot);
3156 // reduce counting -- the total lines can't have
3157 // changed if we haven't done any edits.
3158 if (file_modified != last_file_modified) {
3159 tot = cur + count_lines(dot, end - 1) - 1;
3160 last_file_modified = file_modified;
3163 // current line percent
3164 // ------------- ~~ ----------
3167 percent = (100 * cur) / tot;
3173 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3174 columns : STATUS_BUFFER_LEN-1;
3176 ret = snprintf(status_buffer, trunc_at+1,
3177 #if ENABLE_FEATURE_VI_READONLY
3178 "%c %s%s%s %d/%d %d%%",
3180 "%c %s%s %d/%d %d%%",
3182 cmd_mode_indicator[cmd_mode & 3],
3183 (current_filename != NULL ? current_filename : "No file"),
3184 #if ENABLE_FEATURE_VI_READONLY
3185 (readonly_mode ? " [Readonly]" : ""),
3187 (file_modified ? " [Modified]" : ""),
3190 if (ret >= 0 && ret < trunc_at)
3191 return ret; /* it all fit */
3193 return trunc_at; /* had to truncate */
3197 //----- Force refresh of all Lines -----------------------------
3198 static void redraw(int full_screen)
3202 screen_erase(); // erase the internal screen buffer
3203 last_status_cksum = 0; // force status update
3204 refresh(full_screen); // this will redraw the entire display
3208 //----- Format a text[] line into a buffer ---------------------
3209 static char* format_line(char *src /*, int li*/)
3214 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3216 c = '~'; // char in col 0 in non-existent lines is '~'
3218 while (co < columns + tabstop) {
3219 // have we gone past the end?
3224 if ((c & 0x80) && !Isprint(c)) {
3227 if (c < ' ' || c == 0x7f) {
3231 while ((co % tabstop) != (tabstop - 1)) {
3239 c += '@'; // Ctrl-X -> 'X'
3244 // discard scrolled-off-to-the-left portion,
3245 // in tabstop-sized pieces
3246 if (ofs >= tabstop && co >= tabstop) {
3247 memmove(dest, dest + tabstop, co);
3254 // check "short line, gigantic offset" case
3257 // discard last scrolled off part
3260 // fill the rest with spaces
3262 memset(&dest[co], ' ', columns - co);
3266 //----- Refresh the changed screen lines -----------------------
3267 // Copy the source line from text[] into the buffer and note
3268 // if the current screenline is different from the new buffer.
3269 // If they differ then that line needs redrawing on the terminal.
3271 static void refresh(int full_screen)
3273 #define old_offset refresh__old_offset
3276 char *tp, *sp; // pointer into text[] and screen[]
3278 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3279 unsigned c = columns, r = rows;
3280 query_screen_dimensions();
3281 full_screen |= (c - columns) | (r - rows);
3283 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3284 tp = screenbegin; // index into text[] of top line
3286 // compare text[] to screen[] and mark screen[] lines that need updating
3287 for (li = 0; li < rows - 1; li++) {
3288 int cs, ce; // column start & end
3290 // format current text line
3291 out_buf = format_line(tp /*, li*/);
3293 // skip to the end of the current text[] line
3295 char *t = memchr(tp, '\n', end - tp);
3296 if (!t) t = end - 1;
3300 // see if there are any changes between vitual screen and out_buf
3301 changed = FALSE; // assume no change
3304 sp = &screen[li * columns]; // start of screen line
3306 // force re-draw of every single column from 0 - columns-1
3309 // compare newly formatted buffer with virtual screen
3310 // look forward for first difference between buf and screen
3311 for (; cs <= ce; cs++) {
3312 if (out_buf[cs] != sp[cs]) {
3313 changed = TRUE; // mark for redraw
3318 // look backward for last difference between out_buf and screen
3319 for (; ce >= cs; ce--) {
3320 if (out_buf[ce] != sp[ce]) {
3321 changed = TRUE; // mark for redraw
3325 // now, cs is index of first diff, and ce is index of last diff
3327 // if horz offset has changed, force a redraw
3328 if (offset != old_offset) {
3333 // make a sanity check of columns indexes
3335 if (ce > columns - 1) ce = columns - 1;
3336 if (cs > ce) { cs = 0; ce = columns - 1; }
3337 // is there a change between vitual screen and out_buf
3339 // copy changed part of buffer to virtual screen
3340 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3341 place_cursor(li, cs);
3342 // write line out to terminal
3343 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3347 place_cursor(crow, ccol);
3349 old_offset = offset;
3353 //---------------------------------------------------------------------
3354 //----- the Ascii Chart -----------------------------------------------
3356 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3357 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3358 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3359 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3360 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3361 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3362 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3363 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3364 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3365 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3366 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3367 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3368 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3369 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3370 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3371 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3372 //---------------------------------------------------------------------
3374 //----- Execute a Vi Command -----------------------------------
3375 static void do_cmd(int c)
3377 char *p, *q, *save_dot;
3383 // c1 = c; // quiet the compiler
3384 // cnt = yf = 0; // quiet the compiler
3385 // p = q = save_dot = buf; // quiet the compiler
3386 memset(buf, '\0', sizeof(buf));
3390 /* if this is a cursor key, skip these checks */
3398 case KEYCODE_PAGEUP:
3399 case KEYCODE_PAGEDOWN:
3400 case KEYCODE_DELETE:
3404 if (cmd_mode == 2) {
3405 // flip-flop Insert/Replace mode
3406 if (c == KEYCODE_INSERT)
3408 // we are 'R'eplacing the current *dot with new char
3410 // don't Replace past E-o-l
3411 cmd_mode = 1; // convert to insert
3412 undo_queue_commit();
3414 if (1 <= c || Isprint(c)) {
3416 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3417 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3422 if (cmd_mode == 1) {
3423 // hitting "Insert" twice means "R" replace mode
3424 if (c == KEYCODE_INSERT) goto dc5;
3425 // insert the char c at "dot"
3426 if (1 <= c || Isprint(c)) {
3427 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3442 #if ENABLE_FEATURE_VI_CRASHME
3443 case 0x14: // dc4 ctrl-T
3444 crashme = (crashme == 0) ? 1 : 0;
3474 default: // unrecognized command
3477 not_implemented(buf);
3478 end_cmd_q(); // stop adding to q
3479 case 0x00: // nul- ignore
3481 case 2: // ctrl-B scroll up full screen
3482 case KEYCODE_PAGEUP: // Cursor Key Page Up
3483 dot_scroll(rows - 2, -1);
3485 case 4: // ctrl-D scroll down half screen
3486 dot_scroll((rows - 2) / 2, 1);
3488 case 5: // ctrl-E scroll down one line
3491 case 6: // ctrl-F scroll down full screen
3492 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3493 dot_scroll(rows - 2, 1);
3495 case 7: // ctrl-G show current status
3496 last_status_cksum = 0; // force status update
3498 case 'h': // h- move left
3499 case KEYCODE_LEFT: // cursor key Left
3500 case 8: // ctrl-H- move left (This may be ERASE char)
3501 case 0x7f: // DEL- move left (This may be ERASE char)
3504 } while (--cmdcnt > 0);
3506 case 10: // Newline ^J
3507 case 'j': // j- goto next line, same col
3508 case KEYCODE_DOWN: // cursor key Down
3510 dot_next(); // go to next B-o-l
3511 // try stay in same col
3512 dot = move_to_col(dot, ccol + offset);
3513 } while (--cmdcnt > 0);
3515 case 12: // ctrl-L force redraw whole screen
3516 case 18: // ctrl-R force redraw
3519 //mysleep(10); // why???
3520 screen_erase(); // erase the internal screen buffer
3521 last_status_cksum = 0; // force status update
3522 refresh(TRUE); // this will redraw the entire display
3524 case 13: // Carriage Return ^M
3525 case '+': // +- goto next line
3529 } while (--cmdcnt > 0);
3531 case 21: // ctrl-U scroll up half screen
3532 dot_scroll((rows - 2) / 2, -1);
3534 case 25: // ctrl-Y scroll up one line
3540 cmd_mode = 0; // stop insrting
3541 undo_queue_commit();
3543 last_status_cksum = 0; // force status update
3545 case ' ': // move right
3546 case 'l': // move right
3547 case KEYCODE_RIGHT: // Cursor Key Right
3550 } while (--cmdcnt > 0);
3552 #if ENABLE_FEATURE_VI_YANKMARK
3553 case '"': // "- name a register to use for Delete/Yank
3554 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3555 if ((unsigned)c1 <= 25) { // a-z?
3561 case '\'': // '- goto a specific mark
3562 c1 = (get_one_char() | 0x20) - 'a';
3563 if ((unsigned)c1 <= 25) { // a-z?
3566 if (text <= q && q < end) {
3568 dot_begin(); // go to B-o-l
3571 } else if (c1 == '\'') { // goto previous context
3572 dot = swap_context(dot); // swap current and previous context
3573 dot_begin(); // go to B-o-l
3579 case 'm': // m- Mark a line
3580 // this is really stupid. If there are any inserts or deletes
3581 // between text[0] and dot then this mark will not point to the
3582 // correct location! It could be off by many lines!
3583 // Well..., at least its quick and dirty.
3584 c1 = (get_one_char() | 0x20) - 'a';
3585 if ((unsigned)c1 <= 25) { // a-z?
3586 // remember the line
3592 case 'P': // P- Put register before
3593 case 'p': // p- put register after
3596 status_line_bold("Nothing in register %c", what_reg());
3599 // are we putting whole lines or strings
3600 if (strchr(p, '\n') != NULL) {
3602 dot_begin(); // putting lines- Put above
3605 // are we putting after very last line?
3606 if (end_line(dot) == (end - 1)) {
3607 dot = end; // force dot to end of text[]
3609 dot_next(); // next line, then put before
3614 dot_right(); // move to right, can move to NL
3616 string_insert(dot, p, ALLOW_UNDO); // insert the string
3617 end_cmd_q(); // stop adding to q
3619 #if ENABLE_FEATURE_VI_UNDO
3620 case 'u': // u- undo last operation
3624 case 'U': // U- Undo; replace current line with original version
3625 if (reg[Ureg] != NULL) {
3626 p = begin_line(dot);
3628 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3629 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3634 #endif /* FEATURE_VI_YANKMARK */
3635 case '$': // $- goto end of line
3636 case KEYCODE_END: // Cursor Key End
3638 dot = end_line(dot);
3644 case '%': // %- find matching char of pair () [] {}
3645 for (q = dot; q < end && *q != '\n'; q++) {
3646 if (strchr("()[]{}", *q) != NULL) {
3647 // we found half of a pair
3648 p = find_pair(q, *q);
3660 case 'f': // f- forward to a user specified char
3661 last_forward_char = get_one_char(); // get the search char
3663 // dont separate these two commands. 'f' depends on ';'
3665 //**** fall through to ... ';'
3666 case ';': // ;- look at rest of line for last forward char
3668 if (last_forward_char == 0)
3671 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3674 if (*q == last_forward_char)
3676 } while (--cmdcnt > 0);
3678 case ',': // repeat latest 'f' in opposite direction
3679 if (last_forward_char == 0)
3683 while (q >= text && *q != '\n' && *q != last_forward_char) {
3686 if (q >= text && *q == last_forward_char)
3688 } while (--cmdcnt > 0);
3691 case '-': // -- goto prev line
3695 } while (--cmdcnt > 0);
3697 #if ENABLE_FEATURE_VI_DOT_CMD
3698 case '.': // .- repeat the last modifying command
3699 // Stuff the last_modifying_cmd back into stdin
3700 // and let it be re-executed.
3702 last_modifying_cmd[lmc_len] = 0;
3703 ioq = ioq_start = xstrdup(last_modifying_cmd);
3707 #if ENABLE_FEATURE_VI_SEARCH
3708 case '?': // /- search for a pattern
3709 case '/': // /- search for a pattern
3712 q = get_input_line(buf); // get input line- use "status line"
3713 if (q[0] && !q[1]) {
3714 if (last_search_pattern[0])
3715 last_search_pattern[0] = c;
3716 goto dc3; // if no pat re-use old pat
3718 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3719 // there is a new pat
3720 free(last_search_pattern);
3721 last_search_pattern = xstrdup(q);
3722 goto dc3; // now find the pattern
3724 // user changed mind and erased the "/"- do nothing
3726 case 'N': // N- backward search for last pattern
3727 dir = BACK; // assume BACKWARD search
3729 if (last_search_pattern[0] == '?') {
3733 goto dc4; // now search for pattern
3735 case 'n': // n- repeat search for last pattern
3736 // search rest of text[] starting at next char
3737 // if search fails return orignal "p" not the "p+1" address
3741 dir = FORWARD; // assume FORWARD search
3743 if (last_search_pattern[0] == '?') {
3748 q = char_search(p, last_search_pattern + 1, dir, FULL);
3750 dot = q; // good search, update "dot"
3754 // no pattern found between "dot" and "end"- continue at top
3759 q = char_search(p, last_search_pattern + 1, dir, FULL);
3760 if (q != NULL) { // found something
3761 dot = q; // found new pattern- goto it
3762 msg = "search hit BOTTOM, continuing at TOP";
3764 msg = "search hit TOP, continuing at BOTTOM";
3767 msg = "Pattern not found";
3771 status_line_bold("%s", msg);
3772 } while (--cmdcnt > 0);
3774 case '{': // {- move backward paragraph
3775 q = char_search(dot, "\n\n", BACK, FULL);
3776 if (q != NULL) { // found blank line
3777 dot = next_line(q); // move to next blank line
3780 case '}': // }- move forward paragraph
3781 q = char_search(dot, "\n\n", FORWARD, FULL);
3782 if (q != NULL) { // found blank line
3783 dot = next_line(q); // move to next blank line
3786 #endif /* FEATURE_VI_SEARCH */
3787 case '0': // 0- goto begining of line
3797 if (c == '0' && cmdcnt < 1) {
3798 dot_begin(); // this was a standalone zero
3800 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3803 case ':': // :- the colon mode commands
3804 p = get_input_line(":"); // get input line- use "status line"
3805 #if ENABLE_FEATURE_VI_COLON
3806 colon(p); // execute the command
3809 p++; // move past the ':'
3813 if (strncmp(p, "quit", cnt) == 0
3814 || strncmp(p, "q!", cnt) == 0 // delete lines
3816 if (file_modified && p[1] != '!') {
3817 status_line_bold("No write since last change (:%s! overrides)", p);
3821 } else if (strncmp(p, "write", cnt) == 0
3822 || strncmp(p, "wq", cnt) == 0
3823 || strncmp(p, "wn", cnt) == 0
3824 || (p[0] == 'x' && !p[1])
3826 cnt = file_write(current_filename, text, end - 1);
3829 status_line_bold("Write error: %s", strerror(errno));
3832 last_file_modified = -1;
3833 status_line("'%s' %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3834 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3835 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3840 } else if (strncmp(p, "file", cnt) == 0) {
3841 last_status_cksum = 0; // force status update
3842 } else if (sscanf(p, "%d", &j) > 0) {
3843 dot = find_line(j); // go to line # j
3845 } else { // unrecognized cmd
3848 #endif /* !FEATURE_VI_COLON */
3850 case '<': // <- Left shift something
3851 case '>': // >- Right shift something
3852 cnt = count_lines(text, dot); // remember what line we are on
3853 c1 = get_one_char(); // get the type of thing to delete
3854 find_range(&p, &q, c1);
3855 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3858 i = count_lines(p, q); // # of lines we are shifting
3859 for ( ; i > 0; i--, p = next_line(p)) {
3861 // shift left- remove tab or 8 spaces
3863 // shrink buffer 1 char
3864 text_hole_delete(p, p, NO_UNDO);
3865 } else if (*p == ' ') {
3866 // we should be calculating columns, not just SPACE
3867 for (j = 0; *p == ' ' && j < tabstop; j++) {
3868 text_hole_delete(p, p, NO_UNDO);
3871 } else if (c == '>') {
3872 // shift right -- add tab or 8 spaces
3873 char_insert(p, '\t', ALLOW_UNDO);
3876 dot = find_line(cnt); // what line were we on
3878 end_cmd_q(); // stop adding to q
3880 case 'A': // A- append at e-o-l
3881 dot_end(); // go to e-o-l
3882 //**** fall through to ... 'a'
3883 case 'a': // a- append after current char
3888 case 'B': // B- back a blank-delimited Word
3889 case 'E': // E- end of a blank-delimited word
3890 case 'W': // W- forward a blank-delimited word
3895 if (c == 'W' || isspace(dot[dir])) {
3896 dot = skip_thing(dot, 1, dir, S_TO_WS);
3897 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3900 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3901 } while (--cmdcnt > 0);
3903 case 'C': // C- Change to e-o-l
3904 case 'D': // D- delete to e-o-l
3906 dot = dollar_line(dot); // move to before NL
3907 // copy text into a register and delete
3908 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3910 goto dc_i; // start inserting
3911 #if ENABLE_FEATURE_VI_DOT_CMD
3913 end_cmd_q(); // stop adding to q
3916 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3917 c1 = get_one_char();
3920 buf[1] = c1; // TODO: if Unicode?
3922 not_implemented(buf);
3928 case 'G': // G- goto to a line number (default= E-O-F)
3929 dot = end - 1; // assume E-O-F
3931 dot = find_line(cmdcnt); // what line is #cmdcnt
3935 case 'H': // H- goto top line on screen
3937 if (cmdcnt > (rows - 1)) {
3938 cmdcnt = (rows - 1);
3945 case 'I': // I- insert before first non-blank
3948 //**** fall through to ... 'i'
3949 case 'i': // i- insert before current char
3950 case KEYCODE_INSERT: // Cursor Key Insert
3952 cmd_mode = 1; // start inserting
3953 undo_queue_commit(); // commit queue when cmd_mode changes
3955 case 'J': // J- join current and next lines together
3957 dot_end(); // move to NL
3958 if (dot < end - 1) { // make sure not last char in text[]
3959 #if ENABLE_FEATURE_VI_UNDO
3960 undo_push(dot, 1, UNDO_DEL);
3961 *dot++ = ' '; // replace NL with space
3962 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3967 while (isblank(*dot)) { // delete leading WS
3968 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3971 } while (--cmdcnt > 0);
3972 end_cmd_q(); // stop adding to q
3974 case 'L': // L- goto bottom line on screen
3976 if (cmdcnt > (rows - 1)) {
3977 cmdcnt = (rows - 1);
3985 case 'M': // M- goto middle line on screen
3987 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3988 dot = next_line(dot);
3990 case 'O': // O- open a empty line above
3992 p = begin_line(dot);
3993 if (p[-1] == '\n') {
3995 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3997 dot = char_insert(dot, '\n', ALLOW_UNDO);
4000 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
4005 case 'R': // R- continuous Replace char
4008 undo_queue_commit();
4010 case KEYCODE_DELETE:
4013 case 'X': // X- delete char before dot
4014 case 'x': // x- delete the current char
4015 case 's': // s- substitute the current char
4020 if (dot[dir] != '\n') {
4022 dot--; // delete prev char
4023 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
4025 } while (--cmdcnt > 0);
4026 end_cmd_q(); // stop adding to q
4028 goto dc_i; // start inserting
4030 case 'Z': // Z- if modified, {write}; exit
4031 // ZZ means to save file (if necessary), then exit
4032 c1 = get_one_char();
4037 if (file_modified) {
4038 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
4039 status_line_bold("'%s' is read only", current_filename);
4042 cnt = file_write(current_filename, text, end - 1);
4045 status_line_bold("Write error: %s", strerror(errno));
4046 } else if (cnt == (end - 1 - text + 1)) {
4053 case '^': // ^- move to first non-blank on line
4057 case 'b': // b- back a word
4058 case 'e': // e- end of word
4063 if ((dot + dir) < text || (dot + dir) > end - 1)
4066 if (isspace(*dot)) {
4067 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4069 if (isalnum(*dot) || *dot == '_') {
4070 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4071 } else if (ispunct(*dot)) {
4072 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4074 } while (--cmdcnt > 0);
4076 case 'c': // c- change something
4077 case 'd': // d- delete something
4078 #if ENABLE_FEATURE_VI_YANKMARK
4079 case 'y': // y- yank something
4080 case 'Y': // Y- Yank a line
4083 int yf, ml, whole = 0;
4084 yf = YANKDEL; // assume either "c" or "d"
4085 #if ENABLE_FEATURE_VI_YANKMARK
4086 if (c == 'y' || c == 'Y')
4091 c1 = get_one_char(); // get the type of thing to delete
4092 // determine range, and whether it spans lines
4093 ml = find_range(&p, &q, c1);
4095 if (c1 == 27) { // ESC- user changed mind and wants out
4096 c = c1 = 27; // Escape- do nothing
4097 } else if (strchr("wW", c1)) {
4099 // don't include trailing WS as part of word
4100 while (isblank(*q)) {
4101 if (q <= text || q[-1] == '\n')
4106 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4107 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
4108 // partial line copy text into a register and delete
4109 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4110 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
4111 // whole line copy text into a register and delete
4112 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
4115 // could not recognize object
4116 c = c1 = 27; // error-
4122 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4123 // on the last line of file don't move to prev line
4124 if (whole && dot != (end-1)) {
4127 } else if (c == 'd') {
4133 // if CHANGING, not deleting, start inserting after the delete
4135 strcpy(buf, "Change");
4136 goto dc_i; // start inserting
4139 strcpy(buf, "Delete");
4141 #if ENABLE_FEATURE_VI_YANKMARK
4142 if (c == 'y' || c == 'Y') {
4143 strcpy(buf, "Yank");
4147 for (cnt = 0; p <= q; p++) {
4151 status_line("%s %d lines (%d chars) using [%c]",
4152 buf, cnt, strlen(reg[YDreg]), what_reg());
4154 end_cmd_q(); // stop adding to q
4158 case 'k': // k- goto prev line, same col
4159 case KEYCODE_UP: // cursor key Up
4162 dot = move_to_col(dot, ccol + offset); // try stay in same col
4163 } while (--cmdcnt > 0);
4165 case 'r': // r- replace the current char with user input
4166 c1 = get_one_char(); // get the replacement char
4168 #if ENABLE_FEATURE_VI_UNDO
4169 undo_push(dot, 1, UNDO_DEL);
4171 undo_push(dot, 1, UNDO_INS_CHAIN);
4177 end_cmd_q(); // stop adding to q
4179 case 't': // t- move to char prior to next x
4180 last_forward_char = get_one_char();
4182 if (*dot == last_forward_char)
4184 last_forward_char = 0;
4186 case 'w': // w- forward a word
4188 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4189 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4190 } else if (ispunct(*dot)) { // we are on PUNCT
4191 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4194 dot++; // move over word
4195 if (isspace(*dot)) {
4196 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4198 } while (--cmdcnt > 0);
4201 c1 = get_one_char(); // get the replacement char
4204 cnt = (rows - 2) / 2; // put dot at center
4206 cnt = rows - 2; // put dot at bottom
4207 screenbegin = begin_line(dot); // start dot at top
4208 dot_scroll(cnt, -1);
4210 case '|': // |- move to column "cmdcnt"
4211 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4213 case '~': // ~- flip the case of letters a-z -> A-Z
4215 #if ENABLE_FEATURE_VI_UNDO
4216 if (islower(*dot)) {
4217 undo_push(dot, 1, UNDO_DEL);
4218 *dot = toupper(*dot);
4219 undo_push(dot, 1, UNDO_INS_CHAIN);
4220 } else if (isupper(*dot)) {
4221 undo_push(dot, 1, UNDO_DEL);
4222 *dot = tolower(*dot);
4223 undo_push(dot, 1, UNDO_INS_CHAIN);
4226 if (islower(*dot)) {
4227 *dot = toupper(*dot);
4229 } else if (isupper(*dot)) {
4230 *dot = tolower(*dot);
4235 } while (--cmdcnt > 0);
4236 end_cmd_q(); // stop adding to q
4238 //----- The Cursor and Function Keys -----------------------------
4239 case KEYCODE_HOME: // Cursor Key Home
4242 // The Fn keys could point to do_macro which could translate them
4244 case KEYCODE_FUN1: // Function Key F1
4245 case KEYCODE_FUN2: // Function Key F2
4246 case KEYCODE_FUN3: // Function Key F3
4247 case KEYCODE_FUN4: // Function Key F4
4248 case KEYCODE_FUN5: // Function Key F5
4249 case KEYCODE_FUN6: // Function Key F6
4250 case KEYCODE_FUN7: // Function Key F7
4251 case KEYCODE_FUN8: // Function Key F8
4252 case KEYCODE_FUN9: // Function Key F9
4253 case KEYCODE_FUN10: // Function Key F10
4254 case KEYCODE_FUN11: // Function Key F11
4255 case KEYCODE_FUN12: // Function Key F12
4261 // if text[] just became empty, add back an empty line
4263 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4266 // it is OK for dot to exactly equal to end, otherwise check dot validity
4268 dot = bound_dot(dot); // make sure "dot" is valid
4270 #if ENABLE_FEATURE_VI_YANKMARK
4271 check_context(c); // update the current context
4275 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4276 cnt = dot - begin_line(dot);
4277 // Try to stay off of the Newline
4278 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4282 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4283 #if ENABLE_FEATURE_VI_CRASHME
4284 static int totalcmds = 0;
4285 static int Mp = 85; // Movement command Probability
4286 static int Np = 90; // Non-movement command Probability
4287 static int Dp = 96; // Delete command Probability
4288 static int Ip = 97; // Insert command Probability
4289 static int Yp = 98; // Yank command Probability
4290 static int Pp = 99; // Put command Probability
4291 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4292 static const char chars[20] = "\t012345 abcdABCD-=.$";
4293 static const char *const words[20] = {
4294 "this", "is", "a", "test",
4295 "broadcast", "the", "emergency", "of",
4296 "system", "quick", "brown", "fox",
4297 "jumped", "over", "lazy", "dogs",
4298 "back", "January", "Febuary", "March"
4300 static const char *const lines[20] = {
4301 "You should have received a copy of the GNU General Public License\n",
4302 "char c, cm, *cmd, *cmd1;\n",
4303 "generate a command by percentages\n",
4304 "Numbers may be typed as a prefix to some commands.\n",
4305 "Quit, discarding changes!\n",
4306 "Forced write, if permission originally not valid.\n",
4307 "In general, any ex or ed command (such as substitute or delete).\n",
4308 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4309 "Please get w/ me and I will go over it with you.\n",
4310 "The following is a list of scheduled, committed changes.\n",
4311 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4312 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4313 "Any question about transactions please contact Sterling Huxley.\n",
4314 "I will try to get back to you by Friday, December 31.\n",
4315 "This Change will be implemented on Friday.\n",
4316 "Let me know if you have problems accessing this;\n",
4317 "Sterling Huxley recently added you to the access list.\n",
4318 "Would you like to go to lunch?\n",
4319 "The last command will be automatically run.\n",
4320 "This is too much english for a computer geek.\n",
4322 static char *multilines[20] = {
4323 "You should have received a copy of the GNU General Public License\n",
4324 "char c, cm, *cmd, *cmd1;\n",
4325 "generate a command by percentages\n",
4326 "Numbers may be typed as a prefix to some commands.\n",
4327 "Quit, discarding changes!\n",
4328 "Forced write, if permission originally not valid.\n",
4329 "In general, any ex or ed command (such as substitute or delete).\n",
4330 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4331 "Please get w/ me and I will go over it with you.\n",
4332 "The following is a list of scheduled, committed changes.\n",
4333 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4334 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4335 "Any question about transactions please contact Sterling Huxley.\n",
4336 "I will try to get back to you by Friday, December 31.\n",
4337 "This Change will be implemented on Friday.\n",
4338 "Let me know if you have problems accessing this;\n",
4339 "Sterling Huxley recently added you to the access list.\n",
4340 "Would you like to go to lunch?\n",
4341 "The last command will be automatically run.\n",
4342 "This is too much english for a computer geek.\n",
4345 // create a random command to execute
4346 static void crash_dummy()
4348 static int sleeptime; // how long to pause between commands
4349 char c, cm, *cmd, *cmd1;
4350 int i, cnt, thing, rbi, startrbi, percent;
4352 // "dot" movement commands
4353 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4355 // is there already a command running?
4356 if (readbuffer[0] > 0)
4359 readbuffer[0] = 'X';
4361 sleeptime = 0; // how long to pause between commands
4362 memset(readbuffer, '\0', sizeof(readbuffer));
4363 // generate a command by percentages
4364 percent = (int) lrand48() % 100; // get a number from 0-99
4365 if (percent < Mp) { // Movement commands
4366 // available commands
4369 } else if (percent < Np) { // non-movement commands
4370 cmd = "mz<>\'\""; // available commands
4372 } else if (percent < Dp) { // Delete commands
4373 cmd = "dx"; // available commands
4375 } else if (percent < Ip) { // Inset commands
4376 cmd = "iIaAsrJ"; // available commands
4378 } else if (percent < Yp) { // Yank commands
4379 cmd = "yY"; // available commands
4381 } else if (percent < Pp) { // Put commands
4382 cmd = "pP"; // available commands
4385 // We do not know how to handle this command, try again
4389 // randomly pick one of the available cmds from "cmd[]"
4390 i = (int) lrand48() % strlen(cmd);
4392 if (strchr(":\024", cm))
4393 goto cd0; // dont allow colon or ctrl-T commands
4394 readbuffer[rbi++] = cm; // put cmd into input buffer
4396 // now we have the command-
4397 // there are 1, 2, and multi char commands
4398 // find out which and generate the rest of command as necessary
4399 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4400 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4401 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4402 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4404 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4406 readbuffer[rbi++] = c; // add movement to input buffer
4408 if (strchr("iIaAsc", cm)) { // multi-char commands
4410 // change some thing
4411 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4413 readbuffer[rbi++] = c; // add movement to input buffer
4415 thing = (int) lrand48() % 4; // what thing to insert
4416 cnt = (int) lrand48() % 10; // how many to insert
4417 for (i = 0; i < cnt; i++) {
4418 if (thing == 0) { // insert chars
4419 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4420 } else if (thing == 1) { // insert words
4421 strcat(readbuffer, words[(int) lrand48() % 20]);
4422 strcat(readbuffer, " ");
4423 sleeptime = 0; // how fast to type
4424 } else if (thing == 2) { // insert lines
4425 strcat(readbuffer, lines[(int) lrand48() % 20]);
4426 sleeptime = 0; // how fast to type
4427 } else { // insert multi-lines
4428 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4429 sleeptime = 0; // how fast to type
4432 strcat(readbuffer, "\033");
4434 readbuffer[0] = strlen(readbuffer + 1);
4438 mysleep(sleeptime); // sleep 1/100 sec
4441 // test to see if there are any errors
4442 static void crash_test()
4444 static time_t oldtim;
4451 strcat(msg, "end<text ");
4453 if (end > textend) {
4454 strcat(msg, "end>textend ");
4457 strcat(msg, "dot<text ");
4460 strcat(msg, "dot>end ");
4462 if (screenbegin < text) {
4463 strcat(msg, "screenbegin<text ");
4465 if (screenbegin > end - 1) {
4466 strcat(msg, "screenbegin>end-1 ");
4470 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4471 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4473 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4474 if (d[0] == '\n' || d[0] == '\r')
4479 if (tim >= (oldtim + 3)) {
4480 sprintf(status_buffer,
4481 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4482 totalcmds, M, N, I, D, Y, P, U, end - text + 1);