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 //config:config FEATURE_VI_OPTIMIZE_CURSOR
141 //config: bool "Optimize cursor movement"
143 //config: depends on VI
145 //config: This will make the cursor movement faster, but requires more memory
146 //config: and it makes the applet a tiny bit larger.
148 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
150 //kbuild:lib-$(CONFIG_VI) += vi.o
152 //usage:#define vi_trivial_usage
153 //usage: "[OPTIONS] [FILE]..."
154 //usage:#define vi_full_usage "\n\n"
155 //usage: "Edit FILE\n"
156 //usage: IF_FEATURE_VI_COLON(
157 //usage: "\n -c Initial command to run ($EXINIT also available)"
159 //usage: IF_FEATURE_VI_READONLY(
160 //usage: "\n -R Read-only"
162 //usage: "\n -H Short help regarding available features"
165 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
166 #if ENABLE_FEATURE_VI_REGEX_SEARCH
170 /* the CRASHME code is unmaintained, and doesn't currently build */
171 #define ENABLE_FEATURE_VI_CRASHME 0
174 #if ENABLE_LOCALE_SUPPORT
176 #if ENABLE_FEATURE_VI_8BIT
177 //FIXME: this does not work properly for Unicode anyway
178 # define Isprint(c) (isprint)(c)
180 # define Isprint(c) isprint_asciionly(c)
185 /* 0x9b is Meta-ESC */
186 #if ENABLE_FEATURE_VI_8BIT
187 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
189 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
196 MAX_TABSTOP = 32, // sanity limit
197 // User input len. Need not be extra big.
198 // Lines in file being edited *can* be bigger than this.
200 // Sanity limits. We have only one buffer of this size.
201 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
202 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
205 /* vt102 typical ESC sequence */
206 /* terminal standout start/normal ESC sequence */
207 #define SOs "\033[7m"
208 #define SOn "\033[0m"
209 /* terminal bell sequence */
211 /* Clear-end-of-line and Clear-end-of-screen ESC sequence */
212 #define Ceol "\033[K"
213 #define Ceos "\033[J"
214 /* Cursor motion arbitrary destination ESC sequence */
215 #define CMrc "\033[%u;%uH"
216 /* Cursor motion up and down ESC sequence */
217 #define CMup "\033[A"
220 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
221 // cmds modifying text[]
222 // vda: removed "aAiIs" as they switch us into insert mode
223 // and remembering input for replay after them makes no sense
224 static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
230 FORWARD = 1, // code depends on "1" for array index
231 BACK = -1, // code depends on "-1" for array index
232 LIMITED = 0, // how much of text[] in char_search
233 FULL = 1, // how much of text[] in char_search
235 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
236 S_TO_WS = 2, // used in skip_thing() for moving "dot"
237 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
238 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
239 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
243 /* vi.c expects chars to be unsigned. */
244 /* busybox build system provides that, but it's better */
245 /* to audit and fix the source */
248 /* many references - keep near the top of globals */
249 char *text, *end; // pointers to the user data in memory
250 char *dot; // where all the action takes place
251 int text_size; // size of the allocated buffer
255 #define VI_AUTOINDENT 1
256 #define VI_SHOWMATCH 2
257 #define VI_IGNORECASE 4
258 #define VI_ERR_METHOD 8
259 #define autoindent (vi_setops & VI_AUTOINDENT)
260 #define showmatch (vi_setops & VI_SHOWMATCH )
261 #define ignorecase (vi_setops & VI_IGNORECASE)
262 /* indicate error with beep or flash */
263 #define err_method (vi_setops & VI_ERR_METHOD)
265 #if ENABLE_FEATURE_VI_READONLY
266 smallint readonly_mode;
267 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
268 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
269 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
271 #define SET_READONLY_FILE(flags) ((void)0)
272 #define SET_READONLY_MODE(flags) ((void)0)
273 #define UNSET_READONLY_FILE(flags) ((void)0)
276 smallint editing; // >0 while we are editing a file
277 // [code audit says "can be 0, 1 or 2 only"]
278 smallint cmd_mode; // 0=command 1=insert 2=replace
279 int file_modified; // buffer contents changed (counter, not flag!)
280 int last_file_modified; // = -1;
281 int fn_start; // index of first cmd line file name
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_OPTIMIZE_CURSOR
308 int last_row; // where the cursor was last moved to
310 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
313 #if ENABLE_FEATURE_VI_SEARCH
314 char *last_search_pattern; // last pattern from a '/' or '?' search
318 #if ENABLE_FEATURE_VI_YANKMARK
319 char *edit_file__cur_line;
321 int refresh__old_offset;
322 int format_edit_status__tot;
324 /* a few references only */
325 #if ENABLE_FEATURE_VI_YANKMARK
326 int YDreg, Ureg; // default delete register and orig line for "U"
327 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
328 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
329 char *context_start, *context_end;
331 #if ENABLE_FEATURE_VI_USE_SIGNALS
332 sigjmp_buf restart; // catch_sig()
334 struct termios term_orig, term_vi; // remember what the cooked mode was
335 #if ENABLE_FEATURE_VI_COLON
336 char *initial_cmds[3]; // currently 2 entries, NULL terminated
338 // Should be just enough to hold a key sequence,
339 // but CRASHME mode uses it as generated command buffer too
340 #if ENABLE_FEATURE_VI_CRASHME
341 char readbuffer[128];
343 char readbuffer[KEYCODE_BUFFER_SIZE];
345 #define STATUS_BUFFER_LEN 200
346 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
347 #if ENABLE_FEATURE_VI_DOT_CMD
348 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
350 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
352 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
354 #define G (*ptr_to_globals)
355 #define text (G.text )
356 #define text_size (G.text_size )
361 #define vi_setops (G.vi_setops )
362 #define editing (G.editing )
363 #define cmd_mode (G.cmd_mode )
364 #define file_modified (G.file_modified )
365 #define last_file_modified (G.last_file_modified )
366 #define fn_start (G.fn_start )
367 #define save_argc (G.save_argc )
368 #define cmdcnt (G.cmdcnt )
369 #define rows (G.rows )
370 #define columns (G.columns )
371 #define crow (G.crow )
372 #define ccol (G.ccol )
373 #define offset (G.offset )
374 #define status_buffer (G.status_buffer )
375 #define have_status_msg (G.have_status_msg )
376 #define last_status_cksum (G.last_status_cksum )
377 #define current_filename (G.current_filename )
378 #define screen (G.screen )
379 #define screensize (G.screensize )
380 #define screenbegin (G.screenbegin )
381 #define tabstop (G.tabstop )
382 #define last_forward_char (G.last_forward_char )
383 #define erase_char (G.erase_char )
384 #define last_input_char (G.last_input_char )
385 #if ENABLE_FEATURE_VI_READONLY
386 #define readonly_mode (G.readonly_mode )
388 #define readonly_mode 0
390 #define adding2q (G.adding2q )
391 #define lmc_len (G.lmc_len )
393 #define ioq_start (G.ioq_start )
394 #define last_row (G.last_row )
395 #define my_pid (G.my_pid )
396 #define last_search_pattern (G.last_search_pattern)
398 #define edit_file__cur_line (G.edit_file__cur_line)
399 #define refresh__old_offset (G.refresh__old_offset)
400 #define format_edit_status__tot (G.format_edit_status__tot)
402 #define YDreg (G.YDreg )
403 #define Ureg (G.Ureg )
404 #define mark (G.mark )
405 #define context_start (G.context_start )
406 #define context_end (G.context_end )
407 #define restart (G.restart )
408 #define term_orig (G.term_orig )
409 #define term_vi (G.term_vi )
410 #define initial_cmds (G.initial_cmds )
411 #define readbuffer (G.readbuffer )
412 #define scr_out_buf (G.scr_out_buf )
413 #define last_modifying_cmd (G.last_modifying_cmd )
414 #define get_input_line__buf (G.get_input_line__buf)
416 #define INIT_G() do { \
417 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
418 last_file_modified = -1; \
419 /* "" but has space for 2 chars: */ \
420 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
424 static int init_text_buffer(char *); // init from file or create new
425 static void edit_file(char *); // edit one file
426 static void do_cmd(int); // execute a command
427 static int next_tabstop(int);
428 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
429 static char *begin_line(char *); // return pointer to cur line B-o-l
430 static char *end_line(char *); // return pointer to cur line E-o-l
431 static char *prev_line(char *); // return pointer to prev line B-o-l
432 static char *next_line(char *); // return pointer to next line B-o-l
433 static char *end_screen(void); // get pointer to last char on screen
434 static int count_lines(char *, char *); // count line from start to stop
435 static char *find_line(int); // find begining of line #li
436 static char *move_to_col(char *, int); // move "p" to column l
437 static void dot_left(void); // move dot left- dont leave line
438 static void dot_right(void); // move dot right- dont leave line
439 static void dot_begin(void); // move dot to B-o-l
440 static void dot_end(void); // move dot to E-o-l
441 static void dot_next(void); // move dot to next line B-o-l
442 static void dot_prev(void); // move dot to prev line B-o-l
443 static void dot_scroll(int, int); // move the screen up or down
444 static void dot_skip_over_ws(void); // move dot pat WS
445 static void dot_delete(void); // delete the char at 'dot'
446 static char *bound_dot(char *); // make sure text[0] <= P < "end"
447 static char *new_screen(int, int); // malloc virtual screen memory
448 static char *char_insert(char *, char); // insert the char c at 'p'
449 // might reallocate text[]! use p += stupid_insert(p, ...),
450 // and be careful to not use pointers into potentially freed text[]!
451 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
452 static int find_range(char **, char **, char); // return pointers for an object
453 static int st_test(char *, int, int, char *); // helper for skip_thing()
454 static char *skip_thing(char *, int, int, int); // skip some object
455 static char *find_pair(char *, char); // find matching pair () [] {}
456 static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
457 // might reallocate text[]! use p += text_hole_make(p, ...),
458 // and be careful to not use pointers into potentially freed text[]!
459 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
460 static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
461 static void show_help(void); // display some help info
462 static void rawmode(void); // set "raw" mode on tty
463 static void cookmode(void); // return to "cooked" mode on tty
464 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
465 static int mysleep(int);
466 static int readit(void); // read (maybe cursor) key from stdin
467 static int get_one_char(void); // read 1 char from stdin
468 static int file_size(const char *); // what is the byte size of "fn"
469 #if !ENABLE_FEATURE_VI_READONLY
470 #define file_insert(fn, p, update_ro_status) file_insert(fn, p)
472 // file_insert might reallocate text[]!
473 static int file_insert(const char *, char *, int);
474 static int file_write(char *, char *, char *);
475 #if !ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
476 #define place_cursor(a, b, optimize) place_cursor(a, b)
478 static void place_cursor(int, int, int);
479 static void screen_erase(void);
480 static void clear_to_eol(void);
481 static void clear_to_eos(void);
482 static void go_bottom_and_clear_to_eol(void);
483 static void standout_start(void); // send "start reverse video" sequence
484 static void standout_end(void); // send "end reverse video" sequence
485 static void flash(int); // flash the terminal screen
486 static void show_status_line(void); // put a message on the bottom line
487 static void status_line(const char *, ...); // print to status buf
488 static void status_line_bold(const char *, ...);
489 static void not_implemented(const char *); // display "Not implemented" message
490 static int format_edit_status(void); // format file status on status line
491 static void redraw(int); // force a full screen refresh
492 static char* format_line(char* /*, int*/);
493 static void refresh(int); // update the terminal from screen[]
495 static void Indicate_Error(void); // use flash or beep to indicate error
496 #define indicate_error(c) Indicate_Error()
497 static void Hit_Return(void);
499 #if ENABLE_FEATURE_VI_SEARCH
500 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
502 #if ENABLE_FEATURE_VI_COLON
503 static char *get_one_address(char *, int *); // get colon addr, if present
504 static char *get_address(char *, int *, int *); // get two colon addrs, if present
505 static void colon(char *); // execute the "colon" mode cmds
507 #if ENABLE_FEATURE_VI_USE_SIGNALS
508 static void winch_sig(int); // catch window size changes
509 static void suspend_sig(int); // catch ctrl-Z
510 static void catch_sig(int); // catch ctrl-C and alarm time-outs
512 #if ENABLE_FEATURE_VI_DOT_CMD
513 static void start_new_cmd_q(char); // new queue for command
514 static void end_cmd_q(void); // stop saving input chars
516 #define end_cmd_q() ((void)0)
518 #if ENABLE_FEATURE_VI_SETOPTS
519 static void showmatching(char *); // show the matching pair () [] {}
521 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
522 // might reallocate text[]! use p += string_insert(p, ...),
523 // and be careful to not use pointers into potentially freed text[]!
524 static uintptr_t string_insert(char *, const char *); // insert the string at 'p'
526 #if ENABLE_FEATURE_VI_YANKMARK
527 static char *text_yank(char *, char *, int); // save copy of "p" into a register
528 static char what_reg(void); // what is letter of current YDreg
529 static void check_context(char); // remember context for '' command
531 #if ENABLE_FEATURE_VI_CRASHME
532 static void crash_dummy();
533 static void crash_test();
534 static int crashme = 0;
538 static void write1(const char *out)
543 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
544 int vi_main(int argc, char **argv)
550 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
553 #if ENABLE_FEATURE_VI_CRASHME
554 srand((long) my_pid);
556 #ifdef NO_SUCH_APPLET_YET
557 /* If we aren't "vi", we are "view" */
558 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
559 SET_READONLY_MODE(readonly_mode);
563 vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE;
564 // 1- process $HOME/.exrc file (not inplemented yet)
565 // 2- process EXINIT variable from environment
566 // 3- process command line args
567 #if ENABLE_FEATURE_VI_COLON
569 char *p = getenv("EXINIT");
571 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
574 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
576 #if ENABLE_FEATURE_VI_CRASHME
581 #if ENABLE_FEATURE_VI_READONLY
582 case 'R': // Read-only flag
583 SET_READONLY_MODE(readonly_mode);
586 #if ENABLE_FEATURE_VI_COLON
587 case 'c': // cmd line vi command
589 initial_cmds[initial_cmds[0] != 0] = xstrndup(optarg, MAX_INPUT_LEN);
601 // The argv array can be used by the ":next" and ":rewind" commands
603 fn_start = optind; // remember first file name for :next and :rew
606 //----- This is the main file handling loop --------------
608 edit_file(argv[optind]); /* param might be NULL */
609 if (++optind >= argc)
612 //-----------------------------------------------------------
617 /* read text from file or create an empty buf */
618 /* will also update current_filename */
619 static int init_text_buffer(char *fn)
622 int size = file_size(fn); // file size. -1 means does not exist.
624 /* allocate/reallocate text buffer */
626 text_size = size + 10240;
627 screenbegin = dot = end = text = xzalloc(text_size);
629 if (fn != current_filename) {
630 free(current_filename);
631 current_filename = xstrdup(fn);
634 // file dont exist. Start empty buf with dummy line
635 char_insert(text, '\n');
638 rc = file_insert(fn, text, 1);
641 last_file_modified = -1;
642 #if ENABLE_FEATURE_VI_YANKMARK
643 /* init the marks. */
644 memset(mark, 0, sizeof(mark));
649 #if ENABLE_FEATURE_VI_WIN_RESIZE
650 static int query_screen_dimensions(void)
652 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
653 if (rows > MAX_SCR_ROWS)
655 if (columns > MAX_SCR_COLS)
656 columns = MAX_SCR_COLS;
660 # define query_screen_dimensions() (0)
663 static void edit_file(char *fn)
665 #if ENABLE_FEATURE_VI_YANKMARK
666 #define cur_line edit_file__cur_line
669 #if ENABLE_FEATURE_VI_USE_SIGNALS
673 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
677 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
678 #if ENABLE_FEATURE_VI_ASK_TERMINAL
679 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
681 write1("\033[999;999H" "\033[6n");
683 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
684 if ((int32_t)k == KEYCODE_CURSOR_POS) {
685 uint32_t rc = (k >> 32);
686 columns = (rc & 0x7fff);
687 if (columns > MAX_SCR_COLS)
688 columns = MAX_SCR_COLS;
689 rows = ((rc >> 16) & 0x7fff);
690 if (rows > MAX_SCR_ROWS)
695 new_screen(rows, columns); // get memory for virtual screen
696 init_text_buffer(fn);
698 #if ENABLE_FEATURE_VI_YANKMARK
699 YDreg = 26; // default Yank/Delete reg
700 Ureg = 27; // hold orig line for "U" cmd
701 mark[26] = mark[27] = text; // init "previous context"
704 last_forward_char = last_input_char = '\0';
708 #if ENABLE_FEATURE_VI_USE_SIGNALS
709 signal(SIGINT, catch_sig);
710 signal(SIGWINCH, winch_sig);
711 signal(SIGTSTP, suspend_sig);
712 sig = sigsetjmp(restart, 1);
714 screenbegin = dot = text;
718 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
721 offset = 0; // no horizontal offset
723 #if ENABLE_FEATURE_VI_DOT_CMD
725 ioq = ioq_start = NULL;
730 #if ENABLE_FEATURE_VI_COLON
735 while ((p = initial_cmds[n]) != NULL) {
745 free(initial_cmds[n]);
746 initial_cmds[n] = NULL;
751 redraw(FALSE); // dont force every col re-draw
752 //------This is the main Vi cmd handling loop -----------------------
753 while (editing > 0) {
754 #if ENABLE_FEATURE_VI_CRASHME
756 if ((end - text) > 1) {
757 crash_dummy(); // generate a random command
760 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
766 last_input_char = c = get_one_char(); // get a cmd from user
767 #if ENABLE_FEATURE_VI_YANKMARK
768 // save a copy of the current line- for the 'U" command
769 if (begin_line(dot) != cur_line) {
770 cur_line = begin_line(dot);
771 text_yank(begin_line(dot), end_line(dot), Ureg);
774 #if ENABLE_FEATURE_VI_DOT_CMD
775 // These are commands that change text[].
776 // Remember the input for the "." command
777 if (!adding2q && ioq_start == NULL
778 && cmd_mode == 0 // command mode
779 && c > '\0' // exclude NUL and non-ASCII chars
780 && c < 0x7f // (Unicode and such)
781 && strchr(modifying_cmds, c)
786 do_cmd(c); // execute the user command
788 // poll to see if there is input already waiting. if we are
789 // not able to display output fast enough to keep up, skip
790 // the display update until we catch up with input.
791 if (!readbuffer[0] && mysleep(0) == 0) {
792 // no input pending - so update output
796 #if ENABLE_FEATURE_VI_CRASHME
798 crash_test(); // test editor variables
801 //-------------------------------------------------------------------
803 go_bottom_and_clear_to_eol();
808 //----- The Colon commands -------------------------------------
809 #if ENABLE_FEATURE_VI_COLON
810 static char *get_one_address(char *p, int *addr) // get colon addr, if present
814 IF_FEATURE_VI_YANKMARK(char c;)
815 IF_FEATURE_VI_SEARCH(char *pat;)
817 *addr = -1; // assume no addr
818 if (*p == '.') { // the current line
821 *addr = count_lines(text, q);
823 #if ENABLE_FEATURE_VI_YANKMARK
824 else if (*p == '\'') { // is this a mark addr
828 if (c >= 'a' && c <= 'z') {
831 q = mark[(unsigned char) c];
832 if (q != NULL) { // is mark valid
833 *addr = count_lines(text, q);
838 #if ENABLE_FEATURE_VI_SEARCH
839 else if (*p == '/') { // a search pattern
840 q = strchrnul(++p, '/');
841 pat = xstrndup(p, q - p); // save copy of pattern
845 q = char_search(dot, pat, FORWARD, FULL);
847 *addr = count_lines(text, q);
852 else if (*p == '$') { // the last line in file
854 q = begin_line(end - 1);
855 *addr = count_lines(text, q);
856 } else if (isdigit(*p)) { // specific line number
857 sscanf(p, "%d%n", addr, &st);
860 // unrecognized address - assume -1
866 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
868 //----- get the address' i.e., 1,3 'a,'b -----
869 // get FIRST addr, if present
871 p++; // skip over leading spaces
872 if (*p == '%') { // alias for 1,$
875 *e = count_lines(text, end-1);
878 p = get_one_address(p, b);
881 if (*p == ',') { // is there a address separator
885 // get SECOND addr, if present
886 p = get_one_address(p, e);
890 p++; // skip over trailing spaces
894 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
895 static void setops(const char *args, const char *opname, int flg_no,
896 const char *short_opname, int opt)
898 const char *a = args + flg_no;
899 int l = strlen(opname) - 1; /* opname have + ' ' */
901 // maybe strncmp? we had tons of erroneous strncasecmp's...
902 if (strncasecmp(a, opname, l) == 0
903 || strncasecmp(a, short_opname, 2) == 0
913 // buf must be no longer than MAX_INPUT_LEN!
914 static void colon(char *buf)
916 char c, *orig_buf, *buf1, *q, *r;
917 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
918 int i, l, li, ch, b, e;
919 int useforce, forced = FALSE;
921 // :3154 // if (-e line 3154) goto it else stay put
922 // :4,33w! foo // write a portion of buffer to file "foo"
923 // :w // write all of buffer to current file
925 // :q! // quit- dont care about modified file
926 // :'a,'z!sort -u // filter block through sort
927 // :'f // goto mark "f"
928 // :'fl // list literal the mark "f" line
929 // :.r bar // read file "bar" into buffer before dot
930 // :/123/,/abc/d // delete lines from "123" line to "abc" line
931 // :/xyz/ // goto the "xyz" line
932 // :s/find/replace/ // substitute pattern "find" with "replace"
933 // :!<cmd> // run <cmd> then return
939 buf++; // move past the ':'
943 q = text; // assume 1,$ for the range
945 li = count_lines(text, end - 1);
946 fn = current_filename;
948 // look for optional address(es) :. :1 :1,9 :'q,'a :%
949 buf = get_address(buf, &b, &e);
951 // remember orig command line
954 // get the COMMAND into cmd[]
956 while (*buf != '\0') {
963 while (isblank(*buf))
967 buf1 = last_char_is(cmd, '!');
970 *buf1 = '\0'; // get rid of !
973 // if there is only one addr, then the addr
974 // is the line number of the single line the
975 // user wants. So, reset the end
976 // pointer to point at end of the "b" line
977 q = find_line(b); // what line is #b
982 // we were given two addrs. change the
983 // end pointer to the addr given by user.
984 r = find_line(e); // what line is #e
988 // ------------ now look for the command ------------
990 if (i == 0) { // :123CR goto line #123
992 dot = find_line(b); // what line is #b
996 #if ENABLE_FEATURE_ALLOW_EXEC
997 else if (cmd[0] == '!') { // run a cmd
999 // :!ls run the <cmd>
1000 go_bottom_and_clear_to_eol();
1002 retcode = system(orig_buf + 1); // run the cmd
1004 printf("\nshell returned %i\n\n", retcode);
1006 Hit_Return(); // let user see results
1009 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1010 if (b < 0) { // no addr given- use defaults
1011 b = e = count_lines(text, dot);
1013 status_line("%d", b);
1014 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1015 if (b < 0) { // no addr given- use defaults
1016 q = begin_line(dot); // assume .,. for the range
1019 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1021 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1022 // don't edit, if the current file has been modified
1023 if (file_modified && !useforce) {
1024 status_line_bold("No write since last change (:edit! overrides)");
1028 // the user supplied a file name
1030 } else if (current_filename && current_filename[0]) {
1031 // no user supplied name- use the current filename
1032 // fn = current_filename; was set by default
1034 // no user file name, no current name- punt
1035 status_line_bold("No current filename");
1039 if (init_text_buffer(fn) < 0)
1042 #if ENABLE_FEATURE_VI_YANKMARK
1043 if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) {
1044 free(reg[Ureg]); // free orig line reg- for 'U'
1047 if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) {
1048 free(reg[YDreg]); // free default yank/delete register
1052 // how many lines in text[]?
1053 li = count_lines(text, end - 1);
1054 status_line("\"%s\"%s"
1055 IF_FEATURE_VI_READONLY("%s")
1056 " %dL, %dC", current_filename,
1057 (file_size(fn) < 0 ? " [New file]" : ""),
1058 IF_FEATURE_VI_READONLY(
1059 ((readonly_mode) ? " [Readonly]" : ""),
1062 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1063 if (b != -1 || e != -1) {
1064 status_line_bold("No address allowed on this command");
1068 // user wants a new filename
1069 free(current_filename);
1070 current_filename = xstrdup(args);
1072 // user wants file status info
1073 last_status_cksum = 0; // force status update
1075 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1076 // print out values of all features
1077 go_bottom_and_clear_to_eol();
1082 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1083 if (b < 0) { // no addr given- use defaults
1084 q = begin_line(dot); // assume .,. for the range
1087 go_bottom_and_clear_to_eol();
1089 for (; q <= r; q++) {
1093 c_is_no_print = (c & 0x80) && !Isprint(c);
1094 if (c_is_no_print) {
1100 } else if (c < ' ' || c == 127) {
1112 } else if (strncmp(cmd, "quit", i) == 0 // quit
1113 || strncmp(cmd, "next", i) == 0 // edit next file
1117 // 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)",
1127 (*cmd == 'q' ? "quit" : "next"));
1130 // are there other file to edit
1131 n = save_argc - optind - 1;
1132 if (*cmd == 'q' && n > 0) {
1133 status_line_bold("%d more file(s) to edit", n);
1136 if (*cmd == 'n' && n <= 0) {
1137 status_line_bold("No more files to edit");
1141 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1144 status_line_bold("No filename given");
1147 if (b < 0) { // no addr given- use defaults
1148 q = begin_line(dot); // assume "dot"
1150 // read after current line- unless user said ":0r foo"
1153 { // dance around potentially-reallocated text[]
1154 uintptr_t ofs = q - text;
1155 ch = file_insert(fn, q, 0);
1159 goto ret; // nothing was inserted
1160 // how many lines in text[]?
1161 li = count_lines(q, q + ch - 1);
1162 status_line("\"%s\""
1163 IF_FEATURE_VI_READONLY("%s")
1165 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1168 // if the insert is before "dot" then we need to update
1171 /*file_modified++; - done by file_insert */
1173 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1174 if (file_modified && !useforce) {
1175 status_line_bold("No write since last change (:rewind! overrides)");
1177 // reset the filenames to edit
1178 optind = fn_start - 1;
1181 #if ENABLE_FEATURE_VI_SET
1182 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1183 #if ENABLE_FEATURE_VI_SETOPTS
1186 i = 0; // offset into args
1187 // only blank is regarded as args delmiter. What about tab '\t' ?
1188 if (!args[0] || strcasecmp(args, "all") == 0) {
1189 // print out values of all options
1190 #if ENABLE_FEATURE_VI_SETOPTS
1197 autoindent ? "" : "no",
1198 err_method ? "" : "no",
1199 ignorecase ? "" : "no",
1200 showmatch ? "" : "no",
1206 #if ENABLE_FEATURE_VI_SETOPTS
1209 if (strncmp(argp, "no", 2) == 0)
1210 i = 2; // ":set noautoindent"
1211 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1212 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1213 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1214 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1215 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1217 sscanf(argp + i+8, "%u", &t);
1218 if (t > 0 && t <= MAX_TABSTOP)
1221 argp = skip_non_whitespace(argp);
1222 argp = skip_whitespace(argp);
1224 #endif /* FEATURE_VI_SETOPTS */
1225 #endif /* FEATURE_VI_SET */
1226 #if ENABLE_FEATURE_VI_SEARCH
1227 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1231 // F points to the "find" pattern
1232 // R points to the "replace" pattern
1233 // replace the cmd line delimiters "/" with NULLs
1234 gflag = 0; // global replace flag
1235 c = orig_buf[1]; // what is the delimiter
1236 F = orig_buf + 2; // start of "find"
1237 R = strchr(F, c); // middle delimiter
1240 *R++ = '\0'; // terminate "find"
1241 buf1 = strchr(R, c);
1244 *buf1++ = '\0'; // terminate "replace"
1245 if (*buf1 == 'g') { // :s/foo/bar/g
1247 gflag++; // turn on gflag
1250 if (b < 0) { // maybe :s/foo/bar/
1251 q = begin_line(dot); // start with cur line
1252 b = count_lines(text, q); // cur line number
1255 e = b; // maybe :.s/foo/bar/
1256 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1257 ls = q; // orig line start
1259 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1262 // we found the "find" pattern - delete it
1263 text_hole_delete(buf1, buf1 + strlen(F) - 1);
1264 // inset the "replace" patern
1265 bias = string_insert(buf1, R); // insert the string
1268 /*q += bias; - recalculated anyway */
1269 // check for "global" :s/foo/bar/g
1271 if ((buf1 + strlen(R)) < end_line(ls)) {
1272 q = buf1 + strlen(R);
1273 goto vc4; // don't let q move past cur line
1279 #endif /* FEATURE_VI_SEARCH */
1280 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1281 status_line(BB_VER " " BB_BT);
1282 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1283 || strncmp(cmd, "wq", i) == 0
1284 || strncmp(cmd, "wn", i) == 0
1285 || (cmd[0] == 'x' && !cmd[1])
1287 // is there a file name to write to?
1291 #if ENABLE_FEATURE_VI_READONLY
1292 if (readonly_mode && !useforce) {
1293 status_line_bold("\"%s\" File is read only", fn);
1297 // how many lines in text[]?
1298 li = count_lines(q, r);
1300 // see if file exists- if not, its just a new file request
1302 // if "fn" is not write-able, chmod u+w
1303 // sprintf(syscmd, "chmod u+w %s", fn);
1307 l = file_write(fn, q, r);
1308 if (useforce && forced) {
1310 // sprintf(syscmd, "chmod u-w %s", fn);
1316 status_line_bold("\"%s\" %s", fn, strerror(errno));
1318 status_line("\"%s\" %dL, %dC", fn, li, l);
1319 if (q == text && r == end - 1 && l == ch) {
1321 last_file_modified = -1;
1323 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
1324 || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
1331 #if ENABLE_FEATURE_VI_YANKMARK
1332 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1333 if (b < 0) { // no addr given- use defaults
1334 q = begin_line(dot); // assume .,. for the range
1337 text_yank(q, r, YDreg);
1338 li = count_lines(q, r);
1339 status_line("Yank %d lines (%d chars) into [%c]",
1340 li, strlen(reg[YDreg]), what_reg());
1344 not_implemented(cmd);
1347 dot = bound_dot(dot); // make sure "dot" is valid
1349 #if ENABLE_FEATURE_VI_SEARCH
1351 status_line(":s expression missing delimiters");
1355 #endif /* FEATURE_VI_COLON */
1357 static void Hit_Return(void)
1362 write1("[Hit return to continue]");
1364 while ((c = get_one_char()) != '\n' && c != '\r')
1366 redraw(TRUE); // force redraw all
1369 static int next_tabstop(int col)
1371 return col + ((tabstop - 1) - (col % tabstop));
1374 //----- Synchronize the cursor to Dot --------------------------
1375 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1377 char *beg_cur; // begin and end of "d" line
1381 beg_cur = begin_line(d); // first char of cur line
1383 if (beg_cur < screenbegin) {
1384 // "d" is before top line on screen
1385 // how many lines do we have to move
1386 cnt = count_lines(beg_cur, screenbegin);
1388 screenbegin = beg_cur;
1389 if (cnt > (rows - 1) / 2) {
1390 // we moved too many lines. put "dot" in middle of screen
1391 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1392 screenbegin = prev_line(screenbegin);
1396 char *end_scr; // begin and end of screen
1397 end_scr = end_screen(); // last char of screen
1398 if (beg_cur > end_scr) {
1399 // "d" is after bottom line on screen
1400 // how many lines do we have to move
1401 cnt = count_lines(end_scr, beg_cur);
1402 if (cnt > (rows - 1) / 2)
1403 goto sc1; // too many lines
1404 for (ro = 0; ro < cnt - 1; ro++) {
1405 // move screen begin the same amount
1406 screenbegin = next_line(screenbegin);
1407 // now, move the end of screen
1408 end_scr = next_line(end_scr);
1409 end_scr = end_line(end_scr);
1413 // "d" is on screen- find out which row
1415 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1421 // find out what col "d" is on
1423 while (tp < d) { // drive "co" to correct column
1424 if (*tp == '\n') //vda || *tp == '\0')
1427 // handle tabs like real vi
1428 if (d == tp && cmd_mode) {
1431 co = next_tabstop(co);
1432 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1433 co++; // display as ^X, use 2 columns
1439 // "co" is the column where "dot" is.
1440 // The screen has "columns" columns.
1441 // The currently displayed columns are 0+offset -- columns+ofset
1442 // |-------------------------------------------------------------|
1444 // offset | |------- columns ----------------|
1446 // If "co" is already in this range then we do not have to adjust offset
1447 // but, we do have to subtract the "offset" bias from "co".
1448 // If "co" is outside this range then we have to change "offset".
1449 // If the first char of a line is a tab the cursor will try to stay
1450 // in column 7, but we have to set offset to 0.
1452 if (co < 0 + offset) {
1455 if (co >= columns + offset) {
1456 offset = co - columns + 1;
1458 // if the first char of the line is a tab, and "dot" is sitting on it
1459 // force offset to 0.
1460 if (d == beg_cur && *d == '\t') {
1469 //----- Text Movement Routines ---------------------------------
1470 static char *begin_line(char *p) // return pointer to first char cur line
1473 p = memrchr(text, '\n', p - text);
1481 static char *end_line(char *p) // return pointer to NL of cur line
1484 p = memchr(p, '\n', end - p - 1);
1491 static char *dollar_line(char *p) // return pointer to just before NL line
1494 // Try to stay off of the Newline
1495 if (*p == '\n' && (p - begin_line(p)) > 0)
1500 static char *prev_line(char *p) // return pointer first char prev line
1502 p = begin_line(p); // goto begining of cur line
1503 if (p > text && p[-1] == '\n')
1504 p--; // step to prev line
1505 p = begin_line(p); // goto begining of prev line
1509 static char *next_line(char *p) // return pointer first char next line
1512 if (p < end - 1 && *p == '\n')
1513 p++; // step to next line
1517 //----- Text Information Routines ------------------------------
1518 static char *end_screen(void)
1523 // find new bottom line
1525 for (cnt = 0; cnt < rows - 2; cnt++)
1531 // count line from start to stop
1532 static int count_lines(char *start, char *stop)
1537 if (stop < start) { // start and stop are backwards- reverse them
1543 stop = end_line(stop);
1544 while (start <= stop && start <= end - 1) {
1545 start = end_line(start);
1553 static char *find_line(int li) // find begining of line #li
1557 for (q = text; li > 1; li--) {
1563 //----- Dot Movement Routines ----------------------------------
1564 static void dot_left(void)
1566 if (dot > text && dot[-1] != '\n')
1570 static void dot_right(void)
1572 if (dot < end - 1 && *dot != '\n')
1576 static void dot_begin(void)
1578 dot = begin_line(dot); // return pointer to first char cur line
1581 static void dot_end(void)
1583 dot = end_line(dot); // return pointer to last char cur line
1586 static char *move_to_col(char *p, int l)
1592 while (co < l && p < end) {
1593 if (*p == '\n') //vda || *p == '\0')
1596 co = next_tabstop(co);
1597 } else if (*p < ' ' || *p == 127) {
1598 co++; // display as ^X, use 2 columns
1606 static void dot_next(void)
1608 dot = next_line(dot);
1611 static void dot_prev(void)
1613 dot = prev_line(dot);
1616 static void dot_scroll(int cnt, int dir)
1620 for (; cnt > 0; cnt--) {
1623 // ctrl-Y scroll up one line
1624 screenbegin = prev_line(screenbegin);
1627 // ctrl-E scroll down one line
1628 screenbegin = next_line(screenbegin);
1631 // make sure "dot" stays on the screen so we dont scroll off
1632 if (dot < screenbegin)
1634 q = end_screen(); // find new bottom line
1636 dot = begin_line(q); // is dot is below bottom line?
1640 static void dot_skip_over_ws(void)
1643 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1647 static void dot_delete(void) // delete the char at 'dot'
1649 text_hole_delete(dot, dot);
1652 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1654 if (p >= end && end > text) {
1656 indicate_error('1');
1660 indicate_error('2');
1665 //----- Helper Utility Routines --------------------------------
1667 //----------------------------------------------------------------
1668 //----- Char Routines --------------------------------------------
1669 /* Chars that are part of a word-
1670 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1671 * Chars that are Not part of a word (stoppers)
1672 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1673 * Chars that are WhiteSpace
1674 * TAB NEWLINE VT FF RETURN SPACE
1675 * DO NOT COUNT NEWLINE AS WHITESPACE
1678 static char *new_screen(int ro, int co)
1683 screensize = ro * co + 8;
1684 screen = xmalloc(screensize);
1685 // initialize the new screen. assume this will be a empty file.
1687 // non-existent text[] lines start with a tilde (~).
1688 for (li = 1; li < ro - 1; li++) {
1689 screen[(li * co) + 0] = '~';
1694 #if ENABLE_FEATURE_VI_SEARCH
1696 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1698 // search for pattern starting at p
1699 static char *char_search(char *p, const char *pat, int dir, int range)
1702 struct re_pattern_buffer preg;
1706 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1712 // assume a LIMITED forward search
1720 // count the number of chars to search over, forward or backward
1724 // RANGE could be negative if we are searching backwards
1727 q = (char *)re_compile_pattern(pat, strlen(pat), (struct re_pattern_buffer *)&preg);
1729 // The pattern was not compiled
1730 status_line_bold("bad search pattern: \"%s\": %s", pat, q);
1731 i = 0; // return p if pattern not compiled
1741 // search for the compiled pattern, preg, in p[]
1742 // range < 0- search backward
1743 // range > 0- search forward
1745 // re_search() < 0 not found or error
1746 // re_search() > 0 index of found pattern
1747 // struct pattern char int int int struct reg
1748 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1749 i = re_search(&preg, q, size, 0, range, 0);
1752 i = 0; // return NULL if pattern not found
1755 if (dir == FORWARD) {
1765 # if ENABLE_FEATURE_VI_SETOPTS
1766 static int mycmp(const char *s1, const char *s2, int len)
1769 return strncasecmp(s1, s2, len);
1771 return strncmp(s1, s2, len);
1774 # define mycmp strncmp
1777 static char *char_search(char *p, const char *pat, int dir, int range)
1783 if (dir == FORWARD) {
1784 stop = end - 1; // assume range is p - end-1
1785 if (range == LIMITED)
1786 stop = next_line(p); // range is to next line
1787 for (start = p; start < stop; start++) {
1788 if (mycmp(start, pat, len) == 0) {
1792 } else if (dir == BACK) {
1793 stop = text; // assume range is text - p
1794 if (range == LIMITED)
1795 stop = prev_line(p); // range is to prev line
1796 for (start = p - len; start >= stop; start--) {
1797 if (mycmp(start, pat, len) == 0) {
1802 // pattern not found
1808 #endif /* FEATURE_VI_SEARCH */
1810 static char *char_insert(char *p, char c) // insert the char c at 'p'
1812 if (c == 22) { // Is this an ctrl-V?
1813 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1814 refresh(FALSE); // show the ^
1819 } else if (c == 27) { // Is this an ESC?
1822 end_cmd_q(); // stop adding to q
1823 last_status_cksum = 0; // force status update
1824 if ((p[-1] != '\n') && (dot > text)) {
1827 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1829 if ((p[-1] != '\n') && (dot>text)) {
1831 p = text_hole_delete(p, p); // shrink buffer 1 char
1834 #if ENABLE_FEATURE_VI_SETOPTS
1835 // insert a char into text[]
1836 char *sp; // "save p"
1840 c = '\n'; // translate \r to \n
1841 #if ENABLE_FEATURE_VI_SETOPTS
1842 sp = p; // remember addr of insert
1844 p += 1 + stupid_insert(p, c); // insert the char
1845 #if ENABLE_FEATURE_VI_SETOPTS
1846 if (showmatch && strchr(")]}", *sp) != NULL) {
1849 if (autoindent && c == '\n') { // auto indent the new line
1852 q = prev_line(p); // use prev line as template
1853 len = strspn(q, " \t"); // space or tab
1856 bias = text_hole_make(p, len);
1868 // might reallocate text[]! use p += stupid_insert(p, ...),
1869 // and be careful to not use pointers into potentially freed text[]!
1870 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1873 bias = text_hole_make(p, 1);
1876 //file_modified++; - done by text_hole_make()
1880 static int find_range(char **start, char **stop, char c)
1882 char *save_dot, *p, *q, *t;
1883 int cnt, multiline = 0;
1888 if (strchr("cdy><", c)) {
1889 // these cmds operate on whole lines
1890 p = q = begin_line(p);
1891 for (cnt = 1; cnt < cmdcnt; cnt++) {
1895 } else if (strchr("^%$0bBeEfth\b\177", c)) {
1896 // These cmds operate on char positions
1897 do_cmd(c); // execute movement cmd
1899 } else if (strchr("wW", c)) {
1900 do_cmd(c); // execute movement cmd
1901 // if we are at the next word's first char
1902 // step back one char
1903 // but check the possibilities when it is true
1904 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1905 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1906 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1907 dot--; // move back off of next word
1908 if (dot > text && *dot == '\n')
1909 dot--; // stay off NL
1911 } else if (strchr("H-k{", c)) {
1912 // these operate on multi-lines backwards
1913 q = end_line(dot); // find NL
1914 do_cmd(c); // execute movement cmd
1917 } else if (strchr("L+j}\r\n", c)) {
1918 // these operate on multi-lines forwards
1919 p = begin_line(dot);
1920 do_cmd(c); // execute movement cmd
1921 dot_end(); // find NL
1924 // nothing -- this causes any other values of c to
1925 // represent the one-character range under the
1926 // cursor. this is correct for ' ' and 'l', but
1927 // perhaps no others.
1936 // backward char movements don't include start position
1937 if (q > p && strchr("^0bBh\b\177", c)) q--;
1940 for (t = p; t <= q; t++) {
1953 static int st_test(char *p, int type, int dir, char *tested)
1963 if (type == S_BEFORE_WS) {
1965 test = (!isspace(c) || c == '\n');
1967 if (type == S_TO_WS) {
1969 test = (!isspace(c) || c == '\n');
1971 if (type == S_OVER_WS) {
1975 if (type == S_END_PUNCT) {
1979 if (type == S_END_ALNUM) {
1981 test = (isalnum(c) || c == '_');
1987 static char *skip_thing(char *p, int linecnt, int dir, int type)
1991 while (st_test(p, type, dir, &c)) {
1992 // make sure we limit search to correct number of lines
1993 if (c == '\n' && --linecnt < 1)
1995 if (dir >= 0 && p >= end - 1)
1997 if (dir < 0 && p <= text)
1999 p += dir; // move to next char
2004 // find matching char of pair () [] {}
2005 static char *find_pair(char *p, const char c)
2012 dir = 1; // assume forward
2014 case '(': match = ')'; break;
2015 case '[': match = ']'; break;
2016 case '{': match = '}'; break;
2017 case ')': match = '('; dir = -1; break;
2018 case ']': match = '['; dir = -1; break;
2019 case '}': match = '{'; dir = -1; break;
2021 for (q = p + dir; text <= q && q < end; q += dir) {
2022 // look for match, count levels of pairs (( ))
2024 level++; // increase pair levels
2026 level--; // reduce pair level
2028 break; // found matching pair
2031 q = NULL; // indicate no match
2035 #if ENABLE_FEATURE_VI_SETOPTS
2036 // show the matching char of a pair, () [] {}
2037 static void showmatching(char *p)
2041 // we found half of a pair
2042 q = find_pair(p, *p); // get loc of matching char
2044 indicate_error('3'); // no matching char
2046 // "q" now points to matching pair
2047 save_dot = dot; // remember where we are
2048 dot = q; // go to new loc
2049 refresh(FALSE); // let the user see it
2050 mysleep(40); // give user some time
2051 dot = save_dot; // go back to old loc
2055 #endif /* FEATURE_VI_SETOPTS */
2057 // open a hole in text[]
2058 // might reallocate text[]! use p += text_hole_make(p, ...),
2059 // and be careful to not use pointers into potentially freed text[]!
2060 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2066 end += size; // adjust the new END
2067 if (end >= (text + text_size)) {
2069 text_size += end - (text + text_size) + 10240;
2070 new_text = xrealloc(text, text_size);
2071 bias = (new_text - text);
2072 screenbegin += bias;
2078 memmove(p + size, p, end - size - p);
2079 memset(p, ' ', size); // clear new hole
2084 // close a hole in text[]
2085 static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive
2090 // move forwards, from beginning
2094 if (q < p) { // they are backward- swap them
2098 hole_size = q - p + 1;
2100 if (src < text || src > end)
2102 if (dest < text || dest >= end)
2105 goto thd_atend; // just delete the end of the buffer
2106 memmove(dest, src, cnt);
2108 end = end - hole_size; // adjust the new END
2110 dest = end - 1; // make sure dest in below end-1
2112 dest = end = text; // keep pointers valid
2118 // copy text into register, then delete text.
2119 // if dist <= 0, do not include, or go past, a NewLine
2121 static char *yank_delete(char *start, char *stop, int dist, int yf)
2125 // make sure start <= stop
2127 // they are backwards, reverse them
2133 // we cannot cross NL boundaries
2137 // dont go past a NewLine
2138 for (; p + 1 <= stop; p++) {
2140 stop = p; // "stop" just before NewLine
2146 #if ENABLE_FEATURE_VI_YANKMARK
2147 text_yank(start, stop, YDreg);
2149 if (yf == YANKDEL) {
2150 p = text_hole_delete(start, stop);
2155 static void show_help(void)
2157 puts("These features are available:"
2158 #if ENABLE_FEATURE_VI_SEARCH
2159 "\n\tPattern searches with / and ?"
2161 #if ENABLE_FEATURE_VI_DOT_CMD
2162 "\n\tLast command repeat with \'.\'"
2164 #if ENABLE_FEATURE_VI_YANKMARK
2165 "\n\tLine marking with 'x"
2166 "\n\tNamed buffers with \"x"
2168 #if ENABLE_FEATURE_VI_READONLY
2169 //not implemented: "\n\tReadonly if vi is called as \"view\""
2170 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2172 #if ENABLE_FEATURE_VI_SET
2173 "\n\tSome colon mode commands with \':\'"
2175 #if ENABLE_FEATURE_VI_SETOPTS
2176 "\n\tSettable options with \":set\""
2178 #if ENABLE_FEATURE_VI_USE_SIGNALS
2179 "\n\tSignal catching- ^C"
2180 "\n\tJob suspend and resume with ^Z"
2182 #if ENABLE_FEATURE_VI_WIN_RESIZE
2183 "\n\tAdapt to window re-sizes"
2188 #if ENABLE_FEATURE_VI_DOT_CMD
2189 static void start_new_cmd_q(char c)
2191 // get buffer for new cmd
2192 // if there is a current cmd count put it in the buffer first
2194 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2195 } else { // just save char c onto queue
2196 last_modifying_cmd[0] = c;
2202 static void end_cmd_q(void)
2204 #if ENABLE_FEATURE_VI_YANKMARK
2205 YDreg = 26; // go back to default Yank/Delete reg
2209 #endif /* FEATURE_VI_DOT_CMD */
2211 #if ENABLE_FEATURE_VI_YANKMARK \
2212 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2213 || ENABLE_FEATURE_VI_CRASHME
2214 // might reallocate text[]! use p += string_insert(p, ...),
2215 // and be careful to not use pointers into potentially freed text[]!
2216 static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p'
2222 bias = text_hole_make(p, i);
2225 #if ENABLE_FEATURE_VI_YANKMARK
2228 for (cnt = 0; *s != '\0'; s++) {
2232 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2239 #if ENABLE_FEATURE_VI_YANKMARK
2240 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2243 if (cnt < 0) { // they are backwards- reverse them
2247 free(reg[dest]); // if already a yank register, free it
2248 reg[dest] = xstrndup(p, cnt + 1);
2252 static char what_reg(void)
2256 c = 'D'; // default to D-reg
2257 if (0 <= YDreg && YDreg <= 25)
2258 c = 'a' + (char) YDreg;
2266 static void check_context(char cmd)
2268 // A context is defined to be "modifying text"
2269 // Any modifying command establishes a new context.
2271 if (dot < context_start || dot > context_end) {
2272 if (strchr(modifying_cmds, cmd) != NULL) {
2273 // we are trying to modify text[]- make this the current context
2274 mark[27] = mark[26]; // move cur to prev
2275 mark[26] = dot; // move local to cur
2276 context_start = prev_line(prev_line(dot));
2277 context_end = next_line(next_line(dot));
2278 //loiter= start_loiter= now;
2283 static char *swap_context(char *p) // goto new context for '' command make this the current context
2287 // the current context is in mark[26]
2288 // the previous context is in mark[27]
2289 // only swap context if other context is valid
2290 if (text <= mark[27] && mark[27] <= end - 1) {
2292 mark[27] = mark[26];
2294 p = mark[26]; // where we are going- previous context
2295 context_start = prev_line(prev_line(prev_line(p)));
2296 context_end = next_line(next_line(next_line(p)));
2300 #endif /* FEATURE_VI_YANKMARK */
2302 //----- Set terminal attributes --------------------------------
2303 static void rawmode(void)
2305 tcgetattr(0, &term_orig);
2306 term_vi = term_orig;
2307 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2308 term_vi.c_iflag &= (~IXON & ~ICRNL);
2309 term_vi.c_oflag &= (~ONLCR);
2310 term_vi.c_cc[VMIN] = 1;
2311 term_vi.c_cc[VTIME] = 0;
2312 erase_char = term_vi.c_cc[VERASE];
2313 tcsetattr_stdin_TCSANOW(&term_vi);
2316 static void cookmode(void)
2319 tcsetattr_stdin_TCSANOW(&term_orig);
2322 #if ENABLE_FEATURE_VI_USE_SIGNALS
2323 //----- Come here when we get a window resize signal ---------
2324 static void winch_sig(int sig UNUSED_PARAM)
2326 int save_errno = errno;
2327 // FIXME: do it in main loop!!!
2328 signal(SIGWINCH, winch_sig);
2329 query_screen_dimensions();
2330 new_screen(rows, columns); // get memory for virtual screen
2331 redraw(TRUE); // re-draw the screen
2335 //----- Come here when we get a continue signal -------------------
2336 static void cont_sig(int sig UNUSED_PARAM)
2338 int save_errno = errno;
2339 rawmode(); // terminal to "raw"
2340 last_status_cksum = 0; // force status update
2341 redraw(TRUE); // re-draw the screen
2343 signal(SIGTSTP, suspend_sig);
2344 signal(SIGCONT, SIG_DFL);
2345 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2349 //----- Come here when we get a Suspend signal -------------------
2350 static void suspend_sig(int sig UNUSED_PARAM)
2352 int save_errno = errno;
2353 go_bottom_and_clear_to_eol();
2354 cookmode(); // terminal to "cooked"
2356 signal(SIGCONT, cont_sig);
2357 signal(SIGTSTP, SIG_DFL);
2358 kill(my_pid, SIGTSTP);
2362 //----- Come here when we get a signal ---------------------------
2363 static void catch_sig(int sig)
2365 signal(SIGINT, catch_sig);
2366 siglongjmp(restart, sig);
2368 #endif /* FEATURE_VI_USE_SIGNALS */
2370 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2372 struct pollfd pfd[1];
2374 pfd[0].fd = STDIN_FILENO;
2375 pfd[0].events = POLLIN;
2376 return safe_poll(pfd, 1, hund*10) > 0;
2379 //----- IO Routines --------------------------------------------
2380 static int readit(void) // read (maybe cursor) key from stdin
2385 c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2386 if (c == -1) { // EOF/error
2387 go_bottom_and_clear_to_eol();
2388 cookmode(); // terminal to "cooked"
2389 bb_error_msg_and_die("can't read user input");
2394 //----- IO Routines --------------------------------------------
2395 static int get_one_char(void)
2399 #if ENABLE_FEATURE_VI_DOT_CMD
2401 // we are not adding to the q.
2402 // but, we may be reading from a q
2404 // there is no current q, read from STDIN
2405 c = readit(); // get the users input
2407 // there is a queue to get chars from first
2408 // careful with correct sign expansion!
2409 c = (unsigned char)*ioq++;
2411 // the end of the q, read from STDIN
2413 ioq_start = ioq = 0;
2414 c = readit(); // get the users input
2418 // adding STDIN chars to q
2419 c = readit(); // get the users input
2420 if (lmc_len >= MAX_INPUT_LEN - 1) {
2421 status_line_bold("last_modifying_cmd overrun");
2423 // add new char to q
2424 last_modifying_cmd[lmc_len++] = c;
2428 c = readit(); // get the users input
2429 #endif /* FEATURE_VI_DOT_CMD */
2433 // Get input line (uses "status line" area)
2434 static char *get_input_line(const char *prompt)
2436 // char [MAX_INPUT_LEN]
2437 #define buf get_input_line__buf
2442 strcpy(buf, prompt);
2443 last_status_cksum = 0; // force status update
2444 go_bottom_and_clear_to_eol();
2445 write1(prompt); // write out the :, /, or ? prompt
2448 while (i < MAX_INPUT_LEN) {
2450 if (c == '\n' || c == '\r' || c == 27)
2451 break; // this is end of input
2452 if (c == erase_char || c == 8 || c == 127) {
2453 // user wants to erase prev char
2455 write1("\b \b"); // erase char on screen
2456 if (i <= 0) // user backs up before b-o-l, exit
2458 } else if (c > 0 && c < 256) { // exclude Unicode
2459 // (TODO: need to handle Unicode)
2470 static int file_size(const char *fn) // what is the byte size of "fn"
2476 if (fn && stat(fn, &st_buf) == 0) // see if file exists
2477 cnt = (int) st_buf.st_size;
2481 // might reallocate text[]!
2482 static int file_insert(const char *fn, char *p, int update_ro_status)
2486 struct stat statbuf;
2489 if (stat(fn, &statbuf) < 0) {
2490 status_line_bold("\"%s\" %s", fn, strerror(errno));
2493 if (!S_ISREG(statbuf.st_mode)) {
2494 // This is not a regular file
2495 status_line_bold("\"%s\" Not a regular file", fn);
2498 if (p < text || p > end) {
2499 status_line_bold("Trying to insert file outside of memory");
2503 // read file to buffer
2504 fd = open(fn, O_RDONLY);
2506 status_line_bold("\"%s\" %s", fn, strerror(errno));
2509 size = statbuf.st_size;
2510 p += text_hole_make(p, size);
2511 cnt = safe_read(fd, p, size);
2513 status_line_bold("\"%s\" %s", fn, strerror(errno));
2514 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2515 } else if (cnt < size) {
2516 // There was a partial read, shrink unused space text[]
2517 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2518 status_line_bold("can't read all of file \"%s\"", fn);
2524 #if ENABLE_FEATURE_VI_READONLY
2525 if (update_ro_status
2526 && ((access(fn, W_OK) < 0) ||
2527 /* root will always have access()
2528 * so we check fileperms too */
2529 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2532 SET_READONLY_FILE(readonly_mode);
2538 static int file_write(char *fn, char *first, char *last)
2540 int fd, cnt, charcnt;
2543 status_line_bold("No current filename");
2546 /* By popular request we do not open file with O_TRUNC,
2547 * but instead ftruncate() it _after_ successful write.
2548 * Might reduce amount of data lost on power fail etc.
2550 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2553 cnt = last - first + 1;
2554 charcnt = full_write(fd, first, cnt);
2555 ftruncate(fd, charcnt);
2556 if (charcnt == cnt) {
2558 //file_modified = FALSE;
2566 //----- Terminal Drawing ---------------------------------------
2567 // The terminal is made up of 'rows' line of 'columns' columns.
2568 // classically this would be 24 x 80.
2569 // screen coordinates
2575 // 23,0 ... 23,79 <- status line
2577 //----- Move the cursor to row x col (count from 0, not 1) -------
2578 static void place_cursor(int row, int col, int optimize)
2580 char cm1[sizeof(CMrc) + sizeof(int)*3 * 2];
2581 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2583 SZ_UP = sizeof(CMup),
2584 SZ_DN = sizeof(CMdown),
2585 SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN,
2587 char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size
2591 if (row < 0) row = 0;
2592 if (row >= rows) row = rows - 1;
2593 if (col < 0) col = 0;
2594 if (col >= columns) col = columns - 1;
2596 //----- 1. Try the standard terminal ESC sequence
2597 sprintf(cm1, CMrc, row + 1, col + 1);
2600 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
2601 if (optimize && col < 16) {
2603 int Rrow = last_row;
2604 int diff = Rrow - row;
2606 if (diff < -5 || diff > 5)
2609 //----- find the minimum # of chars to move cursor -------------
2610 //----- 2. Try moving with discreet chars (Newline, [back]space, ...)
2613 // move to the correct row
2614 while (row < Rrow) {
2615 // the cursor has to move up
2619 while (row > Rrow) {
2620 // the cursor has to move down
2621 strcat(cm2, CMdown);
2625 // now move to the correct column
2626 strcat(cm2, "\r"); // start at col 0
2627 // just send out orignal source char to get to correct place
2628 screenp = &screen[row * columns]; // start of screen line
2629 strncat(cm2, screenp, col);
2631 // pick the shortest cursor motion to send out
2632 if (strlen(cm2) < strlen(cm)) {
2638 #endif /* FEATURE_VI_OPTIMIZE_CURSOR */
2642 //----- Erase from cursor to end of line -----------------------
2643 static void clear_to_eol(void)
2645 write1(Ceol); // Erase from cursor to end of line
2648 static void go_bottom_and_clear_to_eol(void)
2650 place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
2651 clear_to_eol(); // erase to end of line
2654 //----- Erase from cursor to end of screen -----------------------
2655 static void clear_to_eos(void)
2657 write1(Ceos); // Erase from cursor to end of screen
2660 //----- Start standout mode ------------------------------------
2661 static void standout_start(void) // send "start reverse video" sequence
2663 write1(SOs); // Start reverse video mode
2666 //----- End standout mode --------------------------------------
2667 static void standout_end(void) // send "end reverse video" sequence
2669 write1(SOn); // End reverse video mode
2672 //----- Flash the screen --------------------------------------
2673 static void flash(int h)
2675 standout_start(); // send "start reverse video" sequence
2678 standout_end(); // send "end reverse video" sequence
2682 static void Indicate_Error(void)
2684 #if ENABLE_FEATURE_VI_CRASHME
2686 return; // generate a random command
2689 write1(bell); // send out a bell character
2695 //----- Screen[] Routines --------------------------------------
2696 //----- Erase the Screen[] memory ------------------------------
2697 static void screen_erase(void)
2699 memset(screen, ' ', screensize); // clear new screen
2702 static int bufsum(char *buf, int count)
2705 char *e = buf + count;
2708 sum += (unsigned char) *buf++;
2712 //----- Draw the status line at bottom of the screen -------------
2713 static void show_status_line(void)
2715 int cnt = 0, cksum = 0;
2717 // either we already have an error or status message, or we
2719 if (!have_status_msg) {
2720 cnt = format_edit_status();
2721 cksum = bufsum(status_buffer, cnt);
2723 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2724 last_status_cksum = cksum; // remember if we have seen this line
2725 go_bottom_and_clear_to_eol();
2726 write1(status_buffer);
2727 if (have_status_msg) {
2728 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2730 have_status_msg = 0;
2733 have_status_msg = 0;
2735 place_cursor(crow, ccol, FALSE); // put cursor back in correct place
2740 //----- format the status buffer, the bottom line of screen ------
2741 // format status buffer, with STANDOUT mode
2742 static void status_line_bold(const char *format, ...)
2746 va_start(args, format);
2747 strcpy(status_buffer, SOs); // Terminal standout mode on
2748 vsprintf(status_buffer + sizeof(SOs)-1, format, args);
2749 strcat(status_buffer, SOn); // Terminal standout mode off
2752 have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2;
2755 // format status buffer
2756 static void status_line(const char *format, ...)
2760 va_start(args, format);
2761 vsprintf(status_buffer, format, args);
2764 have_status_msg = 1;
2767 // copy s to buf, convert unprintable
2768 static void print_literal(char *buf, const char *s)
2782 c_is_no_print = (c & 0x80) && !Isprint(c);
2783 if (c_is_no_print) {
2788 if (c < ' ' || c == 0x7f) {
2790 c |= '@'; /* 0x40 */
2796 if (c_is_no_print) {
2804 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
2809 static void not_implemented(const char *s)
2811 char buf[MAX_INPUT_LEN];
2813 print_literal(buf, s);
2814 status_line_bold("\'%s\' is not implemented", buf);
2817 // show file status on status line
2818 static int format_edit_status(void)
2820 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2822 #define tot format_edit_status__tot
2824 int cur, percent, ret, trunc_at;
2826 // file_modified is now a counter rather than a flag. this
2827 // helps reduce the amount of line counting we need to do.
2828 // (this will cause a mis-reporting of modified status
2829 // once every MAXINT editing operations.)
2831 // it would be nice to do a similar optimization here -- if
2832 // we haven't done a motion that could have changed which line
2833 // we're on, then we shouldn't have to do this count_lines()
2834 cur = count_lines(text, dot);
2836 // reduce counting -- the total lines can't have
2837 // changed if we haven't done any edits.
2838 if (file_modified != last_file_modified) {
2839 tot = cur + count_lines(dot, end - 1) - 1;
2840 last_file_modified = file_modified;
2843 // current line percent
2844 // ------------- ~~ ----------
2847 percent = (100 * cur) / tot;
2853 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2854 columns : STATUS_BUFFER_LEN-1;
2856 ret = snprintf(status_buffer, trunc_at+1,
2857 #if ENABLE_FEATURE_VI_READONLY
2858 "%c %s%s%s %d/%d %d%%",
2860 "%c %s%s %d/%d %d%%",
2862 cmd_mode_indicator[cmd_mode & 3],
2863 (current_filename != NULL ? current_filename : "No file"),
2864 #if ENABLE_FEATURE_VI_READONLY
2865 (readonly_mode ? " [Readonly]" : ""),
2867 (file_modified ? " [Modified]" : ""),
2870 if (ret >= 0 && ret < trunc_at)
2871 return ret; /* it all fit */
2873 return trunc_at; /* had to truncate */
2877 //----- Force refresh of all Lines -----------------------------
2878 static void redraw(int full_screen)
2880 place_cursor(0, 0, FALSE); // put cursor in correct place
2881 clear_to_eos(); // tell terminal to erase display
2882 screen_erase(); // erase the internal screen buffer
2883 last_status_cksum = 0; // force status update
2884 refresh(full_screen); // this will redraw the entire display
2888 //----- Format a text[] line into a buffer ---------------------
2889 static char* format_line(char *src /*, int li*/)
2894 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2896 c = '~'; // char in col 0 in non-existent lines is '~'
2898 while (co < columns + tabstop) {
2899 // have we gone past the end?
2904 if ((c & 0x80) && !Isprint(c)) {
2907 if (c < ' ' || c == 0x7f) {
2911 while ((co % tabstop) != (tabstop - 1)) {
2919 c += '@'; // Ctrl-X -> 'X'
2924 // discard scrolled-off-to-the-left portion,
2925 // in tabstop-sized pieces
2926 if (ofs >= tabstop && co >= tabstop) {
2927 memmove(dest, dest + tabstop, co);
2934 // check "short line, gigantic offset" case
2937 // discard last scrolled off part
2940 // fill the rest with spaces
2942 memset(&dest[co], ' ', columns - co);
2946 //----- Refresh the changed screen lines -----------------------
2947 // Copy the source line from text[] into the buffer and note
2948 // if the current screenline is different from the new buffer.
2949 // If they differ then that line needs redrawing on the terminal.
2951 static void refresh(int full_screen)
2953 #define old_offset refresh__old_offset
2956 char *tp, *sp; // pointer into text[] and screen[]
2958 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
2959 unsigned c = columns, r = rows;
2960 query_screen_dimensions();
2961 full_screen |= (c - columns) | (r - rows);
2963 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2964 tp = screenbegin; // index into text[] of top line
2966 // compare text[] to screen[] and mark screen[] lines that need updating
2967 for (li = 0; li < rows - 1; li++) {
2968 int cs, ce; // column start & end
2970 // format current text line
2971 out_buf = format_line(tp /*, li*/);
2973 // skip to the end of the current text[] line
2975 char *t = memchr(tp, '\n', end - tp);
2976 if (!t) t = end - 1;
2980 // see if there are any changes between vitual screen and out_buf
2981 changed = FALSE; // assume no change
2984 sp = &screen[li * columns]; // start of screen line
2986 // force re-draw of every single column from 0 - columns-1
2989 // compare newly formatted buffer with virtual screen
2990 // look forward for first difference between buf and screen
2991 for (; cs <= ce; cs++) {
2992 if (out_buf[cs] != sp[cs]) {
2993 changed = TRUE; // mark for redraw
2998 // look backward for last difference between out_buf and screen
2999 for (; ce >= cs; ce--) {
3000 if (out_buf[ce] != sp[ce]) {
3001 changed = TRUE; // mark for redraw
3005 // now, cs is index of first diff, and ce is index of last diff
3007 // if horz offset has changed, force a redraw
3008 if (offset != old_offset) {
3013 // make a sanity check of columns indexes
3015 if (ce > columns - 1) ce = columns - 1;
3016 if (cs > ce) { cs = 0; ce = columns - 1; }
3017 // is there a change between vitual screen and out_buf
3019 // copy changed part of buffer to virtual screen
3020 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3022 // move cursor to column of first change
3023 //if (offset != old_offset) {
3024 // // place_cursor is still too stupid
3025 // // to handle offsets correctly
3026 // place_cursor(li, cs, FALSE);
3028 place_cursor(li, cs, TRUE);
3031 // write line out to terminal
3032 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3036 place_cursor(crow, ccol, TRUE);
3038 old_offset = offset;
3042 //---------------------------------------------------------------------
3043 //----- the Ascii Chart -----------------------------------------------
3045 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3046 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3047 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3048 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3049 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3050 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3051 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3052 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3053 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3054 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3055 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3056 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3057 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3058 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3059 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3060 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3061 //---------------------------------------------------------------------
3063 //----- Execute a Vi Command -----------------------------------
3064 static void do_cmd(int c)
3066 char *p, *q, *save_dot;
3072 // c1 = c; // quiet the compiler
3073 // cnt = yf = 0; // quiet the compiler
3074 // p = q = save_dot = buf; // quiet the compiler
3075 memset(buf, '\0', sizeof(buf));
3079 /* if this is a cursor key, skip these checks */
3087 case KEYCODE_PAGEUP:
3088 case KEYCODE_PAGEDOWN:
3089 case KEYCODE_DELETE:
3093 if (cmd_mode == 2) {
3094 // flip-flop Insert/Replace mode
3095 if (c == KEYCODE_INSERT)
3097 // we are 'R'eplacing the current *dot with new char
3099 // don't Replace past E-o-l
3100 cmd_mode = 1; // convert to insert
3102 if (1 <= c || Isprint(c)) {
3104 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3105 dot = char_insert(dot, c); // insert new char
3110 if (cmd_mode == 1) {
3111 // hitting "Insert" twice means "R" replace mode
3112 if (c == KEYCODE_INSERT) goto dc5;
3113 // insert the char c at "dot"
3114 if (1 <= c || Isprint(c)) {
3115 dot = char_insert(dot, c);
3130 #if ENABLE_FEATURE_VI_CRASHME
3131 case 0x14: // dc4 ctrl-T
3132 crashme = (crashme == 0) ? 1 : 0;
3161 //case 'u': // u- FIXME- there is no undo
3163 default: // unrecognized command
3166 not_implemented(buf);
3167 end_cmd_q(); // stop adding to q
3168 case 0x00: // nul- ignore
3170 case 2: // ctrl-B scroll up full screen
3171 case KEYCODE_PAGEUP: // Cursor Key Page Up
3172 dot_scroll(rows - 2, -1);
3174 case 4: // ctrl-D scroll down half screen
3175 dot_scroll((rows - 2) / 2, 1);
3177 case 5: // ctrl-E scroll down one line
3180 case 6: // ctrl-F scroll down full screen
3181 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3182 dot_scroll(rows - 2, 1);
3184 case 7: // ctrl-G show current status
3185 last_status_cksum = 0; // force status update
3187 case 'h': // h- move left
3188 case KEYCODE_LEFT: // cursor key Left
3189 case 8: // ctrl-H- move left (This may be ERASE char)
3190 case 0x7f: // DEL- move left (This may be ERASE char)
3193 } while (--cmdcnt > 0);
3195 case 10: // Newline ^J
3196 case 'j': // j- goto next line, same col
3197 case KEYCODE_DOWN: // cursor key Down
3199 dot_next(); // go to next B-o-l
3200 // try stay in same col
3201 dot = move_to_col(dot, ccol + offset);
3202 } while (--cmdcnt > 0);
3204 case 12: // ctrl-L force redraw whole screen
3205 case 18: // ctrl-R force redraw
3206 place_cursor(0, 0, FALSE); // put cursor in correct place
3207 clear_to_eos(); // tel terminal to erase display
3209 screen_erase(); // erase the internal screen buffer
3210 last_status_cksum = 0; // force status update
3211 refresh(TRUE); // this will redraw the entire display
3213 case 13: // Carriage Return ^M
3214 case '+': // +- goto next line
3218 } while (--cmdcnt > 0);
3220 case 21: // ctrl-U scroll up half screen
3221 dot_scroll((rows - 2) / 2, -1);
3223 case 25: // ctrl-Y scroll up one line
3229 cmd_mode = 0; // stop insrting
3231 last_status_cksum = 0; // force status update
3233 case ' ': // move right
3234 case 'l': // move right
3235 case KEYCODE_RIGHT: // Cursor Key Right
3238 } while (--cmdcnt > 0);
3240 #if ENABLE_FEATURE_VI_YANKMARK
3241 case '"': // "- name a register to use for Delete/Yank
3242 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3243 if ((unsigned)c1 <= 25) { // a-z?
3249 case '\'': // '- goto a specific mark
3250 c1 = (get_one_char() | 0x20) - 'a';
3251 if ((unsigned)c1 <= 25) { // a-z?
3254 if (text <= q && q < end) {
3256 dot_begin(); // go to B-o-l
3259 } else if (c1 == '\'') { // goto previous context
3260 dot = swap_context(dot); // swap current and previous context
3261 dot_begin(); // go to B-o-l
3267 case 'm': // m- Mark a line
3268 // this is really stupid. If there are any inserts or deletes
3269 // between text[0] and dot then this mark will not point to the
3270 // correct location! It could be off by many lines!
3271 // Well..., at least its quick and dirty.
3272 c1 = (get_one_char() | 0x20) - 'a';
3273 if ((unsigned)c1 <= 25) { // a-z?
3274 // remember the line
3280 case 'P': // P- Put register before
3281 case 'p': // p- put register after
3284 status_line_bold("Nothing in register %c", what_reg());
3287 // are we putting whole lines or strings
3288 if (strchr(p, '\n') != NULL) {
3290 dot_begin(); // putting lines- Put above
3293 // are we putting after very last line?
3294 if (end_line(dot) == (end - 1)) {
3295 dot = end; // force dot to end of text[]
3297 dot_next(); // next line, then put before
3302 dot_right(); // move to right, can move to NL
3304 string_insert(dot, p); // insert the string
3305 end_cmd_q(); // stop adding to q
3307 case 'U': // U- Undo; replace current line with original version
3308 if (reg[Ureg] != 0) {
3309 p = begin_line(dot);
3311 p = text_hole_delete(p, q); // delete cur line
3312 p += string_insert(p, reg[Ureg]); // insert orig line
3317 #endif /* FEATURE_VI_YANKMARK */
3318 case '$': // $- goto end of line
3319 case KEYCODE_END: // Cursor Key End
3321 dot = end_line(dot);
3327 case '%': // %- find matching char of pair () [] {}
3328 for (q = dot; q < end && *q != '\n'; q++) {
3329 if (strchr("()[]{}", *q) != NULL) {
3330 // we found half of a pair
3331 p = find_pair(q, *q);
3343 case 'f': // f- forward to a user specified char
3344 last_forward_char = get_one_char(); // get the search char
3346 // dont separate these two commands. 'f' depends on ';'
3348 //**** fall through to ... ';'
3349 case ';': // ;- look at rest of line for last forward char
3351 if (last_forward_char == 0)
3354 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3357 if (*q == last_forward_char)
3359 } while (--cmdcnt > 0);
3361 case ',': // repeat latest 'f' in opposite direction
3362 if (last_forward_char == 0)
3366 while (q >= text && *q != '\n' && *q != last_forward_char) {
3369 if (q >= text && *q == last_forward_char)
3371 } while (--cmdcnt > 0);
3374 case '-': // -- goto prev line
3378 } while (--cmdcnt > 0);
3380 #if ENABLE_FEATURE_VI_DOT_CMD
3381 case '.': // .- repeat the last modifying command
3382 // Stuff the last_modifying_cmd back into stdin
3383 // and let it be re-executed.
3385 last_modifying_cmd[lmc_len] = 0;
3386 ioq = ioq_start = xstrdup(last_modifying_cmd);
3390 #if ENABLE_FEATURE_VI_SEARCH
3391 case '?': // /- search for a pattern
3392 case '/': // /- search for a pattern
3395 q = get_input_line(buf); // get input line- use "status line"
3396 if (q[0] && !q[1]) {
3397 if (last_search_pattern[0])
3398 last_search_pattern[0] = c;
3399 goto dc3; // if no pat re-use old pat
3401 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3402 // there is a new pat
3403 free(last_search_pattern);
3404 last_search_pattern = xstrdup(q);
3405 goto dc3; // now find the pattern
3407 // user changed mind and erased the "/"- do nothing
3409 case 'N': // N- backward search for last pattern
3410 dir = BACK; // assume BACKWARD search
3412 if (last_search_pattern[0] == '?') {
3416 goto dc4; // now search for pattern
3418 case 'n': // n- repeat search for last pattern
3419 // search rest of text[] starting at next char
3420 // if search fails return orignal "p" not the "p+1" address
3424 dir = FORWARD; // assume FORWARD search
3426 if (last_search_pattern[0] == '?') {
3431 q = char_search(p, last_search_pattern + 1, dir, FULL);
3433 dot = q; // good search, update "dot"
3437 // no pattern found between "dot" and "end"- continue at top
3442 q = char_search(p, last_search_pattern + 1, dir, FULL);
3443 if (q != NULL) { // found something
3444 dot = q; // found new pattern- goto it
3445 msg = "search hit BOTTOM, continuing at TOP";
3447 msg = "search hit TOP, continuing at BOTTOM";
3450 msg = "Pattern not found";
3454 status_line_bold("%s", msg);
3455 } while (--cmdcnt > 0);
3457 case '{': // {- move backward paragraph
3458 q = char_search(dot, "\n\n", BACK, FULL);
3459 if (q != NULL) { // found blank line
3460 dot = next_line(q); // move to next blank line
3463 case '}': // }- move forward paragraph
3464 q = char_search(dot, "\n\n", FORWARD, FULL);
3465 if (q != NULL) { // found blank line
3466 dot = next_line(q); // move to next blank line
3469 #endif /* FEATURE_VI_SEARCH */
3470 case '0': // 0- goto begining of line
3480 if (c == '0' && cmdcnt < 1) {
3481 dot_begin(); // this was a standalone zero
3483 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3486 case ':': // :- the colon mode commands
3487 p = get_input_line(":"); // get input line- use "status line"
3488 #if ENABLE_FEATURE_VI_COLON
3489 colon(p); // execute the command
3492 p++; // move past the ':'
3496 if (strncmp(p, "quit", cnt) == 0
3497 || strncmp(p, "q!", cnt) == 0 // delete lines
3499 if (file_modified && p[1] != '!') {
3500 status_line_bold("No write since last change (:quit! overrides)");
3504 } else if (strncmp(p, "write", cnt) == 0
3505 || strncmp(p, "wq", cnt) == 0
3506 || strncmp(p, "wn", cnt) == 0
3507 || (p[0] == 'x' && !p[1])
3509 cnt = file_write(current_filename, text, end - 1);
3512 status_line_bold("Write error: %s", strerror(errno));
3515 last_file_modified = -1;
3516 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3517 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3518 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3523 } else if (strncmp(p, "file", cnt) == 0) {
3524 last_status_cksum = 0; // force status update
3525 } else if (sscanf(p, "%d", &j) > 0) {
3526 dot = find_line(j); // go to line # j
3528 } else { // unrecognized cmd
3531 #endif /* !FEATURE_VI_COLON */
3533 case '<': // <- Left shift something
3534 case '>': // >- Right shift something
3535 cnt = count_lines(text, dot); // remember what line we are on
3536 c1 = get_one_char(); // get the type of thing to delete
3537 find_range(&p, &q, c1);
3538 yank_delete(p, q, 1, YANKONLY); // save copy before change
3541 i = count_lines(p, q); // # of lines we are shifting
3542 for ( ; i > 0; i--, p = next_line(p)) {
3544 // shift left- remove tab or 8 spaces
3546 // shrink buffer 1 char
3547 text_hole_delete(p, p);
3548 } else if (*p == ' ') {
3549 // we should be calculating columns, not just SPACE
3550 for (j = 0; *p == ' ' && j < tabstop; j++) {
3551 text_hole_delete(p, p);
3554 } else if (c == '>') {
3555 // shift right -- add tab or 8 spaces
3556 char_insert(p, '\t');
3559 dot = find_line(cnt); // what line were we on
3561 end_cmd_q(); // stop adding to q
3563 case 'A': // A- append at e-o-l
3564 dot_end(); // go to e-o-l
3565 //**** fall through to ... 'a'
3566 case 'a': // a- append after current char
3571 case 'B': // B- back a blank-delimited Word
3572 case 'E': // E- end of a blank-delimited word
3573 case 'W': // W- forward a blank-delimited word
3578 if (c == 'W' || isspace(dot[dir])) {
3579 dot = skip_thing(dot, 1, dir, S_TO_WS);
3580 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3583 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3584 } while (--cmdcnt > 0);
3586 case 'C': // C- Change to e-o-l
3587 case 'D': // D- delete to e-o-l
3589 dot = dollar_line(dot); // move to before NL
3590 // copy text into a register and delete
3591 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3593 goto dc_i; // start inserting
3594 #if ENABLE_FEATURE_VI_DOT_CMD
3596 end_cmd_q(); // stop adding to q
3599 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3600 c1 = get_one_char();
3603 buf[1] = c1; // TODO: if Unicode?
3605 not_implemented(buf);
3611 case 'G': // G- goto to a line number (default= E-O-F)
3612 dot = end - 1; // assume E-O-F
3614 dot = find_line(cmdcnt); // what line is #cmdcnt
3618 case 'H': // H- goto top line on screen
3620 if (cmdcnt > (rows - 1)) {
3621 cmdcnt = (rows - 1);
3628 case 'I': // I- insert before first non-blank
3631 //**** fall through to ... 'i'
3632 case 'i': // i- insert before current char
3633 case KEYCODE_INSERT: // Cursor Key Insert
3635 cmd_mode = 1; // start inserting
3637 case 'J': // J- join current and next lines together
3639 dot_end(); // move to NL
3640 if (dot < end - 1) { // make sure not last char in text[]
3641 *dot++ = ' '; // replace NL with space
3643 while (isblank(*dot)) { // delete leading WS
3647 } while (--cmdcnt > 0);
3648 end_cmd_q(); // stop adding to q
3650 case 'L': // L- goto bottom line on screen
3652 if (cmdcnt > (rows - 1)) {
3653 cmdcnt = (rows - 1);
3661 case 'M': // M- goto middle line on screen
3663 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3664 dot = next_line(dot);
3666 case 'O': // O- open a empty line above
3668 p = begin_line(dot);
3669 if (p[-1] == '\n') {
3671 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3673 dot = char_insert(dot, '\n');
3676 dot = char_insert(dot, '\n'); // i\n ESC
3681 case 'R': // R- continuous Replace char
3685 case KEYCODE_DELETE:
3688 case 'X': // X- delete char before dot
3689 case 'x': // x- delete the current char
3690 case 's': // s- substitute the current char
3695 if (dot[dir] != '\n') {
3697 dot--; // delete prev char
3698 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3700 } while (--cmdcnt > 0);
3701 end_cmd_q(); // stop adding to q
3703 goto dc_i; // start inserting
3705 case 'Z': // Z- if modified, {write}; exit
3706 // ZZ means to save file (if necessary), then exit
3707 c1 = get_one_char();
3712 if (file_modified) {
3713 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3714 status_line_bold("\"%s\" File is read only", current_filename);
3717 cnt = file_write(current_filename, text, end - 1);
3720 status_line_bold("Write error: %s", strerror(errno));
3721 } else if (cnt == (end - 1 - text + 1)) {
3728 case '^': // ^- move to first non-blank on line
3732 case 'b': // b- back a word
3733 case 'e': // e- end of word
3738 if ((dot + dir) < text || (dot + dir) > end - 1)
3741 if (isspace(*dot)) {
3742 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3744 if (isalnum(*dot) || *dot == '_') {
3745 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3746 } else if (ispunct(*dot)) {
3747 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3749 } while (--cmdcnt > 0);
3751 case 'c': // c- change something
3752 case 'd': // d- delete something
3753 #if ENABLE_FEATURE_VI_YANKMARK
3754 case 'y': // y- yank something
3755 case 'Y': // Y- Yank a line
3758 int yf, ml, whole = 0;
3759 yf = YANKDEL; // assume either "c" or "d"
3760 #if ENABLE_FEATURE_VI_YANKMARK
3761 if (c == 'y' || c == 'Y')
3766 c1 = get_one_char(); // get the type of thing to delete
3767 // determine range, and whether it spans lines
3768 ml = find_range(&p, &q, c1);
3769 if (c1 == 27) { // ESC- user changed mind and wants out
3770 c = c1 = 27; // Escape- do nothing
3771 } else if (strchr("wW", c1)) {
3773 // don't include trailing WS as part of word
3774 while (isblank(*q)) {
3775 if (q <= text || q[-1] == '\n')
3780 dot = yank_delete(p, q, ml, yf); // delete word
3781 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3782 // partial line copy text into a register and delete
3783 dot = yank_delete(p, q, ml, yf); // delete word
3784 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3785 // whole line copy text into a register and delete
3786 dot = yank_delete(p, q, ml, yf); // delete lines
3789 // could not recognize object
3790 c = c1 = 27; // error-
3796 dot = char_insert(dot, '\n');
3797 // on the last line of file don't move to prev line
3798 if (whole && dot != (end-1)) {
3801 } else if (c == 'd') {
3807 // if CHANGING, not deleting, start inserting after the delete
3809 strcpy(buf, "Change");
3810 goto dc_i; // start inserting
3813 strcpy(buf, "Delete");
3815 #if ENABLE_FEATURE_VI_YANKMARK
3816 if (c == 'y' || c == 'Y') {
3817 strcpy(buf, "Yank");
3821 for (cnt = 0; p <= q; p++) {
3825 status_line("%s %d lines (%d chars) using [%c]",
3826 buf, cnt, strlen(reg[YDreg]), what_reg());
3828 end_cmd_q(); // stop adding to q
3832 case 'k': // k- goto prev line, same col
3833 case KEYCODE_UP: // cursor key Up
3836 dot = move_to_col(dot, ccol + offset); // try stay in same col
3837 } while (--cmdcnt > 0);
3839 case 'r': // r- replace the current char with user input
3840 c1 = get_one_char(); // get the replacement char
3845 end_cmd_q(); // stop adding to q
3847 case 't': // t- move to char prior to next x
3848 last_forward_char = get_one_char();
3850 if (*dot == last_forward_char)
3852 last_forward_char = 0;
3854 case 'w': // w- forward a word
3856 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3857 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3858 } else if (ispunct(*dot)) { // we are on PUNCT
3859 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3862 dot++; // move over word
3863 if (isspace(*dot)) {
3864 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3866 } while (--cmdcnt > 0);
3869 c1 = get_one_char(); // get the replacement char
3872 cnt = (rows - 2) / 2; // put dot at center
3874 cnt = rows - 2; // put dot at bottom
3875 screenbegin = begin_line(dot); // start dot at top
3876 dot_scroll(cnt, -1);
3878 case '|': // |- move to column "cmdcnt"
3879 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3881 case '~': // ~- flip the case of letters a-z -> A-Z
3883 if (islower(*dot)) {
3884 *dot = toupper(*dot);
3886 } else if (isupper(*dot)) {
3887 *dot = tolower(*dot);
3891 } while (--cmdcnt > 0);
3892 end_cmd_q(); // stop adding to q
3894 //----- The Cursor and Function Keys -----------------------------
3895 case KEYCODE_HOME: // Cursor Key Home
3898 // The Fn keys could point to do_macro which could translate them
3900 case KEYCODE_FUN1: // Function Key F1
3901 case KEYCODE_FUN2: // Function Key F2
3902 case KEYCODE_FUN3: // Function Key F3
3903 case KEYCODE_FUN4: // Function Key F4
3904 case KEYCODE_FUN5: // Function Key F5
3905 case KEYCODE_FUN6: // Function Key F6
3906 case KEYCODE_FUN7: // Function Key F7
3907 case KEYCODE_FUN8: // Function Key F8
3908 case KEYCODE_FUN9: // Function Key F9
3909 case KEYCODE_FUN10: // Function Key F10
3910 case KEYCODE_FUN11: // Function Key F11
3911 case KEYCODE_FUN12: // Function Key F12
3917 // if text[] just became empty, add back an empty line
3919 char_insert(text, '\n'); // start empty buf with dummy line
3922 // it is OK for dot to exactly equal to end, otherwise check dot validity
3924 dot = bound_dot(dot); // make sure "dot" is valid
3926 #if ENABLE_FEATURE_VI_YANKMARK
3927 check_context(c); // update the current context
3931 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3932 cnt = dot - begin_line(dot);
3933 // Try to stay off of the Newline
3934 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3938 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
3939 #if ENABLE_FEATURE_VI_CRASHME
3940 static int totalcmds = 0;
3941 static int Mp = 85; // Movement command Probability
3942 static int Np = 90; // Non-movement command Probability
3943 static int Dp = 96; // Delete command Probability
3944 static int Ip = 97; // Insert command Probability
3945 static int Yp = 98; // Yank command Probability
3946 static int Pp = 99; // Put command Probability
3947 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3948 static const char chars[20] = "\t012345 abcdABCD-=.$";
3949 static const char *const words[20] = {
3950 "this", "is", "a", "test",
3951 "broadcast", "the", "emergency", "of",
3952 "system", "quick", "brown", "fox",
3953 "jumped", "over", "lazy", "dogs",
3954 "back", "January", "Febuary", "March"
3956 static const char *const lines[20] = {
3957 "You should have received a copy of the GNU General Public License\n",
3958 "char c, cm, *cmd, *cmd1;\n",
3959 "generate a command by percentages\n",
3960 "Numbers may be typed as a prefix to some commands.\n",
3961 "Quit, discarding changes!\n",
3962 "Forced write, if permission originally not valid.\n",
3963 "In general, any ex or ed command (such as substitute or delete).\n",
3964 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3965 "Please get w/ me and I will go over it with you.\n",
3966 "The following is a list of scheduled, committed changes.\n",
3967 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3968 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3969 "Any question about transactions please contact Sterling Huxley.\n",
3970 "I will try to get back to you by Friday, December 31.\n",
3971 "This Change will be implemented on Friday.\n",
3972 "Let me know if you have problems accessing this;\n",
3973 "Sterling Huxley recently added you to the access list.\n",
3974 "Would you like to go to lunch?\n",
3975 "The last command will be automatically run.\n",
3976 "This is too much english for a computer geek.\n",
3978 static char *multilines[20] = {
3979 "You should have received a copy of the GNU General Public License\n",
3980 "char c, cm, *cmd, *cmd1;\n",
3981 "generate a command by percentages\n",
3982 "Numbers may be typed as a prefix to some commands.\n",
3983 "Quit, discarding changes!\n",
3984 "Forced write, if permission originally not valid.\n",
3985 "In general, any ex or ed command (such as substitute or delete).\n",
3986 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3987 "Please get w/ me and I will go over it with you.\n",
3988 "The following is a list of scheduled, committed changes.\n",
3989 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3990 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3991 "Any question about transactions please contact Sterling Huxley.\n",
3992 "I will try to get back to you by Friday, December 31.\n",
3993 "This Change will be implemented on Friday.\n",
3994 "Let me know if you have problems accessing this;\n",
3995 "Sterling Huxley recently added you to the access list.\n",
3996 "Would you like to go to lunch?\n",
3997 "The last command will be automatically run.\n",
3998 "This is too much english for a computer geek.\n",
4001 // create a random command to execute
4002 static void crash_dummy()
4004 static int sleeptime; // how long to pause between commands
4005 char c, cm, *cmd, *cmd1;
4006 int i, cnt, thing, rbi, startrbi, percent;
4008 // "dot" movement commands
4009 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
4011 // is there already a command running?
4012 if (readbuffer[0] > 0)
4015 readbuffer[0] = 'X';
4017 sleeptime = 0; // how long to pause between commands
4018 memset(readbuffer, '\0', sizeof(readbuffer));
4019 // generate a command by percentages
4020 percent = (int) lrand48() % 100; // get a number from 0-99
4021 if (percent < Mp) { // Movement commands
4022 // available commands
4025 } else if (percent < Np) { // non-movement commands
4026 cmd = "mz<>\'\""; // available commands
4028 } else if (percent < Dp) { // Delete commands
4029 cmd = "dx"; // available commands
4031 } else if (percent < Ip) { // Inset commands
4032 cmd = "iIaAsrJ"; // available commands
4034 } else if (percent < Yp) { // Yank commands
4035 cmd = "yY"; // available commands
4037 } else if (percent < Pp) { // Put commands
4038 cmd = "pP"; // available commands
4041 // We do not know how to handle this command, try again
4045 // randomly pick one of the available cmds from "cmd[]"
4046 i = (int) lrand48() % strlen(cmd);
4048 if (strchr(":\024", cm))
4049 goto cd0; // dont allow colon or ctrl-T commands
4050 readbuffer[rbi++] = cm; // put cmd into input buffer
4052 // now we have the command-
4053 // there are 1, 2, and multi char commands
4054 // find out which and generate the rest of command as necessary
4055 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4056 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4057 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4058 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4060 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4062 readbuffer[rbi++] = c; // add movement to input buffer
4064 if (strchr("iIaAsc", cm)) { // multi-char commands
4066 // change some thing
4067 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4069 readbuffer[rbi++] = c; // add movement to input buffer
4071 thing = (int) lrand48() % 4; // what thing to insert
4072 cnt = (int) lrand48() % 10; // how many to insert
4073 for (i = 0; i < cnt; i++) {
4074 if (thing == 0) { // insert chars
4075 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4076 } else if (thing == 1) { // insert words
4077 strcat(readbuffer, words[(int) lrand48() % 20]);
4078 strcat(readbuffer, " ");
4079 sleeptime = 0; // how fast to type
4080 } else if (thing == 2) { // insert lines
4081 strcat(readbuffer, lines[(int) lrand48() % 20]);
4082 sleeptime = 0; // how fast to type
4083 } else { // insert multi-lines
4084 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4085 sleeptime = 0; // how fast to type
4088 strcat(readbuffer, "\033");
4090 readbuffer[0] = strlen(readbuffer + 1);
4094 mysleep(sleeptime); // sleep 1/100 sec
4097 // test to see if there are any errors
4098 static void crash_test()
4100 static time_t oldtim;
4107 strcat(msg, "end<text ");
4109 if (end > textend) {
4110 strcat(msg, "end>textend ");
4113 strcat(msg, "dot<text ");
4116 strcat(msg, "dot>end ");
4118 if (screenbegin < text) {
4119 strcat(msg, "screenbegin<text ");
4121 if (screenbegin > end - 1) {
4122 strcat(msg, "screenbegin>end-1 ");
4126 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4127 totalcmds, last_input_char, msg, SOs, SOn);
4129 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4130 if (d[0] == '\n' || d[0] == '\r')
4135 if (tim >= (oldtim + 3)) {
4136 sprintf(status_buffer,
4137 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4138 totalcmds, M, N, I, D, Y, P, U, end - text + 1);