1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
16 * if mark[] values were line numbers rather than pointers
17 * it would be easier to change the mark when add/delete lines
18 * More intelligence in refresh()
19 * ":r !cmd" and "!cmd" to filter text through an external command
20 * A true "undo" facility
21 * An "ex" line oriented mode- maybe using "cmdedit"
28 //config: 'vi' is a text editor. More specifically, it is the One True
29 //config: text editor <grin>. It does, however, have a rather steep
30 //config: learning curve. If you are not already comfortable with 'vi'
31 //config: you may wish to use something else.
33 //config:config FEATURE_VI_MAX_LEN
34 //config: int "Maximum screen width in vi"
35 //config: range 256 16384
36 //config: default 4096
37 //config: depends on VI
39 //config: Contrary to what you may think, this is not eating much.
40 //config: Make it smaller than 4k only if you are very limited on memory.
42 //config:config FEATURE_VI_8BIT
43 //config: bool "Allow vi to display 8-bit chars (otherwise shows dots)"
45 //config: depends on VI
47 //config: If your terminal can display characters with high bit set,
48 //config: you may want to enable this. Note: vi is not Unicode-capable.
49 //config: If your terminal combines several 8-bit bytes into one character
50 //config: (as in Unicode mode), this will not work properly.
52 //config:config FEATURE_VI_COLON
53 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
55 //config: depends on VI
57 //config: Enable a limited set of colon commands for vi. This does not
58 //config: provide an "ex" mode.
60 //config:config FEATURE_VI_YANKMARK
61 //config: bool "Enable yank/put commands and mark cmds"
63 //config: depends on VI
65 //config: This will enable you to use yank and put, as well as mark in
68 //config:config FEATURE_VI_SEARCH
69 //config: bool "Enable search and replace cmds"
71 //config: depends on VI
73 //config: Select this if you wish to be able to do search and replace in
76 //config:config FEATURE_VI_REGEX_SEARCH
77 //config: bool "Enable regex in search and replace"
78 //config: default n # Uses GNU regex, which may be unavailable. FIXME
79 //config: depends on FEATURE_VI_SEARCH
81 //config: Use extended regex search.
83 //config:config FEATURE_VI_USE_SIGNALS
84 //config: bool "Catch signals"
86 //config: depends on VI
88 //config: Selecting this option will make busybox vi signal aware. This will
89 //config: make busybox vi support SIGWINCH to deal with Window Changes, catch
90 //config: Ctrl-Z and Ctrl-C and alarms.
92 //config:config FEATURE_VI_DOT_CMD
93 //config: bool "Remember previous cmd and \".\" cmd"
95 //config: depends on VI
97 //config: Make busybox vi remember the last command and be able to repeat it.
99 //config:config FEATURE_VI_READONLY
100 //config: bool "Enable -R option and \"view\" mode"
102 //config: depends on VI
104 //config: Enable the read-only command line option, which allows the user to
105 //config: open a file in read-only mode.
107 //config:config FEATURE_VI_SETOPTS
108 //config: bool "Enable set-able options, ai ic showmatch"
110 //config: depends on VI
112 //config: Enable the editor to set some (ai, ic, showmatch) options.
114 //config:config FEATURE_VI_SET
115 //config: bool "Support for :set"
117 //config: depends on VI
119 //config: Support for ":set".
121 //config:config FEATURE_VI_WIN_RESIZE
122 //config: bool "Handle window resize"
124 //config: depends on VI
126 //config: Make busybox vi behave nicely with terminals that get resized.
128 //config:config FEATURE_VI_ASK_TERMINAL
129 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
131 //config: depends on VI
133 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
134 //config: this option makes vi perform a last-ditch effort to find it:
135 //config: position cursor to 999,999 and ask terminal to report real
136 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
138 //config: This is not clean but helps a lot on serial lines and such.
140 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
142 //kbuild:lib-$(CONFIG_VI) += vi.o
144 //usage:#define vi_trivial_usage
145 //usage: "[OPTIONS] [FILE]..."
146 //usage:#define vi_full_usage "\n\n"
147 //usage: "Edit FILE\n"
148 //usage: IF_FEATURE_VI_COLON(
149 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
151 //usage: IF_FEATURE_VI_READONLY(
152 //usage: "\n -R Read-only"
154 //usage: "\n -H List available features"
157 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
158 #if ENABLE_FEATURE_VI_REGEX_SEARCH
162 /* the CRASHME code is unmaintained, and doesn't currently build */
163 #define ENABLE_FEATURE_VI_CRASHME 0
166 #if ENABLE_LOCALE_SUPPORT
168 #if ENABLE_FEATURE_VI_8BIT
169 //FIXME: this does not work properly for Unicode anyway
170 # define Isprint(c) (isprint)(c)
172 # define Isprint(c) isprint_asciionly(c)
177 /* 0x9b is Meta-ESC */
178 #if ENABLE_FEATURE_VI_8BIT
179 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
181 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
188 MAX_TABSTOP = 32, // sanity limit
189 // User input len. Need not be extra big.
190 // Lines in file being edited *can* be bigger than this.
192 // Sanity limits. We have only one buffer of this size.
193 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
194 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
197 /* VT102 ESC sequences.
198 * See "Xterm Control Sequences"
199 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
201 /* Inverse/Normal text */
202 #define ESC_BOLD_TEXT "\033[7m"
203 #define ESC_NORM_TEXT "\033[0m"
205 #define ESC_BELL "\007"
206 /* Clear-to-end-of-line */
207 #define ESC_CLEAR2EOL "\033[K"
208 /* Clear-to-end-of-screen.
209 * (We use default param here.
210 * Full sequence is "ESC [ <num> J",
211 * <num> is 0/1/2 = "erase below/above/all".)
213 #define ESC_CLEAR2EOS "\033[J"
214 /* Cursor to given coordinate (1,1: top left) */
215 #define ESC_SET_CURSOR_POS "\033[%u;%uH"
217 ///* Cursor up and down */
218 //#define ESC_CURSOR_UP "\033[A"
219 //#define ESC_CURSOR_DOWN "\n"
221 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
222 // cmds modifying text[]
223 // vda: removed "aAiIs" as they switch us into insert mode
224 // and remembering input for replay after them makes no sense
225 static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
231 FORWARD = 1, // code depends on "1" for array index
232 BACK = -1, // code depends on "-1" for array index
233 LIMITED = 0, // how much of text[] in char_search
234 FULL = 1, // how much of text[] in char_search
236 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
237 S_TO_WS = 2, // used in skip_thing() for moving "dot"
238 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
239 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
240 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
244 /* vi.c expects chars to be unsigned. */
245 /* busybox build system provides that, but it's better */
246 /* to audit and fix the source */
249 /* many references - keep near the top of globals */
250 char *text, *end; // pointers to the user data in memory
251 char *dot; // where all the action takes place
252 int text_size; // size of the allocated buffer
256 #define VI_AUTOINDENT 1
257 #define VI_SHOWMATCH 2
258 #define VI_IGNORECASE 4
259 #define VI_ERR_METHOD 8
260 #define autoindent (vi_setops & VI_AUTOINDENT)
261 #define showmatch (vi_setops & VI_SHOWMATCH )
262 #define ignorecase (vi_setops & VI_IGNORECASE)
263 /* indicate error with beep or flash */
264 #define err_method (vi_setops & VI_ERR_METHOD)
266 #if ENABLE_FEATURE_VI_READONLY
267 smallint readonly_mode;
268 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
269 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
270 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
272 #define SET_READONLY_FILE(flags) ((void)0)
273 #define SET_READONLY_MODE(flags) ((void)0)
274 #define UNSET_READONLY_FILE(flags) ((void)0)
277 smallint editing; // >0 while we are editing a file
278 // [code audit says "can be 0, 1 or 2 only"]
279 smallint cmd_mode; // 0=command 1=insert 2=replace
280 int file_modified; // buffer contents changed (counter, not flag!)
281 int last_file_modified; // = -1;
282 int save_argc; // how many file names on cmd line
283 int cmdcnt; // repetition count
284 unsigned rows, columns; // the terminal screen is this size
285 #if ENABLE_FEATURE_VI_ASK_TERMINAL
286 int get_rowcol_error;
288 int crow, ccol; // cursor is on Crow x Ccol
289 int offset; // chars scrolled off the screen to the left
290 int have_status_msg; // is default edit status needed?
291 // [don't make smallint!]
292 int last_status_cksum; // hash of current status line
293 char *current_filename;
294 char *screenbegin; // index into text[], of top line on the screen
295 char *screen; // pointer to the virtual screen buffer
296 int screensize; // and its size
298 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
299 char erase_char; // the users erase character
300 char last_input_char; // last char read from user
302 #if ENABLE_FEATURE_VI_DOT_CMD
303 smallint adding2q; // are we currently adding user input to q
304 int lmc_len; // length of last_modifying_cmd
305 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
307 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
310 #if ENABLE_FEATURE_VI_SEARCH
311 char *last_search_pattern; // last pattern from a '/' or '?' search
315 #if ENABLE_FEATURE_VI_YANKMARK
316 char *edit_file__cur_line;
318 int refresh__old_offset;
319 int format_edit_status__tot;
321 /* a few references only */
322 #if ENABLE_FEATURE_VI_YANKMARK
323 int YDreg, Ureg; // default delete register and orig line for "U"
324 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
325 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
326 char *context_start, *context_end;
328 #if ENABLE_FEATURE_VI_USE_SIGNALS
329 sigjmp_buf restart; // catch_sig()
331 struct termios term_orig, term_vi; // remember what the cooked mode was
332 #if ENABLE_FEATURE_VI_COLON
333 char *initial_cmds[3]; // currently 2 entries, NULL terminated
335 // Should be just enough to hold a key sequence,
336 // but CRASHME mode uses it as generated command buffer too
337 #if ENABLE_FEATURE_VI_CRASHME
338 char readbuffer[128];
340 char readbuffer[KEYCODE_BUFFER_SIZE];
342 #define STATUS_BUFFER_LEN 200
343 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
344 #if ENABLE_FEATURE_VI_DOT_CMD
345 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
347 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
349 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
351 #define G (*ptr_to_globals)
352 #define text (G.text )
353 #define text_size (G.text_size )
358 #define vi_setops (G.vi_setops )
359 #define editing (G.editing )
360 #define cmd_mode (G.cmd_mode )
361 #define file_modified (G.file_modified )
362 #define last_file_modified (G.last_file_modified )
363 #define save_argc (G.save_argc )
364 #define cmdcnt (G.cmdcnt )
365 #define rows (G.rows )
366 #define columns (G.columns )
367 #define crow (G.crow )
368 #define ccol (G.ccol )
369 #define offset (G.offset )
370 #define status_buffer (G.status_buffer )
371 #define have_status_msg (G.have_status_msg )
372 #define last_status_cksum (G.last_status_cksum )
373 #define current_filename (G.current_filename )
374 #define screen (G.screen )
375 #define screensize (G.screensize )
376 #define screenbegin (G.screenbegin )
377 #define tabstop (G.tabstop )
378 #define last_forward_char (G.last_forward_char )
379 #define erase_char (G.erase_char )
380 #define last_input_char (G.last_input_char )
381 #if ENABLE_FEATURE_VI_READONLY
382 #define readonly_mode (G.readonly_mode )
384 #define readonly_mode 0
386 #define adding2q (G.adding2q )
387 #define lmc_len (G.lmc_len )
389 #define ioq_start (G.ioq_start )
390 #define my_pid (G.my_pid )
391 #define last_search_pattern (G.last_search_pattern)
393 #define edit_file__cur_line (G.edit_file__cur_line)
394 #define refresh__old_offset (G.refresh__old_offset)
395 #define format_edit_status__tot (G.format_edit_status__tot)
397 #define YDreg (G.YDreg )
398 #define Ureg (G.Ureg )
399 #define mark (G.mark )
400 #define context_start (G.context_start )
401 #define context_end (G.context_end )
402 #define restart (G.restart )
403 #define term_orig (G.term_orig )
404 #define term_vi (G.term_vi )
405 #define initial_cmds (G.initial_cmds )
406 #define readbuffer (G.readbuffer )
407 #define scr_out_buf (G.scr_out_buf )
408 #define last_modifying_cmd (G.last_modifying_cmd )
409 #define get_input_line__buf (G.get_input_line__buf)
411 #define INIT_G() do { \
412 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
413 last_file_modified = -1; \
414 /* "" but has space for 2 chars: */ \
415 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
419 static int init_text_buffer(char *); // init from file or create new
420 static void edit_file(char *); // edit one file
421 static void do_cmd(int); // execute a command
422 static int next_tabstop(int);
423 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
424 static char *begin_line(char *); // return pointer to cur line B-o-l
425 static char *end_line(char *); // return pointer to cur line E-o-l
426 static char *prev_line(char *); // return pointer to prev line B-o-l
427 static char *next_line(char *); // return pointer to next line B-o-l
428 static char *end_screen(void); // get pointer to last char on screen
429 static int count_lines(char *, char *); // count line from start to stop
430 static char *find_line(int); // find begining of line #li
431 static char *move_to_col(char *, int); // move "p" to column l
432 static void dot_left(void); // move dot left- dont leave line
433 static void dot_right(void); // move dot right- dont leave line
434 static void dot_begin(void); // move dot to B-o-l
435 static void dot_end(void); // move dot to E-o-l
436 static void dot_next(void); // move dot to next line B-o-l
437 static void dot_prev(void); // move dot to prev line B-o-l
438 static void dot_scroll(int, int); // move the screen up or down
439 static void dot_skip_over_ws(void); // move dot pat WS
440 static void dot_delete(void); // delete the char at 'dot'
441 static char *bound_dot(char *); // make sure text[0] <= P < "end"
442 static char *new_screen(int, int); // malloc virtual screen memory
443 static char *char_insert(char *, char); // insert the char c at 'p'
444 // might reallocate text[]! use p += stupid_insert(p, ...),
445 // and be careful to not use pointers into potentially freed text[]!
446 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
447 static int find_range(char **, char **, char); // return pointers for an object
448 static int st_test(char *, int, int, char *); // helper for skip_thing()
449 static char *skip_thing(char *, int, int, int); // skip some object
450 static char *find_pair(char *, char); // find matching pair () [] {}
451 static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
452 // might reallocate text[]! use p += text_hole_make(p, ...),
453 // and be careful to not use pointers into potentially freed text[]!
454 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
455 static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
456 static void show_help(void); // display some help info
457 static void rawmode(void); // set "raw" mode on tty
458 static void cookmode(void); // return to "cooked" mode on tty
459 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
460 static int mysleep(int);
461 static int readit(void); // read (maybe cursor) key from stdin
462 static int get_one_char(void); // read 1 char from stdin
463 static int file_size(const char *); // what is the byte size of "fn"
464 #if !ENABLE_FEATURE_VI_READONLY
465 #define file_insert(fn, p, update_ro_status) file_insert(fn, p)
467 // file_insert might reallocate text[]!
468 static int file_insert(const char *, char *, int);
469 static int file_write(char *, char *, char *);
470 static void place_cursor(int, int);
471 static void screen_erase(void);
472 static void clear_to_eol(void);
473 static void clear_to_eos(void);
474 static void go_bottom_and_clear_to_eol(void);
475 static void standout_start(void); // send "start reverse video" sequence
476 static void standout_end(void); // send "end reverse video" sequence
477 static void flash(int); // flash the terminal screen
478 static void show_status_line(void); // put a message on the bottom line
479 static void status_line(const char *, ...); // print to status buf
480 static void status_line_bold(const char *, ...);
481 static void status_line_bold_errno(const char *fn);
482 static void not_implemented(const char *); // display "Not implemented" message
483 static int format_edit_status(void); // format file status on status line
484 static void redraw(int); // force a full screen refresh
485 static char* format_line(char* /*, int*/);
486 static void refresh(int); // update the terminal from screen[]
488 static void Indicate_Error(void); // use flash or beep to indicate error
489 #define indicate_error(c) Indicate_Error()
490 static void Hit_Return(void);
492 #if ENABLE_FEATURE_VI_SEARCH
493 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
495 #if ENABLE_FEATURE_VI_COLON
496 static char *get_one_address(char *, int *); // get colon addr, if present
497 static char *get_address(char *, int *, int *); // get two colon addrs, if present
498 static void colon(char *); // execute the "colon" mode cmds
500 #if ENABLE_FEATURE_VI_USE_SIGNALS
501 static void winch_sig(int); // catch window size changes
502 static void suspend_sig(int); // catch ctrl-Z
503 static void catch_sig(int); // catch ctrl-C and alarm time-outs
505 #if ENABLE_FEATURE_VI_DOT_CMD
506 static void start_new_cmd_q(char); // new queue for command
507 static void end_cmd_q(void); // stop saving input chars
509 #define end_cmd_q() ((void)0)
511 #if ENABLE_FEATURE_VI_SETOPTS
512 static void showmatching(char *); // show the matching pair () [] {}
514 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
515 // might reallocate text[]! use p += string_insert(p, ...),
516 // and be careful to not use pointers into potentially freed text[]!
517 static uintptr_t string_insert(char *, const char *); // insert the string at 'p'
519 #if ENABLE_FEATURE_VI_YANKMARK
520 static char *text_yank(char *, char *, int); // save copy of "p" into a register
521 static char what_reg(void); // what is letter of current YDreg
522 static void check_context(char); // remember context for '' command
524 #if ENABLE_FEATURE_VI_CRASHME
525 static void crash_dummy();
526 static void crash_test();
527 static int crashme = 0;
531 static void write1(const char *out)
536 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
537 int vi_main(int argc, char **argv)
543 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
546 #if ENABLE_FEATURE_VI_CRASHME
547 srand((long) my_pid);
549 #ifdef NO_SUCH_APPLET_YET
550 /* If we aren't "vi", we are "view" */
551 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
552 SET_READONLY_MODE(readonly_mode);
556 // autoindent is not default in vim 7.3
557 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
558 // 1- process $HOME/.exrc file (not inplemented yet)
559 // 2- process EXINIT variable from environment
560 // 3- process command line args
561 #if ENABLE_FEATURE_VI_COLON
563 char *p = getenv("EXINIT");
565 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
568 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
570 #if ENABLE_FEATURE_VI_CRASHME
575 #if ENABLE_FEATURE_VI_READONLY
576 case 'R': // Read-only flag
577 SET_READONLY_MODE(readonly_mode);
580 #if ENABLE_FEATURE_VI_COLON
581 case 'c': // cmd line vi command
583 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
595 // The argv array can be used by the ":next" and ":rewind" commands
599 //----- This is the main file handling loop --------------
602 // "Save cursor, use alternate screen buffer, clear screen"
603 write1("\033[?1049h");
605 edit_file(argv[optind]); /* param might be NULL */
606 if (++optind >= argc)
609 // "Use normal screen buffer, restore cursor"
610 write1("\033[?1049l");
611 //-----------------------------------------------------------
616 /* read text from file or create an empty buf */
617 /* will also update current_filename */
618 static int init_text_buffer(char *fn)
621 int size = file_size(fn); // file size. -1 means does not exist.
623 /* allocate/reallocate text buffer */
625 text_size = size + 10240;
626 screenbegin = dot = end = text = xzalloc(text_size);
628 if (fn != current_filename) {
629 free(current_filename);
630 current_filename = xstrdup(fn);
633 // file dont exist. Start empty buf with dummy line
634 char_insert(text, '\n');
637 rc = file_insert(fn, text, 1);
640 last_file_modified = -1;
641 #if ENABLE_FEATURE_VI_YANKMARK
642 /* init the marks. */
643 memset(mark, 0, sizeof(mark));
648 #if ENABLE_FEATURE_VI_WIN_RESIZE
649 static int query_screen_dimensions(void)
651 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
652 if (rows > MAX_SCR_ROWS)
654 if (columns > MAX_SCR_COLS)
655 columns = MAX_SCR_COLS;
659 # define query_screen_dimensions() (0)
662 static void edit_file(char *fn)
664 #if ENABLE_FEATURE_VI_YANKMARK
665 #define cur_line edit_file__cur_line
668 #if ENABLE_FEATURE_VI_USE_SIGNALS
672 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
676 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
677 #if ENABLE_FEATURE_VI_ASK_TERMINAL
678 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
680 write1("\033[999;999H" "\033[6n");
682 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
683 if ((int32_t)k == KEYCODE_CURSOR_POS) {
684 uint32_t rc = (k >> 32);
685 columns = (rc & 0x7fff);
686 if (columns > MAX_SCR_COLS)
687 columns = MAX_SCR_COLS;
688 rows = ((rc >> 16) & 0x7fff);
689 if (rows > MAX_SCR_ROWS)
694 new_screen(rows, columns); // get memory for virtual screen
695 init_text_buffer(fn);
697 #if ENABLE_FEATURE_VI_YANKMARK
698 YDreg = 26; // default Yank/Delete reg
699 Ureg = 27; // hold orig line for "U" cmd
700 mark[26] = mark[27] = text; // init "previous context"
703 last_forward_char = last_input_char = '\0';
707 #if ENABLE_FEATURE_VI_USE_SIGNALS
708 signal(SIGINT, catch_sig);
709 signal(SIGWINCH, winch_sig);
710 signal(SIGTSTP, suspend_sig);
711 sig = sigsetjmp(restart, 1);
713 screenbegin = dot = text;
717 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
720 offset = 0; // no horizontal offset
722 #if ENABLE_FEATURE_VI_DOT_CMD
724 ioq = ioq_start = NULL;
729 #if ENABLE_FEATURE_VI_COLON
734 while ((p = initial_cmds[n]) != NULL) {
744 free(initial_cmds[n]);
745 initial_cmds[n] = NULL;
750 redraw(FALSE); // dont force every col re-draw
751 //------This is the main Vi cmd handling loop -----------------------
752 while (editing > 0) {
753 #if ENABLE_FEATURE_VI_CRASHME
755 if ((end - text) > 1) {
756 crash_dummy(); // generate a random command
759 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
765 last_input_char = c = get_one_char(); // get a cmd from user
766 #if ENABLE_FEATURE_VI_YANKMARK
767 // save a copy of the current line- for the 'U" command
768 if (begin_line(dot) != cur_line) {
769 cur_line = begin_line(dot);
770 text_yank(begin_line(dot), end_line(dot), Ureg);
773 #if ENABLE_FEATURE_VI_DOT_CMD
774 // These are commands that change text[].
775 // Remember the input for the "." command
776 if (!adding2q && ioq_start == NULL
777 && cmd_mode == 0 // command mode
778 && c > '\0' // exclude NUL and non-ASCII chars
779 && c < 0x7f // (Unicode and such)
780 && strchr(modifying_cmds, c)
785 do_cmd(c); // execute the user command
787 // poll to see if there is input already waiting. if we are
788 // not able to display output fast enough to keep up, skip
789 // the display update until we catch up with input.
790 if (!readbuffer[0] && mysleep(0) == 0) {
791 // no input pending - so update output
795 #if ENABLE_FEATURE_VI_CRASHME
797 crash_test(); // test editor variables
800 //-------------------------------------------------------------------
802 go_bottom_and_clear_to_eol();
807 //----- The Colon commands -------------------------------------
808 #if ENABLE_FEATURE_VI_COLON
809 static char *get_one_address(char *p, int *addr) // get colon addr, if present
813 IF_FEATURE_VI_YANKMARK(char c;)
814 IF_FEATURE_VI_SEARCH(char *pat;)
816 *addr = -1; // assume no addr
817 if (*p == '.') { // the current line
820 *addr = count_lines(text, q);
822 #if ENABLE_FEATURE_VI_YANKMARK
823 else if (*p == '\'') { // is this a mark addr
827 if (c >= 'a' && c <= 'z') {
830 q = mark[(unsigned char) c];
831 if (q != NULL) { // is mark valid
832 *addr = count_lines(text, q);
837 #if ENABLE_FEATURE_VI_SEARCH
838 else if (*p == '/') { // a search pattern
839 q = strchrnul(++p, '/');
840 pat = xstrndup(p, q - p); // save copy of pattern
844 q = char_search(dot, pat, FORWARD, FULL);
846 *addr = count_lines(text, q);
851 else if (*p == '$') { // the last line in file
853 q = begin_line(end - 1);
854 *addr = count_lines(text, q);
855 } else if (isdigit(*p)) { // specific line number
856 sscanf(p, "%d%n", addr, &st);
859 // unrecognized address - assume -1
865 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
867 //----- get the address' i.e., 1,3 'a,'b -----
868 // get FIRST addr, if present
870 p++; // skip over leading spaces
871 if (*p == '%') { // alias for 1,$
874 *e = count_lines(text, end-1);
877 p = get_one_address(p, b);
880 if (*p == ',') { // is there a address separator
884 // get SECOND addr, if present
885 p = get_one_address(p, e);
889 p++; // skip over trailing spaces
893 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
894 static void setops(const char *args, const char *opname, int flg_no,
895 const char *short_opname, int opt)
897 const char *a = args + flg_no;
898 int l = strlen(opname) - 1; /* opname have + ' ' */
900 // maybe strncmp? we had tons of erroneous strncasecmp's...
901 if (strncasecmp(a, opname, l) == 0
902 || strncasecmp(a, short_opname, 2) == 0
912 // buf must be no longer than MAX_INPUT_LEN!
913 static void colon(char *buf)
915 char c, *orig_buf, *buf1, *q, *r;
916 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
917 int i, l, li, ch, b, e;
918 int useforce, forced = FALSE;
920 // :3154 // if (-e line 3154) goto it else stay put
921 // :4,33w! foo // write a portion of buffer to file "foo"
922 // :w // write all of buffer to current file
924 // :q! // quit- dont care about modified file
925 // :'a,'z!sort -u // filter block through sort
926 // :'f // goto mark "f"
927 // :'fl // list literal the mark "f" line
928 // :.r bar // read file "bar" into buffer before dot
929 // :/123/,/abc/d // delete lines from "123" line to "abc" line
930 // :/xyz/ // goto the "xyz" line
931 // :s/find/replace/ // substitute pattern "find" with "replace"
932 // :!<cmd> // run <cmd> then return
938 buf++; // move past the ':'
942 q = text; // assume 1,$ for the range
944 li = count_lines(text, end - 1);
945 fn = current_filename;
947 // look for optional address(es) :. :1 :1,9 :'q,'a :%
948 buf = get_address(buf, &b, &e);
950 // remember orig command line
953 // get the COMMAND into cmd[]
955 while (*buf != '\0') {
962 while (isblank(*buf))
966 buf1 = last_char_is(cmd, '!');
969 *buf1 = '\0'; // get rid of !
972 // if there is only one addr, then the addr
973 // is the line number of the single line the
974 // user wants. So, reset the end
975 // pointer to point at end of the "b" line
976 q = find_line(b); // what line is #b
981 // we were given two addrs. change the
982 // end pointer to the addr given by user.
983 r = find_line(e); // what line is #e
987 // ------------ now look for the command ------------
989 if (i == 0) { // :123CR goto line #123
991 dot = find_line(b); // what line is #b
995 #if ENABLE_FEATURE_ALLOW_EXEC
996 else if (cmd[0] == '!') { // run a cmd
998 // :!ls run the <cmd>
999 go_bottom_and_clear_to_eol();
1001 retcode = system(orig_buf + 1); // run the cmd
1003 printf("\nshell returned %i\n\n", retcode);
1005 Hit_Return(); // let user see results
1008 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1009 if (b < 0) { // no addr given- use defaults
1010 b = e = count_lines(text, dot);
1012 status_line("%d", b);
1013 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1014 if (b < 0) { // no addr given- use defaults
1015 q = begin_line(dot); // assume .,. for the range
1018 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1020 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1021 // don't edit, if the current file has been modified
1022 if (file_modified && !useforce) {
1023 status_line_bold("No write since last change (:%s! overrides)", cmd);
1027 // the user supplied a file name
1029 } else if (current_filename && current_filename[0]) {
1030 // no user supplied name- use the current filename
1031 // fn = current_filename; was set by default
1033 // no user file name, no current name- punt
1034 status_line_bold("No current filename");
1038 if (init_text_buffer(fn) < 0)
1041 #if ENABLE_FEATURE_VI_YANKMARK
1042 if (Ureg >= 0 && Ureg < 28) {
1043 free(reg[Ureg]); // free orig line reg- for 'U'
1046 if (YDreg >= 0 && YDreg < 28) {
1047 free(reg[YDreg]); // free default yank/delete register
1051 // how many lines in text[]?
1052 li = count_lines(text, end - 1);
1053 status_line("'%s'%s"
1054 IF_FEATURE_VI_READONLY("%s")
1055 " %dL, %dC", current_filename,
1056 (file_size(fn) < 0 ? " [New file]" : ""),
1057 IF_FEATURE_VI_READONLY(
1058 ((readonly_mode) ? " [Readonly]" : ""),
1061 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1062 if (b != -1 || e != -1) {
1063 status_line_bold("No address allowed on this command");
1067 // user wants a new filename
1068 free(current_filename);
1069 current_filename = xstrdup(args);
1071 // user wants file status info
1072 last_status_cksum = 0; // force status update
1074 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1075 // print out values of all features
1076 go_bottom_and_clear_to_eol();
1081 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1082 if (b < 0) { // no addr given- use defaults
1083 q = begin_line(dot); // assume .,. for the range
1086 go_bottom_and_clear_to_eol();
1088 for (; q <= r; q++) {
1092 c_is_no_print = (c & 0x80) && !Isprint(c);
1093 if (c_is_no_print) {
1099 } else if (c < ' ' || c == 127) {
1111 } else if (strncmp(cmd, "quit", i) == 0 // quit
1112 || strncmp(cmd, "next", i) == 0 // edit next file
1113 || strncmp(cmd, "prev", i) == 0 // edit previous file
1118 // force end of argv list
1124 // don't exit if the file been modified
1125 if (file_modified) {
1126 status_line_bold("No write since last change (:%s! overrides)", cmd);
1129 // are there other file to edit
1130 n = save_argc - optind - 1;
1131 if (*cmd == 'q' && n > 0) {
1132 status_line_bold("%d more file(s) to edit", n);
1135 if (*cmd == 'n' && n <= 0) {
1136 status_line_bold("No more files to edit");
1140 // are there previous files to edit
1142 status_line_bold("No previous files to edit");
1148 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1151 status_line_bold("No filename given");
1154 if (b < 0) { // no addr given- use defaults
1155 q = begin_line(dot); // assume "dot"
1157 // read after current line- unless user said ":0r foo"
1160 { // dance around potentially-reallocated text[]
1161 uintptr_t ofs = q - text;
1162 ch = file_insert(fn, q, 0);
1166 goto ret; // nothing was inserted
1167 // how many lines in text[]?
1168 li = count_lines(q, q + ch - 1);
1170 IF_FEATURE_VI_READONLY("%s")
1172 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1175 // if the insert is before "dot" then we need to update
1178 /*file_modified++; - done by file_insert */
1180 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1181 if (file_modified && !useforce) {
1182 status_line_bold("No write since last change (:%s! overrides)", cmd);
1184 // reset the filenames to edit
1185 optind = -1; /* start from 0th file */
1188 #if ENABLE_FEATURE_VI_SET
1189 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1190 #if ENABLE_FEATURE_VI_SETOPTS
1193 i = 0; // offset into args
1194 // only blank is regarded as args delimiter. What about tab '\t'?
1195 if (!args[0] || strcasecmp(args, "all") == 0) {
1196 // print out values of all options
1197 #if ENABLE_FEATURE_VI_SETOPTS
1204 autoindent ? "" : "no",
1205 err_method ? "" : "no",
1206 ignorecase ? "" : "no",
1207 showmatch ? "" : "no",
1213 #if ENABLE_FEATURE_VI_SETOPTS
1216 if (strncmp(argp, "no", 2) == 0)
1217 i = 2; // ":set noautoindent"
1218 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1219 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1220 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1221 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1222 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1224 sscanf(argp + i+8, "%u", &t);
1225 if (t > 0 && t <= MAX_TABSTOP)
1228 argp = skip_non_whitespace(argp);
1229 argp = skip_whitespace(argp);
1231 #endif /* FEATURE_VI_SETOPTS */
1232 #endif /* FEATURE_VI_SET */
1233 #if ENABLE_FEATURE_VI_SEARCH
1234 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1235 char *F, *R, *flags;
1236 size_t len_F, len_R;
1237 int gflag; // global replace flag
1239 // F points to the "find" pattern
1240 // R points to the "replace" pattern
1241 // replace the cmd line delimiters "/" with NULs
1242 c = orig_buf[1]; // what is the delimiter
1243 F = orig_buf + 2; // start of "find"
1244 R = strchr(F, c); // middle delimiter
1248 *R++ = '\0'; // terminate "find"
1249 flags = strchr(R, c);
1253 *flags++ = '\0'; // terminate "replace"
1257 if (b < 0) { // maybe :s/foo/bar/
1258 q = begin_line(dot); // start with cur line
1259 b = count_lines(text, q); // cur line number
1262 e = b; // maybe :.s/foo/bar/
1264 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1265 char *ls = q; // orig line start
1268 found = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1271 // we found the "find" pattern - delete it
1272 text_hole_delete(found, found + len_F - 1);
1273 // inset the "replace" patern
1274 bias = string_insert(found, R); // insert the string
1277 /*q += bias; - recalculated anyway */
1278 // check for "global" :s/foo/bar/g
1280 if ((found + len_R) < end_line(ls)) {
1282 goto vc4; // don't let q move past cur line
1288 #endif /* FEATURE_VI_SEARCH */
1289 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1290 status_line(BB_VER " " BB_BT);
1291 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1292 || strncmp(cmd, "wq", i) == 0
1293 || strncmp(cmd, "wn", i) == 0
1294 || (cmd[0] == 'x' && !cmd[1])
1296 // is there a file name to write to?
1300 #if ENABLE_FEATURE_VI_READONLY
1301 if (readonly_mode && !useforce) {
1302 status_line_bold("'%s' is read only", fn);
1306 // how many lines in text[]?
1307 li = count_lines(q, r);
1309 // see if file exists- if not, its just a new file request
1311 // if "fn" is not write-able, chmod u+w
1312 // sprintf(syscmd, "chmod u+w %s", fn);
1316 l = file_write(fn, q, r);
1317 if (useforce && forced) {
1319 // sprintf(syscmd, "chmod u-w %s", fn);
1325 status_line_bold_errno(fn);
1327 status_line("'%s' %dL, %dC", fn, li, l);
1328 if (q == text && r == end - 1 && l == ch) {
1330 last_file_modified = -1;
1332 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
1333 || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
1340 #if ENABLE_FEATURE_VI_YANKMARK
1341 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1342 if (b < 0) { // no addr given- use defaults
1343 q = begin_line(dot); // assume .,. for the range
1346 text_yank(q, r, YDreg);
1347 li = count_lines(q, r);
1348 status_line("Yank %d lines (%d chars) into [%c]",
1349 li, strlen(reg[YDreg]), what_reg());
1353 not_implemented(cmd);
1356 dot = bound_dot(dot); // make sure "dot" is valid
1358 #if ENABLE_FEATURE_VI_SEARCH
1360 status_line(":s expression missing delimiters");
1364 #endif /* FEATURE_VI_COLON */
1366 static void Hit_Return(void)
1371 write1("[Hit return to continue]");
1373 while ((c = get_one_char()) != '\n' && c != '\r')
1375 redraw(TRUE); // force redraw all
1378 static int next_tabstop(int col)
1380 return col + ((tabstop - 1) - (col % tabstop));
1383 //----- Synchronize the cursor to Dot --------------------------
1384 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1386 char *beg_cur; // begin and end of "d" line
1390 beg_cur = begin_line(d); // first char of cur line
1392 if (beg_cur < screenbegin) {
1393 // "d" is before top line on screen
1394 // how many lines do we have to move
1395 cnt = count_lines(beg_cur, screenbegin);
1397 screenbegin = beg_cur;
1398 if (cnt > (rows - 1) / 2) {
1399 // we moved too many lines. put "dot" in middle of screen
1400 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1401 screenbegin = prev_line(screenbegin);
1405 char *end_scr; // begin and end of screen
1406 end_scr = end_screen(); // last char of screen
1407 if (beg_cur > end_scr) {
1408 // "d" is after bottom line on screen
1409 // how many lines do we have to move
1410 cnt = count_lines(end_scr, beg_cur);
1411 if (cnt > (rows - 1) / 2)
1412 goto sc1; // too many lines
1413 for (ro = 0; ro < cnt - 1; ro++) {
1414 // move screen begin the same amount
1415 screenbegin = next_line(screenbegin);
1416 // now, move the end of screen
1417 end_scr = next_line(end_scr);
1418 end_scr = end_line(end_scr);
1422 // "d" is on screen- find out which row
1424 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1430 // find out what col "d" is on
1432 while (tp < d) { // drive "co" to correct column
1433 if (*tp == '\n') //vda || *tp == '\0')
1436 // handle tabs like real vi
1437 if (d == tp && cmd_mode) {
1440 co = next_tabstop(co);
1441 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1442 co++; // display as ^X, use 2 columns
1448 // "co" is the column where "dot" is.
1449 // The screen has "columns" columns.
1450 // The currently displayed columns are 0+offset -- columns+ofset
1451 // |-------------------------------------------------------------|
1453 // offset | |------- columns ----------------|
1455 // If "co" is already in this range then we do not have to adjust offset
1456 // but, we do have to subtract the "offset" bias from "co".
1457 // If "co" is outside this range then we have to change "offset".
1458 // If the first char of a line is a tab the cursor will try to stay
1459 // in column 7, but we have to set offset to 0.
1461 if (co < 0 + offset) {
1464 if (co >= columns + offset) {
1465 offset = co - columns + 1;
1467 // if the first char of the line is a tab, and "dot" is sitting on it
1468 // force offset to 0.
1469 if (d == beg_cur && *d == '\t') {
1478 //----- Text Movement Routines ---------------------------------
1479 static char *begin_line(char *p) // return pointer to first char cur line
1482 p = memrchr(text, '\n', p - text);
1490 static char *end_line(char *p) // return pointer to NL of cur line
1493 p = memchr(p, '\n', end - p - 1);
1500 static char *dollar_line(char *p) // return pointer to just before NL line
1503 // Try to stay off of the Newline
1504 if (*p == '\n' && (p - begin_line(p)) > 0)
1509 static char *prev_line(char *p) // return pointer first char prev line
1511 p = begin_line(p); // goto begining of cur line
1512 if (p > text && p[-1] == '\n')
1513 p--; // step to prev line
1514 p = begin_line(p); // goto begining of prev line
1518 static char *next_line(char *p) // return pointer first char next line
1521 if (p < end - 1 && *p == '\n')
1522 p++; // step to next line
1526 //----- Text Information Routines ------------------------------
1527 static char *end_screen(void)
1532 // find new bottom line
1534 for (cnt = 0; cnt < rows - 2; cnt++)
1540 // count line from start to stop
1541 static int count_lines(char *start, char *stop)
1546 if (stop < start) { // start and stop are backwards- reverse them
1552 stop = end_line(stop);
1553 while (start <= stop && start <= end - 1) {
1554 start = end_line(start);
1562 static char *find_line(int li) // find begining of line #li
1566 for (q = text; li > 1; li--) {
1572 //----- Dot Movement Routines ----------------------------------
1573 static void dot_left(void)
1575 if (dot > text && dot[-1] != '\n')
1579 static void dot_right(void)
1581 if (dot < end - 1 && *dot != '\n')
1585 static void dot_begin(void)
1587 dot = begin_line(dot); // return pointer to first char cur line
1590 static void dot_end(void)
1592 dot = end_line(dot); // return pointer to last char cur line
1595 static char *move_to_col(char *p, int l)
1601 while (co < l && p < end) {
1602 if (*p == '\n') //vda || *p == '\0')
1605 co = next_tabstop(co);
1606 } else if (*p < ' ' || *p == 127) {
1607 co++; // display as ^X, use 2 columns
1615 static void dot_next(void)
1617 dot = next_line(dot);
1620 static void dot_prev(void)
1622 dot = prev_line(dot);
1625 static void dot_scroll(int cnt, int dir)
1629 for (; cnt > 0; cnt--) {
1632 // ctrl-Y scroll up one line
1633 screenbegin = prev_line(screenbegin);
1636 // ctrl-E scroll down one line
1637 screenbegin = next_line(screenbegin);
1640 // make sure "dot" stays on the screen so we dont scroll off
1641 if (dot < screenbegin)
1643 q = end_screen(); // find new bottom line
1645 dot = begin_line(q); // is dot is below bottom line?
1649 static void dot_skip_over_ws(void)
1652 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1656 static void dot_delete(void) // delete the char at 'dot'
1658 text_hole_delete(dot, dot);
1661 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1663 if (p >= end && end > text) {
1665 indicate_error('1');
1669 indicate_error('2');
1674 //----- Helper Utility Routines --------------------------------
1676 //----------------------------------------------------------------
1677 //----- Char Routines --------------------------------------------
1678 /* Chars that are part of a word-
1679 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1680 * Chars that are Not part of a word (stoppers)
1681 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1682 * Chars that are WhiteSpace
1683 * TAB NEWLINE VT FF RETURN SPACE
1684 * DO NOT COUNT NEWLINE AS WHITESPACE
1687 static char *new_screen(int ro, int co)
1692 screensize = ro * co + 8;
1693 screen = xmalloc(screensize);
1694 // initialize the new screen. assume this will be a empty file.
1696 // non-existent text[] lines start with a tilde (~).
1697 for (li = 1; li < ro - 1; li++) {
1698 screen[(li * co) + 0] = '~';
1703 #if ENABLE_FEATURE_VI_SEARCH
1705 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1707 // search for pattern starting at p
1708 static char *char_search(char *p, const char *pat, int dir, int range)
1711 struct re_pattern_buffer preg;
1715 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1721 // assume a LIMITED forward search
1729 // count the number of chars to search over, forward or backward
1733 // RANGE could be negative if we are searching backwards
1736 q = (char *)re_compile_pattern(pat, strlen(pat), (struct re_pattern_buffer *)&preg);
1738 // The pattern was not compiled
1739 status_line_bold("bad search pattern: '%s': %s", pat, q);
1740 i = 0; // return p if pattern not compiled
1750 // search for the compiled pattern, preg, in p[]
1751 // range < 0- search backward
1752 // range > 0- search forward
1754 // re_search() < 0 not found or error
1755 // re_search() > 0 index of found pattern
1756 // struct pattern char int int int struct reg
1757 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1758 i = re_search(&preg, q, size, 0, range, 0);
1761 i = 0; // return NULL if pattern not found
1764 if (dir == FORWARD) {
1774 # if ENABLE_FEATURE_VI_SETOPTS
1775 static int mycmp(const char *s1, const char *s2, int len)
1778 return strncasecmp(s1, s2, len);
1780 return strncmp(s1, s2, len);
1783 # define mycmp strncmp
1786 static char *char_search(char *p, const char *pat, int dir, int range)
1792 if (dir == FORWARD) {
1793 stop = end - 1; // assume range is p - end-1
1794 if (range == LIMITED)
1795 stop = next_line(p); // range is to next line
1796 for (start = p; start < stop; start++) {
1797 if (mycmp(start, pat, len) == 0) {
1801 } else if (dir == BACK) {
1802 stop = text; // assume range is text - p
1803 if (range == LIMITED)
1804 stop = prev_line(p); // range is to prev line
1805 for (start = p - len; start >= stop; start--) {
1806 if (mycmp(start, pat, len) == 0) {
1811 // pattern not found
1817 #endif /* FEATURE_VI_SEARCH */
1819 static char *char_insert(char *p, char c) // insert the char c at 'p'
1821 if (c == 22) { // Is this an ctrl-V?
1822 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1823 refresh(FALSE); // show the ^
1828 } else if (c == 27) { // Is this an ESC?
1831 end_cmd_q(); // stop adding to q
1832 last_status_cksum = 0; // force status update
1833 if ((p[-1] != '\n') && (dot > text)) {
1836 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1838 if ((p[-1] != '\n') && (dot>text)) {
1840 p = text_hole_delete(p, p); // shrink buffer 1 char
1843 #if ENABLE_FEATURE_VI_SETOPTS
1844 // insert a char into text[]
1845 char *sp; // "save p"
1849 c = '\n'; // translate \r to \n
1850 #if ENABLE_FEATURE_VI_SETOPTS
1851 sp = p; // remember addr of insert
1853 p += 1 + stupid_insert(p, c); // insert the char
1854 #if ENABLE_FEATURE_VI_SETOPTS
1855 if (showmatch && strchr(")]}", *sp) != NULL) {
1858 if (autoindent && c == '\n') { // auto indent the new line
1861 q = prev_line(p); // use prev line as template
1862 len = strspn(q, " \t"); // space or tab
1865 bias = text_hole_make(p, len);
1877 // might reallocate text[]! use p += stupid_insert(p, ...),
1878 // and be careful to not use pointers into potentially freed text[]!
1879 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1882 bias = text_hole_make(p, 1);
1885 //file_modified++; - done by text_hole_make()
1889 static int find_range(char **start, char **stop, char c)
1891 char *save_dot, *p, *q, *t;
1892 int cnt, multiline = 0;
1897 if (strchr("cdy><", c)) {
1898 // these cmds operate on whole lines
1899 p = q = begin_line(p);
1900 for (cnt = 1; cnt < cmdcnt; cnt++) {
1904 } else if (strchr("^%$0bBeEfth\b\177", c)) {
1905 // These cmds operate on char positions
1906 do_cmd(c); // execute movement cmd
1908 } else if (strchr("wW", c)) {
1909 do_cmd(c); // execute movement cmd
1910 // if we are at the next word's first char
1911 // step back one char
1912 // but check the possibilities when it is true
1913 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1914 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1915 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1916 dot--; // move back off of next word
1917 if (dot > text && *dot == '\n')
1918 dot--; // stay off NL
1920 } else if (strchr("H-k{", c)) {
1921 // these operate on multi-lines backwards
1922 q = end_line(dot); // find NL
1923 do_cmd(c); // execute movement cmd
1926 } else if (strchr("L+j}\r\n", c)) {
1927 // these operate on multi-lines forwards
1928 p = begin_line(dot);
1929 do_cmd(c); // execute movement cmd
1930 dot_end(); // find NL
1933 // nothing -- this causes any other values of c to
1934 // represent the one-character range under the
1935 // cursor. this is correct for ' ' and 'l', but
1936 // perhaps no others.
1945 // backward char movements don't include start position
1946 if (q > p && strchr("^0bBh\b\177", c)) q--;
1949 for (t = p; t <= q; t++) {
1962 static int st_test(char *p, int type, int dir, char *tested)
1972 if (type == S_BEFORE_WS) {
1974 test = (!isspace(c) || c == '\n');
1976 if (type == S_TO_WS) {
1978 test = (!isspace(c) || c == '\n');
1980 if (type == S_OVER_WS) {
1984 if (type == S_END_PUNCT) {
1988 if (type == S_END_ALNUM) {
1990 test = (isalnum(c) || c == '_');
1996 static char *skip_thing(char *p, int linecnt, int dir, int type)
2000 while (st_test(p, type, dir, &c)) {
2001 // make sure we limit search to correct number of lines
2002 if (c == '\n' && --linecnt < 1)
2004 if (dir >= 0 && p >= end - 1)
2006 if (dir < 0 && p <= text)
2008 p += dir; // move to next char
2013 // find matching char of pair () [] {}
2014 static char *find_pair(char *p, const char c)
2021 dir = 1; // assume forward
2023 case '(': match = ')'; break;
2024 case '[': match = ']'; break;
2025 case '{': match = '}'; break;
2026 case ')': match = '('; dir = -1; break;
2027 case ']': match = '['; dir = -1; break;
2028 case '}': match = '{'; dir = -1; break;
2030 for (q = p + dir; text <= q && q < end; q += dir) {
2031 // look for match, count levels of pairs (( ))
2033 level++; // increase pair levels
2035 level--; // reduce pair level
2037 break; // found matching pair
2040 q = NULL; // indicate no match
2044 #if ENABLE_FEATURE_VI_SETOPTS
2045 // show the matching char of a pair, () [] {}
2046 static void showmatching(char *p)
2050 // we found half of a pair
2051 q = find_pair(p, *p); // get loc of matching char
2053 indicate_error('3'); // no matching char
2055 // "q" now points to matching pair
2056 save_dot = dot; // remember where we are
2057 dot = q; // go to new loc
2058 refresh(FALSE); // let the user see it
2059 mysleep(40); // give user some time
2060 dot = save_dot; // go back to old loc
2064 #endif /* FEATURE_VI_SETOPTS */
2066 // open a hole in text[]
2067 // might reallocate text[]! use p += text_hole_make(p, ...),
2068 // and be careful to not use pointers into potentially freed text[]!
2069 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2075 end += size; // adjust the new END
2076 if (end >= (text + text_size)) {
2078 text_size += end - (text + text_size) + 10240;
2079 new_text = xrealloc(text, text_size);
2080 bias = (new_text - text);
2081 screenbegin += bias;
2085 #if ENABLE_FEATURE_VI_YANKMARK
2088 for (i = 0; i < ARRAY_SIZE(mark); i++)
2095 memmove(p + size, p, end - size - p);
2096 memset(p, ' ', size); // clear new hole
2101 // close a hole in text[]
2102 static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive
2107 // move forwards, from beginning
2111 if (q < p) { // they are backward- swap them
2115 hole_size = q - p + 1;
2117 if (src < text || src > end)
2119 if (dest < text || dest >= end)
2122 goto thd_atend; // just delete the end of the buffer
2123 memmove(dest, src, cnt);
2125 end = end - hole_size; // adjust the new END
2127 dest = end - 1; // make sure dest in below end-1
2129 dest = end = text; // keep pointers valid
2135 // copy text into register, then delete text.
2136 // if dist <= 0, do not include, or go past, a NewLine
2138 static char *yank_delete(char *start, char *stop, int dist, int yf)
2142 // make sure start <= stop
2144 // they are backwards, reverse them
2150 // we cannot cross NL boundaries
2154 // dont go past a NewLine
2155 for (; p + 1 <= stop; p++) {
2157 stop = p; // "stop" just before NewLine
2163 #if ENABLE_FEATURE_VI_YANKMARK
2164 text_yank(start, stop, YDreg);
2166 if (yf == YANKDEL) {
2167 p = text_hole_delete(start, stop);
2172 static void show_help(void)
2174 puts("These features are available:"
2175 #if ENABLE_FEATURE_VI_SEARCH
2176 "\n\tPattern searches with / and ?"
2178 #if ENABLE_FEATURE_VI_DOT_CMD
2179 "\n\tLast command repeat with ."
2181 #if ENABLE_FEATURE_VI_YANKMARK
2182 "\n\tLine marking with 'x"
2183 "\n\tNamed buffers with \"x"
2185 #if ENABLE_FEATURE_VI_READONLY
2186 //not implemented: "\n\tReadonly if vi is called as \"view\""
2187 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2189 #if ENABLE_FEATURE_VI_SET
2190 "\n\tSome colon mode commands with :"
2192 #if ENABLE_FEATURE_VI_SETOPTS
2193 "\n\tSettable options with \":set\""
2195 #if ENABLE_FEATURE_VI_USE_SIGNALS
2196 "\n\tSignal catching- ^C"
2197 "\n\tJob suspend and resume with ^Z"
2199 #if ENABLE_FEATURE_VI_WIN_RESIZE
2200 "\n\tAdapt to window re-sizes"
2205 #if ENABLE_FEATURE_VI_DOT_CMD
2206 static void start_new_cmd_q(char c)
2208 // get buffer for new cmd
2209 // if there is a current cmd count put it in the buffer first
2211 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2212 } else { // just save char c onto queue
2213 last_modifying_cmd[0] = c;
2219 static void end_cmd_q(void)
2221 #if ENABLE_FEATURE_VI_YANKMARK
2222 YDreg = 26; // go back to default Yank/Delete reg
2226 #endif /* FEATURE_VI_DOT_CMD */
2228 #if ENABLE_FEATURE_VI_YANKMARK \
2229 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2230 || ENABLE_FEATURE_VI_CRASHME
2231 // might reallocate text[]! use p += string_insert(p, ...),
2232 // and be careful to not use pointers into potentially freed text[]!
2233 static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p'
2239 bias = text_hole_make(p, i);
2242 #if ENABLE_FEATURE_VI_YANKMARK
2245 for (cnt = 0; *s != '\0'; s++) {
2249 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2256 #if ENABLE_FEATURE_VI_YANKMARK
2257 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2260 if (cnt < 0) { // they are backwards- reverse them
2264 free(reg[dest]); // if already a yank register, free it
2265 reg[dest] = xstrndup(p, cnt + 1);
2269 static char what_reg(void)
2273 c = 'D'; // default to D-reg
2274 if (0 <= YDreg && YDreg <= 25)
2275 c = 'a' + (char) YDreg;
2283 static void check_context(char cmd)
2285 // A context is defined to be "modifying text"
2286 // Any modifying command establishes a new context.
2288 if (dot < context_start || dot > context_end) {
2289 if (strchr(modifying_cmds, cmd) != NULL) {
2290 // we are trying to modify text[]- make this the current context
2291 mark[27] = mark[26]; // move cur to prev
2292 mark[26] = dot; // move local to cur
2293 context_start = prev_line(prev_line(dot));
2294 context_end = next_line(next_line(dot));
2295 //loiter= start_loiter= now;
2300 static char *swap_context(char *p) // goto new context for '' command make this the current context
2304 // the current context is in mark[26]
2305 // the previous context is in mark[27]
2306 // only swap context if other context is valid
2307 if (text <= mark[27] && mark[27] <= end - 1) {
2309 mark[27] = mark[26];
2311 p = mark[26]; // where we are going- previous context
2312 context_start = prev_line(prev_line(prev_line(p)));
2313 context_end = next_line(next_line(next_line(p)));
2317 #endif /* FEATURE_VI_YANKMARK */
2319 //----- Set terminal attributes --------------------------------
2320 static void rawmode(void)
2322 tcgetattr(0, &term_orig);
2323 term_vi = term_orig;
2324 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG on - allow intr's
2325 term_vi.c_iflag &= (~IXON & ~ICRNL);
2326 term_vi.c_oflag &= (~ONLCR);
2327 term_vi.c_cc[VMIN] = 1;
2328 term_vi.c_cc[VTIME] = 0;
2329 erase_char = term_vi.c_cc[VERASE];
2330 tcsetattr_stdin_TCSANOW(&term_vi);
2333 static void cookmode(void)
2336 tcsetattr_stdin_TCSANOW(&term_orig);
2339 #if ENABLE_FEATURE_VI_USE_SIGNALS
2340 //----- Come here when we get a window resize signal ---------
2341 static void winch_sig(int sig UNUSED_PARAM)
2343 int save_errno = errno;
2344 // FIXME: do it in main loop!!!
2345 signal(SIGWINCH, winch_sig);
2346 query_screen_dimensions();
2347 new_screen(rows, columns); // get memory for virtual screen
2348 redraw(TRUE); // re-draw the screen
2352 //----- Come here when we get a continue signal -------------------
2353 static void cont_sig(int sig UNUSED_PARAM)
2355 int save_errno = errno;
2356 rawmode(); // terminal to "raw"
2357 last_status_cksum = 0; // force status update
2358 redraw(TRUE); // re-draw the screen
2360 signal(SIGTSTP, suspend_sig);
2361 signal(SIGCONT, SIG_DFL);
2362 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2366 //----- Come here when we get a Suspend signal -------------------
2367 static void suspend_sig(int sig UNUSED_PARAM)
2369 int save_errno = errno;
2370 go_bottom_and_clear_to_eol();
2371 cookmode(); // terminal to "cooked"
2373 signal(SIGCONT, cont_sig);
2374 signal(SIGTSTP, SIG_DFL);
2375 kill(my_pid, SIGTSTP);
2379 //----- Come here when we get a signal ---------------------------
2380 static void catch_sig(int sig)
2382 signal(SIGINT, catch_sig);
2383 siglongjmp(restart, sig);
2385 #endif /* FEATURE_VI_USE_SIGNALS */
2387 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2389 struct pollfd pfd[1];
2391 pfd[0].fd = STDIN_FILENO;
2392 pfd[0].events = POLLIN;
2393 return safe_poll(pfd, 1, hund*10) > 0;
2396 //----- IO Routines --------------------------------------------
2397 static int readit(void) // read (maybe cursor) key from stdin
2402 c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2403 if (c == -1) { // EOF/error
2404 go_bottom_and_clear_to_eol();
2405 cookmode(); // terminal to "cooked"
2406 bb_error_msg_and_die("can't read user input");
2411 //----- IO Routines --------------------------------------------
2412 static int get_one_char(void)
2416 #if ENABLE_FEATURE_VI_DOT_CMD
2418 // we are not adding to the q.
2419 // but, we may be reading from a q
2421 // there is no current q, read from STDIN
2422 c = readit(); // get the users input
2424 // there is a queue to get chars from first
2425 // careful with correct sign expansion!
2426 c = (unsigned char)*ioq++;
2428 // the end of the q, read from STDIN
2430 ioq_start = ioq = 0;
2431 c = readit(); // get the users input
2435 // adding STDIN chars to q
2436 c = readit(); // get the users input
2437 if (lmc_len >= MAX_INPUT_LEN - 1) {
2438 status_line_bold("last_modifying_cmd overrun");
2440 // add new char to q
2441 last_modifying_cmd[lmc_len++] = c;
2445 c = readit(); // get the users input
2446 #endif /* FEATURE_VI_DOT_CMD */
2450 // Get input line (uses "status line" area)
2451 static char *get_input_line(const char *prompt)
2453 // char [MAX_INPUT_LEN]
2454 #define buf get_input_line__buf
2459 strcpy(buf, prompt);
2460 last_status_cksum = 0; // force status update
2461 go_bottom_and_clear_to_eol();
2462 write1(prompt); // write out the :, /, or ? prompt
2465 while (i < MAX_INPUT_LEN) {
2467 if (c == '\n' || c == '\r' || c == 27)
2468 break; // this is end of input
2469 if (c == erase_char || c == 8 || c == 127) {
2470 // user wants to erase prev char
2472 write1("\b \b"); // erase char on screen
2473 if (i <= 0) // user backs up before b-o-l, exit
2475 } else if (c > 0 && c < 256) { // exclude Unicode
2476 // (TODO: need to handle Unicode)
2487 static int file_size(const char *fn) // what is the byte size of "fn"
2493 if (fn && stat(fn, &st_buf) == 0) // see if file exists
2494 cnt = (int) st_buf.st_size;
2498 // might reallocate text[]!
2499 static int file_insert(const char *fn, char *p, int update_ro_status)
2503 struct stat statbuf;
2506 if (stat(fn, &statbuf) < 0) {
2507 status_line_bold_errno(fn);
2510 if (!S_ISREG(statbuf.st_mode)) {
2511 // This is not a regular file
2512 status_line_bold("'%s' is not a regular file", fn);
2515 if (p < text || p > end) {
2516 status_line_bold("Trying to insert file outside of memory");
2520 // read file to buffer
2521 fd = open(fn, O_RDONLY);
2523 status_line_bold_errno(fn);
2526 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2527 p += text_hole_make(p, size);
2528 cnt = safe_read(fd, p, size);
2530 status_line_bold_errno(fn);
2531 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2532 } else if (cnt < size) {
2533 // There was a partial read, shrink unused space text[]
2534 p = text_hole_delete(p + cnt, p + size - 1); // un-do buffer insert
2535 status_line_bold("can't read '%s'", fn);
2541 #if ENABLE_FEATURE_VI_READONLY
2542 if (update_ro_status
2543 && ((access(fn, W_OK) < 0) ||
2544 /* root will always have access()
2545 * so we check fileperms too */
2546 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2549 SET_READONLY_FILE(readonly_mode);
2555 static int file_write(char *fn, char *first, char *last)
2557 int fd, cnt, charcnt;
2560 status_line_bold("No current filename");
2563 /* By popular request we do not open file with O_TRUNC,
2564 * but instead ftruncate() it _after_ successful write.
2565 * Might reduce amount of data lost on power fail etc.
2567 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2570 cnt = last - first + 1;
2571 charcnt = full_write(fd, first, cnt);
2572 ftruncate(fd, charcnt);
2573 if (charcnt == cnt) {
2575 //file_modified = FALSE;
2583 //----- Terminal Drawing ---------------------------------------
2584 // The terminal is made up of 'rows' line of 'columns' columns.
2585 // classically this would be 24 x 80.
2586 // screen coordinates
2592 // 23,0 ... 23,79 <- status line
2594 //----- Move the cursor to row x col (count from 0, not 1) -------
2595 static void place_cursor(int row, int col)
2597 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
2599 if (row < 0) row = 0;
2600 if (row >= rows) row = rows - 1;
2601 if (col < 0) col = 0;
2602 if (col >= columns) col = columns - 1;
2604 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
2608 //----- Erase from cursor to end of line -----------------------
2609 static void clear_to_eol(void)
2611 write1(ESC_CLEAR2EOL);
2614 static void go_bottom_and_clear_to_eol(void)
2616 place_cursor(rows - 1, 0);
2620 //----- Erase from cursor to end of screen -----------------------
2621 static void clear_to_eos(void)
2623 write1(ESC_CLEAR2EOS);
2626 //----- Start standout mode ------------------------------------
2627 static void standout_start(void)
2629 write1(ESC_BOLD_TEXT);
2632 //----- End standout mode --------------------------------------
2633 static void standout_end(void)
2635 write1(ESC_NORM_TEXT);
2638 //----- Flash the screen --------------------------------------
2639 static void flash(int h)
2648 static void Indicate_Error(void)
2650 #if ENABLE_FEATURE_VI_CRASHME
2652 return; // generate a random command
2661 //----- Screen[] Routines --------------------------------------
2662 //----- Erase the Screen[] memory ------------------------------
2663 static void screen_erase(void)
2665 memset(screen, ' ', screensize); // clear new screen
2668 static int bufsum(char *buf, int count)
2671 char *e = buf + count;
2674 sum += (unsigned char) *buf++;
2678 //----- Draw the status line at bottom of the screen -------------
2679 static void show_status_line(void)
2681 int cnt = 0, cksum = 0;
2683 // either we already have an error or status message, or we
2685 if (!have_status_msg) {
2686 cnt = format_edit_status();
2687 cksum = bufsum(status_buffer, cnt);
2689 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2690 last_status_cksum = cksum; // remember if we have seen this line
2691 go_bottom_and_clear_to_eol();
2692 write1(status_buffer);
2693 if (have_status_msg) {
2694 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2696 have_status_msg = 0;
2699 have_status_msg = 0;
2701 place_cursor(crow, ccol); // put cursor back in correct place
2706 //----- format the status buffer, the bottom line of screen ------
2707 // format status buffer, with STANDOUT mode
2708 static void status_line_bold(const char *format, ...)
2712 va_start(args, format);
2713 strcpy(status_buffer, ESC_BOLD_TEXT);
2714 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
2715 strcat(status_buffer, ESC_NORM_TEXT);
2718 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
2721 static void status_line_bold_errno(const char *fn)
2723 status_line_bold("'%s' %s", fn, strerror(errno));
2726 // format status buffer
2727 static void status_line(const char *format, ...)
2731 va_start(args, format);
2732 vsprintf(status_buffer, format, args);
2735 have_status_msg = 1;
2738 // copy s to buf, convert unprintable
2739 static void print_literal(char *buf, const char *s)
2753 c_is_no_print = (c & 0x80) && !Isprint(c);
2754 if (c_is_no_print) {
2755 strcpy(d, ESC_NORM_TEXT);
2756 d += sizeof(ESC_NORM_TEXT)-1;
2759 if (c < ' ' || c == 0x7f) {
2761 c |= '@'; /* 0x40 */
2767 if (c_is_no_print) {
2768 strcpy(d, ESC_BOLD_TEXT);
2769 d += sizeof(ESC_BOLD_TEXT)-1;
2775 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
2780 static void not_implemented(const char *s)
2782 char buf[MAX_INPUT_LEN];
2784 print_literal(buf, s);
2785 status_line_bold("\'%s\' is not implemented", buf);
2788 // show file status on status line
2789 static int format_edit_status(void)
2791 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2793 #define tot format_edit_status__tot
2795 int cur, percent, ret, trunc_at;
2797 // file_modified is now a counter rather than a flag. this
2798 // helps reduce the amount of line counting we need to do.
2799 // (this will cause a mis-reporting of modified status
2800 // once every MAXINT editing operations.)
2802 // it would be nice to do a similar optimization here -- if
2803 // we haven't done a motion that could have changed which line
2804 // we're on, then we shouldn't have to do this count_lines()
2805 cur = count_lines(text, dot);
2807 // reduce counting -- the total lines can't have
2808 // changed if we haven't done any edits.
2809 if (file_modified != last_file_modified) {
2810 tot = cur + count_lines(dot, end - 1) - 1;
2811 last_file_modified = file_modified;
2814 // current line percent
2815 // ------------- ~~ ----------
2818 percent = (100 * cur) / tot;
2824 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2825 columns : STATUS_BUFFER_LEN-1;
2827 ret = snprintf(status_buffer, trunc_at+1,
2828 #if ENABLE_FEATURE_VI_READONLY
2829 "%c %s%s%s %d/%d %d%%",
2831 "%c %s%s %d/%d %d%%",
2833 cmd_mode_indicator[cmd_mode & 3],
2834 (current_filename != NULL ? current_filename : "No file"),
2835 #if ENABLE_FEATURE_VI_READONLY
2836 (readonly_mode ? " [Readonly]" : ""),
2838 (file_modified ? " [Modified]" : ""),
2841 if (ret >= 0 && ret < trunc_at)
2842 return ret; /* it all fit */
2844 return trunc_at; /* had to truncate */
2848 //----- Force refresh of all Lines -----------------------------
2849 static void redraw(int full_screen)
2853 screen_erase(); // erase the internal screen buffer
2854 last_status_cksum = 0; // force status update
2855 refresh(full_screen); // this will redraw the entire display
2859 //----- Format a text[] line into a buffer ---------------------
2860 static char* format_line(char *src /*, int li*/)
2865 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2867 c = '~'; // char in col 0 in non-existent lines is '~'
2869 while (co < columns + tabstop) {
2870 // have we gone past the end?
2875 if ((c & 0x80) && !Isprint(c)) {
2878 if (c < ' ' || c == 0x7f) {
2882 while ((co % tabstop) != (tabstop - 1)) {
2890 c += '@'; // Ctrl-X -> 'X'
2895 // discard scrolled-off-to-the-left portion,
2896 // in tabstop-sized pieces
2897 if (ofs >= tabstop && co >= tabstop) {
2898 memmove(dest, dest + tabstop, co);
2905 // check "short line, gigantic offset" case
2908 // discard last scrolled off part
2911 // fill the rest with spaces
2913 memset(&dest[co], ' ', columns - co);
2917 //----- Refresh the changed screen lines -----------------------
2918 // Copy the source line from text[] into the buffer and note
2919 // if the current screenline is different from the new buffer.
2920 // If they differ then that line needs redrawing on the terminal.
2922 static void refresh(int full_screen)
2924 #define old_offset refresh__old_offset
2927 char *tp, *sp; // pointer into text[] and screen[]
2929 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
2930 unsigned c = columns, r = rows;
2931 query_screen_dimensions();
2932 full_screen |= (c - columns) | (r - rows);
2934 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2935 tp = screenbegin; // index into text[] of top line
2937 // compare text[] to screen[] and mark screen[] lines that need updating
2938 for (li = 0; li < rows - 1; li++) {
2939 int cs, ce; // column start & end
2941 // format current text line
2942 out_buf = format_line(tp /*, li*/);
2944 // skip to the end of the current text[] line
2946 char *t = memchr(tp, '\n', end - tp);
2947 if (!t) t = end - 1;
2951 // see if there are any changes between vitual screen and out_buf
2952 changed = FALSE; // assume no change
2955 sp = &screen[li * columns]; // start of screen line
2957 // force re-draw of every single column from 0 - columns-1
2960 // compare newly formatted buffer with virtual screen
2961 // look forward for first difference between buf and screen
2962 for (; cs <= ce; cs++) {
2963 if (out_buf[cs] != sp[cs]) {
2964 changed = TRUE; // mark for redraw
2969 // look backward for last difference between out_buf and screen
2970 for (; ce >= cs; ce--) {
2971 if (out_buf[ce] != sp[ce]) {
2972 changed = TRUE; // mark for redraw
2976 // now, cs is index of first diff, and ce is index of last diff
2978 // if horz offset has changed, force a redraw
2979 if (offset != old_offset) {
2984 // make a sanity check of columns indexes
2986 if (ce > columns - 1) ce = columns - 1;
2987 if (cs > ce) { cs = 0; ce = columns - 1; }
2988 // is there a change between vitual screen and out_buf
2990 // copy changed part of buffer to virtual screen
2991 memcpy(sp+cs, out_buf+cs, ce-cs+1);
2992 place_cursor(li, cs);
2993 // write line out to terminal
2994 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
2998 place_cursor(crow, ccol);
3000 old_offset = offset;
3004 //---------------------------------------------------------------------
3005 //----- the Ascii Chart -----------------------------------------------
3007 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3008 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3009 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3010 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3011 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3012 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3013 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3014 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3015 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3016 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3017 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3018 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3019 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3020 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3021 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3022 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3023 //---------------------------------------------------------------------
3025 //----- Execute a Vi Command -----------------------------------
3026 static void do_cmd(int c)
3028 char *p, *q, *save_dot;
3034 // c1 = c; // quiet the compiler
3035 // cnt = yf = 0; // quiet the compiler
3036 // p = q = save_dot = buf; // quiet the compiler
3037 memset(buf, '\0', sizeof(buf));
3041 /* if this is a cursor key, skip these checks */
3049 case KEYCODE_PAGEUP:
3050 case KEYCODE_PAGEDOWN:
3051 case KEYCODE_DELETE:
3055 if (cmd_mode == 2) {
3056 // flip-flop Insert/Replace mode
3057 if (c == KEYCODE_INSERT)
3059 // we are 'R'eplacing the current *dot with new char
3061 // don't Replace past E-o-l
3062 cmd_mode = 1; // convert to insert
3064 if (1 <= c || Isprint(c)) {
3066 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3067 dot = char_insert(dot, c); // insert new char
3072 if (cmd_mode == 1) {
3073 // hitting "Insert" twice means "R" replace mode
3074 if (c == KEYCODE_INSERT) goto dc5;
3075 // insert the char c at "dot"
3076 if (1 <= c || Isprint(c)) {
3077 dot = char_insert(dot, c);
3092 #if ENABLE_FEATURE_VI_CRASHME
3093 case 0x14: // dc4 ctrl-T
3094 crashme = (crashme == 0) ? 1 : 0;
3123 //case 'u': // u- FIXME- there is no undo
3125 default: // unrecognized command
3128 not_implemented(buf);
3129 end_cmd_q(); // stop adding to q
3130 case 0x00: // nul- ignore
3132 case 2: // ctrl-B scroll up full screen
3133 case KEYCODE_PAGEUP: // Cursor Key Page Up
3134 dot_scroll(rows - 2, -1);
3136 case 4: // ctrl-D scroll down half screen
3137 dot_scroll((rows - 2) / 2, 1);
3139 case 5: // ctrl-E scroll down one line
3142 case 6: // ctrl-F scroll down full screen
3143 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3144 dot_scroll(rows - 2, 1);
3146 case 7: // ctrl-G show current status
3147 last_status_cksum = 0; // force status update
3149 case 'h': // h- move left
3150 case KEYCODE_LEFT: // cursor key Left
3151 case 8: // ctrl-H- move left (This may be ERASE char)
3152 case 0x7f: // DEL- move left (This may be ERASE char)
3155 } while (--cmdcnt > 0);
3157 case 10: // Newline ^J
3158 case 'j': // j- goto next line, same col
3159 case KEYCODE_DOWN: // cursor key Down
3161 dot_next(); // go to next B-o-l
3162 // try stay in same col
3163 dot = move_to_col(dot, ccol + offset);
3164 } while (--cmdcnt > 0);
3166 case 12: // ctrl-L force redraw whole screen
3167 case 18: // ctrl-R force redraw
3170 //mysleep(10); // why???
3171 screen_erase(); // erase the internal screen buffer
3172 last_status_cksum = 0; // force status update
3173 refresh(TRUE); // this will redraw the entire display
3175 case 13: // Carriage Return ^M
3176 case '+': // +- goto next line
3180 } while (--cmdcnt > 0);
3182 case 21: // ctrl-U scroll up half screen
3183 dot_scroll((rows - 2) / 2, -1);
3185 case 25: // ctrl-Y scroll up one line
3191 cmd_mode = 0; // stop insrting
3193 last_status_cksum = 0; // force status update
3195 case ' ': // move right
3196 case 'l': // move right
3197 case KEYCODE_RIGHT: // Cursor Key Right
3200 } while (--cmdcnt > 0);
3202 #if ENABLE_FEATURE_VI_YANKMARK
3203 case '"': // "- name a register to use for Delete/Yank
3204 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3205 if ((unsigned)c1 <= 25) { // a-z?
3211 case '\'': // '- goto a specific mark
3212 c1 = (get_one_char() | 0x20) - 'a';
3213 if ((unsigned)c1 <= 25) { // a-z?
3216 if (text <= q && q < end) {
3218 dot_begin(); // go to B-o-l
3221 } else if (c1 == '\'') { // goto previous context
3222 dot = swap_context(dot); // swap current and previous context
3223 dot_begin(); // go to B-o-l
3229 case 'm': // m- Mark a line
3230 // this is really stupid. If there are any inserts or deletes
3231 // between text[0] and dot then this mark will not point to the
3232 // correct location! It could be off by many lines!
3233 // Well..., at least its quick and dirty.
3234 c1 = (get_one_char() | 0x20) - 'a';
3235 if ((unsigned)c1 <= 25) { // a-z?
3236 // remember the line
3242 case 'P': // P- Put register before
3243 case 'p': // p- put register after
3246 status_line_bold("Nothing in register %c", what_reg());
3249 // are we putting whole lines or strings
3250 if (strchr(p, '\n') != NULL) {
3252 dot_begin(); // putting lines- Put above
3255 // are we putting after very last line?
3256 if (end_line(dot) == (end - 1)) {
3257 dot = end; // force dot to end of text[]
3259 dot_next(); // next line, then put before
3264 dot_right(); // move to right, can move to NL
3266 string_insert(dot, p); // insert the string
3267 end_cmd_q(); // stop adding to q
3269 case 'U': // U- Undo; replace current line with original version
3270 if (reg[Ureg] != NULL) {
3271 p = begin_line(dot);
3273 p = text_hole_delete(p, q); // delete cur line
3274 p += string_insert(p, reg[Ureg]); // insert orig line
3279 #endif /* FEATURE_VI_YANKMARK */
3280 case '$': // $- goto end of line
3281 case KEYCODE_END: // Cursor Key End
3283 dot = end_line(dot);
3289 case '%': // %- find matching char of pair () [] {}
3290 for (q = dot; q < end && *q != '\n'; q++) {
3291 if (strchr("()[]{}", *q) != NULL) {
3292 // we found half of a pair
3293 p = find_pair(q, *q);
3305 case 'f': // f- forward to a user specified char
3306 last_forward_char = get_one_char(); // get the search char
3308 // dont separate these two commands. 'f' depends on ';'
3310 //**** fall through to ... ';'
3311 case ';': // ;- look at rest of line for last forward char
3313 if (last_forward_char == 0)
3316 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3319 if (*q == last_forward_char)
3321 } while (--cmdcnt > 0);
3323 case ',': // repeat latest 'f' in opposite direction
3324 if (last_forward_char == 0)
3328 while (q >= text && *q != '\n' && *q != last_forward_char) {
3331 if (q >= text && *q == last_forward_char)
3333 } while (--cmdcnt > 0);
3336 case '-': // -- goto prev line
3340 } while (--cmdcnt > 0);
3342 #if ENABLE_FEATURE_VI_DOT_CMD
3343 case '.': // .- repeat the last modifying command
3344 // Stuff the last_modifying_cmd back into stdin
3345 // and let it be re-executed.
3347 last_modifying_cmd[lmc_len] = 0;
3348 ioq = ioq_start = xstrdup(last_modifying_cmd);
3352 #if ENABLE_FEATURE_VI_SEARCH
3353 case '?': // /- search for a pattern
3354 case '/': // /- search for a pattern
3357 q = get_input_line(buf); // get input line- use "status line"
3358 if (q[0] && !q[1]) {
3359 if (last_search_pattern[0])
3360 last_search_pattern[0] = c;
3361 goto dc3; // if no pat re-use old pat
3363 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3364 // there is a new pat
3365 free(last_search_pattern);
3366 last_search_pattern = xstrdup(q);
3367 goto dc3; // now find the pattern
3369 // user changed mind and erased the "/"- do nothing
3371 case 'N': // N- backward search for last pattern
3372 dir = BACK; // assume BACKWARD search
3374 if (last_search_pattern[0] == '?') {
3378 goto dc4; // now search for pattern
3380 case 'n': // n- repeat search for last pattern
3381 // search rest of text[] starting at next char
3382 // if search fails return orignal "p" not the "p+1" address
3386 dir = FORWARD; // assume FORWARD search
3388 if (last_search_pattern[0] == '?') {
3393 q = char_search(p, last_search_pattern + 1, dir, FULL);
3395 dot = q; // good search, update "dot"
3399 // no pattern found between "dot" and "end"- continue at top
3404 q = char_search(p, last_search_pattern + 1, dir, FULL);
3405 if (q != NULL) { // found something
3406 dot = q; // found new pattern- goto it
3407 msg = "search hit BOTTOM, continuing at TOP";
3409 msg = "search hit TOP, continuing at BOTTOM";
3412 msg = "Pattern not found";
3416 status_line_bold("%s", msg);
3417 } while (--cmdcnt > 0);
3419 case '{': // {- move backward paragraph
3420 q = char_search(dot, "\n\n", BACK, FULL);
3421 if (q != NULL) { // found blank line
3422 dot = next_line(q); // move to next blank line
3425 case '}': // }- move forward paragraph
3426 q = char_search(dot, "\n\n", FORWARD, FULL);
3427 if (q != NULL) { // found blank line
3428 dot = next_line(q); // move to next blank line
3431 #endif /* FEATURE_VI_SEARCH */
3432 case '0': // 0- goto begining of line
3442 if (c == '0' && cmdcnt < 1) {
3443 dot_begin(); // this was a standalone zero
3445 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3448 case ':': // :- the colon mode commands
3449 p = get_input_line(":"); // get input line- use "status line"
3450 #if ENABLE_FEATURE_VI_COLON
3451 colon(p); // execute the command
3454 p++; // move past the ':'
3458 if (strncmp(p, "quit", cnt) == 0
3459 || strncmp(p, "q!", cnt) == 0 // delete lines
3461 if (file_modified && p[1] != '!') {
3462 status_line_bold("No write since last change (:%s! overrides)", p);
3466 } else if (strncmp(p, "write", cnt) == 0
3467 || strncmp(p, "wq", cnt) == 0
3468 || strncmp(p, "wn", cnt) == 0
3469 || (p[0] == 'x' && !p[1])
3471 cnt = file_write(current_filename, text, end - 1);
3474 status_line_bold("Write error: %s", strerror(errno));
3477 last_file_modified = -1;
3478 status_line("'%s' %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3479 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3480 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3485 } else if (strncmp(p, "file", cnt) == 0) {
3486 last_status_cksum = 0; // force status update
3487 } else if (sscanf(p, "%d", &j) > 0) {
3488 dot = find_line(j); // go to line # j
3490 } else { // unrecognized cmd
3493 #endif /* !FEATURE_VI_COLON */
3495 case '<': // <- Left shift something
3496 case '>': // >- Right shift something
3497 cnt = count_lines(text, dot); // remember what line we are on
3498 c1 = get_one_char(); // get the type of thing to delete
3499 find_range(&p, &q, c1);
3500 yank_delete(p, q, 1, YANKONLY); // save copy before change
3503 i = count_lines(p, q); // # of lines we are shifting
3504 for ( ; i > 0; i--, p = next_line(p)) {
3506 // shift left- remove tab or 8 spaces
3508 // shrink buffer 1 char
3509 text_hole_delete(p, p);
3510 } else if (*p == ' ') {
3511 // we should be calculating columns, not just SPACE
3512 for (j = 0; *p == ' ' && j < tabstop; j++) {
3513 text_hole_delete(p, p);
3516 } else if (c == '>') {
3517 // shift right -- add tab or 8 spaces
3518 char_insert(p, '\t');
3521 dot = find_line(cnt); // what line were we on
3523 end_cmd_q(); // stop adding to q
3525 case 'A': // A- append at e-o-l
3526 dot_end(); // go to e-o-l
3527 //**** fall through to ... 'a'
3528 case 'a': // a- append after current char
3533 case 'B': // B- back a blank-delimited Word
3534 case 'E': // E- end of a blank-delimited word
3535 case 'W': // W- forward a blank-delimited word
3540 if (c == 'W' || isspace(dot[dir])) {
3541 dot = skip_thing(dot, 1, dir, S_TO_WS);
3542 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3545 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3546 } while (--cmdcnt > 0);
3548 case 'C': // C- Change to e-o-l
3549 case 'D': // D- delete to e-o-l
3551 dot = dollar_line(dot); // move to before NL
3552 // copy text into a register and delete
3553 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3555 goto dc_i; // start inserting
3556 #if ENABLE_FEATURE_VI_DOT_CMD
3558 end_cmd_q(); // stop adding to q
3561 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3562 c1 = get_one_char();
3565 buf[1] = c1; // TODO: if Unicode?
3567 not_implemented(buf);
3573 case 'G': // G- goto to a line number (default= E-O-F)
3574 dot = end - 1; // assume E-O-F
3576 dot = find_line(cmdcnt); // what line is #cmdcnt
3580 case 'H': // H- goto top line on screen
3582 if (cmdcnt > (rows - 1)) {
3583 cmdcnt = (rows - 1);
3590 case 'I': // I- insert before first non-blank
3593 //**** fall through to ... 'i'
3594 case 'i': // i- insert before current char
3595 case KEYCODE_INSERT: // Cursor Key Insert
3597 cmd_mode = 1; // start inserting
3599 case 'J': // J- join current and next lines together
3601 dot_end(); // move to NL
3602 if (dot < end - 1) { // make sure not last char in text[]
3603 *dot++ = ' '; // replace NL with space
3605 while (isblank(*dot)) { // delete leading WS
3609 } while (--cmdcnt > 0);
3610 end_cmd_q(); // stop adding to q
3612 case 'L': // L- goto bottom line on screen
3614 if (cmdcnt > (rows - 1)) {
3615 cmdcnt = (rows - 1);
3623 case 'M': // M- goto middle line on screen
3625 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3626 dot = next_line(dot);
3628 case 'O': // O- open a empty line above
3630 p = begin_line(dot);
3631 if (p[-1] == '\n') {
3633 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3635 dot = char_insert(dot, '\n');
3638 dot = char_insert(dot, '\n'); // i\n ESC
3643 case 'R': // R- continuous Replace char
3647 case KEYCODE_DELETE:
3650 case 'X': // X- delete char before dot
3651 case 'x': // x- delete the current char
3652 case 's': // s- substitute the current char
3657 if (dot[dir] != '\n') {
3659 dot--; // delete prev char
3660 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3662 } while (--cmdcnt > 0);
3663 end_cmd_q(); // stop adding to q
3665 goto dc_i; // start inserting
3667 case 'Z': // Z- if modified, {write}; exit
3668 // ZZ means to save file (if necessary), then exit
3669 c1 = get_one_char();
3674 if (file_modified) {
3675 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3676 status_line_bold("'%s' is read only", current_filename);
3679 cnt = file_write(current_filename, text, end - 1);
3682 status_line_bold("Write error: %s", strerror(errno));
3683 } else if (cnt == (end - 1 - text + 1)) {
3690 case '^': // ^- move to first non-blank on line
3694 case 'b': // b- back a word
3695 case 'e': // e- end of word
3700 if ((dot + dir) < text || (dot + dir) > end - 1)
3703 if (isspace(*dot)) {
3704 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3706 if (isalnum(*dot) || *dot == '_') {
3707 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3708 } else if (ispunct(*dot)) {
3709 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3711 } while (--cmdcnt > 0);
3713 case 'c': // c- change something
3714 case 'd': // d- delete something
3715 #if ENABLE_FEATURE_VI_YANKMARK
3716 case 'y': // y- yank something
3717 case 'Y': // Y- Yank a line
3720 int yf, ml, whole = 0;
3721 yf = YANKDEL; // assume either "c" or "d"
3722 #if ENABLE_FEATURE_VI_YANKMARK
3723 if (c == 'y' || c == 'Y')
3728 c1 = get_one_char(); // get the type of thing to delete
3729 // determine range, and whether it spans lines
3730 ml = find_range(&p, &q, c1);
3731 if (c1 == 27) { // ESC- user changed mind and wants out
3732 c = c1 = 27; // Escape- do nothing
3733 } else if (strchr("wW", c1)) {
3735 // don't include trailing WS as part of word
3736 while (isblank(*q)) {
3737 if (q <= text || q[-1] == '\n')
3742 dot = yank_delete(p, q, ml, yf); // delete word
3743 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3744 // partial line copy text into a register and delete
3745 dot = yank_delete(p, q, ml, yf); // delete word
3746 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3747 // whole line copy text into a register and delete
3748 dot = yank_delete(p, q, ml, yf); // delete lines
3751 // could not recognize object
3752 c = c1 = 27; // error-
3758 dot = char_insert(dot, '\n');
3759 // on the last line of file don't move to prev line
3760 if (whole && dot != (end-1)) {
3763 } else if (c == 'd') {
3769 // if CHANGING, not deleting, start inserting after the delete
3771 strcpy(buf, "Change");
3772 goto dc_i; // start inserting
3775 strcpy(buf, "Delete");
3777 #if ENABLE_FEATURE_VI_YANKMARK
3778 if (c == 'y' || c == 'Y') {
3779 strcpy(buf, "Yank");
3783 for (cnt = 0; p <= q; p++) {
3787 status_line("%s %d lines (%d chars) using [%c]",
3788 buf, cnt, strlen(reg[YDreg]), what_reg());
3790 end_cmd_q(); // stop adding to q
3794 case 'k': // k- goto prev line, same col
3795 case KEYCODE_UP: // cursor key Up
3798 dot = move_to_col(dot, ccol + offset); // try stay in same col
3799 } while (--cmdcnt > 0);
3801 case 'r': // r- replace the current char with user input
3802 c1 = get_one_char(); // get the replacement char
3807 end_cmd_q(); // stop adding to q
3809 case 't': // t- move to char prior to next x
3810 last_forward_char = get_one_char();
3812 if (*dot == last_forward_char)
3814 last_forward_char = 0;
3816 case 'w': // w- forward a word
3818 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3819 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3820 } else if (ispunct(*dot)) { // we are on PUNCT
3821 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3824 dot++; // move over word
3825 if (isspace(*dot)) {
3826 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3828 } while (--cmdcnt > 0);
3831 c1 = get_one_char(); // get the replacement char
3834 cnt = (rows - 2) / 2; // put dot at center
3836 cnt = rows - 2; // put dot at bottom
3837 screenbegin = begin_line(dot); // start dot at top
3838 dot_scroll(cnt, -1);
3840 case '|': // |- move to column "cmdcnt"
3841 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3843 case '~': // ~- flip the case of letters a-z -> A-Z
3845 if (islower(*dot)) {
3846 *dot = toupper(*dot);
3848 } else if (isupper(*dot)) {
3849 *dot = tolower(*dot);
3853 } while (--cmdcnt > 0);
3854 end_cmd_q(); // stop adding to q
3856 //----- The Cursor and Function Keys -----------------------------
3857 case KEYCODE_HOME: // Cursor Key Home
3860 // The Fn keys could point to do_macro which could translate them
3862 case KEYCODE_FUN1: // Function Key F1
3863 case KEYCODE_FUN2: // Function Key F2
3864 case KEYCODE_FUN3: // Function Key F3
3865 case KEYCODE_FUN4: // Function Key F4
3866 case KEYCODE_FUN5: // Function Key F5
3867 case KEYCODE_FUN6: // Function Key F6
3868 case KEYCODE_FUN7: // Function Key F7
3869 case KEYCODE_FUN8: // Function Key F8
3870 case KEYCODE_FUN9: // Function Key F9
3871 case KEYCODE_FUN10: // Function Key F10
3872 case KEYCODE_FUN11: // Function Key F11
3873 case KEYCODE_FUN12: // Function Key F12
3879 // if text[] just became empty, add back an empty line
3881 char_insert(text, '\n'); // start empty buf with dummy line
3884 // it is OK for dot to exactly equal to end, otherwise check dot validity
3886 dot = bound_dot(dot); // make sure "dot" is valid
3888 #if ENABLE_FEATURE_VI_YANKMARK
3889 check_context(c); // update the current context
3893 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3894 cnt = dot - begin_line(dot);
3895 // Try to stay off of the Newline
3896 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3900 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
3901 #if ENABLE_FEATURE_VI_CRASHME
3902 static int totalcmds = 0;
3903 static int Mp = 85; // Movement command Probability
3904 static int Np = 90; // Non-movement command Probability
3905 static int Dp = 96; // Delete command Probability
3906 static int Ip = 97; // Insert command Probability
3907 static int Yp = 98; // Yank command Probability
3908 static int Pp = 99; // Put command Probability
3909 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3910 static const char chars[20] = "\t012345 abcdABCD-=.$";
3911 static const char *const words[20] = {
3912 "this", "is", "a", "test",
3913 "broadcast", "the", "emergency", "of",
3914 "system", "quick", "brown", "fox",
3915 "jumped", "over", "lazy", "dogs",
3916 "back", "January", "Febuary", "March"
3918 static const char *const lines[20] = {
3919 "You should have received a copy of the GNU General Public License\n",
3920 "char c, cm, *cmd, *cmd1;\n",
3921 "generate a command by percentages\n",
3922 "Numbers may be typed as a prefix to some commands.\n",
3923 "Quit, discarding changes!\n",
3924 "Forced write, if permission originally not valid.\n",
3925 "In general, any ex or ed command (such as substitute or delete).\n",
3926 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3927 "Please get w/ me and I will go over it with you.\n",
3928 "The following is a list of scheduled, committed changes.\n",
3929 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3930 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3931 "Any question about transactions please contact Sterling Huxley.\n",
3932 "I will try to get back to you by Friday, December 31.\n",
3933 "This Change will be implemented on Friday.\n",
3934 "Let me know if you have problems accessing this;\n",
3935 "Sterling Huxley recently added you to the access list.\n",
3936 "Would you like to go to lunch?\n",
3937 "The last command will be automatically run.\n",
3938 "This is too much english for a computer geek.\n",
3940 static char *multilines[20] = {
3941 "You should have received a copy of the GNU General Public License\n",
3942 "char c, cm, *cmd, *cmd1;\n",
3943 "generate a command by percentages\n",
3944 "Numbers may be typed as a prefix to some commands.\n",
3945 "Quit, discarding changes!\n",
3946 "Forced write, if permission originally not valid.\n",
3947 "In general, any ex or ed command (such as substitute or delete).\n",
3948 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3949 "Please get w/ me and I will go over it with you.\n",
3950 "The following is a list of scheduled, committed changes.\n",
3951 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3952 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3953 "Any question about transactions please contact Sterling Huxley.\n",
3954 "I will try to get back to you by Friday, December 31.\n",
3955 "This Change will be implemented on Friday.\n",
3956 "Let me know if you have problems accessing this;\n",
3957 "Sterling Huxley recently added you to the access list.\n",
3958 "Would you like to go to lunch?\n",
3959 "The last command will be automatically run.\n",
3960 "This is too much english for a computer geek.\n",
3963 // create a random command to execute
3964 static void crash_dummy()
3966 static int sleeptime; // how long to pause between commands
3967 char c, cm, *cmd, *cmd1;
3968 int i, cnt, thing, rbi, startrbi, percent;
3970 // "dot" movement commands
3971 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3973 // is there already a command running?
3974 if (readbuffer[0] > 0)
3977 readbuffer[0] = 'X';
3979 sleeptime = 0; // how long to pause between commands
3980 memset(readbuffer, '\0', sizeof(readbuffer));
3981 // generate a command by percentages
3982 percent = (int) lrand48() % 100; // get a number from 0-99
3983 if (percent < Mp) { // Movement commands
3984 // available commands
3987 } else if (percent < Np) { // non-movement commands
3988 cmd = "mz<>\'\""; // available commands
3990 } else if (percent < Dp) { // Delete commands
3991 cmd = "dx"; // available commands
3993 } else if (percent < Ip) { // Inset commands
3994 cmd = "iIaAsrJ"; // available commands
3996 } else if (percent < Yp) { // Yank commands
3997 cmd = "yY"; // available commands
3999 } else if (percent < Pp) { // Put commands
4000 cmd = "pP"; // available commands
4003 // We do not know how to handle this command, try again
4007 // randomly pick one of the available cmds from "cmd[]"
4008 i = (int) lrand48() % strlen(cmd);
4010 if (strchr(":\024", cm))
4011 goto cd0; // dont allow colon or ctrl-T commands
4012 readbuffer[rbi++] = cm; // put cmd into input buffer
4014 // now we have the command-
4015 // there are 1, 2, and multi char commands
4016 // find out which and generate the rest of command as necessary
4017 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4018 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4019 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4020 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4022 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4024 readbuffer[rbi++] = c; // add movement to input buffer
4026 if (strchr("iIaAsc", cm)) { // multi-char commands
4028 // change some thing
4029 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4031 readbuffer[rbi++] = c; // add movement to input buffer
4033 thing = (int) lrand48() % 4; // what thing to insert
4034 cnt = (int) lrand48() % 10; // how many to insert
4035 for (i = 0; i < cnt; i++) {
4036 if (thing == 0) { // insert chars
4037 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4038 } else if (thing == 1) { // insert words
4039 strcat(readbuffer, words[(int) lrand48() % 20]);
4040 strcat(readbuffer, " ");
4041 sleeptime = 0; // how fast to type
4042 } else if (thing == 2) { // insert lines
4043 strcat(readbuffer, lines[(int) lrand48() % 20]);
4044 sleeptime = 0; // how fast to type
4045 } else { // insert multi-lines
4046 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4047 sleeptime = 0; // how fast to type
4050 strcat(readbuffer, "\033");
4052 readbuffer[0] = strlen(readbuffer + 1);
4056 mysleep(sleeptime); // sleep 1/100 sec
4059 // test to see if there are any errors
4060 static void crash_test()
4062 static time_t oldtim;
4069 strcat(msg, "end<text ");
4071 if (end > textend) {
4072 strcat(msg, "end>textend ");
4075 strcat(msg, "dot<text ");
4078 strcat(msg, "dot>end ");
4080 if (screenbegin < text) {
4081 strcat(msg, "screenbegin<text ");
4083 if (screenbegin > end - 1) {
4084 strcat(msg, "screenbegin>end-1 ");
4088 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4089 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4091 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4092 if (d[0] == '\n' || d[0] == '\r')
4097 if (tim >= (oldtim + 3)) {
4098 sprintf(status_buffer,
4099 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4100 totalcmds, M, N, I, D, Y, P, U, end - text + 1);