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_entry;
2215 // UNDO_INS: insertion, undo will remove from buffer
2216 // UNDO_DEL: deleted text, undo will restore to buffer
2217 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2218 // The CHAIN operations are for handling multiple operations that the user
2219 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2220 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2221 // for the INS/DEL operation. The raw values should be equal to the values of
2222 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2224 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2225 // This undo queuing functionality groups multiple character typing or backspaces
2226 // into a single large undo object. This greatly reduces calls to malloc() for
2227 // single-character operations while typing and has the side benefit of letting
2228 // an undo operation remove chunks of text rather than a single character.
2230 case UNDO_EMPTY: // Just in case this ever happens...
2232 case UNDO_DEL_QUEUED:
2234 return; // Only queue single characters
2235 switch (undo_queue_state) {
2237 undo_queue_state = UNDO_DEL;
2239 undo_queue_spos = src;
2241 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2242 // If queue is full, dump it into an object
2243 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2244 undo_queue_commit();
2247 // Switch from storing inserted text to deleted text
2248 undo_queue_commit();
2249 undo_push(src, length, UNDO_DEL_QUEUED);
2253 case UNDO_INS_QUEUED:
2256 switch (undo_queue_state) {
2258 undo_queue_state = UNDO_INS;
2259 undo_queue_spos = src;
2261 undo_q++; // Don't need to save any data for insertions
2262 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2263 undo_queue_commit();
2266 // Switch from storing deleted text to inserted text
2267 undo_queue_commit();
2268 undo_push(src, length, UNDO_INS_QUEUED);
2274 // If undo queuing is disabled, ignore the queuing flag entirely
2275 u_type = u_type & ~UNDO_QUEUED_FLAG;
2278 // Allocate a new undo object and use it as the stack tail
2279 undo_entry = xzalloc(sizeof(*undo_entry));
2280 undo_entry->prev = undo_stack_tail;
2281 undo_stack_tail = undo_entry;
2282 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2283 if ((u_type & UNDO_USE_SPOS) != 0) {
2284 undo_entry->start = undo_queue_spos - text; // use start position from queue
2286 undo_entry->start = src - text; // use offset from start of text buffer
2288 u_type = (u_type & ~UNDO_USE_SPOS);
2290 undo_entry->start = src - text;
2292 // For UNDO_DEL objects, copy the deleted text somewhere
2293 undo_entry->u_type = u_type;
2294 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2295 if ((src + length) == end)
2297 // If this deletion empties text[], strip the newline. When the buffer becomes
2298 // zero-length, a newline is added back, which requires this to compensate.
2299 undo_entry->undo_text = xmalloc(length);
2300 memcpy(undo_entry->undo_text, src, length);
2302 undo_entry->length = length;
2306 static void undo_pop(void) // Undo the last operation
2309 char *u_start, *u_end;
2310 struct undo_object *undo_entry;
2312 // Commit pending undo queue before popping (should be unnecessary)
2313 undo_queue_commit();
2315 undo_entry = undo_stack_tail;
2316 // Check for an empty undo stack
2318 status_line("Already at oldest change");
2322 switch (undo_entry->u_type) {
2324 case UNDO_DEL_CHAIN:
2325 // make hole and put in text that was deleted; deallocate text
2326 u_start = text + undo_entry->start;
2327 text_hole_make(u_start, undo_entry->length);
2328 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2329 free(undo_entry->undo_text);
2330 status_line("Undo [%d] %s %d chars at position %d",
2331 file_modified, "restored",
2332 undo_entry->length, undo_entry->start
2336 case UNDO_INS_CHAIN:
2337 // delete what was inserted
2338 u_start = undo_entry->start + text;
2339 u_end = u_start - 1 + undo_entry->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_entry->length, undo_entry->start
2348 switch (undo_entry->u_type) {
2349 // If this is the end of a chain, lower modification count and refresh display
2352 dot = (text + undo_entry->start);
2355 case UNDO_DEL_CHAIN:
2356 case UNDO_INS_CHAIN:
2360 // Deallocate the undo object we just processed
2361 undo_stack_tail = undo_entry->prev;
2364 // For chained operations, continue popping all the way down the chain.
2366 undo_pop(); // Follow the undo chain if one exists
2370 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2371 static void undo_queue_commit(void) // Flush any queued objects to the undo stack
2373 // Pushes the queue object onto the undo stack
2375 // Deleted character undo events grow from the end
2376 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2378 (undo_queue_state | UNDO_USE_SPOS)
2380 undo_queue_state = UNDO_EMPTY;
2386 #endif /* ENABLE_FEATURE_VI_UNDO */
2388 // open a hole in text[]
2389 // might reallocate text[]! use p += text_hole_make(p, ...),
2390 // and be careful to not use pointers into potentially freed text[]!
2391 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2397 end += size; // adjust the new END
2398 if (end >= (text + text_size)) {
2400 text_size += end - (text + text_size) + 10240;
2401 new_text = xrealloc(text, text_size);
2402 bias = (new_text - text);
2403 screenbegin += bias;
2407 #if ENABLE_FEATURE_VI_YANKMARK
2410 for (i = 0; i < ARRAY_SIZE(mark); i++)
2417 memmove(p + size, p, end - size - p);
2418 memset(p, ' ', size); // clear new hole
2422 // close a hole in text[]
2423 // "undo" value indicates if this operation should be undo-able
2424 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2429 // move forwards, from beginning
2433 if (q < p) { // they are backward- swap them
2437 hole_size = q - p + 1;
2439 #if ENABLE_FEATURE_VI_UNDO
2444 undo_push(p, hole_size, UNDO_DEL);
2446 case ALLOW_UNDO_CHAIN:
2447 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2449 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2450 case ALLOW_UNDO_QUEUED:
2451 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2457 if (src < text || src > end)
2459 if (dest < text || dest >= end)
2463 goto thd_atend; // just delete the end of the buffer
2464 memmove(dest, src, cnt);
2466 end = end - hole_size; // adjust the new END
2468 dest = end - 1; // make sure dest in below end-1
2470 dest = end = text; // keep pointers valid
2475 // copy text into register, then delete text.
2476 // if dist <= 0, do not include, or go past, a NewLine
2478 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2482 // make sure start <= stop
2484 // they are backwards, reverse them
2490 // we cannot cross NL boundaries
2494 // dont go past a NewLine
2495 for (; p + 1 <= stop; p++) {
2497 stop = p; // "stop" just before NewLine
2503 #if ENABLE_FEATURE_VI_YANKMARK
2504 text_yank(start, stop, YDreg);
2506 if (yf == YANKDEL) {
2507 p = text_hole_delete(start, stop, undo);
2512 static void show_help(void)
2514 puts("These features are available:"
2515 #if ENABLE_FEATURE_VI_SEARCH
2516 "\n\tPattern searches with / and ?"
2518 #if ENABLE_FEATURE_VI_DOT_CMD
2519 "\n\tLast command repeat with ."
2521 #if ENABLE_FEATURE_VI_YANKMARK
2522 "\n\tLine marking with 'x"
2523 "\n\tNamed buffers with \"x"
2525 #if ENABLE_FEATURE_VI_READONLY
2526 //not implemented: "\n\tReadonly if vi is called as \"view\""
2527 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2529 #if ENABLE_FEATURE_VI_SET
2530 "\n\tSome colon mode commands with :"
2532 #if ENABLE_FEATURE_VI_SETOPTS
2533 "\n\tSettable options with \":set\""
2535 #if ENABLE_FEATURE_VI_USE_SIGNALS
2536 "\n\tSignal catching- ^C"
2537 "\n\tJob suspend and resume with ^Z"
2539 #if ENABLE_FEATURE_VI_WIN_RESIZE
2540 "\n\tAdapt to window re-sizes"
2545 #if ENABLE_FEATURE_VI_DOT_CMD
2546 static void start_new_cmd_q(char c)
2548 // get buffer for new cmd
2549 // if there is a current cmd count put it in the buffer first
2551 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2552 } else { // just save char c onto queue
2553 last_modifying_cmd[0] = c;
2559 static void end_cmd_q(void)
2561 #if ENABLE_FEATURE_VI_YANKMARK
2562 YDreg = 26; // go back to default Yank/Delete reg
2566 #endif /* FEATURE_VI_DOT_CMD */
2568 #if ENABLE_FEATURE_VI_YANKMARK \
2569 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2570 || ENABLE_FEATURE_VI_CRASHME
2571 // might reallocate text[]! use p += string_insert(p, ...),
2572 // and be careful to not use pointers into potentially freed text[]!
2573 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2579 #if ENABLE_FEATURE_VI_UNDO
2582 undo_push(p, i, UNDO_INS);
2584 case ALLOW_UNDO_CHAIN:
2585 undo_push(p, i, UNDO_INS_CHAIN);
2589 bias = text_hole_make(p, i);
2592 #if ENABLE_FEATURE_VI_YANKMARK
2595 for (cnt = 0; *s != '\0'; s++) {
2599 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2606 #if ENABLE_FEATURE_VI_YANKMARK
2607 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2610 if (cnt < 0) { // they are backwards- reverse them
2614 free(reg[dest]); // if already a yank register, free it
2615 reg[dest] = xstrndup(p, cnt + 1);
2619 static char what_reg(void)
2623 c = 'D'; // default to D-reg
2624 if (0 <= YDreg && YDreg <= 25)
2625 c = 'a' + (char) YDreg;
2633 static void check_context(char cmd)
2635 // A context is defined to be "modifying text"
2636 // Any modifying command establishes a new context.
2638 if (dot < context_start || dot > context_end) {
2639 if (strchr(modifying_cmds, cmd) != NULL) {
2640 // we are trying to modify text[]- make this the current context
2641 mark[27] = mark[26]; // move cur to prev
2642 mark[26] = dot; // move local to cur
2643 context_start = prev_line(prev_line(dot));
2644 context_end = next_line(next_line(dot));
2645 //loiter= start_loiter= now;
2650 static char *swap_context(char *p) // goto new context for '' command make this the current context
2654 // the current context is in mark[26]
2655 // the previous context is in mark[27]
2656 // only swap context if other context is valid
2657 if (text <= mark[27] && mark[27] <= end - 1) {
2659 mark[27] = mark[26];
2661 p = mark[26]; // where we are going- previous context
2662 context_start = prev_line(prev_line(prev_line(p)));
2663 context_end = next_line(next_line(next_line(p)));
2667 #endif /* FEATURE_VI_YANKMARK */
2669 //----- Set terminal attributes --------------------------------
2670 static void rawmode(void)
2672 tcgetattr(0, &term_orig);
2673 term_vi = term_orig;
2674 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG on - allow intr's
2675 term_vi.c_iflag &= (~IXON & ~ICRNL);
2676 term_vi.c_oflag &= (~ONLCR);
2677 term_vi.c_cc[VMIN] = 1;
2678 term_vi.c_cc[VTIME] = 0;
2679 erase_char = term_vi.c_cc[VERASE];
2680 tcsetattr_stdin_TCSANOW(&term_vi);
2683 static void cookmode(void)
2686 tcsetattr_stdin_TCSANOW(&term_orig);
2689 #if ENABLE_FEATURE_VI_USE_SIGNALS
2690 //----- Come here when we get a window resize signal ---------
2691 static void winch_sig(int sig UNUSED_PARAM)
2693 int save_errno = errno;
2694 // FIXME: do it in main loop!!!
2695 signal(SIGWINCH, winch_sig);
2696 query_screen_dimensions();
2697 new_screen(rows, columns); // get memory for virtual screen
2698 redraw(TRUE); // re-draw the screen
2702 //----- Come here when we get a continue signal -------------------
2703 static void cont_sig(int sig UNUSED_PARAM)
2705 int save_errno = errno;
2706 rawmode(); // terminal to "raw"
2707 last_status_cksum = 0; // force status update
2708 redraw(TRUE); // re-draw the screen
2710 signal(SIGTSTP, suspend_sig);
2711 signal(SIGCONT, SIG_DFL);
2712 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2716 //----- Come here when we get a Suspend signal -------------------
2717 static void suspend_sig(int sig UNUSED_PARAM)
2719 int save_errno = errno;
2720 go_bottom_and_clear_to_eol();
2721 cookmode(); // terminal to "cooked"
2723 signal(SIGCONT, cont_sig);
2724 signal(SIGTSTP, SIG_DFL);
2725 kill(my_pid, SIGTSTP);
2729 //----- Come here when we get a signal ---------------------------
2730 static void catch_sig(int sig)
2732 signal(SIGINT, catch_sig);
2733 siglongjmp(restart, sig);
2735 #endif /* FEATURE_VI_USE_SIGNALS */
2737 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2739 struct pollfd pfd[1];
2741 pfd[0].fd = STDIN_FILENO;
2742 pfd[0].events = POLLIN;
2743 return safe_poll(pfd, 1, hund*10) > 0;
2746 //----- IO Routines --------------------------------------------
2747 static int readit(void) // read (maybe cursor) key from stdin
2752 c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2753 if (c == -1) { // EOF/error
2754 go_bottom_and_clear_to_eol();
2755 cookmode(); // terminal to "cooked"
2756 bb_error_msg_and_die("can't read user input");
2761 //----- IO Routines --------------------------------------------
2762 static int get_one_char(void)
2766 #if ENABLE_FEATURE_VI_DOT_CMD
2768 // we are not adding to the q.
2769 // but, we may be reading from a q
2771 // there is no current q, read from STDIN
2772 c = readit(); // get the users input
2774 // there is a queue to get chars from first
2775 // careful with correct sign expansion!
2776 c = (unsigned char)*ioq++;
2778 // the end of the q, read from STDIN
2780 ioq_start = ioq = 0;
2781 c = readit(); // get the users input
2785 // adding STDIN chars to q
2786 c = readit(); // get the users input
2787 if (lmc_len >= MAX_INPUT_LEN - 1) {
2788 status_line_bold("last_modifying_cmd overrun");
2790 // add new char to q
2791 last_modifying_cmd[lmc_len++] = c;
2795 c = readit(); // get the users input
2796 #endif /* FEATURE_VI_DOT_CMD */
2800 // Get input line (uses "status line" area)
2801 static char *get_input_line(const char *prompt)
2803 // char [MAX_INPUT_LEN]
2804 #define buf get_input_line__buf
2809 strcpy(buf, prompt);
2810 last_status_cksum = 0; // force status update
2811 go_bottom_and_clear_to_eol();
2812 write1(prompt); // write out the :, /, or ? prompt
2815 while (i < MAX_INPUT_LEN) {
2817 if (c == '\n' || c == '\r' || c == 27)
2818 break; // this is end of input
2819 if (c == erase_char || c == 8 || c == 127) {
2820 // user wants to erase prev char
2822 write1("\b \b"); // erase char on screen
2823 if (i <= 0) // user backs up before b-o-l, exit
2825 } else if (c > 0 && c < 256) { // exclude Unicode
2826 // (TODO: need to handle Unicode)
2837 static int file_size(const char *fn) // what is the byte size of "fn"
2843 if (fn && stat(fn, &st_buf) == 0) // see if file exists
2844 cnt = (int) st_buf.st_size;
2848 // might reallocate text[]!
2849 static int file_insert(const char *fn, char *p, int update_ro_status)
2853 struct stat statbuf;
2856 if (stat(fn, &statbuf) < 0) {
2857 status_line_bold_errno(fn);
2860 if (!S_ISREG(statbuf.st_mode)) {
2861 // This is not a regular file
2862 status_line_bold("'%s' is not a regular file", fn);
2865 if (p < text || p > end) {
2866 status_line_bold("Trying to insert file outside of memory");
2870 // read file to buffer
2871 fd = open(fn, O_RDONLY);
2873 status_line_bold_errno(fn);
2876 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2877 p += text_hole_make(p, size);
2878 cnt = safe_read(fd, p, size);
2880 status_line_bold_errno(fn);
2881 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2882 } else if (cnt < size) {
2883 // There was a partial read, shrink unused space text[]
2884 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO); // un-do buffer insert
2885 status_line_bold("can't read '%s'", fn);
2891 #if ENABLE_FEATURE_VI_READONLY
2892 if (update_ro_status
2893 && ((access(fn, W_OK) < 0) ||
2894 /* root will always have access()
2895 * so we check fileperms too */
2896 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2899 SET_READONLY_FILE(readonly_mode);
2905 static int file_write(char *fn, char *first, char *last)
2907 int fd, cnt, charcnt;
2910 status_line_bold("No current filename");
2913 /* By popular request we do not open file with O_TRUNC,
2914 * but instead ftruncate() it _after_ successful write.
2915 * Might reduce amount of data lost on power fail etc.
2917 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2920 cnt = last - first + 1;
2921 charcnt = full_write(fd, first, cnt);
2922 ftruncate(fd, charcnt);
2923 if (charcnt == cnt) {
2925 //file_modified = FALSE;
2933 //----- Terminal Drawing ---------------------------------------
2934 // The terminal is made up of 'rows' line of 'columns' columns.
2935 // classically this would be 24 x 80.
2936 // screen coordinates
2942 // 23,0 ... 23,79 <- status line
2944 //----- Move the cursor to row x col (count from 0, not 1) -------
2945 static void place_cursor(int row, int col)
2947 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
2949 if (row < 0) row = 0;
2950 if (row >= rows) row = rows - 1;
2951 if (col < 0) col = 0;
2952 if (col >= columns) col = columns - 1;
2954 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
2958 //----- Erase from cursor to end of line -----------------------
2959 static void clear_to_eol(void)
2961 write1(ESC_CLEAR2EOL);
2964 static void go_bottom_and_clear_to_eol(void)
2966 place_cursor(rows - 1, 0);
2970 //----- Erase from cursor to end of screen -----------------------
2971 static void clear_to_eos(void)
2973 write1(ESC_CLEAR2EOS);
2976 //----- Start standout mode ------------------------------------
2977 static void standout_start(void)
2979 write1(ESC_BOLD_TEXT);
2982 //----- End standout mode --------------------------------------
2983 static void standout_end(void)
2985 write1(ESC_NORM_TEXT);
2988 //----- Flash the screen --------------------------------------
2989 static void flash(int h)
2998 static void Indicate_Error(void)
3000 #if ENABLE_FEATURE_VI_CRASHME
3002 return; // generate a random command
3011 //----- Screen[] Routines --------------------------------------
3012 //----- Erase the Screen[] memory ------------------------------
3013 static void screen_erase(void)
3015 memset(screen, ' ', screensize); // clear new screen
3018 static int bufsum(char *buf, int count)
3021 char *e = buf + count;
3024 sum += (unsigned char) *buf++;
3028 //----- Draw the status line at bottom of the screen -------------
3029 static void show_status_line(void)
3031 int cnt = 0, cksum = 0;
3033 // either we already have an error or status message, or we
3035 if (!have_status_msg) {
3036 cnt = format_edit_status();
3037 cksum = bufsum(status_buffer, cnt);
3039 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3040 last_status_cksum = cksum; // remember if we have seen this line
3041 go_bottom_and_clear_to_eol();
3042 write1(status_buffer);
3043 if (have_status_msg) {
3044 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3046 have_status_msg = 0;
3049 have_status_msg = 0;
3051 place_cursor(crow, ccol); // put cursor back in correct place
3056 //----- format the status buffer, the bottom line of screen ------
3057 // format status buffer, with STANDOUT mode
3058 static void status_line_bold(const char *format, ...)
3062 va_start(args, format);
3063 strcpy(status_buffer, ESC_BOLD_TEXT);
3064 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3065 strcat(status_buffer, ESC_NORM_TEXT);
3068 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3071 static void status_line_bold_errno(const char *fn)
3073 status_line_bold("'%s' %s", fn, strerror(errno));
3076 // format status buffer
3077 static void status_line(const char *format, ...)
3081 va_start(args, format);
3082 vsprintf(status_buffer, format, args);
3085 have_status_msg = 1;
3088 // copy s to buf, convert unprintable
3089 static void print_literal(char *buf, const char *s)
3103 c_is_no_print = (c & 0x80) && !Isprint(c);
3104 if (c_is_no_print) {
3105 strcpy(d, ESC_NORM_TEXT);
3106 d += sizeof(ESC_NORM_TEXT)-1;
3109 if (c < ' ' || c == 0x7f) {
3111 c |= '@'; /* 0x40 */
3117 if (c_is_no_print) {
3118 strcpy(d, ESC_BOLD_TEXT);
3119 d += sizeof(ESC_BOLD_TEXT)-1;
3125 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3130 static void not_implemented(const char *s)
3132 char buf[MAX_INPUT_LEN];
3134 print_literal(buf, s);
3135 status_line_bold("\'%s\' is not implemented", buf);
3138 // show file status on status line
3139 static int format_edit_status(void)
3141 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3143 #define tot format_edit_status__tot
3145 int cur, percent, ret, trunc_at;
3147 // file_modified is now a counter rather than a flag. this
3148 // helps reduce the amount of line counting we need to do.
3149 // (this will cause a mis-reporting of modified status
3150 // once every MAXINT editing operations.)
3152 // it would be nice to do a similar optimization here -- if
3153 // we haven't done a motion that could have changed which line
3154 // we're on, then we shouldn't have to do this count_lines()
3155 cur = count_lines(text, dot);
3157 // reduce counting -- the total lines can't have
3158 // changed if we haven't done any edits.
3159 if (file_modified != last_file_modified) {
3160 tot = cur + count_lines(dot, end - 1) - 1;
3161 last_file_modified = file_modified;
3164 // current line percent
3165 // ------------- ~~ ----------
3168 percent = (100 * cur) / tot;
3174 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3175 columns : STATUS_BUFFER_LEN-1;
3177 ret = snprintf(status_buffer, trunc_at+1,
3178 #if ENABLE_FEATURE_VI_READONLY
3179 "%c %s%s%s %d/%d %d%%",
3181 "%c %s%s %d/%d %d%%",
3183 cmd_mode_indicator[cmd_mode & 3],
3184 (current_filename != NULL ? current_filename : "No file"),
3185 #if ENABLE_FEATURE_VI_READONLY
3186 (readonly_mode ? " [Readonly]" : ""),
3188 (file_modified ? " [Modified]" : ""),
3191 if (ret >= 0 && ret < trunc_at)
3192 return ret; /* it all fit */
3194 return trunc_at; /* had to truncate */
3198 //----- Force refresh of all Lines -----------------------------
3199 static void redraw(int full_screen)
3203 screen_erase(); // erase the internal screen buffer
3204 last_status_cksum = 0; // force status update
3205 refresh(full_screen); // this will redraw the entire display
3209 //----- Format a text[] line into a buffer ---------------------
3210 static char* format_line(char *src /*, int li*/)
3215 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3217 c = '~'; // char in col 0 in non-existent lines is '~'
3219 while (co < columns + tabstop) {
3220 // have we gone past the end?
3225 if ((c & 0x80) && !Isprint(c)) {
3228 if (c < ' ' || c == 0x7f) {
3232 while ((co % tabstop) != (tabstop - 1)) {
3240 c += '@'; // Ctrl-X -> 'X'
3245 // discard scrolled-off-to-the-left portion,
3246 // in tabstop-sized pieces
3247 if (ofs >= tabstop && co >= tabstop) {
3248 memmove(dest, dest + tabstop, co);
3255 // check "short line, gigantic offset" case
3258 // discard last scrolled off part
3261 // fill the rest with spaces
3263 memset(&dest[co], ' ', columns - co);
3267 //----- Refresh the changed screen lines -----------------------
3268 // Copy the source line from text[] into the buffer and note
3269 // if the current screenline is different from the new buffer.
3270 // If they differ then that line needs redrawing on the terminal.
3272 static void refresh(int full_screen)
3274 #define old_offset refresh__old_offset
3277 char *tp, *sp; // pointer into text[] and screen[]
3279 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3280 unsigned c = columns, r = rows;
3281 query_screen_dimensions();
3282 full_screen |= (c - columns) | (r - rows);
3284 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3285 tp = screenbegin; // index into text[] of top line
3287 // compare text[] to screen[] and mark screen[] lines that need updating
3288 for (li = 0; li < rows - 1; li++) {
3289 int cs, ce; // column start & end
3291 // format current text line
3292 out_buf = format_line(tp /*, li*/);
3294 // skip to the end of the current text[] line
3296 char *t = memchr(tp, '\n', end - tp);
3297 if (!t) t = end - 1;
3301 // see if there are any changes between vitual screen and out_buf
3302 changed = FALSE; // assume no change
3305 sp = &screen[li * columns]; // start of screen line
3307 // force re-draw of every single column from 0 - columns-1
3310 // compare newly formatted buffer with virtual screen
3311 // look forward for first difference between buf and screen
3312 for (; cs <= ce; cs++) {
3313 if (out_buf[cs] != sp[cs]) {
3314 changed = TRUE; // mark for redraw
3319 // look backward for last difference between out_buf and screen
3320 for (; ce >= cs; ce--) {
3321 if (out_buf[ce] != sp[ce]) {
3322 changed = TRUE; // mark for redraw
3326 // now, cs is index of first diff, and ce is index of last diff
3328 // if horz offset has changed, force a redraw
3329 if (offset != old_offset) {
3334 // make a sanity check of columns indexes
3336 if (ce > columns - 1) ce = columns - 1;
3337 if (cs > ce) { cs = 0; ce = columns - 1; }
3338 // is there a change between vitual screen and out_buf
3340 // copy changed part of buffer to virtual screen
3341 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3342 place_cursor(li, cs);
3343 // write line out to terminal
3344 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3348 place_cursor(crow, ccol);
3350 old_offset = offset;
3354 //---------------------------------------------------------------------
3355 //----- the Ascii Chart -----------------------------------------------
3357 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3358 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3359 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3360 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3361 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3362 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3363 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3364 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3365 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3366 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3367 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3368 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3369 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3370 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3371 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3372 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3373 //---------------------------------------------------------------------
3375 //----- Execute a Vi Command -----------------------------------
3376 static void do_cmd(int c)
3378 char *p, *q, *save_dot;
3384 // c1 = c; // quiet the compiler
3385 // cnt = yf = 0; // quiet the compiler
3386 // p = q = save_dot = buf; // quiet the compiler
3387 memset(buf, '\0', sizeof(buf));
3391 /* if this is a cursor key, skip these checks */
3399 case KEYCODE_PAGEUP:
3400 case KEYCODE_PAGEDOWN:
3401 case KEYCODE_DELETE:
3405 if (cmd_mode == 2) {
3406 // flip-flop Insert/Replace mode
3407 if (c == KEYCODE_INSERT)
3409 // we are 'R'eplacing the current *dot with new char
3411 // don't Replace past E-o-l
3412 cmd_mode = 1; // convert to insert
3413 undo_queue_commit();
3415 if (1 <= c || Isprint(c)) {
3417 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3418 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3423 if (cmd_mode == 1) {
3424 // hitting "Insert" twice means "R" replace mode
3425 if (c == KEYCODE_INSERT) goto dc5;
3426 // insert the char c at "dot"
3427 if (1 <= c || Isprint(c)) {
3428 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3443 #if ENABLE_FEATURE_VI_CRASHME
3444 case 0x14: // dc4 ctrl-T
3445 crashme = (crashme == 0) ? 1 : 0;
3475 default: // unrecognized command
3478 not_implemented(buf);
3479 end_cmd_q(); // stop adding to q
3480 case 0x00: // nul- ignore
3482 case 2: // ctrl-B scroll up full screen
3483 case KEYCODE_PAGEUP: // Cursor Key Page Up
3484 dot_scroll(rows - 2, -1);
3486 case 4: // ctrl-D scroll down half screen
3487 dot_scroll((rows - 2) / 2, 1);
3489 case 5: // ctrl-E scroll down one line
3492 case 6: // ctrl-F scroll down full screen
3493 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3494 dot_scroll(rows - 2, 1);
3496 case 7: // ctrl-G show current status
3497 last_status_cksum = 0; // force status update
3499 case 'h': // h- move left
3500 case KEYCODE_LEFT: // cursor key Left
3501 case 8: // ctrl-H- move left (This may be ERASE char)
3502 case 0x7f: // DEL- move left (This may be ERASE char)
3505 } while (--cmdcnt > 0);
3507 case 10: // Newline ^J
3508 case 'j': // j- goto next line, same col
3509 case KEYCODE_DOWN: // cursor key Down
3511 dot_next(); // go to next B-o-l
3512 // try stay in same col
3513 dot = move_to_col(dot, ccol + offset);
3514 } while (--cmdcnt > 0);
3516 case 12: // ctrl-L force redraw whole screen
3517 case 18: // ctrl-R force redraw
3520 //mysleep(10); // why???
3521 screen_erase(); // erase the internal screen buffer
3522 last_status_cksum = 0; // force status update
3523 refresh(TRUE); // this will redraw the entire display
3525 case 13: // Carriage Return ^M
3526 case '+': // +- goto next line
3530 } while (--cmdcnt > 0);
3532 case 21: // ctrl-U scroll up half screen
3533 dot_scroll((rows - 2) / 2, -1);
3535 case 25: // ctrl-Y scroll up one line
3541 cmd_mode = 0; // stop insrting
3542 undo_queue_commit();
3544 last_status_cksum = 0; // force status update
3546 case ' ': // move right
3547 case 'l': // move right
3548 case KEYCODE_RIGHT: // Cursor Key Right
3551 } while (--cmdcnt > 0);
3553 #if ENABLE_FEATURE_VI_YANKMARK
3554 case '"': // "- name a register to use for Delete/Yank
3555 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3556 if ((unsigned)c1 <= 25) { // a-z?
3562 case '\'': // '- goto a specific mark
3563 c1 = (get_one_char() | 0x20) - 'a';
3564 if ((unsigned)c1 <= 25) { // a-z?
3567 if (text <= q && q < end) {
3569 dot_begin(); // go to B-o-l
3572 } else if (c1 == '\'') { // goto previous context
3573 dot = swap_context(dot); // swap current and previous context
3574 dot_begin(); // go to B-o-l
3580 case 'm': // m- Mark a line
3581 // this is really stupid. If there are any inserts or deletes
3582 // between text[0] and dot then this mark will not point to the
3583 // correct location! It could be off by many lines!
3584 // Well..., at least its quick and dirty.
3585 c1 = (get_one_char() | 0x20) - 'a';
3586 if ((unsigned)c1 <= 25) { // a-z?
3587 // remember the line
3593 case 'P': // P- Put register before
3594 case 'p': // p- put register after
3597 status_line_bold("Nothing in register %c", what_reg());
3600 // are we putting whole lines or strings
3601 if (strchr(p, '\n') != NULL) {
3603 dot_begin(); // putting lines- Put above
3606 // are we putting after very last line?
3607 if (end_line(dot) == (end - 1)) {
3608 dot = end; // force dot to end of text[]
3610 dot_next(); // next line, then put before
3615 dot_right(); // move to right, can move to NL
3617 string_insert(dot, p, ALLOW_UNDO); // insert the string
3618 end_cmd_q(); // stop adding to q
3620 #if ENABLE_FEATURE_VI_UNDO
3621 case 'u': // u- undo last operation
3625 case 'U': // U- Undo; replace current line with original version
3626 if (reg[Ureg] != NULL) {
3627 p = begin_line(dot);
3629 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3630 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3635 #endif /* FEATURE_VI_YANKMARK */
3636 case '$': // $- goto end of line
3637 case KEYCODE_END: // Cursor Key End
3639 dot = end_line(dot);
3645 case '%': // %- find matching char of pair () [] {}
3646 for (q = dot; q < end && *q != '\n'; q++) {
3647 if (strchr("()[]{}", *q) != NULL) {
3648 // we found half of a pair
3649 p = find_pair(q, *q);
3661 case 'f': // f- forward to a user specified char
3662 last_forward_char = get_one_char(); // get the search char
3664 // dont separate these two commands. 'f' depends on ';'
3666 //**** fall through to ... ';'
3667 case ';': // ;- look at rest of line for last forward char
3669 if (last_forward_char == 0)
3672 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3675 if (*q == last_forward_char)
3677 } while (--cmdcnt > 0);
3679 case ',': // repeat latest 'f' in opposite direction
3680 if (last_forward_char == 0)
3684 while (q >= text && *q != '\n' && *q != last_forward_char) {
3687 if (q >= text && *q == last_forward_char)
3689 } while (--cmdcnt > 0);
3692 case '-': // -- goto prev line
3696 } while (--cmdcnt > 0);
3698 #if ENABLE_FEATURE_VI_DOT_CMD
3699 case '.': // .- repeat the last modifying command
3700 // Stuff the last_modifying_cmd back into stdin
3701 // and let it be re-executed.
3703 last_modifying_cmd[lmc_len] = 0;
3704 ioq = ioq_start = xstrdup(last_modifying_cmd);
3708 #if ENABLE_FEATURE_VI_SEARCH
3709 case '?': // /- search for a pattern
3710 case '/': // /- search for a pattern
3713 q = get_input_line(buf); // get input line- use "status line"
3714 if (q[0] && !q[1]) {
3715 if (last_search_pattern[0])
3716 last_search_pattern[0] = c;
3717 goto dc3; // if no pat re-use old pat
3719 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3720 // there is a new pat
3721 free(last_search_pattern);
3722 last_search_pattern = xstrdup(q);
3723 goto dc3; // now find the pattern
3725 // user changed mind and erased the "/"- do nothing
3727 case 'N': // N- backward search for last pattern
3728 dir = BACK; // assume BACKWARD search
3730 if (last_search_pattern[0] == '?') {
3734 goto dc4; // now search for pattern
3736 case 'n': // n- repeat search for last pattern
3737 // search rest of text[] starting at next char
3738 // if search fails return orignal "p" not the "p+1" address
3742 dir = FORWARD; // assume FORWARD search
3744 if (last_search_pattern[0] == '?') {
3749 q = char_search(p, last_search_pattern + 1, dir, FULL);
3751 dot = q; // good search, update "dot"
3755 // no pattern found between "dot" and "end"- continue at top
3760 q = char_search(p, last_search_pattern + 1, dir, FULL);
3761 if (q != NULL) { // found something
3762 dot = q; // found new pattern- goto it
3763 msg = "search hit BOTTOM, continuing at TOP";
3765 msg = "search hit TOP, continuing at BOTTOM";
3768 msg = "Pattern not found";
3772 status_line_bold("%s", msg);
3773 } while (--cmdcnt > 0);
3775 case '{': // {- move backward paragraph
3776 q = char_search(dot, "\n\n", BACK, FULL);
3777 if (q != NULL) { // found blank line
3778 dot = next_line(q); // move to next blank line
3781 case '}': // }- move forward paragraph
3782 q = char_search(dot, "\n\n", FORWARD, FULL);
3783 if (q != NULL) { // found blank line
3784 dot = next_line(q); // move to next blank line
3787 #endif /* FEATURE_VI_SEARCH */
3788 case '0': // 0- goto begining of line
3798 if (c == '0' && cmdcnt < 1) {
3799 dot_begin(); // this was a standalone zero
3801 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3804 case ':': // :- the colon mode commands
3805 p = get_input_line(":"); // get input line- use "status line"
3806 #if ENABLE_FEATURE_VI_COLON
3807 colon(p); // execute the command
3810 p++; // move past the ':'
3814 if (strncmp(p, "quit", cnt) == 0
3815 || strncmp(p, "q!", cnt) == 0 // delete lines
3817 if (file_modified && p[1] != '!') {
3818 status_line_bold("No write since last change (:%s! overrides)", p);
3822 } else if (strncmp(p, "write", cnt) == 0
3823 || strncmp(p, "wq", cnt) == 0
3824 || strncmp(p, "wn", cnt) == 0
3825 || (p[0] == 'x' && !p[1])
3827 cnt = file_write(current_filename, text, end - 1);
3830 status_line_bold("Write error: %s", strerror(errno));
3833 last_file_modified = -1;
3834 status_line("'%s' %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3835 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3836 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3841 } else if (strncmp(p, "file", cnt) == 0) {
3842 last_status_cksum = 0; // force status update
3843 } else if (sscanf(p, "%d", &j) > 0) {
3844 dot = find_line(j); // go to line # j
3846 } else { // unrecognized cmd
3849 #endif /* !FEATURE_VI_COLON */
3851 case '<': // <- Left shift something
3852 case '>': // >- Right shift something
3853 cnt = count_lines(text, dot); // remember what line we are on
3854 c1 = get_one_char(); // get the type of thing to delete
3855 find_range(&p, &q, c1);
3856 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3859 i = count_lines(p, q); // # of lines we are shifting
3860 for ( ; i > 0; i--, p = next_line(p)) {
3862 // shift left- remove tab or 8 spaces
3864 // shrink buffer 1 char
3865 text_hole_delete(p, p, NO_UNDO);
3866 } else if (*p == ' ') {
3867 // we should be calculating columns, not just SPACE
3868 for (j = 0; *p == ' ' && j < tabstop; j++) {
3869 text_hole_delete(p, p, NO_UNDO);
3872 } else if (c == '>') {
3873 // shift right -- add tab or 8 spaces
3874 char_insert(p, '\t', ALLOW_UNDO);
3877 dot = find_line(cnt); // what line were we on
3879 end_cmd_q(); // stop adding to q
3881 case 'A': // A- append at e-o-l
3882 dot_end(); // go to e-o-l
3883 //**** fall through to ... 'a'
3884 case 'a': // a- append after current char
3889 case 'B': // B- back a blank-delimited Word
3890 case 'E': // E- end of a blank-delimited word
3891 case 'W': // W- forward a blank-delimited word
3896 if (c == 'W' || isspace(dot[dir])) {
3897 dot = skip_thing(dot, 1, dir, S_TO_WS);
3898 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3901 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3902 } while (--cmdcnt > 0);
3904 case 'C': // C- Change to e-o-l
3905 case 'D': // D- delete to e-o-l
3907 dot = dollar_line(dot); // move to before NL
3908 // copy text into a register and delete
3909 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3911 goto dc_i; // start inserting
3912 #if ENABLE_FEATURE_VI_DOT_CMD
3914 end_cmd_q(); // stop adding to q
3917 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3918 c1 = get_one_char();
3921 buf[1] = c1; // TODO: if Unicode?
3923 not_implemented(buf);
3929 case 'G': // G- goto to a line number (default= E-O-F)
3930 dot = end - 1; // assume E-O-F
3932 dot = find_line(cmdcnt); // what line is #cmdcnt
3936 case 'H': // H- goto top line on screen
3938 if (cmdcnt > (rows - 1)) {
3939 cmdcnt = (rows - 1);
3946 case 'I': // I- insert before first non-blank
3949 //**** fall through to ... 'i'
3950 case 'i': // i- insert before current char
3951 case KEYCODE_INSERT: // Cursor Key Insert
3953 cmd_mode = 1; // start inserting
3954 undo_queue_commit(); // commit queue when cmd_mode changes
3956 case 'J': // J- join current and next lines together
3958 dot_end(); // move to NL
3959 if (dot < end - 1) { // make sure not last char in text[]
3960 #if ENABLE_FEATURE_VI_UNDO
3961 undo_push(dot, 1, UNDO_DEL);
3962 *dot++ = ' '; // replace NL with space
3963 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3968 while (isblank(*dot)) { // delete leading WS
3969 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3972 } while (--cmdcnt > 0);
3973 end_cmd_q(); // stop adding to q
3975 case 'L': // L- goto bottom line on screen
3977 if (cmdcnt > (rows - 1)) {
3978 cmdcnt = (rows - 1);
3986 case 'M': // M- goto middle line on screen
3988 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3989 dot = next_line(dot);
3991 case 'O': // O- open a empty line above
3993 p = begin_line(dot);
3994 if (p[-1] == '\n') {
3996 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3998 dot = char_insert(dot, '\n', ALLOW_UNDO);
4001 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
4006 case 'R': // R- continuous Replace char
4009 undo_queue_commit();
4011 case KEYCODE_DELETE:
4014 case 'X': // X- delete char before dot
4015 case 'x': // x- delete the current char
4016 case 's': // s- substitute the current char
4021 if (dot[dir] != '\n') {
4023 dot--; // delete prev char
4024 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
4026 } while (--cmdcnt > 0);
4027 end_cmd_q(); // stop adding to q
4029 goto dc_i; // start inserting
4031 case 'Z': // Z- if modified, {write}; exit
4032 // ZZ means to save file (if necessary), then exit
4033 c1 = get_one_char();
4038 if (file_modified) {
4039 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
4040 status_line_bold("'%s' is read only", current_filename);
4043 cnt = file_write(current_filename, text, end - 1);
4046 status_line_bold("Write error: %s", strerror(errno));
4047 } else if (cnt == (end - 1 - text + 1)) {
4054 case '^': // ^- move to first non-blank on line
4058 case 'b': // b- back a word
4059 case 'e': // e- end of word
4064 if ((dot + dir) < text || (dot + dir) > end - 1)
4067 if (isspace(*dot)) {
4068 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4070 if (isalnum(*dot) || *dot == '_') {
4071 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4072 } else if (ispunct(*dot)) {
4073 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4075 } while (--cmdcnt > 0);
4077 case 'c': // c- change something
4078 case 'd': // d- delete something
4079 #if ENABLE_FEATURE_VI_YANKMARK
4080 case 'y': // y- yank something
4081 case 'Y': // Y- Yank a line
4084 int yf, ml, whole = 0;
4085 yf = YANKDEL; // assume either "c" or "d"
4086 #if ENABLE_FEATURE_VI_YANKMARK
4087 if (c == 'y' || c == 'Y')
4092 c1 = get_one_char(); // get the type of thing to delete
4093 // determine range, and whether it spans lines
4094 ml = find_range(&p, &q, c1);
4096 if (c1 == 27) { // ESC- user changed mind and wants out
4097 c = c1 = 27; // Escape- do nothing
4098 } else if (strchr("wW", c1)) {
4100 // don't include trailing WS as part of word
4101 while (isblank(*q)) {
4102 if (q <= text || q[-1] == '\n')
4107 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4108 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
4109 // partial line copy text into a register and delete
4110 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4111 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
4112 // whole line copy text into a register and delete
4113 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
4116 // could not recognize object
4117 c = c1 = 27; // error-
4123 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4124 // on the last line of file don't move to prev line
4125 if (whole && dot != (end-1)) {
4128 } else if (c == 'd') {
4134 // if CHANGING, not deleting, start inserting after the delete
4136 strcpy(buf, "Change");
4137 goto dc_i; // start inserting
4140 strcpy(buf, "Delete");
4142 #if ENABLE_FEATURE_VI_YANKMARK
4143 if (c == 'y' || c == 'Y') {
4144 strcpy(buf, "Yank");
4148 for (cnt = 0; p <= q; p++) {
4152 status_line("%s %d lines (%d chars) using [%c]",
4153 buf, cnt, strlen(reg[YDreg]), what_reg());
4155 end_cmd_q(); // stop adding to q
4159 case 'k': // k- goto prev line, same col
4160 case KEYCODE_UP: // cursor key Up
4163 dot = move_to_col(dot, ccol + offset); // try stay in same col
4164 } while (--cmdcnt > 0);
4166 case 'r': // r- replace the current char with user input
4167 c1 = get_one_char(); // get the replacement char
4169 #if ENABLE_FEATURE_VI_UNDO
4170 undo_push(dot, 1, UNDO_DEL);
4172 undo_push(dot, 1, UNDO_INS_CHAIN);
4178 end_cmd_q(); // stop adding to q
4180 case 't': // t- move to char prior to next x
4181 last_forward_char = get_one_char();
4183 if (*dot == last_forward_char)
4185 last_forward_char = 0;
4187 case 'w': // w- forward a word
4189 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4190 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4191 } else if (ispunct(*dot)) { // we are on PUNCT
4192 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4195 dot++; // move over word
4196 if (isspace(*dot)) {
4197 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4199 } while (--cmdcnt > 0);
4202 c1 = get_one_char(); // get the replacement char
4205 cnt = (rows - 2) / 2; // put dot at center
4207 cnt = rows - 2; // put dot at bottom
4208 screenbegin = begin_line(dot); // start dot at top
4209 dot_scroll(cnt, -1);
4211 case '|': // |- move to column "cmdcnt"
4212 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4214 case '~': // ~- flip the case of letters a-z -> A-Z
4216 #if ENABLE_FEATURE_VI_UNDO
4217 if (islower(*dot)) {
4218 undo_push(dot, 1, UNDO_DEL);
4219 *dot = toupper(*dot);
4220 undo_push(dot, 1, UNDO_INS_CHAIN);
4221 } else if (isupper(*dot)) {
4222 undo_push(dot, 1, UNDO_DEL);
4223 *dot = tolower(*dot);
4224 undo_push(dot, 1, UNDO_INS_CHAIN);
4227 if (islower(*dot)) {
4228 *dot = toupper(*dot);
4230 } else if (isupper(*dot)) {
4231 *dot = tolower(*dot);
4236 } while (--cmdcnt > 0);
4237 end_cmd_q(); // stop adding to q
4239 //----- The Cursor and Function Keys -----------------------------
4240 case KEYCODE_HOME: // Cursor Key Home
4243 // The Fn keys could point to do_macro which could translate them
4245 case KEYCODE_FUN1: // Function Key F1
4246 case KEYCODE_FUN2: // Function Key F2
4247 case KEYCODE_FUN3: // Function Key F3
4248 case KEYCODE_FUN4: // Function Key F4
4249 case KEYCODE_FUN5: // Function Key F5
4250 case KEYCODE_FUN6: // Function Key F6
4251 case KEYCODE_FUN7: // Function Key F7
4252 case KEYCODE_FUN8: // Function Key F8
4253 case KEYCODE_FUN9: // Function Key F9
4254 case KEYCODE_FUN10: // Function Key F10
4255 case KEYCODE_FUN11: // Function Key F11
4256 case KEYCODE_FUN12: // Function Key F12
4262 // if text[] just became empty, add back an empty line
4264 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4267 // it is OK for dot to exactly equal to end, otherwise check dot validity
4269 dot = bound_dot(dot); // make sure "dot" is valid
4271 #if ENABLE_FEATURE_VI_YANKMARK
4272 check_context(c); // update the current context
4276 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4277 cnt = dot - begin_line(dot);
4278 // Try to stay off of the Newline
4279 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4283 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4284 #if ENABLE_FEATURE_VI_CRASHME
4285 static int totalcmds = 0;
4286 static int Mp = 85; // Movement command Probability
4287 static int Np = 90; // Non-movement command Probability
4288 static int Dp = 96; // Delete command Probability
4289 static int Ip = 97; // Insert command Probability
4290 static int Yp = 98; // Yank command Probability
4291 static int Pp = 99; // Put command Probability
4292 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4293 static const char chars[20] = "\t012345 abcdABCD-=.$";
4294 static const char *const words[20] = {
4295 "this", "is", "a", "test",
4296 "broadcast", "the", "emergency", "of",
4297 "system", "quick", "brown", "fox",
4298 "jumped", "over", "lazy", "dogs",
4299 "back", "January", "Febuary", "March"
4301 static const char *const lines[20] = {
4302 "You should have received a copy of the GNU General Public License\n",
4303 "char c, cm, *cmd, *cmd1;\n",
4304 "generate a command by percentages\n",
4305 "Numbers may be typed as a prefix to some commands.\n",
4306 "Quit, discarding changes!\n",
4307 "Forced write, if permission originally not valid.\n",
4308 "In general, any ex or ed command (such as substitute or delete).\n",
4309 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4310 "Please get w/ me and I will go over it with you.\n",
4311 "The following is a list of scheduled, committed changes.\n",
4312 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4313 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4314 "Any question about transactions please contact Sterling Huxley.\n",
4315 "I will try to get back to you by Friday, December 31.\n",
4316 "This Change will be implemented on Friday.\n",
4317 "Let me know if you have problems accessing this;\n",
4318 "Sterling Huxley recently added you to the access list.\n",
4319 "Would you like to go to lunch?\n",
4320 "The last command will be automatically run.\n",
4321 "This is too much english for a computer geek.\n",
4323 static char *multilines[20] = {
4324 "You should have received a copy of the GNU General Public License\n",
4325 "char c, cm, *cmd, *cmd1;\n",
4326 "generate a command by percentages\n",
4327 "Numbers may be typed as a prefix to some commands.\n",
4328 "Quit, discarding changes!\n",
4329 "Forced write, if permission originally not valid.\n",
4330 "In general, any ex or ed command (such as substitute or delete).\n",
4331 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4332 "Please get w/ me and I will go over it with you.\n",
4333 "The following is a list of scheduled, committed changes.\n",
4334 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4335 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4336 "Any question about transactions please contact Sterling Huxley.\n",
4337 "I will try to get back to you by Friday, December 31.\n",
4338 "This Change will be implemented on Friday.\n",
4339 "Let me know if you have problems accessing this;\n",
4340 "Sterling Huxley recently added you to the access list.\n",
4341 "Would you like to go to lunch?\n",
4342 "The last command will be automatically run.\n",
4343 "This is too much english for a computer geek.\n",
4346 // create a random command to execute
4347 static void crash_dummy()
4349 static int sleeptime; // how long to pause between commands
4350 char c, cm, *cmd, *cmd1;
4351 int i, cnt, thing, rbi, startrbi, percent;
4353 // "dot" movement commands
4354 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4356 // is there already a command running?
4357 if (readbuffer[0] > 0)
4360 readbuffer[0] = 'X';
4362 sleeptime = 0; // how long to pause between commands
4363 memset(readbuffer, '\0', sizeof(readbuffer));
4364 // generate a command by percentages
4365 percent = (int) lrand48() % 100; // get a number from 0-99
4366 if (percent < Mp) { // Movement commands
4367 // available commands
4370 } else if (percent < Np) { // non-movement commands
4371 cmd = "mz<>\'\""; // available commands
4373 } else if (percent < Dp) { // Delete commands
4374 cmd = "dx"; // available commands
4376 } else if (percent < Ip) { // Inset commands
4377 cmd = "iIaAsrJ"; // available commands
4379 } else if (percent < Yp) { // Yank commands
4380 cmd = "yY"; // available commands
4382 } else if (percent < Pp) { // Put commands
4383 cmd = "pP"; // available commands
4386 // We do not know how to handle this command, try again
4390 // randomly pick one of the available cmds from "cmd[]"
4391 i = (int) lrand48() % strlen(cmd);
4393 if (strchr(":\024", cm))
4394 goto cd0; // dont allow colon or ctrl-T commands
4395 readbuffer[rbi++] = cm; // put cmd into input buffer
4397 // now we have the command-
4398 // there are 1, 2, and multi char commands
4399 // find out which and generate the rest of command as necessary
4400 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4401 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4402 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4403 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4405 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4407 readbuffer[rbi++] = c; // add movement to input buffer
4409 if (strchr("iIaAsc", cm)) { // multi-char commands
4411 // change some thing
4412 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4414 readbuffer[rbi++] = c; // add movement to input buffer
4416 thing = (int) lrand48() % 4; // what thing to insert
4417 cnt = (int) lrand48() % 10; // how many to insert
4418 for (i = 0; i < cnt; i++) {
4419 if (thing == 0) { // insert chars
4420 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4421 } else if (thing == 1) { // insert words
4422 strcat(readbuffer, words[(int) lrand48() % 20]);
4423 strcat(readbuffer, " ");
4424 sleeptime = 0; // how fast to type
4425 } else if (thing == 2) { // insert lines
4426 strcat(readbuffer, lines[(int) lrand48() % 20]);
4427 sleeptime = 0; // how fast to type
4428 } else { // insert multi-lines
4429 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4430 sleeptime = 0; // how fast to type
4433 strcat(readbuffer, "\033");
4435 readbuffer[0] = strlen(readbuffer + 1);
4439 mysleep(sleeptime); // sleep 1/100 sec
4442 // test to see if there are any errors
4443 static void crash_test()
4445 static time_t oldtim;
4452 strcat(msg, "end<text ");
4454 if (end > textend) {
4455 strcat(msg, "end>textend ");
4458 strcat(msg, "dot<text ");
4461 strcat(msg, "dot>end ");
4463 if (screenbegin < text) {
4464 strcat(msg, "screenbegin<text ");
4466 if (screenbegin > end - 1) {
4467 strcat(msg, "screenbegin>end-1 ");
4471 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4472 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4474 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4475 if (d[0] == '\n' || d[0] == '\r')
4480 if (tim >= (oldtim + 3)) {
4481 sprintf(status_buffer,
4482 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4483 totalcmds, M, N, I, D, Y, P, U, end - text + 1);