1 /* vi: set sw=4 ts=4: */
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
6 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
12 * $HOME/.exrc and ./.exrc
13 * add magic to search /foo.*bar
16 * if mark[] values were line numbers rather than pointers
17 * it would be easier to change the mark when add/delete lines
18 * More intelligence in refresh()
19 * ":r !cmd" and "!cmd" to filter text through an external command
20 * A true "undo" facility
21 * An "ex" line oriented mode- maybe using "cmdedit"
28 //config: 'vi' is a text editor. More specifically, it is the One True
29 //config: text editor <grin>. It does, however, have a rather steep
30 //config: learning curve. If you are not already comfortable with 'vi'
31 //config: you may wish to use something else.
33 //config:config FEATURE_VI_MAX_LEN
34 //config: int "Maximum screen width in vi"
35 //config: range 256 16384
36 //config: default 4096
37 //config: depends on VI
39 //config: Contrary to what you may think, this is not eating much.
40 //config: Make it smaller than 4k only if you are very limited on memory.
42 //config:config FEATURE_VI_8BIT
43 //config: bool "Allow vi to display 8-bit chars (otherwise shows dots)"
45 //config: depends on VI
47 //config: If your terminal can display characters with high bit set,
48 //config: you may want to enable this. Note: vi is not Unicode-capable.
49 //config: If your terminal combines several 8-bit bytes into one character
50 //config: (as in Unicode mode), this will not work properly.
52 //config:config FEATURE_VI_COLON
53 //config: bool "Enable \":\" colon commands (no \"ex\" mode)"
55 //config: depends on VI
57 //config: Enable a limited set of colon commands for vi. This does not
58 //config: provide an "ex" mode.
60 //config:config FEATURE_VI_YANKMARK
61 //config: bool "Enable yank/put commands and mark cmds"
63 //config: depends on VI
65 //config: This will enable you to use yank and put, as well as mark in
68 //config:config FEATURE_VI_SEARCH
69 //config: bool "Enable search and replace cmds"
71 //config: depends on VI
73 //config: Select this if you wish to be able to do search and replace in
76 //config:config FEATURE_VI_REGEX_SEARCH
77 //config: bool "Enable regex in search and replace"
78 //config: default n # Uses GNU regex, which may be unavailable. FIXME
79 //config: depends on FEATURE_VI_SEARCH
81 //config: Use extended regex search.
83 //config:config FEATURE_VI_USE_SIGNALS
84 //config: bool "Catch signals"
86 //config: depends on VI
88 //config: Selecting this option will make busybox vi signal aware. This will
89 //config: make busybox vi support SIGWINCH to deal with Window Changes, catch
90 //config: Ctrl-Z and Ctrl-C and alarms.
92 //config:config FEATURE_VI_DOT_CMD
93 //config: bool "Remember previous cmd and \".\" cmd"
95 //config: depends on VI
97 //config: Make busybox vi remember the last command and be able to repeat it.
99 //config:config FEATURE_VI_READONLY
100 //config: bool "Enable -R option and \"view\" mode"
102 //config: depends on VI
104 //config: Enable the read-only command line option, which allows the user to
105 //config: open a file in read-only mode.
107 //config:config FEATURE_VI_SETOPTS
108 //config: bool "Enable set-able options, ai ic showmatch"
110 //config: depends on VI
112 //config: Enable the editor to set some (ai, ic, showmatch) options.
114 //config:config FEATURE_VI_SET
115 //config: bool "Support for :set"
117 //config: depends on VI
119 //config: Support for ":set".
121 //config:config FEATURE_VI_WIN_RESIZE
122 //config: bool "Handle window resize"
124 //config: depends on VI
126 //config: Make busybox vi behave nicely with terminals that get resized.
128 //config:config FEATURE_VI_ASK_TERMINAL
129 //config: bool "Use 'tell me cursor position' ESC sequence to measure window"
131 //config: depends on VI
133 //config: If terminal size can't be retrieved and $LINES/$COLUMNS are not set,
134 //config: this option makes vi perform a last-ditch effort to find it:
135 //config: position cursor to 999,999 and ask terminal to report real
136 //config: cursor position using "ESC [ 6 n" escape sequence, then read stdin.
138 //config: This is not clean but helps a lot on serial lines and such.
140 //applet:IF_VI(APPLET(vi, BB_DIR_BIN, BB_SUID_DROP))
142 //kbuild:lib-$(CONFIG_VI) += vi.o
144 //usage:#define vi_trivial_usage
145 //usage: "[OPTIONS] [FILE]..."
146 //usage:#define vi_full_usage "\n\n"
147 //usage: "Edit FILE\n"
148 //usage: IF_FEATURE_VI_COLON(
149 //usage: "\n -c CMD Initial command to run ($EXINIT also available)"
151 //usage: IF_FEATURE_VI_READONLY(
152 //usage: "\n -R Read-only"
154 //usage: "\n -H List available features"
157 /* Should be after libbb.h: on some systems regex.h needs sys/types.h: */
158 #if ENABLE_FEATURE_VI_REGEX_SEARCH
162 /* the CRASHME code is unmaintained, and doesn't currently build */
163 #define ENABLE_FEATURE_VI_CRASHME 0
166 #if ENABLE_LOCALE_SUPPORT
168 #if ENABLE_FEATURE_VI_8BIT
169 //FIXME: this does not work properly for Unicode anyway
170 # define Isprint(c) (isprint)(c)
172 # define Isprint(c) isprint_asciionly(c)
177 /* 0x9b is Meta-ESC */
178 #if ENABLE_FEATURE_VI_8BIT
179 # define Isprint(c) ((unsigned char)(c) >= ' ' && (c) != 0x7f && (unsigned char)(c) != 0x9b)
181 # define Isprint(c) ((unsigned char)(c) >= ' ' && (unsigned char)(c) < 0x7f)
188 MAX_TABSTOP = 32, // sanity limit
189 // User input len. Need not be extra big.
190 // Lines in file being edited *can* be bigger than this.
192 // Sanity limits. We have only one buffer of this size.
193 MAX_SCR_COLS = CONFIG_FEATURE_VI_MAX_LEN,
194 MAX_SCR_ROWS = CONFIG_FEATURE_VI_MAX_LEN,
197 /* VT102 ESC sequences.
198 * See "Xterm Control Sequences"
199 * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
201 /* Inverse/Normal text */
202 #define ESC_BOLD_TEXT "\033[7m"
203 #define ESC_NORM_TEXT "\033[0m"
205 #define ESC_BELL "\007"
206 /* Clear-to-end-of-line */
207 #define ESC_CLEAR2EOL "\033[K"
208 /* Clear-to-end-of-screen.
209 * (We use default param here.
210 * Full sequence is "ESC [ <num> J",
211 * <num> is 0/1/2 = "erase below/above/all".)
213 #define ESC_CLEAR2EOS "\033[J"
214 /* Cursor to given coordinate (1,1: top left) */
215 #define ESC_SET_CURSOR_POS "\033[%u;%uH"
217 ///* Cursor up and down */
218 //#define ESC_CURSOR_UP "\033[A"
219 //#define ESC_CURSOR_DOWN "\n"
221 #if ENABLE_FEATURE_VI_DOT_CMD || ENABLE_FEATURE_VI_YANKMARK
222 // cmds modifying text[]
223 // vda: removed "aAiIs" as they switch us into insert mode
224 // and remembering input for replay after them makes no sense
225 static const char modifying_cmds[] = "cCdDJoOpPrRxX<>~";
231 FORWARD = 1, // code depends on "1" for array index
232 BACK = -1, // code depends on "-1" for array index
233 LIMITED = 0, // how much of text[] in char_search
234 FULL = 1, // how much of text[] in char_search
236 S_BEFORE_WS = 1, // used in skip_thing() for moving "dot"
237 S_TO_WS = 2, // used in skip_thing() for moving "dot"
238 S_OVER_WS = 3, // used in skip_thing() for moving "dot"
239 S_END_PUNCT = 4, // used in skip_thing() for moving "dot"
240 S_END_ALNUM = 5, // used in skip_thing() for moving "dot"
244 /* vi.c expects chars to be unsigned. */
245 /* busybox build system provides that, but it's better */
246 /* to audit and fix the source */
249 /* many references - keep near the top of globals */
250 char *text, *end; // pointers to the user data in memory
251 char *dot; // where all the action takes place
252 int text_size; // size of the allocated buffer
256 #define VI_AUTOINDENT 1
257 #define VI_SHOWMATCH 2
258 #define VI_IGNORECASE 4
259 #define VI_ERR_METHOD 8
260 #define autoindent (vi_setops & VI_AUTOINDENT)
261 #define showmatch (vi_setops & VI_SHOWMATCH )
262 #define ignorecase (vi_setops & VI_IGNORECASE)
263 /* indicate error with beep or flash */
264 #define err_method (vi_setops & VI_ERR_METHOD)
266 #if ENABLE_FEATURE_VI_READONLY
267 smallint readonly_mode;
268 #define SET_READONLY_FILE(flags) ((flags) |= 0x01)
269 #define SET_READONLY_MODE(flags) ((flags) |= 0x02)
270 #define UNSET_READONLY_FILE(flags) ((flags) &= 0xfe)
272 #define SET_READONLY_FILE(flags) ((void)0)
273 #define SET_READONLY_MODE(flags) ((void)0)
274 #define UNSET_READONLY_FILE(flags) ((void)0)
277 smallint editing; // >0 while we are editing a file
278 // [code audit says "can be 0, 1 or 2 only"]
279 smallint cmd_mode; // 0=command 1=insert 2=replace
280 int file_modified; // buffer contents changed (counter, not flag!)
281 int last_file_modified; // = -1;
282 int save_argc; // how many file names on cmd line
283 int cmdcnt; // repetition count
284 unsigned rows, columns; // the terminal screen is this size
285 #if ENABLE_FEATURE_VI_ASK_TERMINAL
286 int get_rowcol_error;
288 int crow, ccol; // cursor is on Crow x Ccol
289 int offset; // chars scrolled off the screen to the left
290 int have_status_msg; // is default edit status needed?
291 // [don't make smallint!]
292 int last_status_cksum; // hash of current status line
293 char *current_filename;
294 char *screenbegin; // index into text[], of top line on the screen
295 char *screen; // pointer to the virtual screen buffer
296 int screensize; // and its size
298 int last_forward_char; // last char searched for with 'f' (int because of Unicode)
299 char erase_char; // the users erase character
300 char last_input_char; // last char read from user
302 #if ENABLE_FEATURE_VI_DOT_CMD
303 smallint adding2q; // are we currently adding user input to q
304 int lmc_len; // length of last_modifying_cmd
305 char *ioq, *ioq_start; // pointer to string for get_one_char to "read"
307 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
310 #if ENABLE_FEATURE_VI_SEARCH
311 char *last_search_pattern; // last pattern from a '/' or '?' search
315 #if ENABLE_FEATURE_VI_YANKMARK
316 char *edit_file__cur_line;
318 int refresh__old_offset;
319 int format_edit_status__tot;
321 /* a few references only */
322 #if ENABLE_FEATURE_VI_YANKMARK
323 int YDreg, Ureg; // default delete register and orig line for "U"
324 char *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
325 char *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
326 char *context_start, *context_end;
328 #if ENABLE_FEATURE_VI_USE_SIGNALS
329 sigjmp_buf restart; // catch_sig()
331 struct termios term_orig, term_vi; // remember what the cooked mode was
332 #if ENABLE_FEATURE_VI_COLON
333 char *initial_cmds[3]; // currently 2 entries, NULL terminated
335 // Should be just enough to hold a key sequence,
336 // but CRASHME mode uses it as generated command buffer too
337 #if ENABLE_FEATURE_VI_CRASHME
338 char readbuffer[128];
340 char readbuffer[KEYCODE_BUFFER_SIZE];
342 #define STATUS_BUFFER_LEN 200
343 char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
344 #if ENABLE_FEATURE_VI_DOT_CMD
345 char last_modifying_cmd[MAX_INPUT_LEN]; // last modifying cmd for "."
347 char get_input_line__buf[MAX_INPUT_LEN]; /* former static */
349 char scr_out_buf[MAX_SCR_COLS + MAX_TABSTOP * 2];
351 #define G (*ptr_to_globals)
352 #define text (G.text )
353 #define text_size (G.text_size )
358 #define vi_setops (G.vi_setops )
359 #define editing (G.editing )
360 #define cmd_mode (G.cmd_mode )
361 #define file_modified (G.file_modified )
362 #define last_file_modified (G.last_file_modified )
363 #define save_argc (G.save_argc )
364 #define cmdcnt (G.cmdcnt )
365 #define rows (G.rows )
366 #define columns (G.columns )
367 #define crow (G.crow )
368 #define ccol (G.ccol )
369 #define offset (G.offset )
370 #define status_buffer (G.status_buffer )
371 #define have_status_msg (G.have_status_msg )
372 #define last_status_cksum (G.last_status_cksum )
373 #define current_filename (G.current_filename )
374 #define screen (G.screen )
375 #define screensize (G.screensize )
376 #define screenbegin (G.screenbegin )
377 #define tabstop (G.tabstop )
378 #define last_forward_char (G.last_forward_char )
379 #define erase_char (G.erase_char )
380 #define last_input_char (G.last_input_char )
381 #if ENABLE_FEATURE_VI_READONLY
382 #define readonly_mode (G.readonly_mode )
384 #define readonly_mode 0
386 #define adding2q (G.adding2q )
387 #define lmc_len (G.lmc_len )
389 #define ioq_start (G.ioq_start )
390 #define my_pid (G.my_pid )
391 #define last_search_pattern (G.last_search_pattern)
393 #define edit_file__cur_line (G.edit_file__cur_line)
394 #define refresh__old_offset (G.refresh__old_offset)
395 #define format_edit_status__tot (G.format_edit_status__tot)
397 #define YDreg (G.YDreg )
398 #define Ureg (G.Ureg )
399 #define mark (G.mark )
400 #define context_start (G.context_start )
401 #define context_end (G.context_end )
402 #define restart (G.restart )
403 #define term_orig (G.term_orig )
404 #define term_vi (G.term_vi )
405 #define initial_cmds (G.initial_cmds )
406 #define readbuffer (G.readbuffer )
407 #define scr_out_buf (G.scr_out_buf )
408 #define last_modifying_cmd (G.last_modifying_cmd )
409 #define get_input_line__buf (G.get_input_line__buf)
411 #define INIT_G() do { \
412 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
413 last_file_modified = -1; \
414 /* "" but has space for 2 chars: */ \
415 IF_FEATURE_VI_SEARCH(last_search_pattern = xzalloc(2);) \
419 static int init_text_buffer(char *); // init from file or create new
420 static void edit_file(char *); // edit one file
421 static void do_cmd(int); // execute a command
422 static int next_tabstop(int);
423 static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
424 static char *begin_line(char *); // return pointer to cur line B-o-l
425 static char *end_line(char *); // return pointer to cur line E-o-l
426 static char *prev_line(char *); // return pointer to prev line B-o-l
427 static char *next_line(char *); // return pointer to next line B-o-l
428 static char *end_screen(void); // get pointer to last char on screen
429 static int count_lines(char *, char *); // count line from start to stop
430 static char *find_line(int); // find begining of line #li
431 static char *move_to_col(char *, int); // move "p" to column l
432 static void dot_left(void); // move dot left- dont leave line
433 static void dot_right(void); // move dot right- dont leave line
434 static void dot_begin(void); // move dot to B-o-l
435 static void dot_end(void); // move dot to E-o-l
436 static void dot_next(void); // move dot to next line B-o-l
437 static void dot_prev(void); // move dot to prev line B-o-l
438 static void dot_scroll(int, int); // move the screen up or down
439 static void dot_skip_over_ws(void); // move dot pat WS
440 static void dot_delete(void); // delete the char at 'dot'
441 static char *bound_dot(char *); // make sure text[0] <= P < "end"
442 static char *new_screen(int, int); // malloc virtual screen memory
443 static char *char_insert(char *, char); // insert the char c at 'p'
444 // might reallocate text[]! use p += stupid_insert(p, ...),
445 // and be careful to not use pointers into potentially freed text[]!
446 static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
447 static int find_range(char **, char **, char); // return pointers for an object
448 static int st_test(char *, int, int, char *); // helper for skip_thing()
449 static char *skip_thing(char *, int, int, int); // skip some object
450 static char *find_pair(char *, char); // find matching pair () [] {}
451 static char *text_hole_delete(char *, char *); // at "p", delete a 'size' byte hole
452 // might reallocate text[]! use p += text_hole_make(p, ...),
453 // and be careful to not use pointers into potentially freed text[]!
454 static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
455 static char *yank_delete(char *, char *, int, int); // yank text[] into register then delete
456 static void show_help(void); // display some help info
457 static void rawmode(void); // set "raw" mode on tty
458 static void cookmode(void); // return to "cooked" mode on tty
459 // sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
460 static int mysleep(int);
461 static int readit(void); // read (maybe cursor) key from stdin
462 static int get_one_char(void); // read 1 char from stdin
463 static int file_size(const char *); // what is the byte size of "fn"
464 #if !ENABLE_FEATURE_VI_READONLY
465 #define file_insert(fn, p, update_ro_status) file_insert(fn, p)
467 // file_insert might reallocate text[]!
468 static int file_insert(const char *, char *, int);
469 static int file_write(char *, char *, char *);
470 static void place_cursor(int, int);
471 static void screen_erase(void);
472 static void clear_to_eol(void);
473 static void clear_to_eos(void);
474 static void go_bottom_and_clear_to_eol(void);
475 static void standout_start(void); // send "start reverse video" sequence
476 static void standout_end(void); // send "end reverse video" sequence
477 static void flash(int); // flash the terminal screen
478 static void show_status_line(void); // put a message on the bottom line
479 static void status_line(const char *, ...); // print to status buf
480 static void status_line_bold(const char *, ...);
481 static void not_implemented(const char *); // display "Not implemented" message
482 static int format_edit_status(void); // format file status on status line
483 static void redraw(int); // force a full screen refresh
484 static char* format_line(char* /*, int*/);
485 static void refresh(int); // update the terminal from screen[]
487 static void Indicate_Error(void); // use flash or beep to indicate error
488 #define indicate_error(c) Indicate_Error()
489 static void Hit_Return(void);
491 #if ENABLE_FEATURE_VI_SEARCH
492 static char *char_search(char *, const char *, int, int); // search for pattern starting at p
494 #if ENABLE_FEATURE_VI_COLON
495 static char *get_one_address(char *, int *); // get colon addr, if present
496 static char *get_address(char *, int *, int *); // get two colon addrs, if present
497 static void colon(char *); // execute the "colon" mode cmds
499 #if ENABLE_FEATURE_VI_USE_SIGNALS
500 static void winch_sig(int); // catch window size changes
501 static void suspend_sig(int); // catch ctrl-Z
502 static void catch_sig(int); // catch ctrl-C and alarm time-outs
504 #if ENABLE_FEATURE_VI_DOT_CMD
505 static void start_new_cmd_q(char); // new queue for command
506 static void end_cmd_q(void); // stop saving input chars
508 #define end_cmd_q() ((void)0)
510 #if ENABLE_FEATURE_VI_SETOPTS
511 static void showmatching(char *); // show the matching pair () [] {}
513 #if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
514 // might reallocate text[]! use p += string_insert(p, ...),
515 // and be careful to not use pointers into potentially freed text[]!
516 static uintptr_t string_insert(char *, const char *); // insert the string at 'p'
518 #if ENABLE_FEATURE_VI_YANKMARK
519 static char *text_yank(char *, char *, int); // save copy of "p" into a register
520 static char what_reg(void); // what is letter of current YDreg
521 static void check_context(char); // remember context for '' command
523 #if ENABLE_FEATURE_VI_CRASHME
524 static void crash_dummy();
525 static void crash_test();
526 static int crashme = 0;
530 static void write1(const char *out)
535 int vi_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
536 int vi_main(int argc, char **argv)
542 #if ENABLE_FEATURE_VI_USE_SIGNALS || ENABLE_FEATURE_VI_CRASHME
545 #if ENABLE_FEATURE_VI_CRASHME
546 srand((long) my_pid);
548 #ifdef NO_SUCH_APPLET_YET
549 /* If we aren't "vi", we are "view" */
550 if (ENABLE_FEATURE_VI_READONLY && applet_name[2]) {
551 SET_READONLY_MODE(readonly_mode);
555 // autoindent is not default in vim 7.3
556 vi_setops = /*VI_AUTOINDENT |*/ VI_SHOWMATCH | VI_IGNORECASE;
557 // 1- process $HOME/.exrc file (not inplemented yet)
558 // 2- process EXINIT variable from environment
559 // 3- process command line args
560 #if ENABLE_FEATURE_VI_COLON
562 char *p = getenv("EXINIT");
564 initial_cmds[0] = xstrndup(p, MAX_INPUT_LEN);
567 while ((c = getopt(argc, argv, "hCRH" IF_FEATURE_VI_COLON("c:"))) != -1) {
569 #if ENABLE_FEATURE_VI_CRASHME
574 #if ENABLE_FEATURE_VI_READONLY
575 case 'R': // Read-only flag
576 SET_READONLY_MODE(readonly_mode);
579 #if ENABLE_FEATURE_VI_COLON
580 case 'c': // cmd line vi command
582 initial_cmds[initial_cmds[0] != NULL] = xstrndup(optarg, MAX_INPUT_LEN);
594 // The argv array can be used by the ":next" and ":rewind" commands
598 //----- This is the main file handling loop --------------
601 // "Save cursor, use alternate screen buffer, clear screen"
602 write1("\033[?1049h");
604 edit_file(argv[optind]); /* param might be NULL */
605 if (++optind >= argc)
608 // "Use normal screen buffer, restore cursor"
609 write1("\033[?1049l");
610 //-----------------------------------------------------------
615 /* read text from file or create an empty buf */
616 /* will also update current_filename */
617 static int init_text_buffer(char *fn)
620 int size = file_size(fn); // file size. -1 means does not exist.
622 /* allocate/reallocate text buffer */
624 text_size = size + 10240;
625 screenbegin = dot = end = text = xzalloc(text_size);
627 if (fn != current_filename) {
628 free(current_filename);
629 current_filename = xstrdup(fn);
632 // file dont exist. Start empty buf with dummy line
633 char_insert(text, '\n');
636 rc = file_insert(fn, text, 1);
639 last_file_modified = -1;
640 #if ENABLE_FEATURE_VI_YANKMARK
641 /* init the marks. */
642 memset(mark, 0, sizeof(mark));
647 #if ENABLE_FEATURE_VI_WIN_RESIZE
648 static int query_screen_dimensions(void)
650 int err = get_terminal_width_height(STDIN_FILENO, &columns, &rows);
651 if (rows > MAX_SCR_ROWS)
653 if (columns > MAX_SCR_COLS)
654 columns = MAX_SCR_COLS;
658 # define query_screen_dimensions() (0)
661 static void edit_file(char *fn)
663 #if ENABLE_FEATURE_VI_YANKMARK
664 #define cur_line edit_file__cur_line
667 #if ENABLE_FEATURE_VI_USE_SIGNALS
671 editing = 1; // 0 = exit, 1 = one file, 2 = multiple files
675 IF_FEATURE_VI_ASK_TERMINAL(G.get_rowcol_error =) query_screen_dimensions();
676 #if ENABLE_FEATURE_VI_ASK_TERMINAL
677 if (G.get_rowcol_error /* TODO? && no input on stdin */) {
679 write1("\033[999;999H" "\033[6n");
681 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
682 if ((int32_t)k == KEYCODE_CURSOR_POS) {
683 uint32_t rc = (k >> 32);
684 columns = (rc & 0x7fff);
685 if (columns > MAX_SCR_COLS)
686 columns = MAX_SCR_COLS;
687 rows = ((rc >> 16) & 0x7fff);
688 if (rows > MAX_SCR_ROWS)
693 new_screen(rows, columns); // get memory for virtual screen
694 init_text_buffer(fn);
696 #if ENABLE_FEATURE_VI_YANKMARK
697 YDreg = 26; // default Yank/Delete reg
698 Ureg = 27; // hold orig line for "U" cmd
699 mark[26] = mark[27] = text; // init "previous context"
702 last_forward_char = last_input_char = '\0';
706 #if ENABLE_FEATURE_VI_USE_SIGNALS
707 signal(SIGINT, catch_sig);
708 signal(SIGWINCH, winch_sig);
709 signal(SIGTSTP, suspend_sig);
710 sig = sigsetjmp(restart, 1);
712 screenbegin = dot = text;
716 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
719 offset = 0; // no horizontal offset
721 #if ENABLE_FEATURE_VI_DOT_CMD
723 ioq = ioq_start = NULL;
728 #if ENABLE_FEATURE_VI_COLON
733 while ((p = initial_cmds[n]) != NULL) {
743 free(initial_cmds[n]);
744 initial_cmds[n] = NULL;
749 redraw(FALSE); // dont force every col re-draw
750 //------This is the main Vi cmd handling loop -----------------------
751 while (editing > 0) {
752 #if ENABLE_FEATURE_VI_CRASHME
754 if ((end - text) > 1) {
755 crash_dummy(); // generate a random command
758 string_insert(text, "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
764 last_input_char = c = get_one_char(); // get a cmd from user
765 #if ENABLE_FEATURE_VI_YANKMARK
766 // save a copy of the current line- for the 'U" command
767 if (begin_line(dot) != cur_line) {
768 cur_line = begin_line(dot);
769 text_yank(begin_line(dot), end_line(dot), Ureg);
772 #if ENABLE_FEATURE_VI_DOT_CMD
773 // These are commands that change text[].
774 // Remember the input for the "." command
775 if (!adding2q && ioq_start == NULL
776 && cmd_mode == 0 // command mode
777 && c > '\0' // exclude NUL and non-ASCII chars
778 && c < 0x7f // (Unicode and such)
779 && strchr(modifying_cmds, c)
784 do_cmd(c); // execute the user command
786 // poll to see if there is input already waiting. if we are
787 // not able to display output fast enough to keep up, skip
788 // the display update until we catch up with input.
789 if (!readbuffer[0] && mysleep(0) == 0) {
790 // no input pending - so update output
794 #if ENABLE_FEATURE_VI_CRASHME
796 crash_test(); // test editor variables
799 //-------------------------------------------------------------------
801 go_bottom_and_clear_to_eol();
806 //----- The Colon commands -------------------------------------
807 #if ENABLE_FEATURE_VI_COLON
808 static char *get_one_address(char *p, int *addr) // get colon addr, if present
812 IF_FEATURE_VI_YANKMARK(char c;)
813 IF_FEATURE_VI_SEARCH(char *pat;)
815 *addr = -1; // assume no addr
816 if (*p == '.') { // the current line
819 *addr = count_lines(text, q);
821 #if ENABLE_FEATURE_VI_YANKMARK
822 else if (*p == '\'') { // is this a mark addr
826 if (c >= 'a' && c <= 'z') {
829 q = mark[(unsigned char) c];
830 if (q != NULL) { // is mark valid
831 *addr = count_lines(text, q);
836 #if ENABLE_FEATURE_VI_SEARCH
837 else if (*p == '/') { // a search pattern
838 q = strchrnul(++p, '/');
839 pat = xstrndup(p, q - p); // save copy of pattern
843 q = char_search(dot, pat, FORWARD, FULL);
845 *addr = count_lines(text, q);
850 else if (*p == '$') { // the last line in file
852 q = begin_line(end - 1);
853 *addr = count_lines(text, q);
854 } else if (isdigit(*p)) { // specific line number
855 sscanf(p, "%d%n", addr, &st);
858 // unrecognized address - assume -1
864 static char *get_address(char *p, int *b, int *e) // get two colon addrs, if present
866 //----- get the address' i.e., 1,3 'a,'b -----
867 // get FIRST addr, if present
869 p++; // skip over leading spaces
870 if (*p == '%') { // alias for 1,$
873 *e = count_lines(text, end-1);
876 p = get_one_address(p, b);
879 if (*p == ',') { // is there a address separator
883 // get SECOND addr, if present
884 p = get_one_address(p, e);
888 p++; // skip over trailing spaces
892 #if ENABLE_FEATURE_VI_SET && ENABLE_FEATURE_VI_SETOPTS
893 static void setops(const char *args, const char *opname, int flg_no,
894 const char *short_opname, int opt)
896 const char *a = args + flg_no;
897 int l = strlen(opname) - 1; /* opname have + ' ' */
899 // maybe strncmp? we had tons of erroneous strncasecmp's...
900 if (strncasecmp(a, opname, l) == 0
901 || strncasecmp(a, short_opname, 2) == 0
911 // buf must be no longer than MAX_INPUT_LEN!
912 static void colon(char *buf)
914 char c, *orig_buf, *buf1, *q, *r;
915 char *fn, cmd[MAX_INPUT_LEN], args[MAX_INPUT_LEN];
916 int i, l, li, ch, b, e;
917 int useforce, forced = FALSE;
919 // :3154 // if (-e line 3154) goto it else stay put
920 // :4,33w! foo // write a portion of buffer to file "foo"
921 // :w // write all of buffer to current file
923 // :q! // quit- dont care about modified file
924 // :'a,'z!sort -u // filter block through sort
925 // :'f // goto mark "f"
926 // :'fl // list literal the mark "f" line
927 // :.r bar // read file "bar" into buffer before dot
928 // :/123/,/abc/d // delete lines from "123" line to "abc" line
929 // :/xyz/ // goto the "xyz" line
930 // :s/find/replace/ // substitute pattern "find" with "replace"
931 // :!<cmd> // run <cmd> then return
937 buf++; // move past the ':'
941 q = text; // assume 1,$ for the range
943 li = count_lines(text, end - 1);
944 fn = current_filename;
946 // look for optional address(es) :. :1 :1,9 :'q,'a :%
947 buf = get_address(buf, &b, &e);
949 // remember orig command line
952 // get the COMMAND into cmd[]
954 while (*buf != '\0') {
961 while (isblank(*buf))
965 buf1 = last_char_is(cmd, '!');
968 *buf1 = '\0'; // get rid of !
971 // if there is only one addr, then the addr
972 // is the line number of the single line the
973 // user wants. So, reset the end
974 // pointer to point at end of the "b" line
975 q = find_line(b); // what line is #b
980 // we were given two addrs. change the
981 // end pointer to the addr given by user.
982 r = find_line(e); // what line is #e
986 // ------------ now look for the command ------------
988 if (i == 0) { // :123CR goto line #123
990 dot = find_line(b); // what line is #b
994 #if ENABLE_FEATURE_ALLOW_EXEC
995 else if (cmd[0] == '!') { // run a cmd
997 // :!ls run the <cmd>
998 go_bottom_and_clear_to_eol();
1000 retcode = system(orig_buf + 1); // run the cmd
1002 printf("\nshell returned %i\n\n", retcode);
1004 Hit_Return(); // let user see results
1007 else if (cmd[0] == '=' && !cmd[1]) { // where is the address
1008 if (b < 0) { // no addr given- use defaults
1009 b = e = count_lines(text, dot);
1011 status_line("%d", b);
1012 } else if (strncmp(cmd, "delete", i) == 0) { // delete lines
1013 if (b < 0) { // no addr given- use defaults
1014 q = begin_line(dot); // assume .,. for the range
1017 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1019 } else if (strncmp(cmd, "edit", i) == 0) { // Edit a file
1020 // don't edit, if the current file has been modified
1021 if (file_modified && !useforce) {
1022 status_line_bold("No write since last change (:%s! overrides)", cmd);
1026 // the user supplied a file name
1028 } else if (current_filename && current_filename[0]) {
1029 // no user supplied name- use the current filename
1030 // fn = current_filename; was set by default
1032 // no user file name, no current name- punt
1033 status_line_bold("No current filename");
1037 if (init_text_buffer(fn) < 0)
1040 #if ENABLE_FEATURE_VI_YANKMARK
1041 if (Ureg >= 0 && Ureg < 28) {
1042 free(reg[Ureg]); // free orig line reg- for 'U'
1045 if (YDreg >= 0 && YDreg < 28) {
1046 free(reg[YDreg]); // free default yank/delete register
1050 // how many lines in text[]?
1051 li = count_lines(text, end - 1);
1052 status_line("\"%s\"%s"
1053 IF_FEATURE_VI_READONLY("%s")
1054 " %dL, %dC", current_filename,
1055 (file_size(fn) < 0 ? " [New file]" : ""),
1056 IF_FEATURE_VI_READONLY(
1057 ((readonly_mode) ? " [Readonly]" : ""),
1060 } else if (strncmp(cmd, "file", i) == 0) { // what File is this
1061 if (b != -1 || e != -1) {
1062 status_line_bold("No address allowed on this command");
1066 // user wants a new filename
1067 free(current_filename);
1068 current_filename = xstrdup(args);
1070 // user wants file status info
1071 last_status_cksum = 0; // force status update
1073 } else if (strncmp(cmd, "features", i) == 0) { // what features are available
1074 // print out values of all features
1075 go_bottom_and_clear_to_eol();
1080 } else if (strncmp(cmd, "list", i) == 0) { // literal print line
1081 if (b < 0) { // no addr given- use defaults
1082 q = begin_line(dot); // assume .,. for the range
1085 go_bottom_and_clear_to_eol();
1087 for (; q <= r; q++) {
1091 c_is_no_print = (c & 0x80) && !Isprint(c);
1092 if (c_is_no_print) {
1098 } else if (c < ' ' || c == 127) {
1110 } else if (strncmp(cmd, "quit", i) == 0 // quit
1111 || strncmp(cmd, "next", i) == 0 // edit next file
1112 || strncmp(cmd, "prev", i) == 0 // edit previous file
1117 // force end of argv list
1123 // don't exit if the file been modified
1124 if (file_modified) {
1125 status_line_bold("No write since last change (:%s! overrides)", cmd);
1128 // are there other file to edit
1129 n = save_argc - optind - 1;
1130 if (*cmd == 'q' && n > 0) {
1131 status_line_bold("%d more file(s) to edit", n);
1134 if (*cmd == 'n' && n <= 0) {
1135 status_line_bold("No more files to edit");
1139 // are there previous files to edit
1141 status_line_bold("No previous files to edit");
1147 } else if (strncmp(cmd, "read", i) == 0) { // read file into text[]
1150 status_line_bold("No filename given");
1153 if (b < 0) { // no addr given- use defaults
1154 q = begin_line(dot); // assume "dot"
1156 // read after current line- unless user said ":0r foo"
1159 { // dance around potentially-reallocated text[]
1160 uintptr_t ofs = q - text;
1161 ch = file_insert(fn, q, 0);
1165 goto ret; // nothing was inserted
1166 // how many lines in text[]?
1167 li = count_lines(q, q + ch - 1);
1168 status_line("\"%s\""
1169 IF_FEATURE_VI_READONLY("%s")
1171 IF_FEATURE_VI_READONLY((readonly_mode ? " [Readonly]" : ""),)
1174 // if the insert is before "dot" then we need to update
1177 /*file_modified++; - done by file_insert */
1179 } else if (strncmp(cmd, "rewind", i) == 0) { // rewind cmd line args
1180 if (file_modified && !useforce) {
1181 status_line_bold("No write since last change (:%s! overrides)", cmd);
1183 // reset the filenames to edit
1184 optind = -1; /* start from 0th file */
1187 #if ENABLE_FEATURE_VI_SET
1188 } else if (strncmp(cmd, "set", i) == 0) { // set or clear features
1189 #if ENABLE_FEATURE_VI_SETOPTS
1192 i = 0; // offset into args
1193 // only blank is regarded as args delimiter. What about tab '\t'?
1194 if (!args[0] || strcasecmp(args, "all") == 0) {
1195 // print out values of all options
1196 #if ENABLE_FEATURE_VI_SETOPTS
1203 autoindent ? "" : "no",
1204 err_method ? "" : "no",
1205 ignorecase ? "" : "no",
1206 showmatch ? "" : "no",
1212 #if ENABLE_FEATURE_VI_SETOPTS
1215 if (strncmp(argp, "no", 2) == 0)
1216 i = 2; // ":set noautoindent"
1217 setops(argp, "autoindent ", i, "ai", VI_AUTOINDENT);
1218 setops(argp, "flash " , i, "fl", VI_ERR_METHOD);
1219 setops(argp, "ignorecase ", i, "ic", VI_IGNORECASE);
1220 setops(argp, "showmatch " , i, "sm", VI_SHOWMATCH );
1221 if (strncmp(argp + i, "tabstop=", 8) == 0) {
1223 sscanf(argp + i+8, "%u", &t);
1224 if (t > 0 && t <= MAX_TABSTOP)
1227 argp = skip_non_whitespace(argp);
1228 argp = skip_whitespace(argp);
1230 #endif /* FEATURE_VI_SETOPTS */
1231 #endif /* FEATURE_VI_SET */
1232 #if ENABLE_FEATURE_VI_SEARCH
1233 } else if (cmd[0] == 's') { // substitute a pattern with a replacement pattern
1234 char *F, *R, *flags;
1235 size_t len_F, len_R;
1236 int gflag; // global replace flag
1238 // F points to the "find" pattern
1239 // R points to the "replace" pattern
1240 // replace the cmd line delimiters "/" with NULs
1241 c = orig_buf[1]; // what is the delimiter
1242 F = orig_buf + 2; // start of "find"
1243 R = strchr(F, c); // middle delimiter
1247 *R++ = '\0'; // terminate "find"
1248 flags = strchr(R, c);
1252 *flags++ = '\0'; // terminate "replace"
1256 if (b < 0) { // maybe :s/foo/bar/
1257 q = begin_line(dot); // start with cur line
1258 b = count_lines(text, q); // cur line number
1261 e = b; // maybe :.s/foo/bar/
1263 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1264 char *ls = q; // orig line start
1267 found = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1270 // we found the "find" pattern - delete it
1271 text_hole_delete(found, found + len_F - 1);
1272 // inset the "replace" patern
1273 bias = string_insert(found, R); // insert the string
1276 /*q += bias; - recalculated anyway */
1277 // check for "global" :s/foo/bar/g
1279 if ((found + len_R) < end_line(ls)) {
1281 goto vc4; // don't let q move past cur line
1287 #endif /* FEATURE_VI_SEARCH */
1288 } else if (strncmp(cmd, "version", i) == 0) { // show software version
1289 status_line(BB_VER " " BB_BT);
1290 } else if (strncmp(cmd, "write", i) == 0 // write text to file
1291 || strncmp(cmd, "wq", i) == 0
1292 || strncmp(cmd, "wn", i) == 0
1293 || (cmd[0] == 'x' && !cmd[1])
1295 // is there a file name to write to?
1299 #if ENABLE_FEATURE_VI_READONLY
1300 if (readonly_mode && !useforce) {
1301 status_line_bold("\"%s\" File is read only", fn);
1305 // how many lines in text[]?
1306 li = count_lines(q, r);
1308 // see if file exists- if not, its just a new file request
1310 // if "fn" is not write-able, chmod u+w
1311 // sprintf(syscmd, "chmod u+w %s", fn);
1315 l = file_write(fn, q, r);
1316 if (useforce && forced) {
1318 // sprintf(syscmd, "chmod u-w %s", fn);
1324 status_line_bold("\"%s\" %s", fn, strerror(errno));
1326 status_line("\"%s\" %dL, %dC", fn, li, l);
1327 if (q == text && r == end - 1 && l == ch) {
1329 last_file_modified = -1;
1331 if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n'
1332 || cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N'
1339 #if ENABLE_FEATURE_VI_YANKMARK
1340 } else if (strncmp(cmd, "yank", i) == 0) { // yank lines
1341 if (b < 0) { // no addr given- use defaults
1342 q = begin_line(dot); // assume .,. for the range
1345 text_yank(q, r, YDreg);
1346 li = count_lines(q, r);
1347 status_line("Yank %d lines (%d chars) into [%c]",
1348 li, strlen(reg[YDreg]), what_reg());
1352 not_implemented(cmd);
1355 dot = bound_dot(dot); // make sure "dot" is valid
1357 #if ENABLE_FEATURE_VI_SEARCH
1359 status_line(":s expression missing delimiters");
1363 #endif /* FEATURE_VI_COLON */
1365 static void Hit_Return(void)
1370 write1("[Hit return to continue]");
1372 while ((c = get_one_char()) != '\n' && c != '\r')
1374 redraw(TRUE); // force redraw all
1377 static int next_tabstop(int col)
1379 return col + ((tabstop - 1) - (col % tabstop));
1382 //----- Synchronize the cursor to Dot --------------------------
1383 static NOINLINE void sync_cursor(char *d, int *row, int *col)
1385 char *beg_cur; // begin and end of "d" line
1389 beg_cur = begin_line(d); // first char of cur line
1391 if (beg_cur < screenbegin) {
1392 // "d" is before top line on screen
1393 // how many lines do we have to move
1394 cnt = count_lines(beg_cur, screenbegin);
1396 screenbegin = beg_cur;
1397 if (cnt > (rows - 1) / 2) {
1398 // we moved too many lines. put "dot" in middle of screen
1399 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
1400 screenbegin = prev_line(screenbegin);
1404 char *end_scr; // begin and end of screen
1405 end_scr = end_screen(); // last char of screen
1406 if (beg_cur > end_scr) {
1407 // "d" is after bottom line on screen
1408 // how many lines do we have to move
1409 cnt = count_lines(end_scr, beg_cur);
1410 if (cnt > (rows - 1) / 2)
1411 goto sc1; // too many lines
1412 for (ro = 0; ro < cnt - 1; ro++) {
1413 // move screen begin the same amount
1414 screenbegin = next_line(screenbegin);
1415 // now, move the end of screen
1416 end_scr = next_line(end_scr);
1417 end_scr = end_line(end_scr);
1421 // "d" is on screen- find out which row
1423 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
1429 // find out what col "d" is on
1431 while (tp < d) { // drive "co" to correct column
1432 if (*tp == '\n') //vda || *tp == '\0')
1435 // handle tabs like real vi
1436 if (d == tp && cmd_mode) {
1439 co = next_tabstop(co);
1440 } else if ((unsigned char)*tp < ' ' || *tp == 0x7f) {
1441 co++; // display as ^X, use 2 columns
1447 // "co" is the column where "dot" is.
1448 // The screen has "columns" columns.
1449 // The currently displayed columns are 0+offset -- columns+ofset
1450 // |-------------------------------------------------------------|
1452 // offset | |------- columns ----------------|
1454 // If "co" is already in this range then we do not have to adjust offset
1455 // but, we do have to subtract the "offset" bias from "co".
1456 // If "co" is outside this range then we have to change "offset".
1457 // If the first char of a line is a tab the cursor will try to stay
1458 // in column 7, but we have to set offset to 0.
1460 if (co < 0 + offset) {
1463 if (co >= columns + offset) {
1464 offset = co - columns + 1;
1466 // if the first char of the line is a tab, and "dot" is sitting on it
1467 // force offset to 0.
1468 if (d == beg_cur && *d == '\t') {
1477 //----- Text Movement Routines ---------------------------------
1478 static char *begin_line(char *p) // return pointer to first char cur line
1481 p = memrchr(text, '\n', p - text);
1489 static char *end_line(char *p) // return pointer to NL of cur line
1492 p = memchr(p, '\n', end - p - 1);
1499 static char *dollar_line(char *p) // return pointer to just before NL line
1502 // Try to stay off of the Newline
1503 if (*p == '\n' && (p - begin_line(p)) > 0)
1508 static char *prev_line(char *p) // return pointer first char prev line
1510 p = begin_line(p); // goto begining of cur line
1511 if (p > text && p[-1] == '\n')
1512 p--; // step to prev line
1513 p = begin_line(p); // goto begining of prev line
1517 static char *next_line(char *p) // return pointer first char next line
1520 if (p < end - 1 && *p == '\n')
1521 p++; // step to next line
1525 //----- Text Information Routines ------------------------------
1526 static char *end_screen(void)
1531 // find new bottom line
1533 for (cnt = 0; cnt < rows - 2; cnt++)
1539 // count line from start to stop
1540 static int count_lines(char *start, char *stop)
1545 if (stop < start) { // start and stop are backwards- reverse them
1551 stop = end_line(stop);
1552 while (start <= stop && start <= end - 1) {
1553 start = end_line(start);
1561 static char *find_line(int li) // find begining of line #li
1565 for (q = text; li > 1; li--) {
1571 //----- Dot Movement Routines ----------------------------------
1572 static void dot_left(void)
1574 if (dot > text && dot[-1] != '\n')
1578 static void dot_right(void)
1580 if (dot < end - 1 && *dot != '\n')
1584 static void dot_begin(void)
1586 dot = begin_line(dot); // return pointer to first char cur line
1589 static void dot_end(void)
1591 dot = end_line(dot); // return pointer to last char cur line
1594 static char *move_to_col(char *p, int l)
1600 while (co < l && p < end) {
1601 if (*p == '\n') //vda || *p == '\0')
1604 co = next_tabstop(co);
1605 } else if (*p < ' ' || *p == 127) {
1606 co++; // display as ^X, use 2 columns
1614 static void dot_next(void)
1616 dot = next_line(dot);
1619 static void dot_prev(void)
1621 dot = prev_line(dot);
1624 static void dot_scroll(int cnt, int dir)
1628 for (; cnt > 0; cnt--) {
1631 // ctrl-Y scroll up one line
1632 screenbegin = prev_line(screenbegin);
1635 // ctrl-E scroll down one line
1636 screenbegin = next_line(screenbegin);
1639 // make sure "dot" stays on the screen so we dont scroll off
1640 if (dot < screenbegin)
1642 q = end_screen(); // find new bottom line
1644 dot = begin_line(q); // is dot is below bottom line?
1648 static void dot_skip_over_ws(void)
1651 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1655 static void dot_delete(void) // delete the char at 'dot'
1657 text_hole_delete(dot, dot);
1660 static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1662 if (p >= end && end > text) {
1664 indicate_error('1');
1668 indicate_error('2');
1673 //----- Helper Utility Routines --------------------------------
1675 //----------------------------------------------------------------
1676 //----- Char Routines --------------------------------------------
1677 /* Chars that are part of a word-
1678 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
1679 * Chars that are Not part of a word (stoppers)
1680 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
1681 * Chars that are WhiteSpace
1682 * TAB NEWLINE VT FF RETURN SPACE
1683 * DO NOT COUNT NEWLINE AS WHITESPACE
1686 static char *new_screen(int ro, int co)
1691 screensize = ro * co + 8;
1692 screen = xmalloc(screensize);
1693 // initialize the new screen. assume this will be a empty file.
1695 // non-existent text[] lines start with a tilde (~).
1696 for (li = 1; li < ro - 1; li++) {
1697 screen[(li * co) + 0] = '~';
1702 #if ENABLE_FEATURE_VI_SEARCH
1704 # if ENABLE_FEATURE_VI_REGEX_SEARCH
1706 // search for pattern starting at p
1707 static char *char_search(char *p, const char *pat, int dir, int range)
1710 struct re_pattern_buffer preg;
1714 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1720 // assume a LIMITED forward search
1728 // count the number of chars to search over, forward or backward
1732 // RANGE could be negative if we are searching backwards
1735 q = (char *)re_compile_pattern(pat, strlen(pat), (struct re_pattern_buffer *)&preg);
1737 // The pattern was not compiled
1738 status_line_bold("bad search pattern: \"%s\": %s", pat, q);
1739 i = 0; // return p if pattern not compiled
1749 // search for the compiled pattern, preg, in p[]
1750 // range < 0- search backward
1751 // range > 0- search forward
1753 // re_search() < 0 not found or error
1754 // re_search() > 0 index of found pattern
1755 // struct pattern char int int int struct reg
1756 // re_search (*pattern_buffer, *string, size, start, range, *regs)
1757 i = re_search(&preg, q, size, 0, range, 0);
1760 i = 0; // return NULL if pattern not found
1763 if (dir == FORWARD) {
1773 # if ENABLE_FEATURE_VI_SETOPTS
1774 static int mycmp(const char *s1, const char *s2, int len)
1777 return strncasecmp(s1, s2, len);
1779 return strncmp(s1, s2, len);
1782 # define mycmp strncmp
1785 static char *char_search(char *p, const char *pat, int dir, int range)
1791 if (dir == FORWARD) {
1792 stop = end - 1; // assume range is p - end-1
1793 if (range == LIMITED)
1794 stop = next_line(p); // range is to next line
1795 for (start = p; start < stop; start++) {
1796 if (mycmp(start, pat, len) == 0) {
1800 } else if (dir == BACK) {
1801 stop = text; // assume range is text - p
1802 if (range == LIMITED)
1803 stop = prev_line(p); // range is to prev line
1804 for (start = p - len; start >= stop; start--) {
1805 if (mycmp(start, pat, len) == 0) {
1810 // pattern not found
1816 #endif /* FEATURE_VI_SEARCH */
1818 static char *char_insert(char *p, char c) // insert the char c at 'p'
1820 if (c == 22) { // Is this an ctrl-V?
1821 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1822 refresh(FALSE); // show the ^
1827 } else if (c == 27) { // Is this an ESC?
1830 end_cmd_q(); // stop adding to q
1831 last_status_cksum = 0; // force status update
1832 if ((p[-1] != '\n') && (dot > text)) {
1835 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1837 if ((p[-1] != '\n') && (dot>text)) {
1839 p = text_hole_delete(p, p); // shrink buffer 1 char
1842 #if ENABLE_FEATURE_VI_SETOPTS
1843 // insert a char into text[]
1844 char *sp; // "save p"
1848 c = '\n'; // translate \r to \n
1849 #if ENABLE_FEATURE_VI_SETOPTS
1850 sp = p; // remember addr of insert
1852 p += 1 + stupid_insert(p, c); // insert the char
1853 #if ENABLE_FEATURE_VI_SETOPTS
1854 if (showmatch && strchr(")]}", *sp) != NULL) {
1857 if (autoindent && c == '\n') { // auto indent the new line
1860 q = prev_line(p); // use prev line as template
1861 len = strspn(q, " \t"); // space or tab
1864 bias = text_hole_make(p, len);
1876 // might reallocate text[]! use p += stupid_insert(p, ...),
1877 // and be careful to not use pointers into potentially freed text[]!
1878 static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1881 bias = text_hole_make(p, 1);
1884 //file_modified++; - done by text_hole_make()
1888 static int find_range(char **start, char **stop, char c)
1890 char *save_dot, *p, *q, *t;
1891 int cnt, multiline = 0;
1896 if (strchr("cdy><", c)) {
1897 // these cmds operate on whole lines
1898 p = q = begin_line(p);
1899 for (cnt = 1; cnt < cmdcnt; cnt++) {
1903 } else if (strchr("^%$0bBeEfth\b\177", c)) {
1904 // These cmds operate on char positions
1905 do_cmd(c); // execute movement cmd
1907 } else if (strchr("wW", c)) {
1908 do_cmd(c); // execute movement cmd
1909 // if we are at the next word's first char
1910 // step back one char
1911 // but check the possibilities when it is true
1912 if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0]))
1913 || (ispunct(dot[-1]) && !ispunct(dot[0]))
1914 || (isalnum(dot[-1]) && !isalnum(dot[0]))))
1915 dot--; // move back off of next word
1916 if (dot > text && *dot == '\n')
1917 dot--; // stay off NL
1919 } else if (strchr("H-k{", c)) {
1920 // these operate on multi-lines backwards
1921 q = end_line(dot); // find NL
1922 do_cmd(c); // execute movement cmd
1925 } else if (strchr("L+j}\r\n", c)) {
1926 // these operate on multi-lines forwards
1927 p = begin_line(dot);
1928 do_cmd(c); // execute movement cmd
1929 dot_end(); // find NL
1932 // nothing -- this causes any other values of c to
1933 // represent the one-character range under the
1934 // cursor. this is correct for ' ' and 'l', but
1935 // perhaps no others.
1944 // backward char movements don't include start position
1945 if (q > p && strchr("^0bBh\b\177", c)) q--;
1948 for (t = p; t <= q; t++) {
1961 static int st_test(char *p, int type, int dir, char *tested)
1971 if (type == S_BEFORE_WS) {
1973 test = (!isspace(c) || c == '\n');
1975 if (type == S_TO_WS) {
1977 test = (!isspace(c) || c == '\n');
1979 if (type == S_OVER_WS) {
1983 if (type == S_END_PUNCT) {
1987 if (type == S_END_ALNUM) {
1989 test = (isalnum(c) || c == '_');
1995 static char *skip_thing(char *p, int linecnt, int dir, int type)
1999 while (st_test(p, type, dir, &c)) {
2000 // make sure we limit search to correct number of lines
2001 if (c == '\n' && --linecnt < 1)
2003 if (dir >= 0 && p >= end - 1)
2005 if (dir < 0 && p <= text)
2007 p += dir; // move to next char
2012 // find matching char of pair () [] {}
2013 static char *find_pair(char *p, const char c)
2020 dir = 1; // assume forward
2022 case '(': match = ')'; break;
2023 case '[': match = ']'; break;
2024 case '{': match = '}'; break;
2025 case ')': match = '('; dir = -1; break;
2026 case ']': match = '['; dir = -1; break;
2027 case '}': match = '{'; dir = -1; break;
2029 for (q = p + dir; text <= q && q < end; q += dir) {
2030 // look for match, count levels of pairs (( ))
2032 level++; // increase pair levels
2034 level--; // reduce pair level
2036 break; // found matching pair
2039 q = NULL; // indicate no match
2043 #if ENABLE_FEATURE_VI_SETOPTS
2044 // show the matching char of a pair, () [] {}
2045 static void showmatching(char *p)
2049 // we found half of a pair
2050 q = find_pair(p, *p); // get loc of matching char
2052 indicate_error('3'); // no matching char
2054 // "q" now points to matching pair
2055 save_dot = dot; // remember where we are
2056 dot = q; // go to new loc
2057 refresh(FALSE); // let the user see it
2058 mysleep(40); // give user some time
2059 dot = save_dot; // go back to old loc
2063 #endif /* FEATURE_VI_SETOPTS */
2065 // open a hole in text[]
2066 // might reallocate text[]! use p += text_hole_make(p, ...),
2067 // and be careful to not use pointers into potentially freed text[]!
2068 static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2074 end += size; // adjust the new END
2075 if (end >= (text + text_size)) {
2077 text_size += end - (text + text_size) + 10240;
2078 new_text = xrealloc(text, text_size);
2079 bias = (new_text - text);
2080 screenbegin += bias;
2084 #if ENABLE_FEATURE_VI_YANKMARK
2087 for (i = 0; i < ARRAY_SIZE(mark); i++)
2094 memmove(p + size, p, end - size - p);
2095 memset(p, ' ', size); // clear new hole
2100 // close a hole in text[]
2101 static char *text_hole_delete(char *p, char *q) // delete "p" through "q", inclusive
2106 // move forwards, from beginning
2110 if (q < p) { // they are backward- swap them
2114 hole_size = q - p + 1;
2116 if (src < text || src > end)
2118 if (dest < text || dest >= end)
2121 goto thd_atend; // just delete the end of the buffer
2122 memmove(dest, src, cnt);
2124 end = end - hole_size; // adjust the new END
2126 dest = end - 1; // make sure dest in below end-1
2128 dest = end = text; // keep pointers valid
2134 // copy text into register, then delete text.
2135 // if dist <= 0, do not include, or go past, a NewLine
2137 static char *yank_delete(char *start, char *stop, int dist, int yf)
2141 // make sure start <= stop
2143 // they are backwards, reverse them
2149 // we cannot cross NL boundaries
2153 // dont go past a NewLine
2154 for (; p + 1 <= stop; p++) {
2156 stop = p; // "stop" just before NewLine
2162 #if ENABLE_FEATURE_VI_YANKMARK
2163 text_yank(start, stop, YDreg);
2165 if (yf == YANKDEL) {
2166 p = text_hole_delete(start, stop);
2171 static void show_help(void)
2173 puts("These features are available:"
2174 #if ENABLE_FEATURE_VI_SEARCH
2175 "\n\tPattern searches with / and ?"
2177 #if ENABLE_FEATURE_VI_DOT_CMD
2178 "\n\tLast command repeat with ."
2180 #if ENABLE_FEATURE_VI_YANKMARK
2181 "\n\tLine marking with 'x"
2182 "\n\tNamed buffers with \"x"
2184 #if ENABLE_FEATURE_VI_READONLY
2185 //not implemented: "\n\tReadonly if vi is called as \"view\""
2186 //redundant: usage text says this too: "\n\tReadonly with -R command line arg"
2188 #if ENABLE_FEATURE_VI_SET
2189 "\n\tSome colon mode commands with :"
2191 #if ENABLE_FEATURE_VI_SETOPTS
2192 "\n\tSettable options with \":set\""
2194 #if ENABLE_FEATURE_VI_USE_SIGNALS
2195 "\n\tSignal catching- ^C"
2196 "\n\tJob suspend and resume with ^Z"
2198 #if ENABLE_FEATURE_VI_WIN_RESIZE
2199 "\n\tAdapt to window re-sizes"
2204 #if ENABLE_FEATURE_VI_DOT_CMD
2205 static void start_new_cmd_q(char c)
2207 // get buffer for new cmd
2208 // if there is a current cmd count put it in the buffer first
2210 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2211 } else { // just save char c onto queue
2212 last_modifying_cmd[0] = c;
2218 static void end_cmd_q(void)
2220 #if ENABLE_FEATURE_VI_YANKMARK
2221 YDreg = 26; // go back to default Yank/Delete reg
2225 #endif /* FEATURE_VI_DOT_CMD */
2227 #if ENABLE_FEATURE_VI_YANKMARK \
2228 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2229 || ENABLE_FEATURE_VI_CRASHME
2230 // might reallocate text[]! use p += string_insert(p, ...),
2231 // and be careful to not use pointers into potentially freed text[]!
2232 static uintptr_t string_insert(char *p, const char *s) // insert the string at 'p'
2238 bias = text_hole_make(p, i);
2241 #if ENABLE_FEATURE_VI_YANKMARK
2244 for (cnt = 0; *s != '\0'; s++) {
2248 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2255 #if ENABLE_FEATURE_VI_YANKMARK
2256 static char *text_yank(char *p, char *q, int dest) // copy text into a register
2259 if (cnt < 0) { // they are backwards- reverse them
2263 free(reg[dest]); // if already a yank register, free it
2264 reg[dest] = xstrndup(p, cnt + 1);
2268 static char what_reg(void)
2272 c = 'D'; // default to D-reg
2273 if (0 <= YDreg && YDreg <= 25)
2274 c = 'a' + (char) YDreg;
2282 static void check_context(char cmd)
2284 // A context is defined to be "modifying text"
2285 // Any modifying command establishes a new context.
2287 if (dot < context_start || dot > context_end) {
2288 if (strchr(modifying_cmds, cmd) != NULL) {
2289 // we are trying to modify text[]- make this the current context
2290 mark[27] = mark[26]; // move cur to prev
2291 mark[26] = dot; // move local to cur
2292 context_start = prev_line(prev_line(dot));
2293 context_end = next_line(next_line(dot));
2294 //loiter= start_loiter= now;
2299 static char *swap_context(char *p) // goto new context for '' command make this the current context
2303 // the current context is in mark[26]
2304 // the previous context is in mark[27]
2305 // only swap context if other context is valid
2306 if (text <= mark[27] && mark[27] <= end - 1) {
2308 mark[27] = mark[26];
2310 p = mark[26]; // where we are going- previous context
2311 context_start = prev_line(prev_line(prev_line(p)));
2312 context_end = next_line(next_line(next_line(p)));
2316 #endif /* FEATURE_VI_YANKMARK */
2318 //----- Set terminal attributes --------------------------------
2319 static void rawmode(void)
2321 tcgetattr(0, &term_orig);
2322 term_vi = term_orig;
2323 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG on - allow intr's
2324 term_vi.c_iflag &= (~IXON & ~ICRNL);
2325 term_vi.c_oflag &= (~ONLCR);
2326 term_vi.c_cc[VMIN] = 1;
2327 term_vi.c_cc[VTIME] = 0;
2328 erase_char = term_vi.c_cc[VERASE];
2329 tcsetattr_stdin_TCSANOW(&term_vi);
2332 static void cookmode(void)
2335 tcsetattr_stdin_TCSANOW(&term_orig);
2338 #if ENABLE_FEATURE_VI_USE_SIGNALS
2339 //----- Come here when we get a window resize signal ---------
2340 static void winch_sig(int sig UNUSED_PARAM)
2342 int save_errno = errno;
2343 // FIXME: do it in main loop!!!
2344 signal(SIGWINCH, winch_sig);
2345 query_screen_dimensions();
2346 new_screen(rows, columns); // get memory for virtual screen
2347 redraw(TRUE); // re-draw the screen
2351 //----- Come here when we get a continue signal -------------------
2352 static void cont_sig(int sig UNUSED_PARAM)
2354 int save_errno = errno;
2355 rawmode(); // terminal to "raw"
2356 last_status_cksum = 0; // force status update
2357 redraw(TRUE); // re-draw the screen
2359 signal(SIGTSTP, suspend_sig);
2360 signal(SIGCONT, SIG_DFL);
2361 //kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
2365 //----- Come here when we get a Suspend signal -------------------
2366 static void suspend_sig(int sig UNUSED_PARAM)
2368 int save_errno = errno;
2369 go_bottom_and_clear_to_eol();
2370 cookmode(); // terminal to "cooked"
2372 signal(SIGCONT, cont_sig);
2373 signal(SIGTSTP, SIG_DFL);
2374 kill(my_pid, SIGTSTP);
2378 //----- Come here when we get a signal ---------------------------
2379 static void catch_sig(int sig)
2381 signal(SIGINT, catch_sig);
2382 siglongjmp(restart, sig);
2384 #endif /* FEATURE_VI_USE_SIGNALS */
2386 static int mysleep(int hund) // sleep for 'hund' 1/100 seconds or stdin ready
2388 struct pollfd pfd[1];
2390 pfd[0].fd = STDIN_FILENO;
2391 pfd[0].events = POLLIN;
2392 return safe_poll(pfd, 1, hund*10) > 0;
2395 //----- IO Routines --------------------------------------------
2396 static int readit(void) // read (maybe cursor) key from stdin
2401 c = read_key(STDIN_FILENO, readbuffer, /*timeout off:*/ -2);
2402 if (c == -1) { // EOF/error
2403 go_bottom_and_clear_to_eol();
2404 cookmode(); // terminal to "cooked"
2405 bb_error_msg_and_die("can't read user input");
2410 //----- IO Routines --------------------------------------------
2411 static int get_one_char(void)
2415 #if ENABLE_FEATURE_VI_DOT_CMD
2417 // we are not adding to the q.
2418 // but, we may be reading from a q
2420 // there is no current q, read from STDIN
2421 c = readit(); // get the users input
2423 // there is a queue to get chars from first
2424 // careful with correct sign expansion!
2425 c = (unsigned char)*ioq++;
2427 // the end of the q, read from STDIN
2429 ioq_start = ioq = 0;
2430 c = readit(); // get the users input
2434 // adding STDIN chars to q
2435 c = readit(); // get the users input
2436 if (lmc_len >= MAX_INPUT_LEN - 1) {
2437 status_line_bold("last_modifying_cmd overrun");
2439 // add new char to q
2440 last_modifying_cmd[lmc_len++] = c;
2444 c = readit(); // get the users input
2445 #endif /* FEATURE_VI_DOT_CMD */
2449 // Get input line (uses "status line" area)
2450 static char *get_input_line(const char *prompt)
2452 // char [MAX_INPUT_LEN]
2453 #define buf get_input_line__buf
2458 strcpy(buf, prompt);
2459 last_status_cksum = 0; // force status update
2460 go_bottom_and_clear_to_eol();
2461 write1(prompt); // write out the :, /, or ? prompt
2464 while (i < MAX_INPUT_LEN) {
2466 if (c == '\n' || c == '\r' || c == 27)
2467 break; // this is end of input
2468 if (c == erase_char || c == 8 || c == 127) {
2469 // user wants to erase prev char
2471 write1("\b \b"); // erase char on screen
2472 if (i <= 0) // user backs up before b-o-l, exit
2474 } else if (c > 0 && c < 256) { // exclude Unicode
2475 // (TODO: need to handle Unicode)
2486 static int file_size(const char *fn) // what is the byte size of "fn"
2492 if (fn && stat(fn, &st_buf) == 0) // see if file exists
2493 cnt = (int) st_buf.st_size;
2497 // might reallocate text[]!
2498 static int file_insert(const char *fn, char *p, int update_ro_status)
2502 struct stat statbuf;
2505 if (stat(fn, &statbuf) < 0) {
2506 status_line_bold("\"%s\" %s", fn, strerror(errno));
2509 if (!S_ISREG(statbuf.st_mode)) {
2510 // This is not a regular file
2511 status_line_bold("\"%s\" Not a regular file", fn);
2514 if (p < text || p > end) {
2515 status_line_bold("Trying to insert file outside of memory");
2519 // read file to buffer
2520 fd = open(fn, O_RDONLY);
2522 status_line_bold("\"%s\" %s", fn, strerror(errno));
2525 size = statbuf.st_size;
2526 p += text_hole_make(p, size);
2527 cnt = safe_read(fd, p, size);
2529 status_line_bold("\"%s\" %s", fn, strerror(errno));
2530 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
2531 } else if (cnt < size) {
2532 // There was a partial read, shrink unused space text[]
2533 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
2534 status_line_bold("can't read all of file \"%s\"", fn);
2540 #if ENABLE_FEATURE_VI_READONLY
2541 if (update_ro_status
2542 && ((access(fn, W_OK) < 0) ||
2543 /* root will always have access()
2544 * so we check fileperms too */
2545 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2548 SET_READONLY_FILE(readonly_mode);
2554 static int file_write(char *fn, char *first, char *last)
2556 int fd, cnt, charcnt;
2559 status_line_bold("No current filename");
2562 /* By popular request we do not open file with O_TRUNC,
2563 * but instead ftruncate() it _after_ successful write.
2564 * Might reduce amount of data lost on power fail etc.
2566 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2569 cnt = last - first + 1;
2570 charcnt = full_write(fd, first, cnt);
2571 ftruncate(fd, charcnt);
2572 if (charcnt == cnt) {
2574 //file_modified = FALSE;
2582 //----- Terminal Drawing ---------------------------------------
2583 // The terminal is made up of 'rows' line of 'columns' columns.
2584 // classically this would be 24 x 80.
2585 // screen coordinates
2591 // 23,0 ... 23,79 <- status line
2593 //----- Move the cursor to row x col (count from 0, not 1) -------
2594 static void place_cursor(int row, int col)
2596 char cm1[sizeof(ESC_SET_CURSOR_POS) + sizeof(int)*3 * 2];
2598 if (row < 0) row = 0;
2599 if (row >= rows) row = rows - 1;
2600 if (col < 0) col = 0;
2601 if (col >= columns) col = columns - 1;
2603 sprintf(cm1, ESC_SET_CURSOR_POS, row + 1, col + 1);
2607 //----- Erase from cursor to end of line -----------------------
2608 static void clear_to_eol(void)
2610 write1(ESC_CLEAR2EOL);
2613 static void go_bottom_and_clear_to_eol(void)
2615 place_cursor(rows - 1, 0);
2619 //----- Erase from cursor to end of screen -----------------------
2620 static void clear_to_eos(void)
2622 write1(ESC_CLEAR2EOS);
2625 //----- Start standout mode ------------------------------------
2626 static void standout_start(void)
2628 write1(ESC_BOLD_TEXT);
2631 //----- End standout mode --------------------------------------
2632 static void standout_end(void)
2634 write1(ESC_NORM_TEXT);
2637 //----- Flash the screen --------------------------------------
2638 static void flash(int h)
2647 static void Indicate_Error(void)
2649 #if ENABLE_FEATURE_VI_CRASHME
2651 return; // generate a random command
2660 //----- Screen[] Routines --------------------------------------
2661 //----- Erase the Screen[] memory ------------------------------
2662 static void screen_erase(void)
2664 memset(screen, ' ', screensize); // clear new screen
2667 static int bufsum(char *buf, int count)
2670 char *e = buf + count;
2673 sum += (unsigned char) *buf++;
2677 //----- Draw the status line at bottom of the screen -------------
2678 static void show_status_line(void)
2680 int cnt = 0, cksum = 0;
2682 // either we already have an error or status message, or we
2684 if (!have_status_msg) {
2685 cnt = format_edit_status();
2686 cksum = bufsum(status_buffer, cnt);
2688 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2689 last_status_cksum = cksum; // remember if we have seen this line
2690 go_bottom_and_clear_to_eol();
2691 write1(status_buffer);
2692 if (have_status_msg) {
2693 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2695 have_status_msg = 0;
2698 have_status_msg = 0;
2700 place_cursor(crow, ccol); // put cursor back in correct place
2705 //----- format the status buffer, the bottom line of screen ------
2706 // format status buffer, with STANDOUT mode
2707 static void status_line_bold(const char *format, ...)
2711 va_start(args, format);
2712 strcpy(status_buffer, ESC_BOLD_TEXT);
2713 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
2714 strcat(status_buffer, ESC_NORM_TEXT);
2717 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
2720 // format status buffer
2721 static void status_line(const char *format, ...)
2725 va_start(args, format);
2726 vsprintf(status_buffer, format, args);
2729 have_status_msg = 1;
2732 // copy s to buf, convert unprintable
2733 static void print_literal(char *buf, const char *s)
2747 c_is_no_print = (c & 0x80) && !Isprint(c);
2748 if (c_is_no_print) {
2749 strcpy(d, ESC_NORM_TEXT);
2750 d += sizeof(ESC_NORM_TEXT)-1;
2753 if (c < ' ' || c == 0x7f) {
2755 c |= '@'; /* 0x40 */
2761 if (c_is_no_print) {
2762 strcpy(d, ESC_BOLD_TEXT);
2763 d += sizeof(ESC_BOLD_TEXT)-1;
2769 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
2774 static void not_implemented(const char *s)
2776 char buf[MAX_INPUT_LEN];
2778 print_literal(buf, s);
2779 status_line_bold("\'%s\' is not implemented", buf);
2782 // show file status on status line
2783 static int format_edit_status(void)
2785 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2787 #define tot format_edit_status__tot
2789 int cur, percent, ret, trunc_at;
2791 // file_modified is now a counter rather than a flag. this
2792 // helps reduce the amount of line counting we need to do.
2793 // (this will cause a mis-reporting of modified status
2794 // once every MAXINT editing operations.)
2796 // it would be nice to do a similar optimization here -- if
2797 // we haven't done a motion that could have changed which line
2798 // we're on, then we shouldn't have to do this count_lines()
2799 cur = count_lines(text, dot);
2801 // reduce counting -- the total lines can't have
2802 // changed if we haven't done any edits.
2803 if (file_modified != last_file_modified) {
2804 tot = cur + count_lines(dot, end - 1) - 1;
2805 last_file_modified = file_modified;
2808 // current line percent
2809 // ------------- ~~ ----------
2812 percent = (100 * cur) / tot;
2818 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2819 columns : STATUS_BUFFER_LEN-1;
2821 ret = snprintf(status_buffer, trunc_at+1,
2822 #if ENABLE_FEATURE_VI_READONLY
2823 "%c %s%s%s %d/%d %d%%",
2825 "%c %s%s %d/%d %d%%",
2827 cmd_mode_indicator[cmd_mode & 3],
2828 (current_filename != NULL ? current_filename : "No file"),
2829 #if ENABLE_FEATURE_VI_READONLY
2830 (readonly_mode ? " [Readonly]" : ""),
2832 (file_modified ? " [Modified]" : ""),
2835 if (ret >= 0 && ret < trunc_at)
2836 return ret; /* it all fit */
2838 return trunc_at; /* had to truncate */
2842 //----- Force refresh of all Lines -----------------------------
2843 static void redraw(int full_screen)
2847 screen_erase(); // erase the internal screen buffer
2848 last_status_cksum = 0; // force status update
2849 refresh(full_screen); // this will redraw the entire display
2853 //----- Format a text[] line into a buffer ---------------------
2854 static char* format_line(char *src /*, int li*/)
2859 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2861 c = '~'; // char in col 0 in non-existent lines is '~'
2863 while (co < columns + tabstop) {
2864 // have we gone past the end?
2869 if ((c & 0x80) && !Isprint(c)) {
2872 if (c < ' ' || c == 0x7f) {
2876 while ((co % tabstop) != (tabstop - 1)) {
2884 c += '@'; // Ctrl-X -> 'X'
2889 // discard scrolled-off-to-the-left portion,
2890 // in tabstop-sized pieces
2891 if (ofs >= tabstop && co >= tabstop) {
2892 memmove(dest, dest + tabstop, co);
2899 // check "short line, gigantic offset" case
2902 // discard last scrolled off part
2905 // fill the rest with spaces
2907 memset(&dest[co], ' ', columns - co);
2911 //----- Refresh the changed screen lines -----------------------
2912 // Copy the source line from text[] into the buffer and note
2913 // if the current screenline is different from the new buffer.
2914 // If they differ then that line needs redrawing on the terminal.
2916 static void refresh(int full_screen)
2918 #define old_offset refresh__old_offset
2921 char *tp, *sp; // pointer into text[] and screen[]
2923 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
2924 unsigned c = columns, r = rows;
2925 query_screen_dimensions();
2926 full_screen |= (c - columns) | (r - rows);
2928 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2929 tp = screenbegin; // index into text[] of top line
2931 // compare text[] to screen[] and mark screen[] lines that need updating
2932 for (li = 0; li < rows - 1; li++) {
2933 int cs, ce; // column start & end
2935 // format current text line
2936 out_buf = format_line(tp /*, li*/);
2938 // skip to the end of the current text[] line
2940 char *t = memchr(tp, '\n', end - tp);
2941 if (!t) t = end - 1;
2945 // see if there are any changes between vitual screen and out_buf
2946 changed = FALSE; // assume no change
2949 sp = &screen[li * columns]; // start of screen line
2951 // force re-draw of every single column from 0 - columns-1
2954 // compare newly formatted buffer with virtual screen
2955 // look forward for first difference between buf and screen
2956 for (; cs <= ce; cs++) {
2957 if (out_buf[cs] != sp[cs]) {
2958 changed = TRUE; // mark for redraw
2963 // look backward for last difference between out_buf and screen
2964 for (; ce >= cs; ce--) {
2965 if (out_buf[ce] != sp[ce]) {
2966 changed = TRUE; // mark for redraw
2970 // now, cs is index of first diff, and ce is index of last diff
2972 // if horz offset has changed, force a redraw
2973 if (offset != old_offset) {
2978 // make a sanity check of columns indexes
2980 if (ce > columns - 1) ce = columns - 1;
2981 if (cs > ce) { cs = 0; ce = columns - 1; }
2982 // is there a change between vitual screen and out_buf
2984 // copy changed part of buffer to virtual screen
2985 memcpy(sp+cs, out_buf+cs, ce-cs+1);
2986 place_cursor(li, cs);
2987 // write line out to terminal
2988 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
2992 place_cursor(crow, ccol);
2994 old_offset = offset;
2998 //---------------------------------------------------------------------
2999 //----- the Ascii Chart -----------------------------------------------
3001 // 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
3002 // 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
3003 // 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
3004 // 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
3005 // 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
3006 // 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
3007 // 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
3008 // 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
3009 // 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
3010 // 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
3011 // 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
3012 // 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
3013 // 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
3014 // 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
3015 // 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
3016 // 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
3017 //---------------------------------------------------------------------
3019 //----- Execute a Vi Command -----------------------------------
3020 static void do_cmd(int c)
3022 char *p, *q, *save_dot;
3028 // c1 = c; // quiet the compiler
3029 // cnt = yf = 0; // quiet the compiler
3030 // p = q = save_dot = buf; // quiet the compiler
3031 memset(buf, '\0', sizeof(buf));
3035 /* if this is a cursor key, skip these checks */
3043 case KEYCODE_PAGEUP:
3044 case KEYCODE_PAGEDOWN:
3045 case KEYCODE_DELETE:
3049 if (cmd_mode == 2) {
3050 // flip-flop Insert/Replace mode
3051 if (c == KEYCODE_INSERT)
3053 // we are 'R'eplacing the current *dot with new char
3055 // don't Replace past E-o-l
3056 cmd_mode = 1; // convert to insert
3058 if (1 <= c || Isprint(c)) {
3060 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3061 dot = char_insert(dot, c); // insert new char
3066 if (cmd_mode == 1) {
3067 // hitting "Insert" twice means "R" replace mode
3068 if (c == KEYCODE_INSERT) goto dc5;
3069 // insert the char c at "dot"
3070 if (1 <= c || Isprint(c)) {
3071 dot = char_insert(dot, c);
3086 #if ENABLE_FEATURE_VI_CRASHME
3087 case 0x14: // dc4 ctrl-T
3088 crashme = (crashme == 0) ? 1 : 0;
3117 //case 'u': // u- FIXME- there is no undo
3119 default: // unrecognized command
3122 not_implemented(buf);
3123 end_cmd_q(); // stop adding to q
3124 case 0x00: // nul- ignore
3126 case 2: // ctrl-B scroll up full screen
3127 case KEYCODE_PAGEUP: // Cursor Key Page Up
3128 dot_scroll(rows - 2, -1);
3130 case 4: // ctrl-D scroll down half screen
3131 dot_scroll((rows - 2) / 2, 1);
3133 case 5: // ctrl-E scroll down one line
3136 case 6: // ctrl-F scroll down full screen
3137 case KEYCODE_PAGEDOWN: // Cursor Key Page Down
3138 dot_scroll(rows - 2, 1);
3140 case 7: // ctrl-G show current status
3141 last_status_cksum = 0; // force status update
3143 case 'h': // h- move left
3144 case KEYCODE_LEFT: // cursor key Left
3145 case 8: // ctrl-H- move left (This may be ERASE char)
3146 case 0x7f: // DEL- move left (This may be ERASE char)
3149 } while (--cmdcnt > 0);
3151 case 10: // Newline ^J
3152 case 'j': // j- goto next line, same col
3153 case KEYCODE_DOWN: // cursor key Down
3155 dot_next(); // go to next B-o-l
3156 // try stay in same col
3157 dot = move_to_col(dot, ccol + offset);
3158 } while (--cmdcnt > 0);
3160 case 12: // ctrl-L force redraw whole screen
3161 case 18: // ctrl-R force redraw
3164 //mysleep(10); // why???
3165 screen_erase(); // erase the internal screen buffer
3166 last_status_cksum = 0; // force status update
3167 refresh(TRUE); // this will redraw the entire display
3169 case 13: // Carriage Return ^M
3170 case '+': // +- goto next line
3174 } while (--cmdcnt > 0);
3176 case 21: // ctrl-U scroll up half screen
3177 dot_scroll((rows - 2) / 2, -1);
3179 case 25: // ctrl-Y scroll up one line
3185 cmd_mode = 0; // stop insrting
3187 last_status_cksum = 0; // force status update
3189 case ' ': // move right
3190 case 'l': // move right
3191 case KEYCODE_RIGHT: // Cursor Key Right
3194 } while (--cmdcnt > 0);
3196 #if ENABLE_FEATURE_VI_YANKMARK
3197 case '"': // "- name a register to use for Delete/Yank
3198 c1 = (get_one_char() | 0x20) - 'a'; // | 0x20 is tolower()
3199 if ((unsigned)c1 <= 25) { // a-z?
3205 case '\'': // '- goto a specific mark
3206 c1 = (get_one_char() | 0x20) - 'a';
3207 if ((unsigned)c1 <= 25) { // a-z?
3210 if (text <= q && q < end) {
3212 dot_begin(); // go to B-o-l
3215 } else if (c1 == '\'') { // goto previous context
3216 dot = swap_context(dot); // swap current and previous context
3217 dot_begin(); // go to B-o-l
3223 case 'm': // m- Mark a line
3224 // this is really stupid. If there are any inserts or deletes
3225 // between text[0] and dot then this mark will not point to the
3226 // correct location! It could be off by many lines!
3227 // Well..., at least its quick and dirty.
3228 c1 = (get_one_char() | 0x20) - 'a';
3229 if ((unsigned)c1 <= 25) { // a-z?
3230 // remember the line
3236 case 'P': // P- Put register before
3237 case 'p': // p- put register after
3240 status_line_bold("Nothing in register %c", what_reg());
3243 // are we putting whole lines or strings
3244 if (strchr(p, '\n') != NULL) {
3246 dot_begin(); // putting lines- Put above
3249 // are we putting after very last line?
3250 if (end_line(dot) == (end - 1)) {
3251 dot = end; // force dot to end of text[]
3253 dot_next(); // next line, then put before
3258 dot_right(); // move to right, can move to NL
3260 string_insert(dot, p); // insert the string
3261 end_cmd_q(); // stop adding to q
3263 case 'U': // U- Undo; replace current line with original version
3264 if (reg[Ureg] != NULL) {
3265 p = begin_line(dot);
3267 p = text_hole_delete(p, q); // delete cur line
3268 p += string_insert(p, reg[Ureg]); // insert orig line
3273 #endif /* FEATURE_VI_YANKMARK */
3274 case '$': // $- goto end of line
3275 case KEYCODE_END: // Cursor Key End
3277 dot = end_line(dot);
3283 case '%': // %- find matching char of pair () [] {}
3284 for (q = dot; q < end && *q != '\n'; q++) {
3285 if (strchr("()[]{}", *q) != NULL) {
3286 // we found half of a pair
3287 p = find_pair(q, *q);
3299 case 'f': // f- forward to a user specified char
3300 last_forward_char = get_one_char(); // get the search char
3302 // dont separate these two commands. 'f' depends on ';'
3304 //**** fall through to ... ';'
3305 case ';': // ;- look at rest of line for last forward char
3307 if (last_forward_char == 0)
3310 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
3313 if (*q == last_forward_char)
3315 } while (--cmdcnt > 0);
3317 case ',': // repeat latest 'f' in opposite direction
3318 if (last_forward_char == 0)
3322 while (q >= text && *q != '\n' && *q != last_forward_char) {
3325 if (q >= text && *q == last_forward_char)
3327 } while (--cmdcnt > 0);
3330 case '-': // -- goto prev line
3334 } while (--cmdcnt > 0);
3336 #if ENABLE_FEATURE_VI_DOT_CMD
3337 case '.': // .- repeat the last modifying command
3338 // Stuff the last_modifying_cmd back into stdin
3339 // and let it be re-executed.
3341 last_modifying_cmd[lmc_len] = 0;
3342 ioq = ioq_start = xstrdup(last_modifying_cmd);
3346 #if ENABLE_FEATURE_VI_SEARCH
3347 case '?': // /- search for a pattern
3348 case '/': // /- search for a pattern
3351 q = get_input_line(buf); // get input line- use "status line"
3352 if (q[0] && !q[1]) {
3353 if (last_search_pattern[0])
3354 last_search_pattern[0] = c;
3355 goto dc3; // if no pat re-use old pat
3357 if (q[0]) { // strlen(q) > 1: new pat- save it and find
3358 // there is a new pat
3359 free(last_search_pattern);
3360 last_search_pattern = xstrdup(q);
3361 goto dc3; // now find the pattern
3363 // user changed mind and erased the "/"- do nothing
3365 case 'N': // N- backward search for last pattern
3366 dir = BACK; // assume BACKWARD search
3368 if (last_search_pattern[0] == '?') {
3372 goto dc4; // now search for pattern
3374 case 'n': // n- repeat search for last pattern
3375 // search rest of text[] starting at next char
3376 // if search fails return orignal "p" not the "p+1" address
3380 dir = FORWARD; // assume FORWARD search
3382 if (last_search_pattern[0] == '?') {
3387 q = char_search(p, last_search_pattern + 1, dir, FULL);
3389 dot = q; // good search, update "dot"
3393 // no pattern found between "dot" and "end"- continue at top
3398 q = char_search(p, last_search_pattern + 1, dir, FULL);
3399 if (q != NULL) { // found something
3400 dot = q; // found new pattern- goto it
3401 msg = "search hit BOTTOM, continuing at TOP";
3403 msg = "search hit TOP, continuing at BOTTOM";
3406 msg = "Pattern not found";
3410 status_line_bold("%s", msg);
3411 } while (--cmdcnt > 0);
3413 case '{': // {- move backward paragraph
3414 q = char_search(dot, "\n\n", BACK, FULL);
3415 if (q != NULL) { // found blank line
3416 dot = next_line(q); // move to next blank line
3419 case '}': // }- move forward paragraph
3420 q = char_search(dot, "\n\n", FORWARD, FULL);
3421 if (q != NULL) { // found blank line
3422 dot = next_line(q); // move to next blank line
3425 #endif /* FEATURE_VI_SEARCH */
3426 case '0': // 0- goto begining of line
3436 if (c == '0' && cmdcnt < 1) {
3437 dot_begin(); // this was a standalone zero
3439 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
3442 case ':': // :- the colon mode commands
3443 p = get_input_line(":"); // get input line- use "status line"
3444 #if ENABLE_FEATURE_VI_COLON
3445 colon(p); // execute the command
3448 p++; // move past the ':'
3452 if (strncmp(p, "quit", cnt) == 0
3453 || strncmp(p, "q!", cnt) == 0 // delete lines
3455 if (file_modified && p[1] != '!') {
3456 status_line_bold("No write since last change (:%s! overrides)", p);
3460 } else if (strncmp(p, "write", cnt) == 0
3461 || strncmp(p, "wq", cnt) == 0
3462 || strncmp(p, "wn", cnt) == 0
3463 || (p[0] == 'x' && !p[1])
3465 cnt = file_write(current_filename, text, end - 1);
3468 status_line_bold("Write error: %s", strerror(errno));
3471 last_file_modified = -1;
3472 status_line("\"%s\" %dL, %dC", current_filename, count_lines(text, end - 1), cnt);
3473 if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n'
3474 || p[0] == 'X' || p[1] == 'Q' || p[1] == 'N'
3479 } else if (strncmp(p, "file", cnt) == 0) {
3480 last_status_cksum = 0; // force status update
3481 } else if (sscanf(p, "%d", &j) > 0) {
3482 dot = find_line(j); // go to line # j
3484 } else { // unrecognized cmd
3487 #endif /* !FEATURE_VI_COLON */
3489 case '<': // <- Left shift something
3490 case '>': // >- Right shift something
3491 cnt = count_lines(text, dot); // remember what line we are on
3492 c1 = get_one_char(); // get the type of thing to delete
3493 find_range(&p, &q, c1);
3494 yank_delete(p, q, 1, YANKONLY); // save copy before change
3497 i = count_lines(p, q); // # of lines we are shifting
3498 for ( ; i > 0; i--, p = next_line(p)) {
3500 // shift left- remove tab or 8 spaces
3502 // shrink buffer 1 char
3503 text_hole_delete(p, p);
3504 } else if (*p == ' ') {
3505 // we should be calculating columns, not just SPACE
3506 for (j = 0; *p == ' ' && j < tabstop; j++) {
3507 text_hole_delete(p, p);
3510 } else if (c == '>') {
3511 // shift right -- add tab or 8 spaces
3512 char_insert(p, '\t');
3515 dot = find_line(cnt); // what line were we on
3517 end_cmd_q(); // stop adding to q
3519 case 'A': // A- append at e-o-l
3520 dot_end(); // go to e-o-l
3521 //**** fall through to ... 'a'
3522 case 'a': // a- append after current char
3527 case 'B': // B- back a blank-delimited Word
3528 case 'E': // E- end of a blank-delimited word
3529 case 'W': // W- forward a blank-delimited word
3534 if (c == 'W' || isspace(dot[dir])) {
3535 dot = skip_thing(dot, 1, dir, S_TO_WS);
3536 dot = skip_thing(dot, 2, dir, S_OVER_WS);
3539 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
3540 } while (--cmdcnt > 0);
3542 case 'C': // C- Change to e-o-l
3543 case 'D': // D- delete to e-o-l
3545 dot = dollar_line(dot); // move to before NL
3546 // copy text into a register and delete
3547 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
3549 goto dc_i; // start inserting
3550 #if ENABLE_FEATURE_VI_DOT_CMD
3552 end_cmd_q(); // stop adding to q
3555 case 'g': // 'gg' goto a line number (vim) (default: very first line)
3556 c1 = get_one_char();
3559 buf[1] = c1; // TODO: if Unicode?
3561 not_implemented(buf);
3567 case 'G': // G- goto to a line number (default= E-O-F)
3568 dot = end - 1; // assume E-O-F
3570 dot = find_line(cmdcnt); // what line is #cmdcnt
3574 case 'H': // H- goto top line on screen
3576 if (cmdcnt > (rows - 1)) {
3577 cmdcnt = (rows - 1);
3584 case 'I': // I- insert before first non-blank
3587 //**** fall through to ... 'i'
3588 case 'i': // i- insert before current char
3589 case KEYCODE_INSERT: // Cursor Key Insert
3591 cmd_mode = 1; // start inserting
3593 case 'J': // J- join current and next lines together
3595 dot_end(); // move to NL
3596 if (dot < end - 1) { // make sure not last char in text[]
3597 *dot++ = ' '; // replace NL with space
3599 while (isblank(*dot)) { // delete leading WS
3603 } while (--cmdcnt > 0);
3604 end_cmd_q(); // stop adding to q
3606 case 'L': // L- goto bottom line on screen
3608 if (cmdcnt > (rows - 1)) {
3609 cmdcnt = (rows - 1);
3617 case 'M': // M- goto middle line on screen
3619 for (cnt = 0; cnt < (rows-1) / 2; cnt++)
3620 dot = next_line(dot);
3622 case 'O': // O- open a empty line above
3624 p = begin_line(dot);
3625 if (p[-1] == '\n') {
3627 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
3629 dot = char_insert(dot, '\n');
3632 dot = char_insert(dot, '\n'); // i\n ESC
3637 case 'R': // R- continuous Replace char
3641 case KEYCODE_DELETE:
3644 case 'X': // X- delete char before dot
3645 case 'x': // x- delete the current char
3646 case 's': // s- substitute the current char
3651 if (dot[dir] != '\n') {
3653 dot--; // delete prev char
3654 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
3656 } while (--cmdcnt > 0);
3657 end_cmd_q(); // stop adding to q
3659 goto dc_i; // start inserting
3661 case 'Z': // Z- if modified, {write}; exit
3662 // ZZ means to save file (if necessary), then exit
3663 c1 = get_one_char();
3668 if (file_modified) {
3669 if (ENABLE_FEATURE_VI_READONLY && readonly_mode) {
3670 status_line_bold("\"%s\" File is read only", current_filename);
3673 cnt = file_write(current_filename, text, end - 1);
3676 status_line_bold("Write error: %s", strerror(errno));
3677 } else if (cnt == (end - 1 - text + 1)) {
3684 case '^': // ^- move to first non-blank on line
3688 case 'b': // b- back a word
3689 case 'e': // e- end of word
3694 if ((dot + dir) < text || (dot + dir) > end - 1)
3697 if (isspace(*dot)) {
3698 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
3700 if (isalnum(*dot) || *dot == '_') {
3701 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
3702 } else if (ispunct(*dot)) {
3703 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
3705 } while (--cmdcnt > 0);
3707 case 'c': // c- change something
3708 case 'd': // d- delete something
3709 #if ENABLE_FEATURE_VI_YANKMARK
3710 case 'y': // y- yank something
3711 case 'Y': // Y- Yank a line
3714 int yf, ml, whole = 0;
3715 yf = YANKDEL; // assume either "c" or "d"
3716 #if ENABLE_FEATURE_VI_YANKMARK
3717 if (c == 'y' || c == 'Y')
3722 c1 = get_one_char(); // get the type of thing to delete
3723 // determine range, and whether it spans lines
3724 ml = find_range(&p, &q, c1);
3725 if (c1 == 27) { // ESC- user changed mind and wants out
3726 c = c1 = 27; // Escape- do nothing
3727 } else if (strchr("wW", c1)) {
3729 // don't include trailing WS as part of word
3730 while (isblank(*q)) {
3731 if (q <= text || q[-1] == '\n')
3736 dot = yank_delete(p, q, ml, yf); // delete word
3737 } else if (strchr("^0bBeEft%$ lh\b\177", c1)) {
3738 // partial line copy text into a register and delete
3739 dot = yank_delete(p, q, ml, yf); // delete word
3740 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
3741 // whole line copy text into a register and delete
3742 dot = yank_delete(p, q, ml, yf); // delete lines
3745 // could not recognize object
3746 c = c1 = 27; // error-
3752 dot = char_insert(dot, '\n');
3753 // on the last line of file don't move to prev line
3754 if (whole && dot != (end-1)) {
3757 } else if (c == 'd') {
3763 // if CHANGING, not deleting, start inserting after the delete
3765 strcpy(buf, "Change");
3766 goto dc_i; // start inserting
3769 strcpy(buf, "Delete");
3771 #if ENABLE_FEATURE_VI_YANKMARK
3772 if (c == 'y' || c == 'Y') {
3773 strcpy(buf, "Yank");
3777 for (cnt = 0; p <= q; p++) {
3781 status_line("%s %d lines (%d chars) using [%c]",
3782 buf, cnt, strlen(reg[YDreg]), what_reg());
3784 end_cmd_q(); // stop adding to q
3788 case 'k': // k- goto prev line, same col
3789 case KEYCODE_UP: // cursor key Up
3792 dot = move_to_col(dot, ccol + offset); // try stay in same col
3793 } while (--cmdcnt > 0);
3795 case 'r': // r- replace the current char with user input
3796 c1 = get_one_char(); // get the replacement char
3801 end_cmd_q(); // stop adding to q
3803 case 't': // t- move to char prior to next x
3804 last_forward_char = get_one_char();
3806 if (*dot == last_forward_char)
3808 last_forward_char = 0;
3810 case 'w': // w- forward a word
3812 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
3813 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
3814 } else if (ispunct(*dot)) { // we are on PUNCT
3815 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
3818 dot++; // move over word
3819 if (isspace(*dot)) {
3820 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
3822 } while (--cmdcnt > 0);
3825 c1 = get_one_char(); // get the replacement char
3828 cnt = (rows - 2) / 2; // put dot at center
3830 cnt = rows - 2; // put dot at bottom
3831 screenbegin = begin_line(dot); // start dot at top
3832 dot_scroll(cnt, -1);
3834 case '|': // |- move to column "cmdcnt"
3835 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
3837 case '~': // ~- flip the case of letters a-z -> A-Z
3839 if (islower(*dot)) {
3840 *dot = toupper(*dot);
3842 } else if (isupper(*dot)) {
3843 *dot = tolower(*dot);
3847 } while (--cmdcnt > 0);
3848 end_cmd_q(); // stop adding to q
3850 //----- The Cursor and Function Keys -----------------------------
3851 case KEYCODE_HOME: // Cursor Key Home
3854 // The Fn keys could point to do_macro which could translate them
3856 case KEYCODE_FUN1: // Function Key F1
3857 case KEYCODE_FUN2: // Function Key F2
3858 case KEYCODE_FUN3: // Function Key F3
3859 case KEYCODE_FUN4: // Function Key F4
3860 case KEYCODE_FUN5: // Function Key F5
3861 case KEYCODE_FUN6: // Function Key F6
3862 case KEYCODE_FUN7: // Function Key F7
3863 case KEYCODE_FUN8: // Function Key F8
3864 case KEYCODE_FUN9: // Function Key F9
3865 case KEYCODE_FUN10: // Function Key F10
3866 case KEYCODE_FUN11: // Function Key F11
3867 case KEYCODE_FUN12: // Function Key F12
3873 // if text[] just became empty, add back an empty line
3875 char_insert(text, '\n'); // start empty buf with dummy line
3878 // it is OK for dot to exactly equal to end, otherwise check dot validity
3880 dot = bound_dot(dot); // make sure "dot" is valid
3882 #if ENABLE_FEATURE_VI_YANKMARK
3883 check_context(c); // update the current context
3887 cmdcnt = 0; // cmd was not a number, reset cmdcnt
3888 cnt = dot - begin_line(dot);
3889 // Try to stay off of the Newline
3890 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
3894 /* NB! the CRASHME code is unmaintained, and doesn't currently build */
3895 #if ENABLE_FEATURE_VI_CRASHME
3896 static int totalcmds = 0;
3897 static int Mp = 85; // Movement command Probability
3898 static int Np = 90; // Non-movement command Probability
3899 static int Dp = 96; // Delete command Probability
3900 static int Ip = 97; // Insert command Probability
3901 static int Yp = 98; // Yank command Probability
3902 static int Pp = 99; // Put command Probability
3903 static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
3904 static const char chars[20] = "\t012345 abcdABCD-=.$";
3905 static const char *const words[20] = {
3906 "this", "is", "a", "test",
3907 "broadcast", "the", "emergency", "of",
3908 "system", "quick", "brown", "fox",
3909 "jumped", "over", "lazy", "dogs",
3910 "back", "January", "Febuary", "March"
3912 static const char *const lines[20] = {
3913 "You should have received a copy of the GNU General Public License\n",
3914 "char c, cm, *cmd, *cmd1;\n",
3915 "generate a command by percentages\n",
3916 "Numbers may be typed as a prefix to some commands.\n",
3917 "Quit, discarding changes!\n",
3918 "Forced write, if permission originally not valid.\n",
3919 "In general, any ex or ed command (such as substitute or delete).\n",
3920 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3921 "Please get w/ me and I will go over it with you.\n",
3922 "The following is a list of scheduled, committed changes.\n",
3923 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3924 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3925 "Any question about transactions please contact Sterling Huxley.\n",
3926 "I will try to get back to you by Friday, December 31.\n",
3927 "This Change will be implemented on Friday.\n",
3928 "Let me know if you have problems accessing this;\n",
3929 "Sterling Huxley recently added you to the access list.\n",
3930 "Would you like to go to lunch?\n",
3931 "The last command will be automatically run.\n",
3932 "This is too much english for a computer geek.\n",
3934 static char *multilines[20] = {
3935 "You should have received a copy of the GNU General Public License\n",
3936 "char c, cm, *cmd, *cmd1;\n",
3937 "generate a command by percentages\n",
3938 "Numbers may be typed as a prefix to some commands.\n",
3939 "Quit, discarding changes!\n",
3940 "Forced write, if permission originally not valid.\n",
3941 "In general, any ex or ed command (such as substitute or delete).\n",
3942 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
3943 "Please get w/ me and I will go over it with you.\n",
3944 "The following is a list of scheduled, committed changes.\n",
3945 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
3946 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
3947 "Any question about transactions please contact Sterling Huxley.\n",
3948 "I will try to get back to you by Friday, December 31.\n",
3949 "This Change will be implemented on Friday.\n",
3950 "Let me know if you have problems accessing this;\n",
3951 "Sterling Huxley recently added you to the access list.\n",
3952 "Would you like to go to lunch?\n",
3953 "The last command will be automatically run.\n",
3954 "This is too much english for a computer geek.\n",
3957 // create a random command to execute
3958 static void crash_dummy()
3960 static int sleeptime; // how long to pause between commands
3961 char c, cm, *cmd, *cmd1;
3962 int i, cnt, thing, rbi, startrbi, percent;
3964 // "dot" movement commands
3965 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
3967 // is there already a command running?
3968 if (readbuffer[0] > 0)
3971 readbuffer[0] = 'X';
3973 sleeptime = 0; // how long to pause between commands
3974 memset(readbuffer, '\0', sizeof(readbuffer));
3975 // generate a command by percentages
3976 percent = (int) lrand48() % 100; // get a number from 0-99
3977 if (percent < Mp) { // Movement commands
3978 // available commands
3981 } else if (percent < Np) { // non-movement commands
3982 cmd = "mz<>\'\""; // available commands
3984 } else if (percent < Dp) { // Delete commands
3985 cmd = "dx"; // available commands
3987 } else if (percent < Ip) { // Inset commands
3988 cmd = "iIaAsrJ"; // available commands
3990 } else if (percent < Yp) { // Yank commands
3991 cmd = "yY"; // available commands
3993 } else if (percent < Pp) { // Put commands
3994 cmd = "pP"; // available commands
3997 // We do not know how to handle this command, try again
4001 // randomly pick one of the available cmds from "cmd[]"
4002 i = (int) lrand48() % strlen(cmd);
4004 if (strchr(":\024", cm))
4005 goto cd0; // dont allow colon or ctrl-T commands
4006 readbuffer[rbi++] = cm; // put cmd into input buffer
4008 // now we have the command-
4009 // there are 1, 2, and multi char commands
4010 // find out which and generate the rest of command as necessary
4011 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
4012 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
4013 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
4014 cmd1 = "abcdefghijklmnopqrstuvwxyz";
4016 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4018 readbuffer[rbi++] = c; // add movement to input buffer
4020 if (strchr("iIaAsc", cm)) { // multi-char commands
4022 // change some thing
4023 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
4025 readbuffer[rbi++] = c; // add movement to input buffer
4027 thing = (int) lrand48() % 4; // what thing to insert
4028 cnt = (int) lrand48() % 10; // how many to insert
4029 for (i = 0; i < cnt; i++) {
4030 if (thing == 0) { // insert chars
4031 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
4032 } else if (thing == 1) { // insert words
4033 strcat(readbuffer, words[(int) lrand48() % 20]);
4034 strcat(readbuffer, " ");
4035 sleeptime = 0; // how fast to type
4036 } else if (thing == 2) { // insert lines
4037 strcat(readbuffer, lines[(int) lrand48() % 20]);
4038 sleeptime = 0; // how fast to type
4039 } else { // insert multi-lines
4040 strcat(readbuffer, multilines[(int) lrand48() % 20]);
4041 sleeptime = 0; // how fast to type
4044 strcat(readbuffer, "\033");
4046 readbuffer[0] = strlen(readbuffer + 1);
4050 mysleep(sleeptime); // sleep 1/100 sec
4053 // test to see if there are any errors
4054 static void crash_test()
4056 static time_t oldtim;
4063 strcat(msg, "end<text ");
4065 if (end > textend) {
4066 strcat(msg, "end>textend ");
4069 strcat(msg, "dot<text ");
4072 strcat(msg, "dot>end ");
4074 if (screenbegin < text) {
4075 strcat(msg, "screenbegin<text ");
4077 if (screenbegin > end - 1) {
4078 strcat(msg, "screenbegin>end-1 ");
4082 printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s",
4083 totalcmds, last_input_char, msg, ESC_BOLD_TEXT, ESC_NORM_TEXT);
4085 while (safe_read(STDIN_FILENO, d, 1) > 0) {
4086 if (d[0] == '\n' || d[0] == '\r')
4091 if (tim >= (oldtim + 3)) {
4092 sprintf(status_buffer,
4093 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
4094 totalcmds, M, N, I, D, Y, P, U, end - text + 1);