1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
11 * $HOME/.exrc and ./.exrc
12 * add magic to search /foo.*bar
15 * if mark[] values were line numbers rather than pointers
16 * it would be easier to change the mark when add/delete lines
17 * More intelligence in refresh()
18 * ":r !cmd" and "!cmd" to filter text through an external command
19 * An "ex" line oriented mode- maybe using "cmdedit"
22 //config: bool "vi (23 kb)"
25 //config: 'vi' is a text editor. More specifically, it is the One True
26 //config: text editor <grin>. It does, however, have a rather steep
27 //config: learning curve. If you are not already comfortable with 'vi'
28 //config: you may wish to use something else.
30 //config:config FEATURE_VI_MAX_LEN
31 //config: int "Maximum screen width"
32 //config: range 256 16384
33 //config: default 4096
34 //config: depends on VI
36 //config: Contrary to what you may think, this is not eating much.
37 //config: Make it smaller than 4k only if you are very limited on memory.
39 //config:config FEATURE_VI_8BIT
40 //config: bool "Allow to display 8-bit chars (otherwise shows dots)"
42 //config: depends on VI
44 //config: If your terminal can display characters with high bit set,
45 //config: you may want to enable this. Note: vi is not Unicode-capable.
46 //config: If your terminal combines several 8-bit bytes into one character
47 //config: (as in Unicode mode), this will not work properly.
49 //config:config FEATURE_VI_COLON
50 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
52 //config: depends on VI
54 //config: Enable a limited set of colon commands. This does not
55 //config: provide an "ex" mode.
57 //config:config FEATURE_VI_YANKMARK
58 //config: bool "Enable yank/put commands and mark cmds"
60 //config: depends on VI
62 //config: This enables you to use yank and put, as well as mark.
64 //config:config FEATURE_VI_SEARCH
65 //config: bool "Enable search and replace cmds"
67 //config: depends on VI
69 //config: Select this if you wish to be able to do search and replace.
71 //config:config FEATURE_VI_REGEX_SEARCH
72 //config: bool "Enable regex in search and replace"
73 //config: default n # Uses GNU regex, which may be unavailable. FIXME
74 //config: depends on FEATURE_VI_SEARCH
76 //config: Use extended regex search.
78 //config:config FEATURE_VI_USE_SIGNALS
79 //config: bool "Catch signals"
81 //config: depends on VI
83 //config: Selecting this option will make vi signal aware. This will support
84 //config: SIGWINCH to deal with Window Changes, catch ^Z and ^C and alarms.
86 //config:config FEATURE_VI_DOT_CMD
87 //config: bool "Remember previous cmd and \".\" cmd"
89 //config: depends on VI
91 //config: Make vi remember the last command and be able to repeat it.
93 //config:config FEATURE_VI_READONLY
94 //config: bool "Enable -R option and \"view\" mode"
96 //config: depends on VI
98 //config: Enable the read-only command line option, which allows the user to
99 //config: open a file in read-only mode.
101 //config:config FEATURE_VI_SETOPTS
102 //config: bool "Enable settable options, ai ic showmatch"
104 //config: depends on VI
106 //config: Enable the editor to set some (ai, ic, showmatch) options.
108 //config:config FEATURE_VI_SET
109 //config: bool "Support :set"
111 //config: depends on VI
113 //config:config FEATURE_VI_WIN_RESIZE
114 //config: bool "Handle window resize"
116 //config: depends on VI
118 //config: Behave nicely with terminals that get resized.
120 //config:config FEATURE_VI_ASK_TERMINAL
121 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
123 //config: depends on VI
125 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
126 //config: this option makes vi perform a last-ditch effort to find it:
127 //config: position cursor to 999,999 and ask terminal to report real
128 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
129 //config: This is not clean but helps a lot on serial lines and such.
131 //config:config FEATURE_VI_UNDO
132 //config: bool "Support undo command \"u\""
134 //config: depends on VI
136 //config: Support the 'u' command to undo insertion, deletion, and replacement
139 //config:config FEATURE_VI_UNDO_QUEUE
140 //config: bool "Enable undo operation queuing"
142 //config: depends on FEATURE_VI_UNDO
144 //config: The vi undo functions can use an intermediate queue to greatly lower
145 //config: malloc() calls and overhead. When the maximum size of this queue is
146 //config: reached, the contents of the queue are committed to the undo stack.
147 //config: This increases the size of the undo code and allows some undo
148 //config: operations (especially un-typing/backspacing) to be far more useful.
150 //config:config FEATURE_VI_UNDO_QUEUE_MAX
151 //config: int "Maximum undo character queue size"
152 //config: default 256
153 //config: range 32 65536
154 //config: depends on FEATURE_VI_UNDO_QUEUE
156 //config: This option sets the number of bytes used at runtime for the queue.
157 //config: Smaller values will create more undo objects and reduce the amount
158 //config: of typed or backspaced characters that are grouped into one undo
159 //config: operation; larger values increase the potential size of each undo
160 //config: and will generally malloc() larger objects and less frequently.
161 //config: Unless you want more (or less) frequent "undo points" while typing,
162 //config: you should probably leave this unchanged.
164 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
166 //kbuild:lib-$(CONFIG_VI) += vi.o
168 //usage:#define vi_trivial_usage
169 //usage: "[OPTIONS] [FILE]..."
170 //usage:#define vi_full_usage "\n\n"
171 //usage: "Edit FILE\n"
172 //usage: IF_FEATURE_VI_COLON(
173 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
175 //usage: IF_FEATURE_VI_READONLY(
176 //usage: "\n -R Read-only"
178 //usage: "\n -H List available features"
181 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
182 #if ENABLE_FEATURE_VI_REGEX_SEARCH
186 /* the CRASHME code is unmaintained, and doesn't currently build */
187 #define ENABLE_FEATURE_VI_CRASHME 0
190 #if ENABLE_LOCALE_SUPPORT
192 #if ENABLE_FEATURE_VI_8BIT
193 //FIXME: this does not work properly for Unicode anyway
194 # define Isprint(c) (isprint)(c)
196 # define Isprint(c) isprint_asciionly(c)
201 /* 0x9b is Meta-ESC */
202 #if ENABLE_FEATURE_VI_8BIT
203 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
205 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
212 MAX_TABSTOP = 32, // sanity limit
213 // User input len. Need not be extra big.
214 // Lines in file being edited *can* be bigger than this.
216 // Sanity limits. We have only one buffer of this size.
217 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
218 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
221 /* VT102 ESC sequences.
222 * See "Xterm Control Sequences"
223 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
226 /* Inverse/Normal text */
227 #define ESC_BOLD_TEXT ESC"[7m"
228 #define ESC_NORM_TEXT ESC"[m"
230 #define ESC_BELL "\007"
231 /* Clear-to-end-of-line */
232 #define ESC_CLEAR2EOL ESC"[K"
233 /* Clear-to-end-of-screen.
234 * (We use default param here.
235 * Full sequence is "ESC [ <num> J",
236 * <num> is 0/1/2 = "erase below/above/all".)
238 #define ESC_CLEAR2EOS ESC"[J"
239 /* Cursor to given coordinate (1,1: top left) */
240 #define ESC_SET_CURSOR_POS ESC"[%u;%uH"
241 #define ESC_SET_CURSOR_TOPLEFT ESC"[H"
243 ///* Cursor up and down */
244 //#define ESC_CURSOR_UP ESC"[A"
245 //#define ESC_CURSOR_DOWN "\n"
247 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
248 // cmds modifying text[]
249 // vda: removed "aAiIs" as they switch us into insert mode
250 // and remembering input for replay after them makes no sense
251 static const char modifying_cmds[] ALIGN1 = "cCdDJoOpPrRxX<>~";
257 FORWARD = 1, // code depends on "1" for array index
258 BACK = -1, // code depends on "-1" for array index
259 LIMITED = 0, // char_search() only current line
260 FULL = 1, // char_search() to the end/beginning of entire text
262 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
263 S_TO_WS = 2, // used in skip_thing() for moving "dot"
264 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
265 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
266 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
270 /* vi.c expects chars to be unsigned. */
271 /* busybox build system provides that, but it's better */
272 /* to audit and fix the source */
275 /* many references - keep near the top of globals */
276 char *text, *end; // pointers to the user data in memory
277 char *dot; // where all the action takes place
278 int text_size; // size of the allocated buffer
282 #define VI_AUTOINDENT 1
283 #define VI_SHOWMATCH 2
284 #define VI_IGNORECASE 4
285 #define VI_ERR_METHOD 8
286 #define autoindent (vi_setops & VI_AUTOINDENT)
287 #define showmatch (vi_setops & VI_SHOWMATCH )
288 #define ignorecase (vi_setops & VI_IGNORECASE)
289 /* indicate error with beep or flash */
290 #define err_method (vi_setops & VI_ERR_METHOD)
292 #if ENABLE_FEATURE_VI_READONLY
293 smallint readonly_mode;
294 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
295 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
296 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
298 #define SET_READONLY_FILE(flags) ((void)0)
299 #define SET_READONLY_MODE(flags) ((void)0)
300 #define UNSET_READONLY_FILE(flags) ((void)0)
303 smallint editing; // >0 while we are editing a file
304 // [code audit says "can be 0, 1 or 2 only"]
305 smallint cmd_mode; // 0=command 1=insert 2=replace
306 int modified_count; // buffer contents changed if !0
307 int last_modified_count; // = -1;
308 int save_argc; // how many file names on cmd line
309 int cmdcnt; // repetition count
310 unsigned rows, columns; // the terminal screen is this size
311 #if ENABLE_FEATURE_VI_ASK_TERMINAL
312 int get_rowcol_error;
314 int crow, ccol; // cursor is on Crow x Ccol
315 int offset; // chars scrolled off the screen to the left
316 int have_status_msg; // is default edit status needed?
317 // [don't make smallint!]
318 int last_status_cksum; // hash of current status line
319 char *current_filename;
320 char *screenbegin; // index into text[], of top line on the screen
321 char *screen; // pointer to the virtual screen buffer
322 int screensize; // and its size
324 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
325 char erase_char; // the users erase character
326 char last_input_char; // last char read from user
328 #if ENABLE_FEATURE_VI_DOT_CMD
329 smallint adding2q; // are we currently adding user input to q
330 int lmc_len; // length of last_modifying_cmd
331 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
333 #if ENABLE_FEATURE_VI_SEARCH
334 char *last_search_pattern; // last pattern from a '/' or '?' search
338 #if ENABLE_FEATURE_VI_YANKMARK
339 char *edit_file__cur_line;
341 int refresh__old_offset;
342 int format_edit_status__tot;
344 /* a few references only */
345 #if ENABLE_FEATURE_VI_YANKMARK
346 smalluint YDreg;//,Ureg;// default delete register and orig line for "U"
348 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
349 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
350 char *context_start, *context_end;
352 #if ENABLE_FEATURE_VI_USE_SIGNALS
353 sigjmp_buf restart; // int_handler() jumps to location remembered here
355 struct termios term_orig; // remember what the cooked mode was
356 #if ENABLE_FEATURE_VI_COLON
357 char *initial_cmds[3]; // currently 2 entries, NULL terminated
359 // Should be just enough to hold a key sequence,
360 // but CRASHME mode uses it as generated command buffer too
361 #if ENABLE_FEATURE_VI_CRASHME
362 char readbuffer[128];
364 char readbuffer[KEYCODE_BUFFER_SIZE];
366 #define STATUS_BUFFER_LEN 200
367 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
368 #if ENABLE_FEATURE_VI_DOT_CMD
369 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
371 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
373 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
374 #if ENABLE_FEATURE_VI_UNDO
375 // undo_push() operations
378 #define UNDO_INS_CHAIN 2
379 #define UNDO_DEL_CHAIN 3
380 // UNDO_*_QUEUED must be equal to UNDO_xxx ORed with UNDO_QUEUED_FLAG
381 #define UNDO_QUEUED_FLAG 4
382 #define UNDO_INS_QUEUED 4
383 #define UNDO_DEL_QUEUED 5
384 #define UNDO_USE_SPOS 32
385 #define UNDO_EMPTY 64
386 // Pass-through flags for functions that can be undone
389 #define ALLOW_UNDO_CHAIN 2
390 # if ENABLE_FEATURE_VI_UNDO_QUEUE
391 #define ALLOW_UNDO_QUEUED 3
392 char undo_queue_state;
394 char *undo_queue_spos; // Start position of queued operation
395 char undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX];
397 // If undo queuing disabled, don't invoke the missing queue logic
398 #define ALLOW_UNDO_QUEUED 1
402 struct undo_object *prev; // Linking back avoids list traversal (LIFO)
403 int start; // Offset where the data should be restored/deleted
404 int length; // total data size
405 uint8_t u_type; // 0=deleted, 1=inserted, 2=swapped
406 char undo_text[1]; // text that was deleted (if deletion)
408 #endif /* ENABLE_FEATURE_VI_UNDO */
410 #define G (*ptr_to_globals)
411 #define text (G.text )
412 #define text_size (G.text_size )
417 #define vi_setops (G.vi_setops )
418 #define editing (G.editing )
419 #define cmd_mode (G.cmd_mode )
420 #define modified_count (G.modified_count )
421 #define last_modified_count (G.last_modified_count)
422 #define save_argc (G.save_argc )
423 #define cmdcnt (G.cmdcnt )
424 #define rows (G.rows )
425 #define columns (G.columns )
426 #define crow (G.crow )
427 #define ccol (G.ccol )
428 #define offset (G.offset )
429 #define status_buffer (G.status_buffer )
430 #define have_status_msg (G.have_status_msg )
431 #define last_status_cksum (G.last_status_cksum )
432 #define current_filename (G.current_filename )
433 #define screen (G.screen )
434 #define screensize (G.screensize )
435 #define screenbegin (G.screenbegin )
436 #define tabstop (G.tabstop )
437 #define last_forward_char (G.last_forward_char )
438 #define erase_char (G.erase_char )
439 #define last_input_char (G.last_input_char )
440 #if ENABLE_FEATURE_VI_READONLY
441 #define readonly_mode (G.readonly_mode )
443 #define readonly_mode 0
445 #define adding2q (G.adding2q )
446 #define lmc_len (G.lmc_len )
448 #define ioq_start (G.ioq_start )
449 #define last_search_pattern (G.last_search_pattern)
451 #define edit_file__cur_line (G.edit_file__cur_line)
452 #define refresh__old_offset (G.refresh__old_offset)
453 #define format_edit_status__tot (G.format_edit_status__tot)
455 #define YDreg (G.YDreg )
456 //#define Ureg (G.Ureg )
457 #define mark (G.mark )
458 #define context_start (G.context_start )
459 #define context_end (G.context_end )
460 #define restart (G.restart )
461 #define term_orig (G.term_orig )
462 #define initial_cmds (G.initial_cmds )
463 #define readbuffer (G.readbuffer )
464 #define scr_out_buf (G.scr_out_buf )
465 #define last_modifying_cmd (G.last_modifying_cmd )
466 #define get_input_line__buf (G.get_input_line__buf)
468 #if ENABLE_FEATURE_VI_UNDO
469 #define undo_stack_tail (G.undo_stack_tail )
470 # if ENABLE_FEATURE_VI_UNDO_QUEUE
471 #define undo_queue_state (G.undo_queue_state)
472 #define undo_q (G.undo_q )
473 #define undo_queue (G.undo_queue )
474 #define undo_queue_spos (G.undo_queue_spos )
478 #define INIT_G() do { \
479 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
480 last_modified_count = -1; \
481 /* "" but has space for 2 chars: */ \
482 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
486 static void edit_file(char *); // edit one file
487 static void do_cmd(int); // execute a command
488 static int next_tabstop(int);
489 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
490 static char *begin_line(char *); // return pointer to cur line B-o-l
491 static char *end_line(char *); // return pointer to cur line E-o-l
492 static char *prev_line(char *); // return pointer to prev line B-o-l
493 static char *next_line(char *); // return pointer to next line B-o-l
494 static char *end_screen(void); // get pointer to last char on screen
495 static int count_lines(char *, char *); // count line from start to stop
496 static char *find_line(int); // find beginning of line #li
497 static char *move_to_col(char *, int); // move "p" to column l
498 static void dot_left(void); // move dot left- dont leave line
499 static void dot_right(void); // move dot right- dont leave line
500 static void dot_begin(void); // move dot to B-o-l
501 static void dot_end(void); // move dot to E-o-l
502 static void dot_next(void); // move dot to next line B-o-l
503 static void dot_prev(void); // move dot to prev line B-o-l
504 static void dot_scroll(int, int); // move the screen up or down
505 static void dot_skip_over_ws(void); // move dot pat WS
506 static char *bound_dot(char *); // make sure text[0] <= P < "end"
507 static char *new_screen(int, int); // malloc virtual screen memory
508 #if !ENABLE_FEATURE_VI_UNDO
509 #define char_insert(a,b,c) char_insert(a,b)
511 static char *char_insert(char *, char, int); // insert the char c at 'p'
512 // might reallocate text[]! use p += stupid_insert(p, ...),
513 // and be careful to not use pointers into potentially freed text[]!
514 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
515 static int find_range(char **, char **, char); // return pointers for an object
516 static int st_test(char *, int, int, char *); // helper for skip_thing()
517 static char *skip_thing(char *, int, int, int); // skip some object
518 static char *find_pair(char *, char); // find matching pair () [] {}
519 #if !ENABLE_FEATURE_VI_UNDO
520 #define text_hole_delete(a,b,c) text_hole_delete(a,b)
522 static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
523 // might reallocate text[]! use p += text_hole_make(p, ...),
524 // and be careful to not use pointers into potentially freed text[]!
525 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
526 #if !ENABLE_FEATURE_VI_UNDO
527 #define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
529 static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
530 static void show_help(void); // display some help info
531 static void rawmode(void); // set "raw" mode on tty
532 static void cookmode(void); // return to "cooked" mode on tty
533 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
534 static int mysleep(int);
535 static int get_one_char(void); // read 1 char from stdin
536 // file_insert might reallocate text[]!
537 static int file_insert(const char *, char *, int);
538 static int file_write(char *, char *, char *);
539 static void screen_erase(void);
540 static void go_bottom_and_clear_to_eol(void);
541 static void standout_start(void); // send "start reverse video" sequence
542 static void standout_end(void); // send "end reverse video" sequence
543 static void flash(int); // flash the terminal screen
544 static void show_status_line(void); // put a message on the bottom line
545 static void status_line(const char *, ...); // print to status buf
546 static void status_line_bold(const char *, ...);
547 static void status_line_bold_errno(const char *fn);
548 static void not_implemented(const char *); // display "Not implemented" message
549 static int format_edit_status(void); // format file status on status line
550 static void redraw(int); // force a full screen refresh
551 static char* format_line(char* /*, int*/);
552 static void refresh(int); // update the terminal from screen[]
554 static void indicate_error(void); // use flash or beep to indicate error
555 static void Hit_Return(void);
557 #if ENABLE_FEATURE_VI_SEARCH
558 static char *char_search(char *, const char *, int); // search for pattern starting at p
560 #if ENABLE_FEATURE_VI_COLON
561 static char *get_one_address(char *, int *); // get colon addr, if present
562 static char *get_address(char *, int *, int *); // get two colon addrs, if present
564 static void colon(char *); // execute the "colon" mode cmds
565 #if ENABLE_FEATURE_VI_USE_SIGNALS
566 static void winch_handler(int); // catch window size changes
567 static void tstp_handler(int); // catch ctrl-Z
568 static void int_handler(int); // catch ctrl-C
570 #if ENABLE_FEATURE_VI_DOT_CMD
571 static void start_new_cmd_q(char); // new queue for command
572 static void end_cmd_q(void); // stop saving input chars
574 #define end_cmd_q() ((void)0)
576 #if ENABLE_FEATURE_VI_SETOPTS
577 static void showmatching(char *); // show the matching pair () [] {}
579 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
580 // might reallocate text[]! use p += string_insert(p, ...),
581 // and be careful to not use pointers into potentially freed text[]!
582 # if !ENABLE_FEATURE_VI_UNDO
583 #define string_insert(a,b,c) string_insert(a,b)
585 static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
587 #if ENABLE_FEATURE_VI_YANKMARK
588 static char *text_yank(char *, char *, int); // save copy of "p" into a register
589 static char what_reg(void); // what is letter of current YDreg
590 static void check_context(char); // remember context for '' command
592 #if ENABLE_FEATURE_VI_UNDO
593 static void flush_undo_data(void);
594 static void undo_push(char *, unsigned, unsigned char); // push an operation on the undo stack
595 static void undo_push_insert(char *, int, int); // convenience function
596 static void undo_pop(void); // undo the last operation
597 # if ENABLE_FEATURE_VI_UNDO_QUEUE
598 static void undo_queue_commit(void); // flush any queued objects to the undo stack
600 # define undo_queue_commit() ((void)0)
603 #define flush_undo_data() ((void)0)
604 #define undo_queue_commit() ((void)0)
607 #if ENABLE_FEATURE_VI_CRASHME
608 static void crash_dummy();
609 static void crash_test();
610 static int crashme = 0;
613 static void write1(const char *out)
618 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
619 int vi_main(int argc, char **argv)
625 #if ENABLE_FEATURE_VI_UNDO
626 /* undo_stack_tail = NULL; - already is */
627 #if ENABLE_FEATURE_VI_UNDO_QUEUE
628 undo_queue_state = UNDO_EMPTY;
629 /* undo_q = 0; - already is */
633 #if ENABLE_FEATURE_VI_CRASHME
634 srand((long) getpid());
636 #ifdef NO_SUCH_APPLET_YET
637 // if we aren't "vi", we are "view"
638 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
639 SET_READONLY_MODE(readonly_mode);
643 // autoindent is not default in vim 7.3
644 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
645 // 1- process $HOME/.exrc file (not inplemented yet)
646 // 2- process EXINIT variable from environment
647 // 3- process command line args
648 #if ENABLE_FEATURE_VI_COLON
650 char *p = getenv("EXINIT");
652 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
655 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
657 #if ENABLE_FEATURE_VI_CRASHME
662 #if ENABLE_FEATURE_VI_READONLY
663 case 'R': // Read-only flag
664 SET_READONLY_MODE(readonly_mode);
667 #if ENABLE_FEATURE_VI_COLON
668 case 'c': // cmd line vi command
670 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
682 // The argv array can be used by the ":next" and ":rewind" commands
686 //----- This is the main file handling loop --------------
689 // "Save cursor, use alternate screen buffer, clear screen"
690 write1(ESC"[?1049h");
692 edit_file(argv[optind]); // param might be NULL
693 if (++optind >= argc)
696 // "Use normal screen buffer, restore cursor"
697 write1(ESC"[?1049l");
698 //-----------------------------------------------------------
703 /* read text from file or create an empty buf */
704 /* will also update current_filename */
705 static int init_text_buffer(char *fn)
709 /* allocate/reallocate text buffer */
712 screenbegin = dot = end = text = xzalloc(text_size);
714 if (fn != current_filename) {
715 free(current_filename);
716 current_filename = xstrdup(fn);
718 rc = file_insert(fn, text, 1);
720 // file doesnt exist. Start empty buf with dummy line
721 char_insert(text, '\n', NO_UNDO);
726 last_modified_count = -1;
727 #if ENABLE_FEATURE_VI_YANKMARK
729 memset(mark, 0, sizeof(mark));
734 #if ENABLE_FEATURE_VI_WIN_RESIZE
735 static int query_screen_dimensions(void)
737 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
738 if (rows > MAX_SCR_ROWS)
740 if (columns > MAX_SCR_COLS)
741 columns = MAX_SCR_COLS;
745 static ALWAYS_INLINE int query_screen_dimensions(void)
751 static void edit_file(char *fn)
753 #if ENABLE_FEATURE_VI_YANKMARK
754 #define cur_line edit_file__cur_line
757 #if ENABLE_FEATURE_VI_USE_SIGNALS
761 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
765 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
766 #if ENABLE_FEATURE_VI_ASK_TERMINAL
767 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
769 write1(ESC"[999;999H" ESC"[6n");
771 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
772 if ((int32_t)k == KEYCODE_CURSOR_POS) {
773 uint32_t rc = (k >> 32);
774 columns = (rc & 0x7fff);
775 if (columns > MAX_SCR_COLS)
776 columns = MAX_SCR_COLS;
777 rows = ((rc >> 16) & 0x7fff);
778 if (rows > MAX_SCR_ROWS)
783 new_screen(rows, columns); // get memory for virtual screen
784 init_text_buffer(fn);
786 #if ENABLE_FEATURE_VI_YANKMARK
787 YDreg = 26; // default Yank/Delete reg
788 // Ureg = 27; - const // hold orig line for "U" cmd
789 mark[26] = mark[27] = text; // init "previous context"
792 last_forward_char = last_input_char = '\0';
796 #if ENABLE_FEATURE_VI_USE_SIGNALS
797 signal(SIGWINCH, winch_handler);
798 signal(SIGTSTP, tstp_handler);
799 sig = sigsetjmp(restart, 1);
801 screenbegin = dot = text;
803 // int_handler() can jump to "restart",
804 // must install handler *after* initializing "restart"
805 signal(SIGINT, int_handler);
808 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
811 offset = 0; // no horizontal offset
813 #if ENABLE_FEATURE_VI_DOT_CMD
815 ioq = ioq_start = NULL;
820 #if ENABLE_FEATURE_VI_COLON
825 while ((p = initial_cmds[n]) != NULL) {
835 free(initial_cmds[n]);
836 initial_cmds[n] = NULL;
841 redraw(FALSE); // dont force every col re-draw
842 //------This is the main Vi cmd handling loop -----------------------
843 while (editing > 0) {
844 #if ENABLE_FEATURE_VI_CRASHME
846 if ((end - text) > 1) {
847 crash_dummy(); // generate a random command
850 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n", NO_UNDO); // insert the string
856 last_input_char = c = get_one_char(); // get a cmd from user
857 #if ENABLE_FEATURE_VI_YANKMARK
858 // save a copy of the current line- for the 'U" command
859 if (begin_line(dot) != cur_line) {
860 cur_line = begin_line(dot);
861 text_yank(begin_line(dot), end_line(dot), Ureg);
864 #if ENABLE_FEATURE_VI_DOT_CMD
865 // These are commands that change text[].
866 // Remember the input for the "." command
867 if (!adding2q && ioq_start == NULL
868 && cmd_mode == 0 // command mode
869 && c > '\0' // exclude NUL and non-ASCII chars
870 && c < 0x7f // (Unicode and such)
871 && strchr(modifying_cmds, c)
876 do_cmd(c); // execute the user command
878 // poll to see if there is input already waiting. if we are
879 // not able to display output fast enough to keep up, skip
880 // the display update until we catch up with input.
881 if (!readbuffer[0] && mysleep(0) == 0) {
882 // no input pending - so update output
886 #if ENABLE_FEATURE_VI_CRASHME
888 crash_test(); // test editor variables
891 //-------------------------------------------------------------------
893 go_bottom_and_clear_to_eol();
898 //----- The Colon commands -------------------------------------
899 #if ENABLE_FEATURE_VI_COLON
900 static char *get_one_address(char *p, int *addr) // get colon addr, if present
904 IF_FEATURE_VI_YANKMARK(char c;)
905 IF_FEATURE_VI_SEARCH(char *pat;)
907 *addr = -1; // assume no addr
908 if (*p == '.') { // the current line
911 *addr = count_lines(text, q);
913 #if ENABLE_FEATURE_VI_YANKMARK
914 else if (*p == '\'') { // is this a mark addr
918 if (c >= 'a' && c <= 'z') {
921 q = mark[(unsigned char) c];
922 if (q != NULL) { // is mark valid
923 *addr = count_lines(text, q);
928 #if ENABLE_FEATURE_VI_SEARCH
929 else if (*p == '/') { // a search pattern
930 q = strchrnul(++p, '/');
931 pat = xstrndup(p, q - p); // save copy of pattern
935 q = char_search(dot, pat, (FORWARD << 1) | FULL);
937 *addr = count_lines(text, q);
942 else if (*p == '$') { // the last line in file
944 q = begin_line(end - 1);
945 *addr = count_lines(text, q);
946 } else if (isdigit(*p)) { // specific line number
947 sscanf(p, "%d%n", addr, &st);
950 // unrecognized address - assume -1
956 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
958 //----- get the address' i.e., 1,3 'a,'b -----
959 // get FIRST addr, if present
961 p++; // skip over leading spaces
962 if (*p == '%') { // alias for 1,$
965 *e = count_lines(text, end-1);
968 p = get_one_address(p, b);
971 if (*p == ',') { // is there a address separator
975 // get SECOND addr, if present
976 p = get_one_address(p, e);
980 p++; // skip over trailing spaces
984 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
985 static void setops(const char *args, const char *opname, int flg_no,
986 const char *short_opname, int opt)
988 const char *a = args + flg_no;
989 int l = strlen(opname) - 1; // opname have + ' '
991 // maybe strncmp? we had tons of erroneous strncasecmp's...
992 if (strncasecmp(a, opname, l) == 0
993 || strncasecmp(a, short_opname, 2) == 0
1003 #endif /* FEATURE_VI_COLON */
1005 // buf must be no longer than MAX_INPUT_LEN!
1006 static void colon(char *buf)
1008 #if !ENABLE_FEATURE_VI_COLON
1009 // Simple ":cmd" handler with minimal set of commands
1018 if (strncmp(p, "quit", cnt) == 0
1019 || strncmp(p, "q!", cnt) == 0
1021 if (modified_count && p[1] != '!') {
1022 status_line_bold("No write since last change (:%s! overrides)", p);
1028 if (strncmp(p, "write", cnt) == 0
1029 || strncmp(p, "wq", cnt) == 0
1030 || strncmp(p, "wn", cnt) == 0
1031 || (p[0] == 'x' && !p[1])
1033 if (modified_count != 0 || p[0] != 'x') {
1034 cnt = file_write(current_filename, text, end - 1);
1038 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
1041 last_modified_count = -1;
1042 status_line("'%s' %dL, %dC",
1044 count_lines(text, end - 1), cnt
1047 || p[1] == 'q' || p[1] == 'n'
1048 || p[1] == 'Q' || p[1] == 'N'
1055 if (strncmp(p, "file", cnt) == 0) {
1056 last_status_cksum = 0; // force status update
1059 if (sscanf(p, "%d", &cnt) > 0) {
1060 dot = find_line(cnt);
1067 char c, *buf1, *q, *r;
1068 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
1071 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
1075 // :3154 // if (-e line 3154) goto it else stay put
1076 // :4,33w! foo // write a portion of buffer to file "foo"
1077 // :w // write all of buffer to current file
1079 // :q! // quit- dont care about modified file
1080 // :'a,'z!sort -u // filter block through sort
1081 // :'f // goto mark "f"
1082 // :'fl // list literal the mark "f" line
1083 // :.r bar // read file "bar" into buffer before dot
1084 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1085 // :/xyz/ // goto the "xyz" line
1086 // :s/find/replace/ // substitute pattern "find" with "replace"
1087 // :!<cmd> // run <cmd> then return
1093 buf++; // move past the ':'
1097 q = text; // assume 1,$ for the range
1099 li = count_lines(text, end - 1);
1100 fn = current_filename;
1102 // look for optional address(es) :. :1 :1,9 :'q,'a :%
1103 buf = get_address(buf, &b, &e);
1105 # if ENABLE_FEATURE_VI_SEARCH || ENABLE_FEATURE_ALLOW_EXEC
1106 // remember orig command line
1110 // get the COMMAND into cmd[]
1112 while (*buf != '\0') {
1118 // get any ARGuments
1119 while (isblank(*buf))
1123 buf1 = last_char_is(cmd, '!');
1126 *buf1 = '\0'; // get rid of !
1129 // if there is only one addr, then the addr
1130 // is the line number of the single line the
1131 // user wants. So, reset the end
1132 // pointer to point at end of the "b" line
1133 q = find_line(b); // what line is #b
1138 // we were given two addrs. change the
1139 // end pointer to the addr given by user.
1140 r = find_line(e); // what line is #e
1144 // ------------ now look for the command ------------
1146 if (i == 0) { // :123CR goto line #123
1148 dot = find_line(b); // what line is #b
1152 # if ENABLE_FEATURE_ALLOW_EXEC
1153 else if (cmd[0] == '!') { // run a cmd
1155 // :!ls run the <cmd>
1156 go_bottom_and_clear_to_eol();
1158 retcode = system(orig_buf + 1); // run the cmd
1160 printf("\nshell returned %i\n\n", retcode);
1162 Hit_Return(); // let user see results
1165 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1166 if (b < 0) { // no addr given- use defaults
1167 b = e = count_lines(text, dot);
1169 status_line("%d", b);
1170 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1171 if (b < 0) { // no addr given- use defaults
1172 q = begin_line(dot); // assume .,. for the range
1175 dot = yank_delete(q, r, 1, YANKDEL, ALLOW_UNDO); // save, then delete lines
1177 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1180 // don't edit, if the current file has been modified
1181 if (modified_count && !useforce) {
1182 status_line_bold("No write since last change (:%s! overrides)", cmd);
1186 // the user supplied a file name
1188 } else if (current_filename && current_filename[0]) {
1189 // no user supplied name- use the current filename
1190 // fn = current_filename; was set by default
1192 // no user file name, no current name- punt
1193 status_line_bold("No current filename");
1197 size = init_text_buffer(fn);
1199 # if ENABLE_FEATURE_VI_YANKMARK
1200 if (Ureg >= 0 && Ureg < 28) {
1201 free(reg[Ureg]); // free orig line reg- for 'U'
1204 if (YDreg >= 0 && YDreg < 28) {
1205 free(reg[YDreg]); // free default yank/delete register
1209 // how many lines in text[]?
1210 li = count_lines(text, end - 1);
1211 status_line("'%s'%s"
1212 IF_FEATURE_VI_READONLY("%s")
1215 (size < 0 ? " [New file]" : ""),
1216 IF_FEATURE_VI_READONLY(
1217 ((readonly_mode) ? " [Readonly]" : ""),
1219 li, (int)(end - text)
1221 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1222 if (b != -1 || e != -1) {
1223 status_line_bold("No address allowed on this command");
1227 // user wants a new filename
1228 free(current_filename);
1229 current_filename = xstrdup(args);
1231 // user wants file status info
1232 last_status_cksum = 0; // force status update
1234 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1235 // print out values of all features
1236 go_bottom_and_clear_to_eol();
1241 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1242 if (b < 0) { // no addr given- use defaults
1243 q = begin_line(dot); // assume .,. for the range
1246 go_bottom_and_clear_to_eol();
1248 for (; q <= r; q++) {
1252 c_is_no_print = (c & 0x80) && !Isprint(c);
1253 if (c_is_no_print) {
1259 } else if (c < ' ' || c == 127) {
1271 } else if (strncmp(cmd, "quit", i) == 0 // quit
1272 || strncmp(cmd, "next", i) == 0 // edit next file
1273 || strncmp(cmd, "prev", i) == 0 // edit previous file
1278 // force end of argv list
1284 // don't exit if the file been modified
1285 if (modified_count) {
1286 status_line_bold("No write since last change (:%s! overrides)", cmd);
1289 // are there other file to edit
1290 n = save_argc - optind - 1;
1291 if (*cmd == 'q' && n > 0) {
1292 status_line_bold("%d more file(s) to edit", n);
1295 if (*cmd == 'n' && n <= 0) {
1296 status_line_bold("No more files to edit");
1300 // are there previous files to edit
1302 status_line_bold("No previous files to edit");
1308 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1313 status_line_bold("No filename given");
1316 if (b < 0) { // no addr given- use defaults
1317 q = begin_line(dot); // assume "dot"
1319 // read after current line- unless user said ":0r foo"
1322 // read after last line
1326 { // dance around potentially-reallocated text[]
1327 uintptr_t ofs = q - text;
1328 size = file_insert(fn, q, 0);
1332 goto ret; // nothing was inserted
1333 // how many lines in text[]?
1334 li = count_lines(q, q + size - 1);
1336 IF_FEATURE_VI_READONLY("%s")
1339 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1343 // if the insert is before "dot" then we need to update
1347 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1348 if (modified_count && !useforce) {
1349 status_line_bold("No write since last change (:%s! overrides)", cmd);
1351 // reset the filenames to edit
1352 optind = -1; // start from 0th file
1355 # if ENABLE_FEATURE_VI_SET
1356 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1357 # if ENABLE_FEATURE_VI_SETOPTS
1360 i = 0; // offset into args
1361 // only blank is regarded as args delimiter. What about tab '\t'?
1362 if (!args[0] || strcasecmp(args, "all") == 0) {
1363 // print out values of all options
1364 # if ENABLE_FEATURE_VI_SETOPTS
1371 autoindent ? "" : "no",
1372 err_method ? "" : "no",
1373 ignorecase ? "" : "no",
1374 showmatch ? "" : "no",
1380 # if ENABLE_FEATURE_VI_SETOPTS
1383 if (strncmp(argp, "no", 2) == 0)
1384 i = 2; // ":set noautoindent"
1385 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1386 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1387 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1388 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1389 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1391 sscanf(argp + i+8, "%u", &t);
1392 if (t > 0 && t <= MAX_TABSTOP)
1395 argp = skip_non_whitespace(argp);
1396 argp = skip_whitespace(argp);
1398 # endif /* FEATURE_VI_SETOPTS */
1399 # endif /* FEATURE_VI_SET */
1401 # if ENABLE_FEATURE_VI_SEARCH
1402 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1403 char *F, *R, *flags;
1404 size_t len_F, len_R;
1405 int gflag; // global replace flag
1406 # if ENABLE_FEATURE_VI_UNDO
1407 int dont_chain_first_item = ALLOW_UNDO;
1410 // F points to the "find" pattern
1411 // R points to the "replace" pattern
1412 // replace the cmd line delimiters "/" with NULs
1413 c = orig_buf[1]; // what is the delimiter
1414 F = orig_buf + 2; // start of "find"
1415 R = strchr(F, c); // middle delimiter
1419 *R++ = '\0'; // terminate "find"
1420 flags = strchr(R, c);
1424 *flags++ = '\0'; // terminate "replace"
1428 if (b < 0) { // maybe :s/foo/bar/
1429 q = begin_line(dot); // start with cur line
1430 b = count_lines(text, q); // cur line number
1433 e = b; // maybe :.s/foo/bar/
1435 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1436 char *ls = q; // orig line start
1439 found = char_search(q, F, (FORWARD << 1) | LIMITED); // search cur line only for "find"
1442 // we found the "find" pattern - delete it
1443 // For undo support, the first item should not be chained
1444 text_hole_delete(found, found + len_F - 1, dont_chain_first_item);
1445 # if ENABLE_FEATURE_VI_UNDO
1446 dont_chain_first_item = ALLOW_UNDO_CHAIN;
1448 // insert the "replace" patern
1449 bias = string_insert(found, R, ALLOW_UNDO_CHAIN);
1452 /*q += bias; - recalculated anyway */
1453 // check for "global" :s/foo/bar/g
1455 if ((found + len_R) < end_line(ls)) {
1457 goto vc4; // don't let q move past cur line
1463 # endif /* FEATURE_VI_SEARCH */
1464 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1465 status_line(BB_VER);
1466 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1467 || strncmp(cmd, "wq", i) == 0
1468 || strncmp(cmd, "wn", i) == 0
1469 || (cmd[0] == 'x' && !cmd[1])
1472 //int forced = FALSE;
1474 // is there a file name to write to?
1478 # if ENABLE_FEATURE_VI_READONLY
1479 if (readonly_mode && !useforce) {
1480 status_line_bold("'%s' is read only", fn);
1485 // if "fn" is not write-able, chmod u+w
1486 // sprintf(syscmd, "chmod u+w %s", fn);
1490 if (modified_count != 0 || cmd[0] != 'x') {
1492 l = file_write(fn, q, r);
1497 //if (useforce && forced) {
1499 // sprintf(syscmd, "chmod u-w %s", fn);
1505 status_line_bold_errno(fn);
1507 // how many lines written
1508 li = count_lines(q, q + l - 1);
1509 status_line("'%s' %dL, %dC", fn, li, l);
1511 if (q == text && q + l == end) {
1513 last_modified_count = -1;
1516 || cmd[1] == 'q' || cmd[1] == 'n'
1517 || cmd[1] == 'Q' || cmd[1] == 'N'
1523 # if ENABLE_FEATURE_VI_YANKMARK
1524 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1525 if (b < 0) { // no addr given- use defaults
1526 q = begin_line(dot); // assume .,. for the range
1529 text_yank(q, r, YDreg);
1530 li = count_lines(q, r);
1531 status_line("Yank %d lines (%d chars) into [%c]",
1532 li, strlen(reg[YDreg]), what_reg());
1536 not_implemented(cmd);
1539 dot = bound_dot(dot); // make sure "dot" is valid
1541 # if ENABLE_FEATURE_VI_SEARCH
1543 status_line(":s expression missing delimiters");
1545 #endif /* FEATURE_VI_COLON */
1548 static void Hit_Return(void)
1553 write1("[Hit return to continue]");
1555 while ((c = get_one_char()) != '\n' && c != '\r')
1557 redraw(TRUE); // force redraw all
1560 static int next_tabstop(int col)
1562 return col + ((tabstop - 1) - (col % tabstop));
1565 //----- Synchronize the cursor to Dot --------------------------
1566 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1568 char *beg_cur; // begin and end of "d" line
1572 beg_cur = begin_line(d); // first char of cur line
1574 if (beg_cur < screenbegin) {
1575 // "d" is before top line on screen
1576 // how many lines do we have to move
1577 cnt = count_lines(beg_cur, screenbegin);
1579 screenbegin = beg_cur;
1580 if (cnt > (rows - 1) / 2) {
1581 // we moved too many lines. put "dot" in middle of screen
1582 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1583 screenbegin = prev_line(screenbegin);
1587 char *end_scr; // begin and end of screen
1588 end_scr = end_screen(); // last char of screen
1589 if (beg_cur > end_scr) {
1590 // "d" is after bottom line on screen
1591 // how many lines do we have to move
1592 cnt = count_lines(end_scr, beg_cur);
1593 if (cnt > (rows - 1) / 2)
1594 goto sc1; // too many lines
1595 for (ro = 0; ro < cnt - 1; ro++) {
1596 // move screen begin the same amount
1597 screenbegin = next_line(screenbegin);
1598 // now, move the end of screen
1599 end_scr = next_line(end_scr);
1600 end_scr = end_line(end_scr);
1604 // "d" is on screen- find out which row
1606 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1612 // find out what col "d" is on
1614 while (tp < d) { // drive "co" to correct column
1615 if (*tp == '\n') //vda || *tp == '\0')
1618 // handle tabs like real vi
1619 if (d == tp && cmd_mode) {
1622 co = next_tabstop(co);
1623 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1624 co++; // display as ^X, use 2 columns
1630 // "co" is the column where "dot" is.
1631 // The screen has "columns" columns.
1632 // The currently displayed columns are 0+offset -- columns+ofset
1633 // |-------------------------------------------------------------|
1635 // offset | |------- columns ----------------|
1637 // If "co" is already in this range then we do not have to adjust offset
1638 // but, we do have to subtract the "offset" bias from "co".
1639 // If "co" is outside this range then we have to change "offset".
1640 // If the first char of a line is a tab the cursor will try to stay
1641 // in column 7, but we have to set offset to 0.
1643 if (co < 0 + offset) {
1646 if (co >= columns + offset) {
1647 offset = co - columns + 1;
1649 // if the first char of the line is a tab, and "dot" is sitting on it
1650 // force offset to 0.
1651 if (d == beg_cur && *d == '\t') {
1660 //----- Text Movement Routines ---------------------------------
1661 static char *begin_line(char *p) // return pointer to first char cur line
1664 p = memrchr(text, '\n', p - text);
1672 static char *end_line(char *p) // return pointer to NL of cur line
1675 p = memchr(p, '\n', end - p - 1);
1682 static char *dollar_line(char *p) // return pointer to just before NL line
1685 // Try to stay off of the Newline
1686 if (*p == '\n' && (p - begin_line(p)) > 0)
1691 static char *prev_line(char *p) // return pointer first char prev line
1693 p = begin_line(p); // goto beginning of cur line
1694 if (p > text && p[-1] == '\n')
1695 p--; // step to prev line
1696 p = begin_line(p); // goto beginning of prev line
1700 static char *next_line(char *p) // return pointer first char next line
1703 if (p < end - 1 && *p == '\n')
1704 p++; // step to next line
1708 //----- Text Information Routines ------------------------------
1709 static char *end_screen(void)
1714 // find new bottom line
1716 for (cnt = 0; cnt < rows - 2; cnt++)
1722 // count line from start to stop
1723 static int count_lines(char *start, char *stop)
1728 if (stop < start) { // start and stop are backwards- reverse them
1734 stop = end_line(stop);
1735 while (start <= stop && start <= end - 1) {
1736 start = end_line(start);
1744 static char *find_line(int li) // find beginning of line #li
1748 for (q = text; li > 1; li--) {
1754 //----- Dot Movement Routines ----------------------------------
1755 static void dot_left(void)
1757 undo_queue_commit();
1758 if (dot > text && dot[-1] != '\n')
1762 static void dot_right(void)
1764 undo_queue_commit();
1765 if (dot < end - 1 && *dot != '\n')
1769 static void dot_begin(void)
1771 undo_queue_commit();
1772 dot = begin_line(dot); // return pointer to first char cur line
1775 static void dot_end(void)
1777 undo_queue_commit();
1778 dot = end_line(dot); // return pointer to last char cur line
1781 static char *move_to_col(char *p, int l)
1787 while (co < l && p < end) {
1788 if (*p == '\n') //vda || *p == '\0')
1791 co = next_tabstop(co);
1792 } else if (*p < ' ' || *p == 127) {
1793 co++; // display as ^X, use 2 columns
1801 static void dot_next(void)
1803 undo_queue_commit();
1804 dot = next_line(dot);
1807 static void dot_prev(void)
1809 undo_queue_commit();
1810 dot = prev_line(dot);
1813 static void dot_scroll(int cnt, int dir)
1817 undo_queue_commit();
1818 for (; cnt > 0; cnt--) {
1821 // ctrl-Y scroll up one line
1822 screenbegin = prev_line(screenbegin);
1825 // ctrl-E scroll down one line
1826 screenbegin = next_line(screenbegin);
1829 // make sure "dot" stays on the screen so we dont scroll off
1830 if (dot < screenbegin)
1832 q = end_screen(); // find new bottom line
1834 dot = begin_line(q); // is dot is below bottom line?
1838 static void dot_skip_over_ws(void)
1841 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1845 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1847 if (p >= end && end > text) {
1858 //----- Helper Utility Routines --------------------------------
1860 //----------------------------------------------------------------
1861 //----- Char Routines --------------------------------------------
1862 /* Chars that are part of a word-
1863 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1864 * Chars that are Not part of a word (stoppers)
1865 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1866 * Chars that are WhiteSpace
1867 * TAB NEWLINE VT FF RETURN SPACE
1868 * DO NOT COUNT NEWLINE AS WHITESPACE
1871 static char *new_screen(int ro, int co)
1876 screensize = ro * co + 8;
1877 screen = xmalloc(screensize);
1878 // initialize the new screen. assume this will be a empty file.
1880 // non-existent text[] lines start with a tilde (~).
1881 for (li = 1; li < ro - 1; li++) {
1882 screen[(li * co) + 0] = '~';
1887 #if ENABLE_FEATURE_VI_SEARCH
1889 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1891 // search for pattern starting at p
1892 static char *char_search(char *p, const char *pat, int dir_and_range)
1894 struct re_pattern_buffer preg;
1901 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1903 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1905 memset(&preg, 0, sizeof(preg));
1906 err = re_compile_pattern(pat, strlen(pat), &preg);
1908 status_line_bold("bad search pattern '%s': %s", pat, err);
1912 range = (dir_and_range & 1);
1913 q = end - 1; // if FULL
1914 if (range == LIMITED)
1916 if (dir_and_range < 0) { // BACK?
1918 if (range == LIMITED)
1922 // RANGE could be negative if we are searching backwards
1932 // search for the compiled pattern, preg, in p[]
1933 // range < 0: search backward
1934 // range > 0: search forward
1936 // re_search() < 0: not found or error
1937 // re_search() >= 0: index of found pattern
1938 // struct pattern char int int int struct reg
1939 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1940 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1944 if (dir_and_range > 0) // FORWARD?
1953 # if ENABLE_FEATURE_VI_SETOPTS
1954 static int mycmp(const char *s1, const char *s2, int len)
1957 return strncasecmp(s1, s2, len);
1959 return strncmp(s1, s2, len);
1962 # define mycmp strncmp
1965 static char *char_search(char *p, const char *pat, int dir_and_range)
1972 range = (dir_and_range & 1);
1973 if (dir_and_range > 0) { //FORWARD?
1974 stop = end - 1; // assume range is p..end-1
1975 if (range == LIMITED)
1976 stop = next_line(p); // range is to next line
1977 for (start = p; start < stop; start++) {
1978 if (mycmp(start, pat, len) == 0) {
1983 stop = text; // assume range is text..p
1984 if (range == LIMITED)
1985 stop = prev_line(p); // range is to prev line
1986 for (start = p - len; start >= stop; start--) {
1987 if (mycmp(start, pat, len) == 0) {
1992 // pattern not found
1998 #endif /* FEATURE_VI_SEARCH */
2000 static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
2002 if (c == 22) { // Is this an ctrl-V?
2003 p += stupid_insert(p, '^'); // use ^ to indicate literal next
2004 refresh(FALSE); // show the ^
2007 #if ENABLE_FEATURE_VI_UNDO
2008 undo_push_insert(p, 1, undo);
2011 #endif /* ENABLE_FEATURE_VI_UNDO */
2013 } else if (c == 27) { // Is this an ESC?
2015 undo_queue_commit();
2017 end_cmd_q(); // stop adding to q
2018 last_status_cksum = 0; // force status update
2019 if ((p[-1] != '\n') && (dot > text)) {
2022 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
2025 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
2028 // insert a char into text[]
2030 c = '\n'; // translate \r to \n
2031 #if ENABLE_FEATURE_VI_UNDO
2032 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2034 undo_queue_commit();
2036 undo_push_insert(p, 1, undo);
2039 #endif /* ENABLE_FEATURE_VI_UNDO */
2040 p += 1 + stupid_insert(p, c); // insert the char
2041 #if ENABLE_FEATURE_VI_SETOPTS
2042 if (showmatch && strchr(")]}", c) != NULL) {
2043 showmatching(p - 1);
2045 if (autoindent && c == '\n') { // auto indent the new line
2048 q = prev_line(p); // use prev line as template
2049 len = strspn(q, " \t"); // space or tab
2052 bias = text_hole_make(p, len);
2055 #if ENABLE_FEATURE_VI_UNDO
2056 undo_push_insert(p, len, undo);
2067 // might reallocate text[]! use p += stupid_insert(p, ...),
2068 // and be careful to not use pointers into potentially freed text[]!
2069 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
2072 bias = text_hole_make(p, 1);
2078 static int find_range(char **start, char **stop, char c)
2080 char *save_dot, *p, *q, *t;
2081 int cnt, multiline = 0;
2086 if (strchr("cdy><", c)) {
2087 // these cmds operate on whole lines
2088 p = q = begin_line(p);
2089 for (cnt = 1; cnt < cmdcnt; cnt++) {
2093 } else if (strchr("^%$0bBeEfth\b\177", c)) {
2094 // These cmds operate on char positions
2095 do_cmd(c); // execute movement cmd
2097 } else if (strchr("wW", c)) {
2098 do_cmd(c); // execute movement cmd
2099 // if we are at the next word's first char
2100 // step back one char
2101 // but check the possibilities when it is true
2102 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
2103 || (ispunct(dot[-1]) && !ispunct(dot[0]))
2104 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
2105 dot--; // move back off of next word
2106 if (dot > text && *dot == '\n')
2107 dot--; // stay off NL
2109 } else if (strchr("H-k{", c)) {
2110 // these operate on multi-lines backwards
2111 q = end_line(dot); // find NL
2112 do_cmd(c); // execute movement cmd
2115 } else if (strchr("L+j}\r\n", c)) {
2116 // these operate on multi-lines forwards
2117 p = begin_line(dot);
2118 do_cmd(c); // execute movement cmd
2119 dot_end(); // find NL
2122 // nothing -- this causes any other values of c to
2123 // represent the one-character range under the
2124 // cursor. this is correct for ' ' and 'l', but
2125 // perhaps no others.
2134 // backward char movements don't include start position
2135 if (q > p && strchr("^0bBh\b\177", c)) q--;
2138 for (t = p; t <= q; t++) {
2151 static int st_test(char *p, int type, int dir, char *tested)
2161 if (type == S_BEFORE_WS) {
2163 test = (!isspace(c) || c == '\n');
2165 if (type == S_TO_WS) {
2167 test = (!isspace(c) || c == '\n');
2169 if (type == S_OVER_WS) {
2173 if (type == S_END_PUNCT) {
2177 if (type == S_END_ALNUM) {
2179 test = (isalnum(c) || c == '_');
2185 static char *skip_thing(char *p, int linecnt, int dir, int type)
2189 while (st_test(p, type, dir, &c)) {
2190 // make sure we limit search to correct number of lines
2191 if (c == '\n' && --linecnt < 1)
2193 if (dir >= 0 && p >= end - 1)
2195 if (dir < 0 && p <= text)
2197 p += dir; // move to next char
2202 // find matching char of pair () [] {}
2203 // will crash if c is not one of these
2204 static char *find_pair(char *p, const char c)
2206 const char *braces = "()[]{}";
2210 dir = strchr(braces, c) - braces;
2212 match = braces[dir];
2213 dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
2215 // look for match, count levels of pairs (( ))
2219 if (p < text || p >= end)
2222 level++; // increase pair levels
2224 level--; // reduce pair level
2226 return p; // found matching pair
2231 #if ENABLE_FEATURE_VI_SETOPTS
2232 // show the matching char of a pair, () [] {}
2233 static void showmatching(char *p)
2237 // we found half of a pair
2238 q = find_pair(p, *p); // get loc of matching char
2240 indicate_error(); // no matching char
2242 // "q" now points to matching pair
2243 save_dot = dot; // remember where we are
2244 dot = q; // go to new loc
2245 refresh(FALSE); // let the user see it
2246 mysleep(40); // give user some time
2247 dot = save_dot; // go back to old loc
2251 #endif /* FEATURE_VI_SETOPTS */
2253 #if ENABLE_FEATURE_VI_UNDO
2254 static void flush_undo_data(void)
2256 struct undo_object *undo_entry;
2258 while (undo_stack_tail) {
2259 undo_entry = undo_stack_tail;
2260 undo_stack_tail = undo_entry->prev;
2265 // Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2266 // Add to the undo stack
2267 static void undo_push(char *src, unsigned length, uint8_t u_type)
2269 struct undo_object *undo_entry;
2272 // UNDO_INS: insertion, undo will remove from buffer
2273 // UNDO_DEL: deleted text, undo will restore to buffer
2274 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2275 // The CHAIN operations are for handling multiple operations that the user
2276 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2277 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2278 // for the INS/DEL operation. The raw values should be equal to the values of
2279 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2281 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2282 // This undo queuing functionality groups multiple character typing or backspaces
2283 // into a single large undo object. This greatly reduces calls to malloc() for
2284 // single-character operations while typing and has the side benefit of letting
2285 // an undo operation remove chunks of text rather than a single character.
2287 case UNDO_EMPTY: // Just in case this ever happens...
2289 case UNDO_DEL_QUEUED:
2291 return; // Only queue single characters
2292 switch (undo_queue_state) {
2294 undo_queue_state = UNDO_DEL;
2296 undo_queue_spos = src;
2298 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2299 // If queue is full, dump it into an object
2300 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2301 undo_queue_commit();
2304 // Switch from storing inserted text to deleted text
2305 undo_queue_commit();
2306 undo_push(src, length, UNDO_DEL_QUEUED);
2310 case UNDO_INS_QUEUED:
2313 switch (undo_queue_state) {
2315 undo_queue_state = UNDO_INS;
2316 undo_queue_spos = src;
2319 undo_q++; // Don't need to save any data for insertions
2320 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2321 undo_queue_commit();
2325 // Switch from storing deleted text to inserted text
2326 undo_queue_commit();
2327 undo_push(src, length, UNDO_INS_QUEUED);
2333 // If undo queuing is disabled, ignore the queuing flag entirely
2334 u_type = u_type & ~UNDO_QUEUED_FLAG;
2337 // Allocate a new undo object
2338 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2339 // For UNDO_DEL objects, save deleted text
2340 if ((text + length) == end)
2342 // If this deletion empties text[], strip the newline. When the buffer becomes
2343 // zero-length, a newline is added back, which requires this to compensate.
2344 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
2345 memcpy(undo_entry->undo_text, src, length);
2347 undo_entry = xzalloc(sizeof(*undo_entry));
2349 undo_entry->length = length;
2350 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2351 if ((u_type & UNDO_USE_SPOS) != 0) {
2352 undo_entry->start = undo_queue_spos - text; // use start position from queue
2354 undo_entry->start = src - text; // use offset from start of text buffer
2356 u_type = (u_type & ~UNDO_USE_SPOS);
2358 undo_entry->start = src - text;
2360 undo_entry->u_type = u_type;
2362 // Push it on undo stack
2363 undo_entry->prev = undo_stack_tail;
2364 undo_stack_tail = undo_entry;
2368 static void undo_push_insert(char *p, int len, int undo)
2372 undo_push(p, len, UNDO_INS);
2374 case ALLOW_UNDO_CHAIN:
2375 undo_push(p, len, UNDO_INS_CHAIN);
2377 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2378 case ALLOW_UNDO_QUEUED:
2379 undo_push(p, len, UNDO_INS_QUEUED);
2385 // Undo the last operation
2386 static void undo_pop(void)
2389 char *u_start, *u_end;
2390 struct undo_object *undo_entry;
2392 // Commit pending undo queue before popping (should be unnecessary)
2393 undo_queue_commit();
2395 undo_entry = undo_stack_tail;
2396 // Check for an empty undo stack
2398 status_line("Already at oldest change");
2402 switch (undo_entry->u_type) {
2404 case UNDO_DEL_CHAIN:
2405 // make hole and put in text that was deleted; deallocate text
2406 u_start = text + undo_entry->start;
2407 text_hole_make(u_start, undo_entry->length);
2408 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2409 status_line("Undo [%d] %s %d chars at position %d",
2410 modified_count, "restored",
2411 undo_entry->length, undo_entry->start
2415 case UNDO_INS_CHAIN:
2416 // delete what was inserted
2417 u_start = undo_entry->start + text;
2418 u_end = u_start - 1 + undo_entry->length;
2419 text_hole_delete(u_start, u_end, NO_UNDO);
2420 status_line("Undo [%d] %s %d chars at position %d",
2421 modified_count, "deleted",
2422 undo_entry->length, undo_entry->start
2427 switch (undo_entry->u_type) {
2428 // If this is the end of a chain, lower modification count and refresh display
2431 dot = (text + undo_entry->start);
2434 case UNDO_DEL_CHAIN:
2435 case UNDO_INS_CHAIN:
2439 // Deallocate the undo object we just processed
2440 undo_stack_tail = undo_entry->prev;
2443 // For chained operations, continue popping all the way down the chain.
2445 undo_pop(); // Follow the undo chain if one exists
2449 #if ENABLE_FEATURE_VI_UNDO_QUEUE
2450 // Flush any queued objects to the undo stack
2451 static void undo_queue_commit(void)
2453 // Pushes the queue object onto the undo stack
2455 // Deleted character undo events grow from the end
2456 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2458 (undo_queue_state | UNDO_USE_SPOS)
2460 undo_queue_state = UNDO_EMPTY;
2466 #endif /* ENABLE_FEATURE_VI_UNDO */
2468 // open a hole in text[]
2469 // might reallocate text[]! use p += text_hole_make(p, ...),
2470 // and be careful to not use pointers into potentially freed text[]!
2471 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2477 end += size; // adjust the new END
2478 if (end >= (text + text_size)) {
2480 text_size += end - (text + text_size) + 10240;
2481 new_text = xrealloc(text, text_size);
2482 bias = (new_text - text);
2483 screenbegin += bias;
2487 #if ENABLE_FEATURE_VI_YANKMARK
2490 for (i = 0; i < ARRAY_SIZE(mark); i++)
2497 memmove(p + size, p, end - size - p);
2498 memset(p, ' ', size); // clear new hole
2502 // close a hole in text[]
2503 // "undo" value indicates if this operation should be undo-able
2504 static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2509 // move forwards, from beginning
2513 if (q < p) { // they are backward- swap them
2517 hole_size = q - p + 1;
2519 #if ENABLE_FEATURE_VI_UNDO
2524 undo_push(p, hole_size, UNDO_DEL);
2526 case ALLOW_UNDO_CHAIN:
2527 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2529 # if ENABLE_FEATURE_VI_UNDO_QUEUE
2530 case ALLOW_UNDO_QUEUED:
2531 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2537 if (src < text || src > end)
2539 if (dest < text || dest >= end)
2543 goto thd_atend; // just delete the end of the buffer
2544 memmove(dest, src, cnt);
2546 end = end - hole_size; // adjust the new END
2548 dest = end - 1; // make sure dest in below end-1
2550 dest = end = text; // keep pointers valid
2555 // copy text into register, then delete text.
2556 // if dist <= 0, do not include, or go past, a NewLine
2558 static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2562 // make sure start <= stop
2564 // they are backwards, reverse them
2570 // we cannot cross NL boundaries
2574 // dont go past a NewLine
2575 for (; p + 1 <= stop; p++) {
2577 stop = p; // "stop" just before NewLine
2583 #if ENABLE_FEATURE_VI_YANKMARK
2584 text_yank(start, stop, YDreg);
2586 if (yf == YANKDEL) {
2587 p = text_hole_delete(start, stop, undo);
2592 static void show_help(void)
2594 puts("These features are available:"
2595 #if ENABLE_FEATURE_VI_SEARCH
2596 "\n\tPattern searches with / and ?"
2598 #if ENABLE_FEATURE_VI_DOT_CMD
2599 "\n\tLast command repeat with ."
2601 #if ENABLE_FEATURE_VI_YANKMARK
2602 "\n\tLine marking with 'x"
2603 "\n\tNamed buffers with \"x"
2605 #if ENABLE_FEATURE_VI_READONLY
2606 //not implemented: "\n\tReadonly if vi is called as \"view\""
2607 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2609 #if ENABLE_FEATURE_VI_SET
2610 "\n\tSome colon mode commands with :"
2612 #if ENABLE_FEATURE_VI_SETOPTS
2613 "\n\tSettable options with \":set\""
2615 #if ENABLE_FEATURE_VI_USE_SIGNALS
2616 "\n\tSignal catching- ^C"
2617 "\n\tJob suspend and resume with ^Z"
2619 #if ENABLE_FEATURE_VI_WIN_RESIZE
2620 "\n\tAdapt to window re-sizes"
2625 #if ENABLE_FEATURE_VI_DOT_CMD
2626 static void start_new_cmd_q(char c)
2628 // get buffer for new cmd
2629 // if there is a current cmd count put it in the buffer first
2631 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2632 } else { // just save char c onto queue
2633 last_modifying_cmd[0] = c;
2639 static void end_cmd_q(void)
2641 #if ENABLE_FEATURE_VI_YANKMARK
2642 YDreg = 26; // go back to default Yank/Delete reg
2646 #endif /* FEATURE_VI_DOT_CMD */
2648 #if ENABLE_FEATURE_VI_YANKMARK \
2649 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2650 || ENABLE_FEATURE_VI_CRASHME
2651 // might reallocate text[]! use p += string_insert(p, ...),
2652 // and be careful to not use pointers into potentially freed text[]!
2653 static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2659 #if ENABLE_FEATURE_VI_UNDO
2660 undo_push_insert(p, i, undo);
2662 bias = text_hole_make(p, i);
2665 #if ENABLE_FEATURE_VI_YANKMARK
2668 for (cnt = 0; *s != '\0'; s++) {
2672 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2679 #if ENABLE_FEATURE_VI_YANKMARK
2680 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2683 if (cnt < 0) { // they are backwards- reverse them
2687 free(reg[dest]); // if already a yank register, free it
2688 reg[dest] = xstrndup(p, cnt + 1);
2692 static char what_reg(void)
2696 c = 'D'; // default to D-reg
2697 if (0 <= YDreg && YDreg <= 25)
2698 c = 'a' + (char) YDreg;
2706 static void check_context(char cmd)
2708 // A context is defined to be "modifying text"
2709 // Any modifying command establishes a new context.
2711 if (dot < context_start || dot > context_end) {
2712 if (strchr(modifying_cmds, cmd) != NULL) {
2713 // we are trying to modify text[]- make this the current context
2714 mark[27] = mark[26]; // move cur to prev
2715 mark[26] = dot; // move local to cur
2716 context_start = prev_line(prev_line(dot));
2717 context_end = next_line(next_line(dot));
2718 //loiter= start_loiter= now;
2723 static char *swap_context(char *p) // goto new context for '' command make this the current context
2727 // the current context is in mark[26]
2728 // the previous context is in mark[27]
2729 // only swap context if other context is valid
2730 if (text <= mark[27] && mark[27] <= end - 1) {
2734 context_start = prev_line(prev_line(prev_line(p)));
2735 context_end = next_line(next_line(next_line(p)));
2739 #endif /* FEATURE_VI_YANKMARK */
2741 //----- Set terminal attributes --------------------------------
2742 static void rawmode(void)
2744 // no TERMIOS_CLEAR_ISIG: leave ISIG on - allow signals
2745 set_termios_to_raw(STDIN_FILENO, &term_orig, TERMIOS_RAW_CRNL);
2746 erase_char = term_orig.c_cc[VERASE];
2749 static void cookmode(void)
2752 tcsetattr_stdin_TCSANOW(&term_orig);
2755 #if ENABLE_FEATURE_VI_USE_SIGNALS
2756 static void winch_handler(int sig UNUSED_PARAM)
2758 int save_errno = errno;
2759 // FIXME: do it in main loop!!!
2760 signal(SIGWINCH, winch_handler);
2761 query_screen_dimensions();
2762 new_screen(rows, columns); // get memory for virtual screen
2763 redraw(TRUE); // re-draw the screen
2766 static void tstp_handler(int sig UNUSED_PARAM)
2768 int save_errno = errno;
2770 // ioctl inside cookmode() was seen to generate SIGTTOU,
2771 // stopping us too early. Prevent that:
2772 signal(SIGTTOU, SIG_IGN);
2774 go_bottom_and_clear_to_eol();
2775 cookmode(); // terminal to "cooked"
2778 //signal(SIGTSTP, SIG_DFL);
2780 raise(SIGSTOP); // avoid "dance" with TSTP handler - use SIGSTOP instead
2781 //signal(SIGTSTP, tstp_handler);
2783 // we have been "continued" with SIGCONT, restore screen and termios
2784 rawmode(); // terminal to "raw"
2785 last_status_cksum = 0; // force status update
2786 redraw(TRUE); // re-draw the screen
2790 static void int_handler(int sig)
2792 signal(SIGINT, int_handler);
2793 siglongjmp(restart, sig);
2795 #endif /* FEATURE_VI_USE_SIGNALS */
2797 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2799 struct pollfd pfd[1];
2804 pfd[0].fd = STDIN_FILENO;
2805 pfd[0].events = POLLIN;
2806 return safe_poll(pfd, 1, hund*10) > 0;
2809 //----- IO Routines --------------------------------------------
2810 static int readit(void) // read (maybe cursor) key from stdin
2816 // Wait for input. TIMEOUT = -1 makes read_key wait even
2817 // on nonblocking stdin.
2818 // Note: read_key sets errno to 0 on success.
2820 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
2821 if (c == -1) { // EOF/error
2822 if (errno == EAGAIN) // paranoia
2824 go_bottom_and_clear_to_eol();
2825 cookmode(); // terminal to "cooked"
2826 bb_error_msg_and_die("can't read user input");
2831 //----- IO Routines --------------------------------------------
2832 static int get_one_char(void)
2836 #if ENABLE_FEATURE_VI_DOT_CMD
2838 // we are not adding to the q.
2839 // but, we may be reading from a q
2841 // there is no current q, read from STDIN
2842 c = readit(); // get the users input
2844 // there is a queue to get chars from first
2845 // careful with correct sign expansion!
2846 c = (unsigned char)*ioq++;
2848 // the end of the q, read from STDIN
2850 ioq_start = ioq = 0;
2851 c = readit(); // get the users input
2855 // adding STDIN chars to q
2856 c = readit(); // get the users input
2857 if (lmc_len >= MAX_INPUT_LEN - 1) {
2858 status_line_bold("last_modifying_cmd overrun");
2860 // add new char to q
2861 last_modifying_cmd[lmc_len++] = c;
2865 c = readit(); // get the users input
2866 #endif /* FEATURE_VI_DOT_CMD */
2870 // Get input line (uses "status line" area)
2871 static char *get_input_line(const char *prompt)
2873 // char [MAX_INPUT_LEN]
2874 #define buf get_input_line__buf
2879 strcpy(buf, prompt);
2880 last_status_cksum = 0; // force status update
2881 go_bottom_and_clear_to_eol();
2882 write1(prompt); // write out the :, /, or ? prompt
2885 while (i < MAX_INPUT_LEN) {
2887 if (c == '\n' || c == '\r' || c == 27)
2888 break; // this is end of input
2889 if (c == erase_char || c == 8 || c == 127) {
2890 // user wants to erase prev char
2892 write1("\b \b"); // erase char on screen
2893 if (i <= 0) // user backs up before b-o-l, exit
2895 } else if (c > 0 && c < 256) { // exclude Unicode
2896 // (TODO: need to handle Unicode)
2907 // might reallocate text[]!
2908 static int file_insert(const char *fn, char *p, int initial)
2912 struct stat statbuf;
2919 fd = open(fn, O_RDONLY);
2922 status_line_bold_errno(fn);
2927 if (fstat(fd, &statbuf) < 0) {
2928 status_line_bold_errno(fn);
2931 if (!S_ISREG(statbuf.st_mode)) {
2932 status_line_bold("'%s' is not a regular file", fn);
2935 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2936 p += text_hole_make(p, size);
2937 cnt = full_read(fd, p, size);
2939 status_line_bold_errno(fn);
2940 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2941 } else if (cnt < size) {
2942 // There was a partial read, shrink unused space
2943 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2944 status_line_bold("can't read '%s'", fn);
2949 #if ENABLE_FEATURE_VI_READONLY
2951 && ((access(fn, W_OK) < 0) ||
2952 // root will always have access()
2953 // so we check fileperms too
2954 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2957 SET_READONLY_FILE(readonly_mode);
2963 static int file_write(char *fn, char *first, char *last)
2965 int fd, cnt, charcnt;
2968 status_line_bold("No current filename");
2971 // By popular request we do not open file with O_TRUNC,
2972 // but instead ftruncate() it _after_ successful write.
2973 // Might reduce amount of data lost on power fail etc.
2974 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2977 cnt = last - first + 1;
2978 charcnt = full_write(fd, first, cnt);
2979 ftruncate(fd, charcnt);
2980 if (charcnt == cnt) {
2982 //modified_count = FALSE;
2990 //----- Terminal Drawing ---------------------------------------
2991 // The terminal is made up of 'rows' line of 'columns' columns.
2992 // classically this would be 24 x 80.
2993 // screen coordinates
2999 // 23,0 ... 23,79 <- status line
3001 //----- Move the cursor to row x col (count from 0, not 1) -------
3002 static void place_cursor(int row, int col)
3004 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
3006 if (row < 0) row = 0;
3007 if (row >= rows) row = rows - 1;
3008 if (col < 0) col = 0;
3009 if (col >= columns) col = columns - 1;
3011 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
3015 //----- Erase from cursor to end of line -----------------------
3016 static void clear_to_eol(void)
3018 write1(ESC_CLEAR2EOL);
3021 static void go_bottom_and_clear_to_eol(void)
3023 place_cursor(rows - 1, 0);
3027 //----- Start standout mode ------------------------------------
3028 static void standout_start(void)
3030 write1(ESC_BOLD_TEXT);
3033 //----- End standout mode --------------------------------------
3034 static void standout_end(void)
3036 write1(ESC_NORM_TEXT);
3039 //----- Flash the screen --------------------------------------
3040 static void flash(int h)
3049 static void indicate_error(void)
3051 #if ENABLE_FEATURE_VI_CRASHME
3062 //----- Screen[] Routines --------------------------------------
3063 //----- Erase the Screen[] memory ------------------------------
3064 static void screen_erase(void)
3066 memset(screen, ' ', screensize); // clear new screen
3069 static int bufsum(char *buf, int count)
3072 char *e = buf + count;
3075 sum += (unsigned char) *buf++;
3079 //----- Draw the status line at bottom of the screen -------------
3080 static void show_status_line(void)
3082 int cnt = 0, cksum = 0;
3084 // either we already have an error or status message, or we
3086 if (!have_status_msg) {
3087 cnt = format_edit_status();
3088 cksum = bufsum(status_buffer, cnt);
3090 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
3091 last_status_cksum = cksum; // remember if we have seen this line
3092 go_bottom_and_clear_to_eol();
3093 write1(status_buffer);
3094 if (have_status_msg) {
3095 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
3097 have_status_msg = 0;
3100 have_status_msg = 0;
3102 place_cursor(crow, ccol); // put cursor back in correct place
3107 //----- format the status buffer, the bottom line of screen ------
3108 // format status buffer, with STANDOUT mode
3109 static void status_line_bold(const char *format, ...)
3113 va_start(args, format);
3114 strcpy(status_buffer, ESC_BOLD_TEXT);
3115 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
3116 strcat(status_buffer, ESC_NORM_TEXT);
3119 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
3122 static void status_line_bold_errno(const char *fn)
3124 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
3127 // format status buffer
3128 static void status_line(const char *format, ...)
3132 va_start(args, format);
3133 vsprintf(status_buffer, format, args);
3136 have_status_msg = 1;
3139 // copy s to buf, convert unprintable
3140 static void print_literal(char *buf, const char *s)
3154 c_is_no_print = (c & 0x80) && !Isprint(c);
3155 if (c_is_no_print) {
3156 strcpy(d, ESC_NORM_TEXT);
3157 d += sizeof(ESC_NORM_TEXT)-1;
3160 if (c < ' ' || c == 0x7f) {
3168 if (c_is_no_print) {
3169 strcpy(d, ESC_BOLD_TEXT);
3170 d += sizeof(ESC_BOLD_TEXT)-1;
3176 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
3181 static void not_implemented(const char *s)
3183 char buf[MAX_INPUT_LEN];
3185 print_literal(buf, s);
3186 status_line_bold("\'%s\' is not implemented", buf);
3189 // show file status on status line
3190 static int format_edit_status(void)
3192 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
3194 #define tot format_edit_status__tot
3196 int cur, percent, ret, trunc_at;
3198 // modified_count is now a counter rather than a flag. this
3199 // helps reduce the amount of line counting we need to do.
3200 // (this will cause a mis-reporting of modified status
3201 // once every MAXINT editing operations.)
3203 // it would be nice to do a similar optimization here -- if
3204 // we haven't done a motion that could have changed which line
3205 // we're on, then we shouldn't have to do this count_lines()
3206 cur = count_lines(text, dot);
3208 // count_lines() is expensive.
3209 // Call it only if something was changed since last time
3211 if (modified_count != last_modified_count) {
3212 tot = cur + count_lines(dot, end - 1) - 1;
3213 last_modified_count = modified_count;
3216 // current line percent
3217 // ------------- ~~ ----------
3220 percent = (100 * cur) / tot;
3226 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
3227 columns : STATUS_BUFFER_LEN-1;
3229 ret = snprintf(status_buffer, trunc_at+1,
3230 #if ENABLE_FEATURE_VI_READONLY
3231 "%c %s%s%s %d/%d %d%%",
3233 "%c %s%s %d/%d %d%%",
3235 cmd_mode_indicator[cmd_mode & 3],
3236 (current_filename != NULL ? current_filename : "No file"),
3237 #if ENABLE_FEATURE_VI_READONLY
3238 (readonly_mode ? " [Readonly]" : ""),
3240 (modified_count ? " [Modified]" : ""),
3243 if (ret >= 0 && ret < trunc_at)
3244 return ret; // it all fit
3246 return trunc_at; // had to truncate
3250 //----- Force refresh of all Lines -----------------------------
3251 static void redraw(int full_screen)
3253 // cursor to top,left; clear to the end of screen
3254 write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
3255 screen_erase(); // erase the internal screen buffer
3256 last_status_cksum = 0; // force status update
3257 refresh(full_screen); // this will redraw the entire display
3261 //----- Format a text[] line into a buffer ---------------------
3262 static char* format_line(char *src /*, int li*/)
3267 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
3269 c = '~'; // char in col 0 in non-existent lines is '~'
3271 while (co < columns + tabstop) {
3272 // have we gone past the end?
3277 if ((c & 0x80) && !Isprint(c)) {
3280 if (c < ' ' || c == 0x7f) {
3284 while ((co % tabstop) != (tabstop - 1)) {
3292 c += '@'; // Ctrl-X -> 'X'
3297 // discard scrolled-off-to-the-left portion,
3298 // in tabstop-sized pieces
3299 if (ofs >= tabstop && co >= tabstop) {
3300 memmove(dest, dest + tabstop, co);
3307 // check "short line, gigantic offset" case
3310 // discard last scrolled off part
3313 // fill the rest with spaces
3315 memset(&dest[co], ' ', columns - co);
3319 //----- Refresh the changed screen lines -----------------------
3320 // Copy the source line from text[] into the buffer and note
3321 // if the current screenline is different from the new buffer.
3322 // If they differ then that line needs redrawing on the terminal.
3324 static void refresh(int full_screen)
3326 #define old_offset refresh__old_offset
3329 char *tp, *sp; // pointer into text[] and screen[]
3331 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
3332 unsigned c = columns, r = rows;
3333 query_screen_dimensions();
3334 #if ENABLE_FEATURE_VI_USE_SIGNALS
3335 full_screen |= (c - columns) | (r - rows);
3337 if (c != columns || r != rows) {
3339 // update screen memory since SIGWINCH won't have done it
3340 new_screen(rows, columns);
3344 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
3345 tp = screenbegin; // index into text[] of top line
3347 // compare text[] to screen[] and mark screen[] lines that need updating
3348 for (li = 0; li < rows - 1; li++) {
3349 int cs, ce; // column start & end
3351 // format current text line
3352 out_buf = format_line(tp /*, li*/);
3354 // skip to the end of the current text[] line
3356 char *t = memchr(tp, '\n', end - tp);
3357 if (!t) t = end - 1;
3361 // see if there are any changes between virtual screen and out_buf
3362 changed = FALSE; // assume no change
3365 sp = &screen[li * columns]; // start of screen line
3367 // force re-draw of every single column from 0 - columns-1
3370 // compare newly formatted buffer with virtual screen
3371 // look forward for first difference between buf and screen
3372 for (; cs <= ce; cs++) {
3373 if (out_buf[cs] != sp[cs]) {
3374 changed = TRUE; // mark for redraw
3379 // look backward for last difference between out_buf and screen
3380 for (; ce >= cs; ce--) {
3381 if (out_buf[ce] != sp[ce]) {
3382 changed = TRUE; // mark for redraw
3386 // now, cs is index of first diff, and ce is index of last diff
3388 // if horz offset has changed, force a redraw
3389 if (offset != old_offset) {
3394 // make a sanity check of columns indexes
3396 if (ce > columns - 1) ce = columns - 1;
3397 if (cs > ce) { cs = 0; ce = columns - 1; }
3398 // is there a change between virtual screen and out_buf
3400 // copy changed part of buffer to virtual screen
3401 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3402 place_cursor(li, cs);
3403 // write line out to terminal
3404 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3408 place_cursor(crow, ccol);
3410 old_offset = offset;
3414 //---------------------------------------------------------------------
3415 //----- the Ascii Chart -----------------------------------------------
3416 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3417 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3418 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3419 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3420 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3421 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3422 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3423 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3424 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3425 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3426 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3427 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3428 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3429 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3430 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3431 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3432 //---------------------------------------------------------------------
3434 //----- Execute a Vi Command -----------------------------------
3435 static void do_cmd(int c)
3437 char *p, *q, *save_dot;
3443 // c1 = c; // quiet the compiler
3444 // cnt = yf = 0; // quiet the compiler
3445 // p = q = save_dot = buf; // quiet the compiler
3446 memset(buf, '\0', sizeof(buf));
3450 // if this is a cursor key, skip these checks
3458 case KEYCODE_PAGEUP:
3459 case KEYCODE_PAGEDOWN:
3460 case KEYCODE_DELETE:
3464 if (cmd_mode == 2) {
3465 // flip-flop Insert/Replace mode
3466 if (c == KEYCODE_INSERT)
3468 // we are 'R'eplacing the current *dot with new char
3470 // don't Replace past E-o-l
3471 cmd_mode = 1; // convert to insert
3472 undo_queue_commit();
3474 if (1 <= c || Isprint(c)) {
3476 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
3477 dot = char_insert(dot, c, ALLOW_UNDO_CHAIN); // insert new char
3482 if (cmd_mode == 1) {
3483 // hitting "Insert" twice means "R" replace mode
3484 if (c == KEYCODE_INSERT) goto dc5;
3485 // insert the char c at "dot"
3486 if (1 <= c || Isprint(c)) {
3487 dot = char_insert(dot, c, ALLOW_UNDO_QUEUED);
3502 #if ENABLE_FEATURE_VI_CRASHME
3503 case 0x14: // dc4 ctrl-T
3504 crashme = (crashme == 0) ? 1 : 0;
3534 default: // unrecognized command
3537 not_implemented(buf);
3538 end_cmd_q(); // stop adding to q
3539 case 0x00: // nul- ignore
3541 case 2: // ctrl-B scroll up full screen
3542 case KEYCODE_PAGEUP: // Cursor Key Page Up
3543 dot_scroll(rows - 2, -1);
3545 case 4: // ctrl-D scroll down half screen
3546 dot_scroll((rows - 2) / 2, 1);
3548 case 5: // ctrl-E scroll down one line
3551 case 6: // ctrl-F scroll down full screen
3552 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3553 dot_scroll(rows - 2, 1);
3555 case 7: // ctrl-G show current status
3556 last_status_cksum = 0; // force status update
3558 case 'h': // h- move left
3559 case KEYCODE_LEFT: // cursor key Left
3560 case 8: // ctrl-H- move left (This may be ERASE char)
3561 case 0x7f: // DEL- move left (This may be ERASE char)
3564 } while (--cmdcnt > 0);
3566 case 10: // Newline ^J
3567 case 'j': // j- goto next line, same col
3568 case KEYCODE_DOWN: // cursor key Down
3570 dot_next(); // go to next B-o-l
3571 // try stay in same col
3572 dot = move_to_col(dot, ccol + offset);
3573 } while (--cmdcnt > 0);
3575 case 12: // ctrl-L force redraw whole screen
3576 case 18: // ctrl-R force redraw
3577 redraw(TRUE); // this will redraw the entire display
3579 case 13: // Carriage Return ^M
3580 case '+': // +- goto next line
3584 } while (--cmdcnt > 0);
3586 case 21: // ctrl-U scroll up half screen
3587 dot_scroll((rows - 2) / 2, -1);
3589 case 25: // ctrl-Y scroll up one line
3595 cmd_mode = 0; // stop insrting
3596 undo_queue_commit();
3598 last_status_cksum = 0; // force status update
3600 case ' ': // move right
3601 case 'l': // move right
3602 case KEYCODE_RIGHT: // Cursor Key Right
3605 } while (--cmdcnt > 0);
3607 #if ENABLE_FEATURE_VI_YANKMARK
3608 case '"': // "- name a register to use for Delete/Yank
3609 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3610 if ((unsigned)c1 <= 25) { // a-z?
3616 case '\'': // '- goto a specific mark
3617 c1 = (get_one_char() | 0x20);
3618 if ((unsigned)(c1 - 'a') <= 25) { // a-z?
3622 if (text <= q && q < end) {
3624 dot_begin(); // go to B-o-l
3627 } else if (c1 == '\'') { // goto previous context
3628 dot = swap_context(dot); // swap current and previous context
3629 dot_begin(); // go to B-o-l
3635 case 'm': // m- Mark a line
3636 // this is really stupid. If there are any inserts or deletes
3637 // between text[0] and dot then this mark will not point to the
3638 // correct location! It could be off by many lines!
3639 // Well..., at least its quick and dirty.
3640 c1 = (get_one_char() | 0x20) - 'a';
3641 if ((unsigned)c1 <= 25) { // a-z?
3642 // remember the line
3648 case 'P': // P- Put register before
3649 case 'p': // p- put register after
3652 status_line_bold("Nothing in register %c", what_reg());
3655 // are we putting whole lines or strings
3656 if (strchr(p, '\n') != NULL) {
3658 dot_begin(); // putting lines- Put above
3661 // are we putting after very last line?
3662 if (end_line(dot) == (end - 1)) {
3663 dot = end; // force dot to end of text[]
3665 dot_next(); // next line, then put before
3670 dot_right(); // move to right, can move to NL
3672 string_insert(dot, p, ALLOW_UNDO); // insert the string
3673 end_cmd_q(); // stop adding to q
3675 case 'U': // U- Undo; replace current line with original version
3676 if (reg[Ureg] != NULL) {
3677 p = begin_line(dot);
3679 p = text_hole_delete(p, q, ALLOW_UNDO); // delete cur line
3680 p += string_insert(p, reg[Ureg], ALLOW_UNDO_CHAIN); // insert orig line
3685 #endif /* FEATURE_VI_YANKMARK */
3686 #if ENABLE_FEATURE_VI_UNDO
3687 case 'u': // u- undo last operation
3691 case '$': // $- goto end of line
3692 case KEYCODE_END: // Cursor Key End
3694 dot = end_line(dot);
3700 case '%': // %- find matching char of pair () [] {}
3701 for (q = dot; q < end && *q != '\n'; q++) {
3702 if (strchr("()[]{}", *q) != NULL) {
3703 // we found half of a pair
3704 p = find_pair(q, *q);
3716 case 'f': // f- forward to a user specified char
3717 last_forward_char = get_one_char(); // get the search char
3719 // dont separate these two commands. 'f' depends on ';'
3721 //**** fall through to ... ';'
3722 case ';': // ;- look at rest of line for last forward char
3724 if (last_forward_char == 0)
3727 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3730 if (*q == last_forward_char)
3732 } while (--cmdcnt > 0);
3734 case ',': // repeat latest 'f' in opposite direction
3735 if (last_forward_char == 0)
3739 while (q >= text && *q != '\n' && *q != last_forward_char) {
3742 if (q >= text && *q == last_forward_char)
3744 } while (--cmdcnt > 0);
3747 case '-': // -- goto prev line
3751 } while (--cmdcnt > 0);
3753 #if ENABLE_FEATURE_VI_DOT_CMD
3754 case '.': // .- repeat the last modifying command
3755 // Stuff the last_modifying_cmd back into stdin
3756 // and let it be re-executed.
3758 last_modifying_cmd[lmc_len] = 0;
3759 ioq = ioq_start = xstrdup(last_modifying_cmd);
3763 #if ENABLE_FEATURE_VI_SEARCH
3764 case '?': // /- search for a pattern
3765 case '/': // /- search for a pattern
3768 q = get_input_line(buf); // get input line- use "status line"
3769 if (q[0] && !q[1]) {
3770 if (last_search_pattern[0])
3771 last_search_pattern[0] = c;
3772 goto dc3; // if no pat re-use old pat
3774 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3775 // there is a new pat
3776 free(last_search_pattern);
3777 last_search_pattern = xstrdup(q);
3778 goto dc3; // now find the pattern
3780 // user changed mind and erased the "/"- do nothing
3782 case 'N': // N- backward search for last pattern
3783 dir = BACK; // assume BACKWARD search
3785 if (last_search_pattern[0] == '?') {
3789 goto dc4; // now search for pattern
3791 case 'n': // n- repeat search for last pattern
3792 // search rest of text[] starting at next char
3793 // if search fails return orignal "p" not the "p+1" address
3797 dir = FORWARD; // assume FORWARD search
3799 if (last_search_pattern[0] == '?') {
3804 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3806 dot = q; // good search, update "dot"
3810 // no pattern found between "dot" and "end"- continue at top
3815 q = char_search(p, last_search_pattern + 1, (dir << 1) | FULL);
3816 if (q != NULL) { // found something
3817 dot = q; // found new pattern- goto it
3818 msg = "search hit BOTTOM, continuing at TOP";
3820 msg = "search hit TOP, continuing at BOTTOM";
3823 msg = "Pattern not found";
3827 status_line_bold("%s", msg);
3828 } while (--cmdcnt > 0);
3830 case '{': // {- move backward paragraph
3831 q = char_search(dot, "\n\n", (BACK << 1) | FULL);
3832 if (q != NULL) { // found blank line
3833 dot = next_line(q); // move to next blank line
3836 case '}': // }- move forward paragraph
3837 q = char_search(dot, "\n\n", (FORWARD << 1) | FULL);
3838 if (q != NULL) { // found blank line
3839 dot = next_line(q); // move to next blank line
3842 #endif /* FEATURE_VI_SEARCH */
3843 case '0': // 0- goto beginning of line
3853 if (c == '0' && cmdcnt < 1) {
3854 dot_begin(); // this was a standalone zero
3856 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3859 case ':': // :- the colon mode commands
3860 p = get_input_line(":"); // get input line- use "status line"
3861 colon(p); // execute the command
3863 case '<': // <- Left shift something
3864 case '>': // >- Right shift something
3865 cnt = count_lines(text, dot); // remember what line we are on
3866 c1 = get_one_char(); // get the type of thing to delete
3867 find_range(&p, &q, c1);
3868 yank_delete(p, q, 1, YANKONLY, NO_UNDO); // save copy before change
3871 i = count_lines(p, q); // # of lines we are shifting
3872 for ( ; i > 0; i--, p = next_line(p)) {
3874 // shift left- remove tab or 8 spaces
3876 // shrink buffer 1 char
3877 text_hole_delete(p, p, NO_UNDO);
3878 } else if (*p == ' ') {
3879 // we should be calculating columns, not just SPACE
3880 for (j = 0; *p == ' ' && j < tabstop; j++) {
3881 text_hole_delete(p, p, NO_UNDO);
3884 } else if (c == '>') {
3885 // shift right -- add tab or 8 spaces
3886 char_insert(p, '\t', ALLOW_UNDO);
3889 dot = find_line(cnt); // what line were we on
3891 end_cmd_q(); // stop adding to q
3893 case 'A': // A- append at e-o-l
3894 dot_end(); // go to e-o-l
3895 //**** fall through to ... 'a'
3896 case 'a': // a- append after current char
3901 case 'B': // B- back a blank-delimited Word
3902 case 'E': // E- end of a blank-delimited word
3903 case 'W': // W- forward a blank-delimited word
3908 if (c == 'W' || isspace(dot[dir])) {
3909 dot = skip_thing(dot, 1, dir, S_TO_WS);
3910 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3913 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3914 } while (--cmdcnt > 0);
3916 case 'C': // C- Change to e-o-l
3917 case 'D': // D- delete to e-o-l
3919 dot = dollar_line(dot); // move to before NL
3920 // copy text into a register and delete
3921 dot = yank_delete(save_dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete to e-o-l
3923 goto dc_i; // start inserting
3924 #if ENABLE_FEATURE_VI_DOT_CMD
3926 end_cmd_q(); // stop adding to q
3929 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3930 c1 = get_one_char();
3933 // c1 < 0 if the key was special. Try "g<up-arrow>"
3934 // TODO: if Unicode?
3935 buf[1] = (c1 >= 0 ? c1 : '*');
3937 not_implemented(buf);
3943 case 'G': // G- goto to a line number (default= E-O-F)
3944 dot = end - 1; // assume E-O-F
3946 dot = find_line(cmdcnt); // what line is #cmdcnt
3950 case 'H': // H- goto top line on screen
3952 if (cmdcnt > (rows - 1)) {
3953 cmdcnt = (rows - 1);
3960 case 'I': // I- insert before first non-blank
3963 //**** fall through to ... 'i'
3964 case 'i': // i- insert before current char
3965 case KEYCODE_INSERT: // Cursor Key Insert
3967 cmd_mode = 1; // start inserting
3968 undo_queue_commit(); // commit queue when cmd_mode changes
3970 case 'J': // J- join current and next lines together
3972 dot_end(); // move to NL
3973 if (dot < end - 1) { // make sure not last char in text[]
3974 #if ENABLE_FEATURE_VI_UNDO
3975 undo_push(dot, 1, UNDO_DEL);
3976 *dot++ = ' '; // replace NL with space
3977 undo_push((dot - 1), 1, UNDO_INS_CHAIN);
3982 while (isblank(*dot)) { // delete leading WS
3983 text_hole_delete(dot, dot, ALLOW_UNDO_CHAIN);
3986 } while (--cmdcnt > 0);
3987 end_cmd_q(); // stop adding to q
3989 case 'L': // L- goto bottom line on screen
3991 if (cmdcnt > (rows - 1)) {
3992 cmdcnt = (rows - 1);
4000 case 'M': // M- goto middle line on screen
4002 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
4003 dot = next_line(dot);
4005 case 'O': // O- open a empty line above
4007 p = begin_line(dot);
4008 if (p[-1] == '\n') {
4010 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
4012 dot = char_insert(dot, '\n', ALLOW_UNDO);
4015 dot = char_insert(dot, '\n', ALLOW_UNDO); // i\n ESC
4020 case 'R': // R- continuous Replace char
4023 undo_queue_commit();
4025 case KEYCODE_DELETE:
4027 dot = yank_delete(dot, dot, 1, YANKDEL, ALLOW_UNDO);
4029 case 'X': // X- delete char before dot
4030 case 'x': // x- delete the current char
4031 case 's': // s- substitute the current char
4036 if (dot[dir] != '\n') {
4038 dot--; // delete prev char
4039 dot = yank_delete(dot, dot, 0, YANKDEL, ALLOW_UNDO); // delete char
4041 } while (--cmdcnt > 0);
4042 end_cmd_q(); // stop adding to q
4044 goto dc_i; // start inserting
4046 case 'Z': // Z- if modified, {write}; exit
4047 // ZZ means to save file (if necessary), then exit
4048 c1 = get_one_char();
4053 if (modified_count) {
4054 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
4055 status_line_bold("'%s' is read only", current_filename);
4058 cnt = file_write(current_filename, text, end - 1);
4061 status_line_bold("Write error: "STRERROR_FMT STRERROR_ERRNO);
4062 } else if (cnt == (end - 1 - text + 1)) {
4069 case '^': // ^- move to first non-blank on line
4073 case 'b': // b- back a word
4074 case 'e': // e- end of word
4079 if ((dot + dir) < text || (dot + dir) > end - 1)
4082 if (isspace(*dot)) {
4083 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
4085 if (isalnum(*dot) || *dot == '_') {
4086 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
4087 } else if (ispunct(*dot)) {
4088 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
4090 } while (--cmdcnt > 0);
4092 case 'c': // c- change something
4093 case 'd': // d- delete something
4094 #if ENABLE_FEATURE_VI_YANKMARK
4095 case 'y': // y- yank something
4096 case 'Y': // Y- Yank a line
4099 int yf, ml, whole = 0;
4100 yf = YANKDEL; // assume either "c" or "d"
4101 #if ENABLE_FEATURE_VI_YANKMARK
4102 if (c == 'y' || c == 'Y')
4107 c1 = get_one_char(); // get the type of thing to delete
4108 // determine range, and whether it spans lines
4109 ml = find_range(&p, &q, c1);
4111 if (c1 == 27) { // ESC- user changed mind and wants out
4112 c = c1 = 27; // Escape- do nothing
4113 } else if (strchr("wW", c1)) {
4115 // don't include trailing WS as part of word
4116 while (isblank(*q)) {
4117 if (q <= text || q[-1] == '\n')
4122 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4123 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
4124 // partial line copy text into a register and delete
4125 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete word
4126 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
4127 // whole line copy text into a register and delete
4128 dot = yank_delete(p, q, ml, yf, ALLOW_UNDO); // delete lines
4131 // could not recognize object
4132 c = c1 = 27; // error-
4138 dot = char_insert(dot, '\n', ALLOW_UNDO_CHAIN);
4139 // on the last line of file don't move to prev line
4140 if (whole && dot != (end-1)) {
4143 } else if (c == 'd') {
4149 // if CHANGING, not deleting, start inserting after the delete
4151 strcpy(buf, "Change");
4152 goto dc_i; // start inserting
4155 strcpy(buf, "Delete");
4157 #if ENABLE_FEATURE_VI_YANKMARK
4158 if (c == 'y' || c == 'Y') {
4159 strcpy(buf, "Yank");
4163 for (cnt = 0; p <= q; p++) {
4167 status_line("%s %u lines (%u chars) using [%c]",
4168 buf, cnt, (unsigned)strlen(reg[YDreg]), what_reg());
4170 end_cmd_q(); // stop adding to q
4174 case 'k': // k- goto prev line, same col
4175 case KEYCODE_UP: // cursor key Up
4178 dot = move_to_col(dot, ccol + offset); // try stay in same col
4179 } while (--cmdcnt > 0);
4181 case 'r': // r- replace the current char with user input
4182 c1 = get_one_char(); // get the replacement char
4184 dot = text_hole_delete(dot, dot, ALLOW_UNDO);
4185 dot = char_insert(dot, c1, ALLOW_UNDO_CHAIN);
4188 end_cmd_q(); // stop adding to q
4190 case 't': // t- move to char prior to next x
4191 last_forward_char = get_one_char();
4193 if (*dot == last_forward_char)
4195 last_forward_char = 0;
4197 case 'w': // w- forward a word
4199 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
4200 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
4201 } else if (ispunct(*dot)) { // we are on PUNCT
4202 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
4205 dot++; // move over word
4206 if (isspace(*dot)) {
4207 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
4209 } while (--cmdcnt > 0);
4212 c1 = get_one_char(); // get the replacement char
4215 cnt = (rows - 2) / 2; // put dot at center
4217 cnt = rows - 2; // put dot at bottom
4218 screenbegin = begin_line(dot); // start dot at top
4219 dot_scroll(cnt, -1);
4221 case '|': // |- move to column "cmdcnt"
4222 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
4224 case '~': // ~- flip the case of letters a-z -> A-Z
4226 #if ENABLE_FEATURE_VI_UNDO
4227 if (islower(*dot)) {
4228 undo_push(dot, 1, UNDO_DEL);
4229 *dot = toupper(*dot);
4230 undo_push(dot, 1, UNDO_INS_CHAIN);
4231 } else if (isupper(*dot)) {
4232 undo_push(dot, 1, UNDO_DEL);
4233 *dot = tolower(*dot);
4234 undo_push(dot, 1, UNDO_INS_CHAIN);
4237 if (islower(*dot)) {
4238 *dot = toupper(*dot);
4240 } else if (isupper(*dot)) {
4241 *dot = tolower(*dot);
4246 } while (--cmdcnt > 0);
4247 end_cmd_q(); // stop adding to q
4249 //----- The Cursor and Function Keys -----------------------------
4250 case KEYCODE_HOME: // Cursor Key Home
4253 // The Fn keys could point to do_macro which could translate them
4255 case KEYCODE_FUN1: // Function Key F1
4256 case KEYCODE_FUN2: // Function Key F2
4257 case KEYCODE_FUN3: // Function Key F3
4258 case KEYCODE_FUN4: // Function Key F4
4259 case KEYCODE_FUN5: // Function Key F5
4260 case KEYCODE_FUN6: // Function Key F6
4261 case KEYCODE_FUN7: // Function Key F7
4262 case KEYCODE_FUN8: // Function Key F8
4263 case KEYCODE_FUN9: // Function Key F9
4264 case KEYCODE_FUN10: // Function Key F10
4265 case KEYCODE_FUN11: // Function Key F11
4266 case KEYCODE_FUN12: // Function Key F12
4272 // if text[] just became empty, add back an empty line
4274 char_insert(text, '\n', NO_UNDO); // start empty buf with dummy line
4277 // it is OK for dot to exactly equal to end, otherwise check dot validity
4279 dot = bound_dot(dot); // make sure "dot" is valid
4281 #if ENABLE_FEATURE_VI_YANKMARK
4282 check_context(c); // update the current context
4286 cmdcnt = 0; // cmd was not a number, reset cmdcnt
4287 cnt = dot - begin_line(dot);
4288 // Try to stay off of the Newline
4289 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
4293 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
4294 #if ENABLE_FEATURE_VI_CRASHME
4295 static int totalcmds = 0;
4296 static int Mp = 85; // Movement command Probability
4297 static int Np = 90; // Non-movement command Probability
4298 static int Dp = 96; // Delete command Probability
4299 static int Ip = 97; // Insert command Probability
4300 static int Yp = 98; // Yank command Probability
4301 static int Pp = 99; // Put command Probability
4302 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
4303 static const char chars[20] = "\t012345 abcdABCD-=.$";
4304 static const char *const words[20] = {
4305 "this", "is", "a", "test",
4306 "broadcast", "the", "emergency", "of",
4307 "system", "quick", "brown", "fox",
4308 "jumped", "over", "lazy", "dogs",
4309 "back", "January", "Febuary", "March"
4311 static const char *const lines[20] = {
4312 "You should have received a copy of the GNU General Public License\n",
4313 "char c, cm, *cmd, *cmd1;\n",
4314 "generate a command by percentages\n",
4315 "Numbers may be typed as a prefix to some commands.\n",
4316 "Quit, discarding changes!\n",
4317 "Forced write, if permission originally not valid.\n",
4318 "In general, any ex or ed command (such as substitute or delete).\n",
4319 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4320 "Please get w/ me and I will go over it with you.\n",
4321 "The following is a list of scheduled, committed changes.\n",
4322 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4323 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4324 "Any question about transactions please contact Sterling Huxley.\n",
4325 "I will try to get back to you by Friday, December 31.\n",
4326 "This Change will be implemented on Friday.\n",
4327 "Let me know if you have problems accessing this;\n",
4328 "Sterling Huxley recently added you to the access list.\n",
4329 "Would you like to go to lunch?\n",
4330 "The last command will be automatically run.\n",
4331 "This is too much english for a computer geek.\n",
4333 static char *multilines[20] = {
4334 "You should have received a copy of the GNU General Public License\n",
4335 "char c, cm, *cmd, *cmd1;\n",
4336 "generate a command by percentages\n",
4337 "Numbers may be typed as a prefix to some commands.\n",
4338 "Quit, discarding changes!\n",
4339 "Forced write, if permission originally not valid.\n",
4340 "In general, any ex or ed command (such as substitute or delete).\n",
4341 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
4342 "Please get w/ me and I will go over it with you.\n",
4343 "The following is a list of scheduled, committed changes.\n",
4344 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
4345 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
4346 "Any question about transactions please contact Sterling Huxley.\n",
4347 "I will try to get back to you by Friday, December 31.\n",
4348 "This Change will be implemented on Friday.\n",
4349 "Let me know if you have problems accessing this;\n",
4350 "Sterling Huxley recently added you to the access list.\n",
4351 "Would you like to go to lunch?\n",
4352 "The last command will be automatically run.\n",
4353 "This is too much english for a computer geek.\n",
4356 // create a random command to execute
4357 static void crash_dummy()
4359 static int sleeptime; // how long to pause between commands
4360 char c, cm, *cmd, *cmd1;
4361 int i, cnt, thing, rbi, startrbi, percent;
4363 // "dot" movement commands
4364 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4366 // is there already a command running?
4367 if (readbuffer[0] > 0)
4370 readbuffer[0] = 'X';
4372 sleeptime = 0; // how long to pause between commands
4373 memset(readbuffer, '\0', sizeof(readbuffer));
4374 // generate a command by percentages
4375 percent = (int) lrand48() % 100; // get a number from 0-99
4376 if (percent < Mp) { // Movement commands
4377 // available commands
4380 } else if (percent < Np) { // non-movement commands
4381 cmd = "mz<>\'\""; // available commands
4383 } else if (percent < Dp) { // Delete commands
4384 cmd = "dx"; // available commands
4386 } else if (percent < Ip) { // Inset commands
4387 cmd = "iIaAsrJ"; // available commands
4389 } else if (percent < Yp) { // Yank commands
4390 cmd = "yY"; // available commands
4392 } else if (percent < Pp) { // Put commands
4393 cmd = "pP"; // available commands
4396 // We do not know how to handle this command, try again
4400 // randomly pick one of the available cmds from "cmd[]"
4401 i = (int) lrand48() % strlen(cmd);
4403 if (strchr(":\024", cm))
4404 goto cd0; // dont allow colon or ctrl-T commands
4405 readbuffer[rbi++] = cm; // put cmd into input buffer
4407 // now we have the command-
4408 // there are 1, 2, and multi char commands
4409 // find out which and generate the rest of command as necessary
4410 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4411 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4412 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4413 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4415 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4417 readbuffer[rbi++] = c; // add movement to input buffer
4419 if (strchr("iIaAsc", cm)) { // multi-char commands
4421 // change some thing
4422 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4424 readbuffer[rbi++] = c; // add movement to input buffer
4426 thing = (int) lrand48() % 4; // what thing to insert
4427 cnt = (int) lrand48() % 10; // how many to insert
4428 for (i = 0; i < cnt; i++) {
4429 if (thing == 0) { // insert chars
4430 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4431 } else if (thing == 1) { // insert words
4432 strcat(readbuffer, words[(int) lrand48() % 20]);
4433 strcat(readbuffer, " ");
4434 sleeptime = 0; // how fast to type
4435 } else if (thing == 2) { // insert lines
4436 strcat(readbuffer, lines[(int) lrand48() % 20]);
4437 sleeptime = 0; // how fast to type
4438 } else { // insert multi-lines
4439 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4440 sleeptime = 0; // how fast to type
4443 strcat(readbuffer, ESC);
4445 readbuffer[0] = strlen(readbuffer + 1);
4449 mysleep(sleeptime); // sleep 1/100 sec
4452 // test to see if there are any errors
4453 static void crash_test()
4455 static time_t oldtim;
4462 strcat(msg, "end<text ");
4464 if (end > textend) {
4465 strcat(msg, "end>textend ");
4468 strcat(msg, "dot<text ");
4471 strcat(msg, "dot>end ");
4473 if (screenbegin < text) {
4474 strcat(msg, "screenbegin<text ");
4476 if (screenbegin > end - 1) {
4477 strcat(msg, "screenbegin>end-1 ");
4481 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4482 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4484 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4485 if (d[0] == '\n' || d[0] == '\r')
4490 if (tim >= (oldtim + 3)) {
4491 sprintf(status_buffer,
4492 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4493 totalcmds, M, N, I, D, Y, P, U, end - text + 1);